feat: add siegetank unit set and implement messaging system

- Created a new unit set for "siegetank" with various crew types and their attributes.
- Implemented a messaging system with types, payloads, and storage functionality.
- Added tests for the messaging system to ensure correct behavior for different message types (private, national, public, diplomacy).
This commit is contained in:
2025-12-30 08:19:10 +00:00
parent 519a0a76cb
commit c5cba31418
16 changed files with 8697 additions and 0 deletions
+1
View File
@@ -3,6 +3,7 @@ export type { RandomGenerator } from '@sammo-ts/common';
export * from './actions/index.js';
export * from './constraints/index.js';
export * from './logging/index.js';
export * from './messages/index.js';
export * from './ports/world.js';
export * from './ports/worldSnapshot.js';
export * from './scenario/index.js';
+1
View File
@@ -0,0 +1 @@
export * from './message.js';
+168
View File
@@ -0,0 +1,168 @@
export type MessageType = 'public' | 'private' | 'national' | 'diplomacy';
export const MESSAGE_MAILBOX_PUBLIC = 9999;
export const MESSAGE_MAILBOX_NATIONAL_BASE = 9000;
export interface MessageTarget {
generalId: number;
generalName: string;
nationId: number;
nationName: string;
color: string;
icon: string;
}
export type MessageOption = Record<string, unknown>;
export interface MessageDraft {
msgType: MessageType;
src: MessageTarget;
dest: MessageTarget;
text: string;
time: Date;
validUntil: Date;
option?: MessageOption | null;
}
export interface MessagePayload {
src: MessageTarget;
dest: MessageTarget;
text: string;
option?: MessageOption | null;
}
export interface MessageRecordDraft {
mailbox: number;
msgType: MessageType;
srcId: number;
destId: number;
time: Date;
validUntil: Date;
payload: MessagePayload;
}
export interface MessageStore {
insertMessage(draft: MessageRecordDraft): Promise<number>;
}
export const isValidMailbox = (mailbox: number): boolean =>
mailbox > 0 && mailbox <= MESSAGE_MAILBOX_PUBLIC;
export const resolveReceiverMailbox = (draft: MessageDraft): number => {
switch (draft.msgType) {
case 'public':
return MESSAGE_MAILBOX_PUBLIC;
case 'national':
case 'diplomacy':
return MESSAGE_MAILBOX_NATIONAL_BASE + draft.dest.nationId;
case 'private':
return draft.dest.generalId;
}
};
export const resolveSenderMailbox = (draft: MessageDraft): number | null => {
switch (draft.msgType) {
case 'public':
return null;
case 'private':
return draft.src.generalId !== draft.dest.generalId
? draft.src.generalId
: null;
case 'national':
return draft.src.nationId !== draft.dest.nationId
? MESSAGE_MAILBOX_NATIONAL_BASE + draft.src.nationId
: null;
case 'diplomacy':
return MESSAGE_MAILBOX_NATIONAL_BASE + draft.src.nationId;
}
};
const buildPayload = (
draft: MessageDraft,
optionOverride?: MessageOption | null
): MessagePayload => ({
src: draft.src,
dest: draft.dest,
text: draft.text,
option: optionOverride ?? draft.option ?? {},
});
const buildRecord = (
draft: MessageDraft,
mailbox: number,
optionOverride?: MessageOption | null
): MessageRecordDraft => {
const payload = buildPayload(draft, optionOverride);
let srcId = draft.src.generalId;
let destId = draft.dest.generalId;
if (draft.msgType === 'public') {
destId = MESSAGE_MAILBOX_PUBLIC;
} else if (draft.msgType === 'national' || draft.msgType === 'diplomacy') {
srcId = MESSAGE_MAILBOX_NATIONAL_BASE + draft.src.nationId;
destId = MESSAGE_MAILBOX_NATIONAL_BASE + draft.dest.nationId;
}
return {
mailbox,
msgType: draft.msgType,
srcId,
destId,
time: draft.time,
validUntil: draft.validUntil,
payload,
};
};
const buildSenderOption = (
draft: MessageDraft,
receiverId: number
): MessageOption => {
const option = {
...(draft.option ?? {}),
receiverMessageID: receiverId,
};
if (draft.msgType === 'diplomacy' && 'action' in option) {
const { action: _action, ...rest } = option;
return rest;
}
return option;
};
// 메시지 전달 규칙(수신/송신 복사본)을 그대로 유지한다.
export const sendMessage = async (
store: MessageStore,
draft: MessageDraft,
options: { sendDestOnly?: boolean } = {}
): Promise<{ receiverId: number; senderId?: number }> => {
const receiverMailbox = resolveReceiverMailbox(draft);
if (!isValidMailbox(receiverMailbox)) {
throw new Error(`Invalid receiver mailbox: ${receiverMailbox}`);
}
const receiverRecord = buildRecord(draft, receiverMailbox);
const receiverId = await store.insertMessage(receiverRecord);
if (!receiverId) {
throw new Error('Failed to send receiver message.');
}
if (options.sendDestOnly) {
return { receiverId };
}
const senderMailbox = resolveSenderMailbox(draft);
if (!senderMailbox || senderMailbox === receiverMailbox) {
return { receiverId };
}
const senderRecord = buildRecord(
draft,
senderMailbox,
buildSenderOption(draft, receiverId)
);
const senderId = await store.insertMessage(senderRecord);
return senderId ? { receiverId, senderId } : { receiverId };
};