mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 11:41:08 +00:00
Implement send resources modal (#2146)
## Description: Fixes https://github.com/openfrontio/OpenFrontIO/issues/2015 Implemented a new interactive modal component for sending troops/gold between players, replacing the previous automatic troop donation system. Screenshots <img width="388" height="569" alt="s1" src="https://github.com/user-attachments/assets/b5b5cfce-972e-474c-848a-4ea0dc7dde8f" /> <img width="383" height="534" alt="s2" src="https://github.com/user-attachments/assets/4c467fa2-7631-4e7b-ab17-898778b08ea6" /> <img width="377" height="548" alt="s3" src="https://github.com/user-attachments/assets/9359ee06-18af-48ea-a47c-586198e26f57" /> ## 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: abodcraft1 --------- Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
@@ -28,8 +28,6 @@ import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
|
||||
import {
|
||||
SendAllianceRequestIntentEvent,
|
||||
SendBreakAllianceIntentEvent,
|
||||
SendDonateGoldIntentEvent,
|
||||
SendDonateTroopsIntentEvent,
|
||||
SendEmbargoIntentEvent,
|
||||
SendEmojiIntentEvent,
|
||||
SendTargetPlayerIntentEvent,
|
||||
@@ -44,6 +42,7 @@ import { UIState } from "../UIState";
|
||||
import { ChatModal } from "./ChatModal";
|
||||
import { EmojiTable } from "./EmojiTable";
|
||||
import { Layer } from "./Layer";
|
||||
import "./SendResourceModal";
|
||||
|
||||
@customElement("player-panel")
|
||||
export class PlayerPanel extends LitElement implements Layer {
|
||||
@@ -51,21 +50,17 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
public eventBus: EventBus;
|
||||
public emojiTable: EmojiTable;
|
||||
public uiState: UIState;
|
||||
|
||||
private actions: PlayerActions | null = null;
|
||||
private tile: TileRef | null = null;
|
||||
private _profileForPlayerId: number | null = null;
|
||||
|
||||
@state()
|
||||
public isVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private allianceExpiryText: string | null = null;
|
||||
|
||||
@state()
|
||||
private allianceExpirySeconds: number | null = null;
|
||||
|
||||
@state()
|
||||
private otherProfile: PlayerProfile | null = null;
|
||||
@state() private sendTarget: PlayerView | null = null;
|
||||
@state() private sendMode: "troops" | "gold" | "none" = "none";
|
||||
@state() public isVisible: boolean = false;
|
||||
@state() private allianceExpiryText: string | null = null;
|
||||
@state() private allianceExpirySeconds: number | null = null;
|
||||
@state() private otherProfile: PlayerProfile | null = null;
|
||||
|
||||
private ctModal: ChatModal;
|
||||
|
||||
@@ -138,6 +133,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
|
||||
public hide() {
|
||||
this.isVisible = false;
|
||||
this.sendMode = "none";
|
||||
this.sendTarget = null;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -166,19 +163,23 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private openSendTroops(target: PlayerView) {
|
||||
this.sendTarget = target;
|
||||
this.sendMode = "troops";
|
||||
}
|
||||
|
||||
private openSendGold(target: PlayerView) {
|
||||
this.sendTarget = target;
|
||||
this.sendMode = "gold";
|
||||
}
|
||||
|
||||
private handleDonateTroopClick(
|
||||
e: Event,
|
||||
myPlayer: PlayerView,
|
||||
other: PlayerView,
|
||||
) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(
|
||||
new SendDonateTroopsIntentEvent(
|
||||
other,
|
||||
myPlayer.troops() * this.uiState.attackRatio,
|
||||
),
|
||||
);
|
||||
this.hide();
|
||||
this.openSendTroops(other);
|
||||
}
|
||||
|
||||
private handleDonateGoldClick(
|
||||
@@ -187,10 +188,20 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
other: PlayerView,
|
||||
) {
|
||||
e.stopPropagation();
|
||||
this.eventBus.emit(new SendDonateGoldIntentEvent(other, null));
|
||||
this.hide();
|
||||
this.openSendGold(other);
|
||||
}
|
||||
|
||||
private closeSend = () => {
|
||||
this.sendTarget = null;
|
||||
};
|
||||
|
||||
private confirmSend = (
|
||||
e: CustomEvent<{ amount: number; closePanel?: boolean }>,
|
||||
) => {
|
||||
this.closeSend();
|
||||
if (e.detail?.closePanel) this.hide();
|
||||
};
|
||||
|
||||
private handleEmbargoClick(
|
||||
e: Event,
|
||||
myPlayer: PlayerView,
|
||||
@@ -312,10 +323,11 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
}
|
||||
|
||||
private getExpiryColorClass(seconds: number | null): string {
|
||||
if (seconds === null) return "text-white";
|
||||
if (seconds <= 30) return "text-red-400";
|
||||
if (seconds <= 60) return "text-yellow-400";
|
||||
return "text-emerald-400";
|
||||
if (seconds === null) return "text-white"; // Default color
|
||||
|
||||
if (seconds <= 30) return "text-red-400"; // Last 30 seconds: Red
|
||||
if (seconds <= 60) return "text-yellow-400"; // Last 60 seconds: Yellow
|
||||
return "text-emerald-400"; // More than 60 seconds: Green
|
||||
}
|
||||
|
||||
private getTraitorRemainingSeconds(player: PlayerView): number | null {
|
||||
@@ -433,31 +445,27 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
return html`
|
||||
<div class="mb-1 flex justify-between gap-2">
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-zinc-800 px-2.5 py-1
|
||||
text-lg font-semibold text-zinc-100"
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-white/[0.04] px-2.5 py-1
|
||||
text-base font-semibold text-zinc-200"
|
||||
>
|
||||
<span class="mr-0.5">💰</span>
|
||||
<span
|
||||
translate="no"
|
||||
class="inline-block w-[45px] text-right text-zinc-50"
|
||||
>
|
||||
<span translate="no" class="inline-block w-[45px] text-right">
|
||||
${renderNumber(other.gold() || 0)}
|
||||
</span>
|
||||
<span class="opacity-95">${translateText("player_panel.gold")}</span>
|
||||
<span class="opacity-95 whitespace-nowrap"
|
||||
>${translateText("player_panel.gold")}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-zinc-800 px-2.5 py-1
|
||||
text-lg font-semibold text-zinc-100"
|
||||
class="inline-flex items-center gap-1.5 rounded-full bg-white/[0.04] px-2.5 py-1
|
||||
text-base font-semibold text-zinc-200"
|
||||
>
|
||||
<span class="mr-0.5">🛡️</span>
|
||||
<span
|
||||
translate="no"
|
||||
class="inline-block w-[45px] text-right text-zinc-50"
|
||||
>
|
||||
<span translate="no" class="inline-block w-[45px] text-right">
|
||||
${renderTroops(other.troops() || 0)}
|
||||
</span>
|
||||
<span class="opacity-95"
|
||||
<span class="opacity-95 whitespace-nowrap"
|
||||
>${translateText("player_panel.troops")}</span
|
||||
>
|
||||
</div>
|
||||
@@ -554,7 +562,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
})
|
||||
: html`
|
||||
<div class="py-2 text-zinc-300">
|
||||
${translateText("player_panel.none")}
|
||||
${translateText("common.none")}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
@@ -567,7 +575,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
if (this.allianceExpiryText === null) return html``;
|
||||
return html`
|
||||
<div class="grid grid-cols-[auto,1fr] gap-x-6 gap-y-2 text-base">
|
||||
<div class="font-semibold text-zinc-400">
|
||||
<div class="font-semibold text-zinc-300">
|
||||
${translateText("player_panel.alliance_time_remaining")}
|
||||
</div>
|
||||
<div class="text-right font-semibold">
|
||||
@@ -713,6 +721,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
return html``;
|
||||
}
|
||||
const other = owner as PlayerView;
|
||||
const myGoldNum = my.gold();
|
||||
const myTroopsNum = Number(my.troops());
|
||||
|
||||
return html`
|
||||
<style>
|
||||
@@ -744,12 +754,11 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
|
||||
<div
|
||||
class="fixed inset-0 z-[1001] flex items-center justify-center overflow-auto
|
||||
bg-black/40 backdrop-blur-sm backdrop-brightness-110 pointer-events-auto"
|
||||
bg-black/15 backdrop-blur-sm backdrop-brightness-110 pointer-events-auto"
|
||||
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
|
||||
@wheel=${(e: MouseEvent) => e.stopPropagation()}
|
||||
@click=${() => this.hide()}
|
||||
>
|
||||
<!-- Stop clicks inside the panel from closing it -->
|
||||
<div
|
||||
class="pointer-events-auto max-h-[90vh] overflow-y-auto min-w-[240px] w-auto px-4 py-2"
|
||||
@click=${(e: MouseEvent) => e.stopPropagation()}
|
||||
@@ -763,8 +772,8 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
@click=${this.handleClose}
|
||||
class="absolute -top-3 -right-3 flex h-7 w-7 items-center justify-center
|
||||
rounded-full bg-zinc-700 text-white shadow hover:bg-red-500 transition-colors"
|
||||
aria-label=${translateText("player_panel.close") || "Close"}
|
||||
title=${translateText("player_panel.close") || "Close"}
|
||||
aria-label=${translateText("common.close") || "Close"}
|
||||
title=${translateText("common.close") || "Close"}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
@@ -775,6 +784,28 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
<!-- Identity (flag, name, type, traitor, relation) -->
|
||||
<div class="mb-1">${this.renderIdentityRow(other, my)}</div>
|
||||
|
||||
${this.sendTarget
|
||||
? html`
|
||||
<send-resource-modal
|
||||
.open=${this.sendMode !== "none"}
|
||||
.mode=${this.sendMode}
|
||||
.total=${this.sendMode === "troops"
|
||||
? myTroopsNum
|
||||
: myGoldNum}
|
||||
.uiState=${this.uiState}
|
||||
.myPlayer=${my}
|
||||
.target=${this.sendTarget}
|
||||
.gameView=${this.g}
|
||||
.eventBus=${this.eventBus}
|
||||
.format=${this.sendMode === "troops"
|
||||
? renderTroops
|
||||
: renderNumber}
|
||||
@confirm=${this.confirmSend}
|
||||
@close=${this.closeSend}
|
||||
></send-resource-modal>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ui-divider></ui-divider>
|
||||
|
||||
<!-- Resources -->
|
||||
|
||||
Reference in New Issue
Block a user