git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7 c06c8d41-db1a-0410-9941-cceddc491573
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
env.map[ count_x + you.x_pos - 9 ]
[ count_y + you.y_pos - 9 ] = buffy[bufcount];
unsigned short bch = buffy[bufcount];
if (mgrd[mapx + 1][mapy + 1] != NON_MONSTER) {
const monsters &m = menv[mgrd[mapx + 1][mapy + 1]];
if (!mons_is_mimic(m.type)
&& mons_char(m.type) == bch)
{
bch |= mons_colour(m.type) << 12;
}
}
env.map[mapx][mapy] = bch;
char colour_code_map( int x, int y )
short dist = point_distance[x + 1]
[y + 1];
return dist > 0 ? BLUE :
dist == PD_EXCLUDED ? LIGHTMAGENTA :
dist == PD_EXCLUDED_RADIUS ? RED :
dist < 0 ? CYAN :
DARKGREY;
}
#if defined(WIN32CONSOLE) || defined(DOS)
static unsigned short dos_reverse_brand(unsigned short colour)
{
if (Options.dos_use_background_intensity)
{
// If the console treats the intensity bit on background colours
// correctly, we can do a very simple colour invert.
// Special casery for shadows. Note this must be matched by the fix
// to libw32c.cc (the unpatched libw32c.cc does not draw spaces of any
// colour).
if (colour == BLACK)
colour = (DARKGREY << 4);
else
colour = (colour & 0xF) << 4;
}
else
{
// If we're on a console that takes its DOSness very seriously the
// background high-intensity bit is actually a blink bit. Blinking is
// evil, so we strip the background high-intensity bit. This, sadly,
// limits us to 7 background colours.
// Strip off high-intensity bit. Special case DARKGREY, since it's the
// high-intensity counterpart of black, and we don't want black on
// black.
//
// We *could* set the foreground colour to WHITE if the background
// intensity bit is set, but I think we've carried the
// angry-fruit-salad theme far enough already.
if (colour == DARKGREY)
colour |= (LIGHTGREY << 4);
else if (colour == BLACK)
colour = LIGHTGREY << 4;
else
{
// Zap out any existing background colour, and the high
// intensity bit.
colour &= 7;
// And swap the foreground colour over to the background
// colour, leaving the foreground black.
colour <<= 4;
}
}
return (colour);
}
static unsigned short dos_hilite_brand(unsigned short colour,
unsigned short hilite)
{
if (!hilite)
return (colour);
if (colour == hilite)
colour = 0;
colour |= (hilite << 4);
return (colour);
}
static unsigned short dos_brand( unsigned short colour,
unsigned brand = CHATTR_REVERSE )
{
if ((brand & CHATTR_ATTRMASK) == CHATTR_NORMAL)
return (colour);
colour &= 0xFF;
if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE)
return dos_hilite_brand(colour, (brand & CHATTR_COLMASK) >> 8);
else
return dos_reverse_brand(colour);
}
#endif
screen_buffer_t colour_code_map( int x, int y, bool item_colour,
bool travel_colour )
if (map_flags & ENVF_DETECT_ITEM)
tc = Options.detected_item_colour;
if (map_flags & ENVF_DETECT_MONS) {
tc = Options.detected_monster_colour;
return (tc);
}
unsigned char ecol = ENVF_COLOR(map_flags);
if (ecol) {
unsigned rmc = Options.remembered_monster_colour & 0xFFFF;
if (rmc == 0xFFFF) // Use real colour
tc = ecol;
else if (rmc == 0) // Don't colour
;
else
tc = rmc;
}
// XXX: [ds] If we've an important colour, override other feature
// colouring. Yes, this is hacky. Story of my life.
if (tc == LIGHTGREEN || tc == LIGHTMAGENTA)
return tc;
if (map_value != mapch2( grid_value ))
return (DARKGREY);
if (map_value != mapch2( grid_value )) {
// If there's an item on this square, change colour to indicate
// that, iff the item's glyph matches map_value. XXX: Potentially
// abusable? -- ds
int item = igrd[x + 1][y + 1];
if (item_colour && item != NON_ITEM
&& map_value == display_glyph(item_env_glyph(mitm[item])))
{
screen_buffer_t ic = mitm[item].colour;
#if defined(WIN32CONSOLE) || defined(DOS) || defined(DOS_TERM)
if (mitm[item].link != NON_ITEM
&& Options.heap_brand != CHATTR_NORMAL)
{
ic = dos_brand(ic, Options.heap_brand);
}
#elif defined(USE_COLOUR_OPTS)
if (mitm[item].link != NON_ITEM )
{
ic |= COLFLAG_ITEM_HEAP;
}
#endif
// If the item colour is the background colour, tweak it to WHITE
// instead to catch the player's eye.
return ic == tc? WHITE : ic;
}
return tc;
}
void clear_map()
{
for (int y = 0; y < GYM - 1; ++y)
{
for (int x = 0; x < GXM - 1; ++x)
{
unsigned short envc = env.map[x][y];
if (!envc)
continue;
bool unmapped = (envc & ENVF_DETECTED) != 0;
// Discard flags at this point.
envc = (unsigned char) envc;
const unsigned char &grdc = grd[x + 1][y + 1];
if (envc == mapch(grdc) || envc == mapch2(grdc))
continue;
// Friendly monsters, mimics, or harmless monsters
// don't disturb the player's running/resting.
//
// Doing it this way causes players in run mode 2
// to move one square, and in mode 1 to stop. This
// means that the character will run one square if
// a monster is in sight... we automatically jump
// to zero if we're resting. -- bwr
if (you.run_x == 0 && you.run_y == 0)
you.running = 0;
else
you.running--;
interrupt_activity( AI_SEE_MONSTER );
if (you.running != 0
#ifdef CLUA_BINDINGS
&& clua.callbooleanfn(true, "ch_stop_run",
"M", monster)
#endif
)
{
// Friendly monsters, mimics, or harmless monsters
// don't disturb the player's running/resting.
//
// Doing it this way causes players in run mode 2
// to move one square, and in mode 1 to stop. This
// means that the character will run one square if
// a monster is in sight... we automatically jump
// to zero if we're resting. -- bwr
if (you.run_x == 0 && you.run_y == 0)
stop_running();
else if (you.running > 1)
you.running--;
else
stop_running();
}
else if (Options.stab_brand != CHATTR_NORMAL
&& !mons_is_mimic(monster->type)
&& monster->type != MONS_OKLOB_PLANT
&& mons_is_stabbable(monster))
{
env.show_col[monster->x - you.x_pos + 9]
[monster->y - you.y_pos + 9]
|= COLFLAG_WILLSTAB;
}
else if (Options.may_stab_brand != CHATTR_NORMAL
&& !mons_is_mimic(monster->type)
&& monster->type != MONS_OKLOB_PLANT
&& mons_maybe_stabbable(monster))
{
env.show_col[monster->x - you.x_pos + 9]
[monster->y - you.y_pos + 9]
|= COLFLAG_MAYSTAB;
}
#elif defined(WIN32CONSOLE) || defined(DOS)
if (Options.friend_brand != CHATTR_NORMAL
&& mons_friendly(monster))
{
// We munge the colours right here for DOS and Windows, because
// we know exactly how the colours will be handled, and we don't
// want to change both DOS and Windows port code to handle
// friend branding.
unsigned short &colour =
env.show_col[monster->x - you.x_pos + 9]
[monster->y - you.y_pos + 9];
colour = dos_brand(colour, Options.friend_brand);
}
if (Options.stab_brand != CHATTR_NORMAL
&& !mons_is_mimic(monster->type)
&& monster->type != MONS_OKLOB_PLANT
&& mons_is_stabbable(monster))
{
unsigned short &colour =
env.show_col[monster->x - you.x_pos + 9]
[monster->y - you.y_pos + 9];
colour = dos_brand(colour, Options.stab_brand);
}
else if (Options.may_stab_brand != CHATTR_NORMAL
&& !mons_is_mimic(monster->type)
&& monster->type != MONS_OKLOB_PLANT
&& mons_maybe_stabbable(monster))
{
unsigned short &colour =
env.show_col[monster->x - you.x_pos + 9]
[monster->y - you.y_pos + 9];
colour = dos_brand(colour, Options.may_stab_brand);
}
static int item_env_glyph(const item_def &item)
{
switch (item.base_type)
{
case OBJ_ORBS:
return 256;
// need + 6 because show is 0 - 12, not -6 - +6
case OBJ_WEAPONS:
case OBJ_MISSILES:
return 258;
case OBJ_ARMOUR:
return 259;
case OBJ_WANDS:
return 260;
case OBJ_FOOD:
return 261;
case OBJ_UNKNOWN_I:
return 262;
case OBJ_SCROLLS:
return 263;
case OBJ_JEWELLERY:
return item.sub_type >= AMU_RAGE? 273 : 264;
case OBJ_POTIONS:
return 265;
case OBJ_UNKNOWN_II:
return 266;
case OBJ_BOOKS:
return 267;
case OBJ_STAVES:
return 269;
case OBJ_MISCELLANY:
return 270;
case OBJ_CORPSES:
return 271;
case OBJ_GOLD:
return 272;
default:
return '8';
}
}
case OBJ_ORBS:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 256;
break;
// need + 6 because show is 0 - 12, not -6 - +6
case OBJ_WEAPONS:
case OBJ_MISSILES:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 258;
break;
case OBJ_ARMOUR:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 259;
break;
case OBJ_WANDS:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 260;
break;
case OBJ_FOOD:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 261;
break;
case OBJ_UNKNOWN_I:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 262;
break;
case OBJ_SCROLLS:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 263;
break;
case OBJ_JEWELLERY:
if (mitm[igrd[count_x][count_y]].sub_type >= AMU_RAGE)
{
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 273;
}
else
{
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 264;
}
break;
case OBJ_POTIONS:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 265;
break;
case OBJ_UNKNOWN_II:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 266;
break;
case OBJ_BOOKS:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 267;
break;
case OBJ_STAVES:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 269;
break;
case OBJ_MISCELLANY:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 270;
break;
case OBJ_CORPSES:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 271;
break;
case OBJ_GOLD:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = 272;
env.show_col[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = YELLOW;
break;
default:
env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] = '8';
break;
// Yes, exact same code as friend-branding.
ecol = dos_brand(ecol, Options.heap_brand);
// Determines if the given feature is present at (x, y) in _grid_ coordinates.
// If you have map coords, add (1, 1) to get grid coords.
// Use one of
// 1. '<' and '>' to look for stairs
// 2. '\t' or '\\' for shops, portals.
// 3. '^' for traps
// 4. '_' for altars
// 5. Anything else will look for the exact same character in the level map.
bool is_feature(int feature, int x, int y) {
unsigned char envfeat = (unsigned char) env.map[x - 1][y - 1];
if (!envfeat)
return false;
// 'grid' can fit in an unsigned char, but making this a short shuts up
// warnings about out-of-range case values.
short grid = grd[x][y];
switch (feature) {
case 'X':
return (point_distance[x][y] == PD_EXCLUDED);
case 'F':
case 'W':
return is_waypoint(x, y);
#ifdef STASH_TRACKING
case 'I':
return is_stash(x, y);
#endif
case '_':
switch (grid) {
case DNGN_ALTAR_ZIN:
case DNGN_ALTAR_SHINING_ONE:
case DNGN_ALTAR_KIKUBAAQUDGHA:
case DNGN_ALTAR_YREDELEMNUL:
case DNGN_ALTAR_XOM:
case DNGN_ALTAR_VEHUMET:
case DNGN_ALTAR_OKAWARU:
case DNGN_ALTAR_MAKHLEB:
case DNGN_ALTAR_SIF_MUNA:
case DNGN_ALTAR_TROG:
case DNGN_ALTAR_NEMELEX_XOBEH:
case DNGN_ALTAR_ELYVILON:
return true;
default:
return false;
}
case '\t':
case '\\':
switch (grid) {
case DNGN_ENTER_HELL:
case DNGN_ENTER_LABYRINTH:
case DNGN_ENTER_SHOP:
case DNGN_ENTER_DIS:
case DNGN_ENTER_GEHENNA:
case DNGN_ENTER_COCYTUS:
case DNGN_ENTER_TARTARUS:
case DNGN_ENTER_ABYSS:
case DNGN_EXIT_ABYSS:
case DNGN_STONE_ARCH:
case DNGN_ENTER_PANDEMONIUM:
case DNGN_EXIT_PANDEMONIUM:
case DNGN_TRANSIT_PANDEMONIUM:
case DNGN_ENTER_ZOT:
case DNGN_RETURN_FROM_ZOT:
return true;
default:
return false;
}
case '<':
switch (grid) {
case DNGN_ROCK_STAIRS_UP:
case DNGN_STONE_STAIRS_UP_I:
case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III:
case DNGN_RETURN_FROM_ORCISH_MINES:
case DNGN_RETURN_FROM_HIVE:
case DNGN_RETURN_FROM_LAIR:
case DNGN_RETURN_FROM_SLIME_PITS:
case DNGN_RETURN_FROM_VAULTS:
case DNGN_RETURN_FROM_CRYPT:
case DNGN_RETURN_FROM_HALL_OF_BLADES:
case DNGN_RETURN_FROM_TEMPLE:
case DNGN_RETURN_FROM_SNAKE_PIT:
case DNGN_RETURN_FROM_ELVEN_HALLS:
case DNGN_RETURN_FROM_TOMB:
case DNGN_RETURN_FROM_SWAMP:
return true;
default:
return false;
}
case '>':
switch (grid) {
case DNGN_ROCK_STAIRS_DOWN:
case DNGN_STONE_STAIRS_DOWN_I:
case DNGN_STONE_STAIRS_DOWN_II:
case DNGN_STONE_STAIRS_DOWN_III:
case DNGN_ENTER_ORCISH_MINES:
case DNGN_ENTER_HIVE:
case DNGN_ENTER_LAIR:
case DNGN_ENTER_SLIME_PITS:
case DNGN_ENTER_VAULTS:
case DNGN_ENTER_CRYPT:
case DNGN_ENTER_HALL_OF_BLADES:
case DNGN_ENTER_TEMPLE:
case DNGN_ENTER_SNAKE_PIT:
case DNGN_ENTER_ELVEN_HALLS:
case DNGN_ENTER_TOMB:
case DNGN_ENTER_SWAMP:
return true;
default:
return false;
}
case '^':
switch (grid) {
case DNGN_TRAP_MECHANICAL:
case DNGN_TRAP_MAGICAL:
case DNGN_TRAP_III:
return true;
default:
return false;
}
default:
return envfeat == feature;
}
}
static int find_feature(unsigned char feature, int curs_x, int curs_y,
int start_x, int start_y, int anchor_x, int anchor_y,
int ignore_count, char *move_x, char *move_y) {
int cx = anchor_x,
cy = anchor_y;
int firstx = -1, firsty = -1;
int matchcount = 0;
// Find the first occurrence of feature 'feature', spiralling around (x,y)
int maxradius = GXM > GYM? GXM : GYM;
for (int radius = 1; radius < maxradius; ++radius) {
for (int axis = -2; axis < 2; ++axis) {
int rad = radius - (axis < 0);
for (int var = -rad; var <= rad; ++var) {
int dx = radius, dy = var;
if (axis % 2)
dx = -dx;
if (axis < 0) {
int temp = dx;
dx = dy;
dy = temp;
}
int x = cx + dx, y = cy + dy;
if (x < 0 || y < 0 || x >= GXM || y >= GYM) continue;
if (is_feature(feature, x + 1, y + 1)) {
++matchcount;
if (!ignore_count--) {
// We want to cursor to (x,y)
*move_x = x - (start_x + curs_x - 1);
*move_y = y - (start_y + curs_y - 1);
return matchcount;
}
else if (firstx == -1) {
firstx = x;
firsty = y;
}
}
}
}
}
// We found something, but ignored it because of an ignorecount
if (firstx != -1) {
*move_x = firstx - (start_x + curs_x - 1);
*move_y = firsty - (start_y + curs_y - 1);
return 1;
}
return 0;
}
void find_features(const std::vector<coord_def>& features,
unsigned char feature, std::vector<coord_def> *found) {
for (unsigned feat = 0; feat < features.size(); ++feat) {
const coord_def& coord = features[feat];
if (is_feature(feature, coord.x, coord.y))
found->push_back(coord);
}
}
static int find_feature( const std::vector<coord_def>& features,
unsigned char feature, int curs_x, int curs_y,
int start_x, int start_y,
int ignore_count, char *move_x, char *move_y) {
int firstx = -1, firsty = -1;
int matchcount = 0;
for (unsigned feat = 0; feat < features.size(); ++feat) {
const coord_def& coord = features[feat];
if (is_feature(feature, coord.x, coord.y)) {
++matchcount;
if (!ignore_count--) {
// We want to cursor to (x,y)
*move_x = coord.x - (start_x + curs_x);
*move_y = coord.y - (start_y + curs_y);
return matchcount;
}
else if (firstx == -1) {
firstx = coord.x;
firsty = coord.y;
}
}
}
// We found something, but ignored it because of an ignorecount
if (firstx != -1) {
*move_x = firstx - (start_x + curs_x);
*move_y = firsty - (start_y + curs_y);
return 1;
}
return 0;
}
// Vector to track all features we can travel to, in order of distance.
std::vector<coord_def> features;
if (!spec_place[0]) {
travel_cache.update();
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
// Sort features into the order the player is likely to prefer.
arrange_features(features);
}
buffer2[bufcount2] = env.map[start_x + i][start_y + j];
buffer2[bufcount2] =
(unsigned char) env.map[start_x + i][start_y + j];
// If we've a waypoint on the current square, *and* the square is
// a normal floor square with nothing on it, show the waypoint
// number.
if (Options.show_waypoints)
{
// XXX: This is a horrible hack.
screen_buffer_t &bc = buffer2[bufcount2];
int gridx = start_x + i + 1, gridy = start_y + j + 1;
unsigned char ch = is_waypoint(gridx, gridy);
if (ch && (bc == mapch2(DNGN_FLOOR) ||
bc == mapch(DNGN_FLOOR)))
bc = ch;
}
#endif
&& (getty < '0' || getty > '9'))
// Keystrokes to initiate travel
&& getty != ',' && getty != '.' && getty != '\r' && getty != ';'
// Keystrokes for jumping to features
&& getty != '<' && getty != '>' && getty != '@' && getty != '\t'
&& getty != '^' && getty != '_'
&& (getty < '0' || getty > '9')
&& getty != CONTROL('X')
&& getty != CONTROL('E')
&& getty != CONTROL('F')
&& getty != CONTROL('W')
&& getty != CONTROL('C')
&& getty != 'X' && getty != 'F' && getty != 'I' && getty != 'W')
#endif
&& getty != '.' && getty != 'S' && (getty < '0' || getty > '9'))
&& getty != '.' && getty != 'S' && (getty < '0' || getty > '9')
// Keystrokes for jumping to features
&& getty != '<' && getty != '>' && getty != '@' && getty != '\t'
&& getty != '^' && getty != '_')
getty = getch();
getty = getchm(KC_LEVELMAP);
#ifdef WIN32CONSOLE
// Translate shifted numpad to shifted vi keys. Yes,
// this is horribly hacky.
{
static int win_keypad[] = { 'B', 'J', 'N',
'H', '5', 'L',
'Y', 'K', 'U' };
if (getty >= '1' && getty <= '9')
getty = win_keypad[ getty - '1' ];
}
#endif
case CONTROL('C'):
clear_map();
break;
case CONTROL('F'):
case CONTROL('W'):
travel_cache.add_waypoint(start_x + curs_x, start_y + curs_y);
// We need to do this all over again so that the user can jump
// to the waypoint he just created.
features.clear();
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
// Sort features into the order the player is likely to prefer.
arrange_features(features);
move_x = move_y = 0;
break;
case CONTROL('E'):
case CONTROL('X'):
{
int x = start_x + curs_x, y = start_y + curs_y;
if (getty == CONTROL('X'))
toggle_exclude(x, y);
else
clear_excludes();
// We now need to redo travel colours
features.clear();
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
// Sort features into the order the player is likely to prefer.
arrange_features(features);
move_x = move_y = 0;
}
break;
break;
#ifndef UNIX
// This is old DOS keypad support
case 'H':
move_y = -1;
move_x = 0;
break;
case 'P':
move_y = 1;
move_x = 0;
break;
case 'K':
move_x = -1;
move_y = 0;
break;
case 'M':
move_x = 1;
move_y = 0;
break;
case 'O':
move_x = -1;
move_y = 1;
break;
case 'I':
move_x = 1;
move_y = -1;
break;
case 'G':
move_y = -1;
move_x = -1;
break;
case 'Q':
move_y = 1;
move_x = 1;
break;
case '<':
case '>':
case '@':
case '\t':
case '^':
case '_':
case 'X':
case 'F':
case 'W':
case 'I':
move_x = 0;
move_y = 0;
if (anchor_x == -1) {
anchor_x = start_x + curs_x - 1;
anchor_y = start_y + curs_y - 1;
}
if (search_feat != getty) {
search_feat = getty;
search_found = 0;
}
if (!spec_place[0])
search_found = find_feature(features, getty, curs_x, curs_y,
start_x, start_y,
search_found, &move_x, &move_y);
else
search_found = find_feature(getty, curs_x, curs_y,
start_x, start_y,
anchor_x, anchor_y,
search_found, &move_x, &move_y);
spec_place[0] = start_x + curs_x;
spec_place[1] = start_y + curs_y;
goto putty;
case ',':
case ';':
{
int x = start_x + curs_x, y = start_y + curs_y;
if (!spec_place[0] && x == you.x_pos && y == you.y_pos)
{
if (you.travel_x > 0 && you.travel_y > 0) {
move_x = you.travel_x - x;
move_y = you.travel_y - y;
}
break;
}
else {
spec_place[0] = x;
spec_place[1] = y;
goto putty;
}
}
env.map[count_x + you.x_pos - 9]
[count_y + you.y_pos - 9] = buffy[bufcount];
unsigned short bch = buffy[bufcount];
if (mgrd[enx + 1][eny + 1] != NON_MONSTER) {
const monsters &m = menv[ mgrd[enx + 1][eny + 1] ];
if (!mons_is_mimic(m.type)
&& mons_char(m.type) == bch)
{
bch |= mons_colour(m.type) << 12;
}
}
env.map[enx][eny] = bch;
"This trident was stolen many years ago from the Octopus's garden "
"by one really unimportant and already dead man. But beware of "
"Octopus's king's wrath!",
"This trident was stolen many years ago from the Octopus king's garden "
"by a really unimportant and already dead man. But beware of "
"the Octopus king's wrath!",
/*
* File: travel.cc
* Summary: Travel stuff
* Written by: Darshan Shaligram
*
* Change History (most recent first):
*
* <1> -/--/-- SD Created
*/
#ifndef TRAVEL_H
# define TRAVEL_H
# include "externs.h"
# include <stdio.h>
# include <string>
# include <vector>
# include <map>
/* ***********************************************************************
* Initialises the travel subsystem.
*
* ***********************************************************************
* called from: initfile (what's a better place to initialise stuff?)
* *********************************************************************** */
void initialise_travel();
void stop_running(void);
void travel_init_new_level();
void toggle_exclude(int x, int y);
void clear_excludes();
unsigned char is_waypoint(int x, int y);
void update_excludes();
bool is_stair(unsigned gridc);
bool is_travelable_stair(unsigned gridc);
int stair_direction(int stair_feat);
bool is_player_mapped(unsigned char envch);
inline bool is_player_mapped(int grid_x, int grid_y)
{
return (is_player_mapped( env.map[grid_x - 1][grid_y - 1] ));
}
/* ***********************************************************************
* Returns the direction to take to move along the shortest path between
* (you_x, you_y) and (you.run_x, you.run_y) in (*move_x, *move_y).
* If move_x or move_y is NULL, the function explores the map outwards from
* (you_x, you_y), populating the coords vector with the coordinates
* of every dungeon feature it finds, features closest to the character
* (travel distance-wise) coming first in the vector. A 'feature' is defined
* as a trap, and any other non-floor, non-water/lava square that the character
* can step onto.
*
* ***********************************************************************
* called from: acr - view
* *********************************************************************** */
void find_travel_pos(int you_x, int you_y, char *move_x, char *move_y,
std::vector<coord_def>* coords = NULL);
/* ***********************************************************************
* Initiates explore - the character runs around the level to map it. Note
* that the caller has to ensure that the level is mappable before calling
* start_explore. start_explore may lock up the game on unmappable levels.
*
* ***********************************************************************
* called from: acr
* *********************************************************************** */
void start_explore();
struct level_pos;
void start_translevel_travel(const level_pos &pos);
void start_translevel_travel(bool prompt_for_destination = true);
void start_travel(int x, int y);
void travel(int *keyin, char *move_x, char *move_y);
int travel_direction(unsigned char branch, int subdungeondepth);
void prevent_travel_to(const std::string &dungeon_feature_name);
int subdungeon_depth(unsigned char branch, int depth);
int absdungeon_depth(unsigned char branch, int subdepth);
// Sort dungeon features as appropriate.
void arrange_features(std::vector<coord_def> &features);
// Magic numbers for point_distance:
// This square is a trap
#define PD_TRAP -42
// The user never wants to travel this square
#define PD_EXCLUDED -20099
// This square is within LOS radius of an excluded square
#define PD_EXCLUDED_RADIUS -20100
// This square is a waypoint
#define PD_WAYPOINT -20200
/* ***********************************************************************
* Array of points on the map, each value being the distance the character
* would have to travel to get there. Negative distances imply that the point
* is a) a trap or hostile terrain or b) only reachable by crossing a trap or
* hostile terrain.
* ***********************************************************************
* referenced in: travel - view
* *********************************************************************** */
extern short point_distance[GXM][GYM];
// Possible values of you.running
enum RUN_MODES
{
RUN_TRAVEL = -1, // Classic or Plain Old travel
RUN_EXPLORE = -2, // Exploring (Ctrl+O)
RUN_INTERLEVEL = -3, // Interlevel travel (Ctrl+G)
};
enum EXPLORE_STOP
{
ES_NONE = 0,
ES_ITEM = 1,
ES_STAIR = 2,
ES_SHOP = 4,
ES_ALTAR = 8,
};
////////////////////////////////////////////////////////////////////////////
// Structs for interlevel travel.
struct level_id
{
unsigned char branch; // The branch in which the level is.
int depth; // What depth (in this branch - starting from 1)
// this level is.
level_id() : branch(0), depth(-1) { }
level_id(unsigned char br, int dep) : branch(br), depth(dep) { }
// Returns the level_id of the current level.
static level_id get_current_level_id();
// Returns the level_id of the level that the stair/portal/whatever at
// 'pos' on the current level leads to.
static level_id get_next_level_id(const coord_def &pos);
bool operator == ( const level_id &id ) const
{
return branch == id.branch && depth == id.depth;
}
bool operator != ( const level_id &id ) const
{
return branch != id.branch || depth != id.depth;
}
struct less_than
{
bool operator () (const level_id &first, const level_id &second) const
{
return first.branch < second.branch ||
(first.branch == second.branch && first.depth < second.depth);
}
};
void save(FILE *) const;
void load(FILE *);
};
// A position on a particular level.
struct level_pos
{
level_id id;
coord_def pos; // The grid coordinates on this level.
level_pos() : id(), pos()
{
pos.x = pos.y = -1;
}
level_pos(const level_id &lid, const coord_def &coord)
: id(lid), pos(coord)
{
}
level_pos(const level_id &lid)
: id(lid), pos()
{
pos.x = pos.y = -1;
}
bool operator == ( const level_pos &lp ) const
{
return id == lp.id && pos == lp.pos;
}
bool operator != ( const level_pos &lp ) const
{
return id != lp.id || pos != lp.pos;
}
bool is_valid() const
{
return id.depth > -1 && pos.x != -1 && pos.y != -1;
}
void save(FILE *) const;
void load(FILE *);
};
struct stair_info
{
coord_def position; // Position of stair
level_pos destination; // The level and the position on the level this
// stair leads to. This may be a guess.
int distance; // The distance traveled to reach this stair.
bool guessed_pos; // true if we're not sure that 'destination' is
// correct.
stair_info() : destination(), distance(-1), guessed_pos(true)
{
position.x = position.y = -1;
}
void reset_distance()
{
distance = -1;
}
void save(FILE *) const;
void load(FILE *);
};
// Information on a level that interlevel travel needs.
struct LevelInfo
{
LevelInfo() : stairs()
{
stair_distances = NULL;
}
LevelInfo(const LevelInfo &li);
~LevelInfo();
const LevelInfo &operator = (const LevelInfo &other);
void save(FILE *) const;
void load(FILE *);
std::vector<stair_info> &get_stairs()
{
return stairs;
}
stair_info *get_stair(int x, int y);
stair_info *get_stair(const coord_def &pos);
int get_stair_index(const coord_def &pos) const;
void reset_distances();
void set_level_excludes();
// Returns the travel distance between two stairs. If either stair is NULL,
// or does not exist in our list of stairs, returns 0.
int distance_between(const stair_info *s1, const stair_info *s2) const;
void update(); // Update LevelInfo to be correct for the
// current level.
// Updates/creates a StairInfo for the stair at (x, y) in grid coordinates
void update_stair(int x, int y, const level_pos &p, bool guess = false);
// Returns true if the given branch is known to be accessible from the
// current level.
bool is_known_branch(unsigned char branch) const;
void add_waypoint(const coord_def &pos);
void remove_waypoint(const coord_def &pos);
void travel_to_waypoint(const coord_def &pos);
private:
// Gets a list of coordinates of all player-known stairs on the current
// level.
static void get_stairs(std::vector<coord_def> &stairs);
void correct_stair_list(const std::vector<coord_def> &s);
void update_stair_distances();
void fixup();
private:
std::vector<stair_info> stairs;
// Squares that are not safe to travel to.
std::vector<coord_def> excludes;
short *stair_distances; // Distances between the various stairs
level_id id;
friend class TravelCache;
};
#define TRAVEL_WAYPOINT_COUNT 10
// Tracks all levels that the player has seen.
class TravelCache
{
public:
void reset_distances();
// Get the LevelInfo for the specified level (defaults to the current
// level).
LevelInfo& get_level_info(unsigned char branch = 0, int depth = -1)
{
return get_level_info( level_id(branch, depth) );
}
LevelInfo& get_level_info(const level_id &lev)
{
LevelInfo &li = levels[lev];
li.id = lev;
return li;
}
bool know_level(const level_id &lev) const
{
return levels.find(lev) != levels.end();
}
const level_pos &get_waypoint(int number) const
{
return waypoints[number];
}
int get_waypoint_count() const;
void set_level_excludes();
void add_waypoint(int x = -1, int y = -1);
unsigned char is_waypoint(const level_pos &lp) const;
void list_waypoints() const;
void travel_to_waypoint(int number);
void update_waypoints() const;
void update();
void save(FILE *) const;
void load(FILE *);
bool is_known_branch(unsigned char branch) const;
private:
void fixup_levels();
private:
std::map<level_id, LevelInfo, level_id::less_than> levels;
level_pos waypoints[TRAVEL_WAYPOINT_COUNT];
};
int level_distance(level_id first, level_id second);
bool can_travel_interlevel();
extern TravelCache travel_cache;
#endif // TRAVEL_H
/*
* File: travel.cc
* Summary: Travel stuff
* Written by: Darshan Shaligram
*
* Known issues:
* Hardcoded dungeon features all over the place - this thing is a devil to
* refactor.
*/
#include "AppHdr.h"
#include "files.h"
#include "FixAry.h"
#include "clua.h"
#include "mon-util.h"
#include "player.h"
#include "stash.h"
#include "stuff.h"
#include "travel.h"
#include "view.h"
#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#ifdef DOS
#include <dos.h>
#endif
#define TC_MAJOR_VERSION ((unsigned char) 4)
#define TC_MINOR_VERSION ((unsigned char) 4)
enum IntertravelDestination
{
// Go down a level
ID_DOWN = -100,
// Go up a level
ID_UP = -99,
// Repeat last travel
ID_REPEAT = -101,
// Cancel interlevel travel
ID_CANCEL = -1000,
};
TravelCache travel_cache;
// Tracks the distance between the target location on the target level and the
// stairs on the level.
static std::vector<stair_info> curr_stairs;
// Squares that are not safe to travel to on the current level.
static std::vector<coord_def> curr_excludes;
// This is where we last tried to take a stair during interlevel travel.
// Note that last_stair.depth should be set to -1 before initiating interlevel
// travel.
static level_id last_stair;
// Where travel wants to get to.
static level_pos travel_target;
// The place in the Vestibule of Hell where all portals to Hell land.
static level_pos travel_hell_entry;
static bool traps_inited = false;
// TODO: Do we need this or would a different header be better?
inline int sgn(int x)
{
return x < 0? -1 : (x > 0);
}
/* These are defined in view.cc. BWR says these may become obsolete in next
* release? */
extern unsigned char (*mapch) (unsigned char);
extern unsigned char (*mapch2) (unsigned char);
extern unsigned char mapchar(unsigned char ldfk);
extern unsigned char mapchar2(unsigned char ldfk);
// Array of points on the map, each value being the distance the character
// would have to travel to get there. Negative distances imply that the point
// is a) a trap or hostile terrain or b) only reachable by crossing a trap or
// hostile terrain.
short point_distance[GXM][GYM];
unsigned char curr_waypoints[GXM][GYM];
signed char curr_traps[GXM][GYM];
static FixedArray< unsigned short, GXM, GYM > mapshadow;
// Clockwise, around the compass from north (same order as enum RUN_DIR)
// Copied from acr.cc
static const struct coord_def Compass[8] =
{
{ 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 },
{ 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 },
};
#define TRAVERSABLE 1
#define IMPASSABLE 0
#define FORBIDDEN -1
// Map of terrain types that are traversable.
static signed char traversable_terrain[256];
static int trans_negotiate_stairs();
static int find_transtravel_square(const level_pos &pos, bool verbose = true);
static bool loadlev_populate_stair_distances(const level_pos &target);
static void populate_stair_distances(const level_pos &target);
// Determines whether the player has seen this square, given the user-visible
// character.
//
// The player is assumed to have seen the square if:
// a. The square is mapped (the env map char is not zero)
// b. The square was *not* magic-mapped.
//
bool is_player_mapped(unsigned char envch)
{
// Note that we're relying here on mapch(DNGN_FLOOR) != mapch2(DNGN_FLOOR)
// and that no *other* dungeon feature renders as mapch(DNGN_FLOOR).
// The check for a ~ is to ensure explore stops for items turned up by
// detect items.
return envch && envch != mapch(DNGN_FLOOR) && envch != '~';
}
inline bool is_trap(unsigned char grid)
{
return (grid == DNGN_TRAP_MECHANICAL || grid == DNGN_TRAP_MAGICAL
|| grid == DNGN_TRAP_III);
}
// Returns true if there is a known trap at (x,y). Returns false for non-trap
// squares as also for undiscovered traps.
//
inline bool is_trap(int x, int y)
{
return is_trap( grd[x][y] );
}
// Returns true if this feature takes extra time to cross.
inline int feature_traverse_cost(unsigned char feature)
{
return (feature == DNGN_SHALLOW_WATER || feature == DNGN_CLOSED_DOOR? 2 :
is_trap(feature) ? 3 : 1);
}
// Returns true if the dungeon feature supplied is an altar.
inline bool is_altar(unsigned char grid)
{
return grid >= DNGN_ALTAR_ZIN && grid <= DNGN_ALTAR_ELYVILON;
}
inline bool is_altar(const coord_def &c)
{
return is_altar(grd[c.x][c.y]);
}
inline bool is_player_altar(unsigned char grid)
{
// An ugly hack, but that's what religion.cc does.
return you.religion
&& grid == DNGN_ALTAR_ZIN - 1 + you.religion;
}
inline bool is_player_altar(const coord_def &c)
{
return is_player_altar(grd[c.x][c.y]);
}
// Copies FixedArray src to FixedArray dest.
//
inline void copy(const FixedArray<unsigned short, GXM, GYM> &src,
FixedArray<unsigned short, GXM, GYM> &dest)
{
dest = src;
}
#ifdef CLUA_BINDINGS
static void init_traps()
{
memset(curr_traps, -1, sizeof curr_traps);
for (int i = 0; i < MAX_TRAPS; ++i)
{
int x = env.trap[i].x,
y = env.trap[i].y;
if (x > 0 && x < GXM && y > 0 && y < GYM)
curr_traps[x][y] = i;
}
traps_inited = true;
}
static const char *trap_names[] =
{
"dart", "arrow", "spear", "axe",
"teleport", "amnesia", "blade",
"bolt", "zot", "needle",
};
static const char *trap_name(int x, int y)
{
if (!traps_inited)
init_traps();
const int ti = curr_traps[x][y];
if (ti != -1)
{
int type = env.trap[ti].type;
if (type >= 0 && type < NUM_TRAPS)
return (trap_names[type]);
}
return ("");
}
#endif
/*
* Returns true if the character can cross this dungeon feature.
*/
inline bool is_traversable(unsigned char grid)
{
return traversable_terrain[(int) grid] == TRAVERSABLE;
}
static bool is_excluded(int x, int y)
{
for (int i = 0, count = curr_excludes.size(); i < count; ++i)
{
const coord_def &c = curr_excludes[i];
int dx = c.x - x,
dy = c.y - y;
if (dx * dx + dy * dy <= Options.travel_exclude_radius2)
return true;
}
return false;
}
static bool is_exclude_root(int x, int y)
{
for (int i = 0, count = curr_excludes.size(); i < count; ++i)
{
const coord_def &c = curr_excludes[i];
if (c.x == x && c.y == y)
return true;
}
return false;
}
const char *run_mode_name(int runmode)
{
return runmode == RUN_TRAVEL? "travel" :
runmode == RUN_INTERLEVEL? "intertravel" :
runmode == RUN_EXPLORE? "explore" :
runmode > 0? "run" :
"";
}
unsigned char is_waypoint(int x, int y)
{
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
|| you.level_type == LEVEL_PANDEMONIUM)
return 0;
return curr_waypoints[x][y];
}
#ifdef STASH_TRACKING
inline bool is_stash(LevelStashes *ls, int x, int y)
{
if (!ls)
return (false);
Stash *s = ls->find_stash(x, y);
return s && s->enabled;
}
#endif
void clear_excludes()
{
// Sanity checks
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
return;
curr_excludes.clear();
if (can_travel_interlevel())
{
LevelInfo &li = travel_cache.get_level_info(
level_id::get_current_level_id());
li.update();
}
}
void toggle_exclude(int x, int y)
{
// Sanity checks
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
return;
if (x <= 0 || x >= GXM || y <= 0 || y >= GYM) return;
if (!env.map[x - 1][y - 1]) return;
if (is_exclude_root(x, y))
{
for (int i = 0, count = curr_excludes.size(); i < count; ++i)
{
const coord_def &c = curr_excludes[i];
if (c.x == x && c.y == y)
{
curr_excludes.erase( curr_excludes.begin() + i );
break ;
}
}
}
else
{
coord_def c = { x, y };
curr_excludes.push_back(c);
}
if (can_travel_interlevel())
{
LevelInfo &li = travel_cache.get_level_info(
level_id::get_current_level_id());
li.update();
}
}
void update_excludes()
{
// Sanity checks
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
return;
for (int i = curr_excludes.size() - 1; i >= 0; --i)
{
int x = curr_excludes[i].x,
y = curr_excludes[i].y;
if (!env.map[x - 1][y - 1])
curr_excludes.erase( curr_excludes.begin() + i );
}
}
void forget_square(int x, int y)
{
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
return;
if (is_exclude_root(x, y))
toggle_exclude(x, y);
}
/*
* Returns true if the square at (x,y) is a dungeon feature the character
* can't (under normal circumstances) safely cross.
*
* Note: is_reseedable can return true for dungeon features that is_traversable
* also returns true for. This is okay, because is_traversable always
* takes precedence over is_reseedable. is_reseedable is used only to
* decide which squares to reseed from when flood-filling outwards to
* colour the level map. It does not affect pathing of actual
* travel/explore.
*/
static bool is_reseedable(int x, int y)
{
if (is_excluded(x, y))
return true;
unsigned char grid = grd[x][y];
return (grid == DNGN_DEEP_WATER || grid == DNGN_SHALLOW_WATER ||
grid == DNGN_LAVA || is_trap(x, y));
}
/*
* Returns true if the square at (x,y) is okay to travel over. If ignore_hostile
* is true, returns true even for dungeon features the character can normally
* not cross safely (deep water, lava, traps).
*/
static bool is_travel_ok(int x, int y, bool ignore_hostile)
{
unsigned char grid = grd[x][y];
unsigned char envc = (unsigned char) env.map[x - 1][y - 1];
if (!envc) return false;
// Special-case secret doors so that we don't run into awkwardness when
// a monster opens a secret door without the hero seeing it, but the travel
// code paths through the secret door because it looks at the actual grid,
// rather than the env overmap. Hopefully there won't be any more such
// cases.
if (envc == mapch2(DNGN_SECRET_DOOR)) return false;
unsigned char mon = mgrd[x][y];
if (mon != NON_MONSTER)
{
// Kludge warning: navigating around zero-exp beasties uses knowledge
// that the player may not have (the player may not
// know that there's a plant at any given (x,y), but we
// know, because we're looking directly at the grid).
// Arguably the utility of this feature is greater than
// the information we're giving the player for free.
// Navigate around plants and fungi. Yet another tasty hack.
if (player_monster_visible(&menv[mon]) &&
mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
{
extern short point_distance[GXM][GYM];
// We have to set the point_distance array if the level map is
// to be properly coloured. The caller isn't going to do it because
// we say this square is inaccessible, so in a horrible hack, we
// do it ourselves. Ecch.
point_distance[x][y] = ignore_hostile? -42 : 42;
return false;
}
}
// If 'ignore_hostile' is true, we're ignoring hazards that can be
// navigated over if the player is willing to take damage, or levitate.
if (ignore_hostile && is_reseedable(x, y))
return true;
return (is_traversable(grid)
#ifdef CLUA_BINDINGS
||
(is_trap(x, y) &&
clua.callbooleanfn(false, "ch_cross_trap",
"s", trap_name(x, y)))
#endif
)
&& !is_excluded(x, y);
}
// Returns true if the location at (x,y) is monster-free and contains no clouds.
static bool is_safe(int x, int y)
{
unsigned char mon = mgrd[x][y];
if (mon != NON_MONSTER)
{
// If this is an invisible critter, say we're safe to get here, but
// turn off travel - the result should be that the player bashes into
// the monster and stops travelling right there. Same treatment applies
// to mimics.
if (!player_monster_visible(&menv[mon]) ||
mons_is_mimic( menv[mon].type ))
{
you.running = 0;
return true;
}
// Stop before wasting energy on plants and fungi.
if (mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
return false;
// If this is any *other* monster, it'll be visible and
// a) Friendly, in which case we'll displace it, no problem.
// b) Unfriendly, in which case we're in deep trouble, since travel
// should have been aborted already by the checks in view.cc.
}
const char cloud = env.cgrid[x][y];
if (cloud == EMPTY_CLOUD)
return true;
// We can also safely run through smoke.
const char cloud_type = env.cloud[ cloud ].type;
return cloud_type == CLOUD_GREY_SMOKE ||
cloud_type == CLOUD_GREY_SMOKE_MON ||
cloud_type == CLOUD_BLUE_SMOKE ||
cloud_type == CLOUD_BLUE_SMOKE_MON ||
cloud_type == CLOUD_PURP_SMOKE ||
cloud_type == CLOUD_PURP_SMOKE_MON ||
cloud_type == CLOUD_BLACK_SMOKE ||
cloud_type == CLOUD_BLACK_SMOKE_MON;
}
static bool player_is_permalevitating()
{
return you.levitation > 1 &&
((you.species == SP_KENKU && you.experience_level >= 15)
|| player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ));
}
static void set_pass_feature(unsigned char grid, signed char pass)
{
if (traversable_terrain[(unsigned) grid] != FORBIDDEN)
traversable_terrain[(unsigned) grid] = pass;
}
/*
* Sets traversable terrain based on the character's role and whether or not he
* has permanent levitation
*/
static void init_terrain_check()
{
// Merfolk get deep water.
signed char water = you.species == SP_MERFOLK? TRAVERSABLE : IMPASSABLE;
// If the player has overridden deep water already, we'll respect that.
set_pass_feature(DNGN_DEEP_WATER, water);
// Permanently levitating players can cross most hostile terrain.
signed char trav = player_is_permalevitating()?
TRAVERSABLE : IMPASSABLE;
if (you.species != SP_MERFOLK)
set_pass_feature(DNGN_DEEP_WATER, trav);
set_pass_feature(DNGN_LAVA, trav);
set_pass_feature(DNGN_TRAP_MECHANICAL, trav);
}
void travel_init_new_level()
{
// Zero out last travel coords
you.run_x = you.run_y = 0;
you.travel_x = you.travel_y = 0;
traps_inited = false;
curr_excludes.clear();
travel_cache.set_level_excludes();
travel_cache.update_waypoints();
}
/*
* Sets up travel-related stuff.
*/
void initialise_travel()
{
// Need a better way to do this. :-(
traversable_terrain[DNGN_FLOOR] =
traversable_terrain[DNGN_ENTER_HELL] =
traversable_terrain[DNGN_OPEN_DOOR] =
traversable_terrain[DNGN_BRANCH_STAIRS] =
traversable_terrain[DNGN_UNDISCOVERED_TRAP] =
traversable_terrain[DNGN_ENTER_SHOP] =
traversable_terrain[DNGN_ENTER_LABYRINTH] =
traversable_terrain[DNGN_STONE_STAIRS_DOWN_I] =
traversable_terrain[DNGN_STONE_STAIRS_DOWN_II] =
traversable_terrain[DNGN_STONE_STAIRS_DOWN_III] =
traversable_terrain[DNGN_ROCK_STAIRS_DOWN] =
traversable_terrain[DNGN_STONE_STAIRS_UP_I] =
traversable_terrain[DNGN_STONE_STAIRS_UP_II] =
traversable_terrain[DNGN_STONE_STAIRS_UP_III] =
traversable_terrain[DNGN_ROCK_STAIRS_UP] =
traversable_terrain[DNGN_ENTER_DIS] =
traversable_terrain[DNGN_ENTER_GEHENNA] =
traversable_terrain[DNGN_ENTER_COCYTUS] =
traversable_terrain[DNGN_ENTER_TARTARUS] =
traversable_terrain[DNGN_ENTER_ABYSS] =
traversable_terrain[DNGN_EXIT_ABYSS] =
traversable_terrain[DNGN_STONE_ARCH] =
traversable_terrain[DNGN_ENTER_PANDEMONIUM] =
traversable_terrain[DNGN_EXIT_PANDEMONIUM] =
traversable_terrain[DNGN_TRANSIT_PANDEMONIUM] =
traversable_terrain[DNGN_ENTER_ORCISH_MINES] =
traversable_terrain[DNGN_ENTER_HIVE] =
traversable_terrain[DNGN_ENTER_LAIR] =
traversable_terrain[DNGN_ENTER_SLIME_PITS] =
traversable_terrain[DNGN_ENTER_VAULTS] =
traversable_terrain[DNGN_ENTER_CRYPT] =
traversable_terrain[DNGN_ENTER_HALL_OF_BLADES] =
traversable_terrain[DNGN_ENTER_ZOT] =
traversable_terrain[DNGN_ENTER_TEMPLE] =
traversable_terrain[DNGN_ENTER_SNAKE_PIT] =
traversable_terrain[DNGN_ENTER_ELVEN_HALLS] =
traversable_terrain[DNGN_ENTER_TOMB] =
traversable_terrain[DNGN_ENTER_SWAMP] =
traversable_terrain[DNGN_RETURN_FROM_ORCISH_MINES] =
traversable_terrain[DNGN_RETURN_FROM_HIVE] =
traversable_terrain[DNGN_RETURN_FROM_LAIR] =
traversable_terrain[DNGN_RETURN_FROM_SLIME_PITS] =
traversable_terrain[DNGN_RETURN_FROM_VAULTS] =
traversable_terrain[DNGN_RETURN_FROM_CRYPT] =
traversable_terrain[DNGN_RETURN_FROM_HALL_OF_BLADES] =
traversable_terrain[DNGN_RETURN_FROM_ZOT] =
traversable_terrain[DNGN_RETURN_FROM_TEMPLE] =
traversable_terrain[DNGN_RETURN_FROM_SNAKE_PIT] =
traversable_terrain[DNGN_RETURN_FROM_ELVEN_HALLS] =
traversable_terrain[DNGN_RETURN_FROM_TOMB] =
traversable_terrain[DNGN_RETURN_FROM_SWAMP] =
traversable_terrain[DNGN_ALTAR_ZIN] =
traversable_terrain[DNGN_ALTAR_SHINING_ONE] =
traversable_terrain[DNGN_ALTAR_KIKUBAAQUDGHA] =
traversable_terrain[DNGN_ALTAR_YREDELEMNUL] =
traversable_terrain[DNGN_ALTAR_XOM] =
traversable_terrain[DNGN_ALTAR_VEHUMET] =
traversable_terrain[DNGN_ALTAR_OKAWARU] =
traversable_terrain[DNGN_ALTAR_MAKHLEB] =
traversable_terrain[DNGN_ALTAR_SIF_MUNA] =
traversable_terrain[DNGN_ALTAR_TROG] =
traversable_terrain[DNGN_ALTAR_NEMELEX_XOBEH] =
traversable_terrain[DNGN_ALTAR_ELYVILON] =
traversable_terrain[DNGN_BLUE_FOUNTAIN] =
traversable_terrain[DNGN_DRY_FOUNTAIN_I] =
traversable_terrain[DNGN_SPARKLING_FOUNTAIN] =
traversable_terrain[DNGN_DRY_FOUNTAIN_II] =
traversable_terrain[DNGN_DRY_FOUNTAIN_III] =
traversable_terrain[DNGN_DRY_FOUNTAIN_IV] =
traversable_terrain[DNGN_DRY_FOUNTAIN_V] =
traversable_terrain[DNGN_DRY_FOUNTAIN_VI] =
traversable_terrain[DNGN_DRY_FOUNTAIN_VII] =
traversable_terrain[DNGN_DRY_FOUNTAIN_VIII] =
traversable_terrain[DNGN_PERMADRY_FOUNTAIN] =
traversable_terrain[DNGN_CLOSED_DOOR] =
traversable_terrain[DNGN_SHALLOW_WATER] =
TRAVERSABLE;
}
/*
* Given a dungeon feature description, returns the feature number. This is a
* crude hack and currently recognises only (deep/shallow) water.
*
* Returns -1 if the feature named is not recognised, else returns the feature
* number (guaranteed to be 0-255).
*/
int get_feature_type(const std::string &feature)
{
if (feature.find("deep water") != std::string::npos)
return DNGN_DEEP_WATER;
if (feature.find("shallow water") != std::string::npos)
return DNGN_SHALLOW_WATER;
return -1;
}
/*
* Given a feature description, prevents travel to locations of that feature
* type.
*/
void prevent_travel_to(const std::string &feature)
{
int feature_type = get_feature_type(feature);
if (feature_type != -1)
traversable_terrain[feature_type] = FORBIDDEN;
}
bool is_stair(unsigned gridc)
{
return (is_travelable_stair(gridc)
|| gridc == DNGN_ENTER_ABYSS
|| gridc == DNGN_ENTER_LABYRINTH
|| gridc == DNGN_ENTER_PANDEMONIUM
|| gridc == DNGN_EXIT_PANDEMONIUM
|| gridc == DNGN_TRANSIT_PANDEMONIUM);
}
/*
* Returns true if the given dungeon feature can be considered a stair.
*/
bool is_travelable_stair(unsigned gridc)
{
switch (gridc)
{
case DNGN_ENTER_HELL:
case DNGN_STONE_STAIRS_DOWN_I:
case DNGN_STONE_STAIRS_DOWN_II:
case DNGN_STONE_STAIRS_DOWN_III:
case DNGN_ROCK_STAIRS_DOWN:
case DNGN_STONE_STAIRS_UP_I:
case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III:
case DNGN_ROCK_STAIRS_UP:
case DNGN_ENTER_DIS:
case DNGN_ENTER_GEHENNA:
case DNGN_ENTER_COCYTUS:
case DNGN_ENTER_TARTARUS:
case DNGN_ENTER_ORCISH_MINES:
case DNGN_ENTER_HIVE:
case DNGN_ENTER_LAIR:
case DNGN_ENTER_SLIME_PITS:
case DNGN_ENTER_VAULTS:
case DNGN_ENTER_CRYPT:
case DNGN_ENTER_HALL_OF_BLADES:
case DNGN_ENTER_ZOT:
case DNGN_ENTER_TEMPLE:
case DNGN_ENTER_SNAKE_PIT:
case DNGN_ENTER_ELVEN_HALLS:
case DNGN_ENTER_TOMB:
case DNGN_ENTER_SWAMP:
case DNGN_RETURN_FROM_ORCISH_MINES:
case DNGN_RETURN_FROM_HIVE:
case DNGN_RETURN_FROM_LAIR:
case DNGN_RETURN_FROM_SLIME_PITS:
case DNGN_RETURN_FROM_VAULTS:
case DNGN_RETURN_FROM_CRYPT:
case DNGN_RETURN_FROM_HALL_OF_BLADES:
case DNGN_RETURN_FROM_ZOT:
case DNGN_RETURN_FROM_TEMPLE:
case DNGN_RETURN_FROM_SNAKE_PIT:
case DNGN_RETURN_FROM_ELVEN_HALLS:
case DNGN_RETURN_FROM_TOMB:
case DNGN_RETURN_FROM_SWAMP:
return true;
default:
return false;
}
}
#define ES_item (Options.explore_stop & ES_ITEM)
#define ES_shop (Options.explore_stop & ES_SHOP)
#define ES_stair (Options.explore_stop & ES_STAIR)
#define ES_altar (Options.explore_stop & ES_ALTAR)
/*
* Given a square that has just become visible during explore, returns true
* if the player might consider the square worth stopping explore for.
*/
static bool is_interesting_square(int x, int y)
{
if (ES_item && igrd[x + 1][y + 1] != NON_ITEM)
return true;
unsigned char grid = grd[x + 1][y + 1];
return (ES_shop && grid == DNGN_ENTER_SHOP)
|| (ES_stair && is_stair(grid))
|| (ES_altar && is_altar(grid)
&& you.where_are_you != BRANCH_ECUMENICAL_TEMPLE);
}
static void userdef_run_stoprunning_hook(void)
{
#ifdef CLUA_BINDINGS
if (you.running)
clua.callfn("ch_stop_running", "s", run_mode_name(you.running));
#endif
}
static void userdef_run_startrunning_hook(void)
{
#ifdef CLUA_BINDINGS
if (you.running)
clua.callfn("ch_start_running", "s", run_mode_name(you.running));
#endif
}
/*
* Stops shift+running and all forms of travel.
*/
void stop_running(void)
{
userdef_run_stoprunning_hook();
you.running = 0;
}
void start_running(void)
{
userdef_run_startrunning_hook();
}
/*
* Top-level travel control (called from input() in acr.cc).
*
* travel() is responsible for making the individual moves that constitute
* (interlevel) travel and explore and deciding when travel and explore
* end.
*
* Don't call travel() if you.running >= 0.
*/
void travel(int *keyin, char *move_x, char *move_y)
{
*keyin = 128;
// Abort travel/explore if you're confused or a key was pressed.
if (kbhit() || you.conf)
{
stop_running();
*keyin = 0;
if (Options.travel_delay == -1)
redraw_screen();
return ;
}
if (Options.explore_stop && you.running == RUN_EXPLORE)
{
// Scan through the shadow map, compare it with the actual map, and if
// there are any squares of the shadow map that have just been
// discovered and contain an item, or have an interesting dungeon
// feature, stop exploring.
for (int y = 0; y < GYM - 1; ++y)
{
for (int x = 0; x < GXM - 1; ++x)
{
if (!is_player_mapped(mapshadow[x][y])
&& is_player_mapped((unsigned char) env.map[x][y])
&& is_interesting_square(x, y))
{
stop_running();
y = GYM;
break;
}
}
}
copy(env.map, mapshadow);
}
if (you.running == RUN_EXPLORE)
{
// Exploring
you.run_x = 0;
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL);
// No place to go?
if (!you.run_x)
stop_running();
}
if (you.running == RUN_INTERLEVEL && !you.run_x)
{
// Interlevel travel. Since you.run_x is zero, we've either just
// initiated travel, or we've just climbed or descended a staircase,
// and we need to figure out where to travel to next.
if (!find_transtravel_square(travel_target) || !you.run_x)
stop_running();
}
if (you.running < 0)
{
// Remember what run-mode we were in so that we can resume explore/
// interlevel travel correctly.
int runmode = you.running;
// Get the next step to make. If the travel command can't find a route,
// we turn off travel (find_travel_pos does that automatically).
find_travel_pos(you.x_pos, you.y_pos, move_x, move_y);
if (!*move_x && !*move_y)
{
// If we've reached the square we were traveling towards, travel
// should stop if this is simple travel. If we're exploring, we
// should continue doing so (explore has its own end condition
// upstairs); if we're traveling between levels and we've reached
// our travel target, we're on a staircase and should take it.
if (you.x_pos == you.run_x && you.y_pos == you.run_y)
{
if (runmode == RUN_EXPLORE)
you.running = RUN_EXPLORE; // Turn explore back on
// For interlevel travel, we'll want to take the stairs unless
// the interlevel travel specified a destination square and
// we've reached that destination square.
else if (runmode == RUN_INTERLEVEL
&& (travel_target.pos.x != you.x_pos
|| travel_target.pos.y != you.y_pos
|| travel_target.id !=
level_id::get_current_level_id()))
{
if (last_stair.depth != -1
&& last_stair == level_id::get_current_level_id())
{
// We're trying to take the same stairs again. Baaad.
// We don't directly call stop_running() because
// you.running is probably 0, and stop_running() won't
// notify Lua hooks if you.running == 0.
you.running = runmode;
stop_running();
return;
}
you.running = RUN_INTERLEVEL;
*keyin = trans_negotiate_stairs();
// If, for some reason, we fail to use the stairs, we
// need to make sure we don't go into an infinite loop
// trying to take it again and again. We'll check
// last_stair before attempting to take stairs again.
last_stair = level_id::get_current_level_id();
// This is important, else we'll probably stop traveling
// the moment we clear the stairs. That's because the
// (run_x, run_y) destination will no longer be valid on
// the new level. Setting run_x to zero forces us to
// recalculate our travel target next turn (see previous
// if block).
you.run_x = you.run_y = 0;
}
else
{
you.running = runmode;
stop_running();
}
}
else
{
you.running = runmode;
stop_running();
}
}
else if (Options.travel_delay > 0)
delay(Options.travel_delay);
}
if (!you.running && Options.travel_delay == -1)
redraw_screen();
}
/*
* The travel algorithm is based on the NetHack travel code written by Warwick
* Allison - used with his permission.
*/
void find_travel_pos(int youx, int youy,
char *move_x, char *move_y,
std::vector<coord_def>* features)
{
init_terrain_check();
int start_x = you.run_x, start_y = you.run_y;
int dest_x = youx, dest_y = youy;
bool floodout = false;
#ifdef STASH_TRACKING
LevelStashes *lev = features? stashes.find_current_level() : NULL;
#endif
// Normally we start from the destination and floodfill outwards, looking
// for the character's current position. If we're merely trying to populate
// the point_distance array (or exploring), we'll want to start from the
// character's current position and fill outwards
if (!move_x || !move_y)
{
start_x = youx;
start_y = youy;
dest_x = dest_y = -1;
floodout = true;
}
// Abort run if we're trying to go someplace evil
if (dest_x != -1 && !is_travel_ok(start_x, start_y, false) &&
!is_trap(start_x, start_y))
{
you.running = 0;
return ;
}
// Abort run if we're going nowhere.
if (start_x == dest_x && start_y == dest_y)
{
you.running = 0;
return ;
}
// How many points are we currently considering? We start off with just one
// point, and spread outwards like a flood-filler.
int points = 1;
// How many points we'll consider next iteration.
int next_iter_points = 0;
// How far we've traveled from (start_x, start_y), in moves (a diagonal move
// is no longer than an orthogonal move).
int traveled_distance = 1;
// Which index of the circumference array are we currently looking at?
int circ_index = 0;
// The circumference points of the floodfilled area, for this iteration
// and the next (this iteration's points being circ_index amd the next one's
// being !circ_index).
static FixedVector<coord_def, GXM * GYM> circumference[2];
// Coordinates of all discovered traps. If we're exploring instead of
// travelling, we'll reseed from these points after we've explored the map
std::vector<coord_def> trap_seeds;
// When set to true, the travel code ignores features, traps and hostile
// terrain, and simply tries to map contiguous floorspace. Will only be set
// to true if we're exploring, instead of travelling.
bool ignore_hostile = false;
// Set the seed point
circumference[circ_index][0].x = start_x;
circumference[circ_index][0].y = start_y;
// Zap out previous distances array
memset(point_distance, 0, sizeof point_distance);
for ( ; points > 0; ++traveled_distance, circ_index = !circ_index,
points = next_iter_points, next_iter_points = 0)
{
for (int i = 0; i < points; ++i)
{
int x = circumference[circ_index][i].x,
y = circumference[circ_index][i].y;
// (x,y) is a known (explored) location - we never put unknown
// points in the circumference vector, so we don't need to examine
// the map array, just the grid array.
unsigned char feature = grd[x][y];
// If this is a feature that'll take time to travel past, we
// simulate that extra turn by taking this feature next turn,
// thereby artificially increasing traveled_distance.
//
// Note: I don't know how slow walking through shallow water and
// opening closed doors is - right now it's considered to have
// the cost of two normal moves.
int feat_cost = feature_traverse_cost(feature);
if (feat_cost > 1
&& point_distance[x][y] > traveled_distance - feat_cost)
{
circumference[!circ_index][next_iter_points].x = x;
circumference[!circ_index][next_iter_points].y = y;
next_iter_points++;
continue;
}
// For each point, we look at all surrounding points. Take them
// orthogonals first so that the travel path doesn't zigzag all over
// the map. Note the (dir = 1) is intentional assignment.
for (int dir = 0; dir < 8; (dir += 2) == 8 && (dir = 1))
{
int dx = x + Compass[dir].x, dy = y + Compass[dir].y;
if (dx <= 0 || dx >= GXM || dy <= 0 || dy >= GYM) continue;
unsigned char envf = env.map[dx - 1][dy - 1];
if (floodout && you.running == RUN_EXPLORE
&& !is_player_mapped(envf))
{
// Setting run_x and run_y here is evil - this function
// should ideally not modify game state in any way.
you.run_x = x;
you.run_y = y;
return;
}
if ((dx != dest_x || dy != dest_y)
&& !is_travel_ok(dx, dy, ignore_hostile))
{
// This point is not okay to travel on, but if this is a
// trap, we'll want to put it on the feature vector anyway.
if (is_reseedable(dx, dy)
&& !point_distance[dx][dy]
&& (dx != start_x || dy != start_y))
{
if (features)
{
coord_def c = { dx, dy };
if (is_trap(dx, dy) || is_exclude_root(dx, dy))
features->push_back(c);
trap_seeds.push_back(c);
}
// Appropriate mystic number. Nobody else should check
// this number, since this square is unsafe for travel.
point_distance[dx][dy] =
is_exclude_root(dx, dy)? PD_EXCLUDED :
is_excluded(dx, dy) ? PD_EXCLUDED_RADIUS :
PD_TRAP;
}
continue;
}
if (dx == dest_x && dy == dest_y)
{
// Hallelujah, we're home!
if (is_safe(x, y) && move_x && move_y)
{
*move_x = sgn(x - dest_x);
*move_y = sgn(y - dest_y);
}
return ;
}
else if (!point_distance[dx][dy])
{
// This point is going to be on the agenda for the next
// iteration
circumference[!circ_index][next_iter_points].x = dx;
circumference[!circ_index][next_iter_points].y = dy;
next_iter_points++;
point_distance[dx][dy] = traveled_distance;
// Negative distances here so that show_map can colour
// the map differently for these squares.
if (ignore_hostile)
{
point_distance[dx][dy] = -point_distance[dx][dy];
if (is_exclude_root(dx, dy))
point_distance[dx][dy] = PD_EXCLUDED;
else if (is_excluded(dx, dy))
point_distance[dx][dy] = PD_EXCLUDED_RADIUS;
}
unsigned char feature = grd[dx][dy];
if (features && !ignore_hostile
&& ((feature != DNGN_FLOOR
&& feature != DNGN_SHALLOW_WATER
&& feature != DNGN_DEEP_WATER
&& feature != DNGN_LAVA)
|| is_waypoint(dx, dy)
#ifdef STASH_TRACKING
|| is_stash(lev, dx, dy)
#endif
)
&& (dx != start_x || dy != start_y))
{
coord_def c = { dx, dy };
features->push_back(c);
}
if (features && is_exclude_root(dx, dy) && dx != start_x
&& dy != start_y)
{
coord_def c = { dx, dy };
features->push_back(c);
}
}
} // for (dir = 0; dir < 8 ...
} // for (i = 0; i < points ...
if (!next_iter_points && features && !move_x && !ignore_hostile
&& trap_seeds.size())
{
// Reseed here
for (unsigned i = 0; i < trap_seeds.size(); ++i)
circumference[!circ_index][i] = trap_seeds[i];
next_iter_points = trap_seeds.size();
ignore_hostile = true;
}
} // for ( ; points > 0 ...
}
// Mappings of which branches spring from which other branches, essential to
// walk backwards up the tree. Yes, this is a horrible abuse of coord_def.
static coord_def branch_backout[] =
{
{ BRANCH_SWAMP, BRANCH_LAIR },
{ BRANCH_SLIME_PITS, BRANCH_LAIR },
{ BRANCH_SNAKE_PIT, BRANCH_LAIR },
{ BRANCH_HALL_OF_BLADES, BRANCH_VAULTS },
{ BRANCH_CRYPT, BRANCH_VAULTS },
{ BRANCH_TOMB, BRANCH_CRYPT },
{ BRANCH_ELVEN_HALLS, BRANCH_ORCISH_MINES },
{ BRANCH_ORCISH_MINES, BRANCH_MAIN_DUNGEON },
{ BRANCH_HIVE, BRANCH_MAIN_DUNGEON },
{ BRANCH_LAIR, BRANCH_MAIN_DUNGEON },
{ BRANCH_VAULTS, BRANCH_MAIN_DUNGEON },
{ BRANCH_HALL_OF_ZOT, BRANCH_MAIN_DUNGEON },
{ BRANCH_ECUMENICAL_TEMPLE, BRANCH_MAIN_DUNGEON },
};
/*
* Given a branch id, returns the parent branch. If the branch id is not found,
* returns BRANCH_MAIN_DUNGEON.
*/
unsigned char find_parent_branch(unsigned char br)
{
for (unsigned i = 0;
i < sizeof(branch_backout) / sizeof(branch_backout[0]);
i++)
{
if (branch_backout[i].x == br)
return (unsigned char) branch_backout[i].y;
}
return 0;
}
extern FixedVector<char, MAX_BRANCHES> stair_level;
void find_parent_branch(unsigned char br, int depth,
unsigned char *pb, int *pd)
{
int lev = stair_level[br];
if (lev <= 0)
{
*pb = 0;
*pd = 0; // Check depth before using *pb.
return ;
}
*pb = find_parent_branch(br);
*pd = subdungeon_depth(*pb, lev);
}
// Appends the passed in branch/depth to the given vector, then attempts to
// repeat the operation with the parent branch of the given branch.
//
// As an example of what it does, assume this dungeon structure
// Stairs to lair on D:11
// Stairs to snake pit on lair:5
//
// If level 3 of the snake pit is the level we want to track back from,
// we'd call trackback(vec, BRANCH_SNAKE_PIT, 3), and the resulting vector will
// look like:
// { BRANCH_SNAKE_PIT, 3 }, { BRANCH_LAIR, 5 }, { BRANCH_MAIN_DUNGEON, 11 }
// (Assuming, of course, that the vector started out empty.)
//
void trackback(std::vector<level_id> &vec,
unsigned char branch, int subdepth)
{
if (subdepth < 1 || subdepth > MAX_LEVELS) return;
level_id lid( branch, subdepth );
vec.push_back(lid);
if (branch != BRANCH_MAIN_DUNGEON)
{
unsigned char pb;
int pd;
find_parent_branch(branch, subdepth, &pb, &pd);
if (pd)
trackback(vec, pb, pd);
}
}
void track_intersect(std::vector<level_id> &cur,
std::vector<level_id> &targ,
level_id *cx)
{
cx->branch = 0;
cx->depth = -1;
int us = cur.size() - 1, them = targ.size() - 1;
for ( ; us >= 0 && them >= 0; us--, them--)
{
if (cur[us].branch != targ[them].branch)
break;
}
us++, them++;
if (us < (int) cur.size() && them < (int) targ.size() && us >= 0 &&
them >= 0)
*cx = targ[them];
}
/*
* Returns the number of stairs the player would need to take to go from
* the 'first' level to the 'second' level. If there's no obvious route between
* 'first' and 'second', returns -1. If first == second, returns 0.
*/
int level_distance(level_id first, level_id second)
{
if (first == second) return 0;
std::vector<level_id> fv, sv;
// If in the same branch, easy.
if (first.branch == second.branch)
return abs(first.depth - second.depth);
// Figure out the dungeon structure between the two levels.
trackback(fv, first.branch, first.depth);
trackback(sv, second.branch, second.depth);
level_id intersect;
track_intersect(fv, sv, &intersect);
if (intersect.depth == -1) // No common ground?
return -1;
int distance = 0;
// If the common branch is not the same as the current branch, we'll
// have to walk up the branch tree until we get to the common branch.
while (first.branch != intersect.branch)
{
distance += first.depth;
find_parent_branch(first.branch, first.depth,
&first.branch, &first.depth);
if (!first.depth)
return -1;
}
// Now first.branch == intersect.branch
distance += abs(first.depth - intersect.depth);
bool ignore_end = true;
for (int i = sv.size() - 1; i >= 0; --i)
{
if (ignore_end)
{
if (sv[i].branch == intersect.branch)
ignore_end = false;
continue;
}
distance += sv[i].depth;
}
return distance;
}
static struct
{
const char *branch_name, *full_name;
char hotkey;
} branches [] =
{
{ "Dungeon", "the Main Dungeon", 'D' },
{ "Dis", "Dis", 'I' },
{ "Gehenna", "Gehenna", 'W' },
{ "Hell", "Hell", 'U' },
{ "Cocytus", "Cocytus", 'X' },
{ "Tartarus", "Tartarus", 'Y' },
{ "Inferno", "", 'R' },
{ "The Pit", "", '0' },
{ "------------", "", '-' },
{ "------------", "", '-' },
{ "Orcish Mines", "the Orcish Mines", 'O' },
{ "Hive", "the Hive", 'H' },
{ "Lair", "the Lair", 'L' },
{ "Slime Pits", "the Slime Pits", 'M' },
{ "Vaults", "the Vaults", 'V' },
{ "Crypt", "the Crypt", 'C' },
{ "Hall of Blades", "the Hall of Blades", 'B' },
{ "Zot", "the Realm of Zot", 'Z' },
{ "Temple", "the Ecumenical Temple", 'T' },
{ "Snake Pit", "the Snake Pit", 'P' },
{ "Elven Halls", "the Elven Halls", 'E' },
{ "Tomb", "the Tomb", 'G' },
{ "Swamp", "the Swamp", 'S' }
};
static struct
{
const char *abbr;
unsigned char branch;
} branch_abbrvs[] =
{
{ "D", BRANCH_MAIN_DUNGEON },
{ "Dis", BRANCH_DIS },
{ "Geh", BRANCH_GEHENNA },
{ "Hell", BRANCH_VESTIBULE_OF_HELL },
{ "Coc", BRANCH_COCYTUS },
{ "Tar", BRANCH_TARTARUS },
{ "inf", BRANCH_INFERNO },
{ "pit", BRANCH_THE_PIT },
{ "Orc", BRANCH_ORCISH_MINES },
{ "Hive", BRANCH_HIVE },
{ "Lair", BRANCH_LAIR },
{ "Slime", BRANCH_SLIME_PITS },
{ "Vault", BRANCH_VAULTS },
{ "Crypt", BRANCH_CRYPT },
{ "Blades", BRANCH_HALL_OF_BLADES },
{ "Zot", BRANCH_HALL_OF_ZOT },
{ "Temple", BRANCH_ECUMENICAL_TEMPLE },
{ "Snake", BRANCH_SNAKE_PIT },
{ "Elf", BRANCH_ELVEN_HALLS },
{ "Tomb", BRANCH_TOMB },
{ "Swamp", BRANCH_SWAMP },
};
void set_trans_travel_dest(char *buffer, int maxlen, const level_pos &target)
{
if (!buffer) return;
const char *branch = NULL;
for (unsigned i = 0; i < sizeof(branch_abbrvs) / sizeof(branch_abbrvs[0]);
++i)
{
if (branch_abbrvs[i].branch == target.id.branch)
{
branch = branch_abbrvs[i].abbr;
break;
}
}
if (!branch) return;
unsigned char branch_id = target.id.branch;
// Show level+depth information and tack on an @(x,y) if the player
// wants to go to a specific square on the target level. We don't use
// actual coordinates since that will give away level information we
// don't want the player to have.
if (branch_id != BRANCH_ECUMENICAL_TEMPLE
&& branch_id != BRANCH_HALL_OF_BLADES
&& branch_id != BRANCH_VESTIBULE_OF_HELL)
snprintf(buffer, maxlen, "%s:%d%s", branch, target.id.depth,
target.pos.x != -1? " @ (x,y)" : "");
else
snprintf(buffer, maxlen, "%s%s", branch,
target.pos.x != -1? " @ (x,y)" : "");
}
// Returns the level on the given branch that's closest to the player's
// current location.
static int get_nearest_level_depth(unsigned char branch)
{
int depth = 1;
// Hell needs special treatment, because we can't walk up
// Hell and its branches to the main dungeon.
if (branch == BRANCH_MAIN_DUNGEON &&
(player_in_branch( BRANCH_VESTIBULE_OF_HELL ) ||
player_in_branch( BRANCH_COCYTUS ) ||
player_in_branch( BRANCH_TARTARUS ) ||
player_in_branch( BRANCH_DIS ) ||
player_in_branch( BRANCH_GEHENNA )))
return you.hell_exit + 1;
level_id id = level_id::get_current_level_id();
do
{
find_parent_branch(id.branch, id.depth,
&id.branch, &id.depth);
if (id.depth && id.branch == branch)
{
depth = id.depth;
break;
}
} while (id.depth);
return depth;
}
static char trans_travel_dest[30];
// Returns true if the player character knows of the existence of the given
// branch (which would make the branch a valid target for interlevel travel).
static bool is_known_branch(unsigned char branch)
{
// The main dungeon is always known.
if (branch == BRANCH_MAIN_DUNGEON) return true;
// If we're in the branch, it darn well is known.
if (you.where_are_you == branch) return true;
// If the overmap knows the stairs to this branch, we know the branch.
unsigned char par;
int pdep;
find_parent_branch(branch, 1, &par, &pdep);
if (pdep)
return true;
// Do a brute force search in the travel cache for this branch.
return travel_cache.is_known_branch(branch);
// Doing this to check for the reachability of a branch is slow enough to
// be noticeable.
// Ask interlevel travel if it knows how to go there. If it knows how to
// get (partway) there, return true.
// level_pos pos;
// pos.id.branch = branch;
// pos.id.depth = 1;
// return find_transtravel_square(pos, false) != 0;
}
/*
* Returns a list of the branches that the player knows the location of the
* stairs to, in the same order as overmap.cc lists them.
*/
static std::vector<unsigned char> get_known_branches()
{
// Lifted from overmap.cc. XXX: Move this to a better place?
const unsigned char list_order[] =
{
BRANCH_MAIN_DUNGEON,
BRANCH_ECUMENICAL_TEMPLE,
BRANCH_ORCISH_MINES, BRANCH_ELVEN_HALLS,
BRANCH_LAIR, BRANCH_SWAMP, BRANCH_SLIME_PITS, BRANCH_SNAKE_PIT,
BRANCH_HIVE,
BRANCH_VAULTS, BRANCH_HALL_OF_BLADES, BRANCH_CRYPT, BRANCH_TOMB,
BRANCH_VESTIBULE_OF_HELL,
BRANCH_DIS, BRANCH_GEHENNA, BRANCH_COCYTUS, BRANCH_TARTARUS,
BRANCH_HALL_OF_ZOT
};
std::vector<unsigned char> branches;
for (unsigned i = 0; i < sizeof list_order / sizeof(*list_order); ++i)
{
if (is_known_branch(list_order[i]))
branches.push_back(list_order[i]);
}
return branches;
}
static int prompt_travel_branch()
{
unsigned char branch = BRANCH_MAIN_DUNGEON; // Default
std::vector<unsigned char> br = get_known_branches();
// Don't kill the prompt even if the only branch we know is the main dungeon
// This keeps things consistent for the player.
if (br.size() < 1) return branch;
bool waypoint_list = false;
int waycount = travel_cache.get_waypoint_count();
for ( ; ; )
{
char buf[100];
if (waypoint_list)
travel_cache.list_waypoints();
else
{
int linec = 0;
std::string line;
for (int i = 0, count = br.size(); i < count; ++i, ++linec)
{
if (linec == 4)
{
linec = 0;
mpr(line.c_str());
line = "";
}
snprintf(buf, sizeof buf, "(%c) %-14s ", branches[br[i]].hotkey,
branches[br[i]].branch_name);
line += buf;
}
if (line.length())
mpr(line.c_str());
}
char shortcuts[100];
*shortcuts = 0;
if (*trans_travel_dest || waycount || waypoint_list)
{
strncpy(shortcuts, "(", sizeof shortcuts);
if (waypoint_list)
strncat(shortcuts, "[*] lists branches", sizeof shortcuts);
else if (waycount)
strncat(shortcuts, "[*] lists waypoints", sizeof shortcuts);
if (*trans_travel_dest)
{
char travel_dest[60];
snprintf(travel_dest, sizeof travel_dest, "[Enter] for %s",
trans_travel_dest);
if (waypoint_list || waycount)
strncat( shortcuts, ", ", sizeof shortcuts);
strncat(shortcuts, travel_dest, sizeof shortcuts);
}
strncat(shortcuts, ") ", sizeof shortcuts);
}
snprintf(buf, sizeof buf, "Where do you want to go? %s", shortcuts);
mpr(buf, MSGCH_PROMPT);
int keyin = get_ch();
switch (keyin)
{
case ESCAPE:
return (ID_CANCEL);
case '\n': case '\r':
return (ID_REPEAT);
case '<':
return (ID_UP);
case '>':
return (ID_DOWN);
case '*':
if (waypoint_list || waycount)
{
waypoint_list = !waypoint_list;
mesclr();
continue;
}
break;
default:
// Is this a branch hotkey?
for (int i = 0, count = br.size(); i < count; ++i)
{
if (toupper(keyin) == branches[br[i]].hotkey)
return (br[i]);
}
// Possibly a waypoint number?
if (keyin >= '0' && keyin <= '9')
return (-1 - (keyin - '0'));
return (ID_CANCEL);
}
}
}
static int prompt_travel_depth(unsigned char branch)
{
// Handle one-level branches by not prompting.
if (branch == BRANCH_ECUMENICAL_TEMPLE ||
branch == BRANCH_VESTIBULE_OF_HELL ||
branch == BRANCH_HALL_OF_BLADES)
return 1;
char buf[100];
int depth = get_nearest_level_depth(branch);
snprintf(buf, sizeof buf, "What level of %s do you want to go to? "
"[default %d] ", branches[branch].full_name, depth);
mesclr();
mpr(buf, MSGCH_PROMPT);
if (!cancelable_get_line( buf, sizeof buf ))
return 0;
if (*buf)
depth = atoi(buf);
return depth;
}
static bool is_hell_branch(int branch)
{
return branch == BRANCH_DIS || branch == BRANCH_TARTARUS
|| branch == BRANCH_COCYTUS || branch == BRANCH_GEHENNA;
}
static level_pos find_up_level()
{
level_id curr = level_id::get_current_level_id();
curr.depth--;
if (is_hell_branch(curr.branch))
{
curr.branch = BRANCH_VESTIBULE_OF_HELL;
curr.depth = 1;
return (curr);
}
if (curr.depth < 1)
{
if (curr.branch != BRANCH_MAIN_DUNGEON)
{
level_id parent;
find_parent_branch(curr.branch, curr.depth,
&parent.branch, &parent.depth);
if (parent.depth > 0)
return (parent);
else if (curr.branch == BRANCH_VESTIBULE_OF_HELL)
{
parent.branch = BRANCH_MAIN_DUNGEON;
parent.depth = you.hell_exit + 1;
return (parent);
}
}
return level_pos();
}
return (curr);
}
static level_pos find_down_level()
{
level_id curr = level_id::get_current_level_id();
curr.depth++;
return (curr);
}
static level_pos prompt_translevel_target()
{
level_pos target;
int branch = prompt_travel_branch();
if (branch == ID_CANCEL)
return (target);
// If user chose to repeat last travel, return that.
if (branch == ID_REPEAT)
return (travel_target);
if (branch == ID_UP)
{
target = find_up_level();
if (target.id.depth > -1)
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
target);
return (target);
}
if (branch == ID_DOWN)
{
target = find_down_level();
if (target.id.depth > -1)
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
target);
return (target);
}
if (branch < 0)
{
travel_cache.travel_to_waypoint(-branch - 1);
return target;
}
target.id.branch = branch;
// User's chosen a branch, so now we ask for a level.
target.id.depth = prompt_travel_depth(target.id.branch);
if (target.id.depth < 1 || target.id.depth >= MAX_LEVELS)
target.id.depth = -1;
if (target.id.depth > -1)
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
target);
return target;
}
void start_translevel_travel(const level_pos &pos)
{
travel_target = pos;
if (pos.id != level_id::get_current_level_id())
{
if (!loadlev_populate_stair_distances(pos))
{
mpr("Level memory is imperfect, aborting.");
return ;
}
}
else
populate_stair_distances(pos);
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
travel_target);
start_translevel_travel(false);
}
void start_translevel_travel(bool prompt_for_destination)
{
// Update information for this level. We need it even for the prompts, so
// we can't wait to confirm that the user chose to initiate travel.
travel_cache.get_level_info(level_id::get_current_level_id()).update();
if (prompt_for_destination)
{
// prompt_translevel_target may actually initiate travel directly if
// the user chose a waypoint instead of a branch + depth. As far as
// we're concerned, if the target depth is unset, we need to take no
// further action.
level_pos target = prompt_translevel_target();
if (target.id.depth == -1) return;
travel_target = target;
}
if (level_id::get_current_level_id() == travel_target.id &&
(travel_target.pos.x == -1 ||
(travel_target.pos.x == you.x_pos &&
travel_target.pos.y == you.y_pos)))
{
mpr("You're already here!");
return ;
}
if (travel_target.id.depth > -1)
{
you.running = RUN_INTERLEVEL;
you.run_x = you.run_y = 0;
last_stair.depth = -1;
start_running();
}
}
int stair_direction(int stair)
{
return ((stair < DNGN_STONE_STAIRS_UP_I
|| stair > DNGN_ROCK_STAIRS_UP)
&& (stair < DNGN_RETURN_FROM_ORCISH_MINES
|| stair > DNGN_RETURN_FROM_SWAMP))
? '>' : '<';
}
int trans_negotiate_stairs()
{
return stair_direction(grd[you.x_pos][you.y_pos]);
}
int absdungeon_depth(unsigned char branch, int subdepth)
{
int realdepth = subdepth - 1;
if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
realdepth = subdepth + you.branch_stairs[branch - 10];
if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
realdepth = subdepth + 26;
return realdepth;
}
int subdungeon_depth(unsigned char branch, int depth)
{
int curr_subdungeon_level = depth + 1;
// maybe last part better expresssed as <= PIT {dlb}
if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
curr_subdungeon_level = depth - 26;
/* Remember, must add this to the death_string in ouch */
if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
curr_subdungeon_level = depth
- you.branch_stairs[branch - 10];
return curr_subdungeon_level;
}
static int target_distance_from(const coord_def &pos)
{
for (int i = 0, count = curr_stairs.size(); i < count; ++i)
if (curr_stairs[i].position == pos)
return curr_stairs[i].distance;
return -1;
}
/*
* Sets best_stair to the coordinates of the best stair on the player's current
* level to take to get to the 'target' level. Should be called with 'distance'
* set to 0, 'stair' set to (you.x_pos, you.y_pos) and 'best_distance' set to
* -1. 'cur' should be the player's current level.
*
* If best_stair remains unchanged when this function returns, there is no
* travel-safe path between the player's current level and the target level OR
* the player's current level *is* the target level.
*
* This function relies on the point_distance array being correctly populated
* with a floodout call to find_travel_pos starting from the player's location.
*/
static int find_transtravel_stair( const level_id &cur,
const level_pos &target,
int distance,
const coord_def &stair,
level_id &closest_level,
int &best_level_distance,
coord_def &best_stair)
{
int local_distance = -1;
level_id player_level = level_id::get_current_level_id();
// Have we reached the target level?
if (cur == target.id)
{
// If there's no target position on the target level, or we're on the
// target, we're home.
if (target.pos.x == -1 || target.pos == stair)
return distance;
// If there *is* a target position, we need to work out our distance
// from it.
int deltadist = target_distance_from(stair);
if (deltadist == -1 && cur == player_level)
{
// Okay, we don't seem to have a distance available to us, which
// means we're either (a) not standing on stairs or (b) whoever
// initiated interlevel travel didn't call
// populate_stair_distances. Assuming we're not on stairs, that
// situation can arise only if interlevel travel has been triggered
// for a location on the same level. If that's the case, we can get
// the distance off the point_distance matrix.
deltadist = point_distance[target.pos.x][target.pos.y];
if (!deltadist &&
(stair.x != target.pos.x || stair.y != target.pos.y))
deltadist = -1;
}
if (deltadist != -1)
{
local_distance = distance + deltadist;
// See if this is a degenerate case of interlevel travel:
// A degenerate case of interlevel travel decays to normal travel;
// we identify this by checking if:
// a. The current level is the target level.
// b. The target square is reachable from the 'current' square.
// c. The current square is where the player is.
//
// Note that even if this *is* degenerate, interlevel travel may
// still be able to find a shorter route, since it can consider
// routes that leave and reenter the current level.
if (player_level == target.id && stair.x == you.x_pos
&& stair.y == you.y_pos)
best_stair = target.pos;
// The local_distance is already set, but there may actually be
// stairs we can take that'll get us to the target faster than the
// direct route, so we also try the stairs.
}
}
LevelInfo &li = travel_cache.get_level_info(cur);
std::vector<stair_info> &stairs = li.get_stairs();
// this_stair being NULL is perfectly acceptable, since we start with
// coords as the player coords, and the player need not be standing on
// stairs.
stair_info *this_stair = li.get_stair(stair);
if (!this_stair && cur != player_level)
{
// Whoops, there's no stair in the travel cache for the current
// position, and we're not on the player's current level (i.e., there
// certainly *should* be a stair here). Since we can't proceed in any
// reasonable way, bail out.
return local_distance;
}
for (int i = 0, count = stairs.size(); i < count; ++i)
{
stair_info &si = stairs[i];
int deltadist = li.distance_between(this_stair, &si);
if (!this_stair)
{
deltadist = point_distance[si.position.x][si.position.y];
if (!deltadist &&
(you.x_pos != si.position.x || you.y_pos != si.position.y))
deltadist = -1;
}
// deltadist == 0 is legal (if this_stair is NULL), since the player
// may be standing on the stairs. If two stairs are disconnected,
// deltadist has to be negative.
if (deltadist < 0) continue;
int dist2stair = distance + deltadist;
if (si.distance == -1 || si.distance > dist2stair)
{
si.distance = dist2stair;
dist2stair += Options.travel_stair_cost;
++dist2stair; // Account for the cost of taking the stairs
// Already too expensive? Short-circuit.
if (local_distance != -1 && dist2stair >= local_distance)
continue;
const level_pos &dest = si.destination;
// We can only short-circuit the stair-following process if we
// have no exact target location. If there *is* an exact target
// location, we can't follow stairs for which we have incomplete
// information.
if (target.pos.x == -1 && dest.id == target.id)
{
if (local_distance == -1 || local_distance > dist2stair)
{
local_distance = dist2stair;
if (cur == player_level && you.x_pos == stair.x &&
you.y_pos == stair.y)
best_stair = si.position;
}
continue;
}
if (dest.id.depth > -1) { // We have a valid level descriptor.
int dist = level_distance(dest.id, target.id);
if (dist != -1 &&
(dist < best_level_distance ||
best_level_distance == -1))
{
best_level_distance = dist;
closest_level = dest.id;
}
}
// If we don't know where these stairs go, we can't take them.
if (!dest.is_valid()) continue;
// We need to get the stairs at the new location and set the
// distance on them as well.
LevelInfo &lo = travel_cache.get_level_info(dest.id);
stair_info *so = lo.get_stair(dest.pos);
if (so)
{
if (so->distance == -1 || so->distance > dist2stair)
so->distance = dist2stair;
else
continue; // We've already been here.
}
// Okay, take these stairs and keep going.
int newdist = find_transtravel_stair(dest.id, target,
dist2stair, dest.pos, closest_level,
best_level_distance, best_stair);
if (newdist != -1 &&
(local_distance == -1 || local_distance > newdist))
{
local_distance = newdist;
if (cur == player_level && you.x_pos == stair.x &&
you.y_pos == stair.y)
best_stair = si.position;
}
}
}
return local_distance;
}
static bool loadlev_populate_stair_distances(const level_pos &target)
{
crawl_environment tmp = env;
if (!travel_load_map(target.id.branch,
absdungeon_depth(target.id.branch, target.id.depth)))
{
env = tmp;
return false;
}
std::vector<coord_def> old_excludes = curr_excludes;
curr_excludes.clear();
LevelInfo &li = travel_cache.get_level_info(target.id);
li.set_level_excludes();
populate_stair_distances(target);
env = tmp;
curr_excludes = old_excludes;
return !curr_stairs.empty();
}
static void populate_stair_distances(const level_pos &target)
{
// Populate point_distance.
find_travel_pos(target.pos.x, target.pos.y, NULL, NULL, NULL);
LevelInfo &li = travel_cache.get_level_info(target.id);
const std::vector<stair_info> &stairs = li.get_stairs();
curr_stairs.clear();
for (int i = 0, count = stairs.size(); i < count; ++i)
{
stair_info si = stairs[i];
si.distance = point_distance[si.position.x][si.position.y];
if (!si.distance && target.pos != si.position)
si.distance = -1;
if (si.distance < -1)
si.distance = -1;
curr_stairs.push_back(si);
}
}
static int find_transtravel_square(const level_pos &target, bool verbose)
{
level_id current = level_id::get_current_level_id();
coord_def best_stair = { -1, -1 };
coord_def cur_stair = { you.x_pos, you.y_pos };
level_id closest_level;
int best_level_distance = -1;
travel_cache.reset_distances();
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
find_transtravel_stair(current, target,
0, cur_stair, closest_level,
best_level_distance, best_stair);
if (best_stair.x != -1 && best_stair.y != -1)
{
you.run_x = best_stair.x;
you.run_y = best_stair.y;
return 1;
}
else if (best_level_distance != -1 && closest_level != current
&& target.pos.x == -1)
{
int current_dist = level_distance(current, target.id);
level_pos newlev;
newlev.id = closest_level;
if (current_dist == -1 || best_level_distance < current_dist)
return find_transtravel_square(newlev, verbose);
}
if (verbose && target.id != current)
mpr("Sorry, I don't know how to get there.");
return 0;
}
void start_travel(int x, int y)
{
// Redundant target?
if (x == you.x_pos && y == you.y_pos) return ;
// Start running
you.running = RUN_TRAVEL;
you.run_x = x;
you.run_y = y;
// Remember where we're going so we can easily go back if interrupted.
you.travel_x = x;
you.travel_y = y;
// Check whether we can get to the square.
find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
if (point_distance[x][y] == 0 &&
(x != you.x_pos || you.run_y != you.y_pos) &&
is_travel_ok(x, y, false))
{
// We'll need interlevel travel to get here.
travel_target.id = level_id::get_current_level_id();
travel_target.pos.x = x;
travel_target.pos.y = y;
you.running = RUN_INTERLEVEL;
you.run_x = you.run_y = 0;
last_stair.depth = -1;
// We need the distance of the target from the various stairs around.
populate_stair_distances(travel_target);
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
travel_target);
}
start_running();
}
void start_explore()
{
you.running = RUN_EXPLORE;
if (Options.explore_stop)
{
// Clone shadow array off map
copy(env.map, mapshadow);
}
start_running();
}
/*
* Given a feature vector, arranges the features in the order that the player
* is most likely to be interested in. Currently, the only thing it does is to
* put altars of the player's religion at the front of the list.
*/
void arrange_features(std::vector<coord_def> &features)
{
for (int i = 0, count = features.size(); i < count; ++i)
{
if (is_player_altar(features[i]))
{
int place = i;
// Shuffle this altar as far up the list as possible.
for (int j = place - 1; j >= 0; --j)
{
if (is_altar(features[j]))
{
if (is_player_altar(features[j]))
break;
coord_def temp = features[j];
features[j] = features[place];
features[place] = temp;
place = j;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// Interlevel travel classes
static void writeCoord(FILE *file, const coord_def &pos)
{
writeShort(file, pos.x);
writeShort(file, pos.y);
}
static void readCoord(FILE *file, coord_def &pos)
{
pos.x = readShort(file);
pos.y = readShort(file);
}
level_id level_id::get_current_level_id()
{
level_id id;
id.branch = you.where_are_you;
id.depth = subdungeon_depth(you.where_are_you, you.your_level);
return id;
}
level_id level_id::get_next_level_id(const coord_def &pos)
{
short gridc = grd[pos.x][pos.y];
level_id id = get_current_level_id();
switch (gridc)
{
case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II:
case DNGN_STONE_STAIRS_DOWN_III: case DNGN_ROCK_STAIRS_DOWN:
id.depth++;
break;
case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III: case DNGN_ROCK_STAIRS_UP:
id.depth--;
break;
case DNGN_ENTER_HELL:
id.branch = BRANCH_VESTIBULE_OF_HELL;
id.depth = 1;
break;
case DNGN_ENTER_DIS:
id.branch = BRANCH_DIS;
id.depth = 1;
break;
case DNGN_ENTER_GEHENNA:
id.branch = BRANCH_GEHENNA;
id.depth = 1;
break;
case DNGN_ENTER_COCYTUS:
id.branch = BRANCH_COCYTUS;
id.depth = 1;
break;
case DNGN_ENTER_TARTARUS:
id.branch = BRANCH_TARTARUS;
id.depth = 1;
break;
case DNGN_ENTER_ORCISH_MINES:
id.branch = BRANCH_ORCISH_MINES;
id.depth = 1;
break;
case DNGN_ENTER_HIVE:
id.branch = BRANCH_HIVE;
id.depth = 1;
break;
case DNGN_ENTER_LAIR:
id.branch = BRANCH_LAIR;
id.depth = 1;
break;
case DNGN_ENTER_SLIME_PITS:
id.branch = BRANCH_SLIME_PITS;
id.depth = 1;
break;
case DNGN_ENTER_VAULTS:
id.branch = BRANCH_VAULTS;
id.depth = 1;
break;
case DNGN_ENTER_CRYPT:
id.branch = BRANCH_CRYPT;
id.depth = 1;
break;
case DNGN_ENTER_HALL_OF_BLADES:
id.branch = BRANCH_HALL_OF_BLADES;
id.depth = 1;
break;
case DNGN_ENTER_ZOT:
id.branch = BRANCH_HALL_OF_ZOT;
id.depth = 1;
break;
case DNGN_ENTER_TEMPLE:
id.branch = BRANCH_ECUMENICAL_TEMPLE;
id.depth = 1;
break;
case DNGN_ENTER_SNAKE_PIT:
id.branch = BRANCH_SNAKE_PIT;
id.depth = 1;
break;
case DNGN_ENTER_ELVEN_HALLS:
id.branch = BRANCH_ELVEN_HALLS;
id.depth = 1;
break;
case DNGN_ENTER_TOMB:
id.branch = BRANCH_TOMB;
id.depth = 1;
break;
case DNGN_ENTER_SWAMP:
id.branch = BRANCH_SWAMP;
id.depth = 1;
break;
case DNGN_RETURN_FROM_ORCISH_MINES: case DNGN_RETURN_FROM_HIVE:
case DNGN_RETURN_FROM_LAIR: case DNGN_RETURN_FROM_SLIME_PITS:
case DNGN_RETURN_FROM_VAULTS: case DNGN_RETURN_FROM_CRYPT:
case DNGN_RETURN_FROM_HALL_OF_BLADES: case DNGN_RETURN_FROM_ZOT:
case DNGN_RETURN_FROM_TEMPLE: case DNGN_RETURN_FROM_SNAKE_PIT:
case DNGN_RETURN_FROM_ELVEN_HALLS: case DNGN_RETURN_FROM_TOMB:
case DNGN_RETURN_FROM_SWAMP:
find_parent_branch(id.branch, id.depth, &id.branch, &id.depth);
if (!id.depth)
{
id.branch = find_parent_branch(you.where_are_you);
id.depth = -1;
}
break;
}
return id;
}
void level_id::save(FILE *file) const
{
writeByte(file, branch);
writeShort(file, depth);
}
void level_id::load(FILE *file)
{
branch = readByte(file);
depth = readShort(file);
}
void level_pos::save(FILE *file) const
{
id.save(file);
writeCoord(file, pos);
}
void level_pos::load(FILE *file)
{
id.load(file);
readCoord(file, pos);
}
void stair_info::save(FILE *file) const
{
writeCoord(file, position);
destination.save(file);
writeByte(file, guessed_pos? 1 : 0);
}
void stair_info::load(FILE *file)
{
readCoord(file, position);
destination.load(file);
guessed_pos = readByte(file) != 0;
}
LevelInfo::LevelInfo(const LevelInfo &other)
{
stairs = other.stairs;
excludes = other.excludes;
int sz = stairs.size() * stairs.size();
stair_distances = new short [ sz ];
if (other.stair_distances)
memcpy(stair_distances, other.stair_distances, sz * sizeof(int));
}
const LevelInfo &LevelInfo::operator = (const LevelInfo &other)
{
if (&other == this)
return *this;
stairs = other.stairs;
excludes = other.excludes;
int sz = stairs.size() * stairs.size();
delete [] stair_distances;
stair_distances = new short [ sz ];
if (other.stair_distances)
memcpy(stair_distances, other.stair_distances, sz * sizeof(short));
return *this;
}
LevelInfo::~LevelInfo()
{
delete [] stair_distances;
}
void LevelInfo::set_level_excludes()
{
curr_excludes = excludes;
}
void LevelInfo::update()
{
// We need to update all stair information and distances on this level.
update_excludes();
// First, set excludes, so that stair distances will be correctly populated.
excludes = curr_excludes;
// First, we get all known stairs
std::vector<coord_def> stair_positions;
get_stairs(stair_positions);
// Make sure our stair list is correct.
correct_stair_list(stair_positions);
update_stair_distances();
}
void LevelInfo::update_stair_distances()
{
// Now we update distances for all the stairs, relative to all other
// stairs.
for (int s = 0, end = stairs.size(); s < end; ++s)
{
// For each stair, we need to ask travel to populate the distance
// array.
find_travel_pos(stairs[s].position.x, stairs[s].position.y,
NULL, NULL, NULL);
for (int other = 0; other < end; ++other)
{
int ox = stairs[other].position.x,
oy = stairs[other].position.y;
int dist = point_distance[ox][oy];
// Note dist == 0 is illegal because we can't have two stairs on
// the same square.
if (dist <= 0) dist = -1;
stair_distances[ s * stairs.size() + other ] = dist;
stair_distances[ other * stairs.size() + s ] = dist;
}
}
}
void LevelInfo::update_stair(int x, int y, const level_pos &p, bool guess)
{
stair_info *si = get_stair(x, y);
// What 'guess' signifies: whenever you take a stair from A to B, the
// travel code knows that the stair takes you from A->B. In that case,
// update_stair() is called with guess == false.
//
// Unfortunately, Crawl doesn't guarantee that A->B implies B->A, but the
// travel code has to assume that anyway (because that's what the player
// will expect), and call update_stair() again with guess == true.
//
// The idea of using 'guess' is that we'll update the stair's destination
// with a guess only if we know that the currently set destination is
// itself a guess.
//
if (si && (si->guessed_pos || !guess))
{
si->destination = p;
si->guessed_pos = guess;
if (!guess && p.id.branch == BRANCH_VESTIBULE_OF_HELL
&& id.branch == BRANCH_MAIN_DUNGEON)
travel_hell_entry = p;
}
}
stair_info *LevelInfo::get_stair(int x, int y)
{
coord_def c = { x, y };
return get_stair(c);
}
stair_info *LevelInfo::get_stair(const coord_def &pos)
{
int index = get_stair_index(pos);
return index != -1? &stairs[index] : NULL;
}
int LevelInfo::get_stair_index(const coord_def &pos) const
{
for (int i = stairs.size() - 1; i >= 0; --i)
{
if (stairs[i].position == pos)
return i;
}
return -1;
}
void LevelInfo::add_waypoint(const coord_def &pos)
{
if (pos.x < 0 || pos.y < 0) return;
// First, make sure we don't already have this position in our stair list.
for (int i = 0, sz = stairs.size(); i < sz; ++i)
if (stairs[i].position == pos)
return;
stair_info si;
si.position = pos;
si.destination.id.depth = -2; // Magic number for waypoints.
stairs.push_back(si);
delete [] stair_distances;
stair_distances = new short [ stairs.size() * stairs.size() ];
update_stair_distances();
}
void LevelInfo::remove_waypoint(const coord_def &pos)
{
for (std::vector<stair_info>::iterator i = stairs.begin();
i != stairs.end(); ++i)
{
if (i->position == pos && i->destination.id.depth == -2)
{
stairs.erase(i);
break;
}
}
delete [] stair_distances;
stair_distances = new short [ stairs.size() * stairs.size() ];
update_stair_distances();
}
void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
{
// If we have a waypoint on this level, we'll always delete stair_distances
delete [] stair_distances;
stair_distances = NULL;
// First we kill any stairs in 'stairs' that aren't there in 's'.
for (std::vector<stair_info>::iterator i = stairs.begin();
i != stairs.end(); ++i)
{
// Waypoints are not stairs, so we skip them.
if (i->destination.id.depth == -2) continue;
bool found = false;
for (int j = s.size() - 1; j >= 0; --j)
{
if (s[j] == i->position)
{
found = true;
break;
}
}
if (!found)
stairs.erase(i--);
}
// For each stair in 's', make sure we have a corresponding stair
// in 'stairs'.
for (int i = 0, sz = s.size(); i < sz; ++i)
{
bool found = false;
for (int j = stairs.size() - 1; j >= 0; --j)
{
if (s[i] == stairs[j].position)
{
found = true;
break;
}
}
if (!found)
{
stair_info si;
si.position = s[i];
si.destination.id = level_id::get_next_level_id(s[i]);
if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
&& id.branch == BRANCH_MAIN_DUNGEON
&& travel_hell_entry.is_valid())
si.destination = travel_hell_entry;
// We don't know where on the next level these stairs go to, but
// that can't be helped. That information will have to be filled
// in whenever the player takes these stairs.
stairs.push_back(si);
}
}
stair_distances = new short [ stairs.size() * stairs.size() ];
}
int LevelInfo::distance_between(const stair_info *s1, const stair_info *s2)
const
{
if (!s1 || !s2) return 0;
if (s1 == s2) return 0;
int i1 = get_stair_index(s1->position),
i2 = get_stair_index(s2->position);
if (i1 == -1 || i2 == -1) return 0;
return stair_distances[ i1 * stairs.size() + i2 ];
}
void LevelInfo::get_stairs(std::vector<coord_def> &st)
{
// These are env map coords, not grid coordinates.
for (int y = 0; y < GYM - 1; ++y)
{
for (int x = 0; x < GXM - 1; ++x)
{
unsigned char grid = grd[x + 1][y + 1];
unsigned char envc = (unsigned char) env.map[x][y];
if (envc && is_travelable_stair(grid))
{
// Convert to grid coords, because that's what we use
// everywhere else.
coord_def stair = { x + 1, y + 1 };
st.push_back(stair);
}
}
}
}
void LevelInfo::reset_distances()
{
for (int i = 0, count = stairs.size(); i < count; ++i)
{
stairs[i].reset_distance();
}
}
bool LevelInfo::is_known_branch(unsigned char branch) const
{
for (int i = 0, count = stairs.size(); i < count; ++i)
{
if (stairs[i].destination.id.branch == branch)
return true;
}
return false;
}
void LevelInfo::travel_to_waypoint(const coord_def &pos)
{
stair_info *target = get_stair(pos);
if (!target) return;
curr_stairs.clear();
for (int i = 0, sz = stairs.size(); i < sz; ++i)
{
if (stairs[i].destination.id.depth == -2) continue;
stair_info si = stairs[i];
si.distance = distance_between(target, &stairs[i]);
curr_stairs.push_back(si);
}
start_translevel_travel(false);
}
void LevelInfo::save(FILE *file) const
{
int stair_count = stairs.size();
// How many stairs do we know of?
writeShort(file, stair_count);
for (int i = 0; i < stair_count; ++i)
stairs[i].save(file);
if (stair_count)
{
// XXX Assert stair_distances != NULL?
// Save stair distances as short ints.
for (int i = stair_count * stair_count - 1; i >= 0; --i)
writeShort(file, stair_distances[i]);
}
writeShort(file, excludes.size());
if (excludes.size())
{
for (int i = 0, count = excludes.size(); i < count; ++i)
{
writeShort(file, excludes[i].x);
writeShort(file, excludes[i].y);
}
}
}
#define EXCLUDE_LOAD_LIMIT 20
void LevelInfo::load(FILE *file)
{
stairs.clear();
int stair_count = readShort(file);
for (int i = 0; i < stair_count; ++i)
{
stair_info si;
si.load(file);
stairs.push_back(si);
if (id.branch == BRANCH_MAIN_DUNGEON &&
si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL &&
!travel_hell_entry.is_valid() &&
si.destination.is_valid())
travel_hell_entry = si.destination;
}
if (stair_count)
{
delete [] stair_distances;
stair_distances = new short [ stair_count * stair_count ];
for (int i = stair_count * stair_count - 1; i >= 0; --i)
stair_distances[i] = readShort(file);
}
excludes.clear();
int nexcludes = readShort(file);
if (nexcludes)
{
for (int i = 0; i < nexcludes; ++i)
{
coord_def c;
c.x = readShort(file);
c.y = readShort(file);
excludes.push_back(c);
}
}
}
void LevelInfo::fixup()
{
// The only fixup we do now is for the hell entry.
if (id.branch != BRANCH_MAIN_DUNGEON || !travel_hell_entry.is_valid())
return;
for (int i = 0, count = stairs.size(); i < count; ++i)
{
stair_info &si = stairs[i];
if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
&& !si.destination.is_valid())
si.destination = travel_hell_entry;
}
}
void TravelCache::travel_to_waypoint(int num)
{
if (num < 0 || num >= TRAVEL_WAYPOINT_COUNT) return;
if (waypoints[num].id.depth == -1) return;
travel_target = waypoints[num];
set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
travel_target);
LevelInfo &li = get_level_info(travel_target.id);
li.travel_to_waypoint(travel_target.pos);
}
void TravelCache::list_waypoints() const
{
std::string line;
char dest[30];
char choice[50];
int count = 0;
for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
{
if (waypoints[i].id.depth == -1) continue;
set_trans_travel_dest(dest, sizeof dest, waypoints[i]);
// All waypoints will have @ (x,y), remove that.
char *at = strchr(dest, '@');
if (at)
*--at = 0;
snprintf(choice, sizeof choice, "(%d) %-8s", i, dest);
line += choice;
if (!(++count % 5))
{
mpr(line.c_str());
line = "";
}
}
if (line.length())
mpr(line.c_str());
}
unsigned char TravelCache::is_waypoint(const level_pos &lp) const
{
for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
{
if (lp == waypoints[i])
return '0' + i;
}
return 0;
}
void TravelCache::update_waypoints() const
{
level_pos lp;
lp.id = level_id::get_current_level_id();
memset(curr_waypoints, 0, sizeof curr_waypoints);
for (lp.pos.x = 1; lp.pos.x < GXM; ++lp.pos.x)
{
for (lp.pos.y = 1; lp.pos.y < GYM; ++lp.pos.y)
{
unsigned char wpc = is_waypoint(lp);
if (wpc)
curr_waypoints[lp.pos.x][lp.pos.y] = wpc;
}
}
}
void TravelCache::add_waypoint(int x, int y)
{
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
|| you.level_type == LEVEL_PANDEMONIUM)
{
mpr("Sorry, you can't set a waypoint here.");
return;
}
mesclr();
if (get_waypoint_count())
{
mpr("Existing waypoints");
list_waypoints();
}
mpr("Assign waypoint to what number? (0-9) ", MSGCH_PROMPT);
int keyin = get_ch();
if (keyin < '0' || keyin > '9') return;
int waynum = keyin - '0';
if (waypoints[waynum].is_valid())
{
bool unique_waypoint = true;
for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
{
if (i == waynum) continue;
if (waypoints[waynum] == waypoints[i])
{
unique_waypoint = false;
break;
}
}
if (unique_waypoint)
{
LevelInfo &li = get_level_info(waypoints[waynum].id);
li.remove_waypoint(waypoints[waynum].pos);
}
}
if (x == -1 || y == -1)
{
x = you.x_pos;
y = you.y_pos;
}
coord_def pos = { x, y };
const level_id &lid = level_id::get_current_level_id();
LevelInfo &li = get_level_info(lid);
li.add_waypoint(pos);
waypoints[waynum].id = lid;
waypoints[waynum].pos = pos;
update_waypoints();
}
int TravelCache::get_waypoint_count() const
{
int count = 0;
for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
if (waypoints[i].is_valid())
count++;
return count;
}
void TravelCache::reset_distances()
{
std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
levels.begin();
for ( ; i != levels.end(); ++i)
i->second.reset_distances();
}
bool TravelCache::is_known_branch(unsigned char branch) const
{
std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
levels.begin();
for ( ; i != levels.end(); ++i)
if (i->second.is_known_branch(branch))
return true;
return false;
}
void TravelCache::save(FILE *file) const
{
// Travel cache version information
writeByte(file, TC_MAJOR_VERSION);
writeByte(file, TC_MINOR_VERSION);
// How many levels do we have?
writeShort(file, levels.size());
// Save all the levels we have
std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
levels.begin();
for ( ; i != levels.end(); ++i)
{
i->first.save(file);
i->second.save(file);
}
for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
waypoints[wp].save(file);
}
void TravelCache::load(FILE *file)
{
levels.clear();
// Check version. If not compatible, we just ignore the file altogether.
unsigned char major = readByte(file),
minor = readByte(file);
if (major != TC_MAJOR_VERSION || minor != TC_MINOR_VERSION) return ;
int level_count = readShort(file);
for (int i = 0; i < level_count; ++i)
{
level_id id;
id.load(file);
LevelInfo linfo;
// Must set id before load, or travel_hell_entry will not be
// correctly set.
linfo.id = id;
linfo.load(file);
levels[id] = linfo;
}
for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
waypoints[wp].load(file);
fixup_levels();
}
void TravelCache::set_level_excludes()
{
if (can_travel_interlevel())
get_level_info(level_id::get_current_level_id()).set_level_excludes();
}
void TravelCache::update()
{
if (can_travel_interlevel())
get_level_info(level_id::get_current_level_id()).update();
else
update_excludes();
}
void TravelCache::fixup_levels()
{
std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
levels.begin();
for ( ; i != levels.end(); ++i)
i->second.fixup();
}
bool can_travel_interlevel()
{
return !(you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
|| you.level_type == LEVEL_PANDEMONIUM);
}
env.map[i][j] = unmarshallByte(th);
if (env.map[i][j] == 201) // what is this??
env.map[i][j] = 239;
if (minorVersion < 8)
env.map[i][j] = (unsigned char) unmarshallByte(th);
else
env.map[i][j] = (unsigned short) unmarshallShort(th);
if ((env.map[i][j] & 0xFF) == 201) // what is this??
env.map[i][j] = (env.map[i][j] & 0xFF00U) | 239;
void seed_rng(long seed)
{
#ifdef USE_SYSTEM_RAND
srand(seed);
#else
// MT19937 -- see mt19937ar.cc for details/licence
init_genrand(seed);
#endif
}
void seed_rng()
{
unsigned long seed = time( NULL );
#ifdef USE_MORE_SECURE_SEED
struct tms buf;
seed += times( &buf ) + getpid();
#endif
seed_rng(seed);
#ifdef USE_SYSTEM_RAND
cf_setseed();
#endif
}
}
// required for stuff::coinflip()
#define IB1 1
#define IB2 2
#define IB5 16
#define IB18 131072
#define MASK (IB1 + IB2 + IB5)
// required for stuff::coinflip()
// I got to thinking a bit more about how much people talk
// about RNGs and RLs and also about the issue of performance
// when it comes to Crawl's RNG ... turning to *Numerical
// Recipies in C* (Chapter 7-4, page 298), I hit upon what
// struck me as a fine solution.
// You can read all the details about this function (pretty
// much stolen shamelessly from NRinC) elsewhere, but having
// tested it out myself I think it satisfies Crawl's incessant
// need to decide things on a 50-50 flip of the coin. No call
// to random2() required -- along with all that wonderful math
// and type casting -- and only a single variable its pointer,
// and some bitwise operations to randomly generate 1s and 0s!
// No parameter passing, nothing. Too good to be true, but it
// works as long as cfseed is not set to absolute zero when it
// is initialized ... good for 2**n-1 random bits before the
// pattern repeats (n = long's bitlength on your platform).
// It also avoids problems with poor implementations of rand()
// on some platforms in regards to low-order bits ... a big
// problem if one is only looking for a 1 or a 0 with random2()!
// Talk about a hard sell! Anyway, it returns bool, so please
// use appropriately -- I set it to bool to prevent such
// tomfoolery, as I think that pure RNG and quickly grabbing
// either a value of 1 or 0 should be separated where possible
// to lower overhead in Crawl ... at least until it assembles
// itself into something a bit more orderly :P 16jan2000 {dlb}
// NB(1): cfseed is defined atop stuff.cc
// NB(2): IB(foo) and MASK are defined somewhere in defines.h
// NB(3): the function assumes that cf_setseed() has been called
// beforehand - the call is presently made in acr::initialise()
// right after srandom() and srand() are called (note also
// that cf_setseed() requires rand() - random2 returns int
// but a long can't hurt there).
bool coinflip(void)
{
extern unsigned long cfseed; // defined atop stuff.cc
unsigned long *ptr_cfseed = &cfseed;
if (*ptr_cfseed & IB18)
{
*ptr_cfseed = ((*ptr_cfseed ^ MASK) << 1) | IB1;
return true;
}
else
{
*ptr_cfseed <<= 1;
return false;
}
} // end coinflip()
// cf_setseed should only be called but once in all of Crawl!!! {dlb}
void cf_setseed(void)
{
extern unsigned long cfseed; // defined atop stuff.cc
unsigned long *ptr_cfseed = &cfseed;
do
{
// using rand() here makes these predictable -- bwr
*ptr_cfseed = rand();
}
while (*ptr_cfseed == 0);
}
static std::stack<long> rng_states;
void push_rng_state()
{
// XXX: Does this even work? randart.cc uses it, but I can't find anything
// that says this will restore the RNG to its original state. Anyway, we're
// now using MT with a deterministic push/pop.
rng_states.push(rand());
}
void pop_rng_state()
{
if (!rng_states.empty())
{
seed_rng(rng_states.top());
rng_states.pop();
}
}
unsigned long random_int( void )
{
return rand();
}
#else // USE_SYSTEM_RAND
// MT19937 -- see mt19937ar.cc for details
unsigned long random_int( void )
{
return (genrand_int32());
}
int random2( int max )
{
if (max <= 1)
return (0);
return (static_cast<int>( genrand_int32() / (0xFFFFFFFFUL / max + 1) ));
}
bool coinflip( void )
{
return (static_cast<bool>( random2(2) ));
}
void push_rng_state()
{
push_mt_state();
}
void pop_rng_state()
{
pop_mt_state();
// I got to thinking a bit more about how much people talk
// about RNGs and RLs and also about the issue of performance
// when it comes to Crawl's RNG ... turning to *Numerical
// Recipies in C* (Chapter 7-4, page 298), I hit upon what
// struck me as a fine solution.
// You can read all the details about this function (pretty
// much stolen shamelessly from NRinC) elsewhere, but having
// tested it out myself I think it satisfies Crawl's incessant
// need to decide things on a 50-50 flip of the coin. No call
// to random2() required -- along with all that wonderful math
// and type casting -- and only a single variable its pointer,
// and some bitwise operations to randomly generate 1s and 0s!
// No parameter passing, nothing. Too good to be true, but it
// works as long as cfseed is not set to absolute zero when it
// is initialized ... good for 2**n-1 random bits before the
// pattern repeats (n = long's bitlength on your platform).
// It also avoids problems with poor implementations of rand()
// on some platforms in regards to low-order bits ... a big
// problem if one is only looking for a 1 or a 0 with random2()!
// Talk about a hard sell! Anyway, it returns bool, so please
// use appropriately -- I set it to bool to prevent such
// tomfoolery, as I think that pure RNG and quickly grabbing
// either a value of 1 or 0 should be separated where possible
// to lower overhead in Crawl ... at least until it assembles
// itself into something a bit more orderly :P 16jan2000 {dlb}
// NB(1): cfseed is defined atop stuff.cc
// NB(2): IB(foo) and MASK are defined somewhere in defines.h
// NB(3): the function assumes that cf_setseed() has been called
// beforehand - the call is presently made in acr::initialise()
// right after srandom() and srand() are called (note also
// that cf_setseed() requires rand() - random2 returns int
// but a long can't hurt there).
bool coinflip(void)
{
extern unsigned long cfseed; // defined atop stuff.cc
unsigned long *ptr_cfseed = &cfseed;
if (*ptr_cfseed & IB18)
{
*ptr_cfseed = ((*ptr_cfseed ^ MASK) << 1) | IB1;
return true;
}
else
{
*ptr_cfseed <<= 1;
return false;
}
} // end coinflip()
// cf_setseed should only be called but once in all of Crawl!!! {dlb}
void cf_setseed(void)
{
extern unsigned long cfseed; // defined atop stuff.cc
unsigned long *ptr_cfseed = &cfseed;
do
{
// using rand() here makes these predictable -- bwr
*ptr_cfseed = rand();
}
while (*ptr_cfseed == 0);
}
/*
* File: stash.h
* Summary: Classes tracking player stashes
* Written by: Darshan Shaligram
*/
#ifndef STASH_H
#define STASH_H
#include "AppHdr.h"
#include "shopping.h"
#include <string>
#ifdef STASH_TRACKING
#include <iostream>
#include <map>
#include <vector>
#include "externs.h"
#include "travel.h"
// Stash definitions
enum STASH_TRACK_MODES
{
STM_NONE, // Stashes are not tracked
STM_EXPLICIT, // Only explicitly marked stashes are tracked
STM_DROPPED, // Dropped items and explicitly marked stashes are
// tracked
STM_ALL // All seen items are tracked
};
struct stash_search_result;
class Stash
{
public:
Stash(int xp = -1, int yp = -1);
static void filter(unsigned char base_type, unsigned char sub_type);
static void filter(const std::string &filt);
static std::string stash_item_name(const item_def &item);
void update();
void save(FILE*) const;
void load(FILE*);
std::string description() const;
bool show_menu(const std::string &place, bool can_travel) const;
bool matches_search(const std::string &prefix,
const base_pattern &search,
stash_search_result &res)
const;
void write(std::ostream &os, int refx = 0, int refy = 0,
std::string place = "",
bool identify = false) const;
bool empty() const
{
return items.empty() && enabled;
}
bool isAt(int xp, int yp) const { return x == xp && y == yp; }
int abs_pos() const { return abspos; }
int getX() const { return (int) x; }
int getY() const { return (int) y; }
bool is_verified() const { return verified; }
bool enabled; // If false, this Stash will neither track
// items nor dump itself. Disabled stashes are
// also never removed from the level's map of
// stashes.
private:
bool verified; // Is this correct to the best of our knowledge?
unsigned char x, y;
int abspos;
std::vector<item_def> items;
/*
* If true (the default), the stash-tracker is a lot more likely to consider
* squares that the player is not standing on as correctly-verified. This
* will lead to cleaner dumps, but the chance of stashes not accurately
* reflecting what's in the dungeon also increases.
*/
static bool aggressive_verify;
static std::vector<item_def> filters;
static bool are_items_same(const item_def &, const item_def &);
static bool is_filtered(const item_def &item);
};
class ShopInfo
{
public:
ShopInfo(int xp, int yp);
bool matches_search(const std::string &prefix,
const base_pattern &search,
stash_search_result &res)
const;
std::string description() const;
// Note that we aren't bothering to use virtual functions here.
void save(FILE*) const;
void load(FILE*);
bool show_menu(const std::string &place, bool can_travel) const;
bool is_visited() const { return items.size() || visited; }
void write(std::ostream &os, bool identify = false) const;
void reset() { items.clear(); visited = true; }
void set_name(const char *s) { name = s; }
void add_item(const item_def &item, unsigned price);
bool isAt(int xp, int yp) const { return x == xp && y == yp; }
private:
int x, y;
std::string name;
int shoptype;
// Set true if the player has visited this shop
bool visited;
// Messy!
struct shop_item
{
item_def item;
unsigned price;
};
std::vector<shop_item> items;
std::string shop_item_name(const shop_item &si) const;
std::string shop_item_desc(const shop_item &si) const;
void describe_shop_item(const shop_item &si) const;
class ShopId {
public:
ShopId(int stype);
~ShopId();
private:
int shoptype;
id_fix_arr id;
};
};
struct stash_search_result
{
// What level is this stash or shop on.
level_id level;
coord_def pos;
// Number of levels the player must cross to reach the level the stash/shop
// is on.
int player_distance;
// Number of items in the stash that match the search.
int matches;
// Count of items stacks in the stash that matched. This will be the same as
// matches if each matching stack's quantity is 1.
int count;
// First item in stash that matched
std::string match;
// The stash or shop in question.
const Stash *stash;
const ShopInfo *shop;
stash_search_result() : level(), player_distance(0), matches(0),
count(0), match(), stash(NULL), shop(NULL)
{
}
bool operator < ( const stash_search_result &ssr ) const
{
return player_distance < ssr.player_distance ||
(player_distance == ssr.player_distance &&
matches > ssr.matches);
}
};
extern std::ostream &operator << (std::ostream &, const Stash &);
class LevelStashes
{
public:
LevelStashes();
Stash *find_stash(int x = -1, int y = -1);
ShopInfo &get_shop(int x, int y);
const ShopInfo *find_shop(int x, int y) const;
void get_matching_stashes(const base_pattern &search,
std::vector<stash_search_result> &results) const;
// Update stash at (x,y) on current level, defaulting to player's current
// location if no parameters are supplied.
bool update_stash(int x = -1, int y = -1);
// Add stash at (x,y), or player's current location if no parameters are
// supplied
void add_stash(int x = -1, int y = -1);
// Mark square (x,y) as not stashworthy. The player's current location is
// used if no parameters are supplied.
void no_stash(int x = -1, int y = -1);
void kill_stash(const Stash &s);
void save(FILE *) const;
void load(FILE *);
void write(std::ostream &os, bool identify = false) const;
std::string level_name() const;
std::string short_level_name() const;
bool in_hell() const;
bool in_branch(int) const;
int stash_count() const { return stashes.size() + shops.size(); }
int visible_stash_count() const { return count_stashes() + shops.size(); }
bool isCurrent() const;
bool isBelowPlayer() const;
bool operator < (const LevelStashes &lev) const;
private:
// which level
unsigned char branch;
int depth;
typedef std::map<int, Stash> c_stashes;
typedef std::vector<ShopInfo> c_shops;
c_stashes stashes;
c_shops shops;
int count_stashes() const;
};
extern std::ostream &operator << (std::ostream &, const LevelStashes &);
class StashTracker
{
public:
static bool is_level_untrackable()
{
return you.level_type == LEVEL_LABYRINTH
|| you.level_type == LEVEL_ABYSS;
}
StashTracker() : levels()
{
}
void search_stashes();
LevelStashes &get_current_level();
LevelStashes *find_current_level();
ShopInfo &get_shop(int x, int y)
{
return get_current_level().get_shop(x, y);
}
void remove_level(const LevelStashes &ls);
enum stash_update_mode
{
ST_PASSIVE, // Maintain existing stashes only.
ST_AGGRESSIVE // Create stashes for each square containing
// objects, even if those squares were not
// previously marked as stashes.
};
void update_visible_stashes(StashTracker::stash_update_mode = ST_PASSIVE);
// Update stash at (x,y) on current level, defaulting to player's current
// location if no parameters are supplied, return true if a stash was
// updated.
bool update_stash(int x = -1, int y = -1);
// Add stash at (x,y), or player's current location if no parameters are
// supplied
void add_stash(int x = -1, int y = -1, bool verbose = false);
// Mark square (x,y) as not stashworthy. The player's current location is
// used if no parameters are supplied.
void no_stash(int x = -1, int y = -1);
void save(FILE*) const;
void load(FILE*);
void write(std::ostream &os, bool identify = false) const;
void dump(const char *filename, bool identify = false) const;
private:
std::vector<LevelStashes> levels;
void get_matching_stashes(const base_pattern &search,
std::vector<stash_search_result> &results) const;
void display_search_results(std::vector<stash_search_result> &results);
};
extern StashTracker stashes;
bool is_stash(int x, int y);
void describe_stash(int x, int y);
#endif // STASH_TRACKING
std::string branch_level_name(unsigned char branch, int sub_depth);
std::string branch_level_name(unsigned short packed_place);
std::string userdef_annotate_item(const char *s, const item_def *item,
bool exclusive = false);
#endif
/*
* File: stash.cc
* Summary: Classes tracking player stashes
* Written by: Darshan Shaligram
*/
#include "AppHdr.h"
#include "chardump.h"
#include "clua.h"
#include "describe.h"
#include "itemname.h"
#include "files.h"
#include "invent.h"
#include "items.h"
#include "Kills.h"
#include "libutil.h"
#include "menu.h"
#include "shopping.h"
#include "spl-book.h"
#include "stash.h"
#include "stuff.h"
#include "tags.h"
#include "travel.h"
#include <cctype>
#include <cstdio>
#include <fstream>
#include <algorithm>
#ifdef STASH_TRACKING
#define ST_MAJOR_VER ((unsigned char) 4)
#define ST_MINOR_VER ((unsigned char) 4)
#define LUA_SEARCH_ANNOTATE "ch_stash_search_annotate_item"
#define LUA_DUMP_ANNOTATE "ch_stash_dump_annotate_item"
#define LUA_VIEW_ANNOTATE "ch_stash_view_annotate_item"
std::string short_place_name(level_id id)
{
return short_place_name(
get_packed_place(id.branch, id.depth, LEVEL_DUNGEON));
}
std::string userdef_annotate_item(const char *s, const item_def *item,
bool exclusive)
{
#ifdef CLUA_BINDINGS
if (exclusive)
lua_set_exclusive_item(item);
std::string ann;
clua.callfn(s, "u>s", item, &ann);
if (exclusive)
lua_set_exclusive_item(NULL);
return (ann);
#else
return ("");
#endif
}
std::string stash_annotate_item(const char *s,
const item_def *item,
bool exclusive = false)
{
std::string text = userdef_annotate_item(s, item, exclusive);
if ( (item->base_type == OBJ_BOOKS &&
item_type_known(*item) &&
item->sub_type != BOOK_MANUAL &&
item->sub_type != BOOK_DESTRUCTION)
|| count_staff_spells(*item, true) > 1 )
{
formatted_string fs;
item_def dup = *item;
spellbook_contents( dup,
item->base_type == OBJ_BOOKS?
RBOOK_READ_SPELL
: RBOOK_USE_STAFF,
&fs );
text += EOL;
text += fs.tostring(2, -2);
}
return text;
}
bool is_stash(int x, int y)
{
LevelStashes *ls = stashes.find_current_level();
if (ls)
{
Stash *s = ls->find_stash(x, y);
return s && s->enabled;
}
return false;
}
void describe_stash(int x, int y)
{
LevelStashes *ls = stashes.find_current_level();
if (ls)
{
Stash *s = ls->find_stash(x, y);
if (s)
{
std::string desc = "[Stash: "
+ s->description() + "]";
mpr(desc.c_str());
}
}
}
static void fully_identify_item(item_def *item)
{
if (!item || !is_valid_item(*item))
return;
set_ident_flags( *item, ISFLAG_IDENT_MASK );
if (item->base_type != OBJ_WEAPONS)
set_ident_type( item->base_type,
item->sub_type,
ID_KNOWN_TYPE );
}
static void save_item(FILE *file, const item_def &item)
{
writeByte(file, item.base_type);
writeByte(file, item.sub_type);
writeShort(file, item.plus);
writeShort(file, item.plus2);
writeLong(file, item.special);
writeShort(file, item.quantity);
writeByte(file, item.colour);
writeLong(file, item.flags);
}
static void load_item(FILE *file, item_def &item)
{
item.base_type = readByte(file);
item.sub_type = readByte(file);
item.plus = readShort(file);
item.plus2 = readShort(file);
item.special = readLong(file);
item.quantity = readShort(file);
item.colour = readByte(file);
item.flags = readLong(file);
}
bool Stash::aggressive_verify = true;
std::vector<item_def> Stash::filters;
Stash::Stash(int xp, int yp) : enabled(true), items()
{
// First, fix what square we're interested in
if (xp == -1)
{
xp = you.x_pos;
yp = you.y_pos;
}
x = (unsigned char) xp;
y = (unsigned char) yp;
abspos = GXM * (int) y + x;
update();
}
bool Stash::are_items_same(const item_def &a, const item_def &b)
{
return a.base_type == b.base_type &&
a.sub_type == b.sub_type &&
a.plus == b.plus &&
a.plus2 == b.plus2 &&
a.special == b.special &&
a.colour == b.colour &&
a.flags == b.flags &&
a.quantity == b.quantity;
}
void Stash::filter(const std::string &str)
{
std::string base = str;
base.erase(base.find_last_not_of(" \n\t") + 1);
base.erase(0, base.find_first_not_of(" \n\t"));
unsigned char subc = 255;
std::string::size_type cpos = base.find(":", 0);
if (cpos != std::string::npos)
{
std::string subs = base.substr(cpos + 1);
subc = atoi(subs.c_str());
base = base.substr(0, cpos);
}
unsigned char basec = atoi(base.c_str());
filter(basec, subc);
}
void Stash::filter(unsigned char base, unsigned char sub)
{
item_def item;
item.base_type = base;
item.sub_type = sub;
filters.push_back(item);
}
bool Stash::is_filtered(const item_def &item)
{
for (int i = 0, count = filters.size(); i < count; ++i)
{
const item_def &filter = filters[i];
if (item.base_type == filter.base_type &&
(filter.sub_type == 255 ||
item.sub_type == filter.sub_type))
return true;
}
return false;
}
void Stash::update()
{
int objl = igrd[x][y];
// If this is your position, you know what's on this square
if (x == you.x_pos && y == you.y_pos)
{
// Zap existing items
items.clear();
// Now, grab all items on that square and fill our vector
while (objl != NON_ITEM)
{
if (!is_filtered(mitm[objl]))
items.push_back(mitm[objl]);
objl = mitm[objl].link;
}
verified = true;
}
// If this is not your position, the only thing we can do is verify that
// what the player sees on the square is the first item in this vector.
else
{
if (objl == NON_ITEM)
{
items.clear();
verified = true;
return ;
}
// There's something on this square. Take a squint at it.
item_def &item = mitm[objl];
if (item.link == NON_ITEM) items.clear();
// We knew of nothing on this square, so we'll assume this is the
// only item here, but mark it as unverified unless we can see nothing
// under the item.
if (items.size() == 0)
{
if (!is_filtered(item))
items.push_back(item);
verified = (item.link == NON_ITEM);
return ;
}
// There's more than one item in this pile. As long as the top item is
// not filtered, we can check to see if it matches what we think the
// top item is.
if (is_filtered(item)) return;
item_def &first = items[0];
// Compare these items
if (!are_items_same(first, item))
{
if (aggressive_verify)
{
// See if 'item' matches any of the items we have. If it does,
// we'll just make that the first item and leave 'verified'
// unchanged.
// Start from 1 because we've already checked items[0]
for (int i = 1, count = items.size(); i < count; ++i)
{
if (are_items_same(items[i], item))
{
// Found it. Swap it to the front of the vector.
item_def temp = items[i];
items[i] = items[0];
items[0] = temp;
// We don't set verified to true. If this stash was
// already unverified, it remains so.
return;
}
}
}
// If this is unverified, forget last item on stack. This isn't
// terribly clever, but it prevents the vector swelling forever.
if (!verified) items.pop_back();
// Items are different. We'll put this item in the front of our
// vector, and mark this as unverified
items.insert(items.begin(), item);
verified = false;
}
}
}
// Returns the item name for a given item, with any appropriate
// stash-tracking pre/suffixes.
std::string Stash::stash_item_name(const item_def &item)
{
char buf[ITEMNAME_SIZE];
if (item.base_type == OBJ_GOLD)
snprintf(buf, sizeof buf, "%d gold piece%s", item.quantity,
(item.quantity > 1? "s" : ""));
else
item_name(item, DESC_NOCAP_A, buf, false);
return buf;
}
class StashMenu : public Menu
{
public:
StashMenu() : Menu(MF_SINGLESELECT), can_travel(false) { }
public:
bool can_travel;
protected:
void draw_title();
bool process_key(int key);
};
void StashMenu::draw_title()
{
if (title)
{
gotoxy(1, 1);
textcolor(title->colour);
cprintf(title->text.c_str());
if (title->quantity)
cprintf(", %d item%s", title->quantity,
title->quantity == 1? "" : "s");
cprintf(")");
if (can_travel)
cprintf(" [ENTER - travel]");
}
}
bool StashMenu::process_key(int key)
{
if (key == CK_ENTER)
{
// Travel activates.
lastch = 1;
return false;
}
return Menu::process_key(key);
}
static void stash_menu_fixup(MenuEntry *me)
{
const item_def *item = static_cast<const item_def *>( me->data );
if (item->base_type == OBJ_GOLD)
{
me->quantity = 0;
me->colour = DARKGREY;
}
}
bool Stash::show_menu(const std::string &prefix, bool can_travel) const
{
StashMenu menu;
MenuEntry *mtitle = new MenuEntry(" Stash (" + prefix);
menu.can_travel = can_travel;
mtitle->colour = WHITE;
mtitle->quantity = items.size();
menu.set_title(mtitle);
populate_item_menu(&menu, items, stash_menu_fixup);
std::vector<MenuEntry*> sel;
while (true)
{
sel = menu.show();
if (menu.getkey() == 1)
return true;
if (sel.size() != 1)
break;
const item_def *item =
static_cast<const item_def *>( sel[0]->data );
describe_item(*item);
}
return false;
}
std::string Stash::description() const
{
if (!enabled || items.empty())
return ("");
const item_def &item = items[0];
std::string desc = stash_item_name(item);
size_t sz = items.size();
if (sz > 1)
{
char additionals[50];
snprintf(additionals, sizeof additionals, " (+%u)", sz - 1);
desc += additionals;
}
return (desc);
}
bool Stash::matches_search(const std::string &prefix,
const base_pattern &search,
stash_search_result &res) const
{
if (!enabled || items.empty()) return false;
for (unsigned i = 0; i < items.size(); ++i)
{
const item_def &item = items[i];
std::string s = stash_item_name(item);
std::string ann = stash_annotate_item(
LUA_SEARCH_ANNOTATE, &item);
if (search.matches(prefix + " " + ann + s))
{
if (!res.count++)
res.match = s;
res.matches += item.quantity;
continue;
}
if (is_dumpable_artifact(item, Options.verbose_dump))
{
std::string desc = munge_description(get_item_description(item,
Options.verbose_dump,
true ));
if (search.matches(desc))
{
if (!res.count++)
res.match = s;
res.matches += item.quantity;
}
}
}
if (res.matches)
{
res.stash = this;
res.pos.x = x;
res.pos.y = y;
}
return !!res.matches;
}
void Stash::write(std::ostream &os,
int refx, int refy,
std::string place,
bool identify)
const
{
if (!enabled || (items.size() == 0 && verified)) return;
os << "(" << ((int) x - refx) << ", " << ((int) y - refy)
<< (place.length()? ", " + place : "")
<< ")"
<< std::endl;
char buf[ITEMNAME_SIZE];
for (unsigned i = 0; i < items.size(); ++i)
{
item_def item = items[i];
if (identify)
fully_identify_item(&item);
std::string s = stash_item_name(item);
strncpy(buf, s.c_str(), sizeof buf);
std::string ann = userdef_annotate_item(
LUA_DUMP_ANNOTATE, &item);
if (!ann.empty())
{
trim_string(ann);
ann = " " + ann;
}
os << " " << buf
<< (!ann.empty()? ann : std::string())
<< (!verified && (items.size() > 1 || i)? " (still there?)" : "")
<< std::endl;
if (is_dumpable_artifact(item, Options.verbose_dump))
{
std::string desc = munge_description(get_item_description(item,
Options.verbose_dump,
true ));
// Kill leading and trailing whitespace
desc.erase(desc.find_last_not_of(" \n\t") + 1);
desc.erase(0, desc.find_first_not_of(" \n\t"));
// If string is not-empty, pad out to a neat indent
if (desc.length())
{
// Walk backwards and prepend indenting spaces to \n characters
for (int i = desc.length() - 1; i >= 0; --i)
if (desc[i] == '\n')
desc.insert(i + 1, " ");
os << " " << desc << std::endl;
}
}
}
if (items.size() <= 1 && !verified)
os << " (unseen)" << std::endl;
}
void Stash::save(FILE *file) const
{
// How many items on this square?
writeShort(file, (short) items.size());
writeByte(file, x);
writeByte(file, y);
// Note: Enabled save value is inverted logic, so that it defaults to true
writeByte(file,
(unsigned char) ((verified? 1 : 0) | (!enabled? 2 : 0)) );
// And dump the items individually. We don't bother saving fields we're
// not interested in (and don't anticipate being interested in).
for (unsigned i = 0; i < items.size(); ++i)
save_item(file, items[i]);
}
void Stash::load(FILE *file)
{
// How many items?
int count = readShort(file);
x = readByte(file);
y = readByte(file);
unsigned char flags = readByte(file);
verified = (flags & 1) != 0;
// Note: Enabled save value is inverted so it defaults to true.
enabled = (flags & 2) == 0;
abspos = GXM * (int) y + x;
// Zap out item vector, in case it's in use (however unlikely)
items.clear();
// Read in the items
for (int i = 0; i < count; ++i)
{
item_def item;
load_item(file, item);
items.push_back(item);
}
}
std::ostream &operator << (std::ostream &os, const Stash &s)
{
s.write(os);
return os;
}
ShopInfo::ShopInfo(int xp, int yp) : x(xp), y(yp), name(), shoptype(-1),
visited(false), items()
{
// Most of our initialization will be done externally; this class is really
// a mildly glorified struct.
const shop_struct *sh = get_shop(x, y);
if (sh)
shoptype = sh->type;
}
ShopInfo::ShopId::ShopId(int stype) : shoptype(stype), id()
{
shop_init_id_type( shoptype, id );
}
ShopInfo::ShopId::~ShopId()
{
shop_uninit_id_type( shoptype, id );
}
void ShopInfo::add_item(const item_def &sitem, unsigned price)
{
shop_item it;
it.item = sitem;
it.price = price;
items.push_back(it);
}
std::string ShopInfo::shop_item_name(const shop_item &si) const
{
char shopitem[ITEMNAME_SIZE * 2];
std::string itemname = Stash::stash_item_name(si.item);
snprintf(shopitem, sizeof shopitem, "%s (%u gold)",
itemname.c_str(), si.price);
return shopitem;
}
std::string ShopInfo::shop_item_desc(const shop_item &si) const
{
std::string desc;
if (is_dumpable_artifact(si.item, Options.verbose_dump))
{
desc = munge_description(get_item_description(si.item,
Options.verbose_dump,
true));
// trim leading whitespace
std::string::size_type notwhite = desc.find_first_not_of(" \t\n");
desc.erase(0, notwhite);
// trim trailing whitespace
notwhite = desc.find_last_not_of(" \t\n");
desc.erase(notwhite + 1);
// Walk backwards and prepend indenting spaces to \n characters
for (int i = desc.length() - 1; i >= 0; --i)
if (desc[i] == '\n')
desc.insert(i + 1, " ");
}
return desc;
}
void ShopInfo::describe_shop_item(const shop_item &si) const
{
describe_item( si.item );
}
bool ShopInfo::show_menu(const std::string &place,
bool can_travel) const
{
ShopId id(shoptype);
StashMenu menu;
MenuEntry *mtitle = new MenuEntry(" " + name + " (" + place);
menu.can_travel = can_travel;
mtitle->colour = WHITE;
mtitle->quantity = items.size();
menu.set_title(mtitle);
menu_letter hotkey;
if (items.empty())
{
MenuEntry *me = new MenuEntry(
visited? " (Shop is empty)" : " (Shop contents are unknown)",
MEL_ITEM,
0,
0);
me->colour = DARKGREY;
menu.add_entry(me);
}
else
{
for (int i = 0, count = items.size(); i < count; ++i)
{
MenuEntry *me = new MenuEntry(shop_item_name(items[i]),
MEL_ITEM,
1,
hotkey++);
me->data = const_cast<shop_item *>( &items[i] );
menu.add_entry(me);
}
}
std::vector<MenuEntry*> sel;
while (true)
{
sel = menu.show();
if (menu.getkey() == 1)
return true;
if (sel.size() != 1)
break;
const shop_item *item =
static_cast<const shop_item *>( sel[0]->data );
describe_shop_item(*item);
}
return false;
}
std::string ShopInfo::description() const
{
return (name);
}
bool ShopInfo::matches_search(const std::string &prefix,
const base_pattern &search,
stash_search_result &res) const
{
if (items.empty() && visited) return false;
ShopId id(shoptype);
bool match = false;
for (unsigned i = 0; i < items.size(); ++i)
{
std::string name = shop_item_name(items[i]);
std::string ann = stash_annotate_item(
LUA_SEARCH_ANNOTATE, &items[i].item, true);
bool thismatch = false;
if (search.matches(prefix + " " + ann + name))
thismatch = true;
else
{
std::string desc = shop_item_desc(items[i]);
if (search.matches(desc))
thismatch = true;
}
if (thismatch)
{
if (!res.count++)
res.match = name;
res.matches++;
}
}
if (!res.matches)
{
std::string shoptitle = prefix + " {shop} " + name;
if (!visited && items.empty())
shoptitle += "*";
if (search.matches(shoptitle))
{
match = true;
res.match = name;
}
}
if (match || res.matches)
{
res.shop = this;
res.pos.x = x;
res.pos.y = y;
}
return match || res.matches;
}
void ShopInfo::write(std::ostream &os, bool identify) const
{
ShopId id(shoptype);
os << "[Shop] " << name << std::endl;
if (items.size() > 0)
{
for (unsigned i = 0; i < items.size(); ++i)
{
shop_item item = items[i];
if (identify)
fully_identify_item(&item.item);
os << " " << shop_item_name(item) << std::endl;
std::string desc = shop_item_desc(item);
if (desc.length() > 0)
os << " " << desc << std::endl;
}
}
else if (visited)
os << " (Shop is empty)" << std::endl;
else
os << " (Shop contents are unknown)" << std::endl;
}
void ShopInfo::save(FILE *file) const
{
writeShort(file, shoptype);
int mangledx = (short) x;
if (!visited)
mangledx |= 1024;
writeShort(file, mangledx);
writeShort(file, (short) y);
writeShort(file, (short) items.size());
writeString(file, name);
for (unsigned i = 0; i < items.size(); ++i)
{
save_item(file, items[i].item);
writeShort(file, (short) items[i].price );
}
}
void ShopInfo::load(FILE *file)
{
shoptype = readShort(file);
x = readShort(file);
visited = !(x & 1024);
x &= 0xFF;
y = readShort(file);
int itemcount = readShort(file);
name = readString(file);
for (int i = 0; i < itemcount; ++i)
{
shop_item item;
load_item(file, item.item);
item.price = (unsigned) readShort(file);
items.push_back(item);
}
}
std::ostream &operator << (std::ostream &os, const ShopInfo &s)
{
s.write(os);
return os;
}
LevelStashes::LevelStashes()
{
branch = you.where_are_you;
depth = you.your_level;
}
bool LevelStashes::operator < (const LevelStashes &lev) const
{
return branch < lev.branch || (branch == lev.branch && depth < lev.depth);
}
bool LevelStashes::isBelowPlayer() const
{
return branch > you.where_are_you
|| (branch == you.where_are_you && depth > you.your_level);
}
Stash *LevelStashes::find_stash(int x, int y)
{
if (x == -1 || y == -1)
{
x = you.x_pos;
y = you.y_pos;
}
const int abspos = (GXM * y) + x;
c_stashes::iterator st = stashes.find(abspos);
return (st == stashes.end()? NULL : &st->second);
}
const ShopInfo *LevelStashes::find_shop(int x, int y) const
{
for (unsigned i = 0; i < shops.size(); ++i)
{
if (shops[i].isAt(x, y))
return (&shops[i]);
}
return (NULL);
}
ShopInfo &LevelStashes::get_shop(int x, int y)
{
for (unsigned i = 0; i < shops.size(); ++i)
{
if (shops[i].isAt(x, y))
return shops[i];
}
ShopInfo si(x, y);
si.set_name(shop_name(x, y));
shops.push_back(si);
return get_shop(x, y);
}
// Updates the stash at (x,y). Returns true if there was a stash at (x,y), false
// otherwise.
bool LevelStashes::update_stash(int x, int y)
{
Stash *s = find_stash(x, y);
if (s)
{
s->update();
if (s->empty())
kill_stash(*s);
return true;
}
return false;
}
// Removes a Stash from the level.
void LevelStashes::kill_stash(const Stash &s)
{
stashes.erase(s.abs_pos());
}
void LevelStashes::no_stash(int x, int y)
{
Stash *s = find_stash(x, y);
bool en = false;
if (s)
{
en = s->enabled = !s->enabled;
s->update();
if (s->empty())
kill_stash(*s);
}
else
{
Stash newStash(x, y);
newStash.enabled = false;
stashes[ newStash.abs_pos() ] = newStash;
}
mpr(en? "I'll no longer ignore what I see on this square."
: "Ok, I'll ignore what I see on this square.");
}
void LevelStashes::add_stash(int x, int y)
{
Stash *s = find_stash(x, y);
if (s)
{
s->update();
if (s->empty())
kill_stash(*s);
}
else
{
Stash newStash(x, y);
if (!newStash.empty())
stashes[ newStash.abs_pos() ] = newStash;
}
}
bool LevelStashes::isCurrent() const
{
return branch == you.where_are_you && depth == you.your_level;
}
bool LevelStashes::in_hell() const
{
return branch >= BRANCH_DIS
&& branch <= BRANCH_THE_PIT
&& branch != BRANCH_VESTIBULE_OF_HELL;
}
bool LevelStashes::in_branch(int branchid) const
{
return branch == branchid;
}
std::string LevelStashes::level_name() const
{
int curr_subdungeon_level = subdungeon_depth( branch, depth );
return (branch_level_name(branch, curr_subdungeon_level));
}
std::string LevelStashes::short_level_name() const
{
return (short_place_name(
get_packed_place( branch,
subdungeon_depth( branch, depth ),
LEVEL_DUNGEON ) ));
}
int LevelStashes::count_stashes() const
{
int rawcount = stashes.size();
if (!rawcount)
return (0);
for (c_stashes::const_iterator iter = stashes.begin();
iter != stashes.end(); iter++)
{
if (!iter->second.enabled)
--rawcount;
}
return rawcount;
}
void LevelStashes::get_matching_stashes(
const base_pattern &search,
std::vector<stash_search_result> &results)
const
{
level_id clid(branch, subdungeon_depth(branch, depth));
std::string lplace = "{" + short_place_name(clid) + "}";
for (c_stashes::const_iterator iter = stashes.begin();
iter != stashes.end(); iter++)
{
if (iter->second.enabled)
{
stash_search_result res;
if (iter->second.matches_search(lplace, search, res))
{
res.level = clid;
results.push_back(res);
}
}
}
for (unsigned i = 0; i < shops.size(); ++i)
{
stash_search_result res;
if (shops[i].matches_search(lplace, search, res))
{
res.level = clid;
results.push_back(res);
}
}
}
void LevelStashes::write(std::ostream &os, bool identify) const
{
if (visible_stash_count() == 0) return ;
os << level_name() << std::endl;
for (unsigned i = 0; i < shops.size(); ++i)
{
shops[i].write(os, identify);
}
if (stashes.size())
{
const Stash &s = stashes.begin()->second;
int refx = s.getX(), refy = s.getY();
std::string level_name = short_level_name();
for (c_stashes::const_iterator iter = stashes.begin();
iter != stashes.end(); iter++)
{
iter->second.write(os, refx, refy, level_name, identify);
}
}
os << std::endl;
}
void LevelStashes::save(FILE *file) const
{
// How many stashes on this level?
writeShort(file, (short) stashes.size());
writeByte(file, branch);
writeShort(file, (short) depth);
// And write the individual stashes
for (c_stashes::const_iterator iter = stashes.begin();
iter != stashes.end(); iter++)
iter->second.save(file);
writeShort(file, (short) shops.size());
for (unsigned i = 0; i < shops.size(); ++i)
shops[i].save(file);
}
void LevelStashes::load(FILE *file)
{
int size = readShort(file);
branch = readByte(file);
depth = readShort(file);
stashes.clear();
for (int i = 0; i < size; ++i)
{
Stash s;
s.load(file);
if (!s.empty())
stashes[ s.abs_pos() ] = s;
}
shops.clear();
int shopc = readShort(file);
for (int i = 0; i < shopc; ++i)
{
ShopInfo si(0, 0);
si.load(file);
shops.push_back(si);
}
}
std::ostream &operator << (std::ostream &os, const LevelStashes &ls)
{
ls.write(os);
return os;
}
LevelStashes &StashTracker::get_current_level()
{
std::vector<LevelStashes>::iterator iter = levels.begin();
for ( ; iter != levels.end() && !iter->isBelowPlayer(); iter++)
{
if (iter->isCurrent()) return *iter;
}
if (iter == levels.end())
levels.push_back(LevelStashes());
else
levels.insert(iter, LevelStashes());
return get_current_level();
}
LevelStashes *StashTracker::find_current_level()
{
std::vector<LevelStashes>::iterator iter = levels.begin();
for ( ; iter != levels.end() && !iter->isBelowPlayer(); iter++)
{
if (iter->isCurrent()) return &*iter;
}
return NULL;
}
bool StashTracker::update_stash(int x, int y)
{
LevelStashes *lev = find_current_level();
if (lev)
{
bool res = lev->update_stash(x, y);
if (!lev->stash_count())
remove_level(*lev);
return res;
}
return false;
}
void StashTracker::remove_level(const LevelStashes &ls)
{
std::vector<LevelStashes>::iterator iter = levels.begin();
for ( ; iter != levels.end(); ++iter)
{
if (&ls == &*iter)
{
levels.erase(iter);
break ;
}
}
}
void StashTracker::no_stash(int x, int y)
{
if (is_level_untrackable())
return ;
LevelStashes ¤t = get_current_level();
current.no_stash(x, y);
if (!current.stash_count())
remove_level(current);
}
void StashTracker::add_stash(int x, int y, bool verbose)
{
if (is_level_untrackable())
return ;
LevelStashes ¤t = get_current_level();
current.add_stash(x, y);
if (verbose)
{
Stash *s = current.find_stash(x, y);
if (s && s->enabled)
mpr("Added stash.");
}
if (!current.stash_count())
remove_level(current);
}
void StashTracker::dump(const char *filename, bool identify) const
{
std::ofstream outf(filename);
if (outf)
{
write(outf, identify);
outf.close();
}
}
void StashTracker::write(std::ostream &os, bool identify) const
{
os << you.your_name << std::endl << std::endl;
if (!levels.size())
os << " You have no stashes." << std::endl;
else
{
std::vector<LevelStashes>::const_iterator iter = levels.begin();
for ( ; iter != levels.end(); iter++)
{
iter->write(os, identify);
}
}
}
void StashTracker::save(FILE *file) const
{
// Write version info first - major + minor
writeByte(file, ST_MAJOR_VER);
writeByte(file, ST_MINOR_VER);
// How many levels have we?
writeShort(file, (short) levels.size());
// And ask each level to write itself to the tag
std::vector<LevelStashes>::const_iterator iter = levels.begin();
for ( ; iter != levels.end(); iter++)
iter->save(file);
}
void StashTracker::load(FILE *file)
{
// Check version. Compatibility isn't important, since stash-tracking
// is non-critical.
unsigned char major = readByte(file),
minor = readByte(file);
if (major != ST_MAJOR_VER || minor != ST_MINOR_VER) return ;
int count = readShort(file);
levels.clear();
for (int i = 0; i < count; ++i)
{
LevelStashes st;
st.load(file);
if (st.stash_count()) levels.push_back(st);
}
}
void StashTracker::update_visible_stashes(
StashTracker::stash_update_mode mode)
{
if (is_level_untrackable())
return ;
LevelStashes *lev = find_current_level();
for (int cy = 1; cy <= 17; ++cy)
{
for (int cx = 9; cx <= 25; ++cx)
{
int x = you.x_pos + cx - 17, y = you.y_pos + cy - 9;
if (x < 0 || x >= GXM || y < 0 || y >= GYM)
continue;
if (!env.show[cx - 8][cy] && !(cx == 17 && cy == 9))
continue;
if ((!lev || !lev->update_stash(x, y))
&& mode == ST_AGGRESSIVE
&& igrd[x][y] != NON_ITEM)
{
if (!lev)
lev = &get_current_level();
lev->add_stash(x, y);
}
if (grd[x][y] == DNGN_ENTER_SHOP)
get_shop(x, y);
}
}
if (lev && !lev->stash_count())
remove_level(*lev);
}
#define SEARCH_SPAM_THRESHOLD 400
static std::string lastsearch;
static input_history search_history(15);
void StashTracker::search_stashes()
{
char prompt[200];
if (lastsearch.length())
snprintf(prompt, sizeof prompt,
"Search your stashes for what item [Enter for \"%s\"]?",
lastsearch.c_str());
else
snprintf(prompt, sizeof prompt,
"Search your stashes for what item?");
mpr(prompt, MSGCH_PROMPT);
// Push the cursor down to the next line.
mpr("", MSGCH_PROMPT);
char buf[400];
bool validline = cancelable_get_line(buf, sizeof buf, 80, &search_history);
mesclr();
if (!validline || (!*buf && !lastsearch.length()))
return;
std::string csearch = *buf? buf : lastsearch;
std::vector<stash_search_result> results;
base_pattern *search = NULL;
text_pattern tpat( csearch, true );
search = &tpat;
#ifdef CLUA_BINDINGS
lua_text_pattern ltpat( csearch );
if (lua_text_pattern::is_lua_pattern(csearch))
search = <pat;
#endif
if (!search->valid())
{
mpr("Your search expression is invalid.", MSGCH_PLAIN);
return ;
}
lastsearch = csearch;
get_matching_stashes(*search, results);
if (results.empty())
{
mpr("That item is not present in any of your stashes.",
MSGCH_PLAIN);
return;
}
if (results.size() > SEARCH_SPAM_THRESHOLD)
{
mpr("Too many matches; use a more specific search.", MSGCH_PLAIN);
return ;
}
display_search_results(results);
}
void StashTracker::get_matching_stashes(
const base_pattern &search,
std::vector<stash_search_result> &results)
const
{
std::vector<LevelStashes>::const_iterator iter = levels.begin();
for ( ; iter != levels.end(); iter++)
{
iter->get_matching_stashes(search, results);
if (results.size() > SEARCH_SPAM_THRESHOLD)
return;
}
level_id curr = level_id::get_current_level_id();
for (unsigned i = 0; i < results.size(); ++i)
{
results[i].player_distance = level_distance(curr, results[i].level);
}
// Sort stashes so that closer stashes come first and stashes on the same
// levels with more items come first.
std::sort(results.begin(), results.end());
}
class StashSearchMenu : public Menu
{
public:
StashSearchMenu() : Menu(), can_travel(true), meta_key(0) { }
public:
bool can_travel;
int meta_key;
protected:
bool process_key(int key);
void draw_title();
};
void StashSearchMenu::draw_title()
{
if (title)
{
gotoxy(1, 1);
textcolor(title->colour);
cprintf(" %d %s%s", title->quantity, title->text.c_str(),
title->quantity > 1? "es" : "");
if (meta_key)
draw_title_suffix(" (x - examine stash)", false);
else
draw_title_suffix(" (x - go to stash; ? - examine stash)", false);
}
}
bool StashSearchMenu::process_key(int key)
{
if (key == '?')
{
if (sel)
sel->clear();
meta_key = !meta_key;
update_title();
return true;
}
return Menu::process_key(key);
}
void StashTracker::display_search_results(
std::vector<stash_search_result> &results)
{
if (results.empty())
return;
bool travelable = can_travel_interlevel();
StashSearchMenu stashmenu;
stashmenu.can_travel = travelable;
std::string title = "matching stash";
MenuEntry *mtitle = new MenuEntry(title);
mtitle->colour = WHITE;
// Abuse of the quantity field.
mtitle->quantity = results.size();
stashmenu.set_title(mtitle);
menu_letter hotkey;
for (unsigned i = 0; i < results.size(); ++i, ++hotkey)
{
stash_search_result &res = results[i];
char matchtitle[ITEMNAME_SIZE];
std::string place = short_place_name(res.level);
if (res.matches > 1 && res.count > 1)
{
snprintf(matchtitle, sizeof matchtitle,
"[%s] %s (%d)",
place.c_str(),
res.match.c_str(),
res.matches);
}
else
{
snprintf(matchtitle, sizeof matchtitle,
"[%s] %s",
place.c_str(),
res.match.c_str());
}
std::string mename = matchtitle;
MenuEntry *me = new MenuEntry(mename, MEL_ITEM, 1, hotkey);
me->data = &res;
if (res.shop && !res.shop->is_visited())
me->colour = CYAN;
stashmenu.add_entry(me);
}
stashmenu.set_flags( MF_SINGLESELECT );
std::vector<MenuEntry*> sel;
while (true)
{
sel = stashmenu.show();
if (sel.size() == 1 && stashmenu.meta_key)
{
stash_search_result *res =
static_cast<stash_search_result *>(sel[0]->data);
bool dotravel = false;
if (res->shop)
{
dotravel = res->shop->show_menu(short_place_name(res->level),
travelable);
}
else if (res->stash)
{
dotravel = res->stash->show_menu(short_place_name(res->level),
travelable);
}
if (dotravel && travelable)
{
redraw_screen();
level_pos lp;
lp.id = res->level;
lp.pos = res->pos;
start_translevel_travel(lp);
return ;
}
continue;
}
break;
}
redraw_screen();
if (travelable && sel.size() == 1 && !stashmenu.meta_key)
{
const stash_search_result *res =
static_cast<stash_search_result *>(sel[0]->data);
level_pos lp;
lp.id = res->level;
lp.pos = res->pos;
start_translevel_travel(lp);
return ;
}
}
// Global
StashTracker stashes;
#endif
std::string branch_level_name(unsigned char branch, int sub_depth)
{
int ltype = sub_depth == 0xFF? branch : 0;
if (ltype == LEVEL_PANDEMONIUM)
return ("Pandemonium");
else if (ltype == LEVEL_ABYSS)
return ("The Abyss");
else if (ltype == LEVEL_LABYRINTH)
return ("A Labyrinth");
else
{
char buf[200];
const char *s = NULL;
*buf = 0;
// level_type == LEVEL_DUNGEON
if (branch != BRANCH_VESTIBULE_OF_HELL
&& branch != BRANCH_ECUMENICAL_TEMPLE
&& branch != BRANCH_HALL_OF_BLADES)
snprintf(buf, sizeof buf, "Level %d", sub_depth);
switch (branch)
{
case BRANCH_MAIN_DUNGEON:
s = " of the Dungeon";
break;
case BRANCH_DIS:
s = " of Dis";
break;
case BRANCH_GEHENNA:
s = " of Gehenna";
break;
case BRANCH_VESTIBULE_OF_HELL:
s = "The Vestibule of Hell";
break;
case BRANCH_COCYTUS:
s = " of Cocytus";
break;
case BRANCH_TARTARUS:
s = " of Tartarus";
break;
case BRANCH_INFERNO:
s = " of the Inferno";
break;
case BRANCH_THE_PIT:
s = " of the Pit";
break;
case BRANCH_ORCISH_MINES:
s = " of the Orcish Mines";
break;
case BRANCH_HIVE:
s = " of the Hive";
break;
case BRANCH_LAIR:
s = " of the Lair";
break;
case BRANCH_SLIME_PITS:
s = " of the Slime Pits";
break;
case BRANCH_VAULTS:
s = " of the Vaults";
break;
case BRANCH_CRYPT:
s = " of the Crypt";
break;
case BRANCH_HALL_OF_BLADES:
s = "The Hall of Blades";
break;
case BRANCH_HALL_OF_ZOT:
s = " of the Realm of Zot";
break;
case BRANCH_ECUMENICAL_TEMPLE:
s = "The Ecumenical Temple";
break;
case BRANCH_SNAKE_PIT:
s = " of the Snake Pit";
break;
case BRANCH_ELVEN_HALLS:
s = " of the Elven Halls";
break;
case BRANCH_TOMB:
s = " of the Tomb";
break;
case BRANCH_SWAMP:
s = " of the Swamp";
break;
}
if (s)
strncat(buf, s, sizeof(buf) - 1);
return (buf);
}
}
std::string branch_level_name(unsigned short packed_place)
{
return branch_level_name(packed_place >> 8, packed_place & 0xFF);
}
const int stype = item.sub_type;
const int type = stype + 40;
if (stype < STAFF_SMITING || stype >= STAFF_AIR)
return (0);
FixedVector< int, SPELLBOOK_SIZE > spell_list;
spellbook_template( type, spell_list );
int num_spells = 0;
for (num_spells = 0; num_spells < SPELLBOOK_SIZE - 1; num_spells++)
{
if (spell_list[ num_spells + 1 ] == SPELL_NO_SPELL)
break;
}
return (num_spells);
}
else if (mitm[igrd[axps][ayps]].base_type != OBJ_CORPSES)
return 0;
else if (class_allowed == CORPSE_SKELETON
&& mitm[igrd[axps][ayps]].sub_type != CORPSE_SKELETON)
return 0;
else
if (raise_corpse( igrd[axps][ayps], axps, ayps,
corps_beh, corps_hit, 1 ) > 0)
int objl = igrd[axps][ayps];
// This searches all the items on the ground for a corpse
while (objl != NON_ITEM)
mpr("The dead are walking!");
if (mitm[objl].base_type != OBJ_CORPSES
|| (class_allowed == CORPSE_SKELETON
&& mitm[objl].sub_type != CORPSE_SKELETON))
{
objl = mitm[objl].link;
continue;
}
if (raise_corpse(objl, axps, ayps,
corps_beh, corps_hit, 1 ) > 0)
mpr("The dead are walking!");
break;
unsigned char j = 0;
if (env.shop[i].type != SHOP_WEAPON_ANTIQUE
&& env.shop[i].type != SHOP_ARMOUR_ANTIQUE
&& env.shop[i].type != SHOP_GENERAL_ANTIQUE)
if (shoptype != SHOP_WEAPON_ANTIQUE
&& shoptype != SHOP_ARMOUR_ANTIQUE
&& shoptype != SHOP_GENERAL_ANTIQUE)
if (env.shop[i].type != SHOP_WEAPON_ANTIQUE
&& env.shop[i].type != SHOP_ARMOUR_ANTIQUE
&& env.shop[i].type != SHOP_GENERAL_ANTIQUE)
void shop_uninit_id_type(int shoptype, const id_fix_arr &shop_id)
{
if (shoptype != SHOP_WEAPON_ANTIQUE
&& shoptype != SHOP_ARMOUR_ANTIQUE
&& shoptype != SHOP_GENERAL_ANTIQUE)
set_ident_type( OBJ_WANDS, j, shop_id[ IDTYPE_WANDS ][j], true );
set_ident_type( OBJ_SCROLLS, j, shop_id[ IDTYPE_SCROLLS ][j], true );
set_ident_type( OBJ_JEWELLERY, j, shop_id[ IDTYPE_JEWELLERY ][j], true );
set_ident_type( OBJ_POTIONS, j, shop_id[ IDTYPE_POTIONS ][j], true );
set_ident_type( OBJ_WANDS, j,
shop_id[ IDTYPE_WANDS ][j], true );
set_ident_type( OBJ_SCROLLS, j,
shop_id[ IDTYPE_SCROLLS ][j], true );
set_ident_type( OBJ_JEWELLERY, j,
shop_id[ IDTYPE_JEWELLERY ][j], true );
set_ident_type( OBJ_POTIONS, j,
shop_id[ IDTYPE_POTIONS ][j], true );
}
}
void run_macro(const char *macroname)
{
if (you.activity != ACT_NONE && you.activity != ACT_MACRO)
return;
#ifdef CLUA_BINDINGS
if (!clua)
{
mpr("Lua not initialized", MSGCH_DIAGNOSTICS);
you.activity = ACT_NONE;
return;
}
if (!clua.callbooleanfn(false, "c_macro", "s", macroname))
{
if (clua.error.length())
mpr(clua.error.c_str());
you.activity = ACT_NONE;
}
else
{
you.activity = ACT_MACRO;
}
#else
you.activity = ACT_NONE;
#endif
}
void perform_activity()
{
switch (you.activity)
{
case ACT_MULTIDROP:
drop();
break;
case ACT_MACRO:
run_macro();
break;
default:
break;
}
}
#ifdef CLUA_BINDINGS
static const char *activity_interrupt_name(ACT_INTERRUPT ai)
{
switch (ai)
{
case AI_FORCE_INTERRUPT: return "force";
case AI_KEYPRESS: return "keypress";
case AI_FULL_HP: return "full_hp";
case AI_FULL_MP: return "full_mp";
case AI_STATUE: return "statue";
case AI_HUNGRY: return "hungry";
case AI_MESSAGE: return "message";
case AI_HP_LOSS: return "hp_loss";
case AI_BURDEN_CHANGE: return "burden";
case AI_STAT_CHANGE: return "stat";
case AI_SEE_MONSTER: return "monster";
case AI_TELEPORT: return "teleport";
default: return "unknown";
static const char *activity_names[] = {
"",
"multidrop",
"run",
"travel",
"macro"
};
static const char *activity_name(int act)
{
if (act < ACT_NONE || act >= ACT_ACTIVITY_COUNT)
return NULL;
return activity_names[act];
}
#endif
static void kill_activity()
{
if (you.running)
stop_running();
you.activity = ACT_NONE;
}
static bool userdef_interrupt_activity( ACT_INTERRUPT ai,
const activity_interrupt_t &at )
{
#ifdef CLUA_BINDINGS
lua_State *ls = clua.state();
if (!ls || ai == AI_FORCE_INTERRUPT)
{
if (ai == AI_FORCE_INTERRUPT || you.activity == ACT_MACRO)
kill_activity();
return true;
}
const char *interrupt_name = activity_interrupt_name(ai);
const char *act_name = activity_name(you.activity);
bool ran = clua.callfn("c_interrupt_activity", "1:ssA",
act_name, interrupt_name, &at);
if (ran)
{
// If the function returned nil, we want to cease processing.
if (lua_isnil(ls, -1))
{
lua_pop(ls, 1);
return false;
}
bool stopact = lua_toboolean(ls, -1);
lua_pop(ls, 1);
if (stopact)
{
kill_activity();
return true;
}
}
if (you.activity == ACT_MACRO &&
clua.callbooleanfn(true, "c_interrupt_macro",
"sA", interrupt_name, &at))
{
kill_activity();
}
#else
if (you.activity == ACT_MACRO)
kill_activity();
#endif
return true;
}
void interrupt_activity( ACT_INTERRUPT ai, const activity_interrupt_t &at )
{
if (you.running && !you.activity)
you.activity = you.running > 0? ACT_RUNNING : ACT_TRAVELING;
if (!you.activity)
return;
if (!userdef_interrupt_activity(ai, at) || !you.activity)
{
if (you.activity == ACT_RUNNING || you.activity == ACT_TRAVELING)
you.activity = ACT_NONE;
return;
}
if (!ai || (Options.activity_interrupts[ you.activity ] & ai)) {
kill_activity();
}
if (you.activity == ACT_RUNNING || you.activity == ACT_TRAVELING)
you.activity = ACT_NONE;
}
in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass, Options.terse_hand );
in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass, false );
int prefcol = menu_colour(str_pass);
if (prefcol != -1)
textcolor(prefcol);
in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass,
Options.terse_hand );
unsigned char* itosym1(int stat)
{
return (unsigned char*)( (stat >= 1) ? "+ " : ". " );
}
unsigned char* itosym3(int stat)
{
return (unsigned char*)( (stat >= 3) ? "+ + +" :
(stat == 2) ? "+ + ." :
(stat == 1) ? "+ . ." :
(stat == 0) ? ". . ." :
"x . .");
}
static const char *s_equip_slot_names[] =
{
"Weapon",
"Cloak",
"Helmet",
"Gloves",
"Boots",
"Shield",
"Armour",
"Left Ring",
"Right Ring",
"Amulet",
};
const char *equip_slot_to_name(int equip)
{
if (equip == EQ_RINGS)
return "Ring";
if (equip < 0 || equip >= NUM_EQUIP)
return "";
return s_equip_slot_names[equip];
}
int equip_name_to_slot(const char *s)
{
for (int i = 0; i < NUM_EQUIP; ++i)
{
if (!stricmp(s_equip_slot_names[i], s))
return i;
}
return -1;
}
void get_full_detail(char* buffer, bool calc_unid)
{
#define FIR_AD buffer,44
#define CUR_AD &buffer[++lines*45],44
#define BUF_SIZE 25*3*45
int lines = 0;
memset(buffer, 0, BUF_SIZE);
snprintf(CUR_AD, "%s the %s", you.your_name, player_title());
lines++;
snprintf(CUR_AD, "Race : %s", species_name(you.species,you.experience_level) );
snprintf(CUR_AD, "Class : %s", you.class_name);
snprintf(CUR_AD, "Worship : %s",
you.religion == GOD_NO_GOD? "" : god_name(you.religion) );
snprintf(CUR_AD, "Level : %7d", you.experience_level);
snprintf(CUR_AD, "Exp : %7lu", you.experience);
if (you.experience_level < 27)
{
int xp_needed = (exp_needed(you.experience_level+2) - you.experience) + 1;
snprintf(CUR_AD, "Next Level : %7lu", exp_needed(you.experience_level + 2) + 1);
snprintf(CUR_AD, "Exp Needed : %7d", xp_needed);
}
else
{
snprintf(CUR_AD, " ");
snprintf(CUR_AD, " ");
}
snprintf(CUR_AD, "Spls.Left : %7d", player_spell_levels() );
snprintf(CUR_AD, "Gold : %7d", you.gold );
lines++;
if (!player_rotted())
{
if (you.hp < you.hp_max)
snprintf(CUR_AD, "HP : %3d/%d", you.hp, you.hp_max);
else
snprintf(CUR_AD, "HP : %3d", you.hp);
}
else
{
snprintf(CUR_AD, "HP : %3d/%d (%d)",
you.hp, you.hp_max, you.hp_max + player_rotted() );
}
if (you.magic_points < you.max_magic_points)
snprintf(CUR_AD, "MP : %3d/%d",
you.magic_points, you.max_magic_points);
else
snprintf(CUR_AD, "MP : %3d", you.magic_points);
if (you.strength == you.max_strength)
snprintf(CUR_AD, "Str : %3d", you.strength);
else
snprintf(CUR_AD, "Str : %3d (%d)",
you.strength, you.max_strength);
if (you.intel == you.max_intel)
snprintf(CUR_AD, "Int : %3d", you.intel);
else
snprintf(CUR_AD, "Int : %3d (%d)", you.intel, you.max_intel);
if (you.dex == you.max_dex)
snprintf(CUR_AD, "Dex : %3d", you.dex);
else
snprintf(CUR_AD, "Dex : %3d (%d)", you.dex, you.max_dex);
snprintf(CUR_AD, "AC : %3d", player_AC() );
snprintf(CUR_AD, "Evasion : %3d", player_evasion() );
snprintf(CUR_AD, "Shield : %3d", player_shield_class() );
lines++;
if (you.real_time != -1)
{
const time_t curr = you.real_time + (time(NULL) - you.start_time);
char buff[200];
make_time_string( curr, buff, sizeof(buff), true );
snprintf(CUR_AD, "Play time : %10s", buff);
snprintf(CUR_AD, "Turns : %10ld", you.num_turns );
}
lines = 27;
snprintf(CUR_AD, "Res.Fire : %s",
itosym3( player_res_fire(calc_unid) ) );
snprintf(CUR_AD, "Res.Cold : %s",
itosym3( player_res_cold(calc_unid) ) );
snprintf(CUR_AD, "Life Prot.: %s",
itosym3( player_prot_life(calc_unid) ) );
snprintf(CUR_AD, "Res.Poison: %s",
itosym1( player_res_poison(calc_unid) ) );
snprintf(CUR_AD, "Res.Elec. : %s",
itosym1( player_res_electricity(calc_unid) ) );
lines++;
snprintf(CUR_AD, "Sust.Abil.: %s",
itosym1( player_sust_abil(calc_unid) ) );
snprintf(CUR_AD, "Res.Mut. : %s",
itosym1( wearing_amulet( AMU_RESIST_MUTATION, calc_unid) ) );
snprintf(CUR_AD, "Res.Slow : %s",
itosym1( wearing_amulet( AMU_RESIST_SLOW, calc_unid) ) );
snprintf(CUR_AD, "Clarity : %s",
itosym1( wearing_amulet( AMU_CLARITY, calc_unid) ) );
lines++;
lines++;
{
char str_pass[ITEMNAME_SIZE];
const int e_order[] =
{
EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK,
EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING
};
for(int i = 0; i < NUM_EQUIP; i++)
{
int eqslot = e_order[i];
const char *slot = equip_slot_to_name( eqslot );
if (eqslot == EQ_LEFT_RING || eqslot == EQ_RIGHT_RING)
slot = "Ring";
else if (eqslot == EQ_BOOTS
&& (you.species == SP_CENTAUR
|| you.species == SP_NAGA))
slot = "Barding";
if ( you.equip[ e_order[i] ] != -1)
{
in_name( you.equip[ e_order[i] ], DESC_PLAIN,
str_pass, Options.terse_hand );
snprintf(CUR_AD, "%-7s: %s", slot, str_pass);
}
else
{
if (e_order[i] == EQ_WEAPON)
{
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
snprintf(CUR_AD, "%-7s: Blade Hands", slot);
else if (you.skills[SK_UNARMED_COMBAT])
snprintf(CUR_AD, "%-7s: Unarmed", slot);
else
snprintf(CUR_AD, "%-7s:", slot);
}
else
{
snprintf(CUR_AD, "%-7s:", slot);
}
}
}
}
lines = 52;
snprintf(CUR_AD, "See Invis. : %s",
itosym1( player_see_invis(calc_unid) ) );
snprintf(CUR_AD, "Warding : %s",
itosym1( wearing_amulet(AMU_WARDING, calc_unid)
|| (you.religion == GOD_VEHUMET &&
you.duration[DUR_PRAYER] &&
!player_under_penance() &&
you.piety >= 75) ) );
snprintf(CUR_AD, "Conserve : %s",
itosym1( wearing_amulet( AMU_CONSERVATION, calc_unid) ) );
snprintf(CUR_AD, "Res.Corr. : %s",
itosym1( wearing_amulet( AMU_RESIST_CORROSION, calc_unid) ) );
if ( !wearing_amulet( AMU_THE_GOURMAND, calc_unid) )
{
switch (you.species)
{
case SP_GHOUL:
snprintf(CUR_AD, "Saprovore : %s", itosym3(3) );
break;
case SP_KOBOLD:
case SP_TROLL:
snprintf(CUR_AD, "Saprovore : %s", itosym3(2) );
break;
case SP_HILL_ORC:
case SP_OGRE:
snprintf(CUR_AD, "Saprovore : %s", itosym3(1) );
break;
#ifdef V_FIX
case SP_OGRE_MAGE:
snprintf(CUR_AD, "Voracious : %s", itosym1(1) );
break;
#endif
default:
snprintf(CUR_AD, "Gourmand : %s", itosym1(0) );
break;
}
}
else
{
snprintf(CUR_AD, "Gourmand : %s",
itosym1( wearing_amulet( AMU_THE_GOURMAND, calc_unid) ) );
}
lines++;
if ( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) )
snprintf(CUR_AD, "Prev.Telep.: %s",
itosym1( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) ) );
else
snprintf(CUR_AD, "Rnd.Telep. : %s",
itosym1( player_teleport(calc_unid) ) );
snprintf(CUR_AD, "Ctrl.Telep.: %s",
itosym1( you.attribute[ATTR_CONTROL_TELEPORT] ) );
snprintf(CUR_AD, "Levitation : %s", itosym1( player_is_levitating() ) );
snprintf(CUR_AD, "Ctrl.Flight: %s",
itosym1( wearing_amulet(AMU_CONTROLLED_FLIGHT, calc_unid) ) );
lines++;
return;
}
static char letter_to_species(int keyn);
static char letter_to_class(int keyn);
////////////////////////////////////////////////////////////////////////
// Remember player's startup options
//
static char ng_race, ng_cls;
static bool ng_random;
static int ng_ck, ng_dk, ng_pr;
static int ng_weapon;
static void reset_newgame_options(void)
{
ng_race = ng_cls = 0;
ng_random = false;
ng_ck = GOD_NO_GOD;
ng_dk = DK_NO_SELECTION;
ng_pr = GOD_NO_GOD;
ng_weapon = WPN_UNKNOWN;
}
static void save_newgame_options(void)
{
// Note that we store race and class directly here, whereas up until
// now we've been storing the hotkey.
Options.prev_race = ng_race;
Options.prev_cls = ng_cls;
Options.prev_randpick = ng_random;
Options.prev_ck = ng_ck;
Options.prev_dk = ng_dk;
Options.prev_pr = ng_pr;
Options.prev_weapon = ng_weapon;
write_newgame_options_file();
}
static void set_startup_options(void)
{
Options.race = Options.prev_race;
Options.cls = Options.prev_cls;
Options.weapon = Options.prev_weapon;
Options.death_knight = Options.prev_dk;
Options.chaos_knight = Options.prev_ck;
Options.priest = Options.prev_pr;
}
static bool prev_startup_options_set(void)
{
// Are these two enough? They should be, in theory, since we'll
// remember the player's weapon and god choices.
return Options.prev_race && Options.prev_cls;
}
static std::string get_opt_race_name(char race)
{
int prace = letter_to_species(race);
return prace? species_name(prace, 1) : "Random";
}
static std::string get_opt_class_name(char oclass)
{
int pcls = letter_to_class(oclass);
return pcls != JOB_UNKNOWN? get_class_name(pcls) : "Random";
}
static std::string prev_startup_description(void)
{
if (Options.prev_race == '?' && Options.prev_cls == '?')
Options.prev_randpick = true;
if (Options.prev_randpick)
return "Random character";
if (Options.prev_cls == '?')
return "Random " + get_opt_race_name(Options.prev_race);
return get_opt_race_name(Options.prev_race) + " " +
get_opt_class_name(Options.prev_cls);
}
cprintf(EOL "? - Random" EOL);
textcolor(BROWN);
cprintf(EOL "? - Random" );
if (Options.prev_weapon != WPN_UNKNOWN)
{
char weapbuf[ITEMNAME_SIZE];
if (Options.prev_weapon != WPN_RANDOM)
standard_name_weap(Options.prev_weapon, weapbuf);
cprintf("; Enter - %s",
Options.prev_weapon == WPN_RANDOM? "Random" :
weapbuf);
}
cprintf(EOL);
while (keyin != '?' && (keyin < 'a' || keyin > ('a' + num_choices)));
while (keyin != '?' &&
((keyin != '\r' && keyin != '\n')
|| Options.prev_weapon == WPN_UNKNOWN) &&
(keyin < 'a' || keyin > ('a' + num_choices)));
if (keyin == '\r' || keyin == '\n')
{
if (Options.prev_weapon == WPN_RANDOM)
keyin = '?';
else
{
for (int i = 0; i < num_choices; ++i)
if (startwep[i] == Options.prev_weapon)
keyin = 'a' + i;
}
}
cprintf(EOL "Press <Enter> to answer this after race and class are chosen."EOL);
{
if (Options.prev_name.length() && Options.remember_name)
cprintf(EOL "Press <Enter> for \"%s\"." EOL,
Options.prev_name.c_str());
else
cprintf(EOL
"Press <Enter> to answer this after race and "
"class are chosen." EOL);
}
static char letter_to_class(int keyn)
{
if (keyn == 'a')
return JOB_FIGHTER;
else if (keyn == 'b')
return JOB_WIZARD;
else if (keyn == 'c')
return JOB_PRIEST;
else if (keyn == 'd')
return JOB_THIEF;
else if (keyn == 'e')
return JOB_GLADIATOR;
else if (keyn == 'f')
return JOB_NECROMANCER;
else if (keyn == 'g')
return JOB_PALADIN;
else if (keyn == 'h')
return JOB_ASSASSIN;
else if (keyn == 'i')
return JOB_BERSERKER;
else if (keyn == 'j')
return JOB_HUNTER;
else if (keyn == 'k')
return JOB_CONJURER;
else if (keyn == 'l')
return JOB_ENCHANTER;
else if (keyn == 'm')
return JOB_FIRE_ELEMENTALIST;
else if (keyn == 'n')
return JOB_ICE_ELEMENTALIST;
else if (keyn == 'o')
return JOB_SUMMONER;
else if (keyn == 'p')
return JOB_AIR_ELEMENTALIST;
else if (keyn == 'q')
return JOB_EARTH_ELEMENTALIST;
else if (keyn == 'r')
return JOB_CRUSADER;
else if (keyn == 's')
return JOB_DEATH_KNIGHT;
else if (keyn == 't')
return JOB_VENOM_MAGE;
else if (keyn == 'u')
return JOB_CHAOS_KNIGHT;
else if (keyn == 'v')
return JOB_TRANSMUTER;
else if (keyn == 'w')
return JOB_HEALER;
else if (keyn == 'y')
return JOB_REAVER;
else if (keyn == 'z')
return JOB_STALKER;
else if (keyn == 'A')
return JOB_MONK;
else if (keyn == 'B')
return JOB_WARPER;
else if (keyn == 'C')
return JOB_WANDERER;
return JOB_UNKNOWN;
}
static char letter_to_species(int keyn)
{
switch (keyn)
{
case 'a':
return SP_HUMAN;
case 'b':
return SP_ELF;
case 'c':
return SP_HIGH_ELF;
case 'd':
return SP_GREY_ELF;
case 'e':
return SP_DEEP_ELF;
case 'f':
return SP_SLUDGE_ELF;
case 'g':
return SP_HILL_DWARF;
case 'h':
return SP_MOUNTAIN_DWARF;
case 'i':
return SP_HALFLING;
case 'j':
return SP_HILL_ORC;
case 'k':
return SP_KOBOLD;
case 'l':
return SP_MUMMY;
case 'm':
return SP_NAGA;
case 'n':
return SP_GNOME;
case 'o':
return SP_OGRE;
case 'p':
return SP_TROLL;
case 'q':
return SP_OGRE_MAGE;
case 'r': // draconian
return SP_RED_DRACONIAN + random2(9); // random drac
case 's':
return SP_CENTAUR;
case 't':
return SP_DEMIGOD;
case 'u':
return SP_SPRIGGAN;
case 'v':
return SP_MINOTAUR;
case 'w':
return SP_DEMONSPAWN;
case 'x':
return SP_GHOUL;
case 'y':
return SP_KENKU;
case 'z':
return SP_MERFOLK;
default:
return 0;
}
}
static char species_to_letter(int spec)
{
if (spec > SP_RED_DRACONIAN && spec <= SP_UNK2_DRACONIAN)
spec = SP_RED_DRACONIAN;
else if (spec > SP_UNK2_DRACONIAN)
spec -= SP_UNK2_DRACONIAN - SP_RED_DRACONIAN;
return 'a' + spec - 1;
}
textcolor( WHITE );
cprintf("You must be new here!" EOL EOL);
if (you.char_class != JOB_UNKNOWN)
{
textcolor( BROWN );
bool shortgreet = false;
if (strlen(you.your_name) || you.char_class != JOB_UNKNOWN)
cprintf("Welcome, ");
else
{
cprintf("Welcome.");
shortgreet = true;
}
textcolor( YELLOW );
if (strlen(you.your_name) > 0)
{
cprintf(you.your_name);
if (you.char_class != JOB_UNKNOWN)
cprintf(" the ");
}
if (you.char_class != JOB_UNKNOWN)
cprintf(get_class_name(you.char_class));
cprintf("a - Human b - Elf" EOL);
cprintf("c - High Elf d - Grey Elf" EOL);
cprintf("e - Deep Elf f - Sludge Elf" EOL);
cprintf("g - Hill Dwarf h - Mountain Dwarf" EOL);
cprintf("i - Halfling j - Hill Orc" EOL);
cprintf("k - Kobold l - Mummy" EOL);
cprintf("m - Naga n - Gnome" EOL);
cprintf("o - Ogre p - Troll" EOL);
cprintf("q - Ogre-Mage r - Draconian" EOL);
cprintf("s - Centaur t - Demigod" EOL);
cprintf("u - Spriggan v - Minotaur" EOL);
cprintf("w - Demonspawn x - Ghoul" EOL);
cprintf("y - Kenku z - Merfolk" EOL);
int linec = 0;
char linebuf[200];
*linebuf = 0;
for (int i = SP_HUMAN; i < NUM_SPECIES; ++i)
{
if (i > SP_RED_DRACONIAN && i <= SP_UNK2_DRACONIAN)
continue;
if (you.char_class != JOB_UNKNOWN &&
!class_allowed(i, you.char_class))
continue;
char buf[100];
char sletter = species_to_letter(i);
snprintf(buf, sizeof buf, "%c - %-26s",
sletter,
species_name(i, 1));
if (sletter == Options.prev_race)
prevraceok = true;
strncat(linebuf, buf, sizeof linebuf);
if (++linec >= 2)
{
cprintf("%s" EOL, linebuf);
*linebuf = 0;
linec = 0;
}
}
if (linec)
cprintf("%s" EOL, linebuf);
cprintf(EOL "? - Random Species * - Random Character" EOL);
cprintf( "X - Quit" EOL);
if (you.char_class == JOB_UNKNOWN)
cprintf(EOL "SPACE - Choose class first; "
"? - Random Species; * - Random Character; X - Quit"
EOL);
else
cprintf(EOL "? - Random; Bksp - Back to class selection; X - Quit"
EOL);
if (Options.prev_race)
{
if (prevraceok)
cprintf("Enter - %s", get_opt_race_name(Options.prev_race).c_str());
if (prev_startup_options_set())
cprintf("%sTAB - %s",
prevraceok? "; " : "",
prev_startup_description().c_str());
cprintf(EOL);
}
keyn = getch();
if (keyn == 0)
keyn = c_getch();
}
if ((keyn == '\r' || keyn == '\n') && Options.prev_race && prevraceok)
keyn = Options.prev_race;
if (keyn == '\t' && prev_startup_options_set())
{
if (Options.prev_randpick ||
(Options.prev_race == '?' && Options.prev_cls == '?'))
case 'a':
you.species = SP_HUMAN;
break;
case 'b':
you.species = SP_ELF;
break;
case 'c':
you.species = SP_HIGH_ELF;
break;
case 'd':
you.species = SP_GREY_ELF;
break;
case 'e':
you.species = SP_DEEP_ELF;
break;
case 'f':
you.species = SP_SLUDGE_ELF;
break;
case 'g':
you.species = SP_HILL_DWARF;
break;
case 'h':
you.species = SP_MOUNTAIN_DWARF;
break;
case 'i':
you.species = SP_HALFLING;
break;
case 'j':
you.species = SP_HILL_ORC;
break;
case 'k':
you.species = SP_KOBOLD;
break;
case 'l':
you.species = SP_MUMMY;
break;
case 'm':
you.species = SP_NAGA;
break;
case 'n':
you.species = SP_GNOME;
break;
case 'o':
you.species = SP_OGRE;
break;
case 'p':
you.species = SP_TROLL;
break;
case 'q':
you.species = SP_OGRE_MAGE;
break;
case 'r': // draconian
you.species = SP_RED_DRACONIAN + random2(9); // random drac
break;
case 's':
you.species = SP_CENTAUR;
break;
case 't':
you.species = SP_DEMIGOD;
break;
case 'u':
you.species = SP_SPRIGGAN;
break;
case 'v':
you.species = SP_MINOTAUR;
break;
case 'w':
you.species = SP_DEMONSPAWN;
break;
case 'x':
you.species = SP_GHOUL;
break;
case 'y':
you.species = SP_KENKU;
break;
case 'z':
you.species = SP_MERFOLK;
break;
case 'X':
cprintf(EOL "Goodbye!");
end(0);
break;
default:
if (Options.race != 0)
switch (keyn)
cprintf(you.your_name);
cprintf(" the ");
textcolor( BROWN );
bool shortgreet = false;
if (strlen(you.your_name) || you.species)
cprintf("Welcome, ");
else
{
cprintf("Welcome.");
shortgreet = true;
}
textcolor( YELLOW );
if (strlen(you.your_name) > 0)
{
cprintf(you.your_name);
if (you.species)
cprintf(" the ");
}
if (you.species)
cprintf(species_name(you.species,you.experience_level));
if (!shortgreet)
cprintf(".");
cprintf(EOL "? - Random; x - Back to species selection; X - Quit" EOL);
if (!you.species)
cprintf(EOL "SPACE - Choose species first; "
"? - Random Class; * - Random Character; X - Quit"
EOL);
else
cprintf(EOL "? - Random; Bksp - Back to species selection; X - Quit"
EOL);
if (keyn == 'a')
you.char_class = JOB_FIGHTER;
else if (keyn == 'b')
you.char_class = JOB_WIZARD;
else if (keyn == 'c')
you.char_class = JOB_PRIEST;
else if (keyn == 'd')
you.char_class = JOB_THIEF;
else if (keyn == 'e')
you.char_class = JOB_GLADIATOR;
else if (keyn == 'f')
you.char_class = JOB_NECROMANCER;
else if (keyn == 'g')
you.char_class = JOB_PALADIN;
else if (keyn == 'h')
you.char_class = JOB_ASSASSIN;
else if (keyn == 'i')
you.char_class = JOB_BERSERKER;
else if (keyn == 'j')
you.char_class = JOB_HUNTER;
else if (keyn == 'k')
you.char_class = JOB_CONJURER;
else if (keyn == 'l')
you.char_class = JOB_ENCHANTER;
else if (keyn == 'm')
you.char_class = JOB_FIRE_ELEMENTALIST;
else if (keyn == 'n')
you.char_class = JOB_ICE_ELEMENTALIST;
else if (keyn == 'o')
you.char_class = JOB_SUMMONER;
else if (keyn == 'p')
you.char_class = JOB_AIR_ELEMENTALIST;
else if (keyn == 'q')
you.char_class = JOB_EARTH_ELEMENTALIST;
else if (keyn == 'r')
you.char_class = JOB_CRUSADER;
else if (keyn == 's')
you.char_class = JOB_DEATH_KNIGHT;
else if (keyn == 't')
you.char_class = JOB_VENOM_MAGE;
else if (keyn == 'u')
you.char_class = JOB_CHAOS_KNIGHT;
else if (keyn == 'v')
you.char_class = JOB_TRANSMUTER;
else if (keyn == 'w')
you.char_class = JOB_HEALER;
else if (keyn == 'y')
you.char_class = JOB_REAVER;
else if (keyn == 'z')
you.char_class = JOB_STALKER;
else if (keyn == 'A')
you.char_class = JOB_MONK;
else if (keyn == 'B')
you.char_class = JOB_WARPER;
else if (keyn == 'C')
you.char_class = JOB_WANDERER;
else if (keyn == '?')
{
// pick a job at random... see god retribution for proof this
// is uniform. -- bwr
int job_count = 0;
int job = -1;
if ((keyn == '\r' || keyn == '\n') && Options.prev_cls && prevclassok)
keyn = Options.prev_cls;
cprintf(EOL "Goodbye!");
end(0);
}
else
{
if (Options.cls != 0)
if (keyn == '?')
{
// pick a job at random... see god retribution for proof this
// is uniform. -- bwr
int job_count = 0;
int job = -1;
for (int i = 0; i < NUM_JOBS; i++)
{
if (!you.species || class_allowed(you.species, i))
{
job_count++;
if (one_chance_in( job_count ))
job = i;
}
}
ASSERT( job != -1 ); // at least one class should have been allowed
you.char_class = job;
ng_cls = '?';
}
else if (keyn == '*')
goto job_query;
else if ((keyn == ' ' && !you.species) ||
keyn == 'x' || keyn == ESCAPE || keyn == CK_BKSP)
{
you.char_class = JOB_UNKNOWN;
return false;
}
else if (keyn == 'X')
{
cprintf(EOL "Goodbye!");
end(0);
}
else
{
if (Options.cls != 0)
{
Options.cls = 0;
printed = false;
}
goto job_query;
}
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
/* initializes mt[N] with a seed */
void init_genrand( unsigned long s );
/* generates a random number on [0,0xffffffff]-interval */
unsigned long genrand_int32( void );
void push_mt_state();
void pop_mt_state();
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
#include <stdio.h>
#include <string.h>
#include <stack>
/* Period parameters */
#define N 624
#define M 397
#define MATRIX_A 0x9908b0dfUL /* constant vector a */
#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
static unsigned long mt[N]; /* the array for the state vector */
static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
struct mtrng_state
{
int mti;
unsigned long mt[N];
};
static std::stack<mtrng_state*> states;
void push_mt_state()
{
// We're doing a new, delete and a lot of copying for saving/restoring
// state. Slow.
mtrng_state *mtst = new mtrng_state;
if (!mtst)
return ;
mtst->mti = mti;
memcpy(mtst->mt, mt, sizeof(mt));
states.push(mtst);
}
void pop_mt_state()
{
if (states.empty())
return;
mtrng_state *mtst = states.top();
states.pop();
mti = mtst->mti;
memcpy(mt, mtst->mt, sizeof(mt));
delete mtst;
}
/* initializes mt[N] with a seed */
void init_genrand(unsigned long s)
{
mt[0]= s & 0xffffffffUL;
for (mti=1; mti<N; mti++) {
mt[mti] =
(1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
mt[mti] &= 0xffffffffUL;
/* for >32 bit machines */
}
}
#if 0
/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
void init_by_array(unsigned long init_key[], int key_length)
{
int i, j, k;
init_genrand(19650218UL);
i=1; j=0;
k = (N>key_length ? N : key_length);
for (; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL))
+ init_key[j] + j; /* non linear */
mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
i++; j++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
if (j>=key_length) j=0;
}
for (k=N-1; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
- i; /* non linear */
mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
i++;
if (i>=N) { mt[0] = mt[N-1]; i=1; }
}
mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
}
#endif
/* generates a random number on [0,0xffffffff]-interval */
unsigned long genrand_int32(void)
{
unsigned long y;
static unsigned long mag01[2]={0x0UL, MATRIX_A};
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (mti >= N) { /* generate N words at one time */
int kk;
if (mti == N+1) /* if init_genrand() has not been called, */
init_genrand(5489UL); /* a default initial seed is used */
for (kk=0;kk<N-M;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
for (;kk<N-1;kk++) {
y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
}
y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
mti = 0;
}
y = mt[mti++];
/* Tempering */
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680UL;
y ^= (y << 15) & 0xefc60000UL;
y ^= (y >> 18);
return y;
}
#if 0
/* generates a random number on [0,0x7fffffff]-interval */
long genrand_int31(void)
{
return (long)(genrand_int32()>>1);
}
/* generates a random number on [0,1]-real-interval */
double genrand_real1(void)
{
return genrand_int32()*(1.0/4294967295.0);
/* divided by 2^32-1 */
}
/* generates a random number on [0,1)-real-interval */
double genrand_real2(void)
{
return genrand_int32()*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on (0,1)-real-interval */
double genrand_real3(void)
{
return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on [0,1) with 53-bit resolution*/
double genrand_res53(void)
{
unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
return(a*67108864.0+b)*(1.0/9007199254740992.0);
}
/* These real versions are due to Isaku Wada, 2002/01/09 added */
int main(void)
{
int i;
unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4;
init_by_array(init, length);
printf("1000 outputs of genrand_int32()\n");
for (i=0; i<1000; i++) {
printf("%10lu ", genrand_int32());
if (i%5==4) printf("\n");
}
printf("\n1000 outputs of genrand_real2()\n");
for (i=0; i<1000; i++) {
printf("%10.8f ", genrand_real2());
if (i%5==4) printf("\n");
}
return 0;
}
#endif
|| (you.religion == GOD_MAKHLEB && you.duration[DUR_PRAYER]
&& (!player_under_penance() && random2(you.piety) >= 30)))
|| (!created_friendly &&
you.religion == GOD_MAKHLEB && you.duration[DUR_PRAYER] &&
(!player_under_penance() && random2(you.piety) >= 30)))
|| target_power > source_power + (relax * 3) / 2);
|| target_power > source_power + (relax * 3) / 2));
}
if(!valid_morph( monster, targetc )) {
strcat( str_polymon, " looks momentarily different.");
player_messaged = simple_monster_message( monster, str_polymon );
return (player_messaged);
// A sleeping monster that sustains damage will wake up.
if ((wake || hurted > 0) && monster->behaviour == BEH_SLEEP)
{
// We have no good coords to give the monster as the source of the
// disturbance other than the cloud itself.
behaviour_event(monster, ME_DISTURB, MHITNOT, monster->x, monster->y);
}
bool mons_is_stabbable(struct monsters *m)
{
// Make sure oklob plants are never highlighted. That'll defeat the
// point of making them look like normal plants.
return (!mons_flag(m->type, M_NO_EXP_GAIN)
&& m->type != MONS_OKLOB_PLANT
&& !mons_friendly(m)
&& m->behaviour == BEH_SLEEP);
}
bool mons_maybe_stabbable(struct monsters *m)
{
return (!mons_flag(m->type, M_NO_EXP_GAIN)
&& m->type != MONS_OKLOB_PLANT
&& !mons_friendly(m)
&& ((m->foe != MHITYOU && !testbits(m->flags, MF_BATTY))
|| (mons_has_ench(m, ENCH_CONFUSION) &&
!mons_flag(m->type, M_CONFUSED))
|| m->behaviour == BEH_FLEE));
}
unsigned char old_level = you.your_level;
unsigned char old_level = you.your_level;
// Interlevel travel data:
bool collect_travel_data = you.level_type != LEVEL_LABYRINTH
&& you.level_type != LEVEL_ABYSS
&& you.level_type != LEVEL_PANDEMONIUM;
level_id old_level_id = level_id::get_current_level_id();
LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id);
int stair_x = you.x_pos, stair_y = you.y_pos;
if (collect_travel_data)
old_level_info.update();
// Tell the travel code that we're now on a new level
travel_init_new_level();
if (collect_travel_data)
{
// Update stair information for the stairs we just ascended, and the
// down stairs we're currently on.
level_id new_level_id = level_id::get_current_level_id();
if (you.level_type != LEVEL_PANDEMONIUM &&
you.level_type != LEVEL_ABYSS &&
you.level_type != LEVEL_LABYRINTH)
{
LevelInfo &new_level_info =
travel_cache.get_level_info(new_level_id);
new_level_info.update();
// First we update the old level's stair.
level_pos lp;
lp.id = new_level_id;
lp.pos.x = you.x_pos;
lp.pos.y = you.y_pos;
bool guess = false;
// Ugly hack warning:
// The stairs in the Vestibule of Hell exhibit special behaviour:
// they always lead back to the dungeon level that the player
// entered the Vestibule from. This means that we need to pretend
// we don't know where the upstairs from the Vestibule go each time
// we take it. If we don't, interlevel travel may try to use portals
// to Hell as shortcuts between dungeon levels, which won't work,
// and will confuse the dickens out of the player (well, it confused
// the dickens out of me when it happened).
if (new_level_id.branch == BRANCH_MAIN_DUNGEON &&
old_level_id.branch == BRANCH_VESTIBULE_OF_HELL)
{
lp.id.depth = -1;
lp.pos.x = lp.pos.y = -1;
guess = true;
}
old_level_info.update_stair(stair_x, stair_y, lp, guess);
// We *guess* that going up a staircase lands us on a downstair,
// and that we can descend that downstair and get back to where we
// came from. This assumption is guaranteed false when climbing out
// of one of the branches of Hell.
if (new_level_id.branch != BRANCH_VESTIBULE_OF_HELL)
{
// Set the new level's stair, assuming arbitrarily that going
// downstairs will land you on the same upstairs you took to
// begin with (not necessarily true).
lp.id = old_level_id;
lp.pos.x = stair_x;
lp.pos.y = stair_y;
new_level_info.update_stair(you.x_pos, you.y_pos, lp, true);
}
}
}
// Interlevel travel data:
bool collect_travel_data = you.level_type != LEVEL_LABYRINTH
&& you.level_type != LEVEL_ABYSS
&& you.level_type != LEVEL_PANDEMONIUM;
level_id old_level_id = level_id::get_current_level_id();
LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id);
int stair_x = you.x_pos, stair_y = you.y_pos;
if (collect_travel_data)
old_level_info.update();
travel_init_new_level();
if (collect_travel_data)
{
// Update stair information for the stairs we just descended, and the
// upstairs we're currently on.
level_id new_level_id = level_id::get_current_level_id();
if (you.level_type != LEVEL_PANDEMONIUM &&
you.level_type != LEVEL_ABYSS &&
you.level_type != LEVEL_LABYRINTH)
{
LevelInfo &new_level_info =
travel_cache.get_level_info(new_level_id);
new_level_info.update();
// First we update the old level's stair.
level_pos lp;
lp.id = new_level_id;
lp.pos.x = you.x_pos;
lp.pos.y = you.y_pos;
old_level_info.update_stair(stair_x, stair_y, lp);
// Then the new level's stair, assuming arbitrarily that going
// upstairs will land you on the same downstairs you took to begin
// with (not necessarily true).
lp.id = old_level_id;
lp.pos.x = stair_x;
lp.pos.y = stair_y;
new_level_info.update_stair(you.x_pos, you.y_pos, lp, true);
}
}
you.running = 0;
// If you're travelling, only certain user-specified messages can break
// travel
if (you.running < 0)
{
std::string message = inf;
for (unsigned i = 0; i < Options.stop_travel.size(); ++i)
{
if (Options.stop_travel[i].is_filtered( channel, message ))
{
stop_running();
break;
}
}
}
if (Options.sound_mappings.size() > 0)
{
std::string message = inf;
for (unsigned i = 0; i < Options.sound_mappings.size(); i++)
{
// Maybe we should allow message channel matching as for
// stop_travel?
if (Options.sound_mappings[i].pattern.matches(message))
{
play_sound(Options.sound_mappings[i].soundfile.c_str());
break;
}
}
}
std::string get_last_messages(int mcount)
{
if (mcount <= 0) return std::string();
if (mcount > NUM_STORED_MESSAGES) mcount = NUM_STORED_MESSAGES;
bool full_buffer = Store_Message[ NUM_STORED_MESSAGES - 1 ].text.length() == 0;
int initial = Next_Message - mcount;
if (initial < 0 || initial > NUM_STORED_MESSAGES)
initial = full_buffer? initial + NUM_STORED_MESSAGES : 0;
std::string text;
int count = 0;
for (int i = initial; i != Next_Message; )
{
if (Store_Message[i].text.length())
{
text += Store_Message[i].text;
text += EOL;
count++;
}
#ifndef __MENU_H__
#define __MENU_H__
#include <string>
#include <vector>
#include <algorithm>
#include "AppHdr.h"
#include "defines.h"
#include "libutil.h"
enum MenuEntryLevel
{
MEL_NONE = -1,
MEL_TITLE,
MEL_SUBTITLE,
MEL_ITEM
};
struct menu_letter
{
char letter;
menu_letter() : letter('a') { }
menu_letter(char c) : letter(c) { }
operator char () const { return letter; }
const menu_letter &operator ++ ()
{
letter = letter == 'z'? 'A' :
letter == 'Z'? 'a' :
letter + 1;
return *this;
}
menu_letter operator ++ (int postfix)
{
menu_letter copy = *this;
this->operator++();
return copy;
}
};
struct item_def;
struct MenuEntry
{
std::string text;
int quantity, selected_qty;
int colour;
std::vector<int> hotkeys;
MenuEntryLevel level;
void *data;
MenuEntry( const std::string &txt = std::string(""),
MenuEntryLevel lev = MEL_ITEM,
int qty = 0,
int hotk = 0 ) :
text(txt), quantity(qty), selected_qty(0), colour(-1),
hotkeys(), level(lev), data(NULL)
{
colour = lev == MEL_ITEM? LIGHTGREY :
lev == MEL_SUBTITLE? BLUE :
WHITE;
if (hotk)
hotkeys.push_back( hotk );
}
virtual ~MenuEntry() { }
void add_hotkey( int key )
{
if (key && !is_hotkey(key))
hotkeys.push_back( key );
}
bool is_hotkey( int key ) const
{
return find( hotkeys.begin(), hotkeys.end(), key ) != hotkeys.end();
}
bool is_primary_hotkey( int key ) const
{
return hotkeys.size()? hotkeys[0] == key : false;
}
virtual std::string get_text() const
{
if (level == MEL_ITEM && hotkeys.size())
{
char buf[300];
snprintf(buf, sizeof buf,
"%c - %s", hotkeys[0], text.c_str());
return std::string(buf);
}
return std::string(level == MEL_SUBTITLE? " " :
level == MEL_ITEM? "" : " ") + text;
}
virtual bool selected() const
{
return selected_qty > 0 && quantity;
}
void select( int qty = -1 )
{
if (selected())
selected_qty = 0;
else if (quantity)
selected_qty = qty == -1? quantity : qty;
}
};
class MenuHighlighter
{
public:
virtual int entry_colour(const MenuEntry *entry) const;
virtual ~MenuHighlighter() { }
};
enum MenuFlag
{
MF_NOSELECT = 0, // No selection is permitted
MF_SINGLESELECT = 1, // Select just one item
MF_MULTISELECT = 2, // Select multiple items
MF_NO_SELECT_QTY = 4, // Disallow partial selections
MF_ANYPRINTABLE = 8, // Any printable character is valid, and
// closes the menu.
MF_SELECT_ANY_PAGE = 16, // Allow selections to occur on any page.
MF_EASY_EXIT = 64,
};
///////////////////////////////////////////////////////////////////////
// NOTE
// As a general contract, any pointers you pass to Menu methods are OWNED BY
// THE MENU, and will be deleted by the Menu on destruction. So any pointers
// you pass in MUST be allocated with new, or Crawl will crash.
#define NUMBUFSIZ 10
class Menu
{
public:
Menu( int flags = MF_MULTISELECT );
virtual ~Menu();
void set_flags( int flags ) { this->flags = flags; }
int get_flags() const { return flags; }
bool is_set( int flag ) const;
bool draw_title_suffix( const std::string &s, bool titlefirst = true );
void update_title();
void set_highlighter( MenuHighlighter *h );
void set_title( MenuEntry *e );
void add_entry( MenuEntry *entry );
void get_selected( std::vector<MenuEntry*> *sel ) const;
void set_select_filter( std::vector<text_pattern> filter )
{
select_filter = filter;
}
unsigned char getkey() const { return lastch; }
void reset();
std::vector<MenuEntry *> show();
public:
typedef std::string (*selitem_tfn)( const std::vector<MenuEntry*> *sel );
selitem_tfn selitem_text;
protected:
MenuEntry *title;
int flags;
int first_entry, y_offset;
int pagesize;
std::vector<MenuEntry*> items;
std::vector<MenuEntry*> *sel;
std::vector<text_pattern> select_filter;
// Class that is queried to colour menu entries.
MenuHighlighter *highlighter;
int num;
unsigned char lastch;
bool alive;
void do_menu( std::vector<MenuEntry*> *selected );
virtual void draw_select_count( int count );
void draw_item( int index ) const;
virtual void draw_title();
void draw_menu( std::vector<MenuEntry*> *selected );
bool page_down();
bool line_down();
bool page_up();
bool line_up();
void deselect_all(bool update_view = true);
void select_items( int key, int qty = -1 );
void select_index( int index, int qty = -1 );
bool is_hotkey(int index, int key );
bool is_selectable(int index) const;
int item_colour(const MenuEntry *me) const;
virtual bool process_key( int keyin );
};
int menu_colour(const std::string &itemtext);
#endif
#include <cctype>
#include "menu.h"
#include "macro.h"
#include "view.h"
Menu::Menu( int _flags )
: selitem_text(NULL),
title(NULL),
flags(_flags),
first_entry(0),
y_offset(0),
pagesize(0),
items(),
sel(NULL),
select_filter(),
highlighter(new MenuHighlighter),
num(-1),
lastch(0),
alive(false)
{
}
Menu::~Menu()
{
for (int i = 0, count = items.size(); i < count; ++i)
delete items[i];
delete title;
delete highlighter;
}
void Menu::set_highlighter( MenuHighlighter *mh )
{
if (highlighter != mh)
delete highlighter;
highlighter = mh;
}
void Menu::set_title( MenuEntry *e )
{
if (title != e)
delete title;
title = e;
title->level = MEL_TITLE;
}
void Menu::add_entry( MenuEntry *entry )
{
items.push_back( entry );
}
void Menu::reset()
{
first_entry = 0;
}
std::vector<MenuEntry *> Menu::show()
{
std::vector< MenuEntry * > selected;
deselect_all(false);
// Lose lines for the title + room for more.
pagesize = get_number_of_lines() - 2;
#ifdef DOS_TERM
char buffer[4600];
gettext(1, 1, 80, 25, buffer);
window(1, 1, 80, 25);
#endif
do_menu( &selected );
#ifdef DOS_TERM
puttext(1, 1, 80, 25, buffer);
#endif
return selected;
}
void Menu::do_menu( std::vector<MenuEntry*> *selected )
{
sel = selected;
draw_menu( selected );
alive = true;
while (alive)
{
int keyin = getchm(c_getch);
if (!process_key( keyin ))
return;
}
}
bool Menu::is_set(int flag) const
{
if (flag == MF_EASY_EXIT && Options.easy_exit_menu)
return true;
return (flags & flag) == flag;
}
bool Menu::process_key( int keyin )
{
if (items.size() == 0)
return false;
bool nav = false, repaint = false;
switch (keyin)
{
case CK_ENTER:
return false;
case CK_ESCAPE:
sel->clear();
lastch = keyin;
return false;
case ' ': case CK_PGDN: case '>': case '\'':
nav = true;
repaint = page_down();
if (!repaint && flags && !is_set(MF_EASY_EXIT))
{
repaint = first_entry != 0;
first_entry = 0;
}
break;
case CK_PGUP: case '<': case ';':
nav = true;
repaint = page_up();
break;
case CK_UP:
nav = true;
repaint = line_up();
break;
case CK_DOWN:
nav = true;
repaint = line_down();
break;
default:
lastch = keyin;
// If no selection at all is allowed, exit now.
if (!(flags & (MF_SINGLESELECT | MF_MULTISELECT)))
return false;
if (!is_set(MF_NO_SELECT_QTY) && isdigit( keyin ))
{
if (num > 999)
num = -1;
num = (num == -1)? keyin - '0' :
num * 10 + keyin - '0';
}
select_items( keyin, num );
get_selected( sel );
if (sel->size() == 1 && (flags & MF_SINGLESELECT))
return false;
draw_select_count( sel->size() );
if (flags & MF_ANYPRINTABLE && !isdigit( keyin ))
return false;
break;
}
if (!isdigit( keyin ))
num = -1;
if (nav)
{
if (repaint)
draw_menu( sel );
// Easy exit should not kill the menu if there are selected items.
else if (sel->empty() && (!flags || is_set(MF_EASY_EXIT)))
{
sel->clear();
return false;
}
}
return true;
}
bool Menu::draw_title_suffix( const std::string &s, bool titlefirst )
{
int oldx = wherex(), oldy = wherey();
if (titlefirst)
draw_title();
int x = wherex();
if (x > GXM || x < 1)
{
gotoxy(oldx, oldy);
return false;
}
// Note: 1 <= x <= GXM; we have no fear of overflow.
unsigned avail_width = GXM - x + 1;
std::string towrite = s.length() > avail_width? s.substr(0, avail_width) :
s.length() == avail_width? s :
s + std::string(avail_width - s.length(), ' ');
cprintf(towrite.c_str());
gotoxy( oldx, oldy );
return true;
}
void Menu::draw_select_count( int count )
{
if (!is_set(MF_MULTISELECT))
return;
if (selitem_text)
{
draw_title_suffix( selitem_text( sel ) );
}
else
{
char buf[100] = "";
if (count)
snprintf(buf, sizeof buf, " (%d item%s) ", count,
(count > 1? "s" : ""));
draw_title_suffix( buf );
}
}
void Menu::get_selected( std::vector<MenuEntry*> *selected ) const
{
selected->clear();
for (int i = 0, count = items.size(); i < count; ++i)
if (items[i]->selected())
selected->push_back( items[i] );
}
void Menu::deselect_all(bool update_view)
{
for (int i = 0, count = items.size(); i < count; ++i)
{
if (items[i]->level == MEL_ITEM)
{
items[i]->select(0);
if (update_view)
draw_item(i);
}
}
}
bool Menu::is_hotkey(int i, int key)
{
int end = first_entry + pagesize;
if (end > (int) items.size()) end = items.size();
bool ishotkey = is_set(MF_SINGLESELECT)?
items[i]->is_primary_hotkey(key)
: items[i]->is_hotkey(key);
return is_set(MF_SELECT_ANY_PAGE)? ishotkey
: ishotkey && i >= first_entry && i < end;
}
void Menu::select_items( int key, int qty )
{
int x = wherex(), y = wherey();
if (key == ',' || key == '*')
select_index( -1, qty );
else if (key == '-')
select_index( -1, 0 );
else
{
int final = items.size();
bool selected = false;
// Process all items, in case user hits hotkey for an
// item not on the current page.
// We have to use some hackery to handle items that share
// the same hotkey (as for pickup when there's a stack of
// >52 items). If there are duplicate hotkeys, the items
// are usually separated by at least a page, so we should
// only select the item on the current page. This is why we
// use two loops, and check to see if we've matched an item
// by its primary hotkey (which is assumed to always be
// hotkeys[0]), in which case, we stop selecting further
// items.
for (int i = first_entry; i < final; ++i)
{
if (is_hotkey( i, key ))
{
select_index( i, qty );
if (items[i]->hotkeys[0] == key)
{
selected = true;
break;
}
}
}
if (!selected)
{
for (int i = 0; i < first_entry; ++i)
{
if (is_hotkey( i, key ))
{
select_index( i, qty );
if (items[i]->hotkeys[0] == key)
{
selected = true;
break;
}
}
}
}
}
gotoxy( x, y );
}
bool Menu::is_selectable(int item) const
{
if (select_filter.empty()) return true;
std::string text = items[item]->get_text();
for (int i = 0, count = select_filter.size(); i < count; ++i)
{
if (select_filter[i].matches(text))
return true;
}
return false;
}
void Menu::select_index( int index, int qty )
{
int si = index == -1? first_entry : index;
if (index == -1)
{
if (flags & MF_MULTISELECT)
{
for (int i = 0, count = items.size(); i < count; ++i)
{
if (items[i]->level != MEL_ITEM) continue;
if (is_hotkey(i, items[i]->hotkeys[0]) && is_selectable(i))
{
items[i]->select( qty );
draw_item( i );
}
}
}
}
else if (items[si]->level == MEL_SUBTITLE && (flags & MF_MULTISELECT))
{
for (int i = si + 1, count = items.size(); i < count; ++i)
{
if (items[i]->level != MEL_ITEM)
break;
if (is_hotkey(i, items[i]->hotkeys[0]))
{
items[i]->select( qty );
draw_item( i );
}
}
}
else if (items[si]->level == MEL_ITEM &&
(flags & (MF_SINGLESELECT | MF_MULTISELECT)))
{
items[si]->select( qty );
draw_item( si );
}
}
void Menu::draw_menu( std::vector< MenuEntry* > *selected )
{
clrscr();
draw_title();
draw_select_count( selected->size() );
y_offset = 2;
int end = first_entry + pagesize;
if (end > (int) items.size()) end = items.size();
for (int i = first_entry; i < end; ++i)
{
draw_item( i );
}
if (end < (int) items.size())
{
gotoxy( 1, y_offset + pagesize );
textcolor( LIGHTGREY );
cprintf("-more-");
}
}
void Menu::update_title()
{
int x = wherex(), y = wherey();
draw_title();
gotoxy(x, y);
}
int Menu::item_colour(const MenuEntry *entry) const
{
int icol = -1;
if (highlighter)
icol = highlighter->entry_colour(entry);
return (icol == -1? entry->colour : icol);
}
void Menu::draw_title()
{
if (title)
{
gotoxy(1, 1);
textcolor( item_colour(title) );
cprintf( "%s", title->get_text().c_str() );
}
}
void Menu::draw_item( int index ) const
{
if (index < first_entry || index >= first_entry + pagesize)
return;
gotoxy( 1, y_offset + index - first_entry );
textcolor( item_colour(items[index]) );
cprintf( "%s", items[index]->get_text().c_str() );
}
bool Menu::page_down()
{
int old_first = first_entry;
if ((int) items.size() > first_entry + pagesize)
{
first_entry += pagesize;
//if (first_entry + pagesize > (int) items.size())
// first_entry = items.size() - pagesize;
if (old_first != first_entry)
return true;
}
return false;
}
bool Menu::page_up()
{
int old_first = first_entry;
if (first_entry > 0)
{
if ((first_entry -= pagesize) < 0)
first_entry = 0;
if (old_first != first_entry)
return true;
}
return false;
}
bool Menu::line_down()
{
if (first_entry + pagesize < (int) items.size())
{
++first_entry;
return true;
}
return false;
}
bool Menu::line_up()
{
if (first_entry > 0)
{
--first_entry;
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////
// Menu colouring
//
int menu_colour(const std::string &text)
{
for (int i = 0, size = Options.menu_colour_mappings.size(); i < size; ++i)
{
colour_mapping &cm = Options.menu_colour_mappings[i];
if (cm.pattern.matches(text))
return (cm.colour);
}
return (-1);
}
int MenuHighlighter::entry_colour(const MenuEntry *entry) const
{
return (::menu_colour(entry->get_text()));
}
# Make file for Dungeon Crawl (dos)
# this file contains a list of the libraries.
# it will make a variable called OBJECTS that contains all the libraries
include makefile.obj
# need .exe so make will find the right file
APPNAME = crawl.exe
CXX = g++
DELETE = rm
COPY = copy
OS_TYPE = WIN32CONSOLE
CFLAGS = -Wall -Wwrite-strings -Wstrict-prototypes \
-Wmissing-prototypes -Wmissing-declarations \
-D$(OS_TYPE) $(EXTRA_FLAGS)
LDFLAGS =
INSTALLDIR = .
#LIB = -lcurso -lpano
LIB = -lwinmm -static -lpcre -lluacore -lluastd
all: $(APPNAME)
install: $(APPNAME)
$(COPY) $(APPNAME) ${INSTALLDIR}
clean:
$(DELETE) *.o
distclean:
$(DELETE) *.o
$(DELETE) bones.*
$(DELETE) morgue.txt
$(DELETE) scores
$(DELETE) $(APPNAME)
$(DELETE) *.sav
$(DELETE) core
$(DELETE) *.0*
$(DELETE) *.lab
$(APPNAME): $(OBJECTS) libw32c.o
${CXX} ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) libw32c.o -o $(APPNAME) $(LIB)
strip $(APPNAME)
debug: $(OBJECTS)
${CXX} ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) -o $(APPNAME) $(LIB)
profile: $(OBJECTS)
${CXX} -g -p ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) -o $(APPNAME) $(LIB)
.cc.o:
${CXX} ${CFLAGS} -c $< ${INCLUDE}
int getchm(void); // keymaps applied (ie for prompts)
enum KeymapContext {
KC_DEFAULT, // For no-arg getchm().
KC_LEVELMAP, // When in the 'X' level map
KC_TARGETING, // Only during 'x' and other targeting modes
KC_CONTEXT_COUNT // Must always be the last
};
int getchm(int (*rgetch)() = NULL); // keymaps applied (ie for prompts)
int getchm(KeymapContext context, int (*rgetch)() = NULL);
#define USERFUNCBASE -10000
static std::vector<std::string> userfunctions;
inline int userfunc_index(int key)
{
int index = (key <= USERFUNCBASE? USERFUNCBASE - key : -1);
return (index < 0 || index >= (int) userfunctions.size()? -1 : index);
}
static int userfunc_index(const keyseq &seq)
{
if (seq.empty())
return (-1);
return userfunc_index(seq.front());
}
bool is_userfunction(int key)
{
return (userfunc_index(key) != -1);
}
static bool is_userfunction(const keyseq &seq)
{
return (userfunc_index(seq) != -1);
}
const char *get_userfunction(int key)
{
int index = userfunc_index(key);
return (index == -1? NULL : userfunctions[index].c_str());
}
static const char *get_userfunction(const keyseq &seq)
{
int index = userfunc_index(seq);
return (index == -1? NULL : userfunctions[index].c_str());
}
static bool userfunc_referenced(int index, const macromap &mm)
{
for (macromap::const_iterator i = mm.begin(); i != mm.end(); i++)
{
if (userfunc_index(i->second) == index)
return (true);
}
return (false);
}
static bool userfunc_referenced(int index)
{
for (unsigned i = 0; i < sizeof(all_maps) / sizeof(*all_maps); ++i)
{
macromap *m = all_maps[i];
if (userfunc_referenced(index, *m))
return (true);
}
return (false);
}
// Expensive function to discard unused function names
static void userfunc_collectgarbage(void)
{
for (int i = userfunctions.size() - 1; i >= 0; --i)
{
if (!userfunctions.empty() && !userfunc_referenced(i))
userfunctions[i].clear();
}
}
static int userfunc_getindex(const std::string &fname)
{
if (fname.length() == 0)
return (-1);
userfunc_collectgarbage();
// Pass 1 to see if we have this string already
for (int i = 0, count = userfunctions.size(); i < count; ++i)
{
if (userfunctions[i] == fname)
return (i);
}
// Pass 2 to hunt for gaps
for (int i = 0, count = userfunctions.size(); i < count; ++i)
{
if (userfunctions[i].empty())
{
userfunctions[i] = fname;
return (i);
}
}
userfunctions.push_back(fname);
return (userfunctions.size() - 1);
}
static void buf2keyseq(const char *buff, keyseq &k)
{
// Sanity check
if (!buff)
return;
// convert c_str to keyseq
// Check for user functions
if (*buff == '=' && buff[1] == '=' && buff[2] == '=' && buff[3])
{
int index = userfunc_getindex(buff + 3);
if (index != -1)
k.push_back( USERFUNCBASE - index );
}
else
{
const int len = strlen( buff );
for (int i = 0; i < len; i++)
k.push_back( buff[i] );
}
}
case 1: // Last char is a '\'
if (c == '\\') {
state = 0;
v.push_back(c);
} else if (c == '{') {
state = 2;
num = 0;
}
// XXX Error handling
break;
case 1: // Last char is a '\'
if (c == '\\') {
state = 0;
v.push_back(c);
} else if (c == '{') {
state = 2;
num = 0;
}
// XXX Error handling
break;
case 2: // Inside \{}
if (c == '}') {
v.push_back(num);
state = 0;
} else if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
}
// XXX Error handling
case 2: // Inside \{}
if (c == '}') {
v.push_back(num);
state = 0;
} else if (c >= '0' && c <= '9') {
num = num * 10 + c - '0';
}
// XXX Error handling
keyseq result = Keymaps[tmp];
// Found a macro. Add the expansion (action) of the
// macro into the buffer.
if (result.size() > 0) {
macro_buf_add( result );
break;
}
// Didn't find a macro. Remove a key from the end
// of the sequence, and try again.
tmp.pop_back();
}
if (tmp.size() == 0) {
// Didn't find a macro. Add the first keypress of the sequence
// into the buffer, remove it from the sequence, and try again.
macro_buf_add( actions.front() );
actions.pop_front();
keyseq result = keymap[tmp];
// Found a macro. Add the expansion (action) of the
// macro into the buffer.
if (result.size() > 0) {
macro_buf_add( result );
break;
}
// Didn't find a macro. Remove a key from the end
// of the sequence, and try again.
tmp.pop_back();
}
if (tmp.size() == 0) {
// Didn't find a macro. Add the first keypress of the sequence
// into the buffer, remove it from the sequence, and try again.
macro_buf_add( actions.front() );
actions.pop_front();
} else {
// Found a macro, which has already been added above. Now just
// remove the macroed keys from the sequence.
for (unsigned int i = 0; i < tmp.size(); i++)
actions.pop_front();
}
} else {
// Found a macro, which has already been added above. Now just
// remove the macroed keys from the sequence.
for (unsigned int i = 0; i < tmp.size(); i++)
actions.pop_front();
}
}
static void write_map(std::ofstream &f, const macromap &mp, const char *key)
{
for (macromap::const_iterator i = mp.begin(); i != mp.end(); i++)
{
// Need this check, since empty values are added into the
// macro struct for all used keyboard commands.
if (i->second.size())
{
f << key << vtostr((*i).first) << std::endl
<< "A:" << vtostr((*i).second) << std::endl << std::endl;
}
}
for (macromap::iterator i = Keymaps.begin(); i != Keymaps.end(); i++)
{
// Need this check, since empty values are added into the
// macro struct for all used keyboard commands.
if ((*i).second.size() > 0)
{
f << "K:" << vtostr((*i).first) << std::endl
<< "A:" << vtostr((*i).second) << std::endl << std::endl;
}
for (int mc = KC_DEFAULT; mc < KC_CONTEXT_COUNT; ++mc) {
char keybuf[30] = "K:";
if (mc)
snprintf(keybuf, sizeof keybuf, "K%d:", mc);
write_map(f, Keymaps[mc], keybuf);
for (macromap::iterator i = Macros.begin(); i != Macros.end(); i++)
{
// Need this check, since empty values are added into the
// macro struct for all used keyboard commands.
if ((*i).second.size() > 0)
{
f << "M:" << vtostr((*i).first) << std::endl
<< "A:" << vtostr((*i).second) << std::endl << std::endl;
}
}
write_map(f, Macros, "M:");
get_input_line( buff, sizeof(buff) );
// convert c_str to keyseq
const int len = strlen( buff );
for (int i = 0; i < len; i++)
act.push_back( buff[i] );
if (Options.macro_meta_entry)
{
macro_add( mapref, key, parse_keyseq(buff) );
}
else
{
macro_add( mapref, key, buff );
}
} else if (s.substr(0, 2) == "A:") {
action = parse_keyseq(s.substr(2));
macro_add( (keymap ? Keymaps : Macros), key, action );
}
} else if (s.substr(0, 2) == "A:") {
action = parse_keyseq(s.substr(2));
macro_add( (keymap ? Keymaps[keymc] : Macros), key, action );
}
#endif
---------------------------------------------------------------------------
-- wield.lua
-- Selects extra items to wield.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/wield.lua
---------------------------------------------------------------------------
function make_hash(ls)
local h = { }
for _, i in ipairs(ls) do
h[i] = true
end
return h
end
function ch_item_wieldable(it)
-- We only need to check for unusual cases - basic matching is handled
-- by Crawl itself.
local spells = make_hash( you.spells() )
if spells["Bone Shards"]
and string.find( item.name(it, "a"), " skeleton" )
then
return true
end
if (spells["Sublimation of Blood"] or spells["Simulacrum"])
and food.ischunk(it)
then
return true
end
if spells["Sandblast"]
and string.find( item.name(it, "a"), " stones?$" )
then
return true
end
if spells["Sticks to Snakes"]
and string.find( item.name(it, "a"), " arrows?$" )
then
return true
end
return false
end
---------------------------------------------------------------------------
-- stash.lua
-- Annotates items for the stash-tracker.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/stash.lua
--
---------------------------------------------------------------------------
-- Annotate items for searches
function ch_stash_search_annotate_item(it)
local annot = ""
if item.artifact(it) then
annot = annot .. "{art} "
elseif item.branded(it) then
annot = annot .. "{ego} "
elseif item.class(it, true) == "book" then
annot = annot .. "{book} "
end
local skill = item.weap_skill(it)
if skill then
annot = annot .. "{" .. skill .. "} "
end
return annot
end
--- If you want dumps (.lst files) to be annotated, uncomment this line:
-- ch_stash_dump_annotate_item = ch_stash_search_annotate_item
-- SPOILER WARNING
--
-- This file contains spoiler information. Do not read or use this file if you
-- don't want to be spoiled. Further, the Lua code in this file may use this
-- spoily information to take actions on your behalf. If you don't want
-- automatic exploitation of spoilers, don't use this.
--
---------------------------------------------------------------------------
-- safechunk.lua:
-- Determines whether a chunk of meat is safe for your character to eat.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/safechunk.lua
--
-- This file has no directly usable functions, but is needed by gourmand.lua
-- and enables auto_eat_chunks in eat.lua.
---------------------------------------------------------------------------
function sc_safechunk(rot, race, mon)
if race == "Ghoul" then
return true
end
if rot then
if race ~= "Kobold" and race ~= "Troll" and not you.gourmand() then
return false
end
end
if sc_pois[mon] and you.res_poison() > 0 then
return true
end
if sc_hcl[mon] or sc_mut[mon] then
return false
end
-- Only contaminated and clean chunks remain, in theory. We'll accept
-- either
return true
end
---------------------------------------------------------------------------
-- runrest.lua: (requires base.lua)
-- Controls shift-running and resting stop conditions.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/runrest.lua
--
-- What it does:
--
-- * Any message in runrest_ignore_message will *not* stop your run.
-- * Poison damage of x will be ignored if you have at least y hp if you've
-- defined a runrest_ignore_poison = x:y option.
--
-- IMPORTANT: You must define runrest_ options *after* sourcing runrest.lua.
---------------------------------------------------------------------------
g_rr_ignored = { }
chk_interrupt_activity.run = function (iname, cause, extra)
if not rr_check_params() then
return false
end
if iname == 'message' then
return rr_handle_message(cause, extra)
end
if iname == 'hp_loss' then
return rr_handle_hploss(cause, extra)
end
return false
end
function rr_handle_message(cause, extra)
local ch, mess = rr_split_channel(cause)
for _, m in ipairs(g_rr_ignored) do
if m:matches(mess, ch) then
return nil
end
end
return false
end
function rr_split_channel(s)
local chi = string.find(s, ':')
local channel = -1
if chi and chi > 1 then
local chstr = string.sub(s, 1, chi - 1)
channel = crawl.msgch_num(chstr)
end
local sor = s
if chi then
s = string.sub(s, chi + 1, -1)
end
return channel, s
end
function rr_handle_hploss(hplost, source)
-- source == 1 for poisoning
if not g_rr_yhpmin or not g_rr_hplmax or source ~= 1 then
return false
end
-- If the hp lost is smaller than configured, and you have more than the
-- minimum health, ignore this poison event.
if hplost <= g_rr_hplmax and you.hp() >= g_rr_yhpmin then
return nil
end
return false
end
function rr_check_params()
if g_rrim ~= options.runrest_ignore_message then
g_rrim = options.runrest_ignore_message
rr_add_messages(nil, g_rrim)
end
if ( not g_rr_hplmax or not g_rr_yhpmin )
and options.runrest_ignore_poison
then
local opt = options.runrest_ignore_poison
local hpl, hpm
_, _, hpl, hpm = string.find(opt, "(%d+)%s*:%s*(%d+)")
if hpl and hpm then
g_rr_hplmax = tonumber(hpl)
g_rr_yhpmin = tonumber(hpm)
end
end
return true
end
function rr_add_message(s)
local channel, str = rr_split_channel(s)
table.insert( g_rr_ignored, crawl.message_filter( str, channel ) )
end
function rr_add_messages(key, value)
local segs = crawl.split(value, ',')
for _, s in ipairs(segs) do
rr_add_message(s)
end
end
chk_lua_option.runrest_ignore_message = rr_add_messages
------------------------------------------------------------------
-- kills.lua:
-- Generates fancier vanquished creatures lists.
--
-- List ideas courtesy Jukka Kuusisto and Erik Piper.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/kills.lua
--
-- Take a look at the c_kill_list function - that's where to start if you
-- want to customize things.
------------------------------------------------------------------
function kill_filter(a, condition)
local t = { }
for i, k in ipairs(a) do
if condition(k) then
table.insert(t, k)
end
end
return t
end
function show_sorted_list(list, baseindent, sortfn)
baseindent = baseindent or " "
sortfn = sortfn or
function (x, y)
return kills.exp(x) > kills.exp(y) or
(kills.exp(x) == kills.exp(y) and
kills.base_name(x) < kills.base_name(y))
end
table.sort(list, sortfn)
for i, k in ipairs(list) do
kills.rawwrite(baseindent .. " " .. kills.desc(k))
end
kills.rawwrite(baseindent .. kills.summary(list))
end
function group_kills(a, namemap, keys, selector)
local count = 0
for i, key in ipairs(keys) do
local selected = kill_filter(a,
function (k)
return selector(key, k)
end
)
if table.getn(selected) > 0 then
if count > 0 then
kills.rawwrite("")
end
count = count + 1
kills.rawwrite(" " .. namemap[key])
show_sorted_list(selected)
end
end
end
function holiness_list(a)
local holies = {
strange = "Strange Monsters",
unique = "Uniques",
holy = "Holy Monsters",
natural = "Natural Monsters",
undead = "Undead Monsters",
demonic = "Demonic Monsters",
nonliving = "Non-Living Monsters",
plant = "Plants",
}
local holysort = { "strange", "unique",
"natural", "nonliving",
"undead", "demonic", "plant" }
kills.rawwrite(" Monster Nature")
group_kills( a, holies, holysort,
function ( key, kill )
return (kills.holiness(kill) == key and not kills.isunique(kill))
or
(key == "unique" and kills.isunique(kill))
end
)
kills.rawwrite(" " .. kills.summary(a))
end
function count_list(a, ascending)
kills.rawwrite(" Ascending Order")
show_sorted_list(a,
" ",
function (x, y)
if ascending then
return kills.nkills(x) < kills.nkills(y)
or (kills.nkills(x) == kills.nkills(y) and
kills.exp(x) > kills.exp(y))
else
return kills.nkill(x) > kills.nkills(y)
or (kills.nkills(x) == kills.nkills(y) and
kills.exp(x) > kills.exp(y))
end
end
)
end
function symbol_list(a)
-- Create a list of monster characters and sort it
local allsyms = { }
for i, k in ipairs(a) do
local ksym = kills.symbol(k)
allsyms[ kills.symbol(k) ] = true
end
local sortedsyms = { }
for sym, dud in pairs(allsyms) do table.insert(sortedsyms, sym) end
table.sort(sortedsyms)
kills.rawwrite(" Monster Symbol")
for i, sym in ipairs(sortedsyms) do
if i > 1 then
kills.rawwrite("")
end
local symlist = kill_filter(a,
function (k)
return kills.symbol(k) == sym
end
)
kills.rawwrite(" All '" .. sym .. "'")
show_sorted_list(symlist)
end
kills.rawwrite(" " .. kills.summary(a))
end
function classic_list(title, a, suffix)
if table.getn(a) == 0 then return end
kills.rawwrite(title)
for i, k in ipairs(a) do
kills.rawwrite(" " .. kills.desc(k))
end
kills.rawwrite(" " .. kills.summary(a))
if suffix then
kills.rawwrite(suffix)
end
end
function separator()
kills.rawwrite("----------------------------------------\n")
end
function newline()
kills.rawwrite("")
end
-----------------------------------------------------------------------------
-- This is the function that Crawl calls when it wants to dump the kill list
-- The parameter "a" is the list (Lua table) of kills, initially sorted in
-- descending order of experience. Kill entries must be inspected using
-- kills.foo(ke).
--
-- NOTE: Comment out the kill lists that you don't like!
--
-- As of 20060224, the kill list is divided into three:
-- * Direct player kills
-- * Monsters killed by friendlies
-- * Monsters killed by other things (traps, etc.)
--
-- For each category, the game calls c_kill_list, with a table of killed
-- monsters, and the killer (who). The killer will be nil for direct player
-- kills, and a string ("collateral kills", "others") otherwise.
--
-- c_kill_list will not be called for a category if the category contains no
-- kills.
--
-- After all categories, c_kill_list will be called with no arguments to
-- indicate the end of the kill listing.
function c_kill_list(a, who, needsep)
-- If there aren't any kills yet, bail out
if not a or table.getn(a) == 0 then
if not a and who and who ~= "" then
-- This is the grand total
separator()
kills.rawwrite(who)
end
return
end
if needsep then
separator()
end
local title = "Vanquished Creatures"
if who then
title = title .. " (" .. who .. ")"
end
kills.rawwrite(title)
-- Group kills by monster symbol
symbol_list(a)
newline()
-- Group by holiness
holiness_list(a)
newline()
-- Sort by kill count (ascending).
count_list(a, true)
newline()
-- Classic list without zombies and skeletons
-- Commented-out - this is a boring list.
--[[
-- First re-sort into descending order of exp, since
-- count_list has re-sorted the array.
table.sort(a, function (x, y)
return kills.exp(x) > kills.exp(y)
end);
-- Filter out zombies and skeletons and display the rest
classic_list(
" Kills minus zombies and skeletons",
kill_filter(a,
function (k)
return kills.modifier(k) ~= "zombie" and
kills.modifier(k) ~= "skeleton"
end
))
separator()
--]]
-- Show only monsters with 3 digit kill count
classic_list(
" The 3-digit Club",
kill_filter(a,
function (k)
return kills.nkills(k) > 99
end
),
"")
end
-- SPOILER WARNING
--
-- This file contains spoiler information. Do not read or use this file if you
-- don't want to be spoiled. Further, the Lua code in this file may use this
-- spoily information to take actions on your behalf. If you don't want
-- automatic exploitation of spoilers, don't use this.
--
---------------------------------------------------------------------------
-- gourmand.lua: (requires eat.lua, chnkdata.lua, and safechunk.lua)
--
-- Eats all available chunks on current square and inventory, swapping to an
-- identified amulet of the gourmand if necessary, with no prompts. Note that
-- it relies on spoiler information to identify chunks it can eat without
-- prompting the user.
--
-- This is a Lua macro, so the action will be interrupted by the hp/stat loss,
-- or monsters.
--
-- To use this, add these line to your init.txt:
-- lua_file = lua/gourmand.lua
--
-- And macro the "eat_gourmand" function to a key.
---------------------------------------------------------------------------
-- Macroable
function eat_gourmand()
local race = you.race()
local all = { }
for _, it in ipairs(you.floor_items()) do table.insert(all, it) end
for _, it in ipairs(item.inventory()) do table.insert(all, it) end
local chunks = { }
local needgourmand = false
local oneedible = false
for _, itym in ipairs(all) do
if food.ischunk(itym) then
table.insert(chunks, itym)
end
end
for _, itym in ipairs(chunks) do
local rot = food.rotting(itym)
local mon = chunkmon(itym)
if food.can_eat(itym) and sc_safechunk and
sc_safechunk(rot, race, mon) then
oneedible = true
end
-- If we can't eat it now, but we could eat it if hungry, a gourmand
-- switch would be nice.
if not food.can_eat(itym) and food.can_eat(itym, false) then
needgourmand = true
end
if sc_safechunk and not sc_safechunk(rot, race, mon)
and sc_safechunk(false, race, mon)
then
needgourmand = true
end
end
if table.getn(chunks) == 0 then
crawl.mpr("No chunks to eat.")
return
end
local amuremoved
if needgourmand and not oneedible then
amuremoved = switch_amulet("gourmand")
end
for _, ch in ipairs(chunks) do
if food.can_eat(ch) and is_chunk_safe(ch) then
while item.quantity(ch) > 0 do
if food.eat(ch) then
coroutine.yield(true)
else
break
end
end
end
end
if amuremoved then
switch_amulet(amuremoved)
end
end
function chunkmon(chunk)
local cname = item.name(chunk)
local mon
_, _, mon = string.find(cname, "chunk of (.+) flesh")
return mon
end
function switch_amulet(amu)
local towear
if type(amu) == "string" then
for _, it in ipairs(item.inventory()) do
if item.class(it, true) == "jewelry"
and item.subtype(it) == amu
and not item.cursed(it) then
towear = item.slot(it)
break
end
end
if not towear then
crawl.mpr("Can't find suitable amulet: " .. amu)
coroutine.yield(false)
end
else
towear = amu
end
local curramu = item.equipped_at("amulet")
if curramu and item.cursed(curramu) then
crawl.mpr("Can't swap out cursed amulet!")
coroutine.yield(false)
end
local wearitem = item.inslot(towear)
if curramu then
if not item.remove(curramu) then
coroutine.yield(false)
end
-- Yield, so that a turn can pass and we can take another action.
coroutine.yield(true)
end
if not item.puton(wearitem) then
coroutine.yield(false)
end
coroutine.yield(true)
return curramu and item.slot(curramu)
end
function c_interrupt_macro(interrupt_name)
return interrupt_name == "hp_loss" or interrupt_name == "stat" or
interrupt_name == "monster" or interrupt_name == "force"
end
------------------------------------------------------------------------
-- gearset.lua: (requires base.lua)
-- Allows for quick switching between two sets of equipment.
--
-- IMPORTANT
-- This Lua script remembers only the *inventory slots* of the gear you're
-- wearing (it could remember item names, but they're prone to change based on
-- identification, enchantment and curse status). If you swap around items in
-- your inventory, the script may attempt to wear odd items (this will not kill
-- the game, but it can be disconcerting if you're not expecting it).
--
-- To use this, source this file in your init.txt:
-- lua_file = lua/gearset.lua
--
-- Then start Crawl and create two macros:
-- 1. Any macro input key (say F2), set macro action to "===rememberkit"
-- 2. Any macro input key (say F3), set macro action to "===swapkit"
-- It helps to save your macros at this point. :-)
--
-- You can now hit F2 to remember the equipment you're wearing. Once you've
-- defined two sets of equipment, hit F3 to swap between them.
--
-- You can also just define one set of equipment; in this case, every time
-- you hit F3 (swapkit), the macro makes sure you're wearing all the items in
-- that set (and only the items in that set). This is handy for transmuters who
-- need to kit up after a transform ends.
------------------------------------------------------------------------
function scan_kit()
local kit = { }
for i = 9, 0, -1 do
local it = item.equipped_at(i)
if it then
table.insert(kit, item.slot(it))
end
end
return kit
end
function kit_items(kit)
local items = { }
local inv = item.inventory()
for _, slot in ipairs(kit) do
for _, it in ipairs(inv) do
if slot == item.slot(it) then
table.insert(items, it)
end
end
end
return items
end
function getkey(vkeys)
local key
while true do
key = crawl.getch()
if key == 27 then return "escape" end
if key > 31 and key < 127 then
local c = string.lower(string.char(key))
if string.find(vkeys, c) then
return c
end
end
end
end
-- Macroable
function rememberkit()
local kit = scan_kit()
crawl.mpr("Is this (T)ravel or (B)attle kit? ", "prompt")
local answer = getkey("tb")
crawl.mesclr()
if answer == "escape" then
return false
end
if answer == 't' then
g_kit_travel = kit
else
g_kit_battle = kit
end
return false
end
function write_array(f, arr, aname)
file.write(f, aname .. " = { ")
for i, v in ipairs(arr) do
file.write(f, v .. ", ")
end
file.write(f, "}\n")
end
function gearset_save(file)
if g_kit_travel then
write_array(file, g_kit_travel, "g_kit_travel")
end
if g_kit_battle then
write_array(file, g_kit_battle, "g_kit_battle")
end
end
function matchkit(kit1, kit2)
local matches = 0
if not kit2 then
-- Return a positive match for an empty gearset so that swapkit
-- switches to the *other* gearset. :-)
return 500
end
for _, v1 in ipairs(kit1) do
for _, v2 in ipairs(kit2) do
if v1 == v2 then
matches = matches + 1
end
end
end
return matches
end
function wear(it)
local _, eqt = item.equip_type(it)
if eqt == "Weapon" then
return item.wield(it)
elseif eqt == "Amulet" or eqt == "Ring" then
return item.puton(it)
elseif eqt ~= "" then
return item.wear(it)
else
return false
end
end
function wearkit(kit)
-- Remove all items not in the kit, then wear all items in the kit
local currkit = scan_kit()
local toremove = { }
local noop = true
for _, v in ipairs(currkit) do
local found = false
for _, v1 in ipairs(kit) do
if v == v1 then
found = true
break
end
end
if not found then
table.insert(toremove, v)
end
end
local remitems = kit_items(toremove)
for _, it in ipairs(remitems) do
noop = false
if not item.remove(it) then
coroutine.yield(false)
end
coroutine.yield(true)
end
local kitems = kit_items(kit)
for _, it in ipairs(kitems) do
if not item.worn(it) then
noop = false
if not wear(it) then
coroutine.yield(false)
end
coroutine.yield(true)
end
end
if noop then
crawl.mpr("Nothing to do.")
end
end
-- Macroable
function swapkit()
if not g_kit_battle and not g_kit_travel then
crawl.mpr("You need to define a gear set first")
return false
end
local kit = scan_kit()
if matchkit(kit, g_kit_travel) < matchkit(kit, g_kit_battle) then
crawl.mpr("Switching to travel kit")
wearkit(g_kit_travel)
else
crawl.mpr("Switching to battle kit")
wearkit(g_kit_battle)
end
end
function swapkit_interrupt_macro(interrupt_name)
return interrupt_name == "hp_loss" or interrupt_name == "stat" or
interrupt_name == "monster" or interrupt_name == "force"
end
-- Add a macro interrupt hook so that we don't get stopped by any old interrupt
chk_interrupt_macro.swapkit = swapkit_interrupt_macro
-- Add ourselves in the Lua save chain
table.insert(chk_lua_save, gearset_save)
---------------------------------------------------------------------------
-- eat.lua:
-- Prompts to eat chunks from inventory.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/eat.lua
--
-- See c_eat in this file if you want to tweak eating behaviour further.
---------------------------------------------------------------------------
function prompt_eat(i)
local iname = item.name(i, "a")
if item.quantity(i) > 1 then
iname = "one of " .. iname
end
crawl.mpr("Eat " .. iname .. "?", "prompt")
local res
res = crawl.getch()
if res == 27 then
res = "escape"
elseif res < 32 or res > 127 then
res = ""
else
res = string.lower(string.format("%c", res))
end
return res
end
function is_chunk_safe(chunk)
local rot = food.rotting(chunk)
local race = you.race()
-- Check if the user has sourced safechunk.lua and chnkdata.lua
if not (sc_cont and sc_pois and sc_hcl and sc_mut and sc_safechunk) then
return false
end
local cname = item.name(chunk)
local mon
_, _, mon = string.find(cname, "chunk of (.+) flesh")
return sc_safechunk(rot, race, mon)
end
-- Called by Crawl. Note that once Crawl sees a c_eat function, it bypasses the
-- built-in (e)at command altogether.
--
function c_eat(floor, inv)
-- To enable auto_eat_chunks, you also need to source chnkdata.lua and
-- safechunk.lua. WARNING: These files contain spoily information.
local auto_eat_chunks = options.auto_eat_chunks == "yes" or
options.auto_eat_chunks == "true"
if auto_eat_chunks then
local all = { }
for _, it in ipairs(floor) do table.insert(all, it) end
for _, it in ipairs(inv) do table.insert(all, it) end
for _, it in ipairs(all) do
if food.ischunk(it) and food.can_eat(it) and is_chunk_safe(it) then
local iname = item.name(it, "a")
if item.quantity(it) > 1 then
iname = "one of " .. iname
end
crawl.mpr("Eating " .. iname)
food.eat(it)
return
end
end
end
-- Prompt to eat chunks off the floor. Returns true if the player chose
-- to eat a chunk.
if food.prompt_floor() then
return
end
for i, it in ipairs(inv) do
-- If we have chunks in inventory that the player can eat, prompt.
if food.ischunk(it) and food.can_eat(it) then
local answer = prompt_eat(it)
if answer == "q" then
break
end
if answer == "escape" then
return
end
if answer == "y" then
food.eat(it)
return
end
end
end
-- Allow the player to choose a snack from inventory
food.prompt_inventory()
end
-- SPOILER WARNING
--
-- This file contains spoiler information. Do not read or use this file if you
-- don't want to be spoiled. Further, the Lua code in this file may use this
-- spoily information to take actions on your behalf. If you don't want
-- automatic exploitation of spoilers, don't use this.
--
---------------------------------------------------------------------------
-- chnkdata.lua:
-- Raw data on chunks of meat, auto-extracted from mon-data.h.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/chnkdata.lua
--
-- This file has no directly usable functions, but is needed by gourmand.lua
-- and enables auto_eat_chunks in eat.lua.
---------------------------------------------------------------------------
sc_cont = {"program bug","ettin","goblin","jackal","manticore","orc","ugly thing","two-headed ogre","hobgoblin","ogre","troll","orc warrior","orc wizard","orc knight","minotaur","beast","iron devil","orc sorcerer","","","hell knight","necromancer","wizard","orc priest","orc high priest","human","gnoll","earth elemental","fire elemental","air elemental","Ice Fiend","Shadow Fiend","spectral warrior","pulsating lump","rock troll","stone giant","flayed ghost","insubstantial wisp","vapour","ogre-mage","dancing weapon","elf","war dog","grey rat","giant mosquito","fire giant","frost giant","firedrake","deep troll","giant blowfly","swamp dragon","swamp drake","hill giant","giant cockroach","white imp","lemure","ufetubus","manes","midge","neqoxec","hellwing","smoke demon","ynoxinul","Executioner","Blue Death","Balrug","Cacodemon","demonic crawler","sun demon","shadow imp","shadow wraith","Mnoleg","Lom Lobon","Cerebov","Gloorx Vloq","orc warlord","deep elf soldier","deep elf fighter","deep elf knight","deep elf mage","deep elf summoner","deep elf conjurer","deep elf priest","deep elf high priest","deep elf demonologist","deep elf annihilator","deep elf sorcerer","deep elf death mage","Terence","Jessica","Ijyb","Sigmund","Blork the orc","Edmund","Psyche","Erolcha","Donald","Urug","Michael","Joseph","Snorg","Erica","Josephine","Harold","Norbert","Jozef","Agnes","Maud","Louise","Francis","Frances","Rupert","Wayne","Duane","Xtahua","Norris","Adolf","Margery","Boris","Geryon","Dispater","Asmodeus","Antaeus","Ereshkigal","vault guard","Killer Klown","ball lightning","orb of fire","boggart","quicksilver dragon","iron dragon","skeletal warrior","","&","warg","","","komodo dragon"}
sc_pois = {"killer bee","killer bee larva","quasit","scorpion","yellow wasp","giant beetle","kobold","queen bee","kobold demonologist","big kobold","wolf spider","brain worm","boulder beetle","giant mite","hydra","mottled dragon","brown snake","death yak","bumblebee","redback","spiny worm","golden dragon","elephant slug","green rat","orange rat","black snake","giant centipede","iron troll","naga","yellow snake","red wasp","soldier ant","queen ant","ant larva","spiny frog","orange demon","Green Death","giant amoeba","giant slug","giant snail","boring beetle","naga mage","naga warrior","brown ooze","azure jelly","death ooze","acid blob","ooze","shining eye","greater naga","eye of devastation","gila monster"}
sc_hcl = {"necrophage","ghoul"}
sc_mut = {"guardian naga","eye of draining","giant orange brain","great orb of eyes","glowing shapeshifter","shapeshifter","very ugly thing"}
function sc_to_hash(list)
local hash = { }
for _, i in ipairs(list) do
hash[i] = true
end
return hash
end
sc_cont = sc_to_hash( sc_cont )
sc_pois = sc_to_hash( sc_pois )
sc_hcl = sc_to_hash( sc_hcl )
sc_mut = sc_to_hash( sc_mut )
---------------------------------------------------------------------------
-- base.lua:
-- Base Lua definitions that other Lua scripts rely on.
-- NOTE: Other Lua scripts may demonstrate buggy behaviour if you do
-- not source this file. If you're using no Lua scripts at all, you
-- needn't source base.lua.
--
-- To use this, add this line to your init.txt:
-- lua_file = lua/base.lua
---------------------------------------------------------------------------
-- Lua global data
chk_interrupt_macro = { }
chk_interrupt_activity = { }
chk_lua_option = { }
-- Push into this table, rather than indexing into it.
chk_lua_save = { }
function c_save(file)
if not chk_lua_save then
return
end
for i, fn in ipairs(chk_lua_save) do
fn(file)
end
end
-- This function returns true to tell Crawl not to process the option further
function c_process_lua_option(key, val)
if chk_lua_option and chk_lua_option[key] then
return chk_lua_option[key](key, val)
end
return false
end
function c_interrupt_macro(iname, cause, extra)
-- If some joker undefined the table, stop all macros
if not chk_interrupt_macro then
return true
end
-- Maybe we don't know what macro is running? Kill it to be safe
-- We also kill macros that don't add an entry to chk_interrupt_macro.
if not c_macro_name or not chk_interrupt_macro[c_macro_name] then
return true
end
return chk_interrupt_macro[c_macro_name](iname, cause, extra)
end
-- Notice that c_interrupt_activity defaults to *false* whereas
-- c_interrupt_macro defaults to *true*. This is because "false" really just
-- means "go ahead and use the default logic to kill this activity" here,
-- whereas false is interpreted as "no, don't stop this macro" for
-- c_interrupt_macro.
--
-- If c_interrupt_activity, or one of the individual hooks wants to ensure that
-- the activity continues, it must return *nil* (make sure you know what you're
-- doing when you return nil!).
function c_interrupt_activity(aname, iname, cause, extra)
-- If some joker undefined the table, bail out
if not chk_interrupt_activity then
return false
end
-- No activity name? Bail!
if not aname or not chk_interrupt_activity[aname] then
return false
end
return chk_interrupt_activity[aname](iname, cause, extra)
end
// JWM, 06/12/2004: Code page setting, as XP does not use ANSI 437 by
// default.
InputCP = GetConsoleCP();
OutputCP = GetConsoleOutputCP();
// DS: Don't kill Crawl if we can't set the codepage. Windows 95/98/ME don't
// have support for setting the input and output codepage. I'm also not
// convinced we need to set the input codepage at all.
if (InputCP != PREFERRED_CODEPAGE)
SetConsoleCP(PREFERRED_CODEPAGE);
if (OutputCP != PREFERRED_CODEPAGE)
SetConsoleOutputCP(PREFERRED_CODEPAGE);
// JWM, 06/12/2004: Code page stuff. If it was the preferred code page, it
// doesn't need restoring. Shouldn't be an error and too bad if there is.
if (InputCP && InputCP != PREFERRED_CODEPAGE)
SetConsoleCP(InputCP);
if (OutputCP && OutputCP != PREFERRED_CODEPAGE)
SetConsoleOutputCP(OutputCP);
{ VK_END, VK_DOWN, VK_NEXT, VK_LEFT, VK_CLEAR, VK_RIGHT, VK_HOME, VK_UP, VK_PRIOR },
{ 'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u' },
{ '1', '2', '3', '4', '5', '6', '7', '8', '9' },
{ 2, 10, 14, 8, 0, 12, 25, 11, 21 },
{ VK_END, VK_DOWN, VK_NEXT, VK_LEFT, VK_CLEAR, VK_RIGHT, VK_HOME, VK_UP, VK_PRIOR, VK_INSERT },
{ CK_END, CK_DOWN, CK_PGDN, CK_LEFT, CK_CLEAR, CK_RIGHT, CK_HOME, CK_UP, CK_PGUP , CK_INSERT },
{ CK_SHIFT_END, CK_SHIFT_DOWN, CK_SHIFT_PGDN, CK_SHIFT_LEFT, CK_SHIFT_CLEAR, CK_SHIFT_RIGHT, CK_SHIFT_HOME, CK_SHIFT_UP, CK_SHIFT_PGUP, CK_SHIFT_INSERT },
{ CK_CTRL_END, CK_CTRL_DOWN, CK_CTRL_PGDN, CK_CTRL_LEFT, CK_CTRL_CLEAR, CK_CTRL_RIGHT, CK_CTRL_HOME, CK_CTRL_UP, CK_CTRL_PGUP, CK_CTRL_INSERT },
static int ck_tr[] = {
'k', 'j', 'h', 'l', '0', 'y', 'b', '.', 'u', 'n',
// 'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u' ,
'8', '2', '4', '6', '0', '7', '1', '5', '9', '3',
// '1', '2', '3', '4', '5', '6', '7', '8', '9' ,
11, 10, 8, 12, '0', 25, 2, 0, 21, 14
// 2, 10, 14, 8, 0, 12, 25, 11, 21 ,
};
int key_to_command( int keyin ) {
if (keyin >= CK_UP && keyin <= CK_CTRL_PGDN)
return ck_tr[ keyin - CK_UP ];
if (keyin == CK_DELETE)
return '.';
return keyin;
}
if (mkey == 9)
return (int)c;
if (mkey == VKEY_MAPPINGS) {
if (c)
return c;
// ds -- Icky hacks to allow keymaps with funky keys.
if (ctrlDown)
VirtCode |= 512;
if (shftDown)
VirtCode |= 1024;
if (altDown)
VirtCode |= 2048;
// ds -- Cheat and returns 256 + VK if the char is zero. This allows us
// to use the VK for macros and is on par for evil with the rest of
// this function anyway.
return VirtCode | 256;
}
// do nothing. We could use PeekConsoleInput, I suppose..
return 0;
INPUT_RECORD ir[10];
DWORD read_count = 0;
PeekConsoleInput(inbuf, ir, sizeof ir / sizeof(ir[0]), &read_count);
if (read_count > 0) {
for (unsigned i = 0; i < read_count; ++i)
if (ir[i].EventType == KEY_EVENT) {
KEY_EVENT_RECORD *kr;
kr = &(ir[i].Event.KeyEvent);
if (kr->bKeyDown)
return 1;
}
}
return 0;
// getch() that returns a consistent set of values for all platforms.
int c_getch();
void play_sound(const char *file);
// Pattern matching
void *compile_pattern(const char *pattern, bool ignore_case = false);
void free_compiled_pattern(void *cp);
bool pattern_match(void *compiled_pattern, const char *text, int length);
class input_history;
// Returns true if user pressed Enter, false if user hit Escape.
bool cancelable_get_line( char *buf, int len, int wrapcol = 80,
input_history *mh = NULL );
std::string & trim_string( std::string &str );
std::vector<std::string> split_string(const char *sep, std::string s);
// Keys that getch() must return for keys Crawl is interested in.
enum KEYS
{
CK_ENTER = '\r',
CK_BKSP = 8,
CK_ESCAPE = '\x1b',
// 128 is off-limits because it's the code that's used when running
CK_DELETE = 129,
// This sequence of enums should not be rearranged.
CK_UP,
CK_DOWN,
CK_LEFT,
CK_RIGHT,
CK_INSERT,
CK_HOME,
CK_END,
CK_CLEAR,
CK_PGUP,
CK_PGDN,
CK_SHIFT_UP,
CK_SHIFT_DOWN,
CK_SHIFT_LEFT,
CK_SHIFT_RIGHT,
CK_SHIFT_INSERT,
CK_SHIFT_HOME,
CK_SHIFT_END,
CK_SHIFT_CLEAR,
CK_SHIFT_PGUP,
CK_SHIFT_PGDN,
CK_CTRL_UP,
CK_CTRL_DOWN,
CK_CTRL_LEFT,
CK_CTRL_RIGHT,
CK_CTRL_INSERT,
CK_CTRL_HOME,
CK_CTRL_END,
CK_CTRL_CLEAR,
CK_CTRL_PGUP,
CK_CTRL_PGDN,
};
class base_pattern
{
public:
virtual ~base_pattern() { };
virtual bool valid() const = 0;
virtual bool matches(const std::string &s) const = 0;
};
class text_pattern : public base_pattern
{
public:
text_pattern(const std::string &s, bool icase = false)
: pattern(s), compiled_pattern(NULL),
isvalid(true), ignore_case(icase)
{
}
text_pattern()
: pattern(), compiled_pattern(NULL),
isvalid(false), ignore_case(false)
{
}
text_pattern(const text_pattern &tp)
: pattern(tp.pattern),
compiled_pattern(NULL),
isvalid(tp.isvalid),
ignore_case(tp.ignore_case)
{
}
~text_pattern()
{
if (compiled_pattern)
free_compiled_pattern(compiled_pattern);
}
const text_pattern &operator= (const text_pattern &tp)
{
if (this == &tp)
return tp;
if (compiled_pattern)
free_compiled_pattern(compiled_pattern);
pattern = tp.pattern;
compiled_pattern = NULL;
isvalid = tp.isvalid;
ignore_case = tp.ignore_case;
return *this;
}
const text_pattern &operator= (const std::string &spattern)
{
if (pattern == spattern)
return *this;
if (compiled_pattern)
free_compiled_pattern(compiled_pattern);
pattern = spattern;
compiled_pattern = NULL;
isvalid = true;
// We don't change ignore_case
return *this;
}
bool compile() const
{
// This function is const because compiled_pattern is not really part of
// the state of the object.
void *&cp = const_cast<text_pattern*>( this )->compiled_pattern;
return !empty()?
!!(cp = compile_pattern(pattern.c_str(), ignore_case))
: false;
}
bool empty() const
{
return !pattern.length();
}
bool valid() const
{
return isvalid &&
(compiled_pattern ||
(const_cast<text_pattern*>( this )->isvalid = compile()));
}
bool matches(const char *s, int length) const
{
return valid() && pattern_match(compiled_pattern, s, length);
}
bool matches(const char *s) const
{
return matches(std::string(s));
}
bool matches(const std::string &s) const
{
return matches(s.c_str(), s.length());
}
const std::string &tostring() const
{
return pattern;
}
private:
std::string pattern;
void *compiled_pattern;
bool isvalid;
bool ignore_case;
};
#ifdef WIN32CONSOLE
#include <windows.h>
#ifdef WINMM_PLAY_SOUNDS
#include <mmsystem.h>
#endif
#endif
#ifdef REGEX_PCRE
// Statically link pcre on Windows
#ifdef WIN32CONSOLE
#define PCRE_STATIC
#endif
#include <pcre.h>
#endif
#ifdef REGEX_POSIX
// Do we still need to include sys/types.h?
#include <sys/types.h>
#include <regex.h>
#endif
// Should return true if the filename contains nothing that
// the shell can do damage with.
bool shell_safe(const char *file)
{
int match = strcspn(file, "`$*?|><");
return !(match >= 0 && file[match]);
}
void play_sound( const char *file )
{
#if defined(WINMM_PLAY_SOUNDS)
// Check whether file exists, is readable, etc.?
if (file && *file)
sndPlaySound(file, SND_ASYNC | SND_NODEFAULT);
#elif defined(SOUND_PLAY_COMMAND)
char command[255];
command[0] = '\0';
if (file && *file && (strlen(file) + strlen(SOUND_PLAY_COMMAND) < 255)
&& shell_safe(file))
{
snprintf(command, sizeof command, SOUND_PLAY_COMMAND, file);
system(command);
}
#endif
}
break;
}
}
#ifdef DOS
static int getch_ck() {
int c = getch();
if (!c) {
switch (c = getch()) {
case 'O': return CK_END;
case 'P': return CK_DOWN;
case 'I': return CK_PGUP;
case 'H': return CK_UP;
case 'G': return CK_HOME;
case 'K': return CK_LEFT;
case 'Q': return CK_PGDN;
case 'M': return CK_RIGHT;
case 119: return CK_CTRL_HOME;
case 141: return CK_CTRL_UP;
case 132: return CK_CTRL_PGUP;
case 116: return CK_CTRL_RIGHT;
case 118: return CK_CTRL_PGDN;
case 145: return CK_CTRL_DOWN;
case 117: return CK_CTRL_END;
case 115: return CK_CTRL_LEFT;
case 'S': return CK_DELETE;
}
}
return c;
}
#endif
// Hacky wrapper around getch() that returns CK_ codes for keys
// we want to use in cancelable_get_line() and menus.
int c_getch() {
#if defined(DOS) || defined(LINUX) || defined(WIN32CONSOLE)
return getch_ck();
#else
return getch();
#endif
}
// cprintf that knows how to wrap down lines (primitive, but what the heck)
int wrapcprintf( int wrapcol, const char *s, ... )
{
char buf[1000]; // Hard max
va_list args;
va_start(args, s);
// XXX: If snprintf isn't available, vsnprintf probably isn't, either.
int len = vsnprintf(buf, sizeof buf, s, args), olen = len;
va_end(args);
char *run = buf;
while (len > 0)
{
int x = wherex(), y = wherey();
if (x > wrapcol) // Somebody messed up!
return 0;
int avail = wrapcol - x + 1;
int c = 0;
if (len > avail) {
c = run[avail];
run[avail] = 0;
}
cprintf("%s", run);
if (len > avail)
run[avail] = c;
if ((len -= avail) > 0)
gotoxy(1, y + 1);
run += avail;
}
return (olen);
}
#define WX(x) ( ((x) - 1) % maxcol + 1 )
#define WY(x,y) ( (y) + ((x) - 1) / maxcol )
#define WC(x,y) WX(x), WY(x,y)
#define GOTOXY(x,y) gotoxy( WC(x,y) )
bool cancelable_get_line( char *buf, int len, int maxcol,
input_history *mh )
{
if (len <= 0) return false;
buf[0] = 0;
char *cur = buf;
int start = wherex(), line = wherey();
int length = 0, pos = 0;
if (mh)
mh->go_end();
for ( ; ; ) {
int ch = c_getch();
switch (ch) {
case CK_ESCAPE:
return false;
case CK_UP:
case CK_DOWN:
{
if (!mh)
break;
const std::string *text = ch == CK_UP? mh->prev() : mh->next();
if (text)
{
int olen = length;
length = text->length();
if (length >= len)
length = len - 1;
memcpy(buf, text->c_str(), length);
buf[length] = 0;
GOTOXY(start, line);
int clear = length < olen? olen - length : 0;
wrapcprintf(maxcol, "%s%*s", buf, clear, "");
pos = length;
cur = buf + pos;
GOTOXY(start + pos, line);
}
break;
}
case CK_ENTER:
buf[length] = 0;
if (mh && length)
mh->new_input(buf);
return true;
case CONTROL('K'):
{
// Kill to end of line
int erase = length - pos;
if (erase)
{
length = pos;
buf[length] = 0;
wrapcprintf( maxcol, "%*s", erase, "" );
GOTOXY(start + pos, line);
}
break;
}
case CK_DELETE:
if (pos < length) {
char *c = cur;
while (c - buf < length) {
*c = c[1];
c++;
}
--length;
GOTOXY( start + pos, line );
buf[length] = 0;
wrapcprintf( maxcol, "%s ", cur );
GOTOXY( start + pos, line );
}
break;
case CK_BKSP:
if (pos) {
--cur;
char *c = cur;
while (*c) {
*c = c[1];
c++;
}
--pos;
--length;
GOTOXY( start + pos, line );
buf[length] = 0;
wrapcprintf( maxcol, "%s ", cur );
GOTOXY( start + pos, line );
}
break;
case CK_LEFT:
if (pos) {
--pos;
cur = buf + pos;
GOTOXY( start + pos, line );
}
case CK_RIGHT:
if (pos < length) {
++pos;
cur = buf + pos;
GOTOXY( start + pos, line );
}
break;
case CK_HOME:
case CONTROL('A'):
pos = 0;
cur = buf + pos;
GOTOXY( start + pos, line );
break;
case CK_END:
case CONTROL('E'):
pos = length;
cur = buf + pos;
GOTOXY( start + pos, line );
break;
default:
if (isprint(ch) && length < len - 1) {
if (pos < length) {
char *c = buf + length - 1;
while (c >= cur) {
c[1] = *c;
c--;
}
}
*cur++ = (char) ch;
++length;
++pos;
putch(ch);
if (pos < length) {
buf[length] = 0;
wrapcprintf( maxcol, "%s", cur );
}
GOTOXY(start + pos, line);
}
break;
}
#undef GOTOXY
#undef WC
#undef WX
#undef WY
// also used with macros
std::string & trim_string( std::string &str )
{
// OK, this is really annoying. Borland C++ seems to define
// basic_string::erase to take iterators, and basic_string::remove
// to take size_t or integer. This is ass-backwards compared to
// nearly all other C++ compilers. Crap. (GDL)
//
// Borland 5.5 does this correctly now... leaving the old code
// around for now in case anyone needs it. -- bwr
// #ifdef __BCPLUSPLUS__
// str.remove( 0, str.find_first_not_of( " \t\n\r" ) );
// str.remove( str.find_last_not_of( " \t\n\r" ) + 1 );
// #else
str.erase( 0, str.find_first_not_of( " \t\n\r" ) );
str.erase( str.find_last_not_of( " \t\n\r" ) + 1 );
// #endif
return (str);
}
std::vector<std::string> split_string(const char *sep, std::string s)
{
std::vector<std::string> segments;
std::string::size_type pos;
while ((pos = s.find(sep, 0)) != std::string::npos) {
if (pos > 0)
segments.push_back(s.substr(0, pos));
s.erase(0, pos + 1);
}
if (s.length() > 0)
segments.push_back(s);
for (int i = 0, count = segments.size(); i < count; ++i)
trim_string(segments[i]);
return segments;
}
///////////////////////////////////////////////////////////////////////
// Pattern matching
inline int pm_lower(int ch, bool icase) {
return icase? tolower(ch) : ch;
}
// Determines whether the pattern specified by 'pattern' matches the given
// text. A pattern is a simple glob, with the traditional * and ? wildcards.
static bool glob_match( const char *pattern, const char *text, bool icase )
{
char p, t;
bool special;
for (;;)
{
p = pm_lower(*pattern++, icase);
t = pm_lower(*text++, icase);
special = true;
if (!p) return t == 0;
if (p == '\\' && *pattern)
{
p = pm_lower(*pattern++, icase);
special = false;
}
if (p == '*' && special)
// Try to match exactly at the current text position...
return !*pattern || glob_match(pattern, text - 1, icase)? true :
// Or skip one character in the text and try the wildcard
// match again. If this is the end of the text, the match has
// failed.
t? glob_match(pattern - 1, text, icase) : false;
else if (!t || (p != t && (p != '?' || !special)))
return false;
}
}
#if defined(REGEX_PCRE)
////////////////////////////////////////////////////////////////////
// Perl Compatible Regular Expressions
void *compile_pattern(const char *pattern, bool icase) {
const char *error;
int erroffset;
int flags = icase? PCRE_CASELESS : 0;
return pcre_compile(pattern,
flags,
&error,
&erroffset,
NULL);
}
void free_compiled_pattern(void *cp) {
if (cp)
pcre_free(cp);
}
bool pattern_match(void *compiled_pattern, const char *text, int length)
{
int ovector[42];
int pcre_rc = pcre_exec(static_cast<pcre *>(compiled_pattern),
NULL,
text,
length,
0,
0,
ovector,
sizeof(ovector) / sizeof(*ovector));
return (pcre_rc >= 0);
}
////////////////////////////////////////////////////////////////////
#elif defined(REGEX_POSIX)
////////////////////////////////////////////////////////////////////
// POSIX regular expressions
void *compile_pattern(const char *pattern, bool icase) {
regex_t *re = new regex_t;
if (!re)
return NULL;
int flags = REG_EXTENDED | REG_NOSUB;
if (icase)
flags |= REG_ICASE;
int rc = regcomp(re, pattern, flags);
// Nonzero return code == failure
if (rc) {
delete re;
return NULL;
}
return re;
}
void free_compiled_pattern(void *cp) {
if (cp) {
regex_t *re = static_cast<regex_t *>( cp );
regfree(re);
delete re;
}
}
bool pattern_match(void *compiled_pattern, const char *text, int length)
{
regex_t *re = static_cast<regex_t *>( compiled_pattern );
return !regexec(re, text, 0, NULL, 0);
}
////////////////////////////////////////////////////////////////////
#else
////////////////////////////////////////////////////////////////////
// Basic glob
struct glob_info
{
std::string s;
bool ignore_case;
};
void *compile_pattern(const char *pattern, bool icase)
{
// If we're using simple globs, we need to box the pattern with '*'
std::string s = std::string("*") + pattern + "*";
glob_info *gi = new glob_info;
if (gi) {
gi->s = s;
gi->ignore_case = icase;
}
return gi;
}
void free_compiled_pattern(void *compiled_pattern)
{
delete static_cast<glob_info *>( compiled_pattern );
}
bool pattern_match(void *compiled_pattern, const char *text, int length)
{
glob_info *gi = static_cast<glob_info *>( compiled_pattern );
return glob_match(gi->s.c_str(), text, gi->ignore_case);
}
////////////////////////////////////////////////////////////////////
#endif
////////////////////////////////////////////////////////////////////
// formatted_string
//
formatted_string::formatted_string(const std::string &s)
: ops()
{
ops.push_back( s );
}
formatted_string::operator std::string() const
{
std::string s;
for (int i = 0, size = ops.size(); i < size; ++i)
{
if (ops[i] == FSOP_TEXT)
s += ops[i].text;
}
return s;
}
inline void cap(int &i, int max)
{
if (i < 0 && -i <= max)
i += max;
if (i >= max)
i = max - 1;
if (i < 0)
i = 0;
}
std::string formatted_string::tostring(int s, int e) const
{
std::string st;
int size = ops.size();
cap(s, size);
cap(e, size);
for (int i = s; i <= e && i < size; ++i)
{
if (ops[i] == FSOP_TEXT)
st += ops[i].text;
}
return st;
}
void formatted_string::display(int s, int e) const
{
int size = ops.size();
if (!size)
return ;
cap(s, size);
cap(e, size);
for (int i = s; i <= e && i < size; ++i)
ops[i].display();
}
void formatted_string::gotoxy(int x, int y)
{
ops.push_back( fs_op(x, y) );
}
void formatted_string::textcolor(int color)
{
ops.push_back(color);
}
void formatted_string::cprintf(const char *s, ...)
{
char buf[1000];
va_list args;
va_start(args, s);
vsnprintf(buf, sizeof buf, s, args);
va_end(args);
cprintf(std::string(buf));
}
void formatted_string::cprintf(const std::string &s)
{
ops.push_back(s);
}
void formatted_string::fs_op::display() const
{
switch (type)
{
case FSOP_CURSOR:
{
int cx = (x == -1? wherex() : x);
int cy = (y == -1? wherey() : y);
::gotoxy(cx, cy);
break;
}
case FSOP_COLOUR:
::textcolor(x);
break;
case FSOP_TEXT:
::cprintf("%s", text.c_str());
break;
}
}
/////////////////////////////////////////////////////////////
// input_history
//
input_history::input_history(size_t size)
: history(), pos(), maxsize(size)
{
if (maxsize < 2)
maxsize = 2;
pos = history.end();
}
void input_history::new_input(const std::string &s)
{
history.remove(s);
if (history.size() == maxsize)
history.pop_front();
history.push_back(s);
// Force the iterator to the end (also revalidates it)
go_end();
}
const std::string *input_history::prev()
{
if (history.empty())
return NULL;
if (pos == history.begin())
pos = history.end();
return &*--pos;
}
const std::string *input_history::next()
{
if (history.empty())
return NULL;
if (pos == history.end() || ++pos == history.end())
pos = history.begin();
return &*pos;
}
void input_history::go_end()
{
pos = history.end();
}
void input_history::clear()
{
history.clear();
go_end();
}
int getch_ck() {
int c = getch();
switch (c) {
case KEY_BACKSPACE: return CK_BKSP;
case KEY_DC: return CK_DELETE;
case KEY_HOME: return CK_HOME;
case KEY_PPAGE: return CK_PGUP;
case KEY_END: return CK_END;
case KEY_NPAGE: return CK_PGDN;
case KEY_UP: return CK_UP;
case KEY_DOWN: return CK_DOWN;
case KEY_LEFT: return CK_LEFT;
case KEY_RIGHT: return CK_RIGHT;
default: return c;
}
}
key_to_command_table[ (int) CONTROL('E') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('F') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('G') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('E') ] = CMD_FORGET_STASH;
key_to_command_table[ (int) CONTROL('F') ] = CMD_SEARCH_STASHES;
key_to_command_table[ (int) CONTROL('G') ] = CMD_INTERLEVEL_TRAVEL;
bool drop_item( int item_dropped, int quant_drop );
int get_equip_slot(const item_def *item);
void origin_set(int x, int y);
void origin_set_monster(item_def &item, const monsters *monster);
void origin_freeze(item_def &item, int x, int y);
bool origin_known(const item_def &item);
bool origin_describable(const item_def &item);
std::string origin_desc(const item_def &item);
void origin_purchased(item_def &item);
void origin_acquired(item_def &item, int agent);
void origin_set_startequip(item_def &item);
void origin_set_unknown(item_def &item);
void origin_set_inventory( void (*oset)(item_def &item) );
}
void pickup_menu(int item_link)
{
std::vector<item_def*> items;
for (int i = item_link; i != NON_ITEM; i = mitm[i].link)
items.push_back( &mitm[i] );
std::vector<SelItem> selected =
select_items( items, "Select items to pick up" );
redraw_screen();
for (int i = 0, count = selected.size(); i < count; ++i) {
for (int j = item_link; j != NON_ITEM; j = mitm[j].link) {
if (&mitm[j] == selected[i].item) {
if (j == item_link)
item_link = mitm[j].link;
unsigned long oldflags = mitm[j].flags;
mitm[j].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
int result = move_item_to_player( j, selected[i].quantity );
// If we cleared any flags on the items, but the pickup was
// partial, reset the flags for the items that remain on the
// floor.
if (is_valid_item(mitm[j]))
mitm[j].flags = oldflags;
if (result == 0)
{
mpr("You can't carry that much weight.");
return;
}
else if (result == -1)
{
mpr("You can't carry that many items.");
return;
}
break;
}
}
}
// We have no idea where the player found this item.
void origin_set_unknown(item_def &item)
{
if (!origin_known(item))
{
item.orig_place = 0xFFFF;
item.orig_monnum = 0;
}
}
// This item is starting equipment.
void origin_set_startequip(item_def &item)
{
if (!origin_known(item))
{
item.orig_place = 0xFFFF;
item.orig_monnum = -1;
}
}
void origin_set_monster(item_def &item, const monsters *monster)
{
if (!origin_known(item))
{
if (!item.orig_monnum)
item.orig_monnum = monster->type + 1;
item.orig_place = get_packed_place();
}
}
void origin_purchased(item_def &item)
{
// We don't need to check origin_known if it's a shop purchase
item.orig_place = get_packed_place();
// Hackiness
item.orig_monnum = -1;
}
void origin_acquired(item_def &item, int agent)
{
// We don't need to check origin_known if it's a divine gift
item.orig_place = get_packed_place();
// Hackiness
item.orig_monnum = -2 - agent;
}
void origin_set_inventory(void (*oset)(item_def &item))
{
for (int i = 0; i < ENDOFPACK; ++i)
{
if (is_valid_item(you.inv[i]))
oset(you.inv[i]);
}
}
static int first_corpse_monnum(int x, int y)
{
// We could look for a corpse on this square and assume that the
// items belonged to it, but that is unsatisfactory.
return (0);
}
void origin_set(int x, int y)
{
int monnum = first_corpse_monnum(x, y);
unsigned short pplace = get_packed_place();
for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link)
{
item_def &item = mitm[link];
if (origin_known(item))
continue;
if (!item.orig_monnum)
item.orig_monnum = static_cast<short>( monnum );
item.orig_place = pplace;
}
}
void origin_set_monstercorpse(item_def &item, int x, int y)
{
item.orig_monnum = first_corpse_monnum(x, y);
}
void origin_freeze(item_def &item, int x, int y)
{
if (!origin_known(item))
{
if (!item.orig_monnum && x != -1 && y != -1)
origin_set_monstercorpse(item, x, y);
item.orig_place = get_packed_place();
}
}
static std::string origin_monster_name(const item_def &item)
{
const int monnum = item.orig_monnum - 1;
if (monnum == MONS_PLAYER_GHOST)
return ("a player ghost");
else if (monnum == MONS_PANDEMONIUM_DEMON)
return ("a demon");
char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
moname(monnum, true, DESC_NOCAP_A, monnamebuf);
return (monnamebuf);
}
static std::string origin_monster_desc(const item_def &item)
{
return (origin_monster_name(item));
}
static std::string origin_place_desc(const item_def &item)
{
std::string place = branch_level_name(item.orig_place);
if (place.length() && place != "Pandemonium")
place[0] = tolower(place[0]);
return (place.find("level") == 0?
"on " + place
: "in " + place);
}
bool is_rune(const item_def &item)
{
return (item.base_type == OBJ_MISCELLANY &&
item.sub_type == MISC_RUNE_OF_ZOT);
}
bool origin_describable(const item_def &item)
{
return (origin_known(item)
&& (item.orig_place != 0xFFFFU || item.orig_monnum == -1)
&& (!is_stackable_item(item) || is_rune(item))
&& item.quantity == 1
&& item.base_type != OBJ_CORPSES
&& (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK)
// Portable altars cannot be tracked meaningfully with Crawl's
// current handling for portable altars.
&& (item.base_type != OBJ_MISCELLANY ||
item.sub_type != MISC_PORTABLE_ALTAR_OF_NEMELEX));
}
std::string article_it(const item_def &item)
{
/*
bool them = false;
if (item.quantity > 1)
them = true;
else if (item.base_type == OBJ_ARMOUR &&
item.sub_type == ARM_BOOTS)
{
if (item.plus2 != TBOOT_NAGA_BARDING &&
item.plus2 != TBOOT_CENTAUR_BARDING)
them = true;
}
else if (item.base_type == OBJ_ARMOUR &&
item.sub_type == ARM_GLOVES)
{
them = true;
}
return them? "them" : "it";
*/
// "it" is always correct, since gloves and boots also come in pairs.
return "it";
}
bool origin_is_original_equip(const item_def &item)
{
return (item.orig_place == 0xFFFFU && item.orig_monnum == -1);
}
std::string origin_desc(const item_def &item)
{
if (!origin_describable(item))
return ("");
if (origin_is_original_equip(item))
return "Original Equipment";
std::string desc;
if (item.orig_monnum)
{
if (item.orig_monnum < 0)
{
int iorig = -item.orig_monnum - 2;
switch (iorig)
{
case -1:
desc += "You bought " + article_it(item) + " in a shop ";
break;
case AQ_SCROLL:
desc += "You acquired " + article_it(item) + " ";
break;
case AQ_CARD_ACQUISITION:
desc += "You drew \"Acquisition\" ";
break;
case AQ_CARD_VIOLENCE:
desc += "You drew \"Violence\" ";
break;
case AQ_CARD_PROTECTION:
desc += "You drew \"Protection\" ";
break;
case AQ_CARD_KNOWLEDGE:
desc += "You drew \"Knowledge\" ";
break;
case AQ_WIZMODE:
desc += "Your wizardly powers created "
+ article_it(item) + " ";
break;
default:
if (iorig > GOD_NO_GOD && iorig < NUM_GODS)
desc += std::string(god_name(iorig))
+ " gifted " + article_it(item) + " to you ";
else
// Bug really.
desc += "You stumbled upon " + article_it(item) + " ";
break;
}
}
else if (item.orig_monnum - 1 == MONS_DANCING_WEAPON)
desc += "You subdued it ";
else
desc += "You took " + article_it(item) + " off "
+ origin_monster_desc(item) + " ";
}
else
desc += "You found " + article_it(item) + " ";
desc += origin_place_desc(item);
return (desc);
}
bool pickup_single_item(int link, int qty)
{
if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR
&& you.duration[DUR_TRANSFORMATION] > 0)
{
mpr("You can't pick up anything in this form!");
return (false);
}
if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
{
mpr("You can't reach the floor from up here.");
return (false);
}
if (qty < 1 || qty > mitm[link].quantity)
qty = mitm[link].quantity;
unsigned long oldflags = mitm[link].flags;
mitm[link].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
int num = move_item_to_player( link, qty );
if (is_valid_item(mitm[link]))
mitm[link].flags = oldflags;
if (num == -1)
{
mpr("You can't carry that many items.");
return (false);
}
else if (num == 0)
{
mpr("You can't carry that much weight.");
return (false);
}
return (true);
}
int find_free_slot(const item_def &i)
{
#define slotisfree(s) \
((s) >= 0 && (s) < ENDOFPACK && !is_valid_item(you.inv[s]))
bool searchforward = false;
// If we're doing Lua, see if there's a Lua function that can give
// us a free slot.
int slot = userdef_find_free_slot(i);
if (slot == -2 || Options.assign_item_slot == SS_FORWARD)
searchforward = true;
if (slotisfree(slot))
return slot;
// See if the item remembers where it's been. Lua code can play with
// this field so be extra careful.
if ((i.slot >= 'a' && i.slot <= 'z') ||
(i.slot >= 'A' && i.slot <= 'Z'))
slot = letter_to_index(i.slot);
if (slotisfree(slot))
return slot;
if (searchforward)
{
// Return first free slot
for (slot = 0; slot < ENDOFPACK; ++slot) {
if (!is_valid_item(you.inv[slot]))
return slot;
}
}
else
{
// This is the new default free slot search. We look for the last
// available slot that does not leave a gap in the inventory.
bool accept_empty = false;
for (slot = ENDOFPACK - 1; slot >= 0; --slot)
{
if (is_valid_item(you.inv[slot]))
{
if (!accept_empty && slot + 1 < ENDOFPACK &&
!is_valid_item(you.inv[slot + 1]))
return (slot + 1);
accept_empty = true;
}
else if (accept_empty)
{
return slot;
}
}
}
return (-1);
#undef slotisfree
}
// find first empty slot
if (!is_valid_item( you.inv[m] ))
{
// copy item
you.inv[m] = mitm[obj];
you.inv[m].x = -1;
you.inv[m].y = -1;
you.inv[m].link = m;
// Something is terribly wrong
return (-1);
}
item_def &item = you.inv[freeslot];
// copy item
item = mitm[obj];
item.x = -1;
item.y = -1;
item.link = freeslot;
origin_freeze(item, you.x_pos, you.y_pos);
if (you.inv[m].base_type == OBJ_ORBS
&& you.char_direction == DIR_DESCENDING)
{
if (!quiet)
mpr("Now all you have to do is get back out of the dungeon!");
you.char_direction = DIR_ASCENDING;
}
break;
}
if (item.base_type == OBJ_ORBS
&& you.char_direction == DIR_DESCENDING)
{
if (!quiet)
mpr("Now all you have to do is get back out of the dungeon!");
you.char_direction = DIR_ASCENDING;
//---------------------------------------------------------------
//
// drop
//
// Prompts the user for an item to drop
//
//---------------------------------------------------------------
void drop(void)
{
int i;
int item_dropped;
int quant_drop = -1;
char str_pass[ ITEMNAME_SIZE ];
if (inv_count() < 1 && you.gold == 0)
{
canned_msg(MSG_NOTHING_CARRIED);
return;
}
// XXX: Need to handle quantities:
item_dropped = prompt_invent_item( "Drop which item?", -1, true, true,
true, '$', &quant_drop );
if (item_dropped == PROMPT_ABORT || quant_drop == 0)
bool drop_item( int item_dropped, int quant_drop ) {
if (item_dropped == PROMPT_GOT_SPECIAL) // ie '$' for gold
return (true);
}
static std::string drop_menu_title( int menuflags, const std::string &oldt ) {
std::string res = oldt;
res.erase(0, res.find_first_not_of(" \n\t"));
if (menuflags & MF_MULTISELECT)
res = "[Multidrop] " + res;
return " " + res;
}
int get_equip_slot(const item_def *item)
{
int worn = -1;
if (item && in_inventory(*item))
{
for (int i = 0; i < NUM_EQUIP; ++i)
{
if (you.equip[i] == item->link)
{
worn = i;
break;
}
}
}
return worn;
}
static std::string drop_selitem_text( const std::vector<MenuEntry*> *s )
{
char buf[130];
bool extraturns = false;
if (!s->size())
return "";
for (int i = 0, size = s->size(); i < size; ++i)
{
const item_def *item = static_cast<item_def *>( (*s)[i]->data );
int eq = get_equip_slot(item);
if (eq > EQ_WEAPON && eq < NUM_EQUIP)
{
extraturns = true;
break;
}
}
snprintf( buf, sizeof buf, " (%u%s turn%s)", s->size(),
extraturns? "+" : "",
s->size() > 1? "s" : "" );
return buf;
}
//---------------------------------------------------------------
//
// drop
//
// Prompts the user for an item to drop
//
//---------------------------------------------------------------
void drop(void)
{
if (inv_count() < 1 && you.gold == 0)
{
canned_msg(MSG_NOTHING_CARRIED);
you.activity = ACT_NONE;
return;
}
static std::vector<SelItem> selected;
if (!you.activity || selected.empty())
selected = prompt_invent_items( "Drop which item?", -1,
drop_menu_title,
true, true, '$',
&Options.drop_filter,
drop_selitem_text );
if (selected.empty())
{
canned_msg( MSG_OK );
you.activity = ACT_NONE;
return;
}
// Throw away invalid items; items usually go invalid because
// of chunks rotting away.
while (!selected.empty()
// Don't look for gold in inventory
&& selected[0].slot != PROMPT_GOT_SPECIAL
&& !is_valid_item(you.inv[ selected[0].slot ]))
selected.erase( selected.begin() );
// Did the defunct item filter above deprive us of a drop target?
if (!selected.empty())
{
drop_item( selected[0].slot, selected[0].quantity );
// Forget the item we just dropped
selected.erase( selected.begin() );
}
// If we still have items that want to be dropped, start the multiturn
// activity
you.activity = selected.empty()? ACT_NONE : ACT_MULTIDROP;
if (Options.autopickups & (1L << mitm[o].base_type))
if ( ((mitm[o].flags & ISFLAG_THROWN) && Options.pickup_thrown) ||
( (Options.autopickups & (1L << mitm[o].base_type)
#ifdef CLUA_BINDINGS
|| clua.callbooleanfn(false, "ch_autopickup", "u", &mitm[o])
#endif
)
&& (Options.pickup_dropped || !(mitm[o].flags & ISFLAG_DROPPED))
&& !is_banned(mitm[o])))
// Disambiguation
if (!terse && item_ident(item, ISFLAG_KNOW_TYPE))
{
#define name_append(x) strncat(buff, x, ITEMNAME_SIZE)
switch (item_clas)
{
case OBJ_STAVES:
switch (item_typ)
{
case STAFF_DESTRUCTION_I:
name_append(" [fire]");
break;
case STAFF_DESTRUCTION_II:
name_append(" [ice]");
break;
case STAFF_DESTRUCTION_III:
name_append(" [iron,fireball,lightning]");
break;
case STAFF_DESTRUCTION_IV:
name_append(" [inacc,magma,cold]");
break;
}
break;
case OBJ_BOOKS:
switch (item_typ)
{
case BOOK_MINOR_MAGIC_I:
name_append(" [flame]");
break;
case BOOK_MINOR_MAGIC_II:
name_append(" [frost]");
break;
case BOOK_MINOR_MAGIC_III:
name_append(" [summ]");
break;
case BOOK_CONJURATIONS_I:
name_append(" [fire]");
break;
case BOOK_CONJURATIONS_II:
name_append(" [ice]");
break;
}
}
#undef name_append
}
void wield_weapon(bool auto_wield)
if (you.equip[EQ_WEAPON] != -1
&& you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_WEAPONS
&& item_cursed( you.inv[you.equip[EQ_WEAPON]] ))
return false;
if (weapon.base_type != OBJ_WEAPONS && weapon.base_type == OBJ_STAVES
&& you.equip[EQ_SHIELD] != -1)
return false;
if ((you.species < SP_OGRE || you.species > SP_OGRE_MAGE)
&& mass_item( weapon ) >= 500)
return false;
if ((you.species == SP_HALFLING || you.species == SP_GNOME
|| you.species == SP_KOBOLD || you.species == SP_SPRIGGAN)
&& (weapon.sub_type == WPN_GREAT_SWORD
|| weapon.sub_type == WPN_TRIPLE_SWORD
|| weapon.sub_type == WPN_GREAT_MACE
|| weapon.sub_type == WPN_GREAT_FLAIL
|| weapon.sub_type == WPN_BATTLEAXE
|| weapon.sub_type == WPN_EXECUTIONERS_AXE
|| weapon.sub_type == WPN_HALBERD
|| weapon.sub_type == WPN_GLAIVE
|| weapon.sub_type == WPN_GIANT_CLUB
|| weapon.sub_type == WPN_GIANT_SPIKED_CLUB
|| weapon.sub_type == WPN_SCYTHE))
return false;
if (hands_reqd_for_weapon( weapon.base_type,
weapon.sub_type ) == HANDS_TWO_HANDED
&& you.equip[EQ_SHIELD] != -1)
return false;
int weap_brand = get_weapon_brand( weapon );
if ((you.is_undead || you.species == SP_DEMONSPAWN)
&& (!is_fixed_artefact( weapon )
&& (weap_brand == SPWPN_HOLY_WRATH
|| weap_brand == SPWPN_DISRUPTION)))
return false;
// We can wield this weapon. Phew!
return true;
}
bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages)
item_slot = prompt_invent_item( "Wield which item (- for none)?",
OBJ_WEAPONS, true, true, true, '-' );
if (!auto_wield)
item_slot = prompt_invent_item( "Wield which item (- for none)?",
OSEL_WIELD, true, true, true, '-' );
else
item_slot = PROMPT_GOT_SPECIAL;
}
int armour_equip_slot(const item_def &item)
{
int wh_equip = EQ_BODY_ARMOUR;
switch (item.sub_type)
{
case ARM_BUCKLER:
case ARM_LARGE_SHIELD:
case ARM_SHIELD:
wh_equip = EQ_SHIELD;
break;
case ARM_CLOAK:
wh_equip = EQ_CLOAK;
break;
case ARM_HELMET:
wh_equip = EQ_HELMET;
break;
case ARM_GLOVES:
wh_equip = EQ_GLOVES;
break;
case ARM_BOOTS:
wh_equip = EQ_BOOTS;
break;
}
return (wh_equip);
switch (you.inv[item].sub_type)
{
case ARM_BUCKLER:
case ARM_LARGE_SHIELD:
case ARM_SHIELD:
wh_equip = EQ_SHIELD;
break;
case ARM_CLOAK:
wh_equip = EQ_CLOAK;
break;
case ARM_HELMET:
wh_equip = EQ_HELMET;
break;
case ARM_GLOVES:
wh_equip = EQ_GLOVES;
break;
case ARM_BOOTS:
wh_equip = EQ_BOOTS;
break;
}
wh_equip = armour_equip_slot(you.inv[item]);
bool is_amulet = false;
int item_slot;
char str_pass[ ITEMNAME_SIZE ];
if (inv_count() < 1)
{
canned_msg(MSG_NOTHING_CARRIED);
return;
}
if (you.berserker)
{
canned_msg(MSG_TOO_BERSERK);
return;
}
item_slot = prompt_invent_item( "Put on which piece of jewellery?",
OBJ_JEWELLERY );
if (item_slot == PROMPT_ABORT)
{
canned_msg( MSG_OK );
return;
}
if (keyin == 'l')
hand_used = 0;
else if (keyin == 'r')
hand_used = 1;
else if (keyin == ESCAPE)
return;
if (keyin == 'l')
hand_used = 0;
else if (keyin == 'r')
hand_used = 1;
else if (keyin == ESCAPE)
return (false);
else
{
mpr("You don't have such a hand!");
return (false);
}
}
return (true);
}
bool puton_ring(int slot, bool prompt_finger)
{
int item_slot;
if (inv_count() < 1)
{
canned_msg(MSG_NOTHING_CARRIED);
return (false);
}
if (you.berserker)
{
canned_msg(MSG_TOO_BERSERK);
return (false);
}
if (slot != -1)
item_slot = slot;
else
item_slot = prompt_invent_item( "Put on which piece of jewellery?",
OBJ_JEWELLERY );
if (item_slot == PROMPT_ABORT)
{
canned_msg( MSG_OK );
return (false);
}
return puton_item(item_slot, prompt_finger);
std::vector<SelItem> prompt_invent_items(
const char *prompt,
int type_expect,
std::string (*titlefn)( int menuflags,
const std::string &oldt )
= NULL,
bool allow_auto_list = true,
bool allow_easy_quit = true,
const char other_valid_char = '\0',
std::vector<text_pattern> *filter = NULL,
Menu::selitem_tfn fn = NULL );
unsigned char nothing = invent( invent_type, false );
redraw_screen();
Menu *m;
std::string (*titlefn)( int menuflags, const std::string &oldt );
InvTitle( Menu *mn, const char *title,
std::string (*tfn)( int menuflags, const std::string &oldt ) )
: MenuEntry( title )
{
m = mn;
titlefn = tfn;
}
const int num_lines = get_number_of_lines();
InvEntry( const item_def &i ) : MenuEntry( "", MEL_ITEM ), item( &i )
{
data = const_cast<item_def *>( item );
char buf[ITEMNAME_SIZE];
if (i.base_type == OBJ_GOLD)
snprintf(buf, sizeof buf, "%d gold piece%s", i.quantity,
(i.quantity > 1? "s" : ""));
else
item_name(i,
in_inventory(i)?
DESC_INVENTORY_EQUIP : DESC_NOCAP_A, buf, false);
text = buf;
FixedVector< int, NUM_OBJECT_CLASSES > inv_class2;
int inv_count = 0;
unsigned char ki = 0;
if (i.base_type != OBJ_GOLD)
{
if (in_inventory(i))
{
text = text.substr( 4 ); // Skip the inventory letter.
add_hotkey(index_to_letter( i.link ));
}
else
add_hotkey(' '); // Dummy hotkey
}
else
{
// Dummy hotkey for gold.
add_hotkey(' ');
}
add_class_hotkeys(i);
for (i = 0; i < 4; i++)
if (InvEntry::show_prices)
{
int value = item_value(*item, temp_id, true);
if (value > 0)
snprintf(suffix, sizeof suffix,
" (%d gold)", value);
}
snprintf( buf, sizeof buf,
"%c %c %s%s",
hotkeys[0],
(!selected_qty? '-' : selected_qty < quantity? '#' : '+'),
text.c_str(),
suffix );
return (buf);
}
private:
void add_class_hotkeys(const item_def &i)
temp_id[i][j] = 1;
case OBJ_GOLD:
add_hotkey('$');
break;
case OBJ_MISSILES:
add_hotkey('(');
break;
case OBJ_WEAPONS:
add_hotkey(')');
break;
case OBJ_ARMOUR:
add_hotkey('[');
break;
case OBJ_WANDS:
add_hotkey('/');
break;
case OBJ_FOOD:
add_hotkey('%');
break;
case OBJ_BOOKS:
add_hotkey('+');
add_hotkey(':');
break;
case OBJ_SCROLLS:
add_hotkey('?');
break;
case OBJ_JEWELLERY:
add_hotkey(i.sub_type >= AMU_RAGE? '"' : '=');
break;
case OBJ_POTIONS:
add_hotkey('!');
break;
case OBJ_STAVES:
add_hotkey('\\');
add_hotkey('|');
break;
case OBJ_MISCELLANY:
add_hotkey('}');
break;
case OBJ_CORPSES:
add_hotkey('&');
break;
default:
break;
if (i < NUM_OBJECT_CLASSES && item_class_inv != i)
inv_class2[i] = 0;
std::string item_class_name( int type, bool terse )
{
if (terse)
{
switch (type)
{
case OBJ_GOLD: return ("gold");
case OBJ_WEAPONS: return ("weapon");
case OBJ_MISSILES: return ("missile");
case OBJ_ARMOUR: return ("armour");
case OBJ_WANDS: return ("wand");
case OBJ_FOOD: return ("food");
case OBJ_UNKNOWN_I: return ("book");
case OBJ_SCROLLS: return ("scroll");
case OBJ_JEWELLERY: return ("jewelry");
case OBJ_POTIONS: return ("potion");
case OBJ_UNKNOWN_II: return ("gem");
case OBJ_BOOKS: return ("book");
case OBJ_STAVES: return ("stave");
case OBJ_ORBS: return ("orb");
case OBJ_MISCELLANY: return ("misc");
case OBJ_CORPSES: return ("carrion");
}
}
else
{
switch (type)
{
case OBJ_GOLD: return ("Gold");
case OBJ_WEAPONS: return ("Hand Weapons");
case OBJ_MISSILES: return ("Missiles");
case OBJ_ARMOUR: return ("Armour");
case OBJ_WANDS: return ("Magical Devices");
case OBJ_FOOD: return ("Comestibles");
case OBJ_UNKNOWN_I: return ("Books");
case OBJ_SCROLLS: return ("Scrolls");
case OBJ_JEWELLERY: return ("Jewellery");
case OBJ_POTIONS: return ("Potions");
case OBJ_UNKNOWN_II: return ("Gems");
case OBJ_BOOKS: return ("Books");
case OBJ_STAVES: return ("Magical Staves and Rods");
case OBJ_ORBS: return ("Orbs of Power");
case OBJ_MISCELLANY: return ("Miscellaneous");
case OBJ_CORPSES: return ("Carrion");
if ((item_class_inv == -1 && inv_count > 0)
|| (item_class_inv != -1 && inv_class2[item_class_inv] > 0)
|| (item_class_inv == OBJ_MISSILES && inv_class2[OBJ_WEAPONS] > 0)
|| (item_class_inv == OBJ_WEAPONS
&& (inv_class2[OBJ_STAVES] > 0 || inv_class2[OBJ_MISCELLANY] > 0))
|| (item_class_inv == OBJ_SCROLLS && inv_class2[OBJ_BOOKS] > 0))
void populate_item_menu( Menu *menu, const std::vector<item_def> &items,
void (*callback)(MenuEntry *me) )
{
FixedVector< int, NUM_OBJECT_CLASSES > inv_class;
for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
inv_class[i] = 0;
for (int i = 0, count = items.size(); i < count; ++i)
inv_class[ items[i].base_type ]++;
menu_letter ckey;
for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
switch (i)
{
case OBJ_WEAPONS: cprintf("Hand Weapons"); break;
case OBJ_MISSILES: cprintf("Missiles"); break;
case OBJ_ARMOUR: cprintf("Armour"); break;
case OBJ_WANDS: cprintf("Magical Devices"); break;
case OBJ_FOOD: cprintf("Comestibles"); break;
case OBJ_UNKNOWN_I: cprintf("Books"); break;
case OBJ_SCROLLS: cprintf("Scrolls"); break;
case OBJ_JEWELLERY: cprintf("Jewellery"); break;
case OBJ_POTIONS: cprintf("Potions"); break;
case OBJ_UNKNOWN_II: cprintf("Gems"); break;
case OBJ_BOOKS: cprintf("Books"); break;
case OBJ_STAVES: cprintf("Magical Staves and Rods"); break;
case OBJ_ORBS: cprintf("Orbs of Power"); break;
case OBJ_MISCELLANY: cprintf("Miscellaneous"); break;
case OBJ_CORPSES: cprintf("Carrion"); break;
//case OBJ_GEMSTONES: cprintf("Miscellaneous"); break;
}
menu.add_entry( new MenuEntry( item_class_name(i), MEL_SUBTITLE ) );
if (ki == ESCAPE)
{
#ifdef DOS_TERM
puttext(1, 1, 80, 25, buffer);
#endif
return (ESCAPE);
}
else if (isalpha(ki) || ki == '?' || ki == '*')
{
#ifdef DOS_TERM
puttext(1, 1, 80, 25, buffer);
menu.add_entry( ie );
ckey = ckey == 'z'? 'A' :
ckey == 'Z'? 'a' :
ckey + 1;
}
}
menu.set_flags( MF_MULTISELECT | MF_SELECT_ANY_PAGE );
std::vector< MenuEntry * > sel = menu.show();
for (int i = 0, count = sel.size(); i < count; ++i)
{
InvEntry *inv = (InvEntry *) sel[i];
selected.push_back( SelItem( inv->hotkeys[0],
inv->selected_qty,
inv->item ) );
}
return selected;
}
static bool item_class_selected(const item_def &i, int selector)
{
const int itype = i.base_type;
if (selector == OSEL_ANY || selector == itype)
return (true);
switch (selector)
{
case OBJ_MISSILES:
return (itype == OBJ_MISSILES || itype == OBJ_WEAPONS);
case OBJ_WEAPONS:
case OSEL_WIELD:
return (itype == OBJ_WEAPONS || itype == OBJ_STAVES
|| itype == OBJ_MISCELLANY);
case OBJ_SCROLLS:
return (itype == OBJ_SCROLLS || itype == OBJ_BOOKS);
default:
return (false);
}
}
static bool userdef_item_selected(const item_def &i, int selector)
{
#if defined(CLUA_BINDINGS)
const char *luafn = selector == OSEL_WIELD? "ch_item_wieldable" :
NULL;
return (luafn && clua.callbooleanfn(false, luafn, "u", &i));
#else
return (false);
lines = 0;
clrscr();
gotoxy(1, 1);
anything = 0;
}
static void get_inv_items_to_show(std::vector<item_def*> &v, int selector)
{
for (int i = 0; i < ENDOFPACK; i++)
{
if (is_valid_item(you.inv[i]) && is_item_selected(you.inv[i], selector))
v.push_back( &you.inv[i] );
}
}
if (is_valid_item(you.inv[j]) && you.inv[j].base_type==i)
{
anything++;
static void set_vectitem_classes(const std::vector<item_def*> &v,
FixedVector<int, NUM_OBJECT_CLASSES> &fv)
{
for (int i = 0; i < NUM_OBJECT_CLASSES; i++)
fv[i] = 0;
for (int i = 0, size = v.size(); i < size; i++)
fv[ v[i]->base_type ]++;
}
if (lines > 0)
cprintf(EOL);
unsigned char invent_select(
int item_selector,
int flags,
std::string (*titlefn)( int menuflags,
const std::string &oldt ),
std::vector<SelItem> *items,
std::vector<text_pattern> *filter,
Menu::selitem_tfn selitemfn )
{
InvMenu menu;
menu.selitem_text = selitemfn;
if (filter)
menu.set_select_filter( *filter );
itoa( item_value( you.inv[j], temp_id, true ),
tmp_quant, 10 );
char title_buf[200];
snprintf( title_buf, sizeof title_buf,
"Inventory: %d.%d/%d.%d aum (%d%%, %d/52 slots)",
you.burden / 10, you.burden % 10,
cap / 10, cap % 10,
(you.burden * 100) / cap,
inv_count );
menu.set_title( new InvTitle( &menu, title_buf, titlefn ) );
if (item_class_inv == OBJ_WEAPONS)
cprintf("You aren't carrying any weapons.");
else if (item_class_inv == OBJ_MISSILES)
cprintf("You aren't carrying any ammunition.");
if (item_selector == OBJ_WEAPONS || item_selector == OSEL_WIELD)
s = "You aren't carrying any weapons.";
else if (item_selector == OBJ_MISSILES)
s = "You aren't carrying any ammunition.";
ki = getch();
if (isalpha(ki) || ki == '?' || ki == '*')
{
#ifdef DOS_TERM
puttext(1, 1, 80, 25, buffer);
#endif
return (ki);
}
if (ki == 0)
ki = getch();
for (int i = 0, count = sel.size(); i < count; ++i)
items->push_back( SelItem( sel[i]->hotkeys[0],
sel[i]->selected_qty ) );
// This function prompts the user for an item, handles the '?' and '*'
// listings, and returns the inventory slot to the caller (which if
// must_exist is true (the default) will be an assigned item, with
// a positive quantity.
//
// It returns PROMPT_ABORT if the player hits escape.
// It returns PROMPT_GOT_SPECIAL if the player hits the "other_valid_char".
//
// Note: This function never checks if the item is appropriate.
std::vector<SelItem> prompt_invent_items(
const char *prompt,
int type_expect,
std::string (*titlefn)( int menuflags,
const std::string &oldt ),
bool allow_auto_list,
bool allow_easy_quit,
const char other_valid_char,
std::vector<text_pattern> *select_filter,
Menu::selitem_tfn fn )
{
unsigned char keyin = 0;
int ret = -1;
bool need_redraw = false;
bool need_prompt = true;
bool need_getch = true;
if (Options.auto_list && allow_auto_list)
{
need_getch = false;
need_redraw = false;
need_prompt = false;
keyin = '?';
}
std::vector< SelItem > items;
int count = -1;
for (;;)
{
if (need_redraw)
{
redraw_screen();
mesclr( true );
}
if (need_prompt)
mpr( prompt, MSGCH_PROMPT );
if (need_getch)
keyin = get_ch();
need_redraw = false;
need_prompt = true;
need_getch = true;
// Note: We handle any "special" character first, so that
// it can be used to override the others.
if (other_valid_char != '\0' && keyin == other_valid_char)
{
ret = PROMPT_GOT_SPECIAL;
break;
}
else if (keyin == '?' || keyin == '*' || keyin == ',')
{
int selmode = Options.drop_mode == DM_SINGLE?
MF_SINGLESELECT | MF_EASY_EXIT | MF_ANYPRINTABLE :
MF_MULTISELECT;
// The "view inventory listing" mode.
int ch = invent_select(
keyin == '*'? -1 : type_expect,
selmode | MF_SELECT_ANY_PAGE,
titlefn, &items, select_filter, fn );
if (selmode & MF_SINGLESELECT)
{
keyin = ch;
need_getch = false;
}
else
{
keyin = 0;
need_getch = true;
}
if (items.size())
{
redraw_screen();
mesclr( true );
for (int i = 0, count = items.size(); i < count; ++i)
items[i].slot = letter_to_index( items[i].slot );
return items;
}
need_redraw = !(keyin == '?' || keyin == '*'
|| keyin == ',' || keyin == '+');
need_prompt = need_redraw;
}
else if (isdigit( keyin ))
{
// The "read in quantity" mode
keyin = get_invent_quant( keyin, count );
need_prompt = false;
need_getch = false;
}
else if (keyin == ESCAPE
|| (Options.easy_quit_item_prompts
&& allow_easy_quit
&& keyin == ' '))
{
ret = PROMPT_ABORT;
break;
}
else if (isalpha( keyin ))
{
ret = letter_to_index( keyin );
if (!is_valid_item( you.inv[ret] ))
mpr( "You do not have any such object." );
else
break;
}
else if (!isspace( keyin ))
{
// we've got a character we don't understand...
canned_msg( MSG_HUH );
}
}
if (ret != PROMPT_ABORT)
items.push_back( SelItem( ret, count ) );
return items;
}
keyin = invent( type_expect, false );
keyin = invent_select( type_expect,
MF_SINGLESELECT | MF_ANYPRINTABLE
| MF_EASY_EXIT,
NULL, &items );
if (items.size())
{
if (count)
*count = items[0].quantity;
redraw_screen();
mesclr( true );
return letter_to_index( keyin );
}
if (keyin == '*')
keyin = invent( -1, false );
else
keyin = invent( type_expect, false );
std::vector< SelItem > items;
keyin = invent_select( keyin == '*'? OSEL_ANY : type_expect,
MF_SINGLESELECT | MF_ANYPRINTABLE
| MF_EASY_EXIT,
NULL,
&items );
if (items.size())
{
if (count)
*count = items[0].quantity;
redraw_screen();
mesclr( true );
return letter_to_index( keyin );
}
(i == 455) ? "Ctrl-D : Destroy inventory item" :
(i == 451) ? "Ctrl-D : Destroy inventory item" :
#endif
(i == 453) ? "Ctrl-G : interlevel travel" :
(i == 455) ? "Ctrl-O : explore" :
#ifdef STASH_TRACKING
(i == 456) ? "Ctrl-S : mark stash" :
(i == 457) ? "Ctrl-E : forget stash" :
(i == 458) ? "Ctrl-F : search stashes" :
void write_newgame_options_file(void);
void save_player_name(void);
std::string channel_to_str(int ch);
int str_to_channel(const std::string &);
class InitLineInput {
public:
virtual ~InitLineInput() { }
virtual bool eof() = 0;
virtual std::string getline() = 0;
};
class FileLineInput : public InitLineInput {
public:
FileLineInput(FILE *f) : file(f) { }
std::string getline() {
char s[256] = "";
if (!eof())
fgets(s, sizeof s, file);
return (s);
}
private:
FILE *file;
};
class StringLineInput : public InitLineInput {
public:
StringLineInput(const std::string &s) : str(s), pos(0) { }
bool eof() {
return pos >= str.length();
}
std::string getline() {
if (eof())
return "";
std::string::size_type newl = str.find("\n", pos);
if (newl == std::string::npos)
newl = str.length();
std::string line = str.substr(pos, newl - pos);
pos = newl + 1;
return line;
}
private:
const std::string &str;
std::string::size_type pos;
};
// also used with macros
std::string & trim_string( std::string &str )
static void read_startup_prefs();
static void read_options(InitLineInput &il, bool runscript);
template<class A, class B> void append_vector(
std::vector<A> &dest, const std::vector<B> &src)
// OK, this is really annoying. Borland C++ seems to define
// basic_string::erase to take iterators, and basic_string::remove
// to take size_t or integer. This is ass-backwards compared to
// nearly all other C++ compilers. Crap. (GDL)
//
// Borland 5.5 does this correctly now... leaving the old code
// around for now in case anyone needs it. -- bwr
// #ifdef __BCPLUSPLUS__
// str.remove( 0, str.find_first_not_of( " \t\n\r" ) );
// str.remove( str.find_last_not_of( " \t\n\r" ) + 1 );
// #else
str.erase( 0, str.find_first_not_of( " \t\n\r" ) );
str.erase( str.find_last_not_of( " \t\n\r" ) + 1 );
// #endif
return (str);
dest.insert( dest.end(), src.begin(), src.end() );
static const std::string message_channel_names[ NUM_MESSAGE_CHANNELS ] =
{
"plain", "prompt", "god", "duration", "danger", "warning", "food",
"recovery", "talk", "intrinsic_gain", "mutation", "monster_spell",
"monster_enchant", "monster_damage", "rotten_meat", "equipment",
"diagnostic",
};
const std::string cols[ NUM_MESSAGE_CHANNELS ] =
{
"plain", "prompt", "god", "duration", "danger", "warning", "food",
"recovery", "talk", "intrinsic_gain", "mutation", "monster_spell",
"monster_enchant", "monster_damage", "rotten_meat", "equipment",
"diagnostic",
};
static std::string weapon_to_str( int weapon )
{
switch (weapon)
{
case WPN_SHORT_SWORD:
return "short sword";
case WPN_MACE:
return "mace";
case WPN_SPEAR:
return "spear";
case WPN_TRIDENT:
return "trident";
case WPN_HAND_AXE:
return "hand axe";
case WPN_RANDOM:
return "random";
default:
return "random";
}
}
void read_init_file(void)
static unsigned curses_attribute(const std::string &field)
{
if (field == "standout") // probably reverses
return CHATTR_STANDOUT;
else if (field == "bold") // probably brightens fg
return CHATTR_BOLD;
else if (field == "blink") // probably brightens bg
return CHATTR_BLINK;
else if (field == "underline")
return CHATTR_UNDERLINE;
else if (field == "reverse")
return CHATTR_REVERSE;
else if (field == "dim")
return CHATTR_DIM;
else if (field.find("hi:") == 0 || field.find("hilite:") == 0 ||
field.find("highlight:") == 0)
{
int col = field.find(":");
int colour = str_to_colour(field.substr(col + 1));
if (colour == -1)
fprintf(stderr, "Bad highlight string -- %s\n", field.c_str());
else
return CHATTR_HILITE | (colour << 8);
}
else
fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
return CHATTR_NORMAL;
}
static void reset_startup_options(bool clear_name = true)
unsigned int i;
if (clear_name)
you.your_name[0] = '\0';
Options.race = '\0';
Options.cls = '\0';
Options.weapon = WPN_UNKNOWN;
Options.random_pick = false;
Options.chaos_knight = GOD_NO_GOD;
Options.death_knight = DK_NO_SELECTION;
Options.priest = GOD_NO_GOD;
}
Options.activity_interrupts[ ACT_MULTIDROP ] =
AI_HP_LOSS | AI_STAT_CHANGE | AI_TELEPORT;
Options.activity_interrupts[ ACT_RUNNING ] =
0xFFFF ^ AI_SEE_MONSTER;
Options.activity_interrupts[ ACT_TRAVELING ] =
0xFFFF ^ (AI_MESSAGE | AI_SEE_MONSTER);
reset_startup_options(clear_name);
Options.prev_race = 0;
Options.prev_cls = 0;
Options.prev_ck = GOD_NO_GOD;
Options.prev_dk = DK_NO_SELECTION;
Options.prev_pr = GOD_NO_GOD;
Options.prev_weapon = WPN_UNKNOWN;
Options.prev_randpick = false;
Options.remember_name = false;
Options.pickup_dropped = true;
Options.travel_colour = true;
Options.travel_delay = -1;
Options.travel_stair_cost = 500;
Options.travel_exclude_radius2 = 68;
Options.show_waypoints = true;
Options.item_colour = false;
Options.detected_item_colour = DARKGREY;
Options.detected_monster_colour= DARKGREY;
Options.remembered_monster_colour = 0;
Options.easy_exit_menu = true;
Options.dos_use_background_intensity = false;
Options.assign_item_slot = SS_FORWARD;
Options.macro_meta_entry = true;
// 10 was the cursor step default on Linux.
Options.level_map_cursor_step = 10;
#ifdef STASH_TRACKING
Options.stash_tracking = STM_NONE;
#endif
Options.explore_stop = ES_ITEM | ES_STAIR | ES_SHOP | ES_ALTAR;
Options.target_zero_exp = true;
Options.target_wrap = false;
Options.target_oos = true;
Options.target_los_first = true;
Options.dump_kill_places = KDO_ONE_PLACE;
Options.dump_message_count = 4;
Options.dump_item_origins = IODS_ARTIFACTS | IODS_RODS;
Options.dump_item_origin_price = -1;
Options.drop_mode = DM_SINGLE;
// Clear vector options.
Options.banned_objects.clear();
Options.stop_travel.clear();
Options.sound_mappings.clear();
Options.menu_colour_mappings.clear();
Options.drop_filter.clear();
Options.named_options.clear();
// Map each category to itself. The user can override in init.txt
Options.kill_map[KC_YOU] = KC_YOU;
Options.kill_map[KC_FRIENDLY] = KC_FRIENDLY;
Options.kill_map[KC_OTHER] = KC_OTHER;
// Setup travel information. What's a better place to do this?
initialise_travel();
}
while (!feof(f))
if (!runscript)
read_startup_prefs();
read_options(f, runscript);
fclose(f);
} // end read_init_file()
static void read_startup_prefs()
{
std::string fn = get_prefs_filename();
FILE *f = fopen(fn.c_str(), "r");
if (!f)
return;
read_options(f);
fclose(f);
Options.prev_randpick = Options.random_pick;
Options.prev_weapon = Options.weapon;
Options.prev_pr = Options.priest;
Options.prev_dk = Options.death_knight;
Options.prev_ck = Options.chaos_knight;
Options.prev_cls = Options.cls;
Options.prev_race = Options.race;
Options.prev_name = you.your_name;
reset_startup_options();
}
static void write_newgame_options(FILE *f)
{
// Write current player name
fprintf(f, "name = %s\n", you.your_name);
if (Options.prev_randpick)
Options.prev_race = Options.prev_cls = '?';
// Race selection
if (Options.prev_race)
fprintf(f, "race = %c\n", Options.prev_race);
if (Options.prev_cls)
fprintf(f, "class = %c\n", Options.prev_cls);
if (Options.prev_weapon != WPN_UNKNOWN)
fprintf(f, "weapon = %s\n", weapon_to_str(Options.prev_weapon).c_str());
if (Options.prev_ck != GOD_NO_GOD)
{
fprintf(f, "chaos_knight = %s\n",
Options.prev_ck == GOD_XOM? "xom" :
Options.prev_ck == GOD_MAKHLEB? "makhleb" :
"random");
}
if (Options.prev_dk != DK_NO_SELECTION)
fgets(s, 255, f);
fprintf(f, "death_knight = %s\n",
Options.prev_dk == DK_NECROMANCY? "necromancy" :
Options.prev_dk == DK_YREDELEMNUL? "yredelemnul" :
"random");
}
if (Options.prev_pr != GOD_NO_GOD)
{
fprintf(f, "priest = %s\n",
Options.prev_pr == GOD_ZIN? "zin" :
Options.prev_pr == GOD_YREDELEMNUL? "yredelemnul" :
"random");
}
}
if (first_dot > 0 && first_dot < first_equals)
{
key = str.substr( 0, first_dot );
subkey = str.substr( first_dot + 1, first_equals - first_dot - 1 );
field = str.substr( first_equals + 1 );
}
else
{
// no subkey (dots are okay in value field)
key = str.substr( 0, first_equals );
subkey = "";
field = str.substr( first_equals + 1 );
}
void read_options(const std::string &s, bool runscript)
{
StringLineInput st(s);
read_options(st, runscript);
}
// Clean up our data...
tolower_string( trim_string( key ) );
tolower_string( trim_string( subkey ) );
static void read_options(InitLineInput &il, bool runscript)
{
unsigned int line = 0;
bool inscriptblock = false;
bool inscriptcond = false;
bool isconditional = false;
// some fields want capitals... none care about external spaces
trim_string( field );
if (key != "name" && key != "crawl_dir"
&& key != "race" && key != "class")
{
tolower_string( field );
}
// everything not a valid line is treated as a comment
if (key == "autopickup")
{
for (i = 0; i < field.length(); i++)
{
char type = field[i];
bool l_init = false;
// Make the amulet symbol equiv to ring -- bwross
switch (type)
{
case '"':
// also represents jewellery
type = '=';
break;
std::string luacond;
std::string luacode;
while (!il.eof())
{
std::string s = il.getline();
std::string str = s;
line++;
for (j = 0; j < obj_syms_len && type != obj_syms[j]; j++)
;
str = str.substr( str.find("L<") == 0? 2 : 1 );
// Is this a one-liner?
if (!str.empty() && str[ str.length() - 1 ] == '>') {
inscriptcond = false;
str = str.substr(0, str.length() - 1);
}
// field is already cleaned up from trim_string()
strncpy(you.your_name, field.c_str(), kNameLen);
you.your_name[ kNameLen - 1 ] = '\0';
inscriptcond = false;
str = str.substr(0, str.length() - 1);
if (!str.empty() && runscript)
luacond += str + "\n";
continue;
// removes monsters/clouds from map
Options.clean_map = read_bool( field, Options.clean_map );
inscriptblock = true;
luacode.clear();
// Strip leading Lua[
str = str.substr( str.find("Lua{") == 0? 4 : 1 );
if (!str.empty() && str.find("}") == str.length() - 1)
{
str = str.substr(0, str.length() - 1);
inscriptblock = false;
}
if (!str.empty())
luacode += str + "\n";
if (!inscriptblock && runscript)
{
#ifdef CLUA_BINDINGS
clua.execstring(luacode.c_str());
if (clua.error.length())
fprintf(stderr, "Lua error: %s\n", clua.error.c_str());
luacode.clear();
#endif
}
continue;
// colour-codes play-screen map
Options.colour_map = read_bool( field, Options.colour_map );
inscriptblock = false;
#ifdef CLUA_BINDINGS
if (runscript)
{
clua.execstring(luacode.c_str());
if (clua.error.length())
fprintf(stderr, "Lua error: %s\n",
clua.error.c_str());
}
#endif
luacode.clear();
continue;
// allows both 'Y'/'N' and 'y'/'n' on yesno() prompts
if (field == "none")
Options.easy_confirm = CONFIRM_NONE_EASY;
else if (field == "safe")
Options.easy_confirm = CONFIRM_SAFE_EASY;
else if (field == "all")
Options.easy_confirm = CONFIRM_ALL_EASY;
luacode += s + "\n";
continue;
else if (key == "easy_armour" || key == "easy_armour")
{
// automatic removal of armour when dropping
Options.easy_armour = read_bool( field, Options.easy_armour );
}
else if (key == "easy_butcher")
{
// automatic knife switching
Options.easy_butcher = read_bool( field, Options.easy_butcher );
}
else if (key == "colour" || key == "color")
}
#endif
}
static int str_to_killcategory(const std::string &s)
{
static const char *kc[] = {
"you",
"friend",
"other",
};
for (unsigned i = 0; i < sizeof(kc) / sizeof(*kc); ++i) {
if (s == kc[i])
return i;
}
return -1;
}
static void do_kill_map(const std::string &from, const std::string &to)
{
int ifrom = str_to_killcategory(from),
ito = str_to_killcategory(to);
if (ifrom != -1 && ito != -1)
Options.kill_map[ifrom] = ito;
}
void parse_option_line(const std::string &str, bool runscript)
{
std::string key = "";
std::string subkey = "";
std::string field = "";
int first_equals = str.find('=');
int first_dot = str.find('.');
// all lines with no equal-signs we ignore
if (first_equals < 0)
return;
if (first_dot > 0 && first_dot < first_equals)
{
key = str.substr( 0, first_dot );
subkey = str.substr( first_dot + 1, first_equals - first_dot - 1 );
field = str.substr( first_equals + 1 );
}
else
{
// no subkey (dots are okay in value field)
key = str.substr( 0, first_equals );
subkey = "";
field = str.substr( first_equals + 1 );
}
// Clean up our data...
tolower_string( trim_string( key ) );
tolower_string( trim_string( subkey ) );
// some fields want capitals... none care about external spaces
trim_string( field );
// Keep unlowercased field around
std::string orig_field = field;
if (key != "name" && key != "crawl_dir"
&& key != "race" && key != "class" && key != "ban_pickup"
&& key != "stop_travel" && key != "sound"
&& key != "drop_filter" && key != "lua_file")
{
tolower_string( field );
}
// everything not a valid line is treated as a comment
if (key == "autopickup")
{
for (size_t i = 0; i < field.length(); i++)
const int orig_col = str_to_colour( subkey );
const int result_col = str_to_colour( field );
char type = field[i];
// Make the amulet symbol equiv to ring -- bwross
switch (type)
{
case '"':
// also represents jewellery
type = '=';
break;
case '|':
// also represents staves
type = '\\';
break;
case ':':
// also represents books
type = '+';
break;
case 'x':
// also corpses
type = 'X';
break;
}
int j;
for (j = 0; j < obj_syms_len && type != obj_syms[j]; j++)
;
else if (key == "channel")
}
else if (key == "name")
{
// field is already cleaned up from trim_string()
strncpy(you.your_name, field.c_str(), kNameLen);
you.your_name[ kNameLen - 1 ] = '\0';
}
else if (key == "verbose_dump")
{
// gives verbose info in char dumps
Options.verbose_dump = read_bool( field, Options.verbose_dump );
}
else if (key == "detailed_stat_dump")
{
Options.detailed_stat_dump =
read_bool( field, Options.detailed_stat_dump );
}
else if (key == "clean_map")
{
// removes monsters/clouds from map
Options.clean_map = read_bool( field, Options.clean_map );
}
else if (key == "colour_map" || key == "color_map")
{
// colour-codes play-screen map
Options.colour_map = read_bool( field, Options.colour_map );
}
else if (key == "easy_confirm")
{
// allows both 'Y'/'N' and 'y'/'n' on yesno() prompts
if (field == "none")
Options.easy_confirm = CONFIRM_NONE_EASY;
else if (field == "safe")
Options.easy_confirm = CONFIRM_SAFE_EASY;
else if (field == "all")
Options.easy_confirm = CONFIRM_ALL_EASY;
}
else if (key == "easy_quit_item_lists")
{
// allow aborting of item lists with space
Options.easy_quit_item_prompts = read_bool( field,
Options.easy_quit_item_prompts );
}
else if (key == "easy_open")
{
// automatic door opening with movement
Options.easy_open = read_bool( field, Options.easy_open );
}
else if (key == "easy_armour" || key == "easy_armour")
{
// automatic removal of armour when dropping
Options.easy_armour = read_bool( field, Options.easy_armour );
}
else if (key == "easy_butcher")
{
// automatic knife switching
Options.easy_butcher = read_bool( field, Options.easy_butcher );
}
else if (key == "lua_file" && runscript)
{
#ifdef CLUA_BINDINGS
clua.execfile(field.c_str());
if (clua.error.length())
fprintf(stderr, "Lua error: %s\n",
clua.error.c_str());
#endif
}
else if (key == "colour" || key == "color")
{
const int orig_col = str_to_colour( subkey );
const int result_col = str_to_colour( field );
if (orig_col != -1 && result_col != -1)
Options.colour[orig_col] = result_col;
else
const int chnl = str_to_channel( subkey );
const int col = str_to_channel_colour( field );
fprintf( stderr, "Bad colour -- %s=%d or %s=%d\n",
subkey.c_str(), orig_col, field.c_str(), result_col );
}
}
else if (key == "channel")
{
const int chnl = str_to_channel( subkey );
const int col = str_to_channel_colour( field );
if (chnl != -1 && col != -1)
Options.channels[chnl] = col;
else if (chnl == -1)
fprintf( stderr, "Bad channel -- %s\n", subkey.c_str() );
else if (col == -1)
fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
}
else if (key == "background")
{
// change background colour
// Experimental! This may look really bad!
const int col = str_to_colour( field );
if (col != -1)
Options.background = col;
else
fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
if (chnl != -1 && col != -1)
Options.channels[chnl] = col;
else if (chnl == -1)
fprintf( stderr, "Bad channel -- %s\n", subkey.c_str() );
else if (col == -1)
fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
}
else if (key == "background")
{
// change background colour
// Experimental! This may look really bad!
}
else if (key == "detected_item_colour")
{
const int col = str_to_colour( field );
if (col != -1)
Options.detected_item_colour = col;
else
fprintf( stderr, "Bad detected_item_colour -- %s\n",
field.c_str());
}
else if (key == "detected_monster_colour")
{
const int col = str_to_colour( field );
if (col != -1)
Options.detected_monster_colour = col;
else
fprintf( stderr, "Bad detected_monster_colour -- %s\n",
field.c_str());
}
else if (key == "remembered_monster_colour")
{
if (field == "real")
Options.remembered_monster_colour = 0xFFFFU;
else if (field == "auto")
Options.remembered_monster_colour = 0;
else {
#ifdef USE_COLOUR_OPTS
else if (key == "friend_brand")
}
else if (key == "friend_brand")
{
// Use curses attributes to mark friend
// Some may look bad on some terminals.
// As a suggestion, try "rxvt -rv -fn 10x20" under Un*xes
Options.friend_brand = curses_attribute(field);
}
else if (key == "stab_brand")
{
Options.stab_brand = curses_attribute(field);
}
else if (key == "may_stab_brand")
{
Options.may_stab_brand = curses_attribute(field);
}
else if (key == "no_dark_brand")
{
// This is useful for terms where dark grey does
// not have standout modes (since it's black on black).
// This option will use light-grey instead in these cases.
Options.no_dark_brand = read_bool( field, Options.no_dark_brand );
}
else if (key == "heap_brand")
{
// See friend_brand option upstairs. no_dark_brand applies
// here as well.
Options.heap_brand = curses_attribute(field);
}
else if (key == "show_uncursed")
{
// label known uncursed items as "uncursed"
Options.show_uncursed = read_bool( field, Options.show_uncursed );
}
else if (key == "always_greet")
{
// show greeting when reloading game
Options.always_greet = read_bool( field, Options.always_greet );
}
else if (key == "weapon")
{
// choose this weapon for classes that get choice
Options.weapon = str_to_weapon( field );
}
else if (key == "chaos_knight")
{
// choose god for Chaos Knights
if (field == "xom")
Options.chaos_knight = GOD_XOM;
else if (field == "makhleb")
Options.chaos_knight = GOD_MAKHLEB;
else if (field == "random")
Options.chaos_knight = GOD_RANDOM;
}
else if (key == "death_knight")
{
if (field == "necromancy")
Options.death_knight = DK_NECROMANCY;
else if (field == "yredelemnul")
Options.death_knight = DK_YREDELEMNUL;
else if (field == "random")
Options.death_knight = DK_RANDOM;
}
else if (key == "priest")
{
// choose this weapon for classes that get choice
if (field == "zin")
Options.priest = GOD_ZIN;
else if (field == "yredelemnul")
Options.priest = GOD_YREDELEMNUL;
else if (field == "random")
Options.priest = GOD_RANDOM;
}
else if (key == "fire_items_start")
{
if (isalpha( field[0] ))
Options.fire_items_start = letter_to_index( field[0] );
else
// Use curses attributes to mark friend
// Some may look bad on some terminals.
// As a suggestion, try "rxvt -rv -fn 10x20" under Un*xes
if (field == "standout") // probably reverses
Options.friend_brand = CHATTR_STANDOUT;
else if (field == "bold") // probably brightens fg
Options.friend_brand = CHATTR_BOLD;
else if (field == "blink") // probably brightens bg
Options.friend_brand = CHATTR_BLINK;
else if (field == "underline")
Options.friend_brand = CHATTR_UNDERLINE;
else if (field == "reverse")
Options.friend_brand = CHATTR_REVERSE;
else if (field == "dim")
Options.friend_brand = CHATTR_DIM;
else
fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
fprintf( stderr, "Bad fire item start index -- %s\n",
field.c_str() );
else if (key == "no_dark_brand")
{
// This is useful for terms where dark grey does
// not have standout modes (since it's black on black).
// This option will use light-grey instead in these cases.
Options.no_dark_brand = read_bool( field, Options.no_dark_brand );
}
#endif
else if (key == "show_uncursed")
{
// label known uncursed items as "uncursed"
Options.show_uncursed = read_bool( field, Options.show_uncursed );
}
else if (key == "always_greet")
{
// show greeting when reloading game
Options.always_greet = read_bool( field, Options.always_greet );
}
else if (key == "weapon")
{
// choose this weapon for classes that get choice
Options.weapon = str_to_weapon( field );
}
else if (key == "chaos_knight")
{
// choose god for Chaos Knights
if (field == "xom")
Options.chaos_knight = GOD_XOM;
else if (field == "makhleb")
Options.chaos_knight = GOD_MAKHLEB;
else if (field == "random")
Options.chaos_knight = GOD_RANDOM;
}
else if (key == "death_knight")
}
else if (key == "assign_item_slot")
{
if (field == "forward")
Options.assign_item_slot = SS_FORWARD;
else if (field == "backward")
Options.assign_item_slot = SS_BACKWARD;
}
else if (key == "fire_order")
{
str_to_fire_order( field, Options.fire_order );
}
else if (key == "random_pick")
{
// randomly generate character
Options.random_pick = read_bool( field, Options.random_pick );
}
else if (key == "remember_name")
{
Options.remember_name = read_bool( field, Options.remember_name );
}
else if (key == "hp_warning")
{
Options.hp_warning = atoi( field.c_str() );
if (Options.hp_warning < 0 || Options.hp_warning > 100)
if (field == "necromancy")
Options.death_knight = DK_NECROMANCY;
else if (field == "yredelemnul")
Options.death_knight = DK_YREDELEMNUL;
else if (field == "random")
Options.death_knight = DK_RANDOM;
Options.hp_warning = 0;
fprintf( stderr, "Bad HP warning percentage -- %s\n",
field.c_str() );
// choose this weapon for classes that get choice
if (field == "zin")
Options.priest = GOD_ZIN;
else if (field == "yredelemnul")
Options.priest = GOD_YREDELEMNUL;
else if (field == "random")
Options.priest = GOD_RANDOM;
Options.hp_attention = 0;
fprintf( stderr, "Bad HP attention percentage -- %s\n",
field.c_str() );
else if (key == "fire_items_start")
}
else if (key == "crawl_dir")
{
// We shouldn't bother to allocate this a second time
// if the user puts two crawl_dir lines in the init file.
if (!SysEnv.crawl_dir)
SysEnv.crawl_dir = (char *) calloc(kPathLen, sizeof(char));
if (SysEnv.crawl_dir)
if (isalpha( field[0] ))
Options.fire_items_start = letter_to_index( field[0] );
else
{
fprintf( stderr, "Bad fire item start index -- %s\n",
field.c_str() );
}
strncpy(SysEnv.crawl_dir, field.c_str(), kNameLen - 1);
SysEnv.crawl_dir[ kNameLen - 1 ] = '\0';
else if (key == "fire_order")
}
else if (key == "race")
{
Options.race = str_to_race( field );
if (Options.race == '\0')
fprintf( stderr, "Unknown race choice: %s\n", field.c_str() );
}
else if (key == "class")
{
Options.cls = str_to_class( field );
if (Options.cls == '\0')
fprintf( stderr, "Unknown class choice: %s\n", field.c_str() );
}
else if (key == "auto_list")
{
Options.auto_list = read_bool( field, Options.auto_list );
}
else if (key == "delay_message_clear")
{
Options.delay_message_clear = read_bool( field, Options.delay_message_clear );
}
else if (key == "terse_hand")
{
Options.terse_hand = read_bool( field, Options.terse_hand );
}
else if (key == "flush")
{
if (subkey == "failure")
Options.hp_warning = atoi( field.c_str() );
if (Options.hp_warning < 0 || Options.hp_warning > 100)
{
Options.hp_warning = 0;
fprintf( stderr, "Bad HP warning percentage -- %s\n",
field.c_str() );
}
Options.flush_input[FLUSH_ON_MESSAGE]
= read_bool(field, Options.flush_input[FLUSH_ON_MESSAGE]);
Options.hp_attention = atoi( field.c_str() );
if (Options.hp_attention < 0 || Options.hp_attention > 100)
{
Options.hp_attention = 0;
fprintf( stderr, "Bad HP attention percentage -- %s\n",
field.c_str() );
}
Options.flush_input[FLUSH_LUA]
= read_bool(field, Options.flush_input[FLUSH_LUA]);
else if (key == "crawl_dir")
{
// We shouldn't bother to allocate this a second time
// if the user puts two crawl_dir lines in the init file.
if (!SysEnv.crawl_dir)
SysEnv.crawl_dir = (char *) calloc(kPathLen, sizeof(char));
}
else if (key == "lowercase_invocations")
{
Options.lowercase_invocations
= read_bool(field, Options.lowercase_invocations);
}
else if (key == "wiz_mode")
{
// wiz_mode is recognized as a legal key in all compiles -- bwr
#ifdef WIZARD
if (field == "never")
Options.wiz_mode = WIZ_NEVER;
else if (field == "no")
Options.wiz_mode = WIZ_NO;
else if (field == "yes")
Options.wiz_mode = WIZ_YES;
else
fprintf(stderr, "Unknown wiz_mode option: %s\n", field.c_str());
#endif
}
else if (key == "ban_pickup")
{
append_vector(Options.banned_objects, split_string(",", field));
}
else if (key == "pickup_thrown")
{
Options.pickup_thrown = read_bool(field, Options.pickup_thrown);
}
else if (key == "pickup_dropped")
{
Options.pickup_dropped = read_bool(field, Options.pickup_dropped);
}
else if (key == "show_waypoints")
{
Options.show_waypoints = read_bool(field, Options.show_waypoints);
}
else if (key == "travel_delay")
{
// Read travel delay in milliseconds.
Options.travel_delay = atoi( field.c_str() );
if (Options.travel_delay < -1)
Options.travel_delay = -1;
if (Options.travel_delay > 2000)
Options.travel_delay = 2000;
}
else if (key == "level_map_cursor_step")
{
Options.level_map_cursor_step = atoi( field.c_str() );
if (Options.level_map_cursor_step < 1)
Options.level_map_cursor_step = 1;
if (Options.level_map_cursor_step > 50)
Options.level_map_cursor_step = 50;
}
else if (key == "macro_meta_entry")
{
Options.macro_meta_entry = read_bool(field, Options.macro_meta_entry);
}
else if (key == "travel_stair_cost")
{
Options.travel_stair_cost = atoi( field.c_str() );
if (Options.travel_stair_cost < 1)
Options.travel_stair_cost = 1;
else if (Options.travel_stair_cost > 1000)
Options.travel_stair_cost = 1000;
}
else if (key == "travel_exclude_radius2")
{
Options.travel_exclude_radius2 = atoi( field.c_str() );
if (Options.travel_exclude_radius2 < 0)
Options.travel_exclude_radius2 = 0;
else if (Options.travel_exclude_radius2 > 400)
Options.travel_exclude_radius2 = 400;
}
else if (key == "stop_travel")
{
std::vector<std::string> fragments = split_string(",", field);
for (int i = 0, count = fragments.size(); i < count; ++i) {
if (fragments[i].length() == 0)
continue;
if (SysEnv.crawl_dir)
{
strncpy(SysEnv.crawl_dir, field.c_str(), kNameLen - 1);
SysEnv.crawl_dir[ kNameLen - 1 ] = '\0';
std::string::size_type pos = fragments[i].find(":");
if (pos && pos != std::string::npos) {
std::string prefix = fragments[i].substr(0, pos);
int channel = str_to_channel( prefix );
if (channel != -1 || prefix == "any") {
std::string s = fragments[i].substr( pos + 1 );
trim_string( s );
Options.stop_travel.push_back(
message_filter( channel, s ) );
continue;
}
else if (key == "class")
}
else if (key == "drop_filter")
{
append_vector(Options.drop_filter, split_string(",", field));
}
else if (key == "travel_avoid_terrain")
{
std::vector<std::string> seg = split_string(",", field);
for (int i = 0, count = seg.size(); i < count; ++i)
prevent_travel_to( seg[i] );
}
else if (key == "travel_colour")
{
Options.travel_colour = read_bool(field, Options.travel_colour);
}
else if (key == "item_colour")
{
Options.item_colour = read_bool(field, Options.item_colour);
}
else if (key == "easy_exit_menu")
{
Options.easy_exit_menu = read_bool(field, Options.easy_exit_menu);
}
else if (key == "dos_use_background_intensity")
{
Options.dos_use_background_intensity =
read_bool(field, Options.dos_use_background_intensity);
}
else if (key == "explore_stop")
{
Options.explore_stop = ES_NONE;
std::vector<std::string> stops = split_string(",", field);
for (int i = 0, count = stops.size(); i < count; ++i)
Options.cls = str_to_class( field );
if (Options.cls == '\0')
fprintf( stderr, "Unknown class choice: %s\n", field.c_str() );
const std::string &c = stops[i];
if (c == "item" || c == "items")
Options.explore_stop |= ES_ITEM;
else if (c == "shop" || c == "shops")
Options.explore_stop |= ES_SHOP;
else if (c == "stair" || c == "stairs")
Options.explore_stop |= ES_STAIR;
else if (c == "altar" || c == "altars")
Options.explore_stop |= ES_ALTAR;
else if (key == "auto_list")
{
Options.auto_list = read_bool( field, Options.auto_list );
}
#ifdef STASH_TRACKING
else if (key == "stash_tracking")
{
Options.stash_tracking =
field == "explicit"? STM_EXPLICIT :
field == "dropped" ? STM_DROPPED :
field == "all" ? STM_ALL :
STM_NONE;
}
else if (key == "stash_filter")
{
std::vector<std::string> seg = split_string(",", field);
for (int i = 0, count = seg.size(); i < count; ++i)
Stash::filter( seg[i] );
}
#endif
else if (key == "sound")
{
std::vector<std::string> seg = split_string(",", field);
for (int i = 0, count = seg.size(); i < count; ++i) {
const std::string &sub = seg[i];
std::string::size_type cpos = sub.find(":", 0);
if (cpos != std::string::npos) {
sound_mapping mapping;
mapping.pattern = sub.substr(0, cpos);
mapping.soundfile = sub.substr(cpos + 1);
Options.sound_mappings.push_back(mapping);
}
else if (key == "delay_message_clear")
{
Options.delay_message_clear = read_bool( field, Options.delay_message_clear );
}
else if (key == "menu_colour" || key == "menu_color")
{
std::vector<std::string> seg = split_string(",", field);
for (int i = 0, count = seg.size(); i < count; ++i) {
const std::string &sub = seg[i];
std::string::size_type cpos = sub.find(":", 0);
if (cpos != std::string::npos) {
colour_mapping mapping;
mapping.pattern = sub.substr(cpos + 1);
mapping.colour = str_to_colour(sub.substr(0, cpos));
if (mapping.colour != -1)
Options.menu_colour_mappings.push_back(mapping);
}
else if (key == "terse_hand")
{
Options.terse_hand = read_bool( field, Options.terse_hand );
}
else if (key == "flush")
}
else if (key == "dump_kill_places")
{
Options.dump_kill_places =
field == "none"? KDO_NO_PLACES :
field == "all" ? KDO_ALL_PLACES :
KDO_ONE_PLACE;
}
else if (key == "kill_map")
{
std::vector<std::string> seg = split_string(",", field);
for (int i = 0, count = seg.size(); i < count; ++i)
if (subkey == "failure")
{
Options.flush_input[FLUSH_ON_FAILURE]
= read_bool(field, Options.flush_input[FLUSH_ON_FAILURE]);
}
else if (subkey == "command")
{
Options.flush_input[FLUSH_BEFORE_COMMAND]
= read_bool(field, Options.flush_input[FLUSH_BEFORE_COMMAND]);
}
else if (subkey == "message")
{
Options.flush_input[FLUSH_ON_MESSAGE]
= read_bool(field, Options.flush_input[FLUSH_ON_MESSAGE]);
const std::string &s = seg[i];
std::string::size_type cpos = s.find(":", 0);
if (cpos != std::string::npos) {
std::string from = s.substr(0, cpos);
std::string to = s.substr(cpos + 1);
do_kill_map(from, to);
else if (key == "lowercase_invocations")
}
else if (key == "dump_message_count")
{
// Capping is implicit
Options.dump_message_count = atoi( field.c_str() );
}
else if (key == "dump_item_origins")
{
Options.dump_item_origins = IODS_PRICE;
std::vector<std::string> choices = split_string(",", field);
for (int i = 0, count = choices.size(); i < count; ++i)
Options.lowercase_invocations
= read_bool(field, Options.lowercase_invocations);
const std::string &ch = choices[i];
if (ch == "artifacts")
Options.dump_item_origins |= IODS_ARTIFACTS;
else if (ch == "ego_arm" || ch == "ego armour"
|| ch == "ego_armour")
Options.dump_item_origins |= IODS_EGO_ARMOUR;
else if (ch == "ego_weap" || ch == "ego weapon"
|| ch == "ego_weapon" || ch == "ego weapons"
|| ch == "ego_weapons")
Options.dump_item_origins |= IODS_EGO_WEAPON;
else if (ch == "jewellery" || ch == "jewelry")
Options.dump_item_origins |= IODS_JEWELLERY;
else if (ch == "runes")
Options.dump_item_origins |= IODS_RUNES;
else if (ch == "rods")
Options.dump_item_origins |= IODS_RODS;
else if (ch == "staves")
Options.dump_item_origins |= IODS_STAVES;
else if (ch == "books")
Options.dump_item_origins |= IODS_BOOKS;
else if (ch == "all" || ch == "everything")
Options.dump_item_origins = IODS_EVERYTHING;
else if (key == "wiz_mode")
}
else if (key == "dump_item_origin_price")
{
Options.dump_item_origin_price = atoi( field.c_str() );
if (Options.dump_item_origin_price < -1)
Options.dump_item_origin_price = -1;
}
else if (key == "target_zero_exp")
{
Options.target_zero_exp = read_bool(field, Options.target_zero_exp);
}
else if (key == "target_wrap")
{
Options.target_wrap = read_bool(field, Options.target_wrap);
}
else if (key == "target_oos")
{
Options.target_oos = read_bool(field, Options.target_oos);
}
else if (key == "target_los_first")
{
Options.target_los_first = read_bool(field, Options.target_los_first);
}
else if (key == "drop_mode")
{
if (field.find("multi") != std::string::npos)
Options.drop_mode = DM_MULTI;
else
Options.drop_mode = DM_SINGLE;
}
// Catch-all else, copies option into map
else
{
#ifdef CLUA_BINDINGS
if (!clua.callbooleanfn(false, "c_process_lua_option", "ss",
key.c_str(), orig_field.c_str()))
#endif
// wiz_mode is recognized as a legal key in all compiles -- bwr
#ifdef WIZARD
if (field == "never")
Options.wiz_mode = WIZ_NEVER;
else if (field == "no")
Options.wiz_mode = WIZ_NO;
else if (field == "yes")
Options.wiz_mode = WIZ_YES;
else
fprintf(stderr, "Unknown wiz_mode option: %s\n", field.c_str());
#ifdef CLUA_BINDINGS
if (clua.error.length())
mpr(clua.error.c_str());
// Find out which slot is our auto-swap slot
if (you.equip[EQ_WEAPON] == a_slot)
swap_slot = b_slot;
// check if the swap slot is appropriate first
if (you.equip[EQ_WEAPON] != swap_slot)
// We'll now proceed to look through the entire inventory for
// choppers/slicers. We'll skip special weapons because
// wielding/unwielding a foo of distortion would be disastrous.
for (int i = 0; i < ENDOFPACK; ++i)
if (is_valid_item( you.inv[ swap_slot ] ) // must have one
// must be able to cut with it
&& can_cut_meat( you.inv[ swap_slot ].base_type,
you.inv[ swap_slot ].sub_type )
// must be known to be uncursed weapon
&& you.inv[ swap_slot ].base_type == OBJ_WEAPONS
&& item_known_uncursed( you.inv[ swap_slot ] ))
if (is_valid_item( you.inv[i] )
&& can_cut_meat( you.inv[i].base_type,
you.inv[i].sub_type )
&& you.inv[i].base_type == OBJ_WEAPONS
&& item_known_uncursed(you.inv[i])
&& item_ident( you.inv[i], ISFLAG_KNOW_TYPE )
&& get_weapon_brand(you.inv[i])
!= SPWPN_DISTORTION
&& can_wield( you.inv[i] ))
return (true);
}
// [ds] Returns true if something was eaten
bool eat_food(bool run_hook)
{
if (you.is_undead == US_UNDEAD)
{
mpr("You can't eat.");
return (false);
}
if (you.hunger >= 11000)
{
mpr("You're too full to eat anything.");
return (false);
}
// If user hook ran, we don't know whether something
// was eaten or not...
if (run_hook && userdef_eat_food())
return (false);
if (igrd[you.x_pos][you.y_pos] != NON_ITEM)
{
if (eat_from_floor())
{
burden_change(); // ghouls regain strength from rotten food
return (true);
}
}
return (prompt_eat_from_inventory());
static bool eat_from_floor(void)
eat_chunk( determine_chunk_effect( chunk_type, rotten ) );
}
else
{
eating( mitm[item_link].base_type, mitm[item_link].sub_type );
}
you.turn_is_over = 1;
dec_mitm_item_quantity( item_link, 1 );
}
bool eat_from_floor(void)
void writeShort(FILE *file, short s);
short readShort(FILE *file);
void writeByte(FILE *file, unsigned char byte);
unsigned char readByte(FILE *file);
void writeString(FILE* file, const std::string &s);
std::string readString(FILE *file);
void writeLong(FILE* file, long num);
long readLong(FILE *file);
std::string get_savedir_filename(const char *prefix, const char *suffix,
const char *extension)
{
char filename[1200];
#ifdef SAVE_DIR_PATH
snprintf(filename, sizeof filename, SAVE_DIR_PATH "%s%d%s.%s",
prefix, (int) getuid(), suffix, extension);
#else
snprintf(filename, sizeof filename, "%s%s.%s",
prefix, suffix, extension);
#ifdef DOS
strupr(filename);
#endif
#endif
return std::string(filename);
}
std::string get_prefs_filename()
{
return get_savedir_filename("start", "ns", "prf");
}
bool travel_load_map( char branch, int absdepth )
{
char cha_fil[kFileNameSize];
make_filename( cha_fil, you.your_name, absdepth, branch,
false, false );
#ifdef DOS
strupr(cha_fil);
#endif
// Try to open level savefile.
FILE *levelFile = fopen(cha_fil, "rb");
if (!levelFile)
return false;
// BEGIN -- must load the old level : pre-load tasks
// LOAD various tags
char majorVersion;
char minorVersion;
if (!determine_level_version( levelFile, majorVersion, minorVersion )
|| majorVersion != 4)
{
fclose(levelFile);
return false;
}
tag_read(levelFile, minorVersion);
fclose( levelFile );
return true;
}
#ifdef STASH_TRACKING
strcat(stashFile, ".st");
#endif
#ifdef CLUA_BINDINGS
strcat(luaFile, ".lua");
#endif
strcat(killFile, ".kil");
strcat(travelCacheFile, ".tc");
#ifdef STASH_TRACKING
FILE *stashf = fopen(stashFile, "wb");
if (stashf)
{
stashes.save(stashf);
fclose(stashf);
#ifdef SHARED_FILES_CHMOD_PRIVATE
// change mode (unices)
chmod(stashFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
}
#endif // STASH_TRACKING
#ifdef CLUA_BINDINGS
clua.save(luaFile);
#ifdef SHARED_FILES_CHMOD_PRIVATE
// change mode; note that luaFile may not exist
chmod(luaFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
#endif // CLUA_BINDINGS
FILE *travelf = fopen(travelCacheFile, "wb");
if (travelf)
{
travel_cache.save(travelf);
fclose(travelf);
#ifdef SHARED_FILES_CHMOD_PRIVATE
// change mode (unices)
chmod(travelCacheFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
}
FILE *killf = fopen(killFile, "wb");
if (killf)
{
you.kills.save(killf);
fclose(killf);
#ifdef SHARED_FILES_CHMOD_PRIVATE
// change mode (unices)
chmod(killFile, SHARED_FILES_CHMOD_PRIVATE);
#endif
}
#ifdef STASH_TRACKING
FILE *stashFile = fopen(stash_f, "rb");
if (stashFile)
{
stashes.load(stashFile);
fclose(stashFile);
}
#endif
#ifdef CLUA_BINDINGS
clua.execfile( lua_f );
#endif // CLUA_BINDINGS
FILE *travelFile = fopen(travel_f, "rb");
if (travelFile)
{
travel_cache.load(travelFile);
fclose(travelFile);
}
FILE *killFile = fopen(kill_f, "rb");
if (killFile)
{
you.kills.load(killFile);
fclose(killFile);
}
// Largest string we'll save
#define STR_CAP 1000
using std::string;
void writeShort(FILE *file, short s)
{
char data[2];
// High byte first - network order
data[0] = (char)((s >> 8) & 0xFF);
data[1] = (char)(s & 0xFF);
write2(file, data, sizeof(data));
}
short readShort(FILE *file)
{
unsigned char data[2];
read2(file, (char *) data, 2);
// High byte first
return (((short) data[0]) << 8) | (short) data[1];
}
void writeByte(FILE *file, unsigned char byte)
{
write2(file, (char *) &byte, sizeof byte);
}
unsigned char readByte(FILE *file)
{
unsigned char byte;
read2(file, (char *) &byte, sizeof byte);
return byte;
}
void writeString(FILE* file, const string &s)
{
int length = s.length();
if (length > STR_CAP) length = STR_CAP;
writeShort(file, length);
write2(file, s.c_str(), length);
}
string readString(FILE *file)
{
char buf[STR_CAP + 1];
short length = readShort(file);
if (length)
read2(file, buf, length);
buf[length] = '\0';
return string(buf);
}
void writeLong(FILE* file, long num)
{
// High word first, network order
writeShort(file, (short) ((num >> 16) & 0xFFFFL));
writeShort(file, (short) (num & 0xFFFFL));
}
long readLong(FILE *file)
{
// We need the unsigned short cast even for the high word because we
// might be on a system where long is more than 4 bytes, and we don't want
// to sign extend the high short.
return ((long) (unsigned short) readShort(file)) << 16 |
(long) (unsigned short) readShort(file);
}
struct monsters;
struct ait_hp_loss;
struct activity_interrupt_t
{
AI_PAYLOAD apt;
const void *data;
activity_interrupt_t()
: apt(AIP_NONE), data(NULL)
{
}
activity_interrupt_t(const int *i)
: apt(AIP_INT), data(i)
{
}
activity_interrupt_t(const char *s)
: apt(AIP_STRING), data(s)
{
}
activity_interrupt_t(const std::string &s)
: apt(AIP_STRING), data(s.c_str())
{
}
activity_interrupt_t(const monsters *m)
: apt(AIP_MONSTER), data(m)
{
}
activity_interrupt_t(const ait_hp_loss *ahl)
: apt(AIP_HP_LOSS), data(ahl)
{
}
activity_interrupt_t(const activity_interrupt_t &a)
: apt(a.apt), data(a.data)
{
}
};
struct message_filter
{
int channel; // Use -1 to match any channel.
text_pattern pattern; // Empty pattern matches any message
message_filter( int ch, const std::string &s )
: channel(ch), pattern(s)
{
}
message_filter( const std::string &s ) : channel(-1), pattern(s) { }
bool is_filtered( int ch, const std::string &s ) const {
bool channel_match = ch == channel || channel == -1;
if (!channel_match || pattern.empty())
return channel_match;
return pattern.matches(s);
}
};
struct sound_mapping
{
text_pattern pattern;
std::string soundfile;
};
struct colour_mapping
{
text_pattern pattern;
int colour;
};
class formatted_string
{
public:
formatted_string() : ops() { }
formatted_string(const std::string &s);
operator std::string() const;
void display(int start = 0, int end = -1) const;
std::string tostring(int start = 0, int end = -1) const;
void cprintf(const char *s, ...);
void cprintf(const std::string &s);
void gotoxy(int x, int y);
void textcolor(int color);
private:
enum fs_op_type
{
FSOP_COLOUR,
FSOP_CURSOR,
FSOP_TEXT,
};
struct fs_op
{
fs_op_type type;
int x, y;
std::string text;
fs_op(int color)
: type(FSOP_COLOUR), x(color), y(-1), text()
{
}
fs_op(int cx, int cy)
: type(FSOP_CURSOR), x(cx), y(cy), text()
{
}
fs_op(const std::string &s)
: type(FSOP_TEXT), x(-1), y(-1), text(s)
{
}
operator fs_op_type () const
{
return type;
}
void display() const;
};
std::vector<text_pattern> banned_objects; // Objects we'll never pick up
bool pickup_thrown; // Pickup thrown missiles
bool pickup_dropped; // Pickup dropped objects
int travel_delay; // How long to pause between travel moves
std::vector<message_filter> stop_travel; // Messages that stop travel
int stash_tracking; // How stashes are tracked
bool travel_colour; // Colour levelmap using travel information?
int travel_stair_cost;
int travel_exclude_radius2; // Square of the travel exclude radius
bool show_waypoints;
bool item_colour; // Colour items on level map
unsigned detected_monster_colour; // Colour of detected monsters
unsigned detected_item_colour; // Colour of detected items
unsigned remembered_monster_colour; // Colour for monsters remembered
// on the map.
unsigned heap_brand; // Highlight heaps of items in the playing area
unsigned stab_brand; // Highlight monsters that are stabbable
unsigned may_stab_brand; // Highlight potential stab candidates
int explore_stop; // Stop exploring if a previously unseen
// item comes into view
std::vector<sound_mapping> sound_mappings;
std::vector<colour_mapping> menu_colour_mappings;
int dump_kill_places; // How to dump place information for kills.
int dump_message_count; // How many old messages to dump
int dump_item_origins; // Show where items came from?
int dump_item_origin_price;
bool target_zero_exp; // If true, targeting targets zero-exp
// monsters.
bool target_wrap; // Wrap around from last to first target
bool target_oos; // 'x' look around can target out-of-LOS
bool target_los_first; // 'x' look around first goes to visible
// objects/features, then goes to stuff
// outside LOS.
int drop_mode; // Controls whether single or multidrop
// is the default.
bool easy_exit_menu; // Menus are easier to get out of
int assign_item_slot; // How free slots are assigned
std::vector<text_pattern> drop_filter;
FixedVector< unsigned, ACT_ACTIVITY_COUNT > activity_interrupts;
// Previous startup options
bool remember_name; // Remember and reprompt with last name
bool dos_use_background_intensity;
int level_map_cursor_step; // The cursor increment in the level
// map.
// If the player prefers to merge kill records, this option can do that.
int kill_map[KC_NCATEGORIES];
typedef std::map<std::string, std::string> opt_map;
opt_map named_options; // All options not caught above are
// recorded here.
///////////////////////////////////////////////////////////////////////
// These options cannot be directly set by the user. Instead they're
// set indirectly to the choices the user made for the last character
// created. XXX: Isn't there a better place for these?
std::string prev_name;
char prev_race;
char prev_cls;
int prev_ck, prev_dk, prev_pr;
int prev_weapon;
bool prev_randpick;
enum ACT_INTERRUPT
{
AI_FORCE_INTERRUPT = 0, // Forcibly kills any activity
AI_KEYPRESS = 0x01,
AI_FULL_HP = 0x02, // Player is fully healed
AI_FULL_MP = 0x04, // Player has recovered all mp
AI_STATUE = 0x08, // Bad statue has come into view
AI_HUNGRY = 0x10, // Hunger increased
AI_MESSAGE = 0x20, // Message was displayed
AI_HP_LOSS = 0x40,
AI_BURDEN_CHANGE = 0x80,
AI_STAT_CHANGE = 0x100,
AI_SEE_MONSTER = 0x200,
AI_TELEPORT = 0x400,
};
enum AI_PAYLOAD
{
AIP_NONE,
AIP_INT,
AIP_STRING,
AIP_MONSTER,
AIP_HP_LOSS,
};
};
enum ITEM_ORIGIN_DUMP_SELECT
{
IODS_PRICE = 0, // Extra info is provided based on price
IODS_ARTIFACTS = 1, // Extra information on artifacts
IODS_EGO_ARMOUR = 2,
IODS_EGO_WEAPON = 4,
IODS_JEWELLERY = 8,
IODS_RUNES = 16,
IODS_RODS = 32,
IODS_STAVES = 64,
IODS_BOOKS = 128,
IODS_EVERYTHING = 0xFF
case SP_RED_DRACONIAN:
case SP_WHITE_DRACONIAN:
case SP_GREEN_DRACONIAN:
case SP_GOLDEN_DRACONIAN:
case SP_GREY_DRACONIAN:
case SP_BLACK_DRACONIAN:
case SP_PURPLE_DRACONIAN:
case SP_MOTTLED_DRACONIAN:
case SP_PALE_DRACONIAN:
case SP_UNK0_DRACONIAN:
case SP_UNK1_DRACONIAN:
case SP_UNK2_DRACONIAN:
enum LOSSelect
{
LOS_ANY = 0x00,
// Check only visible squares
LOS_VISIBLE = 0x01,
// Check only hidden squares
LOS_HIDDEN = 0x02,
LOS_VISMASK = 0x03,
// Flip from visible to hidden when going forward,
// or hidden to visible when going backwards.
LOS_FLIPVH = 0x20,
// Flip from hidden to visible when going forward,
// or visible to hidden when going backwards.
LOS_FLIPHV = 0x40,
LOS_NONE = 0xFFFF,
};
static char mons_find( unsigned char xps, unsigned char yps,
FixedVector<char, 2> &mfp, char direction,
int mode = TARG_ANY );
static bool find_object( int x, int y, int mode );
static bool find_monster( int x, int y, int mode );
static bool find_feature( int x, int y, int mode );
static char find_square( unsigned char xps, unsigned char yps,
FixedVector<char, 2> &mfp, char direction,
bool (*targ)(int, int, int),
int mode = TARG_ANY,
bool wrap = false,
int los = LOS_ANY);
static bool is_mapped(int x, int y)
{
return (is_player_mapped(x, y));
}
// Attempts to describe a square that's not in line-of-sight. If
// there's a stash on the square, announces the top item and number
// of items, otherwise, if there's a stair that's in the travel
// cache and noted in the Dungeon (O)verview, names the stair.
static void describe_oos_square(int x, int y)
{
if (!is_mapped(x, y))
return;
#ifdef STASH_TRACKING
describe_stash(x, y);
#endif
describe_feature(x, y, true);
}
keyin = getch();
keyin = getchm(KC_TARGETING);
// [dshaligram] Classic Crawl behaviour was to use space to select
// targets when targeting. The patch changed the meaning of space
// from 'confirm' to 'cancel', which surprised some folks. I'm now
// arbitrarily defining space as 'cancel' for look-around, and
// 'confirm' for targeting.
if (!justLooking && keyin == ' ')
keyin = '\r';
case '-':
if (mons_find( cx, cy, monsfind_pos, -1, mode ) == 0)
flush_input_buffer( FLUSH_ON_FAILURE );
else
case '^':
case '\t':
case '\\':
case '_':
case '<':
case '>':
{
if (find_square( cx, cy, objfind_pos, 1,
find_feature, keyin, true,
Options.target_los_first
? LOS_FLIPVH : LOS_ANY))
newcx = monsfind_pos[0];
newcy = monsfind_pos[1];
int dir = keyin == '-'? -1 : 1;
if (find_square( cx, cy, monsfind_pos, dir,
find_monster, mode, Options.target_wrap ))
{
newcx = monsfind_pos[0];
newcy = monsfind_pos[1];
}
else
flush_input_buffer( FLUSH_ON_FAILURE );
targChosen = true;
break;
dirChosen = true;
dir = 4;
case '5':
// If we're in look-around mode, and the cursor is on
// the character and there's a valid travel target
// within the viewport, jump to that target.
if (justLooking && cx == 17 && cy == 9)
{
if (you.travel_x > 0 && you.travel_y > 0)
{
int nx = you.travel_x - you.x_pos + 17;
int ny = you.travel_y - you.y_pos + 9;
if (in_viewport_bounds(nx, ny))
{
newcx = nx;
newcy = ny;
targChosen = true;
}
}
}
else
{
dirChosen = true;
dir = 4;
}
// RULE: cannot target what you cannot see
if (env.show[cx - 8][cy] == 0 && !(cx == 17 && cy == 9))
// [dshaligram] We no longer vet the square coordinates if
// we're justLooking. By not vetting the coordinates, we make 'x'
// look_around() nicer for travel purposes.
if (!justLooking)
bool in_vlos(int x, int y)
{
return in_los_bounds(x, y) && (env.show[x - 8][y] || (x == 17 && y == 9));
}
bool in_los(int x, int y)
{
const int tx = x + 9 - you.x_pos,
ty = y + 9 - you.y_pos;
if (!in_los_bounds(tx + 8, ty))
return (false);
return (x == you.x_pos && y == you.y_pos) || env.show[tx][ty];
}
static bool find_monster( int x, int y, int mode )
{
const int targ_mon = mgrd[ x ][ y ];
return (targ_mon != NON_MONSTER
&& in_los(x, y)
&& player_monster_visible( &(menv[targ_mon]) )
&& !mons_is_mimic( menv[targ_mon].type )
&& (mode == TARG_ANY
|| (mode == TARG_FRIEND && mons_friendly( &menv[targ_mon] ))
|| (mode == TARG_ENEMY
&& !mons_friendly( &menv[targ_mon] )
&&
(Options.target_zero_exp ||
!mons_flag( menv[targ_mon].type, M_NO_EXP_GAIN )) )));
}
static bool find_feature( int x, int y, int mode )
{
// The stair need not be in LOS if the square is mapped.
if (!in_los(x, y) && (!Options.target_oos || !is_mapped(x, y)))
return (false);
return is_feature(mode, x, y);
}
static bool find_object(int x, int y, int mode)
{
const int item = igrd[x][y];
// The square need not be in LOS if the stash tracker knows this item.
return (item != NON_ITEM
&& (in_los(x, y)
#ifdef STASH_TRACKING
|| (Options.target_oos && is_mapped(x, y) && is_stash(x, y))
#endif
));
}
static int next_los(int dir, int los, bool wrap)
{
if (los == LOS_ANY)
return (wrap? los : LOS_NONE);
bool vis = los & LOS_VISIBLE;
bool hidden = los & LOS_HIDDEN;
bool flipvh = los & LOS_FLIPVH;
bool fliphv = los & LOS_FLIPHV;
if (!vis && !hidden)
vis = true;
if (wrap)
{
if (!flipvh && !fliphv)
return (los);
// We have to invert flipvh and fliphv if we're wrapping. Here's
// why:
//
// * Say the cursor is on the last item in LOS, there are no
// items outside LOS, and wrap == true. flipvh is true.
// * We set wrap false and flip from visible to hidden, but there
// are no hidden items. So now we need to flip back to visible
// so we can go back to the first item in LOS. Unless we set
// fliphv, we can't flip from hidden to visible.
//
los = flipvh? LOS_FLIPHV : LOS_FLIPVH;
}
else
{
if (!flipvh && !fliphv)
return (LOS_NONE);
if (flipvh && vis != (dir == 1))
return (LOS_NONE);
if (fliphv && vis == (dir == 1))
return (LOS_NONE);
}
los = (los & ~LOS_VISMASK) | (vis? LOS_HIDDEN : LOS_VISIBLE);
return (los);
}
bool in_viewport_bounds(int x, int y)
{
return (x >= 1 && x <= 33 && y >= 1 && y <= 17);
}
bool in_los_bounds(int x, int y)
{
return !(x > 25 || x < 8 || y > 17 || y < 1);
}
// mons_find
// find_square
//
// Finds the next monster/object/whatever (moving in a spiral
// outwards from the player, so closer targets are chosen first;
// starts to player's left) and puts its coordinates in mfp.
// Returns 1 if it found something, zero otherwise. If direction
// is -1, goes backwards.
// Finds the next monster (moving in a spiral outwards from the
// player, so closer monsters are chosen first; starts to player's
// left) and puts its coordinates in mfp. Returns 1 if it found
// a monster, zero otherwise. If direction is -1, goes backwards.
// If the game option target_zero_exp is true, zero experience
// monsters will be targeted.
static char mons_find( unsigned char xps, unsigned char yps,
FixedVector<char, 2> &mfp, char direction, int mode )
static char find_square( unsigned char xps, unsigned char yps,
FixedVector<char, 2> &mfp, char direction,
bool (*find_targ)( int x, int y, int mode ),
int mode, bool wrap, int los )
if (direction == -1 && temp_xps == 17 && temp_yps == 9)
return (0); // can't go backwards from you
if (in_los_bounds(xps, yps))
{
// We've been told to flip between visible/hidden, so we
// need to find what we're currently on.
bool vis = (env.show[xps - 8][yps] || (xps == 17 && yps == 9));
if (wrap && (vis != (los == LOS_FLIPVH)) == (direction == 1))
{
// We've already flipped over into the other direction,
// so correct the flip direction if we're wrapping.
los = los == LOS_FLIPHV? LOS_FLIPVH : LOS_FLIPHV;
}
los = (los & ~LOS_VISMASK) | (vis? LOS_VISIBLE : LOS_HIDDEN);
}
else
{
if (wrap)
los = LOS_HIDDEN | (direction == 1? LOS_FLIPHV : LOS_FLIPVH);
else
los |= LOS_HIDDEN;
}
}
onlyVis = (los & LOS_VISIBLE);
onlyHidden = (los & LOS_HIDDEN);
const int minx = 1, maxx = 33,
miny = -7, maxy = 25,
ctrx = 17, ctry = 9;
while (temp_xps >= minx - 1 && temp_xps <= maxx
&& temp_yps <= maxy && temp_yps >= miny - 1)
{
if (direction == 1 && temp_xps == minx && temp_yps == maxy)
{
return find_square(ctrx, ctry, mfp, direction, find_targ, mode,
false, next_los(direction, los, wrap));
}
if (direction == -1 && temp_xps == ctrx && temp_yps == ctry)
{
return find_square(minx, maxy, mfp, direction, find_targ, mode,
false, next_los(direction, los, wrap));
}
if (targ_mon != NON_MONSTER
&& env.show[temp_xps - 8][temp_yps] != 0
&& player_monster_visible( &(menv[targ_mon]) )
&& !mons_is_mimic( menv[targ_mon].type )
&& (mode == TARG_ANY
|| (mode == TARG_FRIEND && mons_friendly( &menv[targ_mon] ))
|| (mode == TARG_ENEMY && !mons_friendly( &menv[targ_mon] ))))
{
//mpr("Found something!");
//more();
if (find_targ(targ_x, targ_y, mode)) {
return (0);
return (direction == 1?
find_square(ctrx, ctry, mfp, direction, find_targ, mode, false,
next_los(direction, los, wrap))
: find_square(minx, maxy, mfp, direction, find_targ, mode, false,
next_los(direction, los, wrap)));
}
static bool is_shopstair(int x, int y)
{
return (is_stair(grd[x][y]) || grd[x][y] == DNGN_ENTER_SHOP);
}
extern unsigned char (*mapch2) (unsigned char);
static bool is_full_mapped(int x, int y)
{
unsigned grid = grd[x][y];
int envch = env.map[x - 1][y - 1];
return (envch && envch == mapch2(grid));
}
static int surround_nonshopstair_count(int x, int y)
{
int count = 0;
for (int ix = -1; ix < 2; ++ix)
{
for (int iy = -1; iy < 2; ++iy)
{
int nx = x + ix, ny = y + iy;
if (nx <= 0 || nx >= GXM || ny <= 0 || ny >= GYM)
continue;
if (is_full_mapped(nx, ny) && !is_shopstair(nx, ny))
count++;
}
}
return (count);
static void describe_cell(int mx, int my)
// For want of a better name...
static bool clear_mapped(int x, int y)
{
if (!is_full_mapped(x, y))
return (false);
if (is_shopstair(x, y))
return (surround_nonshopstair_count(x, y) > 0);
return (true);
}
static void describe_feature(int mx, int my, bool oos)
{
if (oos && !clear_mapped(mx, my))
return;
unsigned oldfeat = grd[mx][my];
if (oos && env.map[mx - 1][my - 1] == mapch2(DNGN_SECRET_DOOR))
grd[mx][my] = DNGN_ROCK_WALL;
std::string desc = feature_description(mx, my);
grd[mx][my] = oldfeat;
if (desc.length())
{
if (oos)
desc = "[" + desc + "]";
mpr(desc.c_str());
}
}
std::string feature_description(int mx, int my)
switch (grd[mx][my])
{
case DNGN_STONE_WALL:
return ("A stone wall.");
case DNGN_ROCK_WALL:
case DNGN_SECRET_DOOR:
if (you.level_type == LEVEL_PANDEMONIUM)
return ("A wall of the weird stuff which makes up Pandemonium.");
else
return ("A rock wall.");
case DNGN_PERMAROCK_WALL:
return ("An unnaturally hard rock wall.");
case DNGN_CLOSED_DOOR:
return ("A closed door.");
case DNGN_METAL_WALL:
return ("A metal wall.");
case DNGN_GREEN_CRYSTAL_WALL:
return ("A wall of green crystal.");
case DNGN_ORCISH_IDOL:
return ("An orcish idol.");
case DNGN_WAX_WALL:
return ("A wall of solid wax.");
case DNGN_SILVER_STATUE:
return ("A silver statue.");
case DNGN_GRANITE_STATUE:
return ("A granite statue.");
case DNGN_ORANGE_CRYSTAL_STATUE:
return ("An orange crystal statue.");
case DNGN_LAVA:
return ("Some lava.");
case DNGN_DEEP_WATER:
return ("Some deep water.");
case DNGN_SHALLOW_WATER:
return ("Some shallow water.");
case DNGN_UNDISCOVERED_TRAP:
case DNGN_FLOOR:
return ("Floor.");
case DNGN_OPEN_DOOR:
return ("An open door.");
case DNGN_ROCK_STAIRS_DOWN:
return ("A rock staircase leading down.");
case DNGN_STONE_STAIRS_DOWN_I:
case DNGN_STONE_STAIRS_DOWN_II:
case DNGN_STONE_STAIRS_DOWN_III:
return ("A stone staircase leading down.");
case DNGN_ROCK_STAIRS_UP:
return ("A rock staircase leading upwards.");
case DNGN_STONE_STAIRS_UP_I:
case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III:
return ("A stone staircase leading up.");
case DNGN_ENTER_HELL:
return ("A gateway to hell.");
case DNGN_BRANCH_STAIRS:
return ("A staircase to a branch level.");
case DNGN_TRAP_MECHANICAL:
case DNGN_TRAP_MAGICAL:
case DNGN_TRAP_III:
for (trf = 0; trf < MAX_TRAPS; trf++)
{
if (env.trap[trf].x == mx
&& env.trap[trf].y == my)
{
break;
}
if (trf == MAX_TRAPS - 1)
{
mpr("Error - couldn't find that trap.");
error_message_to_player();
break;
}
}
switch (env.trap[trf].type)
{
case TRAP_DART:
return ("A dart trap.");
case TRAP_ARROW:
return ("An arrow trap.");
case TRAP_SPEAR:
return ("A spear trap.");
case TRAP_AXE:
return ("An axe trap.");
case TRAP_TELEPORT:
return ("A teleportation trap.");
case TRAP_AMNESIA:
return ("An amnesia trap.");
case TRAP_BLADE:
return ("A blade trap.");
case TRAP_BOLT:
return ("A bolt trap.");
case TRAP_ZOT:
return ("A Zot trap.");
case TRAP_NEEDLE:
return ("A needle trap.");
default:
mpr("An undefined trap. Huh?");
error_message_to_player();
break;
}
break;
case DNGN_ENTER_SHOP:
return (shop_name(mx, my));
case DNGN_ENTER_LABYRINTH:
return ("A labyrinth entrance.");
case DNGN_ENTER_DIS:
return ("A gateway to the Iron City of Dis.");
case DNGN_ENTER_GEHENNA:
return ("A gateway to Gehenna.");
case DNGN_ENTER_COCYTUS:
return ("A gateway to the freezing wastes of Cocytus.");
case DNGN_ENTER_TARTARUS:
return ("A gateway to the decaying netherworld of Tartarus.");
case DNGN_ENTER_ABYSS:
return ("A gateway to the infinite Abyss.");
case DNGN_EXIT_ABYSS:
return ("A gateway leading out of the Abyss.");
case DNGN_STONE_ARCH:
return ("An empty arch of ancient stone.");
case DNGN_ENTER_PANDEMONIUM:
return ("A gate leading to the halls of Pandemonium.");
case DNGN_EXIT_PANDEMONIUM:
return ("A gate leading out of Pandemonium.");
case DNGN_TRANSIT_PANDEMONIUM:
return ("A gate leading to another region of Pandemonium.");
case DNGN_ENTER_ORCISH_MINES:
return ("A staircase to the Orcish Mines.");
case DNGN_ENTER_HIVE:
return ("A staircase to the Hive.");
case DNGN_ENTER_LAIR:
return ("A staircase to the Lair.");
case DNGN_ENTER_SLIME_PITS:
return ("A staircase to the Slime Pits.");
case DNGN_ENTER_VAULTS:
return ("A staircase to the Vaults.");
case DNGN_ENTER_CRYPT:
return ("A staircase to the Crypt.");
case DNGN_ENTER_HALL_OF_BLADES:
return ("A staircase to the Hall of Blades.");
case DNGN_ENTER_ZOT:
return ("A gate to the Realm of Zot.");
case DNGN_ENTER_TEMPLE:
return ("A staircase to the Ecumenical Temple.");
case DNGN_ENTER_SNAKE_PIT:
return ("A staircase to the Snake Pit.");
case DNGN_ENTER_ELVEN_HALLS:
return ("A staircase to the Elven Halls.");
case DNGN_ENTER_TOMB:
return ("A staircase to the Tomb.");
case DNGN_ENTER_SWAMP:
return ("A staircase to the Swamp.");
case DNGN_RETURN_FROM_ORCISH_MINES:
case DNGN_RETURN_FROM_HIVE:
case DNGN_RETURN_FROM_LAIR:
case DNGN_RETURN_FROM_VAULTS:
case DNGN_RETURN_FROM_TEMPLE:
return ("A staircase back to the Dungeon.");
case DNGN_RETURN_FROM_SLIME_PITS:
case DNGN_RETURN_FROM_SNAKE_PIT:
case DNGN_RETURN_FROM_SWAMP:
return ("A staircase back to the Lair.");
case DNGN_RETURN_FROM_CRYPT:
case DNGN_RETURN_FROM_HALL_OF_BLADES:
return ("A staircase back to the Vaults.");
case DNGN_RETURN_FROM_ELVEN_HALLS:
return ("A staircase back to the Mines.");
case DNGN_RETURN_FROM_TOMB:
return ("A staircase back to the Crypt.");
case DNGN_RETURN_FROM_ZOT:
return ("A gate leading back out of this place.");
case DNGN_ALTAR_ZIN:
return ("A glowing white marble altar of Zin.");
case DNGN_ALTAR_SHINING_ONE:
return ("A glowing golden altar of the Shining One.");
case DNGN_ALTAR_KIKUBAAQUDGHA:
return ("An ancient bone altar of Kikubaaqudgha.");
case DNGN_ALTAR_YREDELEMNUL:
return ("A basalt altar of Yredelemnul.");
case DNGN_ALTAR_XOM:
return ("A shimmering altar of Xom.");
case DNGN_ALTAR_VEHUMET:
return ("A shining altar of Vehumet.");
case DNGN_ALTAR_OKAWARU:
return ("An iron altar of Okawaru.");
case DNGN_ALTAR_MAKHLEB:
return ("A burning altar of Makhleb.");
case DNGN_ALTAR_SIF_MUNA:
return ("A deep blue altar of Sif Muna.");
case DNGN_ALTAR_TROG:
return ("A bloodstained altar of Trog.");
case DNGN_ALTAR_NEMELEX_XOBEH:
return ("A sparkling altar of Nemelex Xobeh.");
case DNGN_ALTAR_ELYVILON:
return ("A silver altar of Elyvilon.");
case DNGN_BLUE_FOUNTAIN:
return ("A fountain of clear blue water.");
case DNGN_SPARKLING_FOUNTAIN:
return ("A fountain of sparkling water.");
case DNGN_DRY_FOUNTAIN_I:
case DNGN_DRY_FOUNTAIN_II:
case DNGN_DRY_FOUNTAIN_IV:
case DNGN_DRY_FOUNTAIN_VI:
case DNGN_DRY_FOUNTAIN_VIII:
case DNGN_PERMADRY_FOUNTAIN:
return ("A dry fountain.");
}
return ("");
}
static void describe_cell(int mx, int my)
{
// wandering hostile with no target in LOS
else if (menv[i].behaviour == BEH_WANDER && !mons_friendly(&menv[i])
&& menv[i].foe == MHITNOT)
// Applies to both friendlies and hostiles
else if (menv[i].behaviour == BEH_FLEE)
{
strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
strcat(info, " is fleeing in terror.");
mpr(info);
}
// hostile with target != you
else if (!mons_friendly(&menv[i]) && menv[i].foe != MHITYOU)
switch (grd[mx][my])
{
case DNGN_STONE_WALL:
mpr("A stone wall.");
break;
case DNGN_ROCK_WALL:
case DNGN_SECRET_DOOR:
if (you.level_type == LEVEL_PANDEMONIUM)
mpr("A wall of the weird stuff which makes up Pandemonium.");
else
mpr("A rock wall.");
break;
case DNGN_PERMAROCK_WALL:
mpr("An unnaturally hard rock wall.");
break;
case DNGN_CLOSED_DOOR:
mpr("A closed door.");
break;
case DNGN_METAL_WALL:
mpr("A metal wall.");
break;
case DNGN_GREEN_CRYSTAL_WALL:
mpr("A wall of green crystal.");
break;
case DNGN_ORCISH_IDOL:
mpr("An orcish idol.");
break;
case DNGN_WAX_WALL:
mpr("A wall of solid wax.");
break;
case DNGN_SILVER_STATUE:
mpr("A silver statue.");
break;
case DNGN_GRANITE_STATUE:
mpr("A granite statue.");
break;
case DNGN_ORANGE_CRYSTAL_STATUE:
mpr("An orange crystal statue.");
break;
case DNGN_LAVA:
mpr("Some lava.");
break;
case DNGN_DEEP_WATER:
mpr("Some deep water.");
break;
case DNGN_SHALLOW_WATER:
mpr("Some shallow water.");
break;
case DNGN_UNDISCOVERED_TRAP:
case DNGN_FLOOR:
mpr("Floor.");
break;
case DNGN_OPEN_DOOR:
mpr("An open door.");
break;
case DNGN_ROCK_STAIRS_DOWN:
mpr("A rock staircase leading down.");
break;
case DNGN_STONE_STAIRS_DOWN_I:
case DNGN_STONE_STAIRS_DOWN_II:
case DNGN_STONE_STAIRS_DOWN_III:
mpr("A stone staircase leading down.");
break;
case DNGN_ROCK_STAIRS_UP:
mpr("A rock staircase leading upwards.");
break;
case DNGN_STONE_STAIRS_UP_I:
case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III:
mpr("A stone staircase leading up.");
break;
case DNGN_ENTER_HELL:
mpr("A gateway to hell.");
break;
case DNGN_BRANCH_STAIRS:
mpr("A staircase to a branch level.");
break;
case DNGN_TRAP_MECHANICAL:
case DNGN_TRAP_MAGICAL:
case DNGN_TRAP_III:
for (trf = 0; trf < MAX_TRAPS; trf++)
{
if (env.trap[trf].x == mx
&& env.trap[trf].y == my)
{
break;
}
if (trf == MAX_TRAPS - 1)
{
mpr("Error - couldn't find that trap.");
error_message_to_player();
break;
}
}
switch (env.trap[trf].type)
{
case TRAP_DART:
mpr("A dart trap.");
break;
case TRAP_ARROW:
mpr("An arrow trap.");
break;
case TRAP_SPEAR:
mpr("A spear trap.");
break;
case TRAP_AXE:
mpr("An axe trap.");
break;
case TRAP_TELEPORT:
mpr("A teleportation trap.");
break;
case TRAP_AMNESIA:
mpr("An amnesia trap.");
break;
case TRAP_BLADE:
mpr("A blade trap.");
break;
case TRAP_BOLT:
mpr("A bolt trap.");
break;
case TRAP_ZOT:
mpr("A Zot trap.");
break;
case TRAP_NEEDLE:
mpr("A needle trap.");
break;
default:
mpr("An undefined trap. Huh?");
error_message_to_player();
break;
}
break;
case DNGN_ENTER_SHOP:
mpr(shop_name(mx, my));
break;
case DNGN_ENTER_LABYRINTH:
mpr("A labyrinth entrance.");
break;
case DNGN_ENTER_DIS:
mpr("A gateway to the Iron City of Dis.");
break;
case DNGN_ENTER_GEHENNA:
mpr("A gateway to Gehenna.");
break;
case DNGN_ENTER_COCYTUS:
mpr("A gateway to the freezing wastes of Cocytus.");
break;
case DNGN_ENTER_TARTARUS:
mpr("A gateway to the decaying netherworld of Tartarus.");
break;
case DNGN_ENTER_ABYSS:
mpr("A gateway to the infinite Abyss.");
break;
case DNGN_EXIT_ABYSS:
mpr("A gateway leading out of the Abyss.");
break;
case DNGN_STONE_ARCH:
mpr("An empty arch of ancient stone.");
break;
case DNGN_ENTER_PANDEMONIUM:
mpr("A gate leading to the halls of Pandemonium.");
break;
case DNGN_EXIT_PANDEMONIUM:
mpr("A gate leading out of Pandemonium.");
break;
case DNGN_TRANSIT_PANDEMONIUM:
mpr("A gate leading to another region of Pandemonium.");
break;
case DNGN_ENTER_ORCISH_MINES:
mpr("A staircase to the Orcish Mines.");
break;
case DNGN_ENTER_HIVE:
mpr("A staircase to the Hive.");
break;
case DNGN_ENTER_LAIR:
mpr("A staircase to the Lair.");
break;
case DNGN_ENTER_SLIME_PITS:
mpr("A staircase to the Slime Pits.");
break;
case DNGN_ENTER_VAULTS:
mpr("A staircase to the Vaults.");
break;
case DNGN_ENTER_CRYPT:
mpr("A staircase to the Crypt.");
break;
case DNGN_ENTER_HALL_OF_BLADES:
mpr("A staircase to the Hall of Blades.");
break;
case DNGN_ENTER_ZOT:
mpr("A gate to the Realm of Zot.");
break;
case DNGN_ENTER_TEMPLE:
mpr("A staircase to the Ecumenical Temple.");
break;
case DNGN_ENTER_SNAKE_PIT:
mpr("A staircase to the Snake Pit.");
break;
case DNGN_ENTER_ELVEN_HALLS:
mpr("A staircase to the Elven Halls.");
break;
case DNGN_ENTER_TOMB:
mpr("A staircase to the Tomb.");
break;
case DNGN_ENTER_SWAMP:
mpr("A staircase to the Swamp.");
break;
case DNGN_RETURN_FROM_ORCISH_MINES:
case DNGN_RETURN_FROM_HIVE:
case DNGN_RETURN_FROM_LAIR:
case DNGN_RETURN_FROM_VAULTS:
case DNGN_RETURN_FROM_TEMPLE:
mpr("A staircase back to the Dungeon.");
break;
case DNGN_RETURN_FROM_SLIME_PITS:
case DNGN_RETURN_FROM_SNAKE_PIT:
case DNGN_RETURN_FROM_SWAMP:
mpr("A staircase back to the Lair.");
break;
case DNGN_RETURN_FROM_CRYPT:
case DNGN_RETURN_FROM_HALL_OF_BLADES:
mpr("A staircase back to the Vaults.");
break;
case DNGN_RETURN_FROM_ELVEN_HALLS:
mpr("A staircase back to the Mines.");
break;
case DNGN_RETURN_FROM_TOMB:
mpr("A staircase back to the Crypt.");
break;
case DNGN_RETURN_FROM_ZOT:
mpr("A gate leading back out of this place.");
break;
case DNGN_ALTAR_ZIN:
mpr("A glowing white marble altar of Zin.");
break;
case DNGN_ALTAR_SHINING_ONE:
mpr("A glowing golden altar of the Shining One.");
break;
case DNGN_ALTAR_KIKUBAAQUDGHA:
mpr("An ancient bone altar of Kikubaaqudgha.");
break;
case DNGN_ALTAR_YREDELEMNUL:
mpr("A basalt altar of Yredelemnul.");
break;
case DNGN_ALTAR_XOM:
mpr("A shimmering altar of Xom.");
break;
case DNGN_ALTAR_VEHUMET:
mpr("A shining altar of Vehumet.");
break;
case DNGN_ALTAR_OKAWARU:
mpr("An iron altar of Okawaru.");
break;
case DNGN_ALTAR_MAKHLEB:
mpr("A burning altar of Makhleb.");
break;
case DNGN_ALTAR_SIF_MUNA:
mpr("A deep blue altar of Sif Muna.");
break;
case DNGN_ALTAR_TROG:
mpr("A bloodstained altar of Trog.");
break;
case DNGN_ALTAR_NEMELEX_XOBEH:
mpr("A sparkling altar of Nemelex Xobeh.");
break;
case DNGN_ALTAR_ELYVILON:
mpr("A silver altar of Elyvilon.");
break;
case DNGN_BLUE_FOUNTAIN:
mpr("A fountain of clear blue water.");
break;
case DNGN_SPARKLING_FOUNTAIN:
mpr("A fountain of sparkling water.");
break;
case DNGN_DRY_FOUNTAIN_I:
case DNGN_DRY_FOUNTAIN_II:
case DNGN_DRY_FOUNTAIN_IV:
case DNGN_DRY_FOUNTAIN_VI:
case DNGN_DRY_FOUNTAIN_VIII:
case DNGN_PERMADRY_FOUNTAIN:
mpr("A dry fountain.");
break;
}
std::string feature_desc = feature_description(mx, my);
mpr(feature_desc.c_str());
// last updated 13oct2003 {darshan}
/* ***********************************************************************
* called from: describe_monsters - describe, kill_ghost - Kills
* *********************************************************************** */
std::string ghost_description(bool concise = false);
print_description(description);
if ( (item.base_type == OBJ_BOOKS && item_type_known(item)
&& item.sub_type != BOOK_DESTRUCTION
&& item.sub_type != BOOK_MANUAL)
||
count_staff_spells(item, true) > 1 )
{
formatted_string fs;
item_def dup = item;
spellbook_contents( dup,
item.base_type == OBJ_BOOKS?
RBOOK_READ_SPELL
: RBOOK_USE_STAFF,
&fs );
fs.display(2, -2);
}
{
char tmp_buff[ INFO_SIZE ];
// We're fudgins stats so that unarmed combat gets based off
// of the ghost's species, not the player's stats... exact
// stats are required anyways, all that matters is whether
// dex >= str. -- bwr
const int dex = 10;
int str;
switch (ghost.values[GVAL_SPECIES])
{
case SP_HILL_DWARF:
case SP_MOUNTAIN_DWARF:
case SP_TROLL:
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_MINOTAUR:
case SP_HILL_ORC:
case SP_CENTAUR:
case SP_NAGA:
case SP_MUMMY:
case SP_GHOUL:
str = 15;
break;
case SP_HUMAN:
case SP_DEMIGOD:
case SP_DEMONSPAWN:
str = 10;
break;
default:
str = 5;
break;
}
snprintf( tmp_buff, sizeof(tmp_buff),
"The apparition of %s the %s, a%s %s %s.$",
ghost.name,
skill_title( ghost.values[GVAL_BEST_SKILL],
ghost.values[GVAL_SKILL_LEVEL],
ghost.values[GVAL_SPECIES],
str, dex, GOD_NO_GOD ),
(ghost.values[GVAL_EXP_LEVEL] < 4) ? " weakling" :
(ghost.values[GVAL_EXP_LEVEL] < 7) ? "n average" :
(ghost.values[GVAL_EXP_LEVEL] < 11) ? "n experienced" :
(ghost.values[GVAL_EXP_LEVEL] < 16) ? " powerful" :
(ghost.values[GVAL_EXP_LEVEL] < 22) ? " mighty" :
(ghost.values[GVAL_EXP_LEVEL] < 26) ? " great" :
(ghost.values[GVAL_EXP_LEVEL] < 27) ? "n awesomely powerful"
: " legendary",
species_name( ghost.values[GVAL_SPECIES],
ghost.values[GVAL_EXP_LEVEL] ),
get_class_name( ghost.values[GVAL_CLASS] ) );
description += tmp_buff;
}
description += "The apparition of ";
description += ghost_description();
description += ".$";
//---------------------------------------------------------------
//
// ghost_description
//
// Describes the current ghost's previous owner. The caller must
// prepend "The apparition of" or whatever and append any trailing
// punctuation that's wanted.
//
//---------------------------------------------------------------
std::string ghost_description(bool concise)
{
char tmp_buff[ INFO_SIZE ];
// We're fudging stats so that unarmed combat gets based off
// of the ghost's species, not the player's stats... exact
// stats are required anyways, all that matters is whether
// dex >= str. -- bwr
const int dex = 10;
int str;
switch (ghost.values[GVAL_SPECIES])
{
case SP_HILL_DWARF:
case SP_MOUNTAIN_DWARF:
case SP_TROLL:
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_MINOTAUR:
case SP_HILL_ORC:
case SP_CENTAUR:
case SP_NAGA:
case SP_MUMMY:
case SP_GHOUL:
str = 15;
break;
case SP_HUMAN:
case SP_DEMIGOD:
case SP_DEMONSPAWN:
str = 10;
break;
default:
str = 5;
break;
}
snprintf( tmp_buff, sizeof(tmp_buff),
"%s the %s, a%s %s %s",
ghost.name,
skill_title( ghost.values[GVAL_BEST_SKILL],
ghost.values[GVAL_SKILL_LEVEL],
ghost.values[GVAL_SPECIES],
str, dex, GOD_NO_GOD ),
(ghost.values[GVAL_EXP_LEVEL] < 4) ? " weakling" :
(ghost.values[GVAL_EXP_LEVEL] < 7) ? "n average" :
(ghost.values[GVAL_EXP_LEVEL] < 11) ? "n experienced" :
(ghost.values[GVAL_EXP_LEVEL] < 16) ? " powerful" :
(ghost.values[GVAL_EXP_LEVEL] < 22) ? " mighty" :
(ghost.values[GVAL_EXP_LEVEL] < 26) ? " great" :
(ghost.values[GVAL_EXP_LEVEL] < 27) ? "n awesomely powerful"
: " legendary",
( concise? get_species_abbrev(ghost.values[GVAL_SPECIES]) :
species_name( ghost.values[GVAL_SPECIES],
ghost.values[GVAL_EXP_LEVEL] ) ),
( concise? get_class_abbrev(ghost.values[GVAL_CLASS]) :
get_class_name( ghost.values[GVAL_CLASS] ) ) );
return std::string(tmp_buff);
}
void swap_inv_slots(int from_slot, int to_slot, bool verbose)
{
// swap items
item_def tmp = you.inv[to_slot];
you.inv[to_slot] = you.inv[from_slot];
you.inv[from_slot] = tmp;
you.inv[from_slot].link = from_slot;
you.inv[to_slot].link = to_slot;
for (int i = 0; i < NUM_EQUIP; i++)
{
if (you.equip[i] == from_slot)
you.equip[i] = to_slot;
else if (you.equip[i] == to_slot)
you.equip[i] = from_slot;
}
if (verbose)
{
char str_pass[ ITEMNAME_SIZE ];
in_name( to_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
if (is_valid_item( you.inv[from_slot] ))
{
in_name( from_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
}
}
if (to_slot == you.equip[EQ_WEAPON] || from_slot == you.equip[EQ_WEAPON])
you.wield_change = true;
}
}
// swap items
item_def tmp = you.inv[to_slot];
you.inv[to_slot] = you.inv[from_slot];
you.inv[from_slot] = tmp;
you.inv[from_slot].link = from_slot;
you.inv[to_slot].link = to_slot;
for (int i = 0; i < NUM_EQUIP; i++)
{
if (you.equip[i] == from_slot)
you.equip[i] = to_slot;
else if (you.equip[i] == to_slot)
you.equip[i] = from_slot;
}
in_name( to_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
if (is_valid_item( you.inv[from_slot] ))
{
in_name( from_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
#ifndef __CLUA_H__
#define __CLUA_H__
#include "AppHdr.h"
#ifdef CLUA_BINDINGS
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
#include <cstdio>
#include <cstdarg>
#include <string>
#include <set>
#include "libutil.h"
#include "externs.h"
class CLua
{
public:
CLua();
~CLua();
lua_State *state();
operator lua_State * ()
{
return state();
}
void save(const char *filename);
void setglobal(const char *name);
void getglobal(const char *name);
// Assigns the value on top of the stack to a unique name in the registry
// and returns the name.
std::string setuniqregistry();
void setregistry(const char *name);
void getregistry(const char *name);
int execstring(const char *str, const char *context = "init.txt");
int execfile(const char *filename);
bool callbooleanfn(bool defval, const char *fn, const char *params, ...);
bool callfn(const char *fn, int nargs, int nret = 1);
bool callfn(const char *fn, const char *params, ...);
void fnreturns(const char *params, ...);
bool runhook(const char *hook, const char *params, ...);
static int file_write(lua_State *ls);
std::string error;
private:
lua_State *_state;
typedef std::set<std::string> sfset;
sfset sourced_files;
unsigned long uniqindex;
void init_lua();
void set_error(int err, lua_State *ls = NULL);
void load_cmacro();
void load_chooks();
void vfnreturns(const char *par, va_list va);
bool proc_returns(const char *par) const;
bool calltopfn(lua_State *ls, const char *format, va_list args,
int retc = -1, va_list *fnr = NULL);
int push_args(lua_State *ls, const char *format, va_list args,
va_list *cpto = NULL);
int return_count(lua_State *ls, const char *format);
struct CLuaSave
{
const char *filename;
FILE *handle;
FILE *get_file();
};
};
class lua_text_pattern : public base_pattern
{
public:
lua_text_pattern(const std::string &pattern);
~lua_text_pattern();
bool valid() const;
bool matches(const std::string &s) const;
static bool is_lua_pattern(const std::string &s);
private:
bool translated;
bool isvalid;
std::string pattern;
std::string lua_fn_name;
static unsigned long lfndx;
bool translate() const;
void pre_pattern(std::string &pat, std::string &fn) const;
void post_pattern(std::string &pat, std::string &fn) const;
static std::string new_fn_name();
};
extern CLua clua;
void lua_set_exclusive_item(const item_def *item = NULL);
#endif // CLUA_BINDINGS
#endif
#include "AppHdr.h"
#ifdef CLUA_BINDINGS
#include "clua.h"
#include "abl-show.h"
#include "command.h"
#include "chardump.h"
#include "food.h"
#include "invent.h"
#include "initfile.h"
#include "itemname.h"
#include "items.h"
#include "item_use.h"
#include "libutil.h"
#include "macro.h"
#include "message.h"
#include "mon-util.h"
#include "output.h"
#include "player.h"
#include "randart.h"
#include "skills2.h"
#include "spl-util.h"
#include "stuff.h"
#include "wpn-misc.h"
#include <cstring>
#ifdef HASH_CONTAINERS
# include <hash_map>
# define CHMAP HASH_CONTAINER_NS::hash_map
#else
# include <map>
# define CHMAP std::map
#endif
#include <cctype>
#define CL_RESETSTACK_RETURN(ls, oldtop, retval) \
if (true) {\
if (oldtop != lua_gettop(ls)) { \
lua_settop(ls, oldtop); \
} \
return (retval); \
} \
else
CLua clua;
CLua::CLua() : _state(NULL), sourced_files(), uniqindex(0L)
{
}
CLua::~CLua()
{
if (_state)
lua_close(_state);
}
// This has the disadvantage of repeatedly trying init_lua if it fails.
lua_State *CLua::state()
{
if (!_state)
init_lua();
return _state;
}
void CLua::setglobal(const char *name)
{
lua_setglobal(state(), name);
}
void CLua::getglobal(const char *name)
{
lua_getglobal(state(), name);
}
std::string CLua::setuniqregistry()
{
char name[100];
snprintf(name, sizeof name, "__cru%lu", uniqindex++);
lua_pushstring(state(), name);
lua_insert(state(), -2);
lua_settable(state(), LUA_REGISTRYINDEX);
return (name);
}
void CLua::setregistry(const char *name)
{
lua_pushstring(state(), name);
// Slide name round before the value
lua_insert(state(), -2);
lua_settable(state(), LUA_REGISTRYINDEX);
}
void CLua::getregistry(const char *name)
{
lua_pushstring(state(), name);
lua_gettable(state(), LUA_REGISTRYINDEX);
}
void CLua::save(const char *file)
{
if (!_state)
return;
CLuaSave clsave = { file, NULL };
callfn("c_save", "u", &clsave);
if (clsave.handle)
fclose(clsave.handle);
}
int CLua::file_write(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Expected filehandle at arg 1");
return (0);
}
CLuaSave *sf = static_cast<CLuaSave *>( lua_touserdata(ls, 1) );
if (!sf)
return (0);
FILE *f = sf->get_file();
if (!f)
return (0);
const char *text = luaL_checkstring(ls, 2);
if (text)
fprintf(f, "%s", text);
return (0);
}
FILE *CLua::CLuaSave::get_file()
{
if (!handle)
handle = fopen(filename, "w");
return (handle);
}
void CLua::set_error(int err, lua_State *ls)
{
if (!err)
{
error.clear();
return;
}
if (!ls && !(ls = _state))
{
error = "<LUA not initialized>";
return;
}
const char *serr = lua_tostring(ls, -1);
lua_pop(ls, 1);
error = serr? serr : "<Unknown error>";
}
int CLua::execstring(const char *s, const char *context)
{
lua_State *ls = state();
int err = luaL_loadbuffer(ls, s, strlen(s), context);
if (err)
{
set_error(err, ls);
return err;
}
err = lua_pcall(ls, 0, 0, 0);
set_error(err, ls);
return err;
}
int CLua::execfile(const char *filename)
{
if (sourced_files.find(filename) != sourced_files.end())
return 0;
sourced_files.insert(filename);
FILE *f = fopen(filename, "r");
if (f)
fclose(f);
else
{
error = std::string("Can't read ") + filename;
return -1;
}
lua_State *ls = state();
if (!ls)
return -1;
int err = luaL_loadfile(ls, filename);
if (!err)
err = lua_pcall(ls, 0, 0, 0);
set_error(err);
return (err);
}
bool CLua::runhook(const char *hook, const char *params, ...)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
// Remember top of stack, for debugging porpoises
int stack_top = lua_gettop(ls);
lua_getglobal(ls, hook);
if (!lua_istable(ls, -1))
{
lua_pop(ls, 1);
CL_RESETSTACK_RETURN( ls, stack_top, false );
}
for (int i = 1; ; ++i)
{
int currtop = lua_gettop(ls);
lua_rawgeti(ls, -1, i);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
break;
}
// So what's on top *is* a function. Call it with the args we have.
va_list args;
va_start(args, params);
calltopfn(ls, params, args);
va_end(args);
lua_settop(ls, currtop);
}
CL_RESETSTACK_RETURN( ls, stack_top, true );
}
void CLua::fnreturns(const char *format, ...)
{
lua_State *ls = _state;
if (!format || !ls)
return;
va_list args;
va_start(args, format);
vfnreturns(format, args);
va_end(args);
}
void CLua::vfnreturns(const char *format, va_list args)
{
lua_State *ls = _state;
int nrets = return_count(ls, format);
int sp = -nrets - 1;
const char *gs = strchr(format, '>');
if (gs)
format = gs + 1;
else if ((gs = strchr(format, ':')))
format = gs + 1;
for (const char *run = format; *run; ++run)
{
char argtype = *run;
++sp;
switch (argtype)
{
case 'u':
if (lua_islightuserdata(ls, sp))
*(va_arg(args, void**)) = lua_touserdata(ls, sp);
break;
case 'd':
if (lua_isnumber(ls, sp))
*(va_arg(args, int*)) = luaL_checkint(ls, sp);
break;
case 'b':
*(va_arg(args, bool *)) = lua_toboolean(ls, sp);
break;
case 's':
{
const char *s = lua_tostring(ls, sp);
if (s)
*(va_arg(args, std::string *)) = s;
break;
}
default:
break;
}
}
// Pop args off the stack
lua_pop(ls, nrets);
}
static void push_monster(lua_State *ls, monsters *mons);
static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t);
int CLua::push_args(lua_State *ls, const char *format, va_list args,
va_list *targ)
{
if (!format)
{
if (targ)
va_copy(*targ, args);
return (0);
}
const char *cs = strchr(format, ':');
if (cs)
format = cs + 1;
int argc = 0;
for (const char *run = format; *run; run++) {
if (*run == '>')
break;
char argtype = *run;
++argc;
switch (argtype) {
case 'u': // Light userdata
lua_pushlightuserdata(ls, va_arg(args, void*));
break;
case 's': // String
{
const char *s = va_arg(args, const char *);
if (s)
lua_pushstring(ls, s);
else
lua_pushnil(ls);
break;
}
case 'd': // Integer
lua_pushnumber(ls, va_arg(args, int));
break;
case 'L':
lua_pushnumber(ls, va_arg(args, long));
break;
case 'b':
lua_pushboolean(ls, va_arg(args, int));
break;
case 'M':
push_monster(ls, va_arg(args, monsters *));
break;
case 'A':
argc += push_activity_interrupt(
ls, va_arg(args, activity_interrupt_t *));
break;
default:
--argc;
break;
}
}
if (targ)
va_copy(*targ, args);
return (argc);
}
int CLua::return_count(lua_State *ls, const char *format)
{
if (!format)
return (0);
const char *gs = strchr(format, '>');
if (gs)
return (strlen(gs + 1));
const char *cs = strchr(format, ':');
if (cs && isdigit(*format))
{
char *es = NULL;
int ci = strtol(format, &es, 10);
// We're capping return at 10 here, which is arbitrary, but avoids
// blowing the stack.
if (ci < 0)
ci = 0;
else if (ci > 5)
ci = 10;
return (ci);
}
return (0);
}
bool CLua::calltopfn(lua_State *ls, const char *params, va_list args,
int retc, va_list *copyto)
{
// We guarantee to remove the function from the stack
int argc = push_args(ls, params, args, copyto);
if (retc == -1)
retc = return_count(ls, params);
int err = lua_pcall(ls, argc, retc, 0);
set_error(err, ls);
return (!err);
}
bool CLua::callbooleanfn(bool def, const char *fn, const char *params, ...)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (def);
int stacktop = lua_gettop(ls);
lua_getglobal(ls, fn);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
CL_RESETSTACK_RETURN(ls, stacktop, def);
}
va_list args;
va_start(args, params);
bool ret = calltopfn(ls, params, args, 1);
if (!ret)
CL_RESETSTACK_RETURN(ls, stacktop, def);
def = lua_toboolean(ls, -1);
CL_RESETSTACK_RETURN(ls, stacktop, def);
}
bool CLua::proc_returns(const char *par) const
{
return (strchr(par, '>') != NULL);
}
bool CLua::callfn(const char *fn, const char *params, ...)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
lua_getglobal(ls, fn);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
return (false);
}
va_list args;
va_list fnret;
va_start(args, params);
bool ret = calltopfn(ls, params, args, -1, &fnret);
if (ret)
{
// If we have a > in format, gather return params now.
if (proc_returns(params))
vfnreturns(params, fnret);
}
va_end(args);
va_end(fnret);
return (ret);
}
bool CLua::callfn(const char *fn, int nargs, int nret)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
lua_getglobal(ls, fn);
if (!lua_isfunction(ls, -1))
{
lua_settop(ls, -nargs - 2);
return (false);
}
// Slide the function in front of its args and call it.
if (nargs)
lua_insert(ls, -nargs - 1);
int err = lua_pcall(ls, nargs, nret, 0);
set_error(err, ls);
return !err;
}
// Defined in Kills.cc because the kill bindings refer to Kills.cc local
// structs
extern void lua_open_kills(lua_State *ls);
void lua_open_you(lua_State *ls);
void lua_open_item(lua_State *ls);
void lua_open_food(lua_State *ls);
void lua_open_crawl(lua_State *ls);
void lua_open_file(lua_State *ls);
void lua_open_options(lua_State *ls);
void lua_open_monsters(lua_State *ls);
void lua_open_globals(lua_State *ls);
void CLua::init_lua()
{
if (_state)
return;
_state = lua_open();
if (!_state)
return;
luaopen_base(_state);
luaopen_string(_state);
luaopen_table(_state);
// Open Crawl bindings
lua_open_kills(_state);
lua_open_you(_state);
lua_open_item(_state);
lua_open_food(_state);
lua_open_crawl(_state);
lua_open_file(_state);
lua_open_options(_state);
lua_open_monsters(_state);
lua_open_globals(_state);
load_cmacro();
load_chooks();
}
void CLua::load_chooks()
{
// All hook names must be chk_????
static const char *c_hooks =
"chk_startgame = { }"
;
execstring(c_hooks, "base");
}
void CLua::load_cmacro()
{
static const char *c_macro =
"function c_macro(fn)"
" if fn == nil then"
" if c_macro_coroutine ~= nil then"
" local coret, mret"
" coret, mret = coroutine.resume(c_macro_coroutine)"
" if not coret or not mret then"
" c_macro_coroutine = nil"
" c_macro_name = nil"
" end"
" if not coret and mret then"
" error(mret)"
" end"
" return (coret and mret)"
" end"
" return false"
" end"
" if _G[fn] == nil or type(_G[fn]) ~= 'function' then"
" return false"
" end"
" c_macro_name = fn"
" c_macro_coroutine = coroutine.create(_G[fn]) "
" return c_macro() "
"end";
execstring(c_macro, "base");
}
/////////////////////////////////////////////////////////////////////
#define LUAWRAP(name, wrapexpr) \
static int name(lua_State *ls) \
{ \
wrapexpr; \
return (0); \
}
#define LUARET1(name, type, val) \
static int name(lua_State *ls) \
{ \
lua_push##type(ls, val); \
return (1); \
}
#define LUARET2(name, type, val1, val2) \
static int name(lua_State *ls) \
{ \
lua_push##type(ls, val1); \
lua_push##type(ls, val2); \
return (2); \
}
template <class T> T *util_get_userdata(lua_State *ls, int ndx)
{
return (lua_islightuserdata(ls, ndx))?
static_cast<T *>( lua_touserdata(ls, ndx) )
: NULL;
}
template <class T> T *clua_get_userdata(lua_State *ls, const char *mt)
{
return static_cast<T*>( luaL_checkudata( ls, 1, mt ) );
}
static void clua_register_metatable(lua_State *ls, const char *tn,
const luaL_reg *lr,
int (*gcfn)(lua_State *ls) = NULL)
{
int top = lua_gettop(ls);
luaL_newmetatable(ls, tn);
lua_pushstring(ls, "__index");
lua_pushvalue(ls, -2);
lua_settable(ls, -3);
if (gcfn)
{
lua_pushstring(ls, "__gc");
lua_pushcfunction(ls, gcfn);
lua_settable(ls, -3);
}
luaL_openlib(ls, NULL, lr, 0);
lua_settop(ls, top);
}
template <class T> T *clua_new_userdata(
lua_State *ls, const char *mt)
{
void *udata = lua_newuserdata( ls, sizeof(T) );
luaL_getmetatable(ls, mt);
lua_setmetatable(ls, -2);
return static_cast<T*>( udata );
}
/////////////////////////////////////////////////////////////////////
// Bindings to get information on the player
//
static const char *transform_name()
{
switch (you.attribute[ATTR_TRANSFORMATION])
{
case TRAN_SPIDER:
return "spider";
case TRAN_BLADE_HANDS:
return "blade";
case TRAN_STATUE:
return "statue";
case TRAN_ICE_BEAST:
return "ice";
case TRAN_DRAGON:
return "dragon";
case TRAN_LICH:
return "lich";
case TRAN_SERPENT_OF_HELL:
return "serpent";
case TRAN_AIR:
return "air";
default:
return "";
}
}
LUARET1(you_turn_is_over, boolean, you.turn_is_over)
LUARET1(you_name, string, you.your_name)
LUARET1(you_race, string, species_name(you.species, you.experience_level))
LUARET1(you_class, string, get_class_name(you.char_class))
LUARET2(you_hp, number, you.hp, you.hp_max)
LUARET2(you_mp, number, you.magic_points, you.max_magic_points)
LUARET1(you_hunger, string, hunger_level())
LUARET2(you_strength, number, you.strength, you.max_strength)
LUARET2(you_intelligence, number, you.intel, you.max_intel)
LUARET2(you_dexterity, number, you.dex, you.max_dex)
LUARET1(you_exp, number, you.experience_level)
LUARET1(you_exp_points, number, you.experience)
LUARET1(you_res_poison, number, player_res_poison(false))
LUARET1(you_res_fire, number, player_res_fire(false))
LUARET1(you_res_cold, number, player_res_cold(false))
LUARET1(you_res_draining, number, player_prot_life(false))
LUARET1(you_res_shock, number, player_res_electricity(false))
LUARET1(you_res_statdrain, number, player_sust_abil(false))
LUARET1(you_res_mutation, number, wearing_amulet(AMU_RESIST_MUTATION, false))
LUARET1(you_res_slowing, number, wearing_amulet(AMU_RESIST_SLOW, false))
LUARET1(you_gourmand, boolean, wearing_amulet(AMU_THE_GOURMAND, false))
LUARET1(you_levitating, boolean,
player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
LUARET1(you_flying, boolean,
player_is_levitating() && wearing_amulet(AMU_CONTROLLED_FLIGHT))
LUARET1(you_transform, string, transform_name())
LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT))
void lua_push_floor_items(lua_State *ls);
static int you_floor_items(lua_State *ls)
{
lua_push_floor_items(ls);
return (1);
}
static int l_you_spells(lua_State *ls)
{
lua_newtable(ls);
int index = 0;
for (int i = 0; i < 52; ++i)
{
const int spell = get_spell_by_letter( index_to_letter(i) );
if (spell == SPELL_NO_SPELL)
continue;
lua_pushstring(ls, spell_title(spell));
lua_rawseti(ls, -2, ++index);
}
return (1);
}
static int l_you_abils(lua_State *ls)
{
lua_newtable(ls);
std::vector<const char *>abils = get_ability_names();
for (int i = 0, size = abils.size(); i < size; ++i)
{
lua_pushstring(ls, abils[i]);
lua_rawseti(ls, -2, i + 1);
}
return (1);
}
static const struct luaL_reg you_lib[] =
{
{ "turn_is_over", you_turn_is_over },
{ "spells" , l_you_spells },
{ "abilities" , l_you_abils },
{ "name" , you_name },
{ "race" , you_race },
{ "class" , you_class },
{ "hp" , you_hp },
{ "mp" , you_mp },
{ "hunger" , you_hunger },
{ "strength" , you_strength },
{ "intelligence", you_intelligence },
{ "dexterity" , you_dexterity },
{ "exp" , you_exp },
{ "exp_points" , you_exp_points },
{ "res_poison" , you_res_poison },
{ "res_fire" , you_res_fire },
{ "res_cold" , you_res_cold },
{ "res_draining", you_res_draining },
{ "res_shock" , you_res_shock },
{ "res_statdrain", you_res_statdrain },
{ "res_mutation", you_res_mutation },
{ "res_slowing", you_res_slowing },
{ "gourmand", you_gourmand },
{ "levitating", you_levitating },
{ "flying", you_flying },
{ "transform", you_transform },
{ "stop_activity", you_stop_activity },
{ "floor_items", you_floor_items },
{ NULL, NULL },
};
void lua_open_you(lua_State *ls)
{
luaL_openlib(ls, "you", you_lib, 0);
}
/////////////////////////////////////////////////////////////////////
// Bindings to get information on items. We must be extremely careful
// to only hand out information the player already has.
//
static const item_def *excl_item = NULL;
#define LUA_ITEM(name, n) \
if (!lua_islightuserdata(ls, n)) \
{ \
luaL_argerror(ls, n, "Unexpected arg type"); \
return (0); \
} \
\
item_def *name = static_cast<item_def *>( lua_touserdata(ls, n ) ); \
if (excl_item && name != excl_item) \
{ \
luaL_argerror(ls, n, "Unexpected item"); \
return (0); \
}
void lua_push_inv_items(lua_State *ls);
void lua_set_exclusive_item(const item_def *item)
{
excl_item = item;
}
static int l_item_inventory(lua_State *ls)
{
lua_push_inv_items(ls);
return (1);
}
static int l_item_index_to_letter(lua_State *ls)
{
int index = luaL_checkint(ls, 1);
char sletter[2] = "?";
if (index >= 0 && index <= ENDOFPACK)
*sletter = index_to_letter(index);
lua_pushstring(ls, sletter);
return (1);
}
static int l_item_letter_to_index(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1);
if (!s || !*s || s[1])
return (0);
lua_pushnumber(ls, letter_to_index(*s));
return (1);
}
static int l_item_swap_slots(lua_State *ls)
{
int slot1 = luaL_checkint(ls, 1),
slot2 = luaL_checkint(ls, 2);
bool verbose = lua_toboolean(ls, 3);
if (slot1 < 0 || slot1 >= ENDOFPACK ||
slot2 < 0 || slot2 >= ENDOFPACK ||
slot1 == slot2 || !is_valid_item(you.inv[slot1]))
return (0);
swap_inv_slots(slot1, slot2, verbose);
return (0);
}
static int l_item_wield(lua_State *ls)
{
if (you.turn_is_over)
return (0);
LUA_ITEM(item, 1);
int slot = -1;
if (item && is_valid_item(*item) && in_inventory(*item))
slot = item->link;
bool res = wield_weapon(true, slot);
lua_pushboolean(ls, res);
return (1);
}
static int l_item_wear(lua_State *ls)
{
if (you.turn_is_over)
return (0);
LUA_ITEM(item, 1);
if (!item || !in_inventory(*item))
return (0);
bool success = do_wear_armour(item->link, false);
lua_pushboolean(ls, success);
return (1);
}
static int l_item_puton(lua_State *ls)
{
if (you.turn_is_over)
return (0);
LUA_ITEM(item, 1);
if (!item || !in_inventory(*item))
return (0);
lua_pushboolean(ls, puton_ring(item->link, false));
return (1);
}
static int l_item_remove(lua_State *ls)
{
if (you.turn_is_over)
{
mpr("Turn is over");
return (0);
}
LUA_ITEM(item, 1);
if (!item || !in_inventory(*item))
{
mpr("Bad item");
return (0);
}
int eq = get_equip_slot(item);
if (eq < 0 || eq >= NUM_EQUIP)
{
mpr("Item is not equipped");
return (0);
}
bool result = false;
if (eq == EQ_WEAPON)
result = wield_weapon(true, -1);
else if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING || eq == EQ_AMULET)
result = remove_ring(item->link);
else
result = takeoff_armour(item->link);
lua_pushboolean(ls, result);
return (1);
}
static int l_item_drop(lua_State *ls)
{
if (you.turn_is_over)
return (0);
LUA_ITEM(item, 1);
if (!item || !in_inventory(*item))
return (0);
int eq = get_equip_slot(item);
if (eq >= 0 && eq < NUM_EQUIP)
{
lua_pushboolean(ls, false);
lua_pushstring(ls, "Can't drop worn items");
return (2);
}
int qty = item->quantity;
if (lua_isnumber(ls, 2))
{
int q = luaL_checkint(ls, 2);
if (q >= 1 && q <= item->quantity)
qty = q;
}
lua_pushboolean(ls, drop_item(item->link, qty));
return (1);
}
static int item_on_floor(const item_def &item, int x, int y);
static item_def *dmx_get_item(lua_State *ls, int ndx, int subndx)
{
if (lua_istable(ls, ndx))
{
lua_rawgeti(ls, ndx, subndx);
item_def *item = util_get_userdata<item_def>(ls, -1);
lua_pop(ls, 1);
return (item);
}
return util_get_userdata<item_def>(ls, ndx);
}
static int dmx_get_qty(lua_State *ls, int ndx, int subndx)
{
int qty = -1;
if (lua_istable(ls, ndx))
{
lua_rawgeti(ls, ndx, subndx);
if (lua_isnumber(ls, -1))
qty = luaL_checkint(ls, -1);
lua_pop(ls, 1);
}
else if (lua_isnumber(ls, ndx))
{
qty = luaL_checkint(ls, ndx);
}
return (qty);
}
static bool l_item_pickup2(item_def *item, int qty)
{
if (!item || in_inventory(*item))
return (false);
int floor_link = item_on_floor(*item, you.x_pos, you.y_pos);
if (floor_link == NON_ITEM)
return (false);
return pickup_single_item(floor_link, qty);
}
static int l_item_pickup(lua_State *ls)
{
if (you.turn_is_over)
return (0);
if (lua_islightuserdata(ls, 1))
{
LUA_ITEM(item, 1);
int qty = item->quantity;
if (lua_isnumber(ls, 2))
qty = luaL_checkint(ls, 2);
if (l_item_pickup2(item, qty))
lua_pushnumber(ls, 1);
else
lua_pushnil(ls);
return (1);
}
else if (lua_istable(ls, 1))
{
int dropped = 0;
for (int i = 1; ; ++i)
{
lua_rawgeti(ls, 1, i);
item_def *item = dmx_get_item(ls, -1, 1);
int qty = dmx_get_qty(ls, -1, 2);
lua_pop(ls, 1);
if (l_item_pickup2(item, qty))
dropped++;
else
{
// Yes, we bail out on first failure.
break;
}
}
if (dropped)
lua_pushnumber(ls, dropped);
else
lua_pushnil(ls);
return (1);
}
return (0);
}
static int l_item_equipped(lua_State *ls)
{
int eq = -1;
if (lua_isnumber(ls, 1))
eq = luaL_checkint(ls, 1);
else if (lua_isstring(ls, 1))
{
const char *eqname = lua_tostring(ls, 1);
if (!eqname)
return (0);
eq = equip_name_to_slot(eqname);
}
if (eq < 0 || eq >= NUM_EQUIP)
return (0);
if (you.equip[eq] != -1)
lua_pushlightuserdata(ls, &you.inv[you.equip[eq]]);
else
lua_pushnil(ls);
return (1);
}
static int l_item_class(lua_State *ls)
{
LUA_ITEM(item, 1);
if (item)
{
bool terse = false;
if (lua_isboolean(ls, 2))
terse = lua_toboolean(ls, 2);
std::string s = item_class_name(item->base_type, terse);
lua_pushstring(ls, s.c_str());
}
else
lua_pushnil(ls);
return (1);
}
// FIXME: Fold this back into itemname.cc.
static const char *ring_types[] = {
"regeneration",
"protection",
"protection from fire",
"poison resistance",
"protection from cold",
"strength",
"slaying",
"see invisible",
"invisibility",
"hunger",
"teleportation",
"evasion",
"sustain abilities",
"sustenance",
"dexterity",
"intelligence",
"wizardry",
"magical power",
"levitation",
"life protection",
"protection from magic",
"fire",
"ice",
"teleport control",
};
static const char *amulet_types[] = {
"rage", "resist slowing", "clarity", "warding", "resist corrosion",
"gourmand", "conservation", "controlled flight", "inaccuracy",
"resist mutation"
};
static int l_item_subtype(lua_State *ls)
{
LUA_ITEM(item, 1);
if (item)
{
if (item_type_known(*item))
{
const char *s = NULL;
if (item->base_type == OBJ_JEWELLERY)
{
if (item->sub_type < AMU_RAGE)
s = ring_types[item->sub_type];
else
s = amulet_types[ item->sub_type - AMU_RAGE ];
}
if (s)
lua_pushstring(ls, s);
else
lua_pushnil(ls);
lua_pushnumber(ls, item->sub_type);
return (2);
}
}
lua_pushnil(ls);
lua_pushnil(ls);
return (2);
}
static int l_item_cursed(lua_State *ls)
{
LUA_ITEM(item, 1);
bool cursed = item && item_ident(*item, ISFLAG_KNOW_CURSE)
&& item_cursed(*item);
lua_pushboolean(ls, cursed);
return (1);
}
static int l_item_worn(lua_State *ls)
{
LUA_ITEM(item, 1);
int worn = get_equip_slot(item);
if (worn != -1)
lua_pushnumber(ls, worn);
else
lua_pushnil(ls);
if (worn != -1)
lua_pushstring(ls, equip_slot_to_name(worn));
else
lua_pushnil(ls);
return (2);
}
static int desc_code(const char *desc)
{
if (!desc)
return DESC_PLAIN;
if (!strcmp("The", desc))
return DESC_CAP_THE;
else if (!strcmp("the", desc))
return DESC_NOCAP_THE;
else if (!strcmp("A", desc))
return DESC_CAP_A;
else if (!strcmp("a", desc))
return DESC_NOCAP_A;
else if (!strcmp("Your", desc))
return DESC_CAP_YOUR;
else if (!strcmp("your", desc))
return DESC_NOCAP_YOUR;
else if (!strcmp("its", desc))
return DESC_NOCAP_ITS;
else if (!strcmp("worn", desc))
return DESC_INVENTORY_EQUIP;
else if (!strcmp("inv", desc))
return DESC_INVENTORY;
return DESC_PLAIN;
}
static int l_item_name(lua_State *ls)
{
LUA_ITEM(item, 1);
if (item)
{
int ndesc = DESC_PLAIN;
if (lua_isstring(ls, 2))
ndesc = desc_code(lua_tostring(ls, 2));
bool terse = lua_toboolean(ls, 3);
char bufitemname[ITEMNAME_SIZE];
item_name(*item, ndesc, bufitemname, terse);
lua_pushstring(ls, bufitemname);
}
else
lua_pushnil(ls);
return (1);
}
static int l_item_quantity(lua_State *ls)
{
LUA_ITEM(item, 1);
lua_pushnumber(ls, item? item->quantity : 0);
return (1);
}
static int l_item_inslot(lua_State *ls)
{
int index = luaL_checkint(ls, 1);
if (index >= 0 && index < 52 && is_valid_item(you.inv[index]))
lua_pushlightuserdata(ls, &you.inv[index]);
else
lua_pushnil(ls);
return (1);
}
static int l_item_slot(lua_State *ls)
{
LUA_ITEM(item, 1);
if (item)
{
int slot = in_inventory(*item)? item->link :
letter_to_index(item->slot);
lua_pushnumber(ls, slot);
}
else
lua_pushnil(ls);
return (1);
}
static int l_item_ininventory(lua_State *ls)
{
LUA_ITEM(item, 1);
lua_pushboolean(ls, item && in_inventory(*item));
return (1);
}
static int l_item_equip_type(lua_State *ls)
{
LUA_ITEM(item, 1);
if (!item || !is_valid_item(*item))
return (0);
int eq = -1;
if (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_STAVES)
eq = EQ_WEAPON;
else if (item->base_type == OBJ_ARMOUR)
eq = armour_equip_slot(*item);
else if (item->base_type == OBJ_JEWELLERY)
eq = item->sub_type >= AMU_RAGE? EQ_AMULET : EQ_RINGS;
if (eq != -1)
lua_pushnumber(ls, eq);
else
lua_pushnil(ls);
if (eq != -1)
lua_pushstring(ls, equip_slot_to_name(eq));
else
lua_pushnil(ls);
return (2);
}
static int l_item_weap_skill(lua_State *ls)
{
LUA_ITEM(item, 1);
if (!item || !is_valid_item(*item))
return (0);
int skill = weapon_skill( item->base_type, item->sub_type );
if (skill == SK_FIGHTING)
return (0);
lua_pushstring(ls, skill_name(skill));
return (1);
}
static int l_item_artifact(lua_State *ls)
{
LUA_ITEM(item, 1);
if (!item || !is_valid_item(*item))
return (0);
lua_pushboolean(ls, item_ident(*item, ISFLAG_KNOW_PROPERTIES)
&& (is_random_artefact(*item) || is_fixed_artefact(*item)));
return (1);
}
static int l_item_branded(lua_State *ls)
{
LUA_ITEM(item, 1);
if (!item || !is_valid_item(*item) || !item_ident(*item, ISFLAG_KNOW_TYPE))
return (0);
bool branded = false;
switch (item->base_type)
{
case OBJ_WEAPONS:
branded = get_weapon_brand(*item) != SPWPN_NORMAL;
break;
case OBJ_ARMOUR:
branded = get_armour_ego_type(*item) != SPARM_NORMAL;
break;
case OBJ_MISSILES:
branded = get_ammo_brand(*item) != SPMSL_NORMAL;
break;
}
lua_pushboolean(ls, branded);
return (1);
}
static const struct luaL_reg item_lib[] =
{
{ "artifact", l_item_artifact },
{ "branded", l_item_branded },
{ "class", l_item_class },
{ "subtype", l_item_subtype },
{ "cursed", l_item_cursed },
{ "worn", l_item_worn },
{ "name", l_item_name },
{ "quantity", l_item_quantity },
{ "inslot", l_item_inslot },
{ "slot", l_item_slot },
{ "ininventory", l_item_ininventory },
{ "inventory", l_item_inventory },
{ "letter_to_index", l_item_letter_to_index },
{ "index_to_letter", l_item_index_to_letter },
{ "swap_slots", l_item_swap_slots },
{ "wield", l_item_wield },
{ "wear", l_item_wear },
{ "puton", l_item_puton },
{ "remove", l_item_remove },
{ "drop", l_item_drop },
{ "pickup", l_item_pickup },
{ "equipped_at", l_item_equipped },
{ "equip_type", l_item_equip_type },
{ "weap_skill", l_item_weap_skill },
{ NULL, NULL },
};
void lua_open_item(lua_State *ls)
{
luaL_openlib(ls, "item", item_lib, 0);
}
/////////////////////////////////////////////////////////////////////
// Food information. Some of this information is spoily (such as whether
// a given chunk is poisonous), but that can't be helped.
//
static int food_do_eat(lua_State *ls)
{
bool eaten = false;
if (!you.turn_is_over)
eaten = eat_food(false);
lua_pushboolean(ls, eaten);
return (1);
}
static int food_prompt_floor(lua_State *ls)
{
bool eaten = false;
if (!you.turn_is_over && (eaten = eat_from_floor()))
burden_change();
lua_pushboolean(ls, eaten);
return (1);
}
static int food_prompt_inventory(lua_State *ls)
{
bool eaten = false;
if (!you.turn_is_over)
eaten = prompt_eat_from_inventory();
lua_pushboolean(ls, eaten);
return (1);
}
static int food_can_eat(lua_State *ls)
{
LUA_ITEM(item, 1);
bool hungercheck = true;
if (lua_isboolean(ls, 2))
hungercheck = lua_toboolean(ls, 2);
bool edible = item && can_ingest(item->base_type,
item->sub_type,
true,
true,
hungercheck);
lua_pushboolean(ls, edible);
return (1);
}
static int item_on_floor(const item_def &item, int x, int y)
{
// Check if the item is on the floor and reachable
for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link)
{
if (&mitm[link] == &item)
return (link);
}
return (NON_ITEM);
}
static bool eat_item(const item_def &item)
{
if (in_inventory(item))
{
eat_from_inventory(item.link);
burden_change();
you.turn_is_over = 1;
return (true);
}
else
{
int ilink = item_on_floor(item, you.x_pos, you.y_pos);
if (ilink != NON_ITEM)
{
eat_floor_item(ilink);
return (true);
}
return (false);
}
}
static int food_eat(lua_State *ls)
{
LUA_ITEM(item, 1);
bool eaten = false;
if (!you.turn_is_over)
{
// When we get down to eating, we don't care if the eating is courtesy
// an un-ided amulet of the gourmand.
bool edible = item && can_ingest(item->base_type,
item->sub_type,
false,
false);
if (edible)
eaten = eat_item(*item);
}
lua_pushboolean(ls, eaten);
return (1);
}
// Giving away chunk type information is spoily.
/*
static int food_chunktype(lua_State *ls)
{
LUA_ITEM(item, 1);
if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
{
int mons_type = item->plus;
int chunk_type = mons_corpse_thingy(mons_type);
const char *schunktype = "unknown";
switch (chunk_type)
{
case CE_HCL:
case CE_MUTAGEN_GOOD:
case CE_MUTAGEN_BAD:
case CE_MUTAGEN_RANDOM:
schunktype = "mutagenic";
break;
case CE_POISONOUS:
schunktype = "poisonous";
break;
case CE_CONTAMINATED:
schunktype = "contaminated";
break;
case CE_CLEAN:
schunktype = "clean";
break;
}
lua_pushstring(ls, schunktype);
}
else
lua_pushnil(ls);
return (1);
}
*/
static int food_rotting(lua_State *ls)
{
LUA_ITEM(item, 1);
bool rotting = false;
if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
{
rotting = item->special < 100;
}
lua_pushboolean(ls, rotting);
return (1);
}
static int food_ischunk(lua_State *ls)
{
LUA_ITEM(item, 1);
lua_pushboolean(ls,
item && item->base_type == OBJ_FOOD
&& item->sub_type == FOOD_CHUNK);
return (1);
}
static const struct luaL_reg food_lib[] =
{
{ "do_eat", food_do_eat },
{ "prompt_floor", food_prompt_floor },
{ "prompt_inventory", food_prompt_inventory },
{ "can_eat", food_can_eat },
{ "eat", food_eat },
{ "rotting", food_rotting },
{ "ischunk", food_ischunk },
{ NULL, NULL },
};
void lua_open_food(lua_State *ls)
{
luaL_openlib(ls, "food", food_lib, 0);
}
/////////////////////////////////////////////////////////////////////
// General game bindings.
//
static int crawl_mpr(lua_State *ls)
{
const char *message = luaL_checkstring(ls, 1);
if (!message)
return (0);
int ch = MSGCH_PLAIN;
const char *channel = lua_tostring(ls, 2);
if (channel)
ch = str_to_channel(channel);
if (ch == -1)
ch = MSGCH_PLAIN;
mpr(message, ch);
return (0);
}
LUAWRAP(crawl_mesclr, mesclr())
LUAWRAP(crawl_redraw_screen, redraw_screen())
static int crawl_input_line(lua_State *ls)
{
// This is arbitrary, but anybody entering so many characters is psychotic.
char linebuf[500];
get_input_line(linebuf, sizeof linebuf);
lua_pushstring(ls, linebuf);
return (1);
}
static int crawl_c_input_line(lua_State *ls)
{
char linebuf[500];
bool valid = cancelable_get_line(linebuf, sizeof linebuf);
if (valid)
lua_pushstring(ls, linebuf);
else
lua_pushnil(ls);
return (1);
}
LUARET1(crawl_getch, number, getch())
LUARET1(crawl_kbhit, number, kbhit())
LUAWRAP(crawl_flush_input, flush_input_buffer(FLUSH_LUA))
static void crawl_sendkeys_proc(lua_State *ls, int argi)
{
if (lua_isstring(ls, argi))
{
const char *keys = luaL_checkstring(ls, argi);
if (!keys)
return;
for ( ; *keys; ++keys)
macro_buf_add(*keys);
}
else if (lua_istable(ls, argi))
{
for (int i = 1; ; ++i)
{
lua_rawgeti(ls, argi, i);
if (lua_isnil(ls, -1))
{
lua_pop(ls, 1);
return;
}
crawl_sendkeys_proc(ls, lua_gettop(ls));
lua_pop(ls, 1);
}
}
else if (lua_isnumber(ls, argi))
{
int key = luaL_checkint(ls, argi);
macro_buf_add(key);
}
}
static int crawl_sendkeys(lua_State *ls)
{
int top = lua_gettop(ls);
for (int i = 1; i <= top; ++i)
crawl_sendkeys_proc(ls, i);
return (0);
}
static int crawl_playsound(lua_State *ls)
{
const char *sf = luaL_checkstring(ls, 1);
if (!sf)
return (0);
play_sound(sf);
return (0);
}
static int crawl_runmacro(lua_State *ls)
{
const char *macroname = luaL_checkstring(ls, 1);
if (!macroname)
return (0);
run_macro(macroname);
return (0);
}
static int crawl_setopt(lua_State *ls)
{
if (!lua_isstring(ls, 1))
return (0);
const char *s = lua_tostring(ls, 1);
if (s)
{
// Note that the conditional script can contain nested Lua[ ]Lua code.
read_options(s, true);
}
return (0);
}
static int crawl_bindkey(lua_State *ls)
{
const char *s = NULL;
if (lua_isstring(ls, 1))
{
s = lua_tostring(ls, 1);
}
if (!s || !lua_isfunction(ls, 2) || !lua_gettop(ls) == 2)
return (0);
lua_pushvalue(ls, 2);
std::string name = clua.setuniqregistry();
if (lua_gettop(ls) != 2)
{
fprintf(stderr, "Stack top has changed!\n");
lua_settop(ls, 2);
}
macro_userfn(s, name.c_str());
return (0);
}
static int crawl_msgch_num(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1);
if (!s)
return (0);
int ch = str_to_channel(s);
if (ch == -1)
return (0);
lua_pushnumber(ls, ch);
return (1);
}
static int crawl_msgch_name(lua_State *ls)
{
int num = luaL_checkint(ls, 1);
std::string name = channel_to_str(num);
lua_pushstring(ls, name.c_str());
return (1);
}
#define REGEX_METATABLE "crawl.regex"
#define MESSF_METATABLE "crawl.messf"
static int crawl_regex(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1);
if (!s)
return (0);
text_pattern **tpudata =
clua_new_userdata< text_pattern* >(ls, REGEX_METATABLE);
if (tpudata)
{
*tpudata = new text_pattern(s);
return (1);
}
return (0);
}
static int crawl_regex_find(lua_State *ls)
{
text_pattern **pattern =
clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
if (!pattern)
return (0);
const char *text = luaL_checkstring(ls, -1);
if (!text)
return (0);
lua_pushboolean(ls, (*pattern)->matches(text));
return (1);
}
static int crawl_regex_gc(lua_State *ls)
{
text_pattern **pattern =
clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
if (pattern)
delete *pattern;
return (0);
}
static const luaL_reg crawl_regex_ops[] =
{
{ "matches", crawl_regex_find },
{ NULL, NULL }
};
static int crawl_message_filter(lua_State *ls)
{
const char *pattern = luaL_checkstring(ls, 1);
if (!pattern)
return (0);
int num = lua_isnumber(ls, 2)? luaL_checkint(ls, 2) : -1;
message_filter **mf =
clua_new_userdata< message_filter* >( ls, MESSF_METATABLE );
if (mf)
{
*mf = new message_filter( num, pattern );
return (1);
}
return (0);
}
static int crawl_messf_matches(lua_State *ls)
{
message_filter **mf =
clua_get_userdata< message_filter* >(ls, MESSF_METATABLE);
if (!mf)
return (0);
const char *pattern = luaL_checkstring(ls, 2);
int ch = luaL_checkint(ls, 3);
if (pattern)
{
bool filt = (*mf)->is_filtered(ch, pattern);
lua_pushboolean(ls, filt);
return (1);
}
return (0);
}
static int crawl_messf_gc(lua_State *ls)
{
message_filter **pattern =
clua_get_userdata< message_filter* >(ls, REGEX_METATABLE);
if (pattern)
delete *pattern;
return (0);
}
static const luaL_reg crawl_messf_ops[] =
{
{ "matches", crawl_messf_matches },
{ NULL, NULL }
};
static int crawl_trim(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1);
if (!s)
return (0);
std::string text = s;
trim_string(text);
lua_pushstring(ls, text.c_str());
return (1);
}
static int crawl_split(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1),
*token = luaL_checkstring(ls, 2);
if (!s || !token)
return (0);
std::vector<std::string> segs = split_string(token, s);
lua_newtable(ls);
for (int i = 0, count = segs.size(); i < count; ++i)
{
lua_pushstring(ls, segs[i].c_str());
lua_rawseti(ls, -2, i + 1);
}
return (1);
}
static const struct luaL_reg crawl_lib[] =
{
{ "mpr", crawl_mpr },
{ "mesclr", crawl_mesclr },
{ "redraw_screen", crawl_redraw_screen },
{ "input_line", crawl_input_line },
{ "c_input_line", crawl_c_input_line},
{ "getch", crawl_getch },
{ "kbhit", crawl_kbhit },
{ "flush_input", crawl_flush_input },
{ "sendkeys", crawl_sendkeys },
{ "playsound", crawl_playsound },
{ "runmacro", crawl_runmacro },
{ "bindkey", crawl_bindkey },
{ "setopt", crawl_setopt },
{ "msgch_num", crawl_msgch_num },
{ "msgch_name", crawl_msgch_name },
{ "regex", crawl_regex },
{ "message_filter", crawl_message_filter },
{ "trim", crawl_trim },
{ "split", crawl_split },
{ NULL, NULL },
};
void lua_open_crawl(lua_State *ls)
{
clua_register_metatable(ls, REGEX_METATABLE, crawl_regex_ops,
crawl_regex_gc);
clua_register_metatable(ls, MESSF_METATABLE, crawl_messf_ops,
crawl_messf_gc);
luaL_openlib(ls, "crawl", crawl_lib, 0);
}
///////////////////////////////////////////////////////////
// File operations
static const struct luaL_reg file_lib[] =
{
{ "write", CLua::file_write },
{ NULL, NULL },
};
void lua_open_file(lua_State *ls)
{
luaL_openlib(ls, "file", file_lib, 0);
}
////////////////////////////////////////////////////////////////
// Option handling
typedef int (*ohandler)(lua_State *ls, const char *name, void *data, bool get);
struct option_handler
{
const char *option;
void *data;
ohandler handler;
};
static int option_hboolean(lua_State *ls, const char *name, void *data,
bool get)
{
if (get)
{
lua_pushboolean(ls, *static_cast<bool*>( data ));
return (1);
}
else
{
if (lua_isboolean(ls, 3))
*static_cast<bool*>( data ) = lua_toboolean(ls, 3);
return (0);
}
}
static option_handler handlers[] =
{
// Boolean options come first
{ "easy_open", &Options.easy_open, option_hboolean },
{ "verbose_dump", &Options.verbose_dump, option_hboolean },
{ "detailed_stat_dump", &Options.detailed_stat_dump, option_hboolean },
{ "colour_map", &Options.colour_map, option_hboolean },
{ "clean_map", &Options.clean_map, option_hboolean },
{ "show_uncursed", &Options.show_uncursed, option_hboolean },
{ "always_greet", &Options.always_greet, option_hboolean },
{ "easy_open", &Options.easy_open, option_hboolean },
{ "easy_armour", &Options.easy_armour, option_hboolean },
{ "easy_butcher", &Options.easy_butcher, option_hboolean },
{ "terse_hand", &Options.terse_hand, option_hboolean },
{ "delay_message_clear", &Options.delay_message_clear, option_hboolean },
{ "no_dark_brand", &Options.no_dark_brand, option_hboolean },
{ "auto_list", &Options.auto_list, option_hboolean },
{ "lowercase_invocations", &Options.lowercase_invocations,
option_hboolean },
{ "pickup_thrown", &Options.pickup_thrown, option_hboolean },
{ "pickup_dropped", &Options.pickup_dropped, option_hboolean },
{ "show_waypoints", &Options.show_waypoints, option_hboolean },
{ "item_colour", &Options.item_colour, option_hboolean },
{ "target_zero_exp", &Options.target_zero_exp, option_hboolean },
{ "target_wrap", &Options.target_wrap, option_hboolean },
{ "easy_exit_menu", &Options.easy_exit_menu, option_hboolean },
{ "dos_use_background_intensity", &Options.dos_use_background_intensity,
option_hboolean },
};
static const option_handler *get_handler(const char *optname)
{
if (optname)
{
for (int i = 0, count = sizeof(handlers) / sizeof(*handlers);
i < count; ++i)
{
if (!strcmp(handlers[i].option, optname))
return &handlers[i];
}
}
return (NULL);
}
static int option_get(lua_State *ls)
{
const char *opt = luaL_checkstring(ls, 2);
if (!opt)
return (0);
// Is this a Lua named option?
game_options::opt_map::iterator i = Options.named_options.find(opt);
if (i != Options.named_options.end())
{
const std::string &ov = i->second;
lua_pushstring(ls, ov.c_str());
return (1);
}
const option_handler *oh = get_handler(opt);
if (oh)
return (oh->handler(ls, opt, oh->data, true));
return (0);
}
static int option_set(lua_State *ls)
{
const char *opt = luaL_checkstring(ls, 2);
if (!opt)
return (0);
const option_handler *oh = get_handler(opt);
if (oh)
oh->handler(ls, opt, oh->data, false);
return (0);
}
#define OPT_METATABLE "options.optaccess"
void lua_open_options(lua_State *ls)
{
int top = lua_gettop(ls);
luaL_newmetatable(ls, OPT_METATABLE);
lua_pushstring(ls, "__index");
lua_pushcfunction(ls, option_get);
lua_settable(ls, -3);
luaL_getmetatable(ls, OPT_METATABLE);
lua_pushstring(ls, "__newindex");
lua_pushcfunction(ls, option_set);
lua_settable(ls, -3);
lua_settop(ls, top);
// Create dummy userdata to front for our metatable
int *dummy = static_cast<int *>( lua_newuserdata(ls, sizeof(int)) );
// Mystic number
*dummy = 42;
luaL_getmetatable(ls, OPT_METATABLE);
lua_setmetatable(ls, -2);
clua.setglobal("options");
}
/////////////////////////////////////////////////////////////////////
// Monster handling
#define MONS_METATABLE "monster.monsaccess"
struct MonsterWrap
{
monsters *mons;
long turn;
};
static int l_mons_name(lua_State *ls, monsters *mons, const char *attr)
{
char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
moname(mons->type, true, DESC_PLAIN, monnamebuf);
lua_pushstring(ls, monnamebuf);
return (1);
}
static int l_mons_x(lua_State *ls, monsters *mons, const char *attr)
{
lua_pushnumber(ls, int(mons->x) - int(you.x_pos));
return (1);
}
static int l_mons_y(lua_State *ls, monsters *mons, const char *attr)
{
lua_pushnumber(ls, int(mons->y) - int(you.y_pos));
return (1);
}
struct MonsAccessor {
const char *attribute;
int (*accessor)(lua_State *ls, monsters *mons, const char *attr);
};
static MonsAccessor mons_attrs[] =
{
{ "name", l_mons_name },
{ "x" , l_mons_x },
{ "y" , l_mons_y },
};
static int monster_get(lua_State *ls)
{
MonsterWrap *mw = clua_get_userdata< MonsterWrap >(ls, MONS_METATABLE);
if (!mw || mw->turn != you.num_turns || !mw->mons)
return (0);
const char *attr = luaL_checkstring(ls, 2);
if (!attr)
return (0);
for (unsigned i = 0; i < sizeof(mons_attrs) / sizeof(mons_attrs[0]); ++i)
{
if (!strcmp(attr, mons_attrs[i].attribute))
return (mons_attrs[i].accessor(ls, mw->mons, attr));
}
return (0);
}
// We currently permit no set operations on monsters
static int monster_set(lua_State *ls)
{
return (0);
}
static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t)
{
if (!t->data)
{
lua_pushnil(ls);
return 0;
}
switch (t->apt)
{
case AIP_HP_LOSS:
{
const ait_hp_loss *ahl = (const ait_hp_loss *) t->data;
lua_pushnumber(ls, ahl->hp);
lua_pushnumber(ls, ahl->hurt_type);
return 1;
}
case AIP_INT:
lua_pushnumber(ls, *(const int *) t->data);
break;
case AIP_STRING:
lua_pushstring(ls, (const char *) t->data);
break;
case AIP_MONSTER:
// FIXME: We're casting away the const...
push_monster(ls, (monsters *) t->data);
break;
default:
lua_pushnil(ls);
break;
}
return 0;
}
static void push_monster(lua_State *ls, monsters *mons)
{
MonsterWrap *mw = clua_new_userdata< MonsterWrap >(ls, MONS_METATABLE);
mw->turn = you.num_turns;
mw->mons = mons;
}
void lua_open_monsters(lua_State *ls)
{
luaL_newmetatable(ls, MONS_METATABLE);
lua_pushstring(ls, "__index");
lua_pushcfunction(ls, monster_get);
lua_settable(ls, -3);
lua_pushstring(ls, "__newindex");
lua_pushcfunction(ls, monster_set);
lua_settable(ls, -3);
// Pop the metatable off the stack.
lua_pop(ls, 1);
}
//////////////////////////////////////////////////////////////////////
// Miscellaneous globals
#define PATTERN_FLUSH_CEILING 100
typedef CHMAP<std::string, text_pattern> pattern_map;
static pattern_map pattern_cache;
static text_pattern &get_text_pattern(const std::string &s, bool checkcase)
{
pattern_map::iterator i = pattern_cache.find(s);
if (i != pattern_cache.end())
return i->second;
if (pattern_cache.size() > PATTERN_FLUSH_CEILING)
pattern_cache.clear();
pattern_cache[s] = text_pattern(s, !checkcase);
return pattern_cache[s];
}
static int lua_pmatch(lua_State *ls)
{
const char *pattern = luaL_checkstring(ls, 1);
if (!pattern)
return (0);
const char *text = luaL_checkstring(ls, 2);
if (!text)
return (0);
bool checkcase = true;
if (lua_isboolean(ls, 3))
checkcase = lua_toboolean(ls, 3);
text_pattern &tp = get_text_pattern(pattern, checkcase);
lua_pushboolean( ls, tp.matches(text) );
return (1);
}
void lua_open_globals(lua_State *ls)
{
lua_pushcfunction(ls, lua_pmatch);
lua_setglobal(ls, "pmatch");
}
////////////////////////////////////////////////////////////////////////
// lua_text_pattern
// We could simplify this a great deal by just using lex and yacc, but I
// don't know if we want to introduce them.
struct lua_pat_op {
const char *token;
const char *luatok;
bool pretext; // Does this follow a pattern?
bool posttext; // Is this followed by a pattern?
};
static lua_pat_op pat_ops[] = {
{ "<<", " ( ", false, true },
{ ">>", " ) ", true, false },
{ "!!", " not ", false, true },
{ "==", " == ", true, true },
{ "^^", " ~= ", true, true },
{ "&&", " and ", true, true },
{ "||", " or ", true, true },
};
unsigned long lua_text_pattern::lfndx = 0L;
bool lua_text_pattern::is_lua_pattern(const std::string &s)
{
for (int i = 0, size = sizeof(pat_ops) / sizeof(*pat_ops);
i < size; ++i)
{
if (s.find(pat_ops[i].token) != std::string::npos)
return (true);
}
return (false);
}
lua_text_pattern::lua_text_pattern(const std::string &_pattern)
: translated(false), isvalid(true), pattern(_pattern), lua_fn_name()
{
lua_fn_name = new_fn_name();
}
lua_text_pattern::~lua_text_pattern()
{
if (translated && !lua_fn_name.empty())
{
lua_State *ls = clua;
if (ls)
{
lua_pushnil(ls);
clua.setglobal(lua_fn_name.c_str());
}
}
}
bool lua_text_pattern::valid() const
{
return translated? isvalid : translate();
}
bool lua_text_pattern::matches( const std::string &s ) const
{
if (isvalid && !translated)
translate();
if (!isvalid)
return (false);
return clua.callbooleanfn(false, lua_fn_name.c_str(), "s", s.c_str());
}
void lua_text_pattern::pre_pattern(std::string &pat, std::string &fn) const
{
// Trim trailing spaces
pat.erase( pat.find_last_not_of(" \t\n\r") + 1 );
fn += " pmatch([[";
fn += pat;
fn += "]], text, false) ";
pat.clear();
}
void lua_text_pattern::post_pattern(std::string &pat, std::string &fn) const
{
pat.erase( 0, pat.find_first_not_of(" \t\n\r") );
fn += " pmatch([[";
fn += pat;
fn += "]], text, false) ";
pat.clear();
}
std::string lua_text_pattern::new_fn_name()
{
char buf[100];
snprintf(buf, sizeof buf, "__ch_stash_search_%lu", lfndx++);
return (buf);
}
bool lua_text_pattern::translate() const
{
if (translated || !isvalid)
return false;
std::string textp;
std::string luafn;
const lua_pat_op *currop = NULL;
for (std::string::size_type i = 0; i < pattern.length(); ++i)
{
bool match = false;
for (unsigned p = 0; p < sizeof pat_ops / sizeof *pat_ops; ++p)
{
const lua_pat_op &lop = pat_ops[p];
if (pattern.find(lop.token, i) == i)
{
match = true;
if (lop.pretext && (!currop || currop->posttext))
{
if (currop)
textp.erase(0, textp.find_first_not_of(" \r\n\t"));
pre_pattern(textp, luafn);
}
currop = &lop;
luafn += lop.luatok;
i += strlen( lop.token ) - 1;
break;
}
}
if (match)
continue;
textp += pattern[i];
}
if (currop && currop->posttext)
post_pattern(textp, luafn);
luafn = "function " + lua_fn_name + "(text) return " + luafn + " end";
const_cast<lua_text_pattern *>(this)->translated = true;
int err = clua.execstring( luafn.c_str(), "stash-search" );
if (err)
{
lua_text_pattern *self = const_cast<lua_text_pattern *>(this);
self->isvalid = self->translated = false;
}
return translated;
}
#endif // CLUA_BINDINGS
// Grabs a screenshot and appends the text into the given std::string,
// using several ugly hacks in the process.
//---------------------------------------------------------------
static void dump_screenshot( std::string &text )
{
// A little message history:
if (Options.dump_message_count > 0)
{
text += " Last Messages" EOL EOL;
text += get_last_messages(Options.dump_message_count);
}
FixedVector < char, 1500 > buffy; //[800]; //392];
int bufcount = 0;
unsigned short ch, color;
int count_x, count_y;
// Urg, ugly screen capture. CVS Crawl may have a better way of doing this,
// but until the next release...
for (count_y = (you.y_pos - 8); (count_y < you.y_pos + 9); count_y++)
{
bufcount += 8;
for (count_x = (you.x_pos - 8); (count_x < you.x_pos + 9); count_x++)
{
if (count_x == you.x_pos && count_y == you.y_pos)
{
extern unsigned char your_sign;
ch = your_sign;
}
else
{
unsigned int object = env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9];
get_non_ibm_symbol(object, &ch, &color);
}
buffy[bufcount++] = (char) ch;
}
bufcount += 8;
}
int maxbuf = bufcount;
bufcount = 0;
for (count_y = 0; count_y < 17; count_y++)
{
for (count_x = 0; count_x < 33; count_x++)
{
if (count_x + you.x_pos - 17 < 3
|| count_y + you.y_pos - 9 < 3
|| count_x + you.x_pos - 14 > (GXM - 3)
|| count_y + you.y_pos - 9 > (GYM - 3))
{
buffy[bufcount++] = ' ';
continue;
}
if (count_x >= 8 && count_x <= 24 && count_y >= 0
&& count_y <= 16 && buffy[bufcount] != 0)
{
bufcount++;
continue;
}
unsigned char envc = (unsigned char)
env.map[count_x + you.x_pos - 17]
[count_y + you.y_pos - 9];
if (envc)
{
// If it's printable, use it directly.
if (envc < 127 && envc >= 32)
ch = envc;
else
{
// Otherwise get what's on the grid and get an ASCII
// character for that.
unsigned int object = grd[count_x + you.x_pos - 16]
[count_y + you.y_pos - 8];
// Special case secret doors so that monsters that open
// doors out of hero's LOS don't reveal the secret door in
// the dump
if (envc == mapch2(DNGN_SECRET_DOOR))
object = DNGN_SECRET_DOOR;
get_non_ibm_symbol(object, &ch, &color);
}
buffy[bufcount++] = (char) ch;
}
else
{
buffy[bufcount++] = ' ';
}
}
}
if (bufcount > maxbuf) maxbuf = bufcount;
while (maxbuf > 0 && (!buffy[maxbuf - 1] || buffy[maxbuf - 1] == ' '))
--maxbuf;
// 33 columns and a null terminator. More hardcoding. :-(
char buf[34];
char *s = buf;
bool leadblanks = true;
for (int i = 0; i < maxbuf; )
{
*s++ = buffy[i]? buffy[i] : ' ';
++i;
if (!(i % 33) || i >= maxbuf)
{
*s = 0;
while (s > buf && *--s == ' ')
*s = 0;
if (s == buf && !*s && leadblanks)
continue;
leadblanks = false;
text += buf;
text += EOL;
s = buf;
}
}
}
//---------------------------------------------------------------
//
//---------------------------------------------------------------
static void dump_stats2( std::string & text, bool calc_unid)
{
char buffer[25*3][45];
char str_pass[80];
char* ptr_n;
get_full_detail(&buffer[0][0], calc_unid);
for (int i = 0; i < 25; i++)
{
ptr_n = &buffer[i][0];
if (buffer[i+25][0] == '\0' && buffer[i+50][0] == '\0')
snprintf(&str_pass[0], 45, "%s", ptr_n);
else
snprintf(&str_pass[0], 45, "%-32s", ptr_n);
text += str_pass;
ptr_n = &buffer[i+25][0];
if (buffer[i+50][0] == '\0')
snprintf(&str_pass[0], 45, "%s", ptr_n);
else
snprintf(&str_pass[0], 45, "%-20s", ptr_n);
text += str_pass;
ptr_n = &buffer[i+50][0];
if (buffer[i+50][0] != '\0')
{
snprintf(&str_pass[0], 45, "%s", ptr_n);
text += str_pass;
}
text += EOL;
}
text += EOL EOL;
}
//---------------------------------------------------------------
//
extern char id[4][50]; // itemname.cc
static bool dump_item_origin(const item_def &item, int value)
{
#define fs(x) (flags & (x))
const int flags = Options.dump_item_origins;
if (flags == IODS_EVERYTHING)
return (true);
if (fs(IODS_ARTIFACTS)
&& (is_random_artefact(item) || is_fixed_artefact(item))
&& item_ident(item, ISFLAG_KNOW_PROPERTIES))
return (true);
if (fs(IODS_EGO_ARMOUR) && item.base_type == OBJ_ARMOUR
&& item_ident( item, ISFLAG_KNOW_TYPE ))
{
const int spec_ench = get_armour_ego_type( item );
return (spec_ench != SPARM_NORMAL);
}
if (fs(IODS_EGO_WEAPON) && item.base_type == OBJ_WEAPONS
&& item_ident( item, ISFLAG_KNOW_TYPE ))
return (get_weapon_brand(item) != SPWPN_NORMAL);
if (fs(IODS_JEWELLERY) && item.base_type == OBJ_JEWELLERY)
return (true);
if (fs(IODS_RUNES) && item.base_type == OBJ_MISCELLANY
&& item.sub_type == MISC_RUNE_OF_ZOT)
return (true);
if (fs(IODS_RODS) && item.base_type == OBJ_STAVES
&& item_is_rod(item))
return (true);
if (fs(IODS_STAVES) && item.base_type == OBJ_STAVES
&& !item_is_rod(item))
return (true);
if (fs(IODS_BOOKS) && item.base_type == OBJ_BOOKS)
return (true);
const int refpr = Options.dump_item_origin_price;
if (refpr == -1)
return (false);
if (value == -1)
value = item_value( item, id, false );
return (value >= refpr);
#undef fs
}
#ifdef STASH_TRACKING
strncat(stash_file_name, ".lst", kPathLen);
stashes.dump(stash_file_name);
#endif
}
#ifdef STASH_TRACKING
else
{
// Grr. Filename is morgue.txt, it needs to be morgue.lst
int len = strlen(stash_file_name);
stash_file_name[len - 3] = 'l';
stash_file_name[len - 2] = 's';
// Fully identified stash dump.
stashes.dump(stash_file_name, true);
}
#endif
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:
case CONTROL('W'):
travel_cache.add_waypoint();
break;
case CMD_INTERLEVEL_TRAVEL:
case CONTROL('G'):
if (!can_travel_interlevel())
{
mpr("Sorry, you can't auto-travel out of here.");
break;
}
start_translevel_travel();
redraw_screen();
break;
case CONTROL('O'):
case CMD_EXPLORE:
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
{
mpr("It would help if you knew where you were, first.");
break;
}
// Start exploring
start_explore();
/*
* File: Kills.h
* Summary: Tracks monsters the player has killed.
* Written by: Darshan Shaligram
*/
#ifndef KILLS_H
#define KILLS_H
#include <vector>
#include <string>
#include <map>
#include <stdio.h>
#include "enum.h"
struct monsters;
// Not intended for external use!
struct kill_monster_desc
{
kill_monster_desc(const struct monsters *);
kill_monster_desc() { }
void save(FILE*) const;
void load(FILE*);
enum name_modifier
{
M_NORMAL, M_ZOMBIE, M_SKELETON, M_SIMULACRUM, M_SPECTRE,
M_SHAPESHIFTER // A shapeshifter pretending to be 'monnum'
};
int monnum; // Number of the beast
name_modifier modifier; // Nature of the beast
struct less_than
{
bool operator () ( const kill_monster_desc &m1,
const kill_monster_desc &m2) const
{
return m1.monnum < m2.monnum ||
(m1.monnum == m2.monnum && m1.modifier < m2.modifier);
}
};
};
#define PLACE_LIMIT 5 // How many unique kill places we're prepared to track
class kill_def
{
public:
kill_def(const struct monsters *mon);
kill_def() : kills(0), exp(0)
{
// This object just says to the world that it's uninitialized
}
void save(FILE*) const;
void load(FILE*);
void add_kill(const struct monsters *mon, unsigned short place);
void add_place(unsigned short place, bool force = false);
void merge(const kill_def &k, bool unique_monster);
std::string info(const kill_monster_desc &md) const;
std::string base_name(const kill_monster_desc &md) const;
unsigned short kills; // How many kills does the player have?
int exp; // Experience gained for slaying the beast.
// Only set *once*, even for shapeshifters.
std::vector<unsigned short> places; // Places where we've killed the beast.
private:
std::string append_places(const kill_monster_desc &md,
const std::string &name) const;
};
// Ghosts and random Pandemonium demons.
class kill_ghost
{
public:
kill_ghost(const struct monsters *mon);
kill_ghost() { }
void save(FILE*) const;
void load(FILE*);
std::string info() const;
std::string ghost_name;
int exp;
unsigned short place;
};
// This is the structure that Lua sees.
struct kill_exp
{
int nkills;
int exp;
std::string base_name;
std::string desc;
int monnum; // Number of the beast
int modifier; // Nature of the beast
std::vector<unsigned short> places;
kill_exp(const kill_def &k, const kill_monster_desc &md)
: nkills(k.kills), exp(k.exp), base_name(k.base_name(md)),
desc(k.info(md)),
monnum(md.monnum), modifier(md.modifier)
{
places = k.places;
}
kill_exp(const kill_ghost &kg)
: nkills(1), exp(kg.exp), base_name(), desc(kg.info()),
monnum(-1), modifier(0)
{
places.push_back(kg.place);
}
// operator< is implemented for a descending sort.
bool operator < ( const kill_exp &b) const
{
return exp == b.exp? (base_name < b.base_name) : (exp > b.exp);
}
};
class Kills
{
public:
void record_kill(const monsters *mon);
void merge(const Kills &k);
bool empty() const;
void save(FILE*) const;
void load(FILE*);
long get_kills(std::vector<kill_exp> &v) const;
private:
typedef std::map<kill_monster_desc,
kill_def,
kill_monster_desc::less_than> kill_map;
typedef std::vector<kill_ghost> ghost_vec;
kill_map kills;
ghost_vec ghosts;
void record_ghost_kill(const struct monsters *mon);
};
class KillMaster
{
public:
void record_kill(const monsters *mon, int killer, bool ispet);
bool empty() const;
void save(FILE*) const;
void load(FILE*);
std::string kill_info() const;
private:
const char *category_name(KillCategory kc) const;
Kills categorized_kills[KC_NCATEGORIES];
private:
void add_kill_info(std::string &, std::vector<kill_exp> &,
long count, const char *c, bool separator)
const;
};
unsigned short get_packed_place();
unsigned short get_packed_place( unsigned char branch, int subdepth,
char level_type );
std::string short_place_name(unsigned short place);
enum KILL_DUMP_OPTIONS
{
KDO_NO_PLACES, // Don't dump places at all
KDO_ONE_PLACE, // Show places only for single kills and uniques.
KDO_ALL_PLACES // Show all available place information
};
#endif
/*
* File: Kills.cc
* Summary: Player kill tracking
* Written by: Darshan Shaligram
*/
#include "AppHdr.h"
#include "chardump.h"
#include "describe.h"
#include "mon-util.h"
#include "files.h"
#include "itemname.h"
#include "travel.h"
#include "tags.h"
#include "Kills.h"
#include "clua.h"
#include <algorithm>
#define KILLS_MAJOR_VERSION 4
#define KILLS_MINOR_VERSION 1
#ifdef CLUA_BINDINGS
static void kill_lua_filltable(std::vector<kill_exp> &v);
#endif
unsigned short get_packed_place( unsigned char branch, int subdepth,
char level_type )
{
unsigned short place = (unsigned short)
( (branch << 8) | subdepth );
if (level_type == LEVEL_ABYSS || level_type == LEVEL_PANDEMONIUM
|| level_type == LEVEL_LABYRINTH)
place = (unsigned short) ( (level_type << 8) | 0xFF );
return place;
}
unsigned short get_packed_place()
{
return get_packed_place( you.where_are_you,
subdungeon_depth(you.where_are_you, you.your_level),
you.level_type );
}
///////////////////////////////////////////////////////////////////////////
// KillMaster
//
const char *kill_category_names[] =
{
"you",
"collateral kills",
"others",
};
const char *KillMaster::category_name(KillCategory kc) const
{
if (kc >= KC_YOU && kc < KC_NCATEGORIES)
return (kill_category_names[kc]);
return (NULL);
}
bool KillMaster::empty() const
{
for (int i = 0; i < KC_NCATEGORIES; ++i)
if (!categorized_kills[i].empty())
return (false);
return (true);
}
void KillMaster::save(FILE *file) const
{
// Write the version of the kills file
writeByte(file, KILLS_MAJOR_VERSION);
writeByte(file, KILLS_MINOR_VERSION);
for (int i = 0; i < KC_NCATEGORIES; ++i)
categorized_kills[i].save(file);
}
void KillMaster::load(FILE *file)
{
unsigned char major = readByte(file),
minor = readByte(file);
if (major != KILLS_MAJOR_VERSION ||
(minor != KILLS_MINOR_VERSION && minor > 0))
return ;
for (int i = 0; i < KC_NCATEGORIES; ++i)
{
categorized_kills[i].load(file);
if (!minor)
break;
}
}
void KillMaster::record_kill(const monsters *mon, int killer, bool ispet)
{
KillCategory kc =
(killer == KILL_YOU || killer == KILL_YOU_MISSILE)? KC_YOU :
(ispet)? KC_FRIENDLY :
KC_OTHER;
categorized_kills[kc].record_kill(mon);
}
std::string KillMaster::kill_info() const
{
if (empty())
return ("");
std::string killtext;
bool needseparator = false;
int categories = 0;
long grandtotal = 0L;
Kills catkills[KC_NCATEGORIES];
for (int i = 0; i < KC_NCATEGORIES; ++i)
{
int targ = Options.kill_map[i];
catkills[targ].merge( categorized_kills[i] );
}
for (int i = KC_YOU; i < KC_NCATEGORIES; ++i)
{
if (catkills[i].empty())
continue;
categories++;
std::vector<kill_exp> kills;
long count = catkills[i].get_kills(kills);
grandtotal += count;
add_kill_info( killtext,
kills,
count,
i == KC_YOU? NULL :
category_name((KillCategory) i),
needseparator );
needseparator = true;
}
std::string grandt;
if (categories > 1)
{
char buf[200];
snprintf(buf, sizeof buf,
"Grand Total: %ld creatures vanquished",
grandtotal);
grandt = buf;
}
#ifdef CLUA_BINDINGS
// Call the kill dump Lua function with null a, to tell it we're done.
if (!clua.callfn("c_kill_list", "ss", NULL, grandt.c_str()))
#endif
{
// We can sum up ourselves, if Lua doesn't want to.
// FIXME: I'm not happy with the looks/wording of the grand total
// count.
if (categories > 1)
{
// Give ourselves a newline first
killtext += EOL;
killtext += grandt + EOL;
}
}
return killtext;
}
void KillMaster::add_kill_info(std::string &killtext,
std::vector<kill_exp> &kills,
long count,
const char *category,
bool separator) const
{
#ifdef CLUA_BINDINGS
// Set a pointer to killtext as a Lua global
lua_pushlightuserdata(clua.state(), &killtext);
clua.setregistry("cr_skill");
// Populate a Lua table with kill_exp structs, in the default order,
// and leave the table on the top of the Lua stack.
kill_lua_filltable(kills);
if (category)
lua_pushstring(clua, category);
else
lua_pushnil(clua);
lua_pushboolean(clua, separator);
if (!clua.callfn("c_kill_list", 3, 0))
#endif
{
#ifdef CLUA_BINDINGS
if (clua.error.length())
{
killtext += "Lua error:\n";
killtext += clua.error + "\n\n";
}
#endif
if (separator)
killtext += EOL;
killtext += "Vanquished Creatures";
if (category)
killtext += std::string(" (") + category + ")";
killtext += EOL;
for (int i = 0, sz = kills.size(); i < sz; ++i)
{
killtext += " " + kills[i].desc;
killtext += EOL;
}
{
char numbuf[100];
snprintf(numbuf, sizeof numbuf,
"%ld creature%s vanquished." EOL, count,
count > 1? "s" : "");
killtext += numbuf;
}
}
}
///////////////////////////////////////////////////////////////////////////
bool Kills::empty() const
{
return kills.empty() && ghosts.empty();
}
void Kills::merge(const Kills &k)
{
ghosts.insert( ghosts.end(), k.ghosts.begin(), k.ghosts.end() );
// Regular kills are messier to merge.
for (kill_map::const_iterator i = k.kills.begin();
i != k.kills.end(); ++i)
{
const kill_monster_desc &kmd = i->first;
kill_def &k = kills[kmd];
const kill_def &ko = i->second;
bool uniq = mons_is_unique(kmd.monnum);
k.merge(ko, uniq);
}
}
void Kills::record_kill(const struct monsters *mon)
{
// Handle player ghosts separately.
if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON)
{
record_ghost_kill(mon);
return ;
}
// Normal monsters
// Create a descriptor
kill_monster_desc descriptor = mon;
kill_def &k = kills[descriptor];
if (k.kills)
k.add_kill(mon, get_packed_place());
else
k = kill_def(mon);
}
long Kills::get_kills(std::vector<kill_exp> &all_kills) const
{
long count = 0;
kill_map::const_iterator iter = kills.begin();
for (; iter != kills.end(); ++iter)
{
const kill_monster_desc &md = iter->first;
const kill_def &k = iter->second;
all_kills.push_back( kill_exp(k, md) );
count += k.kills;
}
ghost_vec::const_iterator gi = ghosts.begin();
for (; gi != ghosts.end(); ++gi)
{
all_kills.push_back( kill_exp(*gi) );
}
count += ghosts.size();
std::sort(all_kills.begin(), all_kills.end());
return (count);
}
// Takes a packed 'place' and returns a compact stringified place name.
// XXX: This is done in several other places; a unified function to
// describe places would be nice.
std::string short_place_name(unsigned short place)
{
unsigned char branch = (unsigned char) ((place >> 8) & 0xFF);
int lev = place & 0xFF;
const char *s;
bool level_num = false;
if (lev == 0xFF)
{
switch (branch)
{
case LEVEL_ABYSS:
s = "Abyss";
break;
case LEVEL_PANDEMONIUM:
s = "Pan";
break;
case LEVEL_LABYRINTH:
s = "Lab";
break;
default:
s = "Buggy Badlands";
break;
}
}
else
{
switch (branch)
{
case BRANCH_VESTIBULE_OF_HELL:
s = "Hell";
break;
case BRANCH_HALL_OF_BLADES:
s = "Blade";
break;
case BRANCH_ECUMENICAL_TEMPLE:
s = "Temple";
break;
default:
level_num = true;
s = (branch == BRANCH_DIS) ? "Dis:" :
(branch == BRANCH_GEHENNA) ? "Geh:" :
(branch == BRANCH_COCYTUS) ? "Coc:" :
(branch == BRANCH_TARTARUS) ? "Tar:" :
(branch == BRANCH_ORCISH_MINES) ? "Orc:" :
(branch == BRANCH_HIVE) ? "Hive:" :
(branch == BRANCH_LAIR) ? "Lair:" :
(branch == BRANCH_SLIME_PITS) ? "Slime:" :
(branch == BRANCH_VAULTS) ? "Vault:" :
(branch == BRANCH_CRYPT) ? "Crypt:" :
(branch == BRANCH_HALL_OF_ZOT) ? "Zot:" :
(branch == BRANCH_SNAKE_PIT) ? "Snake:" :
(branch == BRANCH_ELVEN_HALLS) ? "Elf:" :
(branch == BRANCH_TOMB) ? "Tomb:" :
(branch == BRANCH_SWAMP) ? "Swamp:" : "D:";
break;
}
}
std::string pl = s;
if (level_num)
{
char buf[20];
snprintf(buf, sizeof buf, "%d", lev);
pl += buf;
}
return pl;
}
void Kills::save(FILE *file) const
{
// How many kill records do we have?
writeLong(file, kills.size());
kill_map::const_iterator iter = kills.begin();
for ( ; iter != kills.end(); ++iter)
{
iter->first.save(file);
iter->second.save(file);
}
// How many ghosts do we have?
writeShort(file, ghosts.size());
for (ghost_vec::const_iterator iter = ghosts.begin();
iter != ghosts.end(); ++iter)
{
iter->save(file);
}
}
void Kills::load(FILE *file)
{
// How many kill records?
long kill_count = readLong(file);
kills.clear();
for (long i = 0; i < kill_count; ++i)
{
kill_monster_desc md;
md.load(file);
kills[md].load(file);
}
short ghost_count = readShort(file);
ghosts.clear();
for (short i = 0; i < ghost_count; ++i)
{
kill_ghost kg;
kg.load(file);
ghosts.push_back(kg);
}
}
void Kills::record_ghost_kill(const struct monsters *mon)
{
kill_ghost ghost(mon);
ghosts.push_back(ghost);
}
kill_def::kill_def(const struct monsters *mon) : kills(0), exp(0)
{
exp = exper_value( (struct monsters *) mon);
add_kill(mon, get_packed_place());
}
static bool ends_with(const std::string &s, const char *suffix)
{
std::string other = suffix;
if (s.length() < other.length()) return false;
return (s.substr(s.length() - other.length()) == other);
}
static bool ends_with(const std::string &s, const char *suffixes[])
{
if (!suffixes) return false;
for ( ; *suffixes; suffixes++)
{
if (ends_with(s, *suffixes))
return true;
}
return false;
}
// For monster names ending with these suffixes, we pluralize directly without
// attempting to use the "of" rule. For instance:
//
// moth of wrath => moths of wrath but
// moth of wrath zombie => moth of wrath zombies.
//
// This is not necessary right now, since there are currently no monsters that
// require this special treatment (no monster with 'of' in its name is eligible
// for zombies or skeletons).
static const char *modifier_suffixes[] =
{
"zombie", "skeleton", "simulacrum", NULL,
};
// Pluralizes a monster name. This'll need to be updated for correctness
// whenever new monsters are added.
static std::string pluralize(const std::string &name,
const char *no_of[] = NULL)
{
std::string::size_type pos;
// Pluralize first word of names like 'eye of draining', but only if the
// whole name is not suffixed by a modifier, such as 'zombie' or 'skeleton'
if ( (pos = name.find(" of ")) != std::string::npos
&& !ends_with(name, no_of) )
{
return pluralize(name.substr(0, pos)) + name.substr(pos);
}
else if (ends_with(name, "us"))
// Fungus, ufetubus, for instance.
return name.substr(0, name.length() - 2) + "i";
else if (ends_with(name, "larva") || ends_with(name, "amoeba"))
// Giant amoebae sounds a little weird, to tell the truth.
return name + "e";
else if (ends_with(name, "ex"))
// Vortex; vortexes is legal, but the classic plural is cooler.
return name.substr(0, name.length() - 2) + "ices";
else if (ends_with(name, "cyclops"))
return name.substr(0, name.length() - 1) + "es";
else if (ends_with(name, "y"))
return name.substr(0, name.length() - 1) + "ies";
else if (ends_with(name, "lf"))
// Elf, wolf. Dwarfs can stay dwarfs, if there were dwarfs.
return name.substr(0, name.length() - 1) + "ves";
else if (ends_with(name, "mage"))
// mage -> magi
return name.substr(0, name.length() - 1) + "i";
else if ( ends_with(name, "sheep") || ends_with(name, "manes")
|| ends_with(name, "fish") )
// Maybe we should generalise 'manes' to ends_with("es")?
return name;
else if (ends_with(name, "ch") || ends_with(name, "sh")
|| ends_with(name, "x"))
// To handle cockroaches, fish and sphinxes. Fish will be netted by
// the previous check anyway.
return name + "es";
else if (ends_with(name, "um"))
// simulacrum -> simulacra
return name.substr(0, name.length() - 2) + "a";
else if (ends_with(name, "efreet"))
// efreet -> efreeti. Not sure this is correct.
return name + "i";
return name + "s";
}
// Naively prefix A/an to a monster name. At the moment, we don't have monster
// names that demand more sophistication (maybe ynoxinul - don't know how
// that's pronounced).
static std::string article_a(const std::string &name)
{
if (!name.length()) return name;
switch (name[0])
{
case 'a': case 'e': case 'i': case 'o': case 'u':
case 'A': case 'E': case 'I': case 'O': case 'U':
return "An " + name;
default:
return "A " + name;
}
}
// For a non-unique monster, prefixes a suitable article if we have only one
// kill, else prefixes a kill count and pluralizes the monster name.
static std::string n_names(const std::string &name, int n)
{
if (n > 1)
{
char buf[20];
snprintf(buf, sizeof buf, "%d ", n);
return buf + pluralize(name, modifier_suffixes);
}
else
return article_a(name);
}
// Returns a string describing the number of times a unique has been killed.
// Currently required only for Boris.
//
static std::string kill_times(int kills)
{
char buf[50];
switch (kills)
{
case 1:
strcpy(buf, " (once)");
break;
case 2:
strcpy(buf, " (twice)");
break;
case 3:
strcpy(buf, " (thrice)");
break;
default:
snprintf(buf, sizeof buf, " (%d times)", kills);
break;
}
return std::string(buf);
}
void kill_def::merge(const kill_def &k, bool uniq)
{
if (!kills)
{
*this = k;
}
else
{
kills += k.kills;
for (int i = 0, size = k.places.size(); i < size; ++i)
add_place(k.places[i], uniq);
}
}
void kill_def::add_kill(const struct monsters *mon, unsigned short place)
{
kills++;
add_place(place, mons_is_unique(mon->type));
}
void kill_def::add_place(unsigned short place, bool force)
{
for (unsigned i = 0; i < places.size(); ++i)
if (places[i] == place) return;
if (force || places.size() < PLACE_LIMIT)
places.push_back(place);
}
std::string kill_def::base_name(const kill_monster_desc &md) const
{
char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
moname(md.monnum, true, DESC_PLAIN, monnamebuf);
std::string name = monnamebuf;
switch (md.modifier)
{
case kill_monster_desc::M_ZOMBIE:
name += " zombie";
break;
case kill_monster_desc::M_SKELETON:
name += " skeleton";
break;
case kill_monster_desc::M_SIMULACRUM:
name += " simulacrum";
break;
case kill_monster_desc::M_SPECTRE:
name = "spectral " + name;
break;
default:
// Silence compiler warning about not handling M_NORMAL and
// M_SHAPESHIFTER
break;
}
switch (md.monnum)
{
case MONS_ABOMINATION_LARGE:
name = "large " + name;
break;
case MONS_ABOMINATION_SMALL:
// Do nothing
break;
case MONS_RAKSHASA_FAKE:
name = "illusory " + name;
break;
}
return name;
}
std::string kill_def::info(const kill_monster_desc &md) const
{
std::string name = base_name(md);
if (!mons_is_unique(md.monnum))
{
// Pluralize as needed
name = n_names(name, kills);
// We brand shapeshifters with the (shapeshifter) qualifier. This
// has to be done after doing pluralize(), else we get very odd plurals
// :)
if (md.modifier == kill_monster_desc::M_SHAPESHIFTER &&
md.monnum != MONS_SHAPESHIFTER &&
md.monnum != MONS_GLOWING_SHAPESHIFTER)
name += " (shapeshifter)";
}
else if (kills > 1)
{
// Aha! A resurrected unique
name += kill_times(kills);
}
// What places we killed this type of monster
return append_places(md, name);
}
std::string kill_def::append_places(const kill_monster_desc &md,
const std::string &name) const
{
if (Options.dump_kill_places == KDO_NO_PLACES) return name;
int nplaces = places.size();
if ( nplaces == 1 || mons_is_unique(md.monnum)
|| Options.dump_kill_places == KDO_ALL_PLACES )
{
std::string augmented = name;
augmented += " (";
for (std::vector<unsigned short>::const_iterator iter = places.begin();
iter != places.end(); ++iter)
{
if (iter != places.begin())
augmented += " ";
augmented += short_place_name(*iter);
}
augmented += ")";
return augmented;
}
return name;
}
void kill_def::save(FILE *file) const
{
writeShort(file, kills);
writeShort(file, exp);
writeShort(file, places.size());
for (std::vector<unsigned short>::const_iterator iter = places.begin();
iter != places.end(); ++iter)
{
writeShort(file, *iter);
}
}
void kill_def::load(FILE *file)
{
kills = (unsigned short) readShort(file);
exp = readShort(file);
places.clear();
short place_count = readShort(file);
for (short i = 0; i < place_count; ++i)
{
places.push_back((unsigned short) readShort(file));
}
}
kill_ghost::kill_ghost(const struct monsters *mon)
{
exp = exper_value( (struct monsters *) mon);
place = get_packed_place();
ghost_name = ghost.name;
// Check whether this is really a ghost, since we also have to handle
// the Pandemonic demons.
if (mon->type == MONS_PLAYER_GHOST)
ghost_name = "The ghost of " + ghost_description(true);
}
std::string kill_ghost::info() const
{
return ghost_name +
(Options.dump_kill_places != KDO_NO_PLACES?
" (" + short_place_name(place) + ")" : std::string(""));
}
void kill_ghost::save(FILE *file) const
{
writeString(file, ghost_name);
writeShort(file, (unsigned short) exp);
writeShort(file, place);
}
void kill_ghost::load(FILE *file)
{
ghost_name = readString(file);
exp = readShort(file);
place = (unsigned short) readShort(file);
}
kill_monster_desc::kill_monster_desc(const monsters *mon)
{
// TODO: We need to understand how shapeshifters are handled.
monnum = mon->type;
modifier = M_NORMAL;
switch (mon->type)
{
case MONS_ZOMBIE_LARGE: case MONS_ZOMBIE_SMALL:
modifier = M_ZOMBIE;
break;
case MONS_SKELETON_LARGE: case MONS_SKELETON_SMALL:
modifier = M_SKELETON;
break;
case MONS_SIMULACRUM_LARGE: case MONS_SIMULACRUM_SMALL:
modifier = M_SIMULACRUM;
break;
case MONS_SPECTRAL_THING:
modifier = M_SPECTRE;
break;
}
if (modifier != M_NORMAL) monnum = mon->number;
if (mons_has_ench((struct monsters *) mon, ENCH_SHAPESHIFTER) ||
mons_has_ench((struct monsters *) mon, ENCH_GLOWING_SHAPESHIFTER))
modifier = M_SHAPESHIFTER;
// XXX: Ugly hack - merge all mimics into one mimic record.
if (monnum >= MONS_GOLD_MIMIC && monnum <= MONS_POTION_MIMIC)
monnum = MONS_WEAPON_MIMIC;
}
void kill_monster_desc::save(FILE *file) const
{
writeShort(file, (short) monnum);
writeShort(file, (short) modifier);
}
void kill_monster_desc::load(FILE *file)
{
monnum = (int) readShort(file);
modifier = (name_modifier) readShort(file);
}
#ifdef CLUA_BINDINGS
///////////////////////////////////////////////////////////////////////////
// Kill Lua interface
//
#define KILLEXP_ACCESS(name, type, field) \
static int kill_lualc_##name(lua_State *ls) { \
if (!lua_islightuserdata(ls, 1)) { \
luaL_argerror(ls, 1, "Unexpected argument type"); \
return 0; \
} \
\
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) ); \
if (ke) { \
lua_push##type(ls, ke->field); \
return 1; \
} \
return 0; \
}
KILLEXP_ACCESS(nkills, number, nkills)
KILLEXP_ACCESS(exp, number, exp)
KILLEXP_ACCESS(base_name, string, base_name.c_str())
KILLEXP_ACCESS(desc, string, desc.c_str())
KILLEXP_ACCESS(monnum, number, monnum)
KILLEXP_ACCESS(isghost, boolean,
monnum == -1 &&
ke->desc.find("The ghost of") != std::string::npos)
KILLEXP_ACCESS(ispandemon, boolean,
monnum == -1 &&
ke->desc.find("The ghost of") == std::string::npos)
KILLEXP_ACCESS(isunique, boolean,
monnum != -1 && mons_is_unique(ke->monnum))
static int kill_lualc_modifier(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
if (ke)
{
const char *modifier;
switch (ke->modifier)
{
case kill_monster_desc::M_ZOMBIE:
modifier = "zombie";
break;
case kill_monster_desc::M_SKELETON:
modifier = "skeleton";
break;
case kill_monster_desc::M_SIMULACRUM:
modifier = "simulacrum";
break;
case kill_monster_desc::M_SPECTRE:
modifier = "spectre";
break;
case kill_monster_desc::M_SHAPESHIFTER:
modifier = "shapeshifter";
break;
default:
modifier = "";
break;
}
lua_pushstring(ls, modifier);
return 1;
}
return 0;
}
static int kill_lualc_places(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
if (ke)
{
lua_newtable(ls);
for (int i = 0, count = ke->places.size(); i < count; ++i)
{
lua_pushnumber(ls, ke->places[i]);
lua_rawseti(ls, -2, i + 1);
}
return 1;
}
return 0;
}
static int kill_lualc_place_name(lua_State *ls)
{
int num = luaL_checkint(ls, 1);
std::string plname = short_place_name(num);
lua_pushstring(ls, plname.c_str());
return 1;
}
static bool is_ghost(const kill_exp *ke)
{
return ke->monnum == -1
&& ke->desc.find("The ghost of") != std::string::npos;
}
static int kill_lualc_holiness(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
if (ke)
{
const char *verdict = "strange";
if (ke->monnum == -1)
{
verdict = is_ghost(ke)? "undead" : "demonic";
}
else
{
switch (mons_holiness(ke->monnum))
{
case MH_HOLY: verdict = "holy"; break;
case MH_NATURAL: verdict = "natural"; break;
case MH_UNDEAD: verdict = "undead"; break;
case MH_DEMONIC: verdict = "demonic"; break;
case MH_NONLIVING: verdict = "nonliving"; break;
case MH_PLANT: verdict = "plant"; break;
}
if (ke->modifier != kill_monster_desc::M_NORMAL
&& ke->modifier != kill_monster_desc::M_SHAPESHIFTER)
verdict = "undead";
}
lua_pushstring(ls, verdict);
return 1;
}
return 0;
}
static int kill_lualc_symbol(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
if (ke)
{
unsigned char ch = ke->monnum != -1?
mons_char(ke->monnum) :
is_ghost(ke)? 'p' : '&';
if (ke->monnum == MONS_PROGRAM_BUG)
ch = ' ';
switch (ke->modifier)
{
case kill_monster_desc::M_ZOMBIE:
case kill_monster_desc::M_SKELETON:
case kill_monster_desc::M_SIMULACRUM:
ch = mons_zombie_size(ke->monnum) == Z_SMALL? 'z' : 'Z';
break;
case kill_monster_desc::M_SPECTRE:
ch = 'W';
break;
default:
break;
}
char s[2];
s[0] = (char) ch;
s[1] = 0;
lua_pushstring(ls, s);
return 1;
}
return 0;
}
static int kill_lualc_rawwrite(lua_State *ls)
{
const char *s = luaL_checkstring(ls, 1);
lua_pushstring(ls, "cr_skill");
lua_gettable(ls, LUA_REGISTRYINDEX);
if (!lua_islightuserdata(ls, -1))
{
lua_settop(ls, -2);
fprintf(stderr, "Can't find kill string?\n");
return 0;
}
std::string *skill = static_cast<std::string *>( lua_touserdata(ls, -1) );
// Pop the userdata off the stack.
lua_settop(ls, -2);
*skill += s;
*skill += "\n";
return 0;
}
static int kill_lualc_write(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
if (ke)
{
lua_pushstring(ls, "cr_skill");
lua_gettable(ls, LUA_REGISTRYINDEX);
if (!lua_islightuserdata(ls, -1))
{
lua_settop(ls,-2);
fprintf(stderr, "Can't find kill string?\n");
return 0;
}
std::string *skill = static_cast<std::string *>(
lua_touserdata(ls, -1) );
// Pop the userdata off the stack.
lua_settop(ls, -2);
// Write kill description and a newline.
*skill += ke->desc + "\n";
}
return 0;
}
static int kill_lualc_summary(lua_State *ls)
{
if (!lua_istable(ls, 1))
{
luaL_argerror(ls, 1, "Unexpected argument type, wanted table");
return 0;
}
unsigned long count = 0;
for (int i = 1; ; ++i)
{
lua_rawgeti(ls, 1, i);
if (lua_isnil(ls, -1))
{
lua_settop(ls, -2);
break;
}
if (!lua_islightuserdata(ls, -1))
{
luaL_argerror(ls, 1, "Unexpected argument type");
return 0;
}
kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, -1) );
lua_settop(ls, -2);
if (ke)
count += ke->nkills;
}
char buf[120];
*buf = 0;
if (count)
snprintf(buf, sizeof buf, "%lu creature%s vanquished.",
count, count > 1? "s" : "");
lua_pushstring(ls, buf);
return 1;
}
static const struct luaL_reg kill_lib[] =
{
{ "nkills", kill_lualc_nkills },
{ "exp" , kill_lualc_exp },
{ "base_name", kill_lualc_base_name },
{ "desc", kill_lualc_desc },
{ "monnum", kill_lualc_monnum },
{ "modifier", kill_lualc_modifier },
{ "places", kill_lualc_places },
{ "place_name", kill_lualc_place_name },
{ "holiness", kill_lualc_holiness },
{ "symbol", kill_lualc_symbol },
{ "isghost", kill_lualc_isghost },
{ "ispandemon", kill_lualc_ispandemon },
{ "isunique", kill_lualc_isunique },
{ "rawwrite", kill_lualc_rawwrite },
{ "write", kill_lualc_write },
{ "summary", kill_lualc_summary },
{ NULL, NULL }
};
void lua_open_kills(lua_State *ls)
{
luaL_openlib(ls, "kills", kill_lib, 0);
}
static void kill_lua_filltable(std::vector<kill_exp> &v)
{
lua_State *ls = clua.state();
lua_newtable(ls);
for (int i = 0, count = v.size(); i < count; ++i)
{
lua_pushlightuserdata(ls, &v[i]);
lua_rawseti(ls, -2, i + 1);
}
}
#endif
// More sophisticated character handling
#define CURSES_USE_KEYPAD
// How long (milliseconds) curses should wait for additional characters
// after seeing an Escape (0x1b) keypress. May not be available on all
// curses implementations.
#define CURSES_SET_ESCDELAY 20
// Use this to seed the PRNG with a bit more than just time()... turning
// this off is perfectly okay, the game just becomes more exploitable
// with a bit of hacking (ie only by people who know how).
//
// For now, we'll make it default to on for Linux (who should have
// no problems with compiling this).
#define USE_MORE_SECURE_SEED
// Use POSIX regular expressions
#define REGEX_POSIX
// If you have libpcre, you can use that instead of POSIX regexes -
// uncomment the line below and add -lpcre to your makefile.
// #define REGEX_PCRE
// Uncomment (and edit as appropriate) to play sounds.
//
// WARNING: Enabling sounds may compromise security if Crawl is installed
// setuid or setgid. Filenames passed to this command *are not
// validated in any way*.
//
// #define SOUND_PLAY_COMMAND "/usr/bin/play -v .5 %s 2>/dev/null &"