Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
5 changes: 5 additions & 0 deletions src/client/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/core/execution/FactoryExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/core/execution/PortExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -36,6 +37,7 @@ export class PortExecution implements Execution {
return;
}
this.port = this.player.buildUnit(UnitType.Port, spawn, {});
this.createStation();
}

if (!this.port.isActive()) {
Expand Down Expand Up @@ -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));
}
}
}
}
58 changes: 42 additions & 16 deletions src/core/execution/TrainStationExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ 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;
}

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 {
Expand All @@ -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;
}
}

Expand Down
11 changes: 2 additions & 9 deletions src/core/game/TrainStation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
Loading