From ab63ef558038d212bca62752d16d584df8a33477 Mon Sep 17 00:00:00 2001 From: dshaligram Date: Wed, 6 Sep 2006 08:53:23 +0000 Subject: r90@xenon: dshaligram | 2006-09-06 14:24:41 +051800 Instrumented Crawl to dump detailed damage numbers for any given weapon to facilitate retuning weapon damage calculations. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/branches/stone_soup@34 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/acr.cc | 4 + crawl-ref/source/beam.cc | 3 +- crawl-ref/source/beam.h | 2 + crawl-ref/source/debug.cc | 316 ++++++++++++++++++++++++++++++++++++++++--- crawl-ref/source/debug.h | 1 + crawl-ref/source/externs.h | 6 + crawl-ref/source/fight.cc | 15 +- crawl-ref/source/fight.h | 2 +- crawl-ref/source/initfile.cc | 33 +++++ crawl-ref/source/item_use.cc | 38 ++++-- crawl-ref/source/item_use.h | 3 + crawl-ref/source/message.cc | 15 ++ crawl-ref/source/message.h | 9 ++ 13 files changed, 412 insertions(+), 35 deletions(-) diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc index 669b72315a..b94e55a2c0 100644 --- a/crawl-ref/source/acr.cc +++ b/crawl-ref/source/acr.cc @@ -580,6 +580,10 @@ static void handle_wizard_command( void ) tweak_object(); break; + case 'F': + debug_fight_statistics(); + break; + case 'm': create_spec_monster(); break; diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index ebb4cf0201..e223347e3d 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -71,7 +71,6 @@ static FixedArray < bool, 19, 19 > explode_map; // be public): static void sticky_flame_monster( int mn, bool source, int hurt_final ); static bool affectsWalls(struct bolt &beam); -static int affect(struct bolt &beam, int x, int y); static bool isBouncy(struct bolt &beam); static void beam_drop_object( struct bolt &beam, item_def *item, int x, int y ); static bool beam_term_on_target(struct bolt &beam); @@ -2684,7 +2683,7 @@ static bool fuzzyLine(int nx, int ny, int &tx, int &ty, int lx, int ly, // 4. if cell holds monster, affect monster affect_monster() // 5. return range used affectation. -static int affect(struct bolt &beam, int x, int y) +int affect(struct bolt &beam, int x, int y) { // extra range used by hitting something int rangeUsed = 0; diff --git a/crawl-ref/source/beam.h b/crawl-ref/source/beam.h index 503220db44..d081332bb6 100644 --- a/crawl-ref/source/beam.h +++ b/crawl-ref/source/beam.h @@ -92,4 +92,6 @@ void mimic_alert( struct monsters *mimic ); void zapping( char ztype, int power, struct bolt &pbolt ); +int affect(struct bolt &beam, int x, int y); + #endif diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc index 83b910d236..591cf7ab38 100644 --- a/crawl-ref/source/debug.cc +++ b/crawl-ref/source/debug.cc @@ -29,9 +29,11 @@ #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" @@ -393,30 +395,24 @@ void TRACE(const char *format, ...) // //--------------------------------------------------------------- #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 ); - get_input_line( specs, sizeof( specs ) ); - - if (specs[0] == '\0') - return (-1); +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, obj_name ); + moname( i, true, DESC_PLAIN, mname ); - ptr = strstr( strlwr(obj_name), strlwr(specs) ); + const char *ptr = strstr( strlwr(mname), search ); if (ptr != NULL) { - mpr( obj_name ); - if (ptr == obj_name) + if (ptr == mname) { // we prefer prefixes over partial matches mon = i; @@ -426,9 +422,22 @@ static int debug_prompt_for_monster( void ) 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 //--------------------------------------------------------------- @@ -1715,3 +1724,276 @@ 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_dfs_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 dfs_melee_skill(const item_def *item) +{ + skill_type sk = SK_UNARMED_COMBAT; + if (item) + sk = weapon_skill(*item); + return (sk); +} + +static void dfs_set_melee_skill(int skill, const item_def *item) +{ + you.skills[dfs_melee_skill(item)] = skill; + you.skills[SK_FIGHTING] = skill * 15 / 27; +} + +static void dfs_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 dfs_ranged_item(FILE *out, const item_def *launcher, + int wskill, unsigned long damage, + long iterations, long hits, + int maxdam) +{ + double hitdam = hits? double(damage) / hits : 0.0; + fprintf(out, "Ranged: %s %d. Accuracy: %ld%%, av damage: %.2f, av hitdam: %.2f, max: %d\n", + skill_name( range_skill(*launcher) ), + wskill, + 100 * hits / iterations, + double(damage) / iterations, + hitdam, + maxdam); +} + +static void dfs_melee_item(FILE *out, const item_def *item, + int wskill, unsigned long damage, + long iterations, long hits, + int maxdam) +{ + double hitdam = hits? double(damage) / hits : 0.0; + fprintf(out, "Melee: %s %d. Accuracy: %ld%%, av damage: %.2f, av hitdam: %.2f, max: %d\n", + skill_name( dfs_melee_skill(item) ), + wskill, + 100 * hits / iterations, + double(damage) / iterations, + hitdam, + maxdam); +} + +static bool dfs_ranged_combat(FILE *out, int wskill, int mi, + const item_def *item) +{ + monsters &mon = menv[mi]; + unsigned long cumulative_damage = 0L; + long hits = 0L; + int maxdam = 0; + + const int thrown = get_fire_item_index(); + if (thrown == ENDOFPACK) + { + mprf("No suitable missiles for combat simulation."); + return (false); + } + + dfs_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; + if (throw_it(beam, thrown, &mon)) + hits++; + you.hunger = hunger; + + int damage = (mon.max_hit_points - mon.hit_points); + cumulative_damage += damage; + if (damage > maxdam) + maxdam = damage; + } + dfs_ranged_item(out, item, wskill, cumulative_damage, + iter_limit, hits, maxdam); + + return (true); +} + +static bool dfs_melee_combat(FILE *out, int wskill, int mi, + const item_def *item) +{ + monsters &mon = menv[mi]; + unsigned long cumulative_damage = 0L; + long hits = 0L; + int maxdam = 0; + + dfs_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; + if (you_attack(mi, true)) + hits++; + you.hunger = hunger; + + int damage = (mon.max_hit_points - mon.hit_points); + cumulative_damage += damage; + if (damage > maxdam) + maxdam = damage; + } + dfs_melee_item(out, item, wskill, cumulative_damage, iter_limit, hits, + maxdam); + + return (true); +} + +static bool debug_fight_simulate(FILE *out, int wskill, int mi) +{ + 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 dfs_ranged_combat(out, wskill, mi, iweap); + else + return dfs_melee_combat(out, wskill, mi, iweap); +} + +static std::string dfs_weapon() +{ + 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 = get_fire_item_index(); + 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 void dfs_title(FILE *o, int mon) +{ + char buf[ITEMNAME_SIZE]; + fprintf(o, "Combat simulation: %s %s vs. %s (%ld turns)\n", + species_name(you.species, you.experience_level), + you.class_name, + moname(menv[mon].type, true, DESC_PLAIN, buf), + Options.fsim_rounds); + 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, "\nWeapon : %s\n", dfs_weapon().c_str()); + fprintf(o, "\n"); +} + +static int fsim_stat(int stat) +{ + return (stat < 1 ? 1 : + stat > 60 ? 60 : + stat); +} + +// 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() +{ + int punching_bag = get_monnum(Options.fsim_mons.c_str()); + if (punching_bag == -1) + punching_bag = MONS_WORM; + + int mindex = create_dfs_monster(punching_bag, 500); + if (mindex == -1) + { + mprf("Failed to create punching bag"); + return; + } + + FILE *ostat = fopen("fight.stat", "a"); + if (!ostat) + { + mprf("Can't write fight.stat: %s", strerror(errno)); + return; + } + + FixedVector skill_backup = you.skills; + int ystr = you.strength, + yint = you.intel, + ydex = you.dex; + int yxp = you.experience_level; + + 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); + + dfs_title(ostat, mindex); + for (int wskill = 0; wskill <= 27; ++wskill) + { + mesclr(); + mprf("Calculating average damage for %s at skill %d", + dfs_weapon().c_str(), wskill); + if (!debug_fight_simulate(ostat, wskill, mindex)) + 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) + { + 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", dfs_weapon().c_str()); + +done_combat_sim: + fprintf(ostat, "-----------------------------------\n\n"); + + fclose(ostat); + monster_die(&menv[mindex], KILL_DISMISSED, 0); +} +#endif diff --git a/crawl-ref/source/debug.h b/crawl-ref/source/debug.h index cbc8161d3b..8675ef9de3 100644 --- a/crawl-ref/source/debug.h +++ b/crawl-ref/source/debug.h @@ -143,5 +143,6 @@ void stethoscope(int mwh); void debug_item_scan( void ); void debug_get_religion( void ); void debug_change_species( void ); +void debug_fight_statistics( void ); #endif diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h index afa9084e8b..aab1dd2d87 100644 --- a/crawl-ref/source/externs.h +++ b/crawl-ref/source/externs.h @@ -775,6 +775,12 @@ struct game_options // If the player prefers to merge kill records, this option can do that. int kill_map[KC_NCATEGORIES]; + + // Parameters for fight simulations. + long fsim_rounds; + int fsim_str, fsim_int, fsim_dex; + int fsim_xl; + std::string fsim_mons; typedef std::map opt_map; opt_map named_options; // All options not caught above are diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index 2b59c92f03..a478f6002a 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -127,7 +127,8 @@ int effective_stat_bonus( int wepType ) #endif } -void you_attack(int monster_attacked, bool unarmed_attacks) +// Returns true if you hit the monster. +bool you_attack(int monster_attacked, bool unarmed_attacks) { struct monsters *defender = &menv[monster_attacked]; @@ -273,7 +274,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) if (random2(you.dex) < 4 || one_chance_in(5)) { mpr("Unstable footing causes you to fumble your attack."); - return; + return (false); } } @@ -949,7 +950,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) snprintf( info, INFO_SIZE, "You %s the ball lightning.", damage_noise); mpr(info); } - return; + return (true); } if (damage_done < 1 && player_monster_visible( defender )) @@ -1491,7 +1492,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) if (coinflip()) { monster_die(defender, KILL_RESET, 0); - return; + return (true); } break; @@ -1526,7 +1527,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) if (defender->hit_points < 1) { monster_die(defender, KILL_YOU, 0); - return; + return (hit); } if (unarmed_attacks) @@ -1840,7 +1841,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) strcat(info, "the ball lightning."); mpr(info); } - return; + return (true); } } else @@ -1858,7 +1859,7 @@ void you_attack(int monster_attacked, bool unarmed_attacks) if (hit) print_wounds(defender); - return; + return (hit); } // end you_attack() void monster_attack(int monster_attacking) diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 10515868c6..9b54b8b339 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -32,7 +32,7 @@ int weapon_str_weight( int wpn_class, int wpn_type ); /* *********************************************************************** * called from: acr - it_use3 * *********************************************************************** */ -void you_attack(int monster_attacked, bool unarmed_attacks); +bool you_attack(int monster_attacked, bool unarmed_attacks); // last updated: 08jun2000 {dlb} diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc index 0e64ebd602..e7e62adc1f 100644 --- a/crawl-ref/source/initfile.cc +++ b/crawl-ref/source/initfile.cc @@ -433,6 +433,11 @@ void reset_options(bool clear_name) for (int i = 2; i < NUM_FIRE_TYPES; i++) Options.fire_order[i] = FIRE_NONE; + Options.fsim_rounds = 40000L; + Options.fsim_mons = "worm"; + Options.fsim_str = Options.fsim_int = Options.fsim_dex = 15; + Options.fsim_xl = 10; + // These are only used internally, and only from the commandline: // XXX: These need a better place. Options.sc_entries = 0; @@ -1281,6 +1286,34 @@ void parse_option_line(const std::string &str, bool runscript) { Options.show_waypoints = read_bool(field, Options.show_waypoints); } + else if (key == "fsim_rounds") + { + Options.fsim_rounds = atol(field.c_str()); + if (Options.fsim_rounds < 1000) + Options.fsim_rounds = 1000; + if (Options.fsim_rounds > 500000L) + Options.fsim_rounds = 500000L; + } + else if (key == "fsim_mons") + { + Options.fsim_mons = field; + } + else if (key == "fsim_str") + { + Options.fsim_str = atoi(field.c_str()); + } + else if (key == "fsim_int") + { + Options.fsim_int = atoi(field.c_str()); + } + else if (key == "fsim_dex") + { + Options.fsim_dex = atoi(field.c_str()); + } + else if (key == "fsim_xl") + { + Options.fsim_xl = atoi(field.c_str()); + } else if (key == "travel_delay") { // Read travel delay in milliseconds. diff --git a/crawl-ref/source/item_use.cc b/crawl-ref/source/item_use.cc index f67fe7cfd7..49ee1a8e2e 100644 --- a/crawl-ref/source/item_use.cc +++ b/crawl-ref/source/item_use.cc @@ -68,7 +68,6 @@ #include "wpn-misc.h" bool drink_fountain(void); -static void throw_it(struct bolt &pbolt, int throw_2); void use_randart(unsigned char item_wield_2); static bool enchant_armour( void ); @@ -1159,7 +1158,12 @@ void shoot_thing(void) // throw_it - currently handles player throwing only. Monster // throwing is handled in mstuff2:mons_throw() -static void throw_it(struct bolt &pbolt, int throw_2) +// Note: If dummy_target is non-NULL, throw_it fakes a bolt and calls +// affect() on the monster's square. +// +// Return value is only relevant if dummy_target is non-NULL, and returns +// true if dummy_target is hit. +bool throw_it(struct bolt &pbolt, int throw_2, monsters *dummy_target) { struct dist thr; char shoot_skill = 0; @@ -1176,16 +1180,26 @@ static void throw_it(struct bolt &pbolt, int throw_2) bool launched = false; // item is launched bool thrown = false; // item is sensible thrown item - mpr( STD_DIRECTION_PROMPT, MSGCH_PROMPT ); - message_current_target(); - direction( thr, DIR_NONE, TARG_ENEMY ); + if (dummy_target) + { + thr.isValid = true; + thr.isCancel = false; + thr.tx = dummy_target->x; + thr.ty = dummy_target->y; + } + else + { + mpr( STD_DIRECTION_PROMPT, MSGCH_PROMPT ); + message_current_target(); + direction( thr, DIR_NONE, TARG_ENEMY ); + } if (!thr.isValid) { if (thr.isCancel) canned_msg(MSG_OK); - return; + return (false); } // Must unwield before fire_beam() makes a copy in order to remove things @@ -1783,10 +1797,16 @@ static void throw_it(struct bolt &pbolt, int throw_2) if (wepClass == OBJ_MISSILES || wepClass == OBJ_WEAPONS) item.flags |= ISFLAG_THROWN; + bool hit = false; // using copy, since the launched item might be differect (venom blowgun) - fire_beam( pbolt, &item ); + if (dummy_target) + hit = (affect( pbolt, dummy_target->x, dummy_target->y ) != 0); + else + { + fire_beam( pbolt, &item ); - dec_inv_item_quantity( throw_2, 1 ); + dec_inv_item_quantity( throw_2, 1 ); + } // throwing and blowguns are silent if (launched && lnchType != WPN_BLOWGUN) @@ -1796,6 +1816,8 @@ static void throw_it(struct bolt &pbolt, int throw_2) alert_nearby_monsters(); you.turn_is_over = 1; + + return (hit); } // end throw_it() bool puton_item(int item_slot, bool prompt_finger) diff --git a/crawl-ref/source/item_use.h b/crawl-ref/source/item_use.h index 9115ecf6bd..93d3ec3098 100644 --- a/crawl-ref/source/item_use.h +++ b/crawl-ref/source/item_use.h @@ -15,6 +15,7 @@ #include +#include "externs.h" // last updated 12may2000 {dlb} @@ -130,4 +131,6 @@ bool puton_item(int slot, bool prompt_finger = true); bool enchant_weapon( int which_stat, bool quiet = false ); +bool throw_it(struct bolt &pbolt, int throw_2, monsters *dummy_target = NULL); + #endif diff --git a/crawl-ref/source/message.cc b/crawl-ref/source/message.cc index c6f391a53e..353a069abc 100644 --- a/crawl-ref/source/message.cc +++ b/crawl-ref/source/message.cc @@ -39,6 +39,18 @@ int Next_Message = 0; // end of messages char Message_Line = 0; // line of next (previous?) message +static bool suppress_messages = false; + +no_messages::no_messages() : msuppressed(suppress_messages) +{ + suppress_messages = true; +} + +no_messages::~no_messages() +{ + suppress_messages = msuppressed; +} + static char god_message_altar_colour( char god ) { int rnd; @@ -244,6 +256,9 @@ void mprf( const char *format, ... ) void mpr(const char *inf, int channel, int param) { + if (suppress_messages) + return; + char info2[80]; int colour = channel_to_colour( channel, param ); diff --git a/crawl-ref/source/message.h b/crawl-ref/source/message.h index 8878464d95..83f7787d35 100644 --- a/crawl-ref/source/message.h +++ b/crawl-ref/source/message.h @@ -57,6 +57,15 @@ void mpr(const char *inf, int channel = MSGCH_PLAIN, int param = 0); void mprf( int channel, const char *format, ... ); void mprf( const char *format, ... ); +class no_messages +{ +public: + no_messages(); + ~no_messages(); +private: + bool msuppressed; +}; + // last updated 12may2000 {dlb} /* *********************************************************************** * called from: acr -- cgit v1.2.3-54-g00ecf