diff --git a/src/lib/game/familiar-slice.ts b/src/lib/game/familiar-slice.ts deleted file mode 100755 index 8f06c69..0000000 --- a/src/lib/game/familiar-slice.ts +++ /dev/null @@ -1,367 +0,0 @@ -// ─── Familiar Slice ───────────────────────────────────────────────────────────── -// Actions and computations for the familiar system - -import type { GameState, FamiliarInstance, FamiliarAbilityType } from './types'; -import { FAMILIARS_DEF, getFamiliarXpRequired, getFamiliarAbilityValue, canUnlockFamiliar, STARTING_FAMILIAR } from './data/familiars'; -import { HOURS_PER_TICK } from './constants'; - -// ─── Familiar Actions Interface ───────────────────────────────────────────────── - -export interface FamiliarActions { - // Summoning and management - summonFamiliar: (familiarId: string) => void; - setActiveFamiliar: (instanceIndex: number, active: boolean) => void; - setFamiliarNickname: (instanceIndex: number, nickname: string) => void; - - // Progression - gainFamiliarXp: (amount: number, source: 'combat' | 'gather' | 'meditate' | 'study' | 'time') => void; - upgradeFamiliarAbility: (instanceIndex: number, abilityType: FamiliarAbilityType) => void; - - // Computation - getActiveFamiliarBonuses: () => FamiliarBonuses; - getAvailableFamiliars: () => string[]; -} - -// ─── Computed Bonuses ─────────────────────────────────────────────────────────── - -export interface FamiliarBonuses { - damageMultiplier: number; - manaRegenBonus: number; - autoGatherRate: number; - autoConvertRate: number; - critChanceBonus: number; - castSpeedMultiplier: number; - elementalDamageMultiplier: number; - lifeStealPercent: number; - thornsPercent: number; - insightMultiplier: number; - manaShieldAmount: number; -} - -export const DEFAULT_FAMILIAR_BONUSES: FamiliarBonuses = { - damageMultiplier: 1, - manaRegenBonus: 0, - autoGatherRate: 0, - autoConvertRate: 0, - critChanceBonus: 0, - castSpeedMultiplier: 1, - elementalDamageMultiplier: 1, - lifeStealPercent: 0, - thornsPercent: 0, - insightMultiplier: 1, - manaShieldAmount: 0, -}; - -// ─── Familiar Slice Factory ───────────────────────────────────────────────────── - -export function createFamiliarSlice( - set: (fn: (state: GameState) => Partial) => void, - get: () => GameState -): FamiliarActions { - return { - // Summon a new familiar - summonFamiliar: (familiarId: string) => { - const state = get(); - const familiarDef = FAMILIARS_DEF[familiarId]; - if (!familiarDef) return; - - // Check if already owned - if (state.familiars.some(f => f.familiarId === familiarId)) return; - - // Check unlock condition - if (!canUnlockFamiliar( - familiarDef, - state.maxFloorReached, - state.signedPacts, - state.totalManaGathered, - Object.keys(state.skills).length - )) return; - - // Create new familiar instance - const newInstance: FamiliarInstance = { - familiarId, - level: 1, - bond: 0, - experience: 0, - abilities: familiarDef.abilities.map(a => ({ - type: a.type, - level: 1, - })), - active: false, - }; - - // Add to familiars list - set((s) => ({ - familiars: [...s.familiars, newInstance], - log: [`🌟 ${familiarDef.name} has answered your call!`, ...s.log.slice(0, 49)], - })); - }, - - // Set a familiar as active/inactive - setActiveFamiliar: (instanceIndex: number, active: boolean) => { - const state = get(); - if (instanceIndex < 0 || instanceIndex >= state.familiars.length) return; - - const activeCount = state.familiars.filter(f => f.active).length; - - // Check if we have slots available - if (active && activeCount >= state.activeFamiliarSlots) { - // Deactivate another familiar first - const newFamiliars = [...state.familiars]; - const activeIndex = newFamiliars.findIndex(f => f.active); - if (activeIndex >= 0) { - newFamiliars[activeIndex] = { ...newFamiliars[activeIndex], active: false }; - } - newFamiliars[instanceIndex] = { ...newFamiliars[instanceIndex], active }; - set({ familiars: newFamiliars }); - } else { - // Just toggle the familiar - const newFamiliars = [...state.familiars]; - newFamiliars[instanceIndex] = { ...newFamiliars[instanceIndex], active }; - set({ familiars: newFamiliars }); - } - }, - - // Set a familiar's nickname - setFamiliarNickname: (instanceIndex: number, nickname: string) => { - const state = get(); - if (instanceIndex < 0 || instanceIndex >= state.familiars.length) return; - - const newFamiliars = [...state.familiars]; - newFamiliars[instanceIndex] = { - ...newFamiliars[instanceIndex], - nickname: nickname || undefined - }; - set({ familiars: newFamiliars }); - }, - - // Grant XP to all active familiars - gainFamiliarXp: (amount: number, _source: 'combat' | 'gather' | 'meditate' | 'study' | 'time') => { - const state = get(); - if (state.familiars.length === 0) return; - - const newFamiliars = [...state.familiars]; - let leveled = false; - - for (let i = 0; i < newFamiliars.length; i++) { - const familiar = newFamiliars[i]; - if (!familiar.active) continue; - - const def = FAMILIARS_DEF[familiar.familiarId]; - if (!def) continue; - - // Apply bond multiplier to XP gain - const bondMultiplier = 1 + (familiar.bond / 100); - const xpGain = Math.floor(amount * bondMultiplier); - - let newExp = familiar.experience + xpGain; - let newLevel = familiar.level; - - // Check for level ups - while (newLevel < 100 && newExp >= getFamiliarXpRequired(newLevel)) { - newExp -= getFamiliarXpRequired(newLevel); - newLevel++; - leveled = true; - } - - // Gain bond passively - const newBond = Math.min(100, familiar.bond + 0.01); - - newFamiliars[i] = { - ...familiar, - level: newLevel, - experience: newExp, - bond: newBond, - }; - } - - set({ - familiars: newFamiliars, - totalFamiliarXpEarned: state.totalFamiliarXpEarned + amount, - ...(leveled ? { log: ['📈 Your familiar has grown stronger!', ...state.log.slice(0, 49)] } : {}), - }); - }, - - // Upgrade a familiar's ability - upgradeFamiliarAbility: (instanceIndex: number, abilityType: FamiliarAbilityType) => { - const state = get(); - if (instanceIndex < 0 || instanceIndex >= state.familiars.length) return; - - const familiar = state.familiars[instanceIndex]; - const def = FAMILIARS_DEF[familiar.familiarId]; - if (!def) return; - - // Find the ability - const abilityIndex = familiar.abilities.findIndex(a => a.type === abilityType); - if (abilityIndex < 0) return; - - const ability = familiar.abilities[abilityIndex]; - if (ability.level >= 10) return; // Max level - - // Cost: level * 100 XP - const cost = ability.level * 100; - if (familiar.experience < cost) return; - - // Upgrade - const newAbilities = [...familiar.abilities]; - newAbilities[abilityIndex] = { ...ability, level: ability.level + 1 }; - - const newFamiliars = [...state.familiars]; - newFamiliars[instanceIndex] = { - ...familiar, - abilities: newAbilities, - experience: familiar.experience - cost, - }; - - set({ familiars: newFamiliars }); - }, - - // Get total bonuses from active familiars - getActiveFamiliarBonuses: (): FamiliarBonuses => { - const state = get(); - const bonuses = { ...DEFAULT_FAMILIAR_BONUSES }; - - for (const familiar of state.familiars) { - if (!familiar.active) continue; - - const def = FAMILIARS_DEF[familiar.familiarId]; - if (!def) continue; - - // Bond multiplier: up to 50% bonus at max bond - const bondMultiplier = 1 + (familiar.bond / 200); - - for (const abilityInst of familiar.abilities) { - const abilityDef = def.abilities.find(a => a.type === abilityInst.type); - if (!abilityDef) continue; - - const value = getFamiliarAbilityValue(abilityDef, familiar.level, abilityInst.level) * bondMultiplier; - - switch (abilityInst.type) { - case 'damageBonus': - bonuses.damageMultiplier += value / 100; - break; - case 'manaRegen': - bonuses.manaRegenBonus += value; - break; - case 'autoGather': - bonuses.autoGatherRate += value; - break; - case 'autoConvert': - bonuses.autoConvertRate += value; - break; - case 'critChance': - bonuses.critChanceBonus += value; - break; - case 'castSpeed': - bonuses.castSpeedMultiplier += value / 100; - break; - case 'elementalBonus': - bonuses.elementalDamageMultiplier += value / 100; - break; - case 'lifeSteal': - bonuses.lifeStealPercent += value; - break; - case 'thorns': - bonuses.thornsPercent += value; - break; - case 'bonusGold': - bonuses.insightMultiplier += value / 100; - break; - case 'manaShield': - bonuses.manaShieldAmount += value; - break; - } - } - } - - return bonuses; - }, - - // Get list of available (unlocked but not owned) familiars - getAvailableFamiliars: (): string[] => { - const state = get(); - const owned = new Set(state.familiars.map(f => f.familiarId)); - - return Object.values(FAMILIARS_DEF) - .filter(f => - !owned.has(f.id) && - canUnlockFamiliar( - f, - state.maxFloorReached, - state.signedPacts, - state.totalManaGathered, - Object.keys(state.skills).length - ) - ) - .map(f => f.id); - }, - }; -} - -// ─── Familiar Tick Processing ─────────────────────────────────────────────────── - -// Process familiar-related tick effects (called from main tick) -export function processFamiliarTick( - state: Pick, - familiarBonuses: FamiliarBonuses -): { rawMana: number; elements: GameState['elements']; totalManaGathered: number; gatherLog?: string } { - let rawMana = state.rawMana; - let elements = state.elements; - let totalManaGathered = state.totalManaGathered; - let gatherLog: string | undefined; - - // Auto-gather from familiars - if (familiarBonuses.autoGatherRate > 0) { - const gathered = familiarBonuses.autoGatherRate * HOURS_PER_TICK; - rawMana += gathered; - totalManaGathered += gathered; - if (gathered >= 1) { - gatherLog = `✨ Familiars gathered ${Math.floor(gathered)} mana`; - } - } - - // Auto-convert from familiars - if (familiarBonuses.autoConvertRate > 0) { - const convertAmount = Math.min( - familiarBonuses.autoConvertRate * HOURS_PER_TICK, - Math.floor(rawMana / 5) // 5 raw mana per element - ); - - if (convertAmount > 0) { - // Find unlocked elements with space - const unlockedElements = Object.entries(elements) - .filter(([, e]) => e.unlocked && e.current < e.max) - .sort((a, b) => (b[1].max - b[1].current) - (a[1].max - a[1].current)); - - if (unlockedElements.length > 0) { - const [targetId, targetState] = unlockedElements[0]; - const canConvert = Math.min(convertAmount, targetState.max - targetState.current); - rawMana -= canConvert * 5; - elements = { - ...elements, - [targetId]: { ...targetState, current: targetState.current + canConvert }, - }; - } - } - } - - return { rawMana, elements, totalManaGathered, gatherLog }; -} - -// Grant starting familiar to new players -export function grantStartingFamiliar(): FamiliarInstance[] { - const starterDef = FAMILIARS_DEF[STARTING_FAMILIAR]; - if (!starterDef) return []; - - return [{ - familiarId: STARTING_FAMILIAR, - level: 1, - bond: 0, - experience: 0, - abilities: starterDef.abilities.map(a => ({ - type: a.type, - level: 1, - })), - active: true, // Start with familiar active - }]; -}