Apply 4.1ish monster to-hit calculations. M_FIGHTER monsters get bonus to-hit.
Split melee and beam to-hit calculations again. Melee to-hit is now largely 4.1ish, but uses randomised player evasion.
Applied 4.1 unique rebalancing. Higher level uniques now hit harder and have more hp.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1074 c06c8d41-db1a-0410-9941-cceddc491573
FUEEIUKGHHFPIRZCN3N753GONWAZTWQ2ZWR53IBJAAZ6FZUNGOMAC
ZHFUXYUHS6V47WK2NRH7OU6RX77NRKTXOZC3MND2GG7PEEWSGFTAC
GS2I6EO4OQZ5FHQJQ6YRUDPSJ5XWGLHN544MVQPSF2PATUSJFSSAC
IIDDQROLZFP47VSCQUUCI4A5VGV762RJBTT3GM2K4AFRE5KZ4BFAC
EWYQBSLHJPWW2HWOPSPK7BYJ4Q62IYCM5SI4YK6QQS6TTDWOM7BQC
7KWDC7XFNMBLSUO2HISIROBINZBX5T67LJEEXTAORXW2YZ7VWFGAC
NW74NALMBEWIKEOBIXA65RQULHS6M4GZ4S5IWDMEGWUAAAY7CQNQC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
RIMJO42HB75SAZH44KZ2UH2QULOPIJLMEN4CR2CYNR2EKEKLFHSQC
{ {AT_HIT, AF_PLAIN, 3}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 1, 0, 0, 14 },
{ {AT_HIT, AF_PLAIN, 5}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 1, 0, 0, 20 },
{ {AT_HIT, AF_PLAIN, 14}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 10, 0, 0, 53 },
{ {AT_HIT, AF_PLAIN, 25}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 10, 0, 0, 105 },
{ {AT_HIT, AF_PLAIN, 14}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 11, 0, 0, 60 },
{ {AT_HIT, AF_PLAIN, 18}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 11, 0, 0, 90 },
{ {AT_HIT, AF_PLAIN, 11}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 11, 0, 0, 64 },
{ {AT_HIT, AF_PLAIN, 25}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 11, 0, 0, 140 },
{ {AT_HIT, AF_PLAIN, 14}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 13, 0, 0, 55 },
{ {AT_HIT, AF_PLAIN, 24}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 13, 0, 0, 118 },
{ {AT_HIT, AF_PLAIN, 12}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 13, 0, 0, 52 },
{ {AT_HIT, AF_PLAIN, 17}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 13, 0, 0, 106 },
{ {AT_HIT, AF_PLAIN, 12}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 14, 0, 0, 67 },
{ {AT_HIT, AF_PLAIN, 19}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 14, 0, 0, 110 },
{ {AT_HIT, AF_PLAIN, 11}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 14, 0, 0, 70 },
{ {AT_HIT, AF_PLAIN, 29}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 14, 0, 0, 121 },
{ {AT_HIT, AF_PLAIN, 13}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 16, 0, 0, 80 },
{ {AT_HIT, AF_PLAIN, 21}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 16, 0, 0, 123 },
{ {AT_HIT, AF_PLAIN, 14}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 17, 0, 0, 78 },
{ {AT_HIT, AF_PLAIN, 22}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 17, 0, 0, 140 },
{ {AT_HIT, AF_PLAIN, 14}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 18, 0, 0, 83 },
{ {AT_HIT, AF_PLAIN, 22}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 18, 0, 0, 136 },
{ {AT_HIT, AF_PLAIN, 16}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 20, 0, 0, 95 },
{ {AT_HIT, AF_PLAIN, 36}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 20, 0, 0, 214 },
{ {AT_HIT, AF_PLAIN, 17}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 21, 0, 0, 105 },
{ {AT_HIT, AF_PLAIN, 27}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 21, 0, 0, 159 },
{ {AT_HIT, AF_PLAIN, 18}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 22, 0, 0, 119 },
{ {AT_HIT, AF_PLAIN, 30}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 22, 0, 0, 164 },
{ {AT_HIT, AF_PLAIN, 15}, {AT_TOUCH, AF_DRAIN_XP, 0}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 22, 0, 0, 99 },
{ {AT_HIT, AF_PLAIN, 25}, {AT_TOUCH, AF_DRAIN_XP, 15}, {AT_NONE, AF_PLAIN, 0}, {AT_NONE, AF_PLAIN, 0} },
{ 22, 0, 0, 154 },
bool test_melee_hit(int to_hit, int ev)
{
int roll = -1;
int margin = AUTOMATIC_HIT;
ev *= 2;
if (to_hit >= AUTOMATIC_HIT)
return (true);
else if (random2(1000) < 10 * MIN_HIT_MISS_PERCENTAGE)
margin = (coinflip() ? 1 : -1) * AUTOMATIC_HIT;
else
{
roll = random2( to_hit + 1 );
margin = (roll - random2avg(ev, 2));
}
#if DEBUG_DIAGNOSTICS
float miss;
if (to_hit < ev)
miss = 100.0 - static_cast<float>( MIN_HIT_MISS_PERCENTAGE ) / 2.0;
else
{
miss = static_cast<float>( MIN_HIT_MISS_PERCENTAGE ) / 2.0
+ static_cast<float>( (100 - MIN_HIT_MISS_PERCENTAGE) * ev )
/ static_cast<float>( to_hit );
}
mprf( MSGCH_DIAGNOSTICS,
"to hit: %d; ev: %d; miss: %0.2f%%; roll: %d; result: %s%s (%d)",
to_hit, ev, miss, roll, (margin >= 0) ? "hit" : "miss",
(roll == -1) ? "!!!" : "", margin );
#endif
return (margin >= 0);
}
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);
#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);
// 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
}
//---------------------------------------------------------------
//
// 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
bool melee,
const item_def *weap,
int wskill, unsigned long damage,
long iterations, long hits,
int maxdam, unsigned long time)
bool melee,
const item_def *weap,
const char *wskill,
unsigned long damage,
long iterations, long hits,
int maxdam, unsigned long time)
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);
}
fsim_item(out, false, item, wskill, cumulative_damage,
iter_limit, hits, maxdam, time_taken);
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(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;
static void fsim_defence_title(FILE *o, int mon)
{
fprintf(o, CRAWL " version " VERSION "\n\n");
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),
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 debug_fight_sim(int mindex, int missile_slot)
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))
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;
}
}
combat(ostat, mindex, missile_slot);
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)
for (int i = 0, size = Options.fsim_kit.size(); i < size; ++i)
mprf("Aborting sim on %s", Options.fsim_kit[i].c_str());
goto fsim_mcleanup;
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;