feat: add legacy-compatible neutral auctions
This commit is contained in:
@@ -8,14 +8,14 @@ export const composeCalendarHandlers = (
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
onMonthChanged: (context) => {
|
||||
onMonthChanged: async (context) => {
|
||||
for (const handler of resolved) {
|
||||
handler.onMonthChanged?.(context);
|
||||
await handler.onMonthChanged?.(context);
|
||||
}
|
||||
},
|
||||
onYearChanged: (context) => {
|
||||
onYearChanged: async (context) => {
|
||||
for (const handler of resolved) {
|
||||
handler.onYearChanged?.(context);
|
||||
await handler.onYearChanged?.(context);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -339,6 +339,7 @@ export const createDatabaseTurnHooks = async (
|
||||
createdNations,
|
||||
createdTroops,
|
||||
createdDiplomacy,
|
||||
pendingNeutralAuctions,
|
||||
} = changes;
|
||||
const reservedTurnChanges = options?.reservedTurns?.peekDirtyState();
|
||||
|
||||
@@ -349,6 +350,28 @@ export const createDatabaseTurnHooks = async (
|
||||
meta: asJson(state.meta),
|
||||
};
|
||||
const persist = async (prisma: GamePrisma.TransactionClient): Promise<void> => {
|
||||
let neutralAuctionsToCreate = pendingNeutralAuctions;
|
||||
if (pendingNeutralAuctions.length > 0) {
|
||||
const latestRegistrationKey =
|
||||
pendingNeutralAuctions[pendingNeutralAuctions.length - 1]!.registrationKey;
|
||||
await prisma.$executeRaw`
|
||||
SELECT pg_advisory_xact_lock(
|
||||
hashtext(${'neutral-auction-registration'}),
|
||||
${state.id}
|
||||
)
|
||||
`;
|
||||
const persistedRows = await prisma.$queryRaw<Array<{ meta: unknown }>>`
|
||||
SELECT meta
|
||||
FROM world_state
|
||||
WHERE id = ${state.id}
|
||||
FOR UPDATE
|
||||
`;
|
||||
const persistedMeta = asRecord(persistedRows[0]?.meta);
|
||||
if (persistedMeta.neutralAuctionRegistrationKey === latestRegistrationKey) {
|
||||
neutralAuctionsToCreate = [];
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.worldState.update({
|
||||
where: { id: state.id },
|
||||
data: worldStateUpdate,
|
||||
@@ -425,6 +448,20 @@ export const createDatabaseTurnHooks = async (
|
||||
);
|
||||
}
|
||||
|
||||
if (neutralAuctionsToCreate.length > 0) {
|
||||
await prisma.auction.createMany({
|
||||
data: neutralAuctionsToCreate.map((auction) => ({
|
||||
type: auction.type,
|
||||
targetCode: auction.targetCode,
|
||||
hostGeneralId: auction.hostGeneralId,
|
||||
hostName: auction.hostName,
|
||||
detail: asJson(auction.detail),
|
||||
status: 'OPEN',
|
||||
closeAt: auction.closeAt,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const createdIds = new Set(createdGenerals.map((general) => general.id));
|
||||
const createdNationIds = new Set(createdNations.map((nation) => nation.id));
|
||||
const createdTroopIds = new Set(createdTroops.map((troop) => troop.id));
|
||||
|
||||
@@ -96,7 +96,7 @@ export class InMemoryTurnProcessor implements TurnProcessor {
|
||||
partial = true;
|
||||
break;
|
||||
}
|
||||
this.world.advanceMonth(nextTickTime);
|
||||
await this.world.advanceMonth(nextTickTime);
|
||||
processedTurns += 1;
|
||||
nextTickTime = getNextTickTime(this.world.getState().lastTurnTime, this.tickMinutes);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { City, LogEntryDraft, MessageDraft, Nation, ScenarioConfig, Troop,
|
||||
import { getNextTurnAt } from '@sammo-ts/logic';
|
||||
|
||||
import type { TurnCheckpoint } from '../lifecycle/types.js';
|
||||
import type { TurnDiplomacy, TurnGeneral, TurnWorldSnapshot, TurnWorldState } from './types.js';
|
||||
import type { PendingNeutralAuction, TurnDiplomacy, TurnGeneral, TurnWorldSnapshot, TurnWorldState } from './types.js';
|
||||
import {
|
||||
applyDiplomacyPatch as applyDiplomacyPatchToEntry,
|
||||
buildDefaultDiplomacy,
|
||||
@@ -59,8 +59,8 @@ export interface TurnCalendarContext {
|
||||
|
||||
export interface TurnCalendarHandler {
|
||||
// 월/연 변경에 따른 후처리를 끼워 넣기 위한 확장 포인트.
|
||||
onMonthChanged?(context: TurnCalendarContext): void;
|
||||
onYearChanged?(context: TurnCalendarContext): void;
|
||||
onMonthChanged?(context: TurnCalendarContext): void | Promise<void>;
|
||||
onYearChanged?(context: TurnCalendarContext): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface InMemoryTurnWorldOptions {
|
||||
@@ -85,6 +85,7 @@ export interface TurnWorldChanges {
|
||||
createdNations: Nation[];
|
||||
createdTroops: Troop[];
|
||||
createdDiplomacy: TurnDiplomacy[];
|
||||
pendingNeutralAuctions: PendingNeutralAuction[];
|
||||
}
|
||||
|
||||
const compareTurnOrder = (left: TurnGeneral, right: TurnGeneral): number => {
|
||||
@@ -250,6 +251,7 @@ export class InMemoryTurnWorld {
|
||||
}> = [];
|
||||
private readonly logs: LogEntryDraft[] = [];
|
||||
private readonly messages: MessageDraft[] = [];
|
||||
private readonly pendingNeutralAuctions: PendingNeutralAuction[] = [];
|
||||
private readonly scenarioConfig: ScenarioConfig;
|
||||
private checkpoint?: TurnCheckpoint;
|
||||
private state: TurnWorldState;
|
||||
@@ -308,6 +310,14 @@ export class InMemoryTurnWorld {
|
||||
this.logs.push(entry);
|
||||
}
|
||||
|
||||
queueNeutralAuction(auction: PendingNeutralAuction): void {
|
||||
this.pendingNeutralAuctions.push({
|
||||
...auction,
|
||||
detail: { ...auction.detail },
|
||||
closeAt: new Date(auction.closeAt.getTime()),
|
||||
});
|
||||
}
|
||||
|
||||
getScenarioConfig(): ScenarioConfig {
|
||||
return this.scenarioConfig;
|
||||
}
|
||||
@@ -681,7 +691,7 @@ export class InMemoryTurnWorld {
|
||||
return nextTurnAt;
|
||||
}
|
||||
|
||||
advanceMonth(turnTime: Date): void {
|
||||
async advanceMonth(turnTime: Date): Promise<void> {
|
||||
const previousYear = this.state.currentYear;
|
||||
const previousMonth = this.state.currentMonth;
|
||||
let nextYear = previousYear;
|
||||
@@ -711,9 +721,9 @@ export class InMemoryTurnWorld {
|
||||
turnTime,
|
||||
};
|
||||
this.advanceDiplomacyMonth();
|
||||
this.calendarHandler?.onMonthChanged?.(context);
|
||||
await this.calendarHandler?.onMonthChanged?.(context);
|
||||
if (nextYear !== previousYear) {
|
||||
this.calendarHandler?.onYearChanged?.(context);
|
||||
await this.calendarHandler?.onYearChanged?.(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,6 +761,11 @@ export class InMemoryTurnWorld {
|
||||
const deletedNationSnapshots = this.deletedNationSnapshots.slice();
|
||||
const logs = this.logs.slice();
|
||||
const messages = this.messages.slice();
|
||||
const pendingNeutralAuctions = this.pendingNeutralAuctions.map((auction) => ({
|
||||
...auction,
|
||||
detail: { ...auction.detail },
|
||||
closeAt: new Date(auction.closeAt.getTime()),
|
||||
}));
|
||||
|
||||
return {
|
||||
generals,
|
||||
@@ -768,6 +783,7 @@ export class InMemoryTurnWorld {
|
||||
createdNations,
|
||||
createdTroops,
|
||||
createdDiplomacy,
|
||||
pendingNeutralAuctions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -791,6 +807,7 @@ export class InMemoryTurnWorld {
|
||||
this.deletedNationSnapshots.splice(0, changes.deletedNationSnapshots.length);
|
||||
this.logs.splice(0, changes.logs.length);
|
||||
this.messages.splice(0, changes.messages.length);
|
||||
this.pendingNeutralAuctions.splice(0, changes.pendingNeutralAuctions.length);
|
||||
}
|
||||
|
||||
consumeDirtyState(): TurnWorldChanges {
|
||||
|
||||
@@ -30,6 +30,7 @@ import { shouldUseAi } from './ai/generalAi.js';
|
||||
import { createUnificationHandler } from './unificationHandler.js';
|
||||
import { createAuctionFinalizer } from '../auction/finalizer.js';
|
||||
import { createAuctionBidder } from '../auction/bidder.js';
|
||||
import { createNeutralAuctionRegistrar } from '../auction/neutralRegistrar.js';
|
||||
import { createTournamentRewardFinalizer } from '../tournament/finalizer.js';
|
||||
import { createTournamentAutoStartHandler } from './tournamentAutoStart.js';
|
||||
import { createYearbookHandler } from './yearbookHandler.js';
|
||||
@@ -132,6 +133,13 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
getWorld: () => worldRef,
|
||||
map: snapshot.map ?? null,
|
||||
});
|
||||
const neutralAuctionRegistrar = await createNeutralAuctionRegistrar({
|
||||
databaseUrl: options.databaseUrl,
|
||||
profileName: options.profileName ?? options.profile,
|
||||
getWorld: () => worldRef,
|
||||
getRedisClient: () => redisConnector?.client,
|
||||
getWorldConfig: () => snapshot.worldConfig ?? null,
|
||||
});
|
||||
const tournamentAutoStartHandler = createTournamentAutoStartHandler({
|
||||
profileName: options.profileName ?? options.profile,
|
||||
getRedisClient: () => redisConnector?.client,
|
||||
@@ -148,6 +156,7 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
nationTurnMonthlyHandler,
|
||||
incomeHandler,
|
||||
frontStateHandler,
|
||||
neutralAuctionRegistrar.handler,
|
||||
tournamentAutoStartHandler,
|
||||
yearbookHandler.handler
|
||||
);
|
||||
@@ -327,6 +336,7 @@ export const createTurnDaemonRuntime = async (options: TurnDaemonRuntimeOptions)
|
||||
const baseClose = close;
|
||||
close = async () => {
|
||||
await baseClose();
|
||||
await neutralAuctionRegistrar.close();
|
||||
if (unification) {
|
||||
await unification.close();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ export interface TurnDiplomacy {
|
||||
meta: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface PendingNeutralAuction {
|
||||
registrationKey: string;
|
||||
type: 'BUY_RICE' | 'SELL_RICE';
|
||||
targetCode: string;
|
||||
hostGeneralId: 0;
|
||||
hostName: '상인';
|
||||
detail: Record<string, unknown>;
|
||||
closeAt: Date;
|
||||
}
|
||||
|
||||
export interface TurnWorldSnapshot extends Omit<
|
||||
WorldSnapshot,
|
||||
'generals' | 'cities' | 'nations' | 'troops' | 'diplomacy'
|
||||
|
||||
Reference in New Issue
Block a user