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.
 
 
 

569 lines
14 KiB

  1. let objects = require('../objects/objects');
  2. let physics = require('./physics');
  3. let spawners = require('./spawners');
  4. let resourceSpawner = require('./resourceSpawner');
  5. let globalZone = require('../config/zoneBase');
  6. let randomMap = require('./randomMap/randomMap');
  7. const generateMappings = require('./randomMap/generateMappings');
  8. let events = require('../misc/events');
  9. const mapObjects = require('./map/mapObjects');
  10. const canPathFromPos = require('./map/canPathFromPos');
  11. let mapFile = null;
  12. let mapScale = null;
  13. let padding = null;
  14. const objectifyProperties = oldProperties => {
  15. if (!oldProperties || !oldProperties.push)
  16. return oldProperties || {};
  17. let newProperties = {};
  18. oldProperties.forEach(p => {
  19. newProperties[p.name] = p.value;
  20. });
  21. return newProperties;
  22. };
  23. module.exports = {
  24. name: null,
  25. path: null,
  26. layers: [],
  27. mapFile: null,
  28. //The size of the base map, before mods are applied
  29. originalSize: {
  30. w: 0,
  31. h: 0
  32. },
  33. //The size of the map after mods are applied
  34. size: {
  35. w: 0,
  36. h: 0
  37. },
  38. custom: null,
  39. collisionMap: null,
  40. clientMap: {
  41. zoneId: null,
  42. map: null,
  43. collisionMap: null,
  44. clientObjects: null,
  45. padding: null,
  46. hiddenRooms: null,
  47. staticCamera: false
  48. },
  49. oldLayers: {
  50. tiles: null,
  51. walls: null,
  52. doodads: null
  53. },
  54. objBlueprints: [],
  55. spawn: {
  56. x: 0,
  57. y: 0
  58. },
  59. rooms: [],
  60. hiddenRooms: [],
  61. hiddenWalls: null,
  62. hiddenTiles: null,
  63. zoneConfig: null,
  64. init: function ({ zoneName, path }) {
  65. this.name = zoneName;
  66. this.path = path;
  67. try {
  68. this.zoneConfig = require('../' + this.path + '/' + this.name + '/zone');
  69. } catch (e) {
  70. this.zoneConfig = globalZone;
  71. }
  72. events.emit('onAfterGetZone', this.name, this.zoneConfig);
  73. let chats = null;
  74. try {
  75. chats = require('../' + this.path + '/' + this.name + '/chats');
  76. } catch (e) {}
  77. if (chats) {
  78. if (this.zoneConfig.chats)
  79. extend(this.zoneConfig.chats, chats);
  80. else
  81. this.zoneConfig.chats = chats;
  82. }
  83. let dialogues = null;
  84. try {
  85. dialogues = require('../' + this.path + '/' + this.name + '/dialogues');
  86. } catch (e) {}
  87. events.emit('onBeforeGetDialogue', this.name, dialogues);
  88. if (dialogues)
  89. this.zoneConfig.dialogues = dialogues;
  90. this.zoneConfig = extend({}, globalZone, this.zoneConfig);
  91. let resources = this.zoneConfig.resources || {};
  92. for (let r in resources)
  93. resourceSpawner.register(r, resources[r]);
  94. mapFile = require('../' + this.path + '/' + this.name + '/map');
  95. this.mapFile = mapFile;
  96. //Fix for newer versions of Tiled
  97. this.mapFile.properties = objectifyProperties(this.mapFile.properties);
  98. mapScale = mapFile.tilesets[0].tileheight;
  99. this.custom = mapFile.properties.custom;
  100. if (mapFile.properties.spawn) {
  101. this.spawn = JSON.parse(mapFile.properties.spawn);
  102. if (!this.spawn.push)
  103. this.spawn = [this.spawn];
  104. }
  105. },
  106. create: function () {
  107. this.getMapFile();
  108. const { layers, collisionMap, objBlueprints, hiddenRooms, zoneConfig } = this;
  109. const { rendererConfig = {} } = zoneConfig;
  110. Object.assign(this.clientMap, {
  111. zoneId: -1,
  112. map: layers,
  113. collisionMap,
  114. clientObjects: objBlueprints,
  115. padding,
  116. hiddenRooms,
  117. rendererConfig
  118. });
  119. },
  120. getMapFile: function () {
  121. this.build();
  122. this.randomMap = extend({}, randomMap);
  123. this.oldMap = extend([], this.layers);
  124. this.randomMap.templates = extend([], this.rooms);
  125. generateMappings(this.randomMap, this);
  126. if (!mapFile.properties.isRandom) {
  127. for (let i = 0; i < this.size.w; i++) {
  128. let row = this.layers[i];
  129. for (let j = 0; j < this.size.h; j++) {
  130. let cell = row[j];
  131. if (!cell)
  132. continue;
  133. cell = cell.split(',');
  134. let cLen = cell.length;
  135. let newCell = '';
  136. for (let k = 0; k < cLen; k++) {
  137. let c = cell[k];
  138. let newC = c;
  139. //Randomize tile
  140. const msgBeforeRandomizePosition = {
  141. success: true,
  142. x: i,
  143. y: j,
  144. map: this.name
  145. };
  146. events.emit('onBeforeRandomizePosition', msgBeforeRandomizePosition);
  147. if (msgBeforeRandomizePosition.success)
  148. newC = this.randomMap.randomizeTile(c);
  149. newCell += newC;
  150. //Wall?
  151. if ((c >= 160) && (c <= 352) && (newC === 0))
  152. this.collisionMap[i][j] = 0;
  153. if (k < cLen - 1)
  154. newCell += ',';
  155. }
  156. let fakeContents = [];
  157. const hiddenWall = this.hiddenWalls[i][j];
  158. const hiddenTile = this.hiddenTiles[i][j];
  159. if (hiddenTile)
  160. fakeContents.push(-this.randomMap.randomizeTile(hiddenTile));
  161. if (hiddenWall)
  162. fakeContents.push(-this.randomMap.randomizeTile(hiddenWall));
  163. if (fakeContents.length)
  164. newCell += ',' + fakeContents.join(',');
  165. row[j] = newCell;
  166. }
  167. }
  168. }
  169. //Fix for newer versions of Tiled
  170. this.randomMap.templates
  171. .forEach(r => {
  172. r.properties = objectifyProperties(r.properties);
  173. });
  174. this.randomMap.templates
  175. .filter(r => r.properties.mapping)
  176. .forEach(function (m) {
  177. let x = m.x;
  178. let y = m.y;
  179. let w = m.width;
  180. let h = m.height;
  181. for (let i = x; i < x + w; i++) {
  182. let row = this.layers[i];
  183. for (let j = y; j < y + h; j++)
  184. row[j] = '';
  185. }
  186. }, this);
  187. physics.init(this.collisionMap);
  188. padding = mapFile.properties.padding;
  189. mapFile = null;
  190. },
  191. build: function () {
  192. const mapSize = {
  193. w: mapFile.width,
  194. h: mapFile.height
  195. };
  196. this.originalSize = {
  197. w: mapFile.width,
  198. h: mapFile.height
  199. };
  200. events.emit('onBeforeGetMapSize', this.name, mapSize);
  201. this.size.w = mapSize.w;
  202. this.size.h = mapSize.h;
  203. const { w: oldW, h: oldH } = this.originalSize;
  204. const { w, h } = this.size;
  205. this.layers = _.get2dArray(w, h, null);
  206. this.hiddenWalls = _.get2dArray(w, h, null);
  207. this.hiddenTiles = _.get2dArray(w, h, null);
  208. this.oldLayers.tiles = _.get2dArray(w, h, 0);
  209. this.oldLayers.walls = _.get2dArray(w, h, 0);
  210. this.oldLayers.doodads = _.get2dArray(w, h, 0);
  211. let builders = {
  212. tile: this.builders.tile.bind(this),
  213. object: this.builders.object.bind(this)
  214. };
  215. this.collisionMap = _.get2dArray(w, h);
  216. const layers = [...mapFile.layers.filter(l => l.objects), ...mapFile.layers.filter(l => !l.objects)];
  217. //Rooms need to be ahead of exits
  218. const layerRooms = layers.find(l => l.name === 'rooms') || {};
  219. layerRooms.objects.sort((a, b) => {
  220. const isExitA = a?.properties?.some(p => p.name === 'exit');
  221. const isExitB = b?.properties?.some(p => p.name === 'exit');
  222. if (isExitA && !isExitB)
  223. return 1;
  224. else if (!isExitA && isExitB)
  225. return -1;
  226. return 0;
  227. });
  228. for (let i = 0; i < layers.length; i++) {
  229. let layer = layers[i];
  230. let layerName = layer.name;
  231. if (!layer.visible)
  232. continue;
  233. let data = layer.data || layer.objects;
  234. if (layer.objects) {
  235. let info = {
  236. map: this.name,
  237. layer: layerName,
  238. objects: data,
  239. mapScale,
  240. size: this.size
  241. };
  242. events.emit('onAfterGetLayerObjects', info);
  243. }
  244. if (layer.objects) {
  245. let len = data.length;
  246. for (let j = 0; j < len; j++) {
  247. let cell = data[j];
  248. builders.object(layerName, cell, j);
  249. }
  250. } else {
  251. for (let x = 0; x < w; x++) {
  252. for (let y = 0; y < h; y++) {
  253. let index = (y * oldW) + x;
  254. const msgBuild = {
  255. map: this.name,
  256. layer: layerName,
  257. sheetName: null,
  258. cell: 0,
  259. x,
  260. y
  261. };
  262. if (x < oldW && y < oldH)
  263. msgBuild.cell = data[index];
  264. events.emit('onBeforeBuildLayerTile', msgBuild);
  265. builders.tile(msgBuild);
  266. events.emit('onAfterBuildLayerTile', msgBuild);
  267. }
  268. }
  269. }
  270. }
  271. },
  272. getOffsetCellPos: function (sheetName, cell) {
  273. const { config: { atlasTextureDimensions, atlasTextures } } = clientConfig;
  274. const indexInAtlas = atlasTextures.indexOf(sheetName);
  275. let offset = 0;
  276. for (let i = 0; i < indexInAtlas; i++) {
  277. const dimensions = atlasTextureDimensions[atlasTextures[i]];
  278. offset += (dimensions.width / 8) * (dimensions.height / 8);
  279. }
  280. return cell + offset;
  281. },
  282. getCellInfo: function (gid, x, y, layerName) {
  283. const cellInfoMsg = {
  284. mapName: this.name,
  285. x,
  286. y,
  287. layerName,
  288. tilesets: mapFile.tilesets,
  289. sheetName: null
  290. };
  291. events.emit('onBeforeGetCellInfo', cellInfoMsg);
  292. const tilesets = cellInfoMsg.tilesets;
  293. let flipX = null;
  294. if ((gid ^ 0x80000000) > 0) {
  295. flipX = true;
  296. gid = gid ^ 0x80000000;
  297. }
  298. let firstGid = 0;
  299. let sheetName = cellInfoMsg.sheetName;
  300. if (!sheetName) {
  301. for (let s = 0; s < tilesets.length; s++) {
  302. let tileset = tilesets[s];
  303. if (tileset.firstgid <= gid) {
  304. sheetName = tileset.name;
  305. firstGid = tileset.firstgid;
  306. }
  307. }
  308. gid = gid - firstGid + 1;
  309. }
  310. return {
  311. cell: gid,
  312. sheetName,
  313. flipX
  314. };
  315. },
  316. builders: {
  317. tile: function (info) {
  318. let { x, y, cell, layer: layerName, sheetName } = info;
  319. if (cell === 0) {
  320. if (layerName === 'tiles')
  321. this.collisionMap[x][y] = 1;
  322. return;
  323. }
  324. let cellInfo = this.getCellInfo(cell, x, y, layerName);
  325. if (!sheetName) {
  326. info.sheetName = cellInfo.sheetName;
  327. sheetName = cellInfo.sheetName;
  328. }
  329. const offsetCell = this.getOffsetCellPos(sheetName, cellInfo.cell);
  330. const isHiddenLayer = layerName.indexOf('hidden') === 0;
  331. if (isHiddenLayer)
  332. this[layerName][x][y] = offsetCell;
  333. else {
  334. const layer = this.layers;
  335. if (this.oldLayers[layerName])
  336. this.oldLayers[layerName][x][y] = offsetCell;
  337. layer[x][y] = (layer[x][y] === null) ? offsetCell : layer[x][y] + ',' + offsetCell;
  338. if (layerName.indexOf('walls') > -1)
  339. this.collisionMap[x][y] = 1;
  340. else if (clientConfig.config.blockingTileIndices.includes(offsetCell))
  341. this.collisionMap[x][y] = 1;
  342. }
  343. },
  344. object: function (layerName, cell) {
  345. //Fixes for newer versions of tiled
  346. cell.properties = objectifyProperties(cell.properties);
  347. cell.polyline = cell.polyline || cell.polygon;
  348. const x = cell.x / mapScale;
  349. const y = (cell.y / mapScale) - 1;
  350. let clientObj = (layerName === 'clientObjects');
  351. let cellInfo = this.getCellInfo(cell.gid, x, y, layerName);
  352. let name = (cell.name || '');
  353. let objZoneName = name;
  354. if (name.indexOf('|') > -1) {
  355. let split = name.split('|');
  356. name = split[0];
  357. objZoneName = split[1];
  358. }
  359. let blueprint = {
  360. id: cell.properties.id,
  361. clientObj: clientObj,
  362. sheetName: cell.has('sheetName') ? cell.sheetName : cellInfo.sheetName,
  363. cell: cell.has('cell') ? cell.cell : cellInfo.cell - 1,
  364. x,
  365. y,
  366. name: name,
  367. properties: cell.properties || {},
  368. layerName: layerName
  369. };
  370. if (objZoneName !== name)
  371. blueprint.objZoneName = objZoneName;
  372. if (this.zoneConfig?.objects?.[objZoneName.toLowerCase()])
  373. extend(blueprint, this.zoneConfig.objects[objZoneName.toLowerCase()]);
  374. else if (this.zoneConfig?.mobs?.[objZoneName.toLowerCase()])
  375. extend(blueprint, this.zoneConfig.mobs[objZoneName.toLowerCase()]);
  376. if (blueprint.blocking)
  377. this.collisionMap[blueprint.x][blueprint.y] = 1;
  378. if ((blueprint.properties.cpnNotice) || (blueprint.properties.cpnLightPatch) || (layerName === 'rooms') || (layerName === 'hiddenRooms')) {
  379. blueprint.y++;
  380. blueprint.width = cell.width / mapScale;
  381. blueprint.height = cell.height / mapScale;
  382. } else if (cell.width === 24)
  383. blueprint.x++;
  384. if (cell.polyline)
  385. mapObjects.polyline(this.size, blueprint, cell, mapScale);
  386. if (layerName === 'rooms') {
  387. if (blueprint.properties.exit) {
  388. let room = this.rooms.find(function (r) {
  389. return (!(
  390. (blueprint.x + blueprint.width < r.x) ||
  391. (blueprint.y + blueprint.height < r.y) ||
  392. (blueprint.x >= r.x + r.width) ||
  393. (blueprint.y >= r.y + r.height)
  394. ));
  395. });
  396. room.exits.push(blueprint);
  397. } else if (blueprint.properties.resource)
  398. resourceSpawner.register(blueprint.properties.resource, blueprint);
  399. else {
  400. blueprint.exits = [];
  401. blueprint.objects = [];
  402. this.rooms.push(blueprint);
  403. }
  404. } else if (layerName === 'hiddenRooms') {
  405. blueprint.fog = (cell.properties || {}).fog;
  406. blueprint.interior = (cell.properties || {}).interior;
  407. blueprint.discoverable = (cell.properties || {}).discoverable;
  408. blueprint.layer = ~~((cell.properties || {}).layer || 0);
  409. if (!mapFile.properties.isRandom)
  410. this.hiddenRooms.push(blueprint);
  411. else {
  412. let room = this.rooms.find(r => {
  413. return !(
  414. blueprint.x < r.x ||
  415. blueprint.y < r.y ||
  416. blueprint.x >= r.x + r.width ||
  417. blueprint.y >= r.y + r.height
  418. );
  419. });
  420. room.objects.push(blueprint);
  421. }
  422. } else if (!clientObj) {
  423. if (!mapFile.properties.isRandom)
  424. spawners.register(blueprint, blueprint.spawnCd || mapFile.properties.spawnCd);
  425. else {
  426. let room = this.rooms.find(r => {
  427. return !(
  428. blueprint.x < r.x ||
  429. blueprint.y < r.y ||
  430. blueprint.x >= r.x + r.width ||
  431. blueprint.y >= r.y + r.height
  432. );
  433. });
  434. room.objects.push(blueprint);
  435. }
  436. } else {
  437. if ((cell.width) && (!cell.polyline)) {
  438. blueprint.width = cell.width / mapScale;
  439. blueprint.height = cell.height / mapScale;
  440. }
  441. let obj = objects.buildObjects([blueprint], true).getSimple(true);
  442. this.objBlueprints.push(obj);
  443. }
  444. }
  445. },
  446. getSpawnPos: function (obj) {
  447. let stats = obj.components.find(c => (c.type === 'stats'));
  448. let level = stats.values.level;
  449. let spawns = this.spawn.filter(s => (((s.maxLevel) && (s.maxLevel >= level)) || (!s.maxLevel)));
  450. return spawns[0];
  451. },
  452. //Find if any spawns can path to a position. This is important for when maps change and players
  453. // log in on tiles that aren't blocking but not able to reach anywhere useful
  454. canPathFromPos: function (pos) {
  455. return canPathFromPos(this, pos);
  456. }
  457. };