feat: Implement attunement system with 3 attunements
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m14s

- Add attunement types and state to game state
- Create attunements.ts with Enchanter, Invoker, Fabricator definitions
- Player starts with Enchanter attunement (right hand)
- Enchanter: transference mana, unlocks enchanting
- Invoker: gains mana types from pacts with guardians
- Fabricator: earth mana, crafts golems and earthen/metal gear
- Skills now have attunement field for categorization
- Update skill categories to be attunement-based
This commit is contained in:
2026-03-27 16:53:35 +00:00
parent b2262fd6ac
commit c51c8d8ff4
5 changed files with 813 additions and 1555 deletions

View File

@@ -747,14 +747,28 @@ export const PRESTIGE_DEF: Record<string, PrestigeDef> = {
};
// ─── Skill Categories ─────────────────────────────────────────────────────────
// Skills are now organized by attunement - each attunement grants access to specific skill categories
export const SKILL_CATEGORIES = [
{ id: 'mana', name: 'Mana', icon: '💧' },
{ id: 'enchant', name: 'Enchanting', icon: '✨' },
{ id: 'effectResearch', name: 'Effect Research', icon: '🔬' },
{ id: 'study', name: 'Study', icon: '📚' },
{ id: 'craft', name: 'Crafting', icon: '🔧' },
{ id: 'research', name: 'Research', icon: '🔮' },
{ id: 'ascension', name: 'Ascension', icon: '⭐' },
// Core categories (always available)
{ id: 'mana', name: 'Mana', icon: '💧', attunement: null },
{ id: 'study', name: 'Study', icon: '📚', attunement: null },
{ id: 'research', name: 'Research', icon: '🔮', attunement: null },
{ id: 'ascension', name: 'Ascension', icon: '⭐', attunement: null },
// Enchanter attunement (Right Hand)
{ id: 'enchant', name: 'Enchanting', icon: '✨', attunement: 'enchanter' },
{ id: 'effectResearch', name: 'Effect Research', icon: '🔬', attunement: 'enchanter' },
// Invoker attunement (Chest)
{ id: 'invocation', name: 'Invocation', icon: '💜', attunement: 'invoker' },
{ id: 'pact', name: 'Pact Mastery', icon: '🤝', attunement: 'invoker' },
// Fabricator attunement (Left Hand)
{ id: 'fabrication', name: 'Fabrication', icon: '⚒️', attunement: 'fabricator' },
{ id: 'golemancy', name: 'Golemancy', icon: '🗿', attunement: 'fabricator' },
// Legacy category (for backward compatibility)
{ id: 'craft', name: 'Crafting', icon: '🔧', attunement: null },
];
// ─── Rarity Colors ───────────────────────────────────────────────────────────

View File

