Browse Source

Replace hardcoded class system with database-driven classes

- Removed hardcoded PLAYER_CLASSES and SKILL_POOLS constants
- Added loadClasses() function to fetch classes from /api/classes
- Dynamically builds PLAYER_CLASSES from database with skills
- Dynamically builds SKILL_POOLS from class_skills with choice_groups
- Updated showSkillChoice() to use class-specific skill names
- Updated selectSkill() to display class-specific skill names
- Added safety fallbacks for level-up stat gains
- Classes loaded on app initialization alongside skills/monsters

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
master
HikeMap User 1 month ago
parent
commit
8fa3bd5c69
  1. 178
      index.html

178
index.html

@ -3550,56 +3550,9 @@
}
};
const PLAYER_CLASSES = {
'trail_runner': {
name: 'Trail Runner',
icon: '🏃',
available: true,
description: 'Masters of endurance and speed',
baseStats: { hp: 100, mp: 50, atk: 12, def: 8 },
hpPerLevel: 20,
mpPerLevel: 10,
atkPerLevel: 3,
defPerLevel: 2,
skills: ['basic_attack', 'brand_new_hokas', 'runners_high', 'shin_kick', 'whirlwind']
},
'gym_bro': {
name: 'Gym Bro',
icon: '💪',
available: false,
description: 'Raw power and protein',
baseStats: { hp: 130, mp: 30, atk: 18, def: 10 },
hpPerLevel: 25,
mpPerLevel: 5,
atkPerLevel: 5,
defPerLevel: 3,
skills: []
},
'yoga_master': {
name: 'Yoga Master',
icon: '🧘',
available: false,
description: 'Flexible and mystical',
baseStats: { hp: 80, mp: 80, atk: 8, def: 6 },
hpPerLevel: 15,
mpPerLevel: 15,
atkPerLevel: 2,
defPerLevel: 1,
skills: []
},
'crossfit_crusader': {
name: 'CrossFit Crusader',
icon: '🏋️',
available: false,
description: 'Jack of all workouts',
baseStats: { hp: 110, mp: 40, atk: 15, def: 12 },
hpPerLevel: 22,
mpPerLevel: 8,
atkPerLevel: 4,
defPerLevel: 3,
skills: []
}
};
// Player classes (loaded from database via API)
let PLAYER_CLASSES = {};
let classesLoaded = false;
// Skill definitions
const SKILLS = {
@ -3690,14 +3643,8 @@
}
};
// Skill pools for skill selection at level-up milestones
const SKILL_POOLS = {
'trail_runner': {
2: ['brand_new_hokas', 'quick_step'], // Level 2 choice
3: ['runners_high', 'second_wind'], // Level 3 choice
5: ['shin_kick', 'finish_line_sprint'] // Level 5 choice
}
};
// Skill pools for skill selection at level-up milestones (loaded from database)
let SKILL_POOLS = {};
// Monster type definitions (loaded from database via API)
let MONSTER_TYPES = {};
@ -3813,6 +3760,78 @@
return [];
}
// Load player classes from database
async function loadClasses() {
try {
const response = await fetch('/api/classes');
if (response.ok) {
const classes = await response.json();
for (const cls of classes) {
// Load skills for this class
const skillsResponse = await fetch(`/api/classes/${cls.id}/skills`);
let classSkills = [];
if (skillsResponse.ok) {
classSkills = await skillsResponse.json();
}
// Build the class object
PLAYER_CLASSES[cls.id] = {
name: cls.name,
icon: getClassIcon(cls.id),
available: cls.enabled === 1,
description: cls.description,
baseStats: {
hp: cls.base_hp,
mp: cls.base_mp,
atk: cls.base_atk,
def: cls.base_def
},
baseAccuracy: cls.base_accuracy || 90,
baseDodge: cls.base_dodge || 10,
hpPerLevel: cls.hp_per_level,
mpPerLevel: cls.mp_per_level,
atkPerLevel: cls.atk_per_level,
defPerLevel: cls.def_per_level,
skills: classSkills
.filter(s => !s.choice_group) // Auto-learned skills (no choice)
.map(s => s.custom_name ? s.skill_id : s.skill_id)
};
// Build SKILL_POOLS from skills with choice_groups
SKILL_POOLS[cls.id] = {};
classSkills.forEach(skill => {
if (skill.choice_group) {
const level = skill.unlock_level;
if (!SKILL_POOLS[cls.id][level]) {
SKILL_POOLS[cls.id][level] = [];
}
// Use custom name as skill ID for display, but store skill_id
SKILL_POOLS[cls.id][level].push(skill.skill_id);
}
});
}
classesLoaded = true;
console.log('Loaded classes from database:', Object.keys(PLAYER_CLASSES));
console.log('Built skill pools:', SKILL_POOLS);
}
} catch (err) {
console.error('Failed to load classes:', err);
}
}
// Get icon for a class (can be extended to support custom icons in DB later)
function getClassIcon(classId) {
const icons = {
'trail_runner': '🏃',
'gym_bro': '💪',
'yoga_master': '🧘',
'crossfit_crusader': '🏋️'
};
return icons[classId] || '⚔️';
}
// Get skill display name for a class (or base name if no custom)
function getSkillForClass(skillId, classId) {
const baseSkill = SKILLS_DB[skillId] || SKILLS[skillId];
@ -9814,15 +9833,24 @@
pendingSkillChoice = { level, options };
const optionsHtml = options.map(skillId => {
const skill = SKILLS[skillId];
// Get skill with class-specific name
const skillInfo = getSkillForClass(skillId, playerStats.class);
const hardcodedSkill = SKILLS[skillId];
const skill = skillInfo || hardcodedSkill;
if (!skill) return '';
const displayName = skillInfo?.displayName || skill.name;
const displayDesc = skillInfo?.displayDescription || skill.description;
const icon = hardcodedSkill?.icon || '⚔️';
const mpCost = skill.mpCost || skill.mp_cost || 0;
return `
<div class="skill-choice-option" onclick="selectSkill('${skillId}')">
<span class="skill-choice-icon">${skill.icon}</span>
<span class="skill-choice-icon">${icon}</span>
<div class="skill-choice-details">
<div class="skill-choice-name">${skill.name}</div>
<div class="skill-choice-desc">${skill.description}</div>
<div class="skill-choice-cost">${skill.mpCost} MP</div>
<div class="skill-choice-name">${displayName}</div>
<div class="skill-choice-desc">${displayDesc}</div>
<div class="skill-choice-cost">${mpCost} MP</div>
</div>
</div>
`;
@ -9852,9 +9880,11 @@
document.getElementById('skillChoiceModal').style.display = 'none';
pendingSkillChoice = null;
// Show notification
const skill = SKILLS[skillId];
showNotification(`Learned ${skill.name}!`, 'success');
// Show notification with class-specific name
const skillInfo = getSkillForClass(skillId, playerStats.class);
const skill = skillInfo || SKILLS[skillId];
const displayName = skillInfo?.displayName || skill?.name || skillId;
showNotification(`Learned ${displayName}!`, 'success');
}
// Leaderboard
@ -11703,13 +11733,15 @@
playerStats.level = newLevel;
playerStats.xp -= xpNeeded;
const classData = PLAYER_CLASSES[playerStats.class];
playerStats.maxHp += classData.hpPerLevel;
const classData = PLAYER_CLASSES[playerStats.class] || {
hpPerLevel: 10, mpPerLevel: 5, atkPerLevel: 2, defPerLevel: 1
};
playerStats.maxHp += classData.hpPerLevel || 10;
playerStats.hp = playerStats.maxHp; // Full heal on level up
playerStats.maxMp += classData.mpPerLevel;
playerStats.maxMp += classData.mpPerLevel || 5;
playerStats.mp = playerStats.maxMp; // Full MP restore
playerStats.atk += classData.atkPerLevel;
playerStats.def += classData.defPerLevel;
playerStats.atk += classData.atkPerLevel || 2;
playerStats.def += classData.defPerLevel || 1;
addCombatLog(`LEVEL UP! Now level ${newLevel}!`, 'victory');
@ -11729,8 +11761,8 @@
// END RPG COMBAT SYSTEM FUNCTIONS
// ==========================================
// Load monster types, skills, and spawn settings from database, then initialize auth
Promise.all([loadMonsterTypes(), loadSkillsFromDatabase(), loadSpawnSettings()]).then(() => {
// Load monster types, skills, classes, and spawn settings from database, then initialize auth
Promise.all([loadMonsterTypes(), loadSkillsFromDatabase(), loadClasses(), loadSpawnSettings()]).then(() => {
loadCurrentUser();
});

Loading…
Cancel
Save