diff options
Diffstat (limited to 'crawl-ref/source/wiz-fsim.cc')
-rw-r--r-- | crawl-ref/source/wiz-fsim.cc | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/crawl-ref/source/wiz-fsim.cc b/crawl-ref/source/wiz-fsim.cc new file mode 100644 index 0000000000..fd58cc30b8 --- /dev/null +++ b/crawl-ref/source/wiz-fsim.cc @@ -0,0 +1,581 @@ +/* + * File: wiz-fsim.cc + * Summary: Fight simualtion wizard functions. + * Written by: Linley Henzell and Jesse Jones + */ + +#include "AppHdr.h" + +#include "wiz-fsim.h" + +#include <errno.h> + +#include "beam.h" +#include "dbg-util.h" +#include "fight.h" +#include "items.h" +#include "item_use.h" +#include "it_use2.h" +#include "message.h" +#include "monplace.h" +#include "monster.h" +#include "monstuff.h" +#include "options.h" +#include "player.h" +#include "quiver.h" +#include "skills2.h" +#include "species.h" + +#ifdef WIZARD +static int _create_fsim_monster(int mtype, int hp) +{ + const int mi = + create_monster( + mgen_data::hostile_at( + static_cast<monster_type>(mtype), you.pos())); + + 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; + you.skills[SK_ARMOUR] = skill * 15 / 27; + you.skills[SK_SHIELDS] = skill; + for (int i = 0; i < 15; ++i) + you.skills[SK_SPELLCASTING + i] = skill; +} + +static void _fsim_set_ranged_skill(int skill, const item_def *item) +{ + you.skills[range_skill(*item)] = skill; + you.skills[SK_THROWING] = skill * 15 / 27; +} + +static void _fsim_item(FILE *out, + bool melee, + const item_def *weap, + const char *wskill, + unsigned long damage, + long iterations, long hits, + int maxdam, unsigned long time) +{ + double hitdam = hits? double(damage) / hits : 0.0; + double avspeed = ((double) time / (double) iterations); + fprintf(out, + " %-5s| %3ld%% | %5.2f | %5.2f |" + " %5.2f | %3d | %5.2g\n", + wskill, + 100 * hits / iterations, + double(damage) / iterations, + hitdam, + double(damage) * player_speed() / avspeed / iterations, + maxdam, + avspeed); +} + +static void _fsim_defence_item(FILE *out, long cum, int hits, int max, + int speed, long iters) +{ + // AC | EV | Arm | Dod | Acc | Av.Dam | Av.HitDam | Eff.Dam | Max.Dam | Av.Time + fprintf(out, "%2d %2d %2d %2d %3ld%% %5.2f %5.2f %5.2f %3d" + " %2d\n", + player_AC(), + player_evasion(), + you.skills[SK_DODGING], + you.skills[SK_ARMOUR], + 100 * hits / iters, + double(cum) / iters, + hits? double(cum) / hits : 0.0, + double(cum) / iters * speed / 10, + max, + 100 / speed); +} + + +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 ? you.m_quiver->get_fire_item() : 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(); + + // throw_it() will decrease quantity by 1 + inc_inv_item_quantity(thrown, 1); + + beam.target = mon.pos(); + if (throw_it(beam, thrown, true, DEBUG_COOKIE)) + 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, make_stringf("%2d", wskill).c_str(), + cumulative_damage, iter_limit, hits, maxdam, time_taken); + + return (true); +} + +static bool _fsim_mon_melee(FILE *out, int dodge, int armour, int mi) +{ + you.skills[SK_DODGING] = dodge; + you.skills[SK_ARMOUR] = armour; + + const int yhp = you.hp; + const int ymhp = you.hp_max; + unsigned long cumulative_damage = 0L; + long hits = 0L; + int maxdam = 0; + no_messages mx; + + for (long i = 0; i < Options.fsim_rounds; ++i) + { + you.hp = you.hp_max = 5000; + monster_attack(&menv[mi]); + const int damage = you.hp_max - you.hp; + if (damage) + hits++; + cumulative_damage += damage; + if (damage > maxdam) + maxdam = damage; + } + + you.hp = yhp; + you.hp_max = ymhp; + + _fsim_defence_item(out, cumulative_damage, hits, maxdam, menv[mi].speed, + Options.fsim_rounds); + 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, make_stringf("%2d", wskill).c_str(), + 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(int missile_slot) +{ + const item_def *iweap = _fsim_weap_item(); + if (!iweap && missile_slot != -1) + return skill_name(range_skill(you.inv[missile_slot])); + + if (iweap && iweap->base_type == OBJ_WEAPONS) + { + if (is_range_weapon(*iweap)) + return skill_name(range_skill(*iweap)); + + return skill_name(_fsim_melee_skill(iweap)); + } + return skill_name(SK_UNARMED_COMBAT); +} + +static std::string _fsim_weapon(int missile_slot) +{ + std::string item_buf; + if (you.equip[EQ_WEAPON] != -1 || missile_slot != -1) + { + if (you.equip[EQ_WEAPON] != -1) + { + const item_def &weapon = you.inv[ you.equip[EQ_WEAPON] ]; + item_buf = weapon.name(DESC_PLAIN, true); + if (is_range_weapon(weapon)) + { + const int missile = + (missile_slot == -1 ? you.m_quiver->get_fire_item() + : missile_slot); + + if (missile < ENDOFPACK && missile >= 0) + { + return item_buf + " with " + + you.inv[missile].name(DESC_PLAIN); + } + } + } + else + return you.inv[missile_slot].name(DESC_PLAIN); + } + else + return "unarmed"; + + return item_buf; +} + +static std::string _fsim_time_string() +{ + time_t curr_time = time(NULL); + struct tm *ltime = TIME_FN(&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) +{ + fprintf(o, "Monster : %s\n", mon.name(DESC_PLAIN, true).c_str()); + fprintf(o, "HD : %d\n", mon.hit_dice); + fprintf(o, "AC : %d\n", mon.ac); + fprintf(o, "EV : %d\n", mon.ev); +} + +static void _fsim_title(FILE *o, int mon, int ms) +{ + fprintf(o, CRAWL " version %s\n\n", Version::Long().c_str()); + fprintf(o, "Combat simulation: %s %s vs. %s (%ld rounds) (%s)\n", + species_name(you.species, you.experience_level).c_str(), + you.class_name, + menv[mon].name(DESC_PLAIN, true).c_str(), + 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(ms).c_str()); + fprintf(o, "\n"); + fprintf(o, "Skill | Accuracy | Av.Dam | Av.HitDam | Eff.Dam | Max.Dam | Av.Time\n"); +} + +static void _fsim_defence_title(FILE *o, int mon) +{ + fprintf(o, CRAWL " version %s\n\n", Version::Long().c_str()); + fprintf(o, "Combat simulation: %s vs. %s %s (%ld rounds) (%s)\n", + menv[mon].name(DESC_PLAIN).c_str(), + species_name(you.species, you.experience_level).c_str(), + you.class_name, + 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, "AC | EV | Dod | Arm | Acc | Av.Dam | Av.HitDam | Eff.Dam | Max.Dam | Av.Time\n"); +} + +static bool _fsim_mon_hit_you(FILE *ostat, int mindex, int) +{ + _fsim_defence_title(ostat, mindex); + + for (int sk = 0; sk <= 27; ++sk) + { + mesclr(); + mprf("Calculating average damage for %s at dodging %d", + menv[mindex].name(DESC_PLAIN).c_str(), + sk); + + if (!_fsim_mon_melee(ostat, sk, 0, mindex)) + return (false); + + 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"); + return (false); + } + } + + for (int sk = 0; sk <= 27; ++sk) + { + mesclr(); + mprf("Calculating average damage for %s at armour %d", + menv[mindex].name(DESC_PLAIN).c_str(), + sk); + + if (!_fsim_mon_melee(ostat, 0, sk, mindex)) + return (false); + + 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"); + return (false); + } + } + + mprf("Done defence simulation with %s", + menv[mindex].name(DESC_PLAIN).c_str()); + + return (true); +} + +static bool _fsim_you_hit_mon(FILE *ostat, int mindex, int missile_slot) +{ + _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)) + return (false); + + 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"); + return (false); + } + } + mprf("Done fight simulation with %s", _fsim_weapon(missile_slot).c_str()); + return (true); +} + +static bool debug_fight_sim(int mindex, int missile_slot, + bool (*combat)(FILE *, int mind, int mslot)) +{ + FILE *ostat = fopen("fight.stat", "a"); + if (!ostat) + { + mprf(MSGCH_ERROR, "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 = debug_cap_stat(Options.fsim_str); + you.intel = debug_cap_stat(Options.fsim_int); + you.dex = debug_cap_stat(Options.fsim_dex); + + combat(ostat, mindex, missile_slot); + + you.skills = skill_backup; + you.strength = ystr; + you.intel = yint; + you.dex = ydex; + you.experience_level = yxp; + + fprintf(ostat, "-----------------------------------\n\n"); + fclose(ostat); + + return (success); +} + +int fsim_kit_equip(const std::string &kit) +{ + int missile_slot = -1; + + 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); + } + + if (!weapon.empty()) + { + for (int i = 0; i < ENDOFPACK; ++i) + { + if (!you.inv[i].is_valid()) + continue; + + if (you.inv[i].name(DESC_PLAIN).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; + } + } + } + else if (you.equip[EQ_WEAPON] != -1) + unwield_item(false); + + if (!missile.empty()) + { + for (int i = 0; i < ENDOFPACK; ++i) + { + if (!you.inv[i].is_valid()) + continue; + + if (you.inv[i].name(DESC_PLAIN).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, bool defence) +{ + int punching_bag = get_monster_by_name(Options.fsim_mons); + if (punching_bag == -1 || punching_bag == MONS_NO_MONSTER) + punching_bag = MONS_WORM; + + int mindex = _create_fsim_monster(punching_bag, 500); + if (mindex == -1) + { + mprf("Failed to create punching bag"); + return; + } + + you.exp_available = 0; + + if (!use_defaults || defence) + { + debug_fight_sim(mindex, -1, + defence? _fsim_mon_hit_you : _fsim_you_hit_mon); + } + else + { + 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()); + break; + } + if (!debug_fight_sim(mindex, missile, _fsim_you_hit_mon)) + break; + } + } + monster_die(&menv[mindex], KILL_DISMISSED, NON_MONSTER); +} + +#endif |