mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:00:42 +00:00
04d14853c4
## Description: Fixes a trailing space in the attack ratio troop count. **Before** <img width="257" height="96" alt="Screenshot from 2026-01-19 20-36-57" src="https://github.com/user-attachments/assets/0941c160-97dd-43a5-a111-cc3238ebbdb0" /> **After** <img width="257" height="96" alt="Screenshot from 2026-01-19 20-29-45" src="https://github.com/user-attachments/assets/cac06654-e13c-4831-a0f7-61ba1951338a" /> ## 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: n/a
242 lines
7.1 KiB
TypeScript
242 lines
7.1 KiB
TypeScript
import { LitElement, html } from "lit";
|
|
import { customElement, state } from "lit/decorators.js";
|
|
import { translateText } from "../../../client/Utils";
|
|
import { EventBus } from "../../../core/EventBus";
|
|
import { Gold } from "../../../core/game/Game";
|
|
import { GameView } from "../../../core/game/GameView";
|
|
import { ClientID } from "../../../core/Schemas";
|
|
import { AttackRatioEvent } from "../../InputHandler";
|
|
import { renderNumber, renderTroops } from "../../Utils";
|
|
import { UIState } from "../UIState";
|
|
import { Layer } from "./Layer";
|
|
|
|
@customElement("control-panel")
|
|
export class ControlPanel extends LitElement implements Layer {
|
|
public game: GameView;
|
|
public clientID: ClientID;
|
|
public eventBus: EventBus;
|
|
public uiState: UIState;
|
|
|
|
@state()
|
|
private attackRatio: number = 0.2;
|
|
|
|
@state()
|
|
private _maxTroops: number;
|
|
|
|
@state()
|
|
private troopRate: number;
|
|
|
|
@state()
|
|
private _troops: number;
|
|
|
|
@state()
|
|
private _isVisible = false;
|
|
|
|
@state()
|
|
private _gold: Gold;
|
|
|
|
private _troopRateIsIncreasing: boolean = true;
|
|
|
|
private _lastTroopIncreaseRate: number;
|
|
|
|
init() {
|
|
this.attackRatio = Number(
|
|
localStorage.getItem("settings.attackRatio") ?? "0.2",
|
|
);
|
|
this.uiState.attackRatio = this.attackRatio;
|
|
this.eventBus.on(AttackRatioEvent, (event) => {
|
|
let newAttackRatio =
|
|
(parseInt(
|
|
(document.getElementById("attack-ratio") as HTMLInputElement).value,
|
|
) +
|
|
event.attackRatio) /
|
|
100;
|
|
|
|
if (newAttackRatio < 0.01) {
|
|
newAttackRatio = 0.01;
|
|
}
|
|
|
|
if (newAttackRatio > 1) {
|
|
newAttackRatio = 1;
|
|
}
|
|
|
|
if (newAttackRatio === 0.11 && this.attackRatio === 0.01) {
|
|
// If we're changing the ratio from 1%, then set it to 10% instead of 11% to keep a consistency
|
|
newAttackRatio = 0.1;
|
|
}
|
|
|
|
this.attackRatio = newAttackRatio;
|
|
this.onAttackRatioChange(this.attackRatio);
|
|
});
|
|
}
|
|
|
|
tick() {
|
|
if (!this._isVisible && !this.game.inSpawnPhase()) {
|
|
this.setVisibile(true);
|
|
}
|
|
|
|
const player = this.game.myPlayer();
|
|
if (player === null || !player.isAlive()) {
|
|
this.setVisibile(false);
|
|
return;
|
|
}
|
|
|
|
if (this.game.ticks() % 5 === 0) {
|
|
this.updateTroopIncrease();
|
|
}
|
|
|
|
this._maxTroops = this.game.config().maxTroops(player);
|
|
this._gold = player.gold();
|
|
this._troops = player.troops();
|
|
this.troopRate = this.game.config().troopIncreaseRate(player) * 10;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
private updateTroopIncrease() {
|
|
const player = this.game?.myPlayer();
|
|
if (player === null) return;
|
|
const troopIncreaseRate = this.game.config().troopIncreaseRate(player);
|
|
this._troopRateIsIncreasing =
|
|
troopIncreaseRate >= this._lastTroopIncreaseRate;
|
|
this._lastTroopIncreaseRate = troopIncreaseRate;
|
|
}
|
|
|
|
onAttackRatioChange(newRatio: number) {
|
|
this.uiState.attackRatio = newRatio;
|
|
}
|
|
|
|
renderLayer(context: CanvasRenderingContext2D) {
|
|
// Render any necessary canvas elements
|
|
}
|
|
|
|
shouldTransform(): boolean {
|
|
return false;
|
|
}
|
|
|
|
setVisibile(visible: boolean) {
|
|
this._isVisible = visible;
|
|
this.requestUpdate();
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<style>
|
|
input[type="range"] {
|
|
-webkit-appearance: none;
|
|
background: transparent;
|
|
outline: none;
|
|
}
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 16px;
|
|
height: 16px;
|
|
background: white;
|
|
border-width: 2px;
|
|
border-style: solid;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
}
|
|
input[type="range"]::-moz-range-thumb {
|
|
width: 16px;
|
|
height: 16px;
|
|
background: white;
|
|
border-width: 2px;
|
|
border-style: solid;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
}
|
|
.targetTroopRatio::-webkit-slider-thumb {
|
|
border-color: rgb(59 130 246);
|
|
}
|
|
.targetTroopRatio::-moz-range-thumb {
|
|
border-color: rgb(59 130 246);
|
|
}
|
|
.attackRatio::-webkit-slider-thumb {
|
|
border-color: rgb(239 68 68);
|
|
}
|
|
.attackRatio::-moz-range-thumb {
|
|
border-color: rgb(239 68 68);
|
|
}
|
|
</style>
|
|
<div
|
|
class="pointer-events-auto ${this._isVisible
|
|
? "w-full sm:max-w-[320px] text-sm sm:text-base bg-gray-800/70 p-2 pr-3 sm:p-4 shadow-lg sm:rounded-lg backdrop-blur-sm"
|
|
: "hidden"}"
|
|
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
|
>
|
|
<div class="block bg-black/30 text-white mb-4 p-2 rounded-sm">
|
|
<div class="flex justify-between mb-1">
|
|
<span class="font-bold"
|
|
>${translateText("control_panel.troops")}:</span
|
|
>
|
|
<span translate="no"
|
|
>${renderTroops(this._troops)} / ${renderTroops(this._maxTroops)}
|
|
<span
|
|
class="${this._troopRateIsIncreasing
|
|
? "text-green-500"
|
|
: "text-yellow-500"}"
|
|
translate="no"
|
|
>(+${renderTroops(this.troopRate)})</span
|
|
></span
|
|
>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="font-bold"
|
|
>${translateText("control_panel.gold")}:</span
|
|
>
|
|
<span translate="no">${renderNumber(this._gold)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="relative mb-0 sm:mb-4">
|
|
<label class="block text-white mb-1">
|
|
${translateText("control_panel.attack_ratio")} :
|
|
<span
|
|
class="inline-flex items-center gap-1 [unicode-bidi:isolate]"
|
|
dir="ltr"
|
|
translate="no"
|
|
>
|
|
<span>${(this.attackRatio * 100).toFixed(0)}%</span>
|
|
<span>
|
|
(${renderTroops(
|
|
(this.game?.myPlayer()?.troops() ?? 0) * this.attackRatio,
|
|
)})
|
|
</span>
|
|
</span>
|
|
</label>
|
|
<div class="relative h-8">
|
|
<!-- Background track -->
|
|
<div
|
|
class="absolute left-0 right-0 top-3 h-2 bg-white/20 rounded-sm"
|
|
></div>
|
|
<!-- Fill track -->
|
|
<div
|
|
class="absolute left-0 top-3 h-2 bg-red-500/60 rounded-sm transition-all duration-300 w-(--width)"
|
|
style="--width: ${this.attackRatio * 100}%"
|
|
></div>
|
|
<!-- Range input - exactly overlaying the visual elements -->
|
|
<input
|
|
id="attack-ratio"
|
|
type="range"
|
|
min="1"
|
|
max="100"
|
|
.value=${(this.attackRatio * 100).toString()}
|
|
@input=${(e: Event) => {
|
|
this.attackRatio =
|
|
parseInt((e.target as HTMLInputElement).value) / 100;
|
|
this.onAttackRatioChange(this.attackRatio);
|
|
}}
|
|
class="absolute left-0 right-0 top-2 m-0 h-4 cursor-pointer attackRatio"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
createRenderRoot() {
|
|
return this; // Disable shadow DOM to allow Tailwind styles
|
|
}
|
|
}
|