Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

671 řádky
16 KiB

  1. module.exports = {
  2. templates: null,
  3. tileMappings: null,
  4. rooms: [],
  5. exitAreas: [],
  6. leafConstraints: {
  7. minDistance: 4,
  8. maxDistance: 12,
  9. minCount: 3,
  10. maxCount: 7
  11. },
  12. endConstraints: {
  13. minDistance: 10,
  14. maxDistance: 12
  15. },
  16. bounds: [0, 0, 0, 0],
  17. generate: function (instance) {
  18. const { map } = instance;
  19. map.clientMap.hiddenRooms = [];
  20. this.loadMapProperties(map.mapFile.properties);
  21. this.rooms = [];
  22. this.exitAreas = [];
  23. this.bounds = [0, 0, 0, 0];
  24. this.templates = extend([], map.rooms);
  25. this.setupTemplates(map);
  26. const hasEndRoom = this.templates.some(t => t.properties.end);
  27. if (!hasEndRoom) {
  28. /* eslint-disable-next-line no-console */
  29. console.log(`Random map has no end room defined: ${map.name}`);
  30. return;
  31. }
  32. if (!this.tileMappings)
  33. this.generateMappings(map);
  34. let startTemplate = this.templates.filter(t => t.properties.start);
  35. startTemplate = startTemplate[this.randInt(0, startTemplate.length)];
  36. let startRoom = this.buildRoom(startTemplate);
  37. if (!this.isValidDungeon())
  38. this.generate(instance);
  39. else {
  40. this.offsetRooms(startRoom);
  41. this.buildMap(instance, startRoom);
  42. }
  43. //To spawn in another room
  44. /*const spawnRoom = this.rooms.find(t => t.template.properties.end);
  45. map.spawn = [{
  46. x: spawnRoom.x + ~~(spawnRoom.template.width / 2) - 2,
  47. y: spawnRoom.y + ~~(spawnRoom.template.height / 2) + 6
  48. }];*/
  49. },
  50. loadMapProperties: function ({ leafConstraints, endConstraints }) {
  51. if (leafConstraints)
  52. this.leafConstraints = JSON.parse(leafConstraints);
  53. if (endConstraints)
  54. this.endConstraints = JSON.parse(endConstraints);
  55. },
  56. isValidDungeon: function () {
  57. const { rooms, leafConstraints, endConstraints } = this;
  58. const leafRooms = rooms.filter(r => !r.connections.length);
  59. //Ensure that we have enough leaf rooms
  60. const { minCount: minLeafRooms, maxCount: maxLeafRooms } = leafConstraints;
  61. const leafRoomCount = leafRooms.length;
  62. if (leafRoomCount < minLeafRooms || leafRoomCount > maxLeafRooms)
  63. return false;
  64. //Ensure that the end room exists
  65. const endRoom = rooms.find(r => r.template.properties.end);
  66. if (!endRoom)
  67. return false;
  68. //Ensure that the end room is the correct distance
  69. const { minDistance: minEndDistance, maxDistance: maxEndDistance } = endConstraints;
  70. const endDistance = endRoom.distance;
  71. if (endDistance < minEndDistance || endDistance > maxEndDistance)
  72. return false;
  73. //Ensure that leaf rooms are correct distances
  74. const { minDistance: minLeafDistance, maxDistance: maxLeafDistance } = leafConstraints;
  75. const leafRoomsDistanceOk = !leafRooms.some(({ distance: roomDistance }) => {
  76. return (roomDistance < minLeafDistance || roomDistance > maxLeafDistance);
  77. });
  78. if (!leafRoomsDistanceOk)
  79. return false;
  80. //Ensure that enough minOccur templates have been included
  81. const minOccurOk = this.templates.every(t => {
  82. const minOccur = ~~t.properties.minOccur || 0;
  83. const occurs = rooms.filter(r => r.template.typeId === t.typeId).length;
  84. return occurs >= minOccur;
  85. });
  86. if (!minOccurOk)
  87. return false;
  88. return true;
  89. },
  90. /* eslint-disable-next-line */
  91. setupTemplates: function (map) {
  92. /* eslint-disable-next-line */
  93. this.templates.forEach((r, typeId) => {
  94. if (r.properties.mapping)
  95. return;
  96. r.typeId = typeId;
  97. let { noRotate = false, canFlipX = true, canFlipY = true } = r.properties;
  98. //Property values are strings. So we turn '1' and '0' into 1 and 0
  99. canFlipX = ~~canFlipX;
  100. canFlipY = ~~canFlipY;
  101. //Fix Polygons
  102. r.objects.forEach(o => {
  103. if (!o.fog)
  104. return;
  105. const newArea = o.area.map(p => {
  106. const [ px, py ] = p;
  107. const hpx = px - r.x;
  108. const hpy = py - r.y;
  109. return [hpx, hpy];
  110. });
  111. Object.assign(o, {
  112. x: o.x - r.x,
  113. y: o.y - r.y,
  114. area: newArea
  115. });
  116. });
  117. //FlipX Loop
  118. for (let i = 0; i < 2; i++) {
  119. if (i && !canFlipX)
  120. continue;
  121. //FlipY Loop
  122. for (let j = 0; j < 2; j++) {
  123. if (j && !canFlipY)
  124. continue;
  125. //Rotate Loop
  126. for (let k = 0; k < 2; k++) {
  127. if (k && noRotate)
  128. continue;
  129. if (i + j + k === 0)
  130. continue;
  131. let flipped = extend({
  132. flipX: !!i,
  133. flipY: !!j,
  134. rotate: !!k
  135. }, r);
  136. flipped.exits.forEach(e => {
  137. let direction = JSON.parse(e.properties.exit);
  138. if (flipped.flipX) {
  139. direction[0] *= -1;
  140. e.x = r.x + r.width - (e.x - r.x) - e.width;
  141. }
  142. if (flipped.flipY) {
  143. direction[1] *= -1;
  144. e.y = r.y + r.height - (e.y - r.y) - e.height;
  145. }
  146. if (flipped.rotate) {
  147. direction = [direction[1], direction[0]];
  148. let t = e.x;
  149. e.x = r.x + (e.y - r.y);
  150. e.y = r.y + (t - r.x);
  151. t = e.width;
  152. e.width = e.height;
  153. e.height = t;
  154. }
  155. e.properties.exit = JSON.stringify(direction);
  156. });
  157. flipped.objects.forEach(o => {
  158. if (!o.fog) {
  159. if (flipped.flipX)
  160. o.x = r.x + r.width - (o.x - r.x) - 1;
  161. if (flipped.flipY)
  162. o.y = r.y + r.height - (o.y - r.y) - 1;
  163. if (flipped.rotate) {
  164. let t = o.x;
  165. o.x = r.x + (o.y - r.y);
  166. o.y = r.y + (t - r.x);
  167. }
  168. } else {
  169. if (flipped.flipX) {
  170. const newArea = o.area.map(p => {
  171. const [ px, py ] = p;
  172. const hpx = r.width - px;
  173. return [hpx, py];
  174. });
  175. Object.assign(o, {
  176. area: newArea
  177. });
  178. }
  179. if (flipped.flipY) {
  180. const newArea = o.area.map(p => {
  181. const [ px, py ] = p;
  182. const hpy = r.height - py;
  183. return [px, hpy];
  184. });
  185. Object.assign(o, {
  186. area: newArea
  187. });
  188. }
  189. if (flipped.rotate) {
  190. const newArea = o.area.map(p => {
  191. const [ px, py ] = p;
  192. const t = px;
  193. const hpx = py;
  194. const hpy = t;
  195. return [hpx, hpy];
  196. });
  197. Object.assign(o, {
  198. area: newArea
  199. });
  200. }
  201. //Fix polygon bounds
  202. let lowX = r.width;
  203. let lowY = r.height;
  204. let highX = 0;
  205. let highY = 0;
  206. o.area.forEach(p => {
  207. const [ px, py ] = p;
  208. if (px < lowX)
  209. lowX = px;
  210. if (px > highX)
  211. highX = px;
  212. if (py < lowY)
  213. lowY = py;
  214. if (py > highY)
  215. highY = py;
  216. });
  217. o.x = lowX;
  218. o.y = lowY;
  219. o.width = highX - lowX;
  220. o.height = highY - lowY;
  221. }
  222. });
  223. if (flipped.rotate) {
  224. let t = flipped.width;
  225. flipped.width = flipped.height;
  226. flipped.height = t;
  227. }
  228. this.templates.push(flipped);
  229. }
  230. }
  231. }
  232. });
  233. this.templates.forEach(r => {
  234. let rotate = r.rotate;
  235. let w = rotate ? r.height : r.width;
  236. let h = rotate ? r.width : r.height;
  237. r.map = _.get2dArray(r.width, r.height);
  238. r.tiles = _.get2dArray(r.width, r.height);
  239. r.collisionMap = _.get2dArray(r.width, r.height);
  240. r.oldExits = extend([], r.exits);
  241. for (let i = 0; i < w; i++) {
  242. for (let j = 0; j < h; j++) {
  243. let ii = rotate ? j : i;
  244. let jj = rotate ? i : j;
  245. let x = r.flipX ? (r.x + w - i - 1) : (r.x + i);
  246. let y = r.flipY ? (r.y + h - j - 1) : (r.y + j);
  247. r.map[ii][jj] = map.oldMap[x][y];
  248. r.tiles[ii][jj] = map.oldLayers.tiles[x][y];
  249. r.collisionMap[ii][jj] = map.oldCollisionMap[x][y];
  250. }
  251. }
  252. });
  253. },
  254. generateMappings: function (map) {
  255. this.tileMappings = {};
  256. let oldMap = map.oldMap;
  257. this.templates
  258. .filter(r => r.properties.mapping)
  259. .forEach(m => {
  260. let x = m.x;
  261. let y = m.y;
  262. let w = m.width;
  263. let h = m.height;
  264. let baseTile = oldMap[x][y];
  265. baseTile = (baseTile || '')
  266. .replace('0,', '')
  267. .replace(',', '');
  268. let mapping = this.tileMappings[baseTile] = [];
  269. for (let i = x + 2; i < x + w; i++) {
  270. for (let j = y; j < y + h; j++) {
  271. let oM = oldMap[i][j];
  272. if (oM.replace) {
  273. oM = oM
  274. .replace('0,', '')
  275. .replace(',', '');
  276. }
  277. mapping.push(oM);
  278. }
  279. }
  280. });
  281. },
  282. buildMap: function (instance, startRoom) {
  283. let w = this.bounds[2] - this.bounds[0];
  284. let h = this.bounds[3] - this.bounds[1];
  285. let map = instance.map;
  286. let clientMap = map.clientMap;
  287. clientMap.map = _.get2dArray(w, h);
  288. clientMap.collisionMap = _.get2dArray(w, h, 1);
  289. let startTemplate = startRoom.template;
  290. map.spawn = [{
  291. x: startRoom.x + ~~(startTemplate.width / 2),
  292. y: startRoom.y + ~~(startTemplate.height / 2)
  293. }];
  294. this.drawRoom(instance, startRoom);
  295. instance.physics.init(clientMap.collisionMap);
  296. this.spawnObjects(instance, startRoom);
  297. },
  298. randomizeTile: function (tile, floorTile) {
  299. let mapping = this.tileMappings[tile];
  300. if (!mapping)
  301. return tile;
  302. tile = mapping[this.randInt(0, mapping.length)];
  303. if (!tile)
  304. return 0;
  305. return tile;
  306. },
  307. drawRoom: function (instance, room) {
  308. let map = instance.map.clientMap.map;
  309. let template = room.template;
  310. let collisionMap = instance.map.clientMap.collisionMap;
  311. for (let i = 0; i < template.width; i++) {
  312. let x = room.x + i;
  313. for (let j = 0; j < template.height; j++) {
  314. let y = room.y + j;
  315. let tile = template.map[i][j];
  316. if (!tile)
  317. continue;
  318. let currentTile = map[x][y];
  319. let collides = template.collisionMap[i][j];
  320. let floorTile = template.tiles[i][j];
  321. if (!currentTile) {
  322. let cell = tile.split(',');
  323. let cLen = cell.length;
  324. let newCell = '';
  325. for (let k = 0; k < cLen; k++) {
  326. let c = cell[k];
  327. let newC = this.randomizeTile(c);
  328. newCell += newC;
  329. if (k < cLen - 1)
  330. newCell += ',';
  331. }
  332. map[x][y] = newCell;
  333. collisionMap[x][y] = collides;
  334. continue;
  335. } else {
  336. //Remove objects from this position since it falls in another room
  337. template.objects.spliceWhere(o => {
  338. let ox = o.x - template.x + room.x;
  339. let oy = o.y - template.y + room.y;
  340. return ((ox === x) && (oy === y));
  341. });
  342. }
  343. let didCollide = collisionMap[x][y];
  344. if (collides) {
  345. if (didCollide) {
  346. let isExitTile = this.exitAreas.find(e => {
  347. return (!((x < e.x) || (y < e.y) || (x >= e.x + e.width) || (y >= e.y + e.height)));
  348. });
  349. if (isExitTile) {
  350. let isThisExit = template.oldExits.find(e => {
  351. let ex = room.x + (e.x - template.x);
  352. let ey = room.y + (e.y - template.y);
  353. return (!((x < ex) || (y < ey) || (x >= ex + e.width) || (y >= ey + e.height)));
  354. });
  355. if (isThisExit) {
  356. map[x][y] = this.randomizeTile(floorTile);
  357. collisionMap[x][y] = false;
  358. } else
  359. collisionMap[x][y] = true;
  360. }
  361. }
  362. } else if (didCollide) {
  363. collisionMap[x][y] = false;
  364. map[x][y] = this.randomizeTile(floorTile);
  365. }
  366. }
  367. }
  368. template.oldExits.forEach(function (e) {
  369. this.exitAreas.push({
  370. x: room.x + (e.x - template.x),
  371. y: room.y + (e.y - template.y),
  372. width: e.width,
  373. height: e.height
  374. });
  375. }, this);
  376. room.connections.forEach(c => this.drawRoom(instance, c), this);
  377. },
  378. spawnObjects: function (instance, room) {
  379. let template = room.template;
  380. let spawners = instance.spawners;
  381. let spawnCd = instance.map.mapFile.properties.spawnCd;
  382. template.objects.forEach(o => {
  383. if (!o.fog) {
  384. o.x = o.x - template.x + room.x;
  385. o.y = o.y - template.y + room.y;
  386. spawners.register(o, spawnCd);
  387. } else {
  388. o.x += room.x;
  389. o.y += room.y;
  390. o.area = o.area.map(p => {
  391. const [px, py] = p;
  392. return [px + room.x, py + room.y];
  393. });
  394. instance.map.clientMap.hiddenRooms.push(o);
  395. }
  396. });
  397. room.connections.forEach(c => this.spawnObjects(instance, c), this);
  398. },
  399. buildRoom: function (template, connectTo, templateExit, connectToExit, isHallway) {
  400. let room = {
  401. x: 0,
  402. y: 0,
  403. distance: 0,
  404. isHallway: isHallway,
  405. template: extend({}, template),
  406. connections: []
  407. };
  408. if (connectTo) {
  409. room.x = connectTo.x + connectToExit.x - connectTo.template.x + (template.x - templateExit.x);
  410. room.y = connectTo.y + connectToExit.y - connectTo.template.y + (template.y - templateExit.y);
  411. room.distance = connectTo.distance + 1;
  412. room.parent = connectTo;
  413. }
  414. if (this.doesCollide(room, connectTo))
  415. return false;
  416. if (connectTo)
  417. connectTo.connections.push(room);
  418. this.rooms.push(room);
  419. this.updateBounds(room);
  420. if (room.distance < this.leafConstraints.maxDistance) {
  421. const maxExits = room.template.exits.length;
  422. const minExits = Math.min(maxExits, 2);
  423. const count = this.randInt(minExits, maxExits + 1);
  424. for (let i = 0; i < count; i++)
  425. this.setupConnection(room, !isHallway);
  426. }
  427. if ((isHallway) && (room.connections.length === 0)) {
  428. this.rooms.spliceWhere(r => r === room);
  429. room.parent.connections.spliceWhere(c => c === room);
  430. return false;
  431. }
  432. return room;
  433. },
  434. setupConnection: function (fromRoom, isHallway) {
  435. if (!fromRoom.template.exits.length)
  436. return true;
  437. let fromExit = fromRoom.template.exits.splice(this.randInt(0, fromRoom.template.exits.length), 1)[0];
  438. let exitDirection = JSON.parse(fromExit.properties.exit);
  439. let templates = this.templates.filter(t => {
  440. if (
  441. (t.properties.mapping) ||
  442. (!!t.properties.hallway !== isHallway) ||
  443. (t.properties.start) ||
  444. (
  445. (t.properties.end) &&
  446. (fromRoom.distance + 1 !== this.leafConstraints.maxDistance)
  447. )
  448. )
  449. return false;
  450. let isValid = t.exits.some(e => {
  451. let direction = JSON.parse(e.properties.exit);
  452. return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1]));
  453. });
  454. if ((isValid) && (t.properties.maxOccur)) {
  455. let occurs = this.rooms.filter(r => (r.template.typeId === t.typeId)).length;
  456. if (occurs >= ~~t.properties.maxOccur)
  457. isValid = false;
  458. }
  459. if ((isValid) && (fromRoom.distance + 1 === this.leafConstraints.maxDistance)) {
  460. //If there is an exit available, rather use that
  461. if (!t.properties.end) {
  462. let endsAvailable = this.templates.filter(tt => {
  463. if (!tt.properties.end)
  464. return false;
  465. else if (!~~tt.properties.maxOccur)
  466. return true;
  467. else if (this.rooms.filter(r => r.template.typeId === tt.typeId).length < ~~tt.properties.maxOccur)
  468. return true;
  469. });
  470. if (endsAvailable.length > 0)
  471. isValid = false;
  472. }
  473. }
  474. return isValid;
  475. });
  476. if (templates.length === 0) {
  477. fromRoom.template.exits.push(fromExit);
  478. return false;
  479. }
  480. let template = extend({}, templates[this.randInt(0, templates.length)]);
  481. let templateExit = template.exits.filter(e => {
  482. let direction = JSON.parse(e.properties.exit);
  483. return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1]));
  484. });
  485. templateExit = templateExit[this.randInt(0, templateExit.length)];
  486. let exitIndex = template.exits.findIndex(e => e === templateExit);
  487. template.exits.splice(exitIndex, 1);
  488. let success = this.buildRoom(template, fromRoom, templateExit, fromExit, isHallway);
  489. if (!success) {
  490. fromRoom.template.exits.push(fromExit);
  491. return false;
  492. }
  493. return true;
  494. },
  495. offsetRooms: function (room) {
  496. let bounds = this.bounds;
  497. let dx = (this.bounds[0] < 0) ? -bounds[0] : 0;
  498. let dy = (this.bounds[1] < 0) ? -bounds[1] : 0;
  499. this.performOffset(room, dx, dy);
  500. this.bounds = [bounds[0] + dx, bounds[1] + dy, bounds[2] + dx, bounds[3] + dy];
  501. },
  502. performOffset: function (room, dx, dy) {
  503. room.x += dx;
  504. room.y += dy;
  505. room.connections.forEach(c => this.performOffset(c, dx, dy), this);
  506. },
  507. updateBounds: function (room) {
  508. this.bounds[0] = Math.min(this.bounds[0], room.x);
  509. this.bounds[1] = Math.min(this.bounds[1], room.y);
  510. this.bounds[2] = Math.max(this.bounds[2], room.x + room.template.width);
  511. this.bounds[3] = Math.max(this.bounds[3], room.y + room.template.height);
  512. },
  513. doesCollide: function (room, ignore) {
  514. for (let i = 0; i < this.rooms.length; i++) {
  515. let r = this.rooms[i];
  516. if (r === ignore)
  517. continue;
  518. let collides = (!(
  519. (room.x + room.template.width < r.x) ||
  520. (room.y + room.template.height < r.y) ||
  521. (room.x >= r.x + r.template.width) ||
  522. (room.y >= r.y + r.template.height)
  523. ));
  524. if (collides)
  525. return true;
  526. }
  527. return false;
  528. },
  529. randInt: function (min, max) {
  530. return ~~(Math.random() * (max - min)) + min;
  531. }
  532. };