選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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