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.
 
 
 

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