feat: Implement attunement system with 3 attunements
Some checks failed
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 1m14s
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:
@@ -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 ───────────────────────────────────────────────────────────
|
||||
|
||||
148
src/lib/game/data/attunements.ts
Normal file
148
src/lib/game/data/attunements.ts
Normal 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
@@ -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[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user