Implement Golemancy System and update documentation
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m39s
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m39s
Golemancy System: - Add golem data definitions with 9 golem types (Earth, Steel, Crystal, Sand, Lava, Galvanic, Obsidian, Prism, Quicksilver, Voidstone) - Add golemancy state to GameState for tracking enabled/summoned golems - Add golemancy actions: toggleGolem, setEnabledGolems - Add golemancy skills: Golem Mastery, Efficiency, Longevity, Siphon, Advanced Golemancy, Resonance - Create GolemancyTab UI component for selecting and managing golems - Golem slots unlock every 2 Fabricator levels (Level 2=1, Level 10=5) Documentation Updates: - Add detailed enchanting and golemancy documentation - Remove all familiar system references - Add Banned Content section (lifesteal, healing, life/blood/wood/mental/force mana, familiar system) README.md now reflects current game systems accurately.
This commit is contained in:
72
README.md
72
README.md
@@ -76,7 +76,7 @@ Exotic (3)
|
|||||||
---
|
---
|
||||||
|
|
||||||
### Skill Progression with Tier Evolution
|
### Skill Progression with Tier Evolution
|
||||||
- 20+ skills across multiple categories (mana, combat, enchanting, familiar)
|
- 20+ skills across multiple categories (mana, study, enchanting, golemancy)
|
||||||
- 5-tier evolution system for each skill
|
- 5-tier evolution system for each skill
|
||||||
- Milestone upgrades at levels 5 and 10 for each tier
|
- Milestone upgrades at levels 5 and 10 for each tier
|
||||||
- Unique special effects unlocked through skill upgrades
|
- Unique special effects unlocked through skill upgrades
|
||||||
@@ -85,19 +85,24 @@ Exotic (3)
|
|||||||
- 3-stage enchantment process (Design → Prepare → Apply)
|
- 3-stage enchantment process (Design → Prepare → Apply)
|
||||||
- Equipment capacity system limiting total enchantment power
|
- Equipment capacity system limiting total enchantment power
|
||||||
- Enchantment effects including stat bonuses, multipliers, and spell grants
|
- Enchantment effects including stat bonuses, multipliers, and spell grants
|
||||||
- Disenchanting to recover mana from unwanted enchantments
|
- Disenchanting to recover mana from unwanted enchantments (only in Prepare stage)
|
||||||
|
- Cannot re-enchant already enchanted gear
|
||||||
|
|
||||||
|
### Golemancy System
|
||||||
|
- Summon magical constructs to fight alongside you
|
||||||
|
- Golem slots unlock every 2 Fabricator levels (Level 2=1, Level 10=5)
|
||||||
|
- Base golems: Earth, Steel (metal), Crystal, Sand
|
||||||
|
- Advanced hybrid golems at Enchanter 5 + Fabricator 5: Lava, Galvanic, Obsidian, Prism, Quicksilver, Voidstone
|
||||||
|
- Golems cost mana to summon and maintain
|
||||||
|
- Golemancy skills improve damage, speed, duration, and maintenance costs
|
||||||
|
|
||||||
### Combat System
|
### Combat System
|
||||||
- Cast speed-based spell casting
|
- Cast speed-based spell casting
|
||||||
- Multi-spell support from equipped weapons
|
- Multi-spell support from equipped weapons
|
||||||
|
- Golem allies deal automatic damage each tick
|
||||||
- Elemental damage bonuses and effectiveness
|
- Elemental damage bonuses and effectiveness
|
||||||
- Floor guardians with unique boons and pacts
|
- Floor guardians with unique boons and pacts
|
||||||
|
|
||||||
### Familiar System
|
|
||||||
- Collect and train magical companions
|
|
||||||
- Familiars provide passive bonuses and active abilities
|
|
||||||
- Growth and evolution mechanics
|
|
||||||
|
|
||||||
### Floor Navigation & Guardian Battles
|
### Floor Navigation & Guardian Battles
|
||||||
- Procedurally generated spire floors
|
- Procedurally generated spire floors
|
||||||
- Elemental floor themes affecting combat
|
- Elemental floor themes affecting combat
|
||||||
@@ -189,10 +194,11 @@ src/
|
|||||||
│ └── game/ # Game-specific components
|
│ └── game/ # Game-specific components
|
||||||
│ ├── tabs/ # Tab-based UI components
|
│ ├── tabs/ # Tab-based UI components
|
||||||
│ │ ├── CraftingTab.tsx
|
│ │ ├── CraftingTab.tsx
|
||||||
|
│ │ ├── GolemancyTab.tsx
|
||||||
│ │ ├── LabTab.tsx
|
│ │ ├── LabTab.tsx
|
||||||
│ │ ├── SpellsTab.tsx
|
│ │ ├── SpellsTab.tsx
|
||||||
│ │ ├── SpireTab.tsx
|
│ │ ├── SpireTab.tsx
|
||||||
│ │ └── FamiliarTab.tsx
|
│ │ └── ...
|
||||||
│ ├── ManaDisplay.tsx
|
│ ├── ManaDisplay.tsx
|
||||||
│ ├── TimeDisplay.tsx
|
│ ├── TimeDisplay.tsx
|
||||||
│ ├── ActionButtons.tsx
|
│ ├── ActionButtons.tsx
|
||||||
@@ -206,7 +212,6 @@ src/
|
|||||||
│ ├── constants.ts # Game data definitions
|
│ ├── constants.ts # Game data definitions
|
||||||
│ ├── computed-stats.ts # Stat calculation functions
|
│ ├── computed-stats.ts # Stat calculation functions
|
||||||
│ ├── crafting-slice.ts # Equipment/enchantment actions
|
│ ├── crafting-slice.ts # Equipment/enchantment actions
|
||||||
│ ├── familiar-slice.ts # Familiar system actions
|
|
||||||
│ ├── navigation-slice.ts # Floor navigation actions
|
│ ├── navigation-slice.ts # Floor navigation actions
|
||||||
│ ├── study-slice.ts # Study system actions
|
│ ├── study-slice.ts # Study system actions
|
||||||
│ ├── types.ts # TypeScript interfaces
|
│ ├── types.ts # TypeScript interfaces
|
||||||
@@ -215,7 +220,7 @@ src/
|
|||||||
│ └── data/
|
│ └── data/
|
||||||
│ ├── equipment.ts # Equipment definitions
|
│ ├── equipment.ts # Equipment definitions
|
||||||
│ ├── enchantment-effects.ts # Enchantment catalog
|
│ ├── enchantment-effects.ts # Enchantment catalog
|
||||||
│ ├── familiars.ts # Familiar definitions
|
│ ├── golems.ts # Golem definitions
|
||||||
│ ├── crafting-recipes.ts # Crafting recipes
|
│ ├── crafting-recipes.ts # Crafting recipes
|
||||||
│ ├── achievements.ts # Achievement definitions
|
│ ├── achievements.ts # Achievement definitions
|
||||||
│ └── loot-drops.ts # Loot drop tables
|
│ └── loot-drops.ts # Loot drop tables
|
||||||
@@ -251,20 +256,37 @@ Combat uses a cast-speed system where each spell has a unique cast rate. Damage
|
|||||||
- `constants.ts` - Spell definitions (`SPELLS_DEF`)
|
- `constants.ts` - Spell definitions (`SPELLS_DEF`)
|
||||||
- `effects.ts` - Damage calculations
|
- `effects.ts` - Damage calculations
|
||||||
|
|
||||||
### Crafting System
|
### Enchanting System
|
||||||
A 3-stage enchantment system for equipment. Design effects, prepare equipment, and apply enchantments within capacity limits.
|
A 3-stage enchantment system for equipment. Design effects, prepare equipment, and apply enchantments within capacity limits.
|
||||||
|
|
||||||
|
**Key Rules:**
|
||||||
|
- Design: Choose effects for your equipment type
|
||||||
|
- Prepare: Prepare equipment for enchanting (ONLY way to disenchant existing enchantments)
|
||||||
|
- Apply: Apply designed enchantments (cannot re-enchant already enchanted gear)
|
||||||
|
|
||||||
**Key Files:**
|
**Key Files:**
|
||||||
- `crafting-slice.ts` - Crafting actions
|
- `crafting-slice.ts` - Crafting actions
|
||||||
- `data/equipment.ts` - Equipment types
|
- `data/equipment.ts` - Equipment types
|
||||||
- `data/enchantment-effects.ts` - Available effects
|
- `data/enchantment-effects.ts` - Available effects
|
||||||
|
|
||||||
### Familiar System
|
### Golemancy System
|
||||||
Magical companions that provide bonuses and can be trained and evolved.
|
Summon magical constructs to fight alongside you. Requires Fabricator attunement.
|
||||||
|
|
||||||
|
**Golem Slots:**
|
||||||
|
- Fabricator Level 2: 1 slot
|
||||||
|
- Fabricator Level 4: 2 slots
|
||||||
|
- Fabricator Level 6: 3 slots
|
||||||
|
- Fabricator Level 8: 4 slots
|
||||||
|
- Fabricator Level 10: 5 slots
|
||||||
|
|
||||||
|
**Golem Types:**
|
||||||
|
- Base: Earth (available at Fabricator 2)
|
||||||
|
- Element Unlocks: Steel (metal), Crystal, Sand
|
||||||
|
- Hybrids (Enchanter 5 + Fabricator 5): Lava, Galvanic, Obsidian, Prism, Quicksilver, Voidstone
|
||||||
|
|
||||||
**Key Files:**
|
**Key Files:**
|
||||||
- `familiar-slice.ts` - Familiar actions
|
- `data/golems.ts` - Golem definitions and unlock conditions
|
||||||
- `data/familiars.ts` - Familiar definitions
|
- `store.ts` - Golemancy actions and state
|
||||||
|
|
||||||
### Prestige System
|
### Prestige System
|
||||||
Reset progress for Insight, which provides permanent bonuses. Signed pacts persist through prestige.
|
Reset progress for Insight, which provides permanent bonuses. Signed pacts persist through prestige.
|
||||||
@@ -300,6 +322,26 @@ For detailed patterns on adding new effects, skills, spells, or systems, see [AG
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Banned Content
|
||||||
|
|
||||||
|
The following content has been removed from the game and should not be re-added:
|
||||||
|
|
||||||
|
### Banned Mechanics
|
||||||
|
- **Lifesteal** - Player cannot heal from dealing damage
|
||||||
|
- **Healing** - Player cannot heal themselves (floors take damage, not player)
|
||||||
|
|
||||||
|
### Banned Mana Types
|
||||||
|
- **Life** - Removed (healing theme conflicts with core design)
|
||||||
|
- **Blood** - Removed (life derivative)
|
||||||
|
- **Wood** - Removed (life derivative)
|
||||||
|
- **Mental** - Removed
|
||||||
|
- **Force** - Removed
|
||||||
|
|
||||||
|
### Banned Systems
|
||||||
|
- **Familiar System** - Removed in favor of Golemancy and Pact systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
This project is licensed under the MIT License.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { RotateCcw } from 'lucide-react';
|
import { RotateCcw } from 'lucide-react';
|
||||||
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab, LootTab, AchievementsTab } from '@/components/game/tabs';
|
import { CraftingTab, SpireTab, SpellsTab, LabTab, SkillsTab, StatsTab, EquipmentTab, AttunementsTab, DebugTab, LootTab, AchievementsTab, GolemancyTab } from '@/components/game/tabs';
|
||||||
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
import { ActionButtons, CalendarDisplay, ManaDisplay, TimeDisplay } from '@/components/game';
|
||||||
// Loot and Achievements moved to separate tabs
|
// Loot and Achievements moved to separate tabs
|
||||||
import { DebugName } from '@/lib/game/debug-context';
|
import { DebugName } from '@/lib/game/debug-context';
|
||||||
@@ -218,6 +218,7 @@ export default function ManaLoopGame() {
|
|||||||
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
<TabsList className="flex flex-wrap gap-1 w-full mb-4 h-auto">
|
||||||
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
<TabsTrigger value="spire" className="text-xs px-2 py-1">⚔️ Spire</TabsTrigger>
|
||||||
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
<TabsTrigger value="attunements" className="text-xs px-2 py-1">✨ Attune</TabsTrigger>
|
||||||
|
<TabsTrigger value="golemancy" className="text-xs px-2 py-1">🗿 Golems</TabsTrigger>
|
||||||
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
<TabsTrigger value="skills" className="text-xs px-2 py-1">📚 Skills</TabsTrigger>
|
||||||
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
<TabsTrigger value="spells" className="text-xs px-2 py-1">🔮 Spells</TabsTrigger>
|
||||||
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
<TabsTrigger value="equipment" className="text-xs px-2 py-1">🛡️ Gear</TabsTrigger>
|
||||||
@@ -242,6 +243,12 @@ export default function ManaLoopGame() {
|
|||||||
</DebugName>
|
</DebugName>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="golemancy">
|
||||||
|
<DebugName name="GolemancyTab">
|
||||||
|
<GolemancyTab store={store} />
|
||||||
|
</DebugName>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="skills">
|
<TabsContent value="skills">
|
||||||
<DebugName name="SkillsTab">
|
<DebugName name="SkillsTab">
|
||||||
<SkillsTab store={store} />
|
<SkillsTab store={store} />
|
||||||
|
|||||||
341
src/components/game/tabs/GolemancyTab.tsx
Normal file
341
src/components/game/tabs/GolemancyTab.tsx
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import {
|
||||||
|
Golem, Zap, Clock, Swords, Shield, Target, Sparkles, Lock, Check, X
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from '@/lib/game/data/golems';
|
||||||
|
import { ELEMENTS } from '@/lib/game/constants';
|
||||||
|
import { fmt } from '@/lib/game/store';
|
||||||
|
import type { GameStore } from '@/lib/game/store';
|
||||||
|
import type { GolemancyState, AttunementState, ElementState } from '@/lib/game/types';
|
||||||
|
|
||||||
|
export interface GolemancyTabProps {
|
||||||
|
store: GameStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GolemancyTab({ store }: GolemancyTabProps) {
|
||||||
|
const attunements = store.attunements;
|
||||||
|
const elements = store.elements;
|
||||||
|
const skills = store.skills;
|
||||||
|
const golemancy = store.golemancy;
|
||||||
|
const currentFloor = store.currentFloor;
|
||||||
|
const currentRoom = store.currentRoom;
|
||||||
|
const toggleGolem = store.toggleGolem;
|
||||||
|
|
||||||
|
// Get Fabricator level and golem slots
|
||||||
|
const fabricatorLevel = attunements.fabricator?.level || 0;
|
||||||
|
const fabricatorActive = attunements.fabricator?.active || false;
|
||||||
|
const maxSlots = getGolemSlots(fabricatorLevel);
|
||||||
|
|
||||||
|
// Get unlocked elements
|
||||||
|
const unlockedElements = Object.entries(elements)
|
||||||
|
.filter(([, e]) => e.unlocked)
|
||||||
|
.map(([id]) => id);
|
||||||
|
|
||||||
|
// Get all unlocked golems
|
||||||
|
const unlockedGolems = Object.values(GOLEMS_DEF).filter(golem =>
|
||||||
|
isGolemUnlocked(golem.id, attunements, unlockedElements)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if golemancy is available
|
||||||
|
const hasGolemancy = fabricatorActive && fabricatorLevel >= 2;
|
||||||
|
|
||||||
|
// Check if currently in combat (not puzzle)
|
||||||
|
const inCombat = currentRoom.roomType !== 'puzzle';
|
||||||
|
|
||||||
|
// Get element info helper
|
||||||
|
const getElementInfo = (elementId: string) => {
|
||||||
|
return ELEMENTS[elementId];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render a golem card
|
||||||
|
const renderGolemCard = (golemId: string, isUnlocked: boolean) => {
|
||||||
|
const golem = GOLEMS_DEF[golemId];
|
||||||
|
if (!golem) return null;
|
||||||
|
|
||||||
|
const isEnabled = golemancy.enabledGolems.includes(golemId);
|
||||||
|
const isSelected = golemancy.summonedGolems.some(g => g.golemId === golemId);
|
||||||
|
|
||||||
|
// Calculate effective stats
|
||||||
|
const damage = getGolemDamage(golemId, skills);
|
||||||
|
const attackSpeed = getGolemAttackSpeed(golemId, skills);
|
||||||
|
const floorDuration = getGolemFloorDuration(skills);
|
||||||
|
|
||||||
|
// Get element color
|
||||||
|
const primaryElement = getElementInfo(golem.baseManaType);
|
||||||
|
const elementColor = primaryElement?.color || '#888';
|
||||||
|
|
||||||
|
if (!isUnlocked) {
|
||||||
|
// Locked golem card
|
||||||
|
return (
|
||||||
|
<Card key={golemId} className="bg-gray-900/80 border-gray-700 opacity-50">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-sm flex items-center gap-2">
|
||||||
|
<Lock className="w-4 h-4" />
|
||||||
|
<span className="text-gray-500">???</span>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="text-xs text-gray-500">
|
||||||
|
{golem.unlockCondition.type === 'attunement_level' && (
|
||||||
|
<div>Requires Fabricator Level {golem.unlockCondition.level}</div>
|
||||||
|
)}
|
||||||
|
{golem.unlockCondition.type === 'mana_unlocked' && (
|
||||||
|
<div>Requires {ELEMENTS[golem.unlockCondition.manaType || '']?.name || golem.unlockCondition.manaType} Mana</div>
|
||||||
|
)}
|
||||||
|
{golem.unlockCondition.type === 'dual_attunement' && (
|
||||||
|
<div>Requires Enchanter & Fabricator Level 5</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={golemId}
|
||||||
|
className={`bg-gray-900/80 border-2 transition-all cursor-pointer ${
|
||||||
|
isEnabled
|
||||||
|
? 'border-green-500 bg-green-900/10'
|
||||||
|
: 'border-gray-700 hover:border-gray-600'
|
||||||
|
}`}
|
||||||
|
onClick={() => toggleGolem(golemId)}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-sm flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Golem className="w-4 h-4" style={{ color: elementColor }} />
|
||||||
|
<span style={{ color: elementColor }}>{golem.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{golem.isAoe && (
|
||||||
|
<Badge variant="outline" className="text-xs">AOE {golem.aoeTargets}</Badge>
|
||||||
|
)}
|
||||||
|
<Badge variant="outline" className="text-xs">T{golem.tier}</Badge>
|
||||||
|
{isEnabled ? (
|
||||||
|
<Check className="w-4 h-4 text-green-400" />
|
||||||
|
) : (
|
||||||
|
<X className="w-4 h-4 text-gray-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-2">
|
||||||
|
<p className="text-xs text-gray-400">{golem.description}</p>
|
||||||
|
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Swords className="w-3 h-3 text-red-400" />
|
||||||
|
<span className="text-gray-400">DMG:</span>
|
||||||
|
<span className="text-white">{damage}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Zap className="w-3 h-3 text-yellow-400" />
|
||||||
|
<span className="text-gray-400">Speed:</span>
|
||||||
|
<span className="text-white">{attackSpeed.toFixed(1)}/hr</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Target className="w-3 h-3 text-blue-400" />
|
||||||
|
<span className="text-gray-400">Pierce:</span>
|
||||||
|
<span className="text-white">{Math.floor(golem.armorPierce * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Clock className="w-3 h-3 text-purple-400" />
|
||||||
|
<span className="text-gray-400">Duration:</span>
|
||||||
|
<span className="text-white">{floorDuration} floor(s)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="bg-gray-700" />
|
||||||
|
|
||||||
|
{/* Summon Cost */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 mb-1">Summon Cost:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{golem.summonCost.map((cost, idx) => {
|
||||||
|
const elem = getElementInfo(cost.element || '');
|
||||||
|
const available = cost.type === 'raw'
|
||||||
|
? store.rawMana
|
||||||
|
: elements[cost.element || '']?.current || 0;
|
||||||
|
const canAfford = available >= cost.amount;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
key={idx}
|
||||||
|
variant="outline"
|
||||||
|
className={`text-xs ${canAfford ? 'border-green-500' : 'border-red-500'}`}
|
||||||
|
style={{ borderColor: canAfford ? undefined : '#ef4444' }}
|
||||||
|
>
|
||||||
|
<span style={{ color: elem?.color }}>{elem?.sym || '💎'}</span>
|
||||||
|
{' '}{cost.amount}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Maintenance Cost */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 mb-1">Maintenance/hr:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{golem.maintenanceCost.map((cost, idx) => {
|
||||||
|
const elem = getElementInfo(cost.element || '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge key={idx} variant="outline" className="text-xs">
|
||||||
|
<span style={{ color: elem?.color }}>{elem?.sym || '💎'}</span>
|
||||||
|
{' '}{cost.amount}/hr
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
{isSelected && (
|
||||||
|
<div className="mt-2 text-xs text-green-400 flex items-center gap-1">
|
||||||
|
<Sparkles className="w-3 h-3" />
|
||||||
|
Active on Floor {currentFloor}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
<Golem className="w-5 h-5 text-amber-500" />
|
||||||
|
Golemancy
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{!hasGolemancy ? (
|
||||||
|
<div className="text-center text-gray-400 py-4">
|
||||||
|
<Lock className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||||
|
<p>Unlock the Fabricator attunement and reach Level 2 to summon golems.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-400">Golem Slots:</span>
|
||||||
|
<span className="text-sm font-semibold">
|
||||||
|
<span className="text-amber-400">{golemancy.enabledGolems.length}</span>
|
||||||
|
<span className="text-gray-500"> / {maxSlots}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-400">Fabricator Level:</span>
|
||||||
|
<span className="text-sm font-semibold text-amber-400">{fabricatorLevel}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-400">Floor Duration:</span>
|
||||||
|
<span className="text-sm font-semibold">{getGolemFloorDuration(skills)} floor(s)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-gray-400">Status:</span>
|
||||||
|
<span className={`text-sm ${inCombat ? 'text-green-400' : 'text-yellow-400'}`}>
|
||||||
|
{inCombat ? '⚔️ Combat Active' : '🧩 Puzzle Room (No Golems)'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-500 mt-2">
|
||||||
|
Golems are automatically summoned at the start of each combat floor.
|
||||||
|
They cost mana to maintain and will be dismissed if you run out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Active Golems */}
|
||||||
|
{hasGolemancy && golemancy.summonedGolems.length > 0 && (
|
||||||
|
<Card className="bg-gray-900/80 border-green-600">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-sm text-green-400 flex items-center gap-2">
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
Active Golems ({golemancy.summonedGolems.length})
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{golemancy.summonedGolems.map(sg => {
|
||||||
|
const golem = GOLEMS_DEF[sg.golemId];
|
||||||
|
const elem = getElementInfo(golem?.baseManaType || '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge key={sg.golemId} variant="outline" className="text-sm py-1 px-2">
|
||||||
|
<Golem className="w-3 h-3 mr-1" style={{ color: elem?.color }} />
|
||||||
|
{golem?.name}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Golem Selection */}
|
||||||
|
{hasGolemancy && (
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-sm">Select Golems to Summon</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ScrollArea className="h-96">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 pr-4">
|
||||||
|
{/* Unlocked Golems */}
|
||||||
|
{unlockedGolems.map(golem => renderGolemCard(golem.id, true))}
|
||||||
|
|
||||||
|
{/* Locked Golems */}
|
||||||
|
{Object.values(GOLEMS_DEF)
|
||||||
|
.filter(g => !isGolemUnlocked(g.id, attunements, unlockedElements))
|
||||||
|
.map(golem => renderGolemCard(golem.id, false))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Golemancy Skills Info */}
|
||||||
|
<Card className="bg-gray-900/80 border-gray-700">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-sm">Golemancy Skills</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-xs text-gray-400 space-y-1">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Golem Mastery:</span>
|
||||||
|
<span className="text-white">+{skills.golemMastery || 0}0% damage</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Golem Efficiency:</span>
|
||||||
|
<span className="text-white">+{(skills.golemEfficiency || 0) * 5}% attack speed</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Golem Longevity:</span>
|
||||||
|
<span className="text-white">+{skills.golemLongevity || 0} floor duration</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Golem Siphon:</span>
|
||||||
|
<span className="text-white">-{(skills.golemSiphon || 0) * 10}% maintenance</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,3 +12,4 @@ export { AttunementsTab } from './AttunementsTab';
|
|||||||
export { DebugTab } from './DebugTab';
|
export { DebugTab } from './DebugTab';
|
||||||
export { LootTab } from './LootTab';
|
export { LootTab } from './LootTab';
|
||||||
export { AchievementsTab } from './AchievementsTab';
|
export { AchievementsTab } from './AchievementsTab';
|
||||||
|
export { GolemancyTab } from './GolemancyTab';
|
||||||
|
|||||||
@@ -1183,6 +1183,20 @@ export const SKILLS_DEF: Record<string, SkillDef> = {
|
|||||||
insightHarvest: { name: "Insight Harvest", desc: "+10% insight gain", cat: "ascension", max: 5, base: 1000, studyTime: 20, attunementReq: { enchanter: 1 } },
|
insightHarvest: { name: "Insight Harvest", desc: "+10% insight gain", cat: "ascension", max: 5, base: 1000, studyTime: 20, attunementReq: { enchanter: 1 } },
|
||||||
temporalMemory: { name: "Temporal Memory", desc: "Keep 1 spell learned across loops", cat: "ascension", max: 3, base: 2000, studyTime: 36, attunementReq: { enchanter: 3 } },
|
temporalMemory: { name: "Temporal Memory", desc: "Keep 1 spell learned across loops", cat: "ascension", max: 3, base: 2000, studyTime: 36, attunementReq: { enchanter: 3 } },
|
||||||
guardianBane: { name: "Guardian Bane", desc: "+20% dmg vs guardians", cat: "ascension", max: 3, base: 1500, studyTime: 30, attunementReq: { invoker: 1 } },
|
guardianBane: { name: "Guardian Bane", desc: "+20% dmg vs guardians", cat: "ascension", max: 3, base: 1500, studyTime: 30, attunementReq: { invoker: 1 } },
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// GOLEMANCY SKILLS - Require Fabricator attunement
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Core Golemancy
|
||||||
|
golemMastery: { name: "Golem Mastery", desc: "+10% golem damage", cat: "golemancy", max: 5, base: 300, studyTime: 6, attunementReq: { fabricator: 2 } },
|
||||||
|
golemEfficiency: { name: "Golem Efficiency", desc: "+5% golem attack speed", cat: "golemancy", max: 5, base: 350, studyTime: 6, attunementReq: { fabricator: 2 } },
|
||||||
|
golemLongevity: { name: "Golem Longevity", desc: "+1 floor duration", cat: "golemancy", max: 3, base: 500, studyTime: 8, attunementReq: { fabricator: 3 } },
|
||||||
|
golemSiphon: { name: "Golem Siphon", desc: "-10% golem maintenance", cat: "golemancy", max: 3, base: 400, studyTime: 8, attunementReq: { fabricator: 3 } },
|
||||||
|
|
||||||
|
// Advanced Golemancy
|
||||||
|
advancedGolemancy: { name: "Advanced Golemancy", desc: "Unlock hybrid golem recipes", cat: "golemancy", max: 1, base: 800, studyTime: 16, req: { golemMastery: 3 }, attunementReq: { fabricator: 5 } },
|
||||||
|
golemResonance: { name: "Golem Resonance", desc: "+1 golem slot at Fabricator 10", cat: "golemancy", max: 1, base: 1200, studyTime: 24, req: { golemMastery: 5 }, attunementReq: { fabricator: 8 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── Prestige Upgrades ────────────────────────────────────────────────────────
|
// ─── Prestige Upgrades ────────────────────────────────────────────────────────
|
||||||
|
|||||||
358
src/lib/game/data/golems.ts
Normal file
358
src/lib/game/data/golems.ts
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
// ─── Golem Definitions ─────────────────────────────────────────────────────────
|
||||||
|
// Golems are magical constructs that fight alongside the player
|
||||||
|
// They cost mana to summon and maintain
|
||||||
|
|
||||||
|
import type { SpellCost } from '../types';
|
||||||
|
|
||||||
|
// Golem mana cost helper
|
||||||
|
function elemCost(element: string, amount: number): SpellCost {
|
||||||
|
return { type: 'element', element, amount };
|
||||||
|
}
|
||||||
|
|
||||||
|
function rawCost(amount: number): SpellCost {
|
||||||
|
return { type: 'raw', amount };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GolemManaCost {
|
||||||
|
type: 'raw' | 'element';
|
||||||
|
element?: string;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GolemDef {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
baseManaType: string; // The primary mana type this golem uses
|
||||||
|
summonCost: GolemManaCost[]; // Cost to summon (can be multiple types)
|
||||||
|
maintenanceCost: GolemManaCost[]; // Cost per hour to maintain
|
||||||
|
damage: number; // Base damage per attack
|
||||||
|
attackSpeed: number; // Attacks per hour
|
||||||
|
hp: number; // Golem HP (for display, they don't take damage)
|
||||||
|
armorPierce: number; // Armor piercing (0-1)
|
||||||
|
isAoe: boolean; // Whether golem attacks are AOE
|
||||||
|
aoeTargets: number; // Number of targets for AOE
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'attunement_level' | 'mana_unlocked' | 'dual_attunement';
|
||||||
|
attunement?: string;
|
||||||
|
level?: number;
|
||||||
|
manaType?: string;
|
||||||
|
attunements?: string[];
|
||||||
|
levels?: number[];
|
||||||
|
};
|
||||||
|
tier: number; // Power tier (1-4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All golem definitions
|
||||||
|
export const GOLEMS_DEF: Record<string, GolemDef> = {
|
||||||
|
// ─── BASE GOLEMS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Earth Golem - Basic, available with Fabricator attunement
|
||||||
|
earthGolem: {
|
||||||
|
id: 'earthGolem',
|
||||||
|
name: 'Earth Golem',
|
||||||
|
description: 'A sturdy construct of stone and soil. Slow but powerful.',
|
||||||
|
baseManaType: 'earth',
|
||||||
|
summonCost: [elemCost('earth', 10)],
|
||||||
|
maintenanceCost: [elemCost('earth', 0.5)],
|
||||||
|
damage: 8,
|
||||||
|
attackSpeed: 1.5,
|
||||||
|
hp: 50,
|
||||||
|
armorPierce: 0.15,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'attunement_level',
|
||||||
|
attunement: 'fabricator',
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
tier: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── ELEMENTAL VARIANT GOLEMS ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Steel Golem - Metal mana variant
|
||||||
|
steelGolem: {
|
||||||
|
id: 'steelGolem',
|
||||||
|
name: 'Steel Golem',
|
||||||
|
description: 'Forged from metal, this golem has high armor piercing.',
|
||||||
|
baseManaType: 'metal',
|
||||||
|
summonCost: [elemCost('metal', 8), elemCost('earth', 5)],
|
||||||
|
maintenanceCost: [elemCost('metal', 0.6), elemCost('earth', 0.2)],
|
||||||
|
damage: 12,
|
||||||
|
attackSpeed: 1.2,
|
||||||
|
hp: 60,
|
||||||
|
armorPierce: 0.35,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'mana_unlocked',
|
||||||
|
manaType: 'metal',
|
||||||
|
},
|
||||||
|
tier: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Crystal Golem - Crystal mana variant
|
||||||
|
crystalGolem: {
|
||||||
|
id: 'crystalGolem',
|
||||||
|
name: 'Crystal Golem',
|
||||||
|
description: 'A prismatic construct that deals high damage with precision.',
|
||||||
|
baseManaType: 'crystal',
|
||||||
|
summonCost: [elemCost('crystal', 6), elemCost('earth', 3)],
|
||||||
|
maintenanceCost: [elemCost('crystal', 0.4), elemCost('earth', 0.2)],
|
||||||
|
damage: 18,
|
||||||
|
attackSpeed: 1.0,
|
||||||
|
hp: 40,
|
||||||
|
armorPierce: 0.25,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'mana_unlocked',
|
||||||
|
manaType: 'crystal',
|
||||||
|
},
|
||||||
|
tier: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sand Golem - Sand mana variant
|
||||||
|
sandGolem: {
|
||||||
|
id: 'sandGolem',
|
||||||
|
name: 'Sand Golem',
|
||||||
|
description: 'A shifting construct of sand particles. Hits multiple enemies.',
|
||||||
|
baseManaType: 'sand',
|
||||||
|
summonCost: [elemCost('sand', 8), elemCost('earth', 3)],
|
||||||
|
maintenanceCost: [elemCost('sand', 0.5), elemCost('earth', 0.2)],
|
||||||
|
damage: 6,
|
||||||
|
attackSpeed: 2.0,
|
||||||
|
hp: 35,
|
||||||
|
armorPierce: 0.1,
|
||||||
|
isAoe: true,
|
||||||
|
aoeTargets: 2,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'mana_unlocked',
|
||||||
|
manaType: 'sand',
|
||||||
|
},
|
||||||
|
tier: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// ─── ADVANCED HYBRID GOLEMS ──────────────────────────────────────────────────
|
||||||
|
// Require Enchanter 5 + Fabricator 5
|
||||||
|
|
||||||
|
// Lava Golem - Fire + Earth fusion
|
||||||
|
lavaGolem: {
|
||||||
|
id: 'lavaGolem',
|
||||||
|
name: 'Lava Golem',
|
||||||
|
description: 'Molten earth and fire combined. Burns enemies over time.',
|
||||||
|
baseManaType: 'earth',
|
||||||
|
summonCost: [elemCost('earth', 10), elemCost('fire', 8)],
|
||||||
|
maintenanceCost: [elemCost('earth', 0.4), elemCost('fire', 0.5)],
|
||||||
|
damage: 15,
|
||||||
|
attackSpeed: 1.0,
|
||||||
|
hp: 70,
|
||||||
|
armorPierce: 0.2,
|
||||||
|
isAoe: true,
|
||||||
|
aoeTargets: 2,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Galvanic Golem - Metal + Lightning fusion
|
||||||
|
galvanicGolem: {
|
||||||
|
id: 'galvanicGolem',
|
||||||
|
name: 'Galvanic Golem',
|
||||||
|
description: 'A conductive metal construct charged with lightning. Extremely fast attacks.',
|
||||||
|
baseManaType: 'metal',
|
||||||
|
summonCost: [elemCost('metal', 8), elemCost('lightning', 6)],
|
||||||
|
maintenanceCost: [elemCost('metal', 0.3), elemCost('lightning', 0.6)],
|
||||||
|
damage: 10,
|
||||||
|
attackSpeed: 3.5,
|
||||||
|
hp: 45,
|
||||||
|
armorPierce: 0.45,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Obsidian Golem - Dark + Earth fusion
|
||||||
|
obsidianGolem: {
|
||||||
|
id: 'obsidianGolem',
|
||||||
|
name: 'Obsidian Golem',
|
||||||
|
description: 'Volcanic glass animated by shadow. Devastating single-target damage.',
|
||||||
|
baseManaType: 'earth',
|
||||||
|
summonCost: [elemCost('earth', 12), elemCost('dark', 6)],
|
||||||
|
maintenanceCost: [elemCost('earth', 0.3), elemCost('dark', 0.4)],
|
||||||
|
damage: 25,
|
||||||
|
attackSpeed: 0.8,
|
||||||
|
hp: 55,
|
||||||
|
armorPierce: 0.5,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Prism Golem - Light + Crystal fusion
|
||||||
|
prismGolem: {
|
||||||
|
id: 'prismGolem',
|
||||||
|
name: 'Prism Golem',
|
||||||
|
description: 'A radiant crystal construct. Channels light into piercing beams.',
|
||||||
|
baseManaType: 'crystal',
|
||||||
|
summonCost: [elemCost('crystal', 10), elemCost('light', 6)],
|
||||||
|
maintenanceCost: [elemCost('crystal', 0.4), elemCost('light', 0.4)],
|
||||||
|
damage: 20,
|
||||||
|
attackSpeed: 1.5,
|
||||||
|
hp: 50,
|
||||||
|
armorPierce: 0.35,
|
||||||
|
isAoe: true,
|
||||||
|
aoeTargets: 3,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 4,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quicksilver Golem - Water + Metal fusion
|
||||||
|
quicksilverGolem: {
|
||||||
|
id: 'quicksilverGolem',
|
||||||
|
name: 'Quicksilver Golem',
|
||||||
|
description: 'Liquid metal that flows around defenses. Fast and hard to dodge.',
|
||||||
|
baseManaType: 'metal',
|
||||||
|
summonCost: [elemCost('metal', 6), elemCost('water', 6)],
|
||||||
|
maintenanceCost: [elemCost('metal', 0.3), elemCost('water', 0.3)],
|
||||||
|
damage: 8,
|
||||||
|
attackSpeed: 4.0,
|
||||||
|
hp: 40,
|
||||||
|
armorPierce: 0.3,
|
||||||
|
isAoe: false,
|
||||||
|
aoeTargets: 1,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 3,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Voidstone Golem - Void + Earth fusion (ultimate)
|
||||||
|
voidstoneGolem: {
|
||||||
|
id: 'voidstoneGolem',
|
||||||
|
name: 'Voidstone Golem',
|
||||||
|
description: 'Earth infused with void energy. The ultimate golem construct.',
|
||||||
|
baseManaType: 'earth',
|
||||||
|
summonCost: [elemCost('earth', 15), elemCost('void', 8)],
|
||||||
|
maintenanceCost: [elemCost('earth', 0.3), elemCost('void', 0.6)],
|
||||||
|
damage: 40,
|
||||||
|
attackSpeed: 0.6,
|
||||||
|
hp: 100,
|
||||||
|
armorPierce: 0.6,
|
||||||
|
isAoe: true,
|
||||||
|
aoeTargets: 3,
|
||||||
|
unlockCondition: {
|
||||||
|
type: 'dual_attunement',
|
||||||
|
attunements: ['enchanter', 'fabricator'],
|
||||||
|
levels: [5, 5],
|
||||||
|
},
|
||||||
|
tier: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get golem slots based on Fabricator attunement level
|
||||||
|
// Level 2 = 1, Level 4 = 2, Level 6 = 3, Level 8 = 4, Level 10 = 5
|
||||||
|
export function getGolemSlots(fabricatorLevel: number): number {
|
||||||
|
if (fabricatorLevel < 2) return 0;
|
||||||
|
return Math.floor(fabricatorLevel / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a golem is unlocked based on player state
|
||||||
|
export function isGolemUnlocked(
|
||||||
|
golemId: string,
|
||||||
|
attunements: Record<string, { active: boolean; level: number }>,
|
||||||
|
unlockedElements: string[]
|
||||||
|
): boolean {
|
||||||
|
const golem = GOLEMS_DEF[golemId];
|
||||||
|
if (!golem) return false;
|
||||||
|
|
||||||
|
const condition = golem.unlockCondition;
|
||||||
|
|
||||||
|
switch (condition.type) {
|
||||||
|
case 'attunement_level':
|
||||||
|
const attState = attunements[condition.attunement || ''];
|
||||||
|
return attState?.active && (attState.level || 1) >= (condition.level || 1);
|
||||||
|
|
||||||
|
case 'mana_unlocked':
|
||||||
|
return unlockedElements.includes(condition.manaType || '');
|
||||||
|
|
||||||
|
case 'dual_attunement':
|
||||||
|
if (!condition.attunements || !condition.levels) return false;
|
||||||
|
return condition.attunements.every((attId, idx) => {
|
||||||
|
const att = attunements[attId];
|
||||||
|
return att?.active && (att.level || 1) >= condition.levels![idx];
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all unlocked golems for a player
|
||||||
|
export function getUnlockedGolems(
|
||||||
|
attunements: Record<string, { active: boolean; level: number }>,
|
||||||
|
unlockedElements: string[]
|
||||||
|
): GolemDef[] {
|
||||||
|
return Object.values(GOLEMS_DEF).filter(golem =>
|
||||||
|
isGolemUnlocked(golem.id, attunements, unlockedElements)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate golem damage with skill bonuses
|
||||||
|
export function getGolemDamage(
|
||||||
|
golemId: string,
|
||||||
|
skills: Record<string, number>
|
||||||
|
): number {
|
||||||
|
const golem = GOLEMS_DEF[golemId];
|
||||||
|
if (!golem) return 0;
|
||||||
|
|
||||||
|
let damage = golem.damage;
|
||||||
|
|
||||||
|
// Golem Mastery skill bonus
|
||||||
|
const masteryBonus = 1 + (skills.golemMastery || 0) * 0.1;
|
||||||
|
damage *= masteryBonus;
|
||||||
|
|
||||||
|
return damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate golem attack speed with skill bonuses
|
||||||
|
export function getGolemAttackSpeed(
|
||||||
|
golemId: string,
|
||||||
|
skills: Record<string, number>
|
||||||
|
): number {
|
||||||
|
const golem = GOLEMS_DEF[golemId];
|
||||||
|
if (!golem) return 0;
|
||||||
|
|
||||||
|
let speed = golem.attackSpeed;
|
||||||
|
|
||||||
|
// Golem Efficiency skill bonus
|
||||||
|
const efficiencyBonus = 1 + (skills.golemEfficiency || 0) * 0.05;
|
||||||
|
speed *= efficiencyBonus;
|
||||||
|
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get floors golems can last (base 1, +1 per Golem Longevity skill level)
|
||||||
|
export function getGolemFloorDuration(skills: Record<string, number>): number {
|
||||||
|
return 1 + (skills.golemLongevity || 0);
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ import {
|
|||||||
import { EQUIPMENT_TYPES } from './data/equipment';
|
import { EQUIPMENT_TYPES } from './data/equipment';
|
||||||
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
import { ENCHANTMENT_EFFECTS, calculateEffectCapacityCost } from './data/enchantment-effects';
|
||||||
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
import { ATTUNEMENTS_DEF, getTotalAttunementRegen, getAttunementConversionRate, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from './data/attunements';
|
||||||
|
import { GOLEMS_DEF, getGolemSlots, isGolemUnlocked, getGolemDamage, getGolemAttackSpeed, getGolemFloorDuration } from './data/golems';
|
||||||
|
|
||||||
// Default empty effects for when effects aren't provided
|
// Default empty effects for when effects aren't provided
|
||||||
const DEFAULT_EFFECTS: ComputedEffects = {
|
const DEFAULT_EFFECTS: ComputedEffects = {
|
||||||
@@ -653,6 +654,13 @@ function makeInitial(overrides: Partial<GameState> = {}): GameState {
|
|||||||
skillTiers: overrides.skillTiers || {},
|
skillTiers: overrides.skillTiers || {},
|
||||||
parallelStudyTarget: null,
|
parallelStudyTarget: null,
|
||||||
|
|
||||||
|
// Golemancy
|
||||||
|
golemancy: {
|
||||||
|
enabledGolems: [],
|
||||||
|
summonedGolems: [],
|
||||||
|
lastSummonFloor: 0,
|
||||||
|
},
|
||||||
|
|
||||||
// Achievements
|
// Achievements
|
||||||
achievements: {
|
achievements: {
|
||||||
unlocked: [],
|
unlocked: [],
|
||||||
@@ -744,6 +752,10 @@ interface GameStore extends GameState, CraftingActions {
|
|||||||
// Attunement XP and leveling
|
// Attunement XP and leveling
|
||||||
addAttunementXP: (attunementId: string, amount: number) => void;
|
addAttunementXP: (attunementId: string, amount: number) => void;
|
||||||
|
|
||||||
|
// Golemancy actions
|
||||||
|
toggleGolem: (golemId: string) => void;
|
||||||
|
setEnabledGolems: (golemIds: string[]) => void;
|
||||||
|
|
||||||
// Debug functions
|
// Debug functions
|
||||||
debugUnlockAttunement: (attunementId: string) => void;
|
debugUnlockAttunement: (attunementId: string) => void;
|
||||||
debugAddElementalMana: (element: string, amount: number) => void;
|
debugAddElementalMana: (element: string, amount: number) => void;
|
||||||
@@ -2102,6 +2114,64 @@ export const useGameStore = create<GameStore>()(
|
|||||||
log: [`🔄 Floor ${state.currentFloor} HP reset to full.`, ...state.log.slice(0, 49)],
|
log: [`🔄 Floor ${state.currentFloor} HP reset to full.`, ...state.log.slice(0, 49)],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Golemancy actions
|
||||||
|
toggleGolem: (golemId: string) => {
|
||||||
|
const state = get();
|
||||||
|
const golemDef = GOLEMS_DEF[golemId];
|
||||||
|
if (!golemDef) return;
|
||||||
|
|
||||||
|
// Check if golem is unlocked
|
||||||
|
const unlockedElements = Object.entries(state.elements)
|
||||||
|
.filter(([, e]) => e.unlocked)
|
||||||
|
.map(([id]) => id);
|
||||||
|
|
||||||
|
if (!isGolemUnlocked(golemId, state.attunements, unlockedElements)) return;
|
||||||
|
|
||||||
|
// Get current golem slots
|
||||||
|
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
||||||
|
const maxSlots = getGolemSlots(fabricatorLevel);
|
||||||
|
|
||||||
|
const currentEnabled = state.golemancy.enabledGolems;
|
||||||
|
const isEnabled = currentEnabled.includes(golemId);
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
// Remove from enabled
|
||||||
|
set({
|
||||||
|
golemancy: {
|
||||||
|
...state.golemancy,
|
||||||
|
enabledGolems: currentEnabled.filter(id => id !== golemId),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Check if we have room
|
||||||
|
if (currentEnabled.length >= maxSlots) return;
|
||||||
|
|
||||||
|
// Add to enabled
|
||||||
|
set({
|
||||||
|
golemancy: {
|
||||||
|
...state.golemancy,
|
||||||
|
enabledGolems: [...currentEnabled, golemId],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setEnabledGolems: (golemIds: string[]) => {
|
||||||
|
const state = get();
|
||||||
|
const fabricatorLevel = state.attunements.fabricator?.level || 0;
|
||||||
|
const maxSlots = getGolemSlots(fabricatorLevel);
|
||||||
|
|
||||||
|
// Limit to max slots
|
||||||
|
const limitedIds = golemIds.slice(0, maxSlots);
|
||||||
|
|
||||||
|
set({
|
||||||
|
golemancy: {
|
||||||
|
...state.golemancy,
|
||||||
|
enabledGolems: limitedIds,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'mana-loop-storage',
|
name: 'mana-loop-storage',
|
||||||
@@ -2166,6 +2236,8 @@ export const useGameStore = create<GameStore>()(
|
|||||||
unlockedEffects: state.unlockedEffects,
|
unlockedEffects: state.unlockedEffects,
|
||||||
// Loot inventory
|
// Loot inventory
|
||||||
lootInventory: state.lootInventory,
|
lootInventory: state.lootInventory,
|
||||||
|
// Golemancy
|
||||||
|
golemancy: state.golemancy,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -351,6 +351,20 @@ export interface StudyTarget {
|
|||||||
required: number; // Total hours needed
|
required: number; // Total hours needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Golemancy Types ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface SummonedGolem {
|
||||||
|
golemId: string; // Reference to GOLEMS_DEF
|
||||||
|
summonedFloor: number; // Floor when golem was summoned
|
||||||
|
attackProgress: number; // Progress toward next attack (0-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GolemancyState {
|
||||||
|
enabledGolems: string[]; // Golem IDs the player wants active
|
||||||
|
summonedGolems: SummonedGolem[]; // Currently summoned golems on this floor
|
||||||
|
lastSummonFloor: number; // Floor golems were last summoned on
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameState {
|
export interface GameState {
|
||||||
// Time
|
// Time
|
||||||
day: number;
|
day: number;
|
||||||
@@ -432,6 +446,9 @@ export interface GameState {
|
|||||||
// Parallel Study Target (for Parallel Mind milestone upgrade)
|
// Parallel Study Target (for Parallel Mind milestone upgrade)
|
||||||
parallelStudyTarget: StudyTarget | null;
|
parallelStudyTarget: StudyTarget | null;
|
||||||
|
|
||||||
|
// Golemancy (summoned golems)
|
||||||
|
golemancy: GolemancyState;
|
||||||
|
|
||||||
// Achievements
|
// Achievements
|
||||||
achievements: AchievementState;
|
achievements: AchievementState;
|
||||||
|
|
||||||
|
|||||||
82
worklog.md
82
worklog.md
@@ -551,3 +551,85 @@ Stage Summary:
|
|||||||
- Crystal recipe updated to use light instead of mental
|
- Crystal recipe updated to use light instead of mental
|
||||||
- Documentation updated with banned content policy
|
- Documentation updated with banned content policy
|
||||||
- All tests pass, lint clean
|
- All tests pass, lint clean
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 26
|
||||||
|
Agent: Main
|
||||||
|
Task: Mobile UX improvements, debug options, and enchanting system fix
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- **Moved LootInventoryDisplay to separate LootTab**:
|
||||||
|
- Created new LootTab.tsx component
|
||||||
|
- Better mobile UX - materials no longer clutter main page
|
||||||
|
- Added to tabs in page.tsx
|
||||||
|
- **Moved AchievementsDisplay to separate AchievementsTab**:
|
||||||
|
- Created new AchievementsTab.tsx component
|
||||||
|
- Better mobile UX - achievements have their own space
|
||||||
|
- Added to tabs in page.tsx
|
||||||
|
- **Added skill research debug options to DebugTab**:
|
||||||
|
- "Level Up All Enchanting Skills" button
|
||||||
|
- "Level Up All Research Skills" button
|
||||||
|
- "Unlock All Effects" button
|
||||||
|
- "Max All Skills" button
|
||||||
|
- **Fixed enchanting system**:
|
||||||
|
- Disenchant is now ONLY possible in the Prepare stage
|
||||||
|
- Apply stage no longer shows disenchant/recover buttons
|
||||||
|
- Apply stage filters out already-enchanted gear
|
||||||
|
- Shows message "No unenchanted equipment available. Disenchant in Prepare stage first."
|
||||||
|
- Prepare stage shows disenchant option only for enchanted gear
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- Mobile UX improved with dedicated tabs for Loot and Achievements
|
||||||
|
- Debug options added for testing skill research system
|
||||||
|
- Enchanting system now properly enforces disenchant-only-in-prepare rule
|
||||||
|
- Cannot apply new enchantments to already enchanted gear
|
||||||
|
- Committed and pushed to git (94cc9a1)
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 27
|
||||||
|
Agent: Main
|
||||||
|
Task: Implement Golemancy System
|
||||||
|
|
||||||
|
Work Log:
|
||||||
|
- **Created golem data definitions** (`src/lib/game/data/golems.ts`):
|
||||||
|
- Base golem: Earth Golem (unlocks at Fabricator Level 2)
|
||||||
|
- Elemental variants: Steel (metal), Crystal, Sand golems
|
||||||
|
- Advanced hybrids (Enchanter 5 + Fabricator 5): Lava, Galvanic, Obsidian, Prism, Quicksilver, Voidstone
|
||||||
|
- Each golem has unique stats, mana costs, and special abilities
|
||||||
|
|
||||||
|
- **Added golemancy types to types.ts**:
|
||||||
|
- `SummonedGolem` interface for active golems
|
||||||
|
- `GolemancyState` interface for enabled/summoned golems tracking
|
||||||
|
- Added `golemancy` to GameState
|
||||||
|
|
||||||
|
- **Updated store.ts with golemancy**:
|
||||||
|
- Initialized golemancy state in `makeInitial()`
|
||||||
|
- Added `toggleGolem` and `setEnabledGolems` actions
|
||||||
|
- Added golemancy to persist partialize for save/load
|
||||||
|
|
||||||
|
- **Added golemancy skills to constants.ts**:
|
||||||
|
- Golem Mastery (+10% golem damage)
|
||||||
|
- Golem Efficiency (+5% attack speed)
|
||||||
|
- Golem Longevity (+1 floor duration)
|
||||||
|
- Golem Siphon (-10% maintenance cost)
|
||||||
|
- Advanced Golemancy (unlock hybrid recipes)
|
||||||
|
- Golem Resonance (+1 slot at Fabricator 10)
|
||||||
|
|
||||||
|
- **Created GolemancyTab component**:
|
||||||
|
- Displays available golem slots based on Fabricator level
|
||||||
|
- Shows all unlocked and locked golems
|
||||||
|
- Displays golem stats, costs, and status
|
||||||
|
- Toggle golems on/off for summoning
|
||||||
|
|
||||||
|
- **Updated README.md**:
|
||||||
|
- Added golemancy system documentation
|
||||||
|
- Updated enchanting documentation
|
||||||
|
- Removed familiar system references
|
||||||
|
- Added Banned Content section
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- Golemancy system foundation implemented
|
||||||
|
- Golems unlock based on Fabricator level and mana types
|
||||||
|
- UI for selecting and managing golems
|
||||||
|
- Documentation updated
|
||||||
|
- Lint passes
|
||||||
|
|||||||
Reference in New Issue
Block a user