Files
novafarma/src/systems/SkillTreeSystem.js
2025-12-13 00:02:38 +01:00

485 lines
14 KiB
JavaScript

/**
* SKILL TREE SYSTEM
* Player progression with farming, combat, and survival branches
*/
class SkillTreeSystem {
constructor(scene) {
this.scene = scene;
this.enabled = true;
// Player stats
this.playerLevel = 1;
this.playerXP = 0;
this.skillPoints = 0;
// Skill trees
this.skills = {
farming: new Map(),
combat: new Map(),
survival: new Map()
};
// Active abilities
this.activeAbilities = new Map();
this.abilityCooldowns = new Map();
// Settings
this.settings = {
xpPerLevel: 100,
xpScaling: 1.5, // Each level requires 50% more XP
skillPointsPerLevel: 1,
maxLevel: 50
};
this.loadProgress();
this.init();
console.log('✅ Skill Tree System initialized');
}
init() {
this.defineSkills();
console.log('🌳 Skill trees ready');
}
// ========== SKILL DEFINITIONS ==========
defineSkills() {
// FARMING TREE
this.defineSkill('farming', 'green_thumb', {
name: 'Green Thumb',
description: 'Crops grow 10% faster',
maxLevel: 5,
cost: 1,
requires: null,
bonus: { type: 'crop_speed', value: 0.1 }
});
this.defineSkill('farming', 'efficient_harvest', {
name: 'Efficient Harvest',
description: '+20% crop yield',
maxLevel: 3,
cost: 1,
requires: 'green_thumb',
bonus: { type: 'crop_yield', value: 0.2 }
});
this.defineSkill('farming', 'area_harvest', {
name: 'Area Harvest',
description: 'Harvest 3x3 area at once',
maxLevel: 1,
cost: 2,
requires: 'efficient_harvest',
bonus: { type: 'ability', value: 'area_harvest' }
});
this.defineSkill('farming', 'master_farmer', {
name: 'Master Farmer',
description: 'Crops never wither',
maxLevel: 1,
cost: 3,
requires: 'area_harvest',
bonus: { type: 'crop_immortal', value: true }
});
// COMBAT TREE
this.defineSkill('combat', 'strength', {
name: 'Strength',
description: '+15% melee damage',
maxLevel: 5,
cost: 1,
requires: null,
bonus: { type: 'melee_damage', value: 0.15 }
});
this.defineSkill('combat', 'critical_strike', {
name: 'Critical Strike',
description: '10% chance for 2x damage',
maxLevel: 3,
cost: 1,
requires: 'strength',
bonus: { type: 'crit_chance', value: 0.1 }
});
this.defineSkill('combat', 'dash', {
name: 'Dash',
description: 'Quick dash ability (cooldown: 5s)',
maxLevel: 1,
cost: 2,
requires: 'strength',
bonus: { type: 'ability', value: 'dash' }
});
this.defineSkill('combat', 'berserker', {
name: 'Berserker',
description: '+50% damage when below 30% HP',
maxLevel: 1,
cost: 3,
requires: 'critical_strike',
bonus: { type: 'berserker', value: 0.5 }
});
// SURVIVAL TREE
this.defineSkill('survival', 'vitality', {
name: 'Vitality',
description: '+20 max health',
maxLevel: 5,
cost: 1,
requires: null,
bonus: { type: 'max_health', value: 20 }
});
this.defineSkill('survival', 'regeneration', {
name: 'Regeneration',
description: 'Heal 1 HP every 5 seconds',
maxLevel: 3,
cost: 1,
requires: 'vitality',
bonus: { type: 'health_regen', value: 1 }
});
this.defineSkill('survival', 'iron_skin', {
name: 'Iron Skin',
description: 'Reduce damage taken by 15%',
maxLevel: 3,
cost: 1,
requires: 'vitality',
bonus: { type: 'damage_reduction', value: 0.15 }
});
this.defineSkill('survival', 'second_wind', {
name: 'Second Wind',
description: 'Survive fatal damage once (cooldown: 60s)',
maxLevel: 1,
cost: 3,
requires: 'regeneration',
bonus: { type: 'ability', value: 'second_wind' }
});
}
defineSkill(tree, id, data) {
this.skills[tree].set(id, {
id,
tree,
currentLevel: 0,
...data
});
}
// ========== SKILL MANAGEMENT ==========
unlockSkill(tree, skillId) {
const skill = this.skills[tree].get(skillId);
if (!skill) return false;
// Check requirements
if (skill.requires) {
const required = this.skills[tree].get(skill.requires);
if (!required || required.currentLevel === 0) {
console.log('❌ Requirement not met:', skill.requires);
return false;
}
}
// Check skill points
if (this.skillPoints < skill.cost) {
console.log('❌ Not enough skill points');
return false;
}
// Check max level
if (skill.currentLevel >= skill.maxLevel) {
console.log('❌ Skill already maxed');
return false;
}
// Unlock skill
skill.currentLevel++;
this.skillPoints -= skill.cost;
// Apply bonus
this.applySkillBonus(skill);
// Unlock achievement
if (this.scene.uiGraphics) {
if (skill.currentLevel === skill.maxLevel) {
this.scene.uiGraphics.unlockAchievement('skill_master');
}
}
this.saveProgress();
console.log(`✅ Unlocked: ${skill.name} (Level ${skill.currentLevel})`);
return true;
}
applySkillBonus(skill) {
const bonus = skill.bonus;
switch (bonus.type) {
case 'ability':
this.activeAbilities.set(bonus.value, {
name: skill.name,
cooldown: 0,
ready: true
});
break;
case 'max_health':
if (this.scene.statsSystem) {
this.scene.statsSystem.maxHealth += bonus.value;
}
break;
case 'melee_damage':
case 'crit_chance':
case 'damage_reduction':
case 'crop_speed':
case 'crop_yield':
// These are passive bonuses checked when needed
break;
}
}
// ========== EXPERIENCE & LEVELING ==========
addXP(amount) {
this.playerXP += amount;
// Check for level up
const xpNeeded = this.getXPForLevel(this.playerLevel + 1);
if (this.playerXP >= xpNeeded && this.playerLevel < this.settings.maxLevel) {
this.levelUp();
}
this.saveProgress();
}
getXPForLevel(level) {
return Math.floor(
this.settings.xpPerLevel * Math.pow(this.settings.xpScaling, level - 1)
);
}
levelUp() {
this.playerLevel++;
this.skillPoints += this.settings.skillPointsPerLevel;
// Visual effect
if (this.scene.visualEnhancements) {
const player = this.scene.player;
if (player) {
const pos = player.getPosition();
this.scene.visualEnhancements.createSparkleEffect(pos.x, pos.y);
}
}
// Notification
if (this.scene.screenReader) {
this.scene.screenReader.speak(`Level up! You are now level ${this.playerLevel}!`);
}
console.log(`🎉 Level Up! Now level ${this.playerLevel}`);
this.saveProgress();
}
// ========== ACTIVE ABILITIES ==========
useAbility(abilityName) {
const ability = this.activeAbilities.get(abilityName);
if (!ability || !ability.ready) return false;
switch (abilityName) {
case 'dash':
this.performDash();
this.setAbilityCooldown(abilityName, 5000);
break;
case 'area_harvest':
this.performAreaHarvest();
this.setAbilityCooldown(abilityName, 10000);
break;
case 'second_wind':
// Triggered automatically on fatal damage
break;
}
return true;
}
performDash() {
if (!this.scene.player) return;
const player = this.scene.player;
const direction = player.facing || 'down';
const distance = 3;
let dx = 0, dy = 0;
switch (direction) {
case 'up': dy = -distance; break;
case 'down': dy = distance; break;
case 'left': dx = -distance; break;
case 'right': dx = distance; break;
}
const pos = player.getPosition();
player.setPosition(pos.x + dx, pos.y + dy);
// Dash effect
if (this.scene.visualEnhancements) {
this.scene.visualEnhancements.createSparkleEffect(pos.x, pos.y);
}
console.log('💨 Dash!');
}
performAreaHarvest() {
if (!this.scene.player || !this.scene.farmingSystem) return;
const pos = this.scene.player.getPosition();
const x = Math.floor(pos.x);
const y = Math.floor(pos.y);
// Harvest 3x3 area
let harvested = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (this.scene.farmingSystem.harvestCrop(x + dx, y + dy)) {
harvested++;
}
}
}
if (harvested > 0) {
console.log(`🌾 Area harvest: ${harvested} crops`);
}
}
setAbilityCooldown(abilityName, duration) {
const ability = this.activeAbilities.get(abilityName);
if (!ability) return;
ability.ready = false;
ability.cooldown = duration;
setTimeout(() => {
ability.ready = true;
ability.cooldown = 0;
}, duration);
}
// ========== SKILL BONUSES ==========
getSkillBonus(bonusType) {
let total = 0;
for (const tree of Object.values(this.skills)) {
for (const skill of tree.values()) {
if (skill.currentLevel > 0 && skill.bonus.type === bonusType) {
total += skill.bonus.value * skill.currentLevel;
}
}
}
return total;
}
hasSkill(tree, skillId) {
const skill = this.skills[tree].get(skillId);
return skill && skill.currentLevel > 0;
}
// ========== SKILL RESET ==========
resetSkills(cost = 1000) {
// Check if player has enough gold
if (this.scene.inventorySystem && this.scene.inventorySystem.gold < cost) {
console.log('❌ Not enough gold to reset skills');
return false;
}
// Deduct cost
if (this.scene.inventorySystem) {
this.scene.inventorySystem.gold -= cost;
}
// Reset all skills
let pointsRefunded = 0;
for (const tree of Object.values(this.skills)) {
for (const skill of tree.values()) {
pointsRefunded += skill.currentLevel * skill.cost;
skill.currentLevel = 0;
}
}
this.skillPoints = pointsRefunded;
this.activeAbilities.clear();
this.saveProgress();
console.log(`🔄 Skills reset! ${pointsRefunded} points refunded`);
return true;
}
// ========== PERSISTENCE ==========
saveProgress() {
const data = {
level: this.playerLevel,
xp: this.playerXP,
skillPoints: this.skillPoints,
skills: {}
};
// Save skill levels
for (const [treeName, tree] of Object.entries(this.skills)) {
data.skills[treeName] = {};
for (const [skillId, skill] of tree.entries()) {
if (skill.currentLevel > 0) {
data.skills[treeName][skillId] = skill.currentLevel;
}
}
}
localStorage.setItem('novafarma_skill_tree', JSON.stringify(data));
}
loadProgress() {
const saved = localStorage.getItem('novafarma_skill_tree');
if (saved) {
try {
const data = JSON.parse(saved);
this.playerLevel = data.level || 1;
this.playerXP = data.xp || 0;
this.skillPoints = data.skillPoints || 0;
// Load skill levels after skills are defined
this.savedSkills = data.skills || {};
console.log('✅ Skill progress loaded');
} catch (error) {
console.error('Failed to load skill progress:', error);
}
}
}
applyLoadedProgress() {
if (!this.savedSkills) return;
for (const [treeName, skills] of Object.entries(this.savedSkills)) {
for (const [skillId, level] of Object.entries(skills)) {
const skill = this.skills[treeName]?.get(skillId);
if (skill) {
skill.currentLevel = level;
this.applySkillBonus(skill);
}
}
}
this.savedSkills = null;
}
destroy() {
this.saveProgress();
console.log('🌳 Skill Tree System destroyed');
}
}