/*
* File: main.cc
* Summary: Main entry point, event loop, and some initialization functions
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include <string>
#include <algorithm>
// I don't seem to need values.h for VACPP..
#if !defined(__IBMCPP__)
#include <limits.h>
#endif
#ifdef DEBUG
// this contains the DBL_MAX constant
#include <float.h>
#endif
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <sstream>
#include <iostream>
#ifdef USE_UNIX_SIGNALS
#include <signal.h>
#endif
#include "externs.h"
#include "abl-show.h"
#include "abyss.h"
#include "areas.h"
#include "artefact.h"
#include "arena.h"
#include "branch.h"
#include "chardump.h"
#include "cio.h"
#include "cloud.h"
#include "clua.h"
#include "command.h"
#include "coord.h"
#include "coordit.h"
#include "ctest.h"
#include "crash.h"
#include "database.h"
#include "dbg-maps.h"
#include "dbg-scan.h"
#include "debug.h"
#include "delay.h"
#include "describe.h"
#include "dlua.h"
#include "directn.h"
#include "dungeon.h"
#include "effects.h"
#include "env.h"
#include "map_knowledge.h"
#include "fprop.h"
#include "fight.h"
#include "files.h"
#include "food.h"
#include "godabil.h"
#include "hiscores.h"
#include "initfile.h"
#include "invent.h"
#include "item_use.h"
#include "it_use3.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "lev-pand.h"
#include "los.h"
#include "luaterp.h"
#include "macro.h"
#include "makeitem.h"
#include "mapmark.h"
#include "maps.h"
#include "message.h"
#include "misc.h"
#include "mon-act.h"
#include "mon-cast.h"
#include "mon-iter.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "mutation.h"
#include "newgame.h"
#include "ng-init.h"
#include "notes.h"
#include "options.h"
#include "ouch.h"
#include "output.h"
#include "overmap.h"
#include "player.h"
#include "quiver.h"
#include "random.h"
#include "religion.h"
#include "shopping.h"
#include "skills.h"
#include "skills2.h"
#include "species.h"
#include "spells1.h"
#include "spells2.h"
#include "spells3.h"
#include "spells4.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
#include "stash.h"
#include "state.h"
#include "stuff.h"
#include "tags.h"
#include "terrain.h"
#include "transform.h"
#include "traps.h"
#include "travel.h"
#include "tutorial.h"
#include "view.h"
#include "shout.h"
#include "viewchar.h"
#include "viewgeom.h"
#include "stash.h"
#include "wiz-dgn.h"
#include "wiz-fsim.h"
#include "wiz-item.h"
#include "wiz-mon.h"
#include "wiz-you.h"
#include "xom.h"
#ifdef USE_TILE
#include "tiles.h"
#include "tiledef-dngn.h"
#endif
// ----------------------------------------------------------------------
// Globals whose construction/destruction order needs to be managed
// ----------------------------------------------------------------------
CLua clua(true);
CLua dlua(false); // Lua interpreter for the dungeon builder.
crawl_environment env; // Requires dlua.
player you;
system_environment SysEnv;
game_state crawl_state;
std::string init_file_error; // externed in newgame.cc
char info[ INFO_SIZE ]; // messaging queue extern'd everywhere {dlb}
int stealth; // externed in view.cc
void world_reacts();
static key_recorder repeat_again_rec;
// Clockwise, around the compass from north (same order as enum RUN_DIR)
const struct coord_def Compass[8] =
{
coord_def(0, -1), coord_def(1, -1), coord_def(1, 0), coord_def(1, 1),
coord_def(0, 1), coord_def(-1, 1), coord_def(-1, 0), coord_def(-1, -1),
};
// Functions in main module
static void _do_berserk_no_combat_penalty(void);
static bool _initialise(void);
static void _input(void);
static void _move_player(int move_x, int move_y);
static void _move_player(coord_def move);
static int _check_adjacent(dungeon_feature_type feat, coord_def& delta);
static void _open_door(coord_def move, bool check_confused = true);
static void _open_door(int x, int y, bool check_confused = true)
{
_open_door(coord_def(x, y), check_confused);
}
static void _close_door(coord_def move);
static void _start_running( int dir, int mode );
static void _prep_input();
static command_type _get_next_cmd();
static keycode_type _get_next_keycode();
static command_type _keycode_to_command( keycode_type key );
static void _setup_cmd_repeat();
static void _do_prev_cmd_again();
static void _update_replay_state();
static void _show_commandline_options_help();
static void _wanderer_startup_message();
static void _god_greeting_message( bool game_start );
static void _take_starting_note();
static void _startup_tutorial();
#ifdef DGL_SIMPLE_MESSAGING
static void _read_messages();
#endif
static void _compile_time_asserts();
//
// It all starts here. Some initialisations are run first, then straight
// to new_game and then input.
//
#ifdef USE_TILE
#include <SDL_main.h>
#endif
int main( int argc, char *argv[] )
{
_compile_time_asserts(); // Just to quiet "unused static function" warning.
init_crash_handler();
// Hardcoded initial keybindings.
init_keybindings();
// Load in the system environment variables
get_system_environment();
// Parse command line args -- look only for initfile & crawl_dir entries.
if (!parse_args(argc, argv, true))
{
_show_commandline_options_help();
return 1;
}
// Init monsters up front - needed to handle the mon_glyph option right.
init_monsters();
// Init name cache so that we can parse stash_filter by item name.
init_properties();
init_item_name_cache();
// Read the init file.
init_file_error = read_init_file();
// Now parse the args again, looking for everything else.
parse_args( argc, argv, false );
if (Options.sc_entries != 0 || !SysEnv.scorefile.empty())
{
hiscores_print_all( Options.sc_entries, Options.sc_format );
return 0;
}
else
{
// Don't allow scorefile override for actual gameplay, only for
// score listings.
SysEnv.scorefile.clear();
}
#ifdef USE_TILE
if (!tiles.initialise())
return -1;
#endif
const bool game_start = _initialise();
// Override some options for tutorial.
init_tutorial_options();
msg::stream << "Welcome" << (game_start? "" : " back") << ", "
<< you.your_name << " the "
<< species_name( you.species,you.experience_level )
<< " " << you.class_name << "."
<< std::endl;
// Activate markers only after the welcome message, so the
// player can see any resulting messages.
env.markers.activate_all();
// Call again to make Ctrl-X work correctly for items
// in los on turn 0.
maybe_update_stashes();
#ifdef USE_TILE
viewwindow(false);
#endif
if (game_start && you.char_class == JOB_WANDERER)
_wanderer_startup_message();
_god_greeting_message( game_start );
// Warn player about their weapon, if unsuitable.
wield_warning(false);
_prep_input();
if (game_start)
{
if (Tutorial.tutorial_left)
_startup_tutorial();
_take_starting_note();
}
else
tutorial_load_game();
// Catch up on any experience levels we did not assign last time. This
// can happen if Crawl sees SIGHUP while it is waiting for the player
// to dismiss a level-up prompt.
level_change();
cursor_control ccon(!Options.use_fake_player_cursor);
while (true)
_input();
clear_globals_on_exit();
return 0;
}
static void _show_commandline_options_help()
{
puts("Command line options:");
puts(" -help prints this list of options");
puts(" -name <string> character name");
puts(" -species <arg> preselect race (by letter, abbreviation, or name)");
puts(" -job <arg> preselect class (by letter, abbreviation, or name)");
puts(" -plain don't use IBM extended characters");
puts(" -dir <path> crawl directory");
puts(" -rc <file> init file name");
puts(" -rcdir <dir> directory that contains (included) rc files");
puts(" -morgue <dir> directory to save character dumps");
puts(" -macro <dir> directory to save/find macro.txt");
puts(" -version Crawl version (and compilation info)");
puts(" -save-version <name> Save file version for the given player");
puts("");
puts("Command line options override init file options, which override");
puts("environment options (CRAWL_NAME, CRAWL_DIR, CRAWL_RC).");
puts("");
puts(" -extra-opt-first optname=optval");
puts(" -extra-opt-last optname=optval");
puts("");
puts("Acts as if 'optname=optval' was at the top or bottom of the init");
puts("file. Can be used multiple times.");
puts("");
puts("Highscore list options: (Can be redirected to more, etc.)");
puts(" -scores [N] highscore list");
puts(" -tscores [N] terse highscore list");
puts(" -vscores [N] verbose highscore list");
puts(" -scorefile <filename> scorefile to report on");
puts("");
puts("Arena options: (Stage a tournament between various monsters.)");
puts(" -arena \"<monster list> v <monster list> arena:<arena map>\"");
#if DEBUG_DIAGNOSTICS
puts("");
puts(" -test run test cases in ./test");
puts(" -script <name> run script matching <name> in ./scripts");
#endif
}
static void _wanderer_startup_message()
{
int skill_levels = 0;
for (int i = 0; i < NUM_SKILLS; ++i)
skill_levels += you.skills[ i ];
if (skill_levels <= 2)
{
// Some wanderers stand to not be able to see any of their
// skills at the start of the game (one or two skills should be
// easily guessed from starting equipment). Anyway, we'll give
// the player a message to warn them (and a reason why). - bwr
mpr("You wake up in a daze, and can't recall much.");
}
}
static void _god_greeting_message(bool game_start)
{
switch (you.religion)
{
case GOD_ZIN:
simple_god_message(" says: Spread the light, my child.");
break;
case GOD_SHINING_ONE:
simple_god_message(" says: Lead the forces of light to victory!");
break;
case GOD_KIKUBAAQUDGHA:
simple_god_message(" says: Welcome...");
break;
case GOD_YREDELEMNUL:
simple_god_message(" says: Carry the black torch! Rouse the idle dead!");
break;
case GOD_NEMELEX_XOBEH:
simple_god_message(" says: It's all in the cards!");
break;
case GOD_XOM:
if (game_start)
simple_god_message(" says: A new plaything!");
break;
case GOD_VEHUMET:
simple_god_message(" says: Let it end in hellfire!");
break;
case GOD_OKAWARU:
simple_god_message(" says: Welcome, disciple.");
break;
case GOD_MAKHLEB:
god_speaks(you.religion, "Blood and souls for Makhleb!");
break;
case GOD_SIF_MUNA:
simple_god_message(" whispers: I know many secrets...");
break;
case GOD_TROG:
simple_god_message(" says: Kill them all!");
break;
case GOD_ELYVILON:
simple_god_message(" says: Go forth and aid the weak!");
break;
case GOD_LUGONU:
simple_god_message(" says: Spread carnage and corruption!");
break;
case GOD_BEOGH:
simple_god_message(" says: Drown the unbelievers in a sea of blood!");
break;
case GOD_JIYVA:
god_speaks(you.religion, "Slime for the Slime God!");
break;
case GOD_FEDHAS:
simple_god_message(" says: Spread life and death.");
break;
case GOD_CHEIBRIADOS:
simple_god_message(" says: Take it easy.");
break;
case GOD_NO_GOD:
case NUM_GODS:
case GOD_RANDOM:
case GOD_NAMELESS:
break;
}
}
static void _take_starting_note()
{
std::ostringstream notestr;
notestr << you.your_name << ", the "
<< species_name(you.species,you.experience_level) << " "
<< you.class_name
<< ", began the quest for the Orb.";
take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str()));
notestr.str("");
notestr.clear();
#ifdef WIZARD
if (you.wizard)
{
notestr << "You started the game in wizard mode.";
take_note(Note(NOTE_MESSAGE, 0, 0, notestr.str().c_str()));
notestr.str("");
notestr.clear();
}
#endif
notestr << "HP: " << you.hp << "/" << you.hp_max
<< " MP: " << you.magic_points << "/" << you.max_magic_points;
take_note(Note(NOTE_XP_LEVEL_CHANGE, you.experience_level, 0,
notestr.str().c_str()));
}
static void _startup_tutorial()
{
// Don't allow triggering at game start.
Tutorial.tut_just_triggered = true;
msg::streams(MSGCH_TUTORIAL)
<< "Press any key to start the tutorial intro, or Escape to skip it."
<< std::endl;
flush_prev_message();
const int ch = c_getch();
if (ch != ESCAPE)
tut_starting_screen();
}
#ifdef WIZARD
static void _do_wizard_command(int wiz_command, bool silent_fail)
{
ASSERT(you.wizard);
switch (wiz_command)
{
case '?':
{
const int key = list_wizard_commands(true);
_do_wizard_command(key, true);
return;
}
case CONTROL('B'): you.teleport(true, false, true); break;
case CONTROL('D'): wizard_edit_durations(); break;
case CONTROL('F'): debug_fight_statistics(false, true); break;
case CONTROL('G'): debug_ghosts(); break;
case CONTROL('H'): wizard_set_hunger_state(); break;
case CONTROL('I'): debug_item_statistics(); break;
case CONTROL('L'): wizard_set_xl(); break;
case CONTROL('T'): debug_terp_dlua(); break;
case CONTROL('X'): debug_xom_effects(); break;
case 'O': debug_test_explore(); break;
case 'S': wizard_set_skill_level(); break;
case 'A': wizard_set_all_skills(); break;
case 'a': acquirement(OBJ_RANDOM, AQ_WIZMODE); break;
case 'v': wizard_value_artefact(); break;
case '+': wizard_make_object_randart(); break;
case '|': wizard_create_all_artefacts(); break;
case 'C': wizard_uncurse_item(); break;
case 'g': wizard_exercise_skill(); break;
case 'G': wizard_dismiss_all_monsters(); break;
case 'c': wizard_draw_card(); break;
case 'H': wizard_heal(true); break;
case 'h': wizard_heal(false); break;
case 'b': blink(1000, true, true); break;
case '~': wizard_interlevel_travel(); break;
case '"': debug_list_monsters(); break;
case 't': wizard_tweak_object(); break;
case 'T': debug_make_trap(); break;
case '\\': debug_make_shop(); break;
case 'f': debug_fight_statistics(false); break;
case 'F': debug_fight_statistics(true); break;
case 'm': wizard_create_spec_monster(); break;
case 'M': wizard_create_spec_monster_name(); break;
case 'R': wizard_spawn_control(); break;
case 'r': wizard_change_species(); break;
case '>': wizard_place_stairs(true); break;
case '<': wizard_place_stairs(false); break;
case 'P': wizard_create_portal(); break;
case 'L': debug_place_map(); break;
case 'i': wizard_identify_pack(); break;
case 'I': wizard_unidentify_pack(); break;
case 'Z':
case 'z': wizard_cast_spec_spell(); break;
case '(':
case ')': wizard_create_feature(); break;
case ':': wizard_list_branches(); break;
case '{': wizard_map_level(); break;
case '}': wizard_reveal_traps(); break;
case '@': wizard_set_stats(); break;
case '^': wizard_gain_piety(); break;
case '_': wizard_get_religion(); break;
case '-': wizard_get_god_gift(); break;
case '\'': wizard_list_items(); break;
case 'd': wizard_level_travel(true); break;
case 'D': wizard_detect_creatures(); break;
case 'u': case 'U': wizard_level_travel(false); break;
case '%': case 'o': wizard_create_spec_object(); break;
case 'x':
you.experience = 1 + exp_needed( 2 + you.experience_level );
level_change();
break;
case 's':
you.exp_available = FULL_EXP_POOL;
you.redraw_experience = true;
break;
case '$':
you.add_gold(1000);
if (!Options.show_gold_turns)
{
mprf("You now have %d gold piece%s.",
you.gold, you.gold != 1 ? "s" : "");
}
break;
case 'B':
if (you.level_type != LEVEL_ABYSS)
banished(DNGN_ENTER_ABYSS, "wizard command");
else
down_stairs(you.your_level, DNGN_EXIT_ABYSS);
break;
case CONTROL('A'):
if (you.level_type == LEVEL_ABYSS)
abyss_teleport(true);
else
mpr("You can only abyss_teleport() inside the Abyss.");
break;
case ']':
if (!wizard_add_mutation())
mpr( "Failure to give mutation." );
break;
case '=':
mprf( "Cost level: %d Skill points: %d Next cost level: %d",
you.skill_cost_level, you.total_skill_points,
skill_cost_needed( you.skill_cost_level + 1 ) );
break;
case 'X':
{
int result = 0;
do
{
if (you.religion == GOD_XOM)
result = xom_acts(abs(you.piety - HALF_MAX_PIETY));
else
result = xom_acts(coinflip(), random_range(0, HALF_MAX_PIETY));
}
while (result == 0);
break;
}
case 'p':
dungeon_terrain_changed(you.pos(), DNGN_ENTER_PANDEMONIUM, false);
break;
case 'l':
dungeon_terrain_changed(you.pos(), DNGN_ENTER_LABYRINTH, false);
break;
case 'k':
if (you.level_type == LEVEL_LABYRINTH)
change_labyrinth(true);
else
mpr("This only makes sense in a labyrinth!");
break;
default:
if (!silent_fail)
{
formatted_mpr(formatted_string::parse_string(
"Not a <magenta>Wizard</magenta> Command."));
}
break;
}
// Force the placement of any delayed monster gifts.
you.turn_is_over = true;
religion_turn_end();
you.turn_is_over = false;
}
static void _handle_wizard_command( void )
{
int wiz_command;
// WIZ_NEVER gives protection for those who have wiz compiles,
// and don't want to risk their characters.
if (Options.wiz_mode == WIZ_NEVER)
return;
if (!you.wizard)
{
mpr("WARNING: ABOUT TO ENTER WIZARD MODE!", MSGCH_WARN);
#ifndef SCORE_WIZARD_MODE
mpr("If you continue, your game will not be scored!", MSGCH_WARN);
#endif
if (!yesno( "Do you really want to enter wizard mode?", false, 'n' ))
return;
take_note(Note(NOTE_MESSAGE, 0, 0, "Entered wizard mode."));
you.wizard = true;
redraw_screen();
if (crawl_state.cmd_repeat_start)
{
crawl_state.cancel_cmd_repeat("Can't repeat entering wizard "
"mode.");
return;
}
}
{
mpr("Enter Wizard Command (? - help): ", MSGCH_PROMPT);
cursor_control con(true);
wiz_command = getch();
}
if (crawl_state.cmd_repeat_start)
{
// Easiest to list which wizard commands *can* be repeated.
switch (wiz_command)
{
case 'x':
case '$':
case 'a':
case 'c':
case 'h':
case 'H':
case 'm':
case 'M':
case 'X':
case '!':
case '[':
case ']':
case '^':
case '%':
case 'o':
case 'z':
case 'Z':
break;
default:
crawl_state.cant_cmd_repeat("You cannot repeat that "
"wizard command.");
return;
}
}
_do_wizard_command(wiz_command, false);
}
#endif
// Set up the running variables for the current run.
static void _start_running( int dir, int mode )
{
if (Tutorial.tutorial_events[TUT_SHIFT_RUN] && mode == RMODE_START)
Tutorial.tutorial_events[TUT_SHIFT_RUN] = false;
if (i_feel_safe(true))
you.running.initialise(dir, mode);
}
static bool _cmd_is_repeatable(command_type cmd, bool is_again = false)
{
switch (cmd)
{
// Informational commands
case CMD_LOOK_AROUND:
case CMD_INSPECT_FLOOR:
case CMD_EXAMINE_OBJECT:
case CMD_LIST_WEAPONS:
case CMD_LIST_ARMOUR:
case CMD_LIST_JEWELLERY:
case CMD_LIST_EQUIPMENT:
case CMD_LIST_GOLD:
case CMD_CHARACTER_DUMP:
case CMD_DISPLAY_COMMANDS:
case CMD_DISPLAY_INVENTORY:
case CMD_DISPLAY_KNOWN_OBJECTS:
case CMD_DISPLAY_MUTATIONS:
case CMD_DISPLAY_SKILLS:
case CMD_DISPLAY_OVERMAP:
case CMD_DISPLAY_RELIGION:
case CMD_DISPLAY_CHARACTER_STATUS:
case CMD_DISPLAY_SPELLS:
case CMD_EXPERIENCE_CHECK:
case CMD_RESISTS_SCREEN:
case CMD_READ_MESSAGES:
case CMD_SEARCH_STASHES:
mpr("You can't repeat informational commands.");
return (false);
// Multi-turn commands
case CMD_PICKUP:
case CMD_DROP:
case CMD_BUTCHER:
case CMD_GO_UPSTAIRS:
case CMD_GO_DOWNSTAIRS:
case CMD_WIELD_WEAPON:
case CMD_WEAPON_SWAP:
case CMD_WEAR_JEWELLERY:
case CMD_REMOVE_JEWELLERY:
case CMD_MEMORISE_SPELL:
case CMD_EXPLORE:
case CMD_INTERLEVEL_TRAVEL:
mpr("You can't repeat multi-turn commands.");
return (false);
// Miscellaneous non-repeatable commands.
case CMD_TOGGLE_AUTOPICKUP:
case CMD_TOGGLE_FRIENDLY_PICKUP:
case CMD_ADJUST_INVENTORY:
case CMD_QUIVER_ITEM:
case CMD_REPLAY_MESSAGES:
case CMD_REDRAW_SCREEN:
case CMD_MACRO_ADD:
case CMD_SAVE_GAME:
case CMD_SAVE_GAME_NOW:
case CMD_SUSPEND_GAME:
case CMD_QUIT:
case CMD_DESTROY_ITEM:
case CMD_FORGET_STASH:
case CMD_FIX_WAYPOINT:
case CMD_CLEAR_MAP:
case CMD_INSCRIBE_ITEM:
case CMD_MAKE_NOTE:
case CMD_CYCLE_QUIVER_FORWARD:
#ifdef USE_TILE
case CMD_EDIT_PLAYER_TILE:
#endif
mpr("You can't repeat that command.");
return (false);
case CMD_DISPLAY_MAP:
mpr("You can't repeat map commands.");
return (false);
case CMD_MOUSE_MOVE:
case CMD_MOUSE_CLICK:
mpr("You can't repeat mouse clicks or movements.");
return (false);
case CMD_REPEAT_CMD:
mpr("You can't repeat the repeat command!");
return (false);
case CMD_RUN_LEFT:
case CMD_RUN_DOWN:
case CMD_RUN_UP:
case CMD_RUN_RIGHT:
case CMD_RUN_UP_LEFT:
case CMD_RUN_DOWN_LEFT:
case CMD_RUN_UP_RIGHT:
case CMD_RUN_DOWN_RIGHT:
mpr("Why would you want to repeat a run command?");
return (false);
case CMD_PREV_CMD_AGAIN:
ASSERT(!is_again);
if (crawl_state.prev_cmd == CMD_NO_CMD)
{
mpr("No previous command to repeat.");
return (false);
}
return _cmd_is_repeatable(crawl_state.prev_cmd, true);
case CMD_MOVE_NOWHERE:
case CMD_REST:
case CMD_SEARCH:
return (i_feel_safe(true));
case CMD_MOVE_LEFT:
case CMD_MOVE_DOWN:
case CMD_MOVE_UP:
case CMD_MOVE_RIGHT:
case CMD_MOVE_UP_LEFT:
case CMD_MOVE_DOWN_LEFT:
case CMD_MOVE_UP_RIGHT:
case CMD_MOVE_DOWN_RIGHT:
if (!i_feel_safe())
{
return yesno("Really repeat movement command while monsters "
"are nearby?", false, 'n');
}
return (true);
case CMD_NO_CMD:
case CMD_NO_CMD_DEFAULT:
mpr("Unknown command, not repeating.");
return (false);
default:
return (true);
}
return (false);
}
// Used to determine whether to apply the berserk penalty at end of round.
bool apply_berserk_penalty = false;
static void _center_cursor()
{
#ifndef USE_TILE
const coord_def cwhere = grid2view(you.pos());
cgotoxy(cwhere.x, cwhere.y);
#endif
}
//
// This function handles the player's input. It's called from main(),
// from inside an endless loop.
//
static void _input()
{
#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
if (crawl_state.seen_hups)
sighup_save_and_exit();
#endif
crawl_state.clear_mon_acting();
religion_turn_start();
you.update_beholders();
// Currently only set if Xom accidentally kills the player.
you.reset_escaped_death();
flush_prev_message();
if (crawl_state.is_replaying_keys() && crawl_state.is_repeating_cmd()
&& kbhit())
{
// User pressed a key, so stop repeating commands and discard
// the keypress.
crawl_state.cancel_cmd_repeat("Key pressed, interrupting command "
"repetition.");
crawl_state.prev_cmd = CMD_NO_CMD;
getchm();
return;
}
update_monsters_in_view();
you.turn_is_over = false;
_prep_input();
const bool player_feels_safe = i_feel_safe();
if (Tutorial.tutorial_left)
{
Tutorial.tut_just_triggered = false;
if (you.attribute[ATTR_HELD])
learned_something_new(TUT_CAUGHT_IN_NET);
else if (player_feels_safe && you.level_type != LEVEL_ABYSS)
{
// We don't want those "Whew, it's safe to rest now" messages
// if you were just cast into the Abyss. Right?
if (2 * you.hp < you.hp_max
|| 2 * you.magic_points < you.max_magic_points)
{
tutorial_healing_reminder();
}
else if (!you.running
&& Tutorial.tutorial_events[TUT_SHIFT_RUN]
&& you.num_turns >= 200
&& you.hp == you.hp_max
&& you.magic_points == you.max_magic_points)
{
learned_something_new(TUT_SHIFT_RUN);
}
else if (!you.running
&& Tutorial.tutorial_events[TUT_MAP_VIEW]
&& you.num_turns >= 500
&& you.hp == you.hp_max
&& you.magic_points == you.max_magic_points)
{
learned_something_new(TUT_MAP_VIEW);
}
else if (!you.running
&& Tutorial.tutorial_events[TUT_AUTO_EXPLORE]
&& you.num_turns >= 700
&& you.hp == you.hp_max
&& you.magic_points == you.max_magic_points)
{
learned_something_new(TUT_AUTO_EXPLORE);
}
}
else
{
if (2*you.hp < you.hp_max)
learned_something_new(TUT_RUN_AWAY);
if (Tutorial.tutorial_type == TUT_MAGIC_CHAR && you.magic_points < 1)
learned_something_new(TUT_RETREAT_CASTER);
}
}
if (you.cannot_act())
{
if (crawl_state.repeat_cmd != CMD_WIZARD)
{
crawl_state.cancel_cmd_repeat("Cannot move, cancelling command "
"repetition.");
}
world_reacts();
return;
}
// Stop autoclearing more now that we have control back.
if (!you_are_delayed())
set_more_autoclear(false);
if (need_to_autopickup())
autopickup();
if (need_to_autoinscribe())
autoinscribe();
handle_delay();
if (you_are_delayed() && current_delay_action() != DELAY_MACRO_PROCESS_KEY)
{
if (you.time_taken)
world_reacts();
return;
}
if (you.turn_is_over)
{
world_reacts();
return;
}
crawl_state.check_term_size();
if (crawl_state.terminal_resized)
handle_terminal_resize();
repeat_again_rec.paused = (crawl_state.is_replaying_keys()
&& !crawl_state.cmd_repeat_start);
crawl_state.input_line_curr = 0;
{
flush_prev_message();
clear_macro_process_key_delay();
crawl_state.waiting_for_command = true;
c_input_reset(true);
_center_cursor();
#ifdef USE_TILE
cursor_control con(false);
#endif
const command_type cmd = _get_next_cmd();
#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
if (crawl_state.seen_hups)
sighup_save_and_exit();
#endif
crawl_state.waiting_for_command = false;
if (cmd != CMD_PREV_CMD_AGAIN && cmd != CMD_REPEAT_CMD
&& cmd != CMD_NO_CMD
&& !crawl_state.is_replaying_keys())
{
crawl_state.prev_cmd = cmd;
crawl_state.input_line_strs.clear();
}
if (cmd != CMD_MOUSE_MOVE)
c_input_reset(false);
// [dshaligram] If get_next_cmd encountered a Lua macro
// binding, your turn may be ended by the first invoke of the
// macro.
if (!you.turn_is_over && cmd != CMD_NEXT_CMD)
process_command( cmd );
repeat_again_rec.paused = true;
if (cmd != CMD_MOUSE_MOVE)
c_input_reset(false, true);
// If the command was CMD_REPEAT_CMD, then the key for the
// command to repeat has been placed into the macro buffer,
// so return now to let input() be called again while
// the keys to repeat are recorded.
if (cmd == CMD_REPEAT_CMD)
return;
// If the command was CMD_PREV_CMD_AGAIN then _input() has been
// recursively called by _do_prev_cmd_again() via process_command()
// to re-do the command, so there's nothing more to do.
if (cmd == CMD_PREV_CMD_AGAIN)
return;
}
if (need_to_autoinscribe())
autoinscribe();
if (you.turn_is_over)
{
if (apply_berserk_penalty)
_do_berserk_no_combat_penalty();
world_reacts();
}
else
viewwindow(false);
_update_replay_state();
if (you.num_turns != -1)
{
PlaceInfo& curr_PlaceInfo = you.get_place_info();
PlaceInfo delta;
delta.turns_total++;
delta.elapsed_total += you.time_taken;
switch (you.running)
{
case RMODE_INTERLEVEL:
delta.turns_interlevel++;
delta.elapsed_interlevel += you.time_taken;
break;
case RMODE_EXPLORE_GREEDY:
case RMODE_EXPLORE:
delta.turns_explore++;
delta.elapsed_explore += you.time_taken;
break;
case RMODE_TRAVEL:
delta.turns_travel++;
delta.elapsed_travel += you.time_taken;
break;
default:
// prev_was_rest is needed so that the turn in which
// a player is interrupted from resting is counted
// as a resting turn, rather than "other".
static bool prev_was_rest = false;
if (!you.delay_queue.empty()
&& you.delay_queue.front().type == DELAY_REST)
{
prev_was_rest = true;
}
if (prev_was_rest)
{
delta.turns_resting++;
delta.elapsed_resting += you.time_taken;
}
else
{
delta.turns_other++;
delta.elapsed_other += you.time_taken;
}
if (you.delay_queue.empty()
|| you.delay_queue.front().type != DELAY_REST)
{
prev_was_rest = false;
}
break;
}
you.global_info += delta;
you.global_info.assert_validity();
curr_PlaceInfo += delta;
curr_PlaceInfo.assert_validity();
}
crawl_state.clear_god_acting();
}
static bool _stairs_check_mesmerised()
{
if (you.beheld() && !you.confused())
{
const monsters* beholder = you.get_any_beholder();
mprf("You cannot move away from %s!",
beholder->name(DESC_NOCAP_THE, true).c_str());
return (true);
}
return (false);
}
static bool _marker_vetoes_stair()
{
return marker_vetoes_operation("veto_stair");
}
// Maybe prompt to enter a portal, return true if we should enter the
// portal, false if the user said no at the prompt.
static bool _prompt_dangerous_portal(dungeon_feature_type ftype)
{
switch(ftype)
{
case DNGN_ENTER_PANDEMONIUM:
case DNGN_ENTER_ABYSS:
return yesno("If you enter this portal you will not be able to return "
"immediately. Continue?", false, 'n');
default:
return (true);
}
}
static void _go_downstairs();
static void _go_upstairs()
{
ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
const dungeon_feature_type ygrd = grd(you.pos());
if (_stairs_check_mesmerised())
return;
if (you.attribute[ATTR_HELD])
{
mpr("You're held in a net!");
return;
}
if (ygrd == DNGN_ENTER_SHOP)
{
if (you.berserk())
canned_msg(MSG_TOO_BERSERK);
else
shop();
return;
}
else if (ygrd == DNGN_ENTER_HELL && you.level_type != LEVEL_DUNGEON)
{
mpr("You can't enter Hell from outside the dungeon!",
MSGCH_ERROR);
return;
}
// Up and down both work for portals.
else if (get_feature_dchar(ygrd) == DCHAR_ARCH
&& feat_stair_direction(ygrd) != CMD_NO_CMD
&& ygrd != DNGN_ENTER_ZOT)
{
;
}
else if (feat_stair_direction(ygrd) != CMD_GO_UPSTAIRS)
{
if (ygrd == DNGN_STONE_ARCH)
mpr("There is nothing on the other side of the stone arch.");
else if (ygrd == DNGN_ABANDONED_SHOP)
mpr("This shop appears to be closed.");
else
mpr("You can't go up here!");
return;
}
if (!_prompt_dangerous_portal(ygrd))
return;
// Does the next level have a warning annotation?
if (!check_annotation_exclusion_warning())
return;
if (_marker_vetoes_stair())
return;
if (you.duration[DUR_MISLED])
{
mpr("Away from their source, illusions no longer mislead you.", MSGCH_DURATION);
you.duration[DUR_MISLED] = 0;
}
tag_followers(); // Only those beside us right now can follow.
start_delay(DELAY_ASCENDING_STAIRS,
1 + (you.burden_state > BS_UNENCUMBERED));
}
static void _go_downstairs()
{
ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
const dungeon_feature_type ygrd = grd(you.pos());
const bool shaft = (get_trap_type(you.pos()) == TRAP_SHAFT
&& ygrd != DNGN_UNDISCOVERED_TRAP);
if (_stairs_check_mesmerised())
return;
if (shaft && you.flight_mode() == FL_LEVITATE)
{
mpr("You can't fall through a shaft while levitating.");
return;
}
// Up and down both work for shops.
if (ygrd == DNGN_ENTER_SHOP)
{
if (you.berserk())
canned_msg(MSG_TOO_BERSERK);
else
shop();
return;
}
else if (ygrd == DNGN_ENTER_HELL && you.level_type != LEVEL_DUNGEON)
{
mpr("You can't enter Hell from outside the dungeon!",
MSGCH_ERROR);
return;
}
// Up and down both work for portals.
else if (get_feature_dchar(ygrd) == DCHAR_ARCH
&& feat_stair_direction(ygrd) != CMD_NO_CMD
&& ygrd != DNGN_ENTER_ZOT)
{
;
}
else if (feat_stair_direction(ygrd) != CMD_GO_DOWNSTAIRS
&& !shaft)
{
if (ygrd == DNGN_STONE_ARCH)
mpr("There is nothing on the other side of the stone arch.");
else if (ygrd == DNGN_ABANDONED_SHOP)
mpr("This shop appears to be closed.");
else
mpr( "You can't go down here!" );
return;
}
if (you.attribute[ATTR_HELD])
{
mpr("You're held in a net!");
return;
}
if (!_prompt_dangerous_portal(ygrd))
return;
// Does the next level have a warning annotation?
// Also checks for entering a labyrinth with teleportitis.
if (!check_annotation_exclusion_warning())
return;
if (you.duration[DUR_MISLED])
{
mpr("Away from their source, illusions no longer mislead you.", MSGCH_DURATION);
you.duration[DUR_MISLED] = 0;
}
if (shaft)
{
start_delay( DELAY_DESCENDING_STAIRS, 0, you.your_level );
}
else
{
if (_marker_vetoes_stair())
return;
tag_followers(); // Only those beside us right now can follow.
start_delay(DELAY_DESCENDING_STAIRS,
1 + (you.burden_state > BS_UNENCUMBERED),
you.your_level);
}
}
static void _experience_check()
{
mprf("You are a level %d %s %s.",
you.experience_level,
species_name(you.species,you.experience_level).c_str(),
you.class_name);
if (you.experience_level < 27)
{
int xp_needed = (exp_needed(you.experience_level+2)-you.experience)+1;
mprf( "Level %d requires %ld experience (%d point%s to go!)",
you.experience_level + 1,
exp_needed(you.experience_level + 2) + 1,
xp_needed,
(xp_needed > 1) ? "s" : "");
}
else
{
mpr( "I'm sorry, level 27 is as high as you can go." );
mpr( "With the way you've been playing, I'm surprised you got this far." );
}
if (you.real_time != -1)
{
const time_t curr = you.real_time + (time(NULL) - you.start_time);
msg::stream << "Play time: " << make_time_string(curr)
<< " (" << you.num_turns << " turns)"
<< std::endl;
}
#ifdef DEBUG_DIAGNOSTICS
if (wearing_amulet(AMU_THE_GOURMAND))
mprf(MSGCH_DIAGNOSTICS, "Gourmand charge: %d",
you.duration[DUR_GOURMAND]);
mprf(MSGCH_DIAGNOSTICS, "Turns spent on this level: %d",
env.turns_on_level);
#endif
}
static void _print_friendly_pickup_setting(bool was_changed)
{
std::string now = (was_changed? "now " : "");
if (you.friendly_pickup == FRIENDLY_PICKUP_NONE)
{
mprf("Your intelligent allies are %sforbidden to pick up anything at all.",
now.c_str());
}
else if (you.friendly_pickup == FRIENDLY_PICKUP_FRIEND)
{
mprf("Your intelligent allies may %sonly pick up items dropped by allies.",
now.c_str());
}
else if (you.friendly_pickup == FRIENDLY_PICKUP_PLAYER)
{
mprf("Your intelligent allies may %sonly pick up items dropped by you "
"and your allies.", now.c_str());
}
else if (you.friendly_pickup == FRIENDLY_PICKUP_ALL)
{
mprf("Your intelligent allies may %spick up anything they need.",
now.c_str());
}
else
mprf(MSGCH_ERROR, "Your allies%s are collecting bugs!", now.c_str());
}
// Note that in some actions, you don't want to clear afterwards.
// e.g. list_jewellery, etc.
void process_command( command_type cmd )
{
apply_berserk_penalty = true;
switch (cmd)
{
#ifdef USE_TILE
case CMD_EDIT_PLAYER_TILE:
tiles.draw_doll_edit();
break;
case CMD_TOGGLE_SPELL_DISPLAY:
if (Options.tile_display == TDSP_SPELLS)
Options.tile_display = TDSP_INVENT;
else
Options.tile_display = TDSP_SPELLS;
tiles.update_inventory();
break;
#endif
case CMD_OPEN_DOOR_UP_RIGHT: _open_door( 1, -1); break;
case CMD_OPEN_DOOR_UP: _open_door( 0, -1); break;
case CMD_OPEN_DOOR_UP_LEFT: _open_door(-1, -1); break;
case CMD_OPEN_DOOR_RIGHT: _open_door( 1, 0); break;
case CMD_OPEN_DOOR_DOWN_RIGHT: _open_door( 1, 1); break;
case CMD_OPEN_DOOR_DOWN: _open_door( 0, 1); break;
case CMD_OPEN_DOOR_DOWN_LEFT: _open_door(-1, 1); break;
case CMD_OPEN_DOOR_LEFT: _open_door(-1, 0); break;
case CMD_MOVE_DOWN_LEFT: _move_player(-1, 1); break;
case CMD_MOVE_DOWN: _move_player( 0, 1); break;
case CMD_MOVE_UP_RIGHT: _move_player( 1, -1); break;
case CMD_MOVE_UP: _move_player( 0, -1); break;
case CMD_MOVE_UP_LEFT: _move_player(-1, -1); break;
case CMD_MOVE_LEFT: _move_player(-1, 0); break;
case CMD_MOVE_DOWN_RIGHT: _move_player( 1, 1); break;
case CMD_MOVE_RIGHT: _move_player( 1, 0); break;
case CMD_REST:
if (you.hunger_state == HS_STARVING && !you_min_hunger())
{
mpr("You are too hungry to rest.");
break;
}
if (i_feel_safe())
{
if ((you.hp == you.hp_max || you.species == SP_VAMPIRE
&& you.hunger_state == HS_STARVING)
&& you.magic_points == you.max_magic_points )
{
mpr("You start searching.");
}
else
mpr("You start resting.");
}
_start_running( RDIR_REST, RMODE_REST_DURATION );
break;
case CMD_RUN_DOWN_LEFT:
_start_running( RDIR_DOWN_LEFT, RMODE_START );
break;
case CMD_RUN_DOWN:
_start_running( RDIR_DOWN, RMODE_START );
break;
case CMD_RUN_UP_RIGHT:
_start_running( RDIR_UP_RIGHT, RMODE_START );
break;
case CMD_RUN_UP:
_start_running( RDIR_UP, RMODE_START );
break;
case CMD_RUN_UP_LEFT:
_start_running( RDIR_UP_LEFT, RMODE_START );
break;
case CMD_RUN_LEFT:
_start_running( RDIR_LEFT, RMODE_START );
break;
case CMD_RUN_DOWN_RIGHT:
_start_running( RDIR_DOWN_RIGHT, RMODE_START );
break;
case CMD_RUN_RIGHT:
_start_running( RDIR_RIGHT, RMODE_START );
break;
case CMD_DISABLE_MORE:
Options.show_more_prompt = false;
break;
case CMD_ENABLE_MORE:
Options.show_more_prompt = true;
break;
case CMD_REPEAT_KEYS:
ASSERT(crawl_state.is_repeating_cmd());
if (crawl_state.prev_repetition_turn == you.num_turns
&& crawl_state.repeat_cmd != CMD_WIZARD
&& (crawl_state.repeat_cmd != CMD_PREV_CMD_AGAIN
|| crawl_state.prev_cmd != CMD_WIZARD))
{
// This is a catch-all that shouldn't really happen.
// If the command always takes zero turns, then it
// should be prevented in cmd_is_repeatable(). If
// a command sometimes takes zero turns (because it
// can't be done, for instance), then
// crawl_state.zero_turns_taken() should be called when
// it does take zero turns, to cancel command repetition
// before we reach here.
#ifdef WIZARD
crawl_state.cant_cmd_repeat("Can't repeat a command which "
"takes no turns (unless it's a "
"wizard command), cancelling ");
#else
crawl_state.cant_cmd_repeat("Can't repeat a command which "
"takes no turns, cancelling "
"repetitions.");
#endif
crawl_state.cancel_cmd_repeat();
return;
}
crawl_state.cmd_repeat_count++;
if (crawl_state.cmd_repeat_count >= crawl_state.cmd_repeat_goal)
{
crawl_state.cancel_cmd_repeat();
return;
}
ASSERT(crawl_state.repeat_cmd_keys.size() > 0);
repeat_again_rec.paused = true;
macro_buf_add(crawl_state.repeat_cmd_keys);
macro_buf_add(KEY_REPEAT_KEYS);
crawl_state.prev_repetition_turn = you.num_turns;
break;
case CMD_TOGGLE_AUTOPICKUP:
if (Options.autopickup_on < 1)
Options.autopickup_on = 1;
else
Options.autopickup_on = 0;
mprf("Autopickup is now %s.", Options.autopickup_on > 0 ? "on" : "off");
break;
case CMD_TOGGLE_FRIENDLY_PICKUP:
{
// Toggle pickup mode for friendlies.
_print_friendly_pickup_setting(false);
mpr("Change to (d)efault, (n)othing, (f)riend-dropped, (p)layer, "
"or (a)ll? ", MSGCH_PROMPT);
char type;
{
cursor_control con(true);
type = (char) getchm(KMC_DEFAULT);
}
type = tolower(type);
if (type == 'd')
you.friendly_pickup = Options.default_friendly_pickup;
else if (type == 'n')
you.friendly_pickup = FRIENDLY_PICKUP_NONE;
else if (type == 'f')
you.friendly_pickup = FRIENDLY_PICKUP_FRIEND;
else if (type == 'p')
you.friendly_pickup = FRIENDLY_PICKUP_PLAYER;
else if (type == 'a')
you.friendly_pickup = FRIENDLY_PICKUP_ALL;
else
{
canned_msg( MSG_OK );
break;
}
_print_friendly_pickup_setting(true);
break;
}
case CMD_MAKE_NOTE:
make_user_note();
break;
case CMD_READ_MESSAGES:
#ifdef DGL_SIMPLE_MESSAGING
if (SysEnv.have_messages)
_read_messages();
#endif
break;
case CMD_CLEAR_MAP:
if (player_in_mappable_area())
{
mpr("Clearing level map.");
clear_map();
crawl_view.set_player_at(you.pos());
}
break;
case CMD_DISPLAY_OVERMAP: display_overmap(); break;
case CMD_GO_UPSTAIRS: _go_upstairs(); break;
case CMD_GO_DOWNSTAIRS: _go_downstairs(); break;
case CMD_OPEN_DOOR: _open_door(0, 0); break;
case CMD_CLOSE_DOOR: _close_door(coord_def(0, 0)); break;
case CMD_DROP:
drop();
if (Options.stash_tracking >= STM_DROPPED)
StashTrack.add_stash();
break;
case CMD_SEARCH_STASHES:
if (Tutorial.tut_stashes)
Tutorial.tut_stashes = 0;
StashTrack.search_stashes();
break;
case CMD_FORGET_STASH:
if (Options.stash_tracking >= STM_EXPLICIT)
StashTrack.no_stash();
break;
case CMD_BUTCHER:
butchery();
break;
case CMD_DISPLAY_INVENTORY:
get_invent(OSEL_ANY);
break;
case CMD_EVOKE:
if (!evoke_item())
flush_input_buffer( FLUSH_ON_FAILURE );
break;
case CMD_EVOKE_WIELDED:
if (!evoke_item(you.equip[EQ_WEAPON]))
flush_input_buffer( FLUSH_ON_FAILURE );
break;
case CMD_PICKUP:
pickup();
break;
case CMD_INSPECT_FLOOR:
request_autopickup();
break;
case CMD_FULL_VIEW:
full_describe_view();
break;
case CMD_WIELD_WEAPON:
wield_weapon(false);
break;
case CMD_FIRE:
fire_thing();
break;
case CMD_QUIVER_ITEM:
choose_item_for_quiver();
break;
case CMD_THROW_ITEM_NO_QUIVER:
throw_item_no_quiver();
break;
case CMD_WEAR_ARMOUR:
wear_armour();
break;
case CMD_REMOVE_ARMOUR:
{
if (player_in_bat_form())
{
mpr("You can't wear or remove anything in your present form.");
break;
}
int index = 0;
if (armour_prompt("Take off which item?", &index, OPER_TAKEOFF))
takeoff_armour(index);
}
break;
case CMD_REMOVE_JEWELLERY:
remove_ring();
break;
case CMD_WEAR_JEWELLERY:
puton_ring(-1);
break;
case CMD_ADJUST_INVENTORY:
adjust();
break;
case CMD_MEMORISE_SPELL:
if (!learn_spell())
flush_input_buffer( FLUSH_ON_FAILURE );
break;
case CMD_ZAP_WAND:
zap_wand();
break;
case CMD_EAT:
eat_food();
break;
case CMD_USE_ABILITY:
if (!activate_ability())
flush_input_buffer( FLUSH_ON_FAILURE );
break;
case CMD_DISPLAY_MUTATIONS:
display_mutations();
redraw_screen();
break;
case CMD_EXAMINE_OBJECT:
examine_object();
break;
case CMD_PRAY:
pray();
break;
case CMD_DISPLAY_RELIGION:
describe_god( you.religion, true );
redraw_screen();
break;
case CMD_MOVE_NOWHERE:
case CMD_SEARCH:
search_around();
you.turn_is_over = true;
break;
case CMD_QUAFF:
drink();
break;
case CMD_READ:
read_scroll();
break;
case CMD_LOOK_AROUND:
{
mpr("Move the cursor around to observe a square "
"(v - describe square, ? - help)", MSGCH_PROMPT);
struct dist lmove; // Will be initialised by direction().
direction(lmove, DIR_TARGET, TARG_ANY, -1, true, false);
if (lmove.isValid && lmove.isTarget && !lmove.isCancel
&& !crawl_state.arena_suspended)
{
start_travel( lmove.target );
}
break;
}
case CMD_CAST_SPELL:
case CMD_FORCE_CAST_SPELL:
if (player_in_bat_form()
|| you.attribute[ATTR_TRANSFORMATION] == TRAN_PIG)
{
canned_msg(MSG_PRESENT_FORM);
break;
}
// Randart weapons.
if (scan_artefacts(ARTP_PREVENT_SPELLCASTING))
{
mpr("Something interferes with your magic!");
flush_input_buffer( FLUSH_ON_FAILURE );
break;
}
if (Tutorial.tutorial_left)
Tutorial.tut_spell_counter++;
if (!cast_a_spell(cmd == CMD_CAST_SPELL))
flush_input_buffer( FLUSH_ON_FAILURE );
break;
case CMD_DISPLAY_SPELLS:
inspect_spells();
break;
case CMD_WEAPON_SWAP:
wield_weapon(true);
break;
// [ds] Waypoints can be added from the level-map, and we need
// Ctrl+F for nobler things. Who uses waypoints, anyway?
// Update: Appears people do use waypoints. Reinstating, on
// CONTROL('W'). This means Ctrl+W is no longer a wizmode
// trigger, but there's always '&'. :-)
case CMD_FIX_WAYPOINT:
travel_cache.add_waypoint();
break;
case CMD_INTERLEVEL_TRAVEL:
if (Tutorial.tut_travel)
Tutorial.tut_travel = 0;
if (!can_travel_interlevel())
{
if (you.running.pos == you.pos())
{
mpr("You're already here.");
break;
}
else if (!you.running.pos.x || !you.running.pos.y)
{
mpr("Sorry, you can't auto-travel out of here.");
break;
}
// Don't ask for a destination if you can only travel
// within level anyway.
start_travel(you.running.pos);
}
else
start_translevel_travel_prompt();
if (you.running)
mesclr();
break;
case CMD_ANNOTATE_LEVEL:
annotate_level();
break;
case CMD_EXPLORE:
if (you.hunger_state == HS_STARVING && !you_min_hunger())
{
mpr("You need to eat something NOW!");
break;
}
else if (you.level_type == LEVEL_LABYRINTH)
{
mpr("No exploration algorithm can help you here.");
break;
}
// Start exploring
start_explore(Options.explore_greedy);
break;
case CMD_DISPLAY_MAP:
if (Tutorial.tutorial_events[TUT_MAP_VIEW])
Tutorial.tutorial_events[TUT_MAP_VIEW] = false;
#if (!DEBUG_DIAGNOSTICS)
if (!player_in_mappable_area())
{
mpr("It would help if you knew where you were, first.");
break;
}
#endif
{
level_pos pos;
#ifdef USE_TILE
// Since there's no actual overview map, but the functionality
// exists, give a message to explain what's going on.
std::string str = "Move the cursor to view the level map, or "
"type <w>?</w> for a list of commands.";
print_formatted_paragraph(str);
#endif
show_map(pos, true);
redraw_screen();
#ifdef USE_TILE
mpr("Returning to the game...");
#endif
if (pos.pos.x > 0)
start_translevel_travel(pos);
}
break;
case CMD_DISPLAY_KNOWN_OBJECTS:
check_item_knowledge();
break;
case CMD_REPLAY_MESSAGES:
replay_messages();
redraw_screen();
break;
case CMD_REDRAW_SCREEN:
redraw_screen();
break;
case CMD_SAVE_GAME_NOW:
mpr("Saving game... please wait.");
save_game(true);
break;
#ifdef USE_UNIX_SIGNALS
case CMD_SUSPEND_GAME:
// CTRL-Z suspend behaviour is implemented here,
// because we want to have CTRL-Y available...
// and unfortunately they tend to be stuck together.
clrscr();
#ifndef USE_TILE
unixcurses_shutdown();
kill(0, SIGTSTP);
unixcurses_startup();
#endif
redraw_screen();
break;
#endif
case CMD_DISPLAY_COMMANDS:
list_commands(0, true);
break;
case CMD_EXPERIENCE_CHECK:
_experience_check();
break;
case CMD_SHOUT:
yell();
break;
case CMD_MOUSE_MOVE:
{
const coord_def dest = view2grid(crawl_view.mousep);
if (in_bounds(dest))
terse_describe_square(dest);
break;
}
case CMD_MOUSE_CLICK:
{
// XXX: We should probably use specific commands such as
// CMD_MOUSE_TRAVEL and get rid of CMD_MOUSE_CLICK and
// CMD_MOUSE_MOVE.
c_mouse_event cme = get_mouse_event();
if (cme && crawl_view.in_view_viewport(cme.pos))
{
const coord_def dest = view2grid(cme.pos);
if (cme.left_clicked())
{
if (in_bounds(dest))
start_travel(dest);
}
else if (cme.right_clicked())
{
if (you.see_cell(dest))
full_describe_square(dest);
else
mpr("You can't see that place.");
}
}
break;
}
case CMD_DISPLAY_CHARACTER_STATUS:
display_char_status();
break;
case CMD_RESISTS_SCREEN:
print_overview_screen();
break;
case CMD_DISPLAY_SKILLS:
show_skills();
redraw_screen();
break;
case CMD_CHARACTER_DUMP:
if (dump_char(you.your_name, false))
mpr("Char dumped successfully.");
else
mpr("Char dump unsuccessful! Sorry about that.");
break;
case CMD_MACRO_ADD:
macro_add_query();
break;
case CMD_CYCLE_QUIVER_FORWARD:
case CMD_CYCLE_QUIVER_BACKWARD:
{
int dir = (cmd == CMD_CYCLE_QUIVER_FORWARD ? +1 : -1);
int cur = you.m_quiver->get_fire_item();
const int next = get_next_fire_item(cur, dir);
#ifdef DEBUG_QUIVER
mprf(MSGCH_DIAGNOSTICS, "next slot: %d, item: %s", next,
next == -1 ? "none" : you.inv[next].name(DESC_PLAIN).c_str());
#endif
if (next != -1)
{
// Kind of a hacky way to get quiver to change.
you.m_quiver->on_item_fired(you.inv[next], true);
if (next == cur)
mpr("No other missiles available. Use F to throw any item.");
}
else if (cur == -1)
mpr("No missiles available. Use F to throw any item.");
break;
}
case CMD_LIST_WEAPONS:
list_weapons();
break;
case CMD_LIST_ARMOUR:
list_armour();
break;
case CMD_LIST_JEWELLERY:
list_jewellery();
break;
case CMD_LIST_EQUIPMENT:
get_invent(OSEL_EQUIP);
break;
case CMD_LIST_GOLD:
if (shopping_list.size() == 0)
mprf("You have %d gold piece%s.",
you.gold, you.gold != 1 ? "s" : "");
else
shopping_list.display();
break;
case CMD_INSCRIBE_ITEM:
prompt_inscribe_item();
break;
#ifdef WIZARD
case CMD_WIZARD:
_handle_wizard_command();
break;
#endif
case CMD_SAVE_GAME:
if (yesno("Save game and exit?", true, 'n'))
save_game(true);
break;
case CMD_QUIT:
if (yes_or_no("Are you sure you want to quit"))
ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_QUITTING);
else
canned_msg(MSG_OK);
break;
case CMD_REPEAT_CMD:
_setup_cmd_repeat();
break;
case CMD_PREV_CMD_AGAIN:
_do_prev_cmd_again();
break;
case CMD_NO_CMD:
default:
if (Tutorial.tutorial_left)
{
std::string msg = "Unknown command. (For a list of commands type "
"<w>?\?<lightgrey>.)";
print_formatted_paragraph(msg);
}
else // well, not examine, but...
mpr("Unknown command.", MSGCH_EXAMINE_FILTER);
break;
}
flush_prev_message();
}
static void _prep_input()
{
you.time_taken = player_speed();
you.shield_blocks = 0; // no blocks this round
textcolor(LIGHTGREY);
set_redraw_status( REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK );
print_stats();
}
// Decrement a single duration. Print the message if the duration runs out.
// Returns true if the duration ended.
// At midpoint (defined by get_expiration_threshold() in player.cc)
// print midmsg and decrease duration by midloss (a randomised amount so as
// to make it impossible to know the exact remaining duration for sure).
// NOTE: The maximum possible midloss should be smaller than midpoint,
// otherwise the duration may end in the same turn the warning
// message is printed which would be a bit late.
static bool _decrement_a_duration(duration_type dur, int delay,
const char* endmsg = NULL, int midloss = 0,
const char* midmsg = NULL,
msg_channel_type chan = MSGCH_DURATION)
{
if (you.duration[dur] < 1)
return (false);
const int midpoint = get_expiration_threshold(dur);
int old_dur = you.duration[dur];
you.duration[dur] -= delay;
if (you.duration[dur] < 0)
you.duration[dur] = 0;
// Did we cross the mid point? (No longer likely to hit it exactly) -cao
if (you.duration[dur] <= midpoint && old_dur > midpoint)
{
if (midmsg)
mpr(midmsg, chan);
you.duration[dur] -= midloss * BASELINE_DELAY;
}
// allow fall-through in case midloss ended the duration (it shouldn't)
if (you.duration[dur] == 0)
{
if (endmsg)
mpr(endmsg, chan);
return true;
}
return false;
}
// Perhaps we should write functions like: update_liquid_flames(), etc.
// Even better, we could have a vector of callback functions (or
// objects) which get installed at some point.
static void _decrement_durations()
{
int delay = you.time_taken;
if (wearing_amulet(AMU_THE_GOURMAND))
{
if (you.duration[DUR_GOURMAND] < GOURMAND_MAX && coinflip())
you.duration[DUR_GOURMAND] += delay;
}
else
you.duration[DUR_GOURMAND] = 0;
if (you.duration[DUR_ICEMAIL_DEPLETED] > 0)
{
if(delay > you.duration[DUR_ICEMAIL_DEPLETED])
you.duration[DUR_ICEMAIL_DEPLETED] = 0;
else
you.duration[DUR_ICEMAIL_DEPLETED] -= delay;
if (!you.duration[DUR_ICEMAIL_DEPLETED])
mpr("Your icy envelope is fully restored.", MSGCH_DURATION);
you.redraw_armour_class = true;
}
// Must come before might/haste/berserk.
if (_decrement_a_duration(DUR_BUILDING_RAGE, delay))
go_berserk(false);
if (_decrement_a_duration(DUR_SLEEP, delay))
you.awake();
dec_napalm_player(delay);
if (_decrement_a_duration(DUR_ICY_ARMOUR, delay,
"Your icy armour evaporates.", coinflip(),
"Your icy armour starts to melt."))
{
you.redraw_armour_class = true;
}
if (_decrement_a_duration(DUR_SILENCE, delay, "Your hearing returns."))
you.attribute[ATTR_WAS_SILENCED] = 0;
_decrement_a_duration(DUR_REPEL_MISSILES, delay,
"You feel less protected from missiles.",
coinflip(),
"Your repel missiles spell is about to expire...");
_decrement_a_duration(DUR_DEFLECT_MISSILES, delay,
"You feel less protected from missiles.",
coinflip(),
"Your deflect missiles spell is about to expire...");
if (_decrement_a_duration(DUR_REGENERATION, delay,
NULL, coinflip(),
"Your skin is crawling a little less now."))
{
remove_regen(you.attribute[ATTR_DIVINE_REGENERATION]);
}
if (you.duration[DUR_PRAYER] > 1)
you.duration[DUR_PRAYER]--;
else if (you.duration[DUR_PRAYER] == 1)
end_prayer();
if (you.duration[DUR_DIVINE_SHIELD] > 0)
{
if (you.duration[DUR_DIVINE_SHIELD] > 1)
{
you.duration[DUR_DIVINE_SHIELD] -= delay;
if(you.duration[DUR_DIVINE_SHIELD] <= 1)
{
you.duration[DUR_DIVINE_SHIELD] = 1;
mpr("Your divine shield starts to fade.", MSGCH_DURATION);
}
}
if (you.duration[DUR_DIVINE_SHIELD] == 1 && !one_chance_in(3))
{
you.redraw_armour_class = true;
if (--you.attribute[ATTR_DIVINE_SHIELD] == 0)
{
you.duration[DUR_DIVINE_SHIELD] = 0;
mpr("Your divine shield fades away.", MSGCH_DURATION);
}
}
}
//jmf: More flexible weapon branding code.
int last_value = you.duration[DUR_WEAPON_BRAND];
if (last_value > 0)
{
you.duration[DUR_WEAPON_BRAND] -= delay;
if (you.duration[DUR_WEAPON_BRAND] <= 0)
{
you.duration[DUR_WEAPON_BRAND] = 0;
item_def& weapon = *you.weapon();
const int temp_effect = get_weapon_brand(weapon);
set_item_ego_type(weapon, OBJ_WEAPONS, SPWPN_NORMAL);
std::string msg = weapon.name(DESC_CAP_YOUR);
switch (temp_effect)
{
case SPWPN_VORPAL:
if (get_vorpal_type(weapon) == DVORP_SLICING)
msg += " seems blunter.";
else
msg += " feels lighter.";
break;
case SPWPN_FLAME:
case SPWPN_FLAMING:
msg += " goes out.";
break;
case SPWPN_FREEZING:
msg += " stops glowing.";
break;
case SPWPN_FROST:
msg += "'s frost melts away.";
break;
case SPWPN_VENOM:
msg += " stops dripping with poison.";
break;
case SPWPN_DRAINING:
msg += " stops crackling.";
break;
case SPWPN_DISTORTION:
msg += " seems straighter.";
break;
case SPWPN_PAIN:
msg += " seems less pained.";
break;
default:
msg += " seems inexplicably less special.";
break;
}
mpr(msg.c_str(), MSGCH_DURATION);
you.wield_change = true;
}
}
// FIXME: [ds] Remove this once we've ensured durations can never go < 0?
if (you.duration[DUR_TRANSFORMATION] <= 0
&& you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
{
you.duration[DUR_TRANSFORMATION] = 1;
}
// Vampire bat transformations are permanent (until ended).
if (you.species != SP_VAMPIRE || !player_in_bat_form()
|| you.duration[DUR_TRANSFORMATION] <= 5 * BASELINE_DELAY)
{
if (_decrement_a_duration(DUR_TRANSFORMATION, delay, NULL, random2(3),
"Your transformation is almost over."))
{
untransform();
you.duration[DUR_BREATH_WEAPON] = 0;
}
}
// Must come after transformation duration.
_decrement_a_duration(DUR_BREATH_WEAPON, delay,
"You have got your breath back.", 0, NULL,
MSGCH_RECOVERY);
_decrement_a_duration(DUR_SWIFTNESS, delay,
"You feel sluggish.", coinflip(),
"You start to feel a little slower.");
_decrement_a_duration(DUR_INSULATION, delay,
"You feel conductive.", coinflip(),
"You start to feel a little less insulated.");
if (_decrement_a_duration(DUR_STONEMAIL, delay,
"Your scaly stone armour disappears.",
coinflip(),
"Your scaly stone armour is starting "
"to flake away."))
{
you.redraw_armour_class = true;
burden_change();
}
if (_decrement_a_duration(DUR_PHASE_SHIFT, delay,
"You are firmly grounded in the material plane once more.",
coinflip(),
"You feel closer to the material plane."))
{
you.redraw_evasion = true;
}
if (_decrement_a_duration(DUR_SEE_INVISIBLE, delay)
&& !you.can_see_invisible())
{
mpr("Your eyesight blurs momentarily.", MSGCH_DURATION);
}
_decrement_a_duration(DUR_TELEPATHY, delay, "You feel less empathic.");
if (_decrement_a_duration(DUR_CONDENSATION_SHIELD, delay))
remove_condensation_shield();
if (you.duration[DUR_CONDENSATION_SHIELD] && player_res_cold() < 0)
{
mpr("You feel very cold.");
ouch(2 + random2avg(13, 2), NON_MONSTER, KILLED_BY_FREEZING);
}
if (_decrement_a_duration(DUR_MAGIC_SHIELD, delay,
"Your magical shield disappears."))
{
you.redraw_armour_class = true;
}
if (_decrement_a_duration(DUR_STONESKIN, delay, "Your skin feels tender."))
you.redraw_armour_class = true;
if (_decrement_a_duration(DUR_TELEPORT, delay))
{
// Only to a new area of the abyss sometimes (for abyss teleports).
you_teleport_now(true, one_chance_in(5));
untag_followers();
}
_decrement_a_duration(DUR_CONTROL_TELEPORT, delay,
"You feel uncertain.", coinflip(),
"You start to feel a little uncertain.");
if (_decrement_a_duration(DUR_DEATH_CHANNEL, delay,
"Your unholy channel expires.", coinflip(),
"Your unholy channel is weakening."))
{
you.attribute[ATTR_DIVINE_DEATH_CHANNEL] = 0;
}
_decrement_a_duration(DUR_SAGE, delay, "You feel less studious.");
_decrement_a_duration(DUR_STEALTH, delay, "You feel less stealthy.");
_decrement_a_duration(DUR_RESIST_FIRE, delay, "Your fire resistance expires.");
_decrement_a_duration(DUR_RESIST_COLD, delay, "Your cold resistance expires.");
_decrement_a_duration(DUR_RESIST_POISON, delay, "Your poison resistance expires.");
_decrement_a_duration(DUR_SLAYING, delay, "You feel less lethal.");
_decrement_a_duration(DUR_INVIS, delay, "You flicker back into view.",
coinflip(), "You flicker for a moment.");
_decrement_a_duration(DUR_BARGAIN, delay, "You feel less charismatic.");
_decrement_a_duration(DUR_CONF, delay, "You feel less confused.");
_decrement_a_duration(DUR_LOWERED_MR, delay, "You feel more resistant to magic.");
_decrement_a_duration(DUR_SLIMIFY, delay, "You feel less slimy.",
coinflip(), "Your slime is starting to congeal.");
_decrement_a_duration(DUR_MISLED, delay, "Your thoughts are your own once more.");
if (you.duration[DUR_PARALYSIS] || you.petrified())
{
_decrement_a_duration(DUR_PARALYSIS, delay);
_decrement_a_duration(DUR_PETRIFIED, delay);
if (!you.duration[DUR_PARALYSIS] && !you.petrified())
{
mpr("You can move again.", MSGCH_DURATION);
you.redraw_evasion = true;
}
}
_decrement_a_duration(DUR_EXHAUSTED, delay, "You feel less fatigued.");
_decrement_a_duration(DUR_CONFUSING_TOUCH, delay,
((std::string("Your ") + your_hand(true)) +
" stop glowing.").c_str());
_decrement_a_duration(DUR_SURE_BLADE, delay,
"The bond with your blade fades away.");
if (_decrement_a_duration(DUR_MESMERISED, delay,
"You break out of your daze.",
0, NULL, MSGCH_RECOVERY))
{
you.clear_beholders();
}
dec_slow_player(delay);
dec_haste_player(delay);
if (_decrement_a_duration(DUR_MIGHT, delay,
"You feel a little less mighty now."))
{
modify_stat(STAT_STRENGTH, -5, true, "might running out");
}
if (_decrement_a_duration(DUR_AGILITY, delay,
"You feel a little less agile now."))
{
modify_stat(STAT_DEXTERITY, -5, true, "agility running out");
}
if (_decrement_a_duration(DUR_BRILLIANCE, delay,
"You feel a little less clever now."))
{
modify_stat(STAT_INTELLIGENCE, -5, true, "brilliance running out");
}
if (_decrement_a_duration(DUR_BERSERKER, delay,
"You are no longer berserk."))
{
//jmf: Guilty for berserking /after/ berserk.
did_god_conduct(DID_STIMULANTS, 6 + random2(6));
// Sometimes berserk leaves us physically drained.
//
// Chance of passing out:
// - mutation gives a large plus in order to try and
// avoid the mutation being a "death sentence" to
// certain characters.
// - knowing the spell gives an advantage just
// so that people who have invested 3 spell levels
// are better off than the casual potion drinker...
// this should make it a bit more interesting for
// Crusaders again.
// - similarly for the amulet
if (you.berserk_penalty != NO_BERSERK_PENALTY)
{
const int chance =
10 + player_mutation_level(MUT_BERSERK) * 25
+ (wearing_amulet(AMU_RAGE) ? 10 : 0)
+ (you.has_spell(SPELL_BERSERKER_RAGE) ? 5 : 0);
// Note the beauty of Trog! They get an extra save that's at
// the very least 20% and goes up to 100%.
if (you.religion == GOD_TROG && x_chance_in_y(you.piety, 150)
&& !player_under_penance())
{
mpr("Trog's vigour flows through your veins.");
}
else if (one_chance_in(chance))
{
mpr("You pass out from exhaustion.", MSGCH_WARN);
you.increase_duration(DUR_PARALYSIS, roll_dice(1,4));
}
}
if (!you.duration[DUR_PARALYSIS] && !you.petrified())
mpr("You are exhausted.", MSGCH_WARN);
// This resets from an actual penalty or from NO_BERSERK_PENALTY.
you.berserk_penalty = 0;
int dur = 12 + roll_dice(2, 12);
// For consistency with slow give exhaustion 2 times the nominal
// duration.
you.increase_duration(DUR_EXHAUSTED, dur * 2);
// Don't trigger too many tutorial messages.
const bool tut_slow = Tutorial.tutorial_events[TUT_YOU_ENCHANTED];
Tutorial.tutorial_events[TUT_YOU_ENCHANTED] = false;
{
// Don't give duplicate 'You feel yourself slow down' messages.
no_messages nm;
// While the amulet of resist slowing does prevent the post-berserk
// slowing, exhaustion still ends haste.
if (you.duration[DUR_HASTE] > 0)
{
// Silently cancel haste, then slow player.
you.duration[DUR_HASTE] = 0;
}
slow_player(dur);
}
make_hungry(700, true);
you.hunger = std::max(50, you.hunger);
// 1KB: No berserk healing.
you.hp = (you.hp + 1) * 2 / 3;
calc_hp();
learned_something_new(TUT_POSTBERSERK);
Tutorial.tutorial_events[TUT_YOU_ENCHANTED] = tut_slow;
}
if (you.duration[DUR_CORONA])
{
you.duration[DUR_CORONA] -= delay;
if (you.duration[DUR_CORONA] < 1)
you.duration[DUR_CORONA] = 0;
if (!you.duration[DUR_CORONA] && !you.backlit())
mpr("You are no longer glowing.", MSGCH_DURATION);
}
// Leak piety from the piety pool into actual piety.
// Note that changes of religious status without corresponding actions
// (killing monsters, offering items, ...) might be confusing for characters
// of other religions.
// For now, though, keep information about what happened hidden.
if (you.piety < MAX_PIETY && you.duration[DUR_PIETY_POOL] > 0
&& one_chance_in(5))
{
you.duration[DUR_PIETY_POOL]--;
gain_piety(1);
#if DEBUG_DIAGNOSTICS || DEBUG_SACRIFICE || DEBUG_PIETY
mpr("Piety increases by 1 due to piety pool.", MSGCH_DIAGNOSTICS);
if (you.duration[DUR_PIETY_POOL] == 0)
mpr("Piety pool is now empty.", MSGCH_DIAGNOSTICS);
#endif
}
if (!you.permanent_levitation() && !you.permanent_flight())
{
if (_decrement_a_duration(DUR_LEVITATION, delay,
"You float gracefully downwards.",
random2(6),
"You are starting to lose your buoyancy!"))
{
burden_change();
// Landing kills controlled flight.
you.duration[DUR_CONTROLLED_FLIGHT] = 0;
// Re-enter the terrain.
move_player_to_grid(you.pos(), false, true, true);
}
}
if (!you.permanent_flight()
&& _decrement_a_duration(DUR_CONTROLLED_FLIGHT, delay)
&& you.airborne())
{
mpr("You lose control over your flight.", MSGCH_DURATION);
}
if (you.rotting > 0)
{
// XXX: Mummies have an ability (albeit an expensive one) that
// can fix rotted HPs now... it's probably impossible for them
// to even start rotting right now, but that could be changed. -- bwr
// It's not normal biology, so Cheibriados won't help.
if (you.species == SP_MUMMY)
you.rotting = 0;
else if (x_chance_in_y(you.rotting, 20))
{
mpr("You feel your flesh rotting away.", MSGCH_WARN);
ouch(1, NON_MONSTER, KILLED_BY_ROTTING);
rot_hp(1);
you.rotting--;
}
}
// ghoul rotting is special, but will deduct from you.rotting
// if it happens to be positive - because this is placed after
// the "normal" rotting check, rotting attacks can be somewhat
// more painful on ghouls - reversing order would make rotting
// attacks somewhat less painful, but that seems wrong-headed {dlb}:
if (you.species == SP_GHOUL)
{
if (one_chance_in((you.religion == GOD_CHEIBRIADOS && you.piety >
piety_breakpoint(0)) ? 600 : 400))
{
mpr("You feel your flesh rotting away.", MSGCH_WARN);
ouch(1, NON_MONSTER, KILLED_BY_ROTTING);
rot_hp(1);
if (you.rotting > 0)
you.rotting--;
}
}
dec_disease_player(delay);
dec_poison_player();
if (you.duration[DUR_DEATHS_DOOR])
{
if (you.hp > allowed_deaths_door_hp())
{
mpr("Your life is in your own hands once again.", MSGCH_DURATION);
you.increase_duration(DUR_PARALYSIS, 5 + random2(5));
confuse_player(10 + random2(10));
you.hp_max--;
deflate_hp(you.hp_max, false);
you.duration[DUR_DEATHS_DOOR] = 0;
}
else
{
_decrement_a_duration(DUR_DEATHS_DOOR, delay,
"Your life is in your own hands again!",
random2(6),
"Your time is quickly running out!");
}
}
if (_decrement_a_duration(DUR_DIVINE_VIGOUR, delay))
remove_divine_vigour();
if (_decrement_a_duration(DUR_DIVINE_STAMINA, delay))
remove_divine_stamina();
_decrement_a_duration(DUR_REPEL_STAIRS_MOVE, 1);
_decrement_a_duration(DUR_REPEL_STAIRS_CLIMB, 1);
}
static void _check_banished()
{
if (you.banished)
{
you.banished = false;
if (you.level_type != LEVEL_ABYSS)
{
mpr("You are cast into the Abyss!", MSGCH_BANISHMENT);
more();
banished(DNGN_ENTER_ABYSS, you.banished_by);
}
you.banished_by.clear();
}
}
static void _check_shafts()
{
for (int i = 0; i < MAX_TRAPS; ++i)
{
trap_def &trap = env.trap[i];
if (trap.type != TRAP_SHAFT)
continue;
ASSERT(in_bounds(trap.pos));
handle_items_on_shaft(trap.pos, true);
}
}
static void _check_sanctuary()
{
if (env.sanctuary_time <= 0)
return;
decrease_sanctuary_radius();
}
static void _regenerate_hp_and_mp(int delay)
{
// XXX: using an int tmp to fix the fact that hit_points_regeneration
// is only an unsigned char and is thus likely to overflow. -- bwr
int tmp = you.hit_points_regeneration;
if (you.hp < you.hp_max && !you.disease && !you.duration[DUR_DEATHS_DOOR])
{
int base_val = player_regen();
tmp += div_rand_round(base_val * delay, BASELINE_DELAY);
}
while (tmp >= 100)
{
inc_hp(1, false);
tmp -= 100;
}
// XXX: Don't let DD use guardian spirit for free HP. (due, dpeg)
if (player_spirit_shield() && you.species == SP_DEEP_DWARF)
return;
ASSERT( tmp >= 0 && tmp < 100 );
you.hit_points_regeneration = static_cast<unsigned char>(tmp);
// XXX: Doing the same as the above, although overflow isn't an
// issue with magic point regeneration, yet. -- bwr
tmp = you.magic_points_regeneration;
if (you.magic_points < you.max_magic_points)
{
int base_val = 7 + you.max_magic_points / 2;
tmp += div_rand_round(base_val * delay, BASELINE_DELAY);
}
while (tmp >= 100)
{
inc_mp(1, false);
tmp -= 100;
}
ASSERT( tmp >= 0 && tmp < 100 );
you.magic_points_regeneration = static_cast<unsigned char>(tmp);
}
void world_reacts()
{
crawl_state.clear_mon_acting();
if (!crawl_state.arena)
{
you.turn_is_over = true;
religion_turn_end();
crawl_state.clear_god_acting();
}
#ifdef USE_TILE
if (Tutorial.tutorial_left)
{
tiles.clear_text_tags(TAG_TUTORIAL);
tiles.place_cursor(CURSOR_TUTORIAL, Region::NO_CURSOR);
}
#endif
if (you.num_turns != -1)
{
if (you.num_turns < LONG_MAX)
you.num_turns++;
if (env.turns_on_level < INT_MAX)
env.turns_on_level++;
update_turn_count();
}
_check_banished();
_check_shafts();
_check_sanctuary();
run_environment_effects();
if (!you.cannot_act() && !player_mutation_level(MUT_BLURRY_VISION)
&& x_chance_in_y(you.skills[SK_TRAPS_DOORS], 50))
{
search_around(false); // Check nonadjacent squares too.
}
if (!crawl_state.arena)
stealth = check_stealth();
#ifdef DEBUG_STEALTH
// Too annoying for regular diagnostics.
mprf(MSGCH_DIAGNOSTICS, "stealth: %d", stealth );
#endif
if (you.attribute[ATTR_NOISES])
noisy_equipment();
if (you.attribute[ATTR_SHADOWS])
shadow_lantern_effect();
if (you.unrand_reacts != 0)
unrand_reacts();
if (!crawl_state.arena && one_chance_in(10))
{
// this is instantaneous
if (player_teleport() > 0 && one_chance_in(100 / player_teleport()))
you_teleport_now( true );
else if (you.level_type == LEVEL_ABYSS && one_chance_in(30))
you_teleport_now( false, true ); // to new area of the Abyss
}
if (!crawl_state.arena && env.cgrid(you.pos()) != EMPTY_CLOUD)
in_a_cloud();
if (you.level_type == LEVEL_DUNGEON && you.duration[DUR_TELEPATHY])
detect_creatures( 1 + you.duration[DUR_TELEPATHY] /
(2 * BASELINE_DELAY), true );
_decrement_durations();
int food_use = player_hunger_rate();
food_use = div_rand_round(food_use * you.time_taken, BASELINE_DELAY);
if (food_use > 0 && you.hunger >= 40)
make_hungry(food_use, true);
_regenerate_hp_and_mp(you.time_taken);
// If you're wielding a rod, it'll gradually recharge.
recharge_rods(you.time_taken, false);
viewwindow(true);
maybe_update_stashes();
handle_monsters();
_check_banished();
ASSERT(you.time_taken >= 0);
// Make sure we don't overflow.
ASSERT(DBL_MAX - you.elapsed_time > you.time_taken);
you.elapsed_time += you.time_taken;
handle_time();
manage_clouds();
if (you.duration[DUR_FIRE_SHIELD] > 0)
manage_fire_shield(you.time_taken);
// Food death check.
if (you.is_undead != US_UNDEAD && you.hunger <= 500)
{
if (!you.cannot_act() && one_chance_in(40))
{
mpr("You lose consciousness!", MSGCH_FOOD);
stop_running();
you.increase_duration(DUR_PARALYSIS, 5 + random2(8), 13);
if (you.religion == GOD_XOM)
xom_is_stimulated(get_tension() > 0 ? 255 : 128);
}
if (you.hunger <= 100)
{
mpr("You have starved to death.", MSGCH_FOOD);
ouch(INSTANT_DEATH, NON_MONSTER, KILLED_BY_STARVATION);
}
}
viewwindow(false);
if (you.cannot_act() && any_messages()
&& crawl_state.repeat_cmd != CMD_WIZARD)
{
more();
}
#if defined(DEBUG_TENSION) || defined(DEBUG_RELIGION)
if (you.religion != GOD_NO_GOD)
mprf(MSGCH_DIAGNOSTICS, "TENSION = %d", get_tension());
#endif
}
#ifdef DGL_SIMPLE_MESSAGING
static struct stat mfilestat;
static void _show_message_line(std::string line)
{
const std::string::size_type sender_pos = line.find(":");
if (sender_pos == std::string::npos)
mpr(line.c_str());
else
{
std::string sender = line.substr(0, sender_pos);
line = line.substr(sender_pos + 1);
trim_string(line);
formatted_string fs;
fs.textcolor(WHITE);
fs.cprintf("%s: ", sender.c_str());
fs.textcolor(LIGHTGREY);
fs.cprintf("%s", line.c_str());
formatted_mpr(fs, MSGCH_PLAIN, 0);
take_note(Note( NOTE_MESSAGE, MSGCH_PLAIN, 0,
(sender + ": " + line).c_str() ));
}
}
static void _kill_messaging(FILE *mf)
{
if (mf)
fclose(mf);
SysEnv.have_messages = false;
Options.messaging = false;
}
static void _read_each_message()
{
bool say_got_msg = true;
FILE *mf = fopen(SysEnv.messagefile.c_str(), "r+");
if (!mf)
{
mprf(MSGCH_ERROR, "Couldn't read %s: %s", SysEnv.messagefile.c_str(),
strerror(errno));
_kill_messaging(mf);
return;
}
// Read messages, code borrowed from the SIMPLEMAIL patch.
char line[120];
if (!lock_file_handle(mf, F_RDLCK))
{
mprf(MSGCH_ERROR, "Failed to lock %s: %s", SysEnv.messagefile.c_str(),
strerror(errno));
_kill_messaging(mf);
return;
}
while (fgets(line, sizeof line, mf))
{
unlock_file_handle(mf);
const int len = strlen(line);
if (len)
{
if (line[len - 1] == '\n')
line[len - 1] = 0;
if (say_got_msg)
{
mprf(MSGCH_PROMPT, "Your messages:");
say_got_msg = false;
}
_show_message_line(line);
}
if (!lock_file_handle(mf, F_RDLCK))
{
mprf(MSGCH_ERROR, "Failed to lock %s: %s",
SysEnv.messagefile.c_str(),
strerror(errno));
_kill_messaging(mf);
return;
}
}
if (!lock_file_handle(mf, F_WRLCK))
{
mprf(MSGCH_ERROR, "Unable to write lock %s: %s",
SysEnv.messagefile.c_str(),
strerror(errno));
}
if (!ftruncate(fileno(mf), 0))
mfilestat.st_mtime = 0;
unlock_file_handle(mf);
fclose(mf);
SysEnv.have_messages = false;
}
static void _read_messages()
{
_read_each_message();
update_message_status();
}
static void _announce_messages()
{
// XXX: We could do a NetHack-like mail daemon here at some point.
mprf("Beep! Your pager goes off! Use _ to check your messages.");
}
static void _check_messages()
{
if (!Options.messaging
|| SysEnv.have_messages
|| SysEnv.messagefile.empty()
|| kbhit()
|| (SysEnv.message_check_tick++ % DGL_MESSAGE_CHECK_INTERVAL))
{
return;
}
const bool had_messages = SysEnv.have_messages;
struct stat st;
if (stat(SysEnv.messagefile.c_str(), &st))
{
mfilestat.st_mtime = 0;
return;
}
if (st.st_mtime > mfilestat.st_mtime)
{
if (st.st_size)
SysEnv.have_messages = true;
mfilestat.st_mtime = st.st_mtime;
}
if (SysEnv.have_messages && !had_messages)
{
_announce_messages();
update_message_status();
// Recenter the cursor on the player.
_center_cursor();
}
}
#endif
static command_type _get_next_cmd()
{
#ifdef DGL_SIMPLE_MESSAGING
_check_messages();
#endif
#if DEBUG_DIAGNOSTICS
// Save hunger at start of round for use with hunger "delta-meter"
// in output.cc.
you.old_hunger = you.hunger;
#endif
#if DEBUG_ITEM_SCAN
debug_item_scan();
#endif
#if DEBUG_MONS_SCAN
debug_mons_scan();
#endif
const time_t before = time(NULL);
keycode_type keyin = _get_next_keycode();
const time_t after = time(NULL);
// Clamp idle time so that play time is more meaningful.
if (after - before > IDLE_TIME_CLAMP)
{
you.real_time += int(before - you.start_time) + IDLE_TIME_CLAMP;
you.start_time = after;
}
if (is_userfunction(keyin))
{
run_macro(get_userfunction(keyin).c_str());
return (CMD_NEXT_CMD);
}
return _keycode_to_command(keyin);
}
// We handle the synthetic keys, key_to_command() handles the
// real ones.
static command_type _keycode_to_command( keycode_type key )
{
switch ( key )
{
#ifdef USE_TILE
case CK_MOUSE_CMD: return CMD_NEXT_CMD;
#endif
case KEY_MACRO_DISABLE_MORE: return CMD_DISABLE_MORE;
case KEY_MACRO_ENABLE_MORE: return CMD_ENABLE_MORE;
case KEY_REPEAT_KEYS: return CMD_REPEAT_KEYS;
default:
return key_to_command(key, KMC_DEFAULT);
}
}
static keycode_type _get_next_keycode()
{
keycode_type keyin;
flush_input_buffer( FLUSH_BEFORE_COMMAND );
mouse_control mc(MOUSE_MODE_COMMAND);
keyin = unmangle_direction_keys(getch_with_command_macros());
if (!is_synthetic_key(keyin))
mesclr();
return (keyin);
}
// Check squares adjacent to player for given feature and return how
// many there are. If there's only one, return the dx and dy.
static int _check_adjacent(dungeon_feature_type feat, coord_def& delta)
{
int num = 0;
for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
{
if (grd(*ai) == feat)
{
num++;
delta = *ai - you.pos();
}
}
return num;
}
// Handles some aspects of untrapping. Returns false if the target is a
// closed door that will need to be opened.
static bool _untrap_target(const coord_def move, bool check_confused)
{
const coord_def target = you.pos() + move;
monsters* mon = monster_at(target);
if (mon && player_can_hit_monster(mon))
{
if (mon->caught() && mon->friendly()
&& player_can_open_doors() && !you.confused())
{
const std::string prompt =
make_stringf("Do you want to try to take the net off %s?",
mon->name(DESC_NOCAP_THE).c_str());
if (yesno(prompt.c_str(), true, 'n'))
{
remove_net_from(mon);
return (true);
}
}
you.turn_is_over = true;
you_attack(mon->mindex(), true);
if (you.berserk_penalty != NO_BERSERK_PENALTY)
you.berserk_penalty = 0;
return (true);
}
if (find_trap(target) && grd(target) != DNGN_UNDISCOVERED_TRAP)
{
if (!you.confused())
{
if (!player_can_open_doors())
{
mpr("You can't disarm traps in your present form.");
return (true);
}
const int cloud = env.cgrid(target);
if (cloud != EMPTY_CLOUD
&& is_damaging_cloud(env.cloud[ cloud ].type, true))
{
mpr("You can't get to that trap right now.");
return (true);
}
}
// If you're confused, you may attempt it and stumble into the trap.
disarm_trap(target);
return (true);
}
const dungeon_feature_type feat = grd(target);
if (!feat_is_closed_door(feat) || you.confused())
{
switch (feat)
{
case DNGN_OPEN_DOOR:
_close_door(move); // for convenience
return (true);
default:
{
bool do_msg = true;
// Press trigger/switch/button in wall.
if (feat_is_solid(feat))
{
dgn_event event(DET_WALL_HIT, target);
event.arg1 = NON_MONSTER;
// Listener can veto the event to prevent the "You swing at
// nothing" message.
do_msg =
dungeon_events.fire_vetoable_position_event(event,
target);
}
if (do_msg)
mpr("You swing at nothing.");
make_hungry(3, true);
you.turn_is_over = true;
return (true);
}
}
}
// Else it's a closed door and needs further handling.
return (false);
}
// Opens doors and may also handle untrapping/attacking, etc.
// If either move_x or move_y are non-zero, the pair carries a specific
// direction for the door to be opened (eg if you type ctrl + dir).
static void _open_door(coord_def move, bool check_confused)
{
ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
if (you.attribute[ATTR_HELD])
{
free_self_from_net();
you.turn_is_over = true;
return;
}
// The player used Ctrl + dir or a variant thereof.
if (!move.origin())
{
if (check_confused && you.confused() && !one_chance_in(3))
{
do
move = coord_def(random2(3) - 1, random2(3) - 1);
while (move.origin());
}
if (_untrap_target(move, check_confused))
return;
}
// If we get here, the player either hasn't picked a direction yet,
// or the chosen direction actually contains a closed door.
if (!player_can_open_doors())
{
mpr("You can't open doors in your present form.");
return;
}
dist door_move;
// The player hasn't picked a direction yet.
if (move.origin())
{
const int num = _check_adjacent(DNGN_CLOSED_DOOR, move)
+ _check_adjacent(DNGN_DETECTED_SECRET_DOOR, move);
if (num == 0)
{
mpr("There's nothing to open nearby.");
return;
}
// If there's only one door to open, don't ask.
if (num == 1)
door_move.delta = move;
else
{
mpr("Which direction? ", MSGCH_PROMPT);
direction(door_move, DIR_DIR);
if (!door_move.isValid)
return;
}
}
else
door_move.delta = move;
if (check_confused && you.confused() && !one_chance_in(3))
{
do
door_move.delta = coord_def(random2(3) - 1, random2(3) - 1);
while (door_move.delta.origin());
}
// We got a valid direction.
const coord_def doorpos = you.pos() + door_move.delta;
const dungeon_feature_type feat = (in_bounds(doorpos) ? grd(doorpos)
: DNGN_UNSEEN);
std::string door_already_open = "";
if (in_bounds(doorpos))
door_already_open = env.markers.property_at(doorpos, MAT_ANY,
"door_verb_already_open");
if (!feat_is_closed_door(feat))
{
if (you.confused())
{
mpr("You swing at nothing.");
make_hungry(3, true);
you.turn_is_over = true;
return;
}
switch (feat)
{
// This doesn't ever sem to be triggered.
case DNGN_OPEN_DOOR:
if (!door_already_open.empty())
mpr(door_already_open.c_str());
else
mpr("It's already open!");
break;
default:
mpr("There isn't anything that you can open there!");
break;
}
// Don't lose a turn.
return;
}
// Finally, open the closed door!
std::set<coord_def> all_door;
find_connected_range(doorpos, DNGN_CLOSED_DOOR, DNGN_SECRET_DOOR, all_door);
const char *adj, *noun;
get_door_description(all_door.size(), &adj, &noun);
const std::string door_desc_adj =
env.markers.property_at(doorpos, MAT_ANY,
"door_description_adjective");
const std::string door_desc_noun =
env.markers.property_at(doorpos, MAT_ANY,
"door_description_noun");
if (!door_desc_adj.empty())
adj = door_desc_adj.c_str();
if (!door_desc_noun.empty())
noun = door_desc_noun.c_str();
if (!(check_confused && you.confused()))
{
std::string door_open_prompt =
env.markers.property_at(doorpos, MAT_ANY, "door_open_prompt");
bool ignore_exclude = false;
if (!door_open_prompt.empty())
{
door_open_prompt += " (y/N)";
if (!yesno(door_open_prompt.c_str(), true, 'n', true, false))
{
if (is_exclude_root(doorpos))
canned_msg(MSG_OK);
else
{
if (yesno("Put travel exclusion on door? (Y/n)",
true, 'y'))
{
// Zero radius exclusion right on top of door.
set_exclude(doorpos, 0);
}
}
interrupt_activity(AI_FORCE_INTERRUPT);
return;
}
ignore_exclude = true;
}
if (!ignore_exclude && is_exclude_root(doorpos))
{
std::string prompt =
make_stringf("This %s%s is marked as excluded! Open it "
"anyway?", adj, noun);
if (!yesno(prompt.c_str(), true, 'n', true, false))
{
canned_msg(MSG_OK);
interrupt_activity(AI_FORCE_INTERRUPT);
return;
}
}
}
int skill = you.dex
+ (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2;
std::string berserk_open = env.markers.property_at(doorpos, MAT_ANY,
"door_berserk_verb_open");
std::string berserk_adjective = env.markers.property_at(doorpos, MAT_ANY,
"door_berserk_adjective");
std::string door_open_creak = env.markers.property_at(doorpos, MAT_ANY,
"door_noisy_verb_open");
std::string door_airborne = env.markers.property_at(doorpos, MAT_ANY,
"door_airborne_verb_open");
std::string door_open_verb = env.markers.property_at(doorpos, MAT_ANY,
"door_verb_open");
if (you.berserk())
{
// XXX: Better flavour for larger doors?
if (silenced(you.pos()))
{
if (!berserk_open.empty())
{
berserk_open += ".";
mprf(berserk_open.c_str(), adj, noun);
}
else
mprf("The %s%s flies open!", adj, noun);
}
else
{
if (!berserk_open.empty())
{
if (!berserk_adjective.empty())
berserk_open += " " + berserk_adjective;
else
berserk_open += ".";
mprf(MSGCH_SOUND, berserk_open.c_str(), adj, noun);
}
else
mprf(MSGCH_SOUND, "The %s%s flies open with a bang!", adj, noun);
noisy(15, you.pos());
}
}
else if (one_chance_in(skill) && !silenced(you.pos()))
{
if (!door_open_creak.empty())
mprf(MSGCH_SOUND, door_open_creak.c_str(), adj, noun);
else
mprf(MSGCH_SOUND, "As you open the %s%s, it creaks loudly!",
adj, noun);
noisy(10, you.pos());
}
else
{
const char* verb;
if (you.airborne())
{
if (!door_airborne.empty())
verb = door_airborne.c_str();
else
verb = "You reach down and open the %s%s.";
}
else
{
if (!door_open_verb.empty())
verb = door_open_verb.c_str();
else
verb = "You open the %s%s.";
}
mprf(verb, adj, noun);
}
bool seen_secret = false;
std::vector<coord_def> excludes;
for (std::set<coord_def>::iterator i = all_door.begin();
i != all_door.end(); ++i)
{
const coord_def& dc = *i;
// Even if some of the door is out of LOS, we want the entire
// door to be updated. Hitting this case requires a really big
// door!
if (is_terrain_seen(dc))
{
set_map_knowledge_obj(dc, DNGN_OPEN_DOOR);
#ifdef USE_TILE
env.tile_bk_bg(dc) = TILE_DNGN_OPEN_DOOR;
#endif
if (!seen_secret && grd(dc) == DNGN_SECRET_DOOR)
{
seen_secret = true;
dungeon_feature_type secret
= grid_secret_door_appearance(dc);
mprf("That %s was a secret door!",
feature_description(secret, NUM_TRAPS, false,
DESC_PLAIN, false).c_str());
}
}
grd(dc) = DNGN_OPEN_DOOR;
if (is_excluded(dc))
excludes.push_back(dc);
}
update_exclusion_los(excludes);
you.turn_is_over = true;
}
static void _close_door(coord_def move)
{
if (!player_can_open_doors())
{
mpr("You can't close doors in your present form.");
return;
}
if (you.attribute[ATTR_HELD])
{
mpr("You can't close doors while held in a net.");
return;
}
dist door_move;
// The player hasn't yet told us a direction.
if (move.origin())
{
// If there's only one door to close, don't ask.
int num = _check_adjacent(DNGN_OPEN_DOOR, move);
if (num == 0)
{
mpr("There's nothing to close nearby.");
return;
}
else if (num == 1)
door_move.delta = move;
else
{
mpr("Which direction? ", MSGCH_PROMPT);
direction(door_move, DIR_DIR);
if (!door_move.isValid)
return;
}
}
else
door_move.delta = move;
if (you.confused() && !one_chance_in(3))
{
do
door_move.delta = coord_def(random2(3) - 1, random2(3) - 1);
while (door_move.delta.origin());
}
if (door_move.delta.origin())
{
mpr("You can't close doors on yourself!");
return;
}
const coord_def doorpos = you.pos() + door_move.delta;
const dungeon_feature_type feat = (in_bounds(doorpos) ? grd(doorpos)
: DNGN_UNSEEN);
std::string berserk_close = env.markers.property_at(doorpos, MAT_ANY,
"door_berserk_verb_close");
std::string berserk_adjective = env.markers.property_at(doorpos, MAT_ANY,
"door_berserk_adjective");
std::string door_close_creak = env.markers.property_at(doorpos, MAT_ANY,
"door_noisy_verb_close");
std::string door_airborne = env.markers.property_at(doorpos, MAT_ANY,
"door_airborne_verb_close");
std::string door_close_verb = env.markers.property_at(doorpos, MAT_ANY,
"door_verb_close");
if (feat == DNGN_OPEN_DOOR)
{
std::set<coord_def> all_door;
find_connected_identical(doorpos, grd(doorpos), all_door);
const char *adj, *noun;
get_door_description(all_door.size(), &adj, &noun);
const std::string waynoun_str = make_stringf("%sway", noun);
const char *waynoun = waynoun_str.c_str();
const std::string door_desc_adj =
env.markers.property_at(doorpos, MAT_ANY,
"door_description_adjective");
const std::string door_desc_noun =
env.markers.property_at(doorpos, MAT_ANY,
"door_description_noun");
if (!door_desc_adj.empty())
adj = door_desc_adj.c_str();
if (!door_desc_noun.empty())
{
noun = door_desc_noun.c_str();
waynoun = noun;
}
for (std::set<coord_def>::const_iterator i = all_door.begin();
i != all_door.end(); ++i)
{
const coord_def& dc = *i;
if (monsters* mon = monster_at(dc))
{
// Need to make sure that turn_is_over is set if
// creature is invisible.
if (!you.can_see(mon))
{
mprf("Something is blocking the %s!", waynoun);
you.turn_is_over = true;
}
else
mprf("There's a creature in the %s!", waynoun);
return;
}
if (igrd(dc) != NON_ITEM)
{
mprf("There's something blocking the %s.", waynoun);
return;
}
if (you.pos() == dc)
{
mprf("There's a thick-headed creature in the %s!", waynoun);
return;
}
}
int skill = you.dex
+ (you.skills[SK_TRAPS_DOORS] + you.skills[SK_STEALTH]) / 2;
if (you.berserk())
{
if (silenced(you.pos()))
{
if (!berserk_close.empty())
{
berserk_close += ".";
mprf(berserk_close.c_str(), adj, noun);
}
else
mprf("You slam the %s%s shut!", adj, noun);
}
else
{
if (!berserk_close.empty())
{
if (!berserk_adjective.empty())
berserk_close += " " + berserk_adjective;
else
berserk_close += ".";
mprf(MSGCH_SOUND, berserk_close.c_str(), adj, noun);
}
else
mprf(MSGCH_SOUND, "You slam the %s%s shut with a bang!", adj, noun);
noisy(15, you.pos());
}
}
else if (one_chance_in(skill) && !silenced(you.pos()))
{
if (!door_close_creak.empty())
mprf(MSGCH_SOUND, door_close_creak.c_str(), adj, noun);
else
mprf(MSGCH_SOUND, "As you close the %s%s, it creaks loudly!",
adj, noun);
noisy(10, you.pos());
}
else
{
const char* verb;
if (you.airborne())
{
if (!door_airborne.empty())
verb = door_airborne.c_str();
else
verb = "You reach down and close the %s%s.";
}
else
{
if (!door_close_verb.empty())
verb = door_close_verb.c_str();
else
verb = "You close the %s%s.";
}
mprf(verb, adj, noun);
}
std::vector<coord_def> excludes;
for (std::set<coord_def>::const_iterator i = all_door.begin();
i != all_door.end(); ++i)
{
const coord_def& dc = *i;
// Once opened, formerly secret doors become normal doors.
grd(dc) = DNGN_CLOSED_DOOR;
// Even if some of the door is out of LOS once it's closed
// (or even if some of it is out of LOS when it's open), we
// want the entire door to be updated.
if (is_terrain_seen(dc))
{
set_map_knowledge_obj(dc, DNGN_CLOSED_DOOR);
#ifdef USE_TILE
env.tile_bk_bg(dc) = TILE_DNGN_CLOSED_DOOR;
#endif
}
if (is_excluded(dc))
excludes.push_back(dc);
}
update_exclusion_los(excludes);
you.turn_is_over = true;
}
else if (you.confused())
_open_door(door_move.delta);
else
{
switch (feat)
{
case DNGN_CLOSED_DOOR:
case DNGN_DETECTED_SECRET_DOOR:
mpr("It's already closed!");
break;
default:
mpr("There isn't anything that you can close there!");
break;
}
}
}
// Initialise a whole lot of stuff...
// Returns true if a new character.
static bool _initialise(void)
{
Options.fixup_options();
// Read the options the player used last time they created a new
// character.
read_startup_prefs();
you.symbol = '@';
you.colour = LIGHTGREY;
seed_rng();
get_typeid_array().init(ID_UNKNOWN_TYPE);
init_char_table(Options.char_set);
init_show_table();
init_monster_symbols();
init_spell_descs(); // This needs to be way up top. {dlb}
init_mon_name_cache();
init_mons_spells();
// init_item_name_cache() needs to be redone after init_char_table()
// and init_show_table() have been called, so that the glyphs will
// be set to use with item_names_by_glyph_cache.
init_item_name_cache();
msg::initialise_mpr_streams();
// Init item array.
for (int i = 0; i < MAX_ITEMS; ++i)
init_item(i);
// Empty messaging string.
info[0] = 0;
for (int i = 0; i < MAX_MONSTERS; ++i)
menv[i].reset();
igrd.init(NON_ITEM);
mgrd.init(NON_MONSTER);
env.map_knowledge.init(map_cell());
env.pgrid.init(0);
you.unique_creatures.init(false);
you.unique_items.init(UNIQ_NOT_EXISTS);
// Set up the Lua interpreter for the dungeon builder.
init_dungeon_lua();
#ifdef USE_TILE
// Draw the splash screen before the database gets initialised as that
// may take awhile and it's better if the player can look at a pretty
// screen while this happens.
if (!crawl_state.map_stat_gen && !crawl_state.test
&& Options.tile_title_screen)
{
tiles.draw_title();
}
#endif
// Initialise internal databases.
databaseSystemInit();
init_feat_desc_cache();
init_spell_name_cache();
init_spell_rarities();
// Read special levels and vaults.
read_maps();
if (crawl_state.build_db)
end(0);
cio_init();
// System initialisation stuff.
textbackground(0);
clrscr();
#ifdef DEBUG_DIAGNOSTICS
if (crawl_state.map_stat_gen)
{
generate_map_stats();
end(0, false);
}
#endif
if (crawl_state.test)
{
#if DEBUG_TESTS && !DEBUG
#error "DEBUG must be defined if DEBUG_TESTS is defined"
#endif
#if DEBUG_DIAGNOSTICS || DEBUG_TESTS
#ifdef USE_TILE
init_player_doll();
tiles.initialise_items();
#endif
Options.show_more_prompt = false;
crawl_tests::run_tests(true);
// Superfluous, just to make it clear that this is the end of
// the line.
end(0, false);
#else
end(1, false, "Non-debug Crawl cannot run tests. "
"Please use a debug build (defined FULLDEBUG, DEBUG_DIAGNOSTIC "
"or DEBUG_TESTS)");
#endif
}
if (crawl_state.arena)
{
run_map_preludes();
initialise_item_descriptions();
#ifdef USE_TILE
tiles.initialise_items();
#endif
run_arena();
end(0, false);
}
// Sets up a new game.
const bool newc = new_game();
if (!newc)
restore_game();
// Fix the mutation definitions for the species we're playing.
fixup_mutations();
// Load macros
macro_init();
crawl_state.need_save = true;
calc_hp();
calc_mp();
run_map_preludes();
if (newc && you.char_direction == GDT_GAME_START)
{
// Chaos Knights of Lugonu start out in the Abyss.
you.level_type = LEVEL_ABYSS;
you.entry_cause = EC_UNKNOWN;
}
load(you.entering_level ? you.transit_stair : DNGN_STONE_STAIRS_DOWN_I,
you.entering_level ? LOAD_ENTER_LEVEL :
newc ? LOAD_START_GAME : LOAD_RESTART_GAME,
NUM_LEVEL_AREA_TYPES, -1, you.where_are_you);
// Make sure monsters have a valid LOS before the first turn starts.
// This prevents mesmerization from being broken when a game is
// restored.
for (monster_iterator mi; mi; ++mi)
mi->update_los();
if (newc && you.char_direction == GDT_GAME_START)
{
// Randomise colours properly for the Abyss.
init_pandemonium();
}
#if DEBUG_DIAGNOSTICS
// Debug compiles display a lot of "hidden" information, so we auto-wiz.
you.wizard = true;
#endif
init_properties();
burden_change();
make_hungry(0, true);
you.redraw_strength = true;
you.redraw_intelligence = true;
you.redraw_dexterity = true;
you.redraw_armour_class = true;
you.redraw_evasion = true;
you.redraw_experience = true;
you.redraw_quiver = true;
you.wield_change = true;
// Start timer on session.
you.start_time = time( NULL );
#ifdef CLUA_BINDINGS
clua.runhook("chk_startgame", "b", newc);
std::string yname = you.your_name; // XXX: what's this for?
read_init_file(true);
Options.fixup_options();
you.your_name = yname;
// In case Lua changed the character set.
init_char_table(Options.char_set);
init_show_table();
init_monster_symbols();
#endif
#ifdef USE_TILE
// Override inventory weights options for tiled menus.
if (Options.tile_menu_icons && Options.show_inventory_weights)
Options.show_inventory_weights = false;
init_player_doll();
tiles.resize();
#endif
draw_border();
new_level();
update_turn_count();
// Reset lava/water nearness check to unknown, so it'll be
// recalculated for the next monster that tries to reach us.
you.lava_in_sight = you.water_in_sight = -1;
// Set vision radius to player's current vision.
set_los_radius(you.current_vision);
init_exclusion_los();
trackers_init_new_level(false);
if (newc) // start a new game
{
you.friendly_pickup = Options.default_friendly_pickup;
// Mark items in inventory as of unknown origin.
origin_set_inventory(origin_set_unknown);
// For a new game, wipe out monsters in LOS, and
// for new tutorial games also the items.
zap_los_monsters(Tutorial.tutorial_events[TUT_SEEN_FIRST_OBJECT]);
// For a newly started tutorial, turn secret doors into normal ones.
if (Tutorial.tutorial_left)
tutorial_zap_secret_doors();
}
#ifdef USE_TILE
tiles.initialise_items();
// Must re-run as the feature table wasn't initialised yet.
TileNewLevel(newc);
#endif
maybe_update_stashes();
// This just puts the view up for the first turn.
viewwindow(false);
activate_notes(true);
add_key_recorder(&repeat_again_rec);
return (newc);
}
// An attempt to tone down berserk a little bit. -- bwross
//
// This function does the accounting for not attacking while berserk
// This gives a triangular number function for the additional penalty
// Turn: 1 2 3 4 5 6 7 8
// Penalty: 1 3 6 10 15 21 28 36
//
// Total penalty (including the standard one during upkeep is:
// 2 5 9 14 20 27 35 44
//
static void _do_berserk_no_combat_penalty(void)
{
// Butchering/eating a corpse will maintain a blood rage.
const int delay = current_delay_action();
if (delay == DELAY_BUTCHER || delay == DELAY_EAT)
return;
if (you.berserk_penalty == NO_BERSERK_PENALTY)
return;
if (you.berserk())
{
you.berserk_penalty++;
switch (you.berserk_penalty)
{
case 2:
mpr("You feel a strong urge to attack something.", MSGCH_DURATION);
break;
case 4:
mpr("You feel your anger subside.", MSGCH_DURATION);
break;
case 6:
mpr("Your blood rage is quickly leaving you.", MSGCH_DURATION);
break;
}
// I do these three separately, because the might and
// haste counters can be different.
int berserk_delay_penalty = you.berserk_penalty * BASELINE_DELAY;
you.duration[DUR_BERSERKER] -= berserk_delay_penalty;
if (you.duration[DUR_BERSERKER] < 1)
you.duration[DUR_BERSERKER] = 1;
you.duration[DUR_MIGHT] -= berserk_delay_penalty;
if (you.duration[DUR_MIGHT] < 1)
you.duration[DUR_MIGHT] = 1;
you.duration[DUR_HASTE] -= berserk_delay_penalty;
if (you.duration[DUR_HASTE] < 1)
you.duration[DUR_HASTE] = 1;
}
return;
} // end do_berserk_no_combat_penalty()
// Called when the player moves by walking/running. Also calls attack
// function etc when necessary.
static void _move_player(int move_x, int move_y)
{
_move_player( coord_def(move_x, move_y) );
}
static void _move_player(coord_def move)
{
ASSERT(!crawl_state.arena && !crawl_state.arena_suspended);
bool attacking = false;
bool moving = true; // used to prevent eventual movement (swap)
bool swap = false;
if (you.attribute[ATTR_HELD])
{
free_self_from_net();
you.turn_is_over = true;
return;
}
// When confused, sometimes make a random move
if (you.confused())
{
dungeon_feature_type dangerous = DNGN_FLOOR;
for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
{
if (is_feat_dangerous(grd(*ai))
&& (dangerous == DNGN_FLOOR || grd(*ai) == DNGN_LAVA))
{
dangerous = grd(*ai);
}
}
if (dangerous != DNGN_FLOOR)
{
std::string prompt = "Are you sure you want to move while confused "
"and next to ";
prompt += (dangerous == DNGN_LAVA ? "lava"
: "deep water");
prompt += "? ";
if (!yesno(prompt.c_str(), false, 'n'))
{
canned_msg(MSG_OK);
return;
}
}
if (!one_chance_in(3))
{
move.x = random2(3) - 1;
move.y = random2(3) - 1;
you.reset_prev_move();
}
const coord_def& new_targ = you.pos() + move;
if (!in_bounds(new_targ) || !you.can_pass_through(new_targ))
{
you.turn_is_over = true;
mpr("Ouch!");
apply_berserk_penalty = true;
crawl_state.cancel_cmd_repeat();
return;
}
}
const coord_def& targ = you.pos() + move;
// You can't walk out of bounds!
if (!in_bounds(targ))
return;
const dungeon_feature_type targ_grid = grd(targ);
monsters* targ_monst = monster_at(targ);
if (fedhas_passthrough(targ_monst))
{
// Moving on a plant takes 1.5 x normal move delay. We
// will print a message about it but only when moving
// from open space->plant (hopefully this will cut down
// on the message spam).
you.time_taken = div_rand_round(you.time_taken * 3, 2);
monsters * current = monster_at(you.pos());
if(!current || !fedhas_passthrough(current))
{
// Probably need better messages. -cao
if(mons_genus(targ_monst->type) == MONS_FUNGUS)
{
mprf("You walk carefully through the fungus.");
}
else
mprf("You walk carefully through the plants.");
}
targ_monst = NULL;
}
const bool targ_pass = you.can_pass_through(targ);
// You can swap places with a friendly or good neutral monster if
// you're not confused, or if both of you are inside a sanctuary.
const bool can_swap_places = targ_monst
&& !mons_is_stationary(targ_monst)
&& (targ_monst->wont_attack()
&& !you.confused()
|| is_sanctuary(you.pos())
&& is_sanctuary(targ));
// You cannot move away from a mermaid but you CAN fight monsters on
// neighbouring squares.
monsters *beholder = NULL;
if (!you.confused())
beholder = you.get_beholder(targ);
if (you.running.check_stop_running())
{
// [ds] Do we need this? Shouldn't it be false to start with?
you.turn_is_over = false;
return;
}
coord_def mon_swap_dest;
if (targ_monst && !targ_monst->submerged())
{
if (can_swap_places && !beholder)
{
if (swap_check(targ_monst, mon_swap_dest))
swap = true;
else
moving = false;
}
else if (!can_swap_places) // attack!
{
// XXX: Moving into a normal wall does nothing and uses no
// turns or energy, but moving into a wall which contains
// an invisible monster attacks the monster, thus allowing
// the player to figure out which adjacent wall an invis
// monster is in "for free".
you.turn_is_over = true;
you_attack(targ_monst->mindex(), true);
// We don't want to create a penalty if there isn't
// supposed to be one.
if (you.berserk_penalty != NO_BERSERK_PENALTY)
you.berserk_penalty = 0;
attacking = true;
}
}
if (!attacking && targ_pass && moving && !beholder)
{
you.time_taken *= player_movement_speed();
you.time_taken /= 10;
if (!move_player_to_grid(targ, true, false, false, swap))
return;
if (swap)
swap_places(targ_monst, mon_swap_dest);
you.prev_move = move;
move.reset();
you.turn_is_over = true;
request_autopickup();
}
// BCR - Easy doors single move
if (Options.easy_open && !attacking && feat_is_closed_door(targ_grid))
{
_open_door(move.x, move.y, false);
you.prev_move = move;
}
else if (!targ_pass && !attacking)
{
if (grd(targ) == DNGN_OPEN_SEA)
mpr("You can't go out to sea!");
stop_running();
move.reset();
you.turn_is_over = false;
crawl_state.cancel_cmd_repeat();
return;
}
else if (beholder && !attacking)
{
mprf("You cannot move away from %s!",
beholder->name(DESC_NOCAP_THE, true).c_str());
return;
}
if (you.running == RMODE_START)
you.running = RMODE_CONTINUE;
if (you.level_type == LEVEL_ABYSS
&& (you.pos().x <= 15 || you.pos().x >= (GXM - 16)
|| you.pos().y <= 15 || you.pos().y >= (GYM - 16)))
{
area_shift();
if (you.pet_target != MHITYOU)
you.pet_target = MHITNOT;
#if DEBUG_DIAGNOSTICS
mpr("Shifting.", MSGCH_DIAGNOSTICS);
int j = 0;
for (int i = 0; i < MAX_ITEMS; ++i)
if (mitm[i].is_valid())
++j;
mprf(MSGCH_DIAGNOSTICS, "Number of items present: %d", j);
j = 0;
for (monster_iterator mi; mi; ++mi)
++j;
mprf(MSGCH_DIAGNOSTICS, "Number of monsters present: %d", j);
mprf(MSGCH_DIAGNOSTICS, "Number of clouds present: %d", env.cloud_no);
#endif
}
apply_berserk_penalty = !attacking;
if (!attacking && you.religion == GOD_CHEIBRIADOS && one_chance_in(10)
&& player_equip_ego_type(EQ_BOOTS, SPARM_RUNNING))
{
did_god_conduct(DID_HASTY, 1, true);
}
}
static int _get_num_and_char_keyfun(int &ch)
{
if (ch == CK_BKSP || isdigit(ch) || ch >= 128)
return 1;
return -1;
}
static int _get_num_and_char(const char* prompt, char* buf, int buf_len)
{
if (prompt != NULL)
mpr(prompt, MSGCH_PROMPT);
line_reader reader(buf, buf_len);
reader.set_keyproc(_get_num_and_char_keyfun);
return reader.read_line(true);
}
static void _cancel_cmd_repeat()
{
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
flush_input_buffer(FLUSH_REPLAY_SETUP_FAILURE);
}
static void _setup_cmd_repeat()
{
if (is_processing_macro())
{
flush_input_buffer(FLUSH_ABORT_MACRO);
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
return;
}
ASSERT(!crawl_state.is_repeating_cmd());
char buf[80];
// Function ensures that the buffer contains only digits.
int ch = _get_num_and_char("Number of times to repeat, then command key: ",
buf, 80);
if (ch == ESCAPE)
{
// This *might* be part of the trigger for a macro.
keyseq trigger;
trigger.push_back(ch);
if (get_macro_buf_size() == 0)
{
// Was just a single ESCAPE key, so not a macro trigger.
canned_msg( MSG_OK );
_cancel_cmd_repeat();
return;
}
ch = getchm();
trigger.push_back(ch);
// Now that we have the entirety of the (possible) macro trigger,
// clear out the keypress recorder so that we won't have recorded
// the trigger twice.
repeat_again_rec.clear();
insert_macro_into_buff(trigger);
ch = getchm();
if (ch == ESCAPE)
{
if (get_macro_buf_size() > 0)
// User pressed an Alt key which isn't bound to a macro.
mpr("That key isn't bound to a macro.");
else
// Wasn't a macro trigger, just an ordinary escape.
canned_msg( MSG_OK );
_cancel_cmd_repeat();
return;
}
// *WAS* a macro trigger, keep going.
}
if (strlen(buf) == 0)
{
mpr("You must enter the number of times for the command to repeat.");
_cancel_cmd_repeat();
return;
}
int count = atoi(buf);
if (crawl_state.doing_prev_cmd_again)
count = crawl_state.prev_cmd_repeat_goal;
if (count <= 0)
{
canned_msg( MSG_OK );
_cancel_cmd_repeat();
return;
}
if (crawl_state.doing_prev_cmd_again)
{
// If a "do previous command again" caused a command
// repetition to be redone, the keys to be repeated are
// already in the key recording buffer, so we just need to
// discard all the keys saying how many times the command
// should be repeated.
do
{
repeat_again_rec.keys.pop_front();
}
while (repeat_again_rec.keys.size() > 0
&& repeat_again_rec.keys[0] != ch);
repeat_again_rec.keys.pop_front();
}
// User can type space or enter and then the command key, in case
// they want to repeat a command bound to a number key.
c_input_reset(true);
if (ch == ' ' || ch == CK_ENTER)
{
if (!crawl_state.doing_prev_cmd_again)
repeat_again_rec.keys.pop_back();
mpr("Enter command to be repeated: ", MSGCH_PROMPT);
// Enable the cursor to read input.
cursor_control con(true);
crawl_state.waiting_for_command = true;
ch = _get_next_keycode();
crawl_state.waiting_for_command = false;
}
command_type cmd = _keycode_to_command( (keycode_type) ch);
if (cmd != CMD_MOUSE_MOVE)
c_input_reset(false);
if (!is_processing_macro() && !_cmd_is_repeatable(cmd))
{
_cancel_cmd_repeat();
return;
}
if (!crawl_state.doing_prev_cmd_again && cmd != CMD_PREV_CMD_AGAIN)
crawl_state.prev_cmd_keys = repeat_again_rec.keys;
if (is_processing_macro())
{
// Put back in first key of the expanded macro, which
// get_next_keycode() fetched
repeat_again_rec.paused = true;
macro_buf_add(ch, true);
// If we're repeating a macro, get rid the keys saying how
// many times to repeat, because the way that macros are
// repeated means that the number keys will be repeated if
// they aren't discarded.
keyseq &keys = repeat_again_rec.keys;
ch = keys[keys.size() - 1];
while (isdigit(ch) || ch == ' ' || ch == CK_ENTER)
{
keys.pop_back();
// Handle the case where the user has macroed enter to itself.
if (keys.empty())
{
_cancel_cmd_repeat();
return;
}
ch = keys[keys.size() - 1];
}
}
repeat_again_rec.paused = false;
// Discard the setup for the command repetition, since what's
// going to be repeated is yet to be typed, except for the fist
// key typed which has to be put back in (unless we're repeating a
// macro, in which case everything to be repeated is already in
// the macro buffer).
if (!is_processing_macro())
{
repeat_again_rec.clear();
macro_buf_add(ch, crawl_state.doing_prev_cmd_again);
}
crawl_state.cmd_repeat_start = true;
crawl_state.cmd_repeat_count = 0;
crawl_state.repeat_cmd = cmd;
crawl_state.cmd_repeat_goal = count;
crawl_state.prev_cmd_repeat_goal = count;
crawl_state.prev_repetition_turn = you.num_turns;
crawl_state.cmd_repeat_started_unsafe = !i_feel_safe();
crawl_state.input_line_strs.clear();
}
static void _do_prev_cmd_again()
{
if (is_processing_macro())
{
mpr("Can't re-do previous command from within a macro.");
flush_input_buffer(FLUSH_ABORT_MACRO);
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
return;
}
if (crawl_state.prev_cmd == CMD_NO_CMD)
{
mpr("No previous command to re-do.");
crawl_state.cancel_cmd_again();
crawl_state.cancel_cmd_repeat();
repeat_again_rec.clear();
return;
}
ASSERT(!crawl_state.doing_prev_cmd_again
|| (crawl_state.is_repeating_cmd()
&& crawl_state.repeat_cmd == CMD_PREV_CMD_AGAIN));
crawl_state.doing_prev_cmd_again = true;
repeat_again_rec.paused = false;
if (crawl_state.prev_cmd == CMD_REPEAT_CMD)
{
crawl_state.cmd_repeat_start = true;
crawl_state.cmd_repeat_count = 0;
crawl_state.cmd_repeat_goal = crawl_state.prev_cmd_repeat_goal;
crawl_state.prev_repetition_turn = you.num_turns;
}
const keyseq &keys = crawl_state.prev_cmd_keys;
ASSERT(keys.size() > 0);
// Insert keys at front of input buffer, rather than at the end,
// since if the player holds down the "`" key, then the buffer
// might get two "`" in a row, and if the keys to be replayed go after
// the second "`" then we get an assertion.
macro_buf_add(keys, true);
bool was_doing_repeats = crawl_state.is_repeating_cmd();
_input();
// crawl_state.doing_prev_cmd_again can be set to false
// while input() does its stuff if something causes
// crawl_state.cancel_cmd_again() to be called.
while (!was_doing_repeats && crawl_state.is_repeating_cmd()
&& crawl_state.doing_prev_cmd_again)
{
_input();
}
if (!was_doing_repeats && crawl_state.is_repeating_cmd()
&& !crawl_state.doing_prev_cmd_again)
{
crawl_state.cancel_cmd_repeat();
}
crawl_state.doing_prev_cmd_again = false;
}
static void _update_replay_state()
{
if (crawl_state.is_repeating_cmd())
{
// First repeat is to copy down the keys the user enters,
// grab them so we can go on autopilot for the remaining
// iterations.
if (crawl_state.cmd_repeat_start)
{
ASSERT(repeat_again_rec.keys.size() > 0);
crawl_state.cmd_repeat_start = false;
crawl_state.repeat_cmd_keys = repeat_again_rec.keys;
// Setting up the "previous command key sequence"
// for a repeated command is different from normal,
// since in addition to all of the keystrokes for
// the command, it needs the repeat command plus the
// number of repeats at the very beginning of the
// sequence.
if (!crawl_state.doing_prev_cmd_again)
{
keyseq &prev = crawl_state.prev_cmd_keys;
keyseq &curr = repeat_again_rec.keys;
if (is_processing_macro())
prev = curr;
else
{
// Skip first key, because that's command key that's
// being repeated, which crawl_state.prev_cmd_keys
// aleardy contains.
keyseq::iterator begin = curr.begin();
begin++;
prev.insert(prev.end(), begin, curr.end());
}
}
repeat_again_rec.paused = true;
macro_buf_add(KEY_REPEAT_KEYS);
}
}
if (!crawl_state.is_replaying_keys() && !crawl_state.cmd_repeat_start
&& crawl_state.prev_cmd != CMD_NO_CMD
&& crawl_state.prev_cmd != CMD_NEXT_CMD)
{
if (repeat_again_rec.keys.size() > 0)
crawl_state.prev_cmd_keys = repeat_again_rec.keys;
}
if (!is_processing_macro())
repeat_again_rec.clear();
}
static void _compile_time_asserts()
{
// Check that the numbering comments in enum.h haven't been
// disturbed accidentally.
COMPILE_CHECK(SK_UNARMED_COMBAT == 17 , c1);
COMPILE_CHECK(SK_EVOCATIONS == 38 , c2);
COMPILE_CHECK(SP_VAMPIRE == 30 , c3);
COMPILE_CHECK(SPELL_DEBUGGING_RAY == 102 , c4);
COMPILE_CHECK(SPELL_PETRIFY == 154 , c5);
COMPILE_CHECK(NUM_SPELLS == 198 , c6);
//jmf: NEW ASSERTS: we ought to do a *lot* of these
COMPILE_CHECK(NUM_SPECIES < SP_UNKNOWN , c7);
COMPILE_CHECK(NUM_JOBS < JOB_UNKNOWN , c8);
// Make sure there's enough room in you.unique_items to hold all
// the unrandarts.
COMPILE_CHECK(NO_UNRANDARTS < MAX_UNRANDARTS, c9);
// Non-artefact brands and unrandart indexes both go into
// item.special, so make sure they don't overlap.
COMPILE_CHECK((int) NUM_SPECIAL_WEAPONS < (int) UNRAND_START, c10);
// We have space for 32 brands in the bitfield.
COMPILE_CHECK((int) SP_UNKNOWN_BRAND < 8*sizeof(you.seen_weapon[0]), c11);
COMPILE_CHECK((int) SP_UNKNOWN_BRAND < 8*sizeof(you.seen_armour[0]), c12);
COMPILE_CHECK(NUM_SPECIAL_WEAPONS <= SP_UNKNOWN_BRAND, c13);
COMPILE_CHECK(NUM_SPECIAL_ARMOURS <= SP_UNKNOWN_BRAND, c14);
// Also some runtime stuff; I don't know if the order of branches[]
// needs to match the enum, but it currently does.
for (int i = 0; i < NUM_BRANCHES; ++i)
ASSERT(branches[i].id == i);
}