Compare commits
4 Commits
522079e011
...
2edce9680a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2edce9680a | ||
|
|
cd10918a3b | ||
|
|
dd9528a418 | ||
|
|
0bc2b45b96 |
0
src/lib/game/__tests__/bug-fixes.test.ts
Normal file → Executable file
0
src/lib/game/__tests__/bug-fixes.test.ts
Normal file → Executable file
@@ -39,10 +39,11 @@ export const ELEMENTS: Record<string, ElementDef> = {
|
||||
force: { name: "Force", sym: "💨", color: "#E74C3C", glow: "#E74C3C40", cat: "utility" },
|
||||
|
||||
// Composite Elements
|
||||
blood: { name: "Blood", sym: "🩸", color: "#C0392B", glow: "#C0392B40", cat: "composite", recipe: ["life", "water"] },
|
||||
metal: { name: "Metal", sym: "⚙️", color: "#BDC3C7", glow: "#BDC3C740", cat: "composite", recipe: ["fire", "earth"] },
|
||||
wood: { name: "Wood", sym: "🪵", color: "#8B5E3C", glow: "#8B5E3C40", cat: "composite", recipe: ["life", "earth"] },
|
||||
sand: { name: "Sand", sym: "⏳", color: "#D4AC0D", glow: "#D4AC0D40", cat: "composite", recipe: ["earth", "water"] },
|
||||
blood: { name: "Blood", sym: "🩸", color: "#C0392B", glow: "#C0392B40", cat: "composite", recipe: ["life", "water"] },
|
||||
metal: { name: "Metal", sym: "⚙️", color: "#BDC3C7", glow: "#BDC3C740", cat: "composite", recipe: ["fire", "earth"] },
|
||||
wood: { name: "Wood", sym: "🪵", color: "#8B5E3C", glow: "#8B5E3C40", cat: "composite", recipe: ["life", "earth"] },
|
||||
sand: { name: "Sand", sym: "⏳", color: "#D4AC0D", glow: "#D4AC0D40", cat: "composite", recipe: ["earth", "water"] },
|
||||
lightning: { name: "Lightning", sym: "⚡", color: "#FFEB3B", glow: "#FFEB3B40", cat: "composite", recipe: ["fire", "air"] },
|
||||
|
||||
// Exotic Elements
|
||||
crystal: { name: "Crystal", sym: "💎", color: "#85C1E9", glow: "#85C1E940", cat: "exotic", recipe: ["sand", "sand", "mental"] },
|
||||
@@ -52,10 +53,103 @@ export const ELEMENTS: Record<string, ElementDef> = {
|
||||
|
||||
export const FLOOR_ELEM_CYCLE = ["fire", "water", "air", "earth", "light", "dark", "life", "death"];
|
||||
|
||||
// ─── Room Types ────────────────────────────────────────────────────────────────
|
||||
// Room types for spire floors
|
||||
export type RoomType = 'combat' | 'puzzle' | 'swarm' | 'speed' | 'guardian';
|
||||
|
||||
// Room generation rules:
|
||||
// - Guardian floors (10, 20, 30, etc.) are ALWAYS guardian type
|
||||
// - Every 5th floor (5, 15, 25, etc.) has a chance for special rooms
|
||||
// - Other floors are combat with chance for swarm/speed
|
||||
export const PUZZLE_ROOM_INTERVAL = 7; // Every 7 floors, chance for puzzle
|
||||
export const SWARM_ROOM_CHANCE = 0.15; // 15% chance for swarm room
|
||||
export const SPEED_ROOM_CHANCE = 0.10; // 10% chance for speed room
|
||||
export const PUZZLE_ROOM_CHANCE = 0.20; // 20% chance for puzzle room on puzzle floors
|
||||
|
||||
// Puzzle room definitions - themed around attunements
|
||||
export const PUZZLE_ROOMS: Record<string, {
|
||||
name: string;
|
||||
attunements: string[]; // Which attunements speed up progress
|
||||
baseProgressPerTick: number; // Base progress rate
|
||||
attunementBonus: number; // Multiplier per relevant attunement level
|
||||
description: string;
|
||||
}> = {
|
||||
enchanter_trial: {
|
||||
name: "Enchanter's Trial",
|
||||
attunements: ['enchanter'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Decipher ancient enchantment runes."
|
||||
},
|
||||
fabricator_trial: {
|
||||
name: "Fabricator's Trial",
|
||||
attunements: ['fabricator'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Construct a mana-powered mechanism."
|
||||
},
|
||||
invoker_trial: {
|
||||
name: "Invoker's Trial",
|
||||
attunements: ['invoker'],
|
||||
baseProgressPerTick: 0.02,
|
||||
attunementBonus: 0.03,
|
||||
description: "Commune with guardian spirits."
|
||||
},
|
||||
hybrid_enchanter_fabricator: {
|
||||
name: "Fusion Workshop",
|
||||
attunements: ['enchanter', 'fabricator'],
|
||||
baseProgressPerTick: 0.015,
|
||||
attunementBonus: 0.025,
|
||||
description: "Enchant and construct in harmony."
|
||||
},
|
||||
hybrid_enchanter_invoker: {
|
||||
name: "Ritual Circle",
|
||||
attunements: ['enchanter', 'invoker'],
|
||||
baseProgressPerTick: 0.015,
|
||||
attunementBonus: 0.025,
|
||||
description: "Bind pact energies into enchantments."
|
||||
},
|
||||
hybrid_fabricator_invoker: {
|
||||
name: "Golem Forge",
|
||||
attunements: ['fabricator', 'invoker'],
|
||||
baseProgressPerTick: 0.015,
|
||||
attunementBonus: 0.025,
|
||||
description: "Channel guardian power into constructs."
|
||||
},
|
||||
};
|
||||
|
||||
// Swarm room configuration
|
||||
export const SWARM_CONFIG = {
|
||||
minEnemies: 3,
|
||||
maxEnemies: 6,
|
||||
hpMultiplier: 0.4, // Each enemy has 40% of normal floor HP
|
||||
armorBase: 0, // Swarm enemies start with no armor
|
||||
armorPerFloor: 0.01, // Gain 1% armor per 10 floors
|
||||
};
|
||||
|
||||
// Speed room configuration (dodging enemies)
|
||||
export const SPEED_ROOM_CONFIG = {
|
||||
baseDodgeChance: 0.25, // 25% base dodge chance
|
||||
dodgePerFloor: 0.005, // +0.5% dodge per floor
|
||||
maxDodge: 0.50, // Max 50% dodge
|
||||
speedBonus: 0.5, // 50% less time to complete if dodged
|
||||
};
|
||||
|
||||
// Armor scaling for normal floors
|
||||
export const FLOOR_ARMOR_CONFIG = {
|
||||
baseChance: 0, // No armor on floor 1-9
|
||||
chancePerFloor: 0.01, // +1% chance per floor after 10
|
||||
maxArmorChance: 0.5, // Max 50% of floors have armor
|
||||
minArmor: 0.05, // Min 5% armor
|
||||
maxArmor: 0.25, // Max 25% armor on non-guardians
|
||||
};
|
||||
|
||||
// ─── Guardians ────────────────────────────────────────────────────────────────
|
||||
// All guardians have armor - damage reduction percentage
|
||||
export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
10: {
|
||||
name: "Ignis Prime", element: "fire", hp: 5000, pact: 1.5, color: "#FF6B35",
|
||||
armor: 0.10, // 10% damage reduction
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 5, desc: '+5% Fire damage' },
|
||||
{ type: 'maxMana', value: 50, desc: '+50 max mana' },
|
||||
@@ -66,6 +160,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
20: {
|
||||
name: "Aqua Regia", element: "water", hp: 15000, pact: 1.75, color: "#4ECDC4",
|
||||
armor: 0.15,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 5, desc: '+5% Water damage' },
|
||||
{ type: 'manaRegen', value: 0.5, desc: '+0.5 mana regen' },
|
||||
@@ -76,6 +171,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
30: {
|
||||
name: "Ventus Rex", element: "air", hp: 30000, pact: 2.0, color: "#00D4FF",
|
||||
armor: 0.18,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 5, desc: '+5% Air damage' },
|
||||
{ type: 'castingSpeed', value: 5, desc: '+5% cast speed' },
|
||||
@@ -86,6 +182,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
40: {
|
||||
name: "Terra Firma", element: "earth", hp: 50000, pact: 2.25, color: "#F4A261",
|
||||
armor: 0.25, // Earth guardian - highest armor
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 5, desc: '+5% Earth damage' },
|
||||
{ type: 'maxMana', value: 100, desc: '+100 max mana' },
|
||||
@@ -96,6 +193,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
50: {
|
||||
name: "Lux Aeterna", element: "light", hp: 80000, pact: 2.5, color: "#FFD700",
|
||||
armor: 0.20,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 10, desc: '+10% Light damage' },
|
||||
{ type: 'insightGain', value: 10, desc: '+10% insight gain' },
|
||||
@@ -106,6 +204,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
60: {
|
||||
name: "Umbra Mortis", element: "dark", hp: 120000, pact: 2.75, color: "#9B59B6",
|
||||
armor: 0.22,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 10, desc: '+10% Dark damage' },
|
||||
{ type: 'critDamage', value: 15, desc: '+15% crit damage' },
|
||||
@@ -116,6 +215,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
70: {
|
||||
name: "Vita Sempiterna", element: "life", hp: 180000, pact: 3.0, color: "#2ECC71",
|
||||
armor: 0.20,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 10, desc: '+10% Life damage' },
|
||||
{ type: 'manaRegen', value: 1, desc: '+1 mana regen' },
|
||||
@@ -126,6 +226,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
80: {
|
||||
name: "Mors Ultima", element: "death", hp: 250000, pact: 3.25, color: "#778CA3",
|
||||
armor: 0.25,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 10, desc: '+10% Death damage' },
|
||||
{ type: 'rawDamage', value: 10, desc: '+10% raw damage' },
|
||||
@@ -136,6 +237,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
90: {
|
||||
name: "Primordialis", element: "void", hp: 400000, pact: 4.0, color: "#4A235A",
|
||||
armor: 0.30,
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 15, desc: '+15% Void damage' },
|
||||
{ type: 'maxMana', value: 200, desc: '+200 max mana' },
|
||||
@@ -147,6 +249,7 @@ export const GUARDIANS: Record<number, GuardianDef> = {
|
||||
},
|
||||
100: {
|
||||
name: "The Awakened One", element: "stellar", hp: 1000000, pact: 5.0, color: "#F0E68C",
|
||||
armor: 0.35, // Final boss has highest armor
|
||||
boons: [
|
||||
{ type: 'elementalDamage', value: 20, desc: '+20% Stellar damage' },
|
||||
{ type: 'maxMana', value: 500, desc: '+500 max mana' },
|
||||
@@ -648,6 +751,228 @@ export const SPELLS_DEF: Record<string, SpellDef> = {
|
||||
studyTime: 36,
|
||||
desc: "Shatter crystalline energy."
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// LIGHTNING SPELLS - Fast, armor-piercing, harder to dodge
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Tier 1 - Basic Lightning
|
||||
spark: {
|
||||
name: "Spark",
|
||||
elem: "lightning",
|
||||
dmg: 8,
|
||||
cost: elemCost("lightning", 1),
|
||||
tier: 1,
|
||||
castSpeed: 4,
|
||||
unlock: 120,
|
||||
studyTime: 2,
|
||||
desc: "A quick spark of lightning. Very fast and hard to dodge.",
|
||||
effects: [{ type: 'armor_pierce', value: 0.2 }]
|
||||
},
|
||||
lightningBolt: {
|
||||
name: "Lightning Bolt",
|
||||
elem: "lightning",
|
||||
dmg: 14,
|
||||
cost: elemCost("lightning", 2),
|
||||
tier: 1,
|
||||
castSpeed: 3,
|
||||
unlock: 150,
|
||||
studyTime: 3,
|
||||
desc: "A bolt of lightning that pierces armor.",
|
||||
effects: [{ type: 'armor_pierce', value: 0.3 }]
|
||||
},
|
||||
|
||||
// Tier 2 - Advanced Lightning
|
||||
chainLightning: {
|
||||
name: "Chain Lightning",
|
||||
elem: "lightning",
|
||||
dmg: 25,
|
||||
cost: elemCost("lightning", 5),
|
||||
tier: 2,
|
||||
castSpeed: 2,
|
||||
unlock: 900,
|
||||
studyTime: 8,
|
||||
desc: "Lightning that arcs between enemies. Hits 3 targets.",
|
||||
isAoe: true,
|
||||
aoeTargets: 3,
|
||||
effects: [{ type: 'chain', value: 3 }]
|
||||
},
|
||||
stormCall: {
|
||||
name: "Storm Call",
|
||||
elem: "lightning",
|
||||
dmg: 40,
|
||||
cost: elemCost("lightning", 6),
|
||||
tier: 2,
|
||||
castSpeed: 1.5,
|
||||
unlock: 1100,
|
||||
studyTime: 10,
|
||||
desc: "Call down a storm. Hits 2 targets with armor pierce.",
|
||||
isAoe: true,
|
||||
aoeTargets: 2,
|
||||
effects: [{ type: 'armor_pierce', value: 0.4 }]
|
||||
},
|
||||
|
||||
// Tier 3 - Master Lightning
|
||||
thunderStrike: {
|
||||
name: "Thunder Strike",
|
||||
elem: "lightning",
|
||||
dmg: 150,
|
||||
cost: elemCost("lightning", 15),
|
||||
tier: 3,
|
||||
castSpeed: 0.8,
|
||||
unlock: 10000,
|
||||
studyTime: 24,
|
||||
desc: "Devastating lightning that ignores 50% armor.",
|
||||
effects: [{ type: 'armor_pierce', value: 0.5 }]
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// AOE SPELLS - Hit multiple enemies, less damage per target
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Tier 1 AOE
|
||||
fireballAoe: {
|
||||
name: "Fireball (AOE)",
|
||||
elem: "fire",
|
||||
dmg: 8,
|
||||
cost: elemCost("fire", 3),
|
||||
tier: 1,
|
||||
castSpeed: 2,
|
||||
unlock: 150,
|
||||
studyTime: 3,
|
||||
desc: "An explosive fireball that hits 3 enemies.",
|
||||
isAoe: true,
|
||||
aoeTargets: 3,
|
||||
effects: [{ type: 'aoe', value: 3 }]
|
||||
},
|
||||
frostNova: {
|
||||
name: "Frost Nova",
|
||||
elem: "water",
|
||||
dmg: 6,
|
||||
cost: elemCost("water", 3),
|
||||
tier: 1,
|
||||
castSpeed: 2,
|
||||
unlock: 140,
|
||||
studyTime: 3,
|
||||
desc: "A burst of frost hitting 4 enemies. May freeze.",
|
||||
isAoe: true,
|
||||
aoeTargets: 4,
|
||||
effects: [{ type: 'freeze', value: 0.15, chance: 0.2 }]
|
||||
},
|
||||
|
||||
// Tier 2 AOE
|
||||
meteorShower: {
|
||||
name: "Meteor Shower",
|
||||
elem: "fire",
|
||||
dmg: 20,
|
||||
cost: elemCost("fire", 8),
|
||||
tier: 2,
|
||||
castSpeed: 1,
|
||||
unlock: 1200,
|
||||
studyTime: 10,
|
||||
desc: "Rain meteors on 5 enemies.",
|
||||
isAoe: true,
|
||||
aoeTargets: 5
|
||||
},
|
||||
blizzard: {
|
||||
name: "Blizzard",
|
||||
elem: "water",
|
||||
dmg: 18,
|
||||
cost: elemCost("water", 7),
|
||||
tier: 2,
|
||||
castSpeed: 1.2,
|
||||
unlock: 1000,
|
||||
studyTime: 9,
|
||||
desc: "A freezing blizzard hitting 4 enemies.",
|
||||
isAoe: true,
|
||||
aoeTargets: 4,
|
||||
effects: [{ type: 'freeze', value: 0.1, chance: 0.15 }]
|
||||
},
|
||||
earthquakeAoe: {
|
||||
name: "Earth Tremor",
|
||||
elem: "earth",
|
||||
dmg: 25,
|
||||
cost: elemCost("earth", 8),
|
||||
tier: 2,
|
||||
castSpeed: 0.8,
|
||||
unlock: 1400,
|
||||
studyTime: 10,
|
||||
desc: "Shake the ground, hitting 3 enemies with high damage.",
|
||||
isAoe: true,
|
||||
aoeTargets: 3
|
||||
},
|
||||
|
||||
// Tier 3 AOE
|
||||
apocalypse: {
|
||||
name: "Apocalypse",
|
||||
elem: "fire",
|
||||
dmg: 80,
|
||||
cost: elemCost("fire", 20),
|
||||
tier: 3,
|
||||
castSpeed: 0.5,
|
||||
unlock: 15000,
|
||||
studyTime: 30,
|
||||
desc: "End times. Hits ALL enemies with devastating fire.",
|
||||
isAoe: true,
|
||||
aoeTargets: 10
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MAGIC SWORD ENCHANTMENTS - For weapon enchanting system
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
fireBlade: {
|
||||
name: "Fire Blade",
|
||||
elem: "fire",
|
||||
dmg: 3,
|
||||
cost: rawCost(1),
|
||||
tier: 1,
|
||||
castSpeed: 4,
|
||||
unlock: 100,
|
||||
studyTime: 2,
|
||||
desc: "Enchant a blade with fire. Burns enemies over time.",
|
||||
isWeaponEnchant: true,
|
||||
effects: [{ type: 'burn', value: 2, duration: 3 }]
|
||||
},
|
||||
frostBlade: {
|
||||
name: "Frost Blade",
|
||||
elem: "water",
|
||||
dmg: 3,
|
||||
cost: rawCost(1),
|
||||
tier: 1,
|
||||
castSpeed: 4,
|
||||
unlock: 100,
|
||||
studyTime: 2,
|
||||
desc: "Enchant a blade with frost. Prevents enemy dodge.",
|
||||
isWeaponEnchant: true,
|
||||
effects: [{ type: 'freeze', value: 0, chance: 1 }] // 100% freeze = no dodge
|
||||
},
|
||||
lightningBlade: {
|
||||
name: "Lightning Blade",
|
||||
elem: "lightning",
|
||||
dmg: 4,
|
||||
cost: rawCost(1),
|
||||
tier: 1,
|
||||
castSpeed: 5,
|
||||
unlock: 150,
|
||||
studyTime: 3,
|
||||
desc: "Enchant a blade with lightning. Pierces 30% armor.",
|
||||
isWeaponEnchant: true,
|
||||
effects: [{ type: 'armor_pierce', value: 0.3 }]
|
||||
},
|
||||
voidBlade: {
|
||||
name: "Void Blade",
|
||||
elem: "dark",
|
||||
dmg: 5,
|
||||
cost: rawCost(2),
|
||||
tier: 2,
|
||||
castSpeed: 3,
|
||||
unlock: 800,
|
||||
studyTime: 8,
|
||||
desc: "Enchant a blade with void. 10% lifesteal.",
|
||||
isWeaponEnchant: true,
|
||||
effects: [{ type: 'lifesteal', value: 0.1 }]
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Skills ───────────────────────────────────────────────────────────────────
|
||||
@@ -848,6 +1173,7 @@ export const ELEMENT_OPPOSITES: Record<string, string> = {
|
||||
air: 'earth', earth: 'air',
|
||||
light: 'dark', dark: 'light',
|
||||
life: 'death', death: 'life',
|
||||
lightning: 'earth', // Lightning is weak to earth (grounding)
|
||||
};
|
||||
|
||||
// ─── Element Icon Mapping (Lucide Icons) ──────────────────────────────────────
|
||||
@@ -869,6 +1195,7 @@ export const ELEMENT_ICON_NAMES: Record<string, string> = {
|
||||
metal: 'Target',
|
||||
wood: 'TreeDeciduous',
|
||||
sand: 'Hourglass',
|
||||
lightning: 'Zap',
|
||||
crystal: 'Gem',
|
||||
stellar: 'Star',
|
||||
void: 'CircleDot',
|
||||
|
||||
@@ -5,12 +5,14 @@ import type { EquipmentCategory } from './equipment'
|
||||
|
||||
// Helper to define allowed equipment categories for each effect type
|
||||
const ALL_CASTER: EquipmentCategory[] = ['caster']
|
||||
const CASTER_AND_SWORD: EquipmentCategory[] = ['caster', 'sword']
|
||||
const WEAPON_EQUIPMENT: EquipmentCategory[] = ['caster', 'catalyst', 'sword'] // All main hand equipment
|
||||
const CASTER_AND_HANDS: EquipmentCategory[] = ['caster', 'hands']
|
||||
const BODY_AND_SHIELD: EquipmentCategory[] = ['body', 'shield']
|
||||
const CASTER_CATALYST_ACCESSORY: EquipmentCategory[] = ['caster', 'catalyst', 'accessory']
|
||||
const MANA_EQUIPMENT: EquipmentCategory[] = ['caster', 'catalyst', 'head', 'body', 'accessory']
|
||||
const UTILITY_EQUIPMENT: EquipmentCategory[] = ['caster', 'catalyst', 'head', 'body', 'hands', 'feet', 'accessory']
|
||||
const ALL_EQUIPMENT: EquipmentCategory[] = ['caster', 'shield', 'catalyst', 'head', 'body', 'hands', 'feet', 'accessory']
|
||||
const ALL_EQUIPMENT: EquipmentCategory[] = ['caster', 'shield', 'catalyst', 'sword', 'head', 'body', 'hands', 'feet', 'accessory']
|
||||
|
||||
export type EnchantmentEffectCategory = 'spell' | 'mana' | 'combat' | 'elemental' | 'defense' | 'utility' | 'special'
|
||||
|
||||
@@ -567,6 +569,117 @@ export const ENCHANTMENT_EFFECTS: Record<string, EnchantmentEffectDef> = {
|
||||
allowedEquipmentCategories: CASTER_AND_HANDS,
|
||||
effect: { type: 'special', specialId: 'overpower' }
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// WEAPON MANA EFFECTS - Enchanter level 3+ unlocks these
|
||||
// These add mana capacity and regeneration to weapons for their enchantments
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
weapon_mana_cap_20: {
|
||||
id: 'weapon_mana_cap_20',
|
||||
name: 'Mana Cell',
|
||||
description: '+20 weapon mana capacity (for weapon enchantments)',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 25,
|
||||
maxStacks: 5,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaMax', value: 20 }
|
||||
},
|
||||
weapon_mana_cap_50: {
|
||||
id: 'weapon_mana_cap_50',
|
||||
name: 'Mana Vessel',
|
||||
description: '+50 weapon mana capacity (for weapon enchantments)',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 50,
|
||||
maxStacks: 3,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaMax', value: 50 }
|
||||
},
|
||||
weapon_mana_cap_100: {
|
||||
id: 'weapon_mana_cap_100',
|
||||
name: 'Mana Core',
|
||||
description: '+100 weapon mana capacity (for weapon enchantments)',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 80,
|
||||
maxStacks: 2,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaMax', value: 100 }
|
||||
},
|
||||
weapon_mana_regen_1: {
|
||||
id: 'weapon_mana_regen_1',
|
||||
name: 'Mana Wick',
|
||||
description: '+1 weapon mana regeneration per hour',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 20,
|
||||
maxStacks: 5,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaRegen', value: 1 }
|
||||
},
|
||||
weapon_mana_regen_2: {
|
||||
id: 'weapon_mana_regen_2',
|
||||
name: 'Mana Siphon',
|
||||
description: '+2 weapon mana regeneration per hour',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 35,
|
||||
maxStacks: 3,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaRegen', value: 2 }
|
||||
},
|
||||
weapon_mana_regen_5: {
|
||||
id: 'weapon_mana_regen_5',
|
||||
name: 'Mana Well',
|
||||
description: '+5 weapon mana regeneration per hour',
|
||||
category: 'mana',
|
||||
baseCapacityCost: 60,
|
||||
maxStacks: 2,
|
||||
allowedEquipmentCategories: WEAPON_EQUIPMENT,
|
||||
effect: { type: 'bonus', stat: 'weaponManaRegen', value: 5 }
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MAGIC SWORD ENCHANTMENTS - Elemental weapon effects
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
sword_fire: {
|
||||
id: 'sword_fire',
|
||||
name: 'Fire Enchant',
|
||||
description: 'Enchant blade with fire. Burns enemies over time.',
|
||||
category: 'elemental',
|
||||
baseCapacityCost: 40,
|
||||
maxStacks: 1,
|
||||
allowedEquipmentCategories: CASTER_AND_SWORD,
|
||||
effect: { type: 'special', specialId: 'fireBlade' }
|
||||
},
|
||||
sword_frost: {
|
||||
id: 'sword_frost',
|
||||
name: 'Frost Enchant',
|
||||
description: 'Enchant blade with frost. Prevents enemy dodge.',
|
||||
category: 'elemental',
|
||||
baseCapacityCost: 40,
|
||||
maxStacks: 1,
|
||||
allowedEquipmentCategories: CASTER_AND_SWORD,
|
||||
effect: { type: 'special', specialId: 'frostBlade' }
|
||||
},
|
||||
sword_lightning: {
|
||||
id: 'sword_lightning',
|
||||
name: 'Lightning Enchant',
|
||||
description: 'Enchant blade with lightning. Pierces 30% armor.',
|
||||
category: 'elemental',
|
||||
baseCapacityCost: 50,
|
||||
maxStacks: 1,
|
||||
allowedEquipmentCategories: CASTER_AND_SWORD,
|
||||
effect: { type: 'special', specialId: 'lightningBlade' }
|
||||
},
|
||||
sword_void: {
|
||||
id: 'sword_void',
|
||||
name: 'Void Enchant',
|
||||
description: 'Enchant blade with void. 10% lifesteal.',
|
||||
category: 'elemental',
|
||||
baseCapacityCost: 60,
|
||||
maxStacks: 1,
|
||||
allowedEquipmentCategories: CASTER_AND_SWORD,
|
||||
effect: { type: 'special', specialId: 'voidBlade' }
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Helper Functions ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// ─── Equipment Types ─────────────────────────────────────────────────────────
|
||||
|
||||
export type EquipmentSlot = 'mainHand' | 'offHand' | 'head' | 'body' | 'hands' | 'feet' | 'accessory1' | 'accessory2';
|
||||
export type EquipmentCategory = 'caster' | 'shield' | 'catalyst' | 'head' | 'body' | 'hands' | 'feet' | 'accessory';
|
||||
export type EquipmentCategory = 'caster' | 'shield' | 'catalyst' | 'sword' | 'head' | 'body' | 'hands' | 'feet' | 'accessory';
|
||||
|
||||
// All equipment slots in order
|
||||
export const EQUIPMENT_SLOTS: EquipmentSlot[] = ['mainHand', 'offHand', 'head', 'body', 'hands', 'feet', 'accessory1', 'accessory2'];
|
||||
@@ -13,6 +13,8 @@ export interface EquipmentType {
|
||||
slot: EquipmentSlot;
|
||||
baseCapacity: number;
|
||||
description: string;
|
||||
baseDamage?: number; // For swords
|
||||
baseCastSpeed?: number; // For swords (higher = faster)
|
||||
}
|
||||
|
||||
// ─── Equipment Types Definition ─────────────────────────────────────────────
|
||||
@@ -94,6 +96,60 @@ export const EQUIPMENT_TYPES: Record<string, EquipmentType> = {
|
||||
description: 'A rare catalyst touched by void energy. High capacity but volatile.',
|
||||
},
|
||||
|
||||
// ─── Main Hand - Magic Swords ─────────────────────────────────────────────
|
||||
// Magic swords have low base damage but high cast speed
|
||||
// They can be enchanted with elemental effects that use mana over time
|
||||
ironBlade: {
|
||||
id: 'ironBlade',
|
||||
name: 'Iron Blade',
|
||||
category: 'sword',
|
||||
slot: 'mainHand',
|
||||
baseCapacity: 30,
|
||||
baseDamage: 3,
|
||||
baseCastSpeed: 4,
|
||||
description: 'A simple iron sword. Can be enchanted with elemental effects.',
|
||||
},
|
||||
steelBlade: {
|
||||
id: 'steelBlade',
|
||||
name: 'Steel Blade',
|
||||
category: 'sword',
|
||||
slot: 'mainHand',
|
||||
baseCapacity: 40,
|
||||
baseDamage: 4,
|
||||
baseCastSpeed: 4,
|
||||
description: 'A well-crafted steel sword. Balanced for combat and enchanting.',
|
||||
},
|
||||
crystalBlade: {
|
||||
id: 'crystalBlade',
|
||||
name: 'Crystal Blade',
|
||||
category: 'sword',
|
||||
slot: 'mainHand',
|
||||
baseCapacity: 55,
|
||||
baseDamage: 3,
|
||||
baseCastSpeed: 5,
|
||||
description: 'A blade made of crystallized mana. Excellent for elemental enchantments.',
|
||||
},
|
||||
arcanistBlade: {
|
||||
id: 'arcanistBlade',
|
||||
name: 'Arcanist Blade',
|
||||
category: 'sword',
|
||||
slot: 'mainHand',
|
||||
baseCapacity: 65,
|
||||
baseDamage: 5,
|
||||
baseCastSpeed: 4,
|
||||
description: 'A sword forged for battle mages. High capacity for powerful enchantments.',
|
||||
},
|
||||
voidBlade: {
|
||||
id: 'voidBlade',
|
||||
name: 'Void-Touched Blade',
|
||||
category: 'sword',
|
||||
slot: 'mainHand',
|
||||
baseCapacity: 50,
|
||||
baseDamage: 6,
|
||||
baseCastSpeed: 3,
|
||||
description: 'A blade corrupted by void energy. Powerful but consumes more mana.',
|
||||
},
|
||||
|
||||
// ─── Off Hand - Shields ───────────────────────────────────────────────────
|
||||
basicShield: {
|
||||
id: 'basicShield',
|
||||
@@ -386,6 +442,7 @@ export function getValidSlotsForCategory(category: EquipmentCategory): Equipment
|
||||
switch (category) {
|
||||
case 'caster':
|
||||
case 'catalyst':
|
||||
case 'sword':
|
||||
return ['mainHand'];
|
||||
case 'shield':
|
||||
return ['offHand'];
|
||||
|
||||
0
src/lib/game/debug-context.tsx
Normal file → Executable file
0
src/lib/game/debug-context.tsx
Normal file → Executable file
@@ -2,7 +2,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState } from './types';
|
||||
import type { GameState, GameAction, StudyTarget, SpellCost, SkillUpgradeChoice, EquipmentSlot, EquipmentInstance, EnchantmentDesign, DesignEffect, AttunementState, FloorState, EnemyState, RoomType } from './types';
|
||||
import {
|
||||
ELEMENTS,
|
||||
GUARDIANS,
|
||||
@@ -22,6 +22,14 @@ import {
|
||||
EFFECT_RESEARCH_MAPPING,
|
||||
BASE_UNLOCKED_EFFECTS,
|
||||
ENCHANTING_UNLOCK_EFFECTS,
|
||||
PUZZLE_ROOMS,
|
||||
PUZZLE_ROOM_INTERVAL,
|
||||
PUZZLE_ROOM_CHANCE,
|
||||
SWARM_ROOM_CHANCE,
|
||||
SPEED_ROOM_CHANCE,
|
||||
SWARM_CONFIG,
|
||||
SPEED_ROOM_CONFIG,
|
||||
FLOOR_ARMOR_CONFIG,
|
||||
} from './constants';
|
||||
import { computeEffects, hasSpecial, SPECIAL_EFFECTS, type ComputedEffects } from './upgrade-effects';
|
||||
import {
|
||||
@@ -99,6 +107,174 @@ export function getFloorElement(floor: number): string {
|
||||
return FLOOR_ELEM_CYCLE[(floor - 1) % 8];
|
||||
}
|
||||
|
||||
// ─── Room Generation Functions ────────────────────────────────────────────────
|
||||
|
||||
// Generate room type for a floor
|
||||
export function generateRoomType(floor: number): RoomType {
|
||||
// Guardian floors are always guardian type
|
||||
if (GUARDIANS[floor]) {
|
||||
return 'guardian';
|
||||
}
|
||||
|
||||
// Check for puzzle room (every PUZZLE_ROOM_INTERVAL floors)
|
||||
if (floor % PUZZLE_ROOM_INTERVAL === 0 && Math.random() < PUZZLE_ROOM_CHANCE) {
|
||||
return 'puzzle';
|
||||
}
|
||||
|
||||
// Check for swarm room
|
||||
if (Math.random() < SWARM_ROOM_CHANCE) {
|
||||
return 'swarm';
|
||||
}
|
||||
|
||||
// Check for speed room
|
||||
if (Math.random() < SPEED_ROOM_CHANCE) {
|
||||
return 'speed';
|
||||
}
|
||||
|
||||
// Default to combat
|
||||
return 'combat';
|
||||
}
|
||||
|
||||
// Get armor for a non-guardian floor
|
||||
export function getFloorArmor(floor: number): number {
|
||||
if (GUARDIANS[floor]) {
|
||||
return GUARDIANS[floor].armor || 0;
|
||||
}
|
||||
|
||||
// Armor becomes more common on higher floors
|
||||
if (floor < 10) return 0;
|
||||
|
||||
const armorChance = Math.min(FLOOR_ARMOR_CONFIG.maxArmorChance,
|
||||
FLOOR_ARMOR_CONFIG.baseChance + (floor - 10) * FLOOR_ARMOR_CONFIG.chancePerFloor);
|
||||
|
||||
if (Math.random() > armorChance) return 0;
|
||||
|
||||
// Scale armor with floor
|
||||
const armorRange = FLOOR_ARMOR_CONFIG.maxArmor - FLOOR_ARMOR_CONFIG.minArmor;
|
||||
const floorProgress = Math.min(1, (floor - 10) / 90);
|
||||
return FLOOR_ARMOR_CONFIG.minArmor + armorRange * floorProgress * Math.random();
|
||||
}
|
||||
|
||||
// Get dodge chance for a speed room
|
||||
export function getDodgeChance(floor: number): number {
|
||||
return Math.min(
|
||||
SPEED_ROOM_CONFIG.maxDodge,
|
||||
SPEED_ROOM_CONFIG.baseDodgeChance + floor * SPEED_ROOM_CONFIG.dodgePerFloor
|
||||
);
|
||||
}
|
||||
|
||||
// Generate enemies for a swarm room
|
||||
export function generateSwarmEnemies(floor: number): EnemyState[] {
|
||||
const baseHP = getFloorMaxHP(floor);
|
||||
const element = getFloorElement(floor);
|
||||
const numEnemies = SWARM_CONFIG.minEnemies +
|
||||
Math.floor(Math.random() * (SWARM_CONFIG.maxEnemies - SWARM_CONFIG.minEnemies + 1));
|
||||
|
||||
const enemies: EnemyState[] = [];
|
||||
for (let i = 0; i < numEnemies; i++) {
|
||||
enemies.push({
|
||||
id: `enemy_${i}`,
|
||||
hp: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
|
||||
maxHP: Math.floor(baseHP * SWARM_CONFIG.hpMultiplier),
|
||||
armor: SWARM_CONFIG.armorBase + Math.floor(floor / 10) * SWARM_CONFIG.armorPerFloor,
|
||||
dodgeChance: 0,
|
||||
element,
|
||||
});
|
||||
}
|
||||
return enemies;
|
||||
}
|
||||
|
||||
// Generate initial floor state
|
||||
export function generateFloorState(floor: number): FloorState {
|
||||
const roomType = generateRoomType(floor);
|
||||
const element = getFloorElement(floor);
|
||||
const baseHP = getFloorMaxHP(floor);
|
||||
const guardian = GUARDIANS[floor];
|
||||
|
||||
switch (roomType) {
|
||||
case 'guardian':
|
||||
return {
|
||||
roomType: 'guardian',
|
||||
enemies: [{
|
||||
id: 'guardian',
|
||||
hp: guardian.hp,
|
||||
maxHP: guardian.hp,
|
||||
armor: guardian.armor || 0,
|
||||
dodgeChance: 0,
|
||||
element: guardian.element,
|
||||
}],
|
||||
};
|
||||
|
||||
case 'swarm':
|
||||
return {
|
||||
roomType: 'swarm',
|
||||
enemies: generateSwarmEnemies(floor),
|
||||
};
|
||||
|
||||
case 'speed':
|
||||
return {
|
||||
roomType: 'speed',
|
||||
enemies: [{
|
||||
id: 'speed_enemy',
|
||||
hp: baseHP,
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: getDodgeChance(floor),
|
||||
element,
|
||||
}],
|
||||
};
|
||||
|
||||
case 'puzzle': {
|
||||
// Select a puzzle type based on player's attunements
|
||||
const puzzleKeys = Object.keys(PUZZLE_ROOMS);
|
||||
const selectedPuzzle = puzzleKeys[Math.floor(Math.random() * puzzleKeys.length)];
|
||||
const puzzle = PUZZLE_ROOMS[selectedPuzzle];
|
||||
return {
|
||||
roomType: 'puzzle',
|
||||
enemies: [],
|
||||
puzzleProgress: 0,
|
||||
puzzleRequired: 1,
|
||||
puzzleId: selectedPuzzle,
|
||||
puzzleAttunements: puzzle.attunements,
|
||||
};
|
||||
}
|
||||
|
||||
default: // combat
|
||||
return {
|
||||
roomType: 'combat',
|
||||
enemies: [{
|
||||
id: 'enemy',
|
||||
hp: baseHP,
|
||||
maxHP: baseHP,
|
||||
armor: getFloorArmor(floor),
|
||||
dodgeChance: 0,
|
||||
element,
|
||||
}],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get puzzle progress speed based on attunements
|
||||
export function getPuzzleProgressSpeed(
|
||||
puzzleId: string,
|
||||
attunements: Record<string, AttunementState>
|
||||
): number {
|
||||
const puzzle = PUZZLE_ROOMS[puzzleId];
|
||||
if (!puzzle) return 0.02; // Default slow progress
|
||||
|
||||
let speed = puzzle.baseProgressPerTick;
|
||||
|
||||
// Add bonus for each relevant attunement level
|
||||
for (const attId of puzzle.attunements) {
|
||||
const attState = attunements[attId];
|
||||
if (attState?.active) {
|
||||
speed += puzzle.attunementBonus * (attState.level || 1);
|
||||
}
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
|
||||
// ─── Computed Stats Functions ─────────────────────────────────────────────────
|
||||
|
||||
// Helper to get effective skill level accounting for tiers
|
||||
@@ -466,6 +642,9 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
||||
activeSpell: 'manaBolt',
|
||||
currentAction: 'meditate',
|
||||
castProgress: 0,
|
||||
|
||||
// Initialize room state
|
||||
currentRoom: generateFloorState(startFloor),
|
||||
|
||||
spells: startSpells,
|
||||
skills: overrides.skills || {},
|
||||
@@ -783,10 +962,35 @@ export const useGameStore = create<GameStore>()(
|
||||
}
|
||||
|
||||
// Combat - uses cast speed and spell casting
|
||||
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress } = state;
|
||||
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress, currentRoom } = state;
|
||||
const floorElement = getFloorElement(currentFloor);
|
||||
|
||||
if (state.currentAction === 'climb') {
|
||||
// Handle puzzle rooms separately
|
||||
if (state.currentAction === 'climb' && currentRoom.roomType === 'puzzle') {
|
||||
const progressSpeed = getPuzzleProgressSpeed(
|
||||
currentRoom.puzzleId || '',
|
||||
state.attunements
|
||||
);
|
||||
|
||||
currentRoom = {
|
||||
...currentRoom,
|
||||
puzzleProgress: (currentRoom.puzzleProgress || 0) + progressSpeed * HOURS_PER_TICK,
|
||||
};
|
||||
|
||||
// Check if puzzle is complete
|
||||
if (currentRoom.puzzleProgress >= (currentRoom.puzzleRequired || 1)) {
|
||||
const puzzle = PUZZLE_ROOMS[currentRoom.puzzleId || ''];
|
||||
log = [`🧩 ${puzzle?.name || 'Puzzle'} solved! Proceeding to floor ${currentFloor + 1}.`, ...log.slice(0, 49)];
|
||||
|
||||
currentFloor = currentFloor + 1;
|
||||
if (currentFloor > 100) currentFloor = 100;
|
||||
currentRoom = generateFloorState(currentFloor);
|
||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||
floorHP = currentRoom.enemies[0]?.hp || floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
castProgress = 0;
|
||||
}
|
||||
} else if (state.currentAction === 'climb') {
|
||||
const spellId = state.activeSpell;
|
||||
const spellDef = SPELLS_DEF[spellId];
|
||||
|
||||
@@ -798,8 +1002,11 @@ export const useGameStore = create<GameStore>()(
|
||||
// Get spell cast speed (casts per hour, default 1)
|
||||
const spellCastSpeed = spellDef.castSpeed || 1;
|
||||
|
||||
// Lightning is faster and harder to dodge
|
||||
const lightningBonus = spellDef.elem === 'lightning' ? 0.3 : 0;
|
||||
|
||||
// Effective casts per tick = spellCastSpeed * totalAttackSpeed
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
|
||||
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed * (1 + lightningBonus);
|
||||
|
||||
// Accumulate cast progress
|
||||
castProgress = (castProgress || 0) + progressPerTick;
|
||||
@@ -813,50 +1020,105 @@ export const useGameStore = create<GameStore>()(
|
||||
totalManaGathered += spellDef.cost.amount;
|
||||
|
||||
// Calculate damage
|
||||
let dmg = calcDamage(state, spellId, floorElement);
|
||||
let baseDmg = calcDamage(state, spellId, floorElement);
|
||||
|
||||
// Apply upgrade damage multipliers and bonuses
|
||||
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
baseDmg = baseDmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
|
||||
|
||||
// Executioner: +100% damage to enemies below 25% HP
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
// Determine how many targets to hit
|
||||
const isAoe = spellDef.isAoe || spellDef.aoeTargets;
|
||||
const numTargets = Math.min(
|
||||
isAoe ? (spellDef.aoeTargets || 3) : 1,
|
||||
currentRoom.enemies.filter(e => e.hp > 0).length
|
||||
);
|
||||
|
||||
// Get armor pierce from spell effects
|
||||
const armorPierceEffect = spellDef.effects?.find(e => e.type === 'armor_pierce');
|
||||
const armorPierce = armorPierceEffect?.value || 0;
|
||||
|
||||
// Hit targets
|
||||
const aliveEnemies = currentRoom.enemies.filter(e => e.hp > 0);
|
||||
const targetsToHit = aliveEnemies.slice(0, numTargets);
|
||||
|
||||
for (const enemy of targetsToHit) {
|
||||
let dmg = baseDmg;
|
||||
|
||||
// AOE does less damage per target
|
||||
if (isAoe && numTargets > 1) {
|
||||
dmg *= (1 - 0.1 * (numTargets - 1)); // 10% less per additional target
|
||||
}
|
||||
|
||||
// Check for freeze (prevents dodge)
|
||||
const freezeEffect = spellDef.effects?.find(e => e.type === 'freeze');
|
||||
const isFrozen = freezeEffect && freezeEffect.chance === 1;
|
||||
|
||||
// Check for dodge (speed rooms)
|
||||
if (!isFrozen && enemy.dodgeChance > 0) {
|
||||
// Lightning has lower chance to be dodged
|
||||
const effectiveDodge = spellDef.elem === 'lightning'
|
||||
? enemy.dodgeChance * 0.5
|
||||
: enemy.dodgeChance;
|
||||
|
||||
if (Math.random() < effectiveDodge) {
|
||||
log = [`💨 Enemy dodged the attack!`, ...log.slice(0, 49)];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply armor reduction
|
||||
const effectiveArmor = Math.max(0, enemy.armor - armorPierce);
|
||||
dmg *= (1 - effectiveArmor);
|
||||
|
||||
// Executioner: +100% damage to enemies below 25% HP
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && enemy.hp / enemy.maxHP < 0.25) {
|
||||
dmg *= 2;
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
|
||||
dmg *= 1.5;
|
||||
}
|
||||
|
||||
// Spell echo - chance to cast again
|
||||
const echoChance = (skills.spellEcho || 0) * 0.1;
|
||||
if (Math.random() < echoChance) {
|
||||
dmg *= 2;
|
||||
log = [`✨ Spell Echo! Double damage!`, ...log.slice(0, 49)];
|
||||
}
|
||||
|
||||
// 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 to enemy
|
||||
enemy.hp = Math.max(0, enemy.hp - Math.floor(dmg));
|
||||
}
|
||||
|
||||
// Berserker: +50% damage when below 50% mana
|
||||
if (hasSpecial(effects, SPECIAL_EFFECTS.BERSERKER) && rawMana < maxMana * 0.5) {
|
||||
dmg *= 1.5;
|
||||
}
|
||||
|
||||
// Spell echo - chance to cast again
|
||||
const echoChance = (skills.spellEcho || 0) * 0.1;
|
||||
if (Math.random() < echoChance) {
|
||||
dmg *= 2;
|
||||
log = [`✨ Spell Echo! Double damage!`, ...log.slice(0, 49)];
|
||||
}
|
||||
|
||||
// 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
|
||||
floorHP = Math.max(0, floorHP - dmg);
|
||||
// Update currentRoom with damaged enemies
|
||||
currentRoom = { ...currentRoom, enemies: [...currentRoom.enemies] };
|
||||
|
||||
// Reduce cast progress by 1 (one cast completed)
|
||||
castProgress -= 1;
|
||||
|
||||
if (floorHP <= 0) {
|
||||
|
||||
// Check if all enemies are dead
|
||||
const allDead = currentRoom.enemies.every(e => e.hp <= 0);
|
||||
|
||||
if (allDead) {
|
||||
// Floor cleared
|
||||
const wasGuardian = GUARDIANS[currentFloor];
|
||||
if (wasGuardian && !signedPacts.includes(currentFloor)) {
|
||||
signedPacts = [...signedPacts, currentFloor];
|
||||
log = [`⚔️ ${wasGuardian.name} defeated! Pact signed! (${wasGuardian.pact}x)`, ...log.slice(0, 49)];
|
||||
} else if (!wasGuardian) {
|
||||
if (currentFloor % 5 === 0) {
|
||||
log = [`🏰 Floor ${currentFloor} cleared!`, ...log.slice(0, 49)];
|
||||
const roomTypeName = currentRoom.roomType === 'swarm' ? 'Swarm'
|
||||
: currentRoom.roomType === 'speed' ? 'Speed floor'
|
||||
: currentRoom.roomType === 'puzzle' ? 'Puzzle'
|
||||
: 'Floor';
|
||||
if (currentFloor % 5 === 0 || currentRoom.roomType !== 'combat') {
|
||||
log = [`🏰 ${roomTypeName} ${currentFloor} cleared!`, ...log.slice(0, 49)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,8 +1126,9 @@ export const useGameStore = create<GameStore>()(
|
||||
if (currentFloor > 100) {
|
||||
currentFloor = 100;
|
||||
}
|
||||
currentRoom = generateFloorState(currentFloor);
|
||||
floorMaxHP = getFloorMaxHP(currentFloor);
|
||||
floorHP = floorMaxHP;
|
||||
floorHP = currentRoom.enemies[0]?.hp || floorMaxHP;
|
||||
maxFloorReached = Math.max(maxFloorReached, currentFloor);
|
||||
|
||||
// Reset cast progress on floor change
|
||||
@@ -917,6 +1180,7 @@ export const useGameStore = create<GameStore>()(
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
@@ -940,6 +1204,7 @@ export const useGameStore = create<GameStore>()(
|
||||
floorMaxHP,
|
||||
maxFloorReached,
|
||||
signedPacts,
|
||||
currentRoom,
|
||||
incursionStrength,
|
||||
currentStudyTarget,
|
||||
skills,
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface GuardianDef {
|
||||
pactCost: number; // Mana cost to perform pact ritual
|
||||
pactTime: number; // Hours required for pact ritual
|
||||
uniquePerk: string; // Description of unique perk
|
||||
armor?: number; // Damage reduction (0-1, e.g., 0.2 = 20% reduction)
|
||||
}
|
||||
|
||||
// Spell cost can be raw mana or elemental mana
|
||||
@@ -84,12 +85,17 @@ export interface SpellDef {
|
||||
castSpeed?: number; // Casts per hour (default 1, higher = faster)
|
||||
desc?: string; // Optional spell description
|
||||
effects?: SpellEffect[]; // Optional special effects
|
||||
isAoe?: boolean; // AOE spell that hits multiple enemies
|
||||
aoeTargets?: number; // Number of enemies hit by AOE
|
||||
isWeaponEnchant?: boolean; // Can be used as weapon enchantment (magic swords)
|
||||
}
|
||||
|
||||
export interface SpellEffect {
|
||||
type: 'lifesteal' | 'burn' | 'freeze' | 'stun' | 'pierce' | 'multicast' | 'shield' | 'buff';
|
||||
type: 'lifesteal' | 'burn' | 'freeze' | 'stun' | 'pierce' | 'multicast' | 'shield' | 'buff' | 'chain' | 'aoe' | 'armor_pierce';
|
||||
value: number; // Effect potency
|
||||
duration?: number; // Duration in hours for timed effects
|
||||
targets?: number; // For AOE: number of targets
|
||||
chance?: number; // For chance-based effects (e.g., stun chance)
|
||||
}
|
||||
|
||||
export interface SpellState {
|
||||
@@ -98,6 +104,28 @@ export interface SpellState {
|
||||
studyProgress?: number; // Hours studied so far (for in-progress spells)
|
||||
}
|
||||
|
||||
// ─── Room and Enemy Types ─────────────────────────────────────────────────────
|
||||
|
||||
export type RoomType = 'combat' | 'puzzle' | 'swarm' | 'speed' | 'guardian';
|
||||
|
||||
export interface EnemyState {
|
||||
id: string;
|
||||
hp: number;
|
||||
maxHP: number;
|
||||
armor: number; // Damage reduction (0-1)
|
||||
dodgeChance: number; // For speed rooms (0-1)
|
||||
element: string;
|
||||
}
|
||||
|
||||
export interface FloorState {
|
||||
roomType: RoomType;
|
||||
enemies: EnemyState[]; // For swarm rooms, multiple enemies
|
||||
puzzleProgress?: number; // For puzzle rooms (0-1)
|
||||
puzzleRequired?: number; // Total progress needed
|
||||
puzzleId?: string; // Which puzzle type
|
||||
puzzleAttunements?: string[]; // Which attunements speed up this puzzle
|
||||
}
|
||||
|
||||
export interface SkillDef {
|
||||
name: string;
|
||||
desc: string;
|
||||
@@ -183,6 +211,11 @@ export interface EquipmentInstance {
|
||||
totalCapacity: number; // Base capacity + bonuses
|
||||
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary' | 'mythic';
|
||||
quality: number; // 0-100, affects capacity efficiency
|
||||
weaponMana?: number; // Current mana stored in weapon (for weapon enchantments)
|
||||
weaponManaMax?: number; // Max mana the weapon can store
|
||||
weaponManaRegen?: number; // Mana regen per hour for weapon
|
||||
weaponManaType?: string; // Type of mana the weapon stores
|
||||
activeWeaponEnchant?: string; // Active weapon enchantment (for magic swords)
|
||||
}
|
||||
|
||||
export interface AppliedEnchantment {
|
||||
@@ -346,6 +379,9 @@ export interface GameState {
|
||||
activeSpell: string;
|
||||
currentAction: GameAction;
|
||||
castProgress: number; // Progress towards next spell cast (0-1)
|
||||
|
||||
// Room system for special floors
|
||||
currentRoom: FloorState; // Current room state (swarm, puzzle, speed, etc.)
|
||||
|
||||
// Spells
|
||||
spells: Record<string, SpellState>;
|
||||
|
||||
Reference in New Issue
Block a user