Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/client/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ export class TickMetricsEvent implements GameEvent {
) {}
}

export class AllianceRequestEvent implements GameEvent {
constructor(
public readonly x: number,
public readonly y: number,
) {}
}

export class BreakAllianceEvent implements GameEvent {
constructor(
public readonly x: number,
public readonly y: number,
) {}
}

export class InputHandler {
private lastPointerX: number = 0;
private lastPointerY: number = 0;
Expand Down Expand Up @@ -225,6 +239,8 @@ export class InputHandler {
buildAtomBomb: "Digit8",
buildHydrogenBomb: "Digit9",
buildMIRV: "Digit0",
alliance: "KeyF",
breakAlliance: "KeyG",
...saved,
};

Expand Down Expand Up @@ -343,6 +359,8 @@ export class InputHandler {
"ControlRight",
"ShiftLeft",
"ShiftRight",
this.keybinds.alliance,
this.keybinds.breakAlliance,
].includes(e.code)
) {
this.activeKeys.add(e.code);
Expand Down Expand Up @@ -507,6 +525,18 @@ export class InputHandler {
return;
}

if (this.activeKeys.has(this.keybinds.alliance)) {
this.eventBus.emit(
new AllianceRequestEvent(event.clientX, event.clientY),
);
return;
}

if (this.activeKeys.has(this.keybinds.breakAlliance)) {
this.eventBus.emit(new BreakAllianceEvent(event.clientX, event.clientY));
return;
}

const dist =
Math.abs(event.x - this.lastPointerDownX) +
Math.abs(event.y - this.lastPointerDownY);
Expand Down
2 changes: 1 addition & 1 deletion src/client/graphics/GameRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export function createRenderer(
new RailroadLayer(game, eventBus, transformHandler, uiState),
structureLayer,
samRadiusLayer,
new UnitLayer(game, eventBus, transformHandler),
new UnitLayer(game, eventBus, transformHandler, uiState),
new FxLayer(game, eventBus, transformHandler),
new UILayer(game, eventBus, transformHandler),
new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler, uiState),
Expand Down
54 changes: 53 additions & 1 deletion src/client/graphics/layers/MainRadialMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
import donateTroopIcon from "/images/DonateTroopIconWhite.svg?url";
import swordIcon from "/images/SwordIconWhite.svg?url";

import { ContextMenuEvent } from "../../InputHandler";
import {
AllianceRequestEvent,
BreakAllianceEvent,
ContextMenuEvent,
} from "../../InputHandler";

@customElement("main-radial-menu")
export class MainRadialMenu extends LitElement implements Layer {
Expand Down Expand Up @@ -103,6 +107,54 @@ export class MainRadialMenu extends LitElement implements Layer {
);
});
});

this.eventBus.on(AllianceRequestEvent, (event) => {
const worldCoords = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y,
);
if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) {
return;
}
const myPlayer = this.game.myPlayer();
if (!myPlayer) {
return;
}

const tile = this.game.ref(worldCoords.x, worldCoords.y);
const owner = this.game.owner(tile);
if (owner.isPlayer() && owner !== myPlayer) {
// Send alliance request
this.playerActionHandler.handleAllianceRequest(
myPlayer,
owner as PlayerView,
);
}
});

this.eventBus.on(BreakAllianceEvent, (event) => {
const worldCoords = this.transformHandler.screenToWorldCoordinates(
event.x,
event.y,
);
if (!this.game.isValidCoord(worldCoords.x, worldCoords.y)) {
return;
}
const myPlayer = this.game.myPlayer();
if (!myPlayer) {
return;
}

const tile = this.game.ref(worldCoords.x, worldCoords.y);
const owner = this.game.owner(tile);
if (owner.isPlayer() && owner !== myPlayer) {
// Break alliance
this.playerActionHandler.handleBreakAlliance(
myPlayer,
owner as PlayerView,
);
}
});
}

private async updatePlayerActions(
Expand Down
140 changes: 134 additions & 6 deletions src/client/graphics/layers/UnitLayer.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this makes sense on the unit layer, but i think we can probably move this code altogether if we change the UX to show the keys in the player info panel

Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import { colord, Colord } from "colord";
import { EventBus } from "../../../core/EventBus";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, UnitView } from "../../../core/game/GameView";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { BezenhamLine } from "../../../core/utilities/Line";
import {
AlternateViewEvent,
ContextMenuEvent,
MouseMoveEvent,
MouseUpEvent,
TouchEvent,
UnitSelectionEvent,
} from "../../InputHandler";
import { MoveWarshipIntentEvent } from "../../Transport";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";

