mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 14:10:45 +00:00
Exclude Transport Ship in many cases
This commit is contained in:
@@ -13,7 +13,11 @@ import {
|
||||
import { createPartialGameRecord, replacer } from "../core/Util";
|
||||
import { ServerConfig } from "../core/configuration/Config";
|
||||
import { getConfig } from "../core/configuration/ConfigLoader";
|
||||
import { PlayerActions, UnitType } from "../core/game/Game";
|
||||
import {
|
||||
BuildableUnitsTransportShip,
|
||||
PlayerActions,
|
||||
UnitType,
|
||||
} from "../core/game/Game";
|
||||
import { TileRef } from "../core/game/GameMap";
|
||||
import { GameMapLoader } from "../core/game/GameMapLoader";
|
||||
import {
|
||||
@@ -557,19 +561,20 @@ export class ClientGameRunner {
|
||||
if (myPlayer === null) return;
|
||||
this.myPlayer = myPlayer;
|
||||
}
|
||||
this.myPlayer.actions(tile).then((actions) => {
|
||||
if (this.myPlayer === null) return;
|
||||
if (actions.canAttack) {
|
||||
this.eventBus.emit(
|
||||
new SendAttackIntentEvent(
|
||||
this.gameView.owner(tile).id(),
|
||||
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
|
||||
),
|
||||
);
|
||||
} else if (this.canAutoBoat(actions, tile)) {
|
||||
this.sendBoatAttackIntent(tile);
|
||||
}
|
||||
});
|
||||
this.myPlayer
|
||||
.actions(tile, BuildableUnitsTransportShip.Include)
|
||||
.then((actions) => {
|
||||
if (actions.canAttack) {
|
||||
this.eventBus.emit(
|
||||
new SendAttackIntentEvent(
|
||||
this.gameView.owner(tile).id(),
|
||||
this.myPlayer!.troops() * this.renderer.uiState.attackRatio,
|
||||
),
|
||||
);
|
||||
} else if (this.canAutoBoat(actions, tile)) {
|
||||
this.sendBoatAttackIntent(tile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private autoUpgradeEvent(event: AutoUpgradeEvent) {
|
||||
@@ -654,11 +659,13 @@ export class ClientGameRunner {
|
||||
this.myPlayer = myPlayer;
|
||||
}
|
||||
|
||||
this.myPlayer.actions(tile).then((actions) => {
|
||||
if (this.canBoatAttack(actions) !== false) {
|
||||
this.sendBoatAttackIntent(tile);
|
||||
}
|
||||
});
|
||||
this.myPlayer
|
||||
.actions(tile, BuildableUnitsTransportShip.Only)
|
||||
.then((actions) => {
|
||||
if (this.canBoatAttack(actions) !== false) {
|
||||
this.sendBoatAttackIntent(tile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private doGroundAttackUnderCursor(): void {
|
||||
@@ -674,12 +681,11 @@ export class ClientGameRunner {
|
||||
}
|
||||
|
||||
this.myPlayer.actions(tile).then((actions) => {
|
||||
if (this.myPlayer === null) return;
|
||||
if (actions.canAttack) {
|
||||
this.eventBus.emit(
|
||||
new SendAttackIntentEvent(
|
||||
this.gameView.owner(tile).id(),
|
||||
this.myPlayer.troops() * this.renderer.uiState.attackRatio,
|
||||
this.myPlayer!.troops() * this.renderer.uiState.attackRatio,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { PlayerActions } from "../../../core/game/Game";
|
||||
import {
|
||||
BuildableUnitsTransportShip,
|
||||
PlayerActions,
|
||||
} from "../../../core/game/Game";
|
||||
import { TileRef } from "../../../core/game/GameMap";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
@@ -162,7 +165,7 @@ export class MainRadialMenu extends LitElement implements Layer {
|
||||
if (!this.radialMenu.isMenuVisible() || this.clickedTile === null) return;
|
||||
this.game
|
||||
.myPlayer()!
|
||||
.actions(this.clickedTile)
|
||||
.actions(this.clickedTile, BuildableUnitsTransportShip.Include)
|
||||
.then((actions) => {
|
||||
this.updatePlayerActions(
|
||||
this.game.myPlayer()!,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { WinCheckExecution } from "./execution/WinCheckExecution";
|
||||
import {
|
||||
AllPlayers,
|
||||
Attack,
|
||||
BuildableUnitsTransportShip,
|
||||
Cell,
|
||||
Game,
|
||||
GameUpdates,
|
||||
@@ -195,13 +196,14 @@ export class GameRunner {
|
||||
playerID: PlayerID,
|
||||
x?: number,
|
||||
y?: number,
|
||||
transportShip: BuildableUnitsTransportShip = BuildableUnitsTransportShip.Exclude,
|
||||
): PlayerActions {
|
||||
const player = this.game.player(playerID);
|
||||
const tile =
|
||||
x !== undefined && y !== undefined ? this.game.ref(x, y) : null;
|
||||
const actions = {
|
||||
canAttack: tile !== null && player.canAttack(tile),
|
||||
buildableUnits: player.buildableUnits(tile),
|
||||
buildableUnits: player.buildableUnits(tile, transportShip),
|
||||
canSendEmojiAllPlayers: player.canSendEmoji(AllPlayers),
|
||||
canEmbargoAll: player.canEmbargoAll(),
|
||||
} as PlayerActions;
|
||||
|
||||
+10
-1
@@ -641,7 +641,10 @@ export interface Player {
|
||||
unitCount(type: UnitType): number;
|
||||
unitsConstructed(type: UnitType): number;
|
||||
unitsOwned(type: UnitType): number;
|
||||
buildableUnits(tile: TileRef | null): BuildableUnit[];
|
||||
buildableUnits(
|
||||
tile: TileRef | null,
|
||||
transportShip?: BuildableUnitsTransportShip,
|
||||
): BuildableUnit[];
|
||||
canBuild(type: UnitType, targetTile: TileRef): TileRef | false;
|
||||
buildUnit<T extends UnitType>(
|
||||
type: T,
|
||||
@@ -858,6 +861,12 @@ export interface PlayerActions {
|
||||
interaction?: PlayerInteraction;
|
||||
}
|
||||
|
||||
export enum BuildableUnitsTransportShip {
|
||||
Exclude = "e", // default when undefined to save data between threads
|
||||
Include = "i",
|
||||
Only = "o",
|
||||
}
|
||||
|
||||
export interface BuildableUnit {
|
||||
canBuild: TileRef | false;
|
||||
// unit id of the existing unit that can be upgraded, or false if it cannot be upgraded.
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ClientID, GameID, Player, PlayerCosmetics } from "../Schemas";
|
||||
import { createRandomName } from "../Util";
|
||||
import { WorkerClient } from "../worker/WorkerClient";
|
||||
import {
|
||||
BuildableUnitsTransportShip,
|
||||
Cell,
|
||||
EmojiMessage,
|
||||
GameUpdates,
|
||||
@@ -403,11 +404,15 @@ export class PlayerView {
|
||||
return { hasEmbargo, hasFriendly };
|
||||
}
|
||||
|
||||
async actions(tile?: TileRef): Promise<PlayerActions> {
|
||||
async actions(
|
||||
tile?: TileRef,
|
||||
transportShip?: BuildableUnitsTransportShip,
|
||||
): Promise<PlayerActions> {
|
||||
return this.game.worker.playerInteraction(
|
||||
this.id(),
|
||||
tile && this.game.x(tile),
|
||||
tile && this.game.y(tile),
|
||||
transportShip,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
AllPlayers,
|
||||
Attack,
|
||||
BuildableUnit,
|
||||
BuildableUnitsTransportShip,
|
||||
Cell,
|
||||
ColoredTeams,
|
||||
Embargo,
|
||||
@@ -965,10 +966,21 @@ export class PlayerImpl implements Player {
|
||||
this.recordUnitConstructed(unit.type());
|
||||
}
|
||||
|
||||
public buildableUnits(tile: TileRef | null): BuildableUnit[] {
|
||||
public buildableUnits(
|
||||
tile: TileRef | null,
|
||||
transportShip: BuildableUnitsTransportShip = BuildableUnitsTransportShip.Exclude,
|
||||
): BuildableUnit[] {
|
||||
const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : [];
|
||||
|
||||
return PlayerBuildableTypes.map((u) => {
|
||||
return PlayerBuildableTypes.filter((u) => {
|
||||
if (transportShip === BuildableUnitsTransportShip.Exclude) {
|
||||
return u !== UnitType.TransportShip;
|
||||
}
|
||||
if (transportShip === BuildableUnitsTransportShip.Only) {
|
||||
return u === UnitType.TransportShip;
|
||||
}
|
||||
return true; // Include TransportShip
|
||||
}).map((u) => {
|
||||
const cost = this.mg.config().unitInfo(u).cost(this.mg, this);
|
||||
let canUpgrade: number | false = false;
|
||||
let canBuild: TileRef | false = false;
|
||||
|
||||
@@ -94,6 +94,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
|
||||
message.playerID,
|
||||
message.x,
|
||||
message.y,
|
||||
message.transportShip,
|
||||
);
|
||||
sendMessage({
|
||||
type: "player_actions_result",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BuildableUnitsTransportShip,
|
||||
Cell,
|
||||
PlayerActions,
|
||||
PlayerBorderTiles,
|
||||
@@ -164,6 +165,7 @@ export class WorkerClient {
|
||||
playerID: PlayerID,
|
||||
x?: number,
|
||||
y?: number,
|
||||
transportShip?: BuildableUnitsTransportShip,
|
||||
): Promise<PlayerActions> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isInitialized) {
|
||||
@@ -185,9 +187,10 @@ export class WorkerClient {
|
||||
this.worker.postMessage({
|
||||
type: "player_actions",
|
||||
id: messageId,
|
||||
playerID: playerID,
|
||||
playerID,
|
||||
x: x,
|
||||
y: y,
|
||||
...(transportShip !== undefined ? { transportShip } : {}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BuildableUnitsTransportShip,
|
||||
PlayerActions,
|
||||
PlayerBorderTiles,
|
||||
PlayerID,
|
||||
@@ -62,6 +63,7 @@ export interface PlayerActionsMessage extends BaseWorkerMessage {
|
||||
playerID: PlayerID;
|
||||
x?: number;
|
||||
y?: number;
|
||||
transportShip?: BuildableUnitsTransportShip;
|
||||
}
|
||||
|
||||
export interface PlayerActionsResultMessage extends BaseWorkerMessage {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import Benchmark from "benchmark";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { PlayerInfo, PlayerType } from "../../src/core/game/Game";
|
||||
import { setup } from "../util/Setup";
|
||||
|
||||
// Setup dense territory scenario (large target area)
|
||||
// We use a dense territory to ensure that checks like port spawning, etc. have many candidates if applicable.
|
||||
// buildableUnits(null) checks global availability which might verify conditions across the map.
|
||||
|
||||
const game = await setup(
|
||||
"big_plains",
|
||||
{
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
},
|
||||
[new PlayerInfo("player", PlayerType.Human, "client_id1", "player_id")],
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
const player = game.player("player_id");
|
||||
|
||||
// Conquer a significant portion of the map to have valid spawn locations for things like ports (if near water)
|
||||
// and to ensure we have "validStructureSpawnTiles" cached or calculated.
|
||||
for (let x = 0; x < 50; x++) {
|
||||
for (let y = 0; y < 50; y++) {
|
||||
const tile = game.ref(x, y);
|
||||
if (game.map().isLand(tile)) {
|
||||
player.conquer(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let specificLandTile = game.ref(25, 25);
|
||||
let specificWaterTile = game.ref(0, 0);
|
||||
|
||||
// Search for a water tile if 0,0 is not water
|
||||
if (!game.map().isWater(specificWaterTile)) {
|
||||
for (let x = 0; x < game.map().width(); x++) {
|
||||
for (let y = 0; y < game.map().height(); y++) {
|
||||
const t = game.ref(x, y);
|
||||
if (game.map().isWater(t)) {
|
||||
specificWaterTile = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (game.map().isWater(specificWaterTile)) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure land tile is actually land
|
||||
if (!game.map().isLand(specificLandTile)) {
|
||||
for (let x = 0; x < game.map().width(); x++) {
|
||||
for (let y = 0; y < game.map().height(); y++) {
|
||||
const t = game.ref(x, y);
|
||||
if (game.map().isLand(t)) {
|
||||
specificLandTile = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (game.map().isLand(specificLandTile)) break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Benchmarks ready.");
|
||||
console.log("Land tile:", specificLandTile.toString());
|
||||
console.log("Water tile:", specificWaterTile.toString());
|
||||
|
||||
// Warmup
|
||||
player.buildableUnits(null);
|
||||
player.buildableUnits(specificLandTile);
|
||||
|
||||
const results: string[] = [];
|
||||
|
||||
new Benchmark.Suite()
|
||||
.add("buildableUnits(null)", () => {
|
||||
player.buildableUnits(null);
|
||||
})
|
||||
.add("buildableUnits(landTile)", () => {
|
||||
player.buildableUnits(specificLandTile);
|
||||
})
|
||||
.add("buildableUnits(waterTile)", () => {
|
||||
player.buildableUnits(specificWaterTile);
|
||||
})
|
||||
/*
|
||||
Future: If buildableUnits accepts a filter argument (e.g. ignore TransportShip), add cases here for performance comparison:
|
||||
|
||||
// Test case: Check only TransportShip (e.g. for right-click on water)
|
||||
.add("buildableUnits(landTile, ONLY_TRANSPORT)", () => {
|
||||
// @ts-ignore
|
||||
player.buildableUnits(specificLandTile, { only: UnitType.TransportShip });
|
||||
})
|
||||
|
||||
// Test case: Check everything EXCEPT TransportShip (e.g. build menu on land)
|
||||
.add("buildableUnits(landTile, NO_TRANSPORT)", () => {
|
||||
// @ts-ignore
|
||||
player.buildableUnits(specificLandTile, { exclude: UnitType.TransportShip });
|
||||
})
|
||||
*/
|
||||
.on("cycle", (event: any) => {
|
||||
results.push(String(event.target));
|
||||
})
|
||||
.on("complete", () => {
|
||||
console.log("\n=== buildableUnits Performance Benchmark Results ===");
|
||||
for (const result of results) {
|
||||
console.log(result);
|
||||
}
|
||||
})
|
||||
.run({ async: true });
|
||||
Reference in New Issue
Block a user