fix: complete golemancy component-based redesign cleanup
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
- Fix summonGolemsForRoom to use golemLoadout instead of removed enabledGolems - Fix discBonus hardcoded to 0 in golem-combat.ts pipeline (now computed from discipline effects) - Remove deprecated types: SummonedGolem, ActiveGolem, GolemDef - Remove legacy GolemancyState fields: enabledGolems, summonedGolems, legacyActiveGolems - Remove orphaned store actions: toggleGolem, setEnabledGolems - Delete orphaned legacy data files: base-golems.ts, elemental-golems.ts, hybrid-golems.ts - Update GolemDebugSection to use new golemLoadout system - Update golemancy-spec.md §17 to reflect all features as complete - Update all test files to match new type shapes - All 958 tests passing
This commit is contained in:
@@ -154,17 +154,16 @@ describe('ElementDebugSection store interactions', () => {
|
||||
});
|
||||
|
||||
describe('GolemDebugSection store interactions', () => {
|
||||
it('setEnabledGolems is callable with all golem IDs', () => {
|
||||
const mockSet = vi.fn();
|
||||
const allIds = ['stoneGolem', 'fireGolem'];
|
||||
mockSet(allIds);
|
||||
expect(mockSet).toHaveBeenCalledWith(allIds);
|
||||
it('addGolemDesign is callable', () => {
|
||||
const mockAdd = vi.fn();
|
||||
mockAdd({ id: 'test', name: 'Test', coreId: 'basic', frameId: 'earth', mindCircuitId: 'simple', enchantmentIds: [], selectedManaTypes: [], selectedSpells: [] });
|
||||
expect(mockAdd).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('setEnabledGolems is callable with empty array to disable all', () => {
|
||||
const mockSet = vi.fn();
|
||||
mockSet([]);
|
||||
expect(mockSet).toHaveBeenCalledWith([]);
|
||||
it('toggleGolemLoadoutEntry is callable', () => {
|
||||
const mockToggle = vi.fn();
|
||||
mockToggle('test-design');
|
||||
expect(mockToggle).toHaveBeenCalledWith('test-design');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -107,8 +107,10 @@ function EnchantmentCard({ enchant }: { enchant: GolemEnchantmentDefinition }) {
|
||||
}
|
||||
|
||||
export function GolemDebugSection() {
|
||||
const setEnabledGolems = useCombatStore((s) => s.setEnabledGolems);
|
||||
const enabledGolems = useCombatStore((s) => s.golemancy?.enabledGolems) ?? [];
|
||||
const golemLoadout = useCombatStore((s) => s.golemancy?.golemLoadout) ?? [];
|
||||
const golemDesigns = useCombatStore((s) => s.golemancy?.golemDesigns) ?? {};
|
||||
const toggleGolemLoadoutEntry = useCombatStore((s) => s.toggleGolemLoadoutEntry);
|
||||
const addGolemDesign = useCombatStore((s) => s.addGolemDesign);
|
||||
|
||||
const allComponentIds = [
|
||||
...ALL_CORES.map((c) => c.id),
|
||||
@@ -117,18 +119,36 @@ export function GolemDebugSection() {
|
||||
...ALL_GOLEM_ENCHANTMENTS.map((e) => e.id),
|
||||
];
|
||||
|
||||
const handleEnableAll = () => setEnabledGolems(allComponentIds);
|
||||
const handleCreateTestDesigns = () => {
|
||||
// Create a test design from the first core/frame/circuit
|
||||
const core = ALL_CORES[0];
|
||||
const frame = ALL_FRAMES[0];
|
||||
const circuit = ALL_MIND_CIRCUITS[0];
|
||||
if (!core || !frame || !circuit) return;
|
||||
|
||||
const handleDisableAll = () => setEnabledGolems([]);
|
||||
addGolemDesign({
|
||||
id: `test-design-${Date.now()}`,
|
||||
name: `Test ${core.name} ${frame.name}`,
|
||||
coreId: core.id,
|
||||
frameId: frame.id,
|
||||
mindCircuitId: circuit.id,
|
||||
enchantmentIds: [],
|
||||
selectedManaTypes: core.manaTypes.slice(0, 1),
|
||||
selectedSpells: [],
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleComponent = (id: string) => {
|
||||
if (enabledGolems.includes(id)) {
|
||||
setEnabledGolems(enabledGolems.filter((x) => x !== id));
|
||||
} else {
|
||||
setEnabledGolems([...enabledGolems, id]);
|
||||
const handleClearLoadout = () => {
|
||||
// Disable all loadout entries
|
||||
for (const entry of golemLoadout) {
|
||||
if (entry.enabled) {
|
||||
toggleGolemLoadoutEntry(entry.designId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const enabledCount = golemLoadout.filter((e) => e.enabled).length;
|
||||
|
||||
return (
|
||||
<DebugName name="GolemDebugSection">
|
||||
<Card className="bg-gray-900/80 border-gray-700">
|
||||
@@ -140,15 +160,15 @@ export function GolemDebugSection() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button size="sm" variant="outline" onClick={handleEnableAll}>
|
||||
Enable All Components
|
||||
<Button size="sm" variant="outline" onClick={handleCreateTestDesigns}>
|
||||
Create Test Design
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={handleDisableAll}>
|
||||
Disable All
|
||||
<Button size="sm" variant="outline" onClick={handleClearLoadout}>
|
||||
Disable All Loadout
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
Enabled: {enabledGolems.length} / {allComponentIds.length}
|
||||
Designs: {Object.keys(golemDesigns).length} · Loadout: {enabledCount} / {golemLoadout.length} enabled
|
||||
</div>
|
||||
|
||||
{/* ─── Cores ─────────────────────────────────────────────── */}
|
||||
@@ -160,14 +180,6 @@ export function GolemDebugSection() {
|
||||
{ALL_CORES.map((core) => (
|
||||
<div key={core.id} className="relative">
|
||||
<CoreCard core={core} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={enabledGolems.includes(core.id) ? 'default' : 'outline'}
|
||||
className="absolute top-1 right-1 h-6 px-2 text-[10px]"
|
||||
onClick={() => handleToggleComponent(core.id)}
|
||||
>
|
||||
{enabledGolems.includes(core.id) ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -182,14 +194,6 @@ export function GolemDebugSection() {
|
||||
{ALL_FRAMES.map((frame) => (
|
||||
<div key={frame.id} className="relative">
|
||||
<FrameCard frame={frame} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={enabledGolems.includes(frame.id) ? 'default' : 'outline'}
|
||||
className="absolute top-1 right-1 h-6 px-2 text-[10px]"
|
||||
onClick={() => handleToggleComponent(frame.id)}
|
||||
>
|
||||
{enabledGolems.includes(frame.id) ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -204,14 +208,6 @@ export function GolemDebugSection() {
|
||||
{ALL_MIND_CIRCUITS.map((circuit) => (
|
||||
<div key={circuit.id} className="relative">
|
||||
<MindCircuitCard circuit={circuit} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={enabledGolems.includes(circuit.id) ? 'default' : 'outline'}
|
||||
className="absolute top-1 right-1 h-6 px-2 text-[10px]"
|
||||
onClick={() => handleToggleComponent(circuit.id)}
|
||||
>
|
||||
{enabledGolems.includes(circuit.id) ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -226,14 +222,6 @@ export function GolemDebugSection() {
|
||||
{ALL_GOLEM_ENCHANTMENTS.map((enchant) => (
|
||||
<div key={enchant.id} className="relative">
|
||||
<EnchantmentCard enchant={enchant} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={enabledGolems.includes(enchant.id) ? 'default' : 'outline'}
|
||||
className="absolute top-1 right-1 h-6 px-2 text-[10px]"
|
||||
onClick={() => handleToggleComponent(enchant.id)}
|
||||
>
|
||||
{enabledGolems.includes(enchant.id) ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -242,10 +242,16 @@ describe('canAffordGolemDesign', () => {
|
||||
// ─── Test: Combat store golemancy state ───────────────────────────────────────
|
||||
|
||||
describe('Combat store golemancy', () => {
|
||||
it('toggleGolem is a function', async () => {
|
||||
it('toggleGolemLoadoutEntry is a function', async () => {
|
||||
const { useCombatStore } = await import('@/lib/game/stores/combatStore');
|
||||
const state = useCombatStore.getState();
|
||||
expect(typeof state.toggleGolem).toBe('function');
|
||||
expect(typeof state.toggleGolemLoadoutEntry).toBe('function');
|
||||
});
|
||||
|
||||
it('addGolemDesign is a function', async () => {
|
||||
const { useCombatStore } = await import('@/lib/game/stores/combatStore');
|
||||
const state = useCombatStore.getState();
|
||||
expect(typeof state.addGolemDesign).toBe('function');
|
||||
});
|
||||
|
||||
it('golemancy state has correct shape', async () => {
|
||||
|
||||
@@ -36,7 +36,7 @@ function resetStores() {
|
||||
roomResetState: {},
|
||||
clearedRooms: {},
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -52,7 +52,7 @@ export function resetAllStores() {
|
||||
roomResetState: {},
|
||||
clearedRooms: {},
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -39,7 +39,7 @@ function resetStores() {
|
||||
roomResetState: {},
|
||||
clearedRooms: {},
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -38,7 +38,7 @@ function resetStores() {
|
||||
roomResetState: {},
|
||||
clearedRooms: {},
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -37,7 +37,7 @@ function resetStores() {
|
||||
roomResetState: {},
|
||||
clearedRooms: {},
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -18,7 +18,7 @@ function resetCombatStore() {
|
||||
clearedFloors: {},
|
||||
climbDirection: null,
|
||||
isDescending: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -29,7 +29,7 @@ function resetCombatStore() {
|
||||
clearedFloors: {},
|
||||
climbDirection: null,
|
||||
isDescending: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -46,7 +46,7 @@ function resetAllStores() {
|
||||
clearedFloors: {},
|
||||
climbDirection: null,
|
||||
isDescending: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
equipmentSpellStates: [],
|
||||
comboHitCount: 0,
|
||||
floorHitCount: 0,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// ─── Base Golem Definitions ───────────────────────────────────
|
||||
|
||||
import type { GolemDef } from './types';
|
||||
import { elemCost } from './types';
|
||||
|
||||
export const BASE_GOLEMS: 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,
|
||||
maxRoomDuration: 3,
|
||||
},
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
// ─── Elemental Variant Golems ───────────────────────────────────
|
||||
|
||||
import type { GolemDef } from './types';
|
||||
import { elemCost } from './types';
|
||||
|
||||
export const ELEMENTAL_GOLEMS: Record<string, GolemDef> = {
|
||||
// ─── 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,
|
||||
maxRoomDuration: 3,
|
||||
},
|
||||
|
||||
// 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,
|
||||
maxRoomDuration: 4,
|
||||
},
|
||||
|
||||
// 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', 10), elemCost('earth', 4)],
|
||||
maintenanceCost: [elemCost('sand', 0.6), elemCost('earth', 0.25)],
|
||||
damage: 10,
|
||||
attackSpeed: 2.0,
|
||||
hp: 45,
|
||||
armorPierce: 0.15,
|
||||
isAoe: true,
|
||||
aoeTargets: 2,
|
||||
unlockCondition: {
|
||||
type: 'mana_unlocked',
|
||||
manaType: 'sand',
|
||||
},
|
||||
tier: 2,
|
||||
maxRoomDuration: 3,
|
||||
specialAbilities: [
|
||||
{ name: 'Sandstorm', description: 'Hits multiple enemies (AoE)' },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,163 +0,0 @@
|
||||
// ─── Advanced Hybrid Golems ────────────────────────────────────
|
||||
// Require Enchanter 5 + Fabricator 5
|
||||
|
||||
import type { GolemDef } from './types';
|
||||
import { elemCost } from './types';
|
||||
|
||||
export const HYBRID_GOLEMS: Record<string, GolemDef> = {
|
||||
// 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', 15), elemCost('fire', 12)],
|
||||
maintenanceCost: [elemCost('earth', 0.6), elemCost('fire', 0.7)],
|
||||
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,
|
||||
maxRoomDuration: 4,
|
||||
specialAbilities: [
|
||||
{ name: 'Burn', description: 'Burns enemies over time (DoT)' },
|
||||
],
|
||||
},
|
||||
|
||||
// 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', 12), elemCost('lightning', 8)],
|
||||
maintenanceCost: [elemCost('metal', 0.4), elemCost('lightning', 0.7)],
|
||||
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,
|
||||
maxRoomDuration: 4,
|
||||
specialAbilities: [
|
||||
{ name: 'Lightning Speed', description: 'Extremely fast attacks bonus' },
|
||||
],
|
||||
},
|
||||
|
||||
// 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', 18), elemCost('dark', 10)],
|
||||
maintenanceCost: [elemCost('earth', 0.5), elemCost('dark', 0.6)],
|
||||
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,
|
||||
maxRoomDuration: 5,
|
||||
specialAbilities: [
|
||||
{ name: 'Devastating Strike', description: 'Devastating single-target damage' },
|
||||
],
|
||||
},
|
||||
|
||||
// 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', 16), elemCost('light', 10)],
|
||||
maintenanceCost: [elemCost('crystal', 0.6), elemCost('light', 0.6)],
|
||||
damage: 28,
|
||||
attackSpeed: 2.0,
|
||||
hp: 60,
|
||||
armorPierce: 0.45,
|
||||
isAoe: true,
|
||||
aoeTargets: 3,
|
||||
unlockCondition: {
|
||||
type: 'dual_attunement',
|
||||
attunements: ['enchanter', 'fabricator'],
|
||||
levels: [5, 5],
|
||||
},
|
||||
tier: 4,
|
||||
maxRoomDuration: 5,
|
||||
specialAbilities: [
|
||||
{ name: 'Piercing Beams', description: 'Channels light into piercing beams' },
|
||||
],
|
||||
},
|
||||
|
||||
// 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', 10), elemCost('water', 8)],
|
||||
maintenanceCost: [elemCost('metal', 0.4), elemCost('water', 0.4)],
|
||||
damage: 14,
|
||||
attackSpeed: 4.0,
|
||||
hp: 55,
|
||||
armorPierce: 0.35,
|
||||
isAoe: false,
|
||||
aoeTargets: 1,
|
||||
unlockCondition: {
|
||||
type: 'dual_attunement',
|
||||
attunements: ['enchanter', 'fabricator'],
|
||||
levels: [5, 5],
|
||||
},
|
||||
tier: 3,
|
||||
maxRoomDuration: 4,
|
||||
specialAbilities: [
|
||||
{ name: 'Flow', description: 'Flows around defenses, fast & hard to dodge' },
|
||||
],
|
||||
},
|
||||
|
||||
// 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', 22), elemCost('void', 14)],
|
||||
maintenanceCost: [elemCost('earth', 0.5), elemCost('void', 0.9)],
|
||||
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,
|
||||
maxRoomDuration: 5,
|
||||
specialAbilities: [
|
||||
{ name: 'Void Infusion', description: 'Earth infused with void energy' },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -27,5 +27,4 @@ export { FRAMES, ALL_FRAMES } from './frames';
|
||||
export { MIND_CIRCUITS, ALL_MIND_CIRCUITS } from './mindCircuits';
|
||||
export { GOLEM_ENCHANTMENTS, ALL_GOLEM_ENCHANTMENTS } from './golemEnchantments';
|
||||
|
||||
// Legacy re-exports (deprecated, kept for migration)
|
||||
export type { GolemDef } from './types';
|
||||
|
||||
|
||||
@@ -149,31 +149,4 @@ export interface ActiveGolemV2 {
|
||||
spellCastIndex: number;
|
||||
}
|
||||
|
||||
// ─── Legacy Type (kept for backward compat during migration) ────────────
|
||||
|
||||
/** @deprecated Use GolemDesign instead */
|
||||
export interface GolemDef {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
baseManaType: string;
|
||||
summonCost: GolemManaCost[];
|
||||
maintenanceCost: GolemManaCost[];
|
||||
damage: number;
|
||||
attackSpeed: number;
|
||||
hp: number;
|
||||
armorPierce: number;
|
||||
isAoe: boolean;
|
||||
aoeTargets: number;
|
||||
unlockCondition: {
|
||||
type: 'attunement_level' | 'mana_unlocked' | 'dual_attunement';
|
||||
attunement?: string;
|
||||
level?: number;
|
||||
manaType?: string;
|
||||
attunements?: string[];
|
||||
levels?: number[];
|
||||
};
|
||||
tier: number;
|
||||
maxRoomDuration: number;
|
||||
specialAbilities?: { name: string; description: string }[];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||
import type { CombatStore, CombatState } from './combat-state.types';
|
||||
import type { SpellState, EnemyState, EquipmentInstance, FloorState } from '../types';
|
||||
import { applyOnHitEffect, processDoTPhase } from './dot-runtime';
|
||||
import type { ActiveGolem, RuntimeActiveGolem } from '../types';
|
||||
import type { RuntimeActiveGolem } from '../types';
|
||||
import { getFloorMaxHP, getFloorElement, getMultiElementBonus, calcDamage, calcMeleeDamage, canAffordSpellCost, deductSpellCost } from '../utils';
|
||||
import { computeDisciplineEffects } from '../effects/discipline-effects';
|
||||
import {
|
||||
|
||||
@@ -142,16 +142,17 @@ export function advanceRoomOrFloor(get: GetFn, set: SetFn): void {
|
||||
function summonGolemsForRoom(get: GetFn, set: SetFn): void {
|
||||
const s = get();
|
||||
const manaState = useManaStore.getState();
|
||||
const enabledGolems = s.golemancy?.enabledGolems ?? [];
|
||||
if (enabledGolems.length === 0) return;
|
||||
const loadout = s.golemancy?.golemLoadout ?? [];
|
||||
if (loadout.length === 0) return;
|
||||
|
||||
const currentActiveGolems = s.golemancy?.activeGolems ?? [];
|
||||
const summonResult = summonGolemsOnRoomEntry(
|
||||
enabledGolems,
|
||||
loadout,
|
||||
manaState.rawMana,
|
||||
manaState.elements,
|
||||
s.currentFloor,
|
||||
currentActiveGolems,
|
||||
0, // discBonus — computed in pipeline, not here
|
||||
);
|
||||
|
||||
if (summonResult.logMessages.length > 0) {
|
||||
@@ -244,7 +245,7 @@ export function createEnterSpireMode(get: GetFn, set: SetFn) {
|
||||
roomResetState: {},
|
||||
descentPeak: null,
|
||||
isDescentComplete: false,
|
||||
golemancy: { enabledGolems: [], summonedGolems: [], activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [], legacyActiveGolems: [] },
|
||||
golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
|
||||
});
|
||||
|
||||
get().addActivityLog('floor_transition',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// ─── Combat State Types ────────────────────────────────────────────────────────
|
||||
// Shared types for combat store and combat actions to avoid circular dependency
|
||||
|
||||
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, ActiveGolem, RuntimeActiveGolem, EnemyState, EquipmentInstance, SerializedGolemDesign } from '../types';
|
||||
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, RuntimeActiveGolem, EnemyState, EquipmentInstance, SerializedGolemDesign } from '../types';
|
||||
|
||||
/** Signature for the advanceRoomOrFloor callback to break circular dependency */
|
||||
export type AdvanceRoomFn = (get: () => CombatStore, set: (s: Partial<CombatState>) => void) => void;
|
||||
@@ -128,8 +128,6 @@ export interface CombatActions {
|
||||
stayLongerInRoom: () => void;
|
||||
|
||||
// Golemancy
|
||||
toggleGolem: (golemId: string) => void;
|
||||
setEnabledGolems: (golemIds: string[]) => void;
|
||||
addGolemDesign: (design: SerializedGolemDesign) => void;
|
||||
removeGolemDesign: (designId: string) => void;
|
||||
toggleGolemLoadoutEntry: (designId: string) => void;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { createSafeStorage } from '../utils/safe-persist';
|
||||
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, ActiveGolem, RuntimeActiveGolem, EnemyState, EquipmentInstance } from '../types';
|
||||
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType, RuntimeActiveGolem, EnemyState, EquipmentInstance } from '../types';
|
||||
import { getFloorMaxHP } from '../utils';
|
||||
import { generateSpireFloorState, getRoomsForFloor } from '../utils/spire-utils';
|
||||
import { addActivityLogEntry } from '../utils/activity-log';
|
||||
@@ -55,15 +55,10 @@ export const useCombatStore = create<CombatStore>()(
|
||||
|
||||
// Golemancy (component-based)
|
||||
golemancy: {
|
||||
// New component-based fields
|
||||
golemDesigns: {},
|
||||
golemLoadout: [],
|
||||
activeGolems: [] as RuntimeActiveGolem[],
|
||||
lastSummonFloor: 0,
|
||||
// Legacy fields (deprecated)
|
||||
enabledGolems: [],
|
||||
summonedGolems: [],
|
||||
legacyActiveGolems: [],
|
||||
},
|
||||
|
||||
// Equipment spell states
|
||||
@@ -204,7 +199,7 @@ export const useCombatStore = create<CombatStore>()(
|
||||
currentRoomIndex: 0,
|
||||
roomsPerFloor: 1,
|
||||
maxFloorReached: Math.max(s.maxFloorReached, 1),
|
||||
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[], summonedGolems: [], legacyActiveGolems: [] },
|
||||
golemancy: { ...s.golemancy, activeGolems: [] as RuntimeActiveGolem[] },
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -224,27 +219,6 @@ export const useCombatStore = create<CombatStore>()(
|
||||
stayLongerInRoom: () => stayLongerInRoom(get, set),
|
||||
|
||||
// Golemancy
|
||||
toggleGolem: (golemId: string) => {
|
||||
set((s) => {
|
||||
const enabledGolems = s.golemancy?.enabledGolems || [];
|
||||
const isEnabled = enabledGolems.includes(golemId);
|
||||
return {
|
||||
golemancy: {
|
||||
...s.golemancy,
|
||||
enabledGolems: isEnabled
|
||||
? enabledGolems.filter(id => id !== golemId)
|
||||
: [...enabledGolems, golemId]
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setEnabledGolems: (golemIds: string[]) => {
|
||||
set((s) => ({
|
||||
golemancy: { ...s.golemancy, enabledGolems: golemIds },
|
||||
}));
|
||||
},
|
||||
|
||||
addGolemDesign: (d) => addGolemDesign(set, d),
|
||||
removeGolemDesign: (id) => removeGolemDesign(set, id),
|
||||
toggleGolemLoadoutEntry: (id) => toggleGolemLoadoutEntry(set, id),
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
countdownGolemRoomDuration,
|
||||
} from '../golem-combat-actions';
|
||||
import { useAttunementStore } from '../attunementStore';
|
||||
import { computeDisciplineEffects } from '../../effects/discipline-effects';
|
||||
import type { RuntimeActiveGolem } from '../../types';
|
||||
|
||||
export interface GolemCombatContext {
|
||||
@@ -107,7 +108,8 @@ export function processGolemRoomEntry(
|
||||
const cs = useCombatStore.getState();
|
||||
const attStore = useAttunementStore.getState();
|
||||
const fabLevel = attStore.attunements?.fabricator?.level ?? 0;
|
||||
const discBonus = 0; // TODO: compute from discipline
|
||||
const discEffects = computeDisciplineEffects();
|
||||
const discBonus = Math.floor(discEffects.bonuses.golemCapacity || 0);
|
||||
|
||||
return summonGolemsOnRoomEntry(
|
||||
loadout as any,
|
||||
|
||||
@@ -49,8 +49,6 @@ export type {
|
||||
GameAction,
|
||||
ScheduleBlock,
|
||||
StudyTarget,
|
||||
SummonedGolem,
|
||||
ActiveGolem,
|
||||
GolemancyState,
|
||||
GolemLoadoutEntry,
|
||||
RuntimeActiveGolem,
|
||||
|
||||
@@ -144,19 +144,6 @@ export interface StudyTarget {
|
||||
|
||||
// ─── Golemancy Types ─────────────────────────────────────────────────────────
|
||||
|
||||
/** @deprecated Legacy type for predefined golems. Use GolemDesign instead. */
|
||||
export interface SummonedGolem {
|
||||
golemId: string;
|
||||
summonedFloor: number;
|
||||
attackProgress: number;
|
||||
roomsRemaining: number;
|
||||
}
|
||||
|
||||
/** @deprecated Legacy type. Use ActiveGolemV2 instead. */
|
||||
export interface ActiveGolem extends SummonedGolem {
|
||||
// attackProgress is inherited from SummonedGolem
|
||||
}
|
||||
|
||||
/**
|
||||
* Player-designed golem loadout entry.
|
||||
* Each entry is a complete golem design (Core + Frame + Mind Circuit + Enchantments).
|
||||
@@ -201,11 +188,6 @@ export interface GolemancyState {
|
||||
activeGolems: RuntimeActiveGolem[];
|
||||
/** Floor golems were last summoned on */
|
||||
lastSummonFloor: number;
|
||||
// Legacy fields kept for backward compatibility during migration
|
||||
enabledGolems: string[];
|
||||
summonedGolems: SummonedGolem[];
|
||||
/** @deprecated Use activeGolems instead (RuntimeActiveGolem[]) */
|
||||
legacyActiveGolems: ActiveGolem[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@ export type {
|
||||
GameAction,
|
||||
ScheduleBlock,
|
||||
StudyTarget,
|
||||
SummonedGolem,
|
||||
ActiveGolem,
|
||||
GolemancyState,
|
||||
GolemLoadoutEntry,
|
||||
RuntimeActiveGolem,
|
||||
|
||||
Reference in New Issue
Block a user