/** * @file * @brief Functions for gods blessing followers. **/ #include "AppHdr.h" #include "godblessing.h" #include "artefact.h" // if_artefact() #include "env.h" // mitm #include "itemprop.h" // do_uncurse() #include "item_use.h" #include "items.h" // items_stack() #include "makeitem.h" // item_set_appearance() #include "mgen_data.h" #include "monster.h" #include "mon-gear.h" // give_specific_item() #include "mon-place.h" // create_monster() #include "mon-util.h" // give_monster_proper_name() #include "religion.h" #include "strings.h" // make_stringf #include "view.h" // flash_monster_colour /** * Given the weapon type, find an upgrade. * * @param old_type the current weapon_type of the weapon. * @param has_shield whether we can go from 1h -> 2h. * @param highlevel whether the orc is a strong type. * @returns a new weapon type (or sometimes the old one). */ // TODO: a less awful way of doing this? static int _upgrade_weapon_type(int old_type, bool has_shield, bool highlevel) { switch (old_type) { case WPN_CLUB: case WPN_WHIP: case WPN_MACE: return WPN_FLAIL; case WPN_FLAIL: return WPN_MORNINGSTAR; case WPN_MORNINGSTAR: return !has_shield ? WPN_DIRE_FLAIL : highlevel ? WPN_EVENINGSTAR : WPN_MORNINGSTAR; case WPN_DIRE_FLAIL: return WPN_GREAT_MACE; case WPN_DAGGER: return WPN_FALCHION; case WPN_SHORT_SWORD: return WPN_LONG_SWORD; case WPN_CUTLASS: return WPN_SCIMITAR; case WPN_FALCHION: return WPN_LONG_SWORD; case WPN_LONG_SWORD: return WPN_SCIMITAR; case WPN_SCIMITAR: return !has_shield ? WPN_GREAT_SWORD : highlevel ? WPN_BASTARD_SWORD : WPN_SCIMITAR; case WPN_GREAT_SWORD: return highlevel ? WPN_CLAYMORE : WPN_GREAT_SWORD; case WPN_HAND_AXE: return WPN_WAR_AXE; // Low level orcs shouldn't get fairly rare items. case WPN_WAR_AXE: return !highlevel ? WPN_WAR_AXE : has_shield ? WPN_BROAD_AXE : WPN_BATTLEAXE; case WPN_BATTLEAXE: return highlevel ? WPN_EXECUTIONERS_AXE : WPN_BATTLEAXE; case WPN_SPEAR: return WPN_TRIDENT; case WPN_TRIDENT: return has_shield ? WPN_TRIDENT : WPN_HALBERD; case WPN_HALBERD: return WPN_GLAIVE; case WPN_GLAIVE: return highlevel ? WPN_BARDICHE : WPN_GLAIVE; default: return old_type; } } /** * Get a weapon for beogh to gift to a weaponless orc. * * Tries to give something decent, but worse than player gifts. * * @param[in] mon_type The type of orc to gift to. * @return A weapon type to gift. */ static int _orc_weapon_gift_type(monster_type mon_type) { switch (mon_type) { case MONS_ORC: case MONS_ORC_WIZARD: case MONS_ORC_PRIEST: case MONS_ORC_HIGH_PRIEST: case MONS_ORC_SORCERER: return random_choose_weighted(2, WPN_HAND_AXE, // orcs love axes 1, WPN_SHORT_SWORD, 1, WPN_MACE, 0); case MONS_ORC_WARRIOR: case MONS_ORC_KNIGHT: case MONS_ORC_WARLORD: return random_choose_weighted(2, WPN_WAR_AXE, // orcs love axes 1, WPN_LONG_SWORD, 1, WPN_FLAIL, 1, WPN_SPEAR, 0); // give a lower tier of polearms; reaching is good on followers default: return WPN_CLUB; // shouldn't ever come up? } } /** * Give a weapon to an orc that doesn't have one. * * @param[in] orc The orc to give a weapon to. * @param weapon_type What weapon type to give the orc. */ static void _gift_weapon_to_orc(monster* orc, int weapon_type) { ASSERT(weapon_type != NUM_WEAPONS); item_def weapon; weapon.base_type = OBJ_WEAPONS; weapon.sub_type = weapon_type; weapon.quantity = 1; set_ident_flags(weapon, ISFLAG_IDENT_MASK); give_specific_item(orc, weapon); } /** * Attempt to give a follower appropriate ammo. * * @param[in] orc The orc to give ammo to. * @param[in] initial_gift Whether this is starting ammo or a restock. * (Give more ammo when restocking than initially.) */ void gift_ammo_to_orc(monster* orc, bool initial_gift) { const item_def* launcher = orc->launcher(); item_def ammo; ammo.base_type = OBJ_MISSILES; if (!launcher) ammo.sub_type = MI_TOMAHAWK; else ammo.sub_type = fires_ammo_type(*launcher); if (ammo.sub_type == MI_STONE) ammo.sub_type = MI_SLING_BULLET; // ugly special case // XXX: should beogh be gifting needles? // if not, we'd need special checks in player gifting, etc... better to // go along for now. if (ammo.sub_type == MI_NEEDLE) ammo.special = SPMSL_POISONED; ammo.quantity = 30 + random2(10); if (initial_gift || !launcher) ammo.quantity /= 2; const item_def* old_ammo = orc->missiles(); // don't give a drop message - it'd come before the bless message if (old_ammo && !items_stack(*old_ammo, ammo) && !orc->drop_item(MSLOT_MISSILE, false)) { return; // can't force them to drop the ammo, for some reason? } set_ident_flags(ammo, ISFLAG_IDENT_MASK); give_specific_item(orc, ammo); } /** * Attempt to bless a follower's melee weapon. * * @param[in] mon The follower whose weapon should be blessed. * @return The type of blessing given; may be empty. */ static string _beogh_bless_melee_weapon(monster* mon) { item_def* wpn_ptr = mon->melee_weapon(); ASSERT(wpn_ptr != NULL); item_def& wpn = *wpn_ptr; const int old_weapon_type = wpn.sub_type; // 50% chance of upgrading weapon type. if (!is_artefact(wpn) && coinflip()) { // lower chance of upgrading to very good weapon types bool highlevel_ok = one_chance_in(mon->type == MONS_ORC_WARLORD ? 3 : mon->type == MONS_ORC_KNIGHT ? 6 : 1000000); // it's one in a million! wpn.sub_type = _upgrade_weapon_type(wpn.sub_type, mon->inv[MSLOT_SHIELD] != NON_ITEM, highlevel_ok); } // Enchant and uncurse it. (Lower odds at high weapon enchantment.) const bool enchanted = !x_chance_in_y(wpn.plus, MAX_WPN_ENCHANT) && enchant_weapon(wpn, true); if (enchanted) set_ident_flags(wpn, ISFLAG_KNOW_PLUSES); if (!enchanted && wpn.sub_type == old_weapon_type) { if (wpn.cursed()) { do_uncurse_item(wpn, true, true); return "uncursed armament"; } dprf("Couldn't bless follower's weapon!"); return ""; } item_set_appearance(wpn); return "superior armament"; } /** * Attempt to give a follower a ranged weapon/ammo. * * @param[in] mon The follower who should be blessed. * @return The type of blessing given; may be empty. */ static string _beogh_bless_ranged_weapon(monster* mon) { // if they already have a launcher, restock it. // likewise if they have a shield but no launcher (give tomahawks) const bool mon_has_launcher = mon->launcher() != NULL; if (mon_has_launcher || mon->shield() != NULL) { gift_ammo_to_orc(mon); if (mon->missiles() != NULL) return mon_has_launcher ? "ammunition" : "ranged armament"; dprf("Couldn't give ammo to follower!"); return ""; // ? } // no launcher, no shield: give them a crossbow & some ammo. _gift_weapon_to_orc(mon, WPN_ARBALEST); if (mon->launcher() == NULL) { dprf("Couldn't give crossbow to follower!"); return ""; // ? } gift_ammo_to_orc(mon, true); if (mon->missiles() == NULL) dprf("Couldn't give initial ammo to follower"); return "ranged armament"; } /** * Attempt to bless a follower's weapon. * * @param[in] mon The follower whose weapon should be blessed. * @return The type of blessing given; may be empty. */ static string _beogh_bless_weapon(monster* mon) { const item_def* wpn_ptr = mon->melee_weapon(); if (wpn_ptr == NULL) { _gift_weapon_to_orc(mon, _orc_weapon_gift_type(mon->type)); if (mon->weapon() != NULL) return "armament"; dprf("Couldn't give a weapon to follower!"); return ""; // ? } const item_def* launch_ptr = mon->launcher(); const item_def* ammo_ptr = mon->missiles(); if (launch_ptr != NULL && ammo_ptr == NULL) { gift_ammo_to_orc(mon); if (mon->missiles() != NULL) return "ammunition"; dprf("Couldn't give ammo to follower!"); return ""; // ? } if (coinflip()) return _beogh_bless_melee_weapon(mon); return _beogh_bless_ranged_weapon(mon); } static void _upgrade_shield(item_def &sh) { // Promote from buckler up through large shield. if (sh.sub_type >= ARM_FIRST_SHIELD && sh.sub_type < ARM_LAST_SHIELD) sh.sub_type++; } static void _upgrade_body_armour(item_def &arm) { // Promote from robe up through plate. if (arm.sub_type >= ARM_FIRST_MUNDANE_BODY && arm.sub_type < ARM_LAST_MUNDANE_BODY // These are supposed to be robe-only. && arm.special != SPARM_ARCHMAGI && arm.special != SPARM_RESISTANCE) { arm.sub_type++; } } /** * Give armour (a shield or body armour) to an orc that doesn't have one. * * @param[in] orc The orc to give armour to. * @param[in] shield Whether to give a shield (default is body armour) */ static void _gift_armour_to_orc(monster* orc, bool shield = false) { const bool highlevel = orc->type == MONS_ORC_KNIGHT || orc->type == MONS_ORC_WARLORD; item_def armour; armour.base_type = OBJ_ARMOUR; if (shield) armour.sub_type = highlevel ? ARM_SHIELD : ARM_BUCKLER; else armour.sub_type = highlevel ? ARM_SCALE_MAIL : ARM_RING_MAIL; armour.quantity = 1; set_ident_flags(armour, ISFLAG_IDENT_MASK); give_specific_item(orc, armour); } /** * Attempt to bless a follower's armour. * * @param[in] mon The follower whose armour should be blessed. * @return The type of blessing; may be empty. */ static string _beogh_bless_armour(monster* mon) { const int armour = mon->inv[MSLOT_ARMOUR]; const int shield = mon->inv[MSLOT_SHIELD]; // always give naked orcs armour, if possible if (armour == NON_ITEM) { _gift_armour_to_orc(mon); if (mon->inv[MSLOT_ARMOUR] != NON_ITEM) return "armour"; dprf("Couldn't give armour to an orc!"); //? return ""; } // Pick either a monster's armour or its shield. const item_def* melee_weap = mon->melee_weapon(); const item_def* launcher = mon->launcher(); const bool can_use_shield = (melee_weap == NULL || mon->hands_reqd(*melee_weap) != HANDS_TWO) && (launcher == NULL || mon->hands_reqd(*launcher) != HANDS_TWO); const int slot = coinflip() && can_use_shield ? shield : armour; if (slot == NON_ITEM) { ASSERT(slot == shield); _gift_armour_to_orc(mon, true); if (mon->inv[MSLOT_SHIELD] != NON_ITEM) return "a shield"; dprf("Couldn't give a shield to an orc!"); //? return ""; } item_def& arm(mitm[slot]); const int old_subtype = arm.sub_type; // 50% chance of improving armour/shield type if (!is_artefact(arm) && coinflip()) { if (slot == shield) _upgrade_shield(arm); else _upgrade_body_armour(arm); } // And enchant or uncurse it. (Lower chance for higher enchantment.) int ac_change; const bool enchanted = !x_chance_in_y(arm.plus, armour_max_enchant(arm)) && enchant_armour(ac_change, true, arm); if (enchanted) set_ident_flags(arm, ISFLAG_KNOW_PLUSES); if (!enchanted && old_subtype == arm.sub_type) { dprf("Couldn't enchant follower's armour!"); return ""; } item_set_appearance(arm); return "improved armour"; } static bool _blessing_balms(monster* mon) { // Remove poisoning, sickness, confusion, and rotting, like a potion // of curing, but without the healing. Also, remove slowing and // fatigue. bool success = false; if (mon->del_ench(ENCH_POISON, true)) success = true; if (mon->del_ench(ENCH_SICK, true)) success = true; if (mon->del_ench(ENCH_CONFUSION, true)) success = true; if (mon->del_ench(ENCH_ROT, true)) success = true; if (mon->del_ench(ENCH_SLOW, true)) success = true; if (mon->del_ench(ENCH_FATIGUE, true)) success = true; if (mon->del_ench(ENCH_WRETCHED, true)) success = true; return success; } static bool _blessing_healing(monster* mon) { const int healing = mon->max_hit_points / 4 + 1; // Heal a monster. if (mon->heal(healing + random2(healing + 1))) { // A high-HP monster might get a unique name. if (x_chance_in_y(mon->max_hit_points + 1, 100)) give_monster_proper_name(mon); return true; } return false; } static bool _increase_ench_duration(monster* mon, mon_enchant ench, const int increase) { // Durations are saved as 16-bit signed ints, so clamp at the largest such. const int MARSHALL_MAX = (1 << 15) - 1; const int newdur = min(ench.duration + increase, MARSHALL_MAX); if (ench.duration >= newdur) return false; ench.duration = newdur; mon->update_ench(ench); return true; } /** * Attempt to bless a follower's weapon. * * @param[in] mon The follower whose weapon should be blessed. * @return The type of blessing; may be empty. */ static string _tso_bless_weapon(monster* mon) { if (mon->type == MONS_DANCING_WEAPON) { dprf("Can't bless a dancing weapon's weapon!"); return ""; } item_def* wpn_ptr = mon->weapon(); if (wpn_ptr == NULL) { dprf("Couldn't bless follower's weapon; they have none!"); return ""; } item_def& wpn = *wpn_ptr; // Enchant and uncurse it. if (!enchant_weapon(wpn, true)) { dprf("Couldn't bless follower's weapon!"); return ""; } item_set_appearance(wpn); set_ident_flags(wpn, ISFLAG_KNOW_PLUSES); return "superior armament"; } /** * Attempt to bless a follower's armour. * * @param[in] follower The follower whose armour should be blessed. * @return The type of blessing; may be empty. */ static string _tso_bless_armour(monster* follower) { // Pick either a monster's armour or its shield. const int armour = follower->inv[MSLOT_ARMOUR]; const int shield = follower->inv[MSLOT_SHIELD]; if (armour == NON_ITEM && shield == NON_ITEM) { dprf("Can't bless the armour of a naked follower!"); return ""; } const int slot = shield == NON_ITEM || (coinflip() && armour != NON_ITEM) ? armour : shield; item_def& arm(mitm[slot]); int ac_change; if (!enchant_armour(ac_change, true, arm)) { dprf("Couldn't enchant follower's armour!"); return ""; } item_set_appearance(arm); set_ident_flags(arm, ISFLAG_KNOW_PLUSES); return "improved armour"; } static bool _tso_blessing_extend_stay(monster* mon) { if (!mon->has_ench(ENCH_ABJ)) return false; mon_enchant abj = mon->get_ench(ENCH_ABJ); // These numbers are tenths of a player turn. Holy monsters get a // much bigger boost than random beasties, which get at most double // their current summon duration. if (mon->is_holy()) return _increase_ench_duration(mon, abj, 1100 + random2(1100)); else return _increase_ench_duration(mon, abj, min(abj.duration, 500 + random2(500))); } static bool _tso_blessing_friendliness(monster* mon) { if (!mon->has_ench(ENCH_CHARM)) return false; // [ds] Just increase charm duration, no permanent friendliness. const int base_increase = 700; return _increase_ench_duration(mon, mon->get_ench(ENCH_CHARM), base_increase + random2(base_increase)); } static void _beogh_reinf_callback(const mgen_data &mg, monster *&mon, int placed) { ASSERT(mg.god == GOD_BEOGH); // Beogh tries a second time to place reinforcements. if (!mon) mon = create_monster(mg); if (!mon) return; mon->flags |= MF_ATT_CHANGE_ATTEMPT; bool high_level = (mon->type == MONS_ORC_PRIEST || mon->type == MONS_ORC_WARRIOR || mon->type == MONS_ORC_KNIGHT); // For high level orcs, there's a chance of being named. if (high_level && one_chance_in(5)) give_monster_proper_name(mon); } // If you don't currently have any followers, send a small band to help // you out. static void _beogh_blessing_reinforcements() { // Possible reinforcement. const monster_type followers[] = { MONS_ORC, MONS_ORC, MONS_ORC_WIZARD, MONS_ORC_PRIEST }; const monster_type high_xl_followers[] = { MONS_ORC_PRIEST, MONS_ORC_WARRIOR, MONS_ORC_KNIGHT }; // Send up to four followers. int how_many = random2(4) + 1; monster_type follower_type; for (int i = 0; i < how_many; ++i) { if (random2(you.experience_level) >= 9 && coinflip()) follower_type = RANDOM_ELEMENT(high_xl_followers); else follower_type = RANDOM_ELEMENT(followers); delayed_monster( mgen_data(follower_type, BEH_FRIENDLY, &you, 0, 0, you.pos(), MHITYOU, 0, GOD_BEOGH), _beogh_reinf_callback); } } static bool _beogh_blessing_priesthood(monster* mon) { if (mon->type != MONS_ORC) return false; mon->upgrade_type(MONS_ORC_PRIEST, true, true); return true; } /** * Attempt to bless a follower with curing and/or healing. * * @param[in] follower The follower to heal. * @return The type of healing that occurred; may be empty. */ static string _bless_with_healing(monster* follower) { string blessing = ""; // Maybe try to cure status conditions. bool balms = false; if (coinflip()) { balms = _blessing_balms(follower); if (balms) blessing = "divine balms"; else dprf("Couldn't apply balms."); } // Heal the follower. bool healing = _blessing_healing(follower); // Maybe heal the follower again. if ((!healing || coinflip()) && _blessing_healing(follower)) healing = true; if (healing) { if (balms) blessing += " and "; blessing += "healing"; } else dprf("Couldn't heal monster."); return blessing; } /** * Print a message for a god blessing a follower. * * Also flash the screen, in local tiles. * * @param[in] follower The follower being blessed. * @param god The god doing the blessing. * @param blessing The blessing being delivered. */ static void _display_god_blessing(monster* follower, god_type god, string blessing) { ASSERT(follower); string whom = you.can_see(follower) ? follower->name(DESC_THE) : "a follower"; simple_god_message(make_stringf(" blesses %s with %s.", whom.c_str(), blessing.c_str()).c_str(), god); #ifndef USE_TILE_LOCAL flash_monster_colour(follower, god_colour(god), 200); #endif } /** * Have Beogh attempt to bless the specified follower. * * He may choose to bless another nearby follower, if no follower is specified, * or the follower is invalid. * * @param[in] follower The follower to try to bless. * @param[in] force Whether to check follower validity. * @return Whether a blessing occurred. */ static bool _beogh_bless_follower(monster* follower, bool force) { // If a follower was specified, and it's suitable, pick it. // Otherwise, pick a random follower. // XXX: factor out into another function? if (!follower || (!force && !is_follower(follower))) { if (!one_chance_in(10)) return false; // Choose a random follower in LOS, preferably a named or // priestly one. follower = choose_random_nearby_monster(0, is_follower, true); } if (!follower) { if (coinflip()) return false; // Try *again*, on the entire level (2.5% chance). follower = choose_random_monster_on_level(0, is_follower); } if (!follower) { // If no follower was found, attempt to send // reinforcements. _beogh_blessing_reinforcements(); // Possibly send more reinforcements. if (coinflip()) _beogh_blessing_reinforcements(); delayed_monster_done("Beogh blesses you with " "reinforcements.", ""); // Return true, even though the reinforcements might // not be placed. return true; } string blessing = ""; // 10% chance of blessing to priesthood. if (force || one_chance_in(10)) { if (_beogh_blessing_priesthood(follower)) blessing = "priesthood"; else dprf("Couldn't promote monster to priesthood"); } // ~15% chance of blessing armament (assume that most priest buffs fail) if (blessing.empty() && (force || one_chance_in(7))) { blessing = coinflip() ? _beogh_bless_weapon(follower) : _beogh_bless_armour(follower); } // If they got a good blessing (priesthood or equipment), maybe give them // a name. if (!blessing.empty()) give_monster_proper_name(follower); // ~85% chance of trying to heal. if (blessing.empty()) blessing = _bless_with_healing(follower); if (blessing.empty()) return false; _display_god_blessing(follower, GOD_BEOGH, blessing); return true; } /** * Attempt to increase the duration of a follower. * * Their summon duration, if summoned, or charm duration, if charmed. * * @param[in] follower The follower to bless. * @return The type of blessing that was given; may be empty. */ static string _tso_bless_duration(monster* follower) { // Extend a monster's stay if it's abjurable, or extend charm // duration. If neither is possible, deliberately fall through. const bool more_time = _tso_blessing_extend_stay(follower); const bool friendliness = _tso_blessing_friendliness(follower); if (!more_time && !friendliness) { dprf("Couldn't increase monster's friendliness or summon time."); return ""; } string blessing = ""; if (friendliness) { blessing += "friendliness"; if (more_time) blessing += " and "; } if (more_time) blessing += "more time in this world"; return blessing; } /** * Have The Shining One attempt to bless the specified follower. * * 10% chance of blessing equipment; otherwise, increase duration, or * barring that, just heal & cure. * * @param[in] follower The follower to try to bless. * @param[in] force Whether to check follower validity. * @return Whether a blessing occurred. */ static bool _tso_bless_follower(monster* follower, bool force) { if (!follower || (!force && !is_follower(follower))) return false; string blessing = ""; if (one_chance_in(10) || force) { blessing = coinflip() ? _tso_bless_weapon(follower) : _tso_bless_armour(follower); } if (blessing.empty()) blessing = _tso_bless_duration(follower); if (blessing.empty()) blessing = _bless_with_healing(follower); if (blessing.empty()) return false; _display_god_blessing(follower, GOD_SHINING_ONE, blessing); return true; } // Bless the follower indicated in follower, if any. If there isn't // one, bless a random follower within sight of the player, if any, or, // with decreasing chances, any follower on the level. // Blessing can be enforced with a wizard mode command. bool bless_follower(monster* follower, god_type god, bool force) { switch (god) { case GOD_BEOGH: return _beogh_bless_follower(follower, force); case GOD_SHINING_ONE: return _tso_bless_follower(follower, force); default: return false; // XXX: print something here? } }