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.
 
 
 

512 lines
13 KiB

  1. module.exports = {
  2. templates: null,
  3. tileMappings: {},
  4. gapMappings: {},
  5. rooms: [],
  6. exitAreas: [],
  7. maxDistance: 12,
  8. minDistance: 0,
  9. bounds: [0, 0, 0, 0],
  10. generate: function (instance) {
  11. this.rooms = [];
  12. this.exitAreas = [];
  13. this.tileMappings = {};
  14. this.bounds = [0, 0, 0, 0];
  15. this.templates = extend([], instance.map.rooms);
  16. this.setupTemplates(instance.map);
  17. this.generateMappings(instance.map);
  18. let startTemplate = this.templates.filter(t => t.properties.start);
  19. startTemplate = startTemplate[this.randInt(0, startTemplate.length)];
  20. let startRoom = this.buildRoom(startTemplate);
  21. if (!this.isValidDungeon())
  22. this.generate(instance);
  23. else {
  24. this.offsetRooms(startRoom);
  25. this.buildMap(instance, startRoom);
  26. }
  27. },
  28. isValidDungeon: function () {
  29. let endRooms = this.rooms.filter(r => r.connections.length === 0);
  30. let endDistanceReached = endRooms.find(r => r.distance === this.maxDistance, this);
  31. if (!endDistanceReached)
  32. return false;
  33. let valid = true;
  34. endRooms
  35. .forEach(function (r) {
  36. if (r.distance < this.minDistance)
  37. valid = false;
  38. else if (r.distance > this.maxDistance)
  39. valid = false;
  40. }, this);
  41. return valid;
  42. },
  43. setupTemplates: function (map) {
  44. this.templates.forEach(function (r, typeId) {
  45. if (r.properties.mapping)
  46. return;
  47. r.typeId = typeId;
  48. for (let i = 0; i < 2; i++) {
  49. for (let j = 0; j < 2; j++) {
  50. for (let k = 0; k < 2; k++) {
  51. if (i + j + k === 0)
  52. continue;
  53. let flipped = extend({
  54. flipX: !!i,
  55. flipY: !!j,
  56. rotate: !!k
  57. }, r);
  58. flipped.exits.forEach(function (e) {
  59. let direction = JSON.parse(e.properties.exit);
  60. if (flipped.flipX) {
  61. direction[0] *= -1;
  62. e.x = r.x + r.width - (e.x - r.x) - e.width;
  63. }
  64. if (flipped.flipY) {
  65. direction[1] *= -1;
  66. e.y = r.y + r.height - (e.y - r.y) - e.height;
  67. }
  68. if (flipped.rotate) {
  69. direction = [direction[1], direction[0]];
  70. let t = e.x;
  71. e.x = r.x + (e.y - r.y);
  72. e.y = r.y + (t - r.x);
  73. t = e.width;
  74. e.width = e.height;
  75. e.height = t;
  76. }
  77. e.properties.exit = JSON.stringify(direction);
  78. });
  79. flipped.objects.forEach(function (o) {
  80. if (flipped.flipX)
  81. o.x = r.x + r.width - (o.x - r.x) - 1;
  82. if (flipped.flipY)
  83. o.y = r.y + r.height - (o.y - r.y) - 1;
  84. if (flipped.rotate) {
  85. let t = o.x;
  86. o.x = r.x + (o.y - r.y);
  87. o.y = r.y + (t - r.x);
  88. }
  89. });
  90. if (flipped.rotate) {
  91. let t = flipped.width;
  92. flipped.width = flipped.height;
  93. flipped.height = t;
  94. }
  95. this.templates.push(flipped);
  96. }
  97. }
  98. }
  99. }, this);
  100. this.templates.forEach(function (r) {
  101. let rotate = r.rotate;
  102. let w = rotate ? r.height : r.width;
  103. let h = rotate ? r.width : r.height;
  104. r.map = _.get2dArray(r.width, r.height);
  105. r.tiles = _.get2dArray(r.width, r.height);
  106. r.collisionMap = _.get2dArray(r.width, r.height);
  107. r.oldExits = extend([], r.exits);
  108. for (let i = 0; i < w; i++) {
  109. for (let j = 0; j < h; j++) {
  110. let ii = rotate ? j : i;
  111. let jj = rotate ? i : j;
  112. let x = r.flipX ? (r.x + w - i - 1) : (r.x + i);
  113. let y = r.flipY ? (r.y + h - j - 1) : (r.y + j);
  114. r.map[ii][jj] = map.oldMap[x][y];
  115. r.tiles[ii][jj] = map.oldLayers.tiles[x][y];
  116. r.collisionMap[ii][jj] = map.oldCollisionMap[x][y];
  117. }
  118. }
  119. });
  120. },
  121. generateMappings: function (map) {
  122. let oldMap = map.oldMap;
  123. this.templates
  124. .filter(r => r.properties.mapping)
  125. .forEach(function (m) {
  126. let x = m.x;
  127. let y = m.y;
  128. let w = m.width;
  129. let h = m.height;
  130. let baseTile = oldMap[x][y];
  131. baseTile = baseTile
  132. .replace('0,', '')
  133. .replace(',', '');
  134. let mapping = null;
  135. if ((!m.properties.wall) && (!m.properties.floor))
  136. mapping = this.tileMappings[baseTile] = [];
  137. else
  138. mapping = this.gapMappings[baseTile] = [];
  139. for (let i = x + 2; i < x + w; i++) {
  140. for (let j = y; j < y + h; j++) {
  141. let oM = oldMap[i][j];
  142. if (oM.replace) {
  143. oM = oM
  144. .replace('0,', '')
  145. .replace(',', '');
  146. }
  147. mapping.push(oM);
  148. }
  149. }
  150. }, this);
  151. },
  152. buildMap: function (instance, startRoom) {
  153. let w = this.bounds[2] - this.bounds[0];
  154. let h = this.bounds[3] - this.bounds[1];
  155. let map = instance.map;
  156. let clientMap = map.clientMap;
  157. clientMap.map = _.get2dArray(w, h);
  158. clientMap.collisionMap = _.get2dArray(w, h, 1);
  159. let startTemplate = startRoom.template;
  160. map.spawn = [{
  161. x: startRoom.x + ~~(startTemplate.width / 2),
  162. y: startRoom.y + ~~(startTemplate.height / 2)
  163. }];
  164. this.drawRoom(instance, startRoom);
  165. this.fillGaps(instance);
  166. instance.physics.init(clientMap.collisionMap);
  167. this.spawnObjects(instance, startRoom);
  168. },
  169. fillGaps: function (instance) {
  170. let map = instance.map.clientMap.map;
  171. let oldMap = instance.map.oldMap;
  172. let w = map.length;
  173. let h = map[0].length;
  174. let len = w * h / 120;
  175. let floorTile = this.templates.find(t => t.properties.floor);
  176. floorTile = oldMap[floorTile.x][floorTile.y];
  177. let wallTile = this.templates.find(t => t.properties.wall);
  178. wallTile = oldMap[wallTile.x][wallTile.y];
  179. for (let i = 0; i < len; i++) {
  180. let xMin = this.randInt(0, w);
  181. let yMin = this.randInt(0, h);
  182. let xMax = Math.min(w, xMin + this.randInt(2, 7));
  183. let yMax = Math.min(h, yMin + this.randInt(2, 7));
  184. for (let x = xMin; x < xMax; x++) {
  185. for (let y = yMin; y < yMax; y++) {
  186. if (map[x][y])
  187. continue;
  188. if (this.randInt(0, 10) < 6) {
  189. if (this.randInt(0, 10) < 3)
  190. map[x][y] = this.randomizeTile(wallTile, null, true);
  191. else
  192. map[x][y] = this.randomizeTile(floorTile, null, true);
  193. }
  194. }
  195. }
  196. }
  197. },
  198. randomizeTile: function (tile, floorTile, gapMapping) {
  199. let mapping = gapMapping ? this.gapMappings[tile] : this.tileMappings[tile];
  200. if (!mapping)
  201. return tile;
  202. tile = mapping[this.randInt(0, mapping.length)];
  203. if (!tile) {
  204. if (floorTile)
  205. return this.randomizeTile(floorTile);
  206. return 0;
  207. }
  208. return tile;
  209. },
  210. drawRoom: function (instance, room) {
  211. let map = instance.map.clientMap.map;
  212. let template = room.template;
  213. let collisionMap = instance.map.clientMap.collisionMap;
  214. for (let i = 0; i < template.width; i++) {
  215. let x = room.x + i;
  216. for (let j = 0; j < template.height; j++) {
  217. let y = room.y + j;
  218. let tile = template.map[i][j];
  219. if (!tile)
  220. continue;
  221. let currentTile = map[x][y];
  222. let collides = template.collisionMap[i][j];
  223. let floorTile = template.tiles[i][j];
  224. if (!currentTile) {
  225. map[x][y] = this.randomizeTile(tile, floorTile);
  226. collisionMap[x][y] = collides;
  227. continue;
  228. } else {
  229. //Remove objects from this position since it falls in another room
  230. template.objects.spliceWhere(function (o) {
  231. let ox = o.x - template.x + room.x;
  232. let oy = o.y - template.y + room.y;
  233. return ((ox === x) && (oy === y));
  234. });
  235. }
  236. let didCollide = collisionMap[x][y];
  237. if (collides) {
  238. if (didCollide) {
  239. let isExitTile = this.exitAreas.find(function (e) {
  240. return (!((x < e.x) || (y < e.y) || (x >= e.x + e.width) || (y >= e.y + e.height)));
  241. });
  242. if (isExitTile) {
  243. let isThisExit = template.oldExits.find(function (e) {
  244. let ex = room.x + (e.x - template.x);
  245. let ey = room.y + (e.y - template.y);
  246. return (!((x < ex) || (y < ey) || (x >= ex + e.width) || (y >= ey + e.height)));
  247. });
  248. if (isThisExit) {
  249. map[x][y] = this.randomizeTile(floorTile);
  250. collisionMap[x][y] = false;
  251. } else
  252. collisionMap[x][y] = true;
  253. }
  254. }
  255. } else if (didCollide) {
  256. collisionMap[x][y] = false;
  257. map[x][y] = this.randomizeTile(floorTile);
  258. }
  259. }
  260. }
  261. template.oldExits.forEach(function (e) {
  262. this.exitAreas.push({
  263. x: room.x + (e.x - template.x),
  264. y: room.y + (e.y - template.y),
  265. width: e.width,
  266. height: e.height
  267. });
  268. }, this);
  269. room.connections.forEach(c => this.drawRoom(instance, c), this);
  270. },
  271. spawnObjects: function (instance, room) {
  272. let template = room.template;
  273. let spawners = instance.spawners;
  274. let spawnCd = instance.map.mapFile.properties.spawnCd;
  275. template.objects.forEach(function (o) {
  276. o.x = o.x - template.x + room.x;
  277. o.y = o.y - template.y + room.y;
  278. o.amount = 1;
  279. o.scaleDrops = true;
  280. spawners.register(o, spawnCd);
  281. });
  282. room.connections.forEach(c => this.spawnObjects(instance, c), this);
  283. },
  284. buildRoom: function (template, connectTo, templateExit, connectToExit, isHallway) {
  285. let room = {
  286. x: 0,
  287. y: 0,
  288. distance: 0,
  289. isHallway: isHallway,
  290. template: extend({}, template),
  291. connections: []
  292. };
  293. if (connectTo) {
  294. room.x = connectTo.x + connectToExit.x - connectTo.template.x + (template.x - templateExit.x);
  295. room.y = connectTo.y + connectToExit.y - connectTo.template.y + (template.y - templateExit.y);
  296. room.distance = connectTo.distance + 1;
  297. room.parent = connectTo;
  298. }
  299. if (this.doesCollide(room, connectTo))
  300. return false;
  301. if (connectTo)
  302. connectTo.connections.push(room);
  303. this.rooms.push(room);
  304. this.updateBounds(room);
  305. if (room.distance < this.maxDistance) {
  306. let count = this.randInt(1, room.template.exits.length);
  307. for (let i = 0; i < count; i++)
  308. this.setupConnection(room, !isHallway);
  309. }
  310. if ((isHallway) && (room.connections.length === 0)) {
  311. this.rooms.spliceWhere(r => r === room);
  312. room.parent.connections.spliceWhere(c => c === room);
  313. return false;
  314. }
  315. return room;
  316. },
  317. setupConnection: function (fromRoom, isHallway) {
  318. if (fromRoom.template.exits.length === 0)
  319. return true;
  320. let fromExit = fromRoom.template.exits.splice(this.randInt(0, fromRoom.template.exits.length), 1)[0];
  321. let exitDirection = JSON.parse(fromExit.properties.exit);
  322. let templates = this.templates.filter(function (t) {
  323. if (
  324. (t.properties.mapping) ||
  325. (!!t.properties.hallway !== isHallway) ||
  326. (t.properties.start) ||
  327. (
  328. (t.properties.end) &&
  329. (fromRoom.distance + 1 !== this.maxDistance)
  330. )
  331. )
  332. return false;
  333. let isValid = t.exits.some(function (e) {
  334. let direction = JSON.parse(e.properties.exit);
  335. return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1]));
  336. });
  337. if ((isValid) && (t.properties.maxOccur)) {
  338. let occurs = this.rooms.filter(r => (r.template.typeId === t.typeId)).length;
  339. if (occurs >= ~~t.properties.maxOccur)
  340. isValid = false;
  341. }
  342. if ((isValid) && (fromRoom.distance + 1 === this.maxDistance)) {
  343. //If there is an exit available, rather use that
  344. if (!t.properties.end) {
  345. let endsAvailable = this.templates.filter(function (tt) {
  346. if (!tt.properties.end)
  347. return false;
  348. else if (!~~tt.properties.maxOccur)
  349. return true;
  350. else if (this.rooms.filter(r => r.template.typeId === tt.typeId).length < ~~tt.properties.maxOccur)
  351. return true;
  352. }, this);
  353. if (endsAvailable.length > 0)
  354. isValid = false;
  355. }
  356. }
  357. return isValid;
  358. }, this);
  359. if (templates.length === 0) {
  360. fromRoom.template.exits.push(fromExit);
  361. return false;
  362. }
  363. let template = extend({}, templates[this.randInt(0, templates.length)]);
  364. let templateExit = template.exits.filter(function (e) {
  365. let direction = JSON.parse(e.properties.exit);
  366. return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1]));
  367. });
  368. templateExit = templateExit[this.randInt(0, templateExit.length)];
  369. let exitIndex = template.exits.firstIndex(e => e === templateExit);
  370. template.exits.splice(exitIndex, 1);
  371. let success = this.buildRoom(template, fromRoom, templateExit, fromExit, isHallway);
  372. if (!success) {
  373. fromRoom.template.exits.push(fromExit);
  374. return false;
  375. }
  376. return true;
  377. },
  378. offsetRooms: function (room) {
  379. let bounds = this.bounds;
  380. let dx = (this.bounds[0] < 0) ? -bounds[0] : 0;
  381. let dy = (this.bounds[1] < 0) ? -bounds[1] : 0;
  382. this.performOffset(room, dx, dy);
  383. this.bounds = [bounds[0] + dx, bounds[1] + dy, bounds[2] + dx, bounds[3] + dy];
  384. },
  385. performOffset: function (room, dx, dy) {
  386. room.x += dx;
  387. room.y += dy;
  388. room.connections.forEach(c => this.performOffset(c, dx, dy), this);
  389. },
  390. updateBounds: function (room) {
  391. this.bounds[0] = Math.min(this.bounds[0], room.x);
  392. this.bounds[1] = Math.min(this.bounds[1], room.y);
  393. this.bounds[2] = Math.max(this.bounds[2], room.x + room.template.width);
  394. this.bounds[3] = Math.max(this.bounds[3], room.y + room.template.height);
  395. },
  396. doesCollide: function (room, ignore) {
  397. for (let i = 0; i < this.rooms.length; i++) {
  398. let r = this.rooms[i];
  399. if (r === ignore)
  400. continue;
  401. let collides = (!(
  402. (room.x + room.template.width < r.x) ||
  403. (room.y + room.template.height < r.y) ||
  404. (room.x >= r.x + r.template.width) ||
  405. (room.y >= r.y + r.template.height)
  406. ));
  407. if (collides)
  408. return true;
  409. }
  410. return false;
  411. },
  412. randInt: function (min, max) {
  413. return ~~(Math.random() * (max - min)) + min;
  414. }
  415. };