mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:51:30 +00:00
Build bridges to connect stations across water (#1961)
Derived from [LeviathanLevi PR](https://github.com/openfrontio/OpenFrontIO/pull/1847) Connect stations over water by automatically building bridges Changes: - Railroad construction to water is allowed from shore lines - Railroad construction from water is allowed to shore lines too This creates bridges a few tiles long. <img width="1058" height="680" alt="image" src="https://github.com/user-attachments/assets/493737b9-7aff-4ee2-88ea-7638f6af7c91" /> <img width="361" height="317" alt="image" src="https://github.com/user-attachments/assets/24a71a7a-1ba1-4c88-a89e-876127024148" /> fixes #1837 - [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 regression is found: IngloriousTom
This commit is contained in:
@@ -8,9 +8,9 @@ import {
|
||||
RailTile,
|
||||
RailType,
|
||||
} from "../../../core/game/GameUpdates";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
import { getRailroadRects } from "./RailroadSprites";
|
||||
import { getBridgeRects, getRailroadRects } from "./RailroadSprites";
|
||||
|
||||
type RailRef = {
|
||||
tile: RailTile;
|
||||
@@ -138,31 +138,68 @@ export class RailroadLayer implements Layer {
|
||||
if (!ref || ref.numOccurence <= 0) {
|
||||
this.existingRailroads.delete(railRoad.tile);
|
||||
this.railTileList = this.railTileList.filter((t) => t !== railRoad.tile);
|
||||
this.context.clearRect(
|
||||
this.game.x(railRoad.tile) * 2 - 1,
|
||||
this.game.y(railRoad.tile) * 2 - 1,
|
||||
3,
|
||||
3,
|
||||
);
|
||||
if (this.context === undefined) throw new Error("Not initialized");
|
||||
if (this.game.isWater(railRoad.tile)) {
|
||||
this.context.clearRect(
|
||||
this.game.x(railRoad.tile) * 2 - 2,
|
||||
this.game.y(railRoad.tile) * 2 - 2,
|
||||
5,
|
||||
6,
|
||||
);
|
||||
} else {
|
||||
this.context.clearRect(
|
||||
this.game.x(railRoad.tile) * 2 - 1,
|
||||
this.game.y(railRoad.tile) * 2 - 1,
|
||||
3,
|
||||
3,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paintRail(railRoad: RailTile) {
|
||||
const x = this.game.x(railRoad.tile);
|
||||
const y = this.game.y(railRoad.tile);
|
||||
const owner = this.game.owner(railRoad.tile);
|
||||
const recipient = owner.isPlayer() ? (owner as PlayerView) : null;
|
||||
if (this.context === undefined) throw new Error("Not initialized");
|
||||
const { tile } = railRoad;
|
||||
const { railType } = railRoad;
|
||||
const x = this.game.x(tile);
|
||||
const y = this.game.y(tile);
|
||||
// If rail tile is over water, paint a bridge underlay first
|
||||
if (this.game.isWater(tile)) {
|
||||
this.paintBridge(this.context, x, y, railType);
|
||||
}
|
||||
const owner = this.game.owner(tile);
|
||||
const recipient = owner.isPlayer() ? owner : null;
|
||||
const color = recipient
|
||||
? this.theme.railroadColor(recipient)
|
||||
: new Colord({ r: 255, g: 255, b: 255, a: 1 });
|
||||
this.context.fillStyle = color.toRgbString();
|
||||
this.paintRailRects(x, y, railRoad.railType);
|
||||
this.paintRailRects(this.context, x, y, railType);
|
||||
}
|
||||
|
||||
private paintRailRects(x: number, y: number, direction: RailType) {
|
||||
private paintRailRects(
|
||||
context: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
direction: RailType,
|
||||
) {
|
||||
const railRects = getRailroadRects(direction);
|
||||
for (const [dx, dy, w, h] of railRects) {
|
||||
this.context.fillRect(x * 2 + dx, y * 2 + dy, w, h);
|
||||
context.fillRect(x * 2 + dx, y * 2 + dy, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
private paintBridge(
|
||||
context: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
direction: RailType,
|
||||
) {
|
||||
context.save();
|
||||
context.fillStyle = "rgb(197,69,72)";
|
||||
const bridgeRects = getBridgeRects(direction);
|
||||
for (const [dx, dy, w, h] of bridgeRects) {
|
||||
context.fillRect(x * 2 + dx, y * 2 + dy, w, h);
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ const railTypeToFunctionMap: Record<RailType, () => number[][]> = {
|
||||
[RailType.VERTICAL]: verticalRailroadRects,
|
||||
};
|
||||
|
||||
const railTypeToBridgeFunctionMap: Record<RailType, () => number[][]> = {
|
||||
[RailType.TOP_RIGHT]: topRightBridgeCornerRects,
|
||||
[RailType.BOTTOM_LEFT]: bottomLeftBridgeCornerRects,
|
||||
[RailType.TOP_LEFT]: topLeftBridgeCornerRects,
|
||||
[RailType.BOTTOM_RIGHT]: bottomRightBridgeCornerRects,
|
||||
[RailType.HORIZONTAL]: horizontalBridge,
|
||||
[RailType.VERTICAL]: verticalBridge,
|
||||
};
|
||||
|
||||
export function getRailroadRects(type: RailType): number[][] {
|
||||
const railRects = railTypeToFunctionMap[type];
|
||||
if (!railRects) {
|
||||
@@ -77,3 +86,76 @@ function bottomLeftRailroadCornerRects(): number[][] {
|
||||
];
|
||||
return rects;
|
||||
}
|
||||
|
||||
export function getBridgeRects(type: RailType): number[][] {
|
||||
const bridgeRects = railTypeToBridgeFunctionMap[type];
|
||||
if (!bridgeRects) {
|
||||
// Should never happen
|
||||
throw new Error(`Unsupported RailType: ${type}`);
|
||||
}
|
||||
return bridgeRects();
|
||||
}
|
||||
|
||||
function horizontalBridge(): number[][] {
|
||||
// x/y/w/h
|
||||
return [
|
||||
[-1, -2, 3, 1],
|
||||
[-1, 2, 3, 1],
|
||||
[-1, 3, 1, 1],
|
||||
[1, 3, 1, 1],
|
||||
];
|
||||
}
|
||||
|
||||
function verticalBridge(): number[][] {
|
||||
// x/y/w/h
|
||||
return [
|
||||
[-2, -2, 1, 3],
|
||||
[2, -2, 1, 3],
|
||||
];
|
||||
}
|
||||
// ⌞
|
||||
function topRightBridgeCornerRects(): number[][] {
|
||||
return [
|
||||
[-2, -2, 1, 2],
|
||||
[-1, 0, 1, 1],
|
||||
[0, 1, 1, 1],
|
||||
[1, 2, 2, 1],
|
||||
[2, -2, 1, 1],
|
||||
];
|
||||
}
|
||||
// ⌝
|
||||
function bottomLeftBridgeCornerRects(): number[][] {
|
||||
// x/y/w/h
|
||||
const rects = [
|
||||
[-2, -2, 2, 1],
|
||||
[0, -1, 1, 1],
|
||||
[1, 0, 1, 1],
|
||||
[2, 1, 1, 2],
|
||||
[-2, 2, 1, 1],
|
||||
];
|
||||
return rects;
|
||||
}
|
||||
// ⌟
|
||||
function topLeftBridgeCornerRects(): number[][] {
|
||||
// x/y/w/h
|
||||
const rects = [
|
||||
[-2, -2, 1, 1],
|
||||
[-2, 2, 2, 1],
|
||||
[0, 1, 1, 1],
|
||||
[1, 0, 1, 1],
|
||||
[2, -2, 1, 2],
|
||||
];
|
||||
return rects;
|
||||
}
|
||||
// ⌜
|
||||
function bottomRightBridgeCornerRects(): number[][] {
|
||||
// x/y/w/h
|
||||
const rects = [
|
||||
[-2, 1, 1, 2],
|
||||
[-1, 0, 1, 1],
|
||||
[0, -1, 1, 1],
|
||||
[1, -2, 2, 1],
|
||||
[2, 2, 1, 1],
|
||||
];
|
||||
return rects;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AStar, PathFindResultType } from "./AStar";
|
||||
import { GraphAdapter, SerialAStar } from "./SerialAStar";
|
||||
|
||||
export class GameMapAdapter implements GraphAdapter<TileRef> {
|
||||
private readonly waterPenalty = 3;
|
||||
constructor(
|
||||
private gameMap: GameMap,
|
||||
private waterPath: boolean,
|
||||
@@ -14,7 +15,12 @@ export class GameMapAdapter implements GraphAdapter<TileRef> {
|
||||
}
|
||||
|
||||
cost(node: TileRef): number {
|
||||
return this.gameMap.cost(node);
|
||||
let base = this.gameMap.cost(node);
|
||||
// Avoid crossing water when possible
|
||||
if (!this.waterPath && this.gameMap.isWater(node)) {
|
||||
base += this.waterPenalty;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
position(node: TileRef): { x: number; y: number } {
|
||||
@@ -22,8 +28,14 @@ export class GameMapAdapter implements GraphAdapter<TileRef> {
|
||||
}
|
||||
|
||||
isTraversable(from: TileRef, to: TileRef): boolean {
|
||||
const isWater = this.gameMap.isWater(to);
|
||||
return this.waterPath ? isWater : !isWater;
|
||||
const toWater = this.gameMap.isWater(to);
|
||||
if (this.waterPath) {
|
||||
return toWater;
|
||||
}
|
||||
// Allow water access from/to shore
|
||||
const fromShore = this.gameMap.isShoreline(from);
|
||||
const toShore = this.gameMap.isShoreline(to);
|
||||
return !toWater || fromShore || toShore;
|
||||
}
|
||||
}
|
||||
export class MiniAStar implements AStar<TileRef> {
|
||||
|
||||
Reference in New Issue
Block a user