function Hero() { var game = null; var position = {x : 10, y: 10}; var angle = 90; var destination = null; var maxSpeed = 3; var triggerPlusOne = 0; var coinAddAmount = 0; var triggerHeartPickup = 0; var triggerHeartDecrement = 0; var triggerXpGain = 0; var xpGainAmount = 0; var level = 1; var knockbackStrength = 10; var triggerSale = 0; var saleAmount = 0; var shopMemory = []; var purchaseHistory = []; var triggerWeaponUpgrade = 0; var triggerLevelUp = 0; var inventory = {coins: 0, hearts: 1, xp: 0}; var viewRange = 150; var attentionSpan = 50; var attention = 50; var delay = 0; var aggressiveness = 1.5; var name = ""; var heroColor = ""; this.init = function(canvas, gameReference, index) { game = gameReference; position.x = Math.floor(canvas.width * Math.random()); position.y = Math.floor(canvas.height * Math.random()); inventory.maxSpeed = precisionRound(2.5 * Math.random() + 0.5, 1); inventory.coins = 0; inventory.maxHearts = Math.floor(1 * (6 * Math.random() + 1) + 8); inventory.hearts = inventory.maxHearts; inventory.xp = 0; inventory.weaponStrength = 1; level = 1; viewRange = Math.floor(150 * Math.random() + 50); attentionSpan = Math.floor(viewRange * Math.random() + 100); attention = attentionSpan; aggressiveness = 3 * Math.random(); name = game.nameGenerator.generate(); heroColor = getRandomColor(); if(index + 1 <= 5) { heroColor = colorPicker(index); } }; var colorPicker = function(colorIndex) { switch(colorIndex) { case 0: return "#000000"; case 1: return "#DD3333"; case 2: return "#33DD33"; case 3: return "#3333DD"; case 4: return "#DD33DD"; case 5: return "#33DDDD"; } }; var getRandomColor = function() { var colorIndex = Math.floor(5 * Math.random()); return colorPicker(colorIndex); } this.update = function(canvas) { if(delay > 0) { delay -= maxSpeed; return; } attention--; if(attention <= 0) { destination = null; attention = attentionSpan; } if(destination != null && !destination.isConsumed) { var destPos = destination.getPosition(); angle = Math.atan2(destPos.y - position.y, destPos.x - position.x) * 180 / Math.PI; var deltaX = maxSpeed * Math.cos(angle * Math.PI / 180); var deltaY = maxSpeed * Math.sin(angle * Math.PI / 180); angle %= 360; position.x += deltaX; position.y += deltaY; if(Math.abs(Math.floor(destPos.x - position.x)) <= maxSpeed + 0.5 && Math.abs(Math.floor(destPos.y - position.y)) <= maxSpeed + 0.5) { attention = attentionSpan; if(doneWithDestination(destination.visit(this))) { destination = null; } } } else { var visibleDestinations = game.getVisibleDestinations(position.x, position.y, viewRange); this.setFocus(this.decideDestination(visibleDestinations, game.getAllDestinations())); } }; this.actionDelay = function(delayAmount) { delay += delayAmount; } var doneWithDestination = function(dest) { if(dest.getType() == "monster") { if (inventory.hearts > 1 && !dest.isConsumed) { return false; } } return true; } this.decideDestination = function(visibleDestinations, allDestinations) { //remove self for(var i = visibleDestinations.length - 1; i >= 0; i--) { var hero = visibleDestinations[i]; if(hero.getType() == "hero") { if(hero == this) { visibleDestinations.splice(i, 1); } } } //if low on hearts, prioritize looking for a heart if(inventory.hearts < inventory.maxHearts) { for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) { if(newDest.getType() == "heart") { return newDest; } } } for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) { if (newDest.getType() == "coin") { return newDest; } } for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) { if (newDest.getType() == "monster" && assessThreat(newDest)) { return newDest; } if(newDest.getType() == "shop" && shopMemory.length <= 0) { return newDest; } if(newDest.getType() == "shop" && inventory.coins >= 5) { for(var j = 0; j < shopMemory.length; j++) { if(inventory.coins >= shopMemory[j].cost && hasNeedToBuy(shopMemory[j])) { return newDest; } } } if(newDest.getType() == "hero" && newDest.getLevel() > 5 && assessThreat(newDest)) { return newDest; } } for(var i = 0, newDest = allDestinations[0]; newDest = allDestinations[i]; i++) { if(newDest.getType() == "shop" && shopMemory.length <= 0) { return newDest; } if(newDest.getType() == "shop" && inventory.coins >= 5) { for(var j = 0; j < shopMemory.length; j++) { if(inventory.coins >= shopMemory[j].cost && hasNeedToBuy(shopMemory[j])) { return newDest; } } } if (newDest.getType() == "monster" && assessThreat(newDest)) { return newDest; } } //when in doubt, go back to town for(var i = 0, newDest = allDestinations[0]; newDest = allDestinations[i]; i++) { if(newDest.getType() == "shop") { console.log(name + " has nothing to do"); return newDest; } } //pick randomly //return visibleDestinations[Math.floor(visibleDestinations.length * Math.random())]; } var assessThreat = function(monster) { var isNotSuicide = inventory.hearts + 1 > monster.getLevel(); var itCanKillMe = level < monster.getLevel(); var iCanKillIt = inventory.weaponStrength + aggressiveness > monster.getLevel() + 1; return isNotSuicide && !itCanKillMe || iCanKillIt; } var precisionRound = function(number, precision) { var factor = Math.pow(10, precision); return Math.round(number * factor) / factor; }; this.getHeroColor = function() { return heroColor; } this.draw = function(context, offset) { context.save(); context.translate(position.x, position.y); context.beginPath(); context.rotate(angle * Math.PI / 180); context.strokeStyle = this.getHeroColor(); context.fillStyle = this.getHeroColor(); context.moveTo(10, 0); context.lineTo(-10, -8); context.lineTo(-10, 8); context.lineTo(10, 0); context.fill(); context.stroke(); //DEBUG /*context.beginPath(); context.fillStyle = "#DD3333"; context.rect(12, -2, 4, 4); context.fill(); context.beginPath(); context.strokeStyle = "#000000"; context.arc(0, 0, 15, 0, 2 * Math.PI); context.stroke();*/ context.restore(); context.save(); context.translate(position.x, position.y); if(triggerPlusOne > 0) { context.fillStyle = "#FFFF00"; context.font = "16px Roboto"; context.fillText("+" + coinAddAmount, -10, -30 + triggerPlusOne); triggerPlusOne--; } if(triggerSale > 0) { context.fillStyle = "#FFFF00"; context.font = "16px Roboto"; context.fillText("-"+saleAmount, -10, 30 - triggerSale); triggerSale--; } if(triggerHeartPickup > 0) { context.fillStyle = "#FF0000"; context.font = "16px Roboto"; context.fillText("\u2665", -10, -30 + triggerHeartPickup); triggerHeartPickup--; } if(triggerWeaponUpgrade > 0) { context.fillStyle = "#FF00FF"; context.font = "16px Roboto"; context.fillText("+1 ATK", -20, -30 + triggerWeaponUpgrade); triggerWeaponUpgrade--; } if(triggerHeartDecrement > 0) { context.fillStyle = "#FF0000"; context.font = "16px Roboto"; context.fillText("-\u2665", -10, 30 - triggerHeartDecrement); triggerHeartDecrement--; } if(triggerXpGain > 0) { context.fillStyle = "#FF00FF"; context.font = "16px Roboto"; context.fillText("+" + xpGainAmount + " XP", -20, -30 + triggerXpGain); triggerXpGain--; } if(triggerLevelUp > 0) { context.fillStyle = "#FFFF00"; context.font = "16px Roboto"; context.fillText("**LEVEL UP**", -30, -30 + Math.floor(triggerLevelUp / 2)); triggerLevelUp--; } //debug view range /* context.beginPath(); context.strokeStyle = "#000000"; //context.rect(-viewRange, -viewRange, viewRange * 2, viewRange * 2); context.arc(0, 0, viewRange, 0, 2 * Math.PI); context.stroke(); */ context.restore(); // context.save(); // context.beginPath(); // context.fillStyle = "#000000"; // context.rect(position.x - viewRange, position.y - viewRange, viewRange * 2, viewRange * 2); // context.fill(); // context.beginPath(); // context.arc(position.x, position.y, viewRange, 0, Math.PI * 2); // context.clip(); // draw the image // context.restore(); }; this.mouseMove = function(canvas, x, y) { }; this.getInfo = function() { return { level: level, name: name, inventory: inventory, heroColor: heroColor }; } this.addToLeaderboard = function(element) { var topInfo = "level " + level + " " + name + " " + inventory.hearts + "/" + inventory.maxHearts; var info = "coins:" + inventory.coins + " xp:" + inventory.xp + " dmg:" + inventory.weaponStrength; var topNode = document.createTextNode(topInfo); var infoNode = document.createTextNode(info); var topLine = document.createElement('p'); topLine.style.color = heroColor; var infoLine = document.createElement('p'); infoLine.style.color = heroColor; topLine.appendChild(topNode); infoLine.appendChild(infoNode); element.appendChild(topLine); element.appendChild(infoLine); } this.addCoin = function(amount) { inventory.coins += amount; triggerPlusOne = 30; coinAddAmount = amount; } this.addHeart = function(amount) { if(inventory.hearts != inventory.maxHearts) { triggerHeartPickup = 30; var healedBy = Math.min(inventory.maxHearts - inventory.hearts, amount); console.log(name + " healed by " + healedBy + " points"); inventory.hearts += healedBy; return true; } return false; }; this.deductHeart = function(amount) { inventory.hearts -= Math.min(amount, inventory.hearts); triggerHeartDecrement = 30; if(inventory.hearts <= 0) { game.spawnCoins(position, inventory.coins); for(var i = 0; i < inventory.hearts + level; i++) { game.spawnHeart(position.x, position.y); } game.heroDies(this); return true; } return false; } this.dealDamage = function(monster) { console.log(""+ monster.getName() +"("+monster.getLevel()+") takes " + inventory.weaponStrength + " damage from " + this.getName() + "("+this.getLevel()+")"); if(monster.deductHeart(inventory.weaponStrength)) { console.log("%c"+ monster.getName() +"("+monster.getLevel()+") is killed by " + this.getName() + "("+this.getLevel()+")", 'color: #FF0000'); this.gainXp(monster.getXpValue()); } else { monster.knockBack(this); monster.setFocus(this); } }; this.knockBack = function(monster) { //var destPos = monster.getPosition(); var deltaX = monster.getKnockback() * Math.cos(angle * Math.PI / 180); var deltaY = monster.getKnockback() * Math.sin(angle * Math.PI / 180); position.x -= deltaX; position.y -= deltaY; }; var calculateLevel = function(xp) { return Math.floor(0.5 + Math.sqrt(1 + 8*(xp)/(100)) / 2); } this.gainXp = function(amount) { var prevXP = inventory.xp; inventory.xp += amount; triggerXpGain = 30; xpGainAmount = amount; if(calculateLevel(inventory.xp) > level) { level++; console.log("%c" + name + " is now level " + level, "color: #FF00FF"); triggerLevelUp = 60; //inventory.maxHearts++; inventory.maxHearts += Math.floor(level * (6 * Math.random() + 1)); //inventory.hearts = inventory.maxHearts; } } this.markMemory = function(item) { if(item == "potion") { return; } purchaseHistory.push(item); } var hasNeedToBuy = function(shopItem) { if (shopItem.type == "potion" && inventory.hearts < inventory.maxHearts) { return true; } if(shopItem.type == "weapon1" && purchaseHistory.indexOf("weapon1") == -1) { return true; } if(shopItem.type == "weapon2" && purchaseHistory.indexOf("weapon2") == -1) { return true; } if(shopItem.type == "weapon3" && purchaseHistory.indexOf("weapon3") == -1) { return true; } if(shopItem.type == "weapon4" && purchaseHistory.indexOf("weapon4") == -1) { return true; } if(shopItem.type == "weapon5" && purchaseHistory.indexOf("weapon5") == -1) { return true; } if(shopItem.type == "weapon6" && purchaseHistory.indexOf("weapon6") == -1) { return true; } } this.shop = function(shopInventory) { shopMemory = shopInventory; for(var i = 0; i < shopInventory.length; i++) { if(inventory.coins >= shopInventory[i].cost && hasNeedToBuy(shopInventory[i])) { return shopInventory[i]; } } //console.log(name + " bought nothing", inventory, shopMemory, purchaseHistory); return; } this.upgradeWeapon = function() { inventory.weaponStrength++; triggerWeaponUpgrade = 30; } this.deductCoins = function(amount) { inventory.coins -= amount; triggerSale = 30; saleAmount = amount; } this.getPosition = function() { return position; } this.getType = function() { return "hero"; } this.visit = function(attacker) { attacker.dealDamage(this); return this; } this.getLevel = function() { return level; } this.getKnockback = function() { return knockbackStrength * inventory.weaponStrength; } this.getName = function() { return name; } this.getXpValue = function() { return 25 * level; } this.setFocus = function(newFocus) { //console.log(this.getName() + " is targeting " + newFocus.getName() + "("+ newFocus.getLevel()+ ")", level, aggressiveness); destination = newFocus; } }