Skip to content

Commit 8f53785

Browse files
BUG FIX: Gold double deduction + Rmoval of UnitType.Construction (#2378)
## Description: - Removed the temporary UnitType.Construction and embedded construction state into real units via isUnderConstruction(). - Centralized non-structure spawning to perform a single validation right before unit creation/launch. - Updated UI layers to render construction state without relying on the removed enum. - Adjusted and created tests to match the new flow and to cover the no-refundscenarios. # Tests updated - tests/economy/ConstructionGold.test.ts: covers structure cost deduction and income, tolerant of passive income; ensures no refunds during construction. - tests/nukes/HydrogenAndMirv.test.ts: accounts for single-check launch flow; MIRV test targets a player-owned tile; ensures launch after payment. - tests/client/graphics/UILayer.test.ts: mocks now provide isUnderConstruction and real type strings; ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: CrackeRR1 --------- Co-authored-by: Evan <evanpelle@gmail.com>
1 parent c341aaf commit 8f53785

28 files changed

+526
-273
lines changed

src/client/graphics/layers/RadialMenuElements.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ export const deleteUnitElement: MenuElement = {
452452
.units()
453453
.filter(
454454
(unit) =>
455-
unit.constructionType() === undefined &&
455+
!unit.isUnderConstruction() &&
456456
unit.markedForDeletion() === false &&
457457
params.game.manhattanDist(unit.tile(), params.tile) <=
458458
DELETE_SELECTION_RADIUS,

src/client/graphics/layers/StructureDrawingUtils.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,12 @@ export class SpriteFactory {
143143
const screenPos = this.transformHandler.worldToScreenCoordinates(worldPos);
144144

145145
const isMarkedForDeletion = unit.markedForDeletion() !== false;
146-
const isConstruction = unit.type() === UnitType.Construction;
147-
const constructionType = unit.constructionType();
148-
const structureType = isConstruction ? constructionType! : unit.type();
146+
const isConstruction = unit.isUnderConstruction();
147+
const structureType = unit.type();
149148
const { type, stage } = options;
150149
const { scale } = this.transformHandler;
151150

152151
if (type === "icon" || type === "dot") {
153-
if (isConstruction && constructionType === undefined) {
154-
console.warn(
155-
`Unit ${unit.id()} is a construction but has no construction type.`,
156-
);
157-
return parentContainer;
158-
}
159152
const texture = this.createTexture(
160153
structureType,
161154
unit.owner(),

src/client/graphics/layers/StructureIconsLayer.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -469,10 +469,7 @@ export class StructureIconsLayer implements Layer {
469469
this.checkForOwnershipChange(render, unitView);
470470
this.checkForLevelChange(render, unitView);
471471
}
472-
} else if (
473-
this.structures.has(unitView.type()) ||
474-
unitView.type() === UnitType.Construction
475-
) {
472+
} else if (this.structures.has(unitView.type())) {
476473
this.addNewStructure(unitView);
477474
}
478475
}
@@ -485,10 +482,7 @@ export class StructureIconsLayer implements Layer {
485482
}
486483

487484
private modifyVisibility(render: StructureRenderInfo) {
488-
const structureType =
489-
render.unit.type() === UnitType.Construction
490-
? render.unit.constructionType()!
491-
: render.unit.type();
485+
const structureType = render.unit.type();
492486
const structureInfos = this.structures.get(structureType);
493487

494488
let focusStructure = false;
@@ -529,10 +523,7 @@ export class StructureIconsLayer implements Layer {
529523
render: StructureRenderInfo,
530524
unit: UnitView,
531525
) {
532-
if (
533-
render.underConstruction &&
534-
render.unit.type() !== UnitType.Construction
535-
) {
526+
if (render.underConstruction && !unit.isUnderConstruction()) {
536527
render.underConstruction = false;
537528
render.iconContainer?.destroy();
538529
render.dotContainer?.destroy();
@@ -580,10 +571,7 @@ export class StructureIconsLayer implements Layer {
580571
: screenPos.y,
581572
);
582573

583-
const type =
584-
render.unit.type() === UnitType.Construction
585-
? render.unit.constructionType()
586-
: render.unit.type();
574+
const type = render.unit.type();
587575
const margin =
588576
type !== undefined && STRUCTURE_SHAPES[type] !== undefined
589577
? ICON_SIZE[STRUCTURE_SHAPES[type]]
@@ -637,7 +625,7 @@ export class StructureIconsLayer implements Layer {
637625
this.createLevelSprite(unitView),
638626
this.createDotSprite(unitView),
639627
unitView.level(),
640-
unitView.type() === UnitType.Construction,
628+
unitView.isUnderConstruction(),
641629
);
642630
this.renders.push(render);
643631
this.computeNewLocation(render);

src/client/graphics/layers/StructureLayer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class StructureLayer implements Layer {
190190
)) {
191191
this.paintCell(
192192
new Cell(this.game.x(tile), this.game.y(tile)),
193-
unit.type() === UnitType.Construction
193+
unit.isUnderConstruction()
194194
? underConstructionColor
195195
: unit.owner().territoryColor(),
196196
130,
@@ -199,7 +199,7 @@ export class StructureLayer implements Layer {
199199
}
200200

201201
private handleUnitRendering(unit: UnitView) {
202-
const unitType = unit.constructionType() ?? unit.type();
202+
const unitType = unit.type();
203203
const iconType = unitType;
204204
if (!this.isUnitTypeSupported(unitType)) return;
205205

@@ -208,7 +208,7 @@ export class StructureLayer implements Layer {
208208
let borderColor = unit.owner().borderColor();
209209

210210
// Handle cooldown states and special icons
211-
if (unit.type() === UnitType.Construction) {
211+
if (unit.isUnderConstruction()) {
212212
icon = this.unitIcons.get(iconType);
213213
borderColor = underConstructionColor;
214214
} else {
@@ -247,7 +247,7 @@ export class StructureLayer implements Layer {
247247
unit: UnitView,
248248
) {
249249
let color = unit.owner().borderColor();
250-
if (unit.type() === UnitType.Construction) {
250+
if (unit.isUnderConstruction()) {
251251
// eslint-disable-next-line @typescript-eslint/no-unused-vars
252252
color = underConstructionColor;
253253
}

src/client/graphics/layers/TerritoryLayer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ export class TerritoryLayer implements Layer {
9090
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
9191
unitUpdates.forEach((update) => {
9292
if (update.unitType === UnitType.DefensePost) {
93+
// Only update borders if the defense post is not under construction
94+
if (update.underConstruction) {
95+
return; // Skip barrier creation while under construction
96+
}
97+
9398
const tile = update.pos;
9499
this.game
95100
.bfs(tile, euclDistFN(tile, this.game.config().defensePostRange()))

src/client/graphics/layers/UILayer.ts

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,12 @@ export class UILayer implements Layer {
103103
}
104104

105105
onUnitEvent(unit: UnitView) {
106+
const underConst = unit.isUnderConstruction();
107+
if (underConst) {
108+
this.createLoadingBar(unit);
109+
return;
110+
}
106111
switch (unit.type()) {
107-
case UnitType.Construction: {
108-
const constructionType = unit.constructionType();
109-
if (constructionType === undefined) {
110-
// Skip units without construction type
111-
return;
112-
}
113-
this.createLoadingBar(unit);
114-
break;
115-
}
116112
case UnitType.Warship: {
117113
this.drawHealthBar(unit);
118114
break;
@@ -318,22 +314,20 @@ export class UILayer implements Layer {
318314
if (!unit.isActive()) {
319315
return 1;
320316
}
321-
switch (unit.type()) {
322-
case UnitType.Construction: {
323-
const constructionType = unit.constructionType();
324-
if (constructionType === undefined) {
325-
return 1;
326-
}
327-
const constDuration =
328-
this.game.unitInfo(constructionType).constructionDuration;
329-
if (constDuration === undefined) {
330-
throw new Error("unit does not have constructionTime");
331-
}
332-
return (
333-
(this.game.ticks() - unit.createdAt()) /
334-
(constDuration === 0 ? 1 : constDuration)
335-
);
317+
const underConst = unit.isUnderConstruction();
318+
if (underConst) {
319+
const constDuration = this.game.unitInfo(
320+
unit.type(),
321+
).constructionDuration;
322+
if (constDuration === undefined) {
323+
throw new Error("unit does not have constructionTime");
336324
}
325+
return (
326+
(this.game.ticks() - unit.createdAt()) /
327+
(constDuration === 0 ? 1 : constDuration)
328+
);
329+
}
330+
switch (unit.type()) {
337331
case UnitType.MissileSilo:
338332
case UnitType.SAMLauncher:
339333
return !unit.markedForDeletion()

src/core/configuration/DefaultConfig.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -545,11 +545,6 @@ export class DefaultConfig implements Config {
545545
experimental: true,
546546
upgradable: true,
547547
};
548-
case UnitType.Construction:
549-
return {
550-
cost: () => 0n,
551-
territoryBound: true,
552-
};
553548
case UnitType.Train:
554549
return {
555550
cost: () => 0n,
Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,26 @@
1-
import { Execution, Game, Player, Unit, UnitType } from "../game/Game";
2-
import { TileRef } from "../game/GameMap";
1+
import { Execution, Game, Unit, UnitType } from "../game/Game";
32
import { TrainStationExecution } from "./TrainStationExecution";
43

54
export class CityExecution implements Execution {
65
private mg: Game;
7-
private city: Unit | null = null;
86
private active: boolean = true;
7+
private stationCreated = false;
98

10-
constructor(
11-
private player: Player,
12-
private tile: TileRef,
13-
) {}
9+
constructor(private city: Unit) {}
1410

1511
init(mg: Game, ticks: number): void {
1612
this.mg = mg;
1713
}
1814

1915
tick(ticks: number): void {
20-
if (this.city === null) {
21-
const spawnTile = this.player.canBuild(UnitType.City, this.tile);
22-
if (spawnTile === false) {
23-
console.warn("cannot build city");
24-
this.active = false;
25-
return;
26-
}
27-
this.city = this.player.buildUnit(UnitType.City, spawnTile, {});
16+
if (!this.stationCreated) {
2817
this.createStation();
18+
this.stationCreated = true;
2919
}
3020
if (!this.city.isActive()) {
3121
this.active = false;
3222
return;
3323
}
34-
35-
if (this.player !== this.city.owner()) {
36-
this.player = this.city.owner();
37-
}
3824
}
3925

4026
isActive(): boolean {
@@ -45,16 +31,14 @@ export class CityExecution implements Execution {
4531
return false;
4632
}
4733

48-
createStation(): void {
49-
if (this.city !== null) {
50-
const nearbyFactory = this.mg.hasUnitNearby(
51-
this.city.tile()!,
52-
this.mg.config().trainStationMaxRange(),
53-
UnitType.Factory,
54-
);
55-
if (nearbyFactory) {
56-
this.mg.addExecution(new TrainStationExecution(this.city));
57-
}
34+
private createStation(): void {
35+
const nearbyFactory = this.mg.hasUnitNearby(
36+
this.city.tile()!,
37+
this.mg.config().trainStationMaxRange(),
38+
UnitType.Factory,
39+
);
40+
if (nearbyFactory) {
41+
this.mg.addExecution(new TrainStationExecution(this.city));
5842
}
5943
}
6044
}

0 commit comments

Comments
 (0)