SYSTEMS 4-6/9 COMPLETE: ZombieScoutSkills (skill tree, active/passive abilities), NomadRaiderAI (state machine, pathfinding, loot stealing), FarmRaidSystem (wave spawning, difficulty scaling, rewards). Progress: 6/9 systems (67%).

This commit is contained in:
2026-01-05 19:16:23 +01:00
parent b6bce79176
commit 370c527fcd
3 changed files with 1160 additions and 0 deletions

View File

@@ -0,0 +1,392 @@
/**
* FARM RAID SYSTEM
* Spawn raider waves, defend farm, rewards
* Integrates with DefenseSystem and NomadRaiderAI
*/
export class FarmRaidSystem {
constructor(scene) {
this.scene = scene;
// Raid state
this.isRaidActive = false;
this.currentWave = 0;
this.totalWaves = 0;
this.raidDifficulty = 1;
// Spawn management
this.activeRaiders = [];
this.spawnPoints = [];
// Raid triggers
this.fameThreshold = 500; // Trigger raid at 500 fame
this.resourceThreshold = 5000; // Or 5000 resources
this.timeBetweenRaids = 600000; // 10 minutes minimum
this.lastRaidTime = 0;
// Rewards
this.defenseRewards = new Map();
this.init();
}
init() {
this.initializeSpawnPoints();
this.initializeRewards();
// Check for raid triggers periodically
this.scene.time.addEvent({
delay: 30000, // Check every 30s
callback: () => this.checkRaidTriggers(),
loop: true
});
}
/**
* Initialize raid spawn points around farm
*/
initializeSpawnPoints() {
const farmCenter = { x: 400, y: 300 }; // Adjust to actual farm center
const spawnDistance = 400;
// 8 spawn points around farm (N, NE, E, SE, S, SW, W, NW)
for (let angle = 0; angle < 360; angle += 45) {
const rad = Phaser.Math.DegToRad(angle);
this.spawnPoints.push({
x: farmCenter.x + Math.cos(rad) * spawnDistance,
y: farmCenter.y + Math.sin(rad) * spawnDistance,
angle: angle
});
}
}
/**
* Initialize defense rewards
*/
initializeRewards() {
this.defenseRewards.set(1, { xp: 200, currency: 500, items: ['raider_loot_common'] });
this.defenseRewards.set(2, { xp: 400, currency: 1000, items: ['raider_loot_common', 'raider_weapon'] });
this.defenseRewards.set(3, { xp: 800, currency: 2000, items: ['raider_loot_rare', 'raider_armor'] });
}
/**
* Check if raid should trigger
*/
checkRaidTriggers() {
if (this.isRaidActive) return;
const now = Date.now();
const timeSinceLastRaid = now - this.lastRaidTime;
// Cooldown check
if (timeSinceLastRaid < this.timeBetweenRaids) return;
const fame = this.scene.gameState?.fame || 0;
const totalResources = this.scene.inventorySystem?.getTotalResourceValue() || 0;
// Trigger conditions
if (fame >= this.fameThreshold || totalResources >= this.resourceThreshold) {
this.startRaid();
}
}
/**
* Start a raid
*/
startRaid(difficulty = null) {
if (this.isRaidActive) return;
this.isRaidActive = true;
this.currentWave = 0;
this.raidDifficulty = difficulty || this.calculateDifficulty();
this.totalWaves = 2 + this.raidDifficulty; // 3-5 waves
this.lastRaidTime = Date.now();
// Notification
this.scene.uiSystem?.showNotification(
`RAID INCOMING! ${this.totalWaves} waves approaching!`,
'danger',
{ priority: 'high' }
);
// SFX: Raid horn
this.scene.soundSystem?.play('raid_horn');
// VFX: Red screen flash
this.scene.cameras.main.flash(1000, 255, 0, 0);
// Start first wave
this.scene.time.delayedCall(3000, () => this.spawnWave());
console.log(`🚨 RAID STARTED: Difficulty ${this.raidDifficulty}, ${this.totalWaves} waves`);
}
/**
* Calculate raid difficulty based on game progress
*/
calculateDifficulty() {
const fame = this.scene.gameState?.fame || 0;
const population = this.scene.gameState?.population || 0;
// Scale difficulty
return Math.min(5, Math.floor((fame / 500) + (population / 20)));
}
/**
* Spawn a wave of raiders
*/
spawnWave() {
this.currentWave++;
const raiderCount = 3 + (this.raidDifficulty * 2); // 5-13 raiders
const waveRaiders = [];
// Spawn raiders
for (let i = 0; i < raiderCount; i++) {
const spawnPoint = Phaser.Utils.Array.GetRandom(this.spawnPoints);
const raider = this.spawnRaider(spawnPoint.x, spawnPoint.y);
waveRaiders.push(raider);
}
this.activeRaiders.push(...waveRaiders);
// Notification
this.scene.uiSystem?.showNotification(
`Wave ${this.currentWave}/${this.totalWaves}: ${raiderCount} raiders!`,
'warning'
);
console.log(`🔥 Wave ${this.currentWave}: Spawned ${raiderCount} raiders`);
// Check for next wave
this.scene.time.delayedCall(5000, () => this.checkWaveProgress());
}
/**
* Spawn individual raider
*/
spawnRaider(x, y) {
// Create raider sprite
const raider = this.scene.physics.add.sprite(x, y, 'nomad_raider');
// Stats based on difficulty
raider.maxHealth = 50 + (this.raidDifficulty * 20);
raider.health = raider.maxHealth;
raider.attackDamage = 10 + (this.raidDifficulty * 5);
raider.active = true;
// Add AI
raider.ai = new NomadRaiderAI(this.scene, raider);
// Collisions
this.scene.physics.add.collider(raider, this.scene.player);
this.scene.physics.add.collider(raider, this.scene.zombieScout);
// Health bar
raider.healthBar = this.scene.add.graphics();
// Death handler
raider.takeDamage = (amount) => this.raiderTakeDamage(raider, amount);
return raider;
}
/**
* Raider takes damage
*/
raiderTakeDamage(raider, amount) {
raider.health -= amount;
// Update health bar
this.updateRaiderHealthBar(raider);
// VFX
raider.setTint(0xff0000);
this.scene.time.delayedCall(100, () => raider.clearTint());
// Death
if (raider.health <= 0) {
this.raiderDeath(raider);
}
}
/**
* Raider death
*/
raiderDeath(raider) {
raider.active = false;
// Drop loot
this.dropRaiderLoot(raider.x, raider.y);
// VFX
this.scene.vfxSystem?.playEffect('raider_death', raider.x, raider.y);
// Remove from active list
const index = this.activeRaiders.indexOf(raider);
if (index > -1) {
this.activeRaiders.splice(index, 1);
}
// Cleanup
raider.ai?.destroy();
raider.healthBar?.destroy();
raider.destroy();
// Check if wave complete
this.checkWaveProgress();
}
/**
* Update raider health bar
*/
updateRaiderHealthBar(raider) {
if (!raider.healthBar) return;
raider.healthBar.clear();
const barWidth = 40;
const barHeight = 4;
const healthPercent = raider.health / raider.maxHealth;
// Background
raider.healthBar.fillStyle(0x000000);
raider.healthBar.fillRect(raider.x - barWidth / 2, raider.y - 30, barWidth, barHeight);
// Health
raider.healthBar.fillStyle(0xff0000);
raider.healthBar.fillRect(raider.x - barWidth / 2, raider.y - 30, barWidth * healthPercent, barHeight);
}
/**
* Drop loot from raider
*/
dropRaiderLoot(x, y) {
const lootTable = ['wood', 'stone', 'metal', 'raider_weapon', 'medicine'];
const drop = Phaser.Utils.Array.GetRandom(lootTable);
const amount = Phaser.Math.Between(1, 5);
// Create loot drop
this.scene.lootSystem?.createDrop(x, y, drop, amount);
}
/**
* Check if wave is complete
*/
checkWaveProgress() {
if (!this.isRaidActive) return;
const aliveRaiders = this.activeRaiders.filter(r => r.active);
if (aliveRaiders.length === 0) {
// Wave complete
if (this.currentWave < this.totalWaves) {
// Next wave
this.scene.uiSystem?.showNotification(
`Wave ${this.currentWave} cleared! Next wave incoming...`,
'success'
);
this.scene.time.delayedCall(5000, () => this.spawnWave());
} else {
// Raid complete
this.endRaid(true);
}
}
}
/**
* End raid (success or failure)
*/
endRaid(success) {
this.isRaidActive = false;
if (success) {
// Victory!
const rewards = this.defenseRewards.get(this.raidDifficulty) || this.defenseRewards.get(1);
this.scene.uiSystem?.showNotification(
`RAID DEFENDED! +${rewards.xp} XP, +${rewards.currency} coins`,
'victory',
{ priority: 'high' }
);
// Grant rewards
this.scene.zombieScoutLeveling?.awardXP(rewards.xp, 'raid_defense');
this.scene.economySystem?.addCurrency(rewards.currency);
rewards.items.forEach(item => {
this.scene.inventorySystem?.addItem(item, 1);
});
// VFX
this.scene.vfxSystem?.playEffect('victory', 400, 300);
this.scene.cameras.main.flash(1000, 0, 255, 0);
console.log(`✅ RAID DEFENDED! Rewards granted.`);
} else {
// Defeat
this.scene.uiSystem?.showNotification(
'RAID DEFEATED YOU! Farm damaged.',
'defeat',
{ priority: 'high' }
);
// Damage farm buildings
this.damageFarm();
console.log(`❌ RAID FAILED! Farm damaged.`);
}
// Cleanup remaining raiders
this.activeRaiders.forEach(raider => {
raider.ai?.destroy();
raider.healthBar?.destroy();
raider.destroy();
});
this.activeRaiders = [];
}
/**
* Damage farm on raid failure
*/
damageFarm() {
// Damage random buildings
const buildings = this.scene.buildingSystem?.getAllBuildings() || [];
const damagedCount = Math.min(3, buildings.length);
for (let i = 0; i < damagedCount; i++) {
const building = Phaser.Utils.Array.GetRandom(buildings);
building.takeDamage?.(50);
}
// Destroy random crops
const crops = this.scene.crops || [];
const destroyedCrops = Math.min(10, crops.length);
for (let i = 0; i < destroyedCrops; i++) {
const crop = Phaser.Utils.Array.GetRandom(crops);
crop.destroy?.();
}
}
/**
* Manual raid trigger (for testing/quests)
*/
triggerRaid(difficulty = 1) {
this.startRaid(difficulty);
}
/**
* Get raid state
*/
getRaidState() {
return {
isActive: this.isRaidActive,
currentWave: this.currentWave,
totalWaves: this.totalWaves,
difficulty: this.raidDifficulty,
activeRaiders: this.activeRaiders.filter(r => r.active).length
};
}
}

