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.
 
 
 

550 lines
10 KiB

  1. let pathfinder = require('../misc/pathfinder');
  2. let sqrt = Math.sqrt.bind(Math);
  3. let ceil = Math.ceil.bind(Math);
  4. let mathRand = Math.random.bind(Math);
  5. module.exports = {
  6. graph: null,
  7. collisionMap: null,
  8. cells: [],
  9. width: 0,
  10. height: 0,
  11. init: function (collisionMap) {
  12. this.collisionMap = collisionMap;
  13. this.width = collisionMap.length;
  14. this.height = collisionMap[0].length;
  15. this.cells = _.get2dArray(this.width, this.height, 'array');
  16. this.graph = new pathfinder.Graph(collisionMap, {
  17. diagonal: true
  18. });
  19. },
  20. addRegion: function (obj) {
  21. let lowX = obj.x;
  22. let lowY = obj.y;
  23. let highX = lowX + obj.width;
  24. let highY = lowY + obj.height;
  25. let cells = this.cells;
  26. for (let i = lowX; i < highX; i++) {
  27. let row = cells[i];
  28. for (let j = lowY; j < highY; j++) {
  29. let cell = row[j];
  30. if (!cell)
  31. continue;
  32. let cLen = cell.length;
  33. for (let k = 0; k < cLen; k++) {
  34. let c = cell[k];
  35. c.collisionEnter(obj);
  36. obj.collisionEnter(c);
  37. }
  38. cell.push(obj);
  39. }
  40. }
  41. },
  42. removeRegion: function (obj) {
  43. let oId = obj.id;
  44. let lowX = obj.x;
  45. let lowY = obj.y;
  46. let highX = lowX + obj.width;
  47. let highY = lowY + obj.height;
  48. let cells = this.cells;
  49. for (let i = lowX; i < highX; i++) {
  50. let row = cells[i];
  51. for (let j = lowY; j < highY; j++) {
  52. let cell = row[j];
  53. if (!cell)
  54. continue;
  55. let cLen = cell.length;
  56. for (let k = 0; k < cLen; k++) {
  57. let c = cell[k];
  58. if (c.id !== oId) {
  59. c.collisionExit(obj);
  60. obj.collisionExit(c);
  61. } else {
  62. cell.splice(k, 1);
  63. k--;
  64. cLen--;
  65. }
  66. }
  67. }
  68. }
  69. },
  70. addObject: function (obj, x, y, fromX, fromY) {
  71. let row = this.cells[x];
  72. if (!row)
  73. return;
  74. let cell = row[y];
  75. if (!cell)
  76. return;
  77. let cLen = cell.length;
  78. for (let i = 0; i < cLen; i++) {
  79. let c = cell[i];
  80. //If we have fromX and fromY, check if the target cell doesn't contain the same obj (like a notice area)
  81. if ((c.width) && (fromX)) {
  82. if (c.area) {
  83. if ((this.isInPolygon(x, y, c.area)) && (!this.isInPolygon(fromX, fromY, c.area))) {
  84. c.collisionEnter(obj);
  85. obj.collisionEnter(c);
  86. }
  87. } else if ((fromX < c.x) || (fromY < c.y) || (fromX >= c.x + c.width) || (fromY >= c.y + c.height)) {
  88. c.collisionEnter(obj);
  89. obj.collisionEnter(c);
  90. } else if ((fromX >= c.x) && (fromY >= c.y) && (fromX < c.x + c.width) && (fromY < c.y + c.height)) {
  91. c.collisionStay(obj);
  92. obj.collisionStay(c);
  93. }
  94. } else {
  95. //If a callback returns true, it means we collide
  96. if (c.collisionEnter(obj))
  97. return;
  98. obj.collisionEnter(c);
  99. }
  100. }
  101. //Perhaps a collisionEvent caused us to move somewhere else, in which case, we don't push to the cell
  102. // as we assume that the collisionEvent handled it for us
  103. if (obj.x === x && obj.y === y)
  104. cell.push(obj);
  105. return true;
  106. },
  107. removeObject: function (obj, x, y, toX, toY) {
  108. let row = this.cells[x];
  109. if (!row)
  110. return;
  111. let cell = row[y];
  112. if (!cell)
  113. return;
  114. let oId = obj.id;
  115. let cLen = cell.length;
  116. for (let i = 0; i < cLen; i++) {
  117. let c = cell[i];
  118. if (c.id !== oId) {
  119. //If we have toX and toY, check if the target cell doesn't contain the same obj (like a notice area)
  120. if ((c.width) && (toX)) {
  121. if (c.area) {
  122. if ((this.isInPolygon(x, y, c.area)) && (!this.isInPolygon(toX, toY, c.area))) {
  123. c.collisionExit(obj);
  124. obj.collisionExit(c);
  125. }
  126. } else if ((toX < c.x) || (toY < c.y) || (toX >= c.x + c.width) || (toY >= c.y + c.height)) {
  127. c.collisionExit(obj);
  128. obj.collisionExit(c);
  129. }
  130. } else {
  131. c.collisionExit(obj);
  132. obj.collisionExit(c);
  133. }
  134. } else {
  135. cell.splice(i, 1);
  136. i--;
  137. cLen--;
  138. }
  139. }
  140. },
  141. isValid: function (x, y) {
  142. let row = this.cells[x];
  143. if ((!row) || (row.length <= y) || (!this.graph.grid[x][y]))
  144. return false;
  145. return true;
  146. },
  147. getCell: function (x, y) {
  148. let row = this.cells[x];
  149. if (!row)
  150. return [];
  151. let cell = row[y];
  152. if (!cell)
  153. return [];
  154. return cell;
  155. },
  156. getArea: function (x1, y1, x2, y2, filter) {
  157. let width = this.width;
  158. let height = this.height;
  159. x1 = ~~x1;
  160. y1 = ~~y1;
  161. x2 = ~~x2;
  162. y2 = ~~y2;
  163. if (x1 < 0)
  164. x1 = 0;
  165. if (x2 >= width)
  166. x2 = width - 1;
  167. if (y1 < 0)
  168. y1 = 0;
  169. if (y2 >= height)
  170. y2 = height - 1;
  171. let cells = this.cells;
  172. let grid = this.graph.grid;
  173. let result = [];
  174. for (let i = x1; i <= x2; i++) {
  175. let row = cells[i];
  176. let gridRow = grid[i];
  177. for (let j = y1; j <= y2; j++) {
  178. if (!gridRow[j])
  179. continue;
  180. let cell = row[j];
  181. let cLen = cell.length;
  182. for (let k = 0; k < cLen; k++) {
  183. let c = cell[k];
  184. if (filter) {
  185. if (filter(c))
  186. result.push(c);
  187. } else
  188. result.push(c);
  189. }
  190. }
  191. }
  192. return result;
  193. },
  194. getOpenCellInArea: function (x1, y1, x2, y2) {
  195. let width = this.width;
  196. let height = this.height;
  197. x1 = ~~x1;
  198. y1 = ~~y1;
  199. x2 = ~~x2;
  200. y2 = ~~y2;
  201. if (x1 < 0)
  202. x1 = 0;
  203. else if (x2 >= width)
  204. x2 = width - 1;
  205. if (y1 < 0)
  206. y1 = 0;
  207. else if (y2 >= height)
  208. y2 = height - 1;
  209. let cells = this.cells;
  210. let grid = this.graph.grid;
  211. for (let i = x1; i <= x2; i++) {
  212. let row = cells[i];
  213. let gridRow = grid[i];
  214. for (let j = y1; j <= y2; j++) {
  215. if (!gridRow[j])
  216. continue;
  217. let cell = row[j];
  218. if (cell.length === 0) {
  219. return {
  220. x: i,
  221. y: j
  222. };
  223. }
  224. //If the only contents are notices, we can still use it
  225. let allNotices = !cell.some(c => !c.notice);
  226. if (allNotices) {
  227. return {
  228. x: i,
  229. y: j
  230. };
  231. }
  232. }
  233. }
  234. return null;
  235. },
  236. getPath: function (from, to) {
  237. let graph = this.graph;
  238. let grid = graph.grid;
  239. if (!to) {
  240. to = {
  241. x: ~~(mathRand() * grid.length),
  242. y: ~~(mathRand() * grid[0].length)
  243. };
  244. }
  245. let fromX = ~~from.x;
  246. let fromY = ~~from.y;
  247. if ((!grid[fromX]) || (grid[fromX].length <= fromY) || (fromX < 0) || (fromY < 0))
  248. return [];
  249. let toX = ~~to.x;
  250. let toY = ~~to.y;
  251. if ((!grid[toX]) || (grid[toX].length <= toY) || (toX < 0) || (toY < 0))
  252. return [];
  253. let path = pathfinder.astar.search(graph, {
  254. x: fromX,
  255. y: fromY
  256. }, {
  257. x: toX,
  258. y: toY
  259. }, {
  260. closest: true
  261. });
  262. return path;
  263. },
  264. isTileBlocking: function (x, y) {
  265. if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height))
  266. return true;
  267. x = ~~x;
  268. y = ~~y;
  269. let node = this.graph.grid[x][y];
  270. if (node)
  271. return (node.weight === 0);
  272. return true;
  273. },
  274. isCellOpen: function (x, y) {
  275. if ((x < 0) || (y < 0) || (x >= this.width) | (y >= this.height))
  276. return true;
  277. let cells = this.cells[x][y];
  278. let cLen = cells.length;
  279. for (let i = 0; i < cLen; i++) {
  280. let c = cells[i];
  281. if (!c.notice)
  282. return false;
  283. }
  284. return true;
  285. },
  286. hasLos: function (fromX, fromY, toX, toY) {
  287. if ((fromX < 0) || (fromY < 0) || (fromX >= this.width) | (fromY >= this.height) || (toX < 0) || (toY < 0) || (toX >= this.width) | (toY >= this.height))
  288. return false;
  289. let graphGrid = this.graph.grid;
  290. if ((!graphGrid[fromX][fromY]) || (!graphGrid[toX][toY]))
  291. return false;
  292. let dx = toX - fromX;
  293. let dy = toY - fromY;
  294. let distance = sqrt((dx * dx) + (dy * dy));
  295. dx /= distance;
  296. dy /= distance;
  297. fromX += 0.5;
  298. fromY += 0.5;
  299. distance = ceil(distance);
  300. let x = 0;
  301. let y = 0;
  302. for (let i = 0; i < distance; i++) {
  303. fromX += dx;
  304. fromY += dy;
  305. x = ~~fromX;
  306. y = ~~fromY;
  307. let node = graphGrid[x][y];
  308. if ((!node) || (node.weight === 0))
  309. return false;
  310. else if ((x === toX) && (y === toY))
  311. return true;
  312. }
  313. return true;
  314. },
  315. getClosestPos: function (fromX, fromY, toX, toY, target, obj) {
  316. let tried = {};
  317. let hasLos = this.hasLos.bind(this, toX, toY);
  318. let width = this.width;
  319. let height = this.height;
  320. let collisionMap = this.collisionMap;
  321. let cells = this.cells;
  322. let reverseX = (fromX > toX);
  323. let reverseY = (fromY > toY);
  324. for (let c = 1; c <= 10; c++) {
  325. let x1 = toX - c;
  326. let y1 = toY - c;
  327. let x2 = toX + c;
  328. let y2 = toY + c;
  329. let lowX, lowY, highX, highY, incX, incY;
  330. if (reverseX) {
  331. incX = -1;
  332. lowX = x2;
  333. highX = x1 - 1;
  334. } else {
  335. incX = 1;
  336. lowX = x1;
  337. highX = x2 + 1;
  338. }
  339. if (reverseY) {
  340. incY = -1;
  341. lowY = y2;
  342. highY = y1 - 1;
  343. } else {
  344. incY = 1;
  345. lowY = y1;
  346. highY = y2 + 1;
  347. }
  348. for (let i = lowX; i !== highX; i += incX) {
  349. if ((i < 0) || (i >= width))
  350. continue;
  351. let row = collisionMap[i];
  352. let cellRow = cells[i];
  353. let t = tried[i];
  354. if (!t)
  355. t = tried[i] = {};
  356. for (let j = lowY; j !== highY; j += incY) {
  357. if (t[j])
  358. continue;
  359. t[j] = 1;
  360. if (
  361. ((i === toX) && (j === toY)) ||
  362. ((j < 0) || (j >= height)) ||
  363. (row[j])
  364. )
  365. continue;
  366. if (target && obj) {
  367. let cell = cellRow[j];
  368. if (this.mobsCollide(i, j, obj, target, cell))
  369. continue;
  370. }
  371. if (!hasLos(i, j))
  372. continue;
  373. return {
  374. x: i,
  375. y: j
  376. };
  377. }
  378. }
  379. }
  380. },
  381. //If we pass through a cell it means we want to move to this location but need to check aggro
  382. mobsCollide: function (x, y, obj, target, cell) {
  383. const allowOne = !cell;
  384. if (!cell) {
  385. if (x < 0 || y < 0 || x >= this.width | y >= this.height)
  386. return true;
  387. cell = this.cells[x][y];
  388. }
  389. let cLen = cell.length;
  390. if (allowOne && cLen === 1)
  391. return false;
  392. else if (target.x === x && target.y === y)
  393. return true;
  394. for (let i = 0; i < cLen; i++) {
  395. let c = cell[i];
  396. //If we're first in the cell, we get preference
  397. if (c === obj)
  398. return false;
  399. else if (!c.aggro)
  400. continue;
  401. else if (c.aggro.hasAggroOn(target) || obj.aggro.hasAggroOn(c))
  402. return true;
  403. }
  404. return false;
  405. },
  406. setCollision: function (x, y, collides) {
  407. this.collisionMap[x][y] = collides ? 1 : 0;
  408. let grid = this.graph.grid;
  409. if (!grid[x][y])
  410. grid[x][y] = new pathfinder.gridNode(x, y, collides ? 0 : 1);
  411. else {
  412. grid[x][y].weight = collides ? 0 : 1;
  413. pathfinder.astar.cleanNode(grid[x][y]);
  414. }
  415. },
  416. isInPolygon: function (x, y, verts) {
  417. let inside = false;
  418. let vLen = verts.length;
  419. for (let i = 0, j = vLen - 1; i < vLen; j = i++) {
  420. let vi = verts[i];
  421. let vj = verts[j];
  422. let xi = vi[0];
  423. let yi = vi[1];
  424. let xj = vj[0];
  425. let yj = vj[1];
  426. let doesIntersect = (
  427. ((yi > y) !== (yj > y)) &&
  428. (x < ((((xj - xi) * (y - yi)) / (yj - yi)) + xi))
  429. );
  430. if (doesIntersect)
  431. inside = !inside;
  432. }
  433. return inside;
  434. }
  435. };