fix(#165,#166,#167,#168,#169,#171,#172): resolve 7 open bug issues
#172 - Grimoire tab: removed dead 'loaded' state guard that permanently showed loading #169 - Transference Mana Flow: added elements param to checkDisciplinePrerequisites so mana type unlocks are verified #168 - Perk descriptions: wired 4 broken perks (enchant-2, channel-1, golem-2, efficiency-1) with actual bonus effects; fixed enchant-1 interval (5→50); fixed study-mana-enchantments stat (maxMana→maxManaBonus) #171 - Shields: removed all shield equipment (4 types), recipes, category, slot mappings; added 'shields' to AGENTS.md banned list #166 - regenMultiplier: merged disciplineEffects.multipliers.regenMultiplier into computeAllEffects() #165 - Meditation cap: added meditationCap display to ManaStatsSection UI; updated perk description #167 - XP accumulation: added Meditative Mastery base discipline with disciplineXpBonus stat; wired into tick pipeline
This commit is contained in:
@@ -153,7 +153,7 @@ ManaDrainPerTick = drainBase × (1 + (XP / difficultyFactor)^0.4)
|
|||||||
|
|
||||||
## Banned
|
## Banned
|
||||||
|
|
||||||
Lifesteal/healing, scroll crafting, ascension skills, LabTab, pause mechanics, familiar system, mana types: `life`, `blood`, `wood`, `mental`, `force`
|
Lifesteal/healing, scroll crafting, ascension skills, LabTab, pause mechanics, familiar system, shields, mana types: `life`, `blood`, `wood`, `mental`, `force`
|
||||||
|
|
||||||
## File Limit
|
## File Limit
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Circular Dependencies
|
# Circular Dependencies
|
||||||
Generated: 2026-05-27T17:14:06.661Z
|
Generated: 2026-05-27T19:08:46.353Z
|
||||||
|
|
||||||
No circular dependencies found. ✅
|
No circular dependencies found. ✅
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"generated": "2026-05-27T17:14:04.806Z",
|
"generated": "2026-05-27T19:08:44.608Z",
|
||||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||||
},
|
},
|
||||||
@@ -372,7 +372,8 @@
|
|||||||
"data/fabricator-recipe-types.ts"
|
"data/fabricator-recipe-types.ts"
|
||||||
],
|
],
|
||||||
"data/fabricator-recipe-types.ts": [
|
"data/fabricator-recipe-types.ts": [
|
||||||
"data/equipment/types.ts"
|
"data/equipment/types.ts",
|
||||||
|
"types/equipment.ts"
|
||||||
],
|
],
|
||||||
"data/fabricator-recipes.ts": [
|
"data/fabricator-recipes.ts": [
|
||||||
"data/fabricator-material-recipes.ts",
|
"data/fabricator-material-recipes.ts",
|
||||||
@@ -482,6 +483,15 @@
|
|||||||
"utils/room-utils.ts",
|
"utils/room-utils.ts",
|
||||||
"utils/safe-persist.ts"
|
"utils/safe-persist.ts"
|
||||||
],
|
],
|
||||||
|
"stores/crafting-equipment-tick.ts": [
|
||||||
|
"constants.ts",
|
||||||
|
"crafting-equipment.ts",
|
||||||
|
"data/crafting-recipes.ts",
|
||||||
|
"data/fabricator-recipes.ts",
|
||||||
|
"stores/combatStore.ts",
|
||||||
|
"stores/craftingStore.types.ts",
|
||||||
|
"types/equipment.ts"
|
||||||
|
],
|
||||||
"stores/crafting-initial-state.ts": [
|
"stores/crafting-initial-state.ts": [
|
||||||
"crafting-utils.ts",
|
"crafting-utils.ts",
|
||||||
"types.ts"
|
"types.ts"
|
||||||
@@ -496,6 +506,7 @@
|
|||||||
"crafting-fabricator.ts",
|
"crafting-fabricator.ts",
|
||||||
"crafting-utils.ts",
|
"crafting-utils.ts",
|
||||||
"stores/combatStore.ts",
|
"stores/combatStore.ts",
|
||||||
|
"stores/crafting-equipment-tick.ts",
|
||||||
"stores/crafting-initial-state.ts",
|
"stores/crafting-initial-state.ts",
|
||||||
"stores/craftingStore.types.ts",
|
"stores/craftingStore.types.ts",
|
||||||
"stores/manaStore.ts",
|
"stores/manaStore.ts",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type { SpellDef } from '@/lib/game/types';
|
|||||||
|
|
||||||
export function GrimoireTab() {
|
export function GrimoireTab() {
|
||||||
const [grimoireSpells, setGrimoireSpells] = useState<[string, SpellDef][]>([]);
|
const [grimoireSpells, setGrimoireSpells] = useState<[string, SpellDef][]>([]);
|
||||||
const [loaded, _setLoaded] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined' && SPELLS_DEF) {
|
if (typeof window !== 'undefined' && SPELLS_DEF) {
|
||||||
@@ -20,10 +19,6 @@ export function GrimoireTab() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!loaded) {
|
|
||||||
return <div className="p-4 text-center text-gray-400">Loading grimoire...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grimoireSpells.length === 0) {
|
if (grimoireSpells.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-center text-gray-400">
|
<div className="p-4 text-center text-gray-400">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
Package,
|
Package,
|
||||||
Sword,
|
Sword,
|
||||||
Shield,
|
|
||||||
Shirt,
|
Shirt,
|
||||||
Crown,
|
Crown,
|
||||||
Wrench
|
Wrench
|
||||||
@@ -11,7 +10,6 @@ import {
|
|||||||
|
|
||||||
export const CATEGORY_ICONS: Record<string, typeof Sword> = {
|
export const CATEGORY_ICONS: Record<string, typeof Sword> = {
|
||||||
caster: Sword,
|
caster: Sword,
|
||||||
shield: Shield,
|
|
||||||
catalyst: Sparkles,
|
catalyst: Sparkles,
|
||||||
head: Crown,
|
head: Crown,
|
||||||
body: Shirt,
|
body: Shirt,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const BRANCH_RECIPE_IDS = new Set([
|
|||||||
'oakStaff', 'arcanistStaff', 'battlestaff', 'arcanistCirclet', 'arcanistRobe',
|
'oakStaff', 'arcanistStaff', 'battlestaff', 'arcanistCirclet', 'arcanistRobe',
|
||||||
'voidCatalyst', 'arcanistPendant',
|
'voidCatalyst', 'arcanistPendant',
|
||||||
'crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm', 'battleRobe',
|
'crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm', 'battleRobe',
|
||||||
'battleBoots', 'combatGauntlets', 'runicShield', 'manaShield',
|
'battleBoots', 'combatGauntlets',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function isWizardBranch(recipe: FabricatorRecipe): boolean {
|
function isWizardBranch(recipe: FabricatorRecipe): boolean {
|
||||||
@@ -34,8 +34,7 @@ function isWizardBranch(recipe: FabricatorRecipe): boolean {
|
|||||||
|
|
||||||
function isPhysicalBranch(recipe: FabricatorRecipe): boolean {
|
function isPhysicalBranch(recipe: FabricatorRecipe): boolean {
|
||||||
return ['crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm',
|
return ['crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm',
|
||||||
'battleRobe', 'battleBoots', 'combatGauntlets', 'runicShield',
|
'battleRobe', 'battleBoots', 'combatGauntlets'].includes(recipe.id);
|
||||||
'manaShield'].includes(recipe.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RecipeCard({
|
function RecipeCard({
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export const DisciplinesTab: React.FC = () => {
|
|||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{activeTab?.items.map((disc) => {
|
{activeTab?.items.map((disc) => {
|
||||||
const discState = disciplines[disc.id] ?? { xp: 0, paused: true };
|
const discState = disciplines[disc.id] ?? { xp: 0, paused: true };
|
||||||
const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES);
|
const prereqCheck = checkDisciplinePrerequisites(disc, disciplines, ALL_DISCIPLINES, elements);
|
||||||
return (
|
return (
|
||||||
<DisciplineCard
|
<DisciplineCard
|
||||||
key={disc.id}
|
key={disc.id}
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ export function StatsTab() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<ManaStatsSection
|
<ManaStatsSection
|
||||||
stats={{ ...manaStats, rawMana: manaStats.maxMana }}
|
stats={{
|
||||||
|
...manaStats,
|
||||||
|
rawMana: manaStats.maxMana,
|
||||||
|
meditationCap: manaStats.meditationCap,
|
||||||
|
}}
|
||||||
elemMax={elemMax}
|
elemMax={elemMax}
|
||||||
/>
|
/>
|
||||||
<CombatStatsSection
|
<CombatStatsSection
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface ManaStatsData {
|
|||||||
effectiveRegen: number;
|
effectiveRegen: number;
|
||||||
clickMana: number;
|
clickMana: number;
|
||||||
meditationMultiplier: number;
|
meditationMultiplier: number;
|
||||||
|
meditationCap: number;
|
||||||
upgradeEffects: ComputedEffects;
|
upgradeEffects: ComputedEffects;
|
||||||
incursionStrength: number;
|
incursionStrength: number;
|
||||||
rawMana: number;
|
rawMana: number;
|
||||||
@@ -124,6 +125,10 @@ export function ManaStatsSection({ stats, elemMax }: ManaStatsSectionProps) {
|
|||||||
{fmtDec(meditationMultiplier, 2)}x
|
{fmtDec(meditationMultiplier, 2)}x
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span style={{ color: 'var(--text-muted)' }}>Meditation Cap:</span>
|
||||||
|
<span style={{ color: 'var(--text-secondary)' }}>{fmtDec(stats.meditationCap, 1)}x</span>
|
||||||
|
</div>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span style={{ color: 'var(--text-muted)' }}>Incursion Strength:</span>
|
<span style={{ color: 'var(--text-muted)' }}>Incursion Strength:</span>
|
||||||
<span style={{ color: 'var(--color-danger)' }}>{Math.round(incursionStrength * 100)}%</span>
|
<span style={{ color: 'var(--color-danger)' }}>{Math.round(incursionStrength * 100)}%</span>
|
||||||
|
|||||||
@@ -136,4 +136,54 @@ describe('checkDisciplinePrerequisites', () => {
|
|||||||
expect(result.canProceed).toBe(true);
|
expect(result.canProceed).toBe(true);
|
||||||
expect(result.missingPrereqs).toEqual([]);
|
expect(result.missingPrereqs).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return canProceed true when mana type requirement is unlocked', () => {
|
||||||
|
const disciplines: Record<string, DisciplineState> = {};
|
||||||
|
const prereqDisc: DisciplineDefinition = {
|
||||||
|
...rawMastery,
|
||||||
|
id: 'regen-transference',
|
||||||
|
requires: ['transference'],
|
||||||
|
};
|
||||||
|
const elements = { transference: { unlocked: true } };
|
||||||
|
const result = checkDisciplinePrerequisites(prereqDisc, disciplines, [rawMastery], elements);
|
||||||
|
expect(result.canProceed).toBe(true);
|
||||||
|
expect(result.missingPrereqs).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return canProceed false when mana type requirement is not unlocked', () => {
|
||||||
|
const disciplines: Record<string, DisciplineState> = {};
|
||||||
|
const prereqDisc: DisciplineDefinition = {
|
||||||
|
...rawMastery,
|
||||||
|
id: 'regen-transference',
|
||||||
|
requires: ['transference'],
|
||||||
|
};
|
||||||
|
const elements = { transference: { unlocked: false } };
|
||||||
|
const result = checkDisciplinePrerequisites(prereqDisc, disciplines, [rawMastery], elements);
|
||||||
|
expect(result.canProceed).toBe(false);
|
||||||
|
expect(result.missingPrereqs).toContain('transference mana');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return canProceed false when mana type is missing from elements', () => {
|
||||||
|
const disciplines: Record<string, DisciplineState> = {};
|
||||||
|
const prereqDisc: DisciplineDefinition = {
|
||||||
|
...rawMastery,
|
||||||
|
id: 'regen-fire',
|
||||||
|
requires: ['fire'],
|
||||||
|
};
|
||||||
|
const result = checkDisciplinePrerequisites(prereqDisc, disciplines, [rawMastery], {});
|
||||||
|
expect(result.canProceed).toBe(false);
|
||||||
|
expect(result.missingPrereqs).toContain('fire mana');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not require elements param for backwards compatibility', () => {
|
||||||
|
const disciplines: Record<string, DisciplineState> = {};
|
||||||
|
const prereqDisc: DisciplineDefinition = {
|
||||||
|
...rawMastery,
|
||||||
|
id: 'unknown-req',
|
||||||
|
requires: ['unknown-thing'],
|
||||||
|
};
|
||||||
|
const result = checkDisciplinePrerequisites(prereqDisc, disciplines, [rawMastery]);
|
||||||
|
expect(result.canProceed).toBe(false);
|
||||||
|
expect(result.missingPrereqs).toContain('unknown-thing');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -120,22 +120,6 @@ export const CRAFTING_RECIPES: Record<string, CraftingRecipe> = {
|
|||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
shieldBlueprint: {
|
|
||||||
id: 'shieldBlueprint',
|
|
||||||
equipmentTypeId: 'runicShield',
|
|
||||||
name: 'Runic Shield',
|
|
||||||
description: 'A shield engraved with protective runes.',
|
|
||||||
rarity: 'rare',
|
|
||||||
materials: {
|
|
||||||
manaCrystalDust: 10,
|
|
||||||
arcaneShard: 6,
|
|
||||||
elementalCore: 2,
|
|
||||||
},
|
|
||||||
manaCost: 450,
|
|
||||||
craftTime: 5,
|
|
||||||
minFloor: 28,
|
|
||||||
unlocked: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
hatBlueprint: {
|
hatBlueprint: {
|
||||||
id: 'hatBlueprint',
|
id: 'hatBlueprint',
|
||||||
|
|||||||
@@ -61,10 +61,41 @@ export const baseDisciplines: DisciplineDefinition[] = [
|
|||||||
type: 'capped',
|
type: 'capped',
|
||||||
threshold: 100,
|
threshold: 100,
|
||||||
value: 100,
|
value: 100,
|
||||||
description: 'Every 100 XP: +0.5 max meditation multiplier (7 tiers, up to +3.5)',
|
description: 'Every 100 XP: +0.5 max meditation cap (7 tiers, up to +3.5). Raises Void Meditation ceiling.',
|
||||||
bonus: { stat: 'meditationCapBonus', amount: 0.5 },
|
bonus: { stat: 'meditationCapBonus', amount: 0.5 },
|
||||||
maxTier: 7,
|
maxTier: 7,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'meditative-mastery',
|
||||||
|
name: 'Meditative Mastery',
|
||||||
|
attunement: DisciplinesAttunementType.BASE,
|
||||||
|
manaType: 'raw',
|
||||||
|
baseCost: 5,
|
||||||
|
description:
|
||||||
|
'Deepen your meditation practice to accelerate discipline XP gain. The more you master yourself, the faster all disciplines grow.',
|
||||||
|
statBonus: { stat: 'disciplineXpBonus', baseValue: 0.5, label: 'Discipline XP Bonus/tick' },
|
||||||
|
difficultyFactor: 120,
|
||||||
|
scalingFactor: 60,
|
||||||
|
drainBase: 1,
|
||||||
|
perks: [
|
||||||
|
{
|
||||||
|
id: 'meditative-mastery-1',
|
||||||
|
type: 'once',
|
||||||
|
threshold: 100,
|
||||||
|
value: 0,
|
||||||
|
description: '+0.5 Discipline XP per tick',
|
||||||
|
bonus: { stat: 'disciplineXpBonus', amount: 0.5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'meditative-mastery-2',
|
||||||
|
type: 'infinite',
|
||||||
|
threshold: 200,
|
||||||
|
value: 100,
|
||||||
|
description: 'Every 100 XP: +0.25 Discipline XP per tick',
|
||||||
|
bonus: { stat: 'disciplineXpBonus', amount: 0.25 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
@@ -50,7 +50,7 @@ export const enchanterUtilityDisciplines: DisciplineDefinition[] = [
|
|||||||
manaType: 'transference',
|
manaType: 'transference',
|
||||||
baseCost: 15,
|
baseCost: 15,
|
||||||
description: 'Increase your max mana, unlocking mana enchantments for equipment.',
|
description: 'Increase your max mana, unlocking mana enchantments for equipment.',
|
||||||
statBonus: { stat: 'maxMana', baseValue: 10, label: 'Max Mana' },
|
statBonus: { stat: 'maxManaBonus', baseValue: 10, label: 'Max Mana' },
|
||||||
difficultyFactor: 150,
|
difficultyFactor: 150,
|
||||||
scalingFactor: 100,
|
scalingFactor: 100,
|
||||||
drainBase: 3,
|
drainBase: 3,
|
||||||
|
|||||||
@@ -21,15 +21,18 @@ export const enchanterDisciplines: DisciplineDefinition[] = [
|
|||||||
id: 'enchant-1',
|
id: 'enchant-1',
|
||||||
type: 'infinite',
|
type: 'infinite',
|
||||||
threshold: 150,
|
threshold: 150,
|
||||||
value: 5,
|
value: 50,
|
||||||
description: '+5 Enchantment Power (stacks with skill tiers)',
|
description: '+5 Enchantment Power per tier',
|
||||||
|
bonus: { stat: 'enchantPower', amount: 5 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'enchant-2',
|
id: 'enchant-2',
|
||||||
type: 'capped',
|
type: 'capped',
|
||||||
threshold: 300,
|
threshold: 300,
|
||||||
value: 20,
|
value: 200,
|
||||||
description: 'Double enchantment duration at 300 XP',
|
maxTier: 3,
|
||||||
|
description: '+10 Enchantment Power per tier (max 3)',
|
||||||
|
bonus: { stat: 'enchantPower', amount: 10 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -50,7 +53,8 @@ export const enchanterDisciplines: DisciplineDefinition[] = [
|
|||||||
type: 'once',
|
type: 'once',
|
||||||
threshold: 250,
|
threshold: 250,
|
||||||
value: 0,
|
value: 0,
|
||||||
description: 'Unlock lightning mana boosting',
|
description: '+15 Lightning Mana Capacity',
|
||||||
|
bonus: { stat: 'elementCap_lightning', amount: 15 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ export const fabricatorDisciplines: DisciplineDefinition[] = [
|
|||||||
id: 'golem-2',
|
id: 'golem-2',
|
||||||
type: 'capped',
|
type: 'capped',
|
||||||
threshold: 500,
|
threshold: 500,
|
||||||
value: 5,
|
value: 250,
|
||||||
description: 'Double golem capacity at 500 XP',
|
maxTier: 2,
|
||||||
|
description: '+1 Golem Capacity per tier (max 2)',
|
||||||
|
bonus: { stat: 'golemCapacity', amount: 1 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -50,7 +52,8 @@ export const fabricatorDisciplines: DisciplineDefinition[] = [
|
|||||||
type: 'once',
|
type: 'once',
|
||||||
threshold: 300,
|
threshold: 300,
|
||||||
value: 0,
|
value: 0,
|
||||||
description: 'Unlock reduced crafting costs',
|
description: '+10% Crafting Cost Reduction',
|
||||||
|
bonus: { stat: 'craftingCostReduction', amount: 10 },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { CATALYST_EQUIPMENT, SPELL_FOCUS_EQUIPMENT } from './catalysts';
|
|||||||
import { FEET_EQUIPMENT } from './feet';
|
import { FEET_EQUIPMENT } from './feet';
|
||||||
import { HANDS_EQUIPMENT } from './hands';
|
import { HANDS_EQUIPMENT } from './hands';
|
||||||
import { HEAD_EQUIPMENT } from './head';
|
import { HEAD_EQUIPMENT } from './head';
|
||||||
import { SHIELD_EQUIPMENT } from './shields';
|
|
||||||
import { SWORD_EQUIPMENT } from './swords';
|
import { SWORD_EQUIPMENT } from './swords';
|
||||||
|
|
||||||
export const EQUIPMENT_TYPES = {
|
export const EQUIPMENT_TYPES = {
|
||||||
@@ -22,6 +21,5 @@ export const EQUIPMENT_TYPES = {
|
|||||||
...FEET_EQUIPMENT,
|
...FEET_EQUIPMENT,
|
||||||
...HANDS_EQUIPMENT,
|
...HANDS_EQUIPMENT,
|
||||||
...HEAD_EQUIPMENT,
|
...HEAD_EQUIPMENT,
|
||||||
...SHIELD_EQUIPMENT,
|
|
||||||
...SWORD_EQUIPMENT,
|
...SWORD_EQUIPMENT,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,5 +35,4 @@ export { CATALYST_EQUIPMENT, SPELL_FOCUS_EQUIPMENT } from './catalysts';
|
|||||||
export { FEET_EQUIPMENT } from './feet';
|
export { FEET_EQUIPMENT } from './feet';
|
||||||
export { HANDS_EQUIPMENT } from './hands';
|
export { HANDS_EQUIPMENT } from './hands';
|
||||||
export { HEAD_EQUIPMENT } from './head';
|
export { HEAD_EQUIPMENT } from './head';
|
||||||
export { SHIELD_EQUIPMENT } from './shields';
|
|
||||||
export { SWORD_EQUIPMENT } from './swords';
|
export { SWORD_EQUIPMENT } from './swords';
|
||||||
|
|||||||
@@ -1,39 +1,7 @@
|
|||||||
// ─── Shield Equipment Types ───────────────────────────────────────────
|
// ─── Shield Equipment Types ───────────────────────────────────────────
|
||||||
|
// Shields have been removed from the game. The offHand slot remains for
|
||||||
|
// non-shield items (e.g. catalysts, spell focuses).
|
||||||
|
|
||||||
import type { EquipmentType } from './types';
|
import type { EquipmentType } from './types';
|
||||||
|
|
||||||
export const SHIELD_EQUIPMENT: Record<string, EquipmentType> = {
|
export const SHIELD_EQUIPMENT: Record<string, EquipmentType> = {};
|
||||||
// ─── Off Hand - Shields ───────────────────────────────────────────
|
|
||||||
basicShield: {
|
|
||||||
id: 'basicShield',
|
|
||||||
name: 'Basic Shield',
|
|
||||||
category: 'shield',
|
|
||||||
slot: 'offHand',
|
|
||||||
baseCapacity: 40,
|
|
||||||
description: 'A simple wooden shield. Provides basic protection.',
|
|
||||||
},
|
|
||||||
reinforcedShield: {
|
|
||||||
id: 'reinforcedShield',
|
|
||||||
name: 'Reinforced Shield',
|
|
||||||
category: 'shield',
|
|
||||||
slot: 'offHand',
|
|
||||||
baseCapacity: 55,
|
|
||||||
description: 'A metal-reinforced shield with enhanced durability and capacity.',
|
|
||||||
},
|
|
||||||
runicShield: {
|
|
||||||
id: 'runicShield',
|
|
||||||
name: 'Runic Shield',
|
|
||||||
category: 'shield',
|
|
||||||
slot: 'offHand',
|
|
||||||
baseCapacity: 70,
|
|
||||||
description: 'A shield engraved with protective runes. Excellent for defensive enchantments.',
|
|
||||||
},
|
|
||||||
manaShield: {
|
|
||||||
id: 'manaShield',
|
|
||||||
name: 'Mana Shield',
|
|
||||||
category: 'shield',
|
|
||||||
slot: 'offHand',
|
|
||||||
baseCapacity: 60,
|
|
||||||
description: 'A crystalline shield that can store and reflect mana.',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { EquipmentSlot } from '../../types/equipmentSlot';
|
import type { EquipmentSlot } from '../../types/equipmentSlot';
|
||||||
export type { EquipmentSlot };
|
export type { EquipmentSlot };
|
||||||
export type EquipmentCategory = 'caster' | 'shield' | 'catalyst' | 'sword' | 'head' | 'body' | 'hands' | 'feet' | 'accessory';
|
export type EquipmentCategory = 'caster' | 'catalyst' | 'sword' | 'head' | 'body' | 'hands' | 'feet' | 'accessory';
|
||||||
|
|
||||||
// All equipment slots in order
|
// All equipment slots in order
|
||||||
export const EQUIPMENT_SLOTS: EquipmentSlot[] = ['mainHand', 'offHand', 'head', 'body', 'hands', 'feet', 'accessory1', 'accessory2'];
|
export const EQUIPMENT_SLOTS: EquipmentSlot[] = ['mainHand', 'offHand', 'head', 'body', 'hands', 'feet', 'accessory1', 'accessory2'];
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ export function getValidSlotsForCategory(category: EquipmentCategory): Equipment
|
|||||||
case 'catalyst':
|
case 'catalyst':
|
||||||
case 'sword':
|
case 'sword':
|
||||||
return ['mainHand'];
|
return ['mainHand'];
|
||||||
case 'shield':
|
|
||||||
return ['offHand'];
|
|
||||||
case 'head':
|
case 'head':
|
||||||
return ['head'];
|
return ['head'];
|
||||||
case 'body':
|
case 'body':
|
||||||
|
|||||||
@@ -123,38 +123,4 @@ export const PHYSICAL_BRANCH_RECIPES: FabricatorRecipe[] = [
|
|||||||
{ effectId: 'attack_speed_10', stacks: 1, actualCost: 22 },
|
{ effectId: 'attack_speed_10', stacks: 1, actualCost: 22 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'runicShield',
|
|
||||||
name: 'Runic Shield',
|
|
||||||
description: 'A shield engraved with protective runes. Excellent for defensive enchantments.',
|
|
||||||
manaType: 'earth',
|
|
||||||
equipmentTypeId: 'runicShield',
|
|
||||||
slot: 'offHand',
|
|
||||||
materials: { manaCrystalDust: 8, earthShard: 4, elementalCore: 2 },
|
|
||||||
manaCost: 450,
|
|
||||||
craftTime: 5,
|
|
||||||
rarity: 'rare',
|
|
||||||
gearTrait: '+20% Defensive Enchantment Power, +25 Earth Mana Capacity',
|
|
||||||
bonusEnchantments: [
|
|
||||||
{ effectId: 'earth_cap_10', stacks: 1, actualCost: 30 },
|
|
||||||
{ effectId: 'mana_cap_50', stacks: 1, actualCost: 20 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'manaShield',
|
|
||||||
name: 'Mana Shield',
|
|
||||||
description: 'A crystalline shield that can store and reflect mana. Deadly in the right hands.',
|
|
||||||
manaType: 'crystal',
|
|
||||||
equipmentTypeId: 'manaShield',
|
|
||||||
slot: 'offHand',
|
|
||||||
materials: { manaCrystalDust: 10, crystalShard: 5, elementalCore: 2 },
|
|
||||||
manaCost: 550,
|
|
||||||
craftTime: 6,
|
|
||||||
rarity: 'epic',
|
|
||||||
gearTrait: '+15% Spell Damage, +15% Defensive Enchantment Power',
|
|
||||||
bonusEnchantments: [
|
|
||||||
{ effectId: 'mana_cap_50', stacks: 1, actualCost: 20 },
|
|
||||||
{ effectId: 'damage_5', stacks: 1, actualCost: 15 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export function computeAllEffects(
|
|||||||
perElementCapBonus,
|
perElementCapBonus,
|
||||||
perElementRegenBonus,
|
perElementRegenBonus,
|
||||||
maxManaMultiplier: upgradeEffects.maxManaMultiplier * (equipmentEffects.multipliers.maxMana || 1),
|
maxManaMultiplier: upgradeEffects.maxManaMultiplier * (equipmentEffects.multipliers.maxMana || 1),
|
||||||
regenMultiplier: upgradeEffects.regenMultiplier * (equipmentEffects.multipliers.regen || 1),
|
regenMultiplier: upgradeEffects.regenMultiplier * (equipmentEffects.multipliers.regen || 1) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0)),
|
||||||
clickManaMultiplier: upgradeEffects.clickManaMultiplier * (equipmentEffects.multipliers.clickMana || 1),
|
clickManaMultiplier: upgradeEffects.clickManaMultiplier * (equipmentEffects.multipliers.clickMana || 1),
|
||||||
baseDamageMultiplier: upgradeEffects.baseDamageMultiplier * (equipmentEffects.multipliers.baseDamage || 1),
|
baseDamageMultiplier: upgradeEffects.baseDamageMultiplier * (equipmentEffects.multipliers.baseDamage || 1),
|
||||||
attackSpeedMultiplier: upgradeEffects.attackSpeedMultiplier * (equipmentEffects.multipliers.attackSpeed || 1),
|
attackSpeedMultiplier: upgradeEffects.attackSpeedMultiplier * (equipmentEffects.multipliers.attackSpeed || 1),
|
||||||
|
|||||||
@@ -22,9 +22,14 @@ const KNOWN_BONUS_STATS = new Set([
|
|||||||
'clickManaBonus',
|
'clickManaBonus',
|
||||||
'baseDamageBonus',
|
'baseDamageBonus',
|
||||||
'elementCapBonus',
|
'elementCapBonus',
|
||||||
|
'elementCap_lightning',
|
||||||
'meditationCapBonus',
|
'meditationCapBonus',
|
||||||
'pactAffinityBonus',
|
'pactAffinityBonus',
|
||||||
'guardianBoonMultiplier',
|
'guardianBoonMultiplier',
|
||||||
|
'enchantPower',
|
||||||
|
'golemCapacity',
|
||||||
|
'craftingCostReduction',
|
||||||
|
'disciplineXpBonus',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export interface DisciplineEffectsResult {
|
export interface DisciplineEffectsResult {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export function useManaStats() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const disciplineEffects = computeDisciplineEffects();
|
const disciplineEffects = computeDisciplineEffects();
|
||||||
|
const meditationCap = 5.0 + disciplineEffects.meditationCapBonus;
|
||||||
const meditationMultiplier = useMemo(
|
const meditationMultiplier = useMemo(
|
||||||
() => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus),
|
() => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus),
|
||||||
[meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus]
|
[meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus]
|
||||||
@@ -86,6 +87,7 @@ export function useManaStats() {
|
|||||||
baseRegen,
|
baseRegen,
|
||||||
clickMana,
|
clickMana,
|
||||||
meditationMultiplier,
|
meditationMultiplier,
|
||||||
|
meditationCap,
|
||||||
incursionStrength,
|
incursionStrength,
|
||||||
effectiveRegenWithSpecials,
|
effectiveRegenWithSpecials,
|
||||||
manaCascadeBonus,
|
manaCascadeBonus,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
checkDisciplinePrerequisites,
|
checkDisciplinePrerequisites,
|
||||||
getUnlockedPerks
|
getUnlockedPerks
|
||||||
} from '../utils/discipline-math';
|
} from '../utils/discipline-math';
|
||||||
|
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
||||||
import { baseDisciplines } from '../data/disciplines/base';
|
import { baseDisciplines } from '../data/disciplines/base';
|
||||||
import { elementalAttunementDisciplines } from '../data/disciplines/elemental';
|
import { elementalAttunementDisciplines } from '../data/disciplines/elemental';
|
||||||
import { elementalRegenDisciplines } from '../data/disciplines/elemental-regen';
|
import { elementalRegenDisciplines } from '../data/disciplines/elemental-regen';
|
||||||
@@ -99,8 +100,8 @@ export const useDisciplineStore = create<DisciplineStore>()(
|
|||||||
if (signedPacts.length === 0) return s;
|
if (signedPacts.length === 0) return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check discipline prerequisites (requires field → discipline XP)
|
// Check discipline prerequisites (requires field → discipline XP or mana type unlock)
|
||||||
const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES);
|
const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES, gameState?.elements);
|
||||||
if (!prereqCheck.canProceed) return s;
|
if (!prereqCheck.canProceed) return s;
|
||||||
|
|
||||||
// For conversion disciplines: gate on having all source mana types unlocked
|
// For conversion disciplines: gate on having all source mana types unlocked
|
||||||
@@ -183,8 +184,10 @@ export const useDisciplineStore = create<DisciplineStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oldXP = disc.xp;
|
const oldXP = disc.xp;
|
||||||
newDisciplines[id] = { ...disc, xp: disc.xp + 1 };
|
const xpBonus = computeDisciplineEffects().bonuses.disciplineXpBonus || 0;
|
||||||
newXP += 1;
|
const xpGain = 1 + xpBonus;
|
||||||
|
newDisciplines[id] = { ...disc, xp: disc.xp + xpGain };
|
||||||
|
newXP += xpGain;
|
||||||
|
|
||||||
// Check for newly unlocked perks that unlock effects
|
// Check for newly unlocked perks that unlock effects
|
||||||
if (def.perks.length > 0) {
|
if (def.perks.length > 0) {
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export function checkDisciplinePrerequisites(
|
|||||||
discipline: DisciplineDefinition,
|
discipline: DisciplineDefinition,
|
||||||
allDisciplines: Record<string, DisciplineState>,
|
allDisciplines: Record<string, DisciplineState>,
|
||||||
allDefinitions: DisciplineDefinition[],
|
allDefinitions: DisciplineDefinition[],
|
||||||
|
elements?: Record<string, { unlocked: boolean }>,
|
||||||
): { canProceed: boolean; missingPrereqs: string[] } {
|
): { canProceed: boolean; missingPrereqs: string[] } {
|
||||||
if (!discipline.requires || discipline.requires.length === 0) {
|
if (!discipline.requires || discipline.requires.length === 0) {
|
||||||
return { canProceed: true, missingPrereqs: [] };
|
return { canProceed: true, missingPrereqs: [] };
|
||||||
@@ -138,10 +139,13 @@ export function checkDisciplinePrerequisites(
|
|||||||
missingPrereqs.push(reqDef?.name ?? reqId);
|
missingPrereqs.push(reqDef?.name ?? reqId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Treat as mana type requirement — display as "<name> mana"
|
// Treat as mana type requirement — verify against player's unlocked elements
|
||||||
const typeName = MANA_TYPE_NAMES[reqId];
|
const typeName = MANA_TYPE_NAMES[reqId];
|
||||||
if (typeName) {
|
if (typeName) {
|
||||||
|
const elementState = elements?.[reqId];
|
||||||
|
if (!elementState || !elementState.unlocked) {
|
||||||
missingPrereqs.push(`${typeName} mana`);
|
missingPrereqs.push(`${typeName} mana`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
missingPrereqs.push(reqId);
|
missingPrereqs.push(reqId);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user