diff --git a/src/components/game/LootInventory/icons.ts b/src/components/game/LootInventory/icons.ts
index 9ed7235..a4ee0c2 100644
--- a/src/components/game/LootInventory/icons.ts
+++ b/src/components/game/LootInventory/icons.ts
@@ -3,7 +3,6 @@ import {
Sparkles,
Package,
Sword,
- Shield,
Shirt,
Crown,
Wrench
@@ -11,7 +10,6 @@ import {
export const CATEGORY_ICONS: Record
= {
caster: Sword,
- shield: Shield,
catalyst: Sparkles,
head: Crown,
body: Shirt,
diff --git a/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx b/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx
index 757cf04..b728fd6 100644
--- a/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx
+++ b/src/components/game/tabs/CraftingTab/FabricatorSubTab.tsx
@@ -24,7 +24,7 @@ const BRANCH_RECIPE_IDS = new Set([
'oakStaff', 'arcanistStaff', 'battlestaff', 'arcanistCirclet', 'arcanistRobe',
'voidCatalyst', 'arcanistPendant',
'crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm', 'battleRobe',
- 'battleBoots', 'combatGauntlets', 'runicShield', 'manaShield',
+ 'battleBoots', 'combatGauntlets',
]);
function isWizardBranch(recipe: FabricatorRecipe): boolean {
@@ -34,8 +34,7 @@ function isWizardBranch(recipe: FabricatorRecipe): boolean {
function isPhysicalBranch(recipe: FabricatorRecipe): boolean {
return ['crystalBlade', 'arcanistBlade', 'voidBlade', 'battleHelm',
- 'battleRobe', 'battleBoots', 'combatGauntlets', 'runicShield',
- 'manaShield'].includes(recipe.id);
+ 'battleRobe', 'battleBoots', 'combatGauntlets'].includes(recipe.id);
}
function RecipeCard({
diff --git a/src/components/game/tabs/DisciplinesTab.tsx b/src/components/game/tabs/DisciplinesTab.tsx
index fd69238..d43c1bf 100644
--- a/src/components/game/tabs/DisciplinesTab.tsx
+++ b/src/components/game/tabs/DisciplinesTab.tsx
@@ -257,7 +257,7 @@ export const DisciplinesTab: React.FC = () => {
{activeTab?.items.map((disc) => {
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 (
+
+ Meditation Cap:
+ {fmtDec(stats.meditationCap, 1)}x
+
Incursion Strength:
{Math.round(incursionStrength * 100)}%
diff --git a/src/lib/game/__tests__/discipline-prerequisites.test.ts b/src/lib/game/__tests__/discipline-prerequisites.test.ts
index 5f1fbcc..86599f1 100644
--- a/src/lib/game/__tests__/discipline-prerequisites.test.ts
+++ b/src/lib/game/__tests__/discipline-prerequisites.test.ts
@@ -136,4 +136,54 @@ describe('checkDisciplinePrerequisites', () => {
expect(result.canProceed).toBe(true);
expect(result.missingPrereqs).toEqual([]);
});
+
+ it('should return canProceed true when mana type requirement is unlocked', () => {
+ const disciplines: Record = {};
+ 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 = {};
+ 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 = {};
+ 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 = {};
+ 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');
+ });
});
diff --git a/src/lib/game/data/crafting-recipes.ts b/src/lib/game/data/crafting-recipes.ts
index 4bcd312..3c18631 100644
--- a/src/lib/game/data/crafting-recipes.ts
+++ b/src/lib/game/data/crafting-recipes.ts
@@ -120,23 +120,7 @@ export const CRAFTING_RECIPES: Record = {
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: {
id: 'hatBlueprint',
equipmentTypeId: 'wizardHat',
diff --git a/src/lib/game/data/disciplines/base.ts b/src/lib/game/data/disciplines/base.ts
index 653059d..d7be132 100644
--- a/src/lib/game/data/disciplines/base.ts
+++ b/src/lib/game/data/disciplines/base.ts
@@ -61,10 +61,41 @@ export const baseDisciplines: DisciplineDefinition[] = [
type: 'capped',
threshold: 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 },
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 },
+ },
+ ],
+ },
];
\ No newline at end of file
diff --git a/src/lib/game/data/disciplines/enchanter-utility.ts b/src/lib/game/data/disciplines/enchanter-utility.ts
index 35b5852..b367d56 100644
--- a/src/lib/game/data/disciplines/enchanter-utility.ts
+++ b/src/lib/game/data/disciplines/enchanter-utility.ts
@@ -50,7 +50,7 @@ export const enchanterUtilityDisciplines: DisciplineDefinition[] = [
manaType: 'transference',
baseCost: 15,
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,
scalingFactor: 100,
drainBase: 3,
diff --git a/src/lib/game/data/disciplines/enchanter.ts b/src/lib/game/data/disciplines/enchanter.ts
index f150436..70d377c 100644
--- a/src/lib/game/data/disciplines/enchanter.ts
+++ b/src/lib/game/data/disciplines/enchanter.ts
@@ -21,15 +21,18 @@ export const enchanterDisciplines: DisciplineDefinition[] = [
id: 'enchant-1',
type: 'infinite',
threshold: 150,
- value: 5,
- description: '+5 Enchantment Power (stacks with skill tiers)',
+ value: 50,
+ description: '+5 Enchantment Power per tier',
+ bonus: { stat: 'enchantPower', amount: 5 },
},
{
id: 'enchant-2',
type: 'capped',
threshold: 300,
- value: 20,
- description: 'Double enchantment duration at 300 XP',
+ value: 200,
+ 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',
threshold: 250,
value: 0,
- description: 'Unlock lightning mana boosting',
+ description: '+15 Lightning Mana Capacity',
+ bonus: { stat: 'elementCap_lightning', amount: 15 },
},
],
},
diff --git a/src/lib/game/data/disciplines/fabricator.ts b/src/lib/game/data/disciplines/fabricator.ts
index dd3fd60..c819007 100644
--- a/src/lib/game/data/disciplines/fabricator.ts
+++ b/src/lib/game/data/disciplines/fabricator.ts
@@ -28,8 +28,10 @@ export const fabricatorDisciplines: DisciplineDefinition[] = [
id: 'golem-2',
type: 'capped',
threshold: 500,
- value: 5,
- description: 'Double golem capacity at 500 XP',
+ value: 250,
+ 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',
threshold: 300,
value: 0,
- description: 'Unlock reduced crafting costs',
+ description: '+10% Crafting Cost Reduction',
+ bonus: { stat: 'craftingCostReduction', amount: 10 },
},
],
},
diff --git a/src/lib/game/data/equipment/equipment-types-data.ts b/src/lib/game/data/equipment/equipment-types-data.ts
index fce06d9..0ce00bb 100644
--- a/src/lib/game/data/equipment/equipment-types-data.ts
+++ b/src/lib/game/data/equipment/equipment-types-data.ts
@@ -10,7 +10,6 @@ import { CATALYST_EQUIPMENT, SPELL_FOCUS_EQUIPMENT } from './catalysts';
import { FEET_EQUIPMENT } from './feet';
import { HANDS_EQUIPMENT } from './hands';
import { HEAD_EQUIPMENT } from './head';
-import { SHIELD_EQUIPMENT } from './shields';
import { SWORD_EQUIPMENT } from './swords';
export const EQUIPMENT_TYPES = {
@@ -22,6 +21,5 @@ export const EQUIPMENT_TYPES = {
...FEET_EQUIPMENT,
...HANDS_EQUIPMENT,
...HEAD_EQUIPMENT,
- ...SHIELD_EQUIPMENT,
...SWORD_EQUIPMENT,
};
diff --git a/src/lib/game/data/equipment/index.ts b/src/lib/game/data/equipment/index.ts
index 16239ab..4f6a3f5 100644
--- a/src/lib/game/data/equipment/index.ts
+++ b/src/lib/game/data/equipment/index.ts
@@ -35,5 +35,4 @@ export { CATALYST_EQUIPMENT, SPELL_FOCUS_EQUIPMENT } from './catalysts';
export { FEET_EQUIPMENT } from './feet';
export { HANDS_EQUIPMENT } from './hands';
export { HEAD_EQUIPMENT } from './head';
-export { SHIELD_EQUIPMENT } from './shields';
export { SWORD_EQUIPMENT } from './swords';
diff --git a/src/lib/game/data/equipment/shields.ts b/src/lib/game/data/equipment/shields.ts
index 06bc1f0..1dacdc2 100644
--- a/src/lib/game/data/equipment/shields.ts
+++ b/src/lib/game/data/equipment/shields.ts
@@ -1,39 +1,7 @@
// ─── 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';
-export const SHIELD_EQUIPMENT: Record = {
- // ─── 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.',
- },
-};
+export const SHIELD_EQUIPMENT: Record = {};
diff --git a/src/lib/game/data/equipment/types.ts b/src/lib/game/data/equipment/types.ts
index ae20d69..2793ab2 100644
--- a/src/lib/game/data/equipment/types.ts
+++ b/src/lib/game/data/equipment/types.ts
@@ -2,7 +2,7 @@
import type { EquipmentSlot } from '../../types/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
export const EQUIPMENT_SLOTS: EquipmentSlot[] = ['mainHand', 'offHand', 'head', 'body', 'hands', 'feet', 'accessory1', 'accessory2'];
diff --git a/src/lib/game/data/equipment/utils.ts b/src/lib/game/data/equipment/utils.ts
index 433dac3..3c07b90 100644
--- a/src/lib/game/data/equipment/utils.ts
+++ b/src/lib/game/data/equipment/utils.ts
@@ -27,8 +27,7 @@ export function getValidSlotsForCategory(category: EquipmentCategory): Equipment
case 'catalyst':
case 'sword':
return ['mainHand'];
- case 'shield':
- return ['offHand'];
+
case 'head':
return ['head'];
case 'body':
diff --git a/src/lib/game/data/fabricator-physical-recipes.ts b/src/lib/game/data/fabricator-physical-recipes.ts
index 4896e15..d5ecc73 100644
--- a/src/lib/game/data/fabricator-physical-recipes.ts
+++ b/src/lib/game/data/fabricator-physical-recipes.ts
@@ -123,38 +123,4 @@ export const PHYSICAL_BRANCH_RECIPES: FabricatorRecipe[] = [
{ 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 },
- ],
- },
];
diff --git a/src/lib/game/effects.ts b/src/lib/game/effects.ts
index b7db66c..2275f00 100644
--- a/src/lib/game/effects.ts
+++ b/src/lib/game/effects.ts
@@ -128,7 +128,7 @@ export function computeAllEffects(
perElementCapBonus,
perElementRegenBonus,
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),
baseDamageMultiplier: upgradeEffects.baseDamageMultiplier * (equipmentEffects.multipliers.baseDamage || 1),
attackSpeedMultiplier: upgradeEffects.attackSpeedMultiplier * (equipmentEffects.multipliers.attackSpeed || 1),
diff --git a/src/lib/game/effects/discipline-effects.ts b/src/lib/game/effects/discipline-effects.ts
index 3c44252..009205a 100644
--- a/src/lib/game/effects/discipline-effects.ts
+++ b/src/lib/game/effects/discipline-effects.ts
@@ -22,9 +22,14 @@ const KNOWN_BONUS_STATS = new Set([
'clickManaBonus',
'baseDamageBonus',
'elementCapBonus',
+ 'elementCap_lightning',
'meditationCapBonus',
'pactAffinityBonus',
'guardianBoonMultiplier',
+ 'enchantPower',
+ 'golemCapacity',
+ 'craftingCostReduction',
+ 'disciplineXpBonus',
]);
export interface DisciplineEffectsResult {
diff --git a/src/lib/game/hooks/useGameDerived.ts b/src/lib/game/hooks/useGameDerived.ts
index 3982f3c..365b9ee 100644
--- a/src/lib/game/hooks/useGameDerived.ts
+++ b/src/lib/game/hooks/useGameDerived.ts
@@ -54,6 +54,7 @@ export function useManaStats() {
);
const disciplineEffects = computeDisciplineEffects();
+ const meditationCap = 5.0 + disciplineEffects.meditationCapBonus;
const meditationMultiplier = useMemo(
() => getMeditationBonus(meditateTicks, {}, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus),
[meditateTicks, upgradeEffects.meditationEfficiency, disciplineEffects.meditationCapBonus]
@@ -86,6 +87,7 @@ export function useManaStats() {
baseRegen,
clickMana,
meditationMultiplier,
+ meditationCap,
incursionStrength,
effectiveRegenWithSpecials,
manaCascadeBonus,
diff --git a/src/lib/game/stores/discipline-slice.ts b/src/lib/game/stores/discipline-slice.ts
index b4c8318..83c9dcb 100644
--- a/src/lib/game/stores/discipline-slice.ts
+++ b/src/lib/game/stores/discipline-slice.ts
@@ -10,6 +10,7 @@ import {
checkDisciplinePrerequisites,
getUnlockedPerks
} from '../utils/discipline-math';
+import { computeDisciplineEffects } from '../effects/discipline-effects';
import { baseDisciplines } from '../data/disciplines/base';
import { elementalAttunementDisciplines } from '../data/disciplines/elemental';
import { elementalRegenDisciplines } from '../data/disciplines/elemental-regen';
@@ -99,8 +100,8 @@ export const useDisciplineStore = create()(
if (signedPacts.length === 0) return s;
}
- // Check discipline prerequisites (requires field → discipline XP)
- const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES);
+ // Check discipline prerequisites (requires field → discipline XP or mana type unlock)
+ const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES, gameState?.elements);
if (!prereqCheck.canProceed) return s;
// For conversion disciplines: gate on having all source mana types unlocked
@@ -183,8 +184,10 @@ export const useDisciplineStore = create()(
}
const oldXP = disc.xp;
- newDisciplines[id] = { ...disc, xp: disc.xp + 1 };
- newXP += 1;
+ const xpBonus = computeDisciplineEffects().bonuses.disciplineXpBonus || 0;
+ const xpGain = 1 + xpBonus;
+ newDisciplines[id] = { ...disc, xp: disc.xp + xpGain };
+ newXP += xpGain;
// Check for newly unlocked perks that unlock effects
if (def.perks.length > 0) {
diff --git a/src/lib/game/utils/discipline-math.ts b/src/lib/game/utils/discipline-math.ts
index ec38d97..92bead7 100644
--- a/src/lib/game/utils/discipline-math.ts
+++ b/src/lib/game/utils/discipline-math.ts
@@ -116,6 +116,7 @@ export function checkDisciplinePrerequisites(
discipline: DisciplineDefinition,
allDisciplines: Record,
allDefinitions: DisciplineDefinition[],
+ elements?: Record,
): { canProceed: boolean; missingPrereqs: string[] } {
if (!discipline.requires || discipline.requires.length === 0) {
return { canProceed: true, missingPrereqs: [] };
@@ -138,10 +139,13 @@ export function checkDisciplinePrerequisites(
missingPrereqs.push(reqDef?.name ?? reqId);
}
} else {
- // Treat as mana type requirement — display as " mana"
+ // Treat as mana type requirement — verify against player's unlocked elements
const typeName = MANA_TYPE_NAMES[reqId];
if (typeName) {
- missingPrereqs.push(`${typeName} mana`);
+ const elementState = elements?.[reqId];
+ if (!elementState || !elementState.unlocked) {
+ missingPrereqs.push(`${typeName} mana`);
+ }
} else {
missingPrereqs.push(reqId);
}