mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:20:43 +00:00
Rename to AttackingTroopsOverlay, label transitions
This commit is contained in:
@@ -537,8 +537,8 @@
|
||||
"territory_patterns_desc": "Choose whether to display territory skin designs in game",
|
||||
"coordinate_grid_label": "Coordinate Grid",
|
||||
"coordinate_grid_desc": "Toggle the alphanumeric grid overlay",
|
||||
"troop_advantage_label": "Troop Advantage Display",
|
||||
"troop_advantage_desc": "Show attacker vs defender troop counts on active front lines.",
|
||||
"attacking_troops_overlay_label": "Attacking Troops Overlay",
|
||||
"attacking_troops_overlay_desc": "Show attacker vs defender troop counts on active front lines.",
|
||||
"performance_overlay_label": "Performance Overlay",
|
||||
"performance_overlay_desc": "Toggle the performance overlay. When enabled, the performance overlay will be displayed. Press shift-D during game to toggle.",
|
||||
"easter_writing_speed_label": "Writing Speed Multiplier",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FrameProfiler } from "./FrameProfiler";
|
||||
import { TransformHandler } from "./TransformHandler";
|
||||
import { UIState } from "./UIState";
|
||||
import { AlertFrame } from "./layers/AlertFrame";
|
||||
import { AttackingTroopsOverlay } from "./layers/AttackingTroopsOverlay";
|
||||
import { AttacksDisplay } from "./layers/AttacksDisplay";
|
||||
import { BuildMenu } from "./layers/BuildMenu";
|
||||
import { ChatDisplay } from "./layers/ChatDisplay";
|
||||
@@ -42,7 +43,6 @@ import { StructureLayer } from "./layers/StructureLayer";
|
||||
import { TeamStats } from "./layers/TeamStats";
|
||||
import { TerrainLayer } from "./layers/TerrainLayer";
|
||||
import { TerritoryLayer } from "./layers/TerritoryLayer";
|
||||
import { TroopAdvantageLayer } from "./layers/TroopAdvantageLayer";
|
||||
import { UILayer } from "./layers/UILayer";
|
||||
import { UnitDisplay } from "./layers/UnitDisplay";
|
||||
import { UnitLayer } from "./layers/UnitLayer";
|
||||
@@ -293,8 +293,8 @@ export function createRenderer(
|
||||
new NukeTrajectoryPreviewLayer(game, eventBus, transformHandler, uiState),
|
||||
new StructureIconsLayer(game, eventBus, uiState, transformHandler),
|
||||
new DynamicUILayer(game, transformHandler, eventBus),
|
||||
new TroopAdvantageLayer(game, transformHandler, eventBus, userSettings),
|
||||
new NameLayer(game, transformHandler, eventBus),
|
||||
new AttackingTroopsOverlay(game, transformHandler, eventBus, userSettings),
|
||||
eventsDisplay,
|
||||
attacksDisplay,
|
||||
chatDisplay,
|
||||
|
||||
+78
-28
@@ -21,7 +21,8 @@ export function troopDefenceColor(
|
||||
return attackerTroops > myTroops ? "#ff4444" : "#ff9944";
|
||||
}
|
||||
|
||||
// One label element per disconnected cluster of front-line tiles
|
||||
// An attack can have multiple disconnected front-line segments, so elements
|
||||
// and positions are parallel arrays with one entry per segment.
|
||||
interface AttackLabel {
|
||||
elements: HTMLDivElement[];
|
||||
positions: (Cell | null)[];
|
||||
@@ -30,9 +31,10 @@ interface AttackLabel {
|
||||
defenderTroops: number;
|
||||
}
|
||||
|
||||
export class TroopAdvantageLayer implements Layer {
|
||||
export class AttackingTroopsOverlay implements Layer {
|
||||
private container: HTMLDivElement;
|
||||
private labels = new Map<string, AttackLabel>();
|
||||
// Guard against queuing multiple worker requests in the same tick window.
|
||||
private inFlightRequest = false;
|
||||
private isVisible = true;
|
||||
private onAlternateView: (e: AlternateViewEvent) => void;
|
||||
@@ -49,11 +51,14 @@ export class TroopAdvantageLayer implements Layer {
|
||||
}
|
||||
|
||||
init() {
|
||||
// The container is anchored at the viewport centre (50%, 50%) so that
|
||||
// label transforms can use raw world coordinates without an extra offset.
|
||||
this.container = document.createElement("div");
|
||||
this.container.style.position = "fixed";
|
||||
this.container.style.left = "50%";
|
||||
this.container.style.top = "50%";
|
||||
this.container.style.pointerEvents = "none";
|
||||
// z-index 4 places labels above NameLayer (z-index 3).
|
||||
this.container.style.zIndex = "4";
|
||||
document.body.appendChild(this.container);
|
||||
|
||||
@@ -76,7 +81,7 @@ export class TroopAdvantageLayer implements Layer {
|
||||
}
|
||||
|
||||
tick() {
|
||||
if (!this.userSettings.troopAdvantageLayer() || !this.isVisible) {
|
||||
if (!this.userSettings.attackingTroopsOverlay() || !this.isVisible) {
|
||||
if (this.labels.size > 0) this.clearAllLabels();
|
||||
return;
|
||||
}
|
||||
@@ -94,15 +99,15 @@ export class TroopAdvantageLayer implements Layer {
|
||||
...incoming.map((a) => a.id),
|
||||
]);
|
||||
|
||||
// Remove labels for attacks that no longer exist
|
||||
for (const [id] of this.labels) {
|
||||
if (!activeIDs.has(id)) this.removeLabel(id);
|
||||
}
|
||||
|
||||
const myTroops = myPlayer.troops();
|
||||
|
||||
// Outgoing attacks — ⚔ green if winning, amber if losing
|
||||
// Outgoing attacks — green if winning, amber if losing.
|
||||
for (const attack of outgoing) {
|
||||
// targetID === 0 means the attack is targeting sea/empty tiles; skip it.
|
||||
if (!attack.targetID) {
|
||||
this.removeLabel(attack.id);
|
||||
continue;
|
||||
@@ -115,7 +120,7 @@ export class TroopAdvantageLayer implements Layer {
|
||||
this.ensureLabel(attack.id, attack.troops, defender.troops(), false);
|
||||
}
|
||||
|
||||
// Incoming attacks — red if attacker > my troops, orange if attacker < my troops
|
||||
// Incoming attacks — red if the attacker outnumbers my troops, orange otherwise.
|
||||
for (const attack of incoming) {
|
||||
const attacker = this.game.playerBySmallID(attack.attackerID);
|
||||
if (!attacker || !attacker.isPlayer()) {
|
||||
@@ -125,38 +130,21 @@ export class TroopAdvantageLayer implements Layer {
|
||||
this.ensureLabel(attack.id, attack.troops, myTroops, true);
|
||||
}
|
||||
|
||||
// Single request per tick for all attack cluster positions
|
||||
// Single worker request per tick; skip if the previous one is still in flight.
|
||||
if (this.inFlightRequest) return;
|
||||
this.inFlightRequest = true;
|
||||
|
||||
void myPlayer
|
||||
.attackClusterPositions(myPlayer.smallID())
|
||||
.attackFrontLinePositions()
|
||||
.then((attacks) => {
|
||||
for (const { id, clusters } of attacks) {
|
||||
for (const { id, centers } of attacks) {
|
||||
const lbl = this.labels.get(id);
|
||||
if (!lbl) continue;
|
||||
|
||||
while (lbl.elements.length < clusters.length) {
|
||||
lbl.elements.push(
|
||||
this.createLabelElement(
|
||||
lbl.attackerTroops,
|
||||
lbl.defenderTroops,
|
||||
lbl.isIncoming,
|
||||
),
|
||||
);
|
||||
lbl.positions.push(null);
|
||||
}
|
||||
while (lbl.elements.length > clusters.length) {
|
||||
lbl.elements.pop()!.remove();
|
||||
lbl.positions.pop();
|
||||
}
|
||||
|
||||
for (let i = 0; i < clusters.length; i++) {
|
||||
lbl.positions[i] = clusters[i];
|
||||
}
|
||||
this.reconcileLabelPositions(lbl, centers);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// On error, hide all labels until the next successful response.
|
||||
for (const lbl of this.labels.values()) lbl.positions.fill(null);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -210,11 +198,71 @@ export class TroopAdvantageLayer implements Layer {
|
||||
}
|
||||
|
||||
el.style.display = "block";
|
||||
// Centre the label on its world position and counter-scale so text
|
||||
// stays the same screen size regardless of zoom level.
|
||||
el.style.transform = `translate(${pos.x}px, ${pos.y}px) translate(-50%, -50%) scale(${1 / this.transformHandler.scale})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign each existing label element to the new center closest to its current
|
||||
// position (greedy nearest-neighbour matching). This prevents labels from
|
||||
// swapping front-line segments when their relative sizes change between ticks,
|
||||
// which would otherwise cause visible jumping.
|
||||
private reconcileLabelPositions(lbl: AttackLabel, centers: Cell[]) {
|
||||
const availableCenterIndexes = centers.map((_, i) => i);
|
||||
const updatedPositions: (Cell | null)[] = [];
|
||||
|
||||
for (
|
||||
let elementIndex = 0;
|
||||
elementIndex < lbl.elements.length && availableCenterIndexes.length > 0;
|
||||
elementIndex++
|
||||
) {
|
||||
const currentPos = lbl.positions[elementIndex];
|
||||
if (!currentPos) {
|
||||
// Element has no position yet — assign the first available center.
|
||||
updatedPositions.push(centers[availableCenterIndexes.shift()!]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the available center closest to this element's current position.
|
||||
let closestCenterAt = 0;
|
||||
let closestDistance = Infinity;
|
||||
for (let i = 0; i < availableCenterIndexes.length; i++) {
|
||||
const candidate = centers[availableCenterIndexes[i]];
|
||||
const dx = candidate.x - currentPos.x;
|
||||
const dy = candidate.y - currentPos.y;
|
||||
const squaredDistance = dx * dx + dy * dy;
|
||||
if (squaredDistance < closestDistance) {
|
||||
closestDistance = squaredDistance;
|
||||
closestCenterAt = i;
|
||||
}
|
||||
}
|
||||
updatedPositions.push(
|
||||
centers[availableCenterIndexes.splice(closestCenterAt, 1)[0]],
|
||||
);
|
||||
}
|
||||
|
||||
// Create new label elements for centers that had no existing element to match.
|
||||
for (const centerIndex of availableCenterIndexes) {
|
||||
lbl.elements.push(
|
||||
this.createLabelElement(
|
||||
lbl.attackerTroops,
|
||||
lbl.defenderTroops,
|
||||
lbl.isIncoming,
|
||||
),
|
||||
);
|
||||
updatedPositions.push(centers[centerIndex]);
|
||||
}
|
||||
|
||||
// Remove elements for front-line segments that no longer exist.
|
||||
while (lbl.elements.length > updatedPositions.length) {
|
||||
lbl.elements.pop()!.remove();
|
||||
}
|
||||
|
||||
lbl.positions = updatedPositions;
|
||||
}
|
||||
|
||||
private createLabelElement(
|
||||
attackerTroops: number,
|
||||
defenderTroops: number,
|
||||
@@ -232,6 +280,8 @@ export class TroopAdvantageLayer implements Layer {
|
||||
el.style.backgroundColor = "rgba(0,0,0,0.55)";
|
||||
el.style.pointerEvents = "none";
|
||||
el.style.lineHeight = "1.3";
|
||||
// Smooth the label to its new position as the front line advances.
|
||||
el.style.transition = "transform 0.2s ease-out";
|
||||
this.updateLabelContent(el, attackerTroops, defenderTroops, isIncoming);
|
||||
this.container.appendChild(el);
|
||||
return el;
|
||||
@@ -185,7 +185,6 @@ export class AttacksDisplay extends LitElement implements Layer {
|
||||
if (playerView !== undefined) {
|
||||
if (playerView instanceof PlayerView) {
|
||||
const averagePosition = await playerView.attackAveragePosition(
|
||||
attack.attackerID,
|
||||
attack.id,
|
||||
);
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ export class SettingsModal extends LitElement implements Layer {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private onToggleTroopAdvantageLayerButtonClick() {
|
||||
this.userSettings.toggleTroopAdvantageLayer();
|
||||
private onToggleAttackingTroopsOverlayButtonClick() {
|
||||
this.userSettings.toggleAttackingTroopsOverlay();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@@ -416,19 +416,21 @@ export class SettingsModal extends LitElement implements Layer {
|
||||
|
||||
<button
|
||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||
@click="${this.onToggleTroopAdvantageLayerButtonClick}"
|
||||
@click="${this.onToggleAttackingTroopsOverlayButtonClick}"
|
||||
>
|
||||
<img src=${swordIcon} alt="swordIcon" width="20" height="20" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">
|
||||
${translateText("user_setting.troop_advantage_label")}
|
||||
${translateText(
|
||||
"user_setting.attacking_troops_overlay_label",
|
||||
)}
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${translateText("user_setting.troop_advantage_desc")}
|
||||
${translateText("user_setting.attacking_troops_overlay_desc")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-slate-400">
|
||||
${this.userSettings.troopAdvantageLayer()
|
||||
${this.userSettings.attackingTroopsOverlay()
|
||||
? translateText("user_setting.on")
|
||||
: translateText("user_setting.off")}
|
||||
</div>
|
||||
|
||||
+14
-4
@@ -254,10 +254,10 @@ export class GameRunner {
|
||||
} as PlayerBorderTiles;
|
||||
}
|
||||
|
||||
public attackClusterPositions(
|
||||
public attackFrontLinePositions(
|
||||
playerID: number,
|
||||
attackID?: string,
|
||||
): { id: string; clusters: { x: number; y: number }[] }[] {
|
||||
): { id: string; centers: { x: number; y: number }[] }[] {
|
||||
const player = this.game.playerBySmallID(playerID);
|
||||
if (!player.isPlayer()) {
|
||||
throw new Error(`player with id ${playerID} not found`);
|
||||
@@ -271,7 +271,13 @@ export class GameRunner {
|
||||
? allAttacks.filter((a) => a.id() === attackID)
|
||||
: allAttacks;
|
||||
|
||||
return attacks.map((a) => ({ id: a.id(), clusters: a.clusterPositions() }));
|
||||
return attacks.map((a) => ({
|
||||
id: a.id(),
|
||||
centers: a.frontLinePositions().map((tile) => ({
|
||||
x: this.game.map().x(tile),
|
||||
y: this.game.map().y(tile),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
public attackAveragePosition(
|
||||
@@ -291,7 +297,11 @@ export class GameRunner {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attack.averagePosition();
|
||||
// Use the largest front line's representative tile (index 0, sorted by frontLinePositions)
|
||||
const tiles = attack.frontLinePositions();
|
||||
if (tiles.length === 0) return null;
|
||||
const tile = tiles[0];
|
||||
return new Cell(this.game.map().x(tile), this.game.map().y(tile));
|
||||
}
|
||||
|
||||
public bestTransportShipSpawn(
|
||||
|
||||
+27
-44
@@ -1,4 +1,4 @@
|
||||
import { Attack, Cell, Player, TerraNullius } from "./Game";
|
||||
import { Attack, Player, TerraNullius } from "./Game";
|
||||
import { GameImpl } from "./GameImpl";
|
||||
import { TileRef } from "./GameMap";
|
||||
import { PlayerImpl } from "./PlayerImpl";
|
||||
@@ -97,19 +97,17 @@ export class AttackImpl implements Attack {
|
||||
}
|
||||
}
|
||||
|
||||
clusterPositions(): Cell[] {
|
||||
// Minimum border tiles for a cluster to get its own label.
|
||||
// Clusters smaller than this are suppressed (except we always keep the largest).
|
||||
const MIN_CLUSTER_SIZE = 30;
|
||||
|
||||
frontLinePositions(): TileRef[] {
|
||||
if (this._borderSize === 0) {
|
||||
const avg = this.averagePosition();
|
||||
return avg ? [avg] : [];
|
||||
const tile = this.sourceTile();
|
||||
return tile !== null ? [tile] : [];
|
||||
}
|
||||
|
||||
// Segments smaller than this are suppressed; the largest is always kept.
|
||||
const MIN_FRONT_LINE_LENGTH = 30;
|
||||
const map = this._mg.map();
|
||||
const visited = new Set<TileRef>();
|
||||
const clusters: { centroid: Cell; size: number }[] = [];
|
||||
const clusters: { representative: TileRef; size: number }[] = [];
|
||||
|
||||
for (const startTile of this._border) {
|
||||
if (visited.has(startTile)) continue;
|
||||
@@ -143,44 +141,29 @@ export class AttackImpl implements Attack {
|
||||
}
|
||||
}
|
||||
|
||||
clusters.push({
|
||||
centroid: new Cell(sumX / count, sumY / count),
|
||||
size: count,
|
||||
});
|
||||
}
|
||||
|
||||
// Keep only clusters above the minimum size.
|
||||
// Always keep the largest cluster so there's at least one label.
|
||||
const significant = clusters.filter((c) => c.size >= MIN_CLUSTER_SIZE);
|
||||
if (significant.length === 0) {
|
||||
const largest = clusters.reduce((a, b) => (b.size > a.size ? b : a));
|
||||
return [largest.centroid];
|
||||
}
|
||||
return significant.map((c) => c.centroid);
|
||||
}
|
||||
|
||||
averagePosition(): Cell | null {
|
||||
if (this._borderSize === 0) {
|
||||
if (this.sourceTile() === null) {
|
||||
// No border tiles and no source tile—return a default position or throw an error
|
||||
return null;
|
||||
// Pick the border tile nearest to the cluster centroid as representative
|
||||
const cx = sumX / count;
|
||||
const cy = sumY / count;
|
||||
let best = queue[0];
|
||||
let bestDist = Infinity;
|
||||
for (const tile of queue) {
|
||||
const dx = map.x(tile) - cx;
|
||||
const dy = map.y(tile) - cy;
|
||||
const dist = dx * dx + dy * dy;
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
best = tile;
|
||||
}
|
||||
}
|
||||
// No border tiles yet—use the source tile's location
|
||||
const tile: number = this.sourceTile()!;
|
||||
return new Cell(this._mg.map().x(tile), this._mg.map().y(tile));
|
||||
clusters.push({ representative: best, size: count });
|
||||
}
|
||||
|
||||
let averageX = 0;
|
||||
let averageY = 0;
|
||||
// Sort largest first so index 0 is always the main front line
|
||||
clusters.sort((a, b) => b.size - a.size);
|
||||
|
||||
for (const t of this._border) {
|
||||
averageX += this._mg.map().x(t);
|
||||
averageY += this._mg.map().y(t);
|
||||
}
|
||||
|
||||
averageX = averageX / this._borderSize;
|
||||
averageY = averageY / this._borderSize;
|
||||
|
||||
return new Cell(averageX, averageY);
|
||||
// Keep only clusters above the minimum size; always keep at least the largest
|
||||
const significant = clusters.filter((c) => c.size >= MIN_FRONT_LINE_LENGTH);
|
||||
const kept = significant.length > 0 ? significant : [clusters[0]];
|
||||
return kept.map((c) => c.representative);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,8 +470,7 @@ export interface Attack {
|
||||
removeBorderTile(tile: TileRef): void;
|
||||
clearBorder(): void;
|
||||
borderSize(): number;
|
||||
averagePosition(): Cell | null;
|
||||
clusterPositions(): Cell[];
|
||||
frontLinePositions(): TileRef[];
|
||||
}
|
||||
|
||||
export interface AllianceRequest {
|
||||
|
||||
@@ -453,18 +453,14 @@ export class PlayerView {
|
||||
return this.data.incomingAttacks;
|
||||
}
|
||||
|
||||
async attackAveragePosition(
|
||||
playerID: number,
|
||||
attackID: string,
|
||||
): Promise<Cell | null> {
|
||||
return this.game.worker.attackAveragePosition(playerID, attackID);
|
||||
async attackAveragePosition(attackID: string): Promise<Cell | null> {
|
||||
return this.game.worker.attackAveragePosition(this.smallID(), attackID);
|
||||
}
|
||||
|
||||
async attackClusterPositions(
|
||||
playerID: number,
|
||||
async attackFrontLinePositions(
|
||||
attackID?: string,
|
||||
): Promise<{ id: string; clusters: Cell[] }[]> {
|
||||
return this.game.worker.attackClusterPositions(playerID, attackID);
|
||||
): Promise<{ id: string; centers: Cell[] }[]> {
|
||||
return this.game.worker.attackFrontLinePositions(this.smallID(), attackID);
|
||||
}
|
||||
|
||||
units(...types: UnitType[]): UnitView[] {
|
||||
|
||||
@@ -89,12 +89,12 @@ export class UserSettings {
|
||||
return this.get("settings.territoryPatterns", true);
|
||||
}
|
||||
|
||||
troopAdvantageLayer() {
|
||||
return this.get("settings.troopAdvantageLayer", true);
|
||||
attackingTroopsOverlay() {
|
||||
return this.get("settings.attackingTroopsOverlay", true);
|
||||
}
|
||||
|
||||
toggleTroopAdvantageLayer() {
|
||||
this.set("settings.troopAdvantageLayer", !this.troopAdvantageLayer());
|
||||
toggleAttackingTroopsOverlay() {
|
||||
this.set("settings.attackingTroopsOverlay", !this.attackingTroopsOverlay());
|
||||
}
|
||||
|
||||
cursorCostLabel() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
||||
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
|
||||
import {
|
||||
AttackAveragePositionResultMessage,
|
||||
AttackClusterPositionsResultMessage,
|
||||
AttackFrontLinePositionsResultMessage,
|
||||
InitializedMessage,
|
||||
MainThreadMessage,
|
||||
PlayerActionsResultMessage,
|
||||
@@ -265,28 +265,28 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
throw error;
|
||||
}
|
||||
break;
|
||||
case "attack_cluster_positions":
|
||||
case "attack_front_line_positions":
|
||||
if (!gameRunner) {
|
||||
throw new Error("Game runner not initialized");
|
||||
}
|
||||
|
||||
try {
|
||||
const attacks = (await gameRunner).attackClusterPositions(
|
||||
const attacks = (await gameRunner).attackFrontLinePositions(
|
||||
message.playerID,
|
||||
message.attackID,
|
||||
);
|
||||
sendMessage({
|
||||
type: "attack_cluster_positions_result",
|
||||
type: "attack_front_line_positions_result",
|
||||
id: message.id,
|
||||
attacks,
|
||||
} as AttackClusterPositionsResultMessage);
|
||||
} as AttackFrontLinePositionsResultMessage);
|
||||
} catch (error) {
|
||||
console.error("Failed to get attack cluster positions:", error);
|
||||
console.error("Failed to get attack front line centers:", error);
|
||||
sendMessage({
|
||||
type: "attack_cluster_positions_result",
|
||||
type: "attack_front_line_positions_result",
|
||||
id: message.id,
|
||||
attacks: [],
|
||||
} as AttackClusterPositionsResultMessage);
|
||||
} as AttackFrontLinePositionsResultMessage);
|
||||
}
|
||||
break;
|
||||
case "transport_ship_spawn":
|
||||
|
||||
@@ -266,10 +266,10 @@ export class WorkerClient {
|
||||
});
|
||||
}
|
||||
|
||||
attackClusterPositions(
|
||||
attackFrontLinePositions(
|
||||
playerID: number,
|
||||
attackID?: string,
|
||||
): Promise<{ id: string; clusters: Cell[] }[]> {
|
||||
): Promise<{ id: string; centers: Cell[] }[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
reject(new Error("Worker not initialized"));
|
||||
@@ -280,15 +280,15 @@ export class WorkerClient {
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this.messageHandlers.delete(messageId);
|
||||
reject(new Error("attack_cluster_positions request timed out"));
|
||||
reject(new Error("attack_front_line_positions request timed out"));
|
||||
}, 5000);
|
||||
|
||||
this.messageHandlers.set(messageId, (message) => {
|
||||
clearTimeout(timeout);
|
||||
if (message.type !== "attack_cluster_positions_result") {
|
||||
if (message.type !== "attack_front_line_positions_result") {
|
||||
reject(
|
||||
new Error(
|
||||
`Unexpected message type for attackClusterPositions: ${message.type}`,
|
||||
`Unexpected message type for attackFrontLinePositions: ${message.type}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -296,13 +296,13 @@ export class WorkerClient {
|
||||
resolve(
|
||||
message.attacks.map((a) => ({
|
||||
id: a.id,
|
||||
clusters: a.clusters.map((c) => new Cell(c.x, c.y)),
|
||||
centers: a.centers.map((c) => new Cell(c.x, c.y)),
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
this.worker.postMessage({
|
||||
type: "attack_cluster_positions",
|
||||
type: "attack_front_line_positions",
|
||||
id: messageId,
|
||||
playerID,
|
||||
attackID,
|
||||
|
||||
@@ -26,8 +26,8 @@ export type WorkerMessageType =
|
||||
| "player_border_tiles_result"
|
||||
| "attack_average_position"
|
||||
| "attack_average_position_result"
|
||||
| "attack_cluster_positions"
|
||||
| "attack_cluster_positions_result"
|
||||
| "attack_front_line_positions"
|
||||
| "attack_front_line_positions_result"
|
||||
| "transport_ship_spawn"
|
||||
| "transport_ship_spawn_result";
|
||||
|
||||
@@ -122,15 +122,16 @@ export interface AttackAveragePositionResultMessage extends BaseWorkerMessage {
|
||||
y: number | null;
|
||||
}
|
||||
|
||||
export interface AttackClusterPositionsMessage extends BaseWorkerMessage {
|
||||
type: "attack_cluster_positions";
|
||||
export interface AttackFrontLinePositionsMessage extends BaseWorkerMessage {
|
||||
type: "attack_front_line_positions";
|
||||
playerID: number;
|
||||
attackID?: string;
|
||||
}
|
||||
|
||||
export interface AttackClusterPositionsResultMessage extends BaseWorkerMessage {
|
||||
type: "attack_cluster_positions_result";
|
||||
attacks: { id: string; clusters: { x: number; y: number }[] }[];
|
||||
export interface AttackFrontLinePositionsResultMessage
|
||||
extends BaseWorkerMessage {
|
||||
type: "attack_front_line_positions_result";
|
||||
attacks: { id: string; centers: { x: number; y: number }[] }[];
|
||||
}
|
||||
|
||||
export interface TransportShipSpawnMessage extends BaseWorkerMessage {
|
||||
@@ -153,7 +154,7 @@ export type MainThreadMessage =
|
||||
| PlayerProfileMessage
|
||||
| PlayerBorderTilesMessage
|
||||
| AttackAveragePositionMessage
|
||||
| AttackClusterPositionsMessage
|
||||
| AttackFrontLinePositionsMessage
|
||||
| TransportShipSpawnMessage;
|
||||
|
||||
// Message send from worker
|
||||
@@ -166,5 +167,5 @@ export type WorkerMessage =
|
||||
| PlayerProfileResultMessage
|
||||
| PlayerBorderTilesResultMessage
|
||||
| AttackAveragePositionResultMessage
|
||||
| AttackClusterPositionsResultMessage
|
||||
| AttackFrontLinePositionsResultMessage
|
||||
| TransportShipSpawnResultMessage;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
troopAttackColor,
|
||||
troopDefenceColor,
|
||||
} from "../../../../src/client/graphics/layers/TroopAdvantageLayer";
|
||||
} from "../../../../src/client/graphics/layers/AttackingTroopsOverlay";
|
||||
|
||||
describe("troopAttackColor", () => {
|
||||
test("returns green when attacker has more troops", () => {
|
||||
Reference in New Issue
Block a user