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
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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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)],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user