diff --git a/AGENTS.md b/AGENTS.md
index d3b986d..dcc65fb 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -4,6 +4,28 @@ This document provides a comprehensive overview of the project architecture for
---
+## 🔑 Git Credentials (SAVE THESE)
+
+**Repository:** `git@gitea.tailf367e3.ts.net:Anexim/Mana-Loop.git`
+
+**HTTPS URL with credentials:**
+```
+https://zhipu:5LlnutmdsC2WirDwWgnZuRH7@gitea.tailf367e3.ts.net/Anexim/Mana-Loop.git
+```
+
+**Credentials:**
+- **User:** zhipu
+- **Email:** zhipu@local.local
+- **Password:** 5LlnutmdsC2WirDwWgnZuRH7
+
+**To configure git:**
+```bash
+git config --global user.name "zhipu"
+git config --global user.email "zhipu@local.local"
+```
+
+---
+
## ⚠️ MANDATORY GIT WORKFLOW - MUST BE FOLLOWED
**Before starting ANY work, you MUST:**
diff --git a/src/app/page.tsx b/src/app/page.tsx
index d165635..37ddc89 100755
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -228,15 +228,15 @@ export default function ManaLoopGame() {
{/* Right Panel - Tabs */}
-
- ⚔️ Spire
- 📚 Skills
- ✨ Spells
- 🛡️ Gear
- 🔧 Craft
- 🔬 Lab
- 📊 Stats
- 📖 Grimoire
+
+ ⚔️ Spire
+ 📚 Skills
+ ✨ Spells
+ 🛡️ Gear
+ 🔧 Craft
+ 🔬 Lab
+ 📊 Stats
+ 📖 Grimoire
diff --git a/src/components/game/CalendarDisplay.tsx b/src/components/game/CalendarDisplay.tsx
index c9e0995..eabc08d 100644
--- a/src/components/game/CalendarDisplay.tsx
+++ b/src/components/game/CalendarDisplay.tsx
@@ -4,18 +4,20 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants';
interface CalendarDisplayProps {
- currentDay: number;
+ day: number;
+ hour: number;
+ incursionStrength?: number;
}
-export function CalendarDisplay({ currentDay }: CalendarDisplayProps) {
+export function CalendarDisplay({ day }: CalendarDisplayProps) {
const days: React.ReactElement[] = [];
for (let d = 1; d <= MAX_DAY; d++) {
- let dayClass = 'w-7 h-7 rounded text-xs flex items-center justify-center font-mono border transition-all ';
+ let dayClass = 'w-6 h-6 sm:w-7 sm:h-7 rounded text-xs flex items-center justify-center font-mono border transition-all ';
- if (d < currentDay) {
+ if (d < day) {
dayClass += 'bg-blue-900/30 border-blue-800/50 text-blue-400';
- } else if (d === currentDay) {
+ } else if (d === day) {
dayClass += 'bg-blue-600/40 border-blue-500 text-blue-300 shadow-lg shadow-blue-500/30';
} else {
dayClass += 'bg-gray-800/30 border-gray-700/50 text-gray-500';
@@ -40,5 +42,9 @@ export function CalendarDisplay({ currentDay }: CalendarDisplayProps) {
);
}
- return <>{days}>;
+ return (
+
+ {days}
+
+ );
}
diff --git a/src/lib/game/store.ts b/src/lib/game/store.ts
index f450012..c1e0e9d 100755
--- a/src/lib/game/store.ts
+++ b/src/lib/game/store.ts
@@ -409,6 +409,9 @@ export const useGameStore = create()(
let rawMana = Math.min(state.rawMana + effectiveRegen * HOURS_PER_TICK, maxMana);
let totalManaGathered = state.totalManaGathered;
let elements = state.elements;
+
+ // Increment total ticks early (needed for study tracking)
+ const newTotalTicks = state.totalTicks + 1;
// Study progress
let currentStudyTarget = state.currentStudyTarget;
@@ -419,97 +422,130 @@ export const useGameStore = create()(
let unlockedEffects = state.unlockedEffects;
let consecutiveStudyHours = state.consecutiveStudyHours || 0;
let studyStartedAt = state.studyStartedAt;
+ let lastStudyCost = state.lastStudyCost;
if (state.currentAction === 'study' && currentStudyTarget) {
- // Track when study started (for STUDY_RUSH)
- if (studyStartedAt === null) {
- studyStartedAt = newTotalTicks;
- }
-
- // Calculate study speed with all bonuses
- let studySpeedMult = getStudySpeedMultiplier(skills);
-
- // MENTAL_CLARITY: +10% study speed when mana > 75%
- if (hasSpecial(effects, SPECIAL_EFFECTS.MENTAL_CLARITY) && state.rawMana >= maxMana * 0.75) {
- studySpeedMult *= 1.1;
- }
-
- // STUDY_RUSH: First hour of study is 2x speed
- const hoursStudied = (newTotalTicks - studyStartedAt) * HOURS_PER_TICK;
- if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_RUSH) && hoursStudied < 1) {
- studySpeedMult *= 2;
- }
-
- // STUDY_MOMENTUM: +5% study speed per consecutive hour (max +50%)
- if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_MOMENTUM)) {
- const momentumBonus = Math.min(0.5, consecutiveStudyHours * 0.05);
- studySpeedMult *= 1 + momentumBonus;
- }
-
- const progressGain = HOURS_PER_TICK * studySpeedMult;
-
- // KNOWLEDGE_ECHO: 10% instant study chance
- let instantProgress = 0;
- if (hasSpecial(effects, SPECIAL_EFFECTS.KNOWLEDGE_ECHO) && Math.random() < 0.1) {
- instantProgress = currentStudyTarget.required - currentStudyTarget.progress;
- log = [`⚡ Knowledge Echo! Instant study progress!`, ...log.slice(0, 49)];
- }
-
- currentStudyTarget = {
- ...currentStudyTarget,
- progress: currentStudyTarget.progress + progressGain + instantProgress,
- };
-
- // Increment consecutive study hours
- consecutiveStudyHours += HOURS_PER_TICK;
-
- // Check if study is complete
- if (currentStudyTarget.progress >= currentStudyTarget.required) {
- // STUDY_REFUND: 25% mana back on study complete
- if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_REFUND) && state.lastStudyCost > 0) {
- const refund = Math.floor(state.lastStudyCost * 0.25);
- rawMana = Math.min(rawMana + refund, maxMana);
- log = [`💰 Study Refund! Recovered ${refund} mana!`, ...log.slice(0, 49)];
- }
-
- // Reset study tracking
- studyStartedAt = null;
- consecutiveStudyHours = 0;
-
+ // Calculate mana cost for this tick
+ const manaCostPerTick = (currentStudyTarget.manaCostPerHour || 0) * HOURS_PER_TICK;
+
+ // Check if we have enough mana to continue studying
+ if (rawMana < manaCostPerTick) {
+ // Not enough mana - pause study and save progress
+ const retentionBonus = 1 + (state.skills.knowledgeRetention || 0) * 0.2;
+ const savedProgress = Math.min(currentStudyTarget.progress, currentStudyTarget.required * retentionBonus);
+
if (currentStudyTarget.type === 'skill') {
- const skillId = currentStudyTarget.id;
- const currentLevel = skills[skillId] || 0;
- const newLevel = currentLevel + 1;
- skills = { ...skills, [skillId]: newLevel };
- skillProgress = { ...skillProgress, [skillId]: 0 };
- log = [`✅ ${SKILLS_DEF[skillId]?.name} Lv.${newLevel} mastered!`, ...log.slice(0, 49)];
-
- // Check if this skill unlocks effects (research skills)
- const effectsToUnlock = EFFECT_RESEARCH_MAPPING[skillId];
- if (effectsToUnlock && newLevel >= (SKILLS_DEF[skillId]?.max || 1)) {
- const newEffects = effectsToUnlock.filter(e => !unlockedEffects.includes(e));
- if (newEffects.length > 0) {
- unlockedEffects = [...unlockedEffects, ...newEffects];
- log = [`🔬 Unlocked ${newEffects.length} new enchantment effect(s)!`, ...log.slice(0, 49)];
- }
- }
-
- // Special case: When enchanting skill reaches level 1, unlock mana bolt
- if (skillId === 'enchanting' && newLevel >= 1) {
- const enchantingEffects = ENCHANTING_UNLOCK_EFFECTS.filter(e => !unlockedEffects.includes(e));
- if (enchantingEffects.length > 0) {
- unlockedEffects = [...unlockedEffects, ...enchantingEffects];
- log = [`✨ Enchantment design unlocked! Mana Bolt effect available.`, ...log.slice(0, 49)];
- }
- }
+ skillProgress = { ...skillProgress, [currentStudyTarget.id]: savedProgress };
} else if (currentStudyTarget.type === 'spell') {
- // Spells can no longer be studied directly - they come from equipment
- // This branch is kept for backward compatibility but should not be used
- const spellId = currentStudyTarget.id;
- spells = { ...spells, [spellId]: { learned: true, level: 1, studyProgress: 0 } };
- log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
+ spells = {
+ ...spells,
+ [currentStudyTarget.id]: {
+ ...(spells[currentStudyTarget.id] || { learned: false, level: 0 }),
+ studyProgress: savedProgress,
+ },
+ };
}
+
+ log = [`⚠️ Not enough mana to continue studying! Progress saved.`, ...log.slice(0, 49)];
currentStudyTarget = null;
+ consecutiveStudyHours = 0;
+ studyStartedAt = null;
+ } else {
+ // Deduct mana for this tick of study
+ rawMana -= manaCostPerTick;
+ lastStudyCost = (lastStudyCost || 0) + manaCostPerTick;
+
+ // Track when study started (for STUDY_RUSH)
+ if (studyStartedAt === null) {
+ studyStartedAt = newTotalTicks;
+ }
+
+ // Calculate study speed with all bonuses
+ let studySpeedMult = getStudySpeedMultiplier(skills);
+
+ // MENTAL_CLARITY: +10% study speed when mana > 75%
+ if (hasSpecial(effects, SPECIAL_EFFECTS.MENTAL_CLARITY) && rawMana >= maxMana * 0.75) {
+ studySpeedMult *= 1.1;
+ }
+
+ // STUDY_RUSH: First hour of study is 2x speed
+ const hoursStudied = (newTotalTicks - studyStartedAt) * HOURS_PER_TICK;
+ if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_RUSH) && hoursStudied < 1) {
+ studySpeedMult *= 2;
+ }
+
+ // STUDY_MOMENTUM: +5% study speed per consecutive hour (max +50%)
+ if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_MOMENTUM)) {
+ const momentumBonus = Math.min(0.5, consecutiveStudyHours * 0.05);
+ studySpeedMult *= 1 + momentumBonus;
+ }
+
+ const progressGain = HOURS_PER_TICK * studySpeedMult;
+
+ // KNOWLEDGE_ECHO: 10% instant study chance
+ let instantProgress = 0;
+ if (hasSpecial(effects, SPECIAL_EFFECTS.KNOWLEDGE_ECHO) && Math.random() < 0.1) {
+ instantProgress = currentStudyTarget.required - currentStudyTarget.progress;
+ log = [`⚡ Knowledge Echo! Instant study progress!`, ...log.slice(0, 49)];
+ }
+
+ currentStudyTarget = {
+ ...currentStudyTarget,
+ progress: currentStudyTarget.progress + progressGain + instantProgress,
+ };
+
+ // Increment consecutive study hours
+ consecutiveStudyHours += HOURS_PER_TICK;
+
+ // Check if study is complete
+ if (currentStudyTarget.progress >= currentStudyTarget.required) {
+ // STUDY_REFUND: 25% mana back on study complete
+ if (hasSpecial(effects, SPECIAL_EFFECTS.STUDY_REFUND) && lastStudyCost > 0) {
+ const refund = Math.floor(lastStudyCost * 0.25);
+ rawMana = Math.min(rawMana + refund, maxMana);
+ log = [`💰 Study Refund! Recovered ${refund} mana!`, ...log.slice(0, 49)];
+ }
+
+ // Reset study tracking
+ studyStartedAt = null;
+ consecutiveStudyHours = 0;
+ lastStudyCost = 0;
+
+ if (currentStudyTarget.type === 'skill') {
+ const skillId = currentStudyTarget.id;
+ const currentLevel = skills[skillId] || 0;
+ const newLevel = currentLevel + 1;
+ skills = { ...skills, [skillId]: newLevel };
+ skillProgress = { ...skillProgress, [skillId]: 0 };
+ log = [`✅ ${SKILLS_DEF[skillId]?.name} Lv.${newLevel} mastered!`, ...log.slice(0, 49)];
+
+ // Check if this skill unlocks effects (research skills)
+ const effectsToUnlock = EFFECT_RESEARCH_MAPPING[skillId];
+ if (effectsToUnlock && newLevel >= (SKILLS_DEF[skillId]?.max || 1)) {
+ const newEffects = effectsToUnlock.filter(e => !unlockedEffects.includes(e));
+ if (newEffects.length > 0) {
+ unlockedEffects = [...unlockedEffects, ...newEffects];
+ log = [`🔬 Unlocked ${newEffects.length} new enchantment effect(s)!`, ...log.slice(0, 49)];
+ }
+ }
+
+ // Special case: When enchanting skill reaches level 1, unlock mana bolt
+ if (skillId === 'enchanting' && newLevel >= 1) {
+ const enchantingEffects = ENCHANTING_UNLOCK_EFFECTS.filter(e => !unlockedEffects.includes(e));
+ if (enchantingEffects.length > 0) {
+ unlockedEffects = [...unlockedEffects, ...enchantingEffects];
+ log = [`✨ Enchantment design unlocked! Mana Bolt effect available.`, ...log.slice(0, 49)];
+ }
+ }
+ } else if (currentStudyTarget.type === 'spell') {
+ // Spells can no longer be studied directly - they come from equipment
+ // This branch is kept for backward compatibility but should not be used
+ const spellId = currentStudyTarget.id;
+ spells = { ...spells, [spellId]: { learned: true, level: 1, studyProgress: 0 } };
+ log = [`📖 ${SPELLS_DEF[spellId]?.name} learned!`, ...log.slice(0, 49)];
+ }
+ currentStudyTarget = null;
+ }
}
} else {
// Reset consecutive study hours when not studying
@@ -540,12 +576,9 @@ export const useGameStore = create()(
}
// Combat - MULTI-SPELL casting from all equipped weapons
- let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, equipmentSpellStates, combo, totalTicks, lootInventory, achievements, totalDamageDealt, totalSpellsCast } = state;
+ let { currentFloor, floorHP, floorMaxHP, maxFloorReached, signedPacts, equipmentSpellStates, combo, lootInventory, achievements, totalDamageDealt, totalSpellsCast } = state;
const floorElement = getFloorElement(currentFloor);
- // Increment total ticks
- const newTotalTicks = totalTicks + 1;
-
// Combo decay - decay combo when not climbing or when decay timer expires
let newCombo = { ...combo };
if (state.currentAction !== 'climb') {
@@ -878,6 +911,7 @@ export const useGameStore = create()(
totalSpellsCast,
consecutiveStudyHours,
studyStartedAt,
+ lastStudyCost,
...craftingUpdates,
});
},
diff --git a/src/lib/game/study-slice.ts b/src/lib/game/study-slice.ts
index 7f817e0..cd9ef96 100644
--- a/src/lib/game/study-slice.ts
+++ b/src/lib/game/study-slice.ts
@@ -21,7 +21,7 @@ export function createStudySlice(
get: () => GameState
): StudyActions {
return {
- // Start studying a skill
+ // Start studying a skill - mana is deducted per hour, not upfront
startStudyingSkill: (skillId: string) => {
const state = get();
const sk = SKILLS_DEF[skillId];
@@ -37,22 +37,26 @@ export function createStudySlice(
}
}
- // Check mana cost (with focused mind reduction)
+ // Calculate total mana cost and cost per hour
const costMult = getStudyCostMultiplier(state.skills);
- const cost = Math.floor(sk.base * (currentLevel + 1) * costMult);
- if (state.rawMana < cost) return;
+ const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult);
+ const manaCostPerHour = Math.ceil(totalCost / sk.studyTime);
+
+ // Must have at least 1 hour worth of mana to start
+ if (state.rawMana < manaCostPerHour) return;
- // Start studying
+ // Start studying (no upfront cost - mana is deducted per hour during study)
set({
- rawMana: state.rawMana - cost,
currentAction: 'study',
currentStudyTarget: {
type: 'skill',
id: skillId,
progress: state.skillProgress[skillId] || 0,
required: sk.studyTime,
+ manaCostPerHour: manaCostPerHour,
+ totalCost: totalCost,
},
- log: [`📚 Started studying ${sk.name}...`, ...state.log.slice(0, 49)],
+ log: [`📚 Started studying ${sk.name} (${manaCostPerHour} mana/hr)...`, ...state.log.slice(0, 49)],
});
},
@@ -62,28 +66,31 @@ export function createStudySlice(
const sp = SPELLS_DEF[spellId];
if (!sp || state.spells[spellId]?.learned) return;
- // Check mana cost (with focused mind reduction)
+ // Calculate total mana cost and cost per hour
const costMult = getStudyCostMultiplier(state.skills);
- const cost = Math.floor(sp.unlock * costMult);
- if (state.rawMana < cost) return;
+ const totalCost = Math.floor(sp.unlock * costMult);
+ const studyTime = sp.studyTime || (sp.tier * 4);
+ const manaCostPerHour = Math.ceil(totalCost / studyTime);
+
+ // Must have at least 1 hour worth of mana to start
+ if (state.rawMana < manaCostPerHour) return;
- const studyTime = sp.studyTime || (sp.tier * 4); // Default study time based on tier
-
- // Start studying
+ // Start studying (no upfront cost - mana is deducted per hour during study)
set({
- rawMana: state.rawMana - cost,
currentAction: 'study',
currentStudyTarget: {
type: 'spell',
id: spellId,
progress: state.spells[spellId]?.studyProgress || 0,
required: studyTime,
+ manaCostPerHour: manaCostPerHour,
+ totalCost: totalCost,
},
spells: {
...state.spells,
[spellId]: { ...(state.spells[spellId] || { learned: false, level: 0 }), studyProgress: state.spells[spellId]?.studyProgress || 0 },
},
- log: [`📚 Started studying ${sp.name}...`, ...state.log.slice(0, 49)],
+ log: [`📚 Started studying ${sp.name} (${manaCostPerHour} mana/hr)...`, ...state.log.slice(0, 49)],
});
},
@@ -141,12 +148,19 @@ export function createStudySlice(
// Can't study same thing in parallel
if (state.currentStudyTarget.id === skillId) return;
+ // Calculate mana cost for parallel study
+ const costMult = getStudyCostMultiplier(state.skills);
+ const totalCost = Math.floor(sk.base * (currentLevel + 1) * costMult);
+ const manaCostPerHour = Math.ceil(totalCost / sk.studyTime);
+
set({
parallelStudyTarget: {
type: 'skill',
id: skillId,
progress: state.skillProgress[skillId] || 0,
required: sk.studyTime,
+ manaCostPerHour: Math.ceil(manaCostPerHour / 2), // Half speed = half mana cost per tick
+ totalCost: totalCost,
},
log: [`📚 Started parallel study of ${sk.name}... (50% speed)`, ...state.log.slice(0, 49)],
});
diff --git a/src/lib/game/types.ts b/src/lib/game/types.ts
index fef717a..3fbcb24 100755
--- a/src/lib/game/types.ts
+++ b/src/lib/game/types.ts
@@ -330,6 +330,8 @@ export interface StudyTarget {
id: string;
progress: number; // Hours studied
required: number; // Total hours needed
+ manaCostPerHour: number; // Mana cost per hour of study
+ totalCost: number; // Total mana cost for the entire study
}
export interface GameState {