You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

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