25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

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