rot-js
Version:
A roguelike toolkit in JavaScript
319 lines (318 loc) • 10.1 kB
JavaScript
import RNG from "../rng.js";
;
/**
* @class Dungeon feature; has own .create() method
*/
class Feature {
}
/**
* @class Room
* @augments ROT.Map.Feature
* @param {int} x1
* @param {int} y1
* @param {int} x2
* @param {int} y2
* @param {int} [doorX]
* @param {int} [doorY]
*/
export class Room extends Feature {
constructor(x1, y1, x2, y2, doorX, doorY) {
super();
this._x1 = x1;
this._y1 = y1;
this._x2 = x2;
this._y2 = y2;
this._doors = {};
if (doorX !== undefined && doorY !== undefined) {
this.addDoor(doorX, doorY);
}
}
;
/**
* Room of random size, with a given doors and direction
*/
static createRandomAt(x, y, dx, dy, options) {
let min = options.roomWidth[0];
let max = options.roomWidth[1];
let width = RNG.getUniformInt(min, max);
min = options.roomHeight[0];
max = options.roomHeight[1];
let height = RNG.getUniformInt(min, max);
if (dx == 1) { /* to the right */
let y2 = y - Math.floor(RNG.getUniform() * height);
return new this(x + 1, y2, x + width, y2 + height - 1, x, y);
}
if (dx == -1) { /* to the left */
let y2 = y - Math.floor(RNG.getUniform() * height);
return new this(x - width, y2, x - 1, y2 + height - 1, x, y);
}
if (dy == 1) { /* to the bottom */
let x2 = x - Math.floor(RNG.getUniform() * width);
return new this(x2, y + 1, x2 + width - 1, y + height, x, y);
}
if (dy == -1) { /* to the top */
let x2 = x - Math.floor(RNG.getUniform() * width);
return new this(x2, y - height, x2 + width - 1, y - 1, x, y);
}
throw new Error("dx or dy must be 1 or -1");
}
/**
* Room of random size, positioned around center coords
*/
static createRandomCenter(cx, cy, options) {
let min = options.roomWidth[0];
let max = options.roomWidth[1];
let width = RNG.getUniformInt(min, max);
min = options.roomHeight[0];
max = options.roomHeight[1];
let height = RNG.getUniformInt(min, max);
let x1 = cx - Math.floor(RNG.getUniform() * width);
let y1 = cy - Math.floor(RNG.getUniform() * height);
let x2 = x1 + width - 1;
let y2 = y1 + height - 1;
return new this(x1, y1, x2, y2);
}
/**
* Room of random size within a given dimensions
*/
static createRandom(availWidth, availHeight, options) {
let min = options.roomWidth[0];
let max = options.roomWidth[1];
let width = RNG.getUniformInt(min, max);
min = options.roomHeight[0];
max = options.roomHeight[1];
let height = RNG.getUniformInt(min, max);
let left = availWidth - width - 1;
let top = availHeight - height - 1;
let x1 = 1 + Math.floor(RNG.getUniform() * left);
let y1 = 1 + Math.floor(RNG.getUniform() * top);
let x2 = x1 + width - 1;
let y2 = y1 + height - 1;
return new this(x1, y1, x2, y2);
}
addDoor(x, y) {
this._doors[x + "," + y] = 1;
return this;
}
/**
* @param {function}
*/
getDoors(cb) {
for (let key in this._doors) {
let parts = key.split(",");
cb(parseInt(parts[0]), parseInt(parts[1]));
}
return this;
}
clearDoors() {
this._doors = {};
return this;
}
addDoors(isWallCallback) {
let left = this._x1 - 1;
let right = this._x2 + 1;
let top = this._y1 - 1;
let bottom = this._y2 + 1;
for (let x = left; x <= right; x++) {
for (let y = top; y <= bottom; y++) {
if (x != left && x != right && y != top && y != bottom) {
continue;
}
if (isWallCallback(x, y)) {
continue;
}
this.addDoor(x, y);
}
}
return this;
}
debug() {
console.log("room", this._x1, this._y1, this._x2, this._y2);
}
isValid(isWallCallback, canBeDugCallback) {
let left = this._x1 - 1;
let right = this._x2 + 1;
let top = this._y1 - 1;
let bottom = this._y2 + 1;
for (let x = left; x <= right; x++) {
for (let y = top; y <= bottom; y++) {
if (x == left || x == right || y == top || y == bottom) {
if (!isWallCallback(x, y)) {
return false;
}
}
else {
if (!canBeDugCallback(x, y)) {
return false;
}
}
}
}
return true;
}
/**
* @param {function} digCallback Dig callback with a signature (x, y, value). Values: 0 = empty, 1 = wall, 2 = door. Multiple doors are allowed.
*/
create(digCallback) {
let left = this._x1 - 1;
let right = this._x2 + 1;
let top = this._y1 - 1;
let bottom = this._y2 + 1;
let value = 0;
for (let x = left; x <= right; x++) {
for (let y = top; y <= bottom; y++) {
if (x + "," + y in this._doors) {
value = 2;
}
else if (x == left || x == right || y == top || y == bottom) {
value = 1;
}
else {
value = 0;
}
digCallback(x, y, value);
}
}
}
getCenter() {
return [Math.round((this._x1 + this._x2) / 2), Math.round((this._y1 + this._y2) / 2)];
}
getLeft() { return this._x1; }
getRight() { return this._x2; }
getTop() { return this._y1; }
getBottom() { return this._y2; }
}
/**
* @class Corridor
* @augments ROT.Map.Feature
* @param {int} startX
* @param {int} startY
* @param {int} endX
* @param {int} endY
*/
export class Corridor extends Feature {
constructor(startX, startY, endX, endY) {
super();
this._startX = startX;
this._startY = startY;
this._endX = endX;
this._endY = endY;
this._endsWithAWall = true;
}
static createRandomAt(x, y, dx, dy, options) {
let min = options.corridorLength[0];
let max = options.corridorLength[1];
let length = RNG.getUniformInt(min, max);
return new this(x, y, x + dx * length, y + dy * length);
}
debug() {
console.log("corridor", this._startX, this._startY, this._endX, this._endY);
}
isValid(isWallCallback, canBeDugCallback) {
let sx = this._startX;
let sy = this._startY;
let dx = this._endX - sx;
let dy = this._endY - sy;
let length = 1 + Math.max(Math.abs(dx), Math.abs(dy));
if (dx) {
dx = dx / Math.abs(dx);
}
if (dy) {
dy = dy / Math.abs(dy);
}
let nx = dy;
let ny = -dx;
let ok = true;
for (let i = 0; i < length; i++) {
let x = sx + i * dx;
let y = sy + i * dy;
if (!canBeDugCallback(x, y)) {
ok = false;
}
if (!isWallCallback(x + nx, y + ny)) {
ok = false;
}
if (!isWallCallback(x - nx, y - ny)) {
ok = false;
}
if (!ok) {
length = i;
this._endX = x - dx;
this._endY = y - dy;
break;
}
}
/**
* If the length degenerated, this corridor might be invalid
*/
/* not supported */
if (length == 0) {
return false;
}
/* length 1 allowed only if the next space is empty */
if (length == 1 && isWallCallback(this._endX + dx, this._endY + dy)) {
return false;
}
/**
* We do not want the corridor to crash into a corner of a room;
* if any of the ending corners is empty, the N+1th cell of this corridor must be empty too.
*
* Situation:
* #######1
* .......?
* #######2
*
* The corridor was dug from left to right.
* 1, 2 - problematic corners, ? = N+1th cell (not dug)
*/
let firstCornerBad = !isWallCallback(this._endX + dx + nx, this._endY + dy + ny);
let secondCornerBad = !isWallCallback(this._endX + dx - nx, this._endY + dy - ny);
this._endsWithAWall = isWallCallback(this._endX + dx, this._endY + dy);
if ((firstCornerBad || secondCornerBad) && this._endsWithAWall) {
return false;
}
return true;
}
/**
* @param {function} digCallback Dig callback with a signature (x, y, value). Values: 0 = empty.
*/
create(digCallback) {
let sx = this._startX;
let sy = this._startY;
let dx = this._endX - sx;
let dy = this._endY - sy;
let length = 1 + Math.max(Math.abs(dx), Math.abs(dy));
if (dx) {
dx = dx / Math.abs(dx);
}
if (dy) {
dy = dy / Math.abs(dy);
}
for (let i = 0; i < length; i++) {
let x = sx + i * dx;
let y = sy + i * dy;
digCallback(x, y, 0);
}
return true;
}
createPriorityWalls(priorityWallCallback) {
if (!this._endsWithAWall) {
return;
}
let sx = this._startX;
let sy = this._startY;
let dx = this._endX - sx;
let dy = this._endY - sy;
if (dx) {
dx = dx / Math.abs(dx);
}
if (dy) {
dy = dy / Math.abs(dy);
}
let nx = dy;
let ny = -dx;
priorityWallCallback(this._endX + dx, this._endY + dy);
priorityWallCallback(this._endX + nx, this._endY + ny);
priorityWallCallback(this._endX - nx, this._endY - ny);
}
}