mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 18:26:43 +00:00
Color railroad segments based on cost and add changes required for that
Implement fare-only updates for railroads and enhance client-side coloring - Introduce fare tracking in RailroadLayer and update rendering logic to handle fare-only updates. - Modify FxLayer to skip effects for fare-only updates. - Add fare calculation and update logic in Railroad and TrainExecution classes. - Update RailTile interface to include optional fare property for client-side use. - Ensure significant fare changes trigger updates to clients for visual feedback.
This commit is contained in:
@@ -230,6 +230,10 @@ export class FxLayer implements Layer {
|
||||
}
|
||||
|
||||
onRailroadEvent(railroad: RailroadUpdate) {
|
||||
// Skip FX for fare-only color updates
|
||||
if (railroad.isFareUpdate) {
|
||||
return;
|
||||
}
|
||||
const railTiles = railroad.railTiles;
|
||||
for (const rail of railTiles) {
|
||||
// No need for pseudorandom, this is fx
|
||||
|
||||
@@ -27,6 +27,9 @@ export class RailroadLayer implements Layer {
|
||||
private existingRailroads = new Map<TileRef, RailRef>();
|
||||
private nextRailIndexToCheck = 0;
|
||||
private railTileList: TileRef[] = [];
|
||||
// Track global fare range for coloring
|
||||
private minFare: number = Infinity;
|
||||
private maxFare: number = 0;
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
@@ -116,6 +119,24 @@ export class RailroadLayer implements Layer {
|
||||
}
|
||||
|
||||
private handleRailroadRendering(railUpdate: RailroadUpdate) {
|
||||
// Fare-only updates: recolor existing segments without touching counts
|
||||
if (railUpdate.isActive && railUpdate.isFareUpdate) {
|
||||
for (const railRoad of railUpdate.railTiles) {
|
||||
const ref = this.existingRailroads.get(railRoad.tile);
|
||||
if (!ref) continue;
|
||||
|
||||
if (railRoad.fare !== undefined) {
|
||||
this.minFare = Math.min(this.minFare, railRoad.fare);
|
||||
this.maxFare = Math.max(this.maxFare, railRoad.fare);
|
||||
}
|
||||
// Update the stored tile's fare and repaint
|
||||
ref.tile.fare = railRoad.fare;
|
||||
this.paintRail(ref.tile);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Construction / deletion events
|
||||
for (const railRoad of railUpdate.railTiles) {
|
||||
if (railUpdate.isActive) {
|
||||
this.paintRailroad(railRoad);
|
||||
@@ -126,6 +147,11 @@ export class RailroadLayer implements Layer {
|
||||
}
|
||||
|
||||
private paintRailroad(railRoad: RailTile) {
|
||||
if (railRoad.fare !== undefined) {
|
||||
this.minFare = Math.min(this.minFare, railRoad.fare);
|
||||
this.maxFare = Math.max(this.maxFare, railRoad.fare);
|
||||
}
|
||||
|
||||
const currentOwner = this.game.owner(railRoad.tile)?.id() ?? null;
|
||||
const railTile = this.existingRailroads.get(railRoad.tile);
|
||||
|
||||
@@ -182,9 +208,26 @@ export class RailroadLayer implements Layer {
|
||||
}
|
||||
const owner = this.game.owner(tile);
|
||||
const recipient = owner.isPlayer() ? owner : null;
|
||||
const color = recipient
|
||||
let color = recipient
|
||||
? recipient.borderColor()
|
||||
: colord("rgba(255,255,255,1)");
|
||||
|
||||
const fare = railRoad.fare;
|
||||
if (
|
||||
fare !== undefined &&
|
||||
Number.isFinite(this.minFare) &&
|
||||
this.maxFare > this.minFare
|
||||
) {
|
||||
const t = (fare - this.minFare) / (this.maxFare - this.minFare);
|
||||
const clampedT = Math.max(0, Math.min(1, t));
|
||||
const baseRgb = color.toRgb();
|
||||
const redTarget = 255;
|
||||
const r = Math.round(baseRgb.r + (redTarget - baseRgb.r) * clampedT);
|
||||
const g = Math.round(baseRgb.g * (1 - clampedT));
|
||||
const b = Math.round(baseRgb.b * (1 - clampedT));
|
||||
color = colord({ r, g, b });
|
||||
}
|
||||
|
||||
this.context.fillStyle = color.toRgbString();
|
||||
this.paintRailRects(this.context, x, y, railType);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export class RailroadExecution implements Execution {
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
const tiles = this.railRoad.tiles;
|
||||
const fare = Number(this.railRoad.getFare());
|
||||
// Inverse direction computation for the first tile
|
||||
this.railTiles.push({
|
||||
tile: tiles[0],
|
||||
@@ -28,6 +29,7 @@ export class RailroadExecution implements Execution {
|
||||
tiles.length > 0
|
||||
? this.computeExtremityDirection(tiles[0], tiles[1])
|
||||
: RailType.VERTICAL,
|
||||
fare,
|
||||
});
|
||||
for (let i = 1; i < tiles.length - 1; i++) {
|
||||
const direction = this.computeDirection(
|
||||
@@ -35,7 +37,7 @@ export class RailroadExecution implements Execution {
|
||||
tiles[i],
|
||||
tiles[i + 1],
|
||||
);
|
||||
this.railTiles.push({ tile: tiles[i], railType: direction });
|
||||
this.railTiles.push({ tile: tiles[i], railType: direction, fare });
|
||||
}
|
||||
this.railTiles.push({
|
||||
tile: tiles[tiles.length - 1],
|
||||
@@ -46,7 +48,10 @@ export class RailroadExecution implements Execution {
|
||||
tiles[tiles.length - 2],
|
||||
)
|
||||
: RailType.VERTICAL,
|
||||
fare,
|
||||
});
|
||||
// Cache full geometry on the railroad so it can later emit fare-only updates
|
||||
this.railRoad.setRailTiles(this.railTiles);
|
||||
}
|
||||
|
||||
private computeExtremityDirection(tile: TileRef, next: TileRef): RailType {
|
||||
|
||||
@@ -118,6 +118,10 @@ export class TrainExecution implements Execution {
|
||||
? tiles[Math.floor(tiles.length / 2)]
|
||||
: railroad.getStart().tile();
|
||||
this.player.addGold(-fare, midTile);
|
||||
// Update client-side coloring when fare changes significantly
|
||||
if (this.mg !== null) {
|
||||
rail.updateFare(this.mg);
|
||||
}
|
||||
}
|
||||
|
||||
private leaveRailroad() {
|
||||
|
||||
@@ -90,12 +90,22 @@ export enum RailType {
|
||||
export interface RailTile {
|
||||
tile: TileRef;
|
||||
railType: RailType;
|
||||
/**
|
||||
* Optional current fare for this railroad segment, used for client-side coloring.
|
||||
* Represented as a number for convenience (server BigInt is converted).
|
||||
*/
|
||||
fare?: number;
|
||||
}
|
||||
|
||||
export interface RailroadUpdate {
|
||||
type: GameUpdateType.RailroadEvent;
|
||||
isActive: boolean;
|
||||
railTiles: RailTile[];
|
||||
/**
|
||||
* When true, this update only signals a fare / color change for existing
|
||||
* rail segments and should not be treated as a construction / deletion event.
|
||||
*/
|
||||
isFareUpdate?: boolean;
|
||||
}
|
||||
|
||||
export interface ConquestUpdate {
|
||||
|
||||
@@ -83,6 +83,42 @@ export class Railroad {
|
||||
const congestionFare = baseCongestionFare * congestionFactor;
|
||||
return lengthFare + congestionFare;
|
||||
}
|
||||
|
||||
setRailTiles(tiles: RailTile[]) {
|
||||
this.railTiles = tiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a fare update to clients if the fare has changed significantly.
|
||||
* Currently uses a 10% relative-change threshold.
|
||||
*/
|
||||
updateFare(game: Game) {
|
||||
if (!this.railTiles || this.railTiles.length === 0) return;
|
||||
const newFare = this.getFare();
|
||||
if (this.lastFare !== null) {
|
||||
const prev = this.lastFare;
|
||||
const diff = newFare > prev ? newFare - prev : prev - newFare;
|
||||
const threshold = prev / 10n; // 10%
|
||||
if (threshold > 0n && diff < threshold) {
|
||||
this.lastFare = newFare;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.lastFare = newFare;
|
||||
|
||||
const numericFare = Number(newFare);
|
||||
const railTilesWithFare: RailTile[] = this.railTiles.map((t) => ({
|
||||
...t,
|
||||
fare: numericFare,
|
||||
}));
|
||||
|
||||
game.addUpdate({
|
||||
type: GameUpdateType.RailroadEvent,
|
||||
isActive: true,
|
||||
isFareUpdate: true,
|
||||
railTiles: railTilesWithFare,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrientedRailroad(
|
||||
|
||||
Reference in New Issue
Block a user