import { GameUpdateType } from "../../../core/game/GameUpdates";
import {
getColoredSprite,
isSpriteReady,
loadAllSprites,
} from "../SpriteLoader";
import { TransformHandler } from "../TransformHandler";
import { UIState } from "../UIState";
import { Layer } from "./Layer";

import allianceIconPath from "/images/AllianceIconWhite.svg?url";
import traitorIconPath from "/images/TraitorIconWhite.svg?url";

enum Relationship {
Self,
Expand Down Expand Up @@ -51,13 +55,63 @@ export class UnitLayer implements Layer {
// Configuration for unit selection
private readonly WARSHIP_SELECTION_RADIUS = 10; // Radius in game cells for warship selection hit zone

private fKeyHeld = false;
private gKeyHeld = false;
private mouseX = 0;
private mouseY = 0;

private allianceIcon: HTMLCanvasElement | null = null;
private traitorIcon: HTMLCanvasElement | null = null;

constructor(
private game: GameView,
private eventBus: EventBus,
transformHandler: TransformHandler,
private uiState: UIState,
) {
this.theme = game.config().theme();
this.transformHandler = transformHandler;

this.loadIcons();
}

private loadIcons() {
this.loadAndTintIcon(allianceIconPath, "#53ac75", (icon) => {
this.allianceIcon = icon;
});
this.loadAndTintIcon(traitorIconPath, "#c74848", (icon) => {
this.traitorIcon = icon;
});
}

private loadAndTintIcon(
path: string,
color: string,
callback: (icon: HTMLCanvasElement) => void,
) {
const img = new Image();
img.src = path;
img.onload = () => {
callback(this.tintIcon(img, color));
};
}

private tintIcon(image: HTMLImageElement, color: string): HTMLCanvasElement {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d");
if (!ctx) return canvas;

// Draw the color
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Mask with image
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(image, 0, 0);

return canvas;
}

shouldTransform(): boolean {
Expand All @@ -77,11 +131,28 @@ export class UnitLayer implements Layer {
this.eventBus.on(MouseUpEvent, (e) => this.onMouseUp(e));
this.eventBus.on(TouchEvent, (e) => this.onTouch(e));
this.eventBus.on(UnitSelectionEvent, (e) => this.onUnitSelectionChange(e));
this.eventBus.on(MouseMoveEvent, (e) => {
this.mouseX = e.x;
this.mouseY = e.y;
});

window.addEventListener("keydown", (e) => {
if (e.code === "KeyF") this.fKeyHeld = true;
if (e.code === "KeyG") this.gKeyHeld = true;
});

window.addEventListener("keyup", (e) => {
if (e.code === "KeyF") this.fKeyHeld = false;
if (e.code === "KeyG") this.gKeyHeld = false;
});

this.redraw();

loadAllSprites();
}

// ... rest of the file ...

/**
* Find player-owned warships near the given cell within a configurable radius
* @param clickRef The tile to check
Expand Down Expand Up @@ -214,6 +285,63 @@ export class UnitLayer implements Layer {
this.game.width(),
this.game.height(),
);

this.drawCursorIcon(context);
}

private drawCursorIcon(context: CanvasRenderingContext2D) {
if (!this.fKeyHeld && !this.gKeyHeld) return;

// Determine target under cursor
const cell = this.transformHandler.screenToWorldCoordinates(
this.mouseX,
this.mouseY,
);
if (!this.game.isValidCoord(cell.x, cell.y)) return;

const clickRef = this.game.ref(cell.x, cell.y);
const owner = this.game.owner(clickRef);
const myPlayer = this.game.myPlayer();

if (!myPlayer || !owner.isPlayer() || owner === myPlayer) {
return;
}

const targetPlayer = owner as PlayerView;
const isAllied = targetPlayer.isAlliedWith(myPlayer);

let iconToDraw: HTMLCanvasElement | null = null;

if (this.fKeyHeld && !isAllied) {
// Check if we can actually request? (e.g. not enemy?)
// For now, show icon if F is held and not allied.
iconToDraw = this.allianceIcon;
} else if (this.gKeyHeld && isAllied) {
iconToDraw = this.traitorIcon;
}

if (iconToDraw) {
const x = cell.x - this.game.width() / 2;
const y = cell.y - this.game.height() / 2;

// Draw icon centered at x,y
const drawSize = 32; // size in world units (cells)

// console.log("DrawIcon", { f: this.fKeyHeld, g: this.gKeyHeld, x, y, drawSize, icon: !!iconToDraw });

context.save();
context.globalAlpha = 0.8;

context.drawImage(
iconToDraw,
x - drawSize / 2,
y - drawSize / 2,
drawSize,
drawSize,
);

context.restore();
}
}

onAlternativeViewEvent(event: AlternateViewEvent) {
Expand Down
Loading