Source: mapClass.js

  1. // Util functions
  2. /**
  3. * @global
  4. * @function
  5. * @param t A constructor for the blocks/tiles to be created
  6. * @param w The number of blocks/tiles of type t that will be returned
  7. * @returns {Array} An array of length w of blocks/tiles of type t
  8. *
  9. */
  10. function getTileRow(t, w) { //Creates an array of w tile t objects
  11. var row = [];
  12. for (var i = 0; i < w; i++) {
  13. row.push(new t());
  14. }
  15. return row;
  16. }
  17. /**
  18. * @global
  19. * @function
  20. * @param {Array} first
  21. * @param {Array} last
  22. * @description Concats two 2D arrays into one 2D array
  23. * @returns {Array} A 2D array created by concatenating the last array onto the end of the first array
  24. */
  25. function concatRows(first, last) {
  26. var a = [];
  27. for (var r = 0; r < first.length; r++) {
  28. a[r] = first[r].concat(last[r]);
  29. }
  30. return a;
  31. }
  32. /**
  33. * @name BlockSector
  34. * @class
  35. * @description An object representing a sector. Contains all 2D arrays of blocks and any sector-specific information.
  36. */
  37. function BlockSector() {
  38. /**
  39. * @field {Array} rows - A 2D array of Block IDs in the BlockSector
  40. * @memberof BlockSector
  41. */
  42. this.rows = [];
  43. }
  44. /**
  45. * @class SectoredBlockMap
  46. * @name SectoredBlockMap
  47. * @constructor
  48. * @param {Integer} sectorWidth - The number of blocks wide each sector should be (must be odd!)
  49. * @param {Integer} sectorHeight - The number of blocks high each sector should be (must be odd!)
  50. * @param {WorldGenerator} worldGenerator - The {@link WorldGenerator} Object to be used for this map
  51. * @prop {WorldGenerator} generator - The {@link WorldGenerator} to be used for this map
  52. * @prop {Array} sectorRows - A 2D array of BlockSectors and undefined values that make up the map
  53. * @prop {Integer} sWidth - A number representing the width of the map in sectors.
  54. * @prop {Integer} sHeight - A number representing the height of the map in sectors.
  55. * @prop {Integer} sectorWidth - An odd number representing the width of a sector in blocks
  56. * @prop {Integer} sectorHeight - An odd number representing the height of a sector in blocks
  57. * @prop {Integer} sectorCenterX - The index in the center row where the center sector of the map is
  58. * @prop {Integer} sectorCenterY - The index of the center row where the center sector of the map is
  59. * @prop {Integer} originSectorX - The x coordinate of the origin sector of the map
  60. * @prop {Integer} originSectorY - The y coordinate of the origin sector of the map
  61. * @prop {Integer} sectorRightBound - The x coordinate of the furthest right sector of the map
  62. * @prop {Integer} sectorLeftBound - The x coordinate of the furthest left sector of the map
  63. * @prop {Integer} sectorTopBound - The y coordinate of the furthest up sector of the map
  64. * @prop {Integer} sectorBottomBound - The y coordinate of the furthest down sector of the map
  65. * @prop {Array} generationQue - An array of sector coordinates that need generating during the next update call.
  66. *
  67. */
  68. function SectoredBlockMap(sectorWidth, sectorHeight, worldGenerator) {
  69. /*
  70. * The sectorRows in this sector map will create ONE rectangular shape, however, sectors/sector indexes may be filled in as "undefined" allowing gaps to exist in maps.
  71. *
  72. * For a SectoredBlockMap and for Sectors:
  73. *
  74. * +----------> Right is increasing X (the leftmost sector/block is 0)
  75. * | Down is increasing Y (The highest sector/block is 0)
  76. * |
  77. * V
  78. *
  79. */
  80. this.generator = worldGenerator;
  81. this.sectorRows = [];
  82. this.generationQue = []; // An array of sector coordinates that need generated.
  83. this.sWidth = 1; //Width and height is in sectors, NOT BLOCKS!
  84. this.sHeight = 1;
  85. this.sectorXOffset = 0; //This allows us to add in one sector at a time, we don't need to create a duplicate/empty sector on the other side, simply compensate for the difference
  86. this.sectorYOffset = 0; //If there is more sectors on the right, sectorXOffset should be negative, more on the left, it should be positive
  87. this.sectorWidth = sectorWidth; // Width of sector in blocks
  88. if (this.sectorWidth % 2 == 0) {
  89. this.sectorWidth++; //If the width is not odd, add one to make it odd
  90. }
  91. this.sectorHeight = sectorHeight; // Height of sector in blocks
  92. if (this.sectorHeight % 2 == 0) {
  93. this.sectorHeight++; //If the height is not odd, add one to make it odd
  94. }
  95. //Add the origin sector
  96. this.sectorRows.push(new Array());
  97. this.sectorCenterX = ((this.sectorWidth - 1 - this.sectorXOffset) / 2); //For example, width of 3: 3-1 = 2, 2 / 2 = 1, centerX is 1: [0][1][2]
  98. this.sectorCenterY = ((this.sectorHeight - 1 - this.sectorYOffset) / 2);
  99. this.originSectorX = 0; //Currently, there is only 1 sector existing, so the origin's index is 0,0
  100. this.originSectorY = 0;
  101. this.sectorRightBound = 0; //The X position of the rightmost sector
  102. this.sectorLeftBound = 0; //The X position of the leftmost sector
  103. this.sectorTopBound = 0; //The Y position of the topmost sector
  104. this.sectorBottomBound = 0; //The Y position of the bottommost sector
  105. /**
  106. * @method init
  107. * @memberof SectoredBlockMap
  108. * @description Itializes the map, should be called before the map is used!
  109. */
  110. this.init = function() {
  111. this.generator.generateOrigin(this);
  112. };
  113. /**
  114. * @method sectorCoordsToSectorIndex
  115. * @memberof SectoredBlockMap
  116. * @arg {Number} sectorX
  117. * @arg {Number} sectorY
  118. * @description This methods converts x and y coordinates into the indexes in the map's 2D array to access the specified sector.
  119. * @returns (Object) An object with two properties "sectorXIndex" and "sectorYIndex" that contain the x and y indexes of the sector specified with the x and y coords.
  120. */
  121. this.sectorCoordsToSectorIndex = function(sectorX, sectorY) {
  122. return {
  123. sectorXIndex: sectorX + this.originSectorX,
  124. sectorYIndex: sectorY + this.originSectorY
  125. };
  126. };
  127. /**
  128. * @method getOriginSector
  129. * @memberof SectoredBlockMap
  130. * @returns {BlockSector} The origin BlockSector of the map.
  131. */
  132. this.getOriginSector = function() {
  133. return this.sectorRows[this.originSectorY][this.originSectorX]; //Returns the center tile of the map
  134. };
  135. this.getOriginSector.bind(this);
  136. /**
  137. * @method getSector
  138. * @memberof SectoredBlockMap
  139. * @arg sectorX
  140. * @arg sectorY
  141. * @description Returns the BlockSector specified by the sector coords. Will return a BlockSector full of air/empty blocks if the sector doesn't exist!
  142. * @returns {BlockSector} Returns the BlockSector specified by the sector coords. Will return a BlockSector full of air/empty blocks if the sector doesn't exist!
  143. *
  144. */
  145. this.getSector = function(sectorX, sectorY) {
  146. if (!this.sectorExists(sectorX, sectorY)) {
  147. return new BlockSector(this.sectorWidth, this.sectorHeight); //Return a sector of air/empty blocks if the sector doesn't exist
  148. }
  149. var i = this.sectorCoordsToSectorIndex(sectorX, sectorY);
  150. return this.sectorRows[i.sectorYIndex][i.sectorXIndex];
  151. };
  152. /**
  153. * @method sectorInBounds
  154. * @memberof SectoredBlockMap
  155. * @arg sectorX
  156. * @arg sectorY
  157. * @description Tests to see if the specified sector is within the current bounds of the SectoredBlockMap.
  158. * @returns {Boolean} true if the sector is within the current bounds, false if not. This does not check to see if the sector exists!
  159. */
  160. this.sectorInBounds = function(sectorX, sectorY) { //Checks to see if the given coords point within the current boundaries
  161. if ((sectorX <= this.sectorRightBound) && (sectorX >= this.sectorLeftBound)) { //Too far to the right
  162. if ((sectorY >= this.sectorTopBound) && (sectorY <= this.sectorBottomBound)) {
  163. return true; //Everything checks out, return true
  164. }
  165. }
  166. return false;
  167. };
  168. this.sectorInBounds.bind(this);
  169. /**
  170. * @method sectorExists
  171. * @memberof SectoredBlockMap
  172. * @arg {Number} sectorX
  173. * @arg {Number} sectorY
  174. * @returns {Boolean} True if the sector is within the map bounds and has been generated, false if otherwise.
  175. */
  176. this.sectorExists = function(sectorX, sectorY) { //Checks to see if the given coords point to an extisting sector
  177. if (this.sectorInBounds(sectorX, sectorY)) {
  178. var i = this.sectorCoordsToSectorIndex(sectorX, sectorY);
  179. if (this.sectorRows[i.sectorYIndex][i.sectorXIndex] == undefined) {
  180. return false; //Sector has not been generated/created yet, return false
  181. } else {
  182. return true; //Sector has been generated, return true
  183. }
  184. }
  185. return false; //Sector isn't even within the boundaries, return false
  186. };
  187. //Return 1 if an old sector was replaced with the new one, return 2 if new sector was in the bounds and replaced an undefinied sector, return 3 if the sector was out of the boundaries and the map's structure had to be changed
  188. this.setSector = function(newSector, sectorX, sectorY) { //This can replace sectors and also add on new ones
  189. if (newSector.width != this.sectorWidth || newSector.height != this.sectorHeight) {
  190. if (STUDIO_DEV) {
  191. Studio.error("The new sector's dimensions do not match the sector size of the SectoredBlockMap!");
  192. }
  193. else {
  194. console.log("The new sector's dimensions do not match the sector size of the SectoredBlockMap!");
  195. }
  196. return false;
  197. }
  198. if (this.sectorExists(sectorX, sectorY)) { //Sector already existed, replacing with new Sector, returning 1
  199. var i = this.sectorCoordsToSectorIndex(sectorX, sectorY);
  200. this.sectorRows[i.sectorYIndex][i.sectorXIndex] = newSector;
  201. return 1;
  202. } else {
  203. if (this.sectorInBounds(sectorX, sectorY)) { //Ok, the sector is within the bounds, we don't have to do anything to the map structure, just put in the sector, return 2
  204. var i = this.sectorCoordsToSectorIndex(sectorX, sectorY);
  205. this.sectorRows[i.sectorYIndex][i.sectorXIndex] = newSector;
  206. return 2;
  207. } else {
  208. //Ok, sector is not in the bounds, we will have to adjust the map to fit it in, returning 3
  209. if (sectorX > this.sectorRightBound) {
  210. //console.log("right" + sectorX);
  211. var diff = sectorX - this.sectorRightBound;
  212. for (var row = 0; row < this.sHeight; row++) {
  213. for (var i = 0; i < diff; i++) {
  214. this.sectorRows[i].push(undefined);
  215. }
  216. }
  217. this.sectorRightBound += diff;
  218. this.sWidth += diff;
  219. //When we push elements, the origin sector does not change.
  220. } else if (sectorX < this.sectorLeftBound) {
  221. //console.log("left" + sectorX);
  222. var diff = this.sectorLeftBound - sectorX;
  223. for (var row = 0; row < this.sHeight; row++) {
  224. for (var i = 0; i < diff; i++) {
  225. this.sectorRows[i].unshift(undefined);
  226. }
  227. }
  228. this.sectorLeftBound -= diff;
  229. this.sWidth += diff;
  230. this.originSectorX += diff;
  231. }
  232. if (sectorY < this.sectorTopBound) {
  233. //console.log("top" + sectorY);
  234. var diff = this.sectorTopBound - sectorY;
  235. var blankRow = [];
  236. for (var col = 0; col < this.sWidth; col++) {
  237. blankRow.push(undefined);
  238. }
  239. for (var i = 0; i < diff; i++) {
  240. this.sectorRows.unshift(blankRow);
  241. }
  242. this.sHeight += diff;
  243. this.originSectorY += diff;
  244. this.sectorTopBound -= diff;
  245. } else if (sectorY > this.sectorBottomBound) {
  246. // console.log("bottom" + sectorY);
  247. var diff = sectorY - this.sectorBottomBound;
  248. var blankRow = [];
  249. for (var col = 0; col < this.sWidth; col++) {
  250. blankRow.push(undefined);
  251. }
  252. for (var i = 0; i < diff; i++) {
  253. this.sectorRows.push(blankRow);
  254. }
  255. this.sHeight += diff;
  256. this.sectorBottomBound += diff;
  257. }
  258. var index = this.sectorCoordsToSectorIndex(sectorX, sectorY);
  259. this.sectorRows[index.sectorYIndex][index.sectorXIndex] = newSector;
  260. return 3;
  261. }
  262. }
  263. };
  264. this.getBlockIdAt = function(blockPosition) {
  265. return this.getSector(blockPosition.sectorX, blockPosition.sectorY).rows[Math.ceil(blockPosition.localY)][Math.floor(blockPosition.localX)];
  266. };
  267. this.setBlockIdAt = function(blockPosition, newId) {
  268. this.getSector(blockPosition.sectorX, blockPosition.sectorY).rows[Math.round(blockPosition.localY)][Math.round(blockPosition.localX)] = newId;
  269. };
  270. //TODO: Figure out what the below function does and why it even exists....
  271. this.isInMap = function(blockX, blockY) {
  272. //blockX and blockY are the distance in blocks from the origin block
  273. if (!game.generation.GENERATE_WITH_PLAYER) { //if we have an infinite world, we should always have some map for the cursor to land in
  274. var nX = Math.abs(Math.floor(blockX + this.sectorCenterX));
  275. var nY = Math.abs(Math.floor(blockY + this.sectorCenterY));
  276. var sectorX = (nX / this.sectorWidth) - ((nX % this.sectorWidth) * this.sectorWidth);
  277. var sectorY = (nY / this.sectorHeight) - ((nY % this.sectorHeight) * this.sectorHeight);
  278. if (this.sectorExists(sectorX, sectorY)) {
  279. return true;
  280. } else {
  281. return false;
  282. }
  283. } else {
  284. return true;
  285. }
  286. };
  287. this.isInMap.bind(this);
  288. this.setBlockIdAt.bind(this);
  289. this.getBlockIdAt.bind(this);
  290. // Get the BlockPosition of the block that is relX to right of and relY below the block given by originPosition
  291. /**
  292. * @memberof SectoredBlockMap
  293. * @method getPositionRelativeTo
  294. * @description Returns a new {@link BlockPosition} based upon relX blocks left and right and relY blocks up and down from the original {@link BlockPosition} originPosition
  295. * @param {BlockPosition} originPosition - The position that will be used as a point of reference.
  296. * @param {Integer} relX - Number of blocks to the right of the point of reference. (left if negative)
  297. * @param {Integer} relY - Number of blocks below the point of reference (up if negative).
  298. * @returns {BlockPosition}
  299. */
  300. this.getPositionRelativeTo = function(originPosition, relX, relY) {
  301. var newLocalX = (originPosition.localX) + relX;
  302. var newLocalY = (originPosition.localY) + relY;
  303. var sectorX = originPosition.sectorX;
  304. var sectorY = originPosition.sectorY;
  305. while (newLocalY < 0) {
  306. sectorY--;
  307. newLocalY += this.sectorHeight;
  308. }
  309. while (newLocalY > this.sectorHeight) {
  310. newLocalY = Math.abs(this.sectorHeight - newLocalY);
  311. sectorY++;
  312. }
  313. while (newLocalX < 0) {
  314. sectorX--;
  315. newLocalX += this.sectorWidth;
  316. }
  317. while ((newLocalX/(this.sectorWidth-1)) >= 1) {
  318. newLocalX = Math.abs(this.sectorWidth - newLocalX);
  319. sectorX++;
  320. }
  321. return new BlockPosition(sectorX, sectorY, newLocalX, newLocalY);
  322. };
  323. this.getPositionRelativeTo.bind(this);
  324. /**
  325. * @memberof SectoredBlockMap
  326. * @method getWindow
  327. * @param {BlockPosition} centerPosition - The position of the block that the window is centered around
  328. * @param {Integer} width - The number of blocks from left bound to right bound of the window (must be odd!)
  329. * @param {Integer} height - The number of blocks from the top bound to the bottom bound of the window (must be odd!)
  330. * @description This function takes a center position and dimensions to select an area of blocks from the SectoredBlockMap to return as a 2D array of blocks. It is a way to select all blocks in an area, even if that area contains multiple sectors.
  331. * @returns {Array} A 2D array of blocks that are within the window
  332. *
  333. */
  334. this.getWindow = function(centerPosition, width, height) { //Height and Width must be odd!
  335. //centerPoition holds the position of the center block of the window:
  336. // x is the localX position of the center block
  337. // y is the localY position of the center block
  338. // width is the number of blocks from left bound to right bound of the window
  339. // height is the number of blocks from the top bound to the bottom bound of the window
  340. //First let's figure out the number rows and columns this window will span
  341. var localLeftBound = Math.floor((centerPosition.localX - ((width-1) / 2)));
  342. var localRightBound = Math.floor((centerPosition.localX + ((width-1) / 2)));
  343. var localTopBound = Math.floor((centerPosition.localY - ((height-1) / 2)));
  344. var localBottomBound = Math.floor((centerPosition.localY + ((height-1) / 2)));
  345. //Now lets calculate the number of sectors in each direction (other than the given sector) that will need to be added
  346. var sectorsAbove = 0;
  347. var sectorsBelow = 0;
  348. var sectorsLeft = 0;
  349. var sectorsRight = 0;
  350. if (localTopBound < 0) {
  351. sectorsAbove = Math.ceil((localTopBound + this.sectorHeight) / this.sectorHeight);
  352. }
  353. if (localBottomBound > this.sectorHeight) {
  354. sectorsBelow = Math.ceil((localBottomBound - this.sectorHeight) / this.sectorHeight)
  355. }
  356. if (localLeftBound < 0) {
  357. sectorsLeft = Math.ceil((localLeftBound + this.sectorWidth) / this.sectorWidth);
  358. }
  359. if (localRightBound > this.sectorWidth) {
  360. sectorsRight = Math.ceil((localRightBound - this.sectorWidth) / this.sectorWidth);
  361. }
  362. var windowList = []; // The windowList is an array of rows. Each row is an array of blocks. Top left is 0,0, bottom right is length,length
  363. //Now we will piece together our windowList, we will start from top left, go to the right, and then drop down one sector and repeat until we reach bottom right
  364. var currentSectorX = centerPosition.sectorX - sectorsLeft;
  365. for (var currentSectorY = centerPosition.sectorY - sectorsAbove; currentSectorY <= centerPosition.sectorY + sectorsBelow; currentSectorY++) {
  366. var windowRow = [];
  367. for (var currentSectorX = centerPosition.sectorX - sectorsLeft; currentSectorX <= centerPosition.sectorX + sectorsRight; currentSectorX++) {
  368. //We will calculate the center of a large window in terms of blocks (local) relative to the local bounds of the target sector
  369. var signX = (currentSectorX == centerPosition.sectorX) ? 1 : -1 * (Math.abs(currentSectorX - centerPosition.sectorX) / (currentSectorX - centerPosition.sectorX));
  370. var signY = (currentSectorY == centerPosition.sectorY) ? 1 : -1 * (Math.abs(currentSectorY - centerPosition.sectorY) / (currentSectorY - centerPosition.sectorY));
  371. var relativeLocalX = (game.generation.SECTOR_SIZE * (Math.abs(currentSectorX - centerPosition.sectorX))) + (signX * centerPosition.localX);
  372. var relativeLocalY = (game.generation.SECTOR_SIZE * (Math.abs(currentSectorY - centerPosition.sectorY))) + (signY * centerPosition.localY);
  373. windowRow.push(this.windowOfSector(new BlockPosition(currentSectorX, currentSectorY, relativeLocalX, relativeLocalY), width, height, game.generation.GENERATE_WITH_PLAYER));
  374. }
  375. windowList.push(windowRow);
  376. }
  377. //Now we have to merge all of these windows into one big window to return
  378. var rows = [];
  379. for (var windowRow = 0; windowRow < windowList.length; windowRow++) { // For each row of windows
  380. var mergedRows = [];
  381. for (var windowColumn = 0; windowColumn < windowList[windowRow].length; windowColumn++) { // For each window in a row
  382. //For each row in a window
  383. for (var r = 0; r < windowList[windowRow][windowColumn].length; r++) {
  384. if (windowColumn != 0) {
  385. mergedRows[r] = mergedRows[r].concat(windowList[windowRow][windowColumn][r]);
  386. } else {
  387. mergedRows[r] = windowList[windowRow][windowColumn][r];
  388. }
  389. }
  390. }
  391. if (windowRow != 0) {
  392. rows = rows.concat(mergedRows);
  393. } else {
  394. rows = mergedRows;
  395. }
  396. }
  397. //console.log(rows);
  398. return rows;
  399. };
  400. //This returns a window of the specified sector. Note, it does not include from other sectors. So if the window only include the top right corner of the specified sector, only the top right corner is returned.
  401. this.windowOfSector = function(centerPosition, width, height, generateIfNeeded) {
  402. //centerPosition holds the BlockPosition of the center block of the window
  403. //localX is the x position of the center of the window
  404. //localY is the y position of the center of the window
  405. var sector = this.getSector(centerPosition.sectorX, centerPosition.sectorY).rows;
  406. //First lets calculate the normal boundaries
  407. var localLeftBound = Math.floor((centerPosition.localX - (width / 2)));
  408. var localRightBound = Math.floor((centerPosition.localX + (width / 2)));
  409. var localTopBound = Math.floor((centerPosition.localY - (height / 2)));
  410. var localBottomBound = Math.floor((centerPosition.localY + (height / 2)));
  411. //Now lets limit the boundaries to the sector bounds
  412. if (localTopBound < 0) {
  413. localTopBound = 0;
  414. }
  415. if (localBottomBound > this.sectorHeight) {
  416. localBottomBound = this.sectorHeight - 1;
  417. }
  418. if (localLeftBound < 0) {
  419. localLeftBound = 0;
  420. }
  421. if (localRightBound > this.sectorWidth) {
  422. localRightBound = this.sectorWidth - 1;
  423. }
  424. if (this.sectorExists(centerPosition.sectorX, centerPosition.sectorY)) {
  425. //Now lets splice the array to get our array to return
  426. var rows = sector.slice(localTopBound, localBottomBound + 1);
  427. for (var i = 0; i < rows.length; i++) {
  428. rows[i] = rows[i].slice(localLeftBound, localRightBound + 1);
  429. }
  430. return rows;
  431. } else {
  432. //Sector doesnt exist
  433. if (generateIfNeeded) {
  434. //Add the non-existant sector to the generation que for the next tick if we are generating as the player moves/this sector needs generated
  435. this.generationQue.push(centerPosition);
  436. }
  437. //In the meantime, lets generate a blank set of blocks to be generated
  438. var blankRows = [];
  439. var row = [];
  440. var h = localBottomBound - localTopBound;
  441. var w = localRightBound - localLeftBound;
  442. for (var c = 0; c < w + 1; c++) {
  443. row.push(game.blockIdList["sky"]);
  444. }
  445. for (var r = 0; r < h + 1; r++) {
  446. blankRows.push(row);
  447. }
  448. var bs = new BlockSector();
  449. bs.rows = blankRows;
  450. bs.height = bs.rows.length;
  451. bs.width = bs.rows[0].length;
  452. return blankRows;
  453. }
  454. };
  455. //Below, allows methods to access their parent
  456. this.getWindow.bind(this);
  457. this.sectorExists.bind(this);
  458. this.setSector.bind(this);
  459. this.windowOfSector.bind(this);
  460. this.sectorCoordsToSectorIndex.bind(this);
  461. this.getSector.bind(this);
  462. }