|
|
|
@@ -87,10 +87,10 @@ export class StructureIconsLayer implements Layer {
|
|
|
|
|
private renderSprites = true;
|
|
|
|
|
private factory: SpriteFactory;
|
|
|
|
|
private ghostControls: {
|
|
|
|
|
container: PIXI.Container;
|
|
|
|
|
confirm: PIXI.Graphics;
|
|
|
|
|
cancel: PIXI.Graphics;
|
|
|
|
|
flip: PIXI.Graphics;
|
|
|
|
|
container: HTMLDivElement;
|
|
|
|
|
confirm: HTMLButtonElement;
|
|
|
|
|
cancel: HTMLButtonElement;
|
|
|
|
|
flip: HTMLButtonElement;
|
|
|
|
|
} | null = null;
|
|
|
|
|
private readonly structures: Map<UnitType, { visible: boolean }> = new Map([
|
|
|
|
|
[UnitType.City, { visible: true }],
|
|
|
|
@@ -603,91 +603,63 @@ export class StructureIconsLayer implements Layer {
|
|
|
|
|
private ensureGhostControls() {
|
|
|
|
|
if (this.ghostControls) return;
|
|
|
|
|
|
|
|
|
|
const container = new PIXI.Container();
|
|
|
|
|
this.ghostStage.addChild(container);
|
|
|
|
|
|
|
|
|
|
const buttonSize = 48;
|
|
|
|
|
const gap = 8;
|
|
|
|
|
const buttonRadius = 6;
|
|
|
|
|
const container = document.createElement("div");
|
|
|
|
|
// Fixed positioning keeps controls anchored to the viewport even if the page scrolls
|
|
|
|
|
container.style.position = "fixed";
|
|
|
|
|
container.style.display = "flex";
|
|
|
|
|
container.style.gap = "8px";
|
|
|
|
|
container.style.transform = "translate(-50%, 0)";
|
|
|
|
|
container.style.pointerEvents = "auto";
|
|
|
|
|
container.style.zIndex = "5";
|
|
|
|
|
|
|
|
|
|
const makeButton = (
|
|
|
|
|
label: string,
|
|
|
|
|
color: number,
|
|
|
|
|
x: number,
|
|
|
|
|
background: string,
|
|
|
|
|
onClick: () => void,
|
|
|
|
|
): PIXI.Graphics => {
|
|
|
|
|
const button = new PIXI.Graphics();
|
|
|
|
|
button.beginFill(color);
|
|
|
|
|
button.drawRoundedRect(0, 0, buttonSize, buttonSize, buttonRadius);
|
|
|
|
|
button.endFill();
|
|
|
|
|
button.x = x;
|
|
|
|
|
button.y = 0;
|
|
|
|
|
button.eventMode = "static";
|
|
|
|
|
button.cursor = "pointer";
|
|
|
|
|
|
|
|
|
|
// Add text label
|
|
|
|
|
const text = new PIXI.Text(label, {
|
|
|
|
|
fontFamily: "Arial",
|
|
|
|
|
fontSize: 24,
|
|
|
|
|
fontWeight: "bold",
|
|
|
|
|
fill: 0xffffff,
|
|
|
|
|
});
|
|
|
|
|
text.anchor.set(0.5);
|
|
|
|
|
text.x = buttonSize / 2;
|
|
|
|
|
text.y = buttonSize / 2;
|
|
|
|
|
button.addChild(text);
|
|
|
|
|
|
|
|
|
|
// Hover effects
|
|
|
|
|
button.on("pointerover", () => {
|
|
|
|
|
this.uiState.overGhostControls = true;
|
|
|
|
|
button.tint = 0xdddddd;
|
|
|
|
|
});
|
|
|
|
|
button.on("pointerout", () => {
|
|
|
|
|
this.uiState.overGhostControls = false;
|
|
|
|
|
button.tint = 0xffffff;
|
|
|
|
|
});
|
|
|
|
|
button.on("pointerdown", () => {
|
|
|
|
|
this.uiState.overGhostControls = true;
|
|
|
|
|
button.tint = 0xaaaaaa;
|
|
|
|
|
});
|
|
|
|
|
button.on("pointerup", () => {
|
|
|
|
|
this.uiState.overGhostControls = false;
|
|
|
|
|
button.tint = 0xffffff;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
button.on("pointertap", (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
this.uiState.overGhostControls = false;
|
|
|
|
|
onClick();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
): HTMLButtonElement => {
|
|
|
|
|
const button = document.createElement("button");
|
|
|
|
|
button.type = "button";
|
|
|
|
|
button.textContent = label;
|
|
|
|
|
button.style.minHeight = "48px";
|
|
|
|
|
button.style.minWidth = "48px";
|
|
|
|
|
button.style.padding = "8px 16px";
|
|
|
|
|
button.style.borderRadius = "6px";
|
|
|
|
|
button.style.border = "none";
|
|
|
|
|
button.style.fontWeight = "700";
|
|
|
|
|
button.style.fontSize = "13px";
|
|
|
|
|
button.style.color = "#ffffff";
|
|
|
|
|
button.style.background = background;
|
|
|
|
|
button.style.cursor = "pointer";
|
|
|
|
|
button.style.boxShadow = "0 2px 6px rgba(0,0,0,0.25)";
|
|
|
|
|
button.style.whiteSpace = "nowrap";
|
|
|
|
|
button.addEventListener("click", onClick);
|
|
|
|
|
return button;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const confirm = makeButton("✓", 0x2e7d32, 0, () => {
|
|
|
|
|
const confirm = makeButton("✓", "#2e7d32", () => {
|
|
|
|
|
if (this.uiState.lockedGhostTile) {
|
|
|
|
|
this.emitBuildIntent(this.uiState.lockedGhostTile);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const flip = makeButton("↕", 0x1565c0, buttonSize + gap, () => {
|
|
|
|
|
const flip = makeButton("↕", "#1565c0", () => {
|
|
|
|
|
const next = !this.uiState.rocketDirectionUp;
|
|
|
|
|
this.eventBus.emit(new SwapRocketDirectionEvent(next));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const cancel = makeButton("✕", 0xb71c1c, (buttonSize + gap) * 2, () =>
|
|
|
|
|
const cancel = makeButton("✕", "#b71c1c", () =>
|
|
|
|
|
this.removeGhostStructure(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
container.addChild(confirm, flip, cancel);
|
|
|
|
|
container.append(confirm, flip, cancel);
|
|
|
|
|
document.body.appendChild(container);
|
|
|
|
|
|
|
|
|
|
this.ghostControls = { container, confirm, cancel, flip };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private destroyGhostControls() {
|
|
|
|
|
if (!this.ghostControls) return;
|
|
|
|
|
this.uiState.overGhostControls = false;
|
|
|
|
|
this.ghostControls.container.destroy({ children: true });
|
|
|
|
|
this.ghostControls.container.remove();
|
|
|
|
|
this.ghostControls = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -713,13 +685,9 @@ export class StructureIconsLayer implements Layer {
|
|
|
|
|
0.75,
|
|
|
|
|
Math.min(1.4, this.transformHandler.scale / 2),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Position PIXI container in world coordinates
|
|
|
|
|
const buttonWidth = 48;
|
|
|
|
|
const totalWidth = buttonWidth * 3 + 8 * 2; // 3 buttons + 2 gaps
|
|
|
|
|
this.ghostControls!.container.x = localX - totalWidth / 2;
|
|
|
|
|
this.ghostControls!.container.y = localY + offsetY;
|
|
|
|
|
this.ghostControls!.container.scale.set(scale);
|
|
|
|
|
this.ghostControls!.container.style.left = `${rect.left + localX}px`;
|
|
|
|
|
this.ghostControls!.container.style.top = `${rect.top + localY + offsetY}px`;
|
|
|
|
|
this.ghostControls!.container.style.transform = `translate(-50%, 0) scale(${scale})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private resolveGhostRangeLevel(
|
|
|
|
|