View File

@@ -0,0 +1,348 @@
/**
* NOMAD RAIDER AI SYSTEM
* Enemy AI for raiding bandits
* Pathfinding, combat, loot stealing
*/
export class NomadRaiderAI {
constructor(scene, raider) {
this.scene = scene;
this.raider = raider;
// AI state
this.state = 'idle'; // idle, patrol, attack, steal, flee
this.target = null;
this.homePosition = { x: raider.x, y: raider.y };
// AI parameters
this.detectionRange = 200;
this.attackRange = 40;
this.fleeHealthThreshold = 0.3; // Flee at 30% HP
// Behavior timers
this.stateTimer = 0;
this.decisionInterval = 1000; // Make decision every 1s
// Loot targeting
this.targetedLoot = null;
this.init();
}
init() {
// Start AI loop
this.aiLoop = this.scene.time.addEvent({
delay: this.decisionInterval,
callback: () => this.makeDecision(),
loop: true
});
}
/**
* Main AI decision-making
*/
makeDecision() {
if (!this.raider.active) return;
// Check health for flee condition
const healthPercent = this.raider.health / this.raider.maxHealth;
if (healthPercent < this.fleeHealthThreshold && this.state !== 'flee') {
this.setState('flee');
return;
}
// State machine
switch (this.state) {
case 'idle':
this.idleBehavior();
break;
case 'patrol':
this.patrolBehavior();
break;
case 'attack':
this.attackBehavior();
break;
case 'steal':
this.stealBehavior();
break;
case 'flee':
this.fleeBehavior();
break;
}
}
/**
* STATE: Idle - Look for targets
*/
idleBehavior() {
// Check for player
const player = this.scene.player;
if (this.isInRange(player, this.detectionRange)) {
this.target = player;
this.setState('attack');
return;
}
// Check for Zombie Scout
const scout = this.scene.zombieScout;
if (scout && this.isInRange(scout, this.detectionRange)) {
this.target = scout;
this.setState('attack');
return;
}
// Check for stealable loot (crops, chests)
const loot = this.findNearestLoot();
if (loot) {
this.targetedLoot = loot;
this.setState('steal');
return;
}
// Default: patrol
this.setState('patrol');
}
/**
* STATE: Patrol - Wander around
*/
patrolBehavior() {
// Random wandering
if (this.stateTimer <= 0) {
const randomX = this.homePosition.x + Phaser.Math.Between(-150, 150);
const randomY = this.homePosition.y + Phaser.Math.Between(-150, 150);
this.moveToward(randomX, randomY);
this.stateTimer = Phaser.Math.Between(2000, 4000); // Patrol for 2-4s
}
this.stateTimer -= this.decisionInterval;
// Check for threats while patrolling
if (this.detectThreat()) {
this.setState('attack');
}
}
/**
* STATE: Attack - Combat with player/scout
*/
attackBehavior() {
if (!this.target || !this.target.active) {
this.setState('idle');
return;
}
const distance = Phaser.Math.Distance.Between(
this.raider.x, this.raider.y,
this.target.x, this.target.y
);
// Move toward target
if (distance > this.attackRange) {
this.moveToward(this.target.x, this.target.y);
} else {
// In range: attack
this.performAttack();
}
// Lost target
if (distance > this.detectionRange * 1.5) {
this.target = null;
this.setState('idle');
}
}
/**
* STATE: Steal - Take loot and escape
*/
stealBehavior() {
if (!this.targetedLoot) {
this.setState('idle');
return;
}
const distance = Phaser.Math.Distance.Between(
this.raider.x, this.raider.y,
this.targetedLoot.x, this.targetedLoot.y
);
if (distance > 30) {
// Move toward loot
this.moveToward(this.targetedLoot.x, this.targetedLoot.y);
} else {
// Steal loot
this.stealLoot(this.targetedLoot);
this.targetedLoot = null;
this.setState('flee'); // Escape after stealing
}
}
/**
* STATE: Flee - Escape when low health or after stealing
*/
fleeBehavior() {
// Run away from player
const player = this.scene.player;
if (player) {
const angle = Phaser.Math.Angle.Between(
player.x, player.y,
this.raider.x, this.raider.y
);
const fleeX = this.raider.x + Math.cos(angle) * 200;
const fleeY = this.raider.y + Math.sin(angle) * 200;
this.moveToward(fleeX, fleeY);
}
// Check if safe to stop fleeing
const distanceFromPlayer = Phaser.Math.Distance.Between(
this.raider.x, this.raider.y,
player.x, player.y
);
if (distanceFromPlayer > this.detectionRange * 2) {
// Safe: despawn or return to base
this.setState('idle');
}
}
/**
* Change AI state
*/
setState(newState) {
console.log(`Raider AI: ${this.state}${newState}`);
this.state = newState;
this.stateTimer = 0;
// State entry actions
switch (newState) {
case 'attack':
this.raider.setTint(0xff0000); // Red tint when aggressive
break;
case 'flee':
this.raider.setTint(0xffff00); // Yellow when fleeing
break;
default:
this.raider.clearTint();
}
}
/**
* Movement
*/
moveToward(targetX, targetY) {
const angle = Phaser.Math.Angle.Between(
this.raider.x, this.raider.y,
targetX, targetY
);
const speed = this.state === 'flee' ? 150 : 100; // Faster when fleeing
this.raider.setVelocity(
Math.cos(angle) * speed,
Math.sin(angle) * speed
);
// Flip sprite
this.raider.flipX = targetX < this.raider.x;
}
/**
* Attack execution
*/
performAttack() {
if (!this.target) return;
// Attack cooldown check
const now = Date.now();
if (this.raider.lastAttack && now - this.raider.lastAttack < 1500) return;
this.raider.lastAttack = now;
// Deal damage
this.target.takeDamage?.(this.raider.attackDamage || 10);
// VFX
this.scene.vfxSystem?.playEffect('raider_attack', this.target.x, this.target.y);
this.scene.soundSystem?.play('raider_hit');
console.log(`Raider attacked ${this.target.name || 'target'} for ${this.raider.attackDamage || 10} damage`);
}
/**
* Loot stealing
*/
stealLoot(loot) {
// Determine loot value
const stolenValue = loot.value || Phaser.Math.Between(50, 200);
// Remove loot from world
loot.destroy?.() || loot.setVisible(false);
// Notification
this.scene.uiSystem?.showNotification(
`Raider stole ${stolenValue} worth of loot!`,
'warning'
);
// VFX
this.scene.vfxSystem?.playEffect('loot_stolen', loot.x, loot.y);
console.log(`Raider stole loot worth ${stolenValue}`);
}
/**
* Detection helpers
*/
isInRange(target, range) {
if (!target) return false;
const distance = Phaser.Math.Distance.Between(
this.raider.x, this.raider.y,
target.x, target.y
);
return distance <= range;
}
detectThreat() {
const player = this.scene.player;
const scout = this.scene.zombieScout;
return this.isInRange(player, this.detectionRange) ||
this.isInRange(scout, this.detectionRange);
}
findNearestLoot() {
// Find crops or storage
const lootables = this.scene.crops?.filter(c => c.isHarvestable) || [];
let nearest = null;
let minDist = 300; // Max search range
lootables.forEach(loot => {
const dist = Phaser.Math.Distance.Between(
this.raider.x, this.raider.y,
loot.x, loot.y
);
if (dist < minDist) {
minDist = dist;
nearest = loot;
}
});
return nearest;
}
/**
* Cleanup
*/
destroy() {
if (this.aiLoop) {
this.aiLoop.remove();
}
}
}

