add mobile bomb flip support (#2737)

closes #2678 

## Description:

Update to enable bomb flip support by mobile users too

<img width="420" height="666" alt="image"
src="https://github.com/user-attachments/assets/eb2155a4-2012-4f40-8caa-bd23ebd28521"
/>


----------------------------------------------------------------------------------------------------------------------------------------------------

Also, I slightly updated the player panel to make it more even and take
up less space;
- removed the huge header bar which took up too much space
- fixed ui divider spacing

Before:
<img width="372" height="179" alt="image"
src="https://github.com/user-attachments/assets/2cf82cda-d466-4911-be4f-36eb6e788d5b"
/>
After:
<img width="383" height="134" alt="image"
src="https://github.com/user-attachments/assets/4d827221-f456-48fe-940b-a9ba84d1f4a5"
/>

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

w.o.n
This commit is contained in:
Ryan
2026-01-02 16:48:24 +00:00
committed by GitHub
parent 2f7c9eb930
commit 72f8924a7f
4 changed files with 108 additions and 66 deletions
+4 -1
View File
@@ -700,7 +700,10 @@
"send_alliance": "Send Alliance",
"send_troops": "Send Troops",
"send_gold": "Send Gold",
"emotes": "Emojis"
"emotes": "Emojis",
"arc_up": "Upward arc",
"arc_down": "Downward arc",
"flip_rocket_trajectory": "Flip rocket trajectory"
},
"send_troops_modal": {
"title_with_name": "Send Troops to {name}",
+5 -2
View File
@@ -89,7 +89,9 @@ export class GhostStructureChangedEvent implements GameEvent {
constructor(public readonly ghostStructure: UnitType | null) {}
}
export class SwapRocketDirectionEvent implements GameEvent {}
export class SwapRocketDirectionEvent implements GameEvent {
constructor(public readonly rocketDirectionUp: boolean) {}
}
export class ShowBuildMenuEvent implements GameEvent {
constructor(
@@ -432,7 +434,8 @@ export class InputHandler {
if (e.code === this.keybinds.swapDirection) {
e.preventDefault();
this.eventBus.emit(new SwapRocketDirectionEvent());
const nextDirection = !this.uiState.rocketDirectionUp;
this.eventBus.emit(new SwapRocketDirectionEvent(nextDirection));
}
// Shift-D to toggle performance overlay
@@ -56,9 +56,8 @@ export class NukeTrajectoryPreviewLayer implements Layer {
this.cachedSpawnTile = null;
}
});
this.eventBus.on(SwapRocketDirectionEvent, () => {
// Toggle rocket direction
this.uiState.rocketDirectionUp = !this.uiState.rocketDirectionUp;
this.eventBus.on(SwapRocketDirectionEvent, (event) => {
this.uiState.rocketDirectionUp = event.rocketDirectionUp;
// Force trajectory recalculation
this.lastTargetTile = null;
});
+97 -60
View File
@@ -14,7 +14,11 @@ import { GameView, PlayerView } from "../../../core/game/GameView";
import { Emoji, flattenedEmojiTable } from "../../../core/Util";
import { actionButton } from "../../components/ui/ActionButton";
import "../../components/ui/Divider";
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
import {
CloseViewEvent,
MouseUpEvent,
SwapRocketDirectionEvent,
} from "../../InputHandler";
import {
SendAllianceRequestIntentEvent,
SendBreakAllianceIntentEvent,
@@ -77,6 +81,10 @@ export class PlayerPanel extends LitElement implements Layer {
this.hide();
}
});
eventBus.on(SwapRocketDirectionEvent, (event) => {
this.uiState.rocketDirectionUp = event.rocketDirectionUp;
this.requestUpdate();
});
}
init() {
this.eventBus.on(MouseUpEvent, () => {
@@ -297,6 +305,12 @@ export class PlayerPanel extends LitElement implements Layer {
this.hide();
}
private handleToggleRocketDirection(e: Event) {
e.stopPropagation();
const next = !this.uiState.rocketDirectionUp;
this.eventBus.emit(new SwapRocketDirectionEvent(next));
}
private identityChipProps(type: PlayerType) {
switch (type) {
case PlayerType.Nation:
@@ -488,7 +502,7 @@ export class PlayerPanel extends LitElement implements Layer {
text-white w-[140px] min-w-[140px] flex-shrink-0"
>
<span class="mr-0.5">💰</span>
<span translate="no" class="tabular-nums w-[5ch]font-semibold">
<span translate="no" class="tabular-nums w-[5ch] font-semibold">
${renderNumber(other.gold() || 0)}
</span>
<span class="text-zinc-200 whitespace-nowrap">
@@ -512,6 +526,28 @@ export class PlayerPanel extends LitElement implements Layer {
`;
}
private renderRocketDirectionToggle() {
return html`
<ui-divider></ui-divider>
<button
class="flex w-full items-center justify-between rounded-xl bg-white/[0.05] px-3 py-2 text-left text-white hover:bg-white/[0.08] active:scale-[0.995] transition"
@click=${(e: Event) => this.handleToggleRocketDirection(e)}
>
<div class="flex flex-col">
<span class="text-sm font-semibold tracking-tight">
${translateText("player_panel.flip_rocket_trajectory")}
</span>
<span class="text-xs text-zinc-300" translate="no">
${this.uiState.rocketDirectionUp
? translateText("player_panel.arc_up")
: translateText("player_panel.arc_down")}
</span>
</div>
<span class="text-lg" aria-hidden="true">🔀</span>
</button>
`;
}
private renderStats(other: PlayerView, my: PlayerView) {
return html`
<!-- Betrayals -->
@@ -696,53 +732,53 @@ export class PlayerPanel extends LitElement implements Layer {
: ""}
</div>
<ui-divider></ui-divider>
<div class="grid auto-cols-fr grid-flow-col gap-1">
${other !== my
? canEmbargo
? actionButton({
onClick: (e: MouseEvent) =>
this.handleEmbargoClick(e, my, other),
icon: stopTradingIcon,
iconAlt: "Stop Trading",
title: translateText("player_panel.stop_trade"),
label: translateText("player_panel.stop_trade"),
type: "yellow",
})
: actionButton({
onClick: (e: MouseEvent) =>
this.handleStopEmbargoClick(e, my, other),
icon: startTradingIcon,
iconAlt: "Start Trading",
title: translateText("player_panel.start_trade"),
label: translateText("player_panel.start_trade"),
type: "green",
})
: ""}
${canBreakAlliance
? actionButton({
onClick: (e: MouseEvent) =>
this.handleBreakAllianceClick(e, my, other),
icon: breakAllianceIcon,
iconAlt: "Break Alliance",
title: translateText("player_panel.break_alliance"),
label: translateText("player_panel.break_alliance"),
type: "red",
})
: ""}
${canSendAllianceRequest
? actionButton({
onClick: (e: MouseEvent) =>
this.handleAllianceClick(e, my, other),
icon: allianceIcon,
iconAlt: "Alliance",
title: translateText("player_panel.send_alliance"),
label: translateText("player_panel.send_alliance"),
type: "indigo",
})
: ""}
</div>
${other === my
? html``
: html`
<div class="grid auto-cols-fr grid-flow-col gap-1">
${canEmbargo
? actionButton({
onClick: (e: MouseEvent) =>
this.handleEmbargoClick(e, my, other),
icon: stopTradingIcon,
iconAlt: "Stop Trading",
title: translateText("player_panel.stop_trade"),
label: translateText("player_panel.stop_trade"),
type: "yellow",
})
: actionButton({
onClick: (e: MouseEvent) =>
this.handleStopEmbargoClick(e, my, other),
icon: startTradingIcon,
iconAlt: "Start Trading",
title: translateText("player_panel.start_trade"),
label: translateText("player_panel.start_trade"),
type: "green",
})}
${canBreakAlliance
? actionButton({
onClick: (e: MouseEvent) =>
this.handleBreakAllianceClick(e, my, other),
icon: breakAllianceIcon,
iconAlt: "Break Alliance",
title: translateText("player_panel.break_alliance"),
label: translateText("player_panel.break_alliance"),
type: "red",
})
: ""}
${canSendAllianceRequest
? actionButton({
onClick: (e: MouseEvent) =>
this.handleAllianceClick(e, my, other),
icon: allianceIcon,
iconAlt: "Alliance",
title: translateText("player_panel.send_alliance"),
label: translateText("player_panel.send_alliance"),
type: "indigo",
})
: ""}
</div>
`}
${other === my
? html`<div class="grid auto-cols-fr grid-flow-col gap-1">
${actionButton({
@@ -845,16 +881,14 @@ export class PlayerPanel extends LitElement implements Layer {
<div
style="max-height: calc(100vh - 120px - env(safe-area-inset-bottom)); overflow:auto; -webkit-overflow-scrolling: touch; resize: vertical;"
>
<div class="sticky top-0 z-20 flex justify-end p-2">
<button
@click=${this.handleClose}
class="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("common.close") || "Close"}
title=${translateText("common.close") || "Close"}
>
</button>
</div>
<button
@click=${this.handleClose}
class="absolute right-3 top-3 z-20 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("common.close") || "Close"}
title=${translateText("common.close") || "Close"}
>
</button>
<div
class="p-6 flex flex-col gap-2 font-sans antialiased text-[14.5px] leading-relaxed"
@@ -889,6 +923,9 @@ export class PlayerPanel extends LitElement implements Layer {
<!-- Resources -->
${this.renderResources(other)}
<!-- Rocket direction toggle -->
${other === my ? this.renderRocketDirectionToggle() : ""}
<ui-divider></ui-divider>
<!-- Stats: betrayals / trading -->
@@ -902,7 +939,7 @@ export class PlayerPanel extends LitElement implements Layer {
<!-- Alliance time remaining -->
${this.renderAllianceExpiry()}
<ui-divider class="mt-1"></ui-divider>
<ui-divider></ui-divider>
<!-- Actions -->
${this.renderActions(my, other)}