Skip to content

Commit 38641da

Browse files
committed
InGameScene: Provide tile yields on hover & optimize label conformSize()
1 parent 9af91ae commit 38641da

File tree

11 files changed

+302
-210
lines changed

11 files changed

+302
-210
lines changed

client/src/Game.ts

Lines changed: 159 additions & 176 deletions
Large diffs are not rendered by default.

client/src/map/GameMap.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export class GameMap {
231231
callback: (data) => {
232232
console.log("Received tile yields from server.");
233233
console.log(data);
234-
//TODO: Store yields in a map, so we can access them later.
234+
Tile.setTileYields(data["yields"])
235235
}
236236
});
237237
}

client/src/map/Tile.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class Tile extends Actor {
2525
public static HEIGHT = 32;
2626

2727
private static loadedTileImages = new Map<string, HTMLImageElement>();
28+
private static allTileStats: JSON;
2829

2930
private tileTypes: string[];
3031
private adjacentTiles: Tile[];
@@ -89,6 +90,14 @@ export class Tile extends Actor {
8990
return tile2.getMovementCost();
9091
}
9192

93+
public static setTileYields(data: JSON) {
94+
Tile.allTileStats = data;
95+
}
96+
97+
public static getTileYields() {
98+
return Tile.allTileStats;
99+
}
100+
92101
public async loadImage() {
93102
const key = JSON.stringify(this.tileTypes);
94103

@@ -115,6 +124,27 @@ export class Tile extends Actor {
115124
});*/
116125
}
117126

