docs: Add README.md, update AGENTS.md, audit report, and massive refactoring
Documentation: - Add comprehensive README.md with project overview - Update AGENTS.md with new file structure and slice pattern - Add AUDIT_REPORT.md documenting unimplemented effects Refactoring (page.tsx: 1695 → 434 lines, 74% reduction): - Extract SkillsTab.tsx component - Extract StatsTab.tsx component - Extract UpgradeDialog.tsx component - Move getDamageBreakdown and getTotalDPS to computed-stats.ts - Move ELEMENT_ICON_NAMES to constants.ts All lint checks pass, functionality preserved.
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
||||
MAX_DAY,
|
||||
INCURSION_START_DAY,
|
||||
ELEMENT_OPPOSITES,
|
||||
ELEMENTS,
|
||||
TICK_MS,
|
||||
} from './constants';
|
||||
import type { ComputedEffects } from './upgrade-effects';
|
||||
import { getUnifiedEffects, type UnifiedEffects } from './effects';
|
||||
@@ -395,3 +397,95 @@ export function deductSpellCost(
|
||||
|
||||
return { rawMana, elements: newElements };
|
||||
}
|
||||
|
||||
// ─── Damage Breakdown Helper ───────────────────────────────────────────────────
|
||||
|
||||
export interface DamageBreakdown {
|
||||
base: number;
|
||||
combatTrainBonus: number;
|
||||
arcaneFuryMult: number;
|
||||
elemMasteryMult: number;
|
||||
guardianBaneMult: number;
|
||||
pactMult: number;
|
||||
precisionChance: number;
|
||||
elemBonus: number;
|
||||
elemBonusText: string;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export function getDamageBreakdown(
|
||||
state: Pick<GameState, 'skills' | 'signedPacts'>,
|
||||
activeSpellId: string,
|
||||
floorElem: string,
|
||||
isGuardianFloor: boolean
|
||||
): DamageBreakdown | null {
|
||||
const spell = SPELLS_DEF[activeSpellId];
|
||||
if (!spell) return null;
|
||||
|
||||
const baseDmg = spell.dmg;
|
||||
const combatTrainBonus = (state.skills.combatTrain || 0) * 5;
|
||||
const arcaneFuryMult = 1 + (state.skills.arcaneFury || 0) * 0.1;
|
||||
const elemMasteryMult = 1 + (state.skills.elementalMastery || 0) * 0.15;
|
||||
const guardianBaneMult = isGuardianFloor ? (1 + (state.skills.guardianBane || 0) * 0.2) : 1;
|
||||
const pactMult = state.signedPacts.reduce((m, f) => m * (GUARDIANS[f]?.pact || 1), 1);
|
||||
const precisionChance = (state.skills.precision || 0) * 0.05;
|
||||
|
||||
// Elemental bonus
|
||||
let elemBonus = 1.0;
|
||||
let elemBonusText = '';
|
||||
if (spell.elem !== 'raw' && floorElem) {
|
||||
if (spell.elem === floorElem) {
|
||||
elemBonus = 1.25;
|
||||
elemBonusText = '+25% same element';
|
||||
} else if (ELEMENTS[spell.elem] && ELEMENT_OPPOSITES[floorElem] === spell.elem) {
|
||||
elemBonus = 1.5;
|
||||
elemBonusText = '+50% super effective';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
base: baseDmg,
|
||||
combatTrainBonus,
|
||||
arcaneFuryMult,
|
||||
elemMasteryMult,
|
||||
guardianBaneMult,
|
||||
pactMult,
|
||||
precisionChance,
|
||||
elemBonus,
|
||||
elemBonusText,
|
||||
total: calcDamage(state, activeSpellId, floorElem)
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Total DPS Calculation ─────────────────────────────────────────────────────
|
||||
|
||||
export function getTotalDPS(
|
||||
state: Pick<GameState, 'skills' | 'signedPacts' | 'equippedInstances' | 'equipmentInstances'>,
|
||||
upgradeEffects: { attackSpeedMultiplier: number },
|
||||
floorElem: string
|
||||
): number {
|
||||
const quickCastBonus = 1 + (state.skills.quickCast || 0) * 0.05;
|
||||
const attackSpeedMult = upgradeEffects.attackSpeedMultiplier;
|
||||
const castsPerSecondMult = HOURS_PER_TICK / (TICK_MS / 1000);
|
||||
|
||||
const activeEquipmentSpells = getActiveEquipmentSpells(
|
||||
state.equippedInstances,
|
||||
state.equipmentInstances
|
||||
);
|
||||
|
||||
let totalDPS = 0;
|
||||
|
||||
for (const { spellId } of activeEquipmentSpells) {
|
||||
const spell = SPELLS_DEF[spellId];
|
||||
if (!spell) continue;
|
||||
|
||||
const spellCastSpeed = spell.castSpeed || 1;
|
||||
const totalCastSpeed = spellCastSpeed * quickCastBonus * attackSpeedMult;
|
||||
const damagePerCast = calcDamage(state, spellId, floorElem);
|
||||
const castsPerSecond = totalCastSpeed * castsPerSecondMult;
|
||||
|
||||
totalDPS += damagePerCast * castsPerSecond;
|
||||
}
|
||||
|
||||
return totalDPS;
|
||||
}
|
||||
|
||||
@@ -835,3 +835,28 @@ export const ELEMENT_OPPOSITES: Record<string, string> = {
|
||||
light: 'dark', dark: 'light',
|
||||
life: 'death', death: 'life',
|
||||
};
|
||||
|
||||
// ─── Element Icon Mapping (Lucide Icons) ──────────────────────────────────────
|
||||
// Note: These are string identifiers for dynamic icon loading
|
||||
// The actual Lucide icons are imported in the components
|
||||
export const ELEMENT_ICON_NAMES: Record<string, string> = {
|
||||
fire: 'Flame',
|
||||
water: 'Droplet',
|
||||
air: 'Wind',
|
||||
earth: 'Mountain',
|
||||
light: 'Sun',
|
||||
dark: 'Moon',
|
||||
life: 'Leaf',
|
||||
death: 'Skull',
|
||||
mental: 'Brain',
|
||||
transference: 'Link',
|
||||
force: 'Wind',
|
||||
blood: 'Droplets',
|
||||
metal: 'Target',
|
||||
wood: 'TreeDeciduous',
|
||||
sand: 'Hourglass',
|
||||
crystal: 'Gem',
|
||||
stellar: 'Star',
|
||||
void: 'CircleDot',
|
||||
raw: 'Circle',
|
||||
};
|
||||
|
||||
@@ -72,6 +72,8 @@ import {
|
||||
getIncursionStrength,
|
||||
canAffordSpellCost,
|
||||
deductSpellCost,
|
||||
getTotalDPS,
|
||||
getDamageBreakdown,
|
||||
} from './computed-stats';
|
||||
|
||||
// Re-export formatting functions and computed stats for backward compatibility
|
||||
@@ -87,6 +89,9 @@ export {
|
||||
getIncursionStrength,
|
||||
canAffordSpellCost,
|
||||
getFloorMaxHP,
|
||||
getActiveEquipmentSpells,
|
||||
getTotalDPS,
|
||||
getDamageBreakdown,
|
||||
};
|
||||
|
||||
// ─── Local Helper Functions ────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user