mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 09:32:35 +00:00
Add PVP immunity to 5M starting gold modifier games 🔧 (#3180)
## Description: Adds 30 seconds of PVP immunity to 5M starting gold modifier games. So you cannot insta-nuke other players. Because I'm sure people would be confused "I cannot attack!!!!" I added a HeadsUpMessage which informs about the PVP immunity. We already have a ImmunityTimer progress bar but I don't think its enough. <img width="1270" height="745" alt="image" src="https://github.com/user-attachments/assets/0ee23dc4-1c7b-4d62-8b3d-8de214f03c2b" /> I had a second count in the HeadsUpMessage (seconds until PVP immunity is over) but it felt too busy. So I removed it. You can tell when PVP immunity is over by looking at the progress bar. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --------- Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
@@ -845,7 +845,8 @@
|
||||
"choose_spawn": "Choose a starting location",
|
||||
"random_spawn": "Random spawn is enabled. Selecting starting location for you...",
|
||||
"singleplayer_game_paused": "Game paused",
|
||||
"multiplayer_game_paused": "Game paused by Lobby Creator"
|
||||
"multiplayer_game_paused": "Game paused by Lobby Creator",
|
||||
"pvp_immunity_active": "PVP immunity active for {seconds}s"
|
||||
},
|
||||
"territory_patterns": {
|
||||
"title": "Skins",
|
||||
|
||||
@@ -16,6 +16,9 @@ export class HeadsUpMessage extends LitElement implements Layer {
|
||||
@state()
|
||||
private isPaused = false;
|
||||
|
||||
@state()
|
||||
private isImmunityActive = false;
|
||||
|
||||
@state()
|
||||
private toastMessage: string | import("lit").TemplateResult | null = null;
|
||||
@state()
|
||||
@@ -79,7 +82,18 @@ export class HeadsUpMessage extends LitElement implements Layer {
|
||||
this.isPaused = pauseUpdate.paused;
|
||||
}
|
||||
|
||||
this.isVisible = this.game.inSpawnPhase() || this.isPaused;
|
||||
const showImmunityHudDuration = 10 * 10;
|
||||
const spawnEnd = this.game.config().numSpawnPhaseTurns();
|
||||
const ticksSinceSpawnEnd = this.game.ticks() - spawnEnd;
|
||||
|
||||
this.isImmunityActive =
|
||||
this.game.config().hasExtendedSpawnImmunity() &&
|
||||
!this.game.inSpawnPhase() &&
|
||||
this.game.isSpawnImmunityActive() &&
|
||||
ticksSinceSpawnEnd < showImmunityHudDuration;
|
||||
|
||||
this.isVisible =
|
||||
this.game.inSpawnPhase() || this.isPaused || this.isImmunityActive;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -91,6 +105,11 @@ export class HeadsUpMessage extends LitElement implements Layer {
|
||||
return translateText("heads_up_message.multiplayer_game_paused");
|
||||
}
|
||||
}
|
||||
if (this.isImmunityActive) {
|
||||
return translateText("heads_up_message.pvp_immunity_active", {
|
||||
seconds: Math.round(this.game.config().spawnImmunityDuration() / 10),
|
||||
});
|
||||
}
|
||||
return this.game.config().isRandomSpawn()
|
||||
? translateText("heads_up_message.random_spawn")
|
||||
: translateText("heads_up_message.choose_spawn");
|
||||
|
||||
@@ -41,7 +41,10 @@ export class ImmunityTimer extends LitElement implements Layer {
|
||||
const immunityDuration = this.game.config().spawnImmunityDuration();
|
||||
const spawnPhaseTurns = this.game.config().numSpawnPhaseTurns();
|
||||
|
||||
if (immunityDuration <= 5 * 10 || this.game.inSpawnPhase()) {
|
||||
if (
|
||||
!this.game.config().hasExtendedSpawnImmunity() ||
|
||||
this.game.inSpawnPhase()
|
||||
) {
|
||||
this.setInactive();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export interface NukeMagnitude {
|
||||
|
||||
export interface Config {
|
||||
spawnImmunityDuration(): Tick;
|
||||
hasExtendedSpawnImmunity(): boolean;
|
||||
serverConfig(): ServerConfig;
|
||||
gameConfig(): GameConfig;
|
||||
theme(): Theme;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { PastelThemeDark } from "./PastelThemeDark";
|
||||
|
||||
const DEFENSE_DEBUFF_MIDPOINT = 150_000;
|
||||
const DEFENSE_DEBUFF_DECAY_RATE = Math.LN2 / 50000;
|
||||
const DEFAULT_SPAWN_IMMUNITY_TICKS = 5 * 10;
|
||||
|
||||
const JwksSchema = z.object({
|
||||
keys: z
|
||||
@@ -163,7 +164,12 @@ export class DefaultConfig implements Config {
|
||||
return 30 * 10; // 30 seconds
|
||||
}
|
||||
spawnImmunityDuration(): Tick {
|
||||
return this._gameConfig.spawnImmunityDuration ?? 5 * 10; // default to 5 seconds
|
||||
return (
|
||||
this._gameConfig.spawnImmunityDuration ?? DEFAULT_SPAWN_IMMUNITY_TICKS
|
||||
);
|
||||
}
|
||||
hasExtendedSpawnImmunity(): boolean {
|
||||
return this.spawnImmunityDuration() > DEFAULT_SPAWN_IMMUNITY_TICKS;
|
||||
}
|
||||
|
||||
gameConfig(): GameConfig {
|
||||
|
||||
@@ -722,7 +722,7 @@ export class GameImpl implements Game {
|
||||
public isSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this.config().numSpawnPhaseTurns() +
|
||||
this.config().spawnImmunityDuration() >=
|
||||
this.config().spawnImmunityDuration() >
|
||||
this.ticks()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -813,6 +813,12 @@ export class GameView implements GameMap {
|
||||
inSpawnPhase(): boolean {
|
||||
return this.ticks() <= this._config.numSpawnPhaseTurns();
|
||||
}
|
||||
isSpawnImmunityActive(): boolean {
|
||||
return (
|
||||
this._config.numSpawnPhaseTurns() + this._config.spawnImmunityDuration() >
|
||||
this.ticks()
|
||||
);
|
||||
}
|
||||
config(): Config {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ export class MapPlaylist {
|
||||
gameMode: mode,
|
||||
playerTeams,
|
||||
bots: isCompact ? 100 : 400,
|
||||
spawnImmunityDuration: 5 * 10,
|
||||
spawnImmunityDuration: startingGold ? 30 * 10 : 5 * 10,
|
||||
disabledUnits: [],
|
||||
} satisfies GameConfig;
|
||||
}
|
||||
|
||||
@@ -391,17 +391,17 @@ describe("Attack immunity", () => {
|
||||
|
||||
test("Ensure a player can't attack during all the immunity phase", async () => {
|
||||
// Execute a few ticks but stop right before the immunity phase is over
|
||||
for (let i = 0; i < immunityPhaseTicks - 1; i++) {
|
||||
for (let i = 0; i < immunityPhaseTicks - 2; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
// Player A attacks Player B
|
||||
game.addExecution(new AttackExecution(null, playerA, playerB.id(), null));
|
||||
game.executeNextTick(); // ticks === immunityPhaseTicks here
|
||||
game.executeNextTick(); // ticks === immunityPhaseTicks - 1 here
|
||||
// Attack is not possible during immunity
|
||||
expect(playerA.outgoingAttacks()).toHaveLength(0);
|
||||
|
||||
// Retry after the immunity is over
|
||||
game.executeNextTick(); // ticks === immunityPhaseTicks + 1
|
||||
game.executeNextTick(); // ticks === immunityPhaseTicks
|
||||
game.addExecution(new AttackExecution(null, playerA, playerB.id(), null));
|
||||
game.executeNextTick();
|
||||
// Attack is now possible right after
|
||||
|
||||
Reference in New Issue
Block a user