diff --git a/src/server/world/instancer.js b/src/server/world/instancer.js index 4a9b31ae..e93ad1a0 100644 --- a/src/server/world/instancer.js +++ b/src/server/world/instancer.js @@ -52,6 +52,7 @@ module.exports = { if (!map.oldCollisionMap) map.oldCollisionMap = map.collisionMap; + map.randomMap.init(fakeInstance); this.regenMap(); } @@ -101,11 +102,7 @@ module.exports = { objects.objects.length = 0; objects.objects = []; - map.randomMap.generate({ - map: map, - physics: physics, - spawners: spawners - }); + map.randomMap.generate(); map.seed = _.getGuid(); }; diff --git a/src/server/world/map.js b/src/server/world/map.js index 4039427c..296f1fb3 100644 --- a/src/server/world/map.js +++ b/src/server/world/map.js @@ -3,7 +3,8 @@ let physics = require('./physics'); let spawners = require('./spawners'); let resourceSpawner = require('./resourceSpawner'); let globalZone = require('../config/zoneBase'); -let randomMap = require('./randomMap'); +let randomMap = require('./randomMap/randomMap'); +const generateMappings = require('./randomMap/generateMappings'); let events = require('../misc/events'); const mapObjects = require('./map/mapObjects'); @@ -135,7 +136,7 @@ module.exports = { this.randomMap = extend({}, randomMap); this.oldMap = this.layers; this.randomMap.templates = extend([], this.rooms); - this.randomMap.generateMappings(this); + generateMappings(this.randomMap, this); if (!mapFile.properties.isRandom) { for (let i = 0; i < this.size.w; i++) { diff --git a/src/server/world/randomMap.js b/src/server/world/randomMap.js deleted file mode 100644 index ae12dd52..00000000 --- a/src/server/world/randomMap.js +++ /dev/null @@ -1,670 +0,0 @@ -module.exports = { - templates: null, - tileMappings: null, - rooms: [], - exitAreas: [], - - leafConstraints: { - minDistance: 4, - maxDistance: 12, - minCount: 3, - maxCount: 7 - }, - - endConstraints: { - minDistance: 10, - maxDistance: 12 - }, - - bounds: [0, 0, 0, 0], - - generate: function (instance) { - const { map } = instance; - map.clientMap.hiddenRooms = []; - - this.loadMapProperties(map.mapFile.properties); - - this.rooms = []; - this.exitAreas = []; - this.bounds = [0, 0, 0, 0]; - this.templates = extend([], map.rooms); - - this.setupTemplates(map); - - const hasEndRoom = this.templates.some(t => t.properties.end); - if (!hasEndRoom) { - /* eslint-disable-next-line no-console */ - console.log(`Random map has no end room defined: ${map.name}`); - - return; - } - - if (!this.tileMappings) - this.generateMappings(map); - - let startTemplate = this.templates.filter(t => t.properties.start); - startTemplate = startTemplate[this.randInt(0, startTemplate.length)]; - - let startRoom = this.buildRoom(startTemplate); - - if (!this.isValidDungeon()) - this.generate(instance); - else { - this.offsetRooms(startRoom); - this.buildMap(instance, startRoom); - } - - //To spawn in another room - /*const spawnRoom = this.rooms.find(t => t.template.properties.end); - map.spawn = [{ - x: spawnRoom.x + ~~(spawnRoom.template.width / 2) - 2, - y: spawnRoom.y + ~~(spawnRoom.template.height / 2) + 6 - }];*/ - }, - - loadMapProperties: function ({ leafConstraints, endConstraints }) { - if (leafConstraints) - this.leafConstraints = JSON.parse(leafConstraints); - - if (endConstraints) - this.endConstraints = JSON.parse(endConstraints); - }, - - isValidDungeon: function () { - const { rooms, leafConstraints, endConstraints } = this; - const leafRooms = rooms.filter(r => !r.connections.length); - - //Ensure that we have enough leaf rooms - const { minCount: minLeafRooms, maxCount: maxLeafRooms } = leafConstraints; - - const leafRoomCount = leafRooms.length; - if (leafRoomCount < minLeafRooms || leafRoomCount > maxLeafRooms) - return false; - - //Ensure that the end room exists - const endRoom = rooms.find(r => r.template.properties.end); - - if (!endRoom) - return false; - - //Ensure that the end room is the correct distance - const { minDistance: minEndDistance, maxDistance: maxEndDistance } = endConstraints; - - const endDistance = endRoom.distance; - if (endDistance < minEndDistance || endDistance > maxEndDistance) - return false; - - //Ensure that leaf rooms are correct distances - const { minDistance: minLeafDistance, maxDistance: maxLeafDistance } = leafConstraints; - - const leafRoomsDistanceOk = !leafRooms.some(({ distance: roomDistance }) => { - return (roomDistance < minLeafDistance || roomDistance > maxLeafDistance); - }); - - if (!leafRoomsDistanceOk) - return false; - - //Ensure that enough minOccur templates have been included - const minOccurOk = this.templates.every(t => { - const minOccur = ~~t.properties.minOccur || 0; - const occurs = rooms.filter(r => r.template.typeId === t.typeId).length; - return occurs >= minOccur; - }); - - if (!minOccurOk) - return false; - - return true; - }, - - /* eslint-disable-next-line */ - setupTemplates: function (map) { - /* eslint-disable-next-line */ - this.templates.forEach((r, typeId) => { - if (r.properties.mapping) - return; - - r.typeId = typeId; - - let { noRotate = false, canFlipX = true, canFlipY = true } = r.properties; - //Property values are strings. So we turn '1' and '0' into 1 and 0 - canFlipX = ~~canFlipX; - canFlipY = ~~canFlipY; - - //Fix Polygons - r.objects.forEach(o => { - if (!o.fog) - return; - - const newArea = o.area.map(p => { - const [ px, py ] = p; - - const hpx = px - r.x; - const hpy = py - r.y; - - return [hpx, hpy]; - }); - - Object.assign(o, { - x: o.x - r.x, - y: o.y - r.y, - area: newArea - }); - }); - - //FlipX Loop - for (let i = 0; i < 2; i++) { - if (i && !canFlipX) - continue; - - //FlipY Loop - for (let j = 0; j < 2; j++) { - if (j && !canFlipY) - continue; - - //Rotate Loop - for (let k = 0; k < 2; k++) { - if (k && noRotate) - continue; - - if (i + j + k === 0) - continue; - - let flipped = extend({ - flipX: !!i, - flipY: !!j, - rotate: !!k - }, r); - - flipped.exits.forEach(e => { - let direction = JSON.parse(e.properties.exit); - - if (flipped.flipX) { - direction[0] *= -1; - e.x = r.x + r.width - (e.x - r.x) - e.width; - } - if (flipped.flipY) { - direction[1] *= -1; - e.y = r.y + r.height - (e.y - r.y) - e.height; - } - if (flipped.rotate) { - direction = [direction[1], direction[0]]; - let t = e.x; - e.x = r.x + (e.y - r.y); - e.y = r.y + (t - r.x); - t = e.width; - e.width = e.height; - e.height = t; - } - - e.properties.exit = JSON.stringify(direction); - }); - - flipped.objects.forEach(o => { - if (!o.fog) { - if (flipped.flipX) - o.x = r.x + r.width - (o.x - r.x) - 1; - if (flipped.flipY) - o.y = r.y + r.height - (o.y - r.y) - 1; - if (flipped.rotate) { - let t = o.x; - o.x = r.x + (o.y - r.y); - o.y = r.y + (t - r.x); - } - } else { - if (flipped.flipX) { - const newArea = o.area.map(p => { - const [ px, py ] = p; - - const hpx = r.width - px; - - return [hpx, py]; - }); - - Object.assign(o, { - area: newArea - }); - } - if (flipped.flipY) { - const newArea = o.area.map(p => { - const [ px, py ] = p; - - const hpy = r.height - py; - - return [px, hpy]; - }); - - Object.assign(o, { - area: newArea - }); - } - if (flipped.rotate) { - const newArea = o.area.map(p => { - const [ px, py ] = p; - - const t = px; - const hpx = py; - const hpy = t; - - return [hpx, hpy]; - }); - - Object.assign(o, { - area: newArea - }); - } - - //Fix polygon bounds - let lowX = r.width; - let lowY = r.height; - let highX = 0; - let highY = 0; - - o.area.forEach(p => { - const [ px, py ] = p; - - if (px < lowX) - lowX = px; - if (px > highX) - highX = px; - - if (py < lowY) - lowY = py; - if (py > highY) - highY = py; - }); - - o.x = lowX; - o.y = lowY; - o.width = highX - lowX; - o.height = highY - lowY; - } - }); - - if (flipped.rotate) { - let t = flipped.width; - flipped.width = flipped.height; - flipped.height = t; - } - - this.templates.push(flipped); - } - } - } - }); - - this.templates.forEach(r => { - let rotate = r.rotate; - let w = rotate ? r.height : r.width; - let h = rotate ? r.width : r.height; - - r.map = _.get2dArray(r.width, r.height); - r.tiles = _.get2dArray(r.width, r.height); - r.collisionMap = _.get2dArray(r.width, r.height); - r.oldExits = extend([], r.exits); - - for (let i = 0; i < w; i++) { - for (let j = 0; j < h; j++) { - let ii = rotate ? j : i; - let jj = rotate ? i : j; - - let x = r.flipX ? (r.x + w - i - 1) : (r.x + i); - let y = r.flipY ? (r.y + h - j - 1) : (r.y + j); - - r.map[ii][jj] = map.oldMap[x][y]; - r.tiles[ii][jj] = map.oldLayers.tiles[x][y]; - r.collisionMap[ii][jj] = map.oldCollisionMap[x][y]; - } - } - }); - }, - - generateMappings: function (map) { - this.tileMappings = {}; - - let oldMap = map.oldMap; - - this.templates - .filter(r => r.properties.mapping) - .forEach(m => { - let x = m.x; - let y = m.y; - let w = m.width; - let h = m.height; - - let baseTile = oldMap[x][y]; - baseTile = (baseTile || '') - .replace('0,', '') - .replace(',', ''); - - let mapping = this.tileMappings[baseTile] = []; - - for (let i = x + 2; i < x + w; i++) { - for (let j = y; j < y + h; j++) { - let oM = oldMap[i][j]; - - if (oM.replace) { - oM = oM - .replace('0,', '') - .replace(',', ''); - } - - mapping.push(oM); - } - } - }); - }, - - buildMap: function (instance, startRoom) { - let w = this.bounds[2] - this.bounds[0]; - let h = this.bounds[3] - this.bounds[1]; - - let map = instance.map; - let clientMap = map.clientMap; - - clientMap.map = _.get2dArray(w, h); - clientMap.collisionMap = _.get2dArray(w, h, 1); - - let startTemplate = startRoom.template; - map.spawn = [{ - x: startRoom.x + ~~(startTemplate.width / 2), - y: startRoom.y + ~~(startTemplate.height / 2) - }]; - - this.drawRoom(instance, startRoom); - - instance.physics.init(clientMap.collisionMap); - - this.spawnObjects(instance, startRoom); - }, - - randomizeTile: function (tile, floorTile) { - let mapping = this.tileMappings[tile]; - if (!mapping) - return tile; - - tile = mapping[this.randInt(0, mapping.length)]; - if (!tile) - return 0; - - return tile; - }, - - drawRoom: function (instance, room) { - let map = instance.map.clientMap.map; - let template = room.template; - let collisionMap = instance.map.clientMap.collisionMap; - - for (let i = 0; i < template.width; i++) { - let x = room.x + i; - for (let j = 0; j < template.height; j++) { - let y = room.y + j; - - let tile = template.map[i][j]; - if (!tile) - continue; - - let currentTile = map[x][y]; - let collides = template.collisionMap[i][j]; - let floorTile = template.tiles[i][j]; - - if (!currentTile) { - let cell = tile.split(','); - let cLen = cell.length; - - let newCell = ''; - for (let k = 0; k < cLen; k++) { - let c = cell[k]; - let newC = this.randomizeTile(c); - newCell += newC; - - if (k < cLen - 1) - newCell += ','; - } - - map[x][y] = newCell; - - collisionMap[x][y] = collides; - continue; - } else { - //Remove objects from this position since it falls in another room - template.objects.spliceWhere(o => { - let ox = o.x - template.x + room.x; - let oy = o.y - template.y + room.y; - return ((ox === x) && (oy === y)); - }); - } - - let didCollide = collisionMap[x][y]; - if (collides) { - if (didCollide) { - let isExitTile = this.exitAreas.find(e => { - return (!((x < e.x) || (y < e.y) || (x >= e.x + e.width) || (y >= e.y + e.height))); - }); - if (isExitTile) { - let isThisExit = template.oldExits.find(e => { - let ex = room.x + (e.x - template.x); - let ey = room.y + (e.y - template.y); - return (!((x < ex) || (y < ey) || (x >= ex + e.width) || (y >= ey + e.height))); - }); - if (isThisExit) { - map[x][y] = this.randomizeTile(floorTile); - collisionMap[x][y] = false; - } else - collisionMap[x][y] = true; - } - } - } else if (didCollide) { - collisionMap[x][y] = false; - map[x][y] = this.randomizeTile(floorTile); - } - } - } - - template.oldExits.forEach(function (e) { - this.exitAreas.push({ - x: room.x + (e.x - template.x), - y: room.y + (e.y - template.y), - width: e.width, - height: e.height - }); - }, this); - - room.connections.forEach(c => this.drawRoom(instance, c), this); - }, - - spawnObjects: function (instance, room) { - let template = room.template; - let spawners = instance.spawners; - let spawnCd = instance.map.mapFile.properties.spawnCd; - - template.objects.forEach(o => { - if (!o.fog) { - o.x = o.x - template.x + room.x; - o.y = o.y - template.y + room.y; - - spawners.register(o, spawnCd); - } else { - o.x += room.x; - o.y += room.y; - - o.area = o.area.map(p => { - const [px, py] = p; - - return [px + room.x, py + room.y]; - }); - - instance.map.clientMap.hiddenRooms.push(o); - } - }); - - room.connections.forEach(c => this.spawnObjects(instance, c), this); - }, - - buildRoom: function (template, connectTo, templateExit, connectToExit, isHallway) { - let room = { - x: 0, - y: 0, - distance: 0, - isHallway: isHallway, - template: extend({}, template), - connections: [] - }; - - if (connectTo) { - room.x = connectTo.x + connectToExit.x - connectTo.template.x + (template.x - templateExit.x); - room.y = connectTo.y + connectToExit.y - connectTo.template.y + (template.y - templateExit.y); - room.distance = connectTo.distance + 1; - room.parent = connectTo; - } - - if (this.doesCollide(room, connectTo)) - return false; - - if (connectTo) - connectTo.connections.push(room); - - this.rooms.push(room); - - this.updateBounds(room); - - if (room.distance < this.leafConstraints.maxDistance) { - const maxExits = room.template.exits.length; - const minExits = Math.min(maxExits, 2); - - const count = this.randInt(minExits, maxExits + 1); - - for (let i = 0; i < count; i++) - this.setupConnection(room, !isHallway); - } - - if ((isHallway) && (room.connections.length === 0)) { - this.rooms.spliceWhere(r => r === room); - room.parent.connections.spliceWhere(c => c === room); - return false; - } - - return room; - }, - - setupConnection: function (fromRoom, isHallway) { - if (!fromRoom.template.exits.length) - return true; - - let fromExit = fromRoom.template.exits.splice(this.randInt(0, fromRoom.template.exits.length), 1)[0]; - let exitDirection = JSON.parse(fromExit.properties.exit); - let templates = this.templates.filter(t => { - if ( - (t.properties.mapping) || - (!!t.properties.hallway !== isHallway) || - (t.properties.start) || - ( - (t.properties.end) && - (fromRoom.distance + 1 !== this.leafConstraints.maxDistance) - ) - ) - return false; - - let isValid = t.exits.some(e => { - let direction = JSON.parse(e.properties.exit); - return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1])); - }); - - if ((isValid) && (t.properties.maxOccur)) { - let occurs = this.rooms.filter(r => (r.template.typeId === t.typeId)).length; - if (occurs >= ~~t.properties.maxOccur) - isValid = false; - } - - if ((isValid) && (fromRoom.distance + 1 === this.leafConstraints.maxDistance)) { - //If there is an exit available, rather use that - if (!t.properties.end) { - let endsAvailable = this.templates.filter(tt => { - if (!tt.properties.end) - return false; - else if (!~~tt.properties.maxOccur) - return true; - else if (this.rooms.filter(r => r.template.typeId === tt.typeId).length < ~~tt.properties.maxOccur) - return true; - }); - - if (endsAvailable.length > 0) - isValid = false; - } - } - - return isValid; - }); - - if (templates.length === 0) { - fromRoom.template.exits.push(fromExit); - return false; - } - - let template = extend({}, templates[this.randInt(0, templates.length)]); - - let templateExit = template.exits.filter(e => { - let direction = JSON.parse(e.properties.exit); - return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1])); - }); - templateExit = templateExit[this.randInt(0, templateExit.length)]; - let exitIndex = template.exits.findIndex(e => e === templateExit); - - template.exits.splice(exitIndex, 1); - - let success = this.buildRoom(template, fromRoom, templateExit, fromExit, isHallway); - if (!success) { - fromRoom.template.exits.push(fromExit); - return false; - } - - return true; - }, - - offsetRooms: function (room) { - let bounds = this.bounds; - let dx = (this.bounds[0] < 0) ? -bounds[0] : 0; - let dy = (this.bounds[1] < 0) ? -bounds[1] : 0; - - this.performOffset(room, dx, dy); - - this.bounds = [bounds[0] + dx, bounds[1] + dy, bounds[2] + dx, bounds[3] + dy]; - }, - - performOffset: function (room, dx, dy) { - room.x += dx; - room.y += dy; - - room.connections.forEach(c => this.performOffset(c, dx, dy), this); - }, - - updateBounds: function (room) { - this.bounds[0] = Math.min(this.bounds[0], room.x); - this.bounds[1] = Math.min(this.bounds[1], room.y); - this.bounds[2] = Math.max(this.bounds[2], room.x + room.template.width); - this.bounds[3] = Math.max(this.bounds[3], room.y + room.template.height); - }, - - doesCollide: function (room, ignore) { - for (let i = 0; i < this.rooms.length; i++) { - let r = this.rooms[i]; - if (r === ignore) - continue; - - let collides = (!( - (room.x + room.template.width < r.x) || - (room.y + room.template.height < r.y) || - (room.x >= r.x + r.template.width) || - (room.y >= r.y + r.template.height) - )); - if (collides) - return true; - } - - return false; - }, - - randInt: function (min, max) { - return ~~(Math.random() * (max - min)) + min; - } -}; diff --git a/src/server/world/randomMap/buildMap.js b/src/server/world/randomMap/buildMap.js new file mode 100644 index 00000000..511ff59b --- /dev/null +++ b/src/server/world/randomMap/buildMap.js @@ -0,0 +1,27 @@ +const drawRoom = require('./drawRoom'); +const spawnObjects = require('./spawnObjects'); + +module.exports = (scope, instance, startRoom) => { + const { bounds } = scope; + + let w = bounds[2] - bounds[0]; + let h = bounds[3] - bounds[1]; + + let map = instance.map; + let clientMap = map.clientMap; + + clientMap.map = _.get2dArray(w, h); + clientMap.collisionMap = _.get2dArray(w, h, 1); + + let startTemplate = startRoom.template; + map.spawn = [{ + x: startRoom.x + ~~(startTemplate.width / 2), + y: startRoom.y + ~~(startTemplate.height / 2) + }]; + + drawRoom(scope, instance, startRoom); + + instance.physics.init(clientMap.collisionMap); + + spawnObjects(scope, instance, startRoom); +}; diff --git a/src/server/world/randomMap/buildRoom.js b/src/server/world/randomMap/buildRoom.js new file mode 100644 index 00000000..db319cb0 --- /dev/null +++ b/src/server/world/randomMap/buildRoom.js @@ -0,0 +1,52 @@ +const setupConnection = require('./setupConnection'); +const doesCollide = require('./doesCollide'); + +const buildRoom = (scope, template, connectTo, templateExit, connectToExit, isHallway) => { + const { rooms, leafConstraints, randInt } = scope; + + let room = { + x: 0, + y: 0, + distance: 0, + isHallway: isHallway, + template: extend({}, template), + connections: [] + }; + + if (connectTo) { + room.x = connectTo.x + connectToExit.x - connectTo.template.x + (template.x - templateExit.x); + room.y = connectTo.y + connectToExit.y - connectTo.template.y + (template.y - templateExit.y); + room.distance = connectTo.distance + 1; + room.parent = connectTo; + } + + if (doesCollide(scope, room, connectTo)) + return false; + + if (connectTo) + connectTo.connections.push(room); + + rooms.push(room); + + scope.updateBounds(room); + + if (room.distance < leafConstraints.maxDistance) { + const maxExits = room.template.exits.length; + const minExits = Math.min(maxExits, 2); + + const count = randInt(minExits, maxExits + 1); + + for (let i = 0; i < count; i++) + setupConnection(scope, room, !isHallway, buildRoom); + } + + if ((isHallway) && (room.connections.length === 0)) { + rooms.spliceWhere(r => r === room); + room.parent.connections.spliceWhere(c => c === room); + return false; + } + + return room; +}; + +module.exports = buildRoom; diff --git a/src/server/world/randomMap/doesCollide.js b/src/server/world/randomMap/doesCollide.js new file mode 100644 index 00000000..f1c69afa --- /dev/null +++ b/src/server/world/randomMap/doesCollide.js @@ -0,0 +1,20 @@ +module.exports = (scope, room, ignore) => { + const { rooms } = scope; + + for (let i = 0; i < rooms.length; i++) { + let r = rooms[i]; + if (r === ignore) + continue; + + let collides = (!( + (room.x + room.template.width < r.x) || + (room.y + room.template.height < r.y) || + (room.x >= r.x + r.template.width) || + (room.y >= r.y + r.template.height) + )); + if (collides) + return true; + } + + return false; +}; diff --git a/src/server/world/randomMap/drawRoom.js b/src/server/world/randomMap/drawRoom.js new file mode 100644 index 00000000..b9029394 --- /dev/null +++ b/src/server/world/randomMap/drawRoom.js @@ -0,0 +1,86 @@ +const drawRoom = (scope, instance, room) => { + const { exitAreas } = scope; + + let map = instance.map.clientMap.map; + let template = room.template; + let collisionMap = instance.map.clientMap.collisionMap; + + for (let i = 0; i < template.width; i++) { + let x = room.x + i; + for (let j = 0; j < template.height; j++) { + let y = room.y + j; + + let tile = template.map[i][j]; + if (!tile) + continue; + + let currentTile = map[x][y]; + let collides = template.collisionMap[i][j]; + let floorTile = template.tiles[i][j]; + + if (!currentTile) { + let cell = tile.split(','); + let cLen = cell.length; + + let newCell = ''; + for (let k = 0; k < cLen; k++) { + let c = cell[k]; + let newC = scope.randomizeTile(c); + newCell += newC; + + if (k < cLen - 1) + newCell += ','; + } + + map[x][y] = newCell; + + collisionMap[x][y] = collides; + continue; + } else { + //Remove objects from this position since it falls in another room + template.objects.spliceWhere(o => { + let ox = o.x - template.x + room.x; + let oy = o.y - template.y + room.y; + return ((ox === x) && (oy === y)); + }); + } + + let didCollide = collisionMap[x][y]; + if (collides) { + if (didCollide) { + let isExitTile = exitAreas.find(e => { + return (!((x < e.x) || (y < e.y) || (x >= e.x + e.width) || (y >= e.y + e.height))); + }); + if (isExitTile) { + let isThisExit = template.oldExits.find(e => { + let ex = room.x + (e.x - template.x); + let ey = room.y + (e.y - template.y); + return (!((x < ex) || (y < ey) || (x >= ex + e.width) || (y >= ey + e.height))); + }); + if (isThisExit) { + map[x][y] = scope.randomizeTile(floorTile); + collisionMap[x][y] = false; + } else + collisionMap[x][y] = true; + } + } + } else if (didCollide) { + collisionMap[x][y] = false; + map[x][y] = scope.randomizeTile(floorTile); + } + } + } + + template.oldExits.forEach(e => { + exitAreas.push({ + x: room.x + (e.x - template.x), + y: room.y + (e.y - template.y), + width: e.width, + height: e.height + }); + }); + + room.connections.forEach(c => drawRoom(scope, instance, c)); +}; + +module.exports = drawRoom; diff --git a/src/server/world/randomMap/generateMappings.js b/src/server/world/randomMap/generateMappings.js new file mode 100644 index 00000000..0a2b48ae --- /dev/null +++ b/src/server/world/randomMap/generateMappings.js @@ -0,0 +1,36 @@ +module.exports = (scope, map) => { + const { templates } = scope; + scope.tileMappings = {}; + + let oldMap = map.oldMap; + + templates + .filter(r => r.properties.mapping) + .forEach(m => { + let x = m.x; + let y = m.y; + let w = m.width; + let h = m.height; + + let baseTile = oldMap[x][y]; + baseTile = (baseTile || '') + .replace('0,', '') + .replace(',', ''); + + let mapping = scope.tileMappings[baseTile] = []; + + for (let i = x + 2; i < x + w; i++) { + for (let j = y; j < y + h; j++) { + let oM = oldMap[i][j]; + + if (oM.replace) { + oM = oM + .replace('0,', '') + .replace(',', ''); + } + + mapping.push(oM); + } + } + }); +}; diff --git a/src/server/world/randomMap/isValidDungeon.js b/src/server/world/randomMap/isValidDungeon.js new file mode 100644 index 00000000..550f9131 --- /dev/null +++ b/src/server/world/randomMap/isValidDungeon.js @@ -0,0 +1,46 @@ +module.exports = generator => { + const { rooms, leafConstraints, endConstraints, templates } = generator; + const leafRooms = rooms.filter(r => !r.connections.length); + + //Ensure that we have enough leaf rooms + const { minCount: minLeafRooms, maxCount: maxLeafRooms } = leafConstraints; + + const leafRoomCount = leafRooms.length; + if (leafRoomCount < minLeafRooms || leafRoomCount > maxLeafRooms) + return false; + + //Ensure that the end room exists + const endRoom = rooms.find(r => r.template.properties.end); + + if (!endRoom) + return false; + + //Ensure that the end room is the correct distance + const { minDistance: minEndDistance, maxDistance: maxEndDistance } = endConstraints; + + const endDistance = endRoom.distance; + if (endDistance < minEndDistance || endDistance > maxEndDistance) + return false; + + //Ensure that leaf rooms are correct distances + const { minDistance: minLeafDistance, maxDistance: maxLeafDistance } = leafConstraints; + + const leafRoomsDistanceOk = !leafRooms.some(({ distance: roomDistance }) => { + return (roomDistance < minLeafDistance || roomDistance > maxLeafDistance); + }); + + if (!leafRoomsDistanceOk) + return false; + + //Ensure that enough minOccur templates have been included + const minOccurOk = templates.every(t => { + const minOccur = ~~t.properties.minOccur || 0; + const occurs = rooms.filter(r => r.template.typeId === t.typeId).length; + return occurs >= minOccur; + }); + + if (!minOccurOk) + return false; + + return true; +}; diff --git a/src/server/world/randomMap/randomMap.js b/src/server/world/randomMap/randomMap.js new file mode 100644 index 00000000..00e76155 --- /dev/null +++ b/src/server/world/randomMap/randomMap.js @@ -0,0 +1,126 @@ +const generateMappings = require('./generateMappings'); +const isValidDungeon = require('./isValidDungeon'); +const setupTemplates = require('./setupTemplates'); +const buildRoom = require('./buildRoom'); +const buildMap = require('./buildMap'); + +module.exports = { + instance: null, + + templates: null, + tileMappings: null, + rooms: [], + exitAreas: [], + + leafConstraints: { + minDistance: 4, + maxDistance: 12, + minCount: 3, + maxCount: 7 + }, + + endConstraints: { + minDistance: 10, + maxDistance: 12 + }, + + bounds: [0, 0, 0, 0], + + init: function (instance) { + this.instance = instance; + + const { map } = instance; + + this.loadMapProperties(map.mapFile.properties); + + this.templates = extend([], map.rooms); + setupTemplates(this, map); + generateMappings(this, map); + }, + + generate: function () { + const { instance } = this; + + const { map } = instance; + map.clientMap.hiddenRooms = []; + + this.rooms = []; + this.exitAreas = []; + this.bounds = [0, 0, 0, 0]; + + const hasEndRoom = this.templates.some(t => t.properties.end); + if (!hasEndRoom) { + /* eslint-disable-next-line no-console */ + console.log(`Random map has no end room defined: ${map.name}`); + + return; + } + + let startTemplate = this.templates.filter(t => t.properties.start); + startTemplate = startTemplate[this.randInt(0, startTemplate.length)]; + + let startRoom = buildRoom(this, startTemplate); + + if (!isValidDungeon(this)) + this.generate(instance); + else { + this.offsetRooms(startRoom); + buildMap(this, instance, startRoom); + } + + //To spawn in another room + /*const spawnRoom = this.rooms.find(t => t.template.properties.end); + map.spawn = [{ + x: spawnRoom.x + ~~(spawnRoom.template.width / 2) - 2, + y: spawnRoom.y + ~~(spawnRoom.template.height / 2) + 6 + }];*/ + }, + + loadMapProperties: function ({ leafConstraints, endConstraints }) { + if (leafConstraints) + this.leafConstraints = JSON.parse(leafConstraints); + + if (endConstraints) + this.endConstraints = JSON.parse(endConstraints); + }, + + randomizeTile: function (tile, floorTile) { + let mapping = this.tileMappings[tile]; + if (!mapping) + return tile; + + tile = mapping[this.randInt(0, mapping.length)]; + if (!tile) + return 0; + + return tile; + }, + + offsetRooms: function (room) { + let bounds = this.bounds; + let dx = (this.bounds[0] < 0) ? -bounds[0] : 0; + let dy = (this.bounds[1] < 0) ? -bounds[1] : 0; + + this.performOffset(room, dx, dy); + + this.bounds = [bounds[0] + dx, bounds[1] + dy, bounds[2] + dx, bounds[3] + dy]; + }, + + performOffset: function (room, dx, dy) { + room.x += dx; + room.y += dy; + + room.connections.forEach(c => this.performOffset(c, dx, dy)); + }, + + updateBounds: function (room) { + this.bounds[0] = Math.min(this.bounds[0], room.x); + this.bounds[1] = Math.min(this.bounds[1], room.y); + this.bounds[2] = Math.max(this.bounds[2], room.x + room.template.width); + this.bounds[3] = Math.max(this.bounds[3], room.y + room.template.height); + }, + + randInt: function (min, max) { + return ~~(Math.random() * (max - min)) + min; + } +}; diff --git a/src/server/world/randomMap/setupConnection.js b/src/server/world/randomMap/setupConnection.js new file mode 100644 index 00000000..508fffde --- /dev/null +++ b/src/server/world/randomMap/setupConnection.js @@ -0,0 +1,75 @@ +module.exports = (scope, fromRoom, isHallway, buildRoom) => { + const { templates, rooms, leafConstraints, randInt } = scope; + + if (!fromRoom.template.exits.length) + return true; + + let fromExit = fromRoom.template.exits.splice(randInt(0, fromRoom.template.exits.length), 1)[0]; + let exitDirection = JSON.parse(fromExit.properties.exit); + let allowedTemplates = templates.filter(t => { + if ( + (t.properties.mapping) || + (!!t.properties.hallway !== isHallway) || + (t.properties.start) || + ( + (t.properties.end) && + (fromRoom.distance + 1 !== leafConstraints.maxDistance) + ) + ) + return false; + + let isValid = t.exits.some(e => { + let direction = JSON.parse(e.properties.exit); + return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1])); + }); + + if ((isValid) && (t.properties.maxOccur)) { + let occurs = rooms.some(r => (r.template.typeId === t.typeId)); + if (occurs >= ~~t.properties.maxOccur) + isValid = false; + } + + if ((isValid) && (fromRoom.distance + 1 === leafConstraints.maxDistance)) { + //If there is an exit available, rather use that + if (!t.properties.end) { + let endsAvailable = templates.some(tt => { + if (!tt.properties.end) + return false; + else if (!~~tt.properties.maxOccur) + return true; + else if (rooms.filter(r => r.template.typeId === tt.typeId).length < ~~tt.properties.maxOccur) + return true; + }); + + if (endsAvailable) + isValid = false; + } + } + + return isValid; + }); + + if (allowedTemplates.length === 0) { + fromRoom.template.exits.push(fromExit); + return false; + } + + let template = extend({}, allowedTemplates[randInt(0, allowedTemplates.length)]); + + let templateExit = template.exits.filter(e => { + let direction = JSON.parse(e.properties.exit); + return ((direction[0] === -exitDirection[0]) && (direction[1] === -exitDirection[1])); + }); + templateExit = templateExit[randInt(0, templateExit.length)]; + let exitIndex = template.exits.findIndex(e => e === templateExit); + + template.exits.splice(exitIndex, 1); + + let success = buildRoom(scope, template, fromRoom, templateExit, fromExit, isHallway); + if (!success) { + fromRoom.template.exits.push(fromExit); + return false; + } + + return true; +}; diff --git a/src/server/world/randomMap/setupTemplates.js b/src/server/world/randomMap/setupTemplates.js new file mode 100644 index 00000000..b43d273b --- /dev/null +++ b/src/server/world/randomMap/setupTemplates.js @@ -0,0 +1,201 @@ +module.exports = (scope, map) => { + const { templates } = scope; + + templates.forEach((r, typeId) => { + if (r.properties.mapping) + return; + + r.typeId = typeId; + + let { noRotate = false, canFlipX = true, canFlipY = true } = r.properties; + //Property values are strings. So we turn '1' and '0' into 1 and 0 + canFlipX = ~~canFlipX; + canFlipY = ~~canFlipY; + + //Fix Polygons + r.objects.forEach(o => { + if (!o.fog) + return; + + const newArea = o.area.map(p => { + const [ px, py ] = p; + + const hpx = px - r.x; + const hpy = py - r.y; + + return [hpx, hpy]; + }); + + Object.assign(o, { + x: o.x - r.x, + y: o.y - r.y, + area: newArea + }); + }); + + //FlipX Loop + for (let i = 0; i < 2; i++) { + if (i && !canFlipX) + continue; + + //FlipY Loop + for (let j = 0; j < 2; j++) { + if (j && !canFlipY) + continue; + + //Rotate Loop + for (let k = 0; k < 2; k++) { + if (k && noRotate) + continue; + + if (i + j + k === 0) + continue; + + let flipped = extend({ + flipX: !!i, + flipY: !!j, + rotate: !!k + }, r); + + flipped.exits.forEach(e => { + let direction = JSON.parse(e.properties.exit); + + if (flipped.flipX) { + direction[0] *= -1; + e.x = r.x + r.width - (e.x - r.x) - e.width; + } + if (flipped.flipY) { + direction[1] *= -1; + e.y = r.y + r.height - (e.y - r.y) - e.height; + } + if (flipped.rotate) { + direction = [direction[1], direction[0]]; + let t = e.x; + e.x = r.x + (e.y - r.y); + e.y = r.y + (t - r.x); + t = e.width; + e.width = e.height; + e.height = t; + } + + e.properties.exit = JSON.stringify(direction); + }); + + flipped.objects.forEach(o => { + if (!o.fog) { + if (flipped.flipX) + o.x = r.x + r.width - (o.x - r.x) - 1; + if (flipped.flipY) + o.y = r.y + r.height - (o.y - r.y) - 1; + if (flipped.rotate) { + let t = o.x; + o.x = r.x + (o.y - r.y); + o.y = r.y + (t - r.x); + } + } else { + if (flipped.flipX) { + const newArea = o.area.map(p => { + const [ px, py ] = p; + + const hpx = r.width - px; + + return [hpx, py]; + }); + + Object.assign(o, { + area: newArea + }); + } + if (flipped.flipY) { + const newArea = o.area.map(p => { + const [ px, py ] = p; + + const hpy = r.height - py; + + return [px, hpy]; + }); + + Object.assign(o, { + area: newArea + }); + } + if (flipped.rotate) { + const newArea = o.area.map(p => { + const [ px, py ] = p; + + const t = px; + const hpx = py; + const hpy = t; + + return [hpx, hpy]; + }); + + Object.assign(o, { + area: newArea + }); + } + + //Fix polygon bounds + let lowX = r.width; + let lowY = r.height; + let highX = 0; + let highY = 0; + + o.area.forEach(p => { + const [ px, py ] = p; + + if (px < lowX) + lowX = px; + if (px > highX) + highX = px; + + if (py < lowY) + lowY = py; + if (py > highY) + highY = py; + }); + + o.x = lowX; + o.y = lowY; + o.width = highX - lowX; + o.height = highY - lowY; + } + }); + + if (flipped.rotate) { + let t = flipped.width; + flipped.width = flipped.height; + flipped.height = t; + } + + templates.push(flipped); + } + } + } + }); + + templates.forEach(r => { + let rotate = r.rotate; + let w = rotate ? r.height : r.width; + let h = rotate ? r.width : r.height; + + r.map = _.get2dArray(r.width, r.height); + r.tiles = _.get2dArray(r.width, r.height); + r.collisionMap = _.get2dArray(r.width, r.height); + r.oldExits = extend([], r.exits); + + for (let i = 0; i < w; i++) { + for (let j = 0; j < h; j++) { + let ii = rotate ? j : i; + let jj = rotate ? i : j; + + let x = r.flipX ? (r.x + w - i - 1) : (r.x + i); + let y = r.flipY ? (r.y + h - j - 1) : (r.y + j); + + r.map[ii][jj] = map.oldMap[x][y]; + r.tiles[ii][jj] = map.oldLayers.tiles[x][y]; + r.collisionMap[ii][jj] = map.oldCollisionMap[x][y]; + } + } + }); +}; diff --git a/src/server/world/randomMap/spawnObjects.js b/src/server/world/randomMap/spawnObjects.js new file mode 100644 index 00000000..bc454b94 --- /dev/null +++ b/src/server/world/randomMap/spawnObjects.js @@ -0,0 +1,29 @@ +const spawnObjects = (scope, instance, room) => { + let template = room.template; + let spawners = instance.spawners; + let spawnCd = instance.map.mapFile.properties.spawnCd; + + template.objects.forEach(o => { + if (!o.fog) { + o.x = o.x - template.x + room.x; + o.y = o.y - template.y + room.y; + + spawners.register(o, spawnCd); + } else { + o.x += room.x; + o.y += room.y; + + o.area = o.area.map(p => { + const [px, py] = p; + + return [px + room.x, py + room.y]; + }); + + instance.map.clientMap.hiddenRooms.push(o); + } + }); + + room.connections.forEach(c => spawnObjects(scope, instance, c)); +}; + +module.exports = spawnObjects;