DEFENSE SYSTEM COMPLETE: DefenseSystem.js (3-tier walls, watchtowers with LoS, raid detection, construction, damage mechanics). Generated 8 crop sprites (cannabis/mushrooms 4 stages each), 6 building sprites (hospital/police/mayor), 4 defense sprites (3 walls + watchtower). Total: 18 new sprites + defense code.
This commit is contained in:
549
src/systems/DefenseSystem.js
Normal file
549
src/systems/DefenseSystem.js
Normal file
@@ -0,0 +1,549 @@
|
||||
/**
|
||||
* 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 = [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user