diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-09-18 15:08:25 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-09-18 15:08:25 +0000 |
commit | a4d4f3ecccb29c3f5fc1ce55579119106c399911 (patch) | |
tree | 5677ea04b4dfdadd961c01ba3baf7502f8d6e0d0 /stone_soup/crawl-ref/source/debug.cc | |
parent | 571501e1135989d3b9dc44e3d332562a7cf78b35 (diff) | |
download | crawl-ref-a4d4f3ecccb29c3f5fc1ce55579119106c399911.tar.gz crawl-ref-a4d4f3ecccb29c3f5fc1ce55579119106c399911.zip |
Updated stone_soup-0.1b1 tag to include fix for Poison Arrow of Doom.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/tags/stone_soup-0.1b1@49 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'stone_soup/crawl-ref/source/debug.cc')
-rw-r--r-- | stone_soup/crawl-ref/source/debug.cc | 2136 |
1 files changed, 2136 insertions, 0 deletions
diff --git a/stone_soup/crawl-ref/source/debug.cc b/stone_soup/crawl-ref/source/debug.cc new file mode 100644 index 0000000000..eb29e8c9d9 --- /dev/null +++ b/stone_soup/crawl-ref/source/debug.cc @@ -0,0 +1,2136 @@ +/* + * File: debug.cc + * Summary: Debug and wizard related functions. + * Written by: Linley Henzell and Jesse Jones + * + * Change History (most recent first): + * + * <4> 14/12/99 LRH Added cast_spec_spell_name() + * <3> 5/06/99 JDJ Added TRACE. + * <2> -/--/-- JDJ Added a bunch od debugging macros. + * Old code is now #if WIZARD. + * <1> -/--/-- LRH Created + */ + +#include "AppHdr.h" +#include "debug.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <time.h> +#include <ctype.h> + +#ifdef DOS +#include <conio.h> +#endif + +#include "externs.h" + +#include "direct.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" +#include "monstuff.h" +#include "mon-util.h" +#include "mutation.h" +#include "player.h" +#include "randart.h" +#include "religion.h" +#include "skills.h" +#include "skills2.h" +#include "spl-cast.h" +#include "spl-util.h" +#include "stuff.h" +#include "version.h" + +#ifndef WIZARD +#define WIZARD +#endif + +#if DEBUG && WIN +#define MyDebugBreak() _asm {int 3} +#endif + +//----------------------------------- +// Internal Variables +// +#if WIN +static HANDLE sConsole = NULL; +#endif + +// ======================================================================== +// Internal Functions +// ======================================================================== + +//--------------------------------------------------------------- +// +// BreakStrToDebugger +// +//--------------------------------------------------------------- +#if DEBUG +static void BreakStrToDebugger(const char *mesg) +{ + +#if OSX + 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 + + bool quitting = (bool)::PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE); + + char text[2500]; + + int flags = MB_YESNO + // want abort and ignore buttons + // (too bad we can't ditch the retry button...) + MB_ICONERROR + // display the icon for errors + MB_TASKMODAL + // don't let the user do anything else in the app + MB_SETFOREGROUND; // bring the app to the front + + strcpy(text, mesg); + strcat(text, "\nDo you want to drop into the debugger?"); + + int result = MessageBoxA(NULL, text, "Debug Break", flags); + + if (result == IDYES) + MyDebugBreak(); + + if (quitting) + PostQuitMessage(msg.wParam); + +#else + fprintf(stderr, "%s\n", mesg); + abort(); +#endif +} +#endif + + +//--------------------------------------------------------------- +// +// IsDebuggerPresent95 +// +// From March 1999 Windows Developer's Journal. This should only +// be called if we're running on Win 95 (normally I'd add an +// ASSERT, but that's a bit dicy since this is called by ASSERT...) +// +//--------------------------------------------------------------- +#if WIN +static bool IsDebuggerPresent95() +{ + bool present = false; + + const DWORD kDebuggerPresentFlag = 0x000000001; + const DWORD kProcessDatabaseBytes = 190; + const DWORD kOffsetFlags = 8; + + DWORD threadID = GetCurrentThreadId(); + DWORD processID = GetCurrentProcessId(); + DWORD obfuscator = 0; + +#if __MWERKS__ + asm + { + mov ax, fs + mov es, ax + mov eax, 0x18 + mov eax, es:[eax] + sub eax, 0x10 xor eax,[threadID] mov[obfuscator], eax + } + +#else + _asm + { + mov ax, fs + mov es, ax + mov eax, 18 h + mov eax, es:[eax] + sub eax, 10 h xor eax,[threadID] mov[obfuscator], eax + } +#endif + + const DWORD *processDatabase = + reinterpret_cast< const DWORD * >(processID ^ obfuscator); + + if (!IsBadReadPtr(processDatabase, kProcessDatabaseBytes)) + { + DWORD flags = processDatabase[kOffsetFlags]; + + present = (flags & kDebuggerPresentFlag) != 0; + } + + return present; +} +#endif + + +//--------------------------------------------------------------- +// +// IsDebuggerPresent +// +//--------------------------------------------------------------- +#if WIN +bool IsDebuggerPresent() +{ + bool present = false; + + typedef BOOL(WINAPI * IsDebuggerPresentProc) (); + + HINSTANCE kernelH = LoadLibrary("KERNEL32.DLL"); + + if (kernelH != NULL) + { // should never fail + + IsDebuggerPresentProc proc = + (IsDebuggerPresentProc)::GetProcAddress( kernelH, + "IsDebuggerPresent" ); + + if (proc != NULL) // only present in NT and Win 98 + present = proc() != 0; + else + present = IsDebuggerPresent95(); + } + + return present; +} +#endif + + +//--------------------------------------------------------------- +// +// CreateConsoleWindow +// +//--------------------------------------------------------------- +#if WIN +static void CreateConsoleWindow() +{ + ASSERT(sConsole == NULL); + + // Create the console window + if (::AllocConsole()) + { + // Get the console window's handle + sConsole =::GetStdHandle(STD_ERROR_HANDLE); + if (sConsole == INVALID_HANDLE_VALUE) + sConsole = NULL; + + // Set some options + if (sConsole != NULL) + { + VERIFY(::SetConsoleTextAttribute(sConsole, FOREGROUND_GREEN)); + // green text on a black background (there doesn't appear to + // be a way to get black text) + + VERIFY(::SetConsoleTitle("Debug Log")); + + COORD size = { 80, 120 }; + + VERIFY(::SetConsoleScreenBufferSize(sConsole, size)); + } + else + DEBUGSTR(L "Couldn't get the console window's handle!"); + } + else + DEBUGSTR(L "Couldn't allocate the console window!"); +} +#endif + + +#if DEBUG +//--------------------------------------------------------------- +// +// TraceString +// +//--------------------------------------------------------------- +static void TraceString(const char *mesg) +{ + // Write the string to the debug window +#if WIN + if (IsDebuggerPresent()) + { + OutputDebugStringA(mesg); // if you're using CodeWarrior you'll need to enable the "Log System Messages" checkbox to get this working + } + else + { + if (sConsole == NULL) // otherwise we'll use a console window + CreateConsoleWindow(); + + if (sConsole != NULL) + { + unsigned long written; + + VERIFY(WriteConsoleA(sConsole, mesg, strlen(mesg), &written, NULL)); + } + } +#else + fprintf(stderr, "%s", mesg); +#endif + + // Write the string to the debug log + static bool inited = false; + static FILE *file = NULL; + + if (!inited) + { + ASSERT(file == NULL); + + const char *fileName = "DebugLog.txt"; + + file = fopen(fileName, "w"); + ASSERT(file != NULL); + + inited = true; + } + + if (file != NULL) + { + fputs(mesg, file); + fflush(file); // make sure all the output makes it to the file + + } +} +#endif + +#if MAC +#pragma mark - +#endif + +// ======================================================================== +// Global Functions +// ======================================================================== + +//--------------------------------------------------------------- +// +// AssertFailed +// +//--------------------------------------------------------------- +#if DEBUG +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] != '\\') + --fileName; + + sprintf(mesg, "ASSERT(%s) in '%s' at line %d failed.", expr, fileName, + line); +#endif + + BreakStrToDebugger(mesg); +} +#endif + + +//--------------------------------------------------------------- +// +// DEBUGSTR +// +//--------------------------------------------------------------- +#if DEBUG +void DEBUGSTR(const char *format, ...) +{ + char mesg[2048]; + + va_list args; + + va_start(args, format); + vsprintf(mesg, format, args); + va_end(args); + + BreakStrToDebugger(mesg); +} +#endif + + +//--------------------------------------------------------------- +// +// TRACE +// +//--------------------------------------------------------------- +#if DEBUG +void TRACE(const char *format, ...) +{ + char mesg[2048]; + + va_list args; + + va_start(args, format); + vsprintf(mesg, format, args); + va_end(args); + + TraceString(mesg); +} +#endif // DEBUG + +//--------------------------------------------------------------- +// +// debug_prompt_for_monster +// +//--------------------------------------------------------------- +#ifdef WIZARD + +static int get_monnum(const char *name) +{ + char search[ITEMNAME_SIZE], + mname[ITEMNAME_SIZE]; + strncpy(search, name, sizeof search); + search[ITEMNAME_SIZE - 1] = 0; + strlwr(search); + + int mon = -1; + for (int i = 0; i < NUM_MONSTERS; i++) + { + moname( i, true, DESC_PLAIN, mname ); + + const char *ptr = strstr( strlwr(mname), search ); + if (ptr != NULL) + { + if (ptr == mname) + { + // we prefer prefixes over partial matches + mon = i; + break; + } + else + mon = i; + } + } + return (mon); +} + +static int debug_prompt_for_monster( void ) +{ + char specs[80]; + + mpr( "(Hint: 'generated' names, eg 'orc zombie', won't work)", MSGCH_PROMPT ); + mpr( "Which monster by name? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return (-1); + + return (get_monnum(specs)); +} +#endif + +//--------------------------------------------------------------- +// +// debug_prompt_for_skill +// +//--------------------------------------------------------------- +#ifdef WIZARD +static int debug_prompt_for_skill( const char *prompt ) +{ + char specs[80]; + + mpr( prompt, MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return (-1); + + int skill = -1; + + for (int i = 0; i < NUM_SKILLS; i++) + { + // avoid the bad values: + if (i == SK_UNUSED_1 || (i > SK_UNARMED_COMBAT && i < SK_SPELLCASTING)) + continue; + + char sk_name[80]; + strncpy( sk_name, skill_name(i), sizeof( sk_name ) ); + + char *ptr = strstr( strlwr(sk_name), strlwr(specs) ); + if (ptr != NULL) + { + if (ptr == sk_name && strlen(specs) > 0) + { + // we prefer prefixes over partial matches + skill = i; + break; + } + else + skill = i; + } + } + + return (skill); +} +#endif + +//--------------------------------------------------------------- +// +// debug_change_species +// +//--------------------------------------------------------------- +#ifdef WIZARD +void debug_change_species( void ) +{ + char specs[80]; + int i; + + mpr( "What species would you like to be now? " , MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return; + + int sp = -1; + + for (int i = SP_HUMAN; i < NUM_SPECIES; i++) + { + char sp_name[80]; + strncpy( sp_name, species_name(i, you.experience_level), sizeof( sp_name ) ); + + char *ptr = strstr( strlwr(sp_name), strlwr(specs) ); + if (ptr != NULL) + { + if (ptr == sp_name && strlen(specs) > 0) + { + // we prefer prefixes over partial matches + sp = i; + break; + } + else + sp = i; + } + } + + if (sp == -1) + mpr( "That species isn't available." ); + else + { + for (i = 0; i < NUM_SKILLS; i++) + { + you.skill_points[i] *= species_skills( i, sp ); + you.skill_points[i] /= species_skills( i, you.species ); + } + + you.species = sp; + + redraw_screen(); + } +} +#endif +//--------------------------------------------------------------- +// +// debug_prompt_for_int +// +// If nonneg, then it returns a non-negative number or -1 on fail +// If !nonneg, then it returns an integer, and 0 on fail +// +//--------------------------------------------------------------- +#ifdef WIZARD +static int debug_prompt_for_int( const char *prompt, bool nonneg ) +{ + char specs[80]; + + mpr( prompt, MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return (nonneg ? -1 : 0); + + char *end; + int ret = strtol( specs, &end, 10 ); + + if ((ret < 0 && nonneg) || (ret == 0 && end == specs)) + ret = (nonneg ? -1 : 0); + + return (ret); +} +#endif + +/* + Some debugging functions, accessable through keys like %, $, &, ) etc when + a section of code in input() in acr.cc is uncommented. + */ + +//--------------------------------------------------------------- +// +// cast_spec_spell +// +//--------------------------------------------------------------- +#ifdef WIZARD +void cast_spec_spell(void) +{ + int spell = debug_prompt_for_int( "Cast which spell by number? ", true ); + + if (spell == -1) + canned_msg( MSG_OK ); + else + your_spells( spell, 0, false ); +} +#endif + + +//--------------------------------------------------------------- +// +// cast_spec_spell_name +// +//--------------------------------------------------------------- +#ifdef WIZARD +void cast_spec_spell_name(void) +{ + int i = 0; + char specs[80]; + char spname[80]; + + mpr( "Cast which spell by name? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + for (i = 0; i < NUM_SPELLS; i++) + { + strncpy( spname, spell_title(i), sizeof( spname ) ); + + if (strstr( strlwr(spname), strlwr(specs) ) != NULL) + { + your_spells(i, 0, false); + return; + } + } + + mpr((one_chance_in(20)) ? "Maybe you should go back to WIZARD school." + : "I couldn't find that spell."); +} +#endif + + +//--------------------------------------------------------------- +// +// create_spec_monster +// +//--------------------------------------------------------------- +#ifdef WIZARD +void create_spec_monster(void) +{ + int mon = debug_prompt_for_int( "Create which monster by number? ", true ); + + if (mon == -1) + canned_msg( MSG_OK ); + else + create_monster( mon, 0, BEH_SLEEP, you.x_pos, you.y_pos, MHITNOT, 250 ); +} // end create_spec_monster() +#endif + + +//--------------------------------------------------------------- +// +// create_spec_monster_name +// +//--------------------------------------------------------------- +#ifdef WIZARD +void create_spec_monster_name(void) +{ + int mon = debug_prompt_for_monster(); + + if (mon == -1) + { + mpr("I couldn't find that monster."); + + if (one_chance_in(20)) + mpr("Maybe it's hiding."); + } + else + { + create_monster(mon, 0, BEH_SLEEP, you.x_pos, you.y_pos, MHITNOT, 250); + } +} // end create_spec_monster_name() +#endif + + +//--------------------------------------------------------------- +// +// level_travel +// +//--------------------------------------------------------------- +#ifdef WIZARD +void level_travel( int delta ) +{ + int old_level = you.your_level; + int new_level = you.your_level + delta; + + if (delta == 0) + { + new_level = debug_prompt_for_int( "Travel to which level? ", true ) - 1; + } + + if (new_level < 0 || new_level >= 50) + { + mpr( "That level is out of bounds." ); + return; + } + + you.your_level = new_level - 1; + grd[you.x_pos][you.y_pos] = DNGN_STONE_STAIRS_DOWN_I; + down_stairs(true, old_level); + untag_followers(); +} // end level_travel() +#endif + + +//--------------------------------------------------------------- +// +// create_spec_object +// +//--------------------------------------------------------------- +#ifdef WIZARD +void create_spec_object(void) +{ + static int max_subtype[] = + { + NUM_WEAPONS, + NUM_MISSILES, + NUM_ARMOURS, + NUM_WANDS, + NUM_FOODS, + 0, // unknown I + NUM_SCROLLS, + NUM_JEWELLERY, + NUM_POTIONS, + 0, // unknown II + NUM_BOOKS, + NUM_STAVES, + 0, // Orbs -- only one, handled specially + NUM_MISCELLANY, + 0, // corpses -- handled specially + 0, // gold -- handled specially + 0, // "gemstones" -- no items of type + }; + + char specs[80]; + char obj_name[ ITEMNAME_SIZE ]; + char keyin; + + char * ptr; + int best_index; + int mon; + int i; + + int class_wanted = OBJ_UNASSIGNED; + int type_wanted = -1; + int special_wanted = 0; + + int thing_created; + + for (;;) + { + mpr(") - weapons ( - missiles [ - armour / - wands ? - scrolls", + MSGCH_PROMPT); + mpr("= - jewellery ! - potions : - books | - staves 0 - The Orb", + MSGCH_PROMPT); + mpr("} - miscellany X - corpses %% - food $ - gold ESC - exit", + MSGCH_PROMPT); + + mpr("What class of item? ", MSGCH_PROMPT); + + keyin = toupper( get_ch() ); + + if (keyin == ')') + class_wanted = OBJ_WEAPONS; + else if (keyin == '(') + class_wanted = OBJ_MISSILES; + else if (keyin == '[' || keyin == ']') + class_wanted = OBJ_ARMOUR; + else if (keyin == '/' || keyin == '\\') + class_wanted = OBJ_WANDS; + else if (keyin == '?') + class_wanted = OBJ_SCROLLS; + else if (keyin == '=' || keyin == '"') + class_wanted = OBJ_JEWELLERY; + else if (keyin == '!') + class_wanted = OBJ_POTIONS; + else if (keyin == ':') + class_wanted = OBJ_BOOKS; + else if (keyin == '|') + class_wanted = OBJ_STAVES; + else if (keyin == '0' || keyin == 'O') + class_wanted = OBJ_ORBS; + else if (keyin == '}' || keyin == '{') + class_wanted = OBJ_MISCELLANY; + else if (keyin == 'X') + class_wanted = OBJ_CORPSES; + else if (keyin == '%') + class_wanted = OBJ_FOOD; + else if (keyin == '$') + class_wanted = OBJ_GOLD; + else if (keyin == ESCAPE || keyin == ' ' + || keyin == '\r' || keyin == '\n') + { + canned_msg( MSG_OK ); + return; + } + + if (class_wanted != OBJ_UNASSIGNED) + break; + } + + // allocate an item to play with: + thing_created = get_item_slot(); + if (thing_created == NON_ITEM) + { + mpr( "Could not allocate item." ); + return; + } + + // turn item into appropriate kind: + if (class_wanted == OBJ_ORBS) + { + mitm[thing_created].base_type = OBJ_ORBS; + mitm[thing_created].sub_type = ORB_ZOT; + mitm[thing_created].quantity = 1; + } + else if (class_wanted == OBJ_GOLD) + { + int amount = debug_prompt_for_int( "How much gold? ", true ); + if (amount <= 0) + { + canned_msg( MSG_OK ); + return; + } + + mitm[thing_created].base_type = OBJ_GOLD; + mitm[thing_created].sub_type = 0; + mitm[thing_created].quantity = amount; + } + else if (class_wanted == OBJ_CORPSES) + { + mon = debug_prompt_for_monster(); + + if (mon == -1) + { + mpr( "No such monster." ); + return; + } + + mitm[thing_created].base_type = OBJ_CORPSES; + mitm[thing_created].sub_type = CORPSE_BODY; + 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].quantity = 1; + mitm[thing_created].flags = 0; + } + else + { + mpr( "What type of item? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + { + canned_msg( MSG_OK ); + return; + } + + // In order to get the sub-type, we'll fill out the base type... + // then we're going to iterate over all possible subtype values + // and see if we get a winner. -- bwr + mitm[thing_created].base_type = class_wanted; + mitm[thing_created].sub_type = 0; + mitm[thing_created].plus = 0; + mitm[thing_created].plus2 = 0; + mitm[thing_created].special = 0; + mitm[thing_created].flags = 0; + mitm[thing_created].quantity = 1; + set_ident_flags( mitm[thing_created], ISFLAG_IDENT_MASK ); + + if (class_wanted == OBJ_ARMOUR) + { + if (strstr( "naga barding", specs )) + { + mitm[thing_created].sub_type = ARM_NAGA_BARDING; + } + else if (strstr( "centaur barding", specs )) + { + mitm[thing_created].sub_type = ARM_CENTAUR_BARDING; + } + else if (strstr( "wizard's hat", specs )) + { + mitm[thing_created].sub_type = ARM_HELMET; + mitm[thing_created].plus2 = THELM_WIZARD_HAT; + } + else if (strstr( "cap", specs )) + { + mitm[thing_created].sub_type = ARM_HELMET; + mitm[thing_created].plus2 = THELM_CAP; + } + else if (strstr( "helm", specs )) + { + mitm[thing_created].sub_type = ARM_HELMET; + mitm[thing_created].plus2 = THELM_HELM; + } + } + + if (!mitm[thing_created].sub_type) + { + type_wanted = -1; + best_index = 10000; + + for (i = 0; i < max_subtype[ mitm[thing_created].base_type ]; i++) + { + mitm[thing_created].sub_type = i; + item_name( mitm[thing_created], DESC_PLAIN, obj_name ); + + ptr = strstr( strlwr(obj_name), strlwr(specs) ); + if (ptr != NULL) + { + // earliest match is the winner + if (ptr - obj_name < best_index) + { + mpr( obj_name ); + type_wanted = i; + best_index = ptr - obj_name; + } + } + } + + if (type_wanted == -1) + { + // ds -- if specs is a valid int, try using that. + // Since zero is atoi's copout, the wizard + // must enter (subtype + 1). + if (!(type_wanted = atoi(specs))) + { + mpr( "No such item." ); + return; + } + type_wanted--; + } + + mitm[thing_created].sub_type = type_wanted; + } + + switch (mitm[thing_created].base_type) + { + case OBJ_MISSILES: + mitm[thing_created].quantity = 30; + // intentional fall-through + case OBJ_WEAPONS: + case OBJ_ARMOUR: + mpr( "What ego type? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] != '\0') + { + special_wanted = 0; + best_index = 10000; + + for (i = 1; i < 25; i++) + { + mitm[thing_created].special = i; + item_name( mitm[thing_created], DESC_PLAIN, obj_name ); + + ptr = strstr( strlwr(obj_name), strlwr(specs) ); + if (ptr != NULL) + { + // earliest match is the winner + if (ptr - obj_name < best_index) + { + mpr( obj_name ); + special_wanted = i; + best_index = ptr - obj_name; + } + } + } + + mitm[thing_created].special = special_wanted; + } + break; + + case OBJ_BOOKS: + if (mitm[thing_created].sub_type == BOOK_MANUAL) + { + special_wanted = debug_prompt_for_skill( "A manual for which skill? " ); + if (special_wanted != -1) + mitm[thing_created].plus = special_wanted; + else + mpr( "Sorry, no books on that skill today." ); + } + break; + + case OBJ_WANDS: + 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; + break; + + case OBJ_FOOD: + case OBJ_SCROLLS: + case OBJ_POTIONS: + mitm[thing_created].quantity = 12; + break; + + default: + break; + } + } + + item_colour( mitm[thing_created] ); + + move_item_to_grid( &thing_created, you.x_pos, you.y_pos ); + + if (thing_created != NON_ITEM) + { + origin_acquired( mitm[thing_created], AQ_WIZMODE ); + canned_msg( MSG_SOMETHING_APPEARS ); + } +} +#endif + + +//--------------------------------------------------------------- +// +// create_spec_object +// +//--------------------------------------------------------------- +#ifdef WIZARD +void tweak_object(void) +{ + char specs[50]; + char keyin; + + int item = prompt_invent_item( "Tweak which item? ", -1 ); + if (item == PROMPT_ABORT) + { + canned_msg( MSG_OK ); + return; + } + + if (item == you.equip[EQ_WEAPON]) + you.wield_change = true; + + for (;;) + { + void *field_ptr = NULL; + + for (;;) + { + item_name( you.inv[item], DESC_INVENTORY_EQUIP, info ); + mpr( info ); + + mpr( "a - plus b - plus2 c - special d - quantity ESC - exit", + MSGCH_PROMPT ); + mpr( "Which field? ", MSGCH_PROMPT ); + + keyin = tolower( get_ch() ); + + if (keyin == 'a') + field_ptr = &(you.inv[item].plus); + else if (keyin == 'b') + field_ptr = &(you.inv[item].plus2); + else if (keyin == 'c') + field_ptr = &(you.inv[item].special); + else if (keyin == 'd') + field_ptr = &(you.inv[item].quantity); + else if (keyin == ESCAPE || keyin == ' ' + || keyin == '\r' || keyin == '\n') + { + canned_msg( MSG_OK ); + return; + } + + if (keyin >= 'a' && keyin <= 'd') + break; + } + + if (keyin != 'c') + { + const short *const ptr = static_cast< short * >( field_ptr ); + snprintf( info, INFO_SIZE, "Old value: %d (0x%04x)", *ptr, *ptr ); + } + else + { + const long *const ptr = static_cast< long * >( field_ptr ); + snprintf( info, INFO_SIZE, "Old value: %ld (0x%08lx)", *ptr, *ptr ); + } + + mpr( info ); + + mpr( "New value? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return; + + char *end; + int new_value = strtol( specs, &end, 10 ); + + if (new_value == 0 && end == specs) + return; + + if (keyin != 'c') + { + short *ptr = static_cast< short * >( field_ptr ); + *ptr = new_value; + } + else + { + long *ptr = static_cast< long * >( field_ptr ); + *ptr = new_value; + } + } +} +#endif + + +//--------------------------------------------------------------- +// +// stethoscope +// +//--------------------------------------------------------------- +#if DEBUG_DIAGNOSTICS + +static const char *enchant_names[] = +{ + "None", + "Slow", "Haste", "*BUG-3*", "Fear", "Conf", "Invis", + "YPois-1", "YPois-2", "YPois-3", "YPois-4", + "YShug-1", "YShug-2", "YShug-3", "YShug-4", + "YRot-1", "YRot-2", "YRot-3", "YRot-4", + "Summon", "Abj-1", "Abj-2", "Abj-3", "Abj-4", "Abj-5", "Abj-6", + "Corona-1", "Corona-2", "Corona-3", "Corona-4", + "Charm", "YSticky-1", "YSticky-2", "YSticky-3", "YSticky-4", + "*BUG-35*", "*BUG-36*", "*BUG-37*", + "GlowShapeshifter", "Shapeshifter", + "Tele-1", "Tele-2", "Tele-3", "Tele-4", + "*BUG-44*", "*BUG-45*", "*BUG-46*", "*BUG-47*", "*BUG-48*", "*BUG-49*", + "*BUG-50*", "*BUG-51*", "*BUG-52*", "*BUG-53*", "*BUG-54*", "*BUG-55*", + "*BUG-56*", + "Pois-1", "Pois-2", "Pois-3", "Pois-4", + "Sticky-1", "Sticky-2", "Sticky-3", "Sticky-4", + "OldAbj-1", "OldAbj-2", "OldAbj-3", "OldAbj-4", "OldAbj-5", "OldAbj-6", + "OldCreatedFriendly", "SleepWary", "Submerged", "Short Lived", + "*BUG-too big*" +}; + +void stethoscope(int mwh) +{ + struct dist stth; + int steth_x, steth_y; + int i, j; + + if (mwh != RANDOM_MONSTER) + i = mwh; + else + { + mpr( "Which monster?", MSGCH_PROMPT ); + + direction( stth ); + + if (!stth.isValid) + return; + + if (stth.isTarget) + { + steth_x = stth.tx; + steth_y = stth.ty; + } + else + { + steth_x = you.x_pos + stth.dx; + steth_y = you.x_pos + stth.dy; + } + + if (env.cgrid[steth_x][steth_y] != EMPTY_CLOUD) + { + snprintf( info, INFO_SIZE, "cloud type: %d delay: %d", + env.cloud[ env.cgrid[steth_x][steth_y] ].type, + env.cloud[ env.cgrid[steth_x][steth_y] ].decay ); + + mpr( info, MSGCH_DIAGNOSTICS ); + } + + if (mgrd[steth_x][steth_y] == NON_MONSTER) + { + snprintf( info, INFO_SIZE, "item grid = %d", igrd[steth_x][steth_y] ); + mpr( info, MSGCH_DIAGNOSTICS ); + return; + } + + i = mgrd[steth_x][steth_y]; + } + + // print type of monster + snprintf( info, INFO_SIZE, "%s (id #%d; type=%d loc=(%d,%d) align=%s)", + monam( menv[i].number, menv[i].type, true, DESC_CAP_THE ), + i, menv[i].type, + menv[i].x, menv[i].y, + ((menv[i].attitude == ATT_FRIENDLY) ? "friendly" : + (menv[i].attitude == ATT_HOSTILE) ? "hostile" : + (menv[i].attitude == ATT_NEUTRAL) ? "neutral" + : "unknown alignment") ); + + mpr( info, MSGCH_DIAGNOSTICS ); + + // print stats and other info + snprintf( info, INFO_SIZE,"HD=%d HP=%d/%d AC=%d EV=%d MR=%d SP=%d energy=%d num=%d flags=%02x", + menv[i].hit_dice, + menv[i].hit_points, menv[i].max_hit_points, + menv[i].armour_class, menv[i].evasion, + mons_resist_magic( &menv[i] ), + menv[i].speed, menv[i].speed_increment, + menv[i].number, menv[i].flags ); + + mpr( info, MSGCH_DIAGNOSTICS ); + + // print behaviour information + + const int hab = monster_habitat( menv[i].type ); + + snprintf( info, INFO_SIZE, "hab=%s beh=%s(%d) foe=%s(%d) mem=%d target=(%d,%d)", + ((hab == DNGN_DEEP_WATER) ? "water" : + (hab == DNGN_LAVA) ? "lava" + : "floor"), + + ((menv[i].behaviour == BEH_SLEEP) ? "sleep" : + (menv[i].behaviour == BEH_WANDER) ? "wander" : + (menv[i].behaviour == BEH_SEEK) ? "seek" : + (menv[i].behaviour == BEH_FLEE) ? "flee" : + (menv[i].behaviour == BEH_CORNERED) ? "cornered" + : "unknown"), + menv[i].behaviour, + + ((menv[i].foe == MHITYOU) ? "you" : + (menv[i].foe == MHITNOT) ? "none" : + (menv[menv[i].foe].type == -1) ? "unassigned monster" + : monam( menv[menv[i].foe].number, menv[menv[i].foe].type, + true, DESC_PLAIN )), + menv[i].foe, + menv[i].foe_memory, + + menv[i].target_x, menv[i].target_y ); + + mpr( info, MSGCH_DIAGNOSTICS ); + + // print resistances + snprintf( info, INFO_SIZE, "resist: fire=%d cold=%d elec=%d pois=%d neg=%d", + mons_res_fire( &menv[i] ), + mons_res_cold( &menv[i] ), + mons_res_elec( &menv[i] ), + mons_res_poison( &menv[i] ), + mons_res_negative_energy( &menv[i] ) ); + + mpr( info, MSGCH_DIAGNOSTICS ); + + + // print enchantments + strncpy( info, "ench: ", INFO_SIZE ); + for (j = 0; j < 6; j++) + { + if (menv[i].enchantment[j] >= NUM_ENCHANTMENTS) + strncat( info, enchant_names[ NUM_ENCHANTMENTS ], INFO_SIZE ); + else + strncat( info, enchant_names[ menv[i].enchantment[j] ], INFO_SIZE ); + + if (strlen( info ) <= 70) + strncat( info, " ", INFO_SIZE ); + else if (j < 5) + { + mpr( info, MSGCH_DIAGNOSTICS ); + strncpy( info, "ench: ", INFO_SIZE ); + } + } + + mpr( info, MSGCH_DIAGNOSTICS ); + + if (menv[i].type == MONS_PLAYER_GHOST + || menv[i].type == MONS_PANDEMONIUM_DEMON) + { + snprintf( info, INFO_SIZE, "Ghost damage: %d; brand: %d", + ghost.values[ GVAL_DAMAGE ], ghost.values[ GVAL_BRAND ] ); + mpr( info, MSGCH_DIAGNOSTICS ); + } +} // end stethoscope() +#endif + +#if DEBUG_ITEM_SCAN +//--------------------------------------------------------------- +// +// dump_item +// +//--------------------------------------------------------------- +static void dump_item( const char *name, int num, const item_def &item ) +{ + mpr( name, MSGCH_WARN ); + + snprintf( info, INFO_SIZE, " item #%d: base: %d; sub: %d; plus: %d; plus2: %d; special: %ld", + num, item.base_type, item.sub_type, + item.plus, item.plus2, item.special ); + + mpr( info ); + + snprintf( info, INFO_SIZE, " quant: %d; colour: %d; ident: 0x%08lx; ident_type: %d", + item.quantity, item.colour, item.flags, + get_ident_type( item.base_type, item.sub_type ) ); + + mpr( info ); + + snprintf( info, INFO_SIZE, " x: %d; y: %d; link: %d", + item.x, item.y, item.link ); + + mpr( info ); +} + +//--------------------------------------------------------------- +// +// debug_item_scan +// +//--------------------------------------------------------------- +void debug_item_scan( void ) +{ + int i; + char name[256]; + + // unset marks + for (i = 0; i < MAX_ITEMS; i++) + mitm[i].flags &= (~ISFLAG_DEBUG_MARK); + + // First we're going to check all the stacks on the level: + for (int x = 0; x < GXM; x++) + { + for (int y = 0; y < GYM; y++) + { + // These are unlinked monster inventory items -- skip them: + if (x == 0 && y == 0) + continue; + + // Looking for infinite stacks (ie more links than tems allowed) + // and for items which have bad coordinates (can't find their stack) + for (int obj = igrd[x][y]; obj != NON_ITEM; obj = mitm[obj].link) + { + // Check for invalid (zero quantity) items that are linked in + if (!is_valid_item( mitm[obj] )) + { + snprintf( info, INFO_SIZE, "Linked invalid item at (%d,%d)!", x, y); + mpr( info, MSGCH_WARN ); + item_name( mitm[obj], DESC_PLAIN, name ); + dump_item( name, obj, mitm[obj] ); + } + + // Check that item knows what stack it's in + if (mitm[obj].x != x || mitm[obj].y != y) + { + snprintf( info, INFO_SIZE, "Item position incorrect at (%d,%d)!", x, y); + mpr( info, MSGCH_WARN ); + item_name( mitm[obj], DESC_PLAIN, name ); + dump_item( name, obj, mitm[obj] ); + } + + // If we run into a premarked item we're in real trouble, + // this will also keep this from being an infinite loop. + if (mitm[obj].flags & ISFLAG_DEBUG_MARK) + { + snprintf( info, INFO_SIZE, "Potential INFINITE STACK at (%d, %d)", x, y); + mpr( info, MSGCH_WARN ); + break; + } + + mitm[obj].flags |= ISFLAG_DEBUG_MARK; + } + } + } + + // Now scan all the items on the level: + for (i = 0; i < MAX_ITEMS; i++) + { + if (!is_valid_item( mitm[i] )) + continue; + + item_name( mitm[i], DESC_PLAIN, name ); + + // Don't check (-1,-1) player items or (0,0) monster items + if ((mitm[i].x > 0 || mitm[i].y > 0) + && !(mitm[i].flags & ISFLAG_DEBUG_MARK)) + { + mpr( "Unlinked item:", MSGCH_WARN ); + dump_item( name, i, mitm[i] ); + + snprintf( info, INFO_SIZE, "igrd(%d,%d) = %d", mitm[i].x, mitm[i].y, + igrd[ mitm[i].x ][ mitm[i].y ] ); + mpr( info ); + + // Let's check to see if it's an errant monster object: + for (int j = 0; j < MAX_MONSTERS; j++) + { + for (int k = 0; k < NUM_MONSTER_SLOTS; k++) + { + if (menv[j].inv[k] == i) + { + snprintf( info, INFO_SIZE, "Held by monster #%d: %s at (%d,%d)", + j, ptr_monam( &menv[j], DESC_CAP_A ), + menv[j].x, menv[j].y ); + + mpr( info ); + } + } + } + } + + // Current bad items of interest: + // -- armour and weapons with large enchantments/illegal special vals + // + // -- items described as questionable (the class 100 bug) + // + // -- eggplant is an illegal throwing weapon + // + // -- bola is an illegal fixed artefact + // + // -- items described as buggy (typically adjectives out of range) + // (note: covers buggy, bugginess, buggily, whatever else) + // + if (strstr( name, "questionable" ) != NULL + || strstr( name, "eggplant" ) != NULL + || strstr( name, "bola" ) != NULL + || strstr( name, "bugg" ) != NULL) + { + mpr( "Bad item:", MSGCH_WARN ); + dump_item( name, i, mitm[i] ); + } + else if ((mitm[i].base_type == OBJ_WEAPONS + && (abs(mitm[i].plus) > 30 + || abs(mitm[i].plus2) > 30 + || (!is_random_artefact( mitm[i] ) + && (mitm[i].special >= 30 + && mitm[i].special < 181)))) + + || (mitm[i].base_type == OBJ_MISSILES + && (abs(mitm[i].plus) > 25 + || (!is_random_artefact( mitm[i] ) + && mitm[i].special >= 30))) + + || (mitm[i].base_type == OBJ_ARMOUR + && (abs(mitm[i].plus) > 25 + || (!is_random_artefact( mitm[i] ) + && mitm[i].sub_type != ARM_HELMET + && mitm[i].special >= 30)))) + { + mpr( "Bad plus or special value:", MSGCH_WARN ); + dump_item( name, i, mitm[i] ); + } + } + + // Don't want debugging marks interfering with anything else. + for (i = 0; i < MAX_ITEMS; i++) + mitm[i].flags &= (~ISFLAG_DEBUG_MARK); + + // Quickly scan monsters for "program bug"s. + for (i = 0; i < MAX_MONSTERS; i++) + { + const struct monsters *const monster = &menv[i]; + + if (monster->type == -1) + continue; + + moname( monster->type, true, DESC_PLAIN, name ); + + if (strcmp( name, "program bug" ) == 0) + { + mpr( "Program bug detected!", MSGCH_WARN ); + + snprintf( info, INFO_SIZE, + "Buggy monster detected: monster #%d; position (%d,%d)", + i, monster->x, monster->y ); + + mpr( info, MSGCH_WARN ); + } + } +} +#endif + +//--------------------------------------------------------------- +// +// debug_add_skills +// +//--------------------------------------------------------------- +#ifdef WIZARD +void debug_add_skills(void) +{ + int skill = debug_prompt_for_skill( "Which skill (by name)? " ); + + if (skill == -1) + mpr("That skill doesn't seem to exist."); + else + { + mpr("Exercising..."); + exercise(skill, 100); + } +} // end debug_add_skills() +#endif + +//--------------------------------------------------------------- +// +// debug_set_skills +// +//--------------------------------------------------------------- +#ifdef WIZARD +void debug_set_skills(void) +{ + int skill = debug_prompt_for_skill( "Which skill (by name)? " ); + + if (skill == -1) + mpr("That skill doesn't seem to exist."); + else + { + mpr( skill_name(skill) ); + int amount = debug_prompt_for_int( "To what level? ", true ); + + if (amount == -1) + canned_msg( MSG_OK ); + else + { + const int points = (skill_exp_needed( amount + 1 ) + * species_skills( skill, you.species )) / 100; + + you.skill_points[skill] = points + 1; + you.skills[skill] = amount; + + calc_total_skill_points(); + + redraw_skill( you.your_name, player_title() ); + + switch (skill) + { + case SK_FIGHTING: + calc_hp(); + break; + + case SK_SPELLCASTING: + case SK_INVOCATIONS: + case SK_EVOCATIONS: + calc_mp(); + break; + + case SK_DODGING: + you.redraw_evasion = 1; + break; + + case SK_ARMOUR: + you.redraw_armour_class = 1; + you.redraw_evasion = 1; + break; + + default: + break; + } + } + } +} // end debug_add_skills() +#endif + + +//--------------------------------------------------------------- +// +// debug_set_all_skills +// +//--------------------------------------------------------------- +#ifdef WIZARD +void debug_set_all_skills(void) +{ + int i; + int amount = debug_prompt_for_int( "Set all skills to what level? ", true ); + + if (amount < 0) // cancel returns -1 -- bwr + canned_msg( MSG_OK ); + else + { + if (amount > 27) + amount = 27; + + for (i = SK_FIGHTING; i < NUM_SKILLS; i++) + { + if (i == SK_UNUSED_1 + || (i > SK_UNARMED_COMBAT && i < SK_SPELLCASTING)) + { + continue; + } + + const int points = (skill_exp_needed( amount + 1 ) + * species_skills( i, you.species )) / 100; + + you.skill_points[i] = points + 1; + you.skills[i] = amount; + } + + redraw_skill( you.your_name, player_title() ); + + calc_total_skill_points(); + + calc_hp(); + calc_mp(); + + you.redraw_armour_class = 1; + you.redraw_evasion = 1; + } +} // end debug_add_skills() +#endif + + +//--------------------------------------------------------------- +// +// debug_add_mutation +// +//--------------------------------------------------------------- +#ifdef WIZARD +bool debug_add_mutation(void) +{ + bool success = false; + char specs[80]; + + // Yeah, the gaining message isn't too good for this... but + // there isn't an array of simple mutation names. -- bwr + mpr( "Which mutation (by message when getting mutation)? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return (false); + + int mutation = -1; + + for (int i = 0; i < NUM_MUTATIONS; i++) + { + char mut_name[80]; + strncpy( mut_name, mutation_name( i, 1 ), sizeof( mut_name ) ); + + char *ptr = strstr( strlwr(mut_name), strlwr(specs) ); + if (ptr != NULL) + { + // we take the first mutation that matches + mutation = i; + break; + } + } + + if (mutation == -1) + mpr("I can't warp you that way!"); + else + { + snprintf( info, INFO_SIZE, "Found: %s", mutation_name( mutation, 1 ) ); + mpr( info ); + + int levels = debug_prompt_for_int( "How many levels? ", false ); + + if (levels == 0) + { + canned_msg( MSG_OK ); + success = false; + } + else if (levels > 0) + { + for (int i = 0; i < levels; i++) + { + if (mutate( mutation )) + success = true; + } + } + else + { + for (int i = 0; i < -levels; i++) + { + if (delete_mutation( mutation )) + success = true; + } + } + } + + return (success); +} // end debug_add_mutation() +#endif + + +//--------------------------------------------------------------- +// +// debug_get_religion +// +//--------------------------------------------------------------- +#ifdef WIZARD +void debug_get_religion(void) +{ + char specs[80]; + + mpr( "Which god (by name)? ", MSGCH_PROMPT ); + get_input_line( specs, sizeof( specs ) ); + + if (specs[0] == '\0') + return; + + int god = -1; + + for (int i = 1; i < NUM_GODS; i++) + { + char name[80]; + strncpy( name, god_name(i), sizeof( name ) ); + + char *ptr = strstr( strlwr(name), strlwr(specs) ); + if (ptr != NULL) + { + god = i; + break; + } + } + + if (god == -1) + mpr( "That god doesn't seem to be taking followers today." ); + else + { + grd[you.x_pos][you.y_pos] = 179 + god; + god_pitch(god); + } +} // end debug_add_skills() +#endif + + +void error_message_to_player(void) +{ + mpr("Oh dear. There appears to be a bug in the program."); + 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, "Dungeon Crawl Stone Soup 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 fsim_stat(int stat) +{ + return (stat < 1 ? 1 : + stat > 60 ? 60 : + stat); +} + +static bool debug_fight_sim(int mindex, int missile_slot) +{ + FILE *ostat = fopen("fight.stat", "a"); + if (!ostat) + { + mprf("Can't write fight.stat: %s", strerror(errno)); + 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 = fsim_stat(Options.fsim_str); + you.intel = fsim_stat(Options.fsim_int); + you.dex = fsim_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_monnum(Options.fsim_mons.c_str()); + if (punching_bag == -1) + 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); +} +#endif |