diff options
Diffstat (limited to 'crawl-ref/source/debug.cc')
-rw-r--r-- | crawl-ref/source/debug.cc | 679 |
1 files changed, 609 insertions, 70 deletions
diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index 8bfdb776d8..545ec6ffc3 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -3,6 +3,8 @@ * Summary: Debug and wizard related functions. * Written by: Linley Henzell and Jesse Jones * + * Modified for Crawl Reference by $Author$ on $Date$ + * * Change History (most recent first): * * <4> 14/12/99 LRH Added cast_spec_spell_name() @@ -19,8 +21,13 @@ #include <string.h> #include <stdio.h> #include <stdarg.h> +#include <time.h> #include <ctype.h> +#ifdef UNIX +#include <errno.h> +#endif + #ifdef DOS #include <conio.h> #endif @@ -28,9 +35,13 @@ #include "externs.h" #include "direct.h" +#include "describe.h" #include "dungeon.h" +#include "fight.h" #include "invent.h" #include "itemname.h" +#include "itemprop.h" +#include "item_use.h" #include "items.h" #include "misc.h" #include "monplace.h" @@ -45,10 +56,8 @@ #include "spl-cast.h" #include "spl-util.h" #include "stuff.h" - -#ifndef WIZARD -#define WIZARD -#endif +#include "travel.h" +#include "version.h" #if DEBUG && WIN #define MyDebugBreak() _asm {int 3} @@ -74,23 +83,11 @@ static HANDLE sConsole = NULL; static void BreakStrToDebugger(const char *mesg) { -#if OSX +#if OSX || defined(__MINGW32__) fprintf(stderr, mesg); // raise(SIGINT); // this is what DebugStr() does on OS X according to Tech Note 2030 int* p = NULL; // but this gives us a stack crawl... *p = 0; -#elif MAC - unsigned char s[50]; - - int len = strlen(mesg); - - if (len > 255) - len = 255; - - s[0] = (Byte) len; - BlockMoveData(mesg, s + 1, len); - - DebugStr(s); #elif WIN MSG msg; // remove pending quit messages so the message box displays @@ -309,10 +306,6 @@ static void TraceString(const char *mesg) } #endif -#if MAC -#pragma mark - -#endif - // ======================================================================== // Global Functions // ======================================================================== @@ -327,10 +320,6 @@ void AssertFailed(const char *expr, const char *file, int line) { char mesg[512]; -#if MAC - sprintf(mesg, "ASSERT(%s) in %s at line %d failed.", expr, file, line); - -#else const char *fileName = file + strlen(file); // strip off path while (fileName > file && fileName[-1] != '\\') @@ -338,7 +327,6 @@ void AssertFailed(const char *expr, const char *file, int line) sprintf(mesg, "ASSERT(%s) in '%s' at line %d failed.", expr, fileName, line); -#endif BreakStrToDebugger(mesg); } @@ -386,17 +374,11 @@ void TRACE(const char *format, ...) } #endif // DEBUG -//--------------------------------------------------------------- -// -// debug_prompt_for_monster -// -//--------------------------------------------------------------- #ifdef WIZARD + static int debug_prompt_for_monster( void ) { char specs[80]; - char obj_name[ ITEMNAME_SIZE ]; - char *ptr; mpr( "(Hint: 'generated' names, eg 'orc zombie', won't work)", MSGCH_PROMPT ); mpr( "Which monster by name? ", MSGCH_PROMPT ); @@ -405,28 +387,7 @@ static int debug_prompt_for_monster( void ) if (specs[0] == '\0') return (-1); - int mon = -1; - - for (int i = 0; i < NUM_MONSTERS; i++) - { - moname( i, true, DESC_PLAIN, obj_name ); - - ptr = strstr( strlwr(obj_name), strlwr(specs) ); - if (ptr != NULL) - { - mpr( obj_name ); - if (ptr == obj_name) - { - // we prefer prefixes over partial matches - mon = i; - break; - } - else - mon = i; - } - } - - return (mon); + return (get_monster_by_name(specs)); } #endif @@ -494,7 +455,7 @@ void debug_change_species( void ) int sp = -1; - for (int i = SP_HUMAN; i < NUM_SPECIES; i++) + for (i = SP_HUMAN; i < NUM_SPECIES; i++) { char sp_name[80]; strncpy( sp_name, species_name(i, you.experience_level), sizeof( sp_name ) ); @@ -626,7 +587,8 @@ void create_spec_monster(void) if (mon == -1) canned_msg( MSG_OK ); else - create_monster( mon, 0, BEH_SLEEP, you.x_pos, you.y_pos, MHITNOT, 250 ); + create_monster( mon, 0, BEH_SLEEP, + you.x_pos, you.y_pos, MHITNOT, 250, true ); } // end create_spec_monster() #endif @@ -650,7 +612,8 @@ void create_spec_monster_name(void) } else { - create_monster(mon, 0, BEH_SLEEP, you.x_pos, you.y_pos, MHITNOT, 250); + create_monster(mon, 0, BEH_SLEEP, + you.x_pos, you.y_pos, MHITNOT, 250, false); } } // end create_spec_monster_name() #endif @@ -736,7 +699,7 @@ void create_spec_object(void) MSGCH_PROMPT); mpr("= - jewellery ! - potions : - books | - staves 0 - The Orb", MSGCH_PROMPT); - mpr("} - miscellany X - corpses %% - food $ - gold ESC - exit", + mpr("} - miscellany X - corpses % - food $ - gold ESC - exit", MSGCH_PROMPT); mpr("What class of item? ", MSGCH_PROMPT); @@ -814,7 +777,7 @@ void create_spec_object(void) { mon = debug_prompt_for_monster(); - if (mon == -1) + if (mon == -1 || mon == MONS_PROGRAM_BUG) { mpr( "No such monster." ); return; @@ -825,7 +788,7 @@ void create_spec_object(void) mitm[thing_created].plus = mon; mitm[thing_created].plus2 = 0; mitm[thing_created].special = 210; - mitm[thing_created].colour = mons_colour(mon);; + mitm[thing_created].colour = mons_class_colour(mon);; mitm[thing_created].quantity = 1; mitm[thing_created].flags = 0; } @@ -856,13 +819,11 @@ void create_spec_object(void) { if (strstr( "naga barding", specs )) { - mitm[thing_created].sub_type = ARM_BOOTS; - mitm[thing_created].plus2 = TBOOT_NAGA_BARDING; + mitm[thing_created].sub_type = ARM_NAGA_BARDING; } else if (strstr( "centaur barding", specs )) { - mitm[thing_created].sub_type = ARM_BOOTS; - mitm[thing_created].plus2 = TBOOT_CENTAUR_BARDING; + mitm[thing_created].sub_type = ARM_CENTAUR_BARDING; } else if (strstr( "wizard's hat", specs )) { @@ -972,6 +933,14 @@ void create_spec_object(void) mitm[thing_created].plus = 24; break; + case OBJ_STAVES: + if (item_is_rod( mitm[thing_created] )) + { + mitm[thing_created].plus = MAX_ROD_CHARGE * ROD_CHARGE_MULT; + mitm[thing_created].plus2 = MAX_ROD_CHARGE * ROD_CHARGE_MULT; + } + break; + case OBJ_MISCELLANY: // Runes to "demonic", decks have 50 cards, ignored elsewhere? mitm[thing_created].plus = 50; @@ -1012,7 +981,7 @@ void tweak_object(void) char specs[50]; char keyin; - int item = prompt_invent_item( "Tweak which item? ", -1 ); + int item = prompt_invent_item("Tweak which item? ", MT_INVSELECT, -1); if (item == PROMPT_ABORT) { canned_msg( MSG_OK ); @@ -1031,7 +1000,7 @@ void tweak_object(void) item_name( you.inv[item], DESC_INVENTORY_EQUIP, info ); mpr( info ); - mpr( "a - plus b - plus2 c - special d - quantity ESC - exit", + mpr( "a - plus b - plus2 c - special d - quantity e - flags ESC - exit", MSGCH_PROMPT ); mpr( "Which field? ", MSGCH_PROMPT ); @@ -1045,6 +1014,8 @@ void tweak_object(void) field_ptr = &(you.inv[item].special); else if (keyin == 'd') field_ptr = &(you.inv[item].quantity); + else if (keyin == 'e') + field_ptr = &(you.inv[item].flags); else if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { @@ -1052,11 +1023,11 @@ void tweak_object(void) return; } - if (keyin >= 'a' && keyin <= 'd') + if (keyin >= 'a' && keyin <= 'e') break; } - if (keyin != 'c') + if (keyin != 'c' && keyin != 'e') { const short *const ptr = static_cast< short * >( field_ptr ); snprintf( info, INFO_SIZE, "Old value: %d (0x%04x)", *ptr, *ptr ); @@ -1081,7 +1052,7 @@ void tweak_object(void) if (new_value == 0 && end == specs) return; - if (keyin != 'c') + if (keyin != 'c' && keyin != 'e') { short *ptr = static_cast< short * >( field_ptr ); *ptr = new_value; @@ -1708,3 +1679,571 @@ void error_message_to_player(void) mpr("I suggest you leave this level then save as soon as possible."); } // end error_message_to_player() + +#ifdef WIZARD + +static int create_fsim_monster(int mtype, int hp) +{ + const int mi = + create_monster( mtype, 0, BEH_HOSTILE, you.x_pos, you.y_pos, + MHITNOT, 250 ); + + if (mi == -1) + return (mi); + + monsters *mon = &menv[mi]; + mon->hit_points = mon->max_hit_points = hp; + return (mi); +} + +static skill_type fsim_melee_skill(const item_def *item) +{ + skill_type sk = SK_UNARMED_COMBAT; + if (item) + sk = weapon_skill(*item); + return (sk); +} + +static void fsim_set_melee_skill(int skill, const item_def *item) +{ + you.skills[fsim_melee_skill(item)] = skill; + you.skills[SK_FIGHTING] = skill * 15 / 27; +} + +static void fsim_set_ranged_skill(int skill, const item_def *item) +{ + you.skills[range_skill(*item)] = skill; + you.skills[SK_RANGED_COMBAT] = skill * 15 / 27; +} + +static void fsim_item(FILE *out, + bool melee, + const item_def *weap, + int wskill, unsigned long damage, + long iterations, long hits, + int maxdam, unsigned long time) +{ + double hitdam = hits? double(damage) / hits : 0.0; + int avspeed = (int) (time / iterations); + fprintf(out, " %2d | %3ld%% | %5.2f | %5.2f | %5.2f | %3d | %2ld\n", + wskill, + 100 * hits / iterations, + double(damage) / iterations, + hitdam, + double(damage) * player_speed() / avspeed / iterations, + maxdam, + time / iterations); +} + +static bool fsim_ranged_combat(FILE *out, int wskill, int mi, + const item_def *item, int missile_slot) +{ + monsters &mon = menv[mi]; + unsigned long cumulative_damage = 0L; + unsigned long time_taken = 0L; + long hits = 0L; + int maxdam = 0; + + const int thrown = missile_slot == -1? get_fire_item_index() : missile_slot; + if (thrown == ENDOFPACK || thrown == -1) + { + mprf("No suitable missiles for combat simulation."); + return (false); + } + + fsim_set_ranged_skill(wskill, item); + + no_messages mx; + const long iter_limit = Options.fsim_rounds; + const int hunger = you.hunger; + for (long i = 0; i < iter_limit; ++i) + { + mon.hit_points = mon.max_hit_points; + bolt beam; + you.time_taken = player_speed(); + if (throw_it(beam, thrown, &mon)) + hits++; + you.hunger = hunger; + time_taken += you.time_taken; + + int damage = (mon.max_hit_points - mon.hit_points); + cumulative_damage += damage; + if (damage > maxdam) + maxdam = damage; + } + fsim_item(out, false, item, wskill, cumulative_damage, + iter_limit, hits, maxdam, time_taken); + + return (true); +} + +static bool fsim_melee_combat(FILE *out, int wskill, int mi, + const item_def *item) +{ + monsters &mon = menv[mi]; + unsigned long cumulative_damage = 0L; + unsigned long time_taken = 0L; + long hits = 0L; + int maxdam = 0; + + fsim_set_melee_skill(wskill, item); + + no_messages mx; + const long iter_limit = Options.fsim_rounds; + const int hunger = you.hunger; + for (long i = 0; i < iter_limit; ++i) + { + mon.hit_points = mon.max_hit_points; + you.time_taken = player_speed(); + if (you_attack(mi, true)) + hits++; + + you.hunger = hunger; + time_taken += you.time_taken; + + int damage = (mon.max_hit_points - mon.hit_points); + cumulative_damage += damage; + if (damage > maxdam) + maxdam = damage; + } + fsim_item(out, true, item, wskill, cumulative_damage, iter_limit, hits, + maxdam, time_taken); + + return (true); +} + +static bool debug_fight_simulate(FILE *out, int wskill, int mi, int miss_slot) +{ + int weapon = you.equip[EQ_WEAPON]; + const item_def *iweap = weapon != -1? &you.inv[weapon] : NULL; + + if (iweap && iweap->base_type == OBJ_WEAPONS + && is_range_weapon(*iweap)) + return fsim_ranged_combat(out, wskill, mi, iweap, miss_slot); + else + return fsim_melee_combat(out, wskill, mi, iweap); +} + +static const item_def *fsim_weap_item() +{ + const int weap = you.equip[EQ_WEAPON]; + if (weap == -1) + return NULL; + + return &you.inv[weap]; +} + +static std::string fsim_wskill() +{ + const item_def *iweap = fsim_weap_item(); + return iweap && iweap->base_type == OBJ_WEAPONS + && is_range_weapon(*iweap)? + skill_name( range_skill(*iweap) ) : + iweap? skill_name( fsim_melee_skill(iweap) ) : + skill_name( SK_UNARMED_COMBAT ); +} + +static std::string fsim_weapon(int missile_slot) +{ + char item_buf[ITEMNAME_SIZE]; + if (you.equip[EQ_WEAPON] != -1) + { + const item_def &weapon = you.inv[ you.equip[EQ_WEAPON] ]; + item_name(weapon, DESC_PLAIN, item_buf, true); + + if (is_range_weapon(weapon)) + { + const int missile = + missile_slot == -1? get_fire_item_index() : + missile_slot; + if (missile < ENDOFPACK) + { + std::string base = item_buf; + base += " with "; + in_name(missile, DESC_PLAIN, item_buf, true); + return (base + item_buf); + } + } + } + else + { + strncpy(item_buf, "unarmed", sizeof item_buf); + } + return (item_buf); +} + +static std::string fsim_time_string() +{ + time_t curr_time = time(NULL); + struct tm *ltime = localtime(&curr_time); + if (ltime) + { + char buf[100]; + snprintf(buf, sizeof buf, "%4d%02d%02d/%2d:%02d:%02d", + ltime->tm_year + 1900, + ltime->tm_mon + 1, + ltime->tm_mday, + ltime->tm_hour, + ltime->tm_min, + ltime->tm_sec); + return (buf); + } + return (""); +} + +static void fsim_mon_stats(FILE *o, const monsters &mon) +{ + char buf[ITEMNAME_SIZE]; + fprintf(o, "Monster : %s\n", + moname(mon.type, true, DESC_PLAIN, buf)); + fprintf(o, "HD : %d\n", mon.hit_dice); + fprintf(o, "AC : %d\n", mon.armour_class); + fprintf(o, "EV : %d\n", mon.evasion); +} + +static void fsim_title(FILE *o, int mon, int ms) +{ + char buf[ITEMNAME_SIZE]; + fprintf(o, CRAWL " version " VERSION "\n\n"); + fprintf(o, "Combat simulation: %s %s vs. %s (%ld rounds) (%s)\n", + species_name(you.species, you.experience_level), + you.class_name, + moname(menv[mon].type, true, DESC_PLAIN, buf), + Options.fsim_rounds, + fsim_time_string().c_str()); + fprintf(o, "Experience: %d\n", you.experience_level); + fprintf(o, "Strength : %d\n", you.strength); + fprintf(o, "Intel. : %d\n", you.intel); + fprintf(o, "Dexterity : %d\n", you.dex); + fprintf(o, "Base speed: %d\n", player_speed()); + fprintf(o, "\n"); + fsim_mon_stats(o, menv[mon]); + fprintf(o, "\n"); + fprintf(o, "Weapon : %s\n", fsim_weapon(ms).c_str()); + fprintf(o, "Skill : %s\n", fsim_wskill().c_str()); + fprintf(o, "\n"); + fprintf(o, "Skill | Accuracy | Av.Dam | Av.HitDam | Eff.Dam | Max.Dam | Av.Time\n"); +} + +static int cap_stat(int stat) +{ + return (stat < 1 ? 1 : + stat > 127 ? 127 : + stat); +} + +static bool debug_fight_sim(int mindex, int missile_slot) +{ + FILE *ostat = fopen("fight.stat", "a"); + if (!ostat) + { + // I'm not sure what header provides errno on djgpp, + // and it's insufficiently important for a wizmode-only + // feature. +#ifndef DOS + mprf("Can't write fight.stat: %s", strerror(errno)); +#endif + return (false); + } + + bool success = true; + FixedVector<unsigned char, 50> skill_backup = you.skills; + int ystr = you.strength, + yint = you.intel, + ydex = you.dex; + int yxp = you.experience_level; + + for (int i = SK_FIGHTING; i < NUM_SKILLS; ++i) + you.skills[i] = 0; + + you.experience_level = Options.fsim_xl; + if (you.experience_level < 1) + you.experience_level = 1; + if (you.experience_level > 27) + you.experience_level = 27; + + you.strength = cap_stat(Options.fsim_str); + you.intel = cap_stat(Options.fsim_int); + you.dex = cap_stat(Options.fsim_dex); + + fsim_title(ostat, mindex, missile_slot); + for (int wskill = 0; wskill <= 27; ++wskill) + { + mesclr(); + mprf("Calculating average damage for %s at skill %d", + fsim_weapon(missile_slot).c_str(), wskill); + if (!debug_fight_simulate(ostat, wskill, mindex, missile_slot)) + goto done_combat_sim; + + fflush(ostat); + // Not checking in the combat loop itself; that would be more responsive + // for the user, but slow down the sim with all the calls to kbhit(). + if (kbhit() && getch() == 27) + { + success = false; + mprf("Canceling simulation\n"); + goto done_combat_sim; + } + } + you.skills = skill_backup; + you.strength = ystr; + you.intel = yint; + you.dex = ydex; + you.experience_level = yxp; + + mprf("Done fight simulation with %s", fsim_weapon(missile_slot).c_str()); + +done_combat_sim: + fprintf(ostat, "-----------------------------------\n\n"); + fclose(ostat); + + return (success); +} + +int fsim_kit_equip(const std::string &kit) +{ + int missile_slot = -1; + char item_buf[ITEMNAME_SIZE]; + + std::string::size_type ammo_div = kit.find("/"); + std::string weapon = kit; + std::string missile; + if (ammo_div != std::string::npos) + { + weapon = kit.substr(0, ammo_div); + missile = kit.substr(ammo_div + 1); + trim_string(weapon); + trim_string(missile); + } + + for (int i = 0; i < ENDOFPACK; ++i) + { + if (!is_valid_item(you.inv[i])) + continue; + + in_name(i, DESC_PLAIN, item_buf, true); + if (std::string(item_buf).find(weapon) != std::string::npos) + { + if (i != you.equip[EQ_WEAPON]) + { + wield_weapon(true, i, false); + if (i != you.equip[EQ_WEAPON]) + return -100; + } + break; + } + } + + if (!missile.empty()) + { + for (int i = 0; i < ENDOFPACK; ++i) + { + if (!is_valid_item(you.inv[i])) + continue; + + in_name(i, DESC_PLAIN, item_buf, true); + if (std::string(item_buf).find(missile) != std::string::npos) + { + missile_slot = i; + break; + } + } + } + + return (missile_slot); +} + +// Writes statistics about a fight to fight.stat in the current directory. +// For fight purposes, a punching bag is summoned and given lots of hp, and the +// average damage the player does to the p. bag over 10000 hits is noted, +// advancing the weapon skill from 0 to 27, and keeping fighting skill to 2/5 +// of current weapon skill. +void debug_fight_statistics(bool use_defaults) +{ + int punching_bag = get_monster_by_name(Options.fsim_mons); + if (punching_bag == -1 || punching_bag == MONS_PROGRAM_BUG) + punching_bag = MONS_WORM; + + int mindex = create_fsim_monster(punching_bag, 500); + if (mindex == -1) + { + mprf("Failed to create punching bag"); + return; + } + + if (!use_defaults) + { + debug_fight_sim(mindex, -1); + goto fsim_mcleanup; + } + + for (int i = 0, size = Options.fsim_kit.size(); i < size; ++i) + { + int missile = fsim_kit_equip(Options.fsim_kit[i]); + if (missile == -100) + { + mprf("Aborting sim on %s", Options.fsim_kit[i].c_str()); + goto fsim_mcleanup; + } + if (!debug_fight_sim(mindex, missile)) + break; + } +fsim_mcleanup: + monster_die(&menv[mindex], KILL_DISMISSED, 0); +} + +static int find_trap_slot() +{ + for (int i = 0; i < MAX_TRAPS; ++i) + { + if (env.trap[i].type == TRAP_UNASSIGNED) + return (i); + } + return (-1); +} + +void debug_make_trap() +{ + char requested_trap[80]; + int trap_slot = find_trap_slot(); + trap_type trap = TRAP_UNASSIGNED; + int gridch = grd[you.x_pos][you.y_pos]; + + if (trap_slot == -1) + { + mpr("Sorry, this level can't take any more traps."); + return; + } + + if (gridch != DNGN_FLOOR) + { + mpr("You need to be on a floor square to make a trap."); + return; + } + + mprf(MSGCH_PROMPT, "What kind of trap? "); + get_input_line( requested_trap, sizeof( requested_trap ) ); + if (!*requested_trap) + return; + + strlwr(requested_trap); + for (int t = TRAP_DART; t < NUM_TRAPS; ++t) + { + if (strstr(requested_trap, + trap_name(trap_type(t)))) + { + trap = trap_type(t); + break; + } + } + + if (trap == TRAP_UNASSIGNED) + { + mprf("I know no traps named \"%s\"", requested_trap); + return; + } + + place_specific_trap(you.x_pos, you.y_pos, trap); + + mprf("Created a %s trap, marked it undiscovered", + trap_name(trap)); + + // Also tell travel that its world-view must change. + travel_init_new_level(); +} + +static const char *shop_types[] = { + "weapon", + "armour", + "antique weapon", + "antique armour", + "antiques", + "jewellery", + "wand", + "book", + "food", + "distillery", + "scroll", + "general" +}; + +void debug_make_shop() +{ + char requested_shop[80]; + int gridch = grd[you.x_pos][you.y_pos]; + bool have_shop_slots = false; + int new_shop_type = SHOP_UNASSIGNED; + bool representative = false; + + if (gridch != DNGN_FLOOR) + { + mpr("Insufficient floor-space for new Wal-Mart."); + return; + } + + for (int i = 0; i < MAX_SHOPS; ++i) + { + if (env.shop[i].type == SHOP_UNASSIGNED) + { + have_shop_slots = true; + break; + } + } + + if (!have_shop_slots) + { + mpr("There are too many shops on this level."); + return; + } + + mprf(MSGCH_PROMPT, "What kind of shop? "); + get_input_line( requested_shop, sizeof( requested_shop ) ); + if (!*requested_shop) + return; + + strlwr(requested_shop); + for (unsigned i = 0; i < sizeof(shop_types) / sizeof (*shop_types); ++i) + { + if (strstr(requested_shop, shop_types[i])) + { + new_shop_type = i; + break; + } + } + + if (new_shop_type == SHOP_UNASSIGNED) + { + mprf("Bad shop type: \"%s\"", requested_shop); + return; + } + + representative = !!strchr(requested_shop, '*'); + + place_spec_shop(you.your_level, you.x_pos, you.y_pos, + new_shop_type, representative); + link_items(); + mprf("Done."); +} + +void debug_set_stats() +{ + char buf[80]; + mprf(MSGCH_PROMPT, "Enter values for Str, Int, Dex (space separated): "); + if (cancelable_get_line(buf, sizeof buf)) + return; + + int sstr = you.strength, + sdex = you.dex, + sint = you.intel; + sscanf(buf, "%d %d %d", &sstr, &sint, &sdex); + + you.max_strength = you.strength = cap_stat(sstr); + you.max_dex = you.dex = cap_stat(sdex); + you.max_intel = you.intel = cap_stat(sint); + + you.redraw_strength = true; + you.redraw_dexterity = true; + you.redraw_intelligence = true; +} + +#endif |