Add nuke trail preview (#2350)

## Description:

Implements trajectory preview for nuke selection as requested in #2346.
When selecting an AtomBomb or HydrogenBomb, a dashed line preview now
shows the trajectory path from the launch silo to the target location
before launching. Line uses player's territory color with 70% opacity
and dashed pattern for clear visibility

## Please complete the following:

- [x] I have added screenshots for all UI updates
<img width="716" height="483" alt="image"
src="https://github.com/user-attachments/assets/4c263230-34ba-4e56-9502-4a59c84b5943"
/>
<img width="1199" height="965" alt="image"
src="https://github.com/user-attachments/assets/72eda758-e192-45a0-b01d-5a8f413a07d5"
/>

- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file (No new text strings added)
- [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:

kerverse

---------

Co-authored-by: Evan <evanpelle@gmail.com>
This commit is contained in:
Kerod Kibatu
2025-11-05 10:25:01 -08:00
committed by GitHub
co-authored by Evan
parent e8a04d9a72
commit 7b85114194
5 changed files with 293 additions and 13 deletions
+21 -12
View File
@@ -79,6 +79,10 @@ export class ToggleStructureEvent implements GameEvent {
constructor(public readonly structureTypes: UnitType[] | null) {}
}
export class GhostStructureChangedEvent implements GameEvent {
constructor(public readonly ghostStructure: UnitType | null) {}
}
export class ShowBuildMenuEvent implements GameEvent {
constructor(
public readonly x: number,
@@ -291,7 +295,7 @@ export class InputHandler {
if (e.code === "Escape") {
e.preventDefault();
this.eventBus.emit(new CloseViewEvent());
this.uiState.ghostStructure = null;
this.setGhostStructure(null);
}
if (
@@ -359,52 +363,52 @@ export class InputHandler {
if (e.code === this.keybinds.buildCity) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.City;
this.setGhostStructure(UnitType.City);
}
if (e.code === this.keybinds.buildFactory) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.Factory;
this.setGhostStructure(UnitType.Factory);
}
if (e.code === this.keybinds.buildPort) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.Port;
this.setGhostStructure(UnitType.Port);
}
if (e.code === this.keybinds.buildDefensePost) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.DefensePost;
this.setGhostStructure(UnitType.DefensePost);
}
if (e.code === this.keybinds.buildMissileSilo) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.MissileSilo;
this.setGhostStructure(UnitType.MissileSilo);
}
if (e.code === this.keybinds.buildSamLauncher) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.SAMLauncher;
this.setGhostStructure(UnitType.SAMLauncher);
}
if (e.code === this.keybinds.buildAtomBomb) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.AtomBomb;
this.setGhostStructure(UnitType.AtomBomb);
}
if (e.code === this.keybinds.buildHydrogenBomb) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.HydrogenBomb;
this.setGhostStructure(UnitType.HydrogenBomb);
}
if (e.code === this.keybinds.buildWarship) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.Warship;
this.setGhostStructure(UnitType.Warship);
}
if (e.code === this.keybinds.buildMIRV) {
e.preventDefault();
this.uiState.ghostStructure = UnitType.MIRV;
this.setGhostStructure(UnitType.MIRV);
}
// Shift-D to toggle performance overlay
@@ -545,12 +549,17 @@ export class InputHandler {
private onContextMenu(event: MouseEvent) {
event.preventDefault();
if (this.uiState.ghostStructure !== null) {
this.uiState.ghostStructure = null;
this.setGhostStructure(null);
return;
}
this.eventBus.emit(new ContextMenuEvent(event.clientX, event.clientY));
}
private setGhostStructure(ghostStructure: UnitType | null) {
this.uiState.ghostStructure = ghostStructure;
this.eventBus.emit(new GhostStructureChangedEvent(ghostStructure));
}
private getPinchDistance(): number {
const pointerEvents = Array.from(this.pointers.values());
const dx = pointerEvents[0].clientX - pointerEvents[1].clientX;