Upload files to "js"
This commit is contained in:
313
js/towers.js
Normal file
313
js/towers.js
Normal file
@@ -0,0 +1,313 @@
|
||||
// Defense Tower System
|
||||
const Towers = {
|
||||
list: [],
|
||||
projectiles: [],
|
||||
|
||||
// Tower types
|
||||
TYPES: {
|
||||
'gun_turret': {
|
||||
name: 'Gun Turret',
|
||||
description: 'Rapid-fire bullets',
|
||||
health: 200,
|
||||
maxHealth: 200,
|
||||
range: 200,
|
||||
damage: 15,
|
||||
fireRate: 4, // shots per second
|
||||
projectileSpeed: 500,
|
||||
projectileColor: '#ffcc00',
|
||||
cost: { 'iron-plate': 20, 'gear': 10 },
|
||||
color: '#888899',
|
||||
colorDark: '#555566'
|
||||
},
|
||||
'flame_turret': {
|
||||
name: 'Flame Turret',
|
||||
description: 'Short range, high damage, burns',
|
||||
health: 150,
|
||||
maxHealth: 150,
|
||||
range: 120,
|
||||
damage: 40,
|
||||
fireRate: 2,
|
||||
burnDamage: 10,
|
||||
burnDuration: 3,
|
||||
projectileSpeed: 300,
|
||||
projectileColor: '#ff6600',
|
||||
cost: { 'iron-plate': 15, 'copper-plate': 20, 'coal': 50 },
|
||||
color: '#cc4400',
|
||||
colorDark: '#882200'
|
||||
},
|
||||
'laser_turret': {
|
||||
name: 'Laser Turret',
|
||||
description: 'Long range, high damage',
|
||||
health: 100,
|
||||
maxHealth: 100,
|
||||
range: 350,
|
||||
damage: 50,
|
||||
fireRate: 1,
|
||||
projectileSpeed: 1000,
|
||||
projectileColor: '#ff0000',
|
||||
cost: { 'iron-plate': 30, 'copper-plate': 30, 'circuit': 20 },
|
||||
color: '#cc0044',
|
||||
colorDark: '#880022'
|
||||
},
|
||||
'tesla_turret': {
|
||||
name: 'Tesla Coil',
|
||||
description: 'Chain lightning to multiple enemies',
|
||||
health: 120,
|
||||
maxHealth: 120,
|
||||
range: 180,
|
||||
damage: 25,
|
||||
fireRate: 0.8,
|
||||
chainCount: 3,
|
||||
chainRange: 100,
|
||||
projectileSpeed: 800,
|
||||
projectileColor: '#44aaff',
|
||||
cost: { 'iron-plate': 25, 'copper-plate': 50, 'circuit': 15 },
|
||||
color: '#2266aa',
|
||||
colorDark: '#113366'
|
||||
},
|
||||
'cannon_turret': {
|
||||
name: 'Cannon',
|
||||
description: 'Slow, explosive AoE damage',
|
||||
health: 300,
|
||||
maxHealth: 300,
|
||||
range: 280,
|
||||
damage: 100,
|
||||
fireRate: 0.5,
|
||||
splashRadius: 80,
|
||||
projectileSpeed: 250,
|
||||
projectileColor: '#333333',
|
||||
cost: { 'iron-plate': 50, 'gear': 20, 'coal': 30 },
|
||||
color: '#444455',
|
||||
colorDark: '#222233'
|
||||
}
|
||||
},
|
||||
|
||||
// Initialize
|
||||
init() {
|
||||
this.list = [];
|
||||
this.projectiles = [];
|
||||
},
|
||||
|
||||
// Check if can place tower
|
||||
canPlace(type, x, y) {
|
||||
// Check bounds
|
||||
if (!Utils.inBounds(x, y)) return false;
|
||||
|
||||
// Check collision with buildings
|
||||
if (Buildings.getAt(x, y)) return false;
|
||||
|
||||
// Check collision with other towers
|
||||
if (this.getAt(x, y)) return false;
|
||||
|
||||
// Check cost
|
||||
const towerType = this.TYPES[type];
|
||||
if (!Resources.canAfford(towerType.cost)) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Place a tower
|
||||
place(type, x, y) {
|
||||
if (!this.canPlace(type, x, y)) {
|
||||
Audio.playError();
|
||||
return false;
|
||||
}
|
||||
|
||||
const towerType = this.TYPES[type];
|
||||
Resources.payCost(towerType.cost);
|
||||
|
||||
this.list.push({
|
||||
type,
|
||||
x,
|
||||
y,
|
||||
health: towerType.maxHealth,
|
||||
maxHealth: towerType.maxHealth,
|
||||
cooldown: 0,
|
||||
angle: 0,
|
||||
target: null
|
||||
});
|
||||
|
||||
Audio.playPlace();
|
||||
return true;
|
||||
},
|
||||
|
||||
// Remove a tower
|
||||
remove(x, y) {
|
||||
const tower = this.getAt(x, y);
|
||||
if (!tower) return false;
|
||||
|
||||
const idx = this.list.indexOf(tower);
|
||||
if (idx !== -1) {
|
||||
this.list.splice(idx, 1);
|
||||
// Refund half
|
||||
const towerType = this.TYPES[tower.type];
|
||||
Resources.refundHalf(towerType.cost);
|
||||
Audio.playDelete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Get tower at position
|
||||
getAt(x, y) {
|
||||
return this.list.find(t => t.x === x && t.y === y);
|
||||
},
|
||||
|
||||
// Update all towers
|
||||
update(dt) {
|
||||
// Update towers
|
||||
this.list.forEach(tower => this.updateTower(tower, dt));
|
||||
|
||||
// Update projectiles
|
||||
for (let i = this.projectiles.length - 1; i >= 0; i--) {
|
||||
const proj = this.projectiles[i];
|
||||
this.updateProjectile(proj, dt);
|
||||
|
||||
// Remove dead projectiles
|
||||
if (proj.dead) {
|
||||
this.projectiles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Update single tower
|
||||
updateTower(tower, dt) {
|
||||
const type = this.TYPES[tower.type];
|
||||
const towerX = (tower.x + 0.5) * CONFIG.TILE_SIZE;
|
||||
const towerY = (tower.y + 0.5) * CONFIG.TILE_SIZE;
|
||||
|
||||
// Reduce cooldown
|
||||
tower.cooldown = Math.max(0, tower.cooldown - dt);
|
||||
|
||||
// Find target
|
||||
const target = Enemies.getNearest(towerX, towerY, type.range);
|
||||
tower.target = target;
|
||||
|
||||
if (target) {
|
||||
// Rotate toward target
|
||||
const dx = target.x - towerX;
|
||||
const dy = target.y - towerY;
|
||||
tower.angle = Math.atan2(dy, dx);
|
||||
|
||||
// Fire if ready
|
||||
if (tower.cooldown <= 0) {
|
||||
tower.cooldown = 1 / type.fireRate;
|
||||
this.fire(tower, target);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Fire at target
|
||||
fire(tower, target) {
|
||||
const type = this.TYPES[tower.type];
|
||||
const startX = (tower.x + 0.5) * CONFIG.TILE_SIZE;
|
||||
const startY = (tower.y + 0.5) * CONFIG.TILE_SIZE;
|
||||
|
||||
this.projectiles.push({
|
||||
towerType: tower.type,
|
||||
x: startX,
|
||||
y: startY,
|
||||
targetX: target.x,
|
||||
targetY: target.y,
|
||||
target: target,
|
||||
speed: type.projectileSpeed,
|
||||
damage: type.damage,
|
||||
color: type.projectileColor,
|
||||
splashRadius: type.splashRadius || 0,
|
||||
chainCount: type.chainCount || 0,
|
||||
chainRange: type.chainRange || 0,
|
||||
burnDamage: type.burnDamage || 0,
|
||||
burnDuration: type.burnDuration || 0
|
||||
});
|
||||
|
||||
// Visual feedback
|
||||
tower.firing = true;
|
||||
setTimeout(() => tower.firing = false, 50);
|
||||
|
||||
// Sound
|
||||
Audio.playShoot(tower.type);
|
||||
},
|
||||
|
||||
// Update projectile
|
||||
updateProjectile(proj, dt) {
|
||||
const dx = proj.targetX - proj.x;
|
||||
const dy = proj.targetY - proj.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < 10) {
|
||||
// Hit target
|
||||
this.projectileHit(proj);
|
||||
proj.dead = true;
|
||||
} else {
|
||||
// Move toward target
|
||||
const moveX = (dx / dist) * proj.speed * dt;
|
||||
const moveY = (dy / dist) * proj.speed * dt;
|
||||
proj.x += moveX;
|
||||
proj.y += moveY;
|
||||
}
|
||||
|
||||
// Timeout
|
||||
proj.lifetime = (proj.lifetime || 0) + dt;
|
||||
if (proj.lifetime > 5) proj.dead = true;
|
||||
},
|
||||
|
||||
// Projectile hits
|
||||
projectileHit(proj) {
|
||||
// Splash damage
|
||||
if (proj.splashRadius > 0) {
|
||||
const enemies = Enemies.getInRadius(proj.targetX, proj.targetY, proj.splashRadius);
|
||||
enemies.forEach(e => {
|
||||
Enemies.damage(e, proj.damage);
|
||||
});
|
||||
Audio.playExplosion();
|
||||
} else if (proj.chainCount > 0) {
|
||||
// Chain lightning
|
||||
let targets = [proj.target];
|
||||
let lastTarget = proj.target;
|
||||
|
||||
for (let i = 0; i < proj.chainCount; i++) {
|
||||
const nearby = Enemies.getInRadius(lastTarget.x, lastTarget.y, proj.chainRange)
|
||||
.filter(e => !targets.includes(e));
|
||||
if (nearby.length > 0) {
|
||||
const next = nearby[0];
|
||||
targets.push(next);
|
||||
lastTarget = next;
|
||||
}
|
||||
}
|
||||
|
||||
targets.forEach(e => {
|
||||
Enemies.damage(e, proj.damage);
|
||||
});
|
||||
|
||||
// Store chain for rendering
|
||||
proj.chainTargets = targets;
|
||||
} else {
|
||||
// Single target
|
||||
if (proj.target && proj.target.health > 0) {
|
||||
Enemies.damage(proj.target, proj.damage);
|
||||
|
||||
// Apply burn
|
||||
if (proj.burnDamage > 0) {
|
||||
proj.target.burning = {
|
||||
damage: proj.burnDamage,
|
||||
duration: proj.burnDuration
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Get data for saving
|
||||
getData() {
|
||||
return {
|
||||
list: this.list.map(t => ({ ...t, target: null })),
|
||||
projectiles: [] // Don't save projectiles
|
||||
};
|
||||
},
|
||||
|
||||
// Load data
|
||||
setData(data) {
|
||||
this.list = data.list?.map(t => ({ ...t })) || [];
|
||||
this.projectiles = [];
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user