186 lines
7.6 KiB
TypeScript
Executable File
186 lines
7.6 KiB
TypeScript
Executable File
// ─── Attunement Definitions ─────────────────────────────────────────────────────
|
|
// Attunements are class-like abilities tied to body locations
|
|
// Each provides unique capabilities, primary mana types, and skill access
|
|
|
|
import type { AttunementDef, AttunementSlot } from '../types';
|
|
|
|
// Attunement slot display names
|
|
export const ATTUNEMENT_SLOT_NAMES: Record<AttunementSlot, string> = {
|
|
rightHand: 'Right Hand',
|
|
leftHand: 'Left Hand',
|
|
head: 'Head',
|
|
back: 'Back',
|
|
chest: 'Chest',
|
|
leftLeg: 'Left Leg',
|
|
rightLeg: 'Right Leg',
|
|
};
|
|
|
|
// All attunement definitions
|
|
export const ATTUNEMENTS_DEF: Record<string, AttunementDef> = {
|
|
// ─── Enchanter (Right Hand) ─────────────────────────────────────────────────
|
|
// Unlocks the enchanting system - applying magical effects to equipment
|
|
// Primary mana: Transference (used to move/apply enchantments)
|
|
enchanter: {
|
|
id: 'enchanter',
|
|
name: 'Enchanter',
|
|
desc: 'Channel transference mana through your right hand to apply magical enchantments to equipment. The art of enchanting allows you to imbue items with spell effects, stat bonuses, and special properties.',
|
|
slot: 'rightHand',
|
|
icon: '✨',
|
|
color: '#1ABC9C', // Teal (transference color)
|
|
primaryManaType: 'transference',
|
|
rawManaRegen: 0.5,
|
|
conversionRate: 0.2, // Converts 0.2 raw mana to transference per hour
|
|
unlocked: true, // Starting attunement
|
|
capabilities: ['enchanting', 'disenchanting'],
|
|
skillCategories: ['enchant', 'effectResearch'],
|
|
},
|
|
|
|
// ─── Invoker (Chest/Heart) ───────────────────────────────────────────────────
|
|
// Enables forming pacts with spire guardians
|
|
// No primary mana - instead gains mana types from each pact signed
|
|
invoker: {
|
|
id: 'invoker',
|
|
name: 'Invoker',
|
|
desc: 'Open your heart to the guardians of the spire. Form pacts with defeated guardians to gain their elemental affinity and access to their unique powers. Each pact grants access to a new mana type.',
|
|
slot: 'chest',
|
|
icon: '💜',
|
|
color: '#9B59B6', // Purple
|
|
primaryManaType: undefined, // Invoker has no primary - gains from pacts
|
|
rawManaRegen: 0.3,
|
|
conversionRate: 0, // No automatic conversion - mana comes from pacts
|
|
unlocked: false, // Unlocked through gameplay
|
|
unlockCondition: 'Defeat your first guardian and choose the path of the Invoker',
|
|
capabilities: ['pacts', 'guardianPowers', 'elementalMastery'],
|
|
skillCategories: ['invocation', 'pact'],
|
|
},
|
|
|
|
// ─── Fabricator (Left Hand) ──────────────────────────────────────────────────
|
|
// Crafts earth golems and earthen gear
|
|
// Primary mana: Earth
|
|
// Later with fire mana -> metal mana, can craft metallic gear and golems
|
|
fabricator: {
|
|
id: 'fabricator',
|
|
name: 'Fabricator',
|
|
desc: 'Shape earth and metal through your left hand to craft golems and equipment. Start with earthen constructs, and unlock metalworking when you gain fire mana to create metal mana.',
|
|
slot: 'leftHand',
|
|
icon: '⚒️',
|
|
color: '#F4A261', // Earth color
|
|
primaryManaType: 'earth',
|
|
rawManaRegen: 0.4,
|
|
conversionRate: 0.25, // Converts 0.25 raw mana to earth per hour
|
|
unlocked: false, // Unlocked through gameplay
|
|
unlockCondition: 'Prove your worth as a crafter',
|
|
capabilities: ['golemCrafting', 'gearCrafting', 'earthShaping'],
|
|
skillCategories: ['fabrication', 'golemancy'],
|
|
},
|
|
};
|
|
|
|
// Helper function to get attunement by slot
|
|
export function getAttunementBySlot(slot: AttunementSlot): AttunementDef | undefined {
|
|
return Object.values(ATTUNEMENTS_DEF).find(a => a.slot === slot);
|
|
}
|
|
|
|
// Helper function to get all unlocked attunements for a player
|
|
export function getUnlockedAttunements(attunements: Record<string, { active: boolean; level: number; experience: number }>): AttunementDef[] {
|
|
return Object.entries(attunements)
|
|
.filter(([id, state]) => state.active || ATTUNEMENTS_DEF[id]?.unlocked)
|
|
.map(([id]) => ATTUNEMENTS_DEF[id])
|
|
.filter(Boolean);
|
|
}
|
|
|
|
// Helper function to calculate total raw mana regen from attunements (with level scaling)
|
|
export function getTotalAttunementRegen(attunements: Record<string, { active: boolean; level: number; experience: number }>): number {
|
|
return Object.entries(attunements)
|
|
.filter(([, state]) => state.active)
|
|
.reduce((total, [id, state]) => {
|
|
const def = ATTUNEMENTS_DEF[id];
|
|
if (!def) return total;
|
|
// Exponential scaling: base * (1.5 ^ (level - 1))
|
|
const levelMult = Math.pow(1.5, (state.level || 1) - 1);
|
|
return total + def.rawManaRegen * levelMult;
|
|
}, 0);
|
|
}
|
|
|
|
// Get conversion rate with level scaling
|
|
export function getAttunementConversionRate(attunementId: string, level: number): number {
|
|
const def = ATTUNEMENTS_DEF[attunementId];
|
|
if (!def || def.conversionRate <= 0) return 0;
|
|
// Exponential scaling: base * (1.5 ^ (level - 1))
|
|
return def.conversionRate * Math.pow(1.5, (level || 1) - 1);
|
|
}
|
|
|
|
// XP required for attunement level
|
|
export function getAttunementXPForLevel(level: number): number {
|
|
// New scaling:
|
|
// Level 2: 1000 XP
|
|
// Level 3: 2500 XP
|
|
// Level 4: 5000 XP
|
|
// Level 5: 10000 XP
|
|
// etc. (each level requires 2x the previous, starting from 1000)
|
|
if (level <= 1) return 0;
|
|
if (level === 2) return 1000;
|
|
// For level 3+: 1000 * 2.5^(level-2), but rounded nicely
|
|
return Math.floor(1000 * Math.pow(2, level - 2) * (level >= 3 ? 1.25 : 1));
|
|
}
|
|
|
|
// Calculate XP gained from enchanting based on capacity used
|
|
export function calculateEnchantingXP(capacityUsed: number): number {
|
|
// 1 XP per 10 capacity used, floored, minimum 1
|
|
return Math.max(1, Math.floor(capacityUsed / 10));
|
|
}
|
|
|
|
// Max attunement level
|
|
export const MAX_ATTUNEMENT_LEVEL = 10;
|
|
|
|
// Helper function to get mana types from active attunements and pacts
|
|
export function getAttunementManaTypes(
|
|
attunements: Record<string, { active: boolean; level: number; experience: number }>,
|
|
signedPacts: number[]
|
|
): string[] {
|
|
const manaTypes: string[] = [];
|
|
|
|
// Add primary mana types from active attunements
|
|
Object.entries(attunements)
|
|
.filter(([, state]) => state.active)
|
|
.forEach(([id]) => {
|
|
const def = ATTUNEMENTS_DEF[id];
|
|
if (def?.primaryManaType) {
|
|
manaTypes.push(def.primaryManaType);
|
|
}
|
|
});
|
|
|
|
// Invoker gains mana types from signed pacts
|
|
if (attunements.invoker?.active && signedPacts.length > 0) {
|
|
// Import GUARDIANS would be circular, so this is handled in the store
|
|
// For now, just mark that invoker provides pact-based mana
|
|
manaTypes.push('pactElements');
|
|
}
|
|
|
|
return [...new Set(manaTypes)]; // Remove duplicates
|
|
}
|
|
|
|
// Get skill categories available to player based on active attunements
|
|
export function getAvailableSkillCategories(
|
|
attunements: Record<string, { active: boolean; level: number; experience: number }>
|
|
): string[] {
|
|
const categories = new Set<string>();
|
|
|
|
// Always available categories
|
|
categories.add('mana');
|
|
categories.add('study');
|
|
categories.add('research');
|
|
categories.add('ascension');
|
|
|
|
// Add categories from active attunements
|
|
Object.entries(attunements)
|
|
.filter(([, state]) => state.active)
|
|
.forEach(([id]) => {
|
|
const def = ATTUNEMENTS_DEF[id];
|
|
if (def?.skillCategories) {
|
|
def.skillCategories.forEach(cat => categories.add(cat));
|
|
}
|
|
});
|
|
|
|
return Array.from(categories);
|
|
}
|