From 29b587cdae36b080bfaa378d214c3b8a932b15da Mon Sep 17 00:00:00 2001 From: DevelopingTom Date: Mon, 14 Jul 2025 17:59:51 +0200 Subject: [PATCH] Factory spawns trains (#1408) ## Description: - Change trains so it spawns from factories only - Increase train frequency as they will now spawn from a single structure. - Factory will spawn more trains depending on its level - Fix port to connect to nearby railroads - Add factory description ## 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 - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors ## Please put your Discord username so you can be contacted if a bug or regression is found: IngloriousTom --------- Co-authored-by: Tom Rouillard Co-authored-by: evanpelle --- resources/lang/en.json | 7 ++- src/client/styles.css | 5 ++ src/core/configuration/DefaultConfig.ts | 2 +- src/core/execution/FactoryExecution.ts | 6 +-- src/core/execution/PortExecution.ts | 16 ++++++ src/core/execution/TrainStationExecution.ts | 58 +++++++++++++++------ src/core/game/TrainStation.ts | 11 +--- 7 files changed, 74 insertions(+), 31 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index cbe8d2537..386216e70 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -90,6 +90,8 @@ "build_desc": "Description", "build_city": "City", "build_city_desc": "Increases your max population. Useful when you can't expand your territory or you're about to hit your population limit.", + "build_factory": "Factory", + "build_factory_desc": "Creates railroads automatically with nearby structures, and spawns trains sporadically.", "build_defense": "Defense Post", "build_defense_desc": "Increases defenses around nearby borders, which show a checkered pattern. Attacks from enemies are slower and have more casualties.", "build_port": "Port", @@ -409,8 +411,9 @@ "sam_launcher": "Defends against incoming nukes", "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" + "defense_post": "Increases defenses of nearby borders", + "city": "Increases max population", + "factory": "Creates railroads and spawns trains" }, "not_enough_money": "Not enough money" }, diff --git a/src/client/styles.css b/src/client/styles.css index 26fa47658..8ad456066 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -369,6 +369,11 @@ label.option-card:hover { mask: url("../../resources/images/CityIconWhite.svg") no-repeat center / cover; } +#helpModal .factory-icon { + mask: url("../../resources/images/FactoryIconWhite.svg") no-repeat center / + cover; +} + #helpModal .defense-post-icon { mask: url("../../resources/images/ShieldIconWhite.svg") no-repeat center / cover; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 6705282ef..558280880 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -326,7 +326,7 @@ export class DefaultConfig implements Config { return this._gameConfig.infiniteTroops; } trainSpawnRate(numberOfStations: number): number { - return Math.min(1400, Math.round(70 * Math.pow(numberOfStations, 0.8))); + return Math.min(1400, Math.round(20 * Math.pow(numberOfStations, 0.5))); } trainGold(): Gold { return BigInt(10_000); diff --git a/src/core/execution/FactoryExecution.ts b/src/core/execution/FactoryExecution.ts index cbd1d1667..fd24de674 100644 --- a/src/core/execution/FactoryExecution.ts +++ b/src/core/execution/FactoryExecution.ts @@ -51,11 +51,11 @@ export class FactoryExecution implements Execution { this.game.config().trainStationMaxRange(), [UnitType.City, UnitType.Port, UnitType.Factory], ); - // Use different seeds or trains will spawn simultaneously - let seed = 0; + + this.game.addExecution(new TrainStationExecution(this.factory, true)); for (const { unit } of structures) { if (!unit.hasTrainStation()) { - this.game.addExecution(new TrainStationExecution(unit, ++seed)); + this.game.addExecution(new TrainStationExecution(unit)); } } } diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 2df238fc2..6d3f45351 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -2,6 +2,7 @@ import { Execution, Game, Player, Unit, UnitType } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { PseudoRandom } from "../PseudoRandom"; import { TradeShipExecution } from "./TradeShipExecution"; +import { TrainStationExecution } from "./TrainStationExecution"; export class PortExecution implements Execution { private active = true; @@ -36,6 +37,7 @@ export class PortExecution implements Execution { return; } this.port = this.player.buildUnit(UnitType.Port, spawn, {}); + this.createStation(); } if (!this.port.isActive()) { @@ -84,4 +86,18 @@ export class PortExecution implements Execution { } return false; } + + createStation(): void { + if (this.port !== null) { + const nearbyFactory = this.mg.hasUnitNearby( + this.port.tile()!, + this.mg.config().trainStationMaxRange(), + UnitType.Factory, + this.player.id(), + ); + if (nearbyFactory) { + this.mg.addExecution(new TrainStationExecution(this.port)); + } + } + } } diff --git a/src/core/execution/TrainStationExecution.ts b/src/core/execution/TrainStationExecution.ts index fc8563da1..ee0174bcc 100644 --- a/src/core/execution/TrainStationExecution.ts +++ b/src/core/execution/TrainStationExecution.ts @@ -6,13 +6,17 @@ import { TrainExecution } from "./TrainExecution"; export class TrainStationExecution implements Execution { private mg: Game; private active: boolean = true; - private random: PseudoRandom | null = null; + private random: PseudoRandom; private station: TrainStation | null = null; private numCars: number = 5; + private lastSpawnTick: number = 0; + private ticksCooldown: number = 10; // Minimum cooldown between two trains constructor( private unit: Unit, - private randomSeed?: number, - ) {} + private spawnTrains?: boolean, // If set, the station will spawn trains + ) { + this.unit.setTrainStation(true); + } isActive(): boolean { return this.active; @@ -20,8 +24,9 @@ export class TrainStationExecution implements Execution { init(mg: Game, ticks: number): void { this.mg = mg; - this.random = new PseudoRandom(mg.ticks() + (this.randomSeed ?? 0)); - this.unit.setTrainStation(true); + if (this.spawnTrains) { + this.random = new PseudoRandom(mg.ticks()); + } } tick(ticks: number): void { @@ -36,36 +41,57 @@ export class TrainStationExecution implements Execution { this.station = new TrainStation(this.mg, this.unit); this.mg.railNetwork().connectStation(this.station); } - if (!this.station.isActive() || !this.random) { + if (!this.station.isActive()) { this.active = false; return; } - const cluster = this.station.getCluster(); + this.spawnTrain(this.station, ticks); + } + + private shouldSpawnTrain(clusterSize: number): boolean { + const spawnRate = this.mg.config().trainSpawnRate(clusterSize); + for (let i = 0; i < this.unit!.level(); i++) { + if (this.random.chance(spawnRate)) { + return true; + } + } + return false; + } + + private spawnTrain(station: TrainStation, currentTick: number) { + if ( + !this.spawnTrains || + currentTick - this.lastSpawnTick < this.ticksCooldown + ) { + return; + } + const cluster = station.getCluster(); if (cluster === null) { return; } const availableForTrade = cluster.availableForTrade(this.unit.owner()); - if ( - availableForTrade.size === 0 || - !this.random.chance( - this.mg.config().trainSpawnRate(availableForTrade.size), - ) - ) { + if (availableForTrade.size === 0) { return; } + if (!this.shouldSpawnTrain(availableForTrade.size)) { + return; + } + // Pick a destination randomly. // Could be improved to pick a lucrative trip - const destination = this.random.randFromSet(availableForTrade); - if (destination !== this.station) { + const destination: TrainStation = + this.random.randFromSet(availableForTrade); + if (destination !== station) { this.mg.addExecution( new TrainExecution( this.mg.railNetwork(), this.unit.owner(), - this.station, + station, destination, this.numCars, ), ); + this.lastSpawnTick = currentTick; } } diff --git a/src/core/game/TrainStation.ts b/src/core/game/TrainStation.ts index 027ddd276..b0149e598 100644 --- a/src/core/game/TrainStation.ts +++ b/src/core/game/TrainStation.ts @@ -45,20 +45,13 @@ class PortStopHandler implements TrainStopHandler { } class FactoryStopHandler implements TrainStopHandler { + private factor: bigint = BigInt(2); onStop( mg: Game, station: TrainStation, trainExecution: TrainExecution, ): void { - const goldBonus = mg.config().trainGold(); - station.unit.owner().addGold(goldBonus); - mg.addUpdate({ - type: GameUpdateType.BonusEvent, - tile: station.tile(), - gold: Number(goldBonus), - workers: 0, - troops: 0, - }); + station.unit.owner().addGold(mg.config().trainGold(), station.tile()); } }