경매 관련 진행 준비
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { createGamePostgresConnector, GamePrisma } from '@sammo-ts/infra';
|
||||
|
||||
import type { TurnDaemonCommandResult } from '../lifecycle/types.js';
|
||||
|
||||
export interface AuctionFinalizer {
|
||||
finalize(auctionId: number): Promise<TurnDaemonCommandResult>;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
interface AuctionRow {
|
||||
id: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const createAuctionFinalizer = async (databaseUrl: string): Promise<AuctionFinalizer> => {
|
||||
const connector = createGamePostgresConnector({ url: databaseUrl });
|
||||
await connector.connect();
|
||||
const prisma = connector.prisma;
|
||||
|
||||
return {
|
||||
finalize: async (auctionId: number): Promise<TurnDaemonCommandResult> => {
|
||||
const rows = await prisma.$queryRaw<AuctionRow[]>(
|
||||
GamePrisma.sql`SELECT id, status FROM auction WHERE id = ${auctionId}`
|
||||
);
|
||||
const auction = rows[0];
|
||||
if (!auction) {
|
||||
return {
|
||||
type: 'auctionFinalize',
|
||||
ok: false,
|
||||
auctionId,
|
||||
reason: '경매 정보를 찾을 수 없습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
if (auction.status === 'FINISHED') {
|
||||
return { type: 'auctionFinalize', ok: true, auctionId };
|
||||
}
|
||||
|
||||
if (auction.status !== 'FINALIZING') {
|
||||
return {
|
||||
type: 'auctionFinalize',
|
||||
ok: false,
|
||||
auctionId,
|
||||
reason: '경매가 확정 대기 상태가 아닙니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
await prisma.$executeRaw(
|
||||
GamePrisma.sql`UPDATE auction SET status = 'FINISHED', finished_at = ${now}, updated_at = ${now} WHERE id = ${auctionId}`
|
||||
);
|
||||
|
||||
// TODO: 경매 정산(자원 이동, 로그 기록, 유니크 지급)을 월드 상태와 함께 확정해야 한다.
|
||||
|
||||
return { type: 'auctionFinalize', ok: true, auctionId };
|
||||
},
|
||||
close: async () => {
|
||||
await connector.disconnect();
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -68,6 +68,16 @@ const normalizeCommand = (envelope: TurnDaemonCommandEnvelope): TurnDaemonComman
|
||||
requestId?: string;
|
||||
};
|
||||
switch (command.type) {
|
||||
case 'auctionFinalize': {
|
||||
if (typeof command.auctionId !== 'number') {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'auctionFinalize',
|
||||
requestId: envelope.requestId,
|
||||
auctionId: command.auctionId,
|
||||
};
|
||||
}
|
||||
case 'troopJoin': {
|
||||
if (typeof command.generalId !== 'number' || typeof command.troopId !== 'number') {
|
||||
return null;
|
||||
|
||||
@@ -249,6 +249,7 @@ export class TurnDaemonLifecycle {
|
||||
case 'vacation':
|
||||
case 'setMySetting':
|
||||
case 'dropItem':
|
||||
case 'auctionFinalize':
|
||||
case 'changePermission':
|
||||
case 'kick':
|
||||
case 'appoint':
|
||||
@@ -268,6 +269,7 @@ export class TurnDaemonLifecycle {
|
||||
| { type: 'vacation' }
|
||||
| { type: 'setMySetting' }
|
||||
| { type: 'dropItem' }
|
||||
| { type: 'auctionFinalize' }
|
||||
| { type: 'changePermission' }
|
||||
| { type: 'kick' }
|
||||
| { type: 'appoint' }
|
||||
@@ -277,23 +279,41 @@ export class TurnDaemonLifecycle {
|
||||
try {
|
||||
result = this.commandHandler ? await this.commandHandler.handle(command) : null;
|
||||
if (!result) {
|
||||
if (command.type === 'auctionFinalize') {
|
||||
result = {
|
||||
type: 'auctionFinalize',
|
||||
ok: false,
|
||||
auctionId: command.auctionId,
|
||||
reason: '턴 데몬이 경매 확정을 처리할 수 없습니다.',
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
type: command.type,
|
||||
ok: false,
|
||||
generalId: command.generalId,
|
||||
reason: '턴 데몬이 명령을 처리할 수 없습니다.',
|
||||
...(command.type === 'troopJoin' ? { troopId: command.troopId } : {}),
|
||||
} as TurnDaemonCommandResult;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : 'Unknown command error.';
|
||||
if (command.type === 'auctionFinalize') {
|
||||
result = {
|
||||
type: 'auctionFinalize',
|
||||
ok: false,
|
||||
auctionId: command.auctionId,
|
||||
reason,
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
type: command.type,
|
||||
ok: false,
|
||||
generalId: command.generalId,
|
||||
reason: '턴 데몬이 명령을 처리할 수 없습니다.',
|
||||
reason,
|
||||
...(command.type === 'troopJoin' ? { troopId: command.troopId } : {}),
|
||||
} as TurnDaemonCommandResult;
|
||||
}
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : 'Unknown command error.';
|
||||
result = {
|
||||
type: command.type,
|
||||
ok: false,
|
||||
generalId: command.generalId,
|
||||
reason,
|
||||
...(command.type === 'troopJoin' ? { troopId: command.troopId } : {}),
|
||||
} as TurnDaemonCommandResult;
|
||||
}
|
||||
|
||||
if (this.commandResponder && command.requestId) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { loadTurnCommandProfile } from './turnCommandProfile.js';
|
||||
import { loadTurnWorldFromDatabase } from './worldLoader.js';
|
||||
import { shouldUseAi } from './ai/generalAi.js';
|
||||
import { createUnificationHandler } from './unificationHandler.js';
|
||||
import { createAuctionFinalizer } from '../auction/finalizer.js';
|
||||
|
||||
export interface TurnDaemonRuntimeOptions {
|
||||
profile: string;
|
||||
@@ -170,6 +171,7 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
let hooks: TurnDaemonHooks | undefined;
|
||||
let publishRealtimeEvent: ((event: RealtimeEvent) => Promise<void>) | null = null;
|
||||
let close = async () => {};
|
||||
let auctionFinalizer: Awaited<ReturnType<typeof createAuctionFinalizer>> | null = null;
|
||||
let redisCommandStream: RedisTurnDaemonCommandStream | null = null;
|
||||
let redisConnector: ReturnType<typeof createRedisConnector> | null = null;
|
||||
let pauseGate: (() => Promise<boolean>) | undefined;
|
||||
@@ -189,6 +191,7 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
const dbHooks = await createDatabaseTurnHooks(options.databaseUrl, world, {
|
||||
reservedTurns: reservedTurnStoreHandle?.store,
|
||||
});
|
||||
auctionFinalizer = await createAuctionFinalizer(options.databaseUrl);
|
||||
hooks = {
|
||||
...dbHooks.hooks,
|
||||
onRunError: async (error) => {
|
||||
@@ -197,6 +200,9 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
},
|
||||
};
|
||||
close = async () => {
|
||||
if (auctionFinalizer) {
|
||||
await auctionFinalizer.close();
|
||||
}
|
||||
await dbHooks.close();
|
||||
if (reservedTurnStoreHandle) {
|
||||
await reservedTurnStoreHandle.close();
|
||||
@@ -281,6 +287,7 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
const commandHandler = createTurnDaemonCommandHandler({
|
||||
world,
|
||||
hooks,
|
||||
auctionFinalizer: auctionFinalizer ?? undefined,
|
||||
});
|
||||
|
||||
const defaultBudget: TurnRunBudget = options.defaultBudget ?? {
|
||||
|
||||
@@ -29,6 +29,11 @@ const flushWorld = async (world: InMemoryTurnWorld, hooks?: TurnDaemonHooks): Pr
|
||||
interface CommandHandlerContext {
|
||||
world: InMemoryTurnWorld;
|
||||
hooks?: TurnDaemonHooks;
|
||||
auctionFinalizer?: AuctionFinalizer;
|
||||
}
|
||||
|
||||
interface AuctionFinalizer {
|
||||
finalize(auctionId: number): Promise<TurnDaemonCommandResult>;
|
||||
}
|
||||
|
||||
async function handleTroopJoin(
|
||||
@@ -299,6 +304,21 @@ async function handleDropItem(
|
||||
return { type: 'dropItem', ok: true, generalId: command.generalId };
|
||||
}
|
||||
|
||||
async function handleAuctionFinalize(
|
||||
ctx: CommandHandlerContext,
|
||||
command: Extract<TurnDaemonCommand, { type: 'auctionFinalize' }>
|
||||
): Promise<TurnDaemonCommandResult> {
|
||||
if (!ctx.auctionFinalizer) {
|
||||
return {
|
||||
type: 'auctionFinalize',
|
||||
ok: false,
|
||||
auctionId: command.auctionId,
|
||||
reason: '경매 확정기가 준비되지 않았습니다.',
|
||||
};
|
||||
}
|
||||
return ctx.auctionFinalizer.finalize(command.auctionId);
|
||||
}
|
||||
|
||||
async function handleChangePermission(
|
||||
ctx: CommandHandlerContext,
|
||||
command: Extract<TurnDaemonCommand, { type: 'changePermission' }>
|
||||
@@ -434,8 +454,9 @@ async function handleAppoint(
|
||||
export const createTurnDaemonCommandHandler = (options: {
|
||||
world: InMemoryTurnWorld;
|
||||
hooks?: TurnDaemonHooks;
|
||||
auctionFinalizer?: AuctionFinalizer;
|
||||
}): TurnDaemonCommandHandler => {
|
||||
const ctx = { world: options.world, hooks: options.hooks };
|
||||
const ctx = { world: options.world, hooks: options.hooks, auctionFinalizer: options.auctionFinalizer };
|
||||
|
||||
return {
|
||||
handle: async (command): Promise<TurnDaemonCommandResult | null> => {
|
||||
@@ -456,6 +477,8 @@ export const createTurnDaemonCommandHandler = (options: {
|
||||
return handleSetMySetting(ctx, command);
|
||||
case 'dropItem':
|
||||
return handleDropItem(ctx, command);
|
||||
case 'auctionFinalize':
|
||||
return handleAuctionFinalize(ctx, command);
|
||||
case 'changePermission':
|
||||
return handleChangePermission(ctx, command);
|
||||
case 'kick':
|
||||
|
||||
Reference in New Issue
Block a user