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.

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