Show structure levels (#1305)

## Description:

Show structure levels on the structureIconsLayer
Add new bitmap font (modified from this font:
https://frostyfreeze.itch.io/pixel-bitmap-fonts-png-xml CC0 license)

![image](https://github.com/user-attachments/assets/8c077ec9-00bb-4f36-a9e7-6d81aec3a90f)

## 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
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

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

Vivacious Box
This commit is contained in:
Vivacious Box
2025-07-02 02:09:18 +02:00
committed by GitHub
parent 47ccbc0473
commit c5c04a8d83
5 changed files with 376 additions and 53 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+276
View File
@@ -0,0 +1,276 @@
<?xml version="1.0"?>
<font>
<info face="round_6x6_modified" size="16" bold="0" italic="0"/>
<common lineHeight="15" base="16" scaleW="208" scaleH="114" pages="1" packed="0"/>
<pages>
<page id="0" file="round_6x6_modified.png"/>
</pages>
<chars count="91">
<char id="65" x="0" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- A -->
<char id="66" x="16" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- B -->
<char id="67" x="32" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- C -->
<char id="68" x="48" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- D -->
<char id="69" x="64" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- E -->
<char id="70" x="80" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- F -->
<char id="71" x="96" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- G -->
<char id="72" x="112" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- H -->
<char id="73" x="128" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- I -->
<char id="74" x="144" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- J -->
<char id="75" x="160" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- K -->
<char id="76" x="176" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- L -->
<char id="77" x="192" y="0" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- M -->
<char id="78" x="0" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- N -->
<char id="79" x="16" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- O -->
<char id="80" x="32" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- P -->
<char id="81" x="48" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- Q -->
<char id="82" x="64" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- R -->
<char id="83" x="80" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- S -->
<char id="84" x="96" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- T -->
<char id="85" x="112" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- U -->
<char id="86" x="128" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- V -->
<char id="87" x="144" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- W -->
<char id="88" x="160" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- X -->
<char id="89" x="176" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- Y -->
<char id="90" x="192" y="16" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- Z -->
<char id="97" x="0" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- a -->
<char id="98" x="16" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- b -->
<char id="99" x="32" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- c -->
<char id="100" x="48" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- d -->
<char id="101" x="64" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- e -->
<char id="102" x="80" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- f -->
<char id="103" x="96" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- g -->
<char id="104" x="112" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- h -->
<char id="105" x="128" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- i -->
<char id="106" x="144" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- j -->
<char id="107" x="160" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- k -->
<char id="108" x="176" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- l -->
<char id="109" x="192" y="32" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- m -->
<char id="110" x="0" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- n -->
<char id="111" x="16" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- o -->
<char id="112" x="32" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- p -->
<char id="113" x="48" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- q -->
<char id="114" x="64" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- r -->
<char id="115" x="80" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- s -->
<char id="116" x="96" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- t -->
<char id="117" x="112" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- u -->
<char id="118" x="128" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- v -->
<char id="119" x="144" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- w -->
<char id="120" x="160" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- x -->
<char id="121" x="176" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- y -->
<char id="122" x="192" y="48" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- z -->
<char id="48" x="0" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 0 -->
<char id="49" x="16" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 1 -->
<char id="50" x="32" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 2 -->
<char id="51" x="48" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 3 -->
<char id="52" x="64" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 4 -->
<char id="53" x="80" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 5 -->
<char id="54" x="96" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 6 -->
<char id="55" x="112" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 7 -->
<char id="56" x="128" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 8 -->
<char id="57" x="144" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- 9 -->
<char id="43" x="160" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- + -->
<char id="45" x="176" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- - -->
<char id="61" x="192" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- = -->
<char id="40" x="0" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ( -->
<char id="41" x="16" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ) -->
<char id="91" x="32" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- [ -->
<char id="93" x="48" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ] -->
<char id="123" x="64" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- { -->
<char id="125" x="80" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- } -->
<char id="60" x="96" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- < -->
<char id="62" x="112" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- > -->
<char id="47" x="128" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- / -->
<char id="42" x="144" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- * -->
<char id="58" x="160" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- : -->
<char id="35" x="176" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- # -->
<char id="37" x="192" y="64" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- % -->
<char id="33" x="0" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ! -->
<char id="63" x="16" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ? -->
<char id="46" x="32" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- . -->
<char id="44" x="48" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- , -->
<char id="39" x="64" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- ' -->
<char id="34" x="80" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- " -->
<char id="64" x="96" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- @ -->
<char id="38" x="112" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- & -->
<char id="36" x="128" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- $ -->
<char id="32" x="144" y="96" width="16" height="16" page="0" xadvance="14" xoffset="0" yoffset="0"/>
<!-- -->
</chars>
</font>
@@ -1,4 +1,5 @@
import * as PIXI from "pixi.js";
import bitmapFont from "../../../../resources/fonts/round_6x6_modified.xml";
import anchorIcon from "../../../../resources/images/AnchorIcon.png";
import cityIcon from "../../../../resources/images/CityIcon.png";
import factoryIcon from "../../../../resources/images/FactoryUnit.png";
@@ -17,7 +18,8 @@ class StructureRenderInfo {
constructor(
public unit: UnitView,
public owner: PlayerID,
public pixiSprite: PIXI.Sprite,
public pixiContainer: PIXI.Container,
public level: number = 0,
) {}
}
const ZOOM_THRESHOLD = 2.8; // below this zoom level, structures are not rendered
@@ -54,6 +56,11 @@ export class StructureIconsLayer implements Layer {
}
async setupRenderer() {
try {
await PIXI.Assets.load(bitmapFont);
} catch (error) {
console.error("Failed to load bitmap font:", error);
}
this.renderer = new PIXI.WebGLRenderer();
this.pixicanvas = document.createElement("canvas");
this.pixicanvas.width = window.innerWidth;
@@ -119,41 +126,57 @@ export class StructureIconsLayer implements Layer {
if (unitView === undefined) return;
if (unitView.isActive()) {
if (this.seenUnits.has(unitView)) {
// check if owner has changed
const render = this.renders.find(
(r) => r.unit.id() === unitView.id(),
);
if (render) {
this.ownerChangeCheck(render, unitView);
}
} else if (this.structures.has(unitView.type())) {
// new unit, create render info
this.seenUnits.add(unitView);
const render = new StructureRenderInfo(
unitView,
unitView.owner().id(),
this.createPixiSprite(unitView),
);
this.renders.push(render);
this.computeNewLocation(render);
this.shouldRedraw = true;
}
}
if (!unitView.isActive() && this.seenUnits.has(unitView)) {
const render = this.renders.find(
(r) => r.unit.id() === unitView.id(),
);
if (render) {
this.deleteStructure(render);
}
this.shouldRedraw = true;
return;
this.handleActiveUnit(unitView);
} else if (this.seenUnits.has(unitView)) {
this.handleInactiveUnit(unitView);
}
});
}
private findRenderByUnit(
unitView: UnitView,
): StructureRenderInfo | undefined {
return this.renders.find((render) => render.unit.id() === unitView.id());
}
private handleActiveUnit(unitView: UnitView) {
if (this.seenUnits.has(unitView)) {
const render = this.findRenderByUnit(unitView);
if (render) {
this.checkForOwnershipChange(render, unitView);
this.checkForLevelChange(render, unitView);
}
} else if (this.structures.has(unitView.type())) {
this.addNewStructure(unitView);
}
}
private handleInactiveUnit(unitView: UnitView) {
const render = this.findRenderByUnit(unitView);
if (render) {
this.deleteStructure(render);
this.shouldRedraw = true;
}
}
private checkForOwnershipChange(render: StructureRenderInfo, unit: UnitView) {
if (render && render.owner !== unit.owner().id()) {
render.owner = unit.owner().id();
render.pixiContainer?.destroy();
render.pixiContainer = this.createPixiSprite(unit);
this.shouldRedraw = true;
}
}
private checkForLevelChange(render: StructureRenderInfo, unit: UnitView) {
if (render && render.level !== unit.level()) {
render.level = unit.level();
render.pixiContainer?.destroy();
render.pixiContainer = this.createPixiSprite(unit);
this.shouldRedraw = true;
}
}
redraw() {
this.resizeCanvas();
}
@@ -176,15 +199,6 @@ export class StructureIconsLayer implements Layer {
mainContext.drawImage(this.renderer.canvas, 0, 0);
}
private ownerChangeCheck(render: StructureRenderInfo, unit: UnitView) {
if (render.owner !== unit.owner().id()) {
render.owner = unit.owner().id();
render.pixiSprite?.destroy();
render.pixiSprite = this.createPixiSprite(unit);
this.shouldRedraw = true;
}
}
private createTexture(unit: UnitView): PIXI.Texture {
const cacheKey = `${unit.owner().id()}-${unit.type()}`;
if (this.textureCache.has(cacheKey)) {
@@ -229,7 +243,8 @@ export class StructureIconsLayer implements Layer {
return texture;
}
private createPixiSprite(unit: UnitView): PIXI.Sprite {
private createPixiSprite(unit: UnitView): PIXI.Container {
const parentContainer = new PIXI.Container();
const sprite = new PIXI.Sprite(this.createTexture(unit));
sprite.anchor.set(0.5, 0.5);
const tile = unit.tile();
@@ -238,11 +253,26 @@ export class StructureIconsLayer implements Layer {
const screenPos = this.transformHandler.worldToScreenCoordinates(
new Cell(worldX, worldY),
);
sprite.x = screenPos.x;
sprite.y = screenPos.y - this.transformHandler.scale * OFFSET_ZOOM_Y;
sprite.scale.set(Math.min(1, this.transformHandler.scale));
this.stage.addChild(sprite);
return sprite;
parentContainer.addChild(sprite);
if (unit.level() > 1) {
const text = new PIXI.BitmapText({
text: unit.level().toString(),
style: {
fontFamily: "round_6x6_modified",
fontSize: 12,
},
});
text.anchor.set(0.5, 0.5);
text.position.y = -ICON_SIZE / 2 - 2;
parentContainer.addChild(text);
}
parentContainer.position.set(
Math.round(screenPos.x),
Math.round(screenPos.y - this.transformHandler.scale * OFFSET_ZOOM_Y),
);
parentContainer.scale.set(Math.min(1, this.transformHandler.scale));
this.stage.addChild(parentContainer);
return parentContainer;
}
private getImageColored(
@@ -281,19 +311,32 @@ export class StructureIconsLayer implements Layer {
screenPos.y - margin < this.pixicanvas.height;
if (onScreen) {
render.pixiSprite.x = screenPos.x;
render.pixiSprite.y = screenPos.y;
render.pixiSprite.scale.set(Math.min(1, this.transformHandler.scale));
render.pixiContainer.x = screenPos.x;
render.pixiContainer.y = screenPos.y;
render.pixiContainer.scale.set(Math.min(1, this.transformHandler.scale));
}
if (render.isOnScreen !== onScreen) {
// prevent unnecessary updates
render.isOnScreen = onScreen;
render.pixiSprite.visible = onScreen;
render.pixiContainer.visible = onScreen;
}
}
private addNewStructure(unitView: UnitView) {
this.seenUnits.add(unitView);
const render = new StructureRenderInfo(
unitView,
unitView.owner().id(),
this.createPixiSprite(unitView),
unitView.level(),
);
this.renders.push(render);
this.computeNewLocation(render);
this.shouldRedraw = true;
}
private deleteStructure(render: StructureRenderInfo) {
render.pixiSprite?.destroy();
render.pixiContainer?.destroy();
this.renders = this.renders.filter((r) => r.unit !== render.unit);
this.seenUnits.delete(render.unit);
}
+4
View File
@@ -36,3 +36,7 @@ declare module "*.html" {
const content: string;
export default content;
}
declare module "*.xml" {
const value: string;
export default value;
}
+1 -1
View File
@@ -87,7 +87,7 @@ export default async (env, argv) => {
},
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
test: /\.(woff|woff2|eot|ttf|otf|xml)$/,
type: "asset/resource", // Changed from file-loader
generator: {
filename: "fonts/[name].[contenthash][ext]", // Added content hash and fixed path