Bugfixes
This commit is contained in:
@@ -140,7 +140,7 @@ export class TileMap {
|
||||
return this.tiles[y][x];
|
||||
}
|
||||
|
||||
behavesLikeWall(x, y) {
|
||||
isWallLike(x, y) {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
|
||||
@@ -161,7 +161,7 @@ export class TileMap {
|
||||
* @param {number} y
|
||||
* @returns {boolean}
|
||||
*/
|
||||
behavesLikeFloor(x, y) {
|
||||
isFloorLike(x, y) {
|
||||
x |= 0;
|
||||
y |= 0;
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import { TileOptions } from "../utils/tileOptionsParser.js";
|
||||
import { Orientation } from "./ascii_types.js";
|
||||
|
||||
/** @typedef {string} TileTypeId - a string with a length of 1 */
|
||||
/** @typedef {string} MinimapGrapheme - a string containing a single grapheme */
|
||||
/** @typedef {string|number} ResourceId - id of a resource (such as a texture or another tile) used by a tile */
|
||||
|
||||
/**
|
||||
* Array of __internal__ characters used to identify tile types.
|
||||
* These are __not__ necessarily the characters used to display
|
||||
* These are __not necessarily__ the characters used to display
|
||||
* the tile on the minimap - but they are used when serializing
|
||||
* the maps into a semi-human-readable text-format.
|
||||
* the maps into a human-readable text-format.
|
||||
*
|
||||
* @enum {TileTypeId}
|
||||
*/
|
||||
@@ -27,36 +29,35 @@ export const TileChars = Object.freeze({
|
||||
* must have their actual value supplied by the creator
|
||||
* of the Tile object.
|
||||
*
|
||||
* For instance, if a Tile has a textureId = PropertyPlaceholder.ID, then
|
||||
* the creature of that Tile MUST supply the textureId before the tile can
|
||||
* be used. Such values SHOULD be provided in the constructor, but CAN be
|
||||
* provided later, as long as they are provided before the tile is used
|
||||
* in the actual game.
|
||||
*
|
||||
* For instance, if a Tile has a textureId = REQUIRED_ID, then
|
||||
* the creator of that Tile MUST supply the textureId before the tile can
|
||||
* be used in the game Such values SHOULD be provided in the constructor,
|
||||
* but CAN be provided later, as long as they are provided before the
|
||||
* tile is used in the actual game.
|
||||
*/
|
||||
|
||||
/** Properties with this value must be valid ID values. */
|
||||
const REQUIRED_ID = Symbol("REQUIRED_ID");
|
||||
const REQUIRED_ORIENTATION = Symbol("REQUIRED_ORIENTATION");
|
||||
|
||||
function mustBeId(value) {
|
||||
function sanitizeId(value) {
|
||||
if ((value | 0) === value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Value id not a valid id", { value });
|
||||
throw new Error("Value is not a valid id", { value });
|
||||
}
|
||||
|
||||
value = value.trim();
|
||||
|
||||
if (value === "") {
|
||||
throw new Error("Value id not a valid id", { value });
|
||||
throw new Error("Value is not a valid id", { value });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
function mustBeOrientation(value) {
|
||||
function sanitizeOrientation(value) {
|
||||
const result = Orientation.normalize(value);
|
||||
|
||||
if (result === undefined) {
|
||||
@@ -122,13 +123,13 @@ export const TileTypes = {
|
||||
};
|
||||
|
||||
export class Tile {
|
||||
/** @readonly {string?|number?} Unique (but optional) instance if of this tile */
|
||||
/** @readonly {ResourceId?} Unique (but optional) instance id of this tile - only needed when this tile is referenced by other tiles */
|
||||
id;
|
||||
|
||||
/** @type {TileTypeId} Char that defines this tile */
|
||||
typeId;
|
||||
|
||||
/** @type {TileTypeId} Icon char of tile */
|
||||
/** @type {MinimapGrapheme} Icon char of tile */
|
||||
minimapChar;
|
||||
|
||||
/** @type {string} Color of the icon of tile */
|
||||
@@ -146,18 +147,15 @@ export class Tile {
|
||||
/** @type {boolean} Is this a portal exit and/or entry */
|
||||
isPortal;
|
||||
|
||||
/** @type {string|number} Where is the player transported if they enter the portal */
|
||||
/** @type {ResourceId} id of the type where players will be transported - many portals may share same target */
|
||||
portalTargetId;
|
||||
|
||||
/** @type {number|string} id of texture to use */
|
||||
/** @type {ResourceId} id of texture to use - does not need to be unique */
|
||||
textureId;
|
||||
|
||||
/** @type {number|string} type of encounter located on this tile. May or may not be unique*/
|
||||
/** @type {ResourceId} id of encounter located on this tile. May or may not be unique */
|
||||
encounterId;
|
||||
|
||||
/** @type {number|string} type of trap located on this tile. May or may not be unique*/
|
||||
trapType;
|
||||
|
||||
/** @type {Orientation} */
|
||||
orientation;
|
||||
|
||||
@@ -170,15 +168,71 @@ export class Tile {
|
||||
/** @type {boolean} Has the secret properties of this tile been revealed? */
|
||||
revealed;
|
||||
|
||||
/** @type {TileTypeId} Icon char of tile after tile's secrets have been revealed */
|
||||
/** @type {MinimapGrapheme} Icon char of tile after tile's secrets have been revealed */
|
||||
revealedMinimapChar;
|
||||
|
||||
/** @type {string} Color of the icon char of tile after tile's secrets have been revealed */
|
||||
revealedMinimapColor;
|
||||
|
||||
/** @type {number|string} id of texture to use after the secrets of this tile has been revealed */
|
||||
/** @type {ResourceId} id of texture to use after the secrets of this tile has been revealed */
|
||||
revealedTextureId;
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createWall() {
|
||||
return this.fromChar(TileChars.WALL);
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createEncounterStartPoint(encounterId) {
|
||||
return this.fromChar(TileChars.ENCOUNTER_START_POINT, { encounterId });
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createFloor() {
|
||||
return this.fromChar(TileChars.FLOOR);
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createPlayerStart(orientation) {
|
||||
return this.fromChar(TileChars.PLAYER_START_POINT, { orientation });
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a map symbol,
|
||||
* @param {TileTypeId} typeId
|
||||
* @param {TileOptions|Record<string,any>} options
|
||||
* @returns {Tile}
|
||||
*/
|
||||
static fromChar(typeId, options) {
|
||||
const prototype = TileTypes[typeId];
|
||||
|
||||
if (!prototype) {
|
||||
console.log("unknown type id", { typeId });
|
||||
throw new Error(`Unknown typeId >>>${typeId}<<<`);
|
||||
}
|
||||
|
||||
//
|
||||
// Normalize options into a TileOptions object,
|
||||
//
|
||||
if (!(options instanceof TileOptions)) {
|
||||
options = TileOptions.fromObject(typeId, options ?? {});
|
||||
}
|
||||
|
||||
let optionPos = 0;
|
||||
const properties = {};
|
||||
|
||||
for (let [key, val] of Object.entries(prototype)) {
|
||||
//
|
||||
if (typeof val === "symbol" && val.description.startsWith("REQUIRED_")) {
|
||||
properties[key] = options.getValue(name, optionPos++);
|
||||
} else {
|
||||
properties[key] = shallowCopy(val);
|
||||
}
|
||||
}
|
||||
|
||||
return new Tile(typeId, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TileTypeId} typeId
|
||||
* @param {Tile?} properties
|
||||
@@ -250,80 +304,33 @@ export class Tile {
|
||||
//
|
||||
// Sanitize and normalize
|
||||
//
|
||||
this.id ??= mustBeId(this.id);
|
||||
this.textureId ??= mustBeId(this.textureId);
|
||||
this.portalTargetId ??= mustBeId(this.portalTargetId);
|
||||
this.orientation ??= mustBeOrientation(this.orientation);
|
||||
if (this.id !== undefined) {
|
||||
this.id = sanitizeId(this.id);
|
||||
}
|
||||
if (this.textureId !== undefined) {
|
||||
this.textureId = sanitizeId(this.textureId);
|
||||
}
|
||||
if (this.portalId !== undefined) {
|
||||
this.portalTargetId = sanitizeId(this.portalTargetId);
|
||||
}
|
||||
if (this.orientation !== undefined) {
|
||||
this.orientation = sanitizeOrientation(this.orientation);
|
||||
}
|
||||
|
||||
mustBeSingleGrapheme(this.typeId);
|
||||
mustBeSingleGrapheme(this.minimapChar);
|
||||
mustBeSingleGrapheme(this.revealedMinimapChar);
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createWall() {
|
||||
return this.fromChar(TileChars.WALL);
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createEncounterStartPoint(encounterId) {
|
||||
return this.fromChar(TileChars.ENCOUNTER_START_POINT, { encounterId });
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createFloor() {
|
||||
return this.fromChar(TileChars.FLOOR);
|
||||
}
|
||||
|
||||
/** @returns {Tile} */
|
||||
static createPlayerStart(orientation) {
|
||||
return this.fromChar(TileChars.PLAYER_START_POINT, { orientation });
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a map symbol,
|
||||
* @param {TileTypeId} typeId
|
||||
* @param {TileOptions|Record<string,string>} options
|
||||
* @returns {Tile}
|
||||
*/
|
||||
static fromChar(typeId, options) {
|
||||
const prototype = TileTypes[typeId];
|
||||
|
||||
if (!prototype) {
|
||||
console.log("unknown type id", { typeId });
|
||||
throw new Error(`Unknown typeId >>>${typeId}<<<`);
|
||||
}
|
||||
|
||||
if (options === undefined) {
|
||||
options = TileOptions.fromObject(typeId, TileTypes[typeId]);
|
||||
}
|
||||
|
||||
//
|
||||
// Normalize options into a TileOptions object,
|
||||
//
|
||||
if (!(options instanceof TileOptions)) {
|
||||
options = TileOptions.fromObject(typeId, options);
|
||||
}
|
||||
|
||||
let optionPos = 0;
|
||||
const properties = {};
|
||||
const getOption = (name) => options.getValue(name, optionPos++);
|
||||
for (let [key, val] of Object.entries(prototype)) {
|
||||
properties[key] = val;
|
||||
//
|
||||
const fetchOption = typeof val === "symbol" && val.description.startsWith("REQUIRED_");
|
||||
|
||||
properties[key] = fetchOption ? getOption(key) : shallowCopy(val);
|
||||
}
|
||||
|
||||
return new Tile(typeId, properties);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Tile(this.typeId, { ...this });
|
||||
}
|
||||
|
||||
isWallLike() {
|
||||
if (this.typeId === TileChars.WALL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.is === TileChars.WALL) {
|
||||
return true;
|
||||
}
|
||||
@@ -335,6 +342,10 @@ export class Tile {
|
||||
return this.looksLikeWall && !this.isTraversable;
|
||||
}
|
||||
|
||||
isWall() {
|
||||
return this.typeId === TileChars.WALL;
|
||||
}
|
||||
|
||||
isFloorlike() {
|
||||
if (this.typeId === TileChars.FLOOR) {
|
||||
return true;
|
||||
|
||||
@@ -115,20 +115,17 @@ class DungeonGenerator {
|
||||
let lastNonWallX = undefined; // x-index of the LAST (eastmost) non-wall tile that we encountered on this row
|
||||
|
||||
for (let x = 0; x < this.width; x++) {
|
||||
const isWall = this.map.get(x, y).looksLikeWall;
|
||||
|
||||
if (isWall) {
|
||||
//
|
||||
if (this.map.get(x, y).isWall()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstNonWallX === undefined) {
|
||||
firstNonWallX = x;
|
||||
}
|
||||
firstNonWallX ??= x;
|
||||
lastNonWallX = x;
|
||||
}
|
||||
|
||||
const onlyWalls = lastNonWallX === undefined;
|
||||
if (onlyWalls) {
|
||||
// Did this row contain only walls?
|
||||
if (firstNonWallX === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,7 +223,7 @@ class DungeonGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.map.behavesLikeFloor(x, y)) {
|
||||
if (this.map.get(x, y).isFloor()) {
|
||||
walkabilityCache.push([x, y]);
|
||||
}
|
||||
}
|
||||
@@ -244,7 +241,7 @@ class DungeonGenerator {
|
||||
|
||||
for (let [x, y] of walkabilityCache) {
|
||||
//
|
||||
const walkable = (offsetX, offsetY) => this.map.behavesLikeFloor(x + offsetX, y + offsetY);
|
||||
const walkable = (offsetX, offsetY) => this.map.isFloorLike(x + offsetX, y + offsetY);
|
||||
|
||||
const surroundingFloorCount =
|
||||
0 +
|
||||
@@ -281,7 +278,7 @@ class DungeonGenerator {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.map.behavesLikeFloor(x, y)) {
|
||||
if (this.map.isFloorLike(x, y)) {
|
||||
walkabilityCache.push([x, y]);
|
||||
}
|
||||
}
|
||||
@@ -290,7 +287,7 @@ class DungeonGenerator {
|
||||
const idx = this.random(0, walkabilityCache.length - 1);
|
||||
const [x, y] = walkabilityCache[idx];
|
||||
|
||||
const walkable = (offsetX, offsetY) => this.map.behavesLikeFloor(x + offsetX, y + offsetY);
|
||||
const walkable = (offsetX, offsetY) => this.map.isFloorLike(x + offsetX, y + offsetY);
|
||||
|
||||
//
|
||||
// When spawning in, which direction should the player be oriented?
|
||||
|
||||
Reference in New Issue
Block a user