fix: Ref 유산 포인트 적립 조건을 전면 정합화한다

능동 행동 31개 호출 지점과 소유자·NPC·통일 경계를 고정하고 사용자 저장값을 함께 갱신한다.\n\n최대 내정·임관, 천통 기여, 토너먼트, 숙련·베팅·랭크 계산과 환생 정산을 공통 계산기로 통합한다.
This commit is contained in:
2026-08-23 12:06:59 +00:00
parent 2f793a9518
commit 18e0bed30a
19 changed files with 712 additions and 245 deletions
@@ -1,6 +1,7 @@
import { asRecord, HALL_OF_FAME_TYPES, resolveLegacyTextColor, type HallOfFameType } from '@sammo-ts/common';
import type { GamePrisma, InputJsonValue } from '@sammo-ts/infra';
import { LogCategory, LogScope } from '@sammo-ts/logic';
import { computeInheritanceSettlementBreakdown } from '@sammo-ts/logic/inheritance/pointCalculation.js';
import type { GeneralLifecycleEvent } from './inMemoryWorld.js';
@@ -23,14 +24,6 @@ const readWorldNumber = (record: Record<string, unknown>, key: string, fallback:
return value === 0 && record[key] === undefined ? fallback : Math.floor(value);
};
const computeDexPoint = (meta: Record<string, unknown>): number => {
let total = 0;
for (let dex = 1; dex <= 5; dex += 1) {
total += readNumber(meta, `dex${dex}`);
}
return total * 0.001;
};
const settleInheritance = async (
prisma: GamePrisma.TransactionClient,
event: GeneralLifecycleEvent,
@@ -71,8 +64,6 @@ const settleInheritance = async (
}),
]);
const points = new Map(rows.map((row) => [row.key, row.value]));
const ranks = new Map(rankRows.map((row) => [row.type, row.value]));
const rank = (key: string): number => ranks.get(key) ?? readNumber(meta, `rank_${key}`);
const previous = points.get('previous') ?? 0;
const randomUniqueRefund = meta.inheritRandomUnique
? readWorldNumber(configConst, 'inheritItemRandomPoint', 3000)
@@ -81,30 +72,45 @@ const settleInheritance = async (
? readWorldNumber(configConst, 'inheritSpecificSpecialPoint', 4000)
: 0;
const refund = randomUniqueRefund + specificSpecialRefund;
const lived = readNumber(meta, 'inherit_lived_month');
const maxBelong = readNumber(meta, 'inherit_max_belong') * 10;
const maxDomestic = readNumber(meta, 'max_domestic_critical');
const active = readNumber(meta, 'inherit_active_action') * 3;
const combat = rank('warnum') * 5;
const sabotage = (ranks.get('firenum') ?? readNumber(meta, 'firenum')) * 20;
const dex = computeDexPoint(meta);
const unifier = points.get('unifier') ?? 0;
const earned = isRebirth
? lived + active + combat + sabotage + dex * 0.5
: lived + maxBelong + maxDomestic + active + combat + sabotage + dex + unifier;
const total = Math.trunc(previous + refund + earned);
const calculationMeta = {
...meta,
...Object.fromEntries(rankRows.map((row) => [row.type, row.value])),
...Object.fromEntries(rankRows.map((row) => [`rank_${row.type}`, row.value])),
};
const settlement = computeInheritanceSettlementBreakdown(
{
meta: calculationMeta,
inheritancePoints: Object.fromEntries(points),
},
isRebirth
);
const total = Math.trunc(previous + refund + settlement.totalEarned);
await prisma.inheritancePoint.upsert({
where: { userId_key: { userId, key: 'previous' } },
update: { value: total },
create: { userId, key: 'previous', value: total },
});
await prisma.inheritancePoint.deleteMany({
where: {
userId,
key: isRebirth ? { notIn: ['previous', 'unifier'] } : { not: 'previous' },
},
});
if (isRebirth) {
const retainedEntries = Object.entries(settlement.retained).filter(
([key, value]) => key === 'max_belong' || points.has(key) || value !== 0
);
for (const [key, value] of retainedEntries) {
await prisma.inheritancePoint.upsert({
where: { userId_key: { userId, key } },
update: { value },
create: { userId, key, value },
});
}
await prisma.inheritancePoint.deleteMany({
where: {
userId,
key: { notIn: ['previous', ...retainedEntries.map(([key]) => key)] },
},
});
} else {
await prisma.inheritancePoint.deleteMany({ where: { userId, key: { not: 'previous' } } });
}
const serverId =
typeof worldMeta.serverId === 'string' && worldMeta.serverId.trim() ? worldMeta.serverId.trim() : 'default';
await prisma.inheritanceResult.create({
@@ -117,15 +123,10 @@ const settleInheritance = async (
value: asJson({
previous,
refund,
lived_month: lived,
max_belong: maxBelong,
max_domestic_critical: maxDomestic,
active_action: active,
combat,
sabotage,
dex: isRebirth ? dex * 0.5 : dex,
unifier: isRebirth ? 0 : unifier,
...settlement.earned,
...(isRebirth ? { retained: settlement.retained } : {}),
rebirth: isRebirth,
total,
}),
},
});