@@ -0,0 +1,148 @@
// ─── 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', 'scrollCrafting'],
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
export function getTotalAttunementRegen(attunements: Record<string, { active: boolean; level: number; experience: number }>): number {
return Object.entries(attunements)
.filter(([id, state]) => state.active)
.reduce((total, [id]) => total + (ATTUNEMENTS_DEF[id]?.rawManaRegen || 0), 0);
}
// 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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,34 @@
// ─── Game Types ───────────────────────────────────────────────────────────────
import type { AttunementType, AttunementState, ManaType } from './attunements';
// Re-export attunement types
export type { AttunementType, AttunementState, ManaType, AttunementSlot } from './attunements';
export { ATTUNEMENT_SLOTS } from './attunements';
export type ElementCategory = 'base' | 'utility' | 'composite' | 'exotic';
// Equipment slots for the equipment system
export type EquipmentSlot = 'mainHand' | 'offHand' | 'head' | 'body' | 'hands' | 'feet' | 'accessory1' | 'accessory2';
// Attunement body slots
export type AttunementSlot = 'rightHand' | 'leftHand' | 'head' | 'back' | 'chest' | 'leftLeg' | 'rightLeg';
// Attunement definition
export interface AttunementDef {
id: string;
name: string;
desc: string;
slot: AttunementSlot;
icon: string;
color: string;
primaryManaType?: string; // Primary mana type this attunement generates (null for Invoker)
rawManaRegen: number; // Raw mana regeneration per hour granted by this attunement
conversionRate: number; // Raw mana converted to primary type per hour
unlocked: boolean; // Whether this is unlocked by default
unlockCondition?: string; // Description of how to unlock (for future challenges)
capabilities: string[]; // What this attunement enables (e.g., 'enchanting', 'pacts', 'golemCrafting')
skillCategories: string[]; // Skill categories this attunement provides access to
}
// Attunement instance state (tracks player's attunements)
export interface AttunementState {
id: string;
active: boolean; // Whether this attunement is currently active
level: number; // Attunement level (for future progression)
experience: number; // Progress toward next level
}
export interface ElementDef {
name: string;
@@ -83,6 +102,7 @@ export interface SkillDef {
name: string;
desc: string;
cat: string;
attunement?: string; // Which attunement this skill belongs to (null = core)
max: number;
base: number; // Mana cost to start studying
req?: Record<string, number>;
@@ -92,8 +112,6 @@ export interface SkillDef {
tierUp?: string; // Skill ID this evolves into at max level
baseSkill?: string; // Original skill ID this evolved from
tierMultiplier?: number; // Multiplier for each tier (default 2)
attunement?: AttunementType; // Which attunement this skill belongs to (if any)
manaType?: ManaType; // Primary mana type used (if attunement-specific)
}
// Skill upgrade choices at milestones (level 5 and level 10)
@@ -214,15 +232,6 @@ export interface ApplicationProgress {
manaSpent: number; // Total mana spent so far
}
// Equipment Crafting Progress (crafting from blueprints)
export interface EquipmentCraftingProgress {
blueprintId: string; // Blueprint being crafted
equipmentTypeId: string; // Resulting equipment type
progress: number; // Hours spent crafting
required: number; // Total hours needed
manaSpent: number; // Mana spent so far
}
// Equipment spell state (for multi-spell casting)
export interface EquipmentSpellState {
spellId: string;
@@ -230,85 +239,6 @@ export interface EquipmentSpellState {
castProgress: number; // 0-1 progress toward next cast
}
// ─── Combo System ─────────────────────────────────────────────────────────────
export interface ComboState {
count: number; // Number of consecutive spell casts
multiplier: number; // Current damage multiplier (1.0 = base)
lastCastTime: number; // Game tick when last spell was cast
decayTimer: number; // Ticks until combo decays
maxCombo: number; // Highest combo achieved this loop
elementChain: string[]; // Last 3 elements cast (for elemental chain bonus)
}
export const COMBO_CONFIG = {
maxCombo: 100, // Maximum combo count
baseDecayTicks: 10, // Ticks before combo starts decaying
decayRate: 2, // Combo lost per decay tick
baseMultiplier: 0.02, // +2% damage per combo
maxMultiplier: 3.0, // 300% max multiplier
elementalChainBonus: 0.25, // +25% for 3 different elements
perfectChainBonus: 0.5, // +50% for perfect element wheel
} as const;
// ─── Loot Drop System ─────────────────────────────────────────────────────────
export interface LootDrop {
id: string;
name: string;
rarity: 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
type: 'equipment' | 'material' | 'gold' | 'essence' | 'blueprint';
minFloor: number; // Minimum floor for this drop
dropChance: number; // Base drop chance (0-1)
guardianOnly?: boolean; // Only drops from guardians
effect?: LootEffect;
amount?: { min: number; max: number }; // For gold/essence
}
export interface LootEffect {
type: 'manaBonus' | 'damageBonus' | 'regenBonus' | 'castSpeed' | 'critChance' | 'special';
value: number;
desc: string;
}
export interface LootInventory {
materials: Record<string, number>; // materialId -> count
essence: Record<string, number>; // element -> count
blueprints: string[]; // unlocked blueprint IDs
}
// ─── Achievement System ───────────────────────────────────────────────────────
export interface AchievementDef {
id: string;
name: string;
desc: string;
category: 'combat' | 'progression' | 'crafting' | 'magic' | 'special';
requirement: AchievementRequirement;
reward: AchievementReward;
hidden?: boolean; // Hidden until unlocked
}
export interface AchievementRequirement {
type: 'floor' | 'combo' | 'spells' | 'damage' | 'mana' | 'craft' | 'pact' | 'time';
value: number;
subType?: string; // e.g., specific element, spell type
}
export interface AchievementReward {
insight?: number;
manaBonus?: number;
regenBonus?: number;
damageBonus?: number;
unlockEffect?: string; // Unlocks an enchantment effect
title?: string; // Display title
}
export interface AchievementState {
unlocked: string[]; // Achievement IDs
progress: Record<string, number>; // achievementId -> progress
}
export interface BlueprintDef {
id: string;
name: string;
@@ -338,8 +268,6 @@ export interface StudyTarget {
id: string;
progress: number; // Hours studied
required: number; // Total hours needed
manaCostPerHour: number; // Mana cost per hour of study
totalCost: number; // Total mana cost for the entire study
}
export interface GameState {
@@ -356,6 +284,9 @@ export interface GameState {
meditateTicks: number;
totalManaGathered: number;
// Attunements (class-like system)
attunements: Record<string, AttunementState>; // attunement id -> state
// Elements
elements: Record<string, ElementState>;
@@ -368,11 +299,6 @@ export interface GameState {
activeSpell: string;
currentAction: GameAction;
castProgress: number; // Progress towards next spell cast (0-1)
// Floor Navigation
climbDirection: 'up' | 'down'; // Direction of floor traversal
clearedFloors: Record<number, boolean>; // Floors that have been cleared (need respawn)
lastClearedFloor: number | null; // Last floor that was cleared (for respawn tracking)
// Spells
spells: Record<string, SpellState>;
@@ -392,7 +318,6 @@ export interface GameState {
designProgress: DesignProgress | null;
preparationProgress: PreparationProgress | null;
applicationProgress: ApplicationProgress | null;
equipmentCraftingProgress: EquipmentCraftingProgress | null;
// Unlocked enchantment effects for designing
unlockedEffects: string[]; // Effect IDs that have been researched
@@ -418,11 +343,6 @@ export interface GameState {
// Parallel Study Target (for Parallel Mind milestone upgrade)
parallelStudyTarget: StudyTarget | null;
// Study tracking for special effects
studyStartedAt: number | null; // Tick when study started (for STUDY_RUSH)
consecutiveStudyHours: number; // Consecutive hours studying (for STUDY_MOMENTUM)
lastStudyCost: number; // Cost of starting current study (for STUDY_REFUND)
// Prestige
insight: number;
@@ -431,31 +351,10 @@ export interface GameState {
memorySlots: number;
memories: string[];
// Attunement System
attunements: Record<AttunementType, AttunementState>; // Attunement unlock and state
attunementSkills: Record<string, number>; // Attunement-specific skill levels
attunementSkillProgress: Record<string, number>; // Progress for attunement skills
primaryMana: Record<ManaType, number>; // Primary mana pools by type
primaryManaMax: Record<ManaType, number>; // Max primary mana by type
// Incursion
incursionStrength: number;
containmentWards: number;
// Combo System
combo: ComboState;
totalTicks: number; // Total ticks this loop (for combo timing)
// Loot System
lootInventory: LootInventory;
lootDropsToday: number; // Track drops for diminishing returns
// Achievements
achievements: AchievementState;
totalDamageDealt: number; // For damage achievements
totalSpellsCast: number; // For spell achievements
totalCraftsCompleted: number; // For craft achievements
// Log
log: string[];