This commit is contained in:
Z User
2026-03-25 15:05:12 +00:00
parent 5b6e50c0bd
commit 3b2e89db74
11 changed files with 580 additions and 111 deletions

View File

@@ -714,7 +714,7 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
// Special Effect Research
researchSpecialEffects: { name: "Special Effect Research", desc: "Unlock Echo Chamber, Siphoning, Bane effects", cat: "effectResearch", max: 1, base: 500, studyTime: 10, req: { enchanting: 4 } },
researchExecutioner: { name: "Executioner Research", desc: "Unlock Executioner effect", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSpecialEffects: 1, enchanting: 5 } },
researchOverpower: { name: "Overpower Research", desc: "Unlock Overpower effect", cat: "effectResearch", max: 1, base: 700, studyTime: 12, req: { researchSpecialEffects: 1, enchanting: 5 } },
// Research Skills (longer study times: 12-72 hours)
manaTap: { name: "Mana Tap", desc: "+1 mana/click", cat: "research", max: 1, base: 300, studyTime: 12 },
@@ -806,7 +806,7 @@ export const EFFECT_RESEARCH_MAPPING: Record<string, string[]> = {
// Special Effect Research
researchSpecialEffects: ['spell_echo_10', 'lifesteal_5', 'guardian_dmg_10'],
researchExecutioner: ['execute_25'],
researchOverpower: ['overpower_80'],
};
// Base effects unlocked when player gets enchanting skill level 1

View File

@@ -557,15 +557,15 @@ export const ENCHANTMENT_EFFECTS: Record<string, EnchantmentEffectDef> = {
allowedEquipmentCategories: CASTER_CATALYST_ACCESSORY,
effect: { type: 'multiplier', stat: 'guardianDamage', value: 1.10 }
},
execute_25: {
id: 'execute_25',
name: 'Executioner',
description: '+50% damage to enemies below 25% HP',
overpower_80: {
id: 'overpower_80',
name: 'Overpower',
description: '+50% damage when mana above 80%',
category: 'special',
baseCapacityCost: 55,
maxStacks: 1,
allowedEquipmentCategories: CASTER_AND_HANDS,
effect: { type: 'special', specialId: 'executioner' }
effect: { type: 'special', specialId: 'overpower' }
},
};

View File

