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 14:09:44 -05:00
committed by GitHub
parent e554ffb1b0
commit 4ee3319397
8 changed files with 188 additions and 5 deletions
+1
View File
@@ -49,3 +49,4 @@ Copyright © opentopography.org. All Rights Reserved. [Terms of Use](https://ope
### [The Noun Project](https://thenounproject.com/)
Stats icon by [Meko](https://thenounproject.com/mekoda/) https://thenounproject.com/icon/stats-4942475/
Pay Per Click icon by [Fauzan Adiima](https://thenounproject.com/creator/fauzan94/) https://thenounproject.com/icon/pay-per-click-2586454/
+113
View File
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 40"
x="0px"
y="0px"
version="1.1"
id="svg71"
sodipodi:docname="CursorPriceIcon.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs75">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Invert"
id="filter207"
x="0"
y="0"
width="1"
height="1">
<feColorMatrix
type="hueRotate"
values="180"
result="color1"
id="feColorMatrix203" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 -0.21 -0.72 -0.07 2 0 "
result="fbSourceGraphic"
id="feColorMatrix205" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix209" />
<feColorMatrix
id="feColorMatrix211"
type="hueRotate"
values="180"
result="color1"
in="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix213"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 -0.21 -0.72 -0.07 2 0 "
result="fbSourceGraphic" />
<feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix215" />
<feColorMatrix
id="feColorMatrix217"
type="hueRotate"
values="180"
result="color1"
in="fbSourceGraphic" />
<feColorMatrix
id="feColorMatrix219"
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 -0.21 -0.72 -0.07 2 0 "
result="color2" />
</filter>
</defs>
<sodipodi:namedview
id="namedview73"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="20.175"
inkscape:cx="15.98513"
inkscape:cy="20"
inkscape:window-width="1920"
inkscape:window-height="1010"
inkscape:window-x="1913"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:current-layer="svg71" />
<title
id="title59">pay per click, ppc, click, cost per click, mouse</title>
<g
data-name="Layer 2"
id="g65"
style="filter:url(#filter207)">
<path
d="M29.24,19,20.71,16.9a2.73,2.73,0,0,1-.26.43l-.16.26c-.13.19-.27.38-.41.56s-.27.33-.41.49a8.13,8.13,0,0,1-.83.83c-.16.14-.32.28-.49.41l-.56.41-.26.16a2.73,2.73,0,0,1-.43.26L19,29.24a1,1,0,0,0,.86.75H20a1,1,0,0,0,.89-.55l2.86-5.7,5.7-2.86A1,1,0,0,0,29.24,19Z"
id="path61" />
<path
d="M16,17.24A1,1,0,0,1,17.24,16l1.44.36,2,.51a10,10,0,1,0-3.81,3.81l-.51-2ZM11,17V16H10a1,1,0,0,1,0-2h2.5a.5.5,0,0,0,0-1h-1A2.5,2.5,0,0,1,11,8V7a1,1,0,0,1,2,0V8h1a1,1,0,0,1,0,2H11.5a.5.5,0,0,0,0,1h1a2.5,2.5,0,0,1,.5,5v1a1,1,0,0,1-2,0Z"
id="path63" />
</g>
<text
x="0"
y="47"
fill="#000000"
font-size="5px"
font-weight="bold"
font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif"
id="text67">Created by Fauzan Adiima</text>
<text
x="0"
y="52"
fill="#000000"
font-size="5px"
font-weight="bold"
font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif"
id="text69">from the Noun Project</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

+2
View File
@@ -376,6 +376,8 @@
"special_effects_desc": "Toggle special effects. Deactivate to improve performances",
"structure_sprites_label": "Structure Sprites",
"structure_sprites_desc": "Toggle structure sprites",
"cursor_cost_label_label": "Cursor Build Cost",
"cursor_cost_label_desc": "Show a cost pill under the build cursor icon",
"anonymous_names_label": "Hidden Names",
"anonymous_names_desc": "Hide real player names with random ones on your screen.",
"lobby_id_visibility_label": "Hidden Lobby IDs",
+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());
}