127+
public getTileYield() {
128+
// Use the static getter to ensure we always have the latest tile stats
129+
const allTileStats = Tile.getTileYields();
130+
if (!allTileStats) return undefined;
131+
132+
const tileYield: { [key: string]: number } = {};
133+
for (const tileType of this.tileTypes) {
134+
// Accept both upper and lower case keys for tileTypes
135+
const yieldData = allTileStats[tileType] || allTileStats[tileType.toUpperCase()] || allTileStats[tileType.toLowerCase()];
136+
if (yieldData && yieldData.stats) {
137+
for (const statObj of yieldData.stats) {
138+
for (const [key, value] of Object.entries(statObj)) {
139+
tileYield[key] = (tileYield[key] || 0) + (typeof value === "number" ? value : 0);
140+
}
141+
}
142+
}
143+
}
144+
// Return undefined if no yield found, otherwise the yield object
145+
return Object.keys(tileYield).length > 0 ? tileYield : undefined;
146+
}
147+
118148
public setCity(city: City) {
119149
this.city = city;
120150
this.tileTypes.push("city");

client/src/network/Client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface OnNetworkEventOptions {
2020
export class NetworkEvents {
2121
private static storedEvents: Map<string, CallbackData[]>;
2222

23-
private constructor() {}
23+
private constructor() { }
2424

2525
public static call(eventName: string, data: JSON) {
2626
if (this.storedEvents.has(eventName)) {
@@ -111,7 +111,7 @@ export class NetworkEvents {
111111
export class WebsocketClient {
112112
private static websocket: WebSocket;
113113

114-
private constructor() {}
114+
private constructor() { }
115115
// TODO: Add network events here with string & function that has arguments.
116116
// e.g. setScreen & screenType arg.
117117

@@ -124,6 +124,7 @@ export class WebsocketClient {
124124

125125
this.websocket.addEventListener("open", (event) => {
126126
console.log("Connected to server");
127+
NetworkEvents.call("connected", JSON.parse("{}"));
127128
});
128129

129130
this.websocket.addEventListener("message", (event) => {

client/src/scene/type/InGameScene.ts

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GameImage } from "../../Assets";
1+
import { GameImage, SpriteRegion } from "../../Assets";
22
import { Game } from "../../Game";
33
import { City } from "../../city/City";
44
import { GameMap } from "../../map/GameMap";
@@ -20,6 +20,7 @@ export class InGameScene extends Scene {
2020
private players: AbstractPlayer[];
2121
private clientPlayer: ClientPlayer;
2222
private tileInformationLabel: Label;
23+
private tileYieldActors: Actor[] = [];
2324
private statusBar: StatusBar;
2425
private cityDisplayInfo: CityDisplayInfo;
2526
private nextTurnButton: Button;
@@ -72,6 +73,8 @@ export class InGameScene extends Scene {
7273
text: "N/A",
7374
font: "16px serif",
7475
fontColor: "white",
76+
shadowColor: "black",
77+
lineWidth: 4,
7578
x: 0,
7679
y: 0,
7780
z: 5
@@ -131,9 +134,14 @@ export class InGameScene extends Scene {
131134
});
132135

133136
this.on("tileHovered", (options) => {
134-
if (!options.tile) {
135-
this.tileInformationLabel.setText("");
136-
} else {
137+
// Remove previous yield icons
138+
for (const actor of this.tileYieldActors) {
139+
this.removeActor(actor);
140+
}
141+
142+
this.tileYieldActors = [];
143+
144+
if (options.tile && !this.cityDisplayInfo) {
137145
let tileTypes: string = options.tile.getTileTypes().toString();
138146
tileTypes = tileTypes.replaceAll("_", " ");
139147
tileTypes = tileTypes.replaceAll(",", ", ");
@@ -148,15 +156,72 @@ export class InGameScene extends Scene {
148156

149157
tileTypes = strArray.join("");
150158

159+
// Get tile yields
160+
const yields = options.tile.getTileYield();
161+
162+
// Map stat keys to SpriteRegion
163+
const statSpriteRegions: Record<string, SpriteRegion> = {
164+
food: SpriteRegion.FOOD_ICON,
165+
production: SpriteRegion.PRODUCTION_ICON,
166+
gold: SpriteRegion.GOLD_ICON,
167+
faith: SpriteRegion.FAITH_ICON,
168+
morale: SpriteRegion.MORALE_ICON,
169+
science: SpriteRegion.SCIENCE_ICON,
170+
culture: SpriteRegion.CULTURE_ICON,
171+
};
172+
173+
// Set the label text (without yields)
151174
this.tileInformationLabel.setText(
152-
"[" +
153-
options.tile.getGridX() +
154-
"," +
155-
options.tile.getGridY() +
156-
"] " +
175+
`[${options.tile.getGridX()},${options.tile.getGridY()}] ` +
157176
tileTypes +
158177
(options.tile.hasRiver() ? ", River" : "")
159178
);
179+
180+
181+
182+
this.tileInformationLabel.conformSize().then(() => {
183+
// Positioning for icons (right after the label)
184+
let iconX = this.tileInformationLabel.getX() + this.tileInformationLabel.getWidth();
185+
const iconY = this.tileInformationLabel.getY() - 10;
186+
187+
if (yields) {
188+
for (const [key, value] of Object.entries(yields)) {
189+
if (typeof value === "number" && value > 0 && statSpriteRegions[key]) {
190+
// Create icon actor
191+
const iconActor = new Actor({
192+
image: Game.getInstance().getImage(GameImage.SPRITESHEET),
193+
spriteRegion: statSpriteRegions[key],
194+
x: iconX,
195+
y: iconY,
196+
width: 32,
197+
height: 32,
198+
z: 10,
199+
cameraApplies: false
200+
});
201+
this.addActor(iconActor);
202+
this.tileYieldActors.push(iconActor);
203+
204+
// Create value label
205+
const valueLabel = new Label({
206+
text: value.toString(),
207+
font: "16px serif",
208+
fontColor: "white",
209+
shadowColor: "black",
210+
lineWidth: 4,
211+
x: iconX + iconActor.getWidth() - 6,
212+
y: this.tileInformationLabel.getY(),
213+
z: 10
214+
});
215+
this.addActor(valueLabel);
216+
this.tileYieldActors.push(valueLabel);
217+
218+
// Move X for next icon
219+
iconX += 42;
220+
}
221+
}
222+
}
223+
});
224+
160225
}
161226
});
162227
//DEBUG top layer chunks -
@@ -237,6 +302,10 @@ export class InGameScene extends Scene {
237302

238303
this.removeActor(this.nextTurnButton);
239304
this.removeActor(this.tileInformationLabel);
305+
this.tileInformationLabel.setText("");
306+
this.tileYieldActors.forEach((actor) => {
307+
this.removeActor(actor);
308+
});
240309

241310
this.addActor(this.closeCityDisplayButton);
242311
}

client/src/ui/Button.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export class Button extends ActorGroup {
5656
this.textWidth = -1;
5757
this.textHeight = -1;
5858
this.callbackFunction = options.onClicked;
59-
this.mouseEnterCallbackFunction = options.onMouseEnter || function () {};
60-
this.mouseExitCallbackFunction = options.onMouseExit || function () {};
59+
this.mouseEnterCallbackFunction = options.onMouseEnter || function () { };
60+
this.mouseExitCallbackFunction = options.onMouseExit || function () { };
6161
this.font = options.font ?? "24px serif";
6262
this.fontColor = options.fontColor ?? "black";
6363
this.buttonImage = options.buttonImage || GameImage.BUTTON;
@@ -135,12 +135,9 @@ export class Button extends ActorGroup {
135135
super.draw(canvasContext); //FIXME: Don't draw until we know textWidth & height.
136136

137137
if (this.textWidth == -1 && this.textHeight == -1) {
138-
Game.getInstance()
139-
.measureText(this.text, this.font)
140-
.then(([textWidth, textHeight]) => {
141-
this.textWidth = textWidth;
142-
this.textHeight = textHeight;
143-
});
138+
const { width: textWidth, height: textHeight } = Game.getInstance().measureText(this.text, this.font);
139+
this.textWidth = textWidth;
140+
this.textHeight = textHeight;
144141
return; // Don't render text before we know the height & width of the text
145142
}
146143

client/src/ui/Label.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export class Label extends Actor {
123123
this.height = wrappedHeight;
124124
this.unwrappedWordHeight = unwrappedWordHeight;
125125
} else {
126-
const [textWidth, textHeight] = await Game.getInstance().measureText(this.text, this.font);
126+
const { width: textWidth, height: textHeight } = Game.getInstance().measureText(this.text, this.font);
127127
this.width = textWidth;
128128
this.height = textHeight;
129129
}

client/src/ui/Textbox.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,8 @@ export class TextBox extends Actor {
105105

106106
public draw(canvasContext: CanvasRenderingContext2D) {
107107
if (this.textHeight == -1) {
108-
Game.getInstance()
109-
.measureText("M", this.font)
110-
.then(([width, height]) => {
111-
this.textHeight = height;
112-
});
108+
const { height } = Game.getInstance().measureText("M", this.font);
109+
this.textHeight = height;
113110
}
114111

115112
Game.getInstance().drawRect({
@@ -167,11 +164,8 @@ export class TextBox extends Actor {
167164

168165
public setText(text: string) {
169166
this.text = text;
170-
Game.getInstance()
171-
.measureText(this.text, this.font)
172-
.then(([width, height]) => {
173-
//this.textHeight = height;
174-
this.blinkerX = this.x + 2 + width;
175-
});
167+
const { width } = Game.getInstance().measureText(this.text, this.font);
168+
//this.textHeight = height;
169+
this.blinkerX = this.x + 2 + width;
176170
}
177171
}

server/src/map/GameMap.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,16 @@ export class GameMap {
666666
];
667667
return values;
668668
}
669+
670+
671+
public sendTileYieldsToPlayer(player: Player) {
672+
// Send the full tile stats JSON from tiles.yml
673+
player.sendNetworkEvent({
674+
event: "tileYields",
675+
yields: Tile.getAllTileStats()
676+
});
677+
}
678+
669679
public sendMapChunksToPlayer(player: Player) {
670680
// Send the map-size to player
671681
player.sendNetworkEvent({

server/src/map/Tile.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ export class Tile {
3838

3939
this.addTileType(tileType);
4040
}
41-
42-
public static getAllTileStats() {
41+
public static getAllTileStats(): Record<string, any> {
4342
if (!Tile.allTileStats) {
4443
// Load available civilizations from config file
4544
const tileYAMLData = YAML.parse(fs.readFileSync("./config/tiles.yml", "utf-8"));

0 commit comments

Comments
 (0)