var turretVersion = 0.40;
var keys = [];
var utils = {
radians: function(degrees){
return degrees * Math.PI / 180
},
random: function(min, max){
return min+Math.round(Math.random() * (max - min));
},
nearest: function(numb, to){
return Math.round(numb / to) * to;
},
range: function(entity1,entity2){
return Math.sqrt(Math.pow(entity1.y - entity2.y,2) + Math.pow(entity1.x - entity2.x,2));
},
calculateShot: function(entity1,entity2){
var range = this.range(entity1,entity2);
var speedX = ((entity1.x - entity2.x) / range) * 5;
var speedY = ((entity1.y - entity2.y) / range) * 5;
return {
vx: speedX+entity1.vx,
vy: speedY+entity1.vy,
}
},
squareCollison: function(rect1,rect2){
return rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.height + rect1.y > rect2.y;
},
circleCollision: function(circle1,circle2){
var dx = circle1.x - circle2.x;
var dy = circle1.y - circle2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return (distance < circle1.radius + circle2.radius)
},
roundTo: function(numb,to){
to = Math.pow(10,to);
return Math.round(numb*to)/to;
},
pointToRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
}
$(document).keydown(function (e) {
keys[e.keyCode] = true;
});
$(document).keyup(function (e) {
delete keys[e.keyCode];
});
var pathPoints = [
{ x: 25, y: 0 },
{ x: 25, y: 500 },
{ x: 350, y: 500 },
{ x: 350, y: 250 },
{ x: 100, y: 250 },
{ x: 100, y: 100 },
{ x: 250, y: 100 },
{ x: 250, y: 680 }
];
var enemies = {};
var turrets = {};
var bullets = {};
var particles = {};
var cooldown = 1000;
var FPS = 60;
var lives = 20;
var money = 500;
var nextWaveIn = 100;
var waveCount = 0;
var mouseX = 0;
var mouseY = 0;
// enemies
var basic = {
speed: 2,
color: "black",
hp: 10,
size: 10,
}
// turrets
var normal = {
x: Math.random()*600,
y: 50, // 50
dmg: 2, // 1
fireRate: 25, // 20
fire: 0, // always 0
range: 100, // 100
color: "yellow",
strokeColor: "goldenrod",
level: 0,
upgrades: {
lvl1:{
dmg: 2,
fireRate: 25,
range: 100,
price: 10,
},
lvl2:{
dmg: 2,
fireRate: 20,
range: 110,
price: 20,
},
lvl3:{
dmg: 3,
fireRate: 18,
range: 125,
price: 25,
},
lvl4:{
dmg: 5,
fireRate: 16,
range: 150,
price: 30,
},
lvl5:{
dmg: 5,
fireRate: 14,
range: 200,
price: 30,
}
}
}
function newEnemy(type, x, y, pathNode) {
type = "basic";
var speed = window[type].speed;
var color = window[type].color;
var hp = window[type].hp;
var hpMax = hp;
if (!x){var x = 0;}
if(!y){var y = 20;}
var size = window[type].size;
var special = window[type].special;
x = Math.ceil(x/2) * 2;
if (!pathNode) pathNode = 0;
var id = Math.random();
var e = {
x: x,
y: y,
vx:speed,
vy:speed,
speed: speed,
color: color,
size: size,
hp: hp,
hpMax: hpMax,
special: special,
pathNode: pathNode,
goToPosX: true,
goToPosY: false,
selected: false,
}
enemies[id] = e;
}
function newBullet(x,y, dmg, vx, vy, fire, poison, slow) {
var id = Math.random();
if (!fire) fire = 0;
if (!poison) poison = 0;
if (!slow) slow = 0;
var b = {
x: x,
y: y,
vx: vx,
vy: vy,
life: 0,
dmg: dmg,
}
bullets[id] = b;
}
function newTurret(type, x, y) {
if (!type) type = "normal";
var id = Math.random();
var t = {
x: x,
y: y, // 50
dmg: window[type].dmg, // 1
fireRate: window[type].fireRate, // 20
fire: 0, // always 0
range: window[type].range, // 100
type: type,
strokeColor: window[type].strokeColor,
color: window[type].color,
selected: false,
level: 1,
upgrades: window[type].upgrades,
isSold: false,
}
turrets[id] = t;
}
function newParticle(x, y, life, color){
var randx = Math.random()*2-1;
var randy = Math.random()*2-1;
if (!color) color = "black";
var p = {
x: x,
y: y,
vx: randy,
vy: randx,
color: color,
life: life,
}
particles[Math.random()] = p;
}
var ctx = document.getElementById("canvas").getContext("2d");
function engine() {
$("#gold").html("Gold: " + Math.floor(money * 10) / 10);
$("#lives").html("Lives: " + Math.floor(lives));
$("#wave").html("Wave: " + waveCount);
$("#turret_version").html("v" + turretVersion);
$("#div_header").html('Welcome to Turret Defense!');
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawMap();
for (var key in enemies){
if (enemies[key] && cooldown > 0){
cooldown -= 1;
break;
}
}
var disabledColor = "#5e5e5e";
var enabledColor = "#0cb53c";
$('#specialAttackBtn').html("Special attack
"+cooldown+"s");
if (cooldown <= 0) $('#specialAttackBtn').css("background-color",enabledColor);
if (cooldown > 0) $('#specialAttackBtn').css("background-color",disabledColor);
var enemiesAreAlive = false;
for (var key in enemies){
if (enemies[key])$('#nextWaveBtn').css("background-color",disabledColor);
enemiesAreAlive = true;
}
if (!enemiesAreAlive) $('#nextWaveBtn').css("background-color",enabledColor);
if (lives <= 0){
}
for (var key in enemies) {
entity(enemies[key]);
if (enemies[key].hp <= 0) {
for (var i = 0; i < 10; i++){
newParticle(enemies[key].x,enemies[key].y, 100);
}
money += Math.floor(enemies[key].hpMax / 10);
delete enemies[key];
}
}
for (var key in bullets){
bulletEngine(bullets[key]);
if (bullets[key].x < 0 || bullets[key].x >= canvas.width || bullets[key].y < 0 || bullets[key].y >= canvas.height){
for (var i = 0; i < 10; i++){
newParticle(bullets[key].x,bullets[key].y,150);
}
delete bullets[key];
}
}
for (var key in turrets) {
turretEngine(turrets[key]);
if (turrets[key].isSold){
delete turrets[key];
}
}
for (var key in particles) {
particle(particles[key]);
if (particles[key].life <= 0) {
delete particles[key];
}
}
var towers = ["normal"];
for (var i = 0; i < towers.length; i++){
var checked = document.getElementById(towers[i]).checked;
if (checked){
var tower = window[towers[i]];
ctx.fillStyle = "black";
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(mouseX,mouseY,tower.range,0,Math.PI*2,true);
ctx.stroke();
ctx.fillStyle = tower.color;
ctx.strokeStyle = tower.strokeColor;
ctx.beginPath();
ctx.arc(mouseX,mouseY,10,0,Math.PI*2,true);
ctx.stroke();
ctx.fill();
}
}
setTimeout(engine, 1000 / FPS);
}
engine();
function entity(ent) {
ctx.fillStyle = ent.color;
ctx.fillRect(ent.x, ent.y, ent.size, ent.size);
//ctx.font = "12px Arial";
//ctx.fillText(Math.floor(ent.hp * 10) / 10, ent.x, ent.y - 10);
for (var key in bullets){
if (ent.x < bullets[key].x + 5 && ent.x + ent.size > bullets[key].x && ent.y < bullets[key].y + 5 && ent.size + ent.y > bullets[key].y){
ent.hp -= bullets[key].dmg;
ent.poison = bullets[key].poison;
ent.onFire = bullets[key].fire;
ent.slow = bullets[key].slow;
for (var i = 0; i < 5; i++){
newParticle(bullets[key].x,bullets[key].y,60);
}
delete bullets[key];
}
}
if (ent.y >= 650){
ent.hp = 0;
lives--;
}
var perc = ent.hp / ent.hpMax;
ctx.fillStyle = "black";
ctx.fillRect(ent.x, ent.y-10, ent.size, 5);
ctx.fillStyle = "red";
ctx.fillRect(ent.x, ent.y-10, ent.size * perc, 5);
var path = ent.pathNode;
if (ent.y > pathPoints[path].y){
ent.y-=ent.speed;
ent.vx = 0;
ent.vy = ent.speed;
ent.goToPosY = true;
}
if (ent.y < pathPoints[path].y){
ent.y+=ent.speed;
ent.vx = 0;
ent.vy = ent.speed;
ent.goToPosY = true;
}
if (ent.x < pathPoints[path].x){
ent.x+= ent.speed;
ent.vx = ent.speed;
ent.vy = 0;
ent.goToPosX = true;
ent.goToPosY = false;
}
if (ent.x > pathPoints[path].x){
ent.x-=ent.speed;
ent.vx = -ent.speed;
ent.vy = 0;
ent.goToPosX = false;
ent.goToPosY = false;
}
// 997 - 1003
if (ent.x-3 <= pathPoints[path].x && ent.x+3 >= pathPoints[path].x && ent.y-3 <= pathPoints[path].y && ent.y+3 >= pathPoints[path].y){
ent.pathNode++;
}
if (ent.selected){
ctx.font = "600 16px Arial bold";
ctx.fillStyle = "black";
ctx.fillText((Math.floor(ent.hp*10)/10)+" hp",ent.x-10,ent.y-10);
}
}
function turretEngine(turret) {
if (turret.fire < turret.fireRate){
turret.fire++;
}
ctx.beginPath();
ctx.arc(turret.x, turret.y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = turret.color;
ctx.fill();
ctx.lineWidth = 3;
ctx.strokeStyle = turret.strokeColor;
ctx.stroke();
var showRange = document.getElementById("showrange").checked;
if (showRange == true || turret.selected){
ctx.fillStyle = turret.color;
ctx.beginPath();
ctx.arc(turret.x,turret.y,turret.range,0,Math.PI*2,true);
ctx.stroke();
ctx.font = "600 16px Arial bold";
ctx.fillStyle = "black";
// turret -> upgrades -> lvl1 -> dmg
var upDmg = turret.upgrades["lvl"+(turret.level+1)].dmg;
var upFire = turret.upgrades["lvl"+(turret.level+1)].fireRate;
var upPrice = turret.upgrades["lvl"+(turret.level+1)].price;
if (turret.level == 4){
var fireRate = (Math.floor(1000/60 * turret.fireRate*100/1000)/100);
ctx.fillText(turret.dmg+" dmg",turret.x-10,turret.y-30);
ctx.fillText(fireRate+"s",turret.x-10,turret.y-20);
}
if (turret.level < 4){
ctx.fillText((Math.floor(turret.dmg*100)/100)+" dmg -> "+upDmg,turret.x-10,turret.y-45);
var fireRate = (Math.floor(1000/60 * turret.fireRate*100/1000)/100);
upFire = (Math.floor(1000/60 * upFire*100/1000)/100);
ctx.fillText(fireRate+"s -> "+upFire+"s",turret.x-10,turret.y-30);
ctx.fillText(upPrice+"$",turret.x-10,turret.y-15);
ctx.fillText(turret.level,turret.x-3,turret.y+3);
}
}
var perc = turret.fire / turret.fireRate;
ctx.fillStyle = "black";
ctx.fillRect(turret.x-10, turret.y+10, 20, 5);
ctx.fillStyle = "yellow";
ctx.fillRect(turret.x-10, turret.y+10, 20 * perc, 5);
var firstInLine = null;
if (turret.fire >= turret.fireRate) {
for (var key in enemies){
var range = Math.sqrt(Math.pow(enemies[key].y - turret.y,2) + Math.pow(enemies[key].x - turret.x,2));
if (range <= turret.range){
if (!firstInLine)
firstInLine = enemies[key];
if ((utils.range(enemies[key], pathPoints[enemies[key].pathNode])) <
(utils.range(firstInLine , pathPoints[firstInLine.pathNode])))
{
if (enemies[key].pathNode > firstInLine.pathNode)
firstInLine = enemies[key];
}
var shot = utils.calculateShot(enemies[key], turret);
turret.fire = 0;
}
}
if (firstInLine) {
var shot = utils.calculateShot(firstInLine, turret);
newBullet(turret.x,turret.y,turret.dmg, shot.vx, shot.vy);
}
}
// turret -> upgrades -> lvl1 -> dmg
var level = "lvl"+turret.level;
var stats = turret.upgrades[level];
turret.range = stats.range;
turret.dmg = stats.dmg;
turret.fireRate = stats.fireRate;
var upPrice = turret.upgrades[("lvl"+(turret.level+1))].price;
var nextLevel = turret.upgrades[("lvl"+(turret.level+1))];
// U
if (turret.selected && keys[85] && turret.level < 4){
var newFireRate = (Math.floor(1000/60 * nextLevel.fireRate*100/1000)/100);
var actualFireRate = (Math.floor(1000/60 * turret.fireRate*100/1000)/100);
$('#dialog-msg').html("Are you sure you want to spend "+upPrice+"$ for upgrade?
Damage: "+(Math.floor(turret.dmg*100)/100)+" ➜ "+(Math.floor(nextLevel.dmg*100)/100)+"
Fire rate: "+(Math.floor(actualFireRate*100)/100)+"s ➜ "+(Math.floor(newFireRate*100)/100)+"s
Range: "+turret.range.toFixed(0)+"m ➜ "+nextLevel.range.toFixed(0)+"m");
$('#dialog').dialog({
modal: true,
draggable: false,
resizeable: false,
title: "Are you sure?",
buttons:{
"OK":function(){
if (money >= upPrice){
money -= upPrice;
turret.level++;
$(this).dialog("close");
}
},
"CANCEL":function(){
$(this).dialog("close");
}
}
});
}
// S
if (turret.selected && keys[83]){
var sellPrice = Math.floor(stats.price);
$('#dialog-msg').html("Sell turret for "+sellPrice+"$ ?")
$('#dialog').dialog({
modal: true,
draggable: false,
resizeable: false,
title: "Are you sure?",
buttons:{
"Yes":function(){
money += sellPrice;
turret.isSold = true;
$(this).dialog("close");
},
"No":function(){
$(this).dialog("close");
}
}
});
}
}
function bulletEngine(bullet){
ctx.fillStyle = "black";
if (bullet.fire > 0) ctx.fillStyle = "red";
if (bullet.poison > 0) ctx.fillStyle = "lime";
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 2.5, 0, 2 * Math.PI, false);
ctx.fill();
bullet.x += bullet.vx;
bullet.y += bullet.vy;
}
function particle(particle){
ctx.fillStyle = particle.color;
ctx.fillRect(particle.x,particle.y,particle.life/50,particle.life/50);
particle.x += particle.vx;
particle.y += particle.vy;
particle.life--;
}
function drawMap(){
ctx.beginPath();
ctx.strokeStyle = "brown";
ctx.lineWidth=20;
ctx.lineJoin='round';
var lastPoint = null;
for(var i = 0; i < pathPoints.length; i++) {
var curPoint = pathPoints[i];
if (lastPoint)
ctx.lineTo(curPoint.x, curPoint.y);
ctx.moveTo(curPoint.x, curPoint.y);
lastPoint = curPoint;
}
ctx.stroke();
ctx.lineWidth = 2;
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
window.oncontextmenu = function(){
//document.getElementById("select").checked = true;
return false;
}
function placeTower(){
// check collision with mouse
for (var key in turrets){
var dx = turrets[key].x - mouseX;
var dy = turrets[key].y - mouseY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 13){
turrets[key].selected = true;
break;
}
if (distance > 13){
for (var key in turrets){
turrets[key].selected = false;
}
}
}
for (var key in enemies){
var dx = enemies[key].x - mouseX;
var dy = enemies[key].y - mouseY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < enemies[key].size + 3){
enemies[key].selected = true;
break;
}
if (distance > 13){
for (var key in enemies){
enemies[key].selected = false;
}
}
}
var normalChecked = document.getElementById("normal").checked;
if (normalChecked && money >= 20){
newTurret("normal",mouseX,mouseY);
money -= 20;
}
}
document.onmousemove = function(mouse){
mouseX = mouse.clientX - document.getElementById('canvas').getBoundingClientRect().left;
mouseY = mouse.clientY - document.getElementById('canvas').getBoundingClientRect().top;
};
function wave(type, amount, timeout) {
var x = 0;
function newEnemyToWave() {
if (x < amount) {
newEnemy(type);
}
x++;
setTimeout(newEnemyToWave, timeout);
}
newEnemyToWave();
}
wave("basic",30,1000);
$('input[type=checkbox]').checkboxradio();
$('input[type=radio]').checkboxradio({
icon: false
});