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.

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