Feat: Added cursor price option to user and basic settings (#2655)

## Description:

Following the hotkey cursor price textbox addition of #2650, this
feature adds the option to enable and disable the visual feature via the
User Settings menu or the Basic Settings modal in game. Also added a
[new icon](https://thenounproject.com/icon/pay-per-click-2586454/) for
the Basic Settings modal from the Noun Project and added credit for it
to the `CREDITS.md` file.

### Video Demo


https://github.com/user-attachments/assets/1667081e-45e3-4b11-9bda-3f00c341e03c

### User Settings Menu
<img width="1029" height="1436" alt="image"
src="https://github.com/user-attachments/assets/e4e6bf6d-db59-463a-81fb-f622ef6e3931"
/>

### Basic Settings Menu
<img width="964" height="1545" alt="image"
src="https://github.com/user-attachments/assets/6b083655-b96e-4937-95d6-f3458858f03d"
/>



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

bijx
This commit is contained in:
bijx
2025-12-20 11:09:44 -08:00
committed by GitHub
parent e554ffb1b0
commit 4ee3319397
8 changed files with 188 additions and 5 deletions
+18
View File
@@ -143,6 +143,15 @@ export class UserSettingModal extends LitElement {
console.log("🏠 Structure sprites:", enabled ? "ON" : "OFF");
}
private toggleCursorCostLabel(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;
if (typeof enabled !== "boolean") return;
this.userSettings.set("settings.cursorCostLabel", enabled);
console.log("💰 Cursor build cost:", enabled ? "ON" : "OFF");
}
private toggleAnonymousNames(e: CustomEvent<{ checked: boolean }>) {
const enabled = e.detail?.checked;
if (typeof enabled !== "boolean") return;
@@ -309,6 +318,15 @@ export class UserSettingModal extends LitElement {
@change=${this.toggleStructureSprites}
></setting-toggle>
<!-- 💰 Cursor Price Pill -->
<setting-toggle
label="${translateText("user_setting.cursor_cost_label_label")}"
description="${translateText("user_setting.cursor_cost_label_desc")}"
id="cursor_cost_label-toggle"
.checked=${this.userSettings.cursorCostLabel()}
@change=${this.toggleCursorCostLabel}
></setting-toggle>
<!-- 🖱️ Left Click Menu -->
<setting-toggle
label="${translateText("user_setting.left_click_label")}"
@@ -1,6 +1,7 @@
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import structureIcon from "../../../../resources/images/CityIconWhite.svg";
import cursorPriceIcon from "../../../../resources/images/CursorPriceIconWhite.svg";
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
@@ -152,6 +153,11 @@ export class SettingsModal extends LitElement implements Layer {
this.requestUpdate();
}
private onToggleCursorCostLabelButtonClick() {
this.userSettings.toggleCursorCostLabel();
this.requestUpdate();
}
private onTogglePerformanceOverlayButtonClick() {
this.userSettings.togglePerformanceOverlay();
this.requestUpdate();
@@ -397,6 +403,31 @@ export class SettingsModal extends LitElement implements Layer {
</div>
</button>
<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
@click="${this.onToggleCursorCostLabelButtonClick}"
>
<img
src=${cursorPriceIcon}
alt="cursorCostLabel"
width="20"
height="20"
/>
<div class="flex-1">
<div class="font-medium">
${translateText("user_setting.cursor_cost_label_label")}
</div>
<div class="text-sm text-slate-400">
${translateText("user_setting.cursor_cost_label_desc")}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings.cursorCostLabel()
? translateText("user_setting.on")
: translateText("user_setting.off")}
</div>
</button>
<button
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded text-white transition-colors"
@click="${this.onToggleRandomNameModeButtonClick}"
@@ -114,6 +114,7 @@ export class SpriteFactory {
container: PIXI.Container;
priceText: PIXI.BitmapText;
priceBg: PIXI.Graphics;
priceGroup: PIXI.Container;
priceBox: { height: number; y: number; paddingX: number; minWidth: number };
} {
const parentContainer = new PIXI.Container();
@@ -165,6 +166,7 @@ export class SpriteFactory {
container: parentContainer,
priceText,
priceBg,
priceGroup,
priceBox: { height: boxHeight, y: boxY, paddingX, minWidth },
};
}
@@ -61,6 +61,7 @@ export class StructureIconsLayer implements Layer {
container: PIXI.Container;
priceText: PIXI.BitmapText;
priceBg: PIXI.Graphics;
priceGroup: PIXI.Container;
priceBox: { height: number; y: number; paddingX: number; minWidth: number };
range: PIXI.Container | null;
rangeLevel?: number;
@@ -270,12 +271,13 @@ export class StructureIconsLayer implements Layer {
const unit = actions.buildableUnits.find(
(u) => u.type === this.ghostUnit!.buildableUnit.type,
);
const showPrice = this.game.config().userSettings().cursorCostLabel();
if (!unit) {
Object.assign(this.ghostUnit.buildableUnit, {
canBuild: false,
canUpgrade: false,
});
this.updateGhostPrice(0);
this.updateGhostPrice(0, showPrice);
this.ghostUnit.container.filters = [
new OutlineFilter({ thickness: 2, color: "rgba(255, 0, 0, 1)" }),
];
@@ -283,7 +285,7 @@ export class StructureIconsLayer implements Layer {
}
this.ghostUnit.buildableUnit = unit;
this.updateGhostPrice(unit.cost ?? 0);
this.updateGhostPrice(unit.cost ?? 0, showPrice);
const targetLevel = this.resolveGhostRangeLevel(unit);
this.updateGhostRange(targetLevel);
@@ -318,9 +320,12 @@ export class StructureIconsLayer implements Layer {
});
}
private updateGhostPrice(cost: bigint | number) {
private updateGhostPrice(cost: bigint | number, showPrice: boolean) {
if (!this.ghostUnit) return;
const { priceText, priceBg, priceBox } = this.ghostUnit;
const { priceText, priceBg, priceBox, priceGroup } = this.ghostUnit;
priceGroup.visible = showPrice;
if (!showPrice) return;
priceText.text = renderNumber(cost);
priceText.position.set(0, priceBox.y);
@@ -407,11 +412,13 @@ export class StructureIconsLayer implements Layer {
container: ghost.container,
priceText: ghost.priceText,
priceBg: ghost.priceBg,
priceGroup: ghost.priceGroup,
priceBox: ghost.priceBox,
range: null,
buildableUnit: { type, canBuild: false, canUpgrade: false, cost: 0n },
};
this.updateGhostPrice(0);
const showPrice = this.game.config().userSettings().cursorCostLabel();
this.updateGhostPrice(0, showPrice);
const baseLevel = this.resolveGhostRangeLevel(this.ghostUnit.buildableUnit);
this.updateGhostRange(baseLevel);
}
+9
View File
@@ -73,6 +73,11 @@ export class UserSettings {
return this.get("settings.territoryPatterns", true);
}
cursorCostLabel() {
const legacy = this.get("settings.ghostPricePill", true);
return this.get("settings.cursorCostLabel", legacy);
}
focusLocked() {
return false;
// TODO: re-enable when performance issues are fixed.
@@ -115,6 +120,10 @@ export class UserSettings {
this.set("settings.structureSprites", !this.structureSprites());
}
toggleCursorCostLabel() {
this.set("settings.cursorCostLabel", !this.cursorCostLabel());
}
toggleTerritoryPatterns() {
this.set("settings.territoryPatterns", !this.territoryPatterns());
}