/* * File: wiz-fsim.cc * Summary: Fight simualtion wizard functions. * Written by: Linley Henzell and Jesse Jones */ #include "AppHdr.h" #include "wiz-fsim.h" #include #include "beam.h" #include "dbg-util.h" #include "env.h" #include "fight.h" #include "itemprop.h" #include "items.h" #include "item_use.h" #include "it_use2.h" #include "message.h" #include "mon-place.h" #include "mgen_data.h" #include "monster.h" #include "mon-stuff.h" #include "options.h" #include "player.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(mtype), "the fight simulator", false, 0, 0, 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", you.armour_class(), 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) { if (const item_def* weapon = you.weapon()) { std::string item_buf = weapon->name(DESC_PLAIN, true); // If it's a ranged weapon, add the description of the missile // if applicable. if (is_range_weapon(*weapon)) { const int missile = (missile_slot == -1 ? you.m_quiver->get_fire_item() : missile_slot); if (missile < ENDOFPACK && missile >= 0) item_buf += " with " + you.inv[missile].name(DESC_PLAIN); } return item_buf; } else if (missile_slot != -1) return you.inv[missile_slot].name(DESC_PLAIN); else return "unarmed"; } static std::string _fsim_time_string() { time_t curr_time = time(NULL); struct tm *ltime = TIME_FN(&curr_time); if (ltime) { return make_stringf("%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 (""); } 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 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.weapon()) 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