fix: load and persist legacy inheritance ranks
This commit is contained in:
@@ -32,11 +32,7 @@ import { ensureItemInventory, withSerializedItemInventory } from '@sammo-ts/logi
|
|||||||
import { persistGeneralLifecycleEvents } from './generalTurnLifecyclePersistence.js';
|
import { persistGeneralLifecycleEvents } from './generalTurnLifecyclePersistence.js';
|
||||||
import type { DatabaseTurnDaemonLease } from '../lifecycle/databaseTurnDaemonLease.js';
|
import type { DatabaseTurnDaemonLease } from '../lifecycle/databaseTurnDaemonLease.js';
|
||||||
import { calculateNationBettingRewards } from '../betting/nationBettingSettlement.js';
|
import { calculateNationBettingRewards } from '../betting/nationBettingSettlement.js';
|
||||||
import type {
|
import type { NationBettingCandidate, PendingNationBettingFinish, PendingNationBettingOpen } from './types.js';
|
||||||
NationBettingCandidate,
|
|
||||||
PendingNationBettingFinish,
|
|
||||||
PendingNationBettingOpen,
|
|
||||||
} from './types.js';
|
|
||||||
|
|
||||||
export interface DatabaseTurnHooks {
|
export interface DatabaseTurnHooks {
|
||||||
hooks: TurnDaemonHooks;
|
hooks: TurnDaemonHooks;
|
||||||
@@ -53,11 +49,7 @@ const readBettingCandidates = (value: unknown): NationBettingCandidate[] => {
|
|||||||
return value.flatMap((candidate) => {
|
return value.flatMap((candidate) => {
|
||||||
const item = asRecord(candidate);
|
const item = asRecord(candidate);
|
||||||
const aux = asRecord(item.aux);
|
const aux = asRecord(item.aux);
|
||||||
if (
|
if (typeof item.title !== 'string' || typeof aux.nation !== 'number' || !Number.isInteger(aux.nation)) {
|
||||||
typeof item.title !== 'string' ||
|
|
||||||
typeof aux.nation !== 'number' ||
|
|
||||||
!Number.isInteger(aux.nation)
|
|
||||||
) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
@@ -369,6 +361,9 @@ const buildRankRows = (
|
|||||||
['betwingold', readMeta('betwingold')],
|
['betwingold', readMeta('betwingold')],
|
||||||
['inherit_earned', readMeta('inherit_earned')],
|
['inherit_earned', readMeta('inherit_earned')],
|
||||||
['inherit_spent', readMeta('inherit_spent')],
|
['inherit_spent', readMeta('inherit_spent')],
|
||||||
|
['inherit_earned_dyn', readMeta('inherit_earned_dyn')],
|
||||||
|
['inherit_earned_act', readMeta('inherit_earned_act')],
|
||||||
|
['inherit_spent_dyn', readMeta('inherit_spent_dyn')],
|
||||||
];
|
];
|
||||||
|
|
||||||
return entries.map(([type, value]) => ({
|
return entries.map(([type, value]) => ({
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface TurnGeneral extends General {
|
|||||||
recentWarTime?: Date | null;
|
recentWarTime?: Date | null;
|
||||||
lastTurn?: GeneralLastTurn;
|
lastTurn?: GeneralLastTurn;
|
||||||
penalty?: unknown;
|
penalty?: unknown;
|
||||||
|
inheritancePoints?: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TurnDiplomacy {
|
export interface TurnDiplomacy {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
type TurnEngineDatabaseClient,
|
type TurnEngineDatabaseClient,
|
||||||
type TurnEngineDiplomacyRow,
|
type TurnEngineDiplomacyRow,
|
||||||
type TurnEngineGeneralRow,
|
type TurnEngineGeneralRow,
|
||||||
|
type TurnEngineInheritancePointRow,
|
||||||
|
type TurnEngineRankDataRow,
|
||||||
type TurnEngineNationRow,
|
type TurnEngineNationRow,
|
||||||
type TurnEngineTroopRow,
|
type TurnEngineTroopRow,
|
||||||
} from '@sammo-ts/infra';
|
} from '@sammo-ts/infra';
|
||||||
@@ -154,14 +156,36 @@ const mapScenarioConfig = (raw: JsonValue): ScenarioConfig => {
|
|||||||
return parsed.data;
|
return parsed.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapGeneralRow = (row: TurnEngineGeneralRow): TurnGeneral => {
|
const GENERAL_RANK_META_PREFIX_TYPES = new Set([
|
||||||
|
'warnum',
|
||||||
|
'killnum',
|
||||||
|
'deathnum',
|
||||||
|
'occupied',
|
||||||
|
'killcrew',
|
||||||
|
'deathcrew',
|
||||||
|
'killcrew_person',
|
||||||
|
'deathcrew_person',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mapGeneralRow = (
|
||||||
|
row: TurnEngineGeneralRow,
|
||||||
|
rankRows: readonly TurnEngineRankDataRow[],
|
||||||
|
inheritanceRows: readonly TurnEngineInheritancePointRow[]
|
||||||
|
): TurnGeneral => {
|
||||||
const legacySlots: GeneralItemSlots = {
|
const legacySlots: GeneralItemSlots = {
|
||||||
horse: normalizeCode(row.horseCode),
|
horse: normalizeCode(row.horseCode),
|
||||||
weapon: normalizeCode(row.weaponCode),
|
weapon: normalizeCode(row.weaponCode),
|
||||||
book: normalizeCode(row.bookCode),
|
book: normalizeCode(row.bookCode),
|
||||||
item: normalizeCode(row.itemCode),
|
item: normalizeCode(row.itemCode),
|
||||||
};
|
};
|
||||||
const rawMeta = asTriggerRecord(row.meta) as Record<string, unknown>;
|
const rawMeta = { ...(asTriggerRecord(row.meta) as Record<string, unknown>) };
|
||||||
|
for (const rank of rankRows) {
|
||||||
|
if (rank.type === 'experience' || rank.type === 'dedication') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rawMeta[GENERAL_RANK_META_PREFIX_TYPES.has(rank.type) ? `rank_${rank.type}` : rank.type] = rank.value;
|
||||||
|
}
|
||||||
|
const inheritancePoints = Object.fromEntries(inheritanceRows.map((entry) => [entry.key, entry.value]));
|
||||||
const itemInventory = readItemInventoryFromMeta(rawMeta, legacySlots);
|
const itemInventory = readItemInventoryFromMeta(rawMeta, legacySlots);
|
||||||
return {
|
return {
|
||||||
...((): { meta: TurnGeneral['meta'] } => {
|
...((): { meta: TurnGeneral['meta'] } => {
|
||||||
@@ -218,6 +242,7 @@ const mapGeneralRow = (row: TurnEngineGeneralRow): TurnGeneral => {
|
|||||||
// meta는 상단에서 보장 처리됨.
|
// meta는 상단에서 보장 처리됨.
|
||||||
turnTime: row.turnTime,
|
turnTime: row.turnTime,
|
||||||
recentWarTime: row.recentWarTime ?? null,
|
recentWarTime: row.recentWarTime ?? null,
|
||||||
|
inheritancePoints,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -315,18 +340,39 @@ export const loadTurnWorldFromDatabase = async (options: TurnWorldLoaderOptions)
|
|||||||
throw new Error('world_state row is required to start turn daemon.');
|
throw new Error('world_state row is required to start turn daemon.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [generalRows, cityRows, nationRows, diplomacyRows, troopRows, eventRows] = await Promise.all([
|
const [generalRows, rankRows, inheritanceRows, cityRows, nationRows, diplomacyRows, troopRows, eventRows] =
|
||||||
prisma.general.findMany(),
|
await Promise.all([
|
||||||
prisma.city.findMany(),
|
prisma.general.findMany(),
|
||||||
prisma.nation.findMany(),
|
prisma.rankData.findMany(),
|
||||||
prisma.diplomacy.findMany(),
|
prisma.inheritancePoint.findMany(),
|
||||||
prisma.troop.findMany(),
|
prisma.city.findMany(),
|
||||||
prisma.event.findMany({
|
prisma.nation.findMany(),
|
||||||
orderBy: [{ priority: 'desc' }, { id: 'asc' }],
|
prisma.diplomacy.findMany(),
|
||||||
}),
|
prisma.troop.findMany(),
|
||||||
]);
|
prisma.event.findMany({
|
||||||
|
orderBy: [{ priority: 'desc' }, { id: 'asc' }],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const generals = generalRows.map(mapGeneralRow);
|
const ranksByGeneral = new Map<number, TurnEngineRankDataRow[]>();
|
||||||
|
for (const row of rankRows) {
|
||||||
|
const bucket = ranksByGeneral.get(row.generalId) ?? [];
|
||||||
|
bucket.push(row);
|
||||||
|
ranksByGeneral.set(row.generalId, bucket);
|
||||||
|
}
|
||||||
|
const inheritanceByUser = new Map<string, TurnEngineInheritancePointRow[]>();
|
||||||
|
for (const row of inheritanceRows) {
|
||||||
|
const bucket = inheritanceByUser.get(row.userId) ?? [];
|
||||||
|
bucket.push(row);
|
||||||
|
inheritanceByUser.set(row.userId, bucket);
|
||||||
|
}
|
||||||
|
const generals = generalRows.map((row) =>
|
||||||
|
mapGeneralRow(
|
||||||
|
row,
|
||||||
|
ranksByGeneral.get(row.id) ?? [],
|
||||||
|
row.userId ? (inheritanceByUser.get(row.userId) ?? []) : []
|
||||||
|
)
|
||||||
|
);
|
||||||
const cities = cityRows.map(mapCityRow);
|
const cities = cityRows.map(mapCityRow);
|
||||||
const nations = nationRows.map(mapNationRow);
|
const nations = nationRows.map(mapNationRow);
|
||||||
const diplomacy = diplomacyRows.map(mapDiplomacyRow);
|
const diplomacy = diplomacyRows.map(mapDiplomacyRow);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ integration('CreateManyNPC database persistence', () => {
|
|||||||
const ranks = await db.rankData.findMany({ where: { generalId: createdGeneralId } });
|
const ranks = await db.rankData.findMany({ where: { generalId: createdGeneralId } });
|
||||||
// Legacy inserts 37 RankColumn rows. Core's canonical rank model
|
// Legacy inserts 37 RankColumn rows. Core's canonical rank model
|
||||||
// additionally projects experience/dedication/dex, so it owns 41.
|
// additionally projects experience/dedication/dex, so it owns 41.
|
||||||
expect(ranks).toHaveLength(41);
|
expect(ranks).toHaveLength(44);
|
||||||
expect(ranks.every((rank) => rank.nationId === 0 && rank.value === 0)).toBe(true);
|
expect(ranks.every((rank) => rank.nationId === 0 && rank.value === 0)).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
await db.logEntry.findMany({
|
await db.logEntry.findMany({
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ integration('RaiseInvader database persistence', () => {
|
|||||||
});
|
});
|
||||||
expect(await db.general.count({ where: { nationId: createdNationId } })).toBe(10);
|
expect(await db.general.count({ where: { nationId: createdNationId } })).toBe(10);
|
||||||
expect(await db.generalTurn.count({ where: { generalId: { gte: firstCreatedGeneralId } } })).toBe(300);
|
expect(await db.generalTurn.count({ where: { generalId: { gte: firstCreatedGeneralId } } })).toBe(300);
|
||||||
expect(await db.rankData.count({ where: { generalId: { gte: firstCreatedGeneralId } } })).toBe(410);
|
expect(await db.rankData.count({ where: { generalId: { gte: firstCreatedGeneralId } } })).toBe(440);
|
||||||
expect(await db.nationTurn.count({ where: { nationId: createdNationId } })).toBe(48);
|
expect(await db.nationTurn.count({ where: { nationId: createdNationId } })).toBe(48);
|
||||||
expect(
|
expect(
|
||||||
await db.diplomacy.count({
|
await db.diplomacy.count({
|
||||||
|
|||||||
@@ -228,16 +228,14 @@ integration('monthly NPC support database persistence', () => {
|
|||||||
affinity: 999,
|
affinity: 999,
|
||||||
meta: expect.objectContaining({ killturn: 70 }),
|
meta: expect.objectContaining({ killturn: 70 }),
|
||||||
});
|
});
|
||||||
expect(
|
expect(await db.troop.findUniqueOrThrow({ where: { troopLeaderId: generalId } })).toMatchObject({
|
||||||
await db.troop.findUniqueOrThrow({ where: { troopLeaderId: generalId } })
|
|
||||||
).toMatchObject({
|
|
||||||
nationId,
|
nationId,
|
||||||
name: '㉥부대장 41',
|
name: '㉥부대장 41',
|
||||||
});
|
});
|
||||||
const turns = await db.generalTurn.findMany({ where: { generalId } });
|
const turns = await db.generalTurn.findMany({ where: { generalId } });
|
||||||
expect(turns).toHaveLength(30);
|
expect(turns).toHaveLength(30);
|
||||||
expect(new Set(turns.map((turn) => turn.actionCode))).toEqual(new Set(['che_집합']));
|
expect(new Set(turns.map((turn) => turn.actionCode))).toEqual(new Set(['che_집합']));
|
||||||
expect(await db.rankData.count({ where: { generalId } })).toBe(41);
|
expect(await db.rankData.count({ where: { generalId } })).toBe(44);
|
||||||
expect((await db.worldState.findUniqueOrThrow({ where: { id: stateRow.id } })).meta).toMatchObject({
|
expect((await db.worldState.findUniqueOrThrow({ where: { id: stateRow.id } })).meta).toMatchObject({
|
||||||
lastNPCTroopLeaderID: 41,
|
lastNPCTroopLeaderID: 41,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,10 +65,7 @@ const map: MapDefinition = {
|
|||||||
level: 5,
|
level: 5,
|
||||||
region: 1,
|
region: 1,
|
||||||
position: { x: index, y: 0 },
|
position: { x: index, y: 0 },
|
||||||
connections: [
|
connections: [...(index > 0 ? [rows[index - 1]!] : []), ...(index + 1 < rows.length ? [rows[index + 1]!] : [])],
|
||||||
...(index > 0 ? [rows[index - 1]!] : []),
|
|
||||||
...(index + 1 < rows.length ? [rows[index + 1]!] : []),
|
|
||||||
],
|
|
||||||
max: {
|
max: {
|
||||||
population: 50_000,
|
population: 50_000,
|
||||||
agriculture: 5_000,
|
agriculture: 5_000,
|
||||||
@@ -294,7 +291,7 @@ integration('RaiseNPCNation database persistence', () => {
|
|||||||
});
|
});
|
||||||
expect(await db.generalTurn.count({ where: { generalId: createdGeneralId } })).toBe(30);
|
expect(await db.generalTurn.count({ where: { generalId: createdGeneralId } })).toBe(30);
|
||||||
expect(await db.nationTurn.count({ where: { nationId: createdNationId } })).toBe(48);
|
expect(await db.nationTurn.count({ where: { nationId: createdNationId } })).toBe(48);
|
||||||
expect(await db.rankData.count({ where: { generalId: createdGeneralId } })).toBe(41);
|
expect(await db.rankData.count({ where: { generalId: createdGeneralId } })).toBe(44);
|
||||||
expect(
|
expect(
|
||||||
await db.diplomacy.count({
|
await db.diplomacy.count({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -53,10 +53,7 @@ integration('RegNPC database persistence', () => {
|
|||||||
const clean = async () => {
|
const clean = async () => {
|
||||||
await db.logEntry.deleteMany({
|
await db.logEntry.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [{ generalId: createdGeneralId }, { year: 200, month: 1, text: { contains: 'ⓝ저장장수' } }],
|
||||||
{ generalId: createdGeneralId },
|
|
||||||
{ year: 200, month: 1, text: { contains: 'ⓝ저장장수' } },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await db.generalTurn.deleteMany({ where: { generalId: createdGeneralId } });
|
await db.generalTurn.deleteMany({ where: { generalId: createdGeneralId } });
|
||||||
@@ -207,7 +204,7 @@ integration('RegNPC database persistence', () => {
|
|||||||
expect(turns).toHaveLength(30);
|
expect(turns).toHaveLength(30);
|
||||||
expect(new Set(turns.map((turn) => turn.actionCode))).toEqual(new Set(['휴식']));
|
expect(new Set(turns.map((turn) => turn.actionCode))).toEqual(new Set(['휴식']));
|
||||||
const ranks = await db.rankData.findMany({ where: { generalId: createdGeneralId } });
|
const ranks = await db.rankData.findMany({ where: { generalId: createdGeneralId } });
|
||||||
expect(ranks).toHaveLength(41);
|
expect(ranks).toHaveLength(44);
|
||||||
expect(ranks.every((rank) => rank.nationId === 0 && rank.value === 0)).toBe(true);
|
expect(ranks.every((rank) => rank.nationId === 0 && rank.value === 0)).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
await db.logEntry.findFirst({
|
await db.logEntry.findFirst({
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ export const RANK_DATA_TYPES = [
|
|||||||
'betwingold',
|
'betwingold',
|
||||||
'inherit_earned',
|
'inherit_earned',
|
||||||
'inherit_spent',
|
'inherit_spent',
|
||||||
|
'inherit_earned_dyn',
|
||||||
|
'inherit_earned_act',
|
||||||
|
'inherit_spent_dyn',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type RankDataType = (typeof RANK_DATA_TYPES)[number];
|
export type RankDataType = (typeof RANK_DATA_TYPES)[number];
|
||||||
|
|||||||
@@ -55,6 +55,19 @@ export interface TurnEngineGeneralRow {
|
|||||||
recentWarTime: Date | null;
|
recentWarTime: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TurnEngineRankDataRow {
|
||||||
|
generalId: number;
|
||||||
|
nationId: number;
|
||||||
|
type: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TurnEngineInheritancePointRow {
|
||||||
|
userId: string;
|
||||||
|
key: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TurnEngineCityRow {
|
export interface TurnEngineCityRow {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -363,6 +376,12 @@ export interface TurnEngineDatabaseClient {
|
|||||||
update(args: { where: { id: number }; data: TurnEngineGeneralUpdateInput }): Promise<unknown>;
|
update(args: { where: { id: number }; data: TurnEngineGeneralUpdateInput }): Promise<unknown>;
|
||||||
deleteMany(args?: unknown): Promise<unknown>;
|
deleteMany(args?: unknown): Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
rankData: {
|
||||||
|
findMany(args?: unknown): Promise<TurnEngineRankDataRow[]>;
|
||||||
|
};
|
||||||
|
inheritancePoint: {
|
||||||
|
findMany(args?: unknown): Promise<TurnEngineInheritancePointRow[]>;
|
||||||
|
};
|
||||||
city: {
|
city: {
|
||||||
findMany(args?: unknown): Promise<TurnEngineCityRow[]>;
|
findMany(args?: unknown): Promise<TurnEngineCityRow[]>;
|
||||||
createMany(args: { data: TurnEngineCityCreateManyInput[] }): Promise<unknown>;
|
createMany(args: { data: TurnEngineCityCreateManyInput[] }): Promise<unknown>;
|
||||||
|
|||||||
Reference in New Issue
Block a user