Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

290 рядки
6.5 KiB

  1. let abs = Math.abs.bind(Math);
  2. let rnd = Math.random.bind(Math);
  3. let max = Math.max.bind(Math);
  4. const canPathHome = require('./mob/canPathHome');
  5. const teleportHome = (physics, obj, mob) => {
  6. physics.removeObject(obj, obj.x, obj.y);
  7. obj.x = mob.originX;
  8. obj.y = mob.originY;
  9. const syncer = obj.syncer;
  10. syncer.o.x = obj.x;
  11. syncer.o.y = obj.y;
  12. physics.addObject(obj, obj.x, obj.y);
  13. obj.aggro.clearIgnoreList();
  14. obj.aggro.move();
  15. };
  16. module.exports = {
  17. type: 'mob',
  18. target: null,
  19. physics: null,
  20. originX: 0,
  21. originY: 0,
  22. walkDistance: 1,
  23. maxChaseDistance: 25,
  24. goHome: false,
  25. patrol: null,
  26. patrolTargetNode: 0,
  27. init: function (blueprint) {
  28. this.physics = this.obj.instance.physics;
  29. this.originX = this.obj.x;
  30. this.originY = this.obj.y;
  31. if (blueprint.patrol)
  32. this.patrol = blueprint.patrol;
  33. if (blueprint.maxChaseDistance)
  34. this.maxChaseDistance = blueprint.maxChaseDistance;
  35. },
  36. update: function () {
  37. let obj = this.obj;
  38. let target = null;
  39. if (obj.aggro)
  40. target = obj.aggro.getHighest();
  41. //Have we reached home?
  42. if (this.goHome) {
  43. let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y));
  44. if (!distanceFromHome)
  45. this.goHome = false;
  46. }
  47. //Are we too far from home?
  48. if ((!this.goHome) && (!obj.follower) && (target)) {
  49. if (!this.canChase(target)) {
  50. obj.clearQueue();
  51. obj.aggro.unAggro(target);
  52. target = obj.aggro.getHighest();
  53. }
  54. }
  55. if (!this.goHome) {
  56. if ((target) && (target !== obj) && ((!obj.follower) || (obj.follower.master !== target))) {
  57. //If we just started attacking, patrols need to know where home is
  58. if (!this.target && this.patrol) {
  59. this.originX = obj.x;
  60. this.originY = obj.y;
  61. }
  62. //Are we in fight mode?
  63. this.fight(target);
  64. return;
  65. } else if ((!target) && (this.target)) {
  66. //Is fight mode over?
  67. this.target = null;
  68. obj.clearQueue();
  69. if (canPathHome(this))
  70. this.goHome = true;
  71. else
  72. teleportHome(this.physics, obj, this);
  73. }
  74. }
  75. //If we're already going somewhere, don't calculate a new path
  76. if (obj.actionQueue.length > 0)
  77. return;
  78. //Unless we're going home, don't always move
  79. if (!this.goHome && rnd() < 0.85 && !this.patrol)
  80. return;
  81. //Don't move around if we're not allowed to, unless we're going home
  82. let walkDistance = this.walkDistance;
  83. if ((!this.goHome) && (walkDistance <= 0))
  84. return;
  85. let toX, toY;
  86. //Patrol mobs should not pick random locations unless they're going home
  87. if (this.goHome || !this.patrol) {
  88. toX = this.originX + ~~(rnd() * (walkDistance * 2)) - walkDistance;
  89. toY = this.originY + ~~(rnd() * (walkDistance * 2)) - walkDistance;
  90. } else if (this.patrol) {
  91. do {
  92. let toNode = this.patrol[this.patrolTargetNode];
  93. toX = toNode[0];
  94. toY = toNode[1];
  95. if ((toX - obj.x === 0) && (toY - obj.y === 0)) {
  96. this.patrolTargetNode++;
  97. if (this.patrolTargetNode >= this.patrol.length)
  98. this.patrolTargetNode = 0;
  99. } else
  100. break;
  101. } while (toX - obj.x !== 0 || toY - obj.y !== 0);
  102. }
  103. //We use goHome to force followers to follow us around but they should never stay in that state
  104. // since it messes with combat
  105. if (obj.follower)
  106. this.goHome = false;
  107. const dx = abs(obj.x - toX);
  108. const dy = abs(obj.y - toY);
  109. if (dx + dy === 0)
  110. return;
  111. else if (dx <= 1 && dy <= 1) {
  112. obj.queue({
  113. action: 'move',
  114. data: {
  115. x: toX,
  116. y: toY
  117. }
  118. });
  119. } else {
  120. let path = this.physics.getPath({
  121. x: obj.x,
  122. y: obj.y
  123. }, {
  124. x: toX,
  125. y: toY
  126. }, false);
  127. let pLen = path.length;
  128. for (let i = 0; i < pLen; i++) {
  129. let p = path[i];
  130. obj.queue({
  131. action: 'move',
  132. data: {
  133. x: p.x,
  134. y: p.y
  135. }
  136. });
  137. }
  138. }
  139. },
  140. fight: function (target) {
  141. let obj = this.obj;
  142. if (this.target !== target) {
  143. obj.clearQueue();
  144. this.target = target;
  145. }
  146. //If the target is true, it means we can't reach the target and should wait for a new one
  147. if (this.target === true)
  148. return;
  149. else if (obj.spellbook.isCasting())
  150. return;
  151. let x = obj.x;
  152. let y = obj.y;
  153. let tx = ~~target.x;
  154. let ty = ~~target.y;
  155. let distance = max(abs(x - tx), abs(y - ty));
  156. let furthestAttackRange = obj.spellbook.getFurthestRange(null, true);
  157. let furthestStayRange = obj.spellbook.getFurthestRange(null, false);
  158. let doesCollide = null;
  159. let hasLos = null;
  160. if (distance <= furthestAttackRange) {
  161. doesCollide = this.physics.mobsCollide(x, y, obj, target);
  162. if (!doesCollide) {
  163. hasLos = this.physics.hasLos(x, y, tx, ty);
  164. if (hasLos) {
  165. if (((obj.follower) && (obj.follower.master.player)) || (rnd() < 0.65)) {
  166. let spell = obj.spellbook.getRandomSpell(target);
  167. let success = obj.spellbook.cast({
  168. spell: spell,
  169. target: target
  170. });
  171. //null means we don't have LoS
  172. if (success !== null)
  173. return;
  174. hasLos = false;
  175. } else
  176. return;
  177. }
  178. }
  179. } else if (furthestAttackRange === 0) {
  180. if (distance <= obj.spellbook.closestRange && !this.physics.mobsCollide(x, y, obj, target))
  181. return;
  182. }
  183. let targetPos = this.physics.getClosestPos(x, y, tx, ty, target, obj);
  184. if (!targetPos) {
  185. //Find a new target
  186. obj.aggro.ignore(target);
  187. //TODO: Don't skip a turn
  188. return;
  189. }
  190. let newDistance = max(abs(targetPos.x - tx), abs(targetPos.y - ty));
  191. if (newDistance >= distance && newDistance > furthestStayRange) {
  192. obj.clearQueue();
  193. obj.aggro.ignore(target);
  194. if (!obj.aggro.getHighest()) {
  195. //Nobody left to attack so reset our aggro table
  196. obj.aggro.die();
  197. this.goHome = true;
  198. }
  199. return;
  200. }
  201. if (abs(x - targetPos.x) <= 1 && abs(y - targetPos.y) <= 1) {
  202. obj.queue({
  203. action: 'move',
  204. data: {
  205. x: targetPos.x,
  206. y: targetPos.y
  207. }
  208. });
  209. } else {
  210. let path = this.physics.getPath({
  211. x: x,
  212. y: y
  213. }, {
  214. x: targetPos.x,
  215. y: targetPos.y
  216. });
  217. if (path.length === 0) {
  218. obj.aggro.ignore(target);
  219. //TODO: Don't skip a turn
  220. return;
  221. }
  222. let p = path[0];
  223. obj.queue({
  224. action: 'move',
  225. data: {
  226. x: p.x,
  227. y: p.y
  228. }
  229. });
  230. }
  231. },
  232. canChase: function (obj) {
  233. //Patrol mobs can always chase if they don't have a target yet (since they don't have a home yet)
  234. if (this.patrol && !this.target && !this.goHome)
  235. return true;
  236. let distanceFromHome = Math.max(abs(this.originX - obj.x), abs(this.originY - obj.y));
  237. return ((!this.goHome) && (distanceFromHome <= this.maxChaseDistance));
  238. },
  239. events: {
  240. beforeTakeDamage: function (msg) {
  241. if (this.goHome)
  242. msg.failed = true;
  243. }
  244. }
  245. };