brogue-js
Version:
A 100% Javascript version of Brogue
1,837 lines (1,628 loc) • 1.58 MB
JavaScript
/*
This is Brogue.js, A simple JS roguelike micro-framework for developers new to programming and or JS.
Version 1.7.5, generated on Sat Oct 3 10:08:34 CDT 2020.
*/
/////////////////////////
// Brogue.js
/////////////////////////
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([/* dependencies (as strings) */], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(/* dependencies (from requires) */);
} else {
// Browser globals (root is window)
Object.assign(root, factory(/* root.jQuery, root._ */));
}
}(this, function () {
//
// RogueMain.h
// Brogue
//
// Created by Brian Walker on 12/26/08.
// Copyright 2012. All rights reserved.
//
// This file is part of Brogue.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// #include <stdio.h>
// #include <stdlib.h>
// #include <string.h>
// #include "PlatformDefines.h"
// unicode: comment this line to revert to ASCII
const USE_UNICODE = true;
const NULL = null;
// version string -- no more than 16 bytes:
const BROGUE_VERSION_STRING = "1.7.5-js";
// debug macros -- define DEBUGGING as 1 to enable wizard mode.
const DEBUGGING = 0; // use 1 || 0
const DEBUG = console.debug;
const printf = function(...args) {
args = args.map( (a) => {
if (a instanceof BrogueString) return a.text;
return a;
});
console.log(...args);
}
const MONSTERS_ENABLED = (!DEBUGGING || 1); // Quest room monsters can be generated regardless.
const ITEMS_ENABLED = (!DEBUGGING || 1);
const D_BULLET_TIME = (DEBUGGING && 0);
const D_WORMHOLING = (DEBUGGING && 1);
const D_IMMORTAL = (DEBUGGING && 1);
const D_SAFETY_VISION = (DEBUGGING && 0);
const D_SCENT_VISION = (DEBUGGING && 0);
const D_DISABLE_BACKGROUND_COLORS = (DEBUGGING && 0);
const D_INSPECT_LEVELGEN = (DEBUGGING && 0);
const D_INSPECT_ROOM_ADD = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_DOORS = (D_INSPECT_ROOM_ADD && 0);
const D_INSPECT_LAKES = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_AUTOGENERATORS = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_MACHINES = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_MACHINE_FEATURES = (D_INSPECT_MACHINES && 0);
const D_INSPECT_ITEM_GEN = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_MON_GEN = (D_INSPECT_LEVELGEN && 0);
const D_INSPECT_MONSTER_SPAWN = (DEBUGGING && 0);
const D_MESSAGE_ITEM_GENERATION = (DEBUGGING && 0);
const D_MESSAGE_MONSTER_GENERATION = (DEBUGGING && 0);
const D_MESSAGE_MACHINE_GENERATION = (DEBUGGING && 0);
const D_MONSTER_OMNISCIENCE = (DEBUGGING && 0);
const D_ITEM_OMNISCIENCE = (DEBUGGING && 0);
// set to false to allow multiple loads from the same saved file:
const DELETE_SAVE_FILE_AFTER_LOADING = true;
// set to false to disable references to keystrokes (e.g. for a tablet port)
const KEYBOARD_LABELS = true;
//#define BROGUE_ASSERTS // introduces several assert()s -- useful to find certain array overruns and other bugs
//#define AUDIT_RNG // VERY slow, but sometimes necessary to debug out-of-sync recording errors
//#define GENERATE_FONT_FILES // Displays font in grid upon startup, which can be screen-captured into font files for PC.
function assert(v) {
if (!v) throw new Error('Assertion failed');
}
function brogueAssert(v) {
if (!v) {
throw new Error('Assert failed.');
}
}
// function clone(x) {
// if (Array.isArray(x)) {
// return x.slice();
// }
// else if (typeof x === 'object') {
// return Object.assign({}, x);
// }
// return x;
// }
function cloneDeep(x) {
if (Array.isArray(x)) {
return x.map( (v) => cloneDeep(v) );
}
else if (typeof x === 'object') {
const entries = Object.entries(x);
const out = {};
entries.forEach( ([key, value]) => {
out[key] = cloneDeep(value);
});
return out;
}
return x;
}
const PI = 3.14159265;
// const FLOAT_FUDGE = 0.00001;
const FP_BASE = 16; // Don't change this without recalculating all of the power tables throughout the code!
const FP_FACTOR = (1 << FP_BASE);
// recording and save filenames
const LAST_GAME_PATH = "LastGame.broguesave";
const LAST_GAME_NAME = "LastGame";
const LAST_RECORDING_NAME = "LastRecording";
const RECORDING_SUFFIX = ".broguerec";
const GAME_SUFFIX = ".broguesave";
const ANNOTATION_SUFFIX = ".txt";
const RNG_LOG = "RNGLog.txt";
const BROGUE_FILENAME_MAX = 255; // (Math.min(1024*4, FILENAME_MAX))
// Allows unicode characters:
// #define uchar unsigned short
const MESSAGE_LINES = 3;
// Size of the entire terminal window. These need to be hard-coded here and in Viewport.h
const COLS = 100;
const ROWS = (31 + MESSAGE_LINES);
const MESSAGE_ARCHIVE_LINES = ROWS;
const STAT_BAR_WIDTH = 20; // number of characters in the stats bar to the left of the map
// Size of the portion of the terminal window devoted to displaying the dungeon:
const DCOLS = (COLS - STAT_BAR_WIDTH - 1); // n columns on the left for the sidebar;
// one column to separate the sidebar from the map.
const DROWS = (ROWS - MESSAGE_LINES - 2); // n lines at the top for messages;
// one line at the bottom for flavor text;
// another line at the bottom for the menu bar.
const LOS_SLOPE_GRANULARITY = 32768; // how finely we divide up the squares when calculating slope;
// higher numbers mean fewer artifacts but more memory and processing
const INTERFACE_OPACITY = 95;
const LIGHT_SMOOTHING_THRESHOLD = 150; // light components higher than this magnitude will be toned down a little
const MAX_BOLT_LENGTH = DCOLS*10;
const VISIBILITY_THRESHOLD = 50; // how bright cumulative light has to be before the cell is marked visible
const AMULET_LEVEL = 26; // how deep before the amulet appears
const DEEPEST_LEVEL = 40; // how deep the universe goes
const MACHINES_FACTOR = 1.0; // use this to adjust machine frequency
const MACHINES_BUFFER_LENGTH = 200;
// const DEFENSE_FACTOR = 0.987; // Each point of armor multiplies enemy attackers' accuracy by this value.
// // (displayed armor value is 10% of the real value)
// const WEAPON_ENCHANT_DAMAGE_FACTOR = 1.065; // Each marginal point of weapon enchantment
// // multiplies damage by this factor.
// const WEAPON_ENCHANT_ACCURACY_FACTOR = 1.065; // Each marginal point of weapon enchantment
// // multiplies accuracy by this factor.
const WEAPON_KILLS_TO_AUTO_ID = 20;
const ARMOR_DELAY_TO_AUTO_ID = 1000;
const RING_DELAY_TO_AUTO_ID = 1500;
const FALL_DAMAGE_MIN = 8;
const FALL_DAMAGE_MAX = 10;
const INPUT_RECORD_BUFFER = 1000; // how many bytes of input data to keep in memory before saving it to disk
const DEFAULT_PLAYBACK_DELAY = 50;
const HIGH_SCORES_COUNT = 30;
// color escapes
const COLOR_ESCAPE = 25;
const COLOR_END = 26;
const COLOR_VALUE_INTERCEPT = 25;
function centerText(bstring, len) {
const totalPad = (len - bstring.textLength);
const leftPad = Math.round(totalPad/2);
return bstring.padStart(leftPad + bstring.textlength, ' ').padEnd(len, ' ');
}
function capitalize(bstring) {
bstring = STRING(bstring);
return bstring.capitalize();
}
// display characters:
// #ifdef USE_UNICODE
const FLOOR_CHAR = '\u00b7';
const LIQUID_CHAR = '~';
const CHASM_CHAR = '\u2237';
const TRAP_CHAR = '\u25c7';
const FIRE_CHAR = '\u22CF';
const GRASS_CHAR = '"';
const BRIDGE_CHAR = '=';
const DESCEND_CHAR = '>';
const ASCEND_CHAR = '<';
const WALL_CHAR = '#';
const DOOR_CHAR = '+';
const OPEN_DOOR_CHAR = '\'';
const ASH_CHAR = '\'';
const BONES_CHAR = ',';
const MUD_CHAR = ',';
const WEB_CHAR = ':';
//const FOLIAGE_CHAR = '\u03A8' // upper-case psi;
//const FOLIAGE_CHAR = '\u2648' // Aries symbol (not supported on many browsers);
const FOLIAGE_CHAR = '\u03C8' // lower case psi
const VINE_CHAR = ':';
const ALTAR_CHAR = '|';
const LEVER_CHAR = '/';
const LEVER_PULLED_CHAR = '\\';
const STATUE_CHAR = '\u00df';
const VENT_CHAR = '=';
const DEWAR_CHAR = '&';
const TRAMPLED_FOLIAGE_CHAR = '"' // '\u2034' // '\u2037';
const PLAYER_CHAR = '@';
const AMULET_CHAR = '\u2640';
const FOOD_CHAR = ';';
const SCROLL_CHAR = '\u266A'//'?' // '\u039E';
//const RING_CHAR = '\u26AA' //'\uffee';
const RING_CHAR = 'o';
const CHARM_CHAR = '\u03DE';
const POTION_CHAR = '!';
const ARMOR_CHAR = '[';
const WEAPON_CHAR = '\u2191';
const STAFF_CHAR = '\\';
const WAND_CHAR = '~';
const GOLD_CHAR = '*';
const GEM_CHAR = '\u25cf';
const TOTEM_CHAR = '\u26b2'; // 1.7.4 = '\u2641';
const TURRET_CHAR = '\u25cf';
const UNICORN_CHAR = '\u00da';
const KEY_CHAR = '-';
const ELECTRIC_CRYSTAL_CHAR = '\u00a4'; // 1.7.4 = 164;
const UP_ARROW_CHAR = '\u2191';
const DOWN_ARROW_CHAR = '\u2193';
const LEFT_ARROW_CHAR = '\u2190';
const RIGHT_ARROW_CHAR = '\u2192';
const UP_TRIANGLE_CHAR = '\u2206';
const DOWN_TRIANGLE_CHAR = '\u2207';
const OMEGA_CHAR = '\u03A9';
const THETA_CHAR = '\u03B8';
const LAMDA_CHAR = '\u03BB';
const KOPPA_CHAR = '\u03DF'//'\u03DE';
const LOZENGE_CHAR = '\u29eb'; // 1.7.4 = '\u25C6';
const CROSS_PRODUCT_CHAR = '\u2A2F';
const CHAIN_TOP_LEFT = '\\';
const CHAIN_BOTTOM_RIGHT = '\\';
const CHAIN_TOP_RIGHT = '/';
const CHAIN_BOTTOM_LEFT = '/';
const CHAIN_TOP = '|';
const CHAIN_BOTTOM = '|';
const CHAIN_LEFT = '-';
const CHAIN_RIGHT = '-';
const BAD_MAGIC_CHAR = '\u29f2'; // 1.7.4 = '\u25C6';
const GOOD_MAGIC_CHAR = '\u29f3'; // 1.7.4 = '\u25C7';
// #else
// #define FLOOR_CHAR '.'
// #define LIQUID_CHAR '~'
// #define CHASM_CHAR ':'
// #define TRAP_CHAR '%'
// #define FIRE_CHAR '^'
// #define GRASS_CHAR '"'
// #define BRIDGE_CHAR '='
// #define DESCEND_CHAR '>'
// #define ASCEND_CHAR '<'
// #define WALL_CHAR '#'
// #define DOOR_CHAR '+'
// #define OPEN_DOOR_CHAR '\''
// #define ASH_CHAR '\''
// #define BONES_CHAR ','
// #define MUD_CHAR ','
// #define WEB_CHAR ':'
// #define FOLIAGE_CHAR '&'
// #define VINE_CHAR ':'
// #define ALTAR_CHAR '|'
// #define LEVER_CHAR '/'
// #define LEVER_PULLED_CHAR '\\'
// #define STATUE_CHAR '&'
// #define VENT_CHAR '='
// #define DEWAR_CHAR '&'
//
// #define TRAMPLED_FOLIAGE_CHAR '"'
//
// #define PLAYER_CHAR '@'
//
// #define AMULET_CHAR ','
// #define FOOD_CHAR ';'
// #define SCROLL_CHAR '?'
// #define RING_CHAR '='
// #define CHARM_CHAR '+'
// #define POTION_CHAR '!'
// #define ARMOR_CHAR '['
// #define WEAPON_CHAR '('
// #define STAFF_CHAR '\\'
// #define WAND_CHAR '~'
// #define GOLD_CHAR '*'
// #define GEM_CHAR '+'
// #define TOTEM_CHAR '0'
// #define TURRET_CHAR '*'
// #define UNICORN_CHAR 'U'
// #define KEY_CHAR '-'
// #define ELECTRIC_CRYSTAL_CHAR '$'
//
// #define UP_ARROW_CHAR '^'
// #define DOWN_ARROW_CHAR 'v'
// #define LEFT_ARROW_CHAR '<'
// #define RIGHT_ARROW_CHAR '>'
// #define UP_TRIANGLE_CHAR '^'
// #define DOWN_TRIANGLE_CHAR 'v'
// #define OMEGA_CHAR '^'
// #define THETA_CHAR '0'
// #define LAMDA_CHAR '\\'
// #define KOPPA_CHAR 'k'
// #define LOZENGE_CHAR '+'
// #define CROSS_PRODUCT_CHAR 'x'
//
// #define CHAIN_TOP_LEFT '\\'
// #define CHAIN_BOTTOM_RIGHT '\\'
// #define CHAIN_TOP_RIGHT '/'
// #define CHAIN_BOTTOM_LEFT '/'
// #define CHAIN_TOP '|'
// #define CHAIN_BOTTOM '|'
// #define CHAIN_LEFT '-'
// #define CHAIN_RIGHT '-'
//
// #define BAD_MAGIC_CHAR '+'
// #define GOOD_MAGIC_CHAR '$'
//
// #endif
const GLOBAL = (typeof global !== 'undefined') ? global : this; // this === window
class Enum {
constructor() {
}
toString(v) {
if (v === undefined) return enumName;
return Object.entries(out).reduce( (out, [key, value]) => (value == v) ? key : out, '?' );
}
}
function ENUM(enumName, ...names) {
const out = new Enum();
let offset = 0;
if (typeof names[0] === 'number') {
offset = names.shift();
}
names.forEach( (name, index) => {
GLOBAL[name] = index + offset;
out[name] = index + offset;
});
GLOBAL[enumName] = out;
return out;
}
ENUM('eventTypes',
'UPDATE',
'KEYSTROKE',
'MOUSE_UP',
'MOUSE_DOWN',
'RIGHT_MOUSE_DOWN',
'RIGHT_MOUSE_UP',
'MOUSE_ENTERED_CELL',
'RNG_CHECK',
'SAVED_GAME_LOADED',
'END_OF_RECORDING',
'EVENT_ERROR',
'NUMBER_OF_EVENT_TYPES', // unused
);
// 1.7.4
ENUM('notificationEventTypes',
'GAMEOVER_QUIT',
'GAMEOVER_DEATH',
'GAMEOVER_VICTORY',
'GAMEOVER_SUPERVICTORY',
'GAMEOVER_RECORDING'
);
class RogueEvent {
constructor(type, p1, p2, ctrl, shift) {
/* ENUM eventTypes */ this.eventType = type || 0;
/* signed long */ this.param1 = p1 || 0;
/* signed long */ this.param2 = p2 || 0;
/* boolean */ this.controlKey = ctrl || false;
/* boolean */ this.shiftKey = shift || false;
}
copy(other) {
Object.assign(this, other);
}
clone() {
return new RogueEvent(this.eventType, this.param1, this.param2, this.controlKey, this.shiftKey);
}
}
function rogueEvent(type, p1, p2, ctrl, shift) {
return new RogueEvent(type, p1, p2, ctrl, shift);
}
class RogueHighScoresEntry {
constructor() {
this.score = 0;
this.date = STRING();
this.description = STRING();
}
clear() {
this.score = 0;
strcpy(this.date, "");
strcpy(this.description, "");
}
copy(other) {
this.score = other.score;
strcpy(this.date, other.date);
strcpy(this.description, other.description);
}
}
function rogueHighScoresEntry() {
return new RogueHighScoresEntry();
}
function fileEntry() {
return {
path: '',
date: ''
}
}
ENUM('RNGs',
'RNG_SUBSTANTIVE',
'RNG_COSMETIC',
'NUMBER_OF_RNGS',
);
ENUM('displayDetailValues', 0,
'DV_UNLIT',
'DV_LIT',
'DV_DARK',
);
ENUM('directions', -1,
'NO_DIRECTION',
// Cardinal directions; must be 0-3:
'UP',
'DOWN',
'LEFT',
'RIGHT',
// Secondary directions; must be 4-7:
'UPLEFT',
'DOWNLEFT',
'UPRIGHT',
'DOWNRIGHT',
'DIRECTION_COUNT',
);
ENUM('whipAttackResult', 0,
'WHIP_FAILED',
'WHIP_SUCCESS',
'WHIP_ABORTED'
);
ENUM('boltResult', 0,
'BR_CONTINUES',
'BR_DONE',
'BR_AUTO_ID',
'BR_LIGHTING_CHANGED'
);
ENUM('textEntryTypes', 0,
'TEXT_INPUT_NORMAL',
'TEXT_INPUT_FILENAME',
'TEXT_INPUT_NUMBERS',
'TEXT_INPUT_TYPES',
);
// const NUMBER_DYNAMIC_COLORS = 6;
ENUM('tileType', 0,
'NOTHING',
'GRANITE',
'FLOOR',
'FLOOR_FLOODABLE',
'CARPET',
'MARBLE_FLOOR',
'WALL',
'DOOR',
'OPEN_DOOR',
'SECRET_DOOR',
'LOCKED_DOOR',
'OPEN_IRON_DOOR_INERT',
'DOWN_STAIRS',
'UP_STAIRS',
'DUNGEON_EXIT',
'DUNGEON_PORTAL',
'TORCH_WALL', // wall lit with a torch
'CRYSTAL_WALL',
'PORTCULLIS_CLOSED',
'PORTCULLIS_DORMANT',
'WOODEN_BARRICADE',
'PILOT_LIGHT_DORMANT',
'PILOT_LIGHT',
'HAUNTED_TORCH_DORMANT',
'HAUNTED_TORCH_TRANSITIONING',
'HAUNTED_TORCH',
'WALL_LEVER_HIDDEN',
'WALL_LEVER',
'WALL_LEVER_PULLED',
'WALL_LEVER_HIDDEN_DORMANT',
'STATUE_INERT',
'STATUE_DORMANT',
'STATUE_CRACKING',
'STATUE_INSTACRACK',
'PORTAL',
'TURRET_DORMANT',
'WALL_MONSTER_DORMANT',
'DARK_FLOOR_DORMANT',
'DARK_FLOOR_DARKENING',
'DARK_FLOOR',
'MACHINE_TRIGGER_FLOOR',
'ALTAR_INERT',
'ALTAR_KEYHOLE',
'ALTAR_CAGE_OPEN',
'ALTAR_CAGE_CLOSED',
'ALTAR_SWITCH',
'ALTAR_SWITCH_RETRACTING',
'ALTAR_CAGE_RETRACTABLE',
'PEDESTAL',
'MONSTER_CAGE_OPEN',
'MONSTER_CAGE_CLOSED',
'COFFIN_CLOSED',
'COFFIN_OPEN', ////
'GAS_TRAP_POISON_HIDDEN',
'GAS_TRAP_POISON',
'TRAP_DOOR_HIDDEN',
'TRAP_DOOR',
'GAS_TRAP_PARALYSIS_HIDDEN',
'GAS_TRAP_PARALYSIS',
'MACHINE_PARALYSIS_VENT_HIDDEN',
'MACHINE_PARALYSIS_VENT',
'GAS_TRAP_CONFUSION_HIDDEN',
'GAS_TRAP_CONFUSION',
'FLAMETHROWER_HIDDEN',
'FLAMETHROWER',
'FLOOD_TRAP_HIDDEN',
'FLOOD_TRAP',
'NET_TRAP_HIDDEN',
'NET_TRAP',
'ALARM_TRAP_HIDDEN',
'ALARM_TRAP',
'MACHINE_POISON_GAS_VENT_HIDDEN',
'MACHINE_POISON_GAS_VENT_DORMANT',
'MACHINE_POISON_GAS_VENT',
'MACHINE_METHANE_VENT_HIDDEN',
'MACHINE_METHANE_VENT_DORMANT',
'MACHINE_METHANE_VENT',
'STEAM_VENT',
'MACHINE_PRESSURE_PLATE',
'MACHINE_PRESSURE_PLATE_USED',
'MACHINE_GLYPH',
'MACHINE_GLYPH_INACTIVE',
'DEWAR_CAUSTIC_GAS',
'DEWAR_CONFUSION_GAS',
'DEWAR_PARALYSIS_GAS',
'DEWAR_METHANE_GAS', ////
'DEEP_WATER',
'SHALLOW_WATER',
'MUD',
'CHASM',
'CHASM_EDGE',
'MACHINE_COLLAPSE_EDGE_DORMANT',
'MACHINE_COLLAPSE_EDGE_SPREADING',
'LAVA',
'LAVA_RETRACTABLE',
'LAVA_RETRACTING',
'SUNLIGHT_POOL',
'DARKNESS_PATCH',
'ACTIVE_BRIMSTONE',
'INERT_BRIMSTONE',
'OBSIDIAN',
'BRIDGE',
'BRIDGE_FALLING',
'BRIDGE_EDGE',
'STONE_BRIDGE',
'MACHINE_FLOOD_WATER_DORMANT',
'MACHINE_FLOOD_WATER_SPREADING',
'MACHINE_MUD_DORMANT', ///
'ICE_DEEP', // 1.7.5
'ICE_DEEP_MELT', // 1.7.5
'ICE_SHALLOW', // 1.7.5
'ICE_SHALLOW_MELT', // 1.7.5
'HOLE',
'HOLE_GLOW',
'HOLE_EDGE',
'FLOOD_WATER_DEEP',
'FLOOD_WATER_SHALLOW',
'GRASS',
'DEAD_GRASS',
'GRAY_FUNGUS',
'LUMINESCENT_FUNGUS',
'LICHEN',
'HAY',
'RED_BLOOD',
'GREEN_BLOOD',
'PURPLE_BLOOD',
'ACID_SPLATTER',
'VOMIT',
'URINE',
'UNICORN_POOP',
'WORM_BLOOD',
'ASH',
'BURNED_CARPET',
'PUDDLE',
'BONES',
'RUBBLE',
'JUNK',
'BROKEN_GLASS',
'ECTOPLASM',
'EMBERS',
'SPIDERWEB',
'NETTING',
'FOLIAGE',
'DEAD_FOLIAGE',
'TRAMPLED_FOLIAGE',
'FUNGUS_FOREST',
'TRAMPLED_FUNGUS_FOREST',
'FORCEFIELD',
'FORCEFIELD_MELT',
'SACRED_GLYPH',
'MANACLE_TL',
'MANACLE_BR',
'MANACLE_TR',
'MANACLE_BL',
'MANACLE_T',
'MANACLE_B',
'MANACLE_L',
'MANACLE_R',
'PORTAL_LIGHT',
'GUARDIAN_GLOW', ///
'PLAIN_FIRE',
'BRIMSTONE_FIRE',
'FLAMEDANCER_FIRE',
'GAS_FIRE',
'GAS_EXPLOSION',
'DART_EXPLOSION',
'ITEM_FIRE',
'CREATURE_FIRE', ///
'POISON_GAS',
'CONFUSION_GAS',
'ROT_GAS',
'STENCH_SMOKE_GAS',
'PARALYSIS_GAS',
'METHANE_GAS',
'STEAM',
'DARKNESS_CLOUD',
'HEALING_CLOUD', ///
'BLOODFLOWER_STALK',
'BLOODFLOWER_POD', ///
'HAVEN_BEDROLL', ///
'DEEP_WATER_ALGAE_WELL',
'DEEP_WATER_ALGAE_1',
'DEEP_WATER_ALGAE_2', ///
'ANCIENT_SPIRIT_VINES',
'ANCIENT_SPIRIT_GRASS', ///
'AMULET_SWITCH', ///
'COMMUTATION_ALTAR',
'COMMUTATION_ALTAR_INERT',
'PIPE_GLOWING',
'PIPE_INERT', ///
'RESURRECTION_ALTAR',
'RESURRECTION_ALTAR_INERT',
'MACHINE_TRIGGER_FLOOR_REPEATING', ///
'SACRIFICE_ALTAR_DORMANT', // 1.7.5
'SACRIFICE_ALTAR', // 1.7.5
'SACRIFICE_LAVA', // 1.7.5
'SACRIFICE_CAGE_DORMANT', // 1.7.5
'DEMONIC_STATUE', // 1.7.5
'STATUE_INERT_DOORWAY',
'STATUE_DORMANT_DOORWAY', ///
'CHASM_WITH_HIDDEN_BRIDGE',
'CHASM_WITH_HIDDEN_BRIDGE_ACTIVE',
'MACHINE_CHASM_EDGE', ///
'RAT_TRAP_WALL_DORMANT',
'RAT_TRAP_WALL_CRACKING', ///
'ELECTRIC_CRYSTAL_OFF',
'ELECTRIC_CRYSTAL_ON',
'TURRET_LEVER', ///
'WORM_TUNNEL_MARKER_DORMANT',
'WORM_TUNNEL_MARKER_ACTIVE',
'WORM_TUNNEL_OUTER_WALL', ///
'BRAZIER', ///
'MUD_FLOOR',
'MUD_WALL',
'MUD_DOORWAY', ///
'NUMBER_TILETYPES',
);
ENUM('lightType',
'NO_LIGHT',
'MINERS_LIGHT',
'BURNING_CREATURE_LIGHT',
'WISP_LIGHT',
'SALAMANDER_LIGHT',
'IMP_LIGHT',
'PIXIE_LIGHT',
'LICH_LIGHT',
'FLAMEDANCER_LIGHT',
'SENTINEL_LIGHT',
'UNICORN_LIGHT',
'IFRIT_LIGHT',
'PHOENIX_LIGHT',
'PHOENIX_EGG_LIGHT',
'YENDOR_LIGHT',
'SPECTRAL_BLADE_LIGHT',
'SPECTRAL_IMAGE_LIGHT',
'SPARK_TURRET_LIGHT',
'EXPLOSIVE_BLOAT_LIGHT',
'BOLT_LIGHT_SOURCE',
'TELEPATHY_LIGHT',
'SACRIFICE_MARK_LIGHT', // 1.7.5
'SCROLL_PROTECTION_LIGHT',
'SCROLL_ENCHANTMENT_LIGHT',
'POTION_STRENGTH_LIGHT',
'EMPOWERMENT_LIGHT',
'GENERIC_FLASH_LIGHT',
'FALLEN_TORCH_FLASH_LIGHT',
'SUMMONING_FLASH_LIGHT',
'EXPLOSION_FLARE_LIGHT',
'QUIETUS_FLARE_LIGHT',
'SLAYING_FLARE_LIGHT',
'CHARGE_FLASH_LIGHT',
'TORCH_LIGHT',
'LAVA_LIGHT',
'SUN_LIGHT',
'DARKNESS_PATCH_LIGHT',
'FUNGUS_LIGHT',
'FUNGUS_FOREST_LIGHT',
'LUMINESCENT_ALGAE_BLUE_LIGHT',
'LUMINESCENT_ALGAE_GREEN_LIGHT',
'ECTOPLASM_LIGHT',
'UNICORN_POOP_LIGHT',
'EMBER_LIGHT',
'FIRE_LIGHT',
'BRIMSTONE_FIRE_LIGHT',
'EXPLOSION_LIGHT',
'INCENDIARY_DART_LIGHT',
'PORTAL_ACTIVATE_LIGHT',
'CONFUSION_GAS_LIGHT',
'DARKNESS_CLOUD_LIGHT',
'FORCEFIELD_LIGHT',
'CRYSTAL_WALL_LIGHT',
'CANDLE_LIGHT',
'HAUNTED_TORCH_LIGHT',
'GLYPH_LIGHT_DIM',
'GLYPH_LIGHT_BRIGHT',
'SACRED_GLYPH_LIGHT',
'DESCENT_LIGHT',
'DEMONIC_STATUE_LIGHT', // 1.7.5
'NUMBER_LIGHT_KINDS'
);
function Fl(N) { return (1 << N); }
function flagToText(flagObj, value) {
const inverse = Object.entries(flagObj).reduce( (out, [key, value]) => {
out[value] = key;
return out;
}, {});
const out = [];
for(let index = 0; index < 32; ++index) {
const fl = (1 << index);
if (value & fl) {
out.push(inverse[fl]);
}
}
return out;
}
function FLAG(flagName, values) {
Object.entries(values).forEach( ([key, value]) => {
if (Array.isArray(value)) {
value = value.reduce( (out, name) => {
return out | values[name];
}, 0);
}
values[key] = value;
GLOBAL[key] = value;
});
values.toString = function(v) {
if (!v) return flagName;
return flagToText(values, v);
}
GLOBAL[flagName] = values;
return values;
}
// Item categories
FLAG('itemCategory', {
FOOD : Fl(0),
WEAPON : Fl(1),
ARMOR : Fl(2),
POTION : Fl(3),
SCROLL : Fl(4),
STAFF : Fl(5),
WAND : Fl(6),
RING : Fl(7),
CHARM : Fl(8),
GOLD : Fl(9),
AMULET : Fl(10),
GEM : Fl(11),
KEY : Fl(12),
CAN_BE_DETECTED : ['WEAPON', 'ARMOR', 'POTION', 'SCROLL', 'RING', 'CHARM', 'WAND', 'STAFF', 'AMULET'],
PRENAMED_CATEGORY : ['FOOD', 'GOLD', 'AMULET', 'GEM', 'KEY'],
NEVER_IDENTIFIABLE : ['FOOD', 'CHARM', 'GOLD', 'AMULET', 'GEM', 'KEY'],
// COUNTS_TOWARD_SCORE : ['GOLD', 'AMULET', 'GEM'], // 1.7.4
CAN_BE_SWAPPED : ['WEAPON', 'ARMOR', 'STAFF', 'CHARM', 'RING'],
ALL_ITEMS : ['FOOD', 'POTION', 'WEAPON', 'ARMOR', 'STAFF', 'WAND', 'SCROLL', 'RING', 'CHARM', 'GOLD', 'AMULET', 'GEM', 'KEY'],
});
ENUM('keyKind',
'KEY_DOOR',
'KEY_CAGE',
'KEY_PORTAL',
'NUMBER_KEY_TYPES'
);
ENUM('foodKind',
'RATION',
'FRUIT',
'NUMBER_FOOD_KINDS'
);
ENUM('potionKind',
'POTION_LIFE',
'POTION_STRENGTH',
'POTION_TELEPATHY',
'POTION_LEVITATION',
'POTION_DETECT_MAGIC',
'POTION_HASTE_SELF',
'POTION_FIRE_IMMUNITY',
'POTION_INVISIBILITY',
'POTION_POISON',
'POTION_PARALYSIS',
'POTION_HALLUCINATION',
'POTION_CONFUSION',
'POTION_INCINERATION',
'POTION_DARKNESS',
'POTION_DESCENT',
'POTION_LICHEN',
'NUMBER_POTION_KINDS'
);
ENUM('weaponKind',
'DAGGER',
'SWORD',
'BROADSWORD',
'WHIP',
'RAPIER',
'FLAIL',
'MACE',
'HAMMER',
'SPEAR',
'PIKE',
'AXE',
'WAR_AXE',
'DART',
'INCENDIARY_DART',
'JAVELIN',
'NUMBER_WEAPON_KINDS'
);
ENUM('weaponEnchants',
'W_SPEED',
'W_QUIETUS',
'W_PARALYSIS',
'W_MULTIPLICITY',
'W_SLOWING',
'W_CONFUSION',
'W_FORCE',
'W_SLAYING',
'W_MERCY',
'W_PLENTY',
'NUMBER_WEAPON_RUNIC_KINDS'
);
const NUMBER_GOOD_WEAPON_ENCHANT_KINDS = weaponEnchants.NUMBER_GOOD_WEAPON_ENCHANT_KINDS = W_MERCY; // Everything before this one...
ENUM('armorKind',
'LEATHER_ARMOR',
'SCALE_MAIL',
'CHAIN_MAIL',
'BANDED_MAIL',
'SPLINT_MAIL',
'PLATE_MAIL',
'NUMBER_ARMOR_KINDS'
);
ENUM('armorEnchants',
'A_MULTIPLICITY',
'A_MUTUALITY',
'A_ABSORPTION',
'A_REPRISAL',
'A_IMMUNITY',
'A_REFLECTION',
'A_RESPIRATION',
'A_DAMPENING',
'A_BURDEN',
'A_VULNERABILITY',
'A_IMMOLATION',
'NUMBER_ARMOR_ENCHANT_KINDS',
);
const NUMBER_GOOD_ARMOR_ENCHANT_KINDS = armorEnchants.NUMBER_GOOD_ARMOR_ENCHANT_KINDS = A_BURDEN; // everything before this
ENUM('wandKind',
'WAND_TELEPORT',
'WAND_SLOW',
'WAND_POLYMORPH',
'WAND_NEGATION',
'WAND_DOMINATION',
'WAND_BECKONING',
'WAND_PLENTY',
'WAND_INVISIBILITY',
'WAND_EMPOWERMENT',
'NUMBER_WAND_KINDS'
);
ENUM('staffKind',
'STAFF_LIGHTNING',
'STAFF_FIRE',
'STAFF_POISON',
'STAFF_TUNNELING',
'STAFF_BLINKING',
'STAFF_ENTRANCEMENT',
'STAFF_OBSTRUCTION',
'STAFF_DISCORD',
'STAFF_CONJURATION',
'STAFF_HEALING',
'STAFF_HASTE',
'STAFF_PROTECTION',
'NUMBER_STAFF_KINDS'
);
// these must be wand bolts, in order, and then staff bolts, in order:
ENUM('boltType',
'BOLT_NONE',
'BOLT_TELEPORT',
'BOLT_SLOW',
'BOLT_POLYMORPH',
'BOLT_NEGATION',
'BOLT_DOMINATION',
'BOLT_BECKONING',
'BOLT_PLENTY',
'BOLT_INVISIBILITY',
'BOLT_EMPOWERMENT',
'BOLT_LIGHTNING',
'BOLT_FIRE',
'BOLT_POISON',
'BOLT_TUNNELING',
'BOLT_BLINKING',
'BOLT_ENTRANCEMENT',
'BOLT_OBSTRUCTION',
'BOLT_DISCORD',
'BOLT_CONJURATION',
'BOLT_HEALING',
'BOLT_HASTE',
'BOLT_SLOW_2',
'BOLT_SHIELDING',
'BOLT_SPIDERWEB',
'BOLT_SPARK',
'BOLT_DRAGONFIRE',
'BOLT_DISTANCE_ATTACK',
'BOLT_POISON_DART',
// 'BOLT_ACID_TURRET_ATTACK', // 1.7.4 TODO - ADD BACK IN CUSTOM
'BOLT_ANCIENT_SPIRIT_VINES',
'BOLT_WHIP',
'NUMBER_BOLT_KINDS'
);
ENUM('ringKind',
'RING_CLAIRVOYANCE',
'RING_STEALTH',
'RING_REGENERATION',
'RING_TRANSFERENCE',
'RING_LIGHT',
'RING_AWARENESS',
'RING_WISDOM',
'RING_REAPING',
'NUMBER_RING_KINDS'
);
ENUM('charmKind',
'CHARM_HEALTH',
'CHARM_PROTECTION',
'CHARM_HASTE',
'CHARM_FIRE_IMMUNITY',
'CHARM_INVISIBILITY',
'CHARM_TELEPATHY',
'CHARM_LEVITATION',
'CHARM_SHATTERING',
'CHARM_GUARDIAN',
'CHARM_TELEPORTATION',
'CHARM_RECHARGING',
'CHARM_NEGATION',
'NUMBER_CHARM_KINDS'
);
ENUM('scrollKind',
'SCROLL_ENCHANTING',
'SCROLL_IDENTIFY',
'SCROLL_TELEPORT',
'SCROLL_REMOVE_CURSE',
'SCROLL_RECHARGING',
'SCROLL_PROTECT_ARMOR',
'SCROLL_PROTECT_WEAPON',
'SCROLL_SANCTUARY',
'SCROLL_MAGIC_MAPPING',
'SCROLL_NEGATION',
'SCROLL_SHATTERING',
'SCROLL_DISCORD',
'SCROLL_AGGRAVATE_MONSTER',
'SCROLL_SUMMON_MONSTER',
'NUMBER_SCROLL_KINDS'
);
const MAX_PACK_ITEMS = 26;
ENUM('monsterTypes',
'MK_YOU',
'MK_RAT',
'MK_KOBOLD',
'MK_JACKAL',
'MK_EEL',
'MK_MONKEY',
'MK_BLOAT',
'MK_PIT_BLOAT',
'MK_GOBLIN',
'MK_GOBLIN_CONJURER',
'MK_GOBLIN_MYSTIC',
'MK_GOBLIN_TOTEM',
'MK_PINK_JELLY',
'MK_TOAD',
'MK_VAMPIRE_BAT',
'MK_ARROW_TURRET',
'MK_ACID_MOUND',
'MK_CENTIPEDE',
'MK_OGRE',
'MK_BOG_MONSTER',
'MK_OGRE_TOTEM',
'MK_SPIDER',
'MK_SPARK_TURRET',
'MK_WILL_O_THE_WISP',
'MK_WRAITH',
'MK_ZOMBIE',
'MK_TROLL',
'MK_OGRE_SHAMAN',
'MK_NAGA',
'MK_SALAMANDER',
'MK_EXPLOSIVE_BLOAT',
'MK_DAR_BLADEMASTER',
'MK_DAR_PRIESTESS',
'MK_DAR_BATTLEMAGE',
'MK_ACID_JELLY',
'MK_CENTAUR',
'MK_UNDERWORM',
'MK_SENTINEL',
// 'MK_ACID_TURRET', // 1.7.4 -- TODO: ADD BACK IN CUSTOM
'MK_DART_TURRET',
'MK_KRAKEN',
'MK_LICH',
'MK_PHYLACTERY',
'MK_PIXIE',
'MK_PHANTOM',
'MK_FLAME_TURRET',
'MK_IMP',
'MK_FURY',
'MK_REVENANT',
'MK_TENTACLE_HORROR',
'MK_GOLEM',
'MK_DRAGON',
'MK_GOBLIN_CHIEFTAN',
'MK_BLACK_JELLY',
'MK_VAMPIRE',
'MK_FLAMEDANCER',
'MK_SPECTRAL_BLADE',
'MK_SPECTRAL_IMAGE',
'MK_GUARDIAN',
'MK_WINGED_GUARDIAN',
'MK_CHARM_GUARDIAN',
'MK_WARDEN_OF_YENDOR',
'MK_ELDRITCH_TOTEM',
'MK_MIRRORED_TOTEM',
'MK_UNICORN',
'MK_IFRIT',
'MK_PHOENIX',
'MK_PHOENIX_EGG',
'MK_ANCIENT_SPIRIT',
'NUMBER_MONSTER_KINDS'
);
const NUMBER_MUTATORS = 8;
const MONSTER_CLASS_COUNT = 13;
// flavors
const NUMBER_ITEM_COLORS = 21;
const NUMBER_TITLE_PHONEMES = 21;
const NUMBER_ITEM_WOODS = 21;
const NUMBER_POTION_DESCRIPTIONS = 18;
const NUMBER_ITEM_METALS = 12;
const NUMBER_ITEM_GEMS = 18;
// Dungeon flags
FLAG('tileFlags', {
DISCOVERED : Fl(0),
VISIBLE : Fl(1), // cell has sufficient light and is in field of view, ready to draw.
HAS_PLAYER : Fl(2),
HAS_MONSTER : Fl(3),
HAS_DORMANT_MONSTER : Fl(4), // hidden monster on the square
HAS_ITEM : Fl(5),
IN_FIELD_OF_VIEW : Fl(6), // player has unobstructed line of sight whether or not there is enough light
WAS_VISIBLE : Fl(7),
HAS_STAIRS : Fl(8),
SEARCHED_FROM_HERE : Fl(9), // Player already auto-searched from here; can't auto search here again
IS_IN_SHADOW : Fl(10), // so that a player gains an automatic stealth bonus
MAGIC_MAPPED : Fl(11),
ITEM_DETECTED : Fl(12),
CLAIRVOYANT_VISIBLE : Fl(13),
WAS_CLAIRVOYANT_VISIBLE : Fl(14),
CLAIRVOYANT_DARKENED : Fl(15), // magical blindness from a cursed ring of clairvoyance
CAUGHT_FIRE_THIS_TURN : Fl(16), // so that fire does not spread asymmetrically
PRESSURE_PLATE_DEPRESSED : Fl(17), // so that traps do not trigger repeatedly while you stand on them
STABLE_MEMORY : Fl(18), // redraws will simply be pulled from the memory array, not recalculated
KNOWN_TO_BE_TRAP_FREE : Fl(19), // keep track of where the player has stepped as he knows no traps are there
IS_IN_PATH : Fl(20), // the yellow trail leading to the cursor
IN_LOOP : Fl(21), // this cell is part of a terrain loop
IS_CHOKEPOINT : Fl(22), // if this cell is blocked, part of the map will be rendered inaccessible
IS_GATE_SITE : Fl(23), // consider placing a locked door here
IS_IN_ROOM_MACHINE : Fl(24),
IS_IN_AREA_MACHINE : Fl(25),
IS_POWERED : Fl(26), // has been activated by machine power this turn (can probably be eliminate if needed)
IMPREGNABLE : Fl(27), // no tunneling allowed!
TERRAIN_COLORS_DANCING : Fl(28), // colors here will sparkle when the game is idle
TELEPATHIC_VISIBLE : Fl(29), // potions of telepathy let you see through other creatures' eyes
WAS_TELEPATHIC_VISIBLE : Fl(30), // potions of telepathy let you see through other creatures' eyes
IS_IN_MACHINE : ['IS_IN_ROOM_MACHINE', 'IS_IN_AREA_MACHINE'], // sacred ground; don't generate items here, or teleport randomly to it
PERMANENT_TILE_FLAGS : ['DISCOVERED', 'MAGIC_MAPPED', 'ITEM_DETECTED', 'HAS_ITEM', 'HAS_DORMANT_MONSTER',
'HAS_STAIRS', 'SEARCHED_FROM_HERE', 'PRESSURE_PLATE_DEPRESSED',
'STABLE_MEMORY', 'KNOWN_TO_BE_TRAP_FREE', 'IN_LOOP',
'IS_CHOKEPOINT', 'IS_GATE_SITE', 'IS_IN_MACHINE', 'IMPREGNABLE'],
ANY_KIND_OF_VISIBLE : ['VISIBLE', 'CLAIRVOYANT_VISIBLE', 'TELEPATHIC_VISIBLE'],
});
const TURNS_FOR_FULL_REGEN = 300;
const STOMACH_SIZE = 2150;
const HUNGER_THRESHOLD = (STOMACH_SIZE - 1800);
const WEAK_THRESHOLD = 150;
const FAINT_THRESHOLD = 50;
const MAX_EXP_LEVEL = 20;
const MAX_EXP = 100000000;
// XPXP required to enable telepathic awareness with the ally
const XPXP_NEEDED_FOR_TELEPATHIC_BOND = 1400 ;
const ROOM_MIN_WIDTH = 4;
const ROOM_MAX_WIDTH = 20;
const ROOM_MIN_HEIGHT = 3;
const ROOM_MAX_HEIGHT = 7;
const HORIZONTAL_CORRIDOR_MIN_LENGTH = 5;
const HORIZONTAL_CORRIDOR_MAX_LENGTH = 15;
const VERTICAL_CORRIDOR_MIN_LENGTH = 2;
const VERTICAL_CORRIDOR_MAX_LENGTH = 9;
const CROSS_ROOM_MIN_WIDTH = 3;
const CROSS_ROOM_MAX_WIDTH = 12;
const CROSS_ROOM_MIN_HEIGHT = 2;
const CROSS_ROOM_MAX_HEIGHT = 5;
const MIN_SCALED_ROOM_DIMENSION = 2;
const ROOM_TYPE_COUNT = 8;
const CORRIDOR_WIDTH = 1;
const WAYPOINT_SIGHT_RADIUS = 10;
const MAX_WAYPOINT_COUNT = 40;
const MAX_ITEMS_IN_MONSTER_ITEMS_HOPPER = 100;
// Making these larger means cave generation will take more trials; set them too high and the program will hang.
const CAVE_MIN_WIDTH = 50;
const CAVE_MIN_HEIGHT = 20;
// Keyboard commands:
const UP_KEY = 'k';
const DOWN_KEY = 'j';
const LEFT_KEY = 'h';
const RIGHT_KEY = 'l';
const UPLEFT_KEY = 'y';
const UPRIGHT_KEY = 'u';
const DOWNLEFT_KEY = 'b';
const DOWNRIGHT_KEY = 'n';
const UP_ARROW = 'ArrowUp'; // 63232;
const LEFT_ARROW = 'ArrowLeft'; // 63234;
const DOWN_ARROW = 'ArrowDown'; // 63233;
const RIGHT_ARROW = 'ArrowRight'; // 63235;
const SHIFT_UP_KEY = 'K';
const SHIFT_DOWN_KEY = 'J';
const SHIFT_LEFT_KEY = 'H';
const SHIFT_RIGHT_KEY = 'L';
const SHIFT_UPLEFT_KEY = 'Y';
const SHIFT_UPRIGHT_KEY = 'U';
const SHIFT_DOWNLEFT_KEY = 'B';
const SHIFT_DOWNRIGHT_KEY = 'N';
const SHIFT_UP_ARROW = 'ARROWUP'; // 63232;
const SHIFT_LEFT_ARROW = 'ARROWLEFT'; // 63234;
const SHIFT_DOWN_ARROW = 'ARROWDOWN'; // 63233;
const SHIFT_RIGHT_ARROW = 'ARROWRIGHT'; // 63235;
const DESCEND_KEY = '>';
const ASCEND_KEY = '<';
const REST_KEY = 'z';
const AUTO_REST_KEY = 'Z';
const SEARCH_KEY = 's';
const INVENTORY_KEY = 'i';
const ACKNOWLEDGE_KEY = ' ';
const EQUIP_KEY = 'e';
const UNEQUIP_KEY = 'r';
const APPLY_KEY = 'a';
const THROW_KEY = 't';
const RELABEL_KEY = 'R';
const TRUE_COLORS_KEY = '\\';
const AGGRO_DISPLAY_KEY = ']';
// const WARNING_PAUSE_KEY = '['; // REMOVED in 1.7.5
const DROP_KEY = 'd';
const CALL_KEY = 'c';
const QUIT_KEY = 'Q';
const MESSAGE_ARCHIVE_KEY = 'M';
const HELP_KEY = '?';
const DISCOVERIES_KEY = 'D';
const EXPLORE_KEY = 'x';
const AUTOPLAY_KEY = 'A';
const SEED_KEY = '~';
const EASY_MODE_KEY = '&';
const ESCAPE_KEY = 'Escape'; // '\033';
const RETURN_KEY = 'Enter'; // '\015';
const ENTER_KEY = 'Enter'; // '\012';
const DELETE_KEY = 'Delete'; // '\177';
const BACKSPACE_KEY = 'Backspace';
const TAB_KEY = 'Tab'; // '\t';
// Cocoa reports shift-tab this way for some reason.
const SHIFT_TAB_KEY = 'TAB'; // 25 ;
const PERIOD_KEY = '.';
const VIEW_RECORDING_KEY = 'V';
const LOAD_SAVED_GAME_KEY = 'O';
const SAVE_GAME_KEY = 'S';
const NEW_GAME_KEY = 'N';
const NUMPAD_0 = 'Numpad0'; // 48;
const NUMPAD_1 = 'Numpad1'; // 49;
const NUMPAD_2 = 'Numpad2'; // 50;
const NUMPAD_3 = 'Numpad3'; // 51;
const NUMPAD_4 = 'Numpad4'; // 52;
const NUMPAD_5 = 'Numpad5'; // 53;
const NUMPAD_6 = 'Numpad6'; // 54;
const NUMPAD_7 = 'Numpad7'; // 55;
const NUMPAD_8 = 'Numpad8'; // 56;
const NUMPAD_9 = 'Numpad9'; // 57;
const PAGE_UP_KEY = 'PageUp'; // 63276;
const PAGE_DOWN_KEY = 'PageDown'; // 63277;
const TEST_KEY = 'T';
const UNKNOWN_KEY = (128+19);
const min = Math.min;
const max = Math.max;
const pow = Math.pow;
const abs = Math.abs;
const cos = Math.cos;
const sin = Math.sin;
const sqrt = Math.sqrt;
const log10 = Math.log10;
function clamp(x, low, hi) { return min(hi, max(x, low)); } // pins x to the [y, z] interval
function terrainFlags(x, y) {
return (tileCatalog[pmap[x][y].layers[DUNGEON]].flags
| tileCatalog[pmap[x][y].layers[LIQUID]].flags
| tileCatalog[pmap[x][y].layers[SURFACE]].flags
| tileCatalog[pmap[x][y].layers[GAS]].flags);
}
function terrainMechFlags(x, y) {
return (tileCatalog[pmap[x][y].layers[DUNGEON]].mechFlags
| tileCatalog[pmap[x][y].layers[LIQUID]].mechFlags
| tileCatalog[pmap[x][y].layers[SURFACE]].mechFlags
| tileCatalog[pmap[x][y].layers[GAS]].mechFlags);
}
// #ifdef BROGUE_ASSERTS
// boolean cellHasTerrainFlag(short x, short y, unsigned long flagMask);
// #else
function cellHasTerrainFlag(x, y, flagMask) {
return (flagMask & terrainFlags(x, y)) ? true : false;
}
// #endif
function cellHasTMFlag(x, y, flagMask) {
return (flagMask & terrainMechFlags(x, y)) ? true : false;
}
function cellHasTerrainType(x, y, terrain) {
return (pmap[x][y].layers[DUNGEON] === terrain
|| pmap[x][y].layers[LIQUID] === terrain
|| pmap[x][y].layers[SURFACE] === terrain
|| pmap[x][y].layers[GAS] === terrain) ? true : false;
}
function cellHasKnownTerrainFlag(x, y, flagMask) {
return (flagMask & pmap[x][y].rememberedTerrainFlags) ? true : false;
}
function cellIsPassableOrDoor(x, y) {
return (!cellHasTerrainFlag(x, y, T_PATHING_BLOCKER)
|| (cellHasTMFlag(x, y, (TM_IS_SECRET | TM_PROMOTES_WITH_KEY | TM_CONNECTS_LEVEL))
&& cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)));
}
function coordinatesAreInMap(x, y) { return ((x) >= 0 && (x) < DCOLS && (y) >= 0 && (y) < DROWS); }
function coordinatesAreInWindow(x, y) { return ((x) >= 0 && (x) < COLS && (y) >= 0 && (y) < ROWS); }
function mapToWindowX(x) { return ((x) + STAT_BAR_WIDTH + 1); }
function mapToWindowY(y) { return ((y) + MESSAGE_LINES); }
function windowToMapX(x) { return ((x) - STAT_BAR_WIDTH - 1); }
function windowToMapY(y) { return ((y) - MESSAGE_LINES); }
function playerCanDirectlySee(x, y) { return (pmap[x][y].flags & VISIBLE); }
function playerCanSee(x, y) { return (pmap[x][y].flags & ANY_KIND_OF_VISIBLE); }
function playerCanSeeOrSense(x, y) {
return ((pmap[x][y].flags & ANY_KIND_OF_VISIBLE)
|| (rogue.playbackOmniscience
&& (pmap[x][y].layers[DUNGEON] != GRANITE || (pmap[x][y].flags & DISCOVERED))));
}
// TODO ????
function CYCLE_MONSTERS_AND_PLAYERS(fn) {
let v;
for (v = player; v != NULL; v = (v === player ? monsters.nextCreature : v.nextCreature)) {
fn(v);
}
}
var RNG_STACK = [];
function assureCosmeticRNG() {
RNG_STACK.push(rogue.RNG);
rogue.RNG = RNG_COSMETIC;
}
function restoreRNG() {
rogue.RNG = RNG_STACK.length ? RNG_STACK.pop() : RNG_SUBSTANTIVE;
}
const MIN_COLOR_DIFF = 600;
// weighted sum of the squares of the component differences. Weights are according to color perception.
function COLOR_DIFF(f, b) {
return ((f.red - b.red) * (f.red - b.red) * 0.2126
+ (f.green - b.green) * (f.green - b.green) * 0.7152
+ (f.blue - b.blue) * (f.blue - b.blue) * 0.0722);
}
// game data formulae:
// function staffDamageLow(enchant) { return (Math.floor(3 * (2 + (enchant)) / 4 + FLOAT_FUDGE)); }
// function staffDamageHigh(enchant) { return (Math.floor(4 + 5 * (enchant) / 2 + FLOAT_FUDGE)); }
// function staffDamage(enchant) { return (randClumpedRange(staffDamageLow(enchant), staffDamageHigh(enchant), 1 + (enchant) / 3)); }
// function staffPoison(enchant) { return (Math.floor(5 * pow(1.3, (enchant) - 2) + FLOAT_FUDGE)); }
// function staffBlinkDistance(enchant) { return (Math.floor((enchant) * 2 + 2 + FLOAT_FUDGE)); }
// function staffHasteDuration(enchant) { return (Math.floor(2 + (enchant) * 4 + FLOAT_FUDGE)); }
// function staffBladeCount(enchant) { return (Math.floor((enchant) * 3 / 2 + FLOAT_FUDGE)); }
// function staffDiscordDuration(enchant) { return (Math.floor((enchant) * 4 + FLOAT_FUDGE)); }
// function staffProtection(enchant) { return (Math.floor(50 * pow(1.53, (enchant) - 2) + FLOAT_FUDGE)); }
// function staffEntrancementDuration(enchant) { return (Math.floor((enchant) * 3 + FLOAT_FUDGE)); }
//
// function ringWisdomMultiplier(enchant) { return Math.floor(10 * pow(1.3, min(27, (enchant))) + FLOAT_FUDGE); }
//
// function charmHealing(enchant) { return (Math.floor(clamp(20 * (enchant), 0, 100) + FLOAT_FUDGE)); }
// function charmProtection(enchant) { return (Math.floor(150 * pow(1.35, (double) (enchant) - 1) + FLOAT_FUDGE)); }
// function charmShattering(enchant) { return (Math.floor(4 + (enchant) + FLOAT_FUDGE)); }
// function charmGuardianLifespan(enchant) { return (Math.floor(4 + (2 * (enchant)) + FLOAT_FUDGE)); }
// function charmNegationRadius(enchant) { return (Math.floor(1 + (3 * (enchant)) + FLOAT_FUDGE)); }
//
// function wandDominate(monst) {
// return ((monst.currentHP * 5 < monst.info.maxHP) ? 100 : max(0, 100 * (monst.info.maxHP - monst.currentHP) / monst.info.maxHP));
// }
//
// function weaponParalysisDuration(enchant) { return (max(2, Math.floor(2 + ((enchant) / 2) + FLOAT_FUDGE))); }
// function weaponConfusionDuration(enchant) { return (max(3, Math.floor(1.5 * (enchant) + FLOAT_FUDGE))); }
// function weaponForceDistance(enchant) { return (max(4, ((Math.floor(enchant + FLOAT_FUDGE)) * 2 + 2))); } // Depends on definition of staffBlinkDistance() above.;
// function weaponSlowDuration(enchant) { return (max(3, Math.floor(((enchant) + 2) * ((enchant) + 2) / 3 + FLOAT_FUDGE))); }
// function weaponImageCount(enchant) { return (clamp(Math.floor((enchant) / 3 + FLOAT_FUDGE), 1, 7)); }
// function weaponImageDuration(enchant) { return 3; } //(max((int) (1 + (enchant) / 3), 2));
//
// function armorReprisalPercent(enchant) { return (max(5, Math.floor((enchant) * 5 + FLOAT_FUDGE))); }
// function armorAbsorptionMax(enchant) { return (max(1, Math.floor((enchant) + FLOAT_FUDGE))); }
// function armorImageCount(enchant) { return (clamp(Math.floor((enchant) / 3 + FLOAT_FUDGE), 1, 5)); }
// function reflectionChance(enchant) { return (clamp((100 - Math.floor(100 * pow(0.85, (enchant)) + FLOAT_FUDGE)), 1, 100)); }
//
// // This will max out at full regeneration in about two turns.
// // This is the Syd nerf, after Syd broke the game over his knee with a +18 ring of regeneration.
// function turnsForFullRegen(bonus) { return (Math.floor(1000 * TURNS_FOR_FULL_REGEN * pow(0.75, (bonus)) + 2000 + FLOAT_FUDGE)); }
// structs
ENUM('dungeonLayers', -1,
'NO_LAYER',
'DUNGEON', // dungeon-level tile (e.g. walls)
'LIQUID', // liquid-level tile (e.g. lava)
'GAS', // gas-level tile (e.g. fire, smoke, swamp gas)
'SURFACE', // surface-level tile (e.g. grass)
'NUMBER_TERRAIN_LAYERS'
);
class CellDisplayBuffer {
constructor() {
this.char = '';
this.foreColorComponents = []; // [3];
this.backColorComponents = []; // [3];
this.opacity = 0;
this.needsUpdate = false;
}
copy(other) {
this.char = other.char;
for(let i = 0; i < 3; ++i) {
this.foreColorComponents[i] = other.foreColorComponents[i];
this.backColorComponents[i] = other.backColorComponents[i];
}
this.opacity = other.opacity;
this.needsUpdate = other.needsUpdate;
}
}
// keeps track of graphics so we only redraw if the cell has changed:
function cellDisplayBuffer() {
return new CellDisplayBuffer();
}
class PCell {
constructor() {
this.layers = []; // [NUMBER_TERRAIN_LAYERS]; // terrain /* ENUM tileType */
this.flags = 0; // non-terrain cell flags
this.volume = 0; // quantity of gas in cell
this.machineNumber = 0;
this.rememberedAppearance = cellDisplayBuffer(); // how the player remembers the cell to look
this.rememberedItemCategory = 0; // what category of item the player remembers lying there
this.rememberedItemKind = 0; // what kind of item the player remembers lying there
this.rememberedItemQuantity = 0; // how many of the item the player remembers lying there
this.rememberedTerrain = 0; // what the player remembers as the terrain (i.e. highest priority terrain upon last seeing)
this.rememberedCellFlags = 0; // map cell flags the player remembers from that spot
this.rememberedTerrainFlags = 0; // terrain flags the player remembers from that spot
this.rememberedTMFlags = 0; // TM flags the player remembers from that spot
}
copy(other) {
this.layers = other.layers.slice();
this.flags = other.flags;
this.volume = other.volume; // quantity of gas in cell
this.machineNumber = other.machineNumber;
this.rememberedAppearance.copy(other.rememberedAppearance); // how the player remembers the cell to look
this.rememberedItemCategory = other.rememberedItemCategory; // what category of item the player remembers lying there
this.rememberedItemKind = other.rememberedItemKind; // what kind of item the player remembers lying there
this.rememberedItemQuantity = other.rememberedItemQuantity; // how many of the item the player remembers lying there
this.rememberedTerrain = other.rememberedTerrain; // what the player remembers as the terrain (i.e. highest priority terrain upon last seeing)
this.rememberedCellFlags = other.rememberedCellFlags; // map cell flags the player remembers from that spot
this.rememberedTerrainFlags = other.rememberedTerrainFlags; // terrain flags the player remembers from that spot
this.rememberedTMFlags = other.rememberedTMFlags; // TM flags the player remembers from that spot
}
}
function pcell() { // permanent cell; have to remember this stuff to save levels
return new PCell();
}
function tcell() { // transient cell; stuff we don't need to remember between levels
return {
light: [0, 0, 0], // [3]; // RGB components of lighting
oldLight: [0, 0, 0], // [3]; // compare with subsequent lighting to determine whether to refresh cell
}
}
class RandomRange {
constructor(lower, upper, clump) {
this.lowerBound = lower || 0;
this.upperBound = upper || 0;
this.clumpFactor = clump || 0;
}
copy(other) {
this.lowerBound = other.lowerBound;
this.upperBound = other.upperBound;
this.clumpFactor = other.clumpFactor;
}
}
function randomRange(lower, upper, clump) {
if (arguments.length == 1) {
if (Array.isArray(lower)) {
clump = lower[2];
upper = lower[1];
lower = lower[0];
}
else {
clump = lower.clumpFactor;
upper = lower.upperBound;
lower = lower.lowerBound;
}
}
return new RandomRange(lower, upper, clump);
}
class Color {
constructor(r, g, b, rr, gr, br, rnd, d) {
// base RGB components:
this.red = r || 0;
this.green = g || 0;
this.blue = b || 0;
// random RGB components to add to base components:
this.redRand = rr || 0;
this.greenRand = gr || 0;
this.blueRand = br || 0;
// random scalar to add to all components:
this.rand = rnd || 0;
// Flag: this color "dances" with every refresh:
this.colorDances = d || false;
}
copy(other) {
Object.assign(this, other);
}
clone() {
return new Color(this.red, this.green, this.blue,
this.redRand, this.greenRand, this.blueRand, this.rand,
this.colorDances);
}
}
function color(r, g, b, rr, gr, br, rnd, d) {
if (arguments.length == 1 && r instanceof Color) {
const n = r.clone();
return n;
}
return new Color(r, g, b, rr, gr, br, rnd, d);
}
FLAG('itemFlags', {
ITEM_IDENTIFIED : Fl(0),
ITEM_EQUIPPED : Fl(1),
ITEM_CURSED : Fl(2),
ITEM_PROTECTED : Fl(3),
// unused : Fl(4),
ITEM_RUNIC : Fl(5),
ITEM_RUNIC_HINTED : Fl(6),
ITEM_RUNIC_IDENTIFIED : Fl(7),
ITEM_CAN_BE_IDENTIFIED : Fl(8),
ITEM_PREPLACED : Fl(9),
ITEM_FLAMMABLE : Fl(10),
ITEM_MAGIC_DETECTED : Fl(11),
ITEM_MAX_CHARGES_KNOWN : Fl(12),
ITEM_IS_KEY : Fl(13),
// ITEM_ATTACKS_HIT_SLOWLY : Fl(14), // mace, hammer 1.7.4
ITEM_ATTACKS_STAGGER : Fl(14), // mace, hammer
ITEM_ATTACKS_EXTEND : Fl(15), // whip
ITEM_ATTACKS_QUICKLY : Fl(16), // rapier
ITEM_ATTACKS_PENETRATE : Fl(17), // spear, pike
ITEM_ATTACKS_ALL_ADJACENT : Fl(18), // axe, war axe
ITEM_LUNGE_ATTACKS : Fl(19), // rapier
ITEM_SNEAK_ATTACK_BONUS : Fl(20), // dagger
ITEM_PASS_ATTACKS : Fl(21), // flail
ITEM_KIND_AUTO_ID : Fl(22), // the item type will become known when the item is picked up.
ITEM_PLAYER_AVOIDS : Fl(23), // explore and travel will try to avoid picking the item up
});
const KEY_ID_MAXIMUM = 20;
function keyLocationProfile() {
return {
x: 0,
y: 0,
machine: 0,
disposableHere: false
};
}
function item() {
return {
category: 0,
kind: 0,
flags: 0,
/* randomRange */ damage: randomRange(),
armor: 0,
charges: 0,
enchant1: 0,
enchant2: 0,
timesEnchanted: 0,
/* ENUM monsterTypes */vorpalEnemy: 0,
strengthRequired: 0,
quiverNumber: 0,
displayChar: '',
foreColor: null,
inventoryColor: null,
quantity: 0,
inventoryLetter: '',
inscription: STRING(), // [DCOLS];
xLoc: 0,
yLoc: 0,
/* keyLocationProfile */ keyLoc: ARRAY(KEY_ID_MAXIMUM, keyLocationProfile), // [KEY_ID_MAXIMUM];
originDepth: 0,
/* struct item */ nextItem: null,
};
}
function itemTable(name, flavor, title, freq, value, str, range, ident, called, desc) {
range = range || [];
return {
name: name || '',
flavor: flavor || '',
callTitle: STRING(title || ''), // [30];
frequency: freq || 0,
marketValue: value || 0,
strengthRequired: str || 0,
range: randomRange(range),
identified: ident || false,
called: called || false,
description: desc || '', // [1500];
};
}
ENUM('dungeonFeatureTypes',
'DF_NONE',
'DF_GRANITE_COLUMN',
'DF_CRYSTAL_WALL',
'DF_LUMINESCENT_FUNGUS',
'DF_GRASS',
'DF_DEAD_GRASS',
'DF_BONES',
'DF_RUBBLE',
'DF_FOLIAGE',
'DF_FUNGUS_FOREST',
'DF_DEAD_FOLIAGE',
'DF_SUNLIGHT',
'DF_DARKNESS',
'DF_SHOW_DOOR',
'DF_SHOW_POISON_GAS_TRAP',
'DF_SHOW_PARALYSIS_GAS_TRAP',
'DF_SHOW_TRAPDOOR_HALO',
'DF_SHOW_TRAPDOOR',
'DF_SHOW_CONFUSION_GAS_TRAP',
'DF_SHOW_FLAMETHROWER_TRAP',
'DF_SHOW_FLOOD_TRAP',
'DF_SHOW_NET_TRAP',
'DF_SHOW_ALARM_TRAP',
'DF_RED_BLOOD',
'DF_GREEN_BLOOD',
'DF_PURPLE_BLOOD',
'DF_WORM_BLOOD',
'DF_ACID_BLOOD',
'DF_ASH_BLOOD',
'DF_EMBER_BLOOD',
'DF_ECTOPLASM_BLOOD',
'DF_RUBBLE_BLOOD',
'DF_ROT_GAS_BLOOD',
'DF_VOMIT',
'DF_BLOAT_DEATH',
'DF_BLOAT_EXPLOSION',
'DF_BLOOD_EXPLOSION',
'DF_FLAMEDANCER_CORONA',
'DF_MUTATION_EXPLOSION',
'DF_MUTATION_LICHEN',
'DF_REPEL_CREATURES',
'DF_ROT_GAS_PUFF',
'DF_STEAM_PUFF',
'DF_STEAM_ACCUMULATION',
'DF_METHANE_GAS_PUFF',
'DF_SALAMANDER_FLAME',
'DF_URINE',
'DF_UNICORN_POOP',
'DF_PUDDLE',
'DF_ASH',
'DF_ECTOPLASM_DROPLET',
'DF_FORCEFIELD',
'DF_FORCEFIELD_MELT',
'DF_SACRED_GLYPHS',
'DF_LICHEN_GROW',
'DF_TUNNELIZE',
'DF_SHATTERING_SPELL',
// spiderwebs
'DF_WEB_SMALL',
'DF_WEB_LARGE',
// ancient spirit
'DF_ANCIENT_SPIRIT_VINES',
'DF_ANCIENT_SPIRIT_GRASS',
// foliage
'DF_TRAMPLED_FOLIAGE',
'DF_SMALL_DEAD_GRASS',
'DF_FOLIAGE_REGROW',
'DF_TRAMPLED_FUNGUS_FOREST',
'DF_FUNGUS_FOREST_REGROW',
// brimstone
'DF_ACTIVE_BRIMSTONE',
'DF_INERT_BRIMSTONE',
// bloodwort
'DF_BLOODFLOWER_PODS_GROW_INITIAL',
'DF_BLOODFLOWER_PODS_GROW',
'DF_BLOODFLOWER_POD_BURST',
// dewars
'DF_DEWAR_CAUSTIC',
'DF_DEWAR_CONFUSION',
'DF_DEWAR_PARALYSIS',
'DF_DEWAR_METHANE',
'DF_DEWAR_GLASS',
'DF_CARPET_AREA',
// algae
'DF_BUILD_ALGAE_WELL',
'DF_ALGAE_1',
'DF_ALGAE_2',
'DF_ALGAE_REVERT'