@@ -48,7 +48,7 @@ const COMBAT_TRAIN_TIER1_UPGRADES_L5: SkillUpgradeChoice[] = [
];
const COMBAT_TRAIN_TIER1_UPGRADES_L10: SkillUpgradeChoice[] = [
{ id: 'ct_t1_l10_execute', name: 'Executioner', desc: '+100% damage to enemies below 25% HP', milestone: 10, effect: { type: 'special', specialId: 'executioner', specialDesc: 'Execute bonus' } },
{ id: 'ct_t1_l10_overpower', name: 'Overpower', desc: '+50% damage when mana above 80%', milestone: 10, effect: { type: 'special', specialId: 'overpower', specialDesc: 'High mana damage bonus' } },
{ id: 'ct_t1_l10_berserker', name: 'Berserker', desc: '+50% damage when below 50% mana', milestone: 10, effect: { type: 'special', specialId: 'berserker', specialDesc: 'Low mana damage bonus' } },
{ id: 'ct_t1_l10_combo', name: 'Combo Master', desc: 'Every 5th attack deals 3x damage', milestone: 10, effect: { type: 'special', specialId: 'comboMaster', specialDesc: 'Combo finisher' } },
{ id: 'ct_t1_l10_adrenaline', name: 'Adrenaline Rush', desc: 'Defeating an enemy restores 5% mana', milestone: 10, effect: { type: 'special', specialId: 'adrenalineRush', specialDesc: 'Kill restore' } },

View File

@@ -98,6 +98,43 @@ export function getFloorElement(floor: number): string {
return FLOOR_ELEM_CYCLE[(floor - 1) % 8];
}
// Get all spells from equipped caster weapons (staves, wands, etc.)
// Returns array of { spellId, equipmentInstanceId }
function getActiveEquipmentSpells(
equippedInstances: Record<string, string | null>,
equipmentInstances: Record<string, EquipmentInstance>
): Array<{ spellId: string; equipmentId: string }> {
const spells: Array<{ spellId: string; equipmentId: string }> = [];
// Check main hand and off hand for caster equipment
const weaponSlots = ['mainHand', 'offHand'] as const;
for (const slot of weaponSlots) {
const instanceId = equippedInstances[slot];
if (!instanceId) continue;
const instance = equipmentInstances[instanceId];
if (!instance) continue;
// Check if this is a caster-type equipment
const equipType = EQUIPMENT_TYPES[instance.typeId];
if (!equipType || equipType.category !== 'caster') continue;
// Get spells from enchantments
for (const ench of instance.enchantments) {
const effectDef = ENCHANTMENT_EFFECTS[ench.effectId];
if (effectDef?.effect.type === 'spell' && effectDef.effect.spellId) {
spells.push({
spellId: effectDef.effect.spellId,
equipmentId: instanceId,
});
}
}
}
return spells;
}
// ─── Computed Stats Functions ─────────────────────────────────────────────────
// Helper to get effective skill level accounting for tiers
@@ -186,8 +223,8 @@ export function computeRegen(
* Compute regen with dynamic special effects (needs current mana, max mana, incursion)
*/
export function computeEffectiveRegen(
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers'>,
effects?: ComputedEffects
state: Pick<GameState, 'skills' | 'prestigeUpgrades' | 'rawMana' | 'incursionStrength' | 'skillUpgrades' | 'skillTiers' | 'equipmentInstances' | 'equippedInstances'>,
effects?: ComputedEffects | UnifiedEffects
): number {
// Base regen from existing function
let regen = computeRegen(state, effects);
@@ -369,7 +406,12 @@ function deductSpellCost(
function makeInitial(overrides: Partial<GameState> = {}): GameState {
const pu = overrides.prestigeUpgrades || {};
const startFloor = 1 + (pu.spireKey || 0) * 2;
const elemMax = computeElementMax({ skills: overrides.skills || {}, prestigeUpgrades: pu });
const elemMax = computeElementMax({
skills: overrides.skills || {},
prestigeUpgrades: pu,
skillUpgrades: overrides.skillUpgrades || {},
skillTiers: overrides.skillTiers || {}
});
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
Object.keys(ELEMENTS).forEach((k) => {
@@ -695,18 +737,47 @@ export const useGameStore = create<GameStore>()(
}
}
// Combat - uses cast speed and spell casting
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, castProgress } = state;
// Combat - MULTI-SPELL casting from all equipped weapons
let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, equipmentSpellStates } = state;
const floorElement = getFloorElement(currentFloor);
if (state.currentAction === 'climb') {
const spellId = state.activeSpell;
const spellDef = SPELLS_DEF[spellId];
// Get all spells from equipped caster weapons
const activeSpells = getActiveEquipmentSpells(state.equippedInstances, state.equipmentInstances);
if (spellDef) {
// Compute attack speed from quickCast skill and upgrades
const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05;
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
// Initialize spell states if needed
if (!equipmentSpellStates) {
equipmentSpellStates = [];
}
// Ensure we have state for all active spells
for (const { spellId, equipmentId } of activeSpells) {
if (!equipmentSpellStates.find(s => s.spellId === spellId && s.sourceEquipment === equipmentId)) {
equipmentSpellStates.push({
spellId,
sourceEquipment: equipmentId,
castProgress: 0,
});
}
}
// Remove states for spells that are no longer equipped
equipmentSpellStates = equipmentSpellStates.filter(es =>
activeSpells.some(as => as.spellId === es.spellId && as.equipmentId === es.sourceEquipment)
);
// Compute attack speed from quickCast skill and upgrades
const baseAttackSpeed = 1 + (state.skills.quickCast || 0) * 0.05;
const totalAttackSpeed = baseAttackSpeed * effects.attackSpeedMultiplier;
// Process each active spell
for (const { spellId, equipmentId } of activeSpells) {
const spellDef = SPELLS_DEF[spellId];
if (!spellDef) continue;
// Get or create spell state
let spellState = equipmentSpellStates.find(s => s.spellId === spellId && s.sourceEquipment === equipmentId);
if (!spellState) continue;
// Get spell cast speed (casts per hour, default 1)
const spellCastSpeed = spellDef.castSpeed || 1;
@@ -715,10 +786,10 @@ export const useGameStore = create<GameStore>()(
const progressPerTick = HOURS_PER_TICK * spellCastSpeed * totalAttackSpeed;
// Accumulate cast progress
castProgress = (castProgress || 0) + progressPerTick;
spellState = { ...spellState, castProgress: spellState.castProgress + progressPerTick };
// Process complete casts
while (castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
while (spellState.castProgress >= 1 && canAffordSpellCost(spellDef.cost, rawMana, elements)) {
// Deduct cost
const afterCost = deductSpellCost(spellDef.cost, rawMana, elements);
rawMana = afterCost.rawMana;
@@ -731,9 +802,9 @@ export const useGameStore = create<GameStore>()(
// Apply upgrade damage multipliers and bonuses
dmg = dmg * effects.baseDamageMultiplier + effects.baseDamageBonus;
// Executioner: +100% damage to enemies below 25% HP
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHP / floorMaxHP < 0.25) {
dmg *= 2;
// Overpower: +50% damage when mana above 80%
if (hasSpecial(effects, SPECIAL_EFFECTS.OVERPOWER) && rawMana >= maxMana * 0.8) {
dmg *= 1.5;
}
// Berserker: +50% damage when below 50% mana
@@ -745,7 +816,7 @@ export const useGameStore = create<GameStore>()(
const echoChance = (skills.spellEcho || 0) * 0.1;
if (Math.random() < echoChance) {
dmg *= 2;
log = [`✨ Spell Echo! Double damage!`, ...log.slice(0, 49)];
log = [`✨ Spell Echo! ${spellDef.name} deals double damage!`, ...log.slice(0, 49)];
}
// Lifesteal effect
@@ -759,7 +830,7 @@ export const useGameStore = create<GameStore>()(
floorHP = Math.max(0, floorHP - dmg);
// Reduce cast progress by 1 (one cast completed)
castProgress -= 1;
spellState = { ...spellState, castProgress: spellState.castProgress - 1 };
if (floorHP <= 0) {
// Floor cleared
@@ -781,13 +852,17 @@ export const useGameStore = create<GameStore>()(
floorHP = floorMaxHP;
maxFloorReached = Math.max(maxFloorReached, currentFloor);
// Reset cast progress on floor change
castProgress = 0;
// Reset ALL spell progress on floor change
equipmentSpellStates = equipmentSpellStates.map(s => ({ ...s, castProgress: 0 }));
spellState = { ...spellState, castProgress: 0 };
break; // Exit the while loop - new floor
}
}
} else {
// Not enough mana - pause casting (keep progress)
castProgress = castProgress || 0;
// Update the spell state in the array
equipmentSpellStates = equipmentSpellStates.map(s =>
(s.spellId === spellId && s.sourceEquipment === equipmentId) ? spellState : s
);
}
}
@@ -802,7 +877,7 @@ export const useGameStore = create<GameStore>()(
floorMaxHP,
maxFloorReached,
signedPacts,
castProgress,
equipmentSpellStates,
incursionStrength,
currentStudyTarget,
skills,
@@ -837,8 +912,8 @@ export const useGameStore = create<GameStore>()(
spells,
elements,
log,
castProgress,
});
equipmentSpellStates,
});
return;
}
@@ -861,7 +936,7 @@ export const useGameStore = create<GameStore>()(
elements,
unlockedEffects,
log,
castProgress,
equipmentSpellStates,
...craftingUpdates,
});
},

View File

@@ -2,6 +2,9 @@
export type ElementCategory = 'base' | 'utility' | 'composite' | 'exotic';
// Equipment slots for the equipment system
export type EquipmentSlot = 'mainHand' | 'offHand' | 'head' | 'body' | 'hands' | 'feet' | 'accessory1' | 'accessory2';
export interface ElementDef {
name: string;
sym: string;

View File

@@ -77,7 +77,7 @@ export const SPECIAL_EFFECTS = {
// Combat special effects
BATTLE_FURY: 'battleFury', // +10% damage per consecutive hit
ARMOR_PIERCE: 'armorPierce', // Ignore 10% floor defense
EXECUTIONER: 'executioner', // +100% damage to enemies below 25% HP
OVERPOWER: 'overpower', // +50% damage when mana above 80%
BERSERKER: 'berserker', // +50% damage when below 50% mana
COMBO_MASTER: 'comboMaster', // Every 5th attack deals 3x damage
ADRENALINE_RUSH: 'adrenalineRush', // Defeating enemy restores 5% mana
@@ -354,9 +354,9 @@ export function computeDynamicDamage(
damage *= (1 + furyBonus);
}
// Executioner: +100% damage to enemies below 25% HP
if (hasSpecial(effects, SPECIAL_EFFECTS.EXECUTIONER) && floorHPPct < 0.25) {
damage *= 2;
// Overpower: +50% damage when mana above 80%
if (hasSpecial(effects, SPECIAL_EFFECTS.OVERPOWER) && currentMana >= maxMana * 0.8) {
damage *= 1.5;
}
// Berserker: +50% damage when below 50% mana