feat: add prestige system and skill upgrades with comprehensive documentation
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 5m57s
This commit is contained in:
@@ -15,4 +15,5 @@ export const PRESTIGE_DEF: Record<string, PrestigeDef> = {
|
||||
guardianPact: { name: "Guardian Pact", desc: "+10% pact multiplier", max: 5, cost: 3500 },
|
||||
quickStart: { name: "Quick Start", desc: "Start with 100 raw mana", max: 3, cost: 400 },
|
||||
elemStart: { name: "Elem. Start", desc: "Start with 5 of each unlocked element", max: 3, cost: 800 },
|
||||
unlockedManaTypeCapacity: { name: "Mana Type Capacity", desc: "+10 capacity for selected mana type", max: 5, cost: 1000 },
|
||||
};
|
||||
|
||||
@@ -48,20 +48,20 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
|
||||
researchEarthSpells: { name: "Earth Spell Research", desc: "Unlock Stone Bullet, Rock Spike spell enchantments", cat: "effectResearch", max: 1, base: 350, studyTime: 6, req: { enchanting: 2 }, cost: { type: 'element', element: 'earth', amount: 100 }, attunementReq: { enchanter: 1 } },
|
||||
researchLightSpells: { name: "Light Spell Research", desc: "Unlock Light Lance, Radiance spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'light', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
researchDarkSpells: { name: "Dark Spell Research", desc: "Unlock Shadow Bolt, Dark Pulse spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'dark', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
researchLifeDeathSpells: { name: "Death Research", desc: "Unlock Drain spell enchantment", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 , cost: { type: 'element', element: 'death', amount: 100 }}, attunementReq: { enchanter: 2 } },
|
||||
researchLifeDeathSpells: { name: "Death Research", desc: "Unlock Drain spell enchantment", cat: "effectResearch", max: 1, base: 400, studyTime: 8, req: { enchanting: 3 }, cost: { type: 'element', element: 'death', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
|
||||
// Tier 2 - Advanced Spell Effects - Require Enchanter 3
|
||||
researchAdvancedFire: { name: "Advanced Fire Research", desc: "Unlock Inferno, Flame Wave spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchFireSpells: 1, enchanting: 4 , cost: { type: 'element', element: 'fire', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedWater: { name: "Advanced Water Research", desc: "Unlock Tidal Wave, Ice Storm spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchWaterSpells: 1, enchanting: 4 , cost: { type: 'element', element: 'water', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedAir: { name: "Advanced Air Research", desc: "Unlock Hurricane, Wind Blade spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchAirSpells: 1, enchanting: 4 , cost: { type: 'element', element: 'air', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedEarth: { name: "Advanced Earth Research", desc: "Unlock Earthquake, Stone Barrage spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchEarthSpells: 1, enchanting: 4 , cost: { type: 'element', element: 'earth', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedLight: { name: "Advanced Light Research", desc: "Unlock Solar Flare, Divine Smite spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchLightSpells: 1, enchanting: 5 , cost: { type: 'element', element: 'light', amount: 100 }}, attunementReq: { enchanter: 4 } },
|
||||
researchAdvancedDark: { name: "Advanced Dark Research", desc: "Unlock Void Rift, Shadow Storm spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchDarkSpells: 1, enchanting: 5 , cost: { type: 'element', element: 'dark', amount: 100 }}, attunementReq: { enchanter: 4 } },
|
||||
researchAdvancedFire: { name: "Advanced Fire Research", desc: "Unlock Inferno, Flame Wave spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchFireSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'fire', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedWater: { name: "Advanced Water Research", desc: "Unlock Tidal Wave, Ice Storm spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchWaterSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'water', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedAir: { name: "Advanced Air Research", desc: "Unlock Hurricane, Wind Blade spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchAirSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'air', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedEarth: { name: "Advanced Earth Research", desc: "Unlock Earthquake, Stone Barrage spell enchantments", cat: "effectResearch", max: 1, base: 600, studyTime: 12, req: { researchEarthSpells: 1, enchanting: 4 }, cost: { type: 'element', element: 'earth', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedLight: { name: "Advanced Light Research", desc: "Unlock Solar Flare, Divine Smite spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchLightSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'light', amount: 100 }, attunementReq: { enchanter: 4 } },
|
||||
researchAdvancedDark: { name: "Advanced Dark Research", desc: "Unlock Void Rift, Shadow Storm spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 14, req: { researchDarkSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'dark', amount: 100 }, attunementReq: { enchanter: 4 } },
|
||||
|
||||
// Tier 3 - Master Spell Effects - Require Enchanter 5
|
||||
researchMasterFire: { name: "Master Fire Research", desc: "Unlock Pyroclasm spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedFire: 1, enchanting: 7 , cost: { type: 'element', element: 'fire', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterWater: { name: "Master Water Research", desc: "Unlock Tsunami spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedWater: 1, enchanting: 7 , cost: { type: 'element', element: 'water', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterEarth: { name: "Master Earth Research", desc: "Unlock Meteor Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedEarth: 1, enchanting: 8 , cost: { type: 'element', element: 'earth', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterFire: { name: "Master Fire Research", desc: "Unlock Pyroclasm spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedFire: 1, enchanting: 7 }, cost: { type: 'element', element: 'fire', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
researchMasterWater: { name: "Master Water Research", desc: "Unlock Tsunami spell enchantment", cat: "effectResearch", max: 1, base: 1200, studyTime: 24, req: { researchAdvancedWater: 1, enchanting: 7 }, cost: { type: 'element', element: 'water', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
researchMasterEarth: { name: "Master Earth Research", desc: "Unlock Meteor Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedEarth: 1, enchanting: 8 }, cost: { type: 'element', element: 'earth', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
|
||||
// Combat Effect Research
|
||||
researchDamageEffects: { name: "Damage Effect Research", desc: "Unlock Minor/Moderate Power, Amplification effects", cat: "effectResearch", max: 1, base: 250, studyTime: 5, req: { enchanting: 1 }, attunementReq: { enchanter: 1 } },
|
||||
@@ -83,19 +83,19 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Tier 1 - Basic Compound Spells
|
||||
researchMetalSpells: { name: "Metal Spell Research", desc: "Unlock Metal Shard, Iron Fist spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchEarthSpells: 1, enchanting: 3 , cost: { type: 'element', element: 'metal', amount: 100 }}, attunementReq: { enchanter: 2 } },
|
||||
researchSandSpells: { name: "Sand Spell Research", desc: "Unlock Sand Blast, Sandstorm spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchEarthSpells: 1, researchWaterSpells: 1, enchanting: 3 , cost: { type: 'element', element: 'sand', amount: 100 }}, attunementReq: { enchanter: 2 } },
|
||||
researchLightningSpells: { name: "Lightning Spell Research", desc: "Unlock Spark, Lightning Bolt spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchAirSpells: 1, enchanting: 3 , cost: { type: 'element', element: 'lightning', amount: 100 }}, attunementReq: { enchanter: 2 } },
|
||||
researchMetalSpells: { name: "Metal Spell Research", desc: "Unlock Metal Shard, Iron Fist spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchEarthSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'metal', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
researchSandSpells: { name: "Sand Spell Research", desc: "Unlock Sand Blast, Sandstorm spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchEarthSpells: 1, researchWaterSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'sand', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
researchLightningSpells: { name: "Lightning Spell Research", desc: "Unlock Spark, Lightning Bolt spell enchantments", cat: "effectResearch", max: 1, base: 400, studyTime: 6, req: { researchFireSpells: 1, researchAirSpells: 1, enchanting: 3 }, cost: { type: 'element', element: 'lightning', amount: 100 }, attunementReq: { enchanter: 2 } },
|
||||
|
||||
// Tier 2 - Advanced Compound Spells
|
||||
researchAdvancedMetal: { name: "Advanced Metal Research", desc: "Unlock Steel Tempest spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchMetalSpells: 1, enchanting: 5 , cost: { type: 'element', element: 'metal', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedSand: { name: "Advanced Sand Research", desc: "Unlock Desert Wind spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSandSpells: 1, enchanting: 5 , cost: { type: 'element', element: 'sand', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedLightning: { name: "Advanced Lightning Research", desc: "Unlock Chain Lightning, Storm Call spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchLightningSpells: 1, enchanting: 5 , cost: { type: 'element', element: 'lightning', amount: 100 }}, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedMetal: { name: "Advanced Metal Research", desc: "Unlock Steel Tempest spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchMetalSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'metal', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedSand: { name: "Advanced Sand Research", desc: "Unlock Desert Wind spell enchantment", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSandSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'sand', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
researchAdvancedLightning: { name: "Advanced Lightning Research", desc: "Unlock Chain Lightning, Storm Call spell enchantments", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchLightningSpells: 1, enchanting: 5 }, cost: { type: 'element', element: 'lightning', amount: 100 }, attunementReq: { enchanter: 3 } },
|
||||
|
||||
// Tier 3 - Master Compound Spells
|
||||
researchMasterMetal: { name: "Master Metal Research", desc: "Unlock Furnace Blast spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedMetal: 1, enchanting: 7 , cost: { type: 'element', element: 'metal', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterSand: { name: "Master Sand Research", desc: "Unlock Dune Collapse spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedSand: 1, enchanting: 7 , cost: { type: 'element', element: 'sand', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterLightning: { name: "Master Lightning Research", desc: "Unlock Thunder Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedLightning: 1, enchanting: 7 , cost: { type: 'element', element: 'lightning', amount: 200 }}, attunementReq: { enchanter: 5 } },
|
||||
researchMasterMetal: { name: "Master Metal Research", desc: "Unlock Furnace Blast spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedMetal: 1, enchanting: 7 }, cost: { type: 'element', element: 'metal', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
researchMasterSand: { name: "Master Sand Research", desc: "Unlock Dune Collapse spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedSand: 1, enchanting: 7 }, cost: { type: 'element', element: 'sand', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
researchMasterLightning: { name: "Master Lightning Research", desc: "Unlock Thunder Strike spell enchantment", cat: "effectResearch", max: 1, base: 1300, studyTime: 26, req: { researchAdvancedLightning: 1, enchanting: 7 }, cost: { type: 'element', element: 'lightning', amount: 200 }, attunementReq: { enchanter: 5 } },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// UTILITY MANA SPELL RESEARCH - Transference
|
||||
@@ -292,6 +292,53 @@ export const EFFECT_RESEARCH_MAPPING: Record<string, string[]> = {
|
||||
|
||||
// Tier 3 - Master Utility Spells
|
||||
researchMasterTransference: ['spell_soulTransfer'],
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PER-ELEMENT CAPACITY RESEARCH - Unlocks per-element capacity effects
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Basic Element Capacity Effects (Tier 1 - +10 per stack)
|
||||
researchFireCapacity: ['fire_cap_10'],
|
||||
researchWaterCapacity: ['water_cap_10'],
|
||||
researchAirCapacity: ['air_cap_10'],
|
||||
researchEarthCapacity: ['earth_cap_10'],
|
||||
researchLightCapacity: ['light_cap_10'],
|
||||
researchDarkCapacity: ['dark_cap_10'],
|
||||
researchDeathCapacity: ['death_cap_10'],
|
||||
|
||||
// Advanced Element Capacity Effects (Tier 2 - +25 per stack)
|
||||
researchAdvancedFireCap: ['fire_cap_25'],
|
||||
researchAdvancedWaterCap: ['water_cap_25'],
|
||||
researchAdvancedAirCap: ['air_cap_25'],
|
||||
researchAdvancedEarthCap: ['earth_cap_25'],
|
||||
researchAdvancedLightCap: ['light_cap_25'],
|
||||
researchAdvancedDarkCap: ['dark_cap_25'],
|
||||
researchAdvancedDeathCap: ['death_cap_25'],
|
||||
|
||||
// Master Element Capacity Effects (Tier 3 - +50 per stack)
|
||||
researchMasterFireCap: ['fire_cap_50'],
|
||||
researchMasterWaterCap: ['water_cap_50'],
|
||||
researchMasterAirCap: ['air_cap_50'],
|
||||
researchMasterEarthCap: ['earth_cap_50'],
|
||||
researchMasterLightCap: ['light_cap_50'],
|
||||
researchMasterDarkCap: ['dark_cap_50'],
|
||||
researchMasterDeathCap: ['death_cap_50'],
|
||||
|
||||
// Composite Element Capacity Effects
|
||||
researchMetalCapacity: ['metal_cap_10'],
|
||||
researchAdvancedMetalCap: ['metal_cap_25', 'metal_cap_50'],
|
||||
researchSandCapacity: ['sand_cap_10'],
|
||||
researchAdvancedSandCap: ['sand_cap_25', 'sand_cap_50'],
|
||||
researchLightningCapacity: ['lightning_cap_10'],
|
||||
researchAdvancedLightningCap: ['lightning_cap_25', 'lightning_cap_50'],
|
||||
|
||||
// Exotic Element Capacity Effects
|
||||
researchCrystalCapacity: ['crystal_cap_10'],
|
||||
researchAdvancedCrystalCap: ['crystal_cap_25', 'crystal_cap_50'],
|
||||
researchStellarCapacity: ['stellar_cap_10'],
|
||||
researchAdvancedStellarCap: ['stellar_cap_25', 'stellar_cap_50'],
|
||||
researchVoidCapacity: ['void_cap_10'],
|
||||
researchAdvancedVoidCap: ['void_cap_25', 'void_cap_50'],
|
||||
};
|
||||
|
||||
// Base effects unlocked when player gets enchanting skill level 1
|
||||
|
||||
@@ -7,7 +7,9 @@ import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchant
|
||||
import { CRAFTING_RECIPES, canCraftRecipe, type CraftingRecipe } from './data/crafting-recipes';
|
||||
import { SPELLS_DEF } from './constants';
|
||||
import { calculateEnchantingXP, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects';
|
||||
import { computeEffects } from './upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from './special-effects';
|
||||
import type { ComputedEffects } from './upgrade-effects.types';
|
||||
|
||||
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// ─── Mana Enchantment Effects ────────────────────────────────────────────────
|
||||
// All mana-related enchantment effects that can be applied to equipment
|
||||
|
||||
// Import ELEMENTS to get the list of elements for per-element capacity effects
|
||||
import { ELEMENTS } from '../../constants';
|
||||
|
||||
import type { EquipmentCategory } from '../equipment'
|
||||
import type { EnchantmentEffectDef } from '../enchantment-types'
|
||||
|
||||
@@ -149,4 +152,50 @@ export const MANA_EFFECTS: Record<string, EnchantmentEffectDef> = {
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaRegen', value: 5 }
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PER-ELEMENT CAPACITY EFFECTS - Boosts capacity for specific mana types
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Helper to create per-element capacity effects for a given element
|
||||
// Creates 3 tiers: +10 (5 stacks), +25 (3 stacks), +50 (2 stacks)
|
||||
...Object.fromEntries(
|
||||
Object.entries(ELEMENTS)
|
||||
.filter(([, def]) => def.cat !== 'utility') // Skip utility elements like transference
|
||||
.flatMap(([elemId, elemDef]) => {
|
||||
const capName = elemId.charAt(0).toUpperCase() + elemId.slice(1);
|
||||
return [
|
||||
[`${elemId}_cap_10`, {
|
||||
id: `${elemId}_cap_10`,
|
||||
name: `${capName} Reservoir`,
|
||||
description: `+10 ${elemDef.name} mana capacity`,
|
||||
category: 'mana',
|
||||
baseCapacityCost: 30,
|
||||
maxStacks: 5,
|
||||
allowedEquipmentCategories: MANA_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: `elementCap_${elemId}`, value: 10 }
|
||||
}],
|
||||
[`${elemId}_cap_25`, {
|
||||
id: `${elemId}_cap_25`,
|
||||
name: `${capName} Basin`,
|
||||
description: `+25 ${elemDef.name} mana capacity`,
|
||||
category: 'mana',
|
||||
baseCapacityCost: 60,
|
||||
maxStacks: 3,
|
||||
allowedEquipmentCategories: MANA_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: `elementCap_${elemId}`, value: 25 }
|
||||
}],
|
||||
[`${elemId}_cap_50`, {
|
||||
id: `${elemId}_cap_50`,
|
||||
name: `${capName} Wellspring`,
|
||||
description: `+50 ${elemDef.name} mana capacity`,
|
||||
category: 'mana',
|
||||
baseCapacityCost: 100,
|
||||
maxStacks: 2,
|
||||
allowedEquipmentCategories: MANA_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: `elementCap_${elemId}`, value: 50 }
|
||||
}],
|
||||
];
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// ─── Dynamic Computations ──────────────────────────────────────────────────
|
||||
// Dynamic computation functions that depend on special effects
|
||||
|
||||
import type { ComputedEffects } from './upgrade-effects.types';
|
||||
import { SPECIAL_EFFECTS, hasSpecial } from './special-effects';
|
||||
|
||||
/**
|
||||
* Compute regen with special effects that depend on dynamic values
|
||||
*/
|
||||
export function computeDynamicRegen(
|
||||
effects: ComputedEffects,
|
||||
baseRegen: number,
|
||||
maxMana: number,
|
||||
currentMana: number,
|
||||
incursionStrength: number
|
||||
): number {
|
||||
let regen = baseRegen;
|
||||
|
||||
// Mana Cascade: +0.1 regen per 100 max mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_CASCADE)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.1;
|
||||
}
|
||||
|
||||
// Mana Waterfall: +0.25 regen per 100 max mana (upgraded cascade)
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_WATERFALL)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.25;
|
||||
}
|
||||
|
||||
// Mana Torrent: +50% regen when above 75% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_TORRENT) && currentMana > maxMana * 0.75) {
|
||||
regen *= 1.5;
|
||||
}
|
||||
|
||||
// Desperate Wells / Despair Wells: +50% regen when below 25% mana
|
||||
if ((hasSpecial(effects, SPECIAL_EFFECTS.DESPERATE_WELLS) || hasSpecial(effects, SPECIAL_EFFECTS.DESPAIR_WELLS)) && currentMana < maxMana * 0.25) {
|
||||
regen *= 1.5;
|
||||
}
|
||||
|
||||
// Panic Reserve: +100% regen when below 10% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.PANIC_RESERVE) && currentMana < maxMana * 0.1) {
|
||||
regen *= 2.0;
|
||||
}
|
||||
|
||||
// Deep Reserve: +0.5 regen per 100 max mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.DEEP_RESERVE)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.5;
|
||||
}
|
||||
|
||||
// Mana Core: 0.5% of max mana added as regen
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_CORE)) {
|
||||
regen += maxMana * 0.005;
|
||||
}
|
||||
|
||||
// Mana Tide: Regen pulses ±50% (sinusoidal based on time)
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_TIDE)) {
|
||||
const pulseFactor = 0.5 + 0.5 * Math.sin(Date.now() / 10000);
|
||||
regen *= (0.5 + pulseFactor * 0.5);
|
||||
}
|
||||
|
||||
// Eternal Flow: Regen immune to ALL penalties
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.ETERNAL_FLOW)) {
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
// Steady Stream: Regen immune to incursion
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.STEADY_STREAM)) {
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
// Apply incursion penalty
|
||||
regen *= (1 - incursionStrength);
|
||||
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute click mana with special effects
|
||||
*/
|
||||
export function computeDynamicClickMana(
|
||||
effects: ComputedEffects,
|
||||
baseClickMana: number
|
||||
): number {
|
||||
let clickMana = baseClickMana;
|
||||
|
||||
// Mana Echo: 10% chance to gain double mana from clicks
|
||||
// Note: The chance is handled in the click handler, this just returns the base
|
||||
// The click handler should check hasSpecial and apply the 10% chance
|
||||
|
||||
// Mana Genesis: Generate 1% of max mana per hour passively
|
||||
// This is handled in the game loop (store.ts), not here
|
||||
|
||||
// Mana Heart: +10% max mana per loop (permanent)
|
||||
// This is applied during loop reset in store.ts
|
||||
|
||||
return Math.floor((clickMana + effects.clickManaBonus) * effects.clickManaMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute damage with special effects
|
||||
*/
|
||||
export function computeDynamicDamage(
|
||||
effects: ComputedEffects,
|
||||
baseDamage: number,
|
||||
floorHPPct: number,
|
||||
currentMana: number,
|
||||
maxMana: number
|
||||
): number {
|
||||
let damage = baseDamage * effects.baseDamageMultiplier;
|
||||
|
||||
// Overpower: +50% damage when mana above 80%
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.OVERPOWER) && currentMana >= maxMana * 0.8) {
|
||||
damage *= 1.5;
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && currentMana < maxMana * 0.5) {
|
||||
damage *= 1.5;
|
||||
}
|
||||
|
||||
// Combo Master: Every 5th attack deals 3x damage
|
||||
// Note: The hit counter is tracked in game state, this just returns the multiplier
|
||||
// The combat handler should check hasSpecial and the hit count
|
||||
|
||||
return damage + effects.baseDamageBonus;
|
||||
}
|
||||
+42
-12
@@ -1,4 +1,4 @@
|
||||
// ─── Unified Effect System ─────────────────────────────────────────────────────────
|
||||
// ─── Unified Effect System ─────────────────────────────────────────────────
|
||||
// This module consolidates ALL effect sources into a single computation:
|
||||
// - Skill upgrade effects (from milestone upgrades)
|
||||
// - Equipment enchantment effects (from enchanted gear)
|
||||
@@ -6,19 +6,25 @@
|
||||
|
||||
import type { GameState, EquipmentInstance } from './types';
|
||||
import { ENCHANTMENT_EFFECTS } from './data/enchantment-effects';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects';
|
||||
import { computeEffects } from './upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from './special-effects';
|
||||
import type { ComputedEffects } from './upgrade-effects.types';
|
||||
|
||||
// Re-export for convenience
|
||||
export { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects };
|
||||
export { computeEffects } from './upgrade-effects';
|
||||
export { hasSpecial, SPECIAL_EFFECTS } from './special-effects';
|
||||
export type { ComputedEffects } from './upgrade-effects.types';
|
||||
|
||||
// ─── Equipment Effect Computation ────────────────────────────────────────────────
|
||||
// ─── Equipment Effect Computation ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Compute all effects from equipped enchantments
|
||||
* @param enchantmentPowerMultiplier - Multiplier applied to all enchantment effect values (default 1.0)
|
||||
*/
|
||||
export function computeEquipmentEffects(
|
||||
equipmentInstances: Record<string, EquipmentInstance>,
|
||||
equippedInstances: Record<string, string | null>
|
||||
equippedInstances: Record<string, string | null>,
|
||||
enchantmentPowerMultiplier: number = 1.0
|
||||
): {
|
||||
bonuses: Record<string, number>;
|
||||
multipliers: Record<string, number>;
|
||||
@@ -43,17 +49,27 @@ export function computeEquipmentEffects(
|
||||
|
||||
if (effect.type === 'bonus' && effect.stat && effect.value) {
|
||||
// Bonus effects add to the stat
|
||||
bonuses[effect.stat] = (bonuses[effect.stat] || 0) + effect.value * ench.stacks;
|
||||
// Apply enchantmentPowerMultiplier to the effect value
|
||||
const adjustedValue = effect.value * enchantmentPowerMultiplier;
|
||||
// Handle per-element capacity bonuses (stat format: elementCap_fire, elementCap_water, etc.)
|
||||
if (effect.stat.startsWith('elementCap_')) {
|
||||
const element = effect.stat.replace('elementCap_', '');
|
||||
bonuses[`elementCap_${element}`] = (bonuses[`elementCap_${element}`] || 0) + adjustedValue * ench.stacks;
|
||||
} else {
|
||||
bonuses[effect.stat] = (bonuses[effect.stat] || 0) + adjustedValue * ench.stacks;
|
||||
}
|
||||
} else if (effect.type === 'multiplier' && effect.stat && effect.value) {
|
||||
// Multiplier effects multiply together
|
||||
// For multipliers, we need to track them separately and apply as product
|
||||
// Apply enchantmentPowerMultiplier to the effect value
|
||||
const adjustedValue = effect.value * enchantmentPowerMultiplier;
|
||||
const key = effect.stat;
|
||||
if (!multipliers[key]) {
|
||||
multipliers[key] = 1;
|
||||
}
|
||||
// Each stack applies the multiplier
|
||||
for (let i = 0; i < ench.stacks; i++) {
|
||||
multipliers[key] *= effect.value;
|
||||
multipliers[key] *= adjustedValue;
|
||||
}
|
||||
} else if (effect.type === 'special' && effect.specialId) {
|
||||
specials.add(effect.specialId);
|
||||
@@ -64,7 +80,7 @@ export function computeEquipmentEffects(
|
||||
return { bonuses, multipliers, specials };
|
||||
}
|
||||
|
||||
// ─── Unified Computed Effects ────────────────────────────────────────────────────
|
||||
// ─── Unified Computed Effects ─────────────────────────────────────────────────
|
||||
|
||||
export interface UnifiedEffects extends ComputedEffects {
|
||||
// Equipment bonuses
|
||||
@@ -85,8 +101,21 @@ export function computeAllEffects(
|
||||
// Get skill upgrade effects
|
||||
const upgradeEffects = computeEffects(skillUpgrades, skillTiers);
|
||||
|
||||
// Get equipment effects
|
||||
const equipmentEffects = computeEquipmentEffects(equipmentInstances, equippedInstances);
|
||||
// Get equipment effects, applying the enchantment power multiplier
|
||||
const equipmentEffects = computeEquipmentEffects(
|
||||
equipmentInstances,
|
||||
equippedInstances,
|
||||
upgradeEffects.enchantmentPowerMultiplier
|
||||
);
|
||||
|
||||
// Extract per-element capacity bonuses from equipment effects
|
||||
const perElementCapBonus: Record<string, number> = { ...upgradeEffects.perElementCapBonus };
|
||||
for (const [key, value] of Object.entries(equipmentEffects.bonuses)) {
|
||||
if (key.startsWith('elementCap_')) {
|
||||
const element = key.replace('elementCap_', '');
|
||||
perElementCapBonus[element] = (perElementCapBonus[element] || 0) + value;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the effects
|
||||
const merged: UnifiedEffects = {
|
||||
@@ -97,6 +126,7 @@ export function computeAllEffects(
|
||||
clickManaBonus: upgradeEffects.clickManaBonus + (equipmentEffects.bonuses.clickMana || 0),
|
||||
baseDamageBonus: upgradeEffects.baseDamageBonus + (equipmentEffects.bonuses.baseDamage || 0),
|
||||
elementCapBonus: upgradeEffects.elementCapBonus + (equipmentEffects.bonuses.elementCap || 0),
|
||||
perElementCapBonus,
|
||||
// Merge equipment multipliers with upgrade multipliers
|
||||
maxManaMultiplier: upgradeEffects.maxManaMultiplier * (equipmentEffects.multipliers.maxMana || 1),
|
||||
regenMultiplier: upgradeEffects.regenMultiplier * (equipmentEffects.multipliers.regen || 1),
|
||||
@@ -105,7 +135,7 @@ export function computeAllEffects(
|
||||
attackSpeedMultiplier: upgradeEffects.attackSpeedMultiplier * (equipmentEffects.multipliers.attackSpeed || 1),
|
||||
elementCapMultiplier: upgradeEffects.elementCapMultiplier * (equipmentEffects.multipliers.elementCap || 1),
|
||||
// Merge specials
|
||||
specials: new Set([...upgradeEffects.specials, ...equipmentEffects.specials]),
|
||||
specials: new Set([...Array.from(upgradeEffects.specials), ...Array.from(equipmentEffects.specials)]),
|
||||
// Store equipment effects for reference
|
||||
equipmentBonuses: equipmentEffects.bonuses,
|
||||
equipmentMultipliers: equipmentEffects.multipliers,
|
||||
@@ -147,7 +177,7 @@ export function getUnifiedEffects(state: Pick<GameState, 'skillUpgrades' | 'skil
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Stat Computation with All Effects ───────────────────────────────────────────
|
||||
// ─── Stat Computation with All Effects ───────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Compute max mana with all effect sources
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
getElementalBonus,
|
||||
} from '../store/computed';
|
||||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, getStudySpeedMultiplier, getStudyCostMultiplier, HOURS_PER_TICK, TICK_MS } from '../constants';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../special-effects';
|
||||
|
||||
/**
|
||||
* Hook for all mana-related derived stats
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// ─── Skill Upgrade Selection Hook ────────────────────────
|
||||
// Hook for managing milestone upgrade selection state in SkillsTab
|
||||
|
||||
import { useState, useMemo, useCallback, SetStateAction, Dispatch } from 'react';
|
||||
import type { SkillUpgradeChoice } from '@/lib/game/types';
|
||||
|
||||
export interface UseSkillUpgradeSelectionResult {
|
||||
pendingSelections: string[];
|
||||
setPendingSelections: Dispatch<SetStateAction<string[]>>;
|
||||
toggleUpgrade: (upgradeId: string, available: SkillUpgradeChoice[], alreadySelected: string[]) => void;
|
||||
handleConfirm: (upgradeDialogSkill: string | null, upgradeDialogMilestone: 5 | 10, commitSkillUpgrades: (skillId: string, selections: string[], milestone: 5 | 10) => void, onClose: () => void) => void;
|
||||
handleCancel: (onClose: () => void) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing skill upgrade selection state in the SkillsTab milestone upgrade dialog.
|
||||
* Manages pending selections across the dialog open/close cycle.
|
||||
*/
|
||||
export function useSkillUpgradeSelection(): UseSkillUpgradeSelectionResult {
|
||||
const [pendingSelections, setPendingSelections] = useState<string[]>([]);
|
||||
|
||||
const toggleUpgrade = useCallback((upgradeId: string, available: SkillUpgradeChoice[], alreadySelected: string[]) => {
|
||||
const currentSelections = pendingSelections.length > 0 ? pendingSelections : alreadySelected;
|
||||
if (currentSelections.includes(upgradeId)) {
|
||||
setPendingSelections(currentSelections.filter(id => id !== upgradeId));
|
||||
} else if (currentSelections.length < 2) {
|
||||
setPendingSelections([...currentSelections, upgradeId]);
|
||||
}
|
||||
}, [pendingSelections]);
|
||||
|
||||
const handleConfirm = useCallback((upgradeDialogSkill: string | null, upgradeDialogMilestone: 5 | 10, commitSkillUpgrades: (skillId: string, selections: string[], milestone: 5 | 10) => void, onClose: () => void) => {
|
||||
const currentSelections = pendingSelections.length > 0 ? pendingSelections : [];
|
||||
if (currentSelections.length === 2 && upgradeDialogSkill) {
|
||||
commitSkillUpgrades(upgradeDialogSkill, currentSelections, upgradeDialogMilestone);
|
||||
}
|
||||
setPendingSelections([]);
|
||||
onClose();
|
||||
}, [pendingSelections]);
|
||||
|
||||
const handleCancel = useCallback((onClose: () => void) => {
|
||||
setPendingSelections([]);
|
||||
onClose();
|
||||
}, []);
|
||||
|
||||
return useMemo(() => ({
|
||||
pendingSelections,
|
||||
setPendingSelections,
|
||||
toggleUpgrade,
|
||||
handleConfirm,
|
||||
handleCancel,
|
||||
}), [pendingSelections, toggleUpgrade, handleConfirm, handleCancel]);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// ─── Special Effect IDs ────────────────────────────────────────────────────────
|
||||
// These are the IDs used in the 'specialId' field of special effects
|
||||
|
||||
import type { ComputedEffects } from './upgrade-effects.types';
|
||||
|
||||
export const SPECIAL_EFFECTS = {
|
||||
// Mana Flow special effects
|
||||
MANA_CASCADE: 'manaCascade',
|
||||
STEADY_STREAM: 'steadyStream',
|
||||
MANA_TORRENT: 'manaTorrent',
|
||||
FLOW_SURGE: 'flowSurge',
|
||||
MANA_OVERFLOW: 'manaOverflow',
|
||||
MANA_WATERFALL: 'manaWaterfall',
|
||||
ETERNAL_FLOW: 'eternalFlow',
|
||||
|
||||
// Mana Well special effects
|
||||
DESPAIR_WELLS: 'despairWells',
|
||||
DESPERATE_WELLS: 'desperateWells',
|
||||
MANA_ECHO: 'manaEcho',
|
||||
EMERGENCY_RESERVE: 'emergencyReserve',
|
||||
MANA_THRESHOLD: 'manaThreshold',
|
||||
MANA_CONVERSION: 'manaConversion',
|
||||
PANIC_RESERVE: 'panicReserve',
|
||||
MANA_CONDENSE: 'manaCondense',
|
||||
DEEP_RESERVE: 'deepReserve',
|
||||
MANA_TIDE: 'manaTide',
|
||||
VOID_STORAGE: 'voidStorage',
|
||||
MANA_CORE: 'manaCore',
|
||||
MANA_HEART: 'manaHeart',
|
||||
MANA_GENESIS: 'manaGenesis',
|
||||
|
||||
// Mana Overflow special effects
|
||||
CLICK_SURGE: 'clickSurge',
|
||||
MANA_FLOOD: 'manaFlood',
|
||||
|
||||
// Combat special effects
|
||||
FIRST_STRIKE: 'firstStrike',
|
||||
OVERPOWER: 'overpower',
|
||||
BERSERKER: 'berserker',
|
||||
EXECUTIONER: 'executioner',
|
||||
COMBO_MASTER: 'comboMaster',
|
||||
ADRENALINE_RUSH: 'adrenalineRush',
|
||||
|
||||
// Study special effects
|
||||
QUICK_GRASP: 'quickGrasp',
|
||||
DEEP_CONCENTRATION: 'deepConcentration',
|
||||
QUICK_MASTERY: 'quickMastery',
|
||||
PARALLEL_STUDY: 'parallelStudy',
|
||||
STUDY_MOMENTUM: 'studyMomentum',
|
||||
KNOWLEDGE_ECHO: 'knowledgeEcho',
|
||||
KNOWLEDGE_TRANSFER: 'knowledgeTransfer',
|
||||
MENTAL_CLARITY: 'mentalClarity',
|
||||
STUDY_REFUND: 'studyRefund',
|
||||
DEEP_UNDERSTANDING: 'deepUnderstanding',
|
||||
STUDY_RUSH: 'studyRush',
|
||||
CHAIN_STUDY: 'chainStudy',
|
||||
|
||||
// Element special effects
|
||||
ELEMENTAL_AFFINITY: 'elementalAffinity',
|
||||
EXOTIC_MASTERY: 'exoticMastery',
|
||||
ELEMENTAL_RESONANCE: 'elementalResonance',
|
||||
MANA_CONDUIT: 'manaConduit',
|
||||
|
||||
// Enchanting special effects
|
||||
ENCHANT_MASTERY: 'enchantMastery',
|
||||
ENCHANT_PRESERVATION: 'enchantPreservation',
|
||||
THRIFTY_ENCHANTER: 'thriftyEnchanter',
|
||||
OPTIMIZED_ENCHANTING: 'optimizedEnchanting',
|
||||
HASTY_ENCHANTER: 'hastyEnchanter',
|
||||
INSTANT_DESIGNS: 'instantDesigns',
|
||||
PURE_ESSENCE: 'pureEssence',
|
||||
|
||||
// Crafting special effects
|
||||
BATCH_CRAFTING: 'batchCrafting',
|
||||
MASS_PRODUCTION: 'massProduction',
|
||||
SCAVENGE: 'scavenge',
|
||||
RECLAIM: 'reclaim',
|
||||
|
||||
// Golemancy special effects
|
||||
GOLEM_FURY: 'golemFury',
|
||||
GOLEM_RESONANCE: 'golemResonance',
|
||||
RAPID_STRIKES: 'rapidStrikes',
|
||||
BLITZ_ATTACK: 'blitzAttack',
|
||||
|
||||
// Ascension special effects
|
||||
INSIGHT_BOUNTY: 'insightBounty',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Check if a special effect is active
|
||||
*/
|
||||
export function hasSpecial(effects: ComputedEffects, specialId: string): boolean {
|
||||
return effects?.specials?.has(specialId) ?? false;
|
||||
}
|
||||
+98
-11
@@ -32,7 +32,9 @@ import {
|
||||
SPEED_ROOM_CONFIG,
|
||||
FLOOR_ARMOR_CONFIG,
|
||||
} from './constants';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects';
|
||||
import { computeEffects } from './upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from './special-effects';
|
||||
import type { ComputedEffects } from './upgrade-effects.types';
|
||||
import {
|
||||
computeAllEffects,
|
||||
getUnifiedEffects,
|
||||
@@ -76,11 +78,14 @@ const DEFAULT_EFFECTS: ComputedEffects = {
|
||||
freeStudyChance: 0,
|
||||
elementCapMultiplier: 1,
|
||||
elementCapBonus: 0,
|
||||
perElementCapBonus: {},
|
||||
conversionCostMultiplier: 1,
|
||||
doubleCraftChance: 0,
|
||||
permanentRegenBonus: 0,
|
||||
specials: new Set(),
|
||||
activeUpgrades: [],
|
||||
skillLevelMultiplier: 1,
|
||||
enchantmentPowerMultiplier: 1,
|
||||
};
|
||||
|
||||
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
||||
@@ -166,6 +171,38 @@ export function getDodgeChance(floor: number): number {
|
||||
);
|
||||
}
|
||||
|
||||
// Get health regen for an enemy (0-1 as percentage of max HP per tick)
|
||||
export function getEnemyHealthRegen(floor: number, element: string): number {
|
||||
// Higher floors have a chance for enemies with health regen
|
||||
if (floor < 15) return 0;
|
||||
|
||||
// Health regen becomes more common on higher floors
|
||||
const regenChance = Math.min(0.3, (floor - 15) * 0.005); // Max 30% chance
|
||||
if (Math.random() > regenChance) return 0;
|
||||
|
||||
// Scale regen with floor (0.5% to 3% of max HP per tick)
|
||||
const floorProgress = Math.min(1, (floor - 15) / 85);
|
||||
return 0.005 + floorProgress * 0.025;
|
||||
}
|
||||
|
||||
// Get barrier for an enemy (0-1 as percentage of max HP)
|
||||
export function getEnemyBarrier(floor: number, element: string): number {
|
||||
// Barrier appears on higher floors, more common with certain elements
|
||||
if (floor < 20) return 0;
|
||||
|
||||
// Barrier chance based on element - light/water/earth more likely
|
||||
const barrierElements = ['light', 'water', 'earth'];
|
||||
const baseChance = barrierElements.includes(element) ? 0.15 : 0.08;
|
||||
const floorBonus = Math.min(0.25, (floor - 20) * 0.003); // Max 25% additional chance
|
||||
const barrierChance = Math.min(0.4, baseChance + floorBonus);
|
||||
|
||||
if (Math.random() > barrierChance) return 0;
|
||||
|
||||
// Barrier is 10% to 30% of max HP
|
||||
const floorProgress = Math.min(1, (floor - 20) / 80);
|
||||
return 0.1 + floorProgress * 0.2;
|
||||
}
|
||||
|
||||
// ─── Enemy Naming System ───────────────────────────────────────────────
|
||||
// Generate enemy names based on element and floor tier
|
||||
const ENEMY_NAMES_BY_ELEMENT: Record<string, string[]> = {
|
||||
@@ -211,6 +248,8 @@ export function generateSwarmEnemies(floor: number): EnemyState[] {
|
||||
maxHP: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
|
||||
armor: SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor,
|
||||
dodgeChance: 0,
|
||||
healthRegen: getEnemyHealthRegen(floor, element),
|
||||
barrier: getEnemyBarrier(floor, element),
|
||||
element,
|
||||
});
|
||||
}
|
||||
@@ -235,6 +274,8 @@ export function generateFloorState(floor: number): FloorState {
|
||||
maxHP: guardian.hp,
|
||||
armor: guardian.armor || 0,
|
||||
dodgeChance: 0,
|
||||
healthRegen: 0.01, // Guardians have 1% HP regen per tick
|
||||
barrier: 0, // Guardians don't have barrier by default (could be added later)
|
||||
element: guardian.element,
|
||||
}],
|
||||
};
|
||||
@@ -256,6 +297,8 @@ export function generateFloorState(floor: number): FloorState {
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: getDodgeChance(floor),
|
||||
healthRegen: getEnemyHealthRegen(floor, element),
|
||||
barrier: getEnemyBarrier(floor, element),
|
||||
element,
|
||||
}],
|
||||
};
|
||||
@@ -287,6 +330,8 @@ export function generateFloorState(floor: number): FloorState {
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: 0,
|
||||
healthRegen: getEnemyHealthRegen(floor, element),
|
||||
barrier: getEnemyBarrier(floor, element),
|
||||
element,
|
||||
}],
|
||||
};
|
||||
@@ -370,17 +415,37 @@ export function computeMaxMana(
|
||||
}
|
||||
|
||||
export function computeElementMax(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
|
||||
effects?: ComputedEffects
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'unlockedManaTypeUpgrades'>,
|
||||
effects?: ComputedEffects | UnifiedEffects,
|
||||
element?: string
|
||||
): number {
|
||||
const pu = state.prestigeUpgrades;
|
||||
const base = 10 + (state.skills.elemAttune || 0) * 50 + (pu.elementalAttune || 0) * 25;
|
||||
|
||||
// Apply unlockedManaTypeCapacity bonus for specific element (always apply)
|
||||
let adjustedBase = base;
|
||||
if (element && state.unlockedManaTypeUpgrades) {
|
||||
const typeUpgrades = state.unlockedManaTypeUpgrades.filter(u => u.typeId === element);
|
||||
const totalLevels = typeUpgrades.reduce((sum, u) => sum + u.level, 0);
|
||||
adjustedBase = base + (totalLevels * 10);
|
||||
}
|
||||
|
||||
// Apply upgrade effects if provided
|
||||
if (effects) {
|
||||
return Math.floor((base + effects.elementCapBonus) * effects.elementCapMultiplier);
|
||||
let bonus = effects.elementCapBonus || 0; // Global bonus
|
||||
|
||||
// Add per-element bonus if element is specified and available
|
||||
if (element && (effects as UnifiedEffects).perElementCapBonus) {
|
||||
const perElementBonus = (effects as UnifiedEffects).perElementCapBonus[element];
|
||||
if (perElementBonus) {
|
||||
bonus += perElementBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.floor((adjustedBase + bonus) * (effects.elementCapMultiplier || 1));
|
||||
}
|
||||
return base;
|
||||
|
||||
return adjustedBase;
|
||||
}
|
||||
|
||||
export function computeRegen(
|
||||
@@ -436,7 +501,7 @@ export function computeEffectiveRegenForDisplay(
|
||||
* Compute regen with dynamic special effects (needs current mana, max mana, incursion)
|
||||
*/
|
||||
export function computeEffectiveRegen(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers'>,
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
||||
effects?: ComputedEffects
|
||||
): number {
|
||||
// Base regen from existing function
|
||||
@@ -627,8 +692,9 @@ function deductSpellCost(
|
||||
function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
const pu = overrides.prestigeUpgrades || {};
|
||||
const startFloor = 1 + (pu.spireKey || 0) * 2;
|
||||
const elemMax = computeElementMax({ skills: overrides.skills || {}, prestigeUpgrades: pu });
|
||||
const effects = overrides.skillUpgrades ? computeEffects(overrides.skillUpgrades || {}, overrides.skillTiers || {}) : undefined;
|
||||
const manaHeartBonus = overrides.manaHeartBonus || 0;
|
||||
const unlockedManaTypeUpgrades = overrides.unlockedManaTypeUpgrades || [];
|
||||
|
||||
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
|
||||
Object.keys(ELEMENTS).forEach((k) => {
|
||||
@@ -640,9 +706,18 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
startAmount = pu.elemStart * 5;
|
||||
}
|
||||
|
||||
// Calculate per-element max capacity including unlockedManaTypeCapacity upgrades
|
||||
const baseElemMax = computeElementMax({
|
||||
skills: overrides.skills || {},
|
||||
prestigeUpgrades: pu,
|
||||
skillUpgrades: overrides.skillUpgrades || {},
|
||||
skillTiers: overrides.skillTiers || {},
|
||||
unlockedManaTypeUpgrades
|
||||
}, effects, k);
|
||||
|
||||
elements[k] = {
|
||||
current: overrides.elements?.[k]?.current ?? startAmount,
|
||||
max: elemMax,
|
||||
max: baseElemMax,
|
||||
unlocked: isUnlocked,
|
||||
};
|
||||
});
|
||||
@@ -821,6 +896,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
|
||||
// Activity Log (for Spire Mode UI)
|
||||
activityLog: [],
|
||||
|
||||
// Track selected mana types for unlockedManaTypeCapacity upgrade
|
||||
unlockedManaTypeUpgrades: unlockedManaTypeUpgrades,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -868,7 +946,7 @@ export interface GameStore extends GameState, CraftingActions {
|
||||
convertMana: (element: string, amount: number) => void;
|
||||
unlockElement: (element: string) => void;
|
||||
craftComposite: (target: string) => void;
|
||||
doPrestige: (id: string) => void;
|
||||
doPrestige: (id: string, selectedManaType?: string) => void;
|
||||
startNewLoop: () => void;
|
||||
togglePause: () => void;
|
||||
resetGame: () => void;
|
||||
@@ -2164,7 +2242,7 @@ export const useGameStore = create<GameStore>()(
|
||||
const craftBonus = 1 + (state.skills.elemCrafting || 0) * 0.25;
|
||||
const outputAmount = Math.floor(craftBonus);
|
||||
|
||||
const effects = getUnifiedEffects(state);
|
||||
const effects = getUnifiedEffects(state.skillUpgrades, state.skillTiers, state.equipmentInstances, state.equippedInstances);
|
||||
const elemMax = computeElementMax(state, effects);
|
||||
newElems[target] = {
|
||||
...(newElems[target] || { current: 0, max: elemMax, unlocked: false }),
|
||||
@@ -2179,7 +2257,7 @@ export const useGameStore = create<GameStore>()(
|
||||
});
|
||||
},
|
||||
|
||||
doPrestige: (id: string) => {
|
||||
doPrestige: (id: string, selectedManaType?: string) => {
|
||||
const state = get();
|
||||
const pd = PRESTIGE_DEF[id];
|
||||
if (!pd) return;
|
||||
@@ -2188,10 +2266,18 @@ export const useGameStore = create<GameStore>()(
|
||||
if (lvl >= pd.max || state.insight < pd.cost) return;
|
||||
|
||||
const newPU = { ...state.prestigeUpgrades, [id]: lvl + 1 };
|
||||
|
||||
// For unlockedManaTypeCapacity, track the selected mana type
|
||||
let newUnlockedManaTypeUpgrades = state.unlockedManaTypeUpgrades || [];
|
||||
if (id === 'unlockedManaTypeCapacity' && selectedManaType) {
|
||||
newUnlockedManaTypeUpgrades = [...newUnlockedManaTypeUpgrades, { typeId: selectedManaType, level: 1 }];
|
||||
}
|
||||
|
||||
set({
|
||||
insight: state.insight - pd.cost,
|
||||
prestigeUpgrades: newPU,
|
||||
memorySlots: id === 'deepMemory' ? state.memorySlots + 1 : state.memorySlots,
|
||||
unlockedManaTypeUpgrades: newUnlockedManaTypeUpgrades,
|
||||
log: [`⭐ ${pd.name} upgraded to Lv.${lvl + 1}!`, ...state.log.slice(0, 49)],
|
||||
});
|
||||
},
|
||||
@@ -2231,6 +2317,7 @@ export const useGameStore = create<GameStore>()(
|
||||
memories: state.memories,
|
||||
skills: state.skills, // Keep skills through temporal memory for now
|
||||
manaHeartBonus: newHeartBonus,
|
||||
unlockedManaTypeUpgrades: state.unlockedManaTypeUpgrades || [],
|
||||
});
|
||||
|
||||
// Set the kept mana from EMERGENCY_RESERVE
|
||||
|
||||
@@ -5,7 +5,8 @@ import type { StateCreator } from 'zustand';
|
||||
import type { GameState, GameAction, SpellCost } from '../types';
|
||||
import { GUARDIANS, SPELLS_DEF, ELEMENTS, ELEMENT_OPPOSITES } from '../constants';
|
||||
import { getFloorMaxHP, getFloorElement, calcDamage, computePactMultiplier, canAffordSpellCost, deductSpellCost } from './computed';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../special-effects';
|
||||
|
||||
export interface CombatSlice {
|
||||
// State
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import type { GameState } from '../types';
|
||||
import { ELEMENTS, GUARDIANS, SPELLS_DEF, SKILLS_DEF, PRESTIGE_DEF, ELEMENT_OPPOSITES } from '../constants';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
import type { ComputedEffects } from '../upgrade-effects.types';
|
||||
import type { UnifiedEffects } from '../effects';
|
||||
import { getTierMultiplier } from '../skill-evolution';
|
||||
|
||||
// Helper to get effective skill level accounting for tiers
|
||||
@@ -40,20 +42,9 @@ export function computeMaxMana(
|
||||
return Math.floor((base + computedEffects.maxManaBonus) * computedEffects.maxManaMultiplier * heartMultiplier);
|
||||
}
|
||||
|
||||
export function computeElementMax(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
|
||||
effects?: ReturnType<typeof computeEffects>
|
||||
): number {
|
||||
const pu = state.prestigeUpgrades;
|
||||
const skillTiers = state.skillTiers || {};
|
||||
const skillUpgrades = state.skillUpgrades || {};
|
||||
|
||||
const elemAttuneLevel = getEffectiveSkillLevel(state.skills, 'elemAttune', skillTiers);
|
||||
const base = 10 + elemAttuneLevel.level * 50 * elemAttuneLevel.tierMultiplier + (pu.elementalAttune || 0) * 25;
|
||||
|
||||
const computedEffects = effects ?? computeEffects(skillUpgrades, skillTiers);
|
||||
return Math.floor((base + computedEffects.elementCapBonus) * computedEffects.elementCapMultiplier);
|
||||
}
|
||||
// computeElementMax is now in ../store.ts with support for unlockedManaTypeUpgrades
|
||||
// This file no longer exports computeElementMax to avoid duplicate export issues
|
||||
// Import computeElementMax from '../store' instead
|
||||
|
||||
export function computeRegen(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
import type { GameState, ElementState, SpellCost } from '../types';
|
||||
import { ELEMENTS, MANA_PER_ELEMENT, BASE_UNLOCKED_ELEMENTS } from '../constants';
|
||||
import { computeMaxMana, computeElementMax, computeClickMana, canAffordSpellCost, getMeditationBonus } from './computed';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
import { computeMaxMana, computeClickMana, canAffordSpellCost, getMeditationBonus } from './computed';
|
||||
import { computeElementMax } from '../store';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../special-effects';
|
||||
|
||||
export interface ManaSlice {
|
||||
// State
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { TICK_MS, HOURS_PER_TICK, MAX_DAY, SPELLS_DEF, GUARDIANS, getStudySpeedMultiplier } from '../constants';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from '../upgrade-effects';
|
||||
import { computeEffects } from '../upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from '../special-effects';
|
||||
import {
|
||||
computeMaxMana,
|
||||
computeRegen,
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import type { GameState } from './types';
|
||||
import { SKILLS_DEF, SPELLS_DEF, getStudyCostMultiplier } from './constants';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS } from './upgrade-effects';
|
||||
import { computeEffects } from './upgrade-effects';
|
||||
import { hasSpecial, SPECIAL_EFFECTS } from './special-effects';
|
||||
|
||||
// ─── Study Actions Interface ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ export interface EnemyState {
|
||||
maxHP: number;
|
||||
armor: number; // Damage reduction (0-1)
|
||||
dodgeChance: number; // For speed rooms (0-1)
|
||||
healthRegen?: number; // HP regenerated per tick (0-1 as percentage of max HP)
|
||||
barrier?: number; // Shield that absorbs damage before HP (0-1 as percentage of max HP)
|
||||
element: string;
|
||||
}
|
||||
|
||||
@@ -227,6 +229,8 @@ export interface GameState {
|
||||
prestigeUpgrades: Record<string, number>;
|
||||
memorySlots: number;
|
||||
memories: string[];
|
||||
// Track selected mana types for unlockedManaTypeCapacity upgrade
|
||||
unlockedManaTypeUpgrades: Array<{ typeId: string; level: number }>;
|
||||
|
||||
// Mana Well Effects (Phase 4)
|
||||
manaHeartBonus: number; // Cumulative +10% max mana per loop from MANA_HEART
|
||||
|
||||
+11
-276
@@ -1,153 +1,13 @@
|
||||
// ─── Upgrade Effect System ─────────────────────────────────────────────────────
|
||||
// ─── Upgrade Effect System ────────────────────────────────────────────────────────
|
||||
// This module handles applying skill upgrade effects to game stats
|
||||
|
||||
import type { SkillUpgradeChoice, SkillUpgradeEffect } from './types';
|
||||
import { getUpgradesForSkillAtMilestone, SKILL_EVOLUTION_PATHS } from './skill-evolution';
|
||||
import type { ActiveUpgradeEffect, ComputedEffects } from './upgrade-effects.types';
|
||||
import { SPECIAL_EFFECTS, hasSpecial } from './special-effects';
|
||||
import { computeDynamicRegen, computeDynamicClickMana, computeDynamicDamage } from './dynamic-compute';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ActiveUpgradeEffect {
|
||||
upgradeId: string;
|
||||
skillId: string;
|
||||
milestone: 5 | 10;
|
||||
effect: SkillUpgradeEffect;
|
||||
name: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export interface ComputedEffects {
|
||||
// Mana effects
|
||||
maxManaMultiplier: number;
|
||||
maxManaBonus: number;
|
||||
regenMultiplier: number;
|
||||
regenBonus: number;
|
||||
clickManaMultiplier: number;
|
||||
clickManaBonus: number;
|
||||
meditationEfficiency: number;
|
||||
spellCostMultiplier: number;
|
||||
conversionEfficiency: number;
|
||||
|
||||
// Combat effects
|
||||
baseDamageMultiplier: number;
|
||||
baseDamageBonus: number;
|
||||
attackSpeedMultiplier: number;
|
||||
critChanceBonus: number;
|
||||
critDamageMultiplier: number;
|
||||
elementalDamageMultiplier: number;
|
||||
|
||||
// Study effects
|
||||
studySpeedMultiplier: number;
|
||||
studyCostMultiplier: number;
|
||||
progressRetention: number;
|
||||
instantStudyChance: number;
|
||||
freeStudyChance: number;
|
||||
|
||||
// Element effects
|
||||
elementCapMultiplier: number;
|
||||
elementCapBonus: number;
|
||||
conversionCostMultiplier: number;
|
||||
doubleCraftChance: number;
|
||||
|
||||
// Special values
|
||||
permanentRegenBonus: number;
|
||||
|
||||
// Special effect flags (for game logic to check)
|
||||
specials: Set<string>;
|
||||
|
||||
// All active upgrades for display
|
||||
activeUpgrades: ActiveUpgradeEffect[];
|
||||
|
||||
// DEEP_UNDERSTANDING: +10% bonus from all skill levels
|
||||
skillLevelMultiplier: number;
|
||||
}
|
||||
|
||||
// ─── Special Effect IDs ────────────────────────────────────────────────────────
|
||||
// These are the IDs used in the 'specialId' field of special effects
|
||||
|
||||
export const SPECIAL_EFFECTS = {
|
||||
// Mana Flow special effects
|
||||
MANA_CASCADE: 'manaCascade', // +0.1 regen per 100 max mana
|
||||
STEADY_STREAM: 'steadyStream', // Regen immune to incursion
|
||||
MANA_TORRENT: 'manaTorrent', // +50% regen when above 75% mana
|
||||
FLOW_SURGE: 'flowSurge', // Clicks restore 2x regen for 1 hour
|
||||
MANA_OVERFLOW: 'manaOverflow', // Raw mana can exceed max by 20%
|
||||
MANA_WATERFALL: 'manaWaterfall', // +0.25 regen per 100 max mana (upgraded cascade)
|
||||
ETERNAL_FLOW: 'eternalFlow', // Regen immune to all penalties
|
||||
|
||||
// Mana Well special effects
|
||||
DESPAIR_WELLS: 'despairWells', // +50% regen when below 25% mana (task name)
|
||||
DESPERATE_WELLS: 'desperateWells', // +50% regen when below 25% mana (legacy name)
|
||||
MANA_ECHO: 'manaEcho', // 10% chance double mana from clicks
|
||||
EMERGENCY_RESERVE: 'emergencyReserve', // Keep 10% mana on new loop
|
||||
MANA_THRESHOLD: 'manaThreshold', // +30% max mana, -10% regen trade-off
|
||||
MANA_CONVERSION: 'manaConversion', // Convert 5% max mana to click bonus
|
||||
PANIC_RESERVE: 'panicReserve', // +100% regen when below 10% mana
|
||||
MANA_CONDENSE: 'manaCondense', // +1% max mana per 1000 gathered
|
||||
DEEP_RESERVE: 'deepReserve', // +0.5 regen per 100 max mana
|
||||
MANA_TIDE: 'manaTide', // Regen pulses ±50%
|
||||
VOID_STORAGE: 'voidStorage', // Store 150% max temporarily
|
||||
MANA_CORE: 'manaCore', // 0.5% max mana as regen
|
||||
MANA_HEART: 'manaHeart', // +10% max mana per loop
|
||||
MANA_GENESIS: 'manaGenesis', // Generate 1% max mana per hour
|
||||
|
||||
// Mana Overflow special effects
|
||||
CLICK_SURGE: 'clickSurge', // +50% click mana above 90% mana
|
||||
MANA_FLOOD: 'manaFlood', // +75% click mana above 75% mana
|
||||
|
||||
// Combat special effects
|
||||
FIRST_STRIKE: 'firstStrike', // +15% damage on first attack each floor
|
||||
OVERPOWER: 'overpower', // +50% damage when mana above 80%
|
||||
BERSERKER: 'berserker', // +50% damage when below 50% mana
|
||||
EXECUTIONER: 'executioner', // +50% damage when enemy below 25% HP
|
||||
COMBO_MASTER: 'comboMaster', // Every 5th attack deals 3x damage
|
||||
ADRENALINE_RUSH: 'adrenalineRush', // Defeating enemy restores 5% mana
|
||||
|
||||
// Study special effects
|
||||
QUICK_GRASP: 'quickGrasp', // 5% chance double study progress per hour
|
||||
DEEP_CONCENTRATION: 'deepConcentration', // +20% study speed when mana > 90%
|
||||
QUICK_MASTERY: 'quickMastery', // -20% study time for final 3 levels
|
||||
PARALLEL_STUDY: 'parallelStudy', // Study 2 things at 50% speed
|
||||
STUDY_MOMENTUM: 'studyMomentum', // +5% study speed per consecutive hour
|
||||
KNOWLEDGE_ECHO: 'knowledgeEcho', // 10% chance instant study
|
||||
KNOWLEDGE_TRANSFER: 'knowledgeTransfer', // New skills start at 10% progress
|
||||
MENTAL_CLARITY: 'mentalClarity', // +10% study speed when mana > 75%
|
||||
STUDY_REFUND: 'studyRefund', // 25% mana back on study complete
|
||||
DEEP_UNDERSTANDING: 'deepUnderstanding', // +10% bonus from all skill levels
|
||||
STUDY_RUSH: 'studyRush', // First hour of study is 2x speed
|
||||
CHAIN_STUDY: 'chainStudy', // -5% cost per maxed skill
|
||||
|
||||
// Element special effects
|
||||
ELEMENTAL_AFFINITY: 'elementalAffinity', // New elements start with 10 capacity
|
||||
EXOTIC_MASTERY: 'exoticMastery', // +20% exotic element damage
|
||||
ELEMENTAL_RESONANCE: 'elementalResonance', // Using element spells restores 1 of that element
|
||||
MANA_CONDUIT: 'manaConduit', // Meditation regenerates elemental mana
|
||||
|
||||
// Enchanting special effects
|
||||
ENCHANT_MASTERY: 'enchantMastery', // 2 enchantment designs in progress
|
||||
ENCHANT_PRESERVATION: 'enchantPreservation', // 25% chance free enchant
|
||||
THRIFTY_ENCHANTER: 'thriftyEnchanter', // +10% chance free enchantment
|
||||
OPTIMIZED_ENCHANTING: 'optimizedEnchanting', // +25% chance free enchantment
|
||||
HASTY_ENCHANTER: 'hastyEnchanter', // +25% speed for repeat designs
|
||||
INSTANT_DESIGNS: 'instantDesigns', // 10% instant design completion
|
||||
PURE_ESSENCE: 'pureEssence', // +25% power for tier 1 enchants
|
||||
|
||||
// Crafting special effects
|
||||
BATCH_CRAFTING: 'batchCrafting', // Craft 2 items at 75% speed each
|
||||
MASS_PRODUCTION: 'massProduction', // Craft 3 items at full speed
|
||||
SCAVENGE: 'scavenge', // Recover 10% materials from broken
|
||||
RECLAIM: 'reclaim', // Recover 25% materials from broken
|
||||
|
||||
// Golemancy special effects
|
||||
GOLEM_FURY: 'golemFury', // +50% attack speed for first 2 floors
|
||||
GOLEM_RESONANCE: 'golemResonance', // Golems share 10% damage
|
||||
RAPID_STRIKES: 'rapidStrikes', // +25% attack speed for first 3 floors
|
||||
BLITZ_ATTACK: 'blitzAttack', // +50% attack speed for first 5 floors
|
||||
|
||||
// Ascension special effects
|
||||
INSIGHT_BOUNTY: 'insightBounty', // +25% insight from guardians
|
||||
} as const;
|
||||
|
||||
// ─── Upgrade Definition Cache ─────────────────────────────────────────────────
|
||||
// ─── Upgrade Definition Cache ───────────────────────────
|
||||
|
||||
// Cache all upgrades by ID for quick lookup
|
||||
const upgradeDefinitionsById: Map<string, SkillUpgradeChoice> = new Map();
|
||||
@@ -165,7 +25,7 @@ function buildUpgradeCache(): void {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
||||
// ─── Helper Functions ──────────────────────────────
|
||||
|
||||
/**
|
||||
* Get all selected upgrades with their full effect definitions
|
||||
@@ -229,12 +89,14 @@ export function computeEffects(
|
||||
freeStudyChance: 0,
|
||||
elementCapMultiplier: 1,
|
||||
elementCapBonus: 0,
|
||||
perElementCapBonus: {},
|
||||
conversionCostMultiplier: 1,
|
||||
doubleCraftChance: 0,
|
||||
permanentRegenBonus: 0,
|
||||
specials: new Set<string>(),
|
||||
activeUpgrades,
|
||||
skillLevelMultiplier: 1,
|
||||
enchantmentPowerMultiplier: 1,
|
||||
};
|
||||
|
||||
// Apply DEEP_UNDERSTANDING: +10% bonus from all skill levels
|
||||
@@ -286,10 +148,11 @@ export function computeEffects(
|
||||
effects.conversionCostMultiplier *= effect.value;
|
||||
break;
|
||||
case 'costReduction':
|
||||
// For cost reduction, higher is better (less cost)
|
||||
// This is a multiplier on the reduction effectiveness
|
||||
effects.studyCostMultiplier /= effect.value;
|
||||
break;
|
||||
case 'enchantPower':
|
||||
effects.enchantmentPowerMultiplier *= effect.value;
|
||||
break;
|
||||
}
|
||||
} else if (effect.type === 'bonus' && effect.stat && effect.value !== undefined) {
|
||||
// Bonus effects (add to the stat)
|
||||
@@ -314,7 +177,6 @@ export function computeEffects(
|
||||
break;
|
||||
}
|
||||
} else if (effect.type === 'special' && effect.specialId) {
|
||||
// Special effects - add to the set for game logic to check
|
||||
effects.specials.add(effect.specialId);
|
||||
}
|
||||
}
|
||||
@@ -327,130 +189,3 @@ export function computeEffects(
|
||||
|
||||
return effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a special effect is active
|
||||
*/
|
||||
export function hasSpecial(effects: ComputedEffects, specialId: string): boolean {
|
||||
return effects?.specials?.has(specialId) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute regen with special effects that depend on dynamic values
|
||||
*/
|
||||
export function computeDynamicRegen(
|
||||
effects: ComputedEffects,
|
||||
baseRegen: number,
|
||||
maxMana: number,
|
||||
currentMana: number,
|
||||
incursionStrength: number
|
||||
): number {
|
||||
let regen = baseRegen;
|
||||
|
||||
// Mana Cascade: +0.1 regen per 100 max mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_CASCADE)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.1;
|
||||
}
|
||||
|
||||
// Mana Waterfall: +0.25 regen per 100 max mana (upgraded cascade)
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_WATERFALL)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.25;
|
||||
}
|
||||
|
||||
// Mana Torrent: +50% regen when above 75% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_TORRENT) && currentMana > maxMana * 0.75) {
|
||||
regen *= 1.5;
|
||||
}
|
||||
|
||||
// Desperate Wells / Despair Wells: +50% regen when below 25% mana
|
||||
if ((hasSpecial(effects, SPECIAL_EFFECTS.DESPERATE_WELLS) || hasSpecial(effects, SPECIAL_EFFECTS.DESPAIR_WELLS)) && currentMana < maxMana * 0.25) {
|
||||
regen *= 1.5;
|
||||
}
|
||||
|
||||
// Panic Reserve: +100% regen when below 10% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.PANIC_RESERVE) && currentMana < maxMana * 0.1) {
|
||||
regen *= 2.0;
|
||||
}
|
||||
|
||||
// Deep Reserve: +0.5 regen per 100 max mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.DEEP_RESERVE)) {
|
||||
regen += Math.floor(maxMana / 100) * 0.5;
|
||||
}
|
||||
|
||||
// Mana Core: 0.5% of max mana added as regen
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_CORE)) {
|
||||
regen += maxMana * 0.005;
|
||||
}
|
||||
|
||||
// Mana Tide: Regen pulses ±50% (sinusoidal based on time)
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.MANA_TIDE)) {
|
||||
const pulseFactor = 0.5 + 0.5 * Math.sin(Date.now() / 10000); // 10 second cycles
|
||||
regen *= (0.5 + pulseFactor * 0.5); // Range: 0.5x to 1.0x
|
||||
}
|
||||
|
||||
// Eternal Flow: Regen immune to ALL penalties (stronger than Steady Stream)
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.ETERNAL_FLOW)) {
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
// Steady Stream: Regen immune to incursion
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.STEADY_STREAM)) {
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
// Apply incursion penalty
|
||||
regen *= (1 - incursionStrength);
|
||||
|
||||
return regen * effects.regenMultiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute click mana with special effects
|
||||
*/
|
||||
export function computeDynamicClickMana(
|
||||
effects: ComputedEffects,
|
||||
baseClickMana: number
|
||||
): number {
|
||||
let clickMana = baseClickMana;
|
||||
|
||||
// Mana Echo: 10% chance to gain double mana from clicks
|
||||
// Note: The chance is handled in the click handler, this just returns the base
|
||||
// The click handler should check hasSpecial and apply the 10% chance
|
||||
|
||||
// Mana Genesis: Generate 1% of max mana per hour passively
|
||||
// This is handled in the game loop (store.ts), not here
|
||||
|
||||
// Mana Heart: +10% max mana per loop (permanent)
|
||||
// This is applied during loop reset in store.ts
|
||||
|
||||
return Math.floor((clickMana + effects.clickManaBonus) * effects.clickManaMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute damage with special effects
|
||||
*/
|
||||
export function computeDynamicDamage(
|
||||
effects: ComputedEffects,
|
||||
baseDamage: number,
|
||||
floorHPPct: number,
|
||||
currentMana: number,
|
||||
maxMana: number
|
||||
): number {
|
||||
let damage = baseDamage * effects.baseDamageMultiplier;
|
||||
|
||||
// Overpower: +50% damage when mana above 80%
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.OVERPOWER) && currentMana >= maxMana * 0.8) {
|
||||
damage *= 1.5;
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && currentMana < maxMana * 0.5) {
|
||||
damage *= 1.5;
|
||||
}
|
||||
|
||||
// Combo Master: Every 5th attack deals 3x damage
|
||||
// Note: The hit counter is tracked in game state, this just returns the multiplier
|
||||
// The combat handler should check hasSpecial and the hit count
|
||||
|
||||
return damage + effects.baseDamageBonus;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// ─── Upgrade Effect Types ────────────────────────────────────────────────────
|
||||
// Type interfaces for upgrade effects
|
||||
|
||||
import type { SkillUpgradeChoice, SkillUpgradeEffect } from './types';
|
||||
|
||||
export interface ActiveUpgradeEffect {
|
||||
upgradeId: string;
|
||||
skillId: string;
|
||||
milestone: 5 | 10;
|
||||
effect: SkillUpgradeEffect;
|
||||
name: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
export interface ComputedEffects {
|
||||
// Mana effects
|
||||
maxManaMultiplier: number;
|
||||
maxManaBonus: number;
|
||||
regenMultiplier: number;
|
||||
regenBonus: number;
|
||||
clickManaMultiplier: number;
|
||||
clickManaBonus: number;
|
||||
meditationEfficiency: number;
|
||||
spellCostMultiplier: number;
|
||||
conversionEfficiency: number;
|
||||
|
||||
// Combat effects
|
||||
baseDamageMultiplier: number;
|
||||
baseDamageBonus: number;
|
||||
attackSpeedMultiplier: number;
|
||||
critChanceBonus: number;
|
||||
critDamageMultiplier: number;
|
||||
elementalDamageMultiplier: number;
|
||||
|
||||
// Study effects
|
||||
studySpeedMultiplier: number;
|
||||
studyCostMultiplier: number;
|
||||
progressRetention: number;
|
||||
instantStudyChance: number;
|
||||
freeStudyChance: number;
|
||||
|
||||
// Element effects
|
||||
elementCapMultiplier: number;
|
||||
elementCapBonus: number;
|
||||
perElementCapBonus: Record<string, number>;
|
||||
conversionCostMultiplier: number;
|
||||
doubleCraftChance: number;
|
||||
|
||||
// Special values
|
||||
permanentRegenBonus: number;
|
||||
|
||||
// Special effect flags
|
||||
specials: Set<string>;
|
||||
|
||||
// All active upgrades for display
|
||||
activeUpgrades: ActiveUpgradeEffect[];
|
||||
|
||||
// DEEP_UNDERSTANDING: +10% bonus from all skill levels
|
||||
skillLevelMultiplier: number;
|
||||
|
||||
// Enchantment Power
|
||||
enchantmentPowerMultiplier: number;
|
||||
}
|
||||
@@ -5,13 +5,13 @@ export { fmt, fmtDec } from './formatting';
|
||||
export { getFloorMaxHP, getFloorElement } from './floor-utils';
|
||||
export {
|
||||
computeMaxMana,
|
||||
computeElementMax,
|
||||
computeRegen,
|
||||
computeEffectiveRegen,
|
||||
computeEffectiveRegenForDisplay,
|
||||
computeClickMana,
|
||||
getMeditationBonus
|
||||
} from './mana-utils';
|
||||
// computeElementMax is now in ../store.ts with support for unlockedManaTypeUpgrades
|
||||
export {
|
||||
getElementalBonus,
|
||||
getBoonBonuses,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// ─── Mana & Regen Utilities ──────────────────────────────────────────────────
|
||||
|
||||
import type { GameState } from '../types';
|
||||
import type { ComputedEffects } from '../upgrade-effects';
|
||||
import type { ComputedEffects } from '../upgrade-effects.types';
|
||||
import { HOURS_PER_TICK } from '../constants';
|
||||
import { getTotalAttunementRegen, getTotalAttunementConversionDrain } from '../data/attunements';
|
||||
|
||||
@@ -22,19 +22,9 @@ export function computeMaxMana(
|
||||
return base;
|
||||
}
|
||||
|
||||
export function computeElementMax(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers'>,
|
||||
effects?: ComputedEffects
|
||||
): number {
|
||||
const pu = state.prestigeUpgrades;
|
||||
const base = 10 + (state.skills.elemAttune || 0) * 50 + (pu.elementalAttune || 0) * 25;
|
||||
|
||||
// Apply upgrade effects if provided
|
||||
if (effects) {
|
||||
return Math.floor((base + effects.elementCapBonus) * effects.elementCapMultiplier);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
// computeElementMax is now in ../store.ts with support for unlockedManaTypeUpgrades
|
||||
// This file no longer exports computeElementMax to avoid duplicate export issues
|
||||
// Import computeElementMax from '../store' instead
|
||||
|
||||
export function computeRegen(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
||||
@@ -83,7 +73,7 @@ export function computeEffectiveRegenForDisplay(
|
||||
* Compute regen with dynamic special effects (needs current mana, max mana, incursion)
|
||||
*/
|
||||
export function computeEffectiveRegen(
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers'>,
|
||||
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers' | 'attunements'>,
|
||||
effects?: ComputedEffects
|
||||
): number {
|
||||
// Base regen from existing function
|
||||
|
||||
Reference in New Issue
Block a user