hero.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. function Hero() {
  2. var game = null;
  3. var position = {x : 10, y: 10};
  4. var angle = 90;
  5. var destination = null;
  6. var maxSpeed = 3;
  7. var triggerPlusOne = 0;
  8. var coinAddAmount = 0;
  9. var triggerHeartPickup = 0;
  10. var triggerHeartDecrement = 0;
  11. var triggerXpGain = 0;
  12. var xpGainAmount = 0;
  13. var level = 1;
  14. var knockbackStrength = 10;
  15. var triggerSale = 0;
  16. var saleAmount = 0;
  17. var shopMemory = [];
  18. var purchaseHistory = [];
  19. var triggerWeaponUpgrade = 0;
  20. var triggerLevelUp = 0;
  21. var inventory = {coins: 0, hearts: 1, xp: 0};
  22. var viewRange = 150;
  23. var attentionSpan = 50;
  24. var attention = 50;
  25. var delay = 0;
  26. var aggressiveness = 1.5;
  27. var name = "";
  28. var heroColor = "";
  29. this.init = function(canvas, gameReference, index) {
  30. game = gameReference;
  31. position.x = Math.floor(canvas.width * Math.random());
  32. position.y = Math.floor(canvas.height * Math.random());
  33. inventory.maxSpeed = precisionRound(2.5 * Math.random() + 0.5, 1);
  34. inventory.coins = 0;
  35. inventory.maxHearts = Math.floor(1 * (6 * Math.random() + 1) + 8);
  36. inventory.hearts = inventory.maxHearts;
  37. inventory.xp = 0;
  38. inventory.weaponStrength = 1;
  39. level = 1;
  40. viewRange = Math.floor(150 * Math.random() + 50);
  41. attentionSpan = Math.floor(viewRange * Math.random() + 100);
  42. attention = attentionSpan;
  43. aggressiveness = 3 * Math.random();
  44. name = game.nameGenerator.generate();
  45. heroColor = getRandomColor();
  46. if(index + 1 <= 5) {
  47. heroColor = colorPicker(index);
  48. }
  49. };
  50. var colorPicker = function(colorIndex) {
  51. switch(colorIndex) {
  52. case 0:
  53. return "#000000";
  54. case 1:
  55. return "#DD3333";
  56. case 2:
  57. return "#33DD33";
  58. case 3:
  59. return "#3333DD";
  60. case 4:
  61. return "#DD33DD";
  62. case 5:
  63. return "#33DDDD";
  64. }
  65. };
  66. var getRandomColor = function() {
  67. var colorIndex = Math.floor(5 * Math.random());
  68. return colorPicker(colorIndex);
  69. }
  70. this.update = function(canvas) {
  71. if(delay > 0) {
  72. delay -= maxSpeed;
  73. return;
  74. }
  75. attention--;
  76. if(attention <= 0) {
  77. destination = null;
  78. attention = attentionSpan;
  79. }
  80. if(destination != null && !destination.isConsumed) {
  81. var destPos = destination.getPosition();
  82. angle = Math.atan2(destPos.y - position.y, destPos.x - position.x) * 180 / Math.PI;
  83. var deltaX = maxSpeed * Math.cos(angle * Math.PI / 180);
  84. var deltaY = maxSpeed * Math.sin(angle * Math.PI / 180);
  85. angle %= 360;
  86. position.x += deltaX;
  87. position.y += deltaY;
  88. if(Math.abs(Math.floor(destPos.x - position.x)) <= maxSpeed + 0.5 && Math.abs(Math.floor(destPos.y - position.y)) <= maxSpeed + 0.5) {
  89. attention = attentionSpan;
  90. if(doneWithDestination(destination.visit(this))) {
  91. destination = null;
  92. }
  93. }
  94. } else {
  95. var visibleDestinations = game.getVisibleDestinations(position.x, position.y, viewRange);
  96. this.setFocus(this.decideDestination(visibleDestinations, game.getAllDestinations()));
  97. }
  98. };
  99. this.actionDelay = function(delayAmount) {
  100. delay += delayAmount;
  101. }
  102. var doneWithDestination = function(dest) {
  103. if(dest.getType() == "monster") {
  104. if (inventory.hearts > 1 && !dest.isConsumed) {
  105. return false;
  106. }
  107. }
  108. return true;
  109. }
  110. this.decideDestination = function(visibleDestinations, allDestinations) {
  111. //remove self
  112. for(var i = visibleDestinations.length - 1; i >= 0; i--) {
  113. var hero = visibleDestinations[i];
  114. if(hero.getType() == "hero") {
  115. if(hero == this) {
  116. visibleDestinations.splice(i, 1);
  117. }
  118. }
  119. }
  120. //if low on hearts, prioritize looking for a heart
  121. if(inventory.hearts < inventory.maxHearts) {
  122. for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) {
  123. if(newDest.getType() == "heart") {
  124. return newDest;
  125. }
  126. }
  127. }
  128. for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) {
  129. if (newDest.getType() == "coin") {
  130. return newDest;
  131. }
  132. }
  133. for(var i = 0, newDest = visibleDestinations[0]; newDest = visibleDestinations[i]; i++) {
  134. if (newDest.getType() == "monster" && assessThreat(newDest)) {
  135. return newDest;
  136. }
  137. if(newDest.getType() == "shop" && shopMemory.length <= 0) {
  138. return newDest;
  139. }
  140. if(newDest.getType() == "shop" && inventory.coins >= 5) {
  141. for(var j = 0; j < shopMemory.length; j++) {
  142. if(inventory.coins >= shopMemory[j].cost && hasNeedToBuy(shopMemory[j])) {
  143. return newDest;
  144. }
  145. }
  146. }
  147. if(newDest.getType() == "hero" && newDest.getLevel() > 5 && assessThreat(newDest)) {
  148. return newDest;
  149. }
  150. }
  151. for(var i = 0, newDest = allDestinations[0]; newDest = allDestinations[i]; i++) {
  152. if(newDest.getType() == "shop" && shopMemory.length <= 0) {
  153. return newDest;
  154. }
  155. if(newDest.getType() == "shop" && inventory.coins >= 5) {
  156. for(var j = 0; j < shopMemory.length; j++) {
  157. if(inventory.coins >= shopMemory[j].cost && hasNeedToBuy(shopMemory[j])) {
  158. return newDest;
  159. }
  160. }
  161. }
  162. if (newDest.getType() == "monster" && assessThreat(newDest)) {
  163. return newDest;
  164. }
  165. }
  166. //when in doubt, go back to town
  167. for(var i = 0, newDest = allDestinations[0]; newDest = allDestinations[i]; i++) {
  168. if(newDest.getType() == "shop") {
  169. console.log(name + " has nothing to do");
  170. return newDest;
  171. }
  172. }
  173. //pick randomly
  174. //return visibleDestinations[Math.floor(visibleDestinations.length * Math.random())];
  175. }
  176. var assessThreat = function(monster) {
  177. var isNotSuicide = inventory.hearts + 1 > monster.getLevel();
  178. var itCanKillMe = level < monster.getLevel();
  179. var iCanKillIt = inventory.weaponStrength + aggressiveness > monster.getLevel() + 1;
  180. return isNotSuicide && !itCanKillMe || iCanKillIt;
  181. }
  182. var precisionRound = function(number, precision) {
  183. var factor = Math.pow(10, precision);
  184. return Math.round(number * factor) / factor;
  185. };
  186. this.getHeroColor = function() {
  187. return heroColor;
  188. }
  189. this.draw = function(context, offset) {
  190. context.save();
  191. context.translate(position.x, position.y);
  192. context.beginPath();
  193. context.rotate(angle * Math.PI / 180);
  194. context.strokeStyle = this.getHeroColor();
  195. context.fillStyle = this.getHeroColor();
  196. context.moveTo(10, 0);
  197. context.lineTo(-10, -8);
  198. context.lineTo(-10, 8);
  199. context.lineTo(10, 0);
  200. context.fill();
  201. context.stroke();
  202. //DEBUG
  203. /*context.beginPath();
  204. context.fillStyle = "#DD3333";
  205. context.rect(12, -2, 4, 4);
  206. context.fill();
  207. context.beginPath();
  208. context.strokeStyle = "#000000";
  209. context.arc(0, 0, 15, 0, 2 * Math.PI);
  210. context.stroke();*/
  211. context.restore();
  212. context.save();
  213. context.translate(position.x, position.y);
  214. if(triggerPlusOne > 0) {
  215. context.fillStyle = "#FFFF00";
  216. context.font = "16px Roboto";
  217. context.fillText("+" + coinAddAmount, -10, -30 + triggerPlusOne);
  218. triggerPlusOne--;
  219. }
  220. if(triggerSale > 0) {
  221. context.fillStyle = "#FFFF00";
  222. context.font = "16px Roboto";
  223. context.fillText("-"+saleAmount, -10, 30 - triggerSale);
  224. triggerSale--;
  225. }
  226. if(triggerHeartPickup > 0) {
  227. context.fillStyle = "#FF0000";
  228. context.font = "16px Roboto";
  229. context.fillText("\u2665", -10, -30 + triggerHeartPickup);
  230. triggerHeartPickup--;
  231. }
  232. if(triggerWeaponUpgrade > 0) {
  233. context.fillStyle = "#FF00FF";
  234. context.font = "16px Roboto";
  235. context.fillText("+1 ATK", -20, -30 + triggerWeaponUpgrade);
  236. triggerWeaponUpgrade--;
  237. }
  238. if(triggerHeartDecrement > 0) {
  239. context.fillStyle = "#FF0000";
  240. context.font = "16px Roboto";
  241. context.fillText("-\u2665", -10, 30 - triggerHeartDecrement);
  242. triggerHeartDecrement--;
  243. }
  244. if(triggerXpGain > 0) {
  245. context.fillStyle = "#FF00FF";
  246. context.font = "16px Roboto";
  247. context.fillText("+" + xpGainAmount + " XP", -20, -30 + triggerXpGain);
  248. triggerXpGain--;
  249. }
  250. if(triggerLevelUp > 0) {
  251. context.fillStyle = "#FFFF00";
  252. context.font = "16px Roboto";
  253. context.fillText("**LEVEL UP**", -30, -30 + Math.floor(triggerLevelUp / 2));
  254. triggerLevelUp--;
  255. }
  256. //debug view range
  257. /*
  258. context.beginPath();
  259. context.strokeStyle = "#000000";
  260. //context.rect(-viewRange, -viewRange, viewRange * 2, viewRange * 2);
  261. context.arc(0, 0, viewRange, 0, 2 * Math.PI);
  262. context.stroke();
  263. */
  264. context.restore();
  265. // context.save();
  266. // context.beginPath();
  267. // context.fillStyle = "#000000";
  268. // context.rect(position.x - viewRange, position.y - viewRange, viewRange * 2, viewRange * 2);
  269. // context.fill();
  270. // context.beginPath();
  271. // context.arc(position.x, position.y, viewRange, 0, Math.PI * 2);
  272. // context.clip();
  273. // draw the image
  274. // context.restore();
  275. };
  276. this.mouseMove = function(canvas, x, y) {
  277. };
  278. this.getInfo = function() {
  279. return {
  280. level: level,
  281. name: name,
  282. inventory: inventory,
  283. heroColor: heroColor
  284. };
  285. }
  286. this.addToLeaderboard = function(element) {
  287. var topInfo = "level " + level + " " + name + " " + inventory.hearts + "/" + inventory.maxHearts;
  288. var info = "coins:" + inventory.coins + " xp:" + inventory.xp + " dmg:" + inventory.weaponStrength;
  289. var topNode = document.createTextNode(topInfo);
  290. var infoNode = document.createTextNode(info);
  291. var topLine = document.createElement('p');
  292. topLine.style.color = heroColor;
  293. var infoLine = document.createElement('p');
  294. infoLine.style.color = heroColor;
  295. topLine.appendChild(topNode);
  296. infoLine.appendChild(infoNode);
  297. element.appendChild(topLine);
  298. element.appendChild(infoLine);
  299. }
  300. this.addCoin = function(amount) {
  301. inventory.coins += amount;
  302. triggerPlusOne = 30;
  303. coinAddAmount = amount;
  304. }
  305. this.addHeart = function(amount) {
  306. if(inventory.hearts != inventory.maxHearts) {
  307. triggerHeartPickup = 30;
  308. var healedBy = Math.min(inventory.maxHearts - inventory.hearts, amount);
  309. console.log(name + " healed by " + healedBy + " points");
  310. inventory.hearts += healedBy;
  311. return true;
  312. }
  313. return false;
  314. };
  315. this.deductHeart = function(amount) {
  316. inventory.hearts -= Math.min(amount, inventory.hearts);
  317. triggerHeartDecrement = 30;
  318. if(inventory.hearts <= 0) {
  319. game.spawnCoins(position, inventory.coins);
  320. for(var i = 0; i < inventory.hearts + level; i++) {
  321. game.spawnHeart(position.x, position.y);
  322. }
  323. game.heroDies(this);
  324. return true;
  325. }
  326. return false;
  327. }
  328. this.dealDamage = function(monster) {
  329. console.log(""+ monster.getName() +"("+monster.getLevel()+") takes " + inventory.weaponStrength + " damage from " + this.getName() + "("+this.getLevel()+")");
  330. if(monster.deductHeart(inventory.weaponStrength)) {
  331. console.log("%c"+ monster.getName() +"("+monster.getLevel()+") is killed by " + this.getName() + "("+this.getLevel()+")", 'color: #FF0000');
  332. this.gainXp(monster.getXpValue());
  333. } else {
  334. monster.knockBack(this);
  335. monster.setFocus(this);
  336. }
  337. };
  338. this.knockBack = function(monster) {
  339. //var destPos = monster.getPosition();
  340. var deltaX = monster.getKnockback() * Math.cos(angle * Math.PI / 180);
  341. var deltaY = monster.getKnockback() * Math.sin(angle * Math.PI / 180);
  342. position.x -= deltaX;
  343. position.y -= deltaY;
  344. };
  345. var calculateLevel = function(xp) {
  346. return Math.floor(0.5 + Math.sqrt(1 + 8*(xp)/(100)) / 2);
  347. }
  348. this.gainXp = function(amount) {
  349. var prevXP = inventory.xp;
  350. inventory.xp += amount;
  351. triggerXpGain = 30;
  352. xpGainAmount = amount;
  353. if(calculateLevel(inventory.xp) > level) {
  354. level++;
  355. console.log("%c" + name + " is now level " + level, "color: #FF00FF");
  356. triggerLevelUp = 60;
  357. //inventory.maxHearts++;
  358. inventory.maxHearts += Math.floor(level * (6 * Math.random() + 1));
  359. //inventory.hearts = inventory.maxHearts;
  360. }
  361. }
  362. this.markMemory = function(item) {
  363. if(item == "potion") {
  364. return;
  365. }
  366. purchaseHistory.push(item);
  367. }
  368. var hasNeedToBuy = function(shopItem) {
  369. if (shopItem.type == "potion" && inventory.hearts < inventory.maxHearts) {
  370. return true;
  371. }
  372. if(shopItem.type == "weapon1" && purchaseHistory.indexOf("weapon1") == -1) {
  373. return true;
  374. }
  375. if(shopItem.type == "weapon2" && purchaseHistory.indexOf("weapon2") == -1) {
  376. return true;
  377. }
  378. if(shopItem.type == "weapon3" && purchaseHistory.indexOf("weapon3") == -1) {
  379. return true;
  380. }
  381. if(shopItem.type == "weapon4" && purchaseHistory.indexOf("weapon4") == -1) {
  382. return true;
  383. }
  384. if(shopItem.type == "weapon5" && purchaseHistory.indexOf("weapon5") == -1) {
  385. return true;
  386. }
  387. if(shopItem.type == "weapon6" && purchaseHistory.indexOf("weapon6") == -1) {
  388. return true;
  389. }
  390. }
  391. this.shop = function(shopInventory) {
  392. shopMemory = shopInventory;
  393. for(var i = 0; i < shopInventory.length; i++) {
  394. if(inventory.coins >= shopInventory[i].cost && hasNeedToBuy(shopInventory[i])) {
  395. return shopInventory[i];
  396. }
  397. }
  398. //console.log(name + " bought nothing", inventory, shopMemory, purchaseHistory);
  399. return;
  400. }
  401. this.upgradeWeapon = function() {
  402. inventory.weaponStrength++;
  403. triggerWeaponUpgrade = 30;
  404. }
  405. this.deductCoins = function(amount) {
  406. inventory.coins -= amount;
  407. triggerSale = 30;
  408. saleAmount = amount;
  409. }
  410. this.getPosition = function() {
  411. return position;
  412. }
  413. this.getType = function() {
  414. return "hero";
  415. }
  416. this.visit = function(attacker) {
  417. attacker.dealDamage(this);
  418. return this;
  419. }
  420. this.getLevel = function() {
  421. return level;
  422. }
  423. this.getKnockback = function() {
  424. return knockbackStrength * inventory.weaponStrength;
  425. }
  426. this.getName = function() {
  427. return name;
  428. }
  429. this.getXpValue = function() {
  430. return 25 * level;
  431. }
  432. this.setFocus = function(newFocus) {
  433. //console.log(this.getName() + " is targeting " + newFocus.getName() + "("+ newFocus.getLevel()+ ")", level, aggressiveness);
  434. destination = newFocus;
  435. }
  436. }