485 lines
14 KiB
JavaScript
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');
|
|
}
|
|
}
|