feat: add unification handler and inheritance system
- Implemented `unificationHandler.ts` to manage nation unification logic, including inheritance point calculations and logging. - Created `InheritView.vue` for frontend management of inheritance points, buffs, and logs. - Added database migration for new inheritance tables: `inheritance_point`, `inheritance_log`, `inheritance_result`, and `inheritance_user_state`. - Developed inheritance buff logic in `inheritBuff.ts` to apply buffs during domestic and war actions.
This commit is contained in:
@@ -206,3 +206,50 @@ model LogEntry {
|
||||
@@index([userId, category, id])
|
||||
@@map("log_entry")
|
||||
}
|
||||
|
||||
model InheritancePoint {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @map("user_id")
|
||||
key String
|
||||
value Float @default(0)
|
||||
aux Json @default(dbgenerated("'{}'::jsonb"))
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@unique([userId, key])
|
||||
@@index([userId])
|
||||
@@map("inheritance_point")
|
||||
}
|
||||
|
||||
model InheritanceLog {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @map("user_id")
|
||||
year Int
|
||||
month Int
|
||||
text String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([userId, id])
|
||||
@@map("inheritance_log")
|
||||
}
|
||||
|
||||
model InheritanceResult {
|
||||
id Int @id @default(autoincrement())
|
||||
serverId String @map("server_id")
|
||||
owner String @map("owner")
|
||||
generalId Int @map("general_id")
|
||||
year Int
|
||||
month Int
|
||||
value Json @default(dbgenerated("'{}'::jsonb"))
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([serverId, owner])
|
||||
@@map("inheritance_result")
|
||||
}
|
||||
|
||||
model InheritanceUserState {
|
||||
userId String @id @map("user_id")
|
||||
meta Json @default(dbgenerated("'{}'::jsonb"))
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("inheritance_user_state")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
CREATE TABLE "inheritance_point" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"value" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"aux" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "inheritance_point_user_id_key" ON "inheritance_point"("user_id", "key");
|
||||
CREATE INDEX "inheritance_point_user_id_idx" ON "inheritance_point"("user_id");
|
||||
|
||||
CREATE TABLE "inheritance_log" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"year" INTEGER NOT NULL,
|
||||
"month" INTEGER NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX "inheritance_log_user_id_idx" ON "inheritance_log"("user_id", "id");
|
||||
|
||||
CREATE TABLE "inheritance_result" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"server_id" TEXT NOT NULL,
|
||||
"owner" TEXT NOT NULL,
|
||||
"general_id" INTEGER NOT NULL,
|
||||
"year" INTEGER NOT NULL,
|
||||
"month" INTEGER NOT NULL,
|
||||
"value" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX "inheritance_result_server_owner_idx" ON "inheritance_result"("server_id", "owner");
|
||||
|
||||
CREATE TABLE "inheritance_user_state" (
|
||||
"user_id" TEXT PRIMARY KEY,
|
||||
"meta" JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GamePrisma, GamePrismaClient } from './gamePrisma.js';
|
||||
|
||||
export interface DatabaseClient {
|
||||
$transaction: GamePrismaClient['$transaction'];
|
||||
$transaction?: GamePrismaClient['$transaction'];
|
||||
$queryRaw: GamePrismaClient['$queryRaw'];
|
||||
worldState: GamePrisma.WorldStateDelegate;
|
||||
general: GamePrisma.GeneralDelegate;
|
||||
@@ -10,4 +10,8 @@ export interface DatabaseClient {
|
||||
generalTurn: GamePrisma.GeneralTurnDelegate;
|
||||
nationTurn: GamePrisma.NationTurnDelegate;
|
||||
troop: GamePrisma.TroopDelegate;
|
||||
inheritancePoint: GamePrisma.InheritancePointDelegate;
|
||||
inheritanceLog: GamePrisma.InheritanceLogDelegate;
|
||||
inheritanceResult: GamePrisma.InheritanceResultDelegate;
|
||||
inheritanceUserState: GamePrisma.InheritanceUserStateDelegate;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from './logging/index.js';
|
||||
export * from './messages/index.js';
|
||||
export * from './items/index.js';
|
||||
export { ITEM_KEYS, createItemActionModules, createItemModuleRegistry, loadItemModules } from './items/index.js';
|
||||
export * from './inheritance/inheritBuff.js';
|
||||
export * from './resources/index.js';
|
||||
export * from './ports/world.js';
|
||||
export * from './ports/worldSnapshot.js';
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import type { TriggerDomesticActionType, TriggerDomesticVarType, WarStatName } from '@sammo-ts/logic/triggers/types.js';
|
||||
import type { GeneralActionModule } from '@sammo-ts/logic/triggers/general-action.js';
|
||||
import type { WarActionModule } from '@sammo-ts/logic/war/actions.js';
|
||||
import { asRecord, parseJson } from '@sammo-ts/common';
|
||||
|
||||
export type InheritBuffType =
|
||||
| 'warAvoidRatio'
|
||||
| 'warCriticalRatio'
|
||||
| 'warMagicTrialProb'
|
||||
| 'success'
|
||||
| 'fail'
|
||||
| 'warAvoidRatioOppose'
|
||||
| 'warCriticalRatioOppose'
|
||||
| 'warMagicTrialProbOppose';
|
||||
|
||||
const DOMESTIC_TARGETS = new Set<TriggerDomesticActionType>([
|
||||
'상업',
|
||||
'농업',
|
||||
'치안',
|
||||
'성벽',
|
||||
'수비',
|
||||
'민심',
|
||||
'인구',
|
||||
'기술',
|
||||
]);
|
||||
|
||||
const readBuffLevel = (buff: Record<string, unknown>, key: InheritBuffType): number => {
|
||||
const raw = buff[key];
|
||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(0, Math.min(5, Math.floor(raw)));
|
||||
};
|
||||
|
||||
const parseInheritBuff = (value: unknown): Record<string, unknown> => {
|
||||
if (typeof value === 'string') {
|
||||
const parsed = parseJson<Record<string, unknown>>(value);
|
||||
return parsed ?? {};
|
||||
}
|
||||
return asRecord(value);
|
||||
};
|
||||
|
||||
const resolveBuffRecord = (context: { general: { meta: Record<string, unknown>; triggerState: { meta: Record<string, unknown> } } }): Record<string, unknown> => {
|
||||
const fromTrigger = parseInheritBuff(context.general.triggerState.meta.inheritBuff);
|
||||
if (Object.keys(fromTrigger).length > 0) {
|
||||
return fromTrigger;
|
||||
}
|
||||
return parseInheritBuff(context.general.meta.inheritBuff);
|
||||
};
|
||||
|
||||
const applyDomesticBuff = (
|
||||
buff: Record<string, unknown>,
|
||||
turnType: TriggerDomesticActionType,
|
||||
varType: TriggerDomesticVarType,
|
||||
value: number
|
||||
): number => {
|
||||
if (!DOMESTIC_TARGETS.has(turnType)) {
|
||||
return value;
|
||||
}
|
||||
if (varType === 'success') {
|
||||
const level = readBuffLevel(buff, 'success');
|
||||
return value + level * 0.01;
|
||||
}
|
||||
if (varType === 'fail') {
|
||||
const level = readBuffLevel(buff, 'fail');
|
||||
return value - level * 0.01;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const applyWarBuff = (buff: Record<string, unknown>, statName: WarStatName, value: number | [number, number]) => {
|
||||
if (typeof value !== 'number') {
|
||||
return value;
|
||||
}
|
||||
if (statName === 'warAvoidRatio') {
|
||||
return value + readBuffLevel(buff, 'warAvoidRatio') * 0.01;
|
||||
}
|
||||
if (statName === 'warCriticalRatio') {
|
||||
return value + readBuffLevel(buff, 'warCriticalRatio') * 0.01;
|
||||
}
|
||||
if (statName === 'warMagicTrialProb') {
|
||||
return value + readBuffLevel(buff, 'warMagicTrialProb') * 0.01;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const applyOpposeWarBuff = (
|
||||
buff: Record<string, unknown>,
|
||||
statName: WarStatName,
|
||||
value: number | [number, number]
|
||||
) => {
|
||||
if (typeof value !== 'number') {
|
||||
return value;
|
||||
}
|
||||
if (statName === 'warAvoidRatio') {
|
||||
return value - readBuffLevel(buff, 'warAvoidRatioOppose') * 0.01;
|
||||
}
|
||||
if (statName === 'warCriticalRatio') {
|
||||
return value - readBuffLevel(buff, 'warCriticalRatioOppose') * 0.01;
|
||||
}
|
||||
if (statName === 'warMagicTrialProb') {
|
||||
return value - readBuffLevel(buff, 'warMagicTrialProbOppose') * 0.01;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const createInheritBuffModules = (): { general: GeneralActionModule; war: WarActionModule } => {
|
||||
const general: GeneralActionModule = {
|
||||
onCalcDomestic: (context, turnType, varType, value) =>
|
||||
applyDomesticBuff(resolveBuffRecord(context), turnType, varType, value),
|
||||
};
|
||||
|
||||
const war: WarActionModule = {
|
||||
onCalcStat: (context, statName, value) => applyWarBuff(resolveBuffRecord(context), statName, value),
|
||||
onCalcOpposeStat: (context, statName, value) => applyOpposeWarBuff(resolveBuffRecord(context), statName, value),
|
||||
};
|
||||
|
||||
return { general, war };
|
||||
};
|
||||
Reference in New Issue
Block a user