UNPKG

brogue-js

Version:

A 100% Javascript version of Brogue

1,837 lines (1,628 loc) 1.58 MB
/* 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'