feat: improve combat feedback and progression

This commit is contained in:
2026-07-19 00:34:11 +09:00
parent f29956b90e
commit 87e3ddb047
16 changed files with 1284 additions and 80 deletions

View File

@@ -0,0 +1,109 @@
export const combatPresentationStorageKey = 'heros-web:combat-presentation-mode';
export type CombatPresentationMode = 'always' | 'important' | 'map';
export type CombatPresentationImportance = {
signature?: boolean;
critical?: boolean;
defeated?: boolean;
resonance?: boolean;
growth?: boolean;
};
type StorageLike = Pick<Storage, 'getItem' | 'setItem'>;
export const combatPresentationModes: readonly CombatPresentationMode[] = ['always', 'important', 'map'];
export const combatPresentationModeLabels: Record<CombatPresentationMode, string> = {
always: '항상',
important: '중요 장면만',
map: '지도 연출'
};
export function isCombatPresentationMode(value: unknown): value is CombatPresentationMode {
return typeof value === 'string' && combatPresentationModes.includes(value as CombatPresentationMode);
}
export function nextCombatPresentationMode(mode: CombatPresentationMode): CombatPresentationMode {
const index = combatPresentationModes.indexOf(mode);
return combatPresentationModes[(index + 1) % combatPresentationModes.length];
}
export function combatPresentationModeLabel(mode: CombatPresentationMode) {
return combatPresentationModeLabels[mode];
}
export function shouldUseFullCombatCutIn(
mode: CombatPresentationMode,
importance: CombatPresentationImportance
) {
if (mode === 'always') {
return true;
}
if (mode === 'map') {
return false;
}
return Boolean(
importance.signature ||
importance.critical ||
importance.defeated ||
importance.resonance ||
importance.growth
);
}
export function combatPresentationImportanceReason(importance: CombatPresentationImportance) {
if (importance.signature) {
return '고유 전법';
}
if (importance.defeated) {
return '격파';
}
if (importance.critical) {
return '치명타';
}
if (importance.resonance) {
return '공명';
}
if (importance.growth) {
return '레벨 상승';
}
return '일반 행동';
}
export function loadCombatPresentationMode(storage = resolveStorage()): CombatPresentationMode {
if (!storage) {
return 'important';
}
try {
const value = storage.getItem(combatPresentationStorageKey);
return isCombatPresentationMode(value) ? value : 'important';
} catch {
return 'important';
}
}
export function saveCombatPresentationMode(
mode: CombatPresentationMode,
storage = resolveStorage()
) {
if (!storage) {
return;
}
try {
storage.setItem(combatPresentationStorageKey, mode);
} catch {
// Local storage can be unavailable in private or restricted browser contexts.
}
}
function resolveStorage(): StorageLike | undefined {
if (typeof window === 'undefined') {
return undefined;
}
try {
return window.localStorage;
} catch {
return undefined;
}
}