From 93db562c434bae96c5d2d28ca806fd7be87e3d1f Mon Sep 17 00:00:00 2001 From: gereon77 Date: Sat, 6 Jul 2024 17:13:49 +0200 Subject: [PATCH] Fog improvement: Introduce unit visibility range --- .../src/client/GameSettingsComponent.tsx | 23 ++++-- .../WesterosGameStateComponent.tsx | 2 +- agot-bg-game-server/src/common/EntireGame.ts | 23 +++--- .../ingame-game-state/IngameGameState.ts | 76 +++++++++++-------- .../PostCombatGameState.ts | 1 - .../game-data-structure/UnitType.ts | 5 +- .../game-data-structure/unitTypes.ts | 2 +- .../westeros-card/DenseFogWesterosCardType.ts | 2 +- .../ingame-game-state/vote-system/Vote.ts | 2 +- .../src/server/serializedGameMigrations.ts | 14 ++++ 10 files changed, 94 insertions(+), 56 deletions(-) diff --git a/agot-bg-game-server/src/client/GameSettingsComponent.tsx b/agot-bg-game-server/src/client/GameSettingsComponent.tsx index d9c4dfcc0..ec447740f 100644 --- a/agot-bg-game-server/src/client/GameSettingsComponent.tsx +++ b/agot-bg-game-server/src/client/GameSettingsComponent.tsx @@ -11,6 +11,7 @@ import LobbyGameState from "../common/lobby-game-state/LobbyGameState"; import { OverlayTrigger, Tooltip } from "react-bootstrap"; import { allGameSetups, getGameSetupContainer } from "../common/ingame-game-state/game-data-structure/createGame"; import IngameGameState from "../common/ingame-game-state/IngameGameState"; +import { isMobile } from "react-device-detect"; interface GameSettingsComponentProps { gameClient: GameClient; @@ -394,14 +395,22 @@ export default class GameSettingsComponent extends Component - A community proposal to {this.props.entireGame.isMotherOfDragons ? - "avoid an early gang up against Targaryen" : - "improve Tyrell's starting position"}. For details see
- - Tex's balance proposal - . + {this.props.entireGame.isMotherOfDragons + ? <> + A community proposal to avoid an early gang up against Targaryen.
+ For details see
+ + Tex's balance proposal + . + + : <> + A community proposal to improve Tyrell's starting position by + adding a ship to Redwyne Straight's. + } } - delay={{show: 0, hide: 1500}}> + delay={{show: 0, hide: 1500}} + placement={isMobile ? "auto" : "bottom"} + > } checked={this.gameSettings.customBalancing} diff --git a/agot-bg-game-server/src/client/game-state-panel/WesterosGameStateComponent.tsx b/agot-bg-game-server/src/client/game-state-panel/WesterosGameStateComponent.tsx index 7712e21ed..effb28a29 100644 --- a/agot-bg-game-server/src/client/game-state-panel/WesterosGameStateComponent.tsx +++ b/agot-bg-game-server/src/client/game-state-panel/WesterosGameStateComponent.tsx @@ -73,7 +73,7 @@ export default class WesterosGameStateComponent extends Component diff --git a/agot-bg-game-server/src/common/EntireGame.ts b/agot-bg-game-server/src/common/EntireGame.ts index a18a47201..824623140 100644 --- a/agot-bg-game-server/src/common/EntireGame.ts +++ b/agot-bg-game-server/src/common/EntireGame.ts @@ -70,7 +70,6 @@ export default class EntireGame extends GameState void; onSaveGame?: (updateLastActive: boolean) => void; onGetUser?: (userId: string) => Promise; - onBeforeGameStateChangedTransmitted?: () => void; // Throttled saveGame so we don't spam the website client saveGame: (updateLastActive: boolean) => void = _.throttle(this.privateSaveGame, 2000); @@ -174,10 +173,10 @@ export default class EntireGame extends GameState { - if (this.onBeforeGameStateChangedTransmitted != null) { - this.onBeforeGameStateChangedTransmitted(); - } // To serialize the specific game state that has changed, the code serializes the entire // game state tree and pick the appropriate serializedGameState. // TODO: Find less wasteful way of doing this @@ -222,6 +221,9 @@ export default class EntireGame extends GameState { + if (this.lobbyGameState) { + this.lobbyGameState.players.forEach((user, house) => { // If the game is in "randomize house" mode, don't specify any houses in the PlayerInGame data const playerData: {[key: string]: any} = {}; @@ -656,11 +658,10 @@ export default class EntireGame extends GameState { + this.ingameGameState.players.forEach((player, user) => { // "Important chat rooms" are chat rooms where unseen messages will display // a badge next to the game in the website. // In this case, it's all private rooms with this player in it. The next line @@ -674,7 +675,7 @@ export default class EntireGame extends GameState cr.roomId), - "is_winner": ingame.childGameState instanceof GameEndedGameState ? ingame.childGameState.winner == player.house : false, + "is_winner": this.ingameGameState!.childGameState instanceof GameEndedGameState ? this.ingameGameState!.childGameState.winner == player.house : false, "needed_for_vote": player.isNeededForVote } }); diff --git a/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts index 18e3463e5..51da0e858 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/IngameGameState.ts @@ -75,7 +75,7 @@ export default class IngameGameState extends GameState< @observable ordersOnBoard: BetterMap = new BetterMap(); @observable visibleRegionsPerPlayer: BetterMap = new BetterMap(); @observable publicVisibleRegions: Region[] = []; - unitVisibilityRange = 1; + unitVisibilityRangeModifier = 0; votes: BetterMap = new BetterMap(); @observable paused: Date | null = null; @@ -132,10 +132,6 @@ export default class IngameGameState extends GameState< constructor(entireGame: EntireGame) { super(entireGame); - - entireGame.onBeforeGameStateChangedTransmitted = () => { - this.updateVisibleRegions(); - } } beginGame(housesToCreate: string[], futurePlayers: BetterMap): void { @@ -248,7 +244,6 @@ export default class IngameGameState extends GameState< proceedPlanningGameState(planningRestrictions: PlanningRestriction[] = [], revealedWesterosCards: WesterosCard[] = []): void { this.game.vassalRelations = new BetterMap(); this.broadcastVassalRelations(); - this.updateVisibleRegions(true); this.setChildGameState(new PlanningGameState(this)).firstStart(planningRestrictions, revealedWesterosCards); } @@ -337,7 +332,7 @@ export default class IngameGameState extends GameState< }); if (this.fogOfWar) { - this.unitVisibilityRange = 1; + this.unitVisibilityRangeModifier = 0; this.publicVisibleRegions = []; this.entireGame.users.values.filter(u => u.connected).forEach(u => { this.entireGame.sendMessageToClients([u], { @@ -348,7 +343,7 @@ export default class IngameGameState extends GameState< applyChangesNow: !this.players.has(u) }); }); - this.updateVisibleRegions(true); + // this.updateVisibleRegions(false) will be called by entire game after game state changed } if (this.game.turn > 1) { @@ -1660,6 +1655,11 @@ export default class IngameGameState extends GameState< return this.visibleRegionsPerPlayer.get(player); } + calculateVisibilityRangeForRegion(region: Region): number { + const baseRange = Math.max(...region.units.values.map(u => u.type.baseVisibilityRange)); + return Math.max(0, baseRange + this.unitVisibilityRangeModifier); + } + calculateVisibleRegionsForPlayer(player: Player | null): Region[] { if (!this.fogOfWar || !player) { return []; @@ -1673,34 +1673,46 @@ export default class IngameGameState extends GameState< const allRegionsWithControllers = this.world.getAllRegionsWithControllers(); // We begin with all controlled areas of own and vassal units. We definitely always see them - const result: Region[] = allRegionsWithControllers.filter(([_r, h]) => controlledHouses.includes(h)).map(([r, _h]) => r); - let regionsWithUnits = result.filter(r => r.units.size > 0); - const checkedRegions: Region[] = []; + const result: Set = new Set(allRegionsWithControllers.filter(([_r, h]) => controlledHouses.includes(h)).map(([r, _h]) => r)); + const regionsWithUnits = Array.from(result).filter(r => r.units.size > 0); + const checkedRegions = new Set(); // Additionally we see regions adjacents to our regions with units - for(let i=0; i < this.unitVisibilityRange; i++) { - const additionalRegionsToCheck: Region[] = []; - for (let j=0; j(); + for (let k = 0; k < rootRegions.length; k++) { + const region = rootRegions[k]; + if (checkedRegions.has(region)) { + continue; + } - if (this.unitVisibilityRange > 1) { - regionsWithUnits.push(...additionalRegionsToCheck); - regionsWithUnits = _.uniq(regionsWithUnits); + const adjacent = this.world.getNeighbouringRegions(region); + adjacent.forEach(r => { + allAdjacents.add(r); + result.add(r); + }); + checkedRegions.add(region); + } + rootRegions = Array.from(allAdjacents); } } - result.push(...this.calculateRequiredVisibleRegionsForPlayer(player)); - result.push(...this.publicVisibleRegions) + [...this.calculateRequiredVisibleRegionsForPlayer(player), ...this.publicVisibleRegions].forEach(r => result.add(r)); - return _.uniq(result); + // Add ports of visible castles: + result.forEach(r => { + const port = this.world.getAdjacentPortOfCastle(r); + if (port) { + result.add(port); + } + }); + + return Array.from(result); } calculateRequiredVisibleRegionsForPlayer(player: Player): Region[] { @@ -2308,7 +2320,7 @@ export default class IngameGameState extends GameState< ? this.visibleRegionsPerPlayer.entries.map(([p, regions]) => [p.user.id, regions.map(r => r.id)]) : this.visibleRegionsPerPlayer.entries.filter(([p, _regions]) => p.user == user).map(([p, regions]) => [p.user.id, regions.map(r => r.id)]), publicVisibleRegions: this.publicVisibleRegions.map(r => r.id), - unitVisibilityRange: this.unitVisibilityRange, + unitVisibilityRangeModifier: this.unitVisibilityRangeModifier, oldPlayerIds: this.oldPlayerIds, replacerIds: this.replacerIds, timeoutPlayerIds: this.timeoutPlayerIds, @@ -2335,7 +2347,7 @@ export default class IngameGameState extends GameState< data.visibleRegionsPerPlayer.map(([uid, rids]) => [ingameGameState.players.get(entireGame.users.get(uid)), rids.map(rid => ingameGameState.world.regions.get(rid))]) ); ingameGameState.publicVisibleRegions = data.publicVisibleRegions.map(rid => ingameGameState.world.regions.get(rid)); - ingameGameState.unitVisibilityRange = data.unitVisibilityRange; + ingameGameState.unitVisibilityRangeModifier = data.unitVisibilityRangeModifier; ingameGameState.oldPlayerIds = data.oldPlayerIds; ingameGameState.replacerIds = data.replacerIds; ingameGameState.timeoutPlayerIds = data.timeoutPlayerIds; @@ -2382,7 +2394,7 @@ export interface SerializedIngameGameState { players: SerializedPlayer[]; visibleRegionsPerPlayer: [string, string[]][]; publicVisibleRegions: string[]; - unitVisibilityRange: number; + unitVisibilityRangeModifier: number; oldPlayerIds: string[]; replacerIds: string[]; timeoutPlayerIds: string[]; diff --git a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/combat-game-state/post-combat-game-state/PostCombatGameState.ts b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/combat-game-state/post-combat-game-state/PostCombatGameState.ts index 1930b50ff..4b64c9ac7 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/combat-game-state/post-combat-game-state/PostCombatGameState.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/action-game-state/resolve-march-order-game-state/combat-game-state/post-combat-game-state/PostCombatGameState.ts @@ -433,7 +433,6 @@ export default class PostCombatGameState extends GameState< applyChangesNow: !this.combat.ingameGameState.players.has(u) }); }); - this.combat.ingameGameState.updateVisibleRegions(true); } this.combat.resolveMarchOrderGameState.onResolveSingleMarchOrderGameStateFinish(this.attacker); diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/UnitType.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/UnitType.ts index 5503aaa99..e721fc26b 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/UnitType.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/UnitType.ts @@ -9,6 +9,7 @@ export default class UnitType { walksOn: RegionKind; canTransport: RegionKind | null; canRetreat: boolean; + baseVisibilityRange: number; constructor( id: string, @@ -18,7 +19,8 @@ export default class UnitType { combatStrength: number, combatStrengthOnAttackStructure: number | null = null, canTransport: RegionKind | null = null, - canRetreat = true + canRetreat = true, + baseVisibilityRange = 1 ) { this.id = id; this.name = name; @@ -28,5 +30,6 @@ export default class UnitType { this.combatStrength = combatStrength; this.combatStrengthOnAttackStructure = combatStrengthOnAttackStructure; this.canRetreat = canRetreat; + this.baseVisibilityRange = baseVisibilityRange; } } diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/unitTypes.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/unitTypes.ts index e1fa99abc..e8adaa845 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/unitTypes.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/unitTypes.ts @@ -6,7 +6,7 @@ export const footman = new UnitType("footman", "Footman", "Adds 1 Combat Strengt export const knight = new UnitType("knight", "Knight", "Adds 2 Combat Strength in battle. Costs 2 points of mustering (or 1 point if upgraded from a Footman).", RegionKind.LAND, 2); export const siegeEngine = new UnitType("siege-engine", "Siege Engine", "Adds 4 Combat Strength when attacking (or supporting an attack against) an area containing a Castle or Stronghold, otherwise it adds 0. Siege Engines may not retreat when losing combat; they are destroyed instead. Costs 2 points of mustering (or 1 point if upgraded from a Footman).", RegionKind.LAND, 0, 4, null, false); export const ship = new UnitType("ship", "Ship", "Adds 1 Combat Strength in battle. Costs 1 point of mustering.", RegionKind.SEA, 1, null, RegionKind.LAND); -export const dragon = new UnitType("dragon", "Dragon", "Adds 0-5 Combat Strength in battle, depending on the current position of the dragon strength token. Dragons are extraordinarily rare creatures and cannot be mustered like regular units. Instead, they are in play from the beginning of the game.", RegionKind.LAND, 0); +export const dragon = new UnitType("dragon", "Dragon", "Adds 0-5 Combat Strength in battle, depending on the current position of the dragon strength token. Dragons are extraordinarily rare creatures and cannot be mustered like regular units. Instead, they are in play from the beginning of the game.", RegionKind.LAND, 0, null, null, true, 2); const unitTypes = new BetterMap([ [footman.id, footman], diff --git a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/westeros-card/DenseFogWesterosCardType.ts b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/westeros-card/DenseFogWesterosCardType.ts index 79d8e2dbc..eca303cb5 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/westeros-card/DenseFogWesterosCardType.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/game-data-structure/westeros-card/DenseFogWesterosCardType.ts @@ -3,7 +3,7 @@ import WesterosGameState from "../../westeros-game-state/WesterosGameState"; export default class DenseFogWesterosCardType extends WesterosCardType { execute(westeros: WesterosGameState): void { - westeros.ingame.unitVisibilityRange = 0; + westeros.ingame.unitVisibilityRangeModifier = -1; westeros.ingame.updateVisibleRegions(true); westeros.onWesterosCardEnd(); } diff --git a/agot-bg-game-server/src/common/ingame-game-state/vote-system/Vote.ts b/agot-bg-game-server/src/common/ingame-game-state/vote-system/Vote.ts index 7e0afcb52..04f0d6602 100644 --- a/agot-bg-game-server/src/common/ingame-game-state/vote-system/Vote.ts +++ b/agot-bg-game-server/src/common/ingame-game-state/vote-system/Vote.ts @@ -116,7 +116,7 @@ export default class Vote { checkVoteFinished(): void { if (this.state == VoteState.ACCEPTED) { this.type.executeAccepted(this); - this.ingame.updateVisibleRegions(); + this.ingame.updateVisibleRegions(true); } } diff --git a/agot-bg-game-server/src/server/serializedGameMigrations.ts b/agot-bg-game-server/src/server/serializedGameMigrations.ts index 912c97491..da5d11043 100644 --- a/agot-bg-game-server/src/server/serializedGameMigrations.ts +++ b/agot-bg-game-server/src/server/serializedGameMigrations.ts @@ -2391,6 +2391,20 @@ const serializedGameMigrations: {version: string; migrate: (serializeGamed: any) serializedGame.gameSettings.houseCardsEvolutionRound = 5; return serializedGame; } + }, + { + version: "122", + migrate: (serializedGame: any) => { + if (serializedGame.childGameState.type == "ingame") { + const ingame = serializedGame.childGameState; + if (ingame.unitVisibilityRange === undefined) { + ingame.unitVisibilityRangeModifier = 0; + } else { + ingame.unitVisibilityRangeModifier = ingame.unitVisibilityRange - 1; + } + } + return serializedGame; + } } ];