static long eval_max_dam(struct monster_race *race, int ridx) { int rlev, i; int melee_dam = 0, atk_dam = 0, spell_dam = 0; int dam = 1; /* Extract the monster level, force 1 for town monsters */ rlev = ((race->level >= 1) ? race->level : 1); /* Assume single resist for the elemental attacks */ spell_dam = best_spell_power(race, 1); /* Hack - Apply over 10 rounds */ spell_dam *= 10; /* Scale for frequency and availability of mana / ammo */ if (spell_dam) { int freq = race->freq_spell; /* Hack -- always get 1 shot */ if (freq < 10) freq = 10; /* Adjust for frequency */ spell_dam = spell_dam * freq / 100; } /* Check attacks */ for (i = 0; i < z_info->mon_blows_max; i++) { int effect, method; random_value dice; if (!race->blow) break; /* Extract the attack infomation */ effect = race->blow[i].effect; method = race->blow[i].method; dice = race->blow[i].dice; /* Assume maximum damage */ atk_dam = eval_blow_effect(effect, dice, race->level); /* Factor for dangerous side effects */ if (monster_blow_method_stun(method)) { /* Stun definitely most dangerous*/ atk_dam *= 4; atk_dam /= 3; } else if (monster_blow_method_stun(method)) { /* Cut */ atk_dam *= 7; atk_dam /= 5; } /* Normal melee attack */ if (!rf_has(race->flags, RF_NEVER_BLOW)) { /* Keep a running total */ melee_dam += atk_dam; } } /* Apply damage over 10 rounds. We assume that the monster has to make * contact first. * Hack - speed has more impact on melee as has to stay in contact with * player. * Hack - this is except for pass wall and kill wall monsters which can * always get to the player. * Hack - use different values for huge monsters as they strike out to * range 2. */ if (flags_test(race->flags, RF_SIZE, RF_KILL_WALL, RF_PASS_WALL, FLAG_END)) melee_dam *= 10; else melee_dam = melee_dam * 3 + melee_dam * adj_energy(race) / 7; /* Scale based on attack accuracy. We make a massive number of * assumptions here and just use monster level. */ melee_dam = melee_dam * MIN(45 + rlev * 3, 95) / 100; /* Hack -- Monsters that multiply ignore the following reductions */ if (!rf_has(race->flags, RF_MULTIPLY)) { /*Reduce damamge potential for monsters that move randomly */ if (flags_test(race->flags, RF_SIZE, RF_RAND_25, RF_RAND_50, FLAG_END)) { int reduce = 100; if (rf_has(race->flags, RF_RAND_25)) reduce -= 25; if (rf_has(race->flags, RF_RAND_50)) reduce -= 50; /* Even moving randomly one in 8 times will hit the player */ reduce += (100 - reduce) / 8; /* Adjust the melee damage */ melee_dam = (melee_dam * reduce) / 100; } /* Monsters who can't move are much less of a combat threat */ if (rf_has(race->flags, RF_NEVER_MOVE)) { if (rsf_has(race->spell_flags, RSF_TELE_TO) || rsf_has(race->spell_flags, RSF_BLINK)) { /* Scale for frequency */ melee_dam = melee_dam / 5 + 4 * melee_dam * race->freq_spell / 500; /* Incorporate spell failure chance */ if (!rf_has(race->flags, RF_STUPID)) melee_dam = melee_dam / 5 + 4 * melee_dam * MIN(75 + (rlev + 3) / 4, 100) / 500; } else if (rf_has(race->flags, RF_INVISIBLE)) melee_dam /= 3; else melee_dam /= 5; } } /* But keep at a minimum */ if (melee_dam < 1) melee_dam = 1; /* Combine spell and melee damage */ dam = (spell_dam + melee_dam); highest_threat[ridx] = dam; final_spell_dam[ridx] = spell_dam; final_melee_dam[ridx] = melee_dam; /* Adjust for speed - monster at speed 120 will do double damage, monster * at speed 100 will do half, etc. Bonus for monsters who can haste self */ dam = (dam * adj_energy(race)) / 10; /* Adjust threat for speed -- multipliers are more threatening. */ if (rf_has(race->flags, RF_MULTIPLY)) highest_threat[ridx] = (highest_threat[ridx] * adj_energy(race)) / 5; /* Adjust threat for friends, this can be improved, but is probably good * enough for now. */ if (race->friends) highest_threat[ridx] *= 2; else if (race->friends_base) /* Friends base is weaker, because they are <= monster level */ highest_threat[ridx] = highest_threat[ridx] * 3 / 2; /* But keep at a minimum */ if (dam < 1) dam = 1; /* We're done */ return (dam); }
/** * Attack the player via physical attacks. */ bool make_attack_normal(struct monster *mon, struct player *p) { struct monster_lore *lore = get_lore(mon->race); int ap_cnt; int k, tmp, ac, rlev; char m_name[80]; char ddesc[80]; bool blinked; /* Not allowed to attack */ if (rf_has(mon->race->flags, RF_NEVER_BLOW)) return (false); /* Total armor */ ac = p->state.ac + p->state.to_a; /* Extract the effective monster level */ rlev = ((mon->race->level >= 1) ? mon->race->level : 1); /* Get the monster name (or "it") */ monster_desc(m_name, sizeof(m_name), mon, MDESC_STANDARD); /* Get the "died from" information (i.e. "a kobold") */ monster_desc(ddesc, sizeof(ddesc), mon, MDESC_SHOW | MDESC_IND_VIS); /* Assume no blink */ blinked = false; /* Scan through all blows */ for (ap_cnt = 0; ap_cnt < z_info->mon_blows_max; ap_cnt++) { bool visible = false; bool obvious = false; bool do_break = false; int power = 0; int damage = 0; int do_cut = 0; int do_stun = 0; int sound_msg = MSG_GENERIC; const char *act = NULL; /* Extract the attack infomation */ int effect = mon->race->blow[ap_cnt].effect; int method = mon->race->blow[ap_cnt].method; random_value dice = mon->race->blow[ap_cnt].dice; /* Hack -- no more attacks */ if (!method) break; /* Handle "leaving" */ if (p->is_dead || p->upkeep->generate_level) break; /* Extract visibility (before blink) */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) visible = true; /* Extract visibility from carrying light */ if (rf_has(mon->race->flags, RF_HAS_LIGHT)) visible = true; /* Extract the attack "power" */ power = monster_blow_effect_power(effect); /* Monster hits player */ if (!effect || check_hit(p, power, rlev)) { melee_effect_handler_f effect_handler; /* Always disturbing */ disturb(p, 1); /* Hack -- Apply "protection from evil" */ if (p->timed[TMD_PROTEVIL] > 0) { /* Learn about the evil flag */ if (mflag_has(mon->mflag, MFLAG_VISIBLE)) rf_on(lore->flags, RF_EVIL); if (rf_has(mon->race->flags, RF_EVIL) && p->lev >= rlev && randint0(100) + p->lev > 50) { /* Message */ msg("%s is repelled.", m_name); /* Hack -- Next attack */ continue; } } /* Describe the attack method */ act = monster_blow_method_action(method); do_cut = monster_blow_method_cut(method); do_stun = monster_blow_method_stun(method); sound_msg = monster_blow_method_message(method); /* Message */ if (act) msgt(sound_msg, "%s %s", m_name, act); /* Hack -- assume all attacks are obvious */ obvious = true; /* Roll dice */ damage = randcalc(dice, rlev, RANDOMISE); /* Perform the actual effect. */ effect_handler = melee_handler_for_blow_effect(effect); if (effect_handler != NULL) { melee_effect_handler_context_t context = { p, mon, rlev, method, ac, ddesc, obvious, blinked, do_break, damage, }; effect_handler(&context); /* Save any changes made in the handler for later use. */ obvious = context.obvious; blinked = context.blinked; damage = context.damage; do_break = context.do_break; } else { msg("ERROR: Effect handler not found for %d.", effect); } /* Don't cut or stun if player is dead */ if (p->is_dead) { do_cut = false; do_stun = false; } /* Hack -- only one of cut or stun */ if (do_cut && do_stun) { /* Cancel cut */ if (randint0(100) < 50) do_cut = 0; /* Cancel stun */ else do_stun = 0; } /* Handle cut */ if (do_cut) { /* Critical hit (zero if non-critical) */ tmp = monster_critical(dice, rlev, damage); /* Roll for damage */ switch (tmp) { case 0: k = 0; break; case 1: k = randint1(5); break; case 2: k = randint1(5) + 5; break; case 3: k = randint1(20) + 20; break; case 4: k = randint1(50) + 50; break; case 5: k = randint1(100) + 100; break; case 6: k = 300; break; default: k = 500; break; } /* Apply the cut */ if (k) (void)player_inc_timed(p, TMD_CUT, k, true, true); } /* Handle stun */ if (do_stun) { /* Critical hit (zero if non-critical) */ tmp = monster_critical(dice, rlev, damage); /* Roll for damage */ switch (tmp) { case 0: k = 0; break; case 1: k = randint1(5); break; case 2: k = randint1(10) + 10; break; case 3: k = randint1(20) + 20; break; case 4: k = randint1(30) + 30; break; case 5: k = randint1(40) + 40; break; case 6: k = 100; break; default: k = 200; break; } /* Apply the stun */ if (k) (void)player_inc_timed(p, TMD_STUN, k, true, true); } } else { /* Visible monster missed player, so notify if appropriate. */ if (mflag_has(mon->mflag, MFLAG_VISIBLE) && monster_blow_method_miss(method)) { /* Disturbing */ disturb(p, 1); msg("%s misses you.", m_name); } } /* Analyze "visible" monsters only */ if (visible) { /* Count "obvious" attacks (and ones that cause damage) */ if (obvious || damage || (lore->blows[ap_cnt].times_seen > 10)) { /* Count attacks of this type */ if (lore->blows[ap_cnt].times_seen < UCHAR_MAX) lore->blows[ap_cnt].times_seen++; } } /* Skip the other blows if necessary */ if (do_break) break; } /* Blink away */ if (blinked) { char dice[5]; msg("There is a puff of smoke!"); strnfmt(dice, sizeof(dice), "%d", z_info->max_sight * 2 + 5); effect_simple(EF_TELEPORT, dice, 0, 0, 0, NULL); } /* Always notice cause of death */ if (p->is_dead && (lore->deaths < SHRT_MAX)) lore->deaths++; /* Learn lore */ lore_update(mon->race, lore); /* Assume we attacked */ return (true); }