View File

@@ -0,0 +1,420 @@
/**
* ZOMBIE SCOUT SKILLS SYSTEM
* Skill tree with active/passive abilities
* Integrates with ZombieScoutLevelingSystem
*/
export class ZombieScoutSkills {
constructor(scene) {
this.scene = scene;
// Skill trees
this.skills = new Map();
this.unlockedSkills = new Set();
this.activeSkills = new Map(); // Currently equipped active skills
// Skill categories
this.categories = ['digging', 'combat', 'utility'];
this.init();
}
init() {
this.initializeSkillTree();
}
/**
* Initialize all available skills
*/
initializeSkillTree() {
// DIGGING SKILLS
this.registerSkill({
id: 'speed_dig',
name: 'Speed Dig',
category: 'digging',
type: 'active',
unlockLevel: 10,
cooldown: 30000, // 30 seconds
duration: 10000, // 10 seconds
description: 'Dig 50% faster for 10 seconds',
effect: () => this.activateSpeedDig()
});
this.registerSkill({
id: 'deep_scan',
name: 'Deep Scan',
category: 'digging',
type: 'passive',
unlockLevel: 5,
description: 'Reveal buried items within 5 tiles',
effect: () => this.enableDeepScan()
});
this.registerSkill({
id: 'treasure_sense',
name: 'Treasure Sense',
category: 'digging',
type: 'active',
unlockLevel: 15,
cooldown: 60000, // 60 seconds
description: 'Reveal all buried treasure on screen',
effect: () => this.activateTreasureSense()
});
// COMBAT SKILLS
this.registerSkill({
id: 'basic_attack',
name: 'Claw Swipe',
category: 'combat',
type: 'active',
unlockLevel: 5,
cooldown: 2000, // 2 seconds
damage: 10,
description: 'Basic melee attack dealing 10 damage',
effect: () => this.performBasicAttack()
});
this.registerSkill({
id: 'leap_attack',
name: 'Leap Attack',
category: 'combat',
type: 'active',
unlockLevel: 12,
cooldown: 8000, // 8 seconds
damage: 25,
description: 'Leap at enemy dealing 25 damage',
effect: () => this.performLeapAttack()
});
this.registerSkill({
id: 'tank_stance',
name: 'Undead Resilience',
category: 'combat',
type: 'passive',
unlockLevel: 8,
description: '+20% damage resistance',
effect: () => this.enableTankStance()
});
// UTILITY SKILLS
this.registerSkill({
id: 'scout_speed',
name: 'Scout Sprint',
category: 'utility',
type: 'active',
unlockLevel: 7,
cooldown: 20000, // 20 seconds
duration: 5000, // 5 seconds
description: 'Move 100% faster for 5 seconds',
effect: () => this.activateScoutSpeed()
});
this.registerSkill({
id: 'pack_mule',
name: 'Pack Mule',
category: 'utility',
type: 'passive',
unlockLevel: 6,
description: '+10 inventory slots',
effect: () => this.enablePackMule()
});
this.registerSkill({
id: 'night_vision',
name: 'Night Vision',
category: 'utility',
type: 'passive',
unlockLevel: 10,
description: 'See in darkness without penalty',
effect: () => this.enableNightVision()
});
}
/**
* Register a skill in the system
*/
registerSkill(skillData) {
this.skills.set(skillData.id, {
...skillData,
unlocked: false,
active: false,
lastUsed: 0
});
}
/**
* Unlock skill (called when level requirement met)
*/
unlockSkill(skillId) {
const skill = this.skills.get(skillId);
if (!skill) return false;
const scoutLevel = this.scene.zombieScoutLeveling?.currentLevel || 1;
// Check level requirement
if (scoutLevel < skill.unlockLevel) {
console.warn(`Scout level ${scoutLevel} too low for ${skill.name} (requires ${skill.unlockLevel})`);
return false;
}
skill.unlocked = true;
this.unlockedSkills.add(skillId);
// Auto-activate passive skills
if (skill.type === 'passive') {
skill.effect();
}
// Notification
this.scene.uiSystem?.showNotification(
`Skill Unlocked: ${skill.name}!`,
'skill_unlock',
{ description: skill.description }
);
console.log(`✨ Unlocked skill: ${skill.name}`);
return true;
}
/**
* Use active skill
*/
useSkill(skillId) {
const skill = this.skills.get(skillId);
if (!skill || !skill.unlocked || skill.type !== 'active') return false;
// Check cooldown
const now = Date.now();
const timeSinceLastUse = now - skill.lastUsed;
if (timeSinceLastUse < skill.cooldown) {
const remainingCooldown = Math.ceil((skill.cooldown - timeSinceLastUse) / 1000);
console.log(`Skill on cooldown: ${remainingCooldown}s remaining`);
return false;
}
// Activate skill
skill.lastUsed = now;
skill.effect();
// UI feedback
this.scene.uiSystem?.showSkillActivation(skill.name, skill.cooldown);
console.log(`🔥 Activated: ${skill.name}`);
return true;
}
/**
* SKILL EFFECTS
*/
activateSpeedDig() {
const scout = this.scene.zombieScout;
if (!scout) return;
scout.digSpeed *= 1.5; // 50% faster
// VFX
this.scene.vfxSystem?.playEffect('speed_buff', scout.x, scout.y);
// Restore after duration
this.scene.time.delayedCall(10000, () => {
scout.digSpeed /= 1.5;
console.log('Speed Dig expired');
});
}
enableDeepScan() {
// Enable passive treasure detection
this.scene.gameState.buffs.treasure_detection_range = 5;
console.log('Deep Scan enabled: 5 tile range');
}
activateTreasureSense() {
// Reveal all buried items on screen
this.scene.treasureSystem?.revealAllTreasures();
// VFX: Screen pulse
this.scene.cameras.main.flash(500, 255, 215, 0, true);
console.log('Treasure Sense activated: All treasures revealed');
}
performBasicAttack() {
const scout = this.scene.zombieScout;
if (!scout) return;
// Find nearest enemy
const enemy = this.findNearestEnemy(scout, 50);
if (!enemy) {
console.log('No enemy in range');
return;
}
// Deal damage
enemy.takeDamage(10);
// VFX
this.scene.vfxSystem?.playEffect('claw_swipe', enemy.x, enemy.y);
this.scene.soundSystem?.play('zombie_attack');
console.log('Basic attack: 10 damage');
}
performLeapAttack() {
const scout = this.scene.zombieScout;
if (!scout) return;
// Find nearest enemy
const enemy = this.findNearestEnemy(scout, 150);
if (!enemy) return;
// Leap animation
this.scene.tweens.add({
targets: scout,
x: enemy.x,
y: enemy.y - 20,
duration: 300,
ease: 'Quad.easeOut',
onComplete: () => {
// Deal damage on landing
enemy.takeDamage(25);
this.scene.vfxSystem?.playEffect('impact', enemy.x, enemy.y);
this.scene.cameras.main.shake(200, 0.008);
}
});
console.log('Leap attack: 25 damage');
}
enableTankStance() {
const scout = this.scene.zombieScout;
if (scout) {
scout.damageResistance = (scout.damageResistance || 0) + 0.2; // +20%
console.log('Undead Resilience enabled: +20% damage resistance');
}
}
activateScoutSpeed() {
const scout = this.scene.zombieScout;
if (!scout) return;
scout.moveSpeed *= 2; // 100% faster
// VFX: Speed trails
this.scene.vfxSystem?.playEffect('speed_trail', scout.x, scout.y, { duration: 5000 });
// Restore after duration
this.scene.time.delayedCall(5000, () => {
scout.moveSpeed /= 2;
console.log('Scout Sprint expired');
});
}
enablePackMule() {
this.scene.inventorySystem?.addInventorySlots(10);
console.log('Pack Mule enabled: +10 inventory slots');
}
enableNightVision() {
this.scene.gameState.buffs.night_vision = true;
console.log('Night Vision enabled: Full visibility at night');
}
/**
* Helper: Find nearest enemy
*/
findNearestEnemy(from, maxDistance) {
let nearest = null;
let minDist = maxDistance;
// Search for enemies in scene
const enemies = this.scene.enemies || [];
enemies.forEach(enemy => {
const dist = Phaser.Math.Distance.Between(from.x, from.y, enemy.x, enemy.y);
if (dist < minDist) {
minDist = dist;
nearest = enemy;
}
});
return nearest;
}
/**
* Get unlocked skills by category
*/
getSkillsByCategory(category) {
return Array.from(this.skills.values())
.filter(s => s.category === category && s.unlocked);
}
/**
* Get all unlocked skills
*/
getUnlockedSkills() {
return Array.from(this.skills.values()).filter(s => s.unlocked);
}
/**
* Check skill availability
*/
isSkillAvailable(skillId) {
const skill = this.skills.get(skillId);
if (!skill || !skill.unlocked) return false;
if (skill.type === 'passive') return true;
const now = Date.now();
return (now - skill.lastUsed) >= skill.cooldown;
}
/**
* Get skill cooldown remaining
*/
getCooldownRemaining(skillId) {
const skill = this.skills.get(skillId);
if (!skill) return 0;
const now = Date.now();
const elapsed = now - skill.lastUsed;
const remaining = Math.max(0, skill.cooldown - elapsed);
return Math.ceil(remaining / 1000); // Return in seconds
}
/**
* Save/Load
*/
getSaveData() {
return {
unlockedSkills: Array.from(this.unlockedSkills),
skillCooldowns: Array.from(this.skills.entries()).map(([id, skill]) => ({
id,
lastUsed: skill.lastUsed
}))
};
}
loadSaveData(data) {
this.unlockedSkills = new Set(data.unlockedSkills || []);
// Restore cooldowns
data.skillCooldowns?.forEach(({ id, lastUsed }) => {
const skill = this.skills.get(id);
if (skill) {
skill.lastUsed = lastUsed;
skill.unlocked = this.unlockedSkills.has(id);
}
});
// Re-apply passive skills
this.unlockedSkills.forEach(skillId => {
const skill = this.skills.get(skillId);
if (skill && skill.type === 'passive') {
skill.effect();
}
});
}
}