/** * DEFENSE SYSTEM - City Walls & Watchtowers * Mrtva Dolina - Obrambe pred nomadskimi roparji * * Features: * - 3-tier wall system (wooden → stone → fortress) * - Watchtowers with Line of Sight (LoS) expansion * - Raid detection and prevention * - Patrol system * - Damage and repair mechanics */ export class DefenseSystem { constructor(scene) { this.scene = scene; // Wall configurations this.wallTiers = { wooden: { name: 'Leseno Obzidje', tier: 1, health: 100, defense: 30, cost: { wood: 50, stone: 10 }, buildTime: 30000, // 30 seconds sprite: 'wall_wooden' }, stone: { name: 'Kamnito Obzidje', tier: 2, health: 300, defense: 70, cost: { wood: 20, stone: 100, steel: 10 }, buildTime: 60000, // 1 minute sprite: 'wall_stone' }, fortress: { name: 'Futuristično Obzidje', tier: 3, health: 1000, defense: 95, cost: { steel: 150, glass: 50, tech_parts: 20 }, buildTime: 120000, // 2 minutes sprite: 'wall_fortress' } }; // Watchtower configuration this.watchtowerConfig = { name: 'Opazovalni Stolp', health: 200, losRange: 500, // Line of Sight range in pixels detectionBonus: 0.5, // 50% earlier raid detection cost: { wood: 30, stone: 20 }, buildTime: 45000, // 45 seconds sprite: 'watchtower' }; // Placed walls and towers this.walls = []; this.watchtowers = []; // Patrol system this.patrols = []; this.patrolsUnlocked = false; // Raid tracking this.activeRaids = []; this.cityDefenseRating = 0; this.init(); } init() { // Listen for raid events this.scene.events.on('raid:incoming', this.onRaidIncoming, this); this.scene.events.on('raid:attack', this.onRaidAttack, this); // Start passive defense calculations this.startDefenseUpdates(); console.log('✅ DefenseSystem initialized'); } /** * BUILD WALL SEGMENT */ buildWall(x, y, tier = 'wooden', direction = 'horizontal') { const wallConfig = this.wallTiers[tier]; if (!wallConfig) { console.error(`Unknown wall tier: ${tier}`); return null; } // Check if player has resources if (!this.hasResources(wallConfig.cost)) { this.scene.events.emit('show-notification', { title: '❌ Ni Materialov', message: `Rabiš: ${this.formatCost(wallConfig.cost)}`, icon: '🏗️', duration: 3000, color: '#FF4444' }); return null; } // Deduct resources this.deductResources(wallConfig.cost); // Create wall object const wall = { id: `wall_${Date.now()}`, x: x, y: y, tier: tier, direction: direction, // horizontal or vertical health: wallConfig.health, maxHealth: wallConfig.health, defense: wallConfig.defense, sprite: null, isBuilding: true }; // Show construction animation this.startConstruction(wall, wallConfig); this.walls.push(wall); console.log(`🏗️ Building ${wallConfig.name} at (${x}, ${y})`); return wall; } startConstruction(wall, config) { // Show construction sprite const constructionSite = this.scene.add.sprite(wall.x, wall.y, 'construction_scaffold'); constructionSite.setDepth(10); wall.constructionSprite = constructionSite; // Construction timer setTimeout(() => { this.completeWallConstruction(wall, config); }, config.buildTime); // Show progress bar this.showConstructionProgress(wall, config.buildTime); } completeWallConstruction(wall, config) { // Remove construction sprite if (wall.constructionSprite) { wall.constructionSprite.destroy(); } // Place actual wall const wallSprite = this.scene.add.sprite(wall.x, wall.y, config.sprite); wallSprite.setDepth(5); if (wall.direction === 'vertical') { wallSprite.setRotation(Math.PI / 2); // 90 degrees } wall.sprite = wallSprite; wall.isBuilding = false; // Update defense rating this.updateDefenseRating(); // Show completion notification this.scene.events.emit('show-notification', { title: '✅ Obzidje Zgrajeno', message: `${config.name} je končano!`, icon: '🏰', duration: 3000, color: '#00FF00' }); console.log(`✅ ${config.name} construction complete`); } showConstructionProgress(wall, duration) { // Progress bar above construction site const progressBg = this.scene.add.graphics(); progressBg.fillStyle(0x000000, 0.7); progressBg.fillRect(wall.x - 30, wall.y - 40, 60, 8); const progressBar = this.scene.add.graphics(); wall.progressBar = progressBar; wall.progressBg = progressBg; const startTime = Date.now(); const progressInterval = setInterval(() => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); progressBar.clear(); progressBar.fillStyle(0x00FF00, 1); progressBar.fillRect(wall.x - 29, wall.y - 39, 58 * progress, 6); if (progress >= 1) { clearInterval(progressInterval); progressBg.destroy(); progressBar.destroy(); } }, 100); } /** * BUILD WATCHTOWER */ buildWatchtower(x, y) { const config = this.watchtowerConfig; if (!this.hasResources(config.cost)) { this.scene.events.emit('show-notification', { title: '❌ Ni Materialov', message: `Rabiš: ${this.formatCost(config.cost)}`, icon: '🗼', duration: 3000, color: '#FF4444' }); return null; } this.deductResources(config.cost); const tower = { id: `tower_${Date.now()}`, x: x, y: y, health: config.health, maxHealth: config.health, losRange: config.losRange, detectionBonus: config.detectionBonus, sprite: null, isBuilding: true, losCircle: null }; // Start construction this.startConstruction(tower, config); // Complete construction setTimeout(() => { this.completeWatchtowerConstruction(tower); }, config.buildTime); this.watchtowers.push(tower); console.log(`🗼 Building Watchtower at (${x}, ${y})`); return tower; } completeWatchtowerConstruction(tower) { if (tower.constructionSprite) { tower.constructionSprite.destroy(); } // Place tower sprite const towerSprite = this.scene.add.sprite(tower.x, tower.y, this.watchtowerConfig.sprite); towerSprite.setDepth(15); tower.sprite = towerSprite; tower.isBuilding = false; // Create Line of Sight circle this.createLosIndicator(tower); // Update defense this.updateDefenseRating(); this.scene.events.emit('show-notification', { title: '✅ Stolp Zgrajen', message: 'Opazovalni stolp povečuje vidno polje!', icon: '🗼', duration: 3000, color: '#00FF00' }); console.log(`✅ Watchtower construction complete`); } createLosIndicator(tower) { // Visual LoS circle const losCircle = this.scene.add.graphics(); losCircle.lineStyle(2, 0xFFFF00, 0.3); losCircle.strokeCircle(tower.x, tower.y, tower.losRange); losCircle.setDepth(1); tower.losCircle = losCircle; // Pulse animation this.scene.tweens.add({ targets: losCircle, alpha: 0.5, duration: 2000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); } /** * RAID DETECTION & DEFENSE */ onRaidIncoming(raidData) { // Check if watchtowers can detect early const earlyDetection = this.checkEarlyDetection(raidData); if (earlyDetection) { // Give player extra time to prepare raidData.timeToArrival *= (1 + this.watchtowerConfig.detectionBonus); this.scene.events.emit('show-notification', { title: '🚨 RAID OPAŽEN!', message: `Opazovalni stolp je opazil roparje! +${Math.floor(this.watchtowerConfig.detectionBonus * 100)}% časa za pripravo!`, icon: '🗼', duration: 5000, color: '#FFAA00' }); } this.activeRaids.push(raidData); console.log(`🚨 Raid incoming: ${raidData.strength} strength`); } checkEarlyDetection(raidData) { // Check if any watchtower can detect the raid for (const tower of this.watchtowers) { if (tower.isBuilding) continue; const distance = Phaser.Math.Distance.Between( tower.x, tower.y, raidData.x, raidData.y ); if (distance <= tower.losRange) { return true; } } return false; } onRaidAttack(raidData) { // Calculate defense vs attack const raidStrength = raidData.strength; const cityDefense = this.cityDefenseRating; console.log(`⚔️ Raid Attack: ${raidStrength} vs Defense: ${cityDefense}`); if (cityDefense >= raidStrength) { // City defense holds! this.raidRepelled(raidData); } else { // Walls take damage this.wallsDamaged(raidStrength - cityDefense); } } raidRepelled(raidData) { this.activeRaids = this.activeRaids.filter(r => r.id !== raidData.id); this.scene.events.emit('show-notification', { title: '✅ RAID ODBIJEN!', message: 'Obzidja so ustavila roparje!', icon: '🛡️', duration: 5000, color: '#00FF00' }); // Reward for successful defense if (this.scene.inventorySystem) { this.scene.inventorySystem.addGold(raidData.strength * 10); } console.log(`✅ Raid repelled successfully`); } wallsDamaged(damage) { // Damage weakest walls first const sortedWalls = [...this.walls].sort((a, b) => a.health - b.health); let remainingDamage = damage; for (const wall of sortedWalls) { if (remainingDamage <= 0) break; if (wall.isBuilding) continue; const damageToWall = Math.min(wall.health, remainingDamage); wall.health -= damageToWall; remainingDamage -= damageToWall; // Visual damage if (wall.sprite) { wall.sprite.setTint(0xFF4444); setTimeout(() => wall.sprite.clearTint(), 500); } // Wall destroyed if (wall.health <= 0) { this.destroyWall(wall); } } this.scene.events.emit('show-notification', { title: '⚠️ Obzidja Poškodovana', message: `Raid je povzročil ${damage} škode!`, icon: '💥', duration: 4000, color: '#FF4444' }); this.updateDefenseRating(); } destroyWall(wall) { if (wall.sprite) { // Destruction animation this.scene.tweens.add({ targets: wall.sprite, alpha: 0, scaleX: 0.5, scaleY: 0.5, duration: 500, onComplete: () => wall.sprite.destroy() }); } this.walls = this.walls.filter(w => w.id !== wall.id); console.log(`💥 Wall destroyed: ${wall.id}`); } /** * PATROL SYSTEM */ unlockPatrols() { this.patrolsUnlocked = true; console.log('✅ Patrol system unlocked'); } createPatrol(route) { if (!this.patrolsUnlocked) { console.warn('Patrols not unlocked yet'); return null; } const patrol = { id: `patrol_${Date.now()}`, route: route, // Array of {x, y} waypoints currentWaypoint: 0, guards: [], active: true }; this.patrols.push(patrol); return patrol; } /** * DEFENSE RATING CALCULATION */ updateDefenseRating() { let rating = 0; // Add wall defense this.walls.forEach(wall => { if (!wall.isBuilding) { const healthPercent = wall.health / wall.maxHealth; rating += this.wallTiers[wall.tier].defense * healthPercent; } }); // Add watchtower bonus rating += this.watchtowers.filter(t => !t.isBuilding).length * 10; // Add patrol bonus rating += this.patrols.filter(p => p.active).length * 5; this.cityDefenseRating = Math.floor(rating); // Emit update this.scene.events.emit('defense:rating_updated', this.cityDefenseRating); console.log(`🛡️ Defense Rating: ${this.cityDefenseRating}`); } startDefenseUpdates() { // Update defense every 5 seconds setInterval(() => { this.updateDefenseRating(); }, 5000); } /** * UTILITY FUNCTIONS */ hasResources(cost) { if (!this.scene.inventorySystem) return true; // Dev mode for (const [resource, amount] of Object.entries(cost)) { if (!this.scene.inventorySystem.hasItem(resource, amount)) { return false; } } return true; } deductResources(cost) { if (!this.scene.inventorySystem) return; for (const [resource, amount] of Object.entries(cost)) { this.scene.inventorySystem.removeItem(resource, amount); } } formatCost(cost) { return Object.entries(cost) .map(([resource, amount]) => `${amount}x ${resource}`) .join(', '); } /** * GET STATUS FOR UI */ getDefenseStatus() { return { rating: this.cityDefenseRating, walls: this.walls.length, watchtowers: this.watchtowers.length, patrols: this.patrols.length, activeRaids: this.activeRaids.length }; } destroy() { this.walls.forEach(wall => { if (wall.sprite) wall.sprite.destroy(); if (wall.losCircle) wall.losCircle.destroy(); }); this.watchtowers.forEach(tower => { if (tower.sprite) tower.sprite.destroy(); if (tower.losCircle) tower.losCircle.destroy(); }); this.walls = []; this.watchtowers = []; this.patrols = []; } }