mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 07:07:05 +00:00
ab5b044362
## Description: Trains are made of a primary unit (`TrainExecution.train`) followed by 6 cars (`TrainExecution.cars`). Currently when any of the units is destroyed, a message "Your Train was destroyed" is shown. In worst case scenario, when all cars are blasted by a nuke, 7 messages are displayed to the user, but the train continues. Since the actual logic is unaffected as long as the primary unit stays alive, displaying messages is confusing. This PR fixes it. The message is only displayed when the primary unit is destroyed. The following cars are only there for fancy visuals. ## Screencast ##### Current - multiple messages for single train https://github.com/user-attachments/assets/3df04c71-d899-4f68-af83-36c9d0ffa730 First nuke was a test. Second nuke destroyed the entire train, prompting 7 messages. Third nuke destroyed one full train and then some. Prompting many too many messages. ##### Fixed - one message, only if train was fully destroyed https://github.com/user-attachments/assets/1f3840a7-6c62-487d-af3a-82de39dad9e8 First train was only partially destroyed, no message was shown, delivery mission completed A+. Following 2 trains had the front engine destroyed and therefore were promptly decommissioned. ## Please complete the following: - [x] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] 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: moleole
244 lines
6.2 KiB
TypeScript
244 lines
6.2 KiB
TypeScript
import {
|
|
Execution,
|
|
Game,
|
|
Player,
|
|
TrainType,
|
|
Unit,
|
|
UnitType,
|
|
} from "../game/Game";
|
|
import { TileRef } from "../game/GameMap";
|
|
import { RailNetwork } from "../game/RailNetwork";
|
|
import { getOrientedRailroad, OrientedRailroad } from "../game/Railroad";
|
|
import { TrainStation } from "../game/TrainStation";
|
|
|
|
export class TrainExecution implements Execution {
|
|
private active = true;
|
|
private mg: Game | null = null;
|
|
private train: Unit | null = null; // primary unit
|
|
private cars: Unit[] = []; // stored back to front
|
|
private hasCargo: boolean = false;
|
|
private currentTile: number = 0;
|
|
private spacing = 2;
|
|
private usedTiles: TileRef[] = []; // used for cars behind
|
|
private stations: TrainStation[] = [];
|
|
private currentRailroad: OrientedRailroad | null = null;
|
|
private speed: number = 2;
|
|
|
|
constructor(
|
|
private railNetwork: RailNetwork,
|
|
private player: Player,
|
|
private source: TrainStation,
|
|
private destination: TrainStation,
|
|
private numCars: number,
|
|
) {}
|
|
|
|
public owner(): Player {
|
|
return this.player;
|
|
}
|
|
|
|
init(mg: Game, ticks: number): void {
|
|
this.mg = mg;
|
|
const stations = this.railNetwork.findStationsPath(
|
|
this.source,
|
|
this.destination,
|
|
);
|
|
if (!stations || stations.length <= 1) {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
|
|
this.stations = stations;
|
|
const railroad = getOrientedRailroad(this.stations[0], this.stations[1]);
|
|
if (railroad) {
|
|
this.currentRailroad = railroad;
|
|
} else {
|
|
this.active = false;
|
|
return;
|
|
}
|
|
|
|
const spawn = this.player.canBuild(UnitType.Train, this.stations[0].tile());
|
|
if (spawn === false) {
|
|
console.warn(`cannot build train`);
|
|
this.active = false;
|
|
return;
|
|
}
|
|
this.train = this.createTrainUnits(spawn);
|
|
}
|
|
|
|
tick(ticks: number): void {
|
|
if (this.train === null) {
|
|
throw new Error("Not initialized");
|
|
}
|
|
|
|
if (!this.train.isActive() || !this.activeSourceOrDestination()) {
|
|
this.deleteTrain();
|
|
return;
|
|
}
|
|
|
|
const tile = this.getNextTile();
|
|
if (tile) {
|
|
this.updateCarsPositions(tile);
|
|
} else {
|
|
this.targetReached();
|
|
this.deleteTrain();
|
|
}
|
|
}
|
|
|
|
loadCargo() {
|
|
if (this.hasCargo || this.train === null) {
|
|
return;
|
|
}
|
|
this.hasCargo = true;
|
|
// Starts at 1: don't load tail engine
|
|
for (let i = 1; i < this.cars.length; i++) {
|
|
this.cars[i].setLoaded(true);
|
|
}
|
|
}
|
|
|
|
private targetReached() {
|
|
if (this.train === null) {
|
|
return;
|
|
}
|
|
this.train.setReachedTarget();
|
|
this.cars.forEach((car: Unit) => {
|
|
car.setReachedTarget();
|
|
});
|
|
}
|
|
|
|
private createTrainUnits(tile: TileRef): Unit {
|
|
const train = this.player.buildUnit(UnitType.Train, tile, {
|
|
targetUnit: this.destination.unit,
|
|
trainType: TrainType.Engine,
|
|
});
|
|
// Tail is also an engine, just for cosmetics
|
|
this.cars.push(
|
|
this.player.buildUnit(UnitType.Train, tile, {
|
|
targetUnit: this.destination.unit,
|
|
trainType: TrainType.TailEngine,
|
|
}),
|
|
);
|
|
for (let i = 0; i < this.numCars; i++) {
|
|
this.cars.push(
|
|
this.player.buildUnit(UnitType.Train, tile, {
|
|
trainType: TrainType.Carriage,
|
|
loaded: this.hasCargo,
|
|
}),
|
|
);
|
|
}
|
|
return train;
|
|
}
|
|
|
|
private deleteTrain() {
|
|
this.active = false;
|
|
if (this.train?.isActive()) {
|
|
this.train.delete(false);
|
|
}
|
|
for (const car of this.cars) {
|
|
if (car.isActive()) {
|
|
car.delete(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private activeSourceOrDestination(): boolean {
|
|
return (
|
|
this.stations.length > 1 &&
|
|
this.stations[1].isActive() &&
|
|
this.stations[0].isActive()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Save the tiles the train go through so the cars can reuse them
|
|
* Don't simply save the tiles the engine uses, otherwise the spacing will be dictated by the train speed
|
|
*/
|
|
private saveTraversedTiles(from: number, speed: number) {
|
|
if (!this.currentRailroad) {
|
|
return;
|
|
}
|
|
let tileToSave: number = from;
|
|
for (
|
|
let i = 0;
|
|
i < speed && tileToSave < this.currentRailroad.getTiles().length;
|
|
i++
|
|
) {
|
|
this.saveTile(this.currentRailroad.getTiles()[tileToSave]);
|
|
tileToSave = tileToSave + 1;
|
|
}
|
|
}
|
|
|
|
private saveTile(tile: TileRef) {
|
|
this.usedTiles.push(tile);
|
|
if (this.usedTiles.length > this.cars.length * this.spacing + 3) {
|
|
this.usedTiles.shift();
|
|
}
|
|
}
|
|
|
|
private updateCarsPositions(newTile: TileRef) {
|
|
if (this.cars.length > 0) {
|
|
for (let i = this.cars.length - 1; i >= 0; --i) {
|
|
const carTileIndex = (i + 1) * this.spacing + 2;
|
|
if (this.usedTiles.length > carTileIndex) {
|
|
this.cars[i].move(this.usedTiles[carTileIndex]);
|
|
}
|
|
}
|
|
}
|
|
if (this.train !== null) {
|
|
this.train.move(newTile);
|
|
}
|
|
}
|
|
|
|
private nextStation() {
|
|
if (this.stations.length > 2) {
|
|
this.stations.shift();
|
|
const railRoad = getOrientedRailroad(this.stations[0], this.stations[1]);
|
|
if (railRoad) {
|
|
this.currentRailroad = railRoad;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private canTradeWithDestination() {
|
|
return (
|
|
this.stations.length > 1 && this.stations[1].tradeAvailable(this.player)
|
|
);
|
|
}
|
|
|
|
private getNextTile(): TileRef | null {
|
|
if (this.currentRailroad === null || !this.canTradeWithDestination()) {
|
|
return null;
|
|
}
|
|
this.saveTraversedTiles(this.currentTile, this.speed);
|
|
this.currentTile = this.currentTile + this.speed;
|
|
const leftOver = this.currentTile - this.currentRailroad.getTiles().length;
|
|
if (leftOver >= 0) {
|
|
// Station reached, pick the next station
|
|
this.stationReached();
|
|
if (!this.nextStation()) {
|
|
return null; // Destination reached (or no valid connection)
|
|
}
|
|
this.currentTile = leftOver;
|
|
this.saveTraversedTiles(0, leftOver);
|
|
}
|
|
return this.currentRailroad.getTiles()[this.currentTile];
|
|
}
|
|
|
|
private stationReached() {
|
|
if (this.mg === null || this.player === null) {
|
|
throw new Error("Not initialized");
|
|
}
|
|
this.stations[1].onTrainStop(this);
|
|
return;
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this.active;
|
|
}
|
|
|
|
activeDuringSpawnPhase(): boolean {
|
|
return false;
|
|
}
|
|
}
|