mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 14:51:26 +02:00
7229 lines
176 KiB
JavaScript
7229 lines
176 KiB
JavaScript
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
|
|
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : factory(global.ROT = {});
|
|
})(this, function (exports) {
|
|
'use strict';
|
|
/**
|
|
* This code is an implementation of Alea algorithm; (C) 2010 Johannes Baagøe.
|
|
* Alea is licensed according to the http://en.wikipedia.org/wiki/MIT_License.
|
|
*/
|
|
|
|
var FRAC = 2.3283064365386963e-10;
|
|
/* 2^-32 */
|
|
|
|
var RNG =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function RNG() {
|
|
this._seed = 0;
|
|
this._s0 = 0;
|
|
this._s1 = 0;
|
|
this._s2 = 0;
|
|
this._c = 0;
|
|
}
|
|
|
|
var _proto = RNG.prototype;
|
|
|
|
_proto.getSeed = function getSeed() {
|
|
return this._seed;
|
|
};
|
|
/**
|
|
* Seed the number generator
|
|
*/
|
|
|
|
|
|
_proto.setSeed = function setSeed(seed) {
|
|
seed = seed < 1 ? 1 / seed : seed;
|
|
this._seed = seed;
|
|
this._s0 = (seed >>> 0) * FRAC;
|
|
seed = seed * 69069 + 1 >>> 0;
|
|
this._s1 = seed * FRAC;
|
|
seed = seed * 69069 + 1 >>> 0;
|
|
this._s2 = seed * FRAC;
|
|
this._c = 1;
|
|
return this;
|
|
};
|
|
/**
|
|
* @returns Pseudorandom value [0,1), uniformly distributed
|
|
*/
|
|
|
|
|
|
_proto.getUniform = function getUniform() {
|
|
var t = 2091639 * this._s0 + this._c * FRAC;
|
|
this._s0 = this._s1;
|
|
this._s1 = this._s2;
|
|
this._c = t | 0;
|
|
this._s2 = t - this._c;
|
|
return this._s2;
|
|
};
|
|
/**
|
|
* @param lowerBound The lower end of the range to return a value from, inclusive
|
|
* @param upperBound The upper end of the range to return a value from, inclusive
|
|
* @returns Pseudorandom value [lowerBound, upperBound], using ROT.RNG.getUniform() to distribute the value
|
|
*/
|
|
|
|
|
|
_proto.getUniformInt = function getUniformInt(lowerBound, upperBound) {
|
|
var max = Math.max(lowerBound, upperBound);
|
|
var min = Math.min(lowerBound, upperBound);
|
|
return Math.floor(this.getUniform() * (max - min + 1)) + min;
|
|
};
|
|
/**
|
|
* @param mean Mean value
|
|
* @param stddev Standard deviation. ~95% of the absolute values will be lower than 2*stddev.
|
|
* @returns A normally distributed pseudorandom value
|
|
*/
|
|
|
|
|
|
_proto.getNormal = function getNormal(mean, stddev) {
|
|
if (mean === void 0) {
|
|
mean = 0;
|
|
}
|
|
|
|
if (stddev === void 0) {
|
|
stddev = 1;
|
|
}
|
|
|
|
var u, v, r;
|
|
|
|
do {
|
|
u = 2 * this.getUniform() - 1;
|
|
v = 2 * this.getUniform() - 1;
|
|
r = u * u + v * v;
|
|
} while (r > 1 || r == 0);
|
|
|
|
var gauss = u * Math.sqrt(-2 * Math.log(r) / r);
|
|
return mean + gauss * stddev;
|
|
};
|
|
/**
|
|
* @returns Pseudorandom value [1,100] inclusive, uniformly distributed
|
|
*/
|
|
|
|
|
|
_proto.getPercentage = function getPercentage() {
|
|
return 1 + Math.floor(this.getUniform() * 100);
|
|
};
|
|
/**
|
|
* @returns Randomly picked item, null when length=0
|
|
*/
|
|
|
|
|
|
_proto.getItem = function getItem(array) {
|
|
if (!array.length) {
|
|
return null;
|
|
}
|
|
|
|
return array[Math.floor(this.getUniform() * array.length)];
|
|
};
|
|
/**
|
|
* @returns New array with randomized items
|
|
*/
|
|
|
|
|
|
_proto.shuffle = function shuffle(array) {
|
|
var result = [];
|
|
var clone = array.slice();
|
|
|
|
while (clone.length) {
|
|
var _index = clone.indexOf(this.getItem(clone));
|
|
|
|
result.push(clone.splice(_index, 1)[0]);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
/**
|
|
* @param data key=whatever, value=weight (relative probability)
|
|
* @returns whatever
|
|
*/
|
|
|
|
|
|
_proto.getWeightedValue = function getWeightedValue(data) {
|
|
var total = 0;
|
|
|
|
for (var _id in data) {
|
|
total += data[_id];
|
|
}
|
|
|
|
var random = this.getUniform() * total;
|
|
var id,
|
|
part = 0;
|
|
|
|
for (id in data) {
|
|
part += data[id];
|
|
|
|
if (random < part) {
|
|
return id;
|
|
}
|
|
} // If by some floating-point annoyance we have
|
|
// random >= total, just return the last id.
|
|
|
|
|
|
return id;
|
|
};
|
|
/**
|
|
* Get RNG state. Useful for storing the state and re-setting it via setState.
|
|
* @returns Internal state
|
|
*/
|
|
|
|
|
|
_proto.getState = function getState() {
|
|
return [this._s0, this._s1, this._s2, this._c];
|
|
};
|
|
/**
|
|
* Set a previously retrieved state.
|
|
*/
|
|
|
|
|
|
_proto.setState = function setState(state) {
|
|
this._s0 = state[0];
|
|
this._s1 = state[1];
|
|
this._s2 = state[2];
|
|
this._c = state[3];
|
|
return this;
|
|
};
|
|
/**
|
|
* Returns a cloned RNG
|
|
*/
|
|
|
|
|
|
_proto.clone = function clone() {
|
|
var clone = new RNG();
|
|
return clone.setState(this.getState());
|
|
};
|
|
|
|
return RNG;
|
|
}();
|
|
|
|
var RNG$1 = new RNG().setSeed(Date.now());
|
|
/**
|
|
* @class Abstract display backend module
|
|
* @private
|
|
*/
|
|
|
|
var Backend =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Backend() {}
|
|
|
|
var _proto2 = Backend.prototype;
|
|
|
|
_proto2.getContainer = function getContainer() {
|
|
return null;
|
|
};
|
|
|
|
_proto2.setOptions = function setOptions(options) {
|
|
this._options = options;
|
|
};
|
|
|
|
return Backend;
|
|
}();
|
|
|
|
var Canvas =
|
|
/*#__PURE__*/
|
|
function (_Backend) {
|
|
_inheritsLoose(Canvas, _Backend);
|
|
|
|
function Canvas() {
|
|
var _this;
|
|
|
|
_this = _Backend.call(this) || this;
|
|
_this._ctx = document.createElement("canvas").getContext("2d");
|
|
return _this;
|
|
}
|
|
|
|
var _proto3 = Canvas.prototype;
|
|
|
|
_proto3.schedule = function schedule(cb) {
|
|
requestAnimationFrame(cb);
|
|
};
|
|
|
|
_proto3.getContainer = function getContainer() {
|
|
return this._ctx.canvas;
|
|
};
|
|
|
|
_proto3.setOptions = function setOptions(opts) {
|
|
_Backend.prototype.setOptions.call(this, opts);
|
|
|
|
var style = opts.fontStyle ? opts.fontStyle + " " : "";
|
|
var font = style + " " + opts.fontSize + "px " + opts.fontFamily;
|
|
this._ctx.font = font;
|
|
|
|
this._updateSize();
|
|
|
|
this._ctx.font = font;
|
|
this._ctx.textAlign = "center";
|
|
this._ctx.textBaseline = "middle";
|
|
};
|
|
|
|
_proto3.clear = function clear() {
|
|
this._ctx.fillStyle = this._options.bg;
|
|
|
|
this._ctx.fillRect(0, 0, this._ctx.canvas.width, this._ctx.canvas.height);
|
|
};
|
|
|
|
_proto3.eventToPosition = function eventToPosition(x, y) {
|
|
var canvas = this._ctx.canvas;
|
|
var rect = canvas.getBoundingClientRect();
|
|
x -= rect.left;
|
|
y -= rect.top;
|
|
x *= canvas.width / rect.width;
|
|
y *= canvas.height / rect.height;
|
|
|
|
if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
|
|
return [-1, -1];
|
|
}
|
|
|
|
return this._normalizedEventToPosition(x, y);
|
|
};
|
|
|
|
return Canvas;
|
|
}(Backend);
|
|
/**
|
|
* Always positive modulus
|
|
* @param x Operand
|
|
* @param n Modulus
|
|
* @returns x modulo n
|
|
*/
|
|
|
|
|
|
function mod(x, n) {
|
|
return (x % n + n) % n;
|
|
}
|
|
|
|
function clamp(val, min, max) {
|
|
if (min === void 0) {
|
|
min = 0;
|
|
}
|
|
|
|
if (max === void 0) {
|
|
max = 1;
|
|
}
|
|
|
|
if (val < min) return min;
|
|
if (val > max) return max;
|
|
return val;
|
|
}
|
|
|
|
function capitalize(string) {
|
|
return string.charAt(0).toUpperCase() + string.substring(1);
|
|
}
|
|
/**
|
|
* Format a string in a flexible way. Scans for %s strings and replaces them with arguments. List of patterns is modifiable via String.format.map.
|
|
* @param {string} template
|
|
* @param {any} [argv]
|
|
*/
|
|
|
|
|
|
function format(template) {
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
args[_key - 1] = arguments[_key];
|
|
}
|
|
|
|
var map = format.map;
|
|
|
|
var replacer = function replacer(match, group1, group2, index) {
|
|
if (template.charAt(index - 1) == "%") {
|
|
return match.substring(1);
|
|
}
|
|
|
|
if (!args.length) {
|
|
return match;
|
|
}
|
|
|
|
var obj = args[0];
|
|
var group = group1 || group2;
|
|
var parts = group.split(",");
|
|
var name = parts.shift() || "";
|
|
var method = map[name.toLowerCase()];
|
|
|
|
if (!method) {
|
|
return match;
|
|
}
|
|
|
|
obj = args.shift();
|
|
var replaced = obj[method].apply(obj, parts);
|
|
var first = name.charAt(0);
|
|
|
|
if (first != first.toLowerCase()) {
|
|
replaced = capitalize(replaced);
|
|
}
|
|
|
|
return replaced;
|
|
};
|
|
|
|
return template.replace(/%(?:([a-z]+)|(?:{([^}]+)}))/gi, replacer);
|
|
}
|
|
|
|
format.map = {
|
|
"s": "toString"
|
|
};
|
|
var util =
|
|
/*#__PURE__*/
|
|
Object.freeze({
|
|
mod: mod,
|
|
clamp: clamp,
|
|
capitalize: capitalize,
|
|
format: format
|
|
});
|
|
/**
|
|
* @class Hexagonal backend
|
|
* @private
|
|
*/
|
|
|
|
var Hex =
|
|
/*#__PURE__*/
|
|
function (_Canvas) {
|
|
_inheritsLoose(Hex, _Canvas);
|
|
|
|
function Hex() {
|
|
var _this2;
|
|
|
|
_this2 = _Canvas.call(this) || this;
|
|
_this2._spacingX = 0;
|
|
_this2._spacingY = 0;
|
|
_this2._hexSize = 0;
|
|
return _this2;
|
|
}
|
|
|
|
var _proto4 = Hex.prototype;
|
|
|
|
_proto4.draw = function draw(data, clearBefore) {
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4];
|
|
var px = [(x + 1) * this._spacingX, y * this._spacingY + this._hexSize];
|
|
|
|
if (this._options.transpose) {
|
|
px.reverse();
|
|
}
|
|
|
|
if (clearBefore) {
|
|
this._ctx.fillStyle = bg;
|
|
|
|
this._fill(px[0], px[1]);
|
|
}
|
|
|
|
if (!ch) {
|
|
return;
|
|
}
|
|
|
|
this._ctx.fillStyle = fg;
|
|
var chars = [].concat(ch);
|
|
|
|
for (var i = 0; i < chars.length; i++) {
|
|
this._ctx.fillText(chars[i], px[0], Math.ceil(px[1]));
|
|
}
|
|
};
|
|
|
|
_proto4.computeSize = function computeSize(availWidth, availHeight) {
|
|
if (this._options.transpose) {
|
|
availWidth += availHeight;
|
|
availHeight = availWidth - availHeight;
|
|
availWidth -= availHeight;
|
|
}
|
|
|
|
var width = Math.floor(availWidth / this._spacingX) - 1;
|
|
var height = Math.floor((availHeight - 2 * this._hexSize) / this._spacingY + 1);
|
|
return [width, height];
|
|
};
|
|
|
|
_proto4.computeFontSize = function computeFontSize(availWidth, availHeight) {
|
|
if (this._options.transpose) {
|
|
availWidth += availHeight;
|
|
availHeight = availWidth - availHeight;
|
|
availWidth -= availHeight;
|
|
}
|
|
|
|
var hexSizeWidth = 2 * availWidth / ((this._options.width + 1) * Math.sqrt(3)) - 1;
|
|
var hexSizeHeight = availHeight / (2 + 1.5 * (this._options.height - 1));
|
|
var hexSize = Math.min(hexSizeWidth, hexSizeHeight); // compute char ratio
|
|
|
|
var oldFont = this._ctx.font;
|
|
this._ctx.font = "100px " + this._options.fontFamily;
|
|
var width = Math.ceil(this._ctx.measureText("W").width);
|
|
this._ctx.font = oldFont;
|
|
var ratio = width / 100;
|
|
hexSize = Math.floor(hexSize) + 1; // closest larger hexSize
|
|
// FIXME char size computation does not respect transposed hexes
|
|
|
|
var fontSize = 2 * hexSize / (this._options.spacing * (1 + ratio / Math.sqrt(3))); // closest smaller fontSize
|
|
|
|
return Math.ceil(fontSize) - 1;
|
|
};
|
|
|
|
_proto4._normalizedEventToPosition = function _normalizedEventToPosition(x, y) {
|
|
var nodeSize;
|
|
|
|
if (this._options.transpose) {
|
|
x += y;
|
|
y = x - y;
|
|
x -= y;
|
|
nodeSize = this._ctx.canvas.width;
|
|
} else {
|
|
nodeSize = this._ctx.canvas.height;
|
|
}
|
|
|
|
var size = nodeSize / this._options.height;
|
|
y = Math.floor(y / size);
|
|
|
|
if (mod(y, 2)) {
|
|
/* odd row */
|
|
x -= this._spacingX;
|
|
x = 1 + 2 * Math.floor(x / (2 * this._spacingX));
|
|
} else {
|
|
x = 2 * Math.floor(x / (2 * this._spacingX));
|
|
}
|
|
|
|
return [x, y];
|
|
};
|
|
/**
|
|
* Arguments are pixel values. If "transposed" mode is enabled, then these two are already swapped.
|
|
*/
|
|
|
|
|
|
_proto4._fill = function _fill(cx, cy) {
|
|
var a = this._hexSize;
|
|
var b = this._options.border;
|
|
var ctx = this._ctx;
|
|
ctx.beginPath();
|
|
|
|
if (this._options.transpose) {
|
|
ctx.moveTo(cx - a + b, cy);
|
|
ctx.lineTo(cx - a / 2 + b, cy + this._spacingX - b);
|
|
ctx.lineTo(cx + a / 2 - b, cy + this._spacingX - b);
|
|
ctx.lineTo(cx + a - b, cy);
|
|
ctx.lineTo(cx + a / 2 - b, cy - this._spacingX + b);
|
|
ctx.lineTo(cx - a / 2 + b, cy - this._spacingX + b);
|
|
ctx.lineTo(cx - a + b, cy);
|
|
} else {
|
|
ctx.moveTo(cx, cy - a + b);
|
|
ctx.lineTo(cx + this._spacingX - b, cy - a / 2 + b);
|
|
ctx.lineTo(cx + this._spacingX - b, cy + a / 2 - b);
|
|
ctx.lineTo(cx, cy + a - b);
|
|
ctx.lineTo(cx - this._spacingX + b, cy + a / 2 - b);
|
|
ctx.lineTo(cx - this._spacingX + b, cy - a / 2 + b);
|
|
ctx.lineTo(cx, cy - a + b);
|
|
}
|
|
|
|
ctx.fill();
|
|
};
|
|
|
|
_proto4._updateSize = function _updateSize() {
|
|
var opts = this._options;
|
|
var charWidth = Math.ceil(this._ctx.measureText("W").width);
|
|
this._hexSize = Math.floor(opts.spacing * (opts.fontSize + charWidth / Math.sqrt(3)) / 2);
|
|
this._spacingX = this._hexSize * Math.sqrt(3) / 2;
|
|
this._spacingY = this._hexSize * 1.5;
|
|
var xprop;
|
|
var yprop;
|
|
|
|
if (opts.transpose) {
|
|
xprop = "height";
|
|
yprop = "width";
|
|
} else {
|
|
xprop = "width";
|
|
yprop = "height";
|
|
}
|
|
|
|
this._ctx.canvas[xprop] = Math.ceil((opts.width + 1) * this._spacingX);
|
|
this._ctx.canvas[yprop] = Math.ceil((opts.height - 1) * this._spacingY + 2 * this._hexSize);
|
|
};
|
|
|
|
return Hex;
|
|
}(Canvas);
|
|
/**
|
|
* @class Rectangular backend
|
|
* @private
|
|
*/
|
|
|
|
|
|
var Rect =
|
|
/*#__PURE__*/
|
|
function (_Canvas2) {
|
|
_inheritsLoose(Rect, _Canvas2);
|
|
|
|
function Rect() {
|
|
var _this3;
|
|
|
|
_this3 = _Canvas2.call(this) || this;
|
|
_this3._spacingX = 0;
|
|
_this3._spacingY = 0;
|
|
_this3._canvasCache = {};
|
|
return _this3;
|
|
}
|
|
|
|
var _proto5 = Rect.prototype;
|
|
|
|
_proto5.setOptions = function setOptions(options) {
|
|
_Canvas2.prototype.setOptions.call(this, options);
|
|
|
|
this._canvasCache = {};
|
|
};
|
|
|
|
_proto5.draw = function draw(data, clearBefore) {
|
|
if (Rect.cache) {
|
|
this._drawWithCache(data);
|
|
} else {
|
|
this._drawNoCache(data, clearBefore);
|
|
}
|
|
};
|
|
|
|
_proto5._drawWithCache = function _drawWithCache(data) {
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4];
|
|
var hash = "" + ch + fg + bg;
|
|
var canvas;
|
|
|
|
if (hash in this._canvasCache) {
|
|
canvas = this._canvasCache[hash];
|
|
} else {
|
|
var b = this._options.border;
|
|
canvas = document.createElement("canvas");
|
|
var ctx = canvas.getContext("2d");
|
|
canvas.width = this._spacingX;
|
|
canvas.height = this._spacingY;
|
|
ctx.fillStyle = bg;
|
|
ctx.fillRect(b, b, canvas.width - b, canvas.height - b);
|
|
|
|
if (ch) {
|
|
ctx.fillStyle = fg;
|
|
ctx.font = this._ctx.font;
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
var chars = [].concat(ch);
|
|
|
|
for (var i = 0; i < chars.length; i++) {
|
|
ctx.fillText(chars[i], this._spacingX / 2, Math.ceil(this._spacingY / 2));
|
|
}
|
|
}
|
|
|
|
this._canvasCache[hash] = canvas;
|
|
}
|
|
|
|
this._ctx.drawImage(canvas, x * this._spacingX, y * this._spacingY);
|
|
};
|
|
|
|
_proto5._drawNoCache = function _drawNoCache(data, clearBefore) {
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4];
|
|
|
|
if (clearBefore) {
|
|
var b = this._options.border;
|
|
this._ctx.fillStyle = bg;
|
|
|
|
this._ctx.fillRect(x * this._spacingX + b, y * this._spacingY + b, this._spacingX - b, this._spacingY - b);
|
|
}
|
|
|
|
if (!ch) {
|
|
return;
|
|
}
|
|
|
|
this._ctx.fillStyle = fg;
|
|
var chars = [].concat(ch);
|
|
|
|
for (var i = 0; i < chars.length; i++) {
|
|
this._ctx.fillText(chars[i], (x + 0.5) * this._spacingX, Math.ceil((y + 0.5) * this._spacingY));
|
|
}
|
|
};
|
|
|
|
_proto5.computeSize = function computeSize(availWidth, availHeight) {
|
|
var width = Math.floor(availWidth / this._spacingX);
|
|
var height = Math.floor(availHeight / this._spacingY);
|
|
return [width, height];
|
|
};
|
|
|
|
_proto5.computeFontSize = function computeFontSize(availWidth, availHeight) {
|
|
var boxWidth = Math.floor(availWidth / this._options.width);
|
|
var boxHeight = Math.floor(availHeight / this._options.height);
|
|
/* compute char ratio */
|
|
|
|
var oldFont = this._ctx.font;
|
|
this._ctx.font = "100px " + this._options.fontFamily;
|
|
var width = Math.ceil(this._ctx.measureText("W").width);
|
|
this._ctx.font = oldFont;
|
|
var ratio = width / 100;
|
|
var widthFraction = ratio * boxHeight / boxWidth;
|
|
|
|
if (widthFraction > 1) {
|
|
/* too wide with current aspect ratio */
|
|
boxHeight = Math.floor(boxHeight / widthFraction);
|
|
}
|
|
|
|
return Math.floor(boxHeight / this._options.spacing);
|
|
};
|
|
|
|
_proto5._normalizedEventToPosition = function _normalizedEventToPosition(x, y) {
|
|
return [Math.floor(x / this._spacingX), Math.floor(y / this._spacingY)];
|
|
};
|
|
|
|
_proto5._updateSize = function _updateSize() {
|
|
var opts = this._options;
|
|
var charWidth = Math.ceil(this._ctx.measureText("W").width);
|
|
this._spacingX = Math.ceil(opts.spacing * charWidth);
|
|
this._spacingY = Math.ceil(opts.spacing * opts.fontSize);
|
|
|
|
if (opts.forceSquareRatio) {
|
|
this._spacingX = this._spacingY = Math.max(this._spacingX, this._spacingY);
|
|
}
|
|
|
|
this._ctx.canvas.width = opts.width * this._spacingX;
|
|
this._ctx.canvas.height = opts.height * this._spacingY;
|
|
};
|
|
|
|
return Rect;
|
|
}(Canvas);
|
|
|
|
Rect.cache = false;
|
|
/**
|
|
* @class Tile backend
|
|
* @private
|
|
*/
|
|
|
|
var Tile =
|
|
/*#__PURE__*/
|
|
function (_Canvas3) {
|
|
_inheritsLoose(Tile, _Canvas3);
|
|
|
|
function Tile() {
|
|
var _this4;
|
|
|
|
_this4 = _Canvas3.call(this) || this;
|
|
_this4._colorCanvas = document.createElement("canvas");
|
|
return _this4;
|
|
}
|
|
|
|
var _proto6 = Tile.prototype;
|
|
|
|
_proto6.draw = function draw(data, clearBefore) {
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4];
|
|
var tileWidth = this._options.tileWidth;
|
|
var tileHeight = this._options.tileHeight;
|
|
|
|
if (clearBefore) {
|
|
if (this._options.tileColorize) {
|
|
this._ctx.clearRect(x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
|
} else {
|
|
this._ctx.fillStyle = bg;
|
|
|
|
this._ctx.fillRect(x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
|
}
|
|
}
|
|
|
|
if (!ch) {
|
|
return;
|
|
}
|
|
|
|
var chars = [].concat(ch);
|
|
var fgs = [].concat(fg);
|
|
var bgs = [].concat(bg);
|
|
|
|
for (var i = 0; i < chars.length; i++) {
|
|
var tile = this._options.tileMap[chars[i]];
|
|
|
|
if (!tile) {
|
|
throw new Error("Char \"" + chars[i] + "\" not found in tileMap");
|
|
}
|
|
|
|
if (this._options.tileColorize) {
|
|
// apply colorization
|
|
var canvas = this._colorCanvas;
|
|
var context = canvas.getContext("2d");
|
|
context.globalCompositeOperation = "source-over";
|
|
context.clearRect(0, 0, tileWidth, tileHeight);
|
|
var _fg = fgs[i];
|
|
var _bg = bgs[i];
|
|
context.drawImage(this._options.tileSet, tile[0], tile[1], tileWidth, tileHeight, 0, 0, tileWidth, tileHeight);
|
|
|
|
if (_fg != "transparent") {
|
|
context.fillStyle = _fg;
|
|
context.globalCompositeOperation = "source-atop";
|
|
context.fillRect(0, 0, tileWidth, tileHeight);
|
|
}
|
|
|
|
if (_bg != "transparent") {
|
|
context.fillStyle = _bg;
|
|
context.globalCompositeOperation = "destination-over";
|
|
context.fillRect(0, 0, tileWidth, tileHeight);
|
|
}
|
|
|
|
this._ctx.drawImage(canvas, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
|
} else {
|
|
// no colorizing, easy
|
|
this._ctx.drawImage(this._options.tileSet, tile[0], tile[1], tileWidth, tileHeight, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto6.computeSize = function computeSize(availWidth, availHeight) {
|
|
var width = Math.floor(availWidth / this._options.tileWidth);
|
|
var height = Math.floor(availHeight / this._options.tileHeight);
|
|
return [width, height];
|
|
};
|
|
|
|
_proto6.computeFontSize = function computeFontSize() {
|
|
throw new Error("Tile backend does not understand font size");
|
|
};
|
|
|
|
_proto6._normalizedEventToPosition = function _normalizedEventToPosition(x, y) {
|
|
return [Math.floor(x / this._options.tileWidth), Math.floor(y / this._options.tileHeight)];
|
|
};
|
|
|
|
_proto6._updateSize = function _updateSize() {
|
|
var opts = this._options;
|
|
this._ctx.canvas.width = opts.width * opts.tileWidth;
|
|
this._ctx.canvas.height = opts.height * opts.tileHeight;
|
|
this._colorCanvas.width = opts.tileWidth;
|
|
this._colorCanvas.height = opts.tileHeight;
|
|
};
|
|
|
|
return Tile;
|
|
}(Canvas);
|
|
|
|
function fromString(str) {
|
|
var cached, r;
|
|
|
|
if (str in CACHE) {
|
|
cached = CACHE[str];
|
|
} else {
|
|
if (str.charAt(0) == "#") {
|
|
// hex rgb
|
|
var matched = str.match(/[0-9a-f]/gi) || [];
|
|
var values = matched.map(function (x) {
|
|
return parseInt(x, 16);
|
|
});
|
|
|
|
if (values.length == 3) {
|
|
cached = values.map(function (x) {
|
|
return x * 17;
|
|
});
|
|
} else {
|
|
for (var i = 0; i < 3; i++) {
|
|
values[i + 1] += 16 * values[i];
|
|
values.splice(i, 1);
|
|
}
|
|
|
|
cached = values;
|
|
}
|
|
} else if (r = str.match(/rgb\(([0-9, ]+)\)/i)) {
|
|
// decimal rgb
|
|
cached = r[1].split(/\s*,\s*/).map(function (x) {
|
|
return parseInt(x);
|
|
});
|
|
} else {
|
|
// html name
|
|
cached = [0, 0, 0];
|
|
}
|
|
|
|
CACHE[str] = cached;
|
|
}
|
|
|
|
return cached.slice();
|
|
}
|
|
/**
|
|
* Add two or more colors
|
|
*/
|
|
|
|
|
|
function add(color1) {
|
|
var result = color1.slice();
|
|
|
|
for (var _len2 = arguments.length, colors = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
colors[_key2 - 1] = arguments[_key2];
|
|
}
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
for (var j = 0; j < colors.length; j++) {
|
|
result[i] += colors[j][i];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
/**
|
|
* Add two or more colors, MODIFIES FIRST ARGUMENT
|
|
*/
|
|
|
|
|
|
function add_(color1) {
|
|
for (var _len3 = arguments.length, colors = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
colors[_key3 - 1] = arguments[_key3];
|
|
}
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
for (var j = 0; j < colors.length; j++) {
|
|
color1[i] += colors[j][i];
|
|
}
|
|
}
|
|
|
|
return color1;
|
|
}
|
|
/**
|
|
* Multiply (mix) two or more colors
|
|
*/
|
|
|
|
|
|
function multiply(color1) {
|
|
var result = color1.slice();
|
|
|
|
for (var _len4 = arguments.length, colors = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
|
colors[_key4 - 1] = arguments[_key4];
|
|
}
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
for (var j = 0; j < colors.length; j++) {
|
|
result[i] *= colors[j][i] / 255;
|
|
}
|
|
|
|
result[i] = Math.round(result[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
/**
|
|
* Multiply (mix) two or more colors, MODIFIES FIRST ARGUMENT
|
|
*/
|
|
|
|
|
|
function multiply_(color1) {
|
|
for (var _len5 = arguments.length, colors = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
|
|
colors[_key5 - 1] = arguments[_key5];
|
|
}
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
for (var j = 0; j < colors.length; j++) {
|
|
color1[i] *= colors[j][i] / 255;
|
|
}
|
|
|
|
color1[i] = Math.round(color1[i]);
|
|
}
|
|
|
|
return color1;
|
|
}
|
|
/**
|
|
* Interpolate (blend) two colors with a given factor
|
|
*/
|
|
|
|
|
|
function interpolate(color1, color2, factor) {
|
|
if (factor === void 0) {
|
|
factor = 0.5;
|
|
}
|
|
|
|
var result = color1.slice();
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
var lerp = interpolate;
|
|
/**
|
|
* Interpolate (blend) two colors with a given factor in HSL mode
|
|
*/
|
|
|
|
function interpolateHSL(color1, color2, factor) {
|
|
if (factor === void 0) {
|
|
factor = 0.5;
|
|
}
|
|
|
|
var hsl1 = rgb2hsl(color1);
|
|
var hsl2 = rgb2hsl(color2);
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
hsl1[i] += factor * (hsl2[i] - hsl1[i]);
|
|
}
|
|
|
|
return hsl2rgb(hsl1);
|
|
}
|
|
|
|
var lerpHSL = interpolateHSL;
|
|
/**
|
|
* Create a new random color based on this one
|
|
* @param color
|
|
* @param diff Set of standard deviations
|
|
*/
|
|
|
|
function randomize(color, diff) {
|
|
if (!(diff instanceof Array)) {
|
|
diff = Math.round(RNG$1.getNormal(0, diff));
|
|
}
|
|
|
|
var result = color.slice();
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
result[i] += diff instanceof Array ? Math.round(RNG$1.getNormal(0, diff[i])) : diff;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
/**
|
|
* Converts an RGB color value to HSL. Expects 0..255 inputs, produces 0..1 outputs.
|
|
*/
|
|
|
|
|
|
function rgb2hsl(color) {
|
|
var r = color[0] / 255;
|
|
var g = color[1] / 255;
|
|
var b = color[2] / 255;
|
|
var max = Math.max(r, g, b),
|
|
min = Math.min(r, g, b);
|
|
var h = 0,
|
|
s,
|
|
l = (max + min) / 2;
|
|
|
|
if (max == min) {
|
|
s = 0; // achromatic
|
|
} else {
|
|
var d = max - min;
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
|
|
switch (max) {
|
|
case r:
|
|
h = (g - b) / d + (g < b ? 6 : 0);
|
|
break;
|
|
|
|
case g:
|
|
h = (b - r) / d + 2;
|
|
break;
|
|
|
|
case b:
|
|
h = (r - g) / d + 4;
|
|
break;
|
|
}
|
|
|
|
h /= 6;
|
|
}
|
|
|
|
return [h, s, l];
|
|
}
|
|
|
|
function hue2rgb(p, q, t) {
|
|
if (t < 0) t += 1;
|
|
if (t > 1) t -= 1;
|
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
if (t < 1 / 2) return q;
|
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
return p;
|
|
}
|
|
/**
|
|
* Converts an HSL color value to RGB. Expects 0..1 inputs, produces 0..255 outputs.
|
|
*/
|
|
|
|
|
|
function hsl2rgb(color) {
|
|
var l = color[2];
|
|
|
|
if (color[1] == 0) {
|
|
l = Math.round(l * 255);
|
|
return [l, l, l];
|
|
} else {
|
|
var s = color[1];
|
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
var p = 2 * l - q;
|
|
var r = hue2rgb(p, q, color[0] + 1 / 3);
|
|
var g = hue2rgb(p, q, color[0]);
|
|
var b = hue2rgb(p, q, color[0] - 1 / 3);
|
|
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
|
}
|
|
}
|
|
|
|
function toRGB(color) {
|
|
var clamped = color.map(function (x) {
|
|
return clamp(x, 0, 255);
|
|
});
|
|
return "rgb(" + clamped.join(",") + ")";
|
|
}
|
|
|
|
function toHex(color) {
|
|
var clamped = color.map(function (x) {
|
|
return clamp(x, 0, 255).toString(16).padStart(2, "0");
|
|
});
|
|
return "#" + clamped.join("");
|
|
}
|
|
|
|
var CACHE = {
|
|
"black": [0, 0, 0],
|
|
"navy": [0, 0, 128],
|
|
"darkblue": [0, 0, 139],
|
|
"mediumblue": [0, 0, 205],
|
|
"blue": [0, 0, 255],
|
|
"darkgreen": [0, 100, 0],
|
|
"green": [0, 128, 0],
|
|
"teal": [0, 128, 128],
|
|
"darkcyan": [0, 139, 139],
|
|
"deepskyblue": [0, 191, 255],
|
|
"darkturquoise": [0, 206, 209],
|
|
"mediumspringgreen": [0, 250, 154],
|
|
"lime": [0, 255, 0],
|
|
"springgreen": [0, 255, 127],
|
|
"aqua": [0, 255, 255],
|
|
"cyan": [0, 255, 255],
|
|
"midnightblue": [25, 25, 112],
|
|
"dodgerblue": [30, 144, 255],
|
|
"forestgreen": [34, 139, 34],
|
|
"seagreen": [46, 139, 87],
|
|
"darkslategray": [47, 79, 79],
|
|
"darkslategrey": [47, 79, 79],
|
|
"limegreen": [50, 205, 50],
|
|
"mediumseagreen": [60, 179, 113],
|
|
"turquoise": [64, 224, 208],
|
|
"royalblue": [65, 105, 225],
|
|
"steelblue": [70, 130, 180],
|
|
"darkslateblue": [72, 61, 139],
|
|
"mediumturquoise": [72, 209, 204],
|
|
"indigo": [75, 0, 130],
|
|
"darkolivegreen": [85, 107, 47],
|
|
"cadetblue": [95, 158, 160],
|
|
"cornflowerblue": [100, 149, 237],
|
|
"mediumaquamarine": [102, 205, 170],
|
|
"dimgray": [105, 105, 105],
|
|
"dimgrey": [105, 105, 105],
|
|
"slateblue": [106, 90, 205],
|
|
"olivedrab": [107, 142, 35],
|
|
"slategray": [112, 128, 144],
|
|
"slategrey": [112, 128, 144],
|
|
"lightslategray": [119, 136, 153],
|
|
"lightslategrey": [119, 136, 153],
|
|
"mediumslateblue": [123, 104, 238],
|
|
"lawngreen": [124, 252, 0],
|
|
"chartreuse": [127, 255, 0],
|
|
"aquamarine": [127, 255, 212],
|
|
"maroon": [128, 0, 0],
|
|
"purple": [128, 0, 128],
|
|
"olive": [128, 128, 0],
|
|
"gray": [128, 128, 128],
|
|
"grey": [128, 128, 128],
|
|
"skyblue": [135, 206, 235],
|
|
"lightskyblue": [135, 206, 250],
|
|
"blueviolet": [138, 43, 226],
|
|
"darkred": [139, 0, 0],
|
|
"darkmagenta": [139, 0, 139],
|
|
"saddlebrown": [139, 69, 19],
|
|
"darkseagreen": [143, 188, 143],
|
|
"lightgreen": [144, 238, 144],
|
|
"mediumpurple": [147, 112, 216],
|
|
"darkviolet": [148, 0, 211],
|
|
"palegreen": [152, 251, 152],
|
|
"darkorchid": [153, 50, 204],
|
|
"yellowgreen": [154, 205, 50],
|
|
"sienna": [160, 82, 45],
|
|
"brown": [165, 42, 42],
|
|
"darkgray": [169, 169, 169],
|
|
"darkgrey": [169, 169, 169],
|
|
"lightblue": [173, 216, 230],
|
|
"greenyellow": [173, 255, 47],
|
|
"paleturquoise": [175, 238, 238],
|
|
"lightsteelblue": [176, 196, 222],
|
|
"powderblue": [176, 224, 230],
|
|
"firebrick": [178, 34, 34],
|
|
"darkgoldenrod": [184, 134, 11],
|
|
"mediumorchid": [186, 85, 211],
|
|
"rosybrown": [188, 143, 143],
|
|
"darkkhaki": [189, 183, 107],
|
|
"silver": [192, 192, 192],
|
|
"mediumvioletred": [199, 21, 133],
|
|
"indianred": [205, 92, 92],
|
|
"peru": [205, 133, 63],
|
|
"chocolate": [210, 105, 30],
|
|
"tan": [210, 180, 140],
|
|
"lightgray": [211, 211, 211],
|
|
"lightgrey": [211, 211, 211],
|
|
"palevioletred": [216, 112, 147],
|
|
"thistle": [216, 191, 216],
|
|
"orchid": [218, 112, 214],
|
|
"goldenrod": [218, 165, 32],
|
|
"crimson": [220, 20, 60],
|
|
"gainsboro": [220, 220, 220],
|
|
"plum": [221, 160, 221],
|
|
"burlywood": [222, 184, 135],
|
|
"lightcyan": [224, 255, 255],
|
|
"lavender": [230, 230, 250],
|
|
"darksalmon": [233, 150, 122],
|
|
"violet": [238, 130, 238],
|
|
"palegoldenrod": [238, 232, 170],
|
|
"lightcoral": [240, 128, 128],
|
|
"khaki": [240, 230, 140],
|
|
"aliceblue": [240, 248, 255],
|
|
"honeydew": [240, 255, 240],
|
|
"azure": [240, 255, 255],
|
|
"sandybrown": [244, 164, 96],
|
|
"wheat": [245, 222, 179],
|
|
"beige": [245, 245, 220],
|
|
"whitesmoke": [245, 245, 245],
|
|
"mintcream": [245, 255, 250],
|
|
"ghostwhite": [248, 248, 255],
|
|
"salmon": [250, 128, 114],
|
|
"antiquewhite": [250, 235, 215],
|
|
"linen": [250, 240, 230],
|
|
"lightgoldenrodyellow": [250, 250, 210],
|
|
"oldlace": [253, 245, 230],
|
|
"red": [255, 0, 0],
|
|
"fuchsia": [255, 0, 255],
|
|
"magenta": [255, 0, 255],
|
|
"deeppink": [255, 20, 147],
|
|
"orangered": [255, 69, 0],
|
|
"tomato": [255, 99, 71],
|
|
"hotpink": [255, 105, 180],
|
|
"coral": [255, 127, 80],
|
|
"darkorange": [255, 140, 0],
|
|
"lightsalmon": [255, 160, 122],
|
|
"orange": [255, 165, 0],
|
|
"lightpink": [255, 182, 193],
|
|
"pink": [255, 192, 203],
|
|
"gold": [255, 215, 0],
|
|
"peachpuff": [255, 218, 185],
|
|
"navajowhite": [255, 222, 173],
|
|
"moccasin": [255, 228, 181],
|
|
"bisque": [255, 228, 196],
|
|
"mistyrose": [255, 228, 225],
|
|
"blanchedalmond": [255, 235, 205],
|
|
"papayawhip": [255, 239, 213],
|
|
"lavenderblush": [255, 240, 245],
|
|
"seashell": [255, 245, 238],
|
|
"cornsilk": [255, 248, 220],
|
|
"lemonchiffon": [255, 250, 205],
|
|
"floralwhite": [255, 250, 240],
|
|
"snow": [255, 250, 250],
|
|
"yellow": [255, 255, 0],
|
|
"lightyellow": [255, 255, 224],
|
|
"ivory": [255, 255, 240],
|
|
"white": [255, 255, 255]
|
|
};
|
|
var color =
|
|
/*#__PURE__*/
|
|
Object.freeze({
|
|
fromString: fromString,
|
|
add: add,
|
|
add_: add_,
|
|
multiply: multiply,
|
|
multiply_: multiply_,
|
|
interpolate: interpolate,
|
|
lerp: lerp,
|
|
interpolateHSL: interpolateHSL,
|
|
lerpHSL: lerpHSL,
|
|
randomize: randomize,
|
|
rgb2hsl: rgb2hsl,
|
|
hsl2rgb: hsl2rgb,
|
|
toRGB: toRGB,
|
|
toHex: toHex
|
|
});
|
|
/**
|
|
* @class Tile backend
|
|
* @private
|
|
*/
|
|
|
|
var TileGL =
|
|
/*#__PURE__*/
|
|
function (_Backend2) {
|
|
_inheritsLoose(TileGL, _Backend2);
|
|
|
|
TileGL.isSupported = function isSupported() {
|
|
return !!document.createElement("canvas").getContext("webgl2", {
|
|
preserveDrawingBuffer: true
|
|
});
|
|
};
|
|
|
|
function TileGL() {
|
|
var _this5;
|
|
|
|
_this5 = _Backend2.call(this) || this;
|
|
_this5._uniforms = {};
|
|
|
|
try {
|
|
_this5._gl = _this5._initWebGL();
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
|
|
return _this5;
|
|
}
|
|
|
|
var _proto7 = TileGL.prototype;
|
|
|
|
_proto7.schedule = function schedule(cb) {
|
|
requestAnimationFrame(cb);
|
|
};
|
|
|
|
_proto7.getContainer = function getContainer() {
|
|
return this._gl.canvas;
|
|
};
|
|
|
|
_proto7.setOptions = function setOptions(opts) {
|
|
var _this6 = this;
|
|
|
|
_Backend2.prototype.setOptions.call(this, opts);
|
|
|
|
this._updateSize();
|
|
|
|
var tileSet = this._options.tileSet;
|
|
|
|
if (tileSet && "complete" in tileSet && !tileSet.complete) {
|
|
tileSet.addEventListener("load", function () {
|
|
return _this6._updateTexture(tileSet);
|
|
});
|
|
} else {
|
|
this._updateTexture(tileSet);
|
|
}
|
|
};
|
|
|
|
_proto7.draw = function draw(data, clearBefore) {
|
|
var gl = this._gl;
|
|
var opts = this._options;
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4];
|
|
var scissorY = gl.canvas.height - (y + 1) * opts.tileHeight;
|
|
gl.scissor(x * opts.tileWidth, scissorY, opts.tileWidth, opts.tileHeight);
|
|
|
|
if (clearBefore) {
|
|
if (opts.tileColorize) {
|
|
gl.clearColor(0, 0, 0, 0);
|
|
} else {
|
|
gl.clearColor.apply(gl, parseColor(bg));
|
|
}
|
|
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
if (!ch) {
|
|
return;
|
|
}
|
|
|
|
var chars = [].concat(ch);
|
|
var bgs = [].concat(bg);
|
|
var fgs = [].concat(fg);
|
|
gl.uniform2fv(this._uniforms["targetPosRel"], [x, y]);
|
|
|
|
for (var i = 0; i < chars.length; i++) {
|
|
var tile = this._options.tileMap[chars[i]];
|
|
|
|
if (!tile) {
|
|
throw new Error("Char \"" + chars[i] + "\" not found in tileMap");
|
|
}
|
|
|
|
gl.uniform1f(this._uniforms["colorize"], opts.tileColorize ? 1 : 0);
|
|
gl.uniform2fv(this._uniforms["tilesetPosAbs"], tile);
|
|
|
|
if (opts.tileColorize) {
|
|
gl.uniform4fv(this._uniforms["tint"], parseColor(fgs[i]));
|
|
gl.uniform4fv(this._uniforms["bg"], parseColor(bgs[i]));
|
|
}
|
|
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
/*
|
|
|
|
|
|
for (let i=0;i<chars.length;i++) {
|
|
|
|
if (this._options.tileColorize) { // apply colorization
|
|
let canvas = this._colorCanvas;
|
|
let context = canvas.getContext("2d") as CanvasRenderingContext2D;
|
|
context.globalCompositeOperation = "source-over";
|
|
context.clearRect(0, 0, tileWidth, tileHeight);
|
|
|
|
let fg = fgs[i];
|
|
let bg = bgs[i];
|
|
|
|
context.drawImage(
|
|
this._options.tileSet!,
|
|
tile[0], tile[1], tileWidth, tileHeight,
|
|
0, 0, tileWidth, tileHeight
|
|
);
|
|
|
|
if (fg != "transparent") {
|
|
context.fillStyle = fg;
|
|
context.globalCompositeOperation = "source-atop";
|
|
context.fillRect(0, 0, tileWidth, tileHeight);
|
|
}
|
|
|
|
if (bg != "transparent") {
|
|
context.fillStyle = bg;
|
|
context.globalCompositeOperation = "destination-over";
|
|
context.fillRect(0, 0, tileWidth, tileHeight);
|
|
}
|
|
|
|
this._ctx.drawImage(canvas, x*tileWidth, y*tileHeight, tileWidth, tileHeight);
|
|
} else { // no colorizing, easy
|
|
this._ctx.drawImage(
|
|
this._options.tileSet!,
|
|
tile[0], tile[1], tileWidth, tileHeight,
|
|
x*tileWidth, y*tileHeight, tileWidth, tileHeight
|
|
);
|
|
}
|
|
}
|
|
|
|
*/
|
|
|
|
};
|
|
|
|
_proto7.clear = function clear() {
|
|
var gl = this._gl;
|
|
gl.clearColor.apply(gl, parseColor(this._options.bg));
|
|
gl.scissor(0, 0, gl.canvas.width, gl.canvas.height);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
};
|
|
|
|
_proto7.computeSize = function computeSize(availWidth, availHeight) {
|
|
var width = Math.floor(availWidth / this._options.tileWidth);
|
|
var height = Math.floor(availHeight / this._options.tileHeight);
|
|
return [width, height];
|
|
};
|
|
|
|
_proto7.computeFontSize = function computeFontSize() {
|
|
throw new Error("Tile backend does not understand font size");
|
|
};
|
|
|
|
_proto7.eventToPosition = function eventToPosition(x, y) {
|
|
var canvas = this._gl.canvas;
|
|
var rect = canvas.getBoundingClientRect();
|
|
x -= rect.left;
|
|
y -= rect.top;
|
|
x *= canvas.width / rect.width;
|
|
y *= canvas.height / rect.height;
|
|
|
|
if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
|
|
return [-1, -1];
|
|
}
|
|
|
|
return this._normalizedEventToPosition(x, y);
|
|
};
|
|
|
|
_proto7._initWebGL = function _initWebGL() {
|
|
var _this7 = this;
|
|
|
|
var gl = document.createElement("canvas").getContext("webgl2", {
|
|
preserveDrawingBuffer: true
|
|
});
|
|
window.gl = gl;
|
|
var program = createProgram(gl, VS, FS);
|
|
gl.useProgram(program);
|
|
createQuad(gl);
|
|
UNIFORMS.forEach(function (name) {
|
|
return _this7._uniforms[name] = gl.getUniformLocation(program, name);
|
|
});
|
|
this._program = program;
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
gl.enable(gl.SCISSOR_TEST);
|
|
return gl;
|
|
};
|
|
|
|
_proto7._normalizedEventToPosition = function _normalizedEventToPosition(x, y) {
|
|
return [Math.floor(x / this._options.tileWidth), Math.floor(y / this._options.tileHeight)];
|
|
};
|
|
|
|
_proto7._updateSize = function _updateSize() {
|
|
var gl = this._gl;
|
|
var opts = this._options;
|
|
var canvasSize = [opts.width * opts.tileWidth, opts.height * opts.tileHeight];
|
|
gl.canvas.width = canvasSize[0];
|
|
gl.canvas.height = canvasSize[1];
|
|
gl.viewport(0, 0, canvasSize[0], canvasSize[1]);
|
|
gl.uniform2fv(this._uniforms["tileSize"], [opts.tileWidth, opts.tileHeight]);
|
|
gl.uniform2fv(this._uniforms["targetSize"], canvasSize);
|
|
};
|
|
|
|
_proto7._updateTexture = function _updateTexture(tileSet) {
|
|
createTexture(this._gl, tileSet);
|
|
};
|
|
|
|
return TileGL;
|
|
}(Backend);
|
|
|
|
var UNIFORMS = ["targetPosRel", "tilesetPosAbs", "tileSize", "targetSize", "colorize", "bg", "tint"];
|
|
var VS = "\n#version 300 es\n\nin vec2 tilePosRel;\nout vec2 tilesetPosPx;\n\nuniform vec2 tilesetPosAbs;\nuniform vec2 tileSize;\nuniform vec2 targetSize;\nuniform vec2 targetPosRel;\n\nvoid main() {\n\tvec2 targetPosPx = (targetPosRel + tilePosRel) * tileSize;\n\tvec2 targetPosNdc = ((targetPosPx / targetSize)-0.5)*2.0;\n\ttargetPosNdc.y *= -1.0;\n\n\tgl_Position = vec4(targetPosNdc, 0.0, 1.0);\n\ttilesetPosPx = tilesetPosAbs + tilePosRel * tileSize;\n}".trim();
|
|
var FS = "\n#version 300 es\nprecision highp float;\n\nin vec2 tilesetPosPx;\nout vec4 fragColor;\nuniform sampler2D image;\nuniform bool colorize;\nuniform vec4 bg;\nuniform vec4 tint;\n\nvoid main() {\n\tfragColor = vec4(0, 0, 0, 1);\n\n\tvec4 texel = texelFetch(image, ivec2(tilesetPosPx), 0);\n\n\tif (colorize) {\n\t\ttexel.rgb = tint.a * tint.rgb + (1.0-tint.a) * texel.rgb;\n\t\tfragColor.rgb = texel.a*texel.rgb + (1.0-texel.a)*bg.rgb;\n\t\tfragColor.a = texel.a + (1.0-texel.a)*bg.a;\n\t} else {\n\t\tfragColor = texel;\n\t}\n}".trim();
|
|
|
|
function createProgram(gl, vss, fss) {
|
|
var vs = gl.createShader(gl.VERTEX_SHADER);
|
|
gl.shaderSource(vs, vss);
|
|
gl.compileShader(vs);
|
|
|
|
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
|
|
throw new Error(gl.getShaderInfoLog(vs) || "");
|
|
}
|
|
|
|
var fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
gl.shaderSource(fs, fss);
|
|
gl.compileShader(fs);
|
|
|
|
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
|
|
throw new Error(gl.getShaderInfoLog(fs) || "");
|
|
}
|
|
|
|
var p = gl.createProgram();
|
|
gl.attachShader(p, vs);
|
|
gl.attachShader(p, fs);
|
|
gl.linkProgram(p);
|
|
|
|
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
|
|
throw new Error(gl.getProgramInfoLog(p) || "");
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
function createQuad(gl) {
|
|
var pos = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
|
|
var buf = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
gl.bufferData(gl.ARRAY_BUFFER, pos, gl.STATIC_DRAW);
|
|
gl.enableVertexAttribArray(0);
|
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
}
|
|
|
|
function createTexture(gl, data) {
|
|
var t = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
return t;
|
|
}
|
|
|
|
var colorCache = {};
|
|
|
|
function parseColor(color) {
|
|
if (!(color in colorCache)) {
|
|
var parsed;
|
|
|
|
if (color == "transparent") {
|
|
parsed = [0, 0, 0, 0];
|
|
} else if (color.indexOf("rgba") > -1) {
|
|
parsed = (color.match(/[\d.]+/g) || []).map(Number);
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
parsed[i] = parsed[i] / 255;
|
|
}
|
|
} else {
|
|
parsed = fromString(color).map(function ($) {
|
|
return $ / 255;
|
|
});
|
|
parsed.push(1);
|
|
}
|
|
|
|
colorCache[color] = parsed;
|
|
}
|
|
|
|
return colorCache[color];
|
|
}
|
|
|
|
function clearToAnsi(bg) {
|
|
return "\x1B[0;48;5;" + termcolor(bg) + "m\x1B[2J";
|
|
}
|
|
|
|
function colorToAnsi(fg, bg) {
|
|
return "\x1B[0;38;5;" + termcolor(fg) + ";48;5;" + termcolor(bg) + "m";
|
|
}
|
|
|
|
function positionToAnsi(x, y) {
|
|
return "\x1B[" + (y + 1) + ";" + (x + 1) + "H";
|
|
}
|
|
|
|
function termcolor(color) {
|
|
var SRC_COLORS = 256.0;
|
|
var DST_COLORS = 6.0;
|
|
var COLOR_RATIO = DST_COLORS / SRC_COLORS;
|
|
var rgb = fromString(color);
|
|
var r = Math.floor(rgb[0] * COLOR_RATIO);
|
|
var g = Math.floor(rgb[1] * COLOR_RATIO);
|
|
var b = Math.floor(rgb[2] * COLOR_RATIO);
|
|
return r * 36 + g * 6 + b * 1 + 16;
|
|
}
|
|
|
|
var Term =
|
|
/*#__PURE__*/
|
|
function (_Backend3) {
|
|
_inheritsLoose(Term, _Backend3);
|
|
|
|
function Term() {
|
|
var _this8;
|
|
|
|
_this8 = _Backend3.call(this) || this;
|
|
_this8._offset = [0, 0];
|
|
_this8._cursor = [-1, -1];
|
|
_this8._lastColor = "";
|
|
return _this8;
|
|
}
|
|
|
|
var _proto8 = Term.prototype;
|
|
|
|
_proto8.schedule = function schedule(cb) {
|
|
setTimeout(cb, 1000 / 60);
|
|
};
|
|
|
|
_proto8.setOptions = function setOptions(options) {
|
|
_Backend3.prototype.setOptions.call(this, options);
|
|
|
|
var size = [options.width, options.height];
|
|
var avail = this.computeSize();
|
|
this._offset = avail.map(function (val, index) {
|
|
return Math.floor((val - size[index]) / 2);
|
|
});
|
|
};
|
|
|
|
_proto8.clear = function clear() {
|
|
process.stdout.write(clearToAnsi(this._options.bg));
|
|
};
|
|
|
|
_proto8.draw = function draw(data, clearBefore) {
|
|
// determine where to draw what with what colors
|
|
var x = data[0],
|
|
y = data[1],
|
|
ch = data[2],
|
|
fg = data[3],
|
|
bg = data[4]; // determine if we need to move the terminal cursor
|
|
|
|
var dx = this._offset[0] + x;
|
|
var dy = this._offset[1] + y;
|
|
var size = this.computeSize();
|
|
|
|
if (dx < 0 || dx >= size[0]) {
|
|
return;
|
|
}
|
|
|
|
if (dy < 0 || dy >= size[1]) {
|
|
return;
|
|
}
|
|
|
|
if (dx !== this._cursor[0] || dy !== this._cursor[1]) {
|
|
process.stdout.write(positionToAnsi(dx, dy));
|
|
this._cursor[0] = dx;
|
|
this._cursor[1] = dy;
|
|
} // terminals automatically clear, but if we're clearing when we're
|
|
// not otherwise provided with a character, just use a space instead
|
|
|
|
|
|
if (clearBefore) {
|
|
if (!ch) {
|
|
ch = " ";
|
|
}
|
|
} // if we're not clearing and not provided with a character, do nothing
|
|
|
|
|
|
if (!ch) {
|
|
return;
|
|
} // determine if we need to change colors
|
|
|
|
|
|
var newColor = colorToAnsi(fg, bg);
|
|
|
|
if (newColor !== this._lastColor) {
|
|
process.stdout.write(newColor);
|
|
this._lastColor = newColor;
|
|
} // write the provided symbol to the display
|
|
|
|
|
|
var chars = [].concat(ch);
|
|
process.stdout.write(chars[0]); // update our position, given that we wrote a character
|
|
|
|
this._cursor[0]++;
|
|
|
|
if (this._cursor[0] >= size[0]) {
|
|
this._cursor[0] = 0;
|
|
this._cursor[1]++;
|
|
}
|
|
};
|
|
|
|
_proto8.computeFontSize = function computeFontSize() {
|
|
throw new Error("Terminal backend has no notion of font size");
|
|
};
|
|
|
|
_proto8.eventToPosition = function eventToPosition(x, y) {
|
|
return [x, y];
|
|
};
|
|
|
|
_proto8.computeSize = function computeSize() {
|
|
return [process.stdout.columns, process.stdout.rows];
|
|
};
|
|
|
|
return Term;
|
|
}(Backend);
|
|
/**
|
|
* @namespace
|
|
* Contains text tokenization and breaking routines
|
|
*/
|
|
|
|
|
|
var RE_COLORS = /%([bc]){([^}]*)}/g; // token types
|
|
|
|
var TYPE_TEXT = 0;
|
|
var TYPE_NEWLINE = 1;
|
|
var TYPE_FG = 2;
|
|
var TYPE_BG = 3;
|
|
/**
|
|
* Measure size of a resulting text block
|
|
*/
|
|
|
|
function measure(str, maxWidth) {
|
|
var result = {
|
|
width: 0,
|
|
height: 1
|
|
};
|
|
var tokens = tokenize(str, maxWidth);
|
|
var lineWidth = 0;
|
|
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
|
|
switch (token.type) {
|
|
case TYPE_TEXT:
|
|
lineWidth += token.value.length;
|
|
break;
|
|
|
|
case TYPE_NEWLINE:
|
|
result.height++;
|
|
result.width = Math.max(result.width, lineWidth);
|
|
lineWidth = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.width = Math.max(result.width, lineWidth);
|
|
return result;
|
|
}
|
|
/**
|
|
* Convert string to a series of a formatting commands
|
|
*/
|
|
|
|
|
|
function tokenize(str, maxWidth) {
|
|
var result = [];
|
|
/* first tokenization pass - split texts and color formatting commands */
|
|
|
|
var offset = 0;
|
|
str.replace(RE_COLORS, function (match, type, name, index) {
|
|
/* string before */
|
|
var part = str.substring(offset, index);
|
|
|
|
if (part.length) {
|
|
result.push({
|
|
type: TYPE_TEXT,
|
|
value: part
|
|
});
|
|
}
|
|
/* color command */
|
|
|
|
|
|
result.push({
|
|
type: type == "c" ? TYPE_FG : TYPE_BG,
|
|
value: name.trim()
|
|
});
|
|
offset = index + match.length;
|
|
return "";
|
|
});
|
|
/* last remaining part */
|
|
|
|
var part = str.substring(offset);
|
|
|
|
if (part.length) {
|
|
result.push({
|
|
type: TYPE_TEXT,
|
|
value: part
|
|
});
|
|
}
|
|
|
|
return breakLines(result, maxWidth);
|
|
}
|
|
/* insert line breaks into first-pass tokenized data */
|
|
|
|
|
|
function breakLines(tokens, maxWidth) {
|
|
if (!maxWidth) {
|
|
maxWidth = Infinity;
|
|
}
|
|
|
|
var i = 0;
|
|
var lineLength = 0;
|
|
var lastTokenWithSpace = -1;
|
|
|
|
while (i < tokens.length) {
|
|
/* take all text tokens, remove space, apply linebreaks */
|
|
var token = tokens[i];
|
|
|
|
if (token.type == TYPE_NEWLINE) {
|
|
/* reset */
|
|
lineLength = 0;
|
|
lastTokenWithSpace = -1;
|
|
}
|
|
|
|
if (token.type != TYPE_TEXT) {
|
|
/* skip non-text tokens */
|
|
i++;
|
|
continue;
|
|
}
|
|
/* remove spaces at the beginning of line */
|
|
|
|
|
|
while (lineLength == 0 && token.value.charAt(0) == " ") {
|
|
token.value = token.value.substring(1);
|
|
}
|
|
/* forced newline? insert two new tokens after this one */
|
|
|
|
|
|
var _index2 = token.value.indexOf("\n");
|
|
|
|
if (_index2 != -1) {
|
|
token.value = breakInsideToken(tokens, i, _index2, true);
|
|
/* if there are spaces at the end, we must remove them (we do not want the line too long) */
|
|
|
|
var arr = token.value.split("");
|
|
|
|
while (arr.length && arr[arr.length - 1] == " ") {
|
|
arr.pop();
|
|
}
|
|
|
|
token.value = arr.join("");
|
|
}
|
|
/* token degenerated? */
|
|
|
|
|
|
if (!token.value.length) {
|
|
tokens.splice(i, 1);
|
|
continue;
|
|
}
|
|
|
|
if (lineLength + token.value.length > maxWidth) {
|
|
/* line too long, find a suitable breaking spot */
|
|
|
|
/* is it possible to break within this token? */
|
|
var _index3 = -1;
|
|
|
|
while (1) {
|
|
var nextIndex = token.value.indexOf(" ", _index3 + 1);
|
|
|
|
if (nextIndex == -1) {
|
|
break;
|
|
}
|
|
|
|
if (lineLength + nextIndex > maxWidth) {
|
|
break;
|
|
}
|
|
|
|
_index3 = nextIndex;
|
|
}
|
|
|
|
if (_index3 != -1) {
|
|
/* break at space within this one */
|
|
token.value = breakInsideToken(tokens, i, _index3, true);
|
|
} else if (lastTokenWithSpace != -1) {
|
|
/* is there a previous token where a break can occur? */
|
|
var _token = tokens[lastTokenWithSpace];
|
|
|
|
var breakIndex = _token.value.lastIndexOf(" ");
|
|
|
|
_token.value = breakInsideToken(tokens, lastTokenWithSpace, breakIndex, true);
|
|
i = lastTokenWithSpace;
|
|
} else {
|
|
/* force break in this token */
|
|
token.value = breakInsideToken(tokens, i, maxWidth - lineLength, false);
|
|
}
|
|
} else {
|
|
/* line not long, continue */
|
|
lineLength += token.value.length;
|
|
|
|
if (token.value.indexOf(" ") != -1) {
|
|
lastTokenWithSpace = i;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
/* advance to next token */
|
|
}
|
|
|
|
tokens.push({
|
|
type: TYPE_NEWLINE
|
|
});
|
|
/* insert fake newline to fix the last text line */
|
|
|
|
/* remove trailing space from text tokens before newlines */
|
|
|
|
var lastTextToken = null;
|
|
|
|
for (var _i = 0; _i < tokens.length; _i++) {
|
|
var _token2 = tokens[_i];
|
|
|
|
switch (_token2.type) {
|
|
case TYPE_TEXT:
|
|
lastTextToken = _token2;
|
|
break;
|
|
|
|
case TYPE_NEWLINE:
|
|
if (lastTextToken) {
|
|
/* remove trailing space */
|
|
var _arr = lastTextToken.value.split("");
|
|
|
|
while (_arr.length && _arr[_arr.length - 1] == " ") {
|
|
_arr.pop();
|
|
}
|
|
|
|
lastTextToken.value = _arr.join("");
|
|
}
|
|
|
|
lastTextToken = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tokens.pop();
|
|
/* remove fake token */
|
|
|
|
return tokens;
|
|
}
|
|
/**
|
|
* Create new tokens and insert them into the stream
|
|
* @param {object[]} tokens
|
|
* @param {int} tokenIndex Token being processed
|
|
* @param {int} breakIndex Index within current token's value
|
|
* @param {bool} removeBreakChar Do we want to remove the breaking character?
|
|
* @returns {string} remaining unbroken token value
|
|
*/
|
|
|
|
|
|
function breakInsideToken(tokens, tokenIndex, breakIndex, removeBreakChar) {
|
|
var newBreakToken = {
|
|
type: TYPE_NEWLINE
|
|
};
|
|
var newTextToken = {
|
|
type: TYPE_TEXT,
|
|
value: tokens[tokenIndex].value.substring(breakIndex + (removeBreakChar ? 1 : 0))
|
|
};
|
|
tokens.splice(tokenIndex + 1, 0, newBreakToken, newTextToken);
|
|
return tokens[tokenIndex].value.substring(0, breakIndex);
|
|
}
|
|
|
|
var text =
|
|
/*#__PURE__*/
|
|
Object.freeze({
|
|
TYPE_TEXT: TYPE_TEXT,
|
|
TYPE_NEWLINE: TYPE_NEWLINE,
|
|
TYPE_FG: TYPE_FG,
|
|
TYPE_BG: TYPE_BG,
|
|
measure: measure,
|
|
tokenize: tokenize
|
|
});
|
|
/** Default with for display and map generators */
|
|
|
|
var DEFAULT_WIDTH = 80;
|
|
/** Default height for display and map generators */
|
|
|
|
var DEFAULT_HEIGHT = 25;
|
|
var DIRS = {
|
|
4: [[0, -1], [1, 0], [0, 1], [-1, 0]],
|
|
8: [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]],
|
|
6: [[-1, -1], [1, -1], [2, 0], [1, 1], [-1, 1], [-2, 0]]
|
|
};
|
|
var KEYS = {
|
|
/** Cancel key. */
|
|
VK_CANCEL: 3,
|
|
|
|
/** Help key. */
|
|
VK_HELP: 6,
|
|
|
|
/** Backspace key. */
|
|
VK_BACK_SPACE: 8,
|
|
|
|
/** Tab key. */
|
|
VK_TAB: 9,
|
|
|
|
/** 5 key on Numpad when NumLock is unlocked. Or on Mac, clear key which is positioned at NumLock key. */
|
|
VK_CLEAR: 12,
|
|
|
|
/** Return/enter key on the main keyboard. */
|
|
VK_RETURN: 13,
|
|
|
|
/** Reserved, but not used. */
|
|
VK_ENTER: 14,
|
|
|
|
/** Shift key. */
|
|
VK_SHIFT: 16,
|
|
|
|
/** Control key. */
|
|
VK_CONTROL: 17,
|
|
|
|
/** Alt (Option on Mac) key. */
|
|
VK_ALT: 18,
|
|
|
|
/** Pause key. */
|
|
VK_PAUSE: 19,
|
|
|
|
/** Caps lock. */
|
|
VK_CAPS_LOCK: 20,
|
|
|
|
/** Escape key. */
|
|
VK_ESCAPE: 27,
|
|
|
|
/** Space bar. */
|
|
VK_SPACE: 32,
|
|
|
|
/** Page Up key. */
|
|
VK_PAGE_UP: 33,
|
|
|
|
/** Page Down key. */
|
|
VK_PAGE_DOWN: 34,
|
|
|
|
/** End key. */
|
|
VK_END: 35,
|
|
|
|
/** Home key. */
|
|
VK_HOME: 36,
|
|
|
|
/** Left arrow. */
|
|
VK_LEFT: 37,
|
|
|
|
/** Up arrow. */
|
|
VK_UP: 38,
|
|
|
|
/** Right arrow. */
|
|
VK_RIGHT: 39,
|
|
|
|
/** Down arrow. */
|
|
VK_DOWN: 40,
|
|
|
|
/** Print Screen key. */
|
|
VK_PRINTSCREEN: 44,
|
|
|
|
/** Ins(ert) key. */
|
|
VK_INSERT: 45,
|
|
|
|
/** Del(ete) key. */
|
|
VK_DELETE: 46,
|
|
|
|
/***/
|
|
VK_0: 48,
|
|
|
|
/***/
|
|
VK_1: 49,
|
|
|
|
/***/
|
|
VK_2: 50,
|
|
|
|
/***/
|
|
VK_3: 51,
|
|
|
|
/***/
|
|
VK_4: 52,
|
|
|
|
/***/
|
|
VK_5: 53,
|
|
|
|
/***/
|
|
VK_6: 54,
|
|
|
|
/***/
|
|
VK_7: 55,
|
|
|
|
/***/
|
|
VK_8: 56,
|
|
|
|
/***/
|
|
VK_9: 57,
|
|
|
|
/** Colon (:) key. Requires Gecko 15.0 */
|
|
VK_COLON: 58,
|
|
|
|
/** Semicolon (;) key. */
|
|
VK_SEMICOLON: 59,
|
|
|
|
/** Less-than (<) key. Requires Gecko 15.0 */
|
|
VK_LESS_THAN: 60,
|
|
|
|
/** Equals (=) key. */
|
|
VK_EQUALS: 61,
|
|
|
|
/** Greater-than (>) key. Requires Gecko 15.0 */
|
|
VK_GREATER_THAN: 62,
|
|
|
|
/** Question mark (?) key. Requires Gecko 15.0 */
|
|
VK_QUESTION_MARK: 63,
|
|
|
|
/** Atmark (@) key. Requires Gecko 15.0 */
|
|
VK_AT: 64,
|
|
|
|
/***/
|
|
VK_A: 65,
|
|
|
|
/***/
|
|
VK_B: 66,
|
|
|
|
/***/
|
|
VK_C: 67,
|
|
|
|
/***/
|
|
VK_D: 68,
|
|
|
|
/***/
|
|
VK_E: 69,
|
|
|
|
/***/
|
|
VK_F: 70,
|
|
|
|
/***/
|
|
VK_G: 71,
|
|
|
|
/***/
|
|
VK_H: 72,
|
|
|
|
/***/
|
|
VK_I: 73,
|
|
|
|
/***/
|
|
VK_J: 74,
|
|
|
|
/***/
|
|
VK_K: 75,
|
|
|
|
/***/
|
|
VK_L: 76,
|
|
|
|
/***/
|
|
VK_M: 77,
|
|
|
|
/***/
|
|
VK_N: 78,
|
|
|
|
/***/
|
|
VK_O: 79,
|
|
|
|
/***/
|
|
VK_P: 80,
|
|
|
|
/***/
|
|
VK_Q: 81,
|
|
|
|
/***/
|
|
VK_R: 82,
|
|
|
|
/***/
|
|
VK_S: 83,
|
|
|
|
/***/
|
|
VK_T: 84,
|
|
|
|
/***/
|
|
VK_U: 85,
|
|
|
|
/***/
|
|
VK_V: 86,
|
|
|
|
/***/
|
|
VK_W: 87,
|
|
|
|
/***/
|
|
VK_X: 88,
|
|
|
|
/***/
|
|
VK_Y: 89,
|
|
|
|
/***/
|
|
VK_Z: 90,
|
|
|
|
/***/
|
|
VK_CONTEXT_MENU: 93,
|
|
|
|
/** 0 on the numeric keypad. */
|
|
VK_NUMPAD0: 96,
|
|
|
|
/** 1 on the numeric keypad. */
|
|
VK_NUMPAD1: 97,
|
|
|
|
/** 2 on the numeric keypad. */
|
|
VK_NUMPAD2: 98,
|
|
|
|
/** 3 on the numeric keypad. */
|
|
VK_NUMPAD3: 99,
|
|
|
|
/** 4 on the numeric keypad. */
|
|
VK_NUMPAD4: 100,
|
|
|
|
/** 5 on the numeric keypad. */
|
|
VK_NUMPAD5: 101,
|
|
|
|
/** 6 on the numeric keypad. */
|
|
VK_NUMPAD6: 102,
|
|
|
|
/** 7 on the numeric keypad. */
|
|
VK_NUMPAD7: 103,
|
|
|
|
/** 8 on the numeric keypad. */
|
|
VK_NUMPAD8: 104,
|
|
|
|
/** 9 on the numeric keypad. */
|
|
VK_NUMPAD9: 105,
|
|
|
|
/** * on the numeric keypad. */
|
|
VK_MULTIPLY: 106,
|
|
|
|
/** + on the numeric keypad. */
|
|
VK_ADD: 107,
|
|
|
|
/***/
|
|
VK_SEPARATOR: 108,
|
|
|
|
/** - on the numeric keypad. */
|
|
VK_SUBTRACT: 109,
|
|
|
|
/** Decimal point on the numeric keypad. */
|
|
VK_DECIMAL: 110,
|
|
|
|
/** / on the numeric keypad. */
|
|
VK_DIVIDE: 111,
|
|
|
|
/** F1 key. */
|
|
VK_F1: 112,
|
|
|
|
/** F2 key. */
|
|
VK_F2: 113,
|
|
|
|
/** F3 key. */
|
|
VK_F3: 114,
|
|
|
|
/** F4 key. */
|
|
VK_F4: 115,
|
|
|
|
/** F5 key. */
|
|
VK_F5: 116,
|
|
|
|
/** F6 key. */
|
|
VK_F6: 117,
|
|
|
|
/** F7 key. */
|
|
VK_F7: 118,
|
|
|
|
/** F8 key. */
|
|
VK_F8: 119,
|
|
|
|
/** F9 key. */
|
|
VK_F9: 120,
|
|
|
|
/** F10 key. */
|
|
VK_F10: 121,
|
|
|
|
/** F11 key. */
|
|
VK_F11: 122,
|
|
|
|
/** F12 key. */
|
|
VK_F12: 123,
|
|
|
|
/** F13 key. */
|
|
VK_F13: 124,
|
|
|
|
/** F14 key. */
|
|
VK_F14: 125,
|
|
|
|
/** F15 key. */
|
|
VK_F15: 126,
|
|
|
|
/** F16 key. */
|
|
VK_F16: 127,
|
|
|
|
/** F17 key. */
|
|
VK_F17: 128,
|
|
|
|
/** F18 key. */
|
|
VK_F18: 129,
|
|
|
|
/** F19 key. */
|
|
VK_F19: 130,
|
|
|
|
/** F20 key. */
|
|
VK_F20: 131,
|
|
|
|
/** F21 key. */
|
|
VK_F21: 132,
|
|
|
|
/** F22 key. */
|
|
VK_F22: 133,
|
|
|
|
/** F23 key. */
|
|
VK_F23: 134,
|
|
|
|
/** F24 key. */
|
|
VK_F24: 135,
|
|
|
|
/** Num Lock key. */
|
|
VK_NUM_LOCK: 144,
|
|
|
|
/** Scroll Lock key. */
|
|
VK_SCROLL_LOCK: 145,
|
|
|
|
/** Circumflex (^) key. Requires Gecko 15.0 */
|
|
VK_CIRCUMFLEX: 160,
|
|
|
|
/** Exclamation (!) key. Requires Gecko 15.0 */
|
|
VK_EXCLAMATION: 161,
|
|
|
|
/** Double quote () key. Requires Gecko 15.0 */
|
|
VK_DOUBLE_QUOTE: 162,
|
|
|
|
/** Hash (#) key. Requires Gecko 15.0 */
|
|
VK_HASH: 163,
|
|
|
|
/** Dollar sign ($) key. Requires Gecko 15.0 */
|
|
VK_DOLLAR: 164,
|
|
|
|
/** Percent (%) key. Requires Gecko 15.0 */
|
|
VK_PERCENT: 165,
|
|
|
|
/** Ampersand (&) key. Requires Gecko 15.0 */
|
|
VK_AMPERSAND: 166,
|
|
|
|
/** Underscore (_) key. Requires Gecko 15.0 */
|
|
VK_UNDERSCORE: 167,
|
|
|
|
/** Open parenthesis (() key. Requires Gecko 15.0 */
|
|
VK_OPEN_PAREN: 168,
|
|
|
|
/** Close parenthesis ()) key. Requires Gecko 15.0 */
|
|
VK_CLOSE_PAREN: 169,
|
|
|
|
/* Asterisk (*) key. Requires Gecko 15.0 */
|
|
VK_ASTERISK: 170,
|
|
|
|
/** Plus (+) key. Requires Gecko 15.0 */
|
|
VK_PLUS: 171,
|
|
|
|
/** Pipe (|) key. Requires Gecko 15.0 */
|
|
VK_PIPE: 172,
|
|
|
|
/** Hyphen-US/docs/Minus (-) key. Requires Gecko 15.0 */
|
|
VK_HYPHEN_MINUS: 173,
|
|
|
|
/** Open curly bracket ({) key. Requires Gecko 15.0 */
|
|
VK_OPEN_CURLY_BRACKET: 174,
|
|
|
|
/** Close curly bracket (}) key. Requires Gecko 15.0 */
|
|
VK_CLOSE_CURLY_BRACKET: 175,
|
|
|
|
/** Tilde (~) key. Requires Gecko 15.0 */
|
|
VK_TILDE: 176,
|
|
|
|
/** Comma (,) key. */
|
|
VK_COMMA: 188,
|
|
|
|
/** Period (.) key. */
|
|
VK_PERIOD: 190,
|
|
|
|
/** Slash (/) key. */
|
|
VK_SLASH: 191,
|
|
|
|
/** Back tick (`) key. */
|
|
VK_BACK_QUOTE: 192,
|
|
|
|
/** Open square bracket ([) key. */
|
|
VK_OPEN_BRACKET: 219,
|
|
|
|
/** Back slash (\) key. */
|
|
VK_BACK_SLASH: 220,
|
|
|
|
/** Close square bracket (]) key. */
|
|
VK_CLOSE_BRACKET: 221,
|
|
|
|
/** Quote (''') key. */
|
|
VK_QUOTE: 222,
|
|
|
|
/** Meta key on Linux, Command key on Mac. */
|
|
VK_META: 224,
|
|
|
|
/** AltGr key on Linux. Requires Gecko 15.0 */
|
|
VK_ALTGR: 225,
|
|
|
|
/** Windows logo key on Windows. Or Super or Hyper key on Linux. Requires Gecko 15.0 */
|
|
VK_WIN: 91,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_KANA: 21,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_HANGUL: 21,
|
|
|
|
/** 英数 key on Japanese Mac keyboard. Requires Gecko 15.0 */
|
|
VK_EISU: 22,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_JUNJA: 23,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_FINAL: 24,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_HANJA: 25,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_KANJI: 25,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_CONVERT: 28,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_NONCONVERT: 29,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_ACCEPT: 30,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_MODECHANGE: 31,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_SELECT: 41,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_PRINT: 42,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_EXECUTE: 43,
|
|
|
|
/** Linux support for this keycode was added in Gecko 4.0. */
|
|
VK_SLEEP: 95
|
|
};
|
|
var BACKENDS = {
|
|
"hex": Hex,
|
|
"rect": Rect,
|
|
"tile": Tile,
|
|
"tile-gl": TileGL,
|
|
"term": Term
|
|
};
|
|
var DEFAULT_OPTIONS = {
|
|
width: DEFAULT_WIDTH,
|
|
height: DEFAULT_HEIGHT,
|
|
transpose: false,
|
|
layout: "rect",
|
|
fontSize: 15,
|
|
spacing: 1,
|
|
border: 0,
|
|
forceSquareRatio: false,
|
|
fontFamily: "monospace",
|
|
fontStyle: "",
|
|
fg: "#ccc",
|
|
bg: "#000",
|
|
tileWidth: 32,
|
|
tileHeight: 32,
|
|
tileMap: {},
|
|
tileSet: null,
|
|
tileColorize: false
|
|
};
|
|
/**
|
|
* @class Visual map display
|
|
*/
|
|
|
|
var Display =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Display(options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
this._data = {};
|
|
this._dirty = false; // false = nothing, true = all, object = dirty cells
|
|
|
|
this._options = {};
|
|
options = Object.assign({}, DEFAULT_OPTIONS, options);
|
|
this.setOptions(options);
|
|
this.DEBUG = this.DEBUG.bind(this);
|
|
this._tick = this._tick.bind(this);
|
|
|
|
this._backend.schedule(this._tick);
|
|
}
|
|
/**
|
|
* Debug helper, ideal as a map generator callback. Always bound to this.
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {int} what
|
|
*/
|
|
|
|
|
|
var _proto9 = Display.prototype;
|
|
|
|
_proto9.DEBUG = function DEBUG(x, y, what) {
|
|
var colors = [this._options.bg, this._options.fg];
|
|
this.draw(x, y, null, null, colors[what % colors.length]);
|
|
};
|
|
/**
|
|
* Clear the whole display (cover it with background color)
|
|
*/
|
|
|
|
|
|
_proto9.clear = function clear() {
|
|
this._data = {};
|
|
this._dirty = true;
|
|
};
|
|
/**
|
|
* @see ROT.Display
|
|
*/
|
|
|
|
|
|
_proto9.setOptions = function setOptions(options) {
|
|
Object.assign(this._options, options);
|
|
|
|
if (options.width || options.height || options.fontSize || options.fontFamily || options.spacing || options.layout) {
|
|
if (options.layout) {
|
|
var ctor = BACKENDS[options.layout];
|
|
this._backend = new ctor();
|
|
}
|
|
|
|
this._backend.setOptions(this._options);
|
|
|
|
this._dirty = true;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Returns currently set options
|
|
*/
|
|
|
|
|
|
_proto9.getOptions = function getOptions() {
|
|
return this._options;
|
|
};
|
|
/**
|
|
* Returns the DOM node of this display
|
|
*/
|
|
|
|
|
|
_proto9.getContainer = function getContainer() {
|
|
return this._backend.getContainer();
|
|
};
|
|
/**
|
|
* Compute the maximum width/height to fit into a set of given constraints
|
|
* @param {int} availWidth Maximum allowed pixel width
|
|
* @param {int} availHeight Maximum allowed pixel height
|
|
* @returns {int[2]} cellWidth,cellHeight
|
|
*/
|
|
|
|
|
|
_proto9.computeSize = function computeSize(availWidth, availHeight) {
|
|
return this._backend.computeSize(availWidth, availHeight);
|
|
};
|
|
/**
|
|
* Compute the maximum font size to fit into a set of given constraints
|
|
* @param {int} availWidth Maximum allowed pixel width
|
|
* @param {int} availHeight Maximum allowed pixel height
|
|
* @returns {int} fontSize
|
|
*/
|
|
|
|
|
|
_proto9.computeFontSize = function computeFontSize(availWidth, availHeight) {
|
|
return this._backend.computeFontSize(availWidth, availHeight);
|
|
};
|
|
|
|
_proto9.computeTileSize = function computeTileSize(availWidth, availHeight) {
|
|
var width = Math.floor(availWidth / this._options.width);
|
|
var height = Math.floor(availHeight / this._options.height);
|
|
return [width, height];
|
|
};
|
|
/**
|
|
* Convert a DOM event (mouse or touch) to map coordinates. Uses first touch for multi-touch.
|
|
* @param {Event} e event
|
|
* @returns {int[2]} -1 for values outside of the canvas
|
|
*/
|
|
|
|
|
|
_proto9.eventToPosition = function eventToPosition(e) {
|
|
var x, y;
|
|
|
|
if ("touches" in e) {
|
|
x = e.touches[0].clientX;
|
|
y = e.touches[0].clientY;
|
|
} else {
|
|
x = e.clientX;
|
|
y = e.clientY;
|
|
}
|
|
|
|
return this._backend.eventToPosition(x, y);
|
|
};
|
|
/**
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {string || string[]} ch One or more chars (will be overlapping themselves)
|
|
* @param {string} [fg] foreground color
|
|
* @param {string} [bg] background color
|
|
*/
|
|
|
|
|
|
_proto9.draw = function draw(x, y, ch, fg, bg) {
|
|
if (!fg) {
|
|
fg = this._options.fg;
|
|
}
|
|
|
|
if (!bg) {
|
|
bg = this._options.bg;
|
|
}
|
|
|
|
var key = x + "," + y;
|
|
this._data[key] = [x, y, ch, fg, bg];
|
|
|
|
if (this._dirty === true) {
|
|
return;
|
|
} // will already redraw everything
|
|
|
|
|
|
if (!this._dirty) {
|
|
this._dirty = {};
|
|
} // first!
|
|
|
|
|
|
this._dirty[key] = true;
|
|
};
|
|
/**
|
|
* Draws a text at given position. Optionally wraps at a maximum length. Currently does not work with hex layout.
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {string} text May contain color/background format specifiers, %c{name}/%b{name}, both optional. %c{}/%b{} resets to default.
|
|
* @param {int} [maxWidth] wrap at what width?
|
|
* @returns {int} lines drawn
|
|
*/
|
|
|
|
|
|
_proto9.drawText = function drawText(x, y, text, maxWidth) {
|
|
var fg = null;
|
|
var bg = null;
|
|
var cx = x;
|
|
var cy = y;
|
|
var lines = 1;
|
|
|
|
if (!maxWidth) {
|
|
maxWidth = this._options.width - x;
|
|
}
|
|
|
|
var tokens = tokenize(text, maxWidth);
|
|
|
|
while (tokens.length) {
|
|
// interpret tokenized opcode stream
|
|
var token = tokens.shift();
|
|
|
|
switch (token.type) {
|
|
case TYPE_TEXT:
|
|
var isSpace = false,
|
|
isPrevSpace = false,
|
|
isFullWidth = false,
|
|
isPrevFullWidth = false;
|
|
|
|
for (var i = 0; i < token.value.length; i++) {
|
|
var cc = token.value.charCodeAt(i);
|
|
var c = token.value.charAt(i); // Assign to `true` when the current char is full-width.
|
|
|
|
isFullWidth = cc > 0xff00 && cc < 0xff61 || cc > 0xffdc && cc < 0xffe8 || cc > 0xffee; // Current char is space, whatever full-width or half-width both are OK.
|
|
|
|
isSpace = c.charCodeAt(0) == 0x20 || c.charCodeAt(0) == 0x3000; // The previous char is full-width and
|
|
// current char is nether half-width nor a space.
|
|
|
|
if (isPrevFullWidth && !isFullWidth && !isSpace) {
|
|
cx++;
|
|
} // add an extra position
|
|
// The current char is full-width and
|
|
// the previous char is not a space.
|
|
|
|
|
|
if (isFullWidth && !isPrevSpace) {
|
|
cx++;
|
|
} // add an extra position
|
|
|
|
|
|
this.draw(cx++, cy, c, fg, bg);
|
|
isPrevSpace = isSpace;
|
|
isPrevFullWidth = isFullWidth;
|
|
}
|
|
|
|
break;
|
|
|
|
case TYPE_FG:
|
|
fg = token.value || null;
|
|
break;
|
|
|
|
case TYPE_BG:
|
|
bg = token.value || null;
|
|
break;
|
|
|
|
case TYPE_NEWLINE:
|
|
cx = x;
|
|
cy++;
|
|
lines++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return lines;
|
|
};
|
|
/**
|
|
* Timer tick: update dirty parts
|
|
*/
|
|
|
|
|
|
_proto9._tick = function _tick() {
|
|
this._backend.schedule(this._tick);
|
|
|
|
if (!this._dirty) {
|
|
return;
|
|
}
|
|
|
|
if (this._dirty === true) {
|
|
// draw all
|
|
this._backend.clear();
|
|
|
|
for (var id in this._data) {
|
|
this._draw(id, false);
|
|
} // redraw cached data
|
|
|
|
} else {
|
|
// draw only dirty
|
|
for (var key in this._dirty) {
|
|
this._draw(key, true);
|
|
}
|
|
}
|
|
|
|
this._dirty = false;
|
|
};
|
|
/**
|
|
* @param {string} key What to draw
|
|
* @param {bool} clearBefore Is it necessary to clean before?
|
|
*/
|
|
|
|
|
|
_proto9._draw = function _draw(key, clearBefore) {
|
|
var data = this._data[key];
|
|
|
|
if (data[4] != this._options.bg) {
|
|
clearBefore = true;
|
|
}
|
|
|
|
this._backend.draw(data, clearBefore);
|
|
};
|
|
|
|
return Display;
|
|
}();
|
|
|
|
Display.Rect = Rect;
|
|
Display.Hex = Hex;
|
|
Display.Tile = Tile;
|
|
Display.TileGL = TileGL;
|
|
Display.Term = Term;
|
|
/**
|
|
* @class (Markov process)-based string generator.
|
|
* Copied from a <a href="http://www.roguebasin.roguelikedevelopment.org/index.php?title=Names_from_a_high_order_Markov_Process_and_a_simplified_Katz_back-off_scheme">RogueBasin article</a>.
|
|
* Offers configurable order and prior.
|
|
*/
|
|
|
|
var StringGenerator =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function StringGenerator(options) {
|
|
this._options = {
|
|
words: false,
|
|
order: 3,
|
|
prior: 0.001
|
|
};
|
|
Object.assign(this._options, options);
|
|
this._boundary = String.fromCharCode(0);
|
|
this._suffix = this._boundary;
|
|
this._prefix = [];
|
|
|
|
for (var i = 0; i < this._options.order; i++) {
|
|
this._prefix.push(this._boundary);
|
|
}
|
|
|
|
this._priorValues = {};
|
|
this._priorValues[this._boundary] = this._options.prior;
|
|
this._data = {};
|
|
}
|
|
/**
|
|
* Remove all learning data
|
|
*/
|
|
|
|
|
|
var _proto10 = StringGenerator.prototype;
|
|
|
|
_proto10.clear = function clear() {
|
|
this._data = {};
|
|
this._priorValues = {};
|
|
};
|
|
/**
|
|
* @returns {string} Generated string
|
|
*/
|
|
|
|
|
|
_proto10.generate = function generate() {
|
|
var result = [this._sample(this._prefix)];
|
|
|
|
while (result[result.length - 1] != this._boundary) {
|
|
result.push(this._sample(result));
|
|
}
|
|
|
|
return this._join(result.slice(0, -1));
|
|
};
|
|
/**
|
|
* Observe (learn) a string from a training set
|
|
*/
|
|
|
|
|
|
_proto10.observe = function observe(string) {
|
|
var tokens = this._split(string);
|
|
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
this._priorValues[tokens[i]] = this._options.prior;
|
|
}
|
|
|
|
tokens = this._prefix.concat(tokens).concat(this._suffix);
|
|
/* add boundary symbols */
|
|
|
|
for (var _i2 = this._options.order; _i2 < tokens.length; _i2++) {
|
|
var context = tokens.slice(_i2 - this._options.order, _i2);
|
|
var event = tokens[_i2];
|
|
|
|
for (var j = 0; j < context.length; j++) {
|
|
var subcontext = context.slice(j);
|
|
|
|
this._observeEvent(subcontext, event);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto10.getStats = function getStats() {
|
|
var parts = [];
|
|
var priorCount = Object.keys(this._priorValues).length;
|
|
priorCount--; // boundary
|
|
|
|
parts.push("distinct samples: " + priorCount);
|
|
var dataCount = Object.keys(this._data).length;
|
|
var eventCount = 0;
|
|
|
|
for (var p in this._data) {
|
|
eventCount += Object.keys(this._data[p]).length;
|
|
}
|
|
|
|
parts.push("dictionary size (contexts): " + dataCount);
|
|
parts.push("dictionary size (events): " + eventCount);
|
|
return parts.join(", ");
|
|
};
|
|
/**
|
|
* @param {string}
|
|
* @returns {string[]}
|
|
*/
|
|
|
|
|
|
_proto10._split = function _split(str) {
|
|
return str.split(this._options.words ? /\s+/ : "");
|
|
};
|
|
/**
|
|
* @param {string[]}
|
|
* @returns {string}
|
|
*/
|
|
|
|
|
|
_proto10._join = function _join(arr) {
|
|
return arr.join(this._options.words ? " " : "");
|
|
};
|
|
/**
|
|
* @param {string[]} context
|
|
* @param {string} event
|
|
*/
|
|
|
|
|
|
_proto10._observeEvent = function _observeEvent(context, event) {
|
|
var key = this._join(context);
|
|
|
|
if (!(key in this._data)) {
|
|
this._data[key] = {};
|
|
}
|
|
|
|
var data = this._data[key];
|
|
|
|
if (!(event in data)) {
|
|
data[event] = 0;
|
|
}
|
|
|
|
data[event]++;
|
|
};
|
|
/**
|
|
* @param {string[]}
|
|
* @returns {string}
|
|
*/
|
|
|
|
|
|
_proto10._sample = function _sample(context) {
|
|
context = this._backoff(context);
|
|
|
|
var key = this._join(context);
|
|
|
|
var data = this._data[key];
|
|
var available = {};
|
|
|
|
if (this._options.prior) {
|
|
for (var event in this._priorValues) {
|
|
available[event] = this._priorValues[event];
|
|
}
|
|
|
|
for (var _event in data) {
|
|
available[_event] += data[_event];
|
|
}
|
|
} else {
|
|
available = data;
|
|
}
|
|
|
|
return RNG$1.getWeightedValue(available);
|
|
};
|
|
/**
|
|
* @param {string[]}
|
|
* @returns {string[]}
|
|
*/
|
|
|
|
|
|
_proto10._backoff = function _backoff(context) {
|
|
if (context.length > this._options.order) {
|
|
context = context.slice(-this._options.order);
|
|
} else if (context.length < this._options.order) {
|
|
context = this._prefix.slice(0, this._options.order - context.length).concat(context);
|
|
}
|
|
|
|
while (!(this._join(context) in this._data) && context.length > 0) {
|
|
context = context.slice(1);
|
|
}
|
|
|
|
return context;
|
|
};
|
|
|
|
return StringGenerator;
|
|
}();
|
|
|
|
var EventQueue =
|
|
/*#__PURE__*/
|
|
function () {
|
|
/**
|
|
* @class Generic event queue: stores events and retrieves them based on their time
|
|
*/
|
|
function EventQueue() {
|
|
this._time = 0;
|
|
this._events = [];
|
|
this._eventTimes = [];
|
|
}
|
|
/**
|
|
* @returns {number} Elapsed time
|
|
*/
|
|
|
|
|
|
var _proto11 = EventQueue.prototype;
|
|
|
|
_proto11.getTime = function getTime() {
|
|
return this._time;
|
|
};
|
|
/**
|
|
* Clear all scheduled events
|
|
*/
|
|
|
|
|
|
_proto11.clear = function clear() {
|
|
this._events = [];
|
|
this._eventTimes = [];
|
|
return this;
|
|
};
|
|
/**
|
|
* @param {?} event
|
|
* @param {number} time
|
|
*/
|
|
|
|
|
|
_proto11.add = function add(event, time) {
|
|
var index = this._events.length;
|
|
|
|
for (var i = 0; i < this._eventTimes.length; i++) {
|
|
if (this._eventTimes[i] > time) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
this._events.splice(index, 0, event);
|
|
|
|
this._eventTimes.splice(index, 0, time);
|
|
};
|
|
/**
|
|
* Locates the nearest event, advances time if necessary. Returns that event and removes it from the queue.
|
|
* @returns {? || null} The event previously added by addEvent, null if no event available
|
|
*/
|
|
|
|
|
|
_proto11.get = function get() {
|
|
if (!this._events.length) {
|
|
return null;
|
|
}
|
|
|
|
var time = this._eventTimes.splice(0, 1)[0];
|
|
|
|
if (time > 0) {
|
|
/* advance */
|
|
this._time += time;
|
|
|
|
for (var i = 0; i < this._eventTimes.length; i++) {
|
|
this._eventTimes[i] -= time;
|
|
}
|
|
}
|
|
|
|
return this._events.splice(0, 1)[0];
|
|
};
|
|
/**
|
|
* Get the time associated with the given event
|
|
* @param {?} event
|
|
* @returns {number} time
|
|
*/
|
|
|
|
|
|
_proto11.getEventTime = function getEventTime(event) {
|
|
var index = this._events.indexOf(event);
|
|
|
|
if (index == -1) {
|
|
return undefined;
|
|
}
|
|
|
|
return this._eventTimes[index];
|
|
};
|
|
/**
|
|
* Remove an event from the queue
|
|
* @param {?} event
|
|
* @returns {bool} success?
|
|
*/
|
|
|
|
|
|
_proto11.remove = function remove(event) {
|
|
var index = this._events.indexOf(event);
|
|
|
|
if (index == -1) {
|
|
return false;
|
|
}
|
|
|
|
this._remove(index);
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Remove an event from the queue
|
|
* @param {int} index
|
|
*/
|
|
_proto11._remove = function _remove(index) {
|
|
this._events.splice(index, 1);
|
|
|
|
this._eventTimes.splice(index, 1);
|
|
};
|
|
|
|
return EventQueue;
|
|
}();
|
|
|
|
var Scheduler =
|
|
/*#__PURE__*/
|
|
function () {
|
|
/**
|
|
* @class Abstract scheduler
|
|
*/
|
|
function Scheduler() {
|
|
this._queue = new EventQueue();
|
|
this._repeat = [];
|
|
this._current = null;
|
|
}
|
|
/**
|
|
* @see ROT.EventQueue#getTime
|
|
*/
|
|
|
|
|
|
var _proto12 = Scheduler.prototype;
|
|
|
|
_proto12.getTime = function getTime() {
|
|
return this._queue.getTime();
|
|
};
|
|
/**
|
|
* @param {?} item
|
|
* @param {bool} repeat
|
|
*/
|
|
|
|
|
|
_proto12.add = function add(item, repeat) {
|
|
if (repeat) {
|
|
this._repeat.push(item);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Get the time the given item is scheduled for
|
|
* @param {?} item
|
|
* @returns {number} time
|
|
*/
|
|
|
|
|
|
_proto12.getTimeOf = function getTimeOf(item) {
|
|
return this._queue.getEventTime(item);
|
|
};
|
|
/**
|
|
* Clear all items
|
|
*/
|
|
|
|
|
|
_proto12.clear = function clear() {
|
|
this._queue.clear();
|
|
|
|
this._repeat = [];
|
|
this._current = null;
|
|
return this;
|
|
};
|
|
/**
|
|
* Remove a previously added item
|
|
* @param {?} item
|
|
* @returns {bool} successful?
|
|
*/
|
|
|
|
|
|
_proto12.remove = function remove(item) {
|
|
var result = this._queue.remove(item);
|
|
|
|
var index = this._repeat.indexOf(item);
|
|
|
|
if (index != -1) {
|
|
this._repeat.splice(index, 1);
|
|
}
|
|
|
|
if (this._current == item) {
|
|
this._current = null;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
/**
|
|
* Schedule next item
|
|
* @returns {?}
|
|
*/
|
|
|
|
|
|
_proto12.next = function next() {
|
|
this._current = this._queue.get();
|
|
return this._current;
|
|
};
|
|
|
|
return Scheduler;
|
|
}();
|
|
/**
|
|
* @class Simple fair scheduler (round-robin style)
|
|
*/
|
|
|
|
|
|
var Simple =
|
|
/*#__PURE__*/
|
|
function (_Scheduler) {
|
|
_inheritsLoose(Simple, _Scheduler);
|
|
|
|
function Simple() {
|
|
return _Scheduler.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto13 = Simple.prototype;
|
|
|
|
_proto13.add = function add(item, repeat) {
|
|
this._queue.add(item, 0);
|
|
|
|
return _Scheduler.prototype.add.call(this, item, repeat);
|
|
};
|
|
|
|
_proto13.next = function next() {
|
|
if (this._current !== null && this._repeat.indexOf(this._current) != -1) {
|
|
this._queue.add(this._current, 0);
|
|
}
|
|
|
|
return _Scheduler.prototype.next.call(this);
|
|
};
|
|
|
|
return Simple;
|
|
}(Scheduler);
|
|
/**
|
|
* @class Speed-based scheduler
|
|
*/
|
|
|
|
|
|
var Speed =
|
|
/*#__PURE__*/
|
|
function (_Scheduler2) {
|
|
_inheritsLoose(Speed, _Scheduler2);
|
|
|
|
function Speed() {
|
|
return _Scheduler2.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto14 = Speed.prototype;
|
|
|
|
/**
|
|
* @param {object} item anything with "getSpeed" method
|
|
* @param {bool} repeat
|
|
* @param {number} [time=1/item.getSpeed()]
|
|
* @see ROT.Scheduler#add
|
|
*/
|
|
_proto14.add = function add(item, repeat, time) {
|
|
this._queue.add(item, time !== undefined ? time : 1 / item.getSpeed());
|
|
|
|
return _Scheduler2.prototype.add.call(this, item, repeat);
|
|
};
|
|
/**
|
|
* @see ROT.Scheduler#next
|
|
*/
|
|
|
|
|
|
_proto14.next = function next() {
|
|
if (this._current && this._repeat.indexOf(this._current) != -1) {
|
|
this._queue.add(this._current, 1 / this._current.getSpeed());
|
|
}
|
|
|
|
return _Scheduler2.prototype.next.call(this);
|
|
};
|
|
|
|
return Speed;
|
|
}(Scheduler);
|
|
/**
|
|
* @class Action-based scheduler
|
|
* @augments ROT.Scheduler
|
|
*/
|
|
|
|
|
|
var Action =
|
|
/*#__PURE__*/
|
|
function (_Scheduler3) {
|
|
_inheritsLoose(Action, _Scheduler3);
|
|
|
|
function Action() {
|
|
var _this9;
|
|
|
|
_this9 = _Scheduler3.call(this) || this;
|
|
_this9._defaultDuration = 1;
|
|
/* for newly added */
|
|
|
|
_this9._duration = _this9._defaultDuration;
|
|
/* for this._current */
|
|
|
|
return _this9;
|
|
}
|
|
/**
|
|
* @param {object} item
|
|
* @param {bool} repeat
|
|
* @param {number} [time=1]
|
|
* @see ROT.Scheduler#add
|
|
*/
|
|
|
|
|
|
var _proto15 = Action.prototype;
|
|
|
|
_proto15.add = function add(item, repeat, time) {
|
|
this._queue.add(item, time || this._defaultDuration);
|
|
|
|
return _Scheduler3.prototype.add.call(this, item, repeat);
|
|
};
|
|
|
|
_proto15.clear = function clear() {
|
|
this._duration = this._defaultDuration;
|
|
return _Scheduler3.prototype.clear.call(this);
|
|
};
|
|
|
|
_proto15.remove = function remove(item) {
|
|
if (item == this._current) {
|
|
this._duration = this._defaultDuration;
|
|
}
|
|
|
|
return _Scheduler3.prototype.remove.call(this, item);
|
|
};
|
|
/**
|
|
* @see ROT.Scheduler#next
|
|
*/
|
|
|
|
|
|
_proto15.next = function next() {
|
|
if (this._current !== null && this._repeat.indexOf(this._current) != -1) {
|
|
this._queue.add(this._current, this._duration || this._defaultDuration);
|
|
|
|
this._duration = this._defaultDuration;
|
|
}
|
|
|
|
return _Scheduler3.prototype.next.call(this);
|
|
};
|
|
/**
|
|
* Set duration for the active item
|
|
*/
|
|
|
|
|
|
_proto15.setDuration = function setDuration(time) {
|
|
if (this._current) {
|
|
this._duration = time;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
return Action;
|
|
}(Scheduler);
|
|
|
|
var index = {
|
|
Simple: Simple,
|
|
Speed: Speed,
|
|
Action: Action
|
|
};
|
|
|
|
var FOV =
|
|
/*#__PURE__*/
|
|
function () {
|
|
/**
|
|
* @class Abstract FOV algorithm
|
|
* @param {function} lightPassesCallback Does the light pass through x,y?
|
|
* @param {object} [options]
|
|
* @param {int} [options.topology=8] 4/6/8
|
|
*/
|
|
function FOV(lightPassesCallback, options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
this._lightPasses = lightPassesCallback;
|
|
this._options = Object.assign({
|
|
topology: 8
|
|
}, options);
|
|
}
|
|
/**
|
|
* Return all neighbors in a concentric ring
|
|
* @param {int} cx center-x
|
|
* @param {int} cy center-y
|
|
* @param {int} r range
|
|
*/
|
|
|
|
|
|
var _proto16 = FOV.prototype;
|
|
|
|
_proto16._getCircle = function _getCircle(cx, cy, r) {
|
|
var result = [];
|
|
var dirs, countFactor, startOffset;
|
|
|
|
switch (this._options.topology) {
|
|
case 4:
|
|
countFactor = 1;
|
|
startOffset = [0, 1];
|
|
dirs = [DIRS[8][7], DIRS[8][1], DIRS[8][3], DIRS[8][5]];
|
|
break;
|
|
|
|
case 6:
|
|
dirs = DIRS[6];
|
|
countFactor = 1;
|
|
startOffset = [-1, 1];
|
|
break;
|
|
|
|
case 8:
|
|
dirs = DIRS[4];
|
|
countFactor = 2;
|
|
startOffset = [-1, 1];
|
|
break;
|
|
|
|
default:
|
|
throw new Error("Incorrect topology for FOV computation");
|
|
break;
|
|
}
|
|
/* starting neighbor */
|
|
|
|
|
|
var x = cx + startOffset[0] * r;
|
|
var y = cy + startOffset[1] * r;
|
|
/* circle */
|
|
|
|
for (var i = 0; i < dirs.length; i++) {
|
|
for (var j = 0; j < r * countFactor; j++) {
|
|
result.push([x, y]);
|
|
x += dirs[i][0];
|
|
y += dirs[i][1];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
return FOV;
|
|
}();
|
|
/**
|
|
* @class Discrete shadowcasting algorithm. Obsoleted by Precise shadowcasting.
|
|
* @augments ROT.FOV
|
|
*/
|
|
|
|
|
|
var DiscreteShadowcasting =
|
|
/*#__PURE__*/
|
|
function (_FOV) {
|
|
_inheritsLoose(DiscreteShadowcasting, _FOV);
|
|
|
|
function DiscreteShadowcasting() {
|
|
return _FOV.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto17 = DiscreteShadowcasting.prototype;
|
|
|
|
_proto17.compute = function compute(x, y, R, callback) {
|
|
/* this place is always visible */
|
|
callback(x, y, 0, 1);
|
|
/* standing in a dark place. FIXME is this a good idea? */
|
|
|
|
if (!this._lightPasses(x, y)) {
|
|
return;
|
|
}
|
|
/* start and end angles */
|
|
|
|
|
|
var DATA = [];
|
|
var A, B, cx, cy, blocks;
|
|
/* analyze surrounding cells in concentric rings, starting from the center */
|
|
|
|
for (var r = 1; r <= R; r++) {
|
|
var neighbors = this._getCircle(x, y, r);
|
|
|
|
var angle = 360 / neighbors.length;
|
|
|
|
for (var i = 0; i < neighbors.length; i++) {
|
|
cx = neighbors[i][0];
|
|
cy = neighbors[i][1];
|
|
A = angle * (i - 0.5);
|
|
B = A + angle;
|
|
blocks = !this._lightPasses(cx, cy);
|
|
|
|
if (this._visibleCoords(Math.floor(A), Math.ceil(B), blocks, DATA)) {
|
|
callback(cx, cy, r, 1);
|
|
}
|
|
|
|
if (DATA.length == 2 && DATA[0] == 0 && DATA[1] == 360) {
|
|
return;
|
|
}
|
|
/* cutoff? */
|
|
|
|
}
|
|
/* for all cells in this ring */
|
|
|
|
}
|
|
/* for all rings */
|
|
|
|
};
|
|
/**
|
|
* @param {int} A start angle
|
|
* @param {int} B end angle
|
|
* @param {bool} blocks Does current cell block visibility?
|
|
* @param {int[][]} DATA shadowed angle pairs
|
|
*/
|
|
|
|
|
|
_proto17._visibleCoords = function _visibleCoords(A, B, blocks, DATA) {
|
|
if (A < 0) {
|
|
var v1 = this._visibleCoords(0, B, blocks, DATA);
|
|
|
|
var v2 = this._visibleCoords(360 + A, 360, blocks, DATA);
|
|
|
|
return v1 || v2;
|
|
}
|
|
|
|
var index = 0;
|
|
|
|
while (index < DATA.length && DATA[index] < A) {
|
|
index++;
|
|
}
|
|
|
|
if (index == DATA.length) {
|
|
/* completely new shadow */
|
|
if (blocks) {
|
|
DATA.push(A, B);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var count = 0;
|
|
|
|
if (index % 2) {
|
|
/* this shadow starts in an existing shadow, or within its ending boundary */
|
|
while (index < DATA.length && DATA[index] < B) {
|
|
index++;
|
|
count++;
|
|
}
|
|
|
|
if (count == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (blocks) {
|
|
if (count % 2) {
|
|
DATA.splice(index - count, count, B);
|
|
} else {
|
|
DATA.splice(index - count, count);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
/* this shadow starts outside an existing shadow, or within a starting boundary */
|
|
while (index < DATA.length && DATA[index] < B) {
|
|
index++;
|
|
count++;
|
|
}
|
|
/* visible when outside an existing shadow, or when overlapping */
|
|
|
|
|
|
if (A == DATA[index - count] && count == 1) {
|
|
return false;
|
|
}
|
|
|
|
if (blocks) {
|
|
if (count % 2) {
|
|
DATA.splice(index - count, count, A);
|
|
} else {
|
|
DATA.splice(index - count, count, A, B);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
return DiscreteShadowcasting;
|
|
}(FOV);
|
|
/**
|
|
* @class Precise shadowcasting algorithm
|
|
* @augments ROT.FOV
|
|
*/
|
|
|
|
|
|
var PreciseShadowcasting =
|
|
/*#__PURE__*/
|
|
function (_FOV2) {
|
|
_inheritsLoose(PreciseShadowcasting, _FOV2);
|
|
|
|
function PreciseShadowcasting() {
|
|
return _FOV2.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto18 = PreciseShadowcasting.prototype;
|
|
|
|
_proto18.compute = function compute(x, y, R, callback) {
|
|
/* this place is always visible */
|
|
callback(x, y, 0, 1);
|
|
/* standing in a dark place. FIXME is this a good idea? */
|
|
|
|
if (!this._lightPasses(x, y)) {
|
|
return;
|
|
}
|
|
/* list of all shadows */
|
|
|
|
|
|
var SHADOWS = [];
|
|
var cx, cy, blocks, A1, A2, visibility;
|
|
/* analyze surrounding cells in concentric rings, starting from the center */
|
|
|
|
for (var r = 1; r <= R; r++) {
|
|
var neighbors = this._getCircle(x, y, r);
|
|
|
|
var neighborCount = neighbors.length;
|
|
|
|
for (var i = 0; i < neighborCount; i++) {
|
|
cx = neighbors[i][0];
|
|
cy = neighbors[i][1];
|
|
/* shift half-an-angle backwards to maintain consistency of 0-th cells */
|
|
|
|
A1 = [i ? 2 * i - 1 : 2 * neighborCount - 1, 2 * neighborCount];
|
|
A2 = [2 * i + 1, 2 * neighborCount];
|
|
blocks = !this._lightPasses(cx, cy);
|
|
visibility = this._checkVisibility(A1, A2, blocks, SHADOWS);
|
|
|
|
if (visibility) {
|
|
callback(cx, cy, r, visibility);
|
|
}
|
|
|
|
if (SHADOWS.length == 2 && SHADOWS[0][0] == 0 && SHADOWS[1][0] == SHADOWS[1][1]) {
|
|
return;
|
|
}
|
|
/* cutoff? */
|
|
|
|
}
|
|
/* for all cells in this ring */
|
|
|
|
}
|
|
/* for all rings */
|
|
|
|
};
|
|
/**
|
|
* @param {int[2]} A1 arc start
|
|
* @param {int[2]} A2 arc end
|
|
* @param {bool} blocks Does current arc block visibility?
|
|
* @param {int[][]} SHADOWS list of active shadows
|
|
*/
|
|
|
|
|
|
_proto18._checkVisibility = function _checkVisibility(A1, A2, blocks, SHADOWS) {
|
|
if (A1[0] > A2[0]) {
|
|
/* split into two sub-arcs */
|
|
var v1 = this._checkVisibility(A1, [A1[1], A1[1]], blocks, SHADOWS);
|
|
|
|
var v2 = this._checkVisibility([0, 1], A2, blocks, SHADOWS);
|
|
|
|
return (v1 + v2) / 2;
|
|
}
|
|
/* index1: first shadow >= A1 */
|
|
|
|
|
|
var index1 = 0,
|
|
edge1 = false;
|
|
|
|
while (index1 < SHADOWS.length) {
|
|
var old = SHADOWS[index1];
|
|
var diff = old[0] * A1[1] - A1[0] * old[1];
|
|
|
|
if (diff >= 0) {
|
|
/* old >= A1 */
|
|
if (diff == 0 && !(index1 % 2)) {
|
|
edge1 = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
index1++;
|
|
}
|
|
/* index2: last shadow <= A2 */
|
|
|
|
|
|
var index2 = SHADOWS.length,
|
|
edge2 = false;
|
|
|
|
while (index2--) {
|
|
var _old = SHADOWS[index2];
|
|
|
|
var _diff = A2[0] * _old[1] - _old[0] * A2[1];
|
|
|
|
if (_diff >= 0) {
|
|
/* old <= A2 */
|
|
if (_diff == 0 && index2 % 2) {
|
|
edge2 = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
var visible = true;
|
|
|
|
if (index1 == index2 && (edge1 || edge2)) {
|
|
/* subset of existing shadow, one of the edges match */
|
|
visible = false;
|
|
} else if (edge1 && edge2 && index1 + 1 == index2 && index2 % 2) {
|
|
/* completely equivalent with existing shadow */
|
|
visible = false;
|
|
} else if (index1 > index2 && index1 % 2) {
|
|
/* subset of existing shadow, not touching */
|
|
visible = false;
|
|
}
|
|
|
|
if (!visible) {
|
|
return 0;
|
|
}
|
|
/* fast case: not visible */
|
|
|
|
|
|
var visibleLength;
|
|
/* compute the length of visible arc, adjust list of shadows (if blocking) */
|
|
|
|
var remove = index2 - index1 + 1;
|
|
|
|
if (remove % 2) {
|
|
if (index1 % 2) {
|
|
/* first edge within existing shadow, second outside */
|
|
var P = SHADOWS[index1];
|
|
visibleLength = (A2[0] * P[1] - P[0] * A2[1]) / (P[1] * A2[1]);
|
|
|
|
if (blocks) {
|
|
SHADOWS.splice(index1, remove, A2);
|
|
}
|
|
} else {
|
|
/* second edge within existing shadow, first outside */
|
|
var _P = SHADOWS[index2];
|
|
visibleLength = (_P[0] * A1[1] - A1[0] * _P[1]) / (A1[1] * _P[1]);
|
|
|
|
if (blocks) {
|
|
SHADOWS.splice(index1, remove, A1);
|
|
}
|
|
}
|
|
} else {
|
|
if (index1 % 2) {
|
|
/* both edges within existing shadows */
|
|
var P1 = SHADOWS[index1];
|
|
var P2 = SHADOWS[index2];
|
|
visibleLength = (P2[0] * P1[1] - P1[0] * P2[1]) / (P1[1] * P2[1]);
|
|
|
|
if (blocks) {
|
|
SHADOWS.splice(index1, remove);
|
|
}
|
|
} else {
|
|
/* both edges outside existing shadows */
|
|
if (blocks) {
|
|
SHADOWS.splice(index1, remove, A1, A2);
|
|
}
|
|
|
|
return 1;
|
|
/* whole arc visible! */
|
|
}
|
|
}
|
|
|
|
var arcLength = (A2[0] * A1[1] - A1[0] * A2[1]) / (A1[1] * A2[1]);
|
|
return visibleLength / arcLength;
|
|
};
|
|
|
|
return PreciseShadowcasting;
|
|
}(FOV);
|
|
/** Octants used for translating recursive shadowcasting offsets */
|
|
|
|
|
|
var OCTANTS = [[-1, 0, 0, 1], [0, -1, 1, 0], [0, -1, -1, 0], [-1, 0, 0, -1], [1, 0, 0, -1], [0, 1, -1, 0], [0, 1, 1, 0], [1, 0, 0, 1]];
|
|
/**
|
|
* @class Recursive shadowcasting algorithm
|
|
* Currently only supports 4/8 topologies, not hexagonal.
|
|
* Based on Peter Harkins' implementation of Björn Bergström's algorithm described here: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
|
|
* @augments ROT.FOV
|
|
*/
|
|
|
|
var RecursiveShadowcasting =
|
|
/*#__PURE__*/
|
|
function (_FOV3) {
|
|
_inheritsLoose(RecursiveShadowcasting, _FOV3);
|
|
|
|
function RecursiveShadowcasting() {
|
|
return _FOV3.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto19 = RecursiveShadowcasting.prototype;
|
|
|
|
/**
|
|
* Compute visibility for a 360-degree circle
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {int} R Maximum visibility radius
|
|
* @param {function} callback
|
|
*/
|
|
_proto19.compute = function compute(x, y, R, callback) {
|
|
//You can always see your own tile
|
|
callback(x, y, 0, 1);
|
|
|
|
for (var i = 0; i < OCTANTS.length; i++) {
|
|
this._renderOctant(x, y, OCTANTS[i], R, callback);
|
|
}
|
|
};
|
|
/**
|
|
* Compute visibility for a 180-degree arc
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {int} R Maximum visibility radius
|
|
* @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
|
|
* @param {function} callback
|
|
*/
|
|
|
|
|
|
_proto19.compute180 = function compute180(x, y, R, dir, callback) {
|
|
//You can always see your own tile
|
|
callback(x, y, 0, 1);
|
|
var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 180 degrees
|
|
|
|
var nextPreviousOctant = (dir - 2 + 8) % 8; //Need to retrieve the previous two octants to render a full 180 degrees
|
|
|
|
var nextOctant = (dir + 1 + 8) % 8; //Need to grab to next octant to render a full 180 degrees
|
|
|
|
this._renderOctant(x, y, OCTANTS[nextPreviousOctant], R, callback);
|
|
|
|
this._renderOctant(x, y, OCTANTS[previousOctant], R, callback);
|
|
|
|
this._renderOctant(x, y, OCTANTS[dir], R, callback);
|
|
|
|
this._renderOctant(x, y, OCTANTS[nextOctant], R, callback);
|
|
};
|
|
|
|
/**
|
|
* Compute visibility for a 90-degree arc
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {int} R Maximum visibility radius
|
|
* @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
|
|
* @param {function} callback
|
|
*/
|
|
_proto19.compute90 = function compute90(x, y, R, dir, callback) {
|
|
//You can always see your own tile
|
|
callback(x, y, 0, 1);
|
|
var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 90 degrees
|
|
|
|
this._renderOctant(x, y, OCTANTS[dir], R, callback);
|
|
|
|
this._renderOctant(x, y, OCTANTS[previousOctant], R, callback);
|
|
};
|
|
/**
|
|
* Render one octant (45-degree arc) of the viewshed
|
|
* @param {int} x
|
|
* @param {int} y
|
|
* @param {int} octant Octant to be rendered
|
|
* @param {int} R Maximum visibility radius
|
|
* @param {function} callback
|
|
*/
|
|
|
|
|
|
_proto19._renderOctant = function _renderOctant(x, y, octant, R, callback) {
|
|
//Radius incremented by 1 to provide same coverage area as other shadowcasting radiuses
|
|
this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback);
|
|
};
|
|
/**
|
|
* Actually calculates the visibility
|
|
* @param {int} startX The starting X coordinate
|
|
* @param {int} startY The starting Y coordinate
|
|
* @param {int} row The row to render
|
|
* @param {float} visSlopeStart The slope to start at
|
|
* @param {float} visSlopeEnd The slope to end at
|
|
* @param {int} radius The radius to reach out to
|
|
* @param {int} xx
|
|
* @param {int} xy
|
|
* @param {int} yx
|
|
* @param {int} yy
|
|
* @param {function} callback The callback to use when we hit a block that is visible
|
|
*/
|
|
|
|
|
|
_proto19._castVisibility = function _castVisibility(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback) {
|
|
if (visSlopeStart < visSlopeEnd) {
|
|
return;
|
|
}
|
|
|
|
for (var i = row; i <= radius; i++) {
|
|
var dx = -i - 1;
|
|
var dy = -i;
|
|
var blocked = false;
|
|
var newStart = 0; //'Row' could be column, names here assume octant 0 and would be flipped for half the octants
|
|
|
|
while (dx <= 0) {
|
|
dx += 1; //Translate from relative coordinates to map coordinates
|
|
|
|
var mapX = startX + dx * xx + dy * xy;
|
|
var mapY = startY + dx * yx + dy * yy; //Range of the row
|
|
|
|
var slopeStart = (dx - 0.5) / (dy + 0.5);
|
|
var slopeEnd = (dx + 0.5) / (dy - 0.5); //Ignore if not yet at left edge of Octant
|
|
|
|
if (slopeEnd > visSlopeStart) {
|
|
continue;
|
|
} //Done if past right edge
|
|
|
|
|
|
if (slopeStart < visSlopeEnd) {
|
|
break;
|
|
} //If it's in range, it's visible
|
|
|
|
|
|
if (dx * dx + dy * dy < radius * radius) {
|
|
callback(mapX, mapY, i, 1);
|
|
}
|
|
|
|
if (!blocked) {
|
|
//If tile is a blocking tile, cast around it
|
|
if (!this._lightPasses(mapX, mapY) && i < radius) {
|
|
blocked = true;
|
|
|
|
this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback);
|
|
|
|
newStart = slopeEnd;
|
|
}
|
|
} else {
|
|
//Keep narrowing if scanning across a block
|
|
if (!this._lightPasses(mapX, mapY)) {
|
|
newStart = slopeEnd;
|
|
continue;
|
|
} //Block has ended
|
|
|
|
|
|
blocked = false;
|
|
visSlopeStart = newStart;
|
|
}
|
|
}
|
|
|
|
if (blocked) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
return RecursiveShadowcasting;
|
|
}(FOV);
|
|
|
|
var index$1 = {
|
|
DiscreteShadowcasting: DiscreteShadowcasting,
|
|
PreciseShadowcasting: PreciseShadowcasting,
|
|
RecursiveShadowcasting: RecursiveShadowcasting
|
|
};
|
|
|
|
var Map =
|
|
/*#__PURE__*/
|
|
function () {
|
|
/**
|
|
* @class Base map generator
|
|
* @param {int} [width=ROT.DEFAULT_WIDTH]
|
|
* @param {int} [height=ROT.DEFAULT_HEIGHT]
|
|
*/
|
|
function Map(width, height) {
|
|
if (width === void 0) {
|
|
width = DEFAULT_WIDTH;
|
|
}
|
|
|
|
if (height === void 0) {
|
|
height = DEFAULT_HEIGHT;
|
|
}
|
|
|
|
this._width = width;
|
|
this._height = height;
|
|
}
|
|
|
|
var _proto20 = Map.prototype;
|
|
|
|
_proto20._fillMap = function _fillMap(value) {
|
|
var map = [];
|
|
|
|
for (var i = 0; i < this._width; i++) {
|
|
map.push([]);
|
|
|
|
for (var j = 0; j < this._height; j++) {
|
|
map[i].push(value);
|
|
}
|
|
}
|
|
|
|
return map;
|
|
};
|
|
|
|
return Map;
|
|
}();
|
|
/**
|
|
* @class Simple empty rectangular room
|
|
* @augments ROT.Map
|
|
*/
|
|
|
|
|
|
var Arena =
|
|
/*#__PURE__*/
|
|
function (_Map) {
|
|
_inheritsLoose(Arena, _Map);
|
|
|
|
function Arena() {
|
|
return _Map.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto21 = Arena.prototype;
|
|
|
|
_proto21.create = function create(callback) {
|
|
var w = this._width - 1;
|
|
var h = this._height - 1;
|
|
|
|
for (var i = 0; i <= w; i++) {
|
|
for (var j = 0; j <= h; j++) {
|
|
var empty = i && j && i < w && j < h;
|
|
callback(i, j, empty ? 0 : 1);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
return Arena;
|
|
}(Map);
|
|
/**
|
|
* @class Dungeon map: has rooms and corridors
|
|
* @augments ROT.Map
|
|
*/
|
|
|
|
|
|
var Dungeon =
|
|
/*#__PURE__*/
|
|
function (_Map2) {
|
|
_inheritsLoose(Dungeon, _Map2);
|
|
|
|
function Dungeon(width, height) {
|
|
var _this10;
|
|
|
|
_this10 = _Map2.call(this, width, height) || this;
|
|
_this10._rooms = [];
|
|
_this10._corridors = [];
|
|
return _this10;
|
|
}
|
|
/**
|
|
* Get all generated rooms
|
|
* @returns {ROT.Map.Feature.Room[]}
|
|
*/
|
|
|
|
|
|
var _proto22 = Dungeon.prototype;
|
|
|
|
_proto22.getRooms = function getRooms() {
|
|
return this._rooms;
|
|
};
|
|
/**
|
|
* Get all generated corridors
|
|
* @returns {ROT.Map.Feature.Corridor[]}
|
|
*/
|
|
|
|
|
|
_proto22.getCorridors = function getCorridors() {
|
|
return this._corridors;
|
|
};
|
|
|
|
return Dungeon;
|
|
}(Map);
|
|
/**
|
|
* @class Dungeon feature; has own .create() method
|
|
*/
|
|
|
|
|
|
var Feature = function 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]
|
|
*/
|
|
|
|
|
|
var Room =
|
|
/*#__PURE__*/
|
|
function (_Feature) {
|
|
_inheritsLoose(Room, _Feature);
|
|
|
|
function Room(x1, y1, x2, y2, doorX, doorY) {
|
|
var _this11;
|
|
|
|
_this11 = _Feature.call(this) || this;
|
|
_this11._x1 = x1;
|
|
_this11._y1 = y1;
|
|
_this11._x2 = x2;
|
|
_this11._y2 = y2;
|
|
_this11._doors = {};
|
|
|
|
if (doorX !== undefined && doorY !== undefined) {
|
|
_this11.addDoor(doorX, doorY);
|
|
}
|
|
|
|
return _this11;
|
|
}
|
|
|
|
/**
|
|
* Room of random size, with a given doors and direction
|
|
*/
|
|
Room.createRandomAt = function createRandomAt(x, y, dx, dy, options) {
|
|
var min = options.roomWidth[0];
|
|
var max = options.roomWidth[1];
|
|
var width = RNG$1.getUniformInt(min, max);
|
|
min = options.roomHeight[0];
|
|
max = options.roomHeight[1];
|
|
var height = RNG$1.getUniformInt(min, max);
|
|
|
|
if (dx == 1) {
|
|
/* to the right */
|
|
var y2 = y - Math.floor(RNG$1.getUniform() * height);
|
|
return new this(x + 1, y2, x + width, y2 + height - 1, x, y);
|
|
}
|
|
|
|
if (dx == -1) {
|
|
/* to the left */
|
|
var _y = y - Math.floor(RNG$1.getUniform() * height);
|
|
|
|
return new this(x - width, _y, x - 1, _y + height - 1, x, y);
|
|
}
|
|
|
|
if (dy == 1) {
|
|
/* to the bottom */
|
|
var x2 = x - Math.floor(RNG$1.getUniform() * width);
|
|
return new this(x2, y + 1, x2 + width - 1, y + height, x, y);
|
|
}
|
|
|
|
if (dy == -1) {
|
|
/* to the top */
|
|
var _x = x - Math.floor(RNG$1.getUniform() * width);
|
|
|
|
return new this(_x, y - height, _x + 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
|
|
*/
|
|
|
|
|
|
Room.createRandomCenter = function createRandomCenter(cx, cy, options) {
|
|
var min = options.roomWidth[0];
|
|
var max = options.roomWidth[1];
|
|
var width = RNG$1.getUniformInt(min, max);
|
|
min = options.roomHeight[0];
|
|
max = options.roomHeight[1];
|
|
var height = RNG$1.getUniformInt(min, max);
|
|
var x1 = cx - Math.floor(RNG$1.getUniform() * width);
|
|
var y1 = cy - Math.floor(RNG$1.getUniform() * height);
|
|
var x2 = x1 + width - 1;
|
|
var y2 = y1 + height - 1;
|
|
return new this(x1, y1, x2, y2);
|
|
};
|
|
/**
|
|
* Room of random size within a given dimensions
|
|
*/
|
|
|
|
|
|
Room.createRandom = function createRandom(availWidth, availHeight, options) {
|
|
var min = options.roomWidth[0];
|
|
var max = options.roomWidth[1];
|
|
var width = RNG$1.getUniformInt(min, max);
|
|
min = options.roomHeight[0];
|
|
max = options.roomHeight[1];
|
|
var height = RNG$1.getUniformInt(min, max);
|
|
var left = availWidth - width - 1;
|
|
var top = availHeight - height - 1;
|
|
var x1 = 1 + Math.floor(RNG$1.getUniform() * left);
|
|
var y1 = 1 + Math.floor(RNG$1.getUniform() * top);
|
|
var x2 = x1 + width - 1;
|
|
var y2 = y1 + height - 1;
|
|
return new this(x1, y1, x2, y2);
|
|
};
|
|
|
|
var _proto23 = Room.prototype;
|
|
|
|
_proto23.addDoor = function addDoor(x, y) {
|
|
this._doors[x + "," + y] = 1;
|
|
return this;
|
|
};
|
|
/**
|
|
* @param {function}
|
|
*/
|
|
|
|
|
|
_proto23.getDoors = function getDoors(cb) {
|
|
for (var key in this._doors) {
|
|
var parts = key.split(",");
|
|
cb(parseInt(parts[0]), parseInt(parts[1]));
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
_proto23.clearDoors = function clearDoors() {
|
|
this._doors = {};
|
|
return this;
|
|
};
|
|
|
|
_proto23.addDoors = function addDoors(isWallCallback) {
|
|
var left = this._x1 - 1;
|
|
var right = this._x2 + 1;
|
|
var top = this._y1 - 1;
|
|
var bottom = this._y2 + 1;
|
|
|
|
for (var x = left; x <= right; x++) {
|
|
for (var 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;
|
|
};
|
|
|
|
_proto23.debug = function debug() {
|
|
console.log("room", this._x1, this._y1, this._x2, this._y2);
|
|
};
|
|
|
|
_proto23.isValid = function isValid(isWallCallback, canBeDugCallback) {
|
|
var left = this._x1 - 1;
|
|
var right = this._x2 + 1;
|
|
var top = this._y1 - 1;
|
|
var bottom = this._y2 + 1;
|
|
|
|
for (var x = left; x <= right; x++) {
|
|
for (var 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.
|
|
*/
|
|
|
|
|
|
_proto23.create = function create(digCallback) {
|
|
var left = this._x1 - 1;
|
|
var right = this._x2 + 1;
|
|
var top = this._y1 - 1;
|
|
var bottom = this._y2 + 1;
|
|
var value = 0;
|
|
|
|
for (var x = left; x <= right; x++) {
|
|
for (var 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);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto23.getCenter = function getCenter() {
|
|
return [Math.round((this._x1 + this._x2) / 2), Math.round((this._y1 + this._y2) / 2)];
|
|
};
|
|
|
|
_proto23.getLeft = function getLeft() {
|
|
return this._x1;
|
|
};
|
|
|
|
_proto23.getRight = function getRight() {
|
|
return this._x2;
|
|
};
|
|
|
|
_proto23.getTop = function getTop() {
|
|
return this._y1;
|
|
};
|
|
|
|
_proto23.getBottom = function getBottom() {
|
|
return this._y2;
|
|
};
|
|
|
|
return Room;
|
|
}(Feature);
|
|
/**
|
|
* @class Corridor
|
|
* @augments ROT.Map.Feature
|
|
* @param {int} startX
|
|
* @param {int} startY
|
|
* @param {int} endX
|
|
* @param {int} endY
|
|
*/
|
|
|
|
|
|
var Corridor =
|
|
/*#__PURE__*/
|
|
function (_Feature2) {
|
|
_inheritsLoose(Corridor, _Feature2);
|
|
|
|
function Corridor(startX, startY, endX, endY) {
|
|
var _this12;
|
|
|
|
_this12 = _Feature2.call(this) || this;
|
|
_this12._startX = startX;
|
|
_this12._startY = startY;
|
|
_this12._endX = endX;
|
|
_this12._endY = endY;
|
|
_this12._endsWithAWall = true;
|
|
return _this12;
|
|
}
|
|
|
|
Corridor.createRandomAt = function createRandomAt(x, y, dx, dy, options) {
|
|
var min = options.corridorLength[0];
|
|
var max = options.corridorLength[1];
|
|
var length = RNG$1.getUniformInt(min, max);
|
|
return new this(x, y, x + dx * length, y + dy * length);
|
|
};
|
|
|
|
var _proto24 = Corridor.prototype;
|
|
|
|
_proto24.debug = function debug() {
|
|
console.log("corridor", this._startX, this._startY, this._endX, this._endY);
|
|
};
|
|
|
|
_proto24.isValid = function isValid(isWallCallback, canBeDugCallback) {
|
|
var sx = this._startX;
|
|
var sy = this._startY;
|
|
var dx = this._endX - sx;
|
|
var dy = this._endY - sy;
|
|
var length = 1 + Math.max(Math.abs(dx), Math.abs(dy));
|
|
|
|
if (dx) {
|
|
dx = dx / Math.abs(dx);
|
|
}
|
|
|
|
if (dy) {
|
|
dy = dy / Math.abs(dy);
|
|
}
|
|
|
|
var nx = dy;
|
|
var ny = -dx;
|
|
var ok = true;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var x = sx + i * dx;
|
|
var 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)
|
|
*/
|
|
|
|
|
|
var firstCornerBad = !isWallCallback(this._endX + dx + nx, this._endY + dy + ny);
|
|
var 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.
|
|
*/
|
|
|
|
|
|
_proto24.create = function create(digCallback) {
|
|
var sx = this._startX;
|
|
var sy = this._startY;
|
|
var dx = this._endX - sx;
|
|
var dy = this._endY - sy;
|
|
var 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 (var i = 0; i < length; i++) {
|
|
var x = sx + i * dx;
|
|
var y = sy + i * dy;
|
|
digCallback(x, y, 0);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
_proto24.createPriorityWalls = function createPriorityWalls(priorityWallCallback) {
|
|
if (!this._endsWithAWall) {
|
|
return;
|
|
}
|
|
|
|
var sx = this._startX;
|
|
var sy = this._startY;
|
|
var dx = this._endX - sx;
|
|
var dy = this._endY - sy;
|
|
|
|
if (dx) {
|
|
dx = dx / Math.abs(dx);
|
|
}
|
|
|
|
if (dy) {
|
|
dy = dy / Math.abs(dy);
|
|
}
|
|
|
|
var nx = dy;
|
|
var ny = -dx;
|
|
priorityWallCallback(this._endX + dx, this._endY + dy);
|
|
priorityWallCallback(this._endX + nx, this._endY + ny);
|
|
priorityWallCallback(this._endX - nx, this._endY - ny);
|
|
};
|
|
|
|
return Corridor;
|
|
}(Feature);
|
|
/**
|
|
* @class Dungeon generator which tries to fill the space evenly. Generates independent rooms and tries to connect them.
|
|
* @augments ROT.Map.Dungeon
|
|
*/
|
|
|
|
|
|
var Uniform =
|
|
/*#__PURE__*/
|
|
function (_Dungeon) {
|
|
_inheritsLoose(Uniform, _Dungeon);
|
|
|
|
function Uniform(width, height, options) {
|
|
var _this13;
|
|
|
|
_this13 = _Dungeon.call(this, width, height) || this;
|
|
_this13._options = {
|
|
roomWidth: [3, 9],
|
|
roomHeight: [3, 5],
|
|
roomDugPercentage: 0.1,
|
|
timeLimit: 1000
|
|
/* we stop after this much time has passed (msec) */
|
|
|
|
};
|
|
Object.assign(_this13._options, options);
|
|
_this13._map = [];
|
|
_this13._dug = 0;
|
|
_this13._roomAttempts = 20;
|
|
/* new room is created N-times until is considered as impossible to generate */
|
|
|
|
_this13._corridorAttempts = 20;
|
|
/* corridors are tried N-times until the level is considered as impossible to connect */
|
|
|
|
_this13._connected = [];
|
|
/* list of already connected rooms */
|
|
|
|
_this13._unconnected = [];
|
|
/* list of remaining unconnected rooms */
|
|
|
|
_this13._digCallback = _this13._digCallback.bind(_assertThisInitialized(_assertThisInitialized(_this13)));
|
|
_this13._canBeDugCallback = _this13._canBeDugCallback.bind(_assertThisInitialized(_assertThisInitialized(_this13)));
|
|
_this13._isWallCallback = _this13._isWallCallback.bind(_assertThisInitialized(_assertThisInitialized(_this13)));
|
|
return _this13;
|
|
}
|
|
/**
|
|
* Create a map. If the time limit has been hit, returns null.
|
|
* @see ROT.Map#create
|
|
*/
|
|
|
|
|
|
var _proto25 = Uniform.prototype;
|
|
|
|
_proto25.create = function create(callback) {
|
|
var t1 = Date.now();
|
|
|
|
while (1) {
|
|
var t2 = Date.now();
|
|
|
|
if (t2 - t1 > this._options.timeLimit) {
|
|
return null;
|
|
}
|
|
/* time limit! */
|
|
|
|
|
|
this._map = this._fillMap(1);
|
|
this._dug = 0;
|
|
this._rooms = [];
|
|
this._unconnected = [];
|
|
|
|
this._generateRooms();
|
|
|
|
if (this._rooms.length < 2) {
|
|
continue;
|
|
}
|
|
|
|
if (this._generateCorridors()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (callback) {
|
|
for (var i = 0; i < this._width; i++) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
callback(i, j, this._map[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Generates a suitable amount of rooms
|
|
*/
|
|
|
|
|
|
_proto25._generateRooms = function _generateRooms() {
|
|
var w = this._width - 2;
|
|
var h = this._height - 2;
|
|
var room;
|
|
|
|
do {
|
|
room = this._generateRoom();
|
|
|
|
if (this._dug / (w * h) > this._options.roomDugPercentage) {
|
|
break;
|
|
}
|
|
/* achieved requested amount of free space */
|
|
|
|
} while (room);
|
|
/* either enough rooms, or not able to generate more of them :) */
|
|
|
|
};
|
|
/**
|
|
* Try to generate one room
|
|
*/
|
|
|
|
|
|
_proto25._generateRoom = function _generateRoom() {
|
|
var count = 0;
|
|
|
|
while (count < this._roomAttempts) {
|
|
count++;
|
|
var room = Room.createRandom(this._width, this._height, this._options);
|
|
|
|
if (!room.isValid(this._isWallCallback, this._canBeDugCallback)) {
|
|
continue;
|
|
}
|
|
|
|
room.create(this._digCallback);
|
|
|
|
this._rooms.push(room);
|
|
|
|
return room;
|
|
}
|
|
/* no room was generated in a given number of attempts */
|
|
|
|
|
|
return null;
|
|
};
|
|
/**
|
|
* Generates connectors beween rooms
|
|
* @returns {bool} success Was this attempt successfull?
|
|
*/
|
|
|
|
|
|
_proto25._generateCorridors = function _generateCorridors() {
|
|
var cnt = 0;
|
|
|
|
while (cnt < this._corridorAttempts) {
|
|
cnt++;
|
|
this._corridors = [];
|
|
/* dig rooms into a clear map */
|
|
|
|
this._map = this._fillMap(1);
|
|
|
|
for (var i = 0; i < this._rooms.length; i++) {
|
|
var room = this._rooms[i];
|
|
room.clearDoors();
|
|
room.create(this._digCallback);
|
|
}
|
|
|
|
this._unconnected = RNG$1.shuffle(this._rooms.slice());
|
|
this._connected = [];
|
|
|
|
if (this._unconnected.length) {
|
|
this._connected.push(this._unconnected.pop());
|
|
}
|
|
/* first one is always connected */
|
|
|
|
|
|
while (1) {
|
|
/* 1. pick random connected room */
|
|
var connected = RNG$1.getItem(this._connected);
|
|
|
|
if (!connected) {
|
|
break;
|
|
}
|
|
/* 2. find closest unconnected */
|
|
|
|
|
|
var room1 = this._closestRoom(this._unconnected, connected);
|
|
|
|
if (!room1) {
|
|
break;
|
|
}
|
|
/* 3. connect it to closest connected */
|
|
|
|
|
|
var room2 = this._closestRoom(this._connected, room1);
|
|
|
|
if (!room2) {
|
|
break;
|
|
}
|
|
|
|
var ok = this._connectRooms(room1, room2);
|
|
|
|
if (!ok) {
|
|
break;
|
|
}
|
|
/* stop connecting, re-shuffle */
|
|
|
|
|
|
if (!this._unconnected.length) {
|
|
return true;
|
|
}
|
|
/* done; no rooms remain */
|
|
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* For a given room, find the closest one from the list
|
|
*/
|
|
_proto25._closestRoom = function _closestRoom(rooms, room) {
|
|
var dist = Infinity;
|
|
var center = room.getCenter();
|
|
var result = null;
|
|
|
|
for (var i = 0; i < rooms.length; i++) {
|
|
var r = rooms[i];
|
|
var c = r.getCenter();
|
|
var dx = c[0] - center[0];
|
|
var dy = c[1] - center[1];
|
|
var d = dx * dx + dy * dy;
|
|
|
|
if (d < dist) {
|
|
dist = d;
|
|
result = r;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
_proto25._connectRooms = function _connectRooms(room1, room2) {
|
|
/*
|
|
room1.debug();
|
|
room2.debug();
|
|
*/
|
|
var center1 = room1.getCenter();
|
|
var center2 = room2.getCenter();
|
|
var diffX = center2[0] - center1[0];
|
|
var diffY = center2[1] - center1[1];
|
|
var start;
|
|
var end;
|
|
var dirIndex1, dirIndex2, min, max, index;
|
|
|
|
if (Math.abs(diffX) < Math.abs(diffY)) {
|
|
/* first try connecting north-south walls */
|
|
dirIndex1 = diffY > 0 ? 2 : 0;
|
|
dirIndex2 = (dirIndex1 + 2) % 4;
|
|
min = room2.getLeft();
|
|
max = room2.getRight();
|
|
index = 0;
|
|
} else {
|
|
/* first try connecting east-west walls */
|
|
dirIndex1 = diffX > 0 ? 1 : 3;
|
|
dirIndex2 = (dirIndex1 + 2) % 4;
|
|
min = room2.getTop();
|
|
max = room2.getBottom();
|
|
index = 1;
|
|
}
|
|
|
|
start = this._placeInWall(room1, dirIndex1);
|
|
/* corridor will start here */
|
|
|
|
if (!start) {
|
|
return false;
|
|
}
|
|
|
|
if (start[index] >= min && start[index] <= max) {
|
|
/* possible to connect with straight line (I-like) */
|
|
end = start.slice();
|
|
var value = 0;
|
|
|
|
switch (dirIndex2) {
|
|
case 0:
|
|
value = room2.getTop() - 1;
|
|
break;
|
|
|
|
case 1:
|
|
value = room2.getRight() + 1;
|
|
break;
|
|
|
|
case 2:
|
|
value = room2.getBottom() + 1;
|
|
break;
|
|
|
|
case 3:
|
|
value = room2.getLeft() - 1;
|
|
break;
|
|
}
|
|
|
|
end[(index + 1) % 2] = value;
|
|
|
|
this._digLine([start, end]);
|
|
} else if (start[index] < min - 1 || start[index] > max + 1) {
|
|
/* need to switch target wall (L-like) */
|
|
var diff = start[index] - center2[index];
|
|
var rotation = 0;
|
|
|
|
switch (dirIndex2) {
|
|
case 0:
|
|
case 1:
|
|
rotation = diff < 0 ? 3 : 1;
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
rotation = diff < 0 ? 1 : 3;
|
|
break;
|
|
}
|
|
|
|
dirIndex2 = (dirIndex2 + rotation) % 4;
|
|
end = this._placeInWall(room2, dirIndex2);
|
|
|
|
if (!end) {
|
|
return false;
|
|
}
|
|
|
|
var mid = [0, 0];
|
|
mid[index] = start[index];
|
|
var index2 = (index + 1) % 2;
|
|
mid[index2] = end[index2];
|
|
|
|
this._digLine([start, mid, end]);
|
|
} else {
|
|
/* use current wall pair, but adjust the line in the middle (S-like) */
|
|
var _index4 = (index + 1) % 2;
|
|
|
|
end = this._placeInWall(room2, dirIndex2);
|
|
|
|
if (!end) {
|
|
return false;
|
|
}
|
|
|
|
var _mid = Math.round((end[_index4] + start[_index4]) / 2);
|
|
|
|
var mid1 = [0, 0];
|
|
var mid2 = [0, 0];
|
|
mid1[index] = start[index];
|
|
mid1[_index4] = _mid;
|
|
mid2[index] = end[index];
|
|
mid2[_index4] = _mid;
|
|
|
|
this._digLine([start, mid1, mid2, end]);
|
|
}
|
|
|
|
room1.addDoor(start[0], start[1]);
|
|
room2.addDoor(end[0], end[1]);
|
|
index = this._unconnected.indexOf(room1);
|
|
|
|
if (index != -1) {
|
|
this._unconnected.splice(index, 1);
|
|
|
|
this._connected.push(room1);
|
|
}
|
|
|
|
index = this._unconnected.indexOf(room2);
|
|
|
|
if (index != -1) {
|
|
this._unconnected.splice(index, 1);
|
|
|
|
this._connected.push(room2);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
_proto25._placeInWall = function _placeInWall(room, dirIndex) {
|
|
var start = [0, 0];
|
|
var dir = [0, 0];
|
|
var length = 0;
|
|
|
|
switch (dirIndex) {
|
|
case 0:
|
|
dir = [1, 0];
|
|
start = [room.getLeft(), room.getTop() - 1];
|
|
length = room.getRight() - room.getLeft() + 1;
|
|
break;
|
|
|
|
case 1:
|
|
dir = [0, 1];
|
|
start = [room.getRight() + 1, room.getTop()];
|
|
length = room.getBottom() - room.getTop() + 1;
|
|
break;
|
|
|
|
case 2:
|
|
dir = [1, 0];
|
|
start = [room.getLeft(), room.getBottom() + 1];
|
|
length = room.getRight() - room.getLeft() + 1;
|
|
break;
|
|
|
|
case 3:
|
|
dir = [0, 1];
|
|
start = [room.getLeft() - 1, room.getTop()];
|
|
length = room.getBottom() - room.getTop() + 1;
|
|
break;
|
|
}
|
|
|
|
var avail = [];
|
|
var lastBadIndex = -2;
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var x = start[0] + i * dir[0];
|
|
var y = start[1] + i * dir[1];
|
|
avail.push(null);
|
|
var isWall = this._map[x][y] == 1;
|
|
|
|
if (isWall) {
|
|
if (lastBadIndex != i - 1) {
|
|
avail[i] = [x, y];
|
|
}
|
|
} else {
|
|
lastBadIndex = i;
|
|
|
|
if (i) {
|
|
avail[i - 1] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var _i3 = avail.length - 1; _i3 >= 0; _i3--) {
|
|
if (!avail[_i3]) {
|
|
avail.splice(_i3, 1);
|
|
}
|
|
}
|
|
|
|
return avail.length ? RNG$1.getItem(avail) : null;
|
|
};
|
|
/**
|
|
* Dig a polyline.
|
|
*/
|
|
|
|
|
|
_proto25._digLine = function _digLine(points) {
|
|
for (var i = 1; i < points.length; i++) {
|
|
var start = points[i - 1];
|
|
var end = points[i];
|
|
var corridor = new Corridor(start[0], start[1], end[0], end[1]);
|
|
corridor.create(this._digCallback);
|
|
|
|
this._corridors.push(corridor);
|
|
}
|
|
};
|
|
|
|
_proto25._digCallback = function _digCallback(x, y, value) {
|
|
this._map[x][y] = value;
|
|
|
|
if (value == 0) {
|
|
this._dug++;
|
|
}
|
|
};
|
|
|
|
_proto25._isWallCallback = function _isWallCallback(x, y) {
|
|
if (x < 0 || y < 0 || x >= this._width || y >= this._height) {
|
|
return false;
|
|
}
|
|
|
|
return this._map[x][y] == 1;
|
|
};
|
|
|
|
_proto25._canBeDugCallback = function _canBeDugCallback(x, y) {
|
|
if (x < 1 || y < 1 || x + 1 >= this._width || y + 1 >= this._height) {
|
|
return false;
|
|
}
|
|
|
|
return this._map[x][y] == 1;
|
|
};
|
|
|
|
return Uniform;
|
|
}(Dungeon);
|
|
/**
|
|
* @class Cellular automaton map generator
|
|
* @augments ROT.Map
|
|
* @param {int} [width=ROT.DEFAULT_WIDTH]
|
|
* @param {int} [height=ROT.DEFAULT_HEIGHT]
|
|
* @param {object} [options] Options
|
|
* @param {int[]} [options.born] List of neighbor counts for a new cell to be born in empty space
|
|
* @param {int[]} [options.survive] List of neighbor counts for an existing cell to survive
|
|
* @param {int} [options.topology] Topology 4 or 6 or 8
|
|
*/
|
|
|
|
|
|
var Cellular =
|
|
/*#__PURE__*/
|
|
function (_Map3) {
|
|
_inheritsLoose(Cellular, _Map3);
|
|
|
|
function Cellular(width, height, options) {
|
|
var _this14;
|
|
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
_this14 = _Map3.call(this, width, height) || this;
|
|
_this14._options = {
|
|
born: [5, 6, 7, 8],
|
|
survive: [4, 5, 6, 7, 8],
|
|
topology: 8
|
|
};
|
|
|
|
_this14.setOptions(options);
|
|
|
|
_this14._dirs = DIRS[_this14._options.topology];
|
|
_this14._map = _this14._fillMap(0);
|
|
return _this14;
|
|
}
|
|
/**
|
|
* Fill the map with random values
|
|
* @param {float} probability Probability for a cell to become alive; 0 = all empty, 1 = all full
|
|
*/
|
|
|
|
|
|
var _proto26 = Cellular.prototype;
|
|
|
|
_proto26.randomize = function randomize(probability) {
|
|
for (var i = 0; i < this._width; i++) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
this._map[i][j] = RNG$1.getUniform() < probability ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Change options.
|
|
* @see ROT.Map.Cellular
|
|
*/
|
|
|
|
|
|
_proto26.setOptions = function setOptions(options) {
|
|
Object.assign(this._options, options);
|
|
};
|
|
|
|
_proto26.set = function set(x, y, value) {
|
|
this._map[x][y] = value;
|
|
};
|
|
|
|
_proto26.create = function create(callback) {
|
|
var newMap = this._fillMap(0);
|
|
|
|
var born = this._options.born;
|
|
var survive = this._options.survive;
|
|
|
|
for (var j = 0; j < this._height; j++) {
|
|
var widthStep = 1;
|
|
var widthStart = 0;
|
|
|
|
if (this._options.topology == 6) {
|
|
widthStep = 2;
|
|
widthStart = j % 2;
|
|
}
|
|
|
|
for (var i = widthStart; i < this._width; i += widthStep) {
|
|
var cur = this._map[i][j];
|
|
|
|
var ncount = this._getNeighbors(i, j);
|
|
|
|
if (cur && survive.indexOf(ncount) != -1) {
|
|
/* survive */
|
|
newMap[i][j] = 1;
|
|
} else if (!cur && born.indexOf(ncount) != -1) {
|
|
/* born */
|
|
newMap[i][j] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
this._map = newMap;
|
|
callback && this._serviceCallback(callback);
|
|
};
|
|
|
|
_proto26._serviceCallback = function _serviceCallback(callback) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
var widthStep = 1;
|
|
var widthStart = 0;
|
|
|
|
if (this._options.topology == 6) {
|
|
widthStep = 2;
|
|
widthStart = j % 2;
|
|
}
|
|
|
|
for (var i = widthStart; i < this._width; i += widthStep) {
|
|
callback(i, j, this._map[i][j]);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Get neighbor count at [i,j] in this._map
|
|
*/
|
|
|
|
|
|
_proto26._getNeighbors = function _getNeighbors(cx, cy) {
|
|
var result = 0;
|
|
|
|
for (var i = 0; i < this._dirs.length; i++) {
|
|
var dir = this._dirs[i];
|
|
var x = cx + dir[0];
|
|
var y = cy + dir[1];
|
|
|
|
if (x < 0 || x >= this._width || y < 0 || y >= this._height) {
|
|
continue;
|
|
}
|
|
|
|
result += this._map[x][y] == 1 ? 1 : 0;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
/**
|
|
* Make sure every non-wall space is accessible.
|
|
* @param {function} callback to call to display map when do
|
|
* @param {int} value to consider empty space - defaults to 0
|
|
* @param {function} callback to call when a new connection is made
|
|
*/
|
|
|
|
|
|
_proto26.connect = function connect(callback, value, connectionCallback) {
|
|
if (!value) value = 0;
|
|
var allFreeSpace = [];
|
|
var notConnected = {}; // find all free space
|
|
|
|
var widthStep = 1;
|
|
var widthStarts = [0, 0];
|
|
|
|
if (this._options.topology == 6) {
|
|
widthStep = 2;
|
|
widthStarts = [0, 1];
|
|
}
|
|
|
|
for (var y = 0; y < this._height; y++) {
|
|
for (var x = widthStarts[y % 2]; x < this._width; x += widthStep) {
|
|
if (this._freeSpace(x, y, value)) {
|
|
var p = [x, y];
|
|
notConnected[this._pointKey(p)] = p;
|
|
allFreeSpace.push([x, y]);
|
|
}
|
|
}
|
|
}
|
|
|
|
var start = allFreeSpace[RNG$1.getUniformInt(0, allFreeSpace.length - 1)];
|
|
|
|
var key = this._pointKey(start);
|
|
|
|
var connected = {};
|
|
connected[key] = start;
|
|
delete notConnected[key]; // find what's connected to the starting point
|
|
|
|
this._findConnected(connected, notConnected, [start], false, value);
|
|
|
|
while (Object.keys(notConnected).length > 0) {
|
|
// find two points from notConnected to connected
|
|
var _p = this._getFromTo(connected, notConnected);
|
|
|
|
var from = _p[0]; // notConnected
|
|
|
|
var to = _p[1]; // connected
|
|
// find everything connected to the starting point
|
|
|
|
var local = {};
|
|
local[this._pointKey(from)] = from;
|
|
|
|
this._findConnected(local, notConnected, [from], true, value); // connect to a connected cell
|
|
|
|
|
|
var tunnelFn = this._options.topology == 6 ? this._tunnelToConnected6 : this._tunnelToConnected;
|
|
tunnelFn.call(this, to, from, connected, notConnected, value, connectionCallback); // now all of local is connected
|
|
|
|
for (var k in local) {
|
|
var pp = local[k];
|
|
this._map[pp[0]][pp[1]] = value;
|
|
connected[k] = pp;
|
|
delete notConnected[k];
|
|
}
|
|
}
|
|
|
|
callback && this._serviceCallback(callback);
|
|
};
|
|
/**
|
|
* Find random points to connect. Search for the closest point in the larger space.
|
|
* This is to minimize the length of the passage while maintaining good performance.
|
|
*/
|
|
|
|
|
|
_proto26._getFromTo = function _getFromTo(connected, notConnected) {
|
|
var from = [0, 0],
|
|
to = [0, 0],
|
|
d;
|
|
var connectedKeys = Object.keys(connected);
|
|
var notConnectedKeys = Object.keys(notConnected);
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
if (connectedKeys.length < notConnectedKeys.length) {
|
|
var keys = connectedKeys;
|
|
to = connected[keys[RNG$1.getUniformInt(0, keys.length - 1)]];
|
|
from = this._getClosest(to, notConnected);
|
|
} else {
|
|
var _keys = notConnectedKeys;
|
|
from = notConnected[_keys[RNG$1.getUniformInt(0, _keys.length - 1)]];
|
|
to = this._getClosest(from, connected);
|
|
}
|
|
|
|
d = (from[0] - to[0]) * (from[0] - to[0]) + (from[1] - to[1]) * (from[1] - to[1]);
|
|
|
|
if (d < 64) {
|
|
break;
|
|
}
|
|
} // console.log(">>> connected=" + to + " notConnected=" + from + " dist=" + d);
|
|
|
|
|
|
return [from, to];
|
|
};
|
|
|
|
_proto26._getClosest = function _getClosest(point, space) {
|
|
var minPoint = null;
|
|
var minDist = null;
|
|
|
|
for (var k in space) {
|
|
var p = space[k];
|
|
var d = (p[0] - point[0]) * (p[0] - point[0]) + (p[1] - point[1]) * (p[1] - point[1]);
|
|
|
|
if (minDist == null || d < minDist) {
|
|
minDist = d;
|
|
minPoint = p;
|
|
}
|
|
}
|
|
|
|
return minPoint;
|
|
};
|
|
|
|
_proto26._findConnected = function _findConnected(connected, notConnected, stack, keepNotConnected, value) {
|
|
while (stack.length > 0) {
|
|
var p = stack.splice(0, 1)[0];
|
|
var tests = void 0;
|
|
|
|
if (this._options.topology == 6) {
|
|
tests = [[p[0] + 2, p[1]], [p[0] + 1, p[1] - 1], [p[0] - 1, p[1] - 1], [p[0] - 2, p[1]], [p[0] - 1, p[1] + 1], [p[0] + 1, p[1] + 1]];
|
|
} else {
|
|
tests = [[p[0] + 1, p[1]], [p[0] - 1, p[1]], [p[0], p[1] + 1], [p[0], p[1] - 1]];
|
|
}
|
|
|
|
for (var i = 0; i < tests.length; i++) {
|
|
var key = this._pointKey(tests[i]);
|
|
|
|
if (connected[key] == null && this._freeSpace(tests[i][0], tests[i][1], value)) {
|
|
connected[key] = tests[i];
|
|
|
|
if (!keepNotConnected) {
|
|
delete notConnected[key];
|
|
}
|
|
|
|
stack.push(tests[i]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto26._tunnelToConnected = function _tunnelToConnected(to, from, connected, notConnected, value, connectionCallback) {
|
|
var a, b;
|
|
|
|
if (from[0] < to[0]) {
|
|
a = from;
|
|
b = to;
|
|
} else {
|
|
a = to;
|
|
b = from;
|
|
}
|
|
|
|
for (var xx = a[0]; xx <= b[0]; xx++) {
|
|
this._map[xx][a[1]] = value;
|
|
var p = [xx, a[1]];
|
|
|
|
var pkey = this._pointKey(p);
|
|
|
|
connected[pkey] = p;
|
|
delete notConnected[pkey];
|
|
}
|
|
|
|
if (connectionCallback && a[0] < b[0]) {
|
|
connectionCallback(a, [b[0], a[1]]);
|
|
} // x is now fixed
|
|
|
|
|
|
var x = b[0];
|
|
|
|
if (from[1] < to[1]) {
|
|
a = from;
|
|
b = to;
|
|
} else {
|
|
a = to;
|
|
b = from;
|
|
}
|
|
|
|
for (var yy = a[1]; yy < b[1]; yy++) {
|
|
this._map[x][yy] = value;
|
|
var _p2 = [x, yy];
|
|
|
|
var _pkey = this._pointKey(_p2);
|
|
|
|
connected[_pkey] = _p2;
|
|
delete notConnected[_pkey];
|
|
}
|
|
|
|
if (connectionCallback && a[1] < b[1]) {
|
|
connectionCallback([b[0], a[1]], [b[0], b[1]]);
|
|
}
|
|
};
|
|
|
|
_proto26._tunnelToConnected6 = function _tunnelToConnected6(to, from, connected, notConnected, value, connectionCallback) {
|
|
var a, b;
|
|
|
|
if (from[0] < to[0]) {
|
|
a = from;
|
|
b = to;
|
|
} else {
|
|
a = to;
|
|
b = from;
|
|
} // tunnel diagonally until horizontally level
|
|
|
|
|
|
var xx = a[0];
|
|
var yy = a[1];
|
|
|
|
while (!(xx == b[0] && yy == b[1])) {
|
|
var stepWidth = 2;
|
|
|
|
if (yy < b[1]) {
|
|
yy++;
|
|
stepWidth = 1;
|
|
} else if (yy > b[1]) {
|
|
yy--;
|
|
stepWidth = 1;
|
|
}
|
|
|
|
if (xx < b[0]) {
|
|
xx += stepWidth;
|
|
} else if (xx > b[0]) {
|
|
xx -= stepWidth;
|
|
} else if (b[1] % 2) {
|
|
// Won't step outside map if destination on is map's right edge
|
|
xx -= stepWidth;
|
|
} else {
|
|
// ditto for left edge
|
|
xx += stepWidth;
|
|
}
|
|
|
|
this._map[xx][yy] = value;
|
|
var p = [xx, yy];
|
|
|
|
var pkey = this._pointKey(p);
|
|
|
|
connected[pkey] = p;
|
|
delete notConnected[pkey];
|
|
}
|
|
|
|
if (connectionCallback) {
|
|
connectionCallback(from, to);
|
|
}
|
|
};
|
|
|
|
_proto26._freeSpace = function _freeSpace(x, y, value) {
|
|
return x >= 0 && x < this._width && y >= 0 && y < this._height && this._map[x][y] == value;
|
|
};
|
|
|
|
_proto26._pointKey = function _pointKey(p) {
|
|
return p[0] + "." + p[1];
|
|
};
|
|
|
|
return Cellular;
|
|
}(Map);
|
|
|
|
var FEATURES = {
|
|
"room": Room,
|
|
"corridor": Corridor
|
|
};
|
|
/**
|
|
* Random dungeon generator using human-like digging patterns.
|
|
* Heavily based on Mike Anderson's ideas from the "Tyrant" algo, mentioned at
|
|
* http://www.roguebasin.roguelikedevelopment.org/index.php?title=Dungeon-Building_Algorithm.
|
|
*/
|
|
|
|
var Digger =
|
|
/*#__PURE__*/
|
|
function (_Dungeon2) {
|
|
_inheritsLoose(Digger, _Dungeon2);
|
|
|
|
function Digger(width, height, options) {
|
|
var _this15;
|
|
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
_this15 = _Dungeon2.call(this, width, height) || this;
|
|
_this15._options = Object.assign({
|
|
roomWidth: [3, 9],
|
|
roomHeight: [3, 5],
|
|
corridorLength: [3, 10],
|
|
dugPercentage: 0.2,
|
|
timeLimit: 1000
|
|
/* we stop after this much time has passed (msec) */
|
|
|
|
}, options);
|
|
_this15._features = {
|
|
"room": 4,
|
|
"corridor": 4
|
|
};
|
|
_this15._map = [];
|
|
_this15._featureAttempts = 20;
|
|
/* how many times do we try to create a feature on a suitable wall */
|
|
|
|
_this15._walls = {};
|
|
/* these are available for digging */
|
|
|
|
_this15._dug = 0;
|
|
_this15._digCallback = _this15._digCallback.bind(_assertThisInitialized(_assertThisInitialized(_this15)));
|
|
_this15._canBeDugCallback = _this15._canBeDugCallback.bind(_assertThisInitialized(_assertThisInitialized(_this15)));
|
|
_this15._isWallCallback = _this15._isWallCallback.bind(_assertThisInitialized(_assertThisInitialized(_this15)));
|
|
_this15._priorityWallCallback = _this15._priorityWallCallback.bind(_assertThisInitialized(_assertThisInitialized(_this15)));
|
|
return _this15;
|
|
}
|
|
|
|
var _proto27 = Digger.prototype;
|
|
|
|
_proto27.create = function create(callback) {
|
|
this._rooms = [];
|
|
this._corridors = [];
|
|
this._map = this._fillMap(1);
|
|
this._walls = {};
|
|
this._dug = 0;
|
|
var area = (this._width - 2) * (this._height - 2);
|
|
|
|
this._firstRoom();
|
|
|
|
var t1 = Date.now();
|
|
var priorityWalls;
|
|
|
|
do {
|
|
priorityWalls = 0;
|
|
var t2 = Date.now();
|
|
|
|
if (t2 - t1 > this._options.timeLimit) {
|
|
break;
|
|
}
|
|
/* find a good wall */
|
|
|
|
|
|
var wall = this._findWall();
|
|
|
|
if (!wall) {
|
|
break;
|
|
}
|
|
/* no more walls */
|
|
|
|
|
|
var parts = wall.split(",");
|
|
var x = parseInt(parts[0]);
|
|
var y = parseInt(parts[1]);
|
|
|
|
var dir = this._getDiggingDirection(x, y);
|
|
|
|
if (!dir) {
|
|
continue;
|
|
}
|
|
/* this wall is not suitable */
|
|
// console.log("wall", x, y);
|
|
|
|
/* try adding a feature */
|
|
|
|
|
|
var featureAttempts = 0;
|
|
|
|
do {
|
|
featureAttempts++;
|
|
|
|
if (this._tryFeature(x, y, dir[0], dir[1])) {
|
|
/* feature added */
|
|
//if (this._rooms.length + this._corridors.length == 2) { this._rooms[0].addDoor(x, y); } /* first room oficially has doors */
|
|
this._removeSurroundingWalls(x, y);
|
|
|
|
this._removeSurroundingWalls(x - dir[0], y - dir[1]);
|
|
|
|
break;
|
|
}
|
|
} while (featureAttempts < this._featureAttempts);
|
|
|
|
for (var id in this._walls) {
|
|
if (this._walls[id] > 1) {
|
|
priorityWalls++;
|
|
}
|
|
}
|
|
} while (this._dug / area < this._options.dugPercentage || priorityWalls);
|
|
/* fixme number of priority walls */
|
|
|
|
|
|
this._addDoors();
|
|
|
|
if (callback) {
|
|
for (var i = 0; i < this._width; i++) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
callback(i, j, this._map[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._walls = {};
|
|
this._map = [];
|
|
return this;
|
|
};
|
|
|
|
_proto27._digCallback = function _digCallback(x, y, value) {
|
|
if (value == 0 || value == 2) {
|
|
/* empty */
|
|
this._map[x][y] = 0;
|
|
this._dug++;
|
|
} else {
|
|
/* wall */
|
|
this._walls[x + "," + y] = 1;
|
|
}
|
|
};
|
|
|
|
_proto27._isWallCallback = function _isWallCallback(x, y) {
|
|
if (x < 0 || y < 0 || x >= this._width || y >= this._height) {
|
|
return false;
|
|
}
|
|
|
|
return this._map[x][y] == 1;
|
|
};
|
|
|
|
_proto27._canBeDugCallback = function _canBeDugCallback(x, y) {
|
|
if (x < 1 || y < 1 || x + 1 >= this._width || y + 1 >= this._height) {
|
|
return false;
|
|
}
|
|
|
|
return this._map[x][y] == 1;
|
|
};
|
|
|
|
_proto27._priorityWallCallback = function _priorityWallCallback(x, y) {
|
|
this._walls[x + "," + y] = 2;
|
|
};
|
|
|
|
_proto27._firstRoom = function _firstRoom() {
|
|
var cx = Math.floor(this._width / 2);
|
|
var cy = Math.floor(this._height / 2);
|
|
var room = Room.createRandomCenter(cx, cy, this._options);
|
|
|
|
this._rooms.push(room);
|
|
|
|
room.create(this._digCallback);
|
|
};
|
|
/**
|
|
* Get a suitable wall
|
|
*/
|
|
|
|
|
|
_proto27._findWall = function _findWall() {
|
|
var prio1 = [];
|
|
var prio2 = [];
|
|
|
|
for (var _id2 in this._walls) {
|
|
var prio = this._walls[_id2];
|
|
|
|
if (prio == 2) {
|
|
prio2.push(_id2);
|
|
} else {
|
|
prio1.push(_id2);
|
|
}
|
|
}
|
|
|
|
var arr = prio2.length ? prio2 : prio1;
|
|
|
|
if (!arr.length) {
|
|
return null;
|
|
}
|
|
/* no walls :/ */
|
|
|
|
|
|
var id = RNG$1.getItem(arr.sort()); // sort to make the order deterministic
|
|
|
|
delete this._walls[id];
|
|
return id;
|
|
};
|
|
/**
|
|
* Tries adding a feature
|
|
* @returns {bool} was this a successful try?
|
|
*/
|
|
|
|
|
|
_proto27._tryFeature = function _tryFeature(x, y, dx, dy) {
|
|
var featureName = RNG$1.getWeightedValue(this._features);
|
|
var ctor = FEATURES[featureName];
|
|
var feature = ctor.createRandomAt(x, y, dx, dy, this._options);
|
|
|
|
if (!feature.isValid(this._isWallCallback, this._canBeDugCallback)) {
|
|
// console.log("not valid");
|
|
// feature.debug();
|
|
return false;
|
|
}
|
|
|
|
feature.create(this._digCallback); // feature.debug();
|
|
|
|
if (feature instanceof Room) {
|
|
this._rooms.push(feature);
|
|
}
|
|
|
|
if (feature instanceof Corridor) {
|
|
feature.createPriorityWalls(this._priorityWallCallback);
|
|
|
|
this._corridors.push(feature);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
_proto27._removeSurroundingWalls = function _removeSurroundingWalls(cx, cy) {
|
|
var deltas = DIRS[4];
|
|
|
|
for (var i = 0; i < deltas.length; i++) {
|
|
var delta = deltas[i];
|
|
var x = cx + delta[0];
|
|
var y = cy + delta[1];
|
|
delete this._walls[x + "," + y];
|
|
x = cx + 2 * delta[0];
|
|
y = cy + 2 * delta[1];
|
|
delete this._walls[x + "," + y];
|
|
}
|
|
};
|
|
/**
|
|
* Returns vector in "digging" direction, or false, if this does not exist (or is not unique)
|
|
*/
|
|
|
|
|
|
_proto27._getDiggingDirection = function _getDiggingDirection(cx, cy) {
|
|
if (cx <= 0 || cy <= 0 || cx >= this._width - 1 || cy >= this._height - 1) {
|
|
return null;
|
|
}
|
|
|
|
var result = null;
|
|
var deltas = DIRS[4];
|
|
|
|
for (var i = 0; i < deltas.length; i++) {
|
|
var delta = deltas[i];
|
|
var x = cx + delta[0];
|
|
var y = cy + delta[1];
|
|
|
|
if (!this._map[x][y]) {
|
|
/* there already is another empty neighbor! */
|
|
if (result) {
|
|
return null;
|
|
}
|
|
|
|
result = delta;
|
|
}
|
|
}
|
|
/* no empty neighbor */
|
|
|
|
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
|
|
return [-result[0], -result[1]];
|
|
};
|
|
/**
|
|
* Find empty spaces surrounding rooms, and apply doors.
|
|
*/
|
|
|
|
|
|
_proto27._addDoors = function _addDoors() {
|
|
var data = this._map;
|
|
|
|
function isWallCallback(x, y) {
|
|
return data[x][y] == 1;
|
|
}
|
|
|
|
for (var i = 0; i < this._rooms.length; i++) {
|
|
var room = this._rooms[i];
|
|
room.clearDoors();
|
|
room.addDoors(isWallCallback);
|
|
}
|
|
};
|
|
|
|
return Digger;
|
|
}(Dungeon);
|
|
/**
|
|
* Join lists with "i" and "i+1"
|
|
*/
|
|
|
|
|
|
function addToList(i, L, R) {
|
|
R[L[i + 1]] = R[i];
|
|
L[R[i]] = L[i + 1];
|
|
R[i] = i + 1;
|
|
L[i + 1] = i;
|
|
}
|
|
/**
|
|
* Remove "i" from its list
|
|
*/
|
|
|
|
|
|
function removeFromList(i, L, R) {
|
|
R[L[i]] = R[i];
|
|
L[R[i]] = L[i];
|
|
R[i] = i;
|
|
L[i] = i;
|
|
}
|
|
/**
|
|
* Maze generator - Eller's algorithm
|
|
* See http://homepages.cwi.nl/~tromp/maze.html for explanation
|
|
*/
|
|
|
|
|
|
var EllerMaze =
|
|
/*#__PURE__*/
|
|
function (_Map4) {
|
|
_inheritsLoose(EllerMaze, _Map4);
|
|
|
|
function EllerMaze() {
|
|
return _Map4.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto28 = EllerMaze.prototype;
|
|
|
|
_proto28.create = function create(callback) {
|
|
var map = this._fillMap(1);
|
|
|
|
var w = Math.ceil((this._width - 2) / 2);
|
|
var rand = 9 / 24;
|
|
var L = [];
|
|
var R = [];
|
|
|
|
for (var i = 0; i < w; i++) {
|
|
L.push(i);
|
|
R.push(i);
|
|
}
|
|
|
|
L.push(w - 1);
|
|
/* fake stop-block at the right side */
|
|
|
|
var j;
|
|
|
|
for (j = 1; j + 3 < this._height; j += 2) {
|
|
/* one row */
|
|
for (var _i4 = 0; _i4 < w; _i4++) {
|
|
/* cell coords (will be always empty) */
|
|
var x = 2 * _i4 + 1;
|
|
var y = j;
|
|
map[x][y] = 0;
|
|
/* right connection */
|
|
|
|
if (_i4 != L[_i4 + 1] && RNG$1.getUniform() > rand) {
|
|
addToList(_i4, L, R);
|
|
map[x + 1][y] = 0;
|
|
}
|
|
/* bottom connection */
|
|
|
|
|
|
if (_i4 != L[_i4] && RNG$1.getUniform() > rand) {
|
|
/* remove connection */
|
|
removeFromList(_i4, L, R);
|
|
} else {
|
|
/* create connection */
|
|
map[x][y + 1] = 0;
|
|
}
|
|
}
|
|
}
|
|
/* last row */
|
|
|
|
|
|
for (var _i5 = 0; _i5 < w; _i5++) {
|
|
/* cell coords (will be always empty) */
|
|
var _x2 = 2 * _i5 + 1;
|
|
|
|
var _y2 = j;
|
|
map[_x2][_y2] = 0;
|
|
/* right connection */
|
|
|
|
if (_i5 != L[_i5 + 1] && (_i5 == L[_i5] || RNG$1.getUniform() > rand)) {
|
|
/* dig right also if the cell is separated, so it gets connected to the rest of maze */
|
|
addToList(_i5, L, R);
|
|
map[_x2 + 1][_y2] = 0;
|
|
}
|
|
|
|
removeFromList(_i5, L, R);
|
|
}
|
|
|
|
for (var _i6 = 0; _i6 < this._width; _i6++) {
|
|
for (var _j = 0; _j < this._height; _j++) {
|
|
callback(_i6, _j, map[_i6][_j]);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
return EllerMaze;
|
|
}(Map);
|
|
/**
|
|
* @class Recursively divided maze, http://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_division_method
|
|
* @augments ROT.Map
|
|
*/
|
|
|
|
|
|
var DividedMaze =
|
|
/*#__PURE__*/
|
|
function (_Map5) {
|
|
_inheritsLoose(DividedMaze, _Map5);
|
|
|
|
function DividedMaze() {
|
|
var _this16;
|
|
|
|
_this16 = _Map5.apply(this, arguments) || this;
|
|
_this16._stack = [];
|
|
_this16._map = [];
|
|
return _this16;
|
|
}
|
|
|
|
var _proto29 = DividedMaze.prototype;
|
|
|
|
_proto29.create = function create(callback) {
|
|
var w = this._width;
|
|
var h = this._height;
|
|
this._map = [];
|
|
|
|
for (var i = 0; i < w; i++) {
|
|
this._map.push([]);
|
|
|
|
for (var j = 0; j < h; j++) {
|
|
var border = i == 0 || j == 0 || i + 1 == w || j + 1 == h;
|
|
|
|
this._map[i].push(border ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
this._stack = [[1, 1, w - 2, h - 2]];
|
|
|
|
this._process();
|
|
|
|
for (var _i7 = 0; _i7 < w; _i7++) {
|
|
for (var _j2 = 0; _j2 < h; _j2++) {
|
|
callback(_i7, _j2, this._map[_i7][_j2]);
|
|
}
|
|
}
|
|
|
|
this._map = [];
|
|
return this;
|
|
};
|
|
|
|
_proto29._process = function _process() {
|
|
while (this._stack.length) {
|
|
var room = this._stack.shift();
|
|
/* [left, top, right, bottom] */
|
|
|
|
|
|
this._partitionRoom(room);
|
|
}
|
|
};
|
|
|
|
_proto29._partitionRoom = function _partitionRoom(room) {
|
|
var availX = [];
|
|
var availY = [];
|
|
|
|
for (var i = room[0] + 1; i < room[2]; i++) {
|
|
var top = this._map[i][room[1] - 1];
|
|
var bottom = this._map[i][room[3] + 1];
|
|
|
|
if (top && bottom && !(i % 2)) {
|
|
availX.push(i);
|
|
}
|
|
}
|
|
|
|
for (var j = room[1] + 1; j < room[3]; j++) {
|
|
var left = this._map[room[0] - 1][j];
|
|
var right = this._map[room[2] + 1][j];
|
|
|
|
if (left && right && !(j % 2)) {
|
|
availY.push(j);
|
|
}
|
|
}
|
|
|
|
if (!availX.length || !availY.length) {
|
|
return;
|
|
}
|
|
|
|
var x = RNG$1.getItem(availX);
|
|
var y = RNG$1.getItem(availY);
|
|
this._map[x][y] = 1;
|
|
var walls = [];
|
|
var w = [];
|
|
walls.push(w);
|
|
/* left part */
|
|
|
|
for (var _i8 = room[0]; _i8 < x; _i8++) {
|
|
this._map[_i8][y] = 1;
|
|
w.push([_i8, y]);
|
|
}
|
|
|
|
w = [];
|
|
walls.push(w);
|
|
/* right part */
|
|
|
|
for (var _i9 = x + 1; _i9 <= room[2]; _i9++) {
|
|
this._map[_i9][y] = 1;
|
|
w.push([_i9, y]);
|
|
}
|
|
|
|
w = [];
|
|
walls.push(w);
|
|
/* top part */
|
|
|
|
for (var _j3 = room[1]; _j3 < y; _j3++) {
|
|
this._map[x][_j3] = 1;
|
|
w.push([x, _j3]);
|
|
}
|
|
|
|
w = [];
|
|
walls.push(w);
|
|
/* bottom part */
|
|
|
|
for (var _j4 = y + 1; _j4 <= room[3]; _j4++) {
|
|
this._map[x][_j4] = 1;
|
|
w.push([x, _j4]);
|
|
}
|
|
|
|
var solid = RNG$1.getItem(walls);
|
|
|
|
for (var _i10 = 0; _i10 < walls.length; _i10++) {
|
|
var _w = walls[_i10];
|
|
|
|
if (_w == solid) {
|
|
continue;
|
|
}
|
|
|
|
var hole = RNG$1.getItem(_w);
|
|
this._map[hole[0]][hole[1]] = 0;
|
|
}
|
|
|
|
this._stack.push([room[0], room[1], x - 1, y - 1]);
|
|
/* left top */
|
|
|
|
|
|
this._stack.push([x + 1, room[1], room[2], y - 1]);
|
|
/* right top */
|
|
|
|
|
|
this._stack.push([room[0], y + 1, x - 1, room[3]]);
|
|
/* left bottom */
|
|
|
|
|
|
this._stack.push([x + 1, y + 1, room[2], room[3]]);
|
|
/* right bottom */
|
|
|
|
};
|
|
|
|
return DividedMaze;
|
|
}(Map);
|
|
/**
|
|
* Icey's Maze generator
|
|
* See http://www.roguebasin.roguelikedevelopment.org/index.php?title=Simple_maze for explanation
|
|
*/
|
|
|
|
|
|
var IceyMaze =
|
|
/*#__PURE__*/
|
|
function (_Map6) {
|
|
_inheritsLoose(IceyMaze, _Map6);
|
|
|
|
function IceyMaze(width, height, regularity) {
|
|
var _this17;
|
|
|
|
if (regularity === void 0) {
|
|
regularity = 0;
|
|
}
|
|
|
|
_this17 = _Map6.call(this, width, height) || this;
|
|
_this17._regularity = regularity;
|
|
_this17._map = [];
|
|
return _this17;
|
|
}
|
|
|
|
var _proto30 = IceyMaze.prototype;
|
|
|
|
_proto30.create = function create(callback) {
|
|
var width = this._width;
|
|
var height = this._height;
|
|
|
|
var map = this._fillMap(1);
|
|
|
|
width -= width % 2 ? 1 : 2;
|
|
height -= height % 2 ? 1 : 2;
|
|
var cx = 0;
|
|
var cy = 0;
|
|
var nx = 0;
|
|
var ny = 0;
|
|
var done = 0;
|
|
var blocked = false;
|
|
var dirs = [[0, 0], [0, 0], [0, 0], [0, 0]];
|
|
|
|
do {
|
|
cx = 1 + 2 * Math.floor(RNG$1.getUniform() * (width - 1) / 2);
|
|
cy = 1 + 2 * Math.floor(RNG$1.getUniform() * (height - 1) / 2);
|
|
|
|
if (!done) {
|
|
map[cx][cy] = 0;
|
|
}
|
|
|
|
if (!map[cx][cy]) {
|
|
this._randomize(dirs);
|
|
|
|
do {
|
|
if (Math.floor(RNG$1.getUniform() * (this._regularity + 1)) == 0) {
|
|
this._randomize(dirs);
|
|
}
|
|
|
|
blocked = true;
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
nx = cx + dirs[i][0] * 2;
|
|
ny = cy + dirs[i][1] * 2;
|
|
|
|
if (this._isFree(map, nx, ny, width, height)) {
|
|
map[nx][ny] = 0;
|
|
map[cx + dirs[i][0]][cy + dirs[i][1]] = 0;
|
|
cx = nx;
|
|
cy = ny;
|
|
blocked = false;
|
|
done++;
|
|
break;
|
|
}
|
|
}
|
|
} while (!blocked);
|
|
}
|
|
} while (done + 1 < width * height / 4);
|
|
|
|
for (var _i11 = 0; _i11 < this._width; _i11++) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
callback(_i11, j, map[_i11][j]);
|
|
}
|
|
}
|
|
|
|
this._map = [];
|
|
return this;
|
|
};
|
|
|
|
_proto30._randomize = function _randomize(dirs) {
|
|
for (var i = 0; i < 4; i++) {
|
|
dirs[i][0] = 0;
|
|
dirs[i][1] = 0;
|
|
}
|
|
|
|
switch (Math.floor(RNG$1.getUniform() * 4)) {
|
|
case 0:
|
|
dirs[0][0] = -1;
|
|
dirs[1][0] = 1;
|
|
dirs[2][1] = -1;
|
|
dirs[3][1] = 1;
|
|
break;
|
|
|
|
case 1:
|
|
dirs[3][0] = -1;
|
|
dirs[2][0] = 1;
|
|
dirs[1][1] = -1;
|
|
dirs[0][1] = 1;
|
|
break;
|
|
|
|
case 2:
|
|
dirs[2][0] = -1;
|
|
dirs[3][0] = 1;
|
|
dirs[0][1] = -1;
|
|
dirs[1][1] = 1;
|
|
break;
|
|
|
|
case 3:
|
|
dirs[1][0] = -1;
|
|
dirs[0][0] = 1;
|
|
dirs[3][1] = -1;
|
|
dirs[2][1] = 1;
|
|
break;
|
|
}
|
|
};
|
|
|
|
_proto30._isFree = function _isFree(map, x, y, width, height) {
|
|
if (x < 1 || y < 1 || x >= width || y >= height) {
|
|
return false;
|
|
}
|
|
|
|
return map[x][y];
|
|
};
|
|
|
|
return IceyMaze;
|
|
}(Map);
|
|
/**
|
|
* Dungeon generator which uses the "orginal" Rogue dungeon generation algorithm. See http://kuoi.com/~kamikaze/GameDesign/art07_rogue_dungeon.php
|
|
* @author hyakugei
|
|
*/
|
|
|
|
|
|
var Rogue =
|
|
/*#__PURE__*/
|
|
function (_Map7) {
|
|
_inheritsLoose(Rogue, _Map7);
|
|
|
|
function Rogue(width, height, options) {
|
|
var _this18;
|
|
|
|
_this18 = _Map7.call(this, width, height) || this;
|
|
_this18.map = [];
|
|
_this18.rooms = [];
|
|
_this18.connectedCells = [];
|
|
options = Object.assign({
|
|
cellWidth: 3,
|
|
cellHeight: 3 // ie. as an array with min-max values for each direction....
|
|
|
|
}, options);
|
|
/*
|
|
Set the room sizes according to the over-all width of the map,
|
|
and the cell sizes.
|
|
*/
|
|
|
|
if (!options.hasOwnProperty("roomWidth")) {
|
|
options["roomWidth"] = _this18._calculateRoomSize(_this18._width, options["cellWidth"]);
|
|
}
|
|
|
|
if (!options.hasOwnProperty("roomHeight")) {
|
|
options["roomHeight"] = _this18._calculateRoomSize(_this18._height, options["cellHeight"]);
|
|
}
|
|
|
|
_this18._options = options;
|
|
return _this18;
|
|
}
|
|
|
|
var _proto31 = Rogue.prototype;
|
|
|
|
_proto31.create = function create(callback) {
|
|
this.map = this._fillMap(1);
|
|
this.rooms = [];
|
|
this.connectedCells = [];
|
|
|
|
this._initRooms();
|
|
|
|
this._connectRooms();
|
|
|
|
this._connectUnconnectedRooms();
|
|
|
|
this._createRandomRoomConnections();
|
|
|
|
this._createRooms();
|
|
|
|
this._createCorridors();
|
|
|
|
if (callback) {
|
|
for (var i = 0; i < this._width; i++) {
|
|
for (var j = 0; j < this._height; j++) {
|
|
callback(i, j, this.map[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
_proto31._calculateRoomSize = function _calculateRoomSize(size, cell) {
|
|
var max = Math.floor(size / cell * 0.8);
|
|
var min = Math.floor(size / cell * 0.25);
|
|
|
|
if (min < 2) {
|
|
min = 2;
|
|
}
|
|
|
|
if (max < 2) {
|
|
max = 2;
|
|
}
|
|
|
|
return [min, max];
|
|
};
|
|
|
|
_proto31._initRooms = function _initRooms() {
|
|
// create rooms array. This is the "grid" list from the algo.
|
|
for (var i = 0; i < this._options.cellWidth; i++) {
|
|
this.rooms.push([]);
|
|
|
|
for (var j = 0; j < this._options.cellHeight; j++) {
|
|
this.rooms[i].push({
|
|
"x": 0,
|
|
"y": 0,
|
|
"width": 0,
|
|
"height": 0,
|
|
"connections": [],
|
|
"cellx": i,
|
|
"celly": j
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto31._connectRooms = function _connectRooms() {
|
|
//pick random starting grid
|
|
var cgx = RNG$1.getUniformInt(0, this._options.cellWidth - 1);
|
|
var cgy = RNG$1.getUniformInt(0, this._options.cellHeight - 1);
|
|
var idx;
|
|
var ncgx;
|
|
var ncgy;
|
|
var found = false;
|
|
var room;
|
|
var otherRoom;
|
|
var dirToCheck; // find unconnected neighbour cells
|
|
|
|
do {
|
|
//dirToCheck = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
dirToCheck = [0, 2, 4, 6];
|
|
dirToCheck = RNG$1.shuffle(dirToCheck);
|
|
|
|
do {
|
|
found = false;
|
|
idx = dirToCheck.pop();
|
|
ncgx = cgx + DIRS[8][idx][0];
|
|
ncgy = cgy + DIRS[8][idx][1];
|
|
|
|
if (ncgx < 0 || ncgx >= this._options.cellWidth) {
|
|
continue;
|
|
}
|
|
|
|
if (ncgy < 0 || ncgy >= this._options.cellHeight) {
|
|
continue;
|
|
}
|
|
|
|
room = this.rooms[cgx][cgy];
|
|
|
|
if (room["connections"].length > 0) {
|
|
// as long as this room doesn't already coonect to me, we are ok with it.
|
|
if (room["connections"][0][0] == ncgx && room["connections"][0][1] == ncgy) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
otherRoom = this.rooms[ncgx][ncgy];
|
|
|
|
if (otherRoom["connections"].length == 0) {
|
|
otherRoom["connections"].push([cgx, cgy]);
|
|
this.connectedCells.push([ncgx, ncgy]);
|
|
cgx = ncgx;
|
|
cgy = ncgy;
|
|
found = true;
|
|
}
|
|
} while (dirToCheck.length > 0 && found == false);
|
|
} while (dirToCheck.length > 0);
|
|
};
|
|
|
|
_proto31._connectUnconnectedRooms = function _connectUnconnectedRooms() {
|
|
//While there are unconnected rooms, try to connect them to a random connected neighbor
|
|
//(if a room has no connected neighbors yet, just keep cycling, you'll fill out to it eventually).
|
|
var cw = this._options.cellWidth;
|
|
var ch = this._options.cellHeight;
|
|
this.connectedCells = RNG$1.shuffle(this.connectedCells);
|
|
var room;
|
|
var otherRoom;
|
|
var validRoom;
|
|
|
|
for (var i = 0; i < this._options.cellWidth; i++) {
|
|
for (var j = 0; j < this._options.cellHeight; j++) {
|
|
room = this.rooms[i][j];
|
|
|
|
if (room["connections"].length == 0) {
|
|
var directions = [0, 2, 4, 6];
|
|
directions = RNG$1.shuffle(directions);
|
|
validRoom = false;
|
|
|
|
do {
|
|
var dirIdx = directions.pop();
|
|
var newI = i + DIRS[8][dirIdx][0];
|
|
var newJ = j + DIRS[8][dirIdx][1];
|
|
|
|
if (newI < 0 || newI >= cw || newJ < 0 || newJ >= ch) {
|
|
continue;
|
|
}
|
|
|
|
otherRoom = this.rooms[newI][newJ];
|
|
validRoom = true;
|
|
|
|
if (otherRoom["connections"].length == 0) {
|
|
break;
|
|
}
|
|
|
|
for (var k = 0; k < otherRoom["connections"].length; k++) {
|
|
if (otherRoom["connections"][k][0] == i && otherRoom["connections"][k][1] == j) {
|
|
validRoom = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (validRoom) {
|
|
break;
|
|
}
|
|
} while (directions.length);
|
|
|
|
if (validRoom) {
|
|
room["connections"].push([otherRoom["cellx"], otherRoom["celly"]]);
|
|
} else {
|
|
console.log("-- Unable to connect room.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto31._createRandomRoomConnections = function _createRandomRoomConnections() {// Empty for now.
|
|
};
|
|
|
|
_proto31._createRooms = function _createRooms() {
|
|
var w = this._width;
|
|
var h = this._height;
|
|
var cw = this._options.cellWidth;
|
|
var ch = this._options.cellHeight;
|
|
var cwp = Math.floor(this._width / cw);
|
|
var chp = Math.floor(this._height / ch);
|
|
var roomw;
|
|
var roomh;
|
|
var roomWidth = this._options["roomWidth"];
|
|
var roomHeight = this._options["roomHeight"];
|
|
var sx;
|
|
var sy;
|
|
var otherRoom;
|
|
|
|
for (var i = 0; i < cw; i++) {
|
|
for (var j = 0; j < ch; j++) {
|
|
sx = cwp * i;
|
|
sy = chp * j;
|
|
|
|
if (sx == 0) {
|
|
sx = 1;
|
|
}
|
|
|
|
if (sy == 0) {
|
|
sy = 1;
|
|
}
|
|
|
|
roomw = RNG$1.getUniformInt(roomWidth[0], roomWidth[1]);
|
|
roomh = RNG$1.getUniformInt(roomHeight[0], roomHeight[1]);
|
|
|
|
if (j > 0) {
|
|
otherRoom = this.rooms[i][j - 1];
|
|
|
|
while (sy - (otherRoom["y"] + otherRoom["height"]) < 3) {
|
|
sy++;
|
|
}
|
|
}
|
|
|
|
if (i > 0) {
|
|
otherRoom = this.rooms[i - 1][j];
|
|
|
|
while (sx - (otherRoom["x"] + otherRoom["width"]) < 3) {
|
|
sx++;
|
|
}
|
|
}
|
|
|
|
var sxOffset = Math.round(RNG$1.getUniformInt(0, cwp - roomw) / 2);
|
|
var syOffset = Math.round(RNG$1.getUniformInt(0, chp - roomh) / 2);
|
|
|
|
while (sx + sxOffset + roomw >= w) {
|
|
if (sxOffset) {
|
|
sxOffset--;
|
|
} else {
|
|
roomw--;
|
|
}
|
|
}
|
|
|
|
while (sy + syOffset + roomh >= h) {
|
|
if (syOffset) {
|
|
syOffset--;
|
|
} else {
|
|
roomh--;
|
|
}
|
|
}
|
|
|
|
sx = sx + sxOffset;
|
|
sy = sy + syOffset;
|
|
this.rooms[i][j]["x"] = sx;
|
|
this.rooms[i][j]["y"] = sy;
|
|
this.rooms[i][j]["width"] = roomw;
|
|
this.rooms[i][j]["height"] = roomh;
|
|
|
|
for (var ii = sx; ii < sx + roomw; ii++) {
|
|
for (var jj = sy; jj < sy + roomh; jj++) {
|
|
this.map[ii][jj] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto31._getWallPosition = function _getWallPosition(aRoom, aDirection) {
|
|
var rx;
|
|
var ry;
|
|
var door;
|
|
|
|
if (aDirection == 1 || aDirection == 3) {
|
|
rx = RNG$1.getUniformInt(aRoom["x"] + 1, aRoom["x"] + aRoom["width"] - 2);
|
|
|
|
if (aDirection == 1) {
|
|
ry = aRoom["y"] - 2;
|
|
door = ry + 1;
|
|
} else {
|
|
ry = aRoom["y"] + aRoom["height"] + 1;
|
|
door = ry - 1;
|
|
}
|
|
|
|
this.map[rx][door] = 0; // i'm not setting a specific 'door' tile value right now, just empty space.
|
|
} else {
|
|
ry = RNG$1.getUniformInt(aRoom["y"] + 1, aRoom["y"] + aRoom["height"] - 2);
|
|
|
|
if (aDirection == 2) {
|
|
rx = aRoom["x"] + aRoom["width"] + 1;
|
|
door = rx - 1;
|
|
} else {
|
|
rx = aRoom["x"] - 2;
|
|
door = rx + 1;
|
|
}
|
|
|
|
this.map[door][ry] = 0; // i'm not setting a specific 'door' tile value right now, just empty space.
|
|
}
|
|
|
|
return [rx, ry];
|
|
};
|
|
|
|
_proto31._drawCorridor = function _drawCorridor(startPosition, endPosition) {
|
|
var xOffset = endPosition[0] - startPosition[0];
|
|
var yOffset = endPosition[1] - startPosition[1];
|
|
var xpos = startPosition[0];
|
|
var ypos = startPosition[1];
|
|
var tempDist;
|
|
var xDir;
|
|
var yDir;
|
|
var move; // 2 element array, element 0 is the direction, element 1 is the total value to move.
|
|
|
|
var moves = []; // a list of 2 element arrays
|
|
|
|
var xAbs = Math.abs(xOffset);
|
|
var yAbs = Math.abs(yOffset);
|
|
var percent = RNG$1.getUniform(); // used to split the move at different places along the long axis
|
|
|
|
var firstHalf = percent;
|
|
var secondHalf = 1 - percent;
|
|
xDir = xOffset > 0 ? 2 : 6;
|
|
yDir = yOffset > 0 ? 4 : 0;
|
|
|
|
if (xAbs < yAbs) {
|
|
// move firstHalf of the y offset
|
|
tempDist = Math.ceil(yAbs * firstHalf);
|
|
moves.push([yDir, tempDist]); // move all the x offset
|
|
|
|
moves.push([xDir, xAbs]); // move sendHalf of the y offset
|
|
|
|
tempDist = Math.floor(yAbs * secondHalf);
|
|
moves.push([yDir, tempDist]);
|
|
} else {
|
|
// move firstHalf of the x offset
|
|
tempDist = Math.ceil(xAbs * firstHalf);
|
|
moves.push([xDir, tempDist]); // move all the y offset
|
|
|
|
moves.push([yDir, yAbs]); // move secondHalf of the x offset.
|
|
|
|
tempDist = Math.floor(xAbs * secondHalf);
|
|
moves.push([xDir, tempDist]);
|
|
}
|
|
|
|
this.map[xpos][ypos] = 0;
|
|
|
|
while (moves.length > 0) {
|
|
move = moves.pop();
|
|
|
|
while (move[1] > 0) {
|
|
xpos += DIRS[8][move[0]][0];
|
|
ypos += DIRS[8][move[0]][1];
|
|
this.map[xpos][ypos] = 0;
|
|
move[1] = move[1] - 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto31._createCorridors = function _createCorridors() {
|
|
// Draw Corridors between connected rooms
|
|
var cw = this._options.cellWidth;
|
|
var ch = this._options.cellHeight;
|
|
var room;
|
|
var connection;
|
|
var otherRoom;
|
|
var wall;
|
|
var otherWall;
|
|
|
|
for (var i = 0; i < cw; i++) {
|
|
for (var j = 0; j < ch; j++) {
|
|
room = this.rooms[i][j];
|
|
|
|
for (var k = 0; k < room["connections"].length; k++) {
|
|
connection = room["connections"][k];
|
|
otherRoom = this.rooms[connection[0]][connection[1]]; // figure out what wall our corridor will start one.
|
|
// figure out what wall our corridor will end on.
|
|
|
|
if (otherRoom["cellx"] > room["cellx"]) {
|
|
wall = 2;
|
|
otherWall = 4;
|
|
} else if (otherRoom["cellx"] < room["cellx"]) {
|
|
wall = 4;
|
|
otherWall = 2;
|
|
} else if (otherRoom["celly"] > room["celly"]) {
|
|
wall = 3;
|
|
otherWall = 1;
|
|
} else {
|
|
wall = 1;
|
|
otherWall = 3;
|
|
}
|
|
|
|
this._drawCorridor(this._getWallPosition(room, wall), this._getWallPosition(otherRoom, otherWall));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return Rogue;
|
|
}(Map);
|
|
|
|
var index$2 = {
|
|
Arena: Arena,
|
|
Uniform: Uniform,
|
|
Cellular: Cellular,
|
|
Digger: Digger,
|
|
EllerMaze: EllerMaze,
|
|
DividedMaze: DividedMaze,
|
|
IceyMaze: IceyMaze,
|
|
Rogue: Rogue
|
|
};
|
|
/**
|
|
* Base noise generator
|
|
*/
|
|
|
|
var Noise = function Noise() {};
|
|
|
|
var F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
var G2 = (3 - Math.sqrt(3)) / 6;
|
|
/**
|
|
* A simple 2d implementation of simplex noise by Ondrej Zara
|
|
*
|
|
* Based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java.
|
|
* Which is based on example code by Stefan Gustavson (stegu@itn.liu.se).
|
|
* With Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).
|
|
* Better rank ordering method by Stefan Gustavson in 2012.
|
|
*/
|
|
|
|
var Simplex =
|
|
/*#__PURE__*/
|
|
function (_Noise) {
|
|
_inheritsLoose(Simplex, _Noise);
|
|
|
|
/**
|
|
* @param gradients Random gradients
|
|
*/
|
|
function Simplex(gradients) {
|
|
var _this19;
|
|
|
|
if (gradients === void 0) {
|
|
gradients = 256;
|
|
}
|
|
|
|
_this19 = _Noise.call(this) || this;
|
|
_this19._gradients = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]];
|
|
var permutations = [];
|
|
|
|
for (var i = 0; i < gradients; i++) {
|
|
permutations.push(i);
|
|
}
|
|
|
|
permutations = RNG$1.shuffle(permutations);
|
|
_this19._perms = [];
|
|
_this19._indexes = [];
|
|
|
|
for (var _i12 = 0; _i12 < 2 * gradients; _i12++) {
|
|
_this19._perms.push(permutations[_i12 % gradients]);
|
|
|
|
_this19._indexes.push(_this19._perms[_i12] % _this19._gradients.length);
|
|
}
|
|
|
|
return _this19;
|
|
}
|
|
|
|
var _proto32 = Simplex.prototype;
|
|
|
|
_proto32.get = function get(xin, yin) {
|
|
var perms = this._perms;
|
|
var indexes = this._indexes;
|
|
var count = perms.length / 2;
|
|
var n0 = 0,
|
|
n1 = 0,
|
|
n2 = 0,
|
|
gi; // Noise contributions from the three corners
|
|
// Skew the input space to determine which simplex cell we're in
|
|
|
|
var s = (xin + yin) * F2; // Hairy factor for 2D
|
|
|
|
var i = Math.floor(xin + s);
|
|
var j = Math.floor(yin + s);
|
|
var t = (i + j) * G2;
|
|
var X0 = i - t; // Unskew the cell origin back to (x,y) space
|
|
|
|
var Y0 = j - t;
|
|
var x0 = xin - X0; // The x,y distances from the cell origin
|
|
|
|
var y0 = yin - Y0; // For the 2D case, the simplex shape is an equilateral triangle.
|
|
// Determine which simplex we are in.
|
|
|
|
var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
|
|
|
|
if (x0 > y0) {
|
|
i1 = 1;
|
|
j1 = 0;
|
|
} else {
|
|
// lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
|
i1 = 0;
|
|
j1 = 1;
|
|
} // upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
|
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
|
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
|
// c = (3-sqrt(3))/6
|
|
|
|
|
|
var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
|
|
|
|
var y1 = y0 - j1 + G2;
|
|
var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
|
|
|
|
var y2 = y0 - 1 + 2 * G2; // Work out the hashed gradient indices of the three simplex corners
|
|
|
|
var ii = mod(i, count);
|
|
var jj = mod(j, count); // Calculate the contribution from the three corners
|
|
|
|
var t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
|
|
if (t0 >= 0) {
|
|
t0 *= t0;
|
|
gi = indexes[ii + perms[jj]];
|
|
var grad = this._gradients[gi];
|
|
n0 = t0 * t0 * (grad[0] * x0 + grad[1] * y0);
|
|
}
|
|
|
|
var t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
|
|
if (t1 >= 0) {
|
|
t1 *= t1;
|
|
gi = indexes[ii + i1 + perms[jj + j1]];
|
|
var _grad = this._gradients[gi];
|
|
n1 = t1 * t1 * (_grad[0] * x1 + _grad[1] * y1);
|
|
}
|
|
|
|
var t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
|
|
if (t2 >= 0) {
|
|
t2 *= t2;
|
|
gi = indexes[ii + 1 + perms[jj + 1]];
|
|
var _grad2 = this._gradients[gi];
|
|
n2 = t2 * t2 * (_grad2[0] * x2 + _grad2[1] * y2);
|
|
} // Add contributions from each corner to get the final noise value.
|
|
// The result is scaled to return values in the interval [-1,1].
|
|
|
|
|
|
return 70 * (n0 + n1 + n2);
|
|
};
|
|
|
|
return Simplex;
|
|
}(Noise);
|
|
|
|
var index$3 = {
|
|
Simplex: Simplex
|
|
};
|
|
/**
|
|
* @class Abstract pathfinder
|
|
* @param {int} toX Target X coord
|
|
* @param {int} toY Target Y coord
|
|
* @param {function} passableCallback Callback to determine map passability
|
|
* @param {object} [options]
|
|
* @param {int} [options.topology=8]
|
|
*/
|
|
|
|
var Path =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Path(toX, toY, passableCallback, options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
this._toX = toX;
|
|
this._toY = toY;
|
|
this._passableCallback = passableCallback;
|
|
this._options = Object.assign({
|
|
topology: 8
|
|
}, options);
|
|
this._dirs = DIRS[this._options.topology];
|
|
|
|
if (this._options.topology == 8) {
|
|
/* reorder dirs for more aesthetic result (vertical/horizontal first) */
|
|
this._dirs = [this._dirs[0], this._dirs[2], this._dirs[4], this._dirs[6], this._dirs[1], this._dirs[3], this._dirs[5], this._dirs[7]];
|
|
}
|
|
}
|
|
|
|
var _proto33 = Path.prototype;
|
|
|
|
_proto33._getNeighbors = function _getNeighbors(cx, cy) {
|
|
var result = [];
|
|
|
|
for (var i = 0; i < this._dirs.length; i++) {
|
|
var dir = this._dirs[i];
|
|
var x = cx + dir[0];
|
|
var y = cy + dir[1];
|
|
|
|
if (!this._passableCallback(x, y)) {
|
|
continue;
|
|
}
|
|
|
|
result.push([x, y]);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
return Path;
|
|
}();
|
|
/**
|
|
* @class Simplified Dijkstra's algorithm: all edges have a value of 1
|
|
* @augments ROT.Path
|
|
* @see ROT.Path
|
|
*/
|
|
|
|
|
|
var Dijkstra =
|
|
/*#__PURE__*/
|
|
function (_Path) {
|
|
_inheritsLoose(Dijkstra, _Path);
|
|
|
|
function Dijkstra(toX, toY, passableCallback, options) {
|
|
var _this20;
|
|
|
|
_this20 = _Path.call(this, toX, toY, passableCallback, options) || this;
|
|
_this20._computed = {};
|
|
_this20._todo = [];
|
|
|
|
_this20._add(toX, toY, null);
|
|
|
|
return _this20;
|
|
}
|
|
/**
|
|
* Compute a path from a given point
|
|
* @see ROT.Path#compute
|
|
*/
|
|
|
|
|
|
var _proto34 = Dijkstra.prototype;
|
|
|
|
_proto34.compute = function compute(fromX, fromY, callback) {
|
|
var key = fromX + "," + fromY;
|
|
|
|
if (!(key in this._computed)) {
|
|
this._compute(fromX, fromY);
|
|
}
|
|
|
|
if (!(key in this._computed)) {
|
|
return;
|
|
}
|
|
|
|
var item = this._computed[key];
|
|
|
|
while (item) {
|
|
callback(item.x, item.y);
|
|
item = item.prev;
|
|
}
|
|
};
|
|
/**
|
|
* Compute a non-cached value
|
|
*/
|
|
|
|
|
|
_proto34._compute = function _compute(fromX, fromY) {
|
|
while (this._todo.length) {
|
|
var item = this._todo.shift();
|
|
|
|
if (item.x == fromX && item.y == fromY) {
|
|
return;
|
|
}
|
|
|
|
var neighbors = this._getNeighbors(item.x, item.y);
|
|
|
|
for (var i = 0; i < neighbors.length; i++) {
|
|
var neighbor = neighbors[i];
|
|
var x = neighbor[0];
|
|
var y = neighbor[1];
|
|
var id = x + "," + y;
|
|
|
|
if (id in this._computed) {
|
|
continue;
|
|
}
|
|
/* already done */
|
|
|
|
|
|
this._add(x, y, item);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto34._add = function _add(x, y, prev) {
|
|
var obj = {
|
|
x: x,
|
|
y: y,
|
|
prev: prev
|
|
};
|
|
this._computed[x + "," + y] = obj;
|
|
|
|
this._todo.push(obj);
|
|
};
|
|
|
|
return Dijkstra;
|
|
}(Path);
|
|
/**
|
|
* @class Simplified A* algorithm: all edges have a value of 1
|
|
* @augments ROT.Path
|
|
* @see ROT.Path
|
|
*/
|
|
|
|
|
|
var AStar =
|
|
/*#__PURE__*/
|
|
function (_Path2) {
|
|
_inheritsLoose(AStar, _Path2);
|
|
|
|
function AStar(toX, toY, passableCallback, options) {
|
|
var _this21;
|
|
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
_this21 = _Path2.call(this, toX, toY, passableCallback, options) || this;
|
|
_this21._todo = [];
|
|
_this21._done = {};
|
|
return _this21;
|
|
}
|
|
/**
|
|
* Compute a path from a given point
|
|
* @see ROT.Path#compute
|
|
*/
|
|
|
|
|
|
var _proto35 = AStar.prototype;
|
|
|
|
_proto35.compute = function compute(fromX, fromY, callback) {
|
|
this._todo = [];
|
|
this._done = {};
|
|
this._fromX = fromX;
|
|
this._fromY = fromY;
|
|
|
|
this._add(this._toX, this._toY, null);
|
|
|
|
while (this._todo.length) {
|
|
var _item = this._todo.shift();
|
|
|
|
var id = _item.x + "," + _item.y;
|
|
|
|
if (id in this._done) {
|
|
continue;
|
|
}
|
|
|
|
this._done[id] = _item;
|
|
|
|
if (_item.x == fromX && _item.y == fromY) {
|
|
break;
|
|
}
|
|
|
|
var neighbors = this._getNeighbors(_item.x, _item.y);
|
|
|
|
for (var i = 0; i < neighbors.length; i++) {
|
|
var neighbor = neighbors[i];
|
|
var x = neighbor[0];
|
|
var y = neighbor[1];
|
|
|
|
var _id3 = x + "," + y;
|
|
|
|
if (_id3 in this._done) {
|
|
continue;
|
|
}
|
|
|
|
this._add(x, y, _item);
|
|
}
|
|
}
|
|
|
|
var item = this._done[fromX + "," + fromY];
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
while (item) {
|
|
callback(item.x, item.y);
|
|
item = item.prev;
|
|
}
|
|
};
|
|
|
|
_proto35._add = function _add(x, y, prev) {
|
|
var h = this._distance(x, y);
|
|
|
|
var obj = {
|
|
x: x,
|
|
y: y,
|
|
prev: prev,
|
|
g: prev ? prev.g + 1 : 0,
|
|
h: h
|
|
};
|
|
/* insert into priority queue */
|
|
|
|
var f = obj.g + obj.h;
|
|
|
|
for (var i = 0; i < this._todo.length; i++) {
|
|
var item = this._todo[i];
|
|
var itemF = item.g + item.h;
|
|
|
|
if (f < itemF || f == itemF && h < item.h) {
|
|
this._todo.splice(i, 0, obj);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
this._todo.push(obj);
|
|
};
|
|
|
|
_proto35._distance = function _distance(x, y) {
|
|
switch (this._options.topology) {
|
|
case 4:
|
|
return Math.abs(x - this._fromX) + Math.abs(y - this._fromY);
|
|
break;
|
|
|
|
case 6:
|
|
var dx = Math.abs(x - this._fromX);
|
|
var dy = Math.abs(y - this._fromY);
|
|
return dy + Math.max(0, (dx - dy) / 2);
|
|
break;
|
|
|
|
case 8:
|
|
return Math.max(Math.abs(x - this._fromX), Math.abs(y - this._fromY));
|
|
break;
|
|
}
|
|
};
|
|
|
|
return AStar;
|
|
}(Path);
|
|
|
|
var index$4 = {
|
|
Dijkstra: Dijkstra,
|
|
AStar: AStar
|
|
};
|
|
/**
|
|
* @class Asynchronous main loop
|
|
* @param {ROT.Scheduler} scheduler
|
|
*/
|
|
|
|
var Engine =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Engine(scheduler) {
|
|
this._scheduler = scheduler;
|
|
this._lock = 1;
|
|
}
|
|
/**
|
|
* Start the main loop. When this call returns, the loop is locked.
|
|
*/
|
|
|
|
|
|
var _proto36 = Engine.prototype;
|
|
|
|
_proto36.start = function start() {
|
|
return this.unlock();
|
|
};
|
|
/**
|
|
* Interrupt the engine by an asynchronous action
|
|
*/
|
|
|
|
|
|
_proto36.lock = function lock() {
|
|
this._lock++;
|
|
return this;
|
|
};
|
|
/**
|
|
* Resume execution (paused by a previous lock)
|
|
*/
|
|
|
|
|
|
_proto36.unlock = function unlock() {
|
|
if (!this._lock) {
|
|
throw new Error("Cannot unlock unlocked engine");
|
|
}
|
|
|
|
this._lock--;
|
|
|
|
while (!this._lock) {
|
|
var actor = this._scheduler.next();
|
|
|
|
if (!actor) {
|
|
return this.lock();
|
|
}
|
|
/* no actors */
|
|
|
|
|
|
var result = actor.act();
|
|
|
|
if (result && result.then) {
|
|
/* actor returned a "thenable", looks like a Promise */
|
|
this.lock();
|
|
result.then(this.unlock.bind(this));
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
return Engine;
|
|
}();
|
|
/**
|
|
* Lighting computation, based on a traditional FOV for multiple light sources and multiple passes.
|
|
*/
|
|
|
|
|
|
var Lighting =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Lighting(reflectivityCallback, options) {
|
|
if (options === void 0) {
|
|
options = {};
|
|
}
|
|
|
|
this._reflectivityCallback = reflectivityCallback;
|
|
this._options = {};
|
|
options = Object.assign({
|
|
passes: 1,
|
|
emissionThreshold: 100,
|
|
range: 10
|
|
}, options);
|
|
this._lights = {};
|
|
this._reflectivityCache = {};
|
|
this._fovCache = {};
|
|
this.setOptions(options);
|
|
}
|
|
/**
|
|
* Adjust options at runtime
|
|
*/
|
|
|
|
|
|
var _proto37 = Lighting.prototype;
|
|
|
|
_proto37.setOptions = function setOptions(options) {
|
|
Object.assign(this._options, options);
|
|
|
|
if (options && options.range) {
|
|
this.reset();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Set the used Field-Of-View algo
|
|
*/
|
|
|
|
|
|
_proto37.setFOV = function setFOV(fov) {
|
|
this._fov = fov;
|
|
this._fovCache = {};
|
|
return this;
|
|
};
|
|
/**
|
|
* Set (or remove) a light source
|
|
*/
|
|
|
|
|
|
_proto37.setLight = function setLight(x, y, color) {
|
|
var key = x + "," + y;
|
|
|
|
if (color) {
|
|
this._lights[key] = typeof color == "string" ? fromString(color) : color;
|
|
} else {
|
|
delete this._lights[key];
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Remove all light sources
|
|
*/
|
|
|
|
|
|
_proto37.clearLights = function clearLights() {
|
|
this._lights = {};
|
|
};
|
|
/**
|
|
* Reset the pre-computed topology values. Call whenever the underlying map changes its light-passability.
|
|
*/
|
|
|
|
|
|
_proto37.reset = function reset() {
|
|
this._reflectivityCache = {};
|
|
this._fovCache = {};
|
|
return this;
|
|
};
|
|
/**
|
|
* Compute the lighting
|
|
*/
|
|
|
|
|
|
_proto37.compute = function compute(lightingCallback) {
|
|
var doneCells = {};
|
|
var emittingCells = {};
|
|
var litCells = {};
|
|
|
|
for (var key in this._lights) {
|
|
/* prepare emitters for first pass */
|
|
var light = this._lights[key];
|
|
emittingCells[key] = [0, 0, 0];
|
|
add_(emittingCells[key], light);
|
|
}
|
|
|
|
for (var i = 0; i < this._options.passes; i++) {
|
|
/* main loop */
|
|
this._emitLight(emittingCells, litCells, doneCells);
|
|
|
|
if (i + 1 == this._options.passes) {
|
|
continue;
|
|
}
|
|
/* not for the last pass */
|
|
|
|
|
|
emittingCells = this._computeEmitters(litCells, doneCells);
|
|
}
|
|
|
|
for (var litKey in litCells) {
|
|
/* let the user know what and how is lit */
|
|
var parts = litKey.split(",");
|
|
var x = parseInt(parts[0]);
|
|
var y = parseInt(parts[1]);
|
|
lightingCallback(x, y, litCells[litKey]);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Compute one iteration from all emitting cells
|
|
* @param emittingCells These emit light
|
|
* @param litCells Add projected light to these
|
|
* @param doneCells These already emitted, forbid them from further calculations
|
|
*/
|
|
|
|
|
|
_proto37._emitLight = function _emitLight(emittingCells, litCells, doneCells) {
|
|
for (var key in emittingCells) {
|
|
var parts = key.split(",");
|
|
var x = parseInt(parts[0]);
|
|
var y = parseInt(parts[1]);
|
|
|
|
this._emitLightFromCell(x, y, emittingCells[key], litCells);
|
|
|
|
doneCells[key] = 1;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Prepare a list of emitters for next pass
|
|
*/
|
|
|
|
|
|
_proto37._computeEmitters = function _computeEmitters(litCells, doneCells) {
|
|
var result = {};
|
|
|
|
for (var key in litCells) {
|
|
if (key in doneCells) {
|
|
continue;
|
|
}
|
|
/* already emitted */
|
|
|
|
|
|
var _color = litCells[key];
|
|
var reflectivity = void 0;
|
|
|
|
if (key in this._reflectivityCache) {
|
|
reflectivity = this._reflectivityCache[key];
|
|
} else {
|
|
var parts = key.split(",");
|
|
var x = parseInt(parts[0]);
|
|
var y = parseInt(parts[1]);
|
|
reflectivity = this._reflectivityCallback(x, y);
|
|
this._reflectivityCache[key] = reflectivity;
|
|
}
|
|
|
|
if (reflectivity == 0) {
|
|
continue;
|
|
}
|
|
/* will not reflect at all */
|
|
|
|
/* compute emission color */
|
|
|
|
|
|
var emission = [0, 0, 0];
|
|
var intensity = 0;
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
var part = Math.round(_color[i] * reflectivity);
|
|
emission[i] = part;
|
|
intensity += part;
|
|
}
|
|
|
|
if (intensity > this._options.emissionThreshold) {
|
|
result[key] = emission;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
/**
|
|
* Compute one iteration from one cell
|
|
*/
|
|
|
|
|
|
_proto37._emitLightFromCell = function _emitLightFromCell(x, y, color, litCells) {
|
|
var key = x + "," + y;
|
|
var fov;
|
|
|
|
if (key in this._fovCache) {
|
|
fov = this._fovCache[key];
|
|
} else {
|
|
fov = this._updateFOV(x, y);
|
|
}
|
|
|
|
for (var fovKey in fov) {
|
|
var formFactor = fov[fovKey];
|
|
var result = void 0;
|
|
|
|
if (fovKey in litCells) {
|
|
/* already lit */
|
|
result = litCells[fovKey];
|
|
} else {
|
|
/* newly lit */
|
|
result = [0, 0, 0];
|
|
litCells[fovKey] = result;
|
|
}
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
result[i] += Math.round(color[i] * formFactor);
|
|
}
|
|
/* add light color */
|
|
|
|
}
|
|
|
|
return this;
|
|
};
|
|
/**
|
|
* Compute FOV ("form factor") for a potential light source at [x,y]
|
|
*/
|
|
|
|
|
|
_proto37._updateFOV = function _updateFOV(x, y) {
|
|
var key1 = x + "," + y;
|
|
var cache = {};
|
|
this._fovCache[key1] = cache;
|
|
var range = this._options.range;
|
|
|
|
function cb(x, y, r, vis) {
|
|
var key2 = x + "," + y;
|
|
var formFactor = vis * (1 - r / range);
|
|
|
|
if (formFactor == 0) {
|
|
return;
|
|
}
|
|
|
|
cache[key2] = formFactor;
|
|
}
|
|
|
|
this._fov.compute(x, y, range, cb.bind(this));
|
|
|
|
return cache;
|
|
};
|
|
|
|
return Lighting;
|
|
}();
|
|
|
|
var Util = util;
|
|
var Color = color;
|
|
var Text = text;
|
|
exports.Util = Util;
|
|
exports.Color = Color;
|
|
exports.Text = Text;
|
|
exports.RNG = RNG$1;
|
|
exports.Display = Display;
|
|
exports.StringGenerator = StringGenerator;
|
|
exports.EventQueue = EventQueue;
|
|
exports.Scheduler = index;
|
|
exports.FOV = index$1;
|
|
exports.Map = index$2;
|
|
exports.Noise = index$3;
|
|
exports.Path = index$4;
|
|
exports.Engine = Engine;
|
|
exports.Lighting = Lighting;
|
|
exports.DEFAULT_WIDTH = DEFAULT_WIDTH;
|
|
exports.DEFAULT_HEIGHT = DEFAULT_HEIGHT;
|
|
exports.DIRS = DIRS;
|
|
exports.KEYS = KEYS;
|
|
Object.defineProperty(exports, '__esModule', {
|
|
value: true
|
|
});
|
|
});
|
|
|