Refactor large files into modular components
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m9s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m9s
- Refactored page.tsx (613→252 lines) with GameOverScreen and LeftPanel extracted - Refactored StatsTab.tsx (584→92 lines) with section components - Refactored SkillsTab.tsx (434→54 lines) with sub-components - Created modular structure for GameContext, LootInventory, and other components - All extracted components organized into feature directories
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
// ─── 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,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// ─── 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,
|
||||
},
|
||||
|
||||
// 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,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
// ─── 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', 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,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// ─── Golem Definitions Index ─────────────────────────────────
|
||||
// Re-exports from all golem modules
|
||||
|
||||
// Re-export types
|
||||
export type { GolemDef, GolemManaCost } from './types';
|
||||
|
||||
// Re-export data
|
||||
export { GOLEMS_DEF } from './data';
|
||||
|
||||
// Re-export utility functions
|
||||
export {
|
||||
getGolemSlots,
|
||||
isGolemUnlocked,
|
||||
getUnlockedGolems,
|
||||
getGolemDamage,
|
||||
getGolemAttackSpeed,
|
||||
getGolemFloorDuration,
|
||||
getGolemMaintenanceMultiplier,
|
||||
canAffordGolemSummon,
|
||||
deductGolemSummonCost,
|
||||
canAffordGolemMaintenance,
|
||||
deductGolemMaintenance,
|
||||
} from './utils';
|
||||
@@ -0,0 +1,42 @@
|
||||
// ─── Golem Types ─────────────────────────────────────────────────
|
||||
|
||||
import type { SpellCost } from '../types';
|
||||
|
||||
// Golem mana cost helper
|
||||
export function elemCost(element: string, amount: number): SpellCost {
|
||||
return { type: 'element', element, amount };
|
||||
}
|
||||
|
||||
export 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)
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// ─── Golem Helper Functions ─────────────────────────
|
||||
|
||||
import type { GolemDef, GolemManaCost } from './types';
|
||||
import { GOLEMS_DEF } from './index';
|
||||
|
||||
// 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)
|
||||
) as GolemDef[];
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Get maintenance cost multiplier (Golem Siphon reduces by 10% per level)
|
||||
export function getGolemMaintenanceMultiplier(skills: Record<string, number>): number {
|
||||
return 1 - (skills.golemSiphon || 0) * 0.1;
|
||||
}
|
||||
|
||||
// Check if player can afford golem summon cost
|
||||
export function canAffordGolemSummon(
|
||||
golemId: string,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>
|
||||
): boolean {
|
||||
const golem = GOLEMS_DEF[golemId];
|
||||
if (!golem) return false;
|
||||
|
||||
for (const cost of golem.summonCost) {
|
||||
if (cost.type === 'raw') {
|
||||
if (rawMana < cost.amount) return false;
|
||||
} else if (cost.element) {
|
||||
const elem = elements[cost.element];
|
||||
if (!elem || !elem.unlocked || elem.current < cost.amount) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deduct golem summon cost from mana pools
|
||||
export function deductGolemSummonCost(
|
||||
golemId: string,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>
|
||||
): { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> } {
|
||||
const golem = GOLEMS_DEF[golemId];
|
||||
if (!golem) return { rawMana, elements };
|
||||
|
||||
let newRawMana = rawMana;
|
||||
let newElements = { ...elements };
|
||||
|
||||
for (const cost of golem.summonCost) {
|
||||
if (cost.type === 'raw') {
|
||||
newRawMana -= cost.amount;
|
||||
} else if (cost.element && newElements[cost.element]) {
|
||||
newElements = {
|
||||
...newElements,
|
||||
[cost.element]: {
|
||||
...newElements[cost.element],
|
||||
current: newElements[cost.element].current - cost.amount,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { rawMana: newRawMana, elements: newElements };
|
||||
}
|
||||
|
||||
// Check if player can afford golem maintenance for one tick
|
||||
export function canAffordGolemMaintenance(
|
||||
golemId: string,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
skills: Record<string, number>
|
||||
): boolean {
|
||||
const golem = GOLEMS_DEF[golemId];
|
||||
if (!golem) return false;
|
||||
|
||||
const maintenanceMult = getGolemMaintenanceMultiplier(skills);
|
||||
|
||||
for (const cost of golem.maintenanceCost) {
|
||||
const adjustedAmount = cost.amount * maintenanceMult;
|
||||
if (cost.type === 'raw') {
|
||||
if (rawMana < adjustedAmount) return false;
|
||||
} else if (cost.element) {
|
||||
const elem = elements[cost.element];
|
||||
if (!elem || !elem.unlocked || elem.current < adjustedAmount) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deduct golem maintenance cost for one tick
|
||||
export function deductGolemMaintenance(
|
||||
golemId: string,
|
||||
rawMana: number,
|
||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||
skills: Record<string, number>
|
||||
): { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> } {
|
||||
const golem = GOLEMS_DEF[golemId];
|
||||
if (!golem) return { rawMana, elements };
|
||||
|
||||
const maintenanceMult = getGolemMaintenanceMultiplier(skills);
|
||||
|
||||
let newRawMana = rawMana;
|
||||
let newElements = { ...elements };
|
||||
|
||||
for (const cost of golem.maintenanceCost) {
|
||||
const adjustedAmount = cost.amount * maintenanceMult;
|
||||
if (cost.type === 'raw') {
|
||||
newRawMana -= adjustedAmount;
|
||||
} else if (cost.element && newElements[cost.element]) {
|
||||
newElements = {
|
||||
...newElements,
|
||||
[cost.element]: {
|
||||
...newElements[cost.element],
|
||||
current: newElements[cost.element].current - adjustedAmount,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { rawMana: newRawMana, elements: newElements };
|
||||
}
|
||||
Reference in New Issue
Block a user