mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:20:15 +00:00
rename UILayer/StructureIconsLayer to controllers, move to src/client/controllers/
UILayer → WarshipSelectionController and StructureIconsLayer → BuildPreviewController. These are the two real Controller implementations (state + click handling, no rendering) — the new names + location reflect what they actually do now that all rendering lives in WebGL passes.
This commit is contained in:
+14
-14
@@ -1,43 +1,43 @@
|
||||
/**
|
||||
* StructureIconsLayer — build-ghost state machine + click-to-build flow.
|
||||
* BuildPreviewController — build-ghost state machine + click-to-build flow.
|
||||
*
|
||||
* All rendering for the build ghost (outline, range circle, rail snap,
|
||||
* crosshair) lives in the WebGL renderer. This layer just owns the state:
|
||||
* crosshair) lives in the WebGL renderer. This controller owns the state:
|
||||
* it queries buildables for the cursor tile, tracks whether the placement
|
||||
* is valid, and emits GhostPreviewUpdatedEvent to feed the renderer.
|
||||
*/
|
||||
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { wouldNukeBreakAlliance } from "../../../core/execution/Util";
|
||||
import { EventBus } from "../../core/EventBus";
|
||||
import { wouldNukeBreakAlliance } from "../../core/execution/Util";
|
||||
import {
|
||||
BuildableUnit,
|
||||
PlayerBuildableUnitType,
|
||||
UnitType,
|
||||
} from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
} from "../../core/game/Game";
|
||||
import { TileRef } from "../../core/game/GameMap";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { Controller } from "../graphics/layers/Controller";
|
||||
import { TransformHandler } from "../graphics/TransformHandler";
|
||||
import { UIState } from "../graphics/UIState";
|
||||
import {
|
||||
ConfirmGhostStructureEvent,
|
||||
GhostPreviewUpdatedEvent,
|
||||
GhostStructureChangedEvent,
|
||||
MouseMoveEvent,
|
||||
MouseUpEvent,
|
||||
} from "../../InputHandler";
|
||||
import type { GhostPreviewData } from "../../render/types";
|
||||
} from "../InputHandler";
|
||||
import type { GhostPreviewData } from "../render/types";
|
||||
import {
|
||||
BuildUnitIntentEvent,
|
||||
SendUpgradeStructureIntentEvent,
|
||||
} from "../../Transport";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { UIState } from "../UIState";
|
||||
import { Controller } from "./Controller";
|
||||
} from "../Transport";
|
||||
|
||||
/** True for nuke types (AtomBomb, HydrogenBomb): ghost is preserved after placement so user can place multiple or keep selection (Enter/key confirm). */
|
||||
export function shouldPreserveGhostAfterBuild(unitType: UnitType): boolean {
|
||||
return unitType === UnitType.AtomBomb || unitType === UnitType.HydrogenBomb;
|
||||
}
|
||||
|
||||
export class StructureIconsLayer implements Controller {
|
||||
export class BuildPreviewController implements Controller {
|
||||
/** Current ghost (null when no build type is active). */
|
||||
private ghostUnit: { buildableUnit: BuildableUnit } | null = null;
|
||||
private readonly connectedAllySmallIds: Set<number> = new Set();
|
||||
+17
-14
@@ -1,8 +1,10 @@
|
||||
import { Cell } from "src/core/game/Game";
|
||||
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 { EventBus } from "../../core/EventBus";
|
||||
import { UnitType } from "../../core/game/Game";
|
||||
import { TileRef } from "../../core/game/GameMap";
|
||||
import { GameView, UnitView } from "../../core/game/GameView";
|
||||
import { Controller } from "../graphics/layers/Controller";
|
||||
import { TransformHandler } from "../graphics/TransformHandler";
|
||||
import {
|
||||
CloseViewEvent,
|
||||
ContextMenuEvent,
|
||||
@@ -13,23 +15,24 @@ import {
|
||||
WarshipSelectionBoxCancelEvent,
|
||||
WarshipSelectionBoxCompleteEvent,
|
||||
WarshipSelectionBoxUpdateEvent,
|
||||
} from "../../InputHandler";
|
||||
import { MoveWarshipIntentEvent } from "../../Transport";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Controller } from "./Controller";
|
||||
} from "../InputHandler";
|
||||
import { MoveWarshipIntentEvent } from "../Transport";
|
||||
|
||||
const WARSHIP_SELECTION_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Layer responsible for warship selection state + click handling.
|
||||
* Controller for warship selection state + click handling.
|
||||
*
|
||||
* Drawing for selection boxes (single + multi) lives in the WebGL
|
||||
* SelectionBoxPass; the drag-rectangle preview is a screen-space DOM
|
||||
* overlay (dragRectEl). This layer does not draw to canvas2D at all —
|
||||
* it stays in the Layer list for lifecycle hooks (init / tick / event
|
||||
* subscriptions).
|
||||
* SelectionBoxPass (forwarded via UnitSelectionEvent from ClientGameRunner).
|
||||
* The drag-rectangle preview is a screen-space DOM overlay (dragRectEl) we
|
||||
* own here.
|
||||
*
|
||||
* This class does not render anything to canvas2D — it's purely a state +
|
||||
* click controller. The "Controller" pattern: main-thread analog of the
|
||||
* worker's Execution (init + tick + event subscriptions).
|
||||
*/
|
||||
export class UILayer implements Controller {
|
||||
export class WarshipSelectionController implements Controller {
|
||||
// Currently selected single warship (game-logic readers use this; the
|
||||
// visual is drawn by WebGL SelectionBoxPass).
|
||||
private selectedUnit: UnitView | null = null;
|
||||
@@ -2,6 +2,8 @@ import { EventBus } from "../../core/EventBus";
|
||||
import { GameView } from "../../core/game/GameView";
|
||||
import { UserSettings } from "../../core/game/UserSettings";
|
||||
import { GameStartingModal } from "../GameStartingModal";
|
||||
import { BuildPreviewController } from "../controllers/BuildPreviewController";
|
||||
import { WarshipSelectionController } from "../controllers/WarshipSelectionController";
|
||||
import { FrameProfiler } from "./FrameProfiler";
|
||||
import { TransformHandler } from "./TransformHandler";
|
||||
import { UIState } from "./UIState";
|
||||
@@ -29,9 +31,7 @@ import { PlayerPanel } from "./layers/PlayerPanel";
|
||||
import { ReplayPanel } from "./layers/ReplayPanel";
|
||||
import { SettingsModal } from "./layers/SettingsModal";
|
||||
import { SpawnTimer } from "./layers/SpawnTimer";
|
||||
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
|
||||
import { TeamStats } from "./layers/TeamStats";
|
||||
import { UILayer } from "./layers/UILayer";
|
||||
import { UnitDisplay } from "./layers/UnitDisplay";
|
||||
import { WinModal } from "./layers/WinModal";
|
||||
|
||||
@@ -256,12 +256,9 @@ export function createRenderer(
|
||||
}
|
||||
inGamePromo.game = game;
|
||||
|
||||
// When updating these layers please be mindful of the order.
|
||||
// Try to group layers by the return value of shouldTransform.
|
||||
// Not grouping the layers may cause excessive calls to context.save() and context.restore().
|
||||
const layers: Controller[] = [
|
||||
new UILayer(game, eventBus, transformHandler),
|
||||
new StructureIconsLayer(game, eventBus, uiState, transformHandler),
|
||||
new WarshipSelectionController(game, eventBus, transformHandler),
|
||||
new BuildPreviewController(game, eventBus, uiState, transformHandler),
|
||||
new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings),
|
||||
eventsDisplay,
|
||||
attacksDisplay,
|
||||
|
||||
+3
-9
@@ -1,14 +1,8 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { shouldPreserveGhostAfterBuild } from "../../../../src/client/graphics/layers/StructureIconsLayer";
|
||||
import { UnitType } from "../../../../src/core/game/Game";
|
||||
import { shouldPreserveGhostAfterBuild } from "../../../src/client/controllers/BuildPreviewController";
|
||||
import { UnitType } from "../../../src/core/game/Game";
|
||||
|
||||
/**
|
||||
* Tests for StructureIconsLayer edge cases mentioned in comments:
|
||||
* - Locked nuke / AtomBomb / HydrogenBomb: when confirming placement (Enter or key),
|
||||
* the ghost is preserved so the user can place multiple nukes or keep the nuke
|
||||
* selected. Other structure types clear the ghost after placement.
|
||||
*/
|
||||
describe("StructureIconsLayer ghost preservation (locked nuke / Enter confirm)", () => {
|
||||
describe("BuildPreviewController ghost preservation (locked nuke / Enter confirm)", () => {
|
||||
describe("shouldPreserveGhostAfterBuild", () => {
|
||||
test("returns true for AtomBomb so ghost is not cleared after placement", () => {
|
||||
expect(shouldPreserveGhostAfterBuild(UnitType.AtomBomb)).toBe(true);
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
import { UILayer } from "../../../src/client/graphics/layers/UILayer";
|
||||
import { WarshipSelectionController } from "../../../src/client/controllers/WarshipSelectionController";
|
||||
import { UnitSelectionEvent } from "../../../src/client/InputHandler";
|
||||
|
||||
describe("UILayer", () => {
|
||||
describe("WarshipSelectionController", () => {
|
||||
let game: any;
|
||||
let eventBus: any;
|
||||
let transformHandler: any;
|
||||
@@ -29,7 +29,7 @@ describe("UILayer", () => {
|
||||
});
|
||||
|
||||
it("tracks the selected unit on single-unit selection (rendering is WebGL)", () => {
|
||||
const ui = new UILayer(game, eventBus, transformHandler);
|
||||
const ui = new WarshipSelectionController(game, eventBus, transformHandler);
|
||||
const unit = {
|
||||
type: () => "Warship",
|
||||
isActive: () => true,
|
||||
@@ -45,7 +45,7 @@ describe("UILayer", () => {
|
||||
});
|
||||
|
||||
it("clears selection on deselect", () => {
|
||||
const ui = new UILayer(game, eventBus, transformHandler);
|
||||
const ui = new WarshipSelectionController(game, eventBus, transformHandler);
|
||||
const unit = {
|
||||
type: () => "Warship",
|
||||
isActive: () => true,
|
||||
@@ -61,7 +61,7 @@ describe("UILayer", () => {
|
||||
});
|
||||
|
||||
it("tracks multi-selection list", () => {
|
||||
const ui = new UILayer(game, eventBus, transformHandler);
|
||||
const ui = new WarshipSelectionController(game, eventBus, transformHandler);
|
||||
const units = [
|
||||
{ id: () => 1, isActive: () => true },
|
||||
{ id: () => 2, isActive: () => true },
|
||||
Reference in New Issue
Block a user