550 lines
16 KiB
JavaScript
550 lines
16 KiB
JavaScript
/**
|
|
* 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 = [];
|
|
}
|
|
}
|