Fixed factory ghost radius (#4337)

> **Before opening a PR:** discuss new features on
[Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small
improvements as
[issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose).
You must be assigned to an `approved` issue — unsolicited PRs will be
auto-closed.

**Add approved & assigned issue number here:**

Resolves #4323 

## Description:

Made stations use euclidean distance for radius for checking if other
stations are close enough, removed redundant if check and unneeded
config

<img width="1920" height="1080" alt="Screenshot from 2026-06-18
14-19-48"
src="https://github.com/user-attachments/assets/a84f29f8-0cc1-46ea-9b96-3d70d6b0b20a"
/>


## 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

## Please put your Discord username so you can be contacted if a bug or
regression is found:

tktk1234567
This commit is contained in:
TKTK123456
2026-06-19 22:27:54 -04:00
committed by GitHub
parent 805f0968b1
commit 08af8470fa
3 changed files with 36 additions and 47 deletions
-3
View File
@@ -243,9 +243,6 @@ export class Config {
trainStationMaxRange(): number { trainStationMaxRange(): number {
return 110; return 110;
} }
railroadMaxSize(): number {
return this.trainStationMaxRange();
}
tradeShipGold(dist: number, player: Player | PlayerView): Gold { tradeShipGold(dist: number, player: Player | PlayerView): Gold {
// Sigmoid: concave start, sharp S-curve middle, linear end - heavily punishes trades under range debuff. // Sigmoid: concave start, sharp S-curve middle, linear end - heavily punishes trades under range debuff.
+2 -7
View File
@@ -251,7 +251,6 @@ export class RailNetworkImpl implements RailNetwork {
const maxRange = this.game.config().trainStationMaxRange(); const maxRange = this.game.config().trainStationMaxRange();
const minRangeSquared = this.game.config().trainStationMinRange() ** 2; const minRangeSquared = this.game.config().trainStationMinRange() ** 2;
const maxPathSize = this.game.config().railroadMaxSize();
// A City or Port only joins the rail network when a Factory is already in // A City or Port only joins the rail network when a Factory is already in
// range (see CityExecution/PortExecution). A Factory always becomes a // range (see CityExecution/PortExecution). A Factory always becomes a
@@ -301,15 +300,13 @@ export class RailNetworkImpl implements RailNetwork {
} else { } else {
continue; continue;
} }
const path = this.pathService.findTilePath(tile, targetTile); const path = this.pathService.findTilePath(tile, targetTile);
if (path.length > 0 && path.length < maxPathSize) { if (path.length === 0) continue;
paths.push(path); paths.push(path);
if (neighborStation) { if (neighborStation) {
connectedStations.push(neighborStation); connectedStations.push(neighborStation);
} }
} }
}
return paths; return paths;
} }
@@ -378,7 +375,7 @@ export class RailNetworkImpl implements RailNetwork {
private connect(from: TrainStation, to: TrainStation) { private connect(from: TrainStation, to: TrainStation) {
const path = this.pathService.findTilePath(from.tile(), to.tile()); const path = this.pathService.findTilePath(from.tile(), to.tile());
if (path.length > 0 && path.length < this.game.config().railroadMaxSize()) { if (path.length === 0) return false;
const railroad = new Railroad(from, to, path, this.nextId++); const railroad = new Railroad(from, to, path, this.nextId++);
this.game.addUpdate({ this.game.addUpdate({
type: GameUpdateType.RailroadConstructionEvent, type: GameUpdateType.RailroadConstructionEvent,
@@ -390,8 +387,6 @@ export class RailNetworkImpl implements RailNetwork {
this.railGrid.register(railroad); this.railGrid.register(railroad);
return true; return true;
} }
return false;
}
private distanceFrom( private distanceFrom(
start: TrainStation, start: TrainStation,
+21 -24
View File
@@ -71,7 +71,6 @@ describe("RailNetworkImpl", () => {
config: () => ({ config: () => ({
trainStationMaxRange: () => 80, trainStationMaxRange: () => 80,
trainStationMinRange: () => 10, trainStationMinRange: () => 10,
railroadMaxSize: () => 100,
}), }),
x: vi.fn(() => 0), x: vi.fn(() => 0),
y: vi.fn(() => 0), y: vi.fn(() => 0),
@@ -250,7 +249,11 @@ describe("RailNetworkImpl", () => {
pathService.findTilePath.mockReturnValue(mockPath); pathService.findTilePath.mockReturnValue(mockPath);
game.nearbyUnits.mockReturnValue([ game.nearbyUnits.mockReturnValue([
{ unit: neighborStation.unit, distSquared: 400 }, {
unit: neighborStation.unit,
distSquared: 400,
euclideanDist: Math.sqrt(400),
},
]); ]);
const result = network.computeGhostRailPaths(UnitType.City, tile); const result = network.computeGhostRailPaths(UnitType.City, tile);
@@ -269,7 +272,11 @@ describe("RailNetworkImpl", () => {
// distSquared = 50 <= minRange^2 (10^2 = 100) // distSquared = 50 <= minRange^2 (10^2 = 100)
game.nearbyUnits.mockReturnValue([ game.nearbyUnits.mockReturnValue([
{ unit: neighborStation.unit, distSquared: 50 }, {
unit: neighborStation.unit,
distSquared: 50,
euclideanDist: Math.sqrt(50),
},
]); ]);
const result = network.computeGhostRailPaths(UnitType.City, tile); const result = network.computeGhostRailPaths(UnitType.City, tile);
@@ -283,26 +290,8 @@ describe("RailNetworkImpl", () => {
stationManager.findStation.mockReturnValue(null); stationManager.findStation.mockReturnValue(null);
game.nearbyUnits.mockReturnValue([{ unit: { id: 1 }, distSquared: 400 }]);
const result = network.computeGhostRailPaths(UnitType.City, tile);
expect(result).toEqual([]);
});
test("skips paths that exceed max railroad size", () => {
const tile = 42 as any;
const railGridMock = { query: vi.fn(() => new Set()) };
(network as any).railGrid = railGridMock;
const neighborStation = createMockStation(1);
neighborStation.tile.mockReturnValue(100);
stationManager.findStation.mockReturnValue(neighborStation);
// Path length >= railroadMaxSize (100)
pathService.findTilePath.mockReturnValue(new Array(100));
game.nearbyUnits.mockReturnValue([ game.nearbyUnits.mockReturnValue([
{ unit: neighborStation.unit, distSquared: 400 }, { unit: { id: 1 }, distSquared: 400, euclideanDist: Math.sqrt(400) },
]); ]);
const result = network.computeGhostRailPaths(UnitType.City, tile); const result = network.computeGhostRailPaths(UnitType.City, tile);
@@ -314,11 +303,19 @@ describe("RailNetworkImpl", () => {
const railGridMock = { query: vi.fn(() => new Set()) }; const railGridMock = { query: vi.fn(() => new Set()) };
(network as any).railGrid = railGridMock; (network as any).railGrid = railGridMock;
const neighbors: Array<{ unit: any; distSquared: number }> = []; const neighbors: Array<{
unit: any;
distSquared: number;
euclideanDist: number;
}> = [];
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
const station = createMockStation(i); const station = createMockStation(i);
station.tile.mockReturnValue(100 + i); station.tile.mockReturnValue(100 + i);
neighbors.push({ unit: station.unit, distSquared: 400 + i }); neighbors.push({
unit: station.unit,
distSquared: 400 + i,
euclideanDist: Math.sqrt(400 + i),
});
} }
stationManager.findStation.mockImplementation((unit: any) => { stationManager.findStation.mockImplementation((unit: any) => {