mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +00:00
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:
@@ -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/
|
||||
|
||||
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user