Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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