mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:10:42 +00:00
a05814a7fb
## Description:
This update encourages alliances by modifying how train-based trading
works. Trading is now restricted to allied networks, and allied trades
offer better rewards than self-trades, incentivizing cooperation.
Changes:
- Train destinations are now limited to stations owned by the player or
their allies.
- Delivering a train to one’s own station grants 4,000 gold.
- Delivering a train to an ally’s station grants 5,000 gold to both the
sender and the receiver.
This system encourages players to form alliances, especially with
factory-focused players.
## 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 have read and accepted the CLA agreement (only required once).
## Please put your Discord username so you can be contacted if a bug or
regression is found:
IngloriousTom
243 lines
6.1 KiB
TypeScript
243 lines
6.1 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;
|
|
private cars: Unit[] = [];
|
|
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.Engine,
|
|
}),
|
|
);
|
|
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;
|
|
}
|
|
}
|