From cc9ad11aff2c6d15ba3a1e76c22caed2ca382cad Mon Sep 17 00:00:00 2001 From: 1brucben <1benjbruce@gmail.com> Date: Tue, 3 Jun 2025 18:22:03 +0200 Subject: [PATCH] add hospital unit --- resources/images/HospitalIconWhite.svg | 3 + resources/images/buildings/hospital.png | Bin 0 -> 156 bytes resources/lang/en.json | 6 +- src/client/graphics/layers/BuildMenu.ts | 8 +++ src/client/graphics/layers/StructureLayer.ts | 7 ++ src/client/utilities/RenderUnitTypeOptions.ts | 1 + src/core/configuration/DefaultConfig.ts | 21 ++++++ src/core/execution/ConstructionExecution.ts | 4 ++ src/core/execution/HospitalExecution.ts | 60 ++++++++++++++++++ src/core/game/Game.ts | 3 + src/core/game/PlayerImpl.ts | 1 + 11 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 resources/images/HospitalIconWhite.svg create mode 100644 resources/images/buildings/hospital.png create mode 100644 src/core/execution/HospitalExecution.ts diff --git a/resources/images/HospitalIconWhite.svg b/resources/images/HospitalIconWhite.svg new file mode 100644 index 000000000..093b2c47c --- /dev/null +++ b/resources/images/HospitalIconWhite.svg @@ -0,0 +1,3 @@ + + H + diff --git a/resources/images/buildings/hospital.png b/resources/images/buildings/hospital.png new file mode 100644 index 0000000000000000000000000000000000000000..1a36874e318fc7c7156617769852bb69dc344b58 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Y&=~YLoEE0 zQx*sq1g)6g_>1k&|NsB(PlWdL^k}p_7CK&V(7=cx{LO-P-g5>EZZu4k;a>8L_tkUO upjZZm7*h{HRU@7GKy&y~BRtc5eHpZXYz`m>flI-YL6oPfpUXO@geCyg9xf*U literal 0 HcmV?d00001 diff --git a/resources/lang/en.json b/resources/lang/en.json index d65dd52c0..9e1d5c5ca 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -225,7 +225,8 @@ "sam_launcher": "SAM Launcher", "atom_bomb": "Atom Bomb", "hydrogen_bomb": "Hydrogen Bomb", - "mirv": "MIRV" + "mirv": "MIRV", + "hospital": "Hospital" }, "user_setting": { "title": "User Settings", @@ -354,7 +355,8 @@ "warship": "Captures trade ships, destroys ships and boats", "port": "Sends trade ships to generate gold", "defense_post": "Increase defenses of nearby borders", - "city": "Increase max population" + "city": "Increase max population", + "hospital": "Boosts population growth by 5%" }, "not_enough_money": "Not enough money" }, diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts index 70f28ea65..0b99ceee6 100644 --- a/src/client/graphics/layers/BuildMenu.ts +++ b/src/client/graphics/layers/BuildMenu.ts @@ -3,6 +3,7 @@ import { customElement, state } from "lit/decorators.js"; import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg"; import cityIcon from "../../../../resources/images/CityIconWhite.svg"; import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg"; +import hospitalIcon from "../../../../resources/images/HospitalIconWhite.svg"; import mirvIcon from "../../../../resources/images/MIRVIcon.svg"; import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg"; import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg"; @@ -93,6 +94,13 @@ const buildTable: BuildItemDisplay[][] = [ key: "unit_type.city", countable: true, }, + { + unitType: UnitType.Hospital, + icon: hospitalIcon, + description: "build_menu.desc.hospital", + key: "unit_type.hospital", + countable: true, + }, ], ]; diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts index f8ce3db82..c7cdbb10e 100644 --- a/src/client/graphics/layers/StructureLayer.ts +++ b/src/client/graphics/layers/StructureLayer.ts @@ -8,6 +8,7 @@ import { UnitInfoModal } from "./UnitInfoModal"; import cityIcon from "../../../../resources/images/buildings/cityAlt1.png"; import shieldIcon from "../../../../resources/images/buildings/fortAlt2.png"; +import hospitalIcon from "../../../../resources/images/buildings/hospital.png"; import anchorIcon from "../../../../resources/images/buildings/port1.png"; import MissileSiloReloadingIcon from "../../../../resources/images/buildings/silo1-reloading.png"; import missileSiloIcon from "../../../../resources/images/buildings/silo1.png"; @@ -83,6 +84,12 @@ export class StructureLayer implements Layer { territoryRadius: 6.525, borderType: UnitBorderType.Square, }, + [UnitType.Hospital]: { + icon: hospitalIcon, + borderRadius: 8.525, + territoryRadius: 6.525, + borderType: UnitBorderType.Square, + }, }; constructor( diff --git a/src/client/utilities/RenderUnitTypeOptions.ts b/src/client/utilities/RenderUnitTypeOptions.ts index c74aaf7ef..9fea55226 100644 --- a/src/client/utilities/RenderUnitTypeOptions.ts +++ b/src/client/utilities/RenderUnitTypeOptions.ts @@ -18,6 +18,7 @@ const unitOptions: { type: UnitType; translationKey: string }[] = [ { type: UnitType.AtomBomb, translationKey: "unit_type.atom_bomb" }, { type: UnitType.HydrogenBomb, translationKey: "unit_type.hydrogen_bomb" }, { type: UnitType.MIRV, translationKey: "unit_type.mirv" }, + { type: UnitType.Hospital, translationKey: "unit_type.hospital" }, ]; export function renderUnitTypeOptions({ diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 46d3b7919..28767a7d1 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -435,6 +435,23 @@ export class DefaultConfig implements Config { cost: () => 0n, territoryBound: true, }; + case UnitType.Hospital: + return { + cost: (p: Player) => + p.type() === PlayerType.Human && this.infiniteGold() + ? 0n + : BigInt( + Math.min( + 12_000_000, + Math.pow( + 2, + p.unitsIncludingConstruction(UnitType.City).length, + ) * 3_000_000, + ), + ), + territoryBound: true, + constructionDuration: this.instantBuild() ? 0 : 2 * 10, + }; default: assertNever(type); } @@ -677,6 +694,10 @@ export class DefaultConfig implements Config { toAdd *= 0.7; } + if (player.units(UnitType.Hospital).length > 0) { + toAdd *= 1 + 0.05 * player.units(UnitType.Hospital).length; + // hospital boosts population growth + } if (player.type() === PlayerType.FakeHuman) { switch (this._gameConfig.difficulty) { case Difficulty.Easy: diff --git a/src/core/execution/ConstructionExecution.ts b/src/core/execution/ConstructionExecution.ts index ba7a7c9c0..073f342d9 100644 --- a/src/core/execution/ConstructionExecution.ts +++ b/src/core/execution/ConstructionExecution.ts @@ -12,6 +12,7 @@ import { import { TileRef } from "../game/GameMap"; import { CityExecution } from "./CityExecution"; import { DefensePostExecution } from "./DefensePostExecution"; +import { HospitalExecution } from "./HospitalExecution"; import { MirvExecution } from "./MIRVExecution"; import { MissileSiloExecution } from "./MissileSiloExecution"; import { NukeExecution } from "./NukeExecution"; @@ -124,6 +125,9 @@ export class ConstructionExecution implements Execution { case UnitType.City: this.mg.addExecution(new CityExecution(player.id(), this.tile)); break; + case UnitType.Hospital: + this.mg.addExecution(new HospitalExecution(player.id(), this.tile)); + break; default: throw Error(`unit type ${this.constructionType} not supported`); } diff --git a/src/core/execution/HospitalExecution.ts b/src/core/execution/HospitalExecution.ts new file mode 100644 index 000000000..f6ac3a544 --- /dev/null +++ b/src/core/execution/HospitalExecution.ts @@ -0,0 +1,60 @@ +import { consolex } from "../Consolex"; +import { + Execution, + Game, + Player, + PlayerID, + Unit, + UnitType, +} from "../game/Game"; +import { TileRef } from "../game/GameMap"; + +export class HospitalExecution implements Execution { + private player: Player; + private mg: Game; + private hospital: Unit | null = null; + private active: boolean = true; + + constructor( + private ownerId: PlayerID, + private tile: TileRef, + ) {} + + init(mg: Game, ticks: number): void { + this.mg = mg; + if (!mg.hasPlayer(this.ownerId)) { + console.warn(`HospitalExecution: player ${this.ownerId} not found`); + this.active = false; + return; + } + this.player = mg.player(this.ownerId); + } + + tick(ticks: number): void { + if (this.hospital === null) { + const spawnTile = this.player.canBuild(UnitType.Hospital, this.tile); + if (spawnTile === false) { + consolex.warn("cannot build hospital"); + this.active = false; + return; + } + this.hospital = this.player.buildUnit(UnitType.Hospital, spawnTile, {}); + } + if (!this.hospital.isActive()) { + this.active = false; + return; + } + + if (this.player !== this.hospital.owner()) { + this.player = this.hospital.owner(); + } + } + + isActive(): boolean { + return this.active; + } + + activeDuringSpawnPhase(): boolean { + return false; + } +} diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index b2de35cbb..730952d2c 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -148,6 +148,7 @@ export enum UnitType { MIRV = "MIRV", MIRVWarhead = "MIRV Warhead", Construction = "Construction", + Hospital = "Hospital", } export interface OwnerComp { @@ -195,6 +196,8 @@ export interface UnitParamsMap { [UnitType.MIRV]: {}; + [UnitType.Hospital]: {}; + [UnitType.MIRVWarhead]: { targetTile?: number; }; diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index b5ba70d79..d5acf261d 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -809,6 +809,7 @@ export class PlayerImpl implements Player { case UnitType.DefensePost: case UnitType.SAMLauncher: case UnitType.City: + case UnitType.Hospital: case UnitType.Construction: return this.landBasedStructureSpawn(targetTile, validTiles); default: