/*
* File: stuff.cc
* Summary: Misc stuff.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "stuff.h"
#include "areas.h"
#include "beam.h"
#include "cio.h"
#include "coord.h"
#include "coordit.h"
#include "database.h"
#include "directn.h"
#include "env.h"
#include "los.h"
#include "message.h"
#include "misc.h"
#include "mon-place.h"
#include "terrain.h"
#include "mgen_data.h"
#include "state.h"
#include "travel.h"
#include "view.h"
#include "viewchar.h"
#include "viewgeom.h"
#include <cstdarg>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <stack>
#ifdef UNIX
#ifndef USE_TILE
#include "libunix.h"
#endif
#endif
#include "branch.h"
#include "delay.h"
#include "externs.h"
#include "options.h"
#include "items.h"
#include "macro.h"
#include "misc.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "notes.h"
#include "output.h"
#include "player.h"
#include "religion.h"
#include "tutorial.h"
#include "view.h"
stack_iterator::stack_iterator(const coord_def& pos, bool accesible)
{
cur_link = accesible ? you.visible_igrd(pos) : igrd(pos);
if ( cur_link != NON_ITEM )
next_link = mitm[cur_link].link;
else
next_link = NON_ITEM;
}
stack_iterator::stack_iterator(int start_link)
{
cur_link = start_link;
if ( cur_link != NON_ITEM )
next_link = mitm[cur_link].link;
else
next_link = NON_ITEM;
}
stack_iterator::operator bool() const
{
return ( cur_link != NON_ITEM );
}
item_def& stack_iterator::operator*() const
{
ASSERT( cur_link != NON_ITEM );
return mitm[cur_link];
}
item_def* stack_iterator::operator->() const
{
ASSERT( cur_link != NON_ITEM );
return &mitm[cur_link];
}
int stack_iterator::link() const
{
return cur_link;
}
const stack_iterator& stack_iterator::operator ++ ()
{
cur_link = next_link;
if ( cur_link != NON_ITEM )
next_link = mitm[cur_link].link;
return *this;
}
stack_iterator stack_iterator::operator++(int dummy)
{
const stack_iterator copy = *this;
++(*this);
return copy;
}
// Crude, but functional.
std::string make_time_string(time_t abs_time, bool terse)
{
const int days = abs_time / 86400;
const int hours = (abs_time % 86400) / 3600;
const int mins = (abs_time % 3600) / 60;
const int secs = abs_time % 60;
std::ostringstream buff;
buff << std::setfill('0');
if (days > 0)
{
if (terse)
buff << days << ", ";
else
buff << days << (days > 1 ? " days" : "day");
}
buff << std::setw(2) << hours << ':'
<< std::setw(2) << mins << ':'
<< std::setw(2) << secs;
return buff.str();
}
std::string make_file_time(time_t when)
{
if (tm *loc = TIME_FN(&when))
{
return make_stringf("%04d%02d%02d-%02d%02d%02d",
loc->tm_year + 1900,
loc->tm_mon + 1,
loc->tm_mday,
loc->tm_hour,
loc->tm_min,
loc->tm_sec);
}
return ("");
}
void set_redraw_status(unsigned long flags)
{
you.redraw_status_flags |= flags;
}
static bool _tag_follower_at(const coord_def &pos)
{
if (!in_bounds(pos) || pos == you.pos())
return (false);
monsters *fmenv = monster_at(pos);
if (fmenv == NULL)
return (false);
if (fmenv->type == MONS_PLAYER_GHOST
|| !fmenv->alive()
|| fmenv->incapacitated()
|| !mons_can_use_stairs(fmenv)
|| mons_is_stationary(fmenv))
{
return (false);
}
if (!monster_habitable_grid(fmenv, DNGN_FLOOR))
return (false);
if (fmenv->speed_increment < 50)
return (false);
// Only friendly monsters, or those actively seeking the
// player, will follow up/down stairs.
if (!fmenv->friendly()
&& (!mons_is_seeking(fmenv) || fmenv->foe != MHITYOU))
{
return (false);
}
// Monsters that are not directly adjacent are subject to more
// stringent checks.
if ((pos - you.pos()).abs() > 2)
{
if (!fmenv->friendly())
return (false);
// Undead will follow Yredelemnul worshippers, and orcs will
// follow Beogh worshippers.
if ((you.religion != GOD_YREDELEMNUL && you.religion != GOD_BEOGH)
|| !is_follower(fmenv))
{
return (false);
}
}
// Monster is chasing player through stairs.
fmenv->flags |= MF_TAKING_STAIRS;
// Clear patrolling/travel markers.
fmenv->patrol_point.reset();
fmenv->travel_path.clear();
fmenv->travel_target = MTRAV_NONE;
dprf("%s is marked for following.",
fmenv->name(DESC_CAP_THE, true).c_str() );
return (true);
}
static int follower_tag_radius2()
{
// If only friendlies are adjacent, we set a max radius of 6, otherwise
// only adjacent friendlies may follow.
for (adjacent_iterator ai(you.pos()); ai; ++ai)
{
if (const monsters *mon = monster_at(*ai))
if (!mon->friendly())
return (2);
}
return (6 * 6);
}
void tag_followers()
{
const int radius2 = follower_tag_radius2();
int n_followers = 18;
std::vector<coord_def> places[2];
int place_set = 0;
places[place_set].push_back(you.pos());
memset(travel_point_distance, 0, sizeof(travel_distance_grid_t));
while (!places[place_set].empty())
{
for (int i = 0, size = places[place_set].size(); i < size; ++i)
{
const coord_def &p = places[place_set][i];
coord_def fp;
for (fp.x = p.x - 1; fp.x <= p.x + 1; ++fp.x)
for (fp.y = p.y - 1; fp.y <= p.y + 1; ++fp.y)
{
if (fp == p || (fp - you.pos()).abs() > radius2
|| !in_bounds(fp) || travel_point_distance[fp.x][fp.y])
{
continue;
}
travel_point_distance[fp.x][fp.y] = 1;
if (_tag_follower_at(fp))
{
// If we've run out of our follower allowance, bail.
if (--n_followers <= 0)
return;
places[!place_set].push_back(fp);
}
}
}
places[place_set].clear();
place_set = !place_set;
}
}
void untag_followers()
{
for (int m = 0; m < MAX_MONSTERS; ++m)
menv[m].flags &= (~MF_TAKING_STAIRS);
}
unsigned char get_ch()
{
mouse_control mc(MOUSE_MODE_MORE);
unsigned char gotched = getch();
if (gotched == 0)
gotched = getch();
return gotched;
}
void cio_init()
{
crawl_state.io_inited = true;
#if defined(UNIX) && !defined(USE_TILE)
unixcurses_startup();
#endif
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
init_libw32c();
#endif
#ifdef TARGET_OS_DOS
init_libdos();
#endif
set_cursor_enabled(false);
crawl_view.init_geometry();
#ifdef USE_TILE
tiles.resize();
#endif
if (Options.char_set == CSET_UNICODE && !crawl_state.unicode_ok)
{
crawl_state.add_startup_error(
"Unicode glyphs are not available, falling back to ASCII.");
Options.char_set = CSET_ASCII;
}
}
void cio_cleanup()
{
if (!crawl_state.io_inited)
return;
#if defined(USE_TILE)
tiles.shutdown();
#elif defined(UNIX)
unixcurses_shutdown();
#endif
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
deinit_libw32c();
#endif
msg::deinitialise_mpr_streams();
clear_globals_on_exit();
crawl_state.io_inited = false;
}
// Clear some globally defined variables.
void clear_globals_on_exit()
{
clear_rays_on_exit();
clear_zap_info_on_exit();
}
// Used by do_crash_dump() to tell if the crash happened during exit() hooks.
// Not a part of crawl_state, since that's a global C++ instance which is
// free'd by exit() hooks when exit() is called, and we don't want to reference
// free'd memory.
bool CrawlIsExiting = false;
void end(int exit_code, bool print_error, const char *format, ...)
{
std::string error = print_error? strerror(errno) : "";
cio_cleanup();
databaseSystemShutdown();
if (format)
{
va_list arg;
va_start(arg, format);
char buffer[1024];
vsnprintf(buffer, sizeof buffer, format, arg);
va_end(arg);
if (error.empty())
error = std::string(buffer);
else
error = std::string(buffer) + ": " + error;
}
if (!error.empty())
{
if (error[error.length() - 1] != '\n')
error += "\n";
fprintf(stderr, "%s", error.c_str());
error.clear();
}
#if (defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)) || \
defined(TARGET_OS_DOS) || \
defined(DGL_PAUSE_AFTER_ERROR)
if (exit_code && !crawl_state.arena
&& !crawl_state.seen_hups && !crawl_state.test)
{
fprintf(stderr, "Hit Enter to continue...\n");
getchar();
}
#endif
CrawlIsExiting = true;
exit(exit_code);
}
void redraw_screen(void)
{
if (!crawl_state.need_save)
{
// If the game hasn't started, don't do much.
clrscr();
return;
}
draw_border();
you.redraw_hit_points = true;
you.redraw_magic_points = 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.wield_change = true;
you.redraw_quiver = true;
set_redraw_status(
REDRAW_LINE_1_MASK | REDRAW_LINE_2_MASK | REDRAW_LINE_3_MASK );
print_stats();
if (Options.delay_message_clear)
mesclr(true);
bool note_status = notes_are_active();
activate_notes(false);
new_level();
#ifdef DGL_SIMPLE_MESSAGING
update_message_status();
#endif
update_turn_count();
activate_notes(note_status);
viewwindow(false);
}
// STEPDOWN FUNCTION to replace conditional chains in spells2.cc 12jan2000 {dlb}
// it is a bit more extensible and optimises the logical structure, as well
// usage: cast_summon_swarm() cast_haunt() cast_summon_scorpions()
// cast_summon_horrible_things()
// ex(1): stepdown_value (foo, 2, 2, 6, 8) replaces the following block:
//
/*
if (foo > 2)
foo = (foo - 2) / 2 + 2;
if (foo > 4)
foo = (foo - 4) / 2 + 4;
if (foo > 6)
foo = (foo - 6) / 2 + 6;
if (foo > 8)
foo = 8;
*/
//
// ex(2): bar = stepdown_value(bar, 2, 2, 6, -1) replaces the following block:
//
/*
if (bar > 2)
bar = (bar - 2) / 2 + 2;
if (bar > 4)
bar = (bar - 4) / 2 + 4;
if (bar > 6)
bar = (bar - 6) / 2 + 6;
*/
// I hope this permits easier/more experimentation with value stepdowns
// in the code. It really needs to be rewritten to accept arbitrary
// (unevenly spaced) steppings.
int stepdown_value(int base_value, int stepping, int first_step,
int last_step, int ceiling_value)
{
int return_value = base_value;
// values up to the first "step" returned unchanged:
if (return_value <= first_step)
return return_value;
for (int this_step = first_step; this_step <= last_step;
this_step += stepping)
{
if (return_value > this_step)
return_value = ((return_value - this_step) / 2) + this_step;
else
break; // exit loop iff value fully "stepped down"
}
// "no final ceiling" == -1
if (ceiling_value != -1 && return_value > ceiling_value)
return ceiling_value; // highest value to return is "ceiling"
else
return return_value; // otherwise, value returned "as is"
}
int skill_bump( int skill )
{
return ((you.skills[skill] < 3) ? you.skills[skill] * 2
: you.skills[skill] + 3);
}
// This gives (default div = 20, shift = 3):
// - shift/div% @ stat_level = 0; (default 3/20 = 15%, or 20% at stat 1)
// - even (100%) @ stat_level = div - shift; (default 17)
// - 1/div% per stat_level (default 1/20 = 5%)
int stat_mult( int stat_level, int value, int div, int shift )
{
return (((stat_level + shift) * value) / ((div > 1) ? div : 1));
}
// As above but inverted (ie 5x penalty at stat 1)
int stat_div( int stat_level, int value, int mult, int shift )
{
int div = stat_level + shift;
if (div < 1)
div = 1;
return ((mult * value) / div);
}
int div_round_up(int num, int den)
{
return (num / den + (num % den != 0));
}
// Simple little function to quickly modify all three stats
// at once - does check for '0' modifiers to prevent needless
// adding .. could use checking for sums less than zero, I guess.
// Used in conjunction with newgame::species_stat_init() and
// newgame::job_stat_init() routines 24jan2000. {dlb}
void modify_all_stats(int STmod, int IQmod, int DXmod)
{
if (STmod)
{
you.strength += STmod;
you.max_strength += STmod;
you.redraw_strength = true;
}
if (IQmod)
{
you.intel += IQmod;
you.max_intel += IQmod;
you.redraw_intelligence = true;
}
if (DXmod)
{
you.dex += DXmod;
you.max_dex += DXmod;
you.redraw_dexterity = true;
}
}
void canned_msg(canned_message_type which_message)
{
switch (which_message)
{
case MSG_SOMETHING_APPEARS:
mprf("Something appears %s!",
(you.species == SP_NAGA || player_mutation_level(MUT_HOOVES))
? "before you" : "at your feet");
break;
case MSG_NOTHING_HAPPENS:
mpr("Nothing appears to happen.");
break;
case MSG_YOU_RESIST:
mpr("You resist.");
learned_something_new(TUT_YOU_RESIST);
break;
case MSG_YOU_PARTIALLY_RESIST:
mpr("You partially resist.");
break;
case MSG_TOO_BERSERK:
mpr("You are too berserk!");
crawl_state.cancel_cmd_repeat();
break;
case MSG_PRESENT_FORM:
mpr("You can't do that in your present form.");
crawl_state.cancel_cmd_repeat();
break;
case MSG_NOTHING_CARRIED:
mpr("You aren't carrying anything.");
crawl_state.cancel_cmd_repeat();
break;
case MSG_CANNOT_DO_YET:
mpr("You can't do that yet.");
crawl_state.cancel_cmd_repeat();
break;
case MSG_OK:
mpr("Okay, then.", MSGCH_PROMPT);
crawl_state.cancel_cmd_repeat();
break;
case MSG_UNTHINKING_ACT:
mpr("Why would you want to do that?");
crawl_state.cancel_cmd_repeat();
break;
case MSG_SPELL_FIZZLES:
mpr("The spell fizzles.");
break;
case MSG_HUH:
mpr("Huh?", MSGCH_EXAMINE_FILTER);
crawl_state.cancel_cmd_repeat();
break;
case MSG_EMPTY_HANDED:
mpr("You are now empty-handed.");
break;
}
}
// Like yesno, but requires a full typed answer.
// Unlike yesno, prompt should have no trailing space.
// Returns true if the user typed "yes", false if something else or cancel.
bool yes_or_no( const char* fmt, ... )
{
char buf[200];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof buf, fmt, args);
va_end(args);
buf[sizeof(buf)-1] = 0;
mprf(MSGCH_PROMPT, "%s? (Confirm with \"yes\".) ", buf);
if (cancelable_get_line(buf, sizeof buf))
return (false);
if (strcasecmp(buf, "yes") != 0)
return (false);
return (true);
}
// jmf: general helper (should be used all over in code)
// -- idea borrowed from Nethack
bool yesno( const char *str, bool safe, int safeanswer, bool clear_after,
bool interrupt_delays, bool noprompt,
const explicit_keymap *map )
{
if (interrupt_delays && !crawl_state.is_repeating_cmd())
interrupt_activity( AI_FORCE_INTERRUPT );
std::string prompt = make_stringf("%s ", str ? str : "Buggy prompt?");
while (true)
{
if (!noprompt)
mpr(prompt.c_str(), MSGCH_PROMPT);
int tmp = getchm(KMC_CONFIRM);
#if defined(USE_UNIX_SIGNALS) && defined(SIGHUP_SAVE) && defined(USE_CURSES)
// Prevent infinite loop if Curses HUP signal handling happens;
// if there is no safe answer, then just save-and-exit immediately,
// since there's no way to know if it would be better to return
// true or false.
if (crawl_state.seen_hups && !safeanswer)
sighup_save_and_exit();
#endif
if (map && map->find(tmp) != map->end())
tmp = map->find(tmp)->second;
if (safeanswer
&& (tmp == ' ' || tmp == ESCAPE || tmp == CONTROL('G')
|| tmp == '\r' || tmp == '\n' || crawl_state.seen_hups))
{
tmp = safeanswer;
}
if (Options.easy_confirm == CONFIRM_ALL_EASY
|| tmp == safeanswer
|| Options.easy_confirm == CONFIRM_SAFE_EASY && safe)
{
tmp = toupper( tmp );
}
if (clear_after)
mesclr();
if (tmp == 'N')
return (false);
else if (tmp == 'Y')
return (true);
else if (!noprompt)
mpr("[Y]es or [N]o only, please.");
}
}
static std::string _list_alternative_yes(char yes1, char yes2,
bool lowered = false,
bool brackets = false)
{
std::string help = "";
bool print_yes = false;
if (yes1 != 'Y')
{
if (lowered)
help += tolower(yes1);
else
help += yes1;
print_yes = true;
}
if (yes2 != 'Y' && yes2 != yes1)
{
if (print_yes)
help += "/";
if (lowered)
help += tolower(yes2);
else
help += yes2;
print_yes = true;
}
if (print_yes)
{
if (brackets)
help = " (" + help + ")";
else
help = "/" + help;
}
return help;
}
static std::string _list_allowed_keys(char yes1, char yes2,
bool lowered = false,
bool allow_all = false)
{
std::string result = " [";
result += (lowered ? "y" : "Y");
result += _list_alternative_yes(yes1, yes2, lowered);
if (allow_all)
result += (lowered? "/a" : "/A");
result += (lowered ? "/n/q" : "/N/Q");
result += "]";
return (result);
}
// Like yesno(), but returns 0 for no, 1 for yes, and -1 for quit.
// alt_yes and alt_yes2 allow up to two synonyms for 'Y'.
// FIXME: This function is shaping up to be a monster. Help!
int yesnoquit( const char* str, bool safe, int safeanswer, bool allow_all,
bool clear_after, char alt_yes, char alt_yes2 )
{
if (!crawl_state.is_repeating_cmd())
interrupt_activity( AI_FORCE_INTERRUPT );
std::string prompt =
make_stringf("%s%s ", str ? str : "Buggy prompt?",
_list_allowed_keys(alt_yes, alt_yes2,
safe, allow_all).c_str());
while (true)
{
mpr(prompt.c_str(), MSGCH_PROMPT);
int tmp = getchm(KMC_CONFIRM);
if (tmp == CK_ESCAPE || tmp == CONTROL('G') || tmp == 'q' || tmp == 'Q'
|| crawl_state.seen_hups)
{
return -1;
}
if ((tmp == ' ' || tmp == '\r' || tmp == '\n') && safeanswer)
tmp = safeanswer;
if (Options.easy_confirm == CONFIRM_ALL_EASY
|| tmp == safeanswer
|| safe && Options.easy_confirm == CONFIRM_SAFE_EASY)
{
tmp = toupper( tmp );
}
if (clear_after)
mesclr();
if (tmp == 'N')
return 0;
else if (tmp == 'Y' || tmp == alt_yes || tmp == alt_yes2)
return 1;
else if (allow_all)
{
if (tmp == 'A')
return 2;
else
mprf("Choose [Y]es%s, [N]o, [Q]uit, or [A]ll!",
_list_alternative_yes(alt_yes, alt_yes2, false, true).c_str());
}
else
{
mprf("[Y]es%s, [N]o or [Q]uit only, please.",
_list_alternative_yes(alt_yes, alt_yes2, false, true).c_str());
}
}
}
bool player_can_hear(const coord_def& p)
{
return (!silenced(p) && !silenced(you.pos()));
}
char index_to_letter(int the_index)
{
return (the_index + ((the_index < 26) ? 'a' : ('A' - 26)));
}
int letter_to_index(int the_letter)
{
if (the_letter >= 'a' && the_letter <= 'z')
// returns range [0-25] {dlb}
the_letter -= 'a';
else if (the_letter >= 'A' && the_letter <= 'Z')
// returns range [26-51] {dlb}
the_letter -= ('A' - 26);
return (the_letter);
}
// Returns 0 if the point is not near stairs.
// Returns 1 if the point is near unoccupied stairs.
// Returns 2 if the point is near player-occupied stairs.
int near_stairs(const coord_def &p, int max_dist,
dungeon_char_type &stair_type, branch_type &branch)
{
for (radius_iterator ri(p, max_dist, true, false); ri; ++ri)
{
const dungeon_feature_type feat = grd(*ri);
if (feat_is_stair(feat))
{
// Shouldn't happen for escape hatches.
if (feat_is_escape_hatch(feat))
continue;
stair_type = get_feature_dchar(feat);
// Is it a branch stair?
for (int i = 0; i < NUM_BRANCHES; ++i)
{
if (branches[i].entry_stairs == feat)
{
branch = branches[i].id;
break;
}
else if (branches[i].exit_stairs == feat)
{
branch = branches[i].parent_branch;
break;
}
}
return (*ri == you.pos()) ? 2 : 1;
}
}
return (false);
}
bool is_trap_square(dungeon_feature_type grid)
{
return (grid >= DNGN_TRAP_MECHANICAL && grid <= DNGN_UNDISCOVERED_TRAP);
}
// Does the equivalent of KILL_RESET on all monsters in LOS. Should only be
// applied to new games.
void zap_los_monsters(bool items_also)
{
// Not using player LOS since clouds might temporarily
// block monsters.
los_def los(you.pos(), opc_fullyopaque);
los.update();
for (radius_iterator ri(&los); ri; ++ri)
{
if (items_also)
{
int item = igrd(*ri);
if (item != NON_ITEM && mitm[item].is_valid() )
destroy_item(item);
}
// If we ever allow starting with a friendly monster,
// we'll have to check here.
monsters *mon = monster_at(*ri);
if (mon == NULL || mons_class_flag(mon->type, M_NO_EXP_GAIN))
continue;
dprf("Dismissing %s",
mon->name(DESC_PLAIN, true).c_str() );
// Do a hard reset so the monster's items will be discarded.
mon->flags |= MF_HARD_RESET;
// Do a silent, wizard-mode monster_die() just to be extra sure the
// player sees nothing.
monster_die(mon, KILL_DISMISSED, NON_MONSTER, true, true);
}
}
int integer_sqrt(int value)
{
if (value <= 0)
return (value);
int very_old_retval = -1;
int old_retval = 0;
int retval = 1;
while (very_old_retval != old_retval
&& very_old_retval != retval
&& retval != old_retval)
{
very_old_retval = old_retval;
old_retval = retval;
retval = (value / old_retval + old_retval) / 2;
}
return (retval);
}
int random_rod_subtype()
{
return STAFF_FIRST_ROD + random2(NUM_STAVES - STAFF_FIRST_ROD);
}
maybe_bool frombool(bool b)
{
return (b ? B_TRUE : B_FALSE);
}
bool tobool(maybe_bool mb)
{
ASSERT (mb != B_MAYBE);
return (mb == B_TRUE);
}
bool tobool(maybe_bool mb, bool def)
{
switch (mb)
{
case B_TRUE:
return (true);
case B_FALSE:
return (false);
case B_MAYBE:
default:
return (def);
}
}