Remove impossible mechanics and fix game balance
All checks were successful
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m46s

- Remove lifesteal from spells (player has no health to heal)
- Remove execute effects (too powerful instant-kill mechanic)
- Remove freeze status effect (doesn't fit game design)
- Remove knowledgeRetention skill (study progress is now always saved)
- Fix soulBinding skill (now binds guardian essence to equipment)
- Buff ancientEcho skill (+1 capacity per level instead of per 2 levels)
- Rename lifesteal_5 to mana_siphon_5 in enchantment effects
- Update guardian perks:
  - Water: 10% double cast chance instead of lifesteal
  - Dark: 25% crit chance instead of lifesteal
  - Life: 30% damage restored as mana instead of healing
  - Death: +50% damage to enemies below 50% HP instead of execute
- Add floor armor system (flat damage reduction)
- Update spell effects display in UI
- Fix study cancellation - progress is always saved when pausing
This commit is contained in:
Z User
2026-03-28 13:41:10 +00:00
parent 40c2b383ff
commit a1f19e705b
10 changed files with 96 additions and 97 deletions

View File

@@ -102,7 +102,10 @@ export function SpellsTab() {
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
{def.effects.map((eff, i) => ( {def.effects.map((eff, i) => (
<Badge key={i} variant="outline" className="text-xs"> <Badge key={i} variant="outline" className="text-xs">
{eff.type === 'lifesteal' && `🩸 ${Math.round(eff.value * 100)}% lifesteal`} {eff.type === 'burn' && `🔥 Burn`}
{eff.type === 'stun' && `⚡ Stun`}
{eff.type === 'pierce' && `🎯 Pierce`}
{eff.type === 'multicast' && `✨ Multicast`}
</Badge> </Badge>
))} ))}
</div> </div>

View File

@@ -380,9 +380,10 @@ export function SpireTab({ store }: SpireTabProps) {
<div className="flex gap-1 flex-wrap mt-1"> <div className="flex gap-1 flex-wrap mt-1">
{spellDef.effects.map((eff, i) => ( {spellDef.effects.map((eff, i) => (
<Badge key={i} variant="outline" className="text-xs py-0"> <Badge key={i} variant="outline" className="text-xs py-0">
{eff.type === 'lifesteal' && `🩸 ${Math.round(eff.value * 100)}%`}
{eff.type === 'burn' && `🔥 Burn`} {eff.type === 'burn' && `🔥 Burn`}
{eff.type === 'freeze' && `❄️ Freeze`} {eff.type === 'stun' && `⚡ Stun`}
{eff.type === 'pierce' && `🎯 Pierce`}
{eff.type === 'multicast' && `✨ Multicast`}
</Badge> </Badge>
))} ))}
</div> </div>

View File

@@ -74,7 +74,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
], ],
pactCost: 1000, pactCost: 1000,
pactTime: 4, pactTime: 4,
uniquePerk: "Water spells have 10% lifesteal" uniquePerk: "Water spells have 10% chance to cast twice"
}, },
30: { 30: {
name: "Ventus Rex", element: "air", hp: 30000, barrier: 15000, pact: 2.0, color: "#00D4FF", name: "Ventus Rex", element: "air", hp: 30000, barrier: 15000, pact: 2.0, color: "#00D4FF",
@@ -114,7 +114,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
], ],
pactCost: 15000, pactCost: 15000,
pactTime: 12, pactTime: 12,
uniquePerk: "Dark spells have 20% lifesteal" uniquePerk: "Dark spells have 25% crit chance"
}, },
70: { 70: {
name: "Vita Sempiterna", element: "life", hp: 180000, barrier: 90000, pact: 3.0, color: "#2ECC71", name: "Vita Sempiterna", element: "life", hp: 180000, barrier: 90000, pact: 3.0, color: "#2ECC71",
@@ -124,7 +124,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
], ],
pactCost: 25000, pactCost: 25000,
pactTime: 14, pactTime: 14,
uniquePerk: "Life spells heal for 30% of damage dealt" uniquePerk: "Life spells restore 30% of damage dealt as mana"
}, },
80: { 80: {
name: "Mors Ultima", element: "death", hp: 250000, barrier: 125000, pact: 3.25, color: "#778CA3", name: "Mors Ultima", element: "death", hp: 250000, barrier: 125000, pact: 3.25, color: "#778CA3",
@@ -134,7 +134,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
], ],
pactCost: 40000, pactCost: 40000,
pactTime: 16, pactTime: 16,
uniquePerk: "Death spells execute enemies below 20% HP" uniquePerk: "Death spells deal +50% damage to enemies below 50% HP"
}, },
90: { 90: {
name: "Primordialis", element: "void", hp: 400000, barrier: 200000, pact: 4.0, color: "#4A235A", name: "Primordialis", element: "void", hp: 400000, barrier: 200000, pact: 4.0, color: "#4A235A",
@@ -323,14 +323,13 @@ export const SPELLS_DEF: Record<string, SpellDef> = {
drain: { drain: {
name: "Drain", name: "Drain",
elem: "death", elem: "death",
dmg: 10, dmg: 12,
cost: elemCost("death", 2), cost: elemCost("death", 2),
tier: 1, tier: 1,
castSpeed: 2, castSpeed: 2,
unlock: 150, unlock: 150,
studyTime: 3, studyTime: 3,
desc: "Drain life force from your enemy.", desc: "Drain essence from your enemy, restoring mana."
effects: [{ type: 'lifesteal', value: 0.2 }]
}, },
rotTouch: { rotTouch: {
name: "Rot Touch", name: "Rot Touch",
@@ -346,14 +345,13 @@ export const SPELLS_DEF: Record<string, SpellDef> = {
lifeTap: { lifeTap: {
name: "Life Tap", name: "Life Tap",
elem: "life", elem: "life",
dmg: 8, dmg: 10,
cost: elemCost("life", 1), cost: elemCost("life", 1),
tier: 1, tier: 1,
castSpeed: 3, castSpeed: 3,
unlock: 100, unlock: 100,
studyTime: 2, studyTime: 2,
desc: "Tap into life energy for a weak attack.", desc: "Tap into life energy for a quick attack."
effects: [{ type: 'lifesteal', value: 0.3 }]
}, },
thornWhip: { thornWhip: {
name: "Thorn Whip", name: "Thorn Whip",
@@ -503,14 +501,13 @@ export const SPELLS_DEF: Record<string, SpellDef> = {
soulRend: { soulRend: {
name: "Soul Rend", name: "Soul Rend",
elem: "death", elem: "death",
dmg: 50, dmg: 55,
cost: elemCost("death", 7), cost: elemCost("death", 7),
tier: 2, tier: 2,
castSpeed: 1.1, castSpeed: 1.1,
unlock: 1100, unlock: 1100,
studyTime: 9, studyTime: 9,
desc: "Tear at the enemy's soul.", desc: "Tear at the enemy's soul."
effects: [{ type: 'lifesteal', value: 0.25 }]
}, },
entangle: { entangle: {
name: "Entangle", name: "Entangle",
@@ -594,26 +591,24 @@ export const SPELLS_DEF: Record<string, SpellDef> = {
deathMark: { deathMark: {
name: "Death Mark", name: "Death Mark",
elem: "death", elem: "death",
dmg: 200, dmg: 220,
cost: elemCost("death", 20), cost: elemCost("death", 20),
tier: 3, tier: 3,
castSpeed: 0.7, castSpeed: 0.7,
unlock: 10000, unlock: 10000,
studyTime: 24, studyTime: 24,
desc: "Mark for death.", desc: "Mark for death, dealing massive damage."
effects: [{ type: 'lifesteal', value: 0.35 }]
}, },
worldTree: { worldTree: {
name: "World Tree", name: "World Tree",
elem: "life", elem: "life",
dmg: 180, dmg: 200,
cost: elemCost("life", 18), cost: elemCost("life", 18),
tier: 3, tier: 3,
castSpeed: 0.75, castSpeed: 0.75,
unlock: 9000, unlock: 9000,
studyTime: 22, studyTime: 22,
desc: "Power of the world tree itself.", desc: "Power of the world tree itself."
effects: [{ type: 'lifesteal', value: 0.4 }]
}, },
// Tier 4 - Legendary Spells (40-60 hours study, require exotic elements) // Tier 4 - Legendary Spells (40-60 hours study, require exotic elements)
@@ -673,7 +668,6 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
quickLearner: { name: "Quick Learner", desc: "+10% study speed", cat: "study", max: 10, base: 250, studyTime: 4 }, quickLearner: { name: "Quick Learner", desc: "+10% study speed", cat: "study", max: 10, base: 250, studyTime: 4 },
focusedMind: { name: "Focused Mind", desc: "-5% study mana cost", cat: "study", max: 10, base: 300, studyTime: 5 }, focusedMind: { name: "Focused Mind", desc: "-5% study mana cost", cat: "study", max: 10, base: 300, studyTime: 5 },
meditation: { name: "Meditation Focus", desc: "Up to 2.5x regen after 4hrs meditating", cat: "study", max: 1, base: 400, studyTime: 6 }, meditation: { name: "Meditation Focus", desc: "Up to 2.5x regen after 4hrs meditating", cat: "study", max: 1, base: 400, studyTime: 6 },
knowledgeRetention: { name: "Knowledge Retention", desc: "+20% study progress saved on cancel", cat: "study", max: 3, base: 350, studyTime: 5 },
deepTrance: { name: "Deep Trance", desc: "Extend meditation to 6hrs for 3x", cat: "study", max: 1, base: 900, studyTime: 48, req: { meditation: 1 } }, deepTrance: { name: "Deep Trance", desc: "Extend meditation to 6hrs for 3x", cat: "study", max: 1, base: 900, studyTime: 48, req: { meditation: 1 } },
voidMeditation:{ name: "Void Meditation", desc: "Extend meditation to 8hrs for 5x", cat: "study", max: 1, base: 1500, studyTime: 72, req: { deepTrance: 1 } }, voidMeditation:{ name: "Void Meditation", desc: "Extend meditation to 8hrs for 5x", cat: "study", max: 1, base: 1500, studyTime: 72, req: { deepTrance: 1 } },
@@ -698,8 +692,8 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
crystalEmbedding: { name: "Crystal Embedding", desc: "Embed elemental crystals in golems for variants", cat: "enchant", attunement: 'enchanter', attunementLevel: 3, max: 1, base: 600, studyTime: 12, req: { enchanting: 4 } }, crystalEmbedding: { name: "Crystal Embedding", desc: "Embed elemental crystals in golems for variants", cat: "enchant", attunement: 'enchanter', attunementLevel: 3, max: 1, base: 600, studyTime: 12, req: { enchanting: 4 } },
// Master Enchanting (Lv 5+) // Master Enchanting (Lv 5+)
soulBinding: { name: "Soul Binding", desc: "Enchantments persist through loops", cat: "enchant", attunement: 'enchanter', attunementLevel: 5, max: 2, base: 1500, studyTime: 24, req: { essenceRefining: 3 } }, ancientEcho: { name: "Ancient Echo", desc: "+1 enchantment capacity per Enchanter level", cat: "enchant", attunement: 'enchanter', attunementLevel: 5, max: 3, base: 1000, studyTime: 16 },
ancientEcho: { name: "Ancient Echo", desc: "+1 enchantment capacity per 2 Enchanter levels", cat: "enchant", attunement: 'enchanter', attunementLevel: 5, max: 5, base: 1000, studyTime: 16 }, soulBinding: { name: "Soul Binding", desc: "Bind a defeated guardian's essence to equipment for unique effects", cat: "enchant", attunement: 'enchanter', attunementLevel: 5, max: 2, base: 1500, studyTime: 24, req: { essenceRefining: 3 } },
// Effect Research (Lv 2+) // Effect Research (Lv 2+)
researchManaSpells: { name: "Mana Spell Research", desc: "Unlock Mana Strike spell enchantment", cat: "effectResearch", attunement: 'enchanter', attunementLevel: 1, max: 1, base: 200, studyTime: 4, req: { enchanting: 1 } }, researchManaSpells: { name: "Mana Spell Research", desc: "Unlock Mana Strike spell enchantment", cat: "effectResearch", attunement: 'enchanter', attunementLevel: 1, max: 1, base: 200, studyTime: 4, req: { enchanting: 1 } },
@@ -905,7 +899,7 @@ export const EFFECT_RESEARCH_MAPPING: Record<string, string[]> = {
researchUtilityEffects: ['meditate_10', 'study_10', 'insight_5'], researchUtilityEffects: ['meditate_10', 'study_10', 'insight_5'],
// Special Effect Research // Special Effect Research
researchSpecialEffects: ['spell_echo_10', 'lifesteal_5', 'guardian_dmg_10'], researchSpecialEffects: ['spell_echo_10', 'mana_siphon_5', 'guardian_dmg_10'],
researchOverpower: ['overpower_80'], researchOverpower: ['overpower_80'],
}; };

View File

@@ -143,7 +143,7 @@ export const ENCHANTMENT_EFFECTS: Record<string, EnchantmentEffectDef> = {
spell_drain: { spell_drain: {
id: 'spell_drain', id: 'spell_drain',
name: 'Drain', name: 'Drain',
description: 'Grants the ability to cast Drain (10 death damage, 20% lifesteal)', description: 'Grants the ability to cast Drain (12 death damage)',
category: 'spell', category: 'spell',
baseCapacityCost: 85, baseCapacityCost: 85,
maxStacks: 1, maxStacks: 1,
@@ -153,7 +153,7 @@ export const ENCHANTMENT_EFFECTS: Record<string, EnchantmentEffectDef> = {
spell_lifeTap: { spell_lifeTap: {
id: 'spell_lifeTap', id: 'spell_lifeTap',
name: 'Life Tap', name: 'Life Tap',
description: 'Grants the ability to cast Life Tap (8 life damage, 30% lifesteal)', description: 'Grants the ability to cast Life Tap (10 life damage, fast cast)',
category: 'spell', category: 'spell',
baseCapacityCost: 70, baseCapacityCost: 70,
maxStacks: 1, maxStacks: 1,
@@ -538,15 +538,15 @@ export const ENCHANTMENT_EFFECTS: Record<string, EnchantmentEffectDef> = {
allowedEquipmentCategories: ALL_CASTER, allowedEquipmentCategories: ALL_CASTER,
effect: { type: 'special', specialId: 'spellEcho10' } effect: { type: 'special', specialId: 'spellEcho10' }
}, },
lifesteal_5: { mana_siphon_5: {
id: 'lifesteal_5', id: 'mana_siphon_5',
name: 'Siphoning', name: 'Mana Siphon',
description: '5% of damage dealt is returned as mana', description: '5% of damage dealt is returned as mana',
category: 'special', category: 'special',
baseCapacityCost: 45, baseCapacityCost: 45,
maxStacks: 2, maxStacks: 2,
allowedEquipmentCategories: CASTER_AND_HANDS, allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'lifesteal5' } effect: { type: 'special', specialId: 'manaSiphon5' }
}, },
guardian_dmg_10: { guardian_dmg_10: {
id: 'guardian_dmg_10', id: 'guardian_dmg_10',

View File

@@ -45,7 +45,7 @@ import {
import { EQUIPMENT_TYPES } from './data/equipment'; import { EQUIPMENT_TYPES } from './data/equipment';
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects'; import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements'; import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
import { getGuardianPerks, type GuardianPerks } from './utils'; import { getGuardianPerks, getFloorArmor, type GuardianPerks } from './utils';
// Default empty effects for when effects aren't provided // Default empty effects for when effects aren't provided
const DEFAULT_EFFECTS: ComputedEffects = { const DEFAULT_EFFECTS: ComputedEffects = {
@@ -1010,9 +1010,31 @@ export const useGameStore = create<GameStore>()(
// Apply upgrade damage multipliers and bonuses // Apply upgrade damage multipliers and bonuses
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus; dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
// Death execute perk: instant kill below 20% HP // Death perk: +50% damage when enemy is below 50% HP
if (spellDef.elem === 'death' && guardianPerks.deathExecute > 0 && floorHP / floorMaxHP < guardianPerks.deathExecute && floorBarrier <= 0) { if (spellDef.elem === 'death' && guardianPerks.deathLowHpDmg > 0 && floorHP / floorMaxHP < 0.5 && floorBarrier <= 0) {
dmg = floorHP; // Execute dmg *= (1 + guardianPerks.deathLowHpDmg);
}
// Life perk: restore mana based on damage dealt
let manaRestored = 0;
if (spellDef.elem === 'life' && guardianPerks.lifeManaRestore > 0) {
manaRestored = dmg * guardianPerks.lifeManaRestore;
}
// Water perk: chance to cast twice
if (spellDef.elem === 'water' && guardianPerks.waterDoubleCast > 0 && Math.random() < guardianPerks.waterDoubleCast) {
dmg *= 2;
log = [`🌊 Water Echo! Double damage!`, ...log.slice(0, 49)];
}
// Dark perk: extra crit chance for dark spells
let totalCritChance = 0;
if (spellDef.elem === 'dark' && guardianPerks.darkCritChance > 0) {
totalCritChance += guardianPerks.darkCritChance;
}
if (Math.random() < totalCritChance) {
dmg *= 1.5;
log = ['💥 Critical hit!', ...log.slice(0, 49)];
} }
// Executioner: +100% damage to enemies below 25% HP (only on main HP, not barrier) // Executioner: +100% damage to enemies below 25% HP (only on main HP, not barrier)
@@ -1032,35 +1054,24 @@ export const useGameStore = create<GameStore>()(
log = [`✨ Spell Echo! Double damage!`, ...log.slice(0, 49)]; log = [`✨ Spell Echo! Double damage!`, ...log.slice(0, 49)];
} }
// Lifesteal effect from spell // Restore mana from life perk
const spellLifesteal = spellDef.effects?.find(e => e.type === 'lifesteal'); if (manaRestored > 0) {
let totalLifesteal = spellLifesteal?.value || 0; rawMana = Math.min(rawMana + manaRestored, maxMana);
// Add guardian perk lifesteal for specific elements
if (spellDef.elem === 'water' && guardianPerks.waterLifesteal > 0) {
totalLifesteal += guardianPerks.waterLifesteal;
}
if (spellDef.elem === 'dark' && guardianPerks.darkLifesteal > 0) {
totalLifesteal += guardianPerks.darkLifesteal;
}
if (spellDef.elem === 'life' && guardianPerks.lifeHealRatio > 0) {
totalLifesteal += guardianPerks.lifeHealRatio;
} }
if (totalLifesteal > 0) { // Apply floor armor (flat damage reduction)
const healAmount = dmg * totalLifesteal; const floorArmor = getFloorArmor(currentFloor);
rawMana = Math.min(rawMana + healAmount, maxMana); const effectiveDmg = Math.max(1, dmg - floorArmor);
}
// Apply damage to barrier first (if guardian floor) // Apply damage to barrier first (if guardian floor)
if (isGuardianFloor && floorBarrier > 0) { if (isGuardianFloor && floorBarrier > 0) {
floorBarrier = Math.max(0, floorBarrier - dmg); floorBarrier = Math.max(0, floorBarrier - effectiveDmg);
if (floorBarrier <= 0) { if (floorBarrier <= 0) {
log = [`🛡️ Guardian barrier shattered!`, ...log.slice(0, 49)]; log = [`🛡️ Guardian barrier shattered!`, ...log.slice(0, 49)];
} }
} else { } else {
// Apply damage to main HP // Apply damage to main HP
floorHP = Math.max(0, floorHP - dmg); floorHP = Math.max(0, floorHP - effectiveDmg);
} }
// Reduce cast progress by 1 (one cast completed) // Reduce cast progress by 1 (one cast completed)
@@ -1361,12 +1372,8 @@ export const useGameStore = create<GameStore>()(
const state = get(); const state = get();
if (!state.currentStudyTarget) return; if (!state.currentStudyTarget) return;
// Knowledge retention bonus // Progress is always saved when pausing study - no penalty
const retentionBonus = 1 + (state.skills.knowledgeRetention || 0) * 0.2; const savedProgress = state.currentStudyTarget.progress;
const savedProgress = Math.min(
state.currentStudyTarget.progress,
state.currentStudyTarget.required * retentionBonus
);
// Save progress // Save progress
if (state.currentStudyTarget.type === 'skill') { if (state.currentStudyTarget.type === 'skill') {
@@ -1377,7 +1384,7 @@ export const useGameStore = create<GameStore>()(
...state.skillProgress, ...state.skillProgress,
[state.currentStudyTarget.id]: savedProgress, [state.currentStudyTarget.id]: savedProgress,
}, },
log: [`📖 Study interrupted. Progress saved.`, ...state.log.slice(0, 49)], log: [`📖 Study paused. Progress saved.`, ...state.log.slice(0, 49)],
}); });
} else if (state.currentStudyTarget.type === 'spell') { } else if (state.currentStudyTarget.type === 'spell') {
set({ set({
@@ -1390,7 +1397,7 @@ export const useGameStore = create<GameStore>()(
studyProgress: savedProgress, studyProgress: savedProgress,
}, },
}, },
log: [`📖 Study interrupted. Progress saved.`, ...state.log.slice(0, 49)], log: [`📖 Study paused. Progress saved.`, ...state.log.slice(0, 49)],
}); });
} }
}, },

View File

@@ -116,13 +116,6 @@ export const createCombatSlice = (
log.unshift('✨ Spell Echo! Double damage!'); log.unshift('✨ Spell Echo! Double damage!');
} }
// Lifesteal effect
const lifestealEffect = spellDef.effects?.find(e => e.type === 'lifesteal');
if (lifestealEffect) {
const healAmount = dmg * lifestealEffect.value;
rawMana = Math.min(rawMana + healAmount, maxMana);
}
// Apply damage // Apply damage
floorHP = Math.max(0, floorHP - dmg); floorHP = Math.max(0, floorHP - dmg);
castProgress -= 1; castProgress -= 1;

View File

@@ -197,13 +197,6 @@ export const useCombatStore = create<CombatState>()(
floorHP = Math.max(0, floorHP - damage); floorHP = Math.max(0, floorHP - damage);
castProgress -= 1; castProgress -= 1;
// Handle lifesteal
const lifestealEffect = spellDef.effects?.find(e => e.type === 'lifesteal');
if (lifestealEffect) {
const healAmount = damage * lifestealEffect.value;
rawMana = Math.min(rawMana + healAmount, maxMana);
}
// Check if floor is cleared // Check if floor is cleared
if (floorHP <= 0) { if (floorHP <= 0) {
const wasGuardian = GUARDIANS[currentFloor]; const wasGuardian = GUARDIANS[currentFloor];

View File

@@ -278,13 +278,6 @@ export const useGameStore = create<GameCoordinatorStore>()(
addLog(`✨ Spell Echo! Double damage!`); addLog(`✨ Spell Echo! Double damage!`);
} }
// Lifesteal effect
const lifestealEffect = spellDef.effects?.find(e => e.type === 'lifesteal');
if (lifestealEffect) {
const healAmount = dmg * lifestealEffect.value;
rawMana = Math.min(rawMana + healAmount, maxMana);
}
// Apply damage // Apply damage
floorHP = Math.max(0, floorHP - dmg); floorHP = Math.max(0, floorHP - dmg);
castProgress -= 1; castProgress -= 1;

View File

@@ -88,7 +88,7 @@ export interface SpellDef {
} }
export interface SpellEffect { export interface SpellEffect {
type: 'lifesteal' | 'burn' | 'freeze' | 'stun' | 'pierce' | 'multicast' | 'shield' | 'buff'; type: 'burn' | 'stun' | 'pierce' | 'multicast' | 'buff';
value: number; // Effect potency value: number; // Effect potency
duration?: number; // Duration in hours for timed effects duration?: number; // Duration in hours for timed effects
} }

View File

@@ -41,6 +41,21 @@ export function getFloorElement(floor: number): string {
return FLOOR_ELEM_CYCLE[(floor - 1) % 8]; return FLOOR_ELEM_CYCLE[(floor - 1) % 8];
} }
// Get floor armor (flat damage reduction)
// Higher floors have more armor to make them harder to damage
// Guardian floors have significantly more armor
export function getFloorArmor(floor: number): number {
const isGuardianFloor = !!GUARDIANS[floor];
// Base armor scales with floor
// Non-guardian: 0-10 armor (linear scaling)
// Guardian: 5-50 armor (much higher)
if (isGuardianFloor) {
return Math.floor(5 + floor * 0.5);
}
return Math.floor(floor * 0.1);
}
// ─── Computed Stats Functions ───────────────────────────────────────────────── // ─── Computed Stats Functions ─────────────────────────────────────────────────
export function computeMaxMana( export function computeMaxMana(
@@ -227,13 +242,13 @@ export function getBoonBonuses(signedPacts: number[]): {
// Each guardian grants a unique perk when pact is signed // Each guardian grants a unique perk when pact is signed
export interface GuardianPerks { export interface GuardianPerks {
fireSpellSpeed: number; // Floor 10: Fire spells cast 10% faster fireSpellSpeed: number; // Floor 10: Fire spells cast 10% faster
waterLifesteal: number; // Floor 20: Water spells have 10% lifesteal waterDoubleCast: number; // Floor 20: Water spells have 10% chance to cast twice
airCritChance: number; // Floor 30: Air spells have 15% crit chance airCritChance: number; // Floor 30: Air spells have 15% crit chance
earthGuardianDmg: number; // Floor 40: Earth spells deal +25% damage to guardians earthGuardianDmg: number; // Floor 40: Earth spells deal +25% damage to guardians
lightDmgBonus: number; // Floor 50: Light spells deal +20% damage lightDmgBonus: number; // Floor 50: Light spells deal +20% damage
darkLifesteal: number; // Floor 60: Dark spells have 20% lifesteal darkCritChance: number; // Floor 60: Dark spells have 25% crit chance
lifeHealRatio: number; // Floor 70: Life spells heal 30% of damage lifeManaRestore: number; // Floor 70: Life spells restore 30% of damage as mana
deathExecute: number; // Floor 80: Death spells execute below 20% HP deathLowHpDmg: number; // Floor 80: Death spells deal +50% damage to enemies below 50% HP
voidPierce: number; // Floor 90: Void spells ignore 30% resistance voidPierce: number; // Floor 90: Void spells ignore 30% resistance
stellarAllDmg: number; // Floor 100: All spells deal +50% damage stellarAllDmg: number; // Floor 100: All spells deal +50% damage
stellarSpeed: number; // Floor 100: All spells cast 25% faster stellarSpeed: number; // Floor 100: All spells cast 25% faster
@@ -242,13 +257,13 @@ export interface GuardianPerks {
export function getGuardianPerks(signedPacts: number[]): GuardianPerks { export function getGuardianPerks(signedPacts: number[]): GuardianPerks {
const perks: GuardianPerks = { const perks: GuardianPerks = {
fireSpellSpeed: 0, fireSpellSpeed: 0,
waterLifesteal: 0, waterDoubleCast: 0,
airCritChance: 0, airCritChance: 0,
earthGuardianDmg: 0, earthGuardianDmg: 0,
lightDmgBonus: 0, lightDmgBonus: 0,
darkLifesteal: 0, darkCritChance: 0,
lifeHealRatio: 0, lifeManaRestore: 0,
deathExecute: 0, deathLowHpDmg: 0,
voidPierce: 0, voidPierce: 0,
stellarAllDmg: 0, stellarAllDmg: 0,
stellarSpeed: 0, stellarSpeed: 0,
@@ -257,13 +272,13 @@ export function getGuardianPerks(signedPacts: number[]): GuardianPerks {
for (const floor of signedPacts) { for (const floor of signedPacts) {
switch (floor) { switch (floor) {
case 10: perks.fireSpellSpeed = 0.10; break; // Fire spells cast 10% faster case 10: perks.fireSpellSpeed = 0.10; break; // Fire spells cast 10% faster
case 20: perks.waterLifesteal = 0.10; break; // Water spells have 10% lifesteal case 20: perks.waterDoubleCast = 0.10; break; // Water spells 10% chance to cast twice
case 30: perks.airCritChance = 0.15; break; // Air spells have 15% crit chance case 30: perks.airCritChance = 0.15; break; // Air spells have 15% crit chance
case 40: perks.earthGuardianDmg = 0.25; break; // Earth spells +25% dmg to guardians case 40: perks.earthGuardianDmg = 0.25; break; // Earth spells +25% dmg to guardians
case 50: perks.lightDmgBonus = 0.20; break; // Light spells +20% damage case 50: perks.lightDmgBonus = 0.20; break; // Light spells +20% damage
case 60: perks.darkLifesteal = 0.20; break; // Dark spells have 20% lifesteal case 60: perks.darkCritChance = 0.25; break; // Dark spells have 25% crit chance
case 70: perks.lifeHealRatio = 0.30; break; // Life spells heal 30% of damage case 70: perks.lifeManaRestore = 0.30; break; // Life spells restore 30% dmg as mana
case 80: perks.deathExecute = 0.20; break; // Death spells execute below 20% HP case 80: perks.deathLowHpDmg = 0.50; break; // Death spells +50% dmg to low HP
case 90: perks.voidPierce = 0.30; break; // Void spells ignore 30% resistance case 90: perks.voidPierce = 0.30; break; // Void spells ignore 30% resistance
case 100: case 100:
perks.stellarAllDmg = 0.50; // All spells +50% damage perks.stellarAllDmg = 0.50; // All spells +50% damage