Parcourir la source

refactored randomMap generator

tags/v0.8.0
Shaun Kichenbrand il y a 3 ans
Parent
révision
0010c63152
13 fichiers modifiés avec 703 ajouts et 677 suppressions
  1. +2
    -5
      src/server/world/instancer.js
  2. +3
    -2
      src/server/world/map.js
  3. +0
    -670
      src/server/world/randomMap.js
  4. +27
    -0
      src/server/world/randomMap/buildMap.js
  5. +52
    -0
      src/server/world/randomMap/buildRoom.js
  6. +20
    -0
      src/server/world/randomMap/doesCollide.js
  7. +86
    -0
      src/server/world/randomMap/drawRoom.js
  8. +36
    -0
      src/server/world/randomMap/generateMappings.js
  9. +46
    -0
      src/server/world/randomMap/isValidDungeon.js
  10. +126
    -0
      src/server/world/randomMap/randomMap.js
  11. +75
    -0
      src/server/world/randomMap/setupConnection.js
  12. +201
    -0
      src/server/world/randomMap/setupTemplates.js
  13. +29
    -0
      src/server/world/randomMap/spawnObjects.js

+ 2
- 5
src/server/world/instancer.js Voir le fichier

@@ -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
- 2
src/server/world/map.js Voir le fichier

@@ -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++) {


+ 0
- 670
src/server/world/randomMap.js Voir le fichier

@@ -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;
}
};

+ 27
- 0
src/server/world/randomMap/buildMap.js Voir le fichier

@@ -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);
};

+ 52
- 0
src/server/world/randomMap/buildRoom.js Voir le fichier

@@ -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;

+ 20
- 0
src/server/world/randomMap/doesCollide.js Voir le fichier

@@ -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;
};

+ 86
- 0
src/server/world/randomMap/drawRoom.js Voir le fichier

@@ -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;

+ 36
- 0
src/server/world/randomMap/generateMappings.js Voir le fichier

@@ -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);
}
}
});
};

+ 46
- 0
src/server/world/randomMap/isValidDungeon.js Voir le fichier

@@ -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;
};

+ 126
- 0
src/server/world/randomMap/randomMap.js Voir le fichier

@@ -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;
}
};

+ 75
- 0
src/server/world/randomMap/setupConnection.js Voir le fichier

@@ -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;
};

+ 201
- 0
src/server/world/randomMap/setupTemplates.js Voir le fichier

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

+ 29
- 0
src/server/world/randomMap/spawnObjects.js Voir le fichier

@@ -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;

Chargement…
Annuler
Enregistrer