Ref처럼 장수와 월 실행 시각을 logical tick bucket으로 비교해 이미 실행된 장수만 다음 달부터 예약 턴을 표시한다. 12월 연도 전환과 Date-only fallback을 단위 테스트 및 production Chromium으로 검증한다.
96 lines
3.9 KiB
TypeScript
96 lines
3.9 KiB
TypeScript
import { GAME_TICKS_PER_TURN } from '@sammo-ts/common';
|
|
|
|
export interface GeneralBasicStats {
|
|
leadership: number;
|
|
strength: number;
|
|
intelligence: number;
|
|
}
|
|
|
|
export const resolveGeneralTypeCall = (stats: GeneralBasicStats, chiefStatMin: number, statGradeLevel = 5): string => {
|
|
const { leadership, strength, intelligence } = stats;
|
|
if (leadership < 40) {
|
|
if (strength + intelligence < 40) return '아둔';
|
|
if (intelligence >= chiefStatMin && strength < intelligence * 0.8) return '학자';
|
|
if (strength >= chiefStatMin && intelligence < strength * 0.8) return '장사';
|
|
return '명사';
|
|
}
|
|
|
|
const maxStat = Math.max(leadership, strength, intelligence);
|
|
const sumTwoStats = Math.min(leadership + strength, strength + intelligence, intelligence + leadership);
|
|
if (maxStat >= chiefStatMin + statGradeLevel && sumTwoStats >= maxStat * 1.7) return '만능';
|
|
if (strength >= chiefStatMin - statGradeLevel && intelligence < strength * 0.8) return '용장';
|
|
if (intelligence >= chiefStatMin - statGradeLevel && strength < intelligence * 0.8) return '명장';
|
|
if (leadership >= chiefStatMin - statGradeLevel && strength + intelligence < leadership) return '차장';
|
|
return '평범';
|
|
};
|
|
|
|
export const resolveLeadershipBonus = (officerLevel: number, nationLevel: number): number => {
|
|
if (officerLevel === 12) return nationLevel * 2;
|
|
if (officerLevel >= 5) return nationLevel;
|
|
return 0;
|
|
};
|
|
|
|
export const resolveRefreshScoreText = (score: number): string => {
|
|
if (score < 50) return '안함';
|
|
if (score < 100) return '무관심';
|
|
if (score < 200) return '보통';
|
|
if (score < 400) return '가끔';
|
|
if (score < 800) return '자주';
|
|
if (score < 1_600) return '열심';
|
|
if (score < 3_200) return '중독';
|
|
if (score < 6_400) return '폐인';
|
|
if (score < 12_800) return '경고';
|
|
return '헐...';
|
|
};
|
|
|
|
export const resolveRemainingMinutes = (
|
|
turnTime: Date,
|
|
lastExecuted: Date | null,
|
|
turnTermSeconds: number
|
|
): number | null => {
|
|
if (!lastExecuted || !Number.isFinite(lastExecuted.getTime()) || turnTermSeconds <= 0) return null;
|
|
let nextTurnMillis = turnTime.getTime();
|
|
if (nextTurnMillis < lastExecuted.getTime()) {
|
|
nextTurnMillis += turnTermSeconds * 1_000;
|
|
}
|
|
return Math.floor(Math.min(999, Math.max(0, (nextTurnMillis - lastExecuted.getTime()) / 60_000)));
|
|
};
|
|
|
|
export interface NextTurnMonthOffsetInput {
|
|
turnTime: Date;
|
|
turnTick?: bigint | number | null;
|
|
lastExecuted: Date | null;
|
|
lastTurnTick?: bigint | number | null;
|
|
turnSeconds: number;
|
|
}
|
|
|
|
const normalizeTick = (value: bigint | number | null | undefined): bigint | null => {
|
|
if (typeof value === 'bigint') return value;
|
|
if (typeof value === 'number' && Number.isSafeInteger(value)) return BigInt(value);
|
|
return null;
|
|
};
|
|
|
|
const turnBucket = (tick: bigint): bigint => {
|
|
const ticksPerTurn = BigInt(GAME_TICKS_PER_TURN);
|
|
const quotient = tick / ticksPerTurn;
|
|
return tick % ticksPerTurn < 0 ? quotient - 1n : quotient;
|
|
};
|
|
|
|
/**
|
|
* Ref Command.GetReservedCommand cuts both clocks to a gameplay-turn bucket.
|
|
* A general in the next bucket has already acted in the displayed world month,
|
|
* so the first reserved command belongs to the following month.
|
|
*/
|
|
export const resolveNextTurnMonthOffset = (input: NextTurnMonthOffsetInput): 0 | 1 => {
|
|
const turnTick = normalizeTick(input.turnTick);
|
|
const lastTurnTick = normalizeTick(input.lastTurnTick);
|
|
if (turnTick !== null && lastTurnTick !== null) {
|
|
return turnBucket(turnTick) > turnBucket(lastTurnTick) ? 1 : 0;
|
|
}
|
|
|
|
const turnTimeMs = input.turnTime.getTime();
|
|
const lastExecutedMs = input.lastExecuted?.getTime() ?? Number.NaN;
|
|
if (!Number.isFinite(turnTimeMs) || !Number.isFinite(lastExecutedMs) || input.turnSeconds <= 0) return 0;
|
|
return turnTimeMs >= lastExecutedMs + input.turnSeconds * 1_000 ? 1 : 0;
|
|
};
|