diff --git a/resources/images/AnchorIcon.png b/resources/images/AnchorIcon.png
new file mode 100644
index 000000000..75a031b75
Binary files /dev/null and b/resources/images/AnchorIcon.png differ
diff --git a/resources/images/PortIcon.svg b/resources/images/PortIcon.svg
new file mode 100644
index 000000000..b3e417cd6
--- /dev/null
+++ b/resources/images/PortIcon.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/src/client/Transport.ts b/src/client/Transport.ts
index 0098b54d6..208a7f098 100644
--- a/src/client/Transport.ts
+++ b/src/client/Transport.ts
@@ -1,7 +1,7 @@
import { Config } from "../core/configuration/Config"
import { EventBus, GameEvent } from "../core/EventBus"
-import { AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType, Tile } from "../core/game/Game"
-import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, CreateDestroyerIntent, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas"
+import { AllianceRequest, AllPlayers, Cell, Item, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game"
+import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, BuildUnitIntentSchema, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas"
import { LocalServer } from "./LocalServer"
@@ -47,8 +47,9 @@ export class SendBoatAttackIntentEvent implements GameEvent {
) { }
}
-export class SendCreateDestroyerIntentEvent implements GameEvent {
+export class BuildUnitIntentEvent implements GameEvent {
constructor(
+ public readonly unit: UnitType,
public readonly cell: Cell,
) { }
}
@@ -121,7 +122,7 @@ export class Transport {
this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e))
this.eventBus.on(SendNukeIntentEvent, (e) => this.onSendNukeIntent(e))
this.eventBus.on(SendSetTargetTroopRatioEvent, (e) => this.onSendSetTargetTroopRatioEvent(e))
- this.eventBus.on(SendCreateDestroyerIntentEvent, (e) => this.onCreateDestroyerIntent(e))
+ this.eventBus.on(BuildUnitIntentEvent, (e) => this.onCreateDestroyerIntent(e))
}
connect(onconnect: () => void, onmessage: (message: ServerMessage) => void) {
@@ -321,11 +322,12 @@ export class Transport {
})
}
- private onCreateDestroyerIntent(event: SendCreateDestroyerIntentEvent) {
+ private onCreateDestroyerIntent(event: BuildUnitIntentEvent) {
this.sendIntent({
- type: "create_destroyer",
+ type: "build_unit",
clientID: this.clientID,
player: this.playerID,
+ unit: event.unit,
x: event.cell.x,
y: event.cell.y,
})
diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts
index c2faf248e..f0ebe0301 100644
--- a/src/client/graphics/layers/UnitLayer.ts
+++ b/src/client/graphics/layers/UnitLayer.ts
@@ -5,6 +5,8 @@ import { bfs, dist, euclDist } from "../../../core/Util";
import { Layer } from "./Layer";
import { EventBus } from "../../../core/EventBus";
+import anchorIncon from '../../../../../resources/images/AnchorIcon.png';
+
export class UnitLayer implements Layer {
private canvas: HTMLCanvasElement
private context: CanvasRenderingContext2D
diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts
index 21fb6e44f..43ac74632 100644
--- a/src/client/graphics/layers/radial/BuildMenu.ts
+++ b/src/client/graphics/layers/radial/BuildMenu.ts
@@ -1,11 +1,12 @@
import { LitElement, html, css } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { EventBus } from '../../../../core/EventBus';
-import { Cell, Game, Item, Items, Player } from '../../../../core/game/Game';
-import { SendCreateDestroyerIntentEvent, SendNukeIntentEvent } from '../../../Transport';
+import { Cell, Game, Item, Items, Player, UnitType } from '../../../../core/game/Game';
+import { BuildUnitIntentEvent as BuildItemIntentEvent, BuildUnitIntentEvent, SendNukeIntentEvent } from '../../../Transport';
import nukeIcon from '../../../../../resources/images/NukeIconWhite.svg';
import destroyerIcon from '../../../../../resources/images/DestroyerIconWhite.svg';
import goldCoinIcon from '../../../../../resources/images/GoldCoinIcon.svg';
+import portIcon from '../../../../../resources/images/PortIcon.svg';
import { renderNumber } from '../../Utils';
import { ContextMenuEvent } from '../../../InputHandler';
@@ -18,7 +19,7 @@ const buildTable: BuildItem[][] = [
[
{ item: Items.Nuke, icon: nukeIcon },
{ item: Items.Destroyer, icon: destroyerIcon },
- // { id: 'battleship', name: 'Battleship', icon: '🚢', cost: 500, buildTime: 20 }
+ { item: Items.Port, icon: portIcon }
]
];
@@ -154,8 +155,9 @@ export class BuildMenu extends LitElement {
this.eventBus.emit(new SendNukeIntentEvent(this.myPlayer, this.clickedCell, null))
break
case "Destroyer":
- this.eventBus.emit(new SendCreateDestroyerIntentEvent(this.clickedCell))
-
+ this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Destroyer, this.clickedCell))
+ case "Port":
+ this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Port, this.clickedCell))
}
this.hideMenu()
};
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index 8d4b1be04..11133fbd6 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { Difficulty, GameMap, PlayerType } from './game/Game';
+import { Difficulty, GameMap, PlayerType, UnitType } from './game/Game';
export type GameID = string
export type ClientID = string
@@ -15,7 +15,7 @@ export type Intent = SpawnIntent
| DonateIntent
| NukeIntent
| TargetTroopRatioIntent
- | CreateDestroyerIntent
+ | BuildUnitIntent
export type AttackIntent = z.infer
export type SpawnIntent = z.infer
@@ -28,7 +28,7 @@ export type EmojiIntent = z.infer
export type DonateIntent = z.infer
export type NukeIntent = z.infer
export type TargetTroopRatioIntent = z.infer
-export type CreateDestroyerIntent = z.infer
+export type BuildUnitIntent = z.infer
export type Turn = z.infer
export type GameConfig = z.infer
@@ -69,7 +69,7 @@ const EmojiSchema = z.string().refine(
);
// Zod schemas
const BaseIntentSchema = z.object({
- type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji', 'nuke', 'troop_ratio', 'create_destroyer']),
+ type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji', 'nuke', 'troop_ratio', 'build_unit']),
clientID: z.string(),
});
@@ -161,9 +161,10 @@ export const TargetTroopRatioIntentSchema = BaseIntentSchema.extend({
ratio: z.number().min(0).max(1),
})
-export const CreateDestroyerIntentSchema = BaseIntentSchema.extend({
- type: z.literal('create_destroyer'),
+export const BuildUnitIntentSchema = BaseIntentSchema.extend({
+ type: z.literal('build_unit'),
player: z.string(),
+ unit: z.nativeEnum(UnitType),
x: z.number(),
y: z.number(),
})
@@ -180,7 +181,7 @@ const IntentSchema = z.union([
DonateIntentSchema,
NukeIntentSchema,
TargetTroopRatioIntentSchema,
- CreateDestroyerIntentSchema,
+ BuildUnitIntentSchema,
]);
const TurnSchema = z.object({
diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts
index 49575021e..1f860b57a 100644
--- a/src/core/execution/ExecutionManager.ts
+++ b/src/core/execution/ExecutionManager.ts
@@ -1,4 +1,4 @@
-import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, AllianceRequestReplyEvent, Difficulty } from "../game/Game";
+import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, AllianceRequestReplyEvent, Difficulty, UnitType } from "../game/Game";
import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
import { AttackExecution } from "./AttackExecution";
import { SpawnExecution } from "./SpawnExecution";
@@ -17,6 +17,7 @@ import { DonateExecution } from "./DonateExecution";
import { NukeExecution } from "./NukeExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { DestroyerExecution } from "./DestroyerExecution";
+import { PortExecution } from "./PortExecution";
@@ -81,8 +82,15 @@ export class Executor {
return new NukeExecution(intent.sender, new Cell(intent.x, intent.y), intent.magnitude);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
- case "create_destroyer":
- return new DestroyerExecution(intent.player, new Cell(intent.x, intent.y))
+ case "build_unit":
+ switch (intent.unit) {
+ case UnitType.Destroyer:
+ return new DestroyerExecution(intent.player, new Cell(intent.x, intent.y))
+ case UnitType.Port:
+ return new PortExecution(intent.player, new Cell(intent.x, intent.y))
+ default:
+ throw Error(`unit type ${intent.unit} not supported`)
+ }
default:
throw new Error(`intent type ${intent} not found`);
}
diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts
new file mode 100644
index 000000000..73504f4e0
--- /dev/null
+++ b/src/core/execution/PortExecution.ts
@@ -0,0 +1,31 @@
+import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, PlayerID } from "../game/Game";
+
+export class PortExecution implements Execution {
+
+ private active = true
+
+ constructor(
+ private _owner: PlayerID,
+ private cell: Cell
+ ) { }
+
+
+ init(mg: MutableGame, ticks: number): void {
+ }
+
+ tick(ticks: number): void {
+ }
+
+ owner(): MutablePlayer {
+ return null
+ }
+
+ isActive(): boolean {
+ return this.active
+ }
+
+ activeDuringSpawnPhase(): boolean {
+ return false
+ }
+
+}
\ No newline at end of file
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 7a20e73cd..0976b5744 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -27,7 +27,8 @@ export enum GameMap {
export enum UnitType {
TransportShip,
- Destroyer
+ Destroyer,
+ Port
}
export class Item {
@@ -36,7 +37,8 @@ export class Item {
export const Items = {
Nuke: new Item("Nuke", 1_000_000),
- Destroyer: new Item("Destroyer", 10)
+ Destroyer: new Item("Destroyer", 10),
+ Port: new Item("Port", 10)
} as const;
export class Nation {