@@ -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(); | |||
}; | |||
@@ -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++) { | |||
@@ -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; | |||
} | |||
}; |
@@ -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); | |||
}; |
@@ -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; |
@@ -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; | |||
}; |
@@ -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; |
@@ -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); | |||
} | |||
} | |||
}); | |||
}; |
@@ -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; | |||
}; |
@@ -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; | |||
} | |||
}; |
@@ -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; | |||
}; |
@@ -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]; | |||
} | |||
} | |||
}); | |||
}; |
@@ -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; |