diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index fb67b0bad..1cf69ed36 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -33,7 +33,8 @@ export type Intent = | BuildUnitIntent | EmbargoIntent | QuickChatIntent - | MoveWarshipIntent; + | MoveWarshipIntent + | MarkIdleIntent; export type AttackIntent = z.infer; export type CancelAttackIntent = z.infer; @@ -56,6 +57,7 @@ export type TargetTroopRatioIntent = z.infer< export type BuildUnitIntent = z.infer; export type MoveWarshipIntent = z.infer; export type QuickChatIntent = z.infer; +export type MarkIdleIntent = z.infer; export type Turn = z.infer; export type GameConfig = z.infer; @@ -166,6 +168,7 @@ const BaseIntentSchema = z.object({ "attack", "cancel_attack", "spawn", + "mark_idle", "boat", "cancel_boat", "name", @@ -290,10 +293,16 @@ export const QuickChatIntentSchema = BaseIntentSchema.extend({ variables: z.record(SafeString).optional(), }); +export const MarkIdleIntentSchema = BaseIntentSchema.extend({ + type: z.literal("mark_idle"), + isIdle: z.boolean(), +}); + const IntentSchema = z.union([ AttackIntentSchema, CancelAttackIntentSchema, SpawnIntentSchema, + MarkIdleIntentSchema, BoatAttackIntentSchema, CancelBoatIntentSchema, AllianceRequestIntentSchema, diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index 0cd160cb0..4b85a5d7c 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -15,6 +15,7 @@ import { DonateTroopsExecution } from "./DonateTroopExecution"; import { EmbargoExecution } from "./EmbargoExecution"; import { EmojiExecution } from "./EmojiExecution"; import { FakeHumanExecution } from "./FakeHumanExecution"; +import { MarkIdleExecution } from "./MarkIdleExecution"; import { MoveWarshipExecution } from "./MoveWarshipExecution"; import { NoOpExecution } from "./NoOpExecution"; import { QuickChatExecution } from "./QuickChatExecution"; @@ -120,6 +121,8 @@ export class Executor { intent.quickChatKey, intent.variables ?? {}, ); + case "mark_idle": + return new MarkIdleExecution(playerID, intent.isIdle); default: throw new Error(`intent type ${intent} not found`); } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index eb52a6b10..ca160d09f 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -433,6 +433,9 @@ export interface Player { largestClusterBoundingBox: { min: Cell; max: Cell } | null; lastTileChange(): Tick; + isIdle(): boolean; + markIdle(isIdle: boolean): void; + hasSpawned(): boolean; setHasSpawned(hasSpawned: boolean): void; diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts index 7f3acd1cd..0e79cb395 100644 --- a/src/core/game/GameUpdates.ts +++ b/src/core/game/GameUpdates.ts @@ -102,6 +102,7 @@ export interface PlayerUpdate { smallID: number; playerType: PlayerType; isAlive: boolean; + isIdle: boolean; tilesOwned: number; gold: number; population: number; diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index b32170dc7..a0b1ffe09 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -292,6 +292,9 @@ export class PlayerView { hasSpawned(): boolean { return this.data.hasSpawned; } + isIdle(): boolean { + return this.data.isIdle; + } } export class GameView implements GameMap { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 49aa91777..70eb9b67b 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -99,6 +99,7 @@ export class PlayerImpl implements Player { public _outgoingLandAttacks: Attack[] = []; private _hasSpawned = false; + private _isIdle = false; constructor( private mg: GameImpl, @@ -136,6 +137,7 @@ export class PlayerImpl implements Player { smallID: this.smallID(), playerType: this.type(), isAlive: this.isAlive(), + isIdle: this.isIdle(), tilesOwned: this.numTilesOwned(), gold: Number(this._gold), population: this.population(), @@ -925,6 +927,14 @@ export class PlayerImpl implements Player { return this._lastTileChange; } + isIdle(): boolean { + return this._isIdle; + } + + markIdle(isIdle: boolean): void { + this._isIdle = isIdle; + } + hash(): number { return ( simpleHash(this.id()) * (this.population() + this.numTilesOwned()) + diff --git a/src/server/Client.ts b/src/server/Client.ts index 216150dbf..375fbd890 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -4,7 +4,9 @@ import { Tick } from "../core/game/Game"; import { ClientID } from "../core/Schemas"; export class Client { - public lastPing: number; + public lastPing: number = Date.now(); + public lastAction: number = Date.now(); + public isIdle: boolean = false; public hashes: Map = new Map(); diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index caf67e0a1..13c096f5b 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -533,6 +533,15 @@ export class GameServer { } } + private markClientIdle(client: Client, isIdle: boolean) { + client.isIdle = isIdle; + this.addIntent({ + type: "mark_idle", + clientID: client.clientID, + isIdle: isIdle, + }); + } + private archiveGame() { this.log.info("archiving game", { gameID: this.id,