@@ -372,6 +379,7 @@
+
;
@@ -50,6 +52,7 @@ export type TargetTroopRatioIntent = z.infer<
>;
export type BuildUnitIntent = z.infer;
export type MoveWarshipIntent = z.infer;
+export type QuickChatIntent = z.infer;
export type Turn = z.infer;
export type GameConfig = z.infer;
@@ -270,6 +273,19 @@ export const MoveWarshipIntentSchema = BaseIntentSchema.extend({
tile: z.number(),
});
+export const QuickChatKeySchema = z.enum(
+ Object.entries(quickChatData).flatMap(([category, entries]) =>
+ entries.map((entry) => `${category}.${entry.key}`),
+ ) as [string, ...string[]],
+);
+
+export const QuickChatIntentSchema = BaseIntentSchema.extend({
+ type: z.literal("quick_chat"),
+ recipient: ID,
+ quickChatKey: QuickChatKeySchema,
+ variables: z.record(SafeString).optional(),
+});
+
const IntentSchema = z.union([
AttackIntentSchema,
CancelAttackIntentSchema,
@@ -286,6 +302,7 @@ const IntentSchema = z.union([
BuildUnitIntentSchema,
EmbargoIntentSchema,
MoveWarshipIntentSchema,
+ QuickChatIntentSchema,
]);
export const TurnSchema = z.object({
diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts
index 73bfb97e9..15c3c3241 100644
--- a/src/core/execution/ExecutionManager.ts
+++ b/src/core/execution/ExecutionManager.ts
@@ -15,6 +15,7 @@ import { EmojiExecution } from "./EmojiExecution";
import { FakeHumanExecution } from "./FakeHumanExecution";
import { MoveWarshipExecution } from "./MoveWarshipExecution";
import { NoOpExecution } from "./NoOpExecution";
+import { QuickChatExecution } from "./QuickChatExecution";
import { RetreatExecution } from "./RetreatExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { SpawnExecution } from "./SpawnExecution";
@@ -108,6 +109,13 @@ export class Executor {
this.mg.ref(intent.x, intent.y),
intent.unit,
);
+ case "quick_chat":
+ return new QuickChatExecution(
+ playerID,
+ intent.recipient,
+ intent.quickChatKey,
+ intent.variables ?? {},
+ );
default:
throw new Error(`intent type ${intent} not found`);
}
diff --git a/src/core/execution/QuickChatExecution.ts b/src/core/execution/QuickChatExecution.ts
new file mode 100644
index 000000000..919979997
--- /dev/null
+++ b/src/core/execution/QuickChatExecution.ts
@@ -0,0 +1,98 @@
+import quickChatData from "../../../resources/QuickChat.json";
+import { consolex } from "../Consolex";
+import { Execution, Game, MessageType, Player, PlayerID } from "../game/Game";
+
+export class QuickChatExecution implements Execution {
+ private sender: Player;
+ private recipient: Player;
+ private mg: Game;
+
+ private active = true;
+
+ constructor(
+ private senderID: PlayerID,
+ private recipientID: PlayerID,
+ private quickChatKey: string,
+ private variables: Record,
+ ) {}
+
+ init(mg: Game, ticks: number): void {
+ this.mg = mg;
+ if (!mg.hasPlayer(this.senderID)) {
+ consolex.warn(`QuickChatExecution: sender ${this.senderID} not found`);
+ this.active = false;
+ return;
+ }
+ if (!mg.hasPlayer(this.recipientID)) {
+ consolex.warn(
+ `QuickChatExecution: recipient ${this.recipientID} not found`,
+ );
+ this.active = false;
+ return;
+ }
+
+ this.sender = mg.player(this.senderID);
+ this.recipient = mg.player(this.recipientID);
+ }
+
+ tick(ticks: number): void {
+ const message = this.getMessageFromKey(this.quickChatKey, this.variables);
+
+ this.mg.displayMessage(
+ `${this.sender.name()}: ${message}`,
+ MessageType.CHAT,
+ this.recipient.id(),
+ );
+
+ this.mg.displayMessage(
+ `You sent to ${this.recipient.name()}: ${message}`,
+ MessageType.CHAT,
+ this.sender.id(),
+ );
+
+ consolex.log(
+ `[QuickChat] ${this.sender.name} → ${this.recipient.name}: ${message}`,
+ );
+
+ this.active = false;
+ }
+
+ owner(): Player {
+ return this.sender;
+ }
+
+ isActive(): boolean {
+ return this.active;
+ }
+
+ activeDuringSpawnPhase(): boolean {
+ return false;
+ }
+
+ private getMessageFromKey(
+ fullKey: string,
+ vars: Record,
+ ): string {
+ // Key for translation
+ const [category, key] = fullKey.split(".");
+ const phrases = quickChatData[category];
+
+ if (!phrases) {
+ consolex.warn(`QuickChat: Unknown category '${category}'`);
+ return `[${fullKey}]`;
+ }
+
+ const phraseObj = phrases.find((p) => p.key === key);
+ if (!phraseObj) {
+ consolex.warn(
+ `QuickChat: Key '${key}' not found in category '${category}'`,
+ );
+ return `[${fullKey}]`;
+ }
+
+ return phraseObj.text.replace(
+ /\[(\w+)\]/g,
+ (_, p1) => vars[p1] || `[${p1}]`,
+ );
+ }
+}
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 17cc19e63..492dd8489 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -557,6 +557,7 @@ export enum MessageType {
INFO,
WARN,
ERROR,
+ CHAT,
}
export interface NameViewData {