diff options
Diffstat (limited to 'src/xtra2.cc')
-rw-r--r-- | src/xtra2.cc | 5335 |
1 files changed, 5335 insertions, 0 deletions
diff --git a/src/xtra2.cc b/src/xtra2.cc new file mode 100644 index 00000000..d8b87f51 --- /dev/null +++ b/src/xtra2.cc @@ -0,0 +1,5335 @@ +/* + * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke + * + * This software may be copied and distributed for educational, research, and + * not for profit purposes provided that this copyright and statement are + * included in all such copies. + */ + +#include "xtra2.hpp" + +#include "artifact_type.hpp" +#include "cave.hpp" +#include "cave_type.hpp" +#include "corrupt.hpp" +#include "dungeon_info_type.hpp" +#include "ego_item_type.hpp" +#include "feature_flag.hpp" +#include "feature_type.hpp" +#include "files.hpp" +#include "game.hpp" +#include "gods.hpp" +#include "hook_player_level_in.hpp" +#include "hook_monster_death_in.hpp" +#include "hooks.hpp" +#include "melee2.hpp" +#include "mimic.hpp" +#include "monster1.hpp" +#include "monster2.hpp" +#include "monster3.hpp" +#include "monster_ego.hpp" +#include "monster_race.hpp" +#include "monster_race_flag.hpp" +#include "monster_type.hpp" +#include "notes.hpp" +#include "object1.hpp" +#include "object2.hpp" +#include "object_flag.hpp" +#include "object_kind.hpp" +#include "options.hpp" +#include "player_class.hpp" +#include "player_race.hpp" +#include "player_race_flag.hpp" +#include "player_race_mod.hpp" +#include "player_type.hpp" +#include "point.hpp" +#include "randart.hpp" +#include "skill_type.hpp" +#include "skills.hpp" +#include "spells1.hpp" +#include "spells2.hpp" +#include "stats.hpp" +#include "store_info_type.hpp" +#include "tables.hpp" +#include "util.hpp" +#include "util.h" +#include "variable.h" +#include "variable.hpp" +#include "wilderness_map.hpp" +#include "wilderness_type_info.hpp" +#include "wizard2.hpp" +#include "xtra1.hpp" +#include "z-rand.hpp" + +#include <boost/algorithm/string/predicate.hpp> +#include <cassert> +#include <fmt/format.h> +#include <type_traits> + + + +using boost::algorithm::iequals; + +static void corrupt_corrupted(); + +/* + * Set "p_ptr->parasite" and "p_ptr->parasite_r_idx" + * notice observable changes + */ +bool_ set_parasite(int v, int r) +{ + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!p_ptr->parasite) + { + msg_print("You feel something growing in you."); + notice = TRUE; + } + } + + /* Shut */ + else + { + if (p_ptr->parasite) + { + if (magik(80)) + { + char r_name[80]; + int wx, wy; + int attempts = 500; + + monster_race_desc(r_name, p_ptr->parasite_r_idx, 0); + + do + { + scatter(&wy, &wx, p_ptr->py, p_ptr->px, 10); + } + while (!(in_bounds(wy, wx) && cave_floor_bold(wy, wx)) && --attempts); + + if (place_monster_one(wy, wx, p_ptr->parasite_r_idx, 0, FALSE, MSTATUS_ENEMY)) + { + cmsg_format(TERM_L_BLUE, "Your body convulses and spawns %s.", r_name); + p_ptr->food -= 750; + if (p_ptr->food < 100) p_ptr->food = 100; + } + } + else + { + cmsg_print(TERM_L_BLUE, "The hideous thing growing in you seems to die."); + } + notice = TRUE; + } + } + + /* Use the value */ + p_ptr->parasite = v; + p_ptr->parasite_r_idx = r; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Result */ + return (TRUE); +} + +/* + * Set a simple player field. + */ +static bool_ set_simple_field( + s16b *p_field, + s16b v, + byte activate_color, + cptr activate_msg, + byte deactivate_color, + cptr deactivate_msg) +{ + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!*p_field) + { + cmsg_print(activate_color, activate_msg); + notice = TRUE; + } + } + + /* Shut */ + else + { + if (*p_field) + { + cmsg_print(deactivate_color, deactivate_msg); + notice = TRUE; + } + } + + /* Use the value */ + *p_field = v; + + /* Nothing to notice */ + if (!notice) + return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Result */ + return (TRUE); +} + +/* + * Set "p_ptr->tim_project" and others + * notice observable changes + */ +bool_ set_project(int v, s16b gf, s16b dam, s16b rad, s16b flag) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_project, v, + TERM_WHITE, "Your weapon starts glowing.", + TERM_WHITE, "Your weapon stops glowing."); + + /* Use the values */ + p_ptr->tim_project_gf = gf; + p_ptr->tim_project_dam = dam; + p_ptr->tim_project_rad = rad; + p_ptr->tim_project_flag = flag; + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->tim_roots" and others + * notice observable changes + */ +bool_ set_roots(int v, s16b ac, s16b dam) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_roots, v, + TERM_WHITE, "Roots dive into the floor from your feet.", + TERM_WHITE, "The roots of your feet suddenly vanish."); + + /* Use the values */ + p_ptr->tim_roots_dam = dam; + p_ptr->tim_roots_ac = ac; + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->tim_(magic|water)_breath" and others + * notice observable changes + */ +bool_ set_tim_breath(int v, bool_ magical) +{ + if (magical) + { + return set_simple_field( + &p_ptr->tim_magic_breath, v, + TERM_WHITE, "Air seems to fill your lungs without breathing.", + TERM_WHITE, "You need to breathe again."); + } + else + { + return set_simple_field( + &p_ptr->tim_water_breath, v, + TERM_WHITE, "Water seems to fill your lungs.", + TERM_WHITE, "The water filling your lungs evaporates."); + } +} + +/* + * Set timered precognition + */ +bool_ set_tim_precognition(int v) +{ + return set_simple_field( + &p_ptr->tim_precognition, v, + TERM_WHITE, "You feel able to predict the future.", + TERM_WHITE, "You feel less able to predict the future."); +} + +/* + * Set "p_ptr->absorb_soul" + * notice observable changes + */ +bool_ set_absorb_soul(int v) +{ + return set_simple_field( + &p_ptr->absorb_soul, v, + TERM_L_DARK, "You start absorbing the souls of your foes.", + TERM_L_DARK, "You stop absorbing the souls of dead foes."); +} + +/* + * Set "p_ptr->disrupt_shield" + * notice observable changes + */ +bool_ set_disrupt_shield(int v) +{ + return set_simple_field( + &p_ptr->disrupt_shield, v, + TERM_L_BLUE, "You feel invulnerable.", + TERM_L_RED, "You are more vulnerable."); +} + +/* + * Set "p_ptr->prob_travel" + * notice observable changes + */ +bool_ set_prob_travel(int v) +{ + return set_simple_field( + &p_ptr->prob_travel, v, + TERM_WHITE, "You feel instable.", + TERM_WHITE, "You are more stable."); +} + +/* + * Set "p_ptr->tim_invis", and "p_ptr->tim_inv_pow", + * notice observable changes + */ +bool_ set_invis(int v, int p) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_invisible, v, + TERM_WHITE, "You feel your body fade away.", + TERM_WHITE, "You are no longer invisible."); + + /* Use the power value */ + p_ptr->tim_inv_pow = p; + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->tim_poison", + * notice observable changes + */ +bool_ set_poison(int v) +{ + return set_simple_field( + &p_ptr->tim_poison, v, + TERM_WHITE, "Your hands are dripping with venom.", + TERM_WHITE, "The venom source dries out."); +} + +/* + * Set "no_breeds" + */ +bool_ set_no_breeders(int v) +{ + return set_simple_field( + &no_breeds, v, + TERM_WHITE, "You feel an anti-sexual aura.", + TERM_WHITE, "You no longer feel an anti-sexual aura."); +} + +/* + * Set "p_ptr->tim_deadly" + */ +bool_ set_tim_deadly(int v) +{ + return set_simple_field( + &p_ptr->tim_deadly, v, + TERM_WHITE, "You feel extremely accurate.", + TERM_WHITE, "You are suddenly much less accurate."); +} + +/* + * Set "p_ptr->tim_ffall" + */ +bool_ set_tim_ffall(int v) +{ + return set_simple_field( + &p_ptr->tim_ffall, v, + TERM_WHITE, "You feel very light.", + TERM_WHITE, "You are suddenly heavier."); +} + +/* + * Set "p_ptr->tim_fly" + */ +bool_ set_tim_fly(int v) +{ + return set_simple_field( + &p_ptr->tim_fly, v, + TERM_WHITE, "You feel able to reach the clouds.", + TERM_WHITE, "You are suddenly a lot heavier."); +} + +/* + * Set "p_ptr->tim_reflect" + */ +bool_ set_tim_reflect(int v) +{ + return set_simple_field( + &p_ptr->tim_reflect, v, + TERM_WHITE, "You start reflecting the world around you.", + TERM_WHITE, "You stop reflecting."); +} + +/* + * Set "p_ptr->strike" + */ +bool_ set_strike(int v) +{ + return set_simple_field( + &p_ptr->strike, v, + TERM_WHITE, "You feel very accurate.", + TERM_WHITE, "You are no longer very accurate."); +} + +/* + * Set "p_ptr->oppose_cc" + */ +bool_ set_oppose_cc(int v) +{ + return set_simple_field( + &p_ptr->oppose_cc, v, + TERM_WHITE, "You feel protected against raw chaos.", + TERM_WHITE, "You are no longer protected against chaos."); +} + +/* + * Set "p_ptr->tim_mimic", and "p_ptr->mimic_form", + * notice observable changes + */ +bool_ set_mimic(int v, int p, int level) +{ + auto &s_info = game->s_info; + + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!p_ptr->tim_mimic) + { + msg_print("You feel your body change."); + p_ptr->mimic_form = p; + notice = TRUE; + } + } + + /* Shut */ + else + { + if (p_ptr->tim_mimic) + { + msg_print("You are no longer transformed."); + p_ptr->mimic_form = 0; + notice = TRUE; + if (p == resolve_mimic_name("Bear")) + { + s_info[SKILL_BEAR].hidden = true; + select_default_melee(); + } + p = 0; + } + } + + /* Use the value */ + p_ptr->tim_mimic = v; + p_ptr->mimic_level = level; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Redraw title */ + p_ptr->redraw |= (PR_FRAME); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BODY | PU_BONUS | PU_SANITY); + + /* Result */ + return (TRUE); +} + +/* + * Set "p_ptr->blind", notice observable changes + * + * Note the use of "PU_UN_VIEW", which is needed to memorize any terrain + * features which suddenly become "visible". + * Note that blindness is currently the only thing which can affect + * "player_can_see_bold()". + */ +bool_ set_blind(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->blind, v, + TERM_WHITE, "You are blind!", + TERM_WHITE, "You can see again."); + + if (notice) + { + /* Fully update the visuals */ + p_ptr->update |= (PU_UN_VIEW | PU_VIEW | PU_MONSTERS | PU_MON_LITE); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Redraw the "blind" */ + p_ptr->redraw |= (PR_FRAME); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->tim_lite", notice observable changes + * + * Note the use of "PU_VIEW", which is needed to + * memorize any terrain features which suddenly become "visible". + * Note that blindness is currently the only thing which can affect + * "player_can_see_bold()". + */ +bool_ set_lite(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_lite, v, + TERM_WHITE, "You suddenly seem brighter!", + TERM_WHITE, "You are no longer bright."); + + if (notice) + { + /* Fully update the visuals */ + p_ptr->update |= (PU_VIEW | PU_MONSTERS); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->confused", notice observable changes + */ +bool_ set_confused(int v) +{ + bool_ notice = + set_simple_field( + &p_ptr->confused, v, + TERM_WHITE, "You are confused!", + TERM_WHITE, "You feel less confused now."); + + if (notice) + { + /* Redraw the "confused" */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->poisoned", notice observable changes + */ +bool_ set_poisoned(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->poisoned, v, + TERM_WHITE, "You are poisoned!", + TERM_WHITE, "You are no longer poisoned."); + + if (notice) + { + /* Redraw the "poisoned" */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->afraid", notice observable changes + */ +bool_ set_afraid(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->afraid, v, + TERM_WHITE, "You are terrified!", + TERM_WHITE, "You feel bolder now."); + + if (notice) + { + /* Redraw the "afraid" */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Mechanics for setting the "paralyzed" field. + */ +static bool_ set_paralyzed_aux(int v) +{ + bool_ notice; + + /* Normal processing */ + notice = set_simple_field( + &p_ptr->paralyzed, v, + TERM_WHITE, "You are paralyzed!", + TERM_WHITE, "You can move again."); + + if (notice) + { + /* Redraw the state */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->paralyzed", notice observable changes + */ +bool_ set_paralyzed(int v) +{ + /* Paralysis effects do not accumulate -- this is to + prevent the uninteresting insta-death effect, but + still leave paralyzers highly dangerous if they're + faster than the player. */ + + if (p_ptr->paralyzed > 0) { + return FALSE; + } + + /* Normal processing */ + return set_paralyzed_aux(v); +} + +/* + * Decrement "p_ptr->paralyzed", notice observable changes + */ +void dec_paralyzed() +{ + set_paralyzed_aux(p_ptr->paralyzed - 1); +} + + +/* + * Set "p_ptr->image", notice observable changes + * + * Note that we must redraw the map when hallucination changes. + */ +bool_ set_image(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->image, v, + TERM_WHITE, "Oh, wow! Everything looks so cosmic now!", + TERM_WHITE, "You can see clearly again."); + + if (notice) + { + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Update monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD | PW_M_LIST); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->lightspeed", notice observable changes + */ +bool_ set_light_speed(int v) +{ + bool_ notice = + set_simple_field( + &p_ptr->lightspeed, v, + TERM_WHITE, "You feel as if time has stopped!", + TERM_WHITE, "You feel time returning to its normal rate."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +bool_ set_fast(int v, int p) +{ + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!p_ptr->fast) + { + msg_print("You feel yourself moving faster!"); + notice = TRUE; + } + } + + /* Shut */ + else + { + if (p_ptr->fast) + { + msg_print("You feel yourself slow down."); + p = 0; + notice = TRUE; + } + } + + /* Use the value */ + p_ptr->fast = v; + p_ptr->speed_factor = p; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + + +/* + * Set "p_ptr->slow", notice observable changes + */ +bool_ set_slow(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->slow, v, + TERM_WHITE, "You feel yourself moving slower!", + TERM_WHITE, "You feel yourself speed up."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->shield", notice observable changes + */ +bool_ set_shield(int v, int p, s16b o, s16b d1, s16b d2) +{ + bool_ notice = set_simple_field( + &p_ptr->shield, v, + TERM_WHITE, "A mystic shield forms around your body!", + TERM_WHITE, "Your mystic shield crumbles away."); + + /* Use the values */ + p_ptr->shield_power = p; + p_ptr->shield_opt = o; + p_ptr->shield_power_opt = d1; + p_ptr->shield_power_opt2 = d2; + + /* Notice? */ + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + + +/* + * Set "p_ptr->blessed", notice observable changes + */ +bool_ set_blessed(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->blessed, v, + TERM_WHITE, "You feel righteous!", + TERM_WHITE, "The prayer has expired."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->hero", notice observable changes + */ +bool_ set_hero(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->hero, v, + TERM_WHITE, "You feel like a hero!", + TERM_WHITE, "The heroism wears off."); + + if (notice) + { + /* Recalculate hitpoints */ + p_ptr->update |= (PU_HP); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->holy", notice observable changes + */ +bool_ set_holy(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->holy, v, + TERM_WHITE, "You feel a holy aura around you!", + TERM_WHITE, "The holy aura vanishes."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->shero", notice observable changes + */ +bool_ set_shero(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->shero, v, + TERM_WHITE, "You feel like a killing machine!", + TERM_WHITE, "You feel less berserk."); + + if (notice) + { + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Update monsters */ + p_ptr->update |= (PU_MONSTERS | PU_HP); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->protevil", notice observable changes + */ +bool_ set_protevil(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->protevil, v, + TERM_WHITE, "You feel safe from evil!", + TERM_WHITE, "You no longer feel safe from evil."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->set_shadow", notice observable changes + */ +bool_ set_shadow(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_wraith, v, + TERM_WHITE, "You leave the physical world and turn into a wraith-being!", + TERM_WHITE, "You feel opaque."); + + if (notice) + { + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Update monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + + + +/* + * Set "p_ptr->invuln", notice observable changes + */ +bool_ set_invuln(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->invuln, v, + TERM_L_BLUE, "Invulnerability!", + TERM_L_RED, "The invulnerability wears off."); + + if (notice) + { + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Update monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + + +/* + * Set "p_ptr->tim_esp", notice observable changes + */ +bool_ set_tim_esp(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_esp, v, + TERM_WHITE, "You feel your consciousness expand!", + TERM_WHITE, "Your consciousness contracts again."); + + if (notice) + { + /* Update the monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + +/* + * Set "p_ptr->tim_thunder", notice observable changes + */ +bool_ set_tim_thunder(int v, int p1, int p2) +{ + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!p_ptr->tim_thunder) + { + msg_print("The air around you charges with lightning!"); + notice = TRUE; + } + } + + /* Shut */ + else + { + if (p_ptr->tim_thunder) + { + msg_print("The air around you discharges."); + notice = TRUE; + p1 = p2 = 0; + } + } + + /* Use the value */ + p_ptr->tim_thunder = v; + p_ptr->tim_thunder_p1 = p1; + p_ptr->tim_thunder_p2 = p2; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Update the monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + +/* + * Set "p_ptr->tim_invis", notice observable changes + */ +bool_ set_tim_invis(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_invis, v, + TERM_WHITE, "Your eyes feel very sensitive!", + TERM_WHITE, "Your eyes feel less sensitive."); + + if (notice) + { + /* Update the monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->tim_infra", notice observable changes + */ +bool_ set_tim_infra(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->tim_infra, v, + TERM_WHITE, "Your eyes begin to tingle!", + TERM_WHITE, "Your eyes stop tingling."); + + if (notice) + { + /* Update the monsters */ + p_ptr->update |= (PU_MONSTERS); + + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->oppose_acid", notice observable changes + */ +bool_ set_oppose_acid(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->oppose_acid, v, + TERM_WHITE, "You feel resistant to acid!", + TERM_WHITE, "You feel less resistant to acid."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->oppose_elec", notice observable changes + */ +bool_ set_oppose_elec(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->oppose_elec, v, + TERM_WHITE, "You feel resistant to electricity!", + TERM_WHITE, "You feel less resistant to electricity."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->oppose_fire", notice observable changes + */ +bool_ set_oppose_fire(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->oppose_fire, v, + TERM_WHITE, "You feel resistant to fire!", + TERM_WHITE, "You feel less resistant to fire."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->oppose_cold", notice observable changes + */ +bool_ set_oppose_cold(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->oppose_cold, v, + TERM_WHITE, "You feel resistant to cold!", + TERM_WHITE, "You feel less resistant to cold."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->oppose_pois", notice observable changes + */ +bool_ set_oppose_pois(int v) +{ + bool_ notice = set_simple_field( + &p_ptr->oppose_pois, v, + TERM_WHITE, "You feel resistant to poison!", + TERM_WHITE, "You feel less resistant to poison."); + + if (notice) + { + /* Handle stuff */ + handle_stuff(); + } + + /* Result */ + return notice; +} + + +/* + * Set "p_ptr->tim_regen", notice observable changes + */ +bool_ set_tim_regen(int v, int p) +{ + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + /* Open */ + if (v) + { + if (!p_ptr->tim_regen) + { + msg_print("Your body regenerates much more quickly!"); + notice = TRUE; + } + } + + /* Shut */ + else + { + if (p_ptr->tim_regen) + { + p = 0; + msg_print("Your body regenerates much more slowly."); + notice = TRUE; + } + } + + /* Use the value */ + p_ptr->tim_regen = v; + p_ptr->tim_regen_pow = p; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + + +/* + * Set "p_ptr->stun", notice observable changes + * + * Note the special code to only notice "range" changes. + */ +bool_ set_stun(int v) +{ + int old_aux, new_aux; + bool_ notice = FALSE; + + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + if (race_flags_p(PR_NO_STUN)) v = 0; + + /* Knocked out */ + if (p_ptr->stun > 100) + { + old_aux = 3; + } + + /* Heavy stun */ + else if (p_ptr->stun > 50) + { + old_aux = 2; + } + + /* Stun */ + else if (p_ptr->stun > 0) + { + old_aux = 1; + } + + /* None */ + else + { + old_aux = 0; + } + + /* Knocked out */ + if (v > 100) + { + new_aux = 3; + } + + /* Heavy stun */ + else if (v > 50) + { + new_aux = 2; + } + + /* Stun */ + else if (v > 0) + { + new_aux = 1; + } + + /* None */ + else + { + new_aux = 0; + } + + /* Increase cut */ + if (new_aux > old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* Stun */ + case 1: + msg_print("You have been stunned."); + break; + + /* Heavy stun */ + case 2: + msg_print("You have been heavily stunned."); + break; + + /* Knocked out */ + case 3: + msg_print("You have been knocked out."); + break; + } + + if (randint(1000) < v || randint(16) == 1) + { + + msg_print("A vicious blow hits your head."); + if (randint(3) == 1) + { + if (!p_ptr->sustain_int) + { + do_dec_stat(A_INT, STAT_DEC_NORMAL); + } + if (!p_ptr->sustain_wis) + { + do_dec_stat(A_WIS, STAT_DEC_NORMAL); + } + } + else if (randint(2) == 1) + { + if (!p_ptr->sustain_int) + { + do_dec_stat(A_INT, STAT_DEC_NORMAL); + } + } + else + { + if (!p_ptr->sustain_wis) + { + do_dec_stat(A_WIS, STAT_DEC_NORMAL); + } + } + } + + /* Notice */ + notice = TRUE; + } + + /* Decrease cut */ + else if (new_aux < old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* None */ + case 0: + msg_print("You are no longer stunned."); + disturb_on_state(); + break; + } + + /* Notice */ + notice = TRUE; + } + + /* Use the value */ + p_ptr->stun = v; + + /* No change */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Redraw the "stun" */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + + +/* + * Set "p_ptr->cut", notice observable changes + * + * Note the special code to only notice "range" changes. + */ +bool_ set_cut(int v) +{ + int old_aux, new_aux; + + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 10000) ? 10000 : (v < 0) ? 0 : v; + + if (race_flags_p(PR_NO_CUT)) v = 0; + + /* Mortal wound */ + if (p_ptr->cut > 1000) + { + old_aux = 7; + } + + /* Deep gash */ + else if (p_ptr->cut > 200) + { + old_aux = 6; + } + + /* Severe cut */ + else if (p_ptr->cut > 100) + { + old_aux = 5; + } + + /* Nasty cut */ + else if (p_ptr->cut > 50) + { + old_aux = 4; + } + + /* Bad cut */ + else if (p_ptr->cut > 25) + { + old_aux = 3; + } + + /* Light cut */ + else if (p_ptr->cut > 10) + { + old_aux = 2; + } + + /* Graze */ + else if (p_ptr->cut > 0) + { + old_aux = 1; + } + + /* None */ + else + { + old_aux = 0; + } + + /* Mortal wound */ + if (v > 1000) + { + new_aux = 7; + } + + /* Deep gash */ + else if (v > 200) + { + new_aux = 6; + } + + /* Severe cut */ + else if (v > 100) + { + new_aux = 5; + } + + /* Nasty cut */ + else if (v > 50) + { + new_aux = 4; + } + + /* Bad cut */ + else if (v > 25) + { + new_aux = 3; + } + + /* Light cut */ + else if (v > 10) + { + new_aux = 2; + } + + /* Graze */ + else if (v > 0) + { + new_aux = 1; + } + + /* None */ + else + { + new_aux = 0; + } + + /* Increase cut */ + if (new_aux > old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* Graze */ + case 1: + msg_print("You have been given a graze."); + break; + + /* Light cut */ + case 2: + msg_print("You have been given a light cut."); + break; + + /* Bad cut */ + case 3: + msg_print("You have been given a bad cut."); + break; + + /* Nasty cut */ + case 4: + msg_print("You have been given a nasty cut."); + break; + + /* Severe cut */ + case 5: + msg_print("You have been given a severe cut."); + break; + + /* Deep gash */ + case 6: + msg_print("You have been given a deep gash."); + break; + + /* Mortal wound */ + case 7: + msg_print("You have been given a mortal wound."); + break; + } + + /* Notice */ + notice = TRUE; + + if (randint(1000) < v || randint(16) == 1) + { + if (!p_ptr->sustain_chr) + { + msg_print("You have been horribly scarred."); + + do_dec_stat(A_CHR, STAT_DEC_NORMAL); + } + } + } + + /* Decrease cut */ + else if (new_aux < old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* None */ + case 0: + msg_print("You are no longer bleeding."); + disturb_on_state(); + break; + } + + /* Notice */ + notice = TRUE; + } + + /* Use the value */ + p_ptr->cut = v; + + /* No change */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Redraw the "cut" */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + +void drop_from_wild() +{ + /* Hack -- Not if player were in normal mode in previous turn */ + if (!p_ptr->old_wild_mode) return; + + if (p_ptr->wild_mode && (!dun_level)) + { + p_ptr->wilderness_x = p_ptr->px; + p_ptr->wilderness_y = p_ptr->py; + change_wild_mode(); + p_ptr->energy = 100; + energy_use = 0; + } +} + +/* + * Set "p_ptr->food", notice observable changes + * + * The "p_ptr->food" variable can get as large as 20000, allowing the + * addition of the most "filling" item, Elvish Waybread, which adds + * 7500 food units, without overflowing the 32767 maximum limit. + * + * Perhaps we should disturb the player with various messages, + * especially messages about hunger status changes. XXX XXX XXX + * + * Digestion of food is handled in "dungeon.c", in which, normally, + * the player digests about 20 food units per 100 game turns, more + * when "fast", more when "regenerating", less with "slow digestion", + * but when the player is "gorged", he digests 100 food units per 10 + * game turns, or a full 1000 food units per 100 game turns. + * + * Note that the player's speed is reduced by 10 units while gorged, + * so if the player eats a single food ration (5000 food units) when + * full (15000 food units), he will be gorged for (5000/100)*10 = 500 + * game turns, or 500/(100/5) = 25 player turns (if nothing else is + * affecting the player speed). + */ +bool_ set_food(int v) +{ + int old_aux, new_aux; + + bool_ notice = FALSE; + + /* Hack -- Force good values */ + v = (v > 20000) ? 20000 : (v < 0) ? 0 : v; + + /* Fainting / Starving */ + if (p_ptr->food < PY_FOOD_FAINT) + { + old_aux = 0; + } + + /* Weak */ + else if (p_ptr->food < PY_FOOD_WEAK) + { + old_aux = 1; + } + + /* Hungry */ + else if (p_ptr->food < PY_FOOD_ALERT) + { + old_aux = 2; + } + + /* Normal */ + else if (p_ptr->food < PY_FOOD_FULL) + { + old_aux = 3; + } + + /* Full */ + else if (p_ptr->food < PY_FOOD_MAX) + { + old_aux = 4; + } + + /* Gorged */ + else + { + old_aux = 5; + } + + /* Fainting / Starving */ + if (v < PY_FOOD_FAINT) + { + new_aux = 0; + } + + /* Weak */ + else if (v < PY_FOOD_WEAK) + { + new_aux = 1; + } + + /* Hungry */ + else if (v < PY_FOOD_ALERT) + { + new_aux = 2; + } + + /* Normal */ + else if (v < PY_FOOD_FULL) + { + new_aux = 3; + } + + /* Full */ + else if (v < PY_FOOD_MAX) + { + new_aux = 4; + } + + /* Gorged */ + else + { + new_aux = 5; + } + + /* Food increase */ + if (new_aux > old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* Weak */ + case 1: + msg_print("You are still weak."); + break; + + /* Hungry */ + case 2: + msg_print("You are still hungry."); + break; + + /* Normal */ + case 3: + msg_print("You are no longer hungry."); + break; + + /* Full */ + case 4: + msg_print("You are full!"); + break; + + /* Bloated */ + case 5: + msg_print("You have gorged yourself!"); + break; + } + + /* Change */ + notice = TRUE; + } + + /* Food decrease */ + else if (new_aux < old_aux) + { + /* Describe the state */ + switch (new_aux) + { + /* Fainting / Starving */ + case 0: + msg_print("You are getting faint from hunger!"); + drop_from_wild(); + break; + + /* Weak */ + case 1: + msg_print("You are getting weak from hunger!"); + drop_from_wild(); + break; + + /* Hungry */ + case 2: + msg_print("You are getting hungry."); + break; + + /* Normal */ + case 3: + msg_print("You are no longer full."); + break; + + /* Full */ + case 4: + msg_print("You are no longer gorged."); + break; + } + + /* Change */ + notice = TRUE; + } + + /* Use the value */ + p_ptr->food = v; + + /* Nothing to notice */ + if (!notice) return (FALSE); + + /* Disturb */ + disturb_on_state(); + + /* Recalculate bonuses */ + p_ptr->update |= (PU_BONUS); + + /* Redraw hunger */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + + /* Result */ + return (TRUE); +} + + +/* + * Advance experience levels and print experience + */ +void check_experience() +{ + int gained = 0; + bool_ level_corruption = FALSE; + + + /* Hack -- lower limit */ + if (p_ptr->exp < 0) p_ptr->exp = 0; + + /* Hack -- lower limit */ + if (p_ptr->max_exp < 0) p_ptr->max_exp = 0; + + /* Hack -- upper limit */ + if (p_ptr->exp > PY_MAX_EXP) p_ptr->exp = PY_MAX_EXP; + + /* Hack -- upper limit */ + if (p_ptr->max_exp > PY_MAX_EXP) p_ptr->max_exp = PY_MAX_EXP; + + /* Hack -- maintain "max" experience */ + if (p_ptr->exp > p_ptr->max_exp) p_ptr->max_exp = p_ptr->exp; + + /* Redraw experience */ + p_ptr->redraw |= (PR_FRAME); + + /* Handle stuff */ + handle_stuff(); + + + /* Lose levels while possible */ + while ((p_ptr->lev > 1) && + (p_ptr->exp < (player_exp[p_ptr->lev - 2] * p_ptr->expfact / 100L))) + { + /* Lose a level */ + p_ptr->lev--; + gained--; + lite_spot(p_ptr->py, p_ptr->px); + + /* Update some stuff */ + p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_SANITY); + + /* Redraw some stuff */ + p_ptr->redraw |= (PR_FRAME); + + /* Window stuff */ + p_ptr->window |= (PW_PLAYER); + + /* Handle stuff */ + handle_stuff(); + } + + + /* Gain levels while possible */ + while ((p_ptr->lev < PY_MAX_LEVEL) && + (p_ptr->exp >= (player_exp[p_ptr->lev - 1] * p_ptr->expfact / 100L))) + { + /* Gain a level */ + p_ptr->lev++; + gained++; + lite_spot(p_ptr->py, p_ptr->px); + + /* Save the highest level */ + if (p_ptr->lev > p_ptr->max_plv) + { + p_ptr->max_plv = p_ptr->lev; + if ((race_flags_p(PR_CORRUPT)) && + (randint(3) == 1)) + { + level_corruption = TRUE; + } + } + + /* Message */ + cmsg_format(TERM_L_GREEN, "Welcome to level %d.", p_ptr->lev); + + if (p_ptr->skill_last_level < p_ptr->lev) + { + p_ptr->skill_last_level = p_ptr->lev; + p_ptr->skill_points += modules[game_module_idx].skills.skill_per_level; + cmsg_format(TERM_L_GREEN, "You can increase %d more skills.", p_ptr->skill_points); + p_ptr->redraw |= PR_FRAME; + } + + /* Gain this level's abilities */ + apply_level_abilities(p_ptr->lev); + + /* Note level gain */ + { + char note[80]; + + /* Write note */ + sprintf(note, "Reached level %d", p_ptr->lev); + add_note(note, 'L'); + } + + /* Update some stuff */ + p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_SANITY); + + /* Redraw some stuff */ + p_ptr->redraw |= (PR_FRAME); + + /* Window stuff */ + p_ptr->window |= (PW_PLAYER); + + /* Handle stuff */ + handle_stuff(); + + if (level_corruption) + { + msg_print("You feel different..."); + corrupt_corrupted(); + level_corruption = FALSE; + } + } + + /* Hook it! */ + { + hook_player_level_in in = { gained }; + process_hooks_new(HOOK_PLAYER_LEVEL, &in, NULL); + } +} + +/* + * Advance experience levels and print experience + */ +void check_experience_obj(object_type *o_ptr) +{ + /* Hack -- lower limit */ + if (o_ptr->exp < 0) o_ptr->exp = 0; + + /* Hack -- upper limit */ + if (o_ptr->exp > PY_MAX_EXP) o_ptr->exp = PY_MAX_EXP; + + /* Gain levels while possible */ + while ((o_ptr->elevel < PY_MAX_LEVEL) && + (o_ptr->exp >= calc_object_need_exp(o_ptr))) + { + char buf[100]; + + /* Add a level */ + o_ptr->elevel++; + + /* Get object name */ + object_desc(buf, o_ptr, 1, 0); + cmsg_format(TERM_L_BLUE, "%s gains a level!", buf); + + /* What does it gains ? */ + object_gain_level(o_ptr); + } +} + + +/* + * Gain experience + */ +void gain_exp(s32b amount) +{ + if ((p_ptr->max_exp > 0) && (race_flags_p(PR_CORRUPT))) + { + if ((randint(p_ptr->max_exp) < amount) || (randint(12000000) < amount)) + { + msg_print("You feel different..."); + corrupt_corrupted(); + }; + /* 12,000,000 is equal to double Morgoth's raw XP value (60,000 * his Dlev (100))*/ + }; + + /* Gain some experience */ + p_ptr->exp += amount; + + /* Slowly recover from experience drainage */ + if (p_ptr->exp < p_ptr->max_exp) + { + /* Gain max experience (20%) (was 10%) */ + p_ptr->max_exp += amount / 5; + } + + /* Check Experience */ + check_experience(); +} + + +/* + * Lose experience + */ +void lose_exp(s32b amount) +{ + /* Never drop below zero experience */ + if (amount > p_ptr->exp) amount = p_ptr->exp; + + /* Lose some experience */ + p_ptr->exp -= amount; + + /* Check Experience */ + check_experience(); +} + + + + +/* + * Hack -- Return the "automatic coin type" of a monster race + * Used to allocate proper treasure when "Creeping coins" die + * + * XXX XXX XXX Note the use of actual "monster names" + */ +int get_coin_type(std::shared_ptr<monster_race const> r_ptr) +{ + cptr name = r_ptr->name; + + /* Analyze "coin" monsters */ + if (r_ptr->d_char == '$') + { + /* Look for textual clues */ + if (strstr(name, " copper ")) return (2); + if (strstr(name, " silver ")) return (5); + if (strstr(name, " gold ")) return (10); + if (strstr(name, " mithril ")) return (16); + if (strstr(name, " adamantite ")) return (17); + + /* Look for textual clues */ + if (strstr(name, "Copper ")) return (2); + if (strstr(name, "Silver ")) return (5); + if (strstr(name, "Gold ")) return (10); + if (strstr(name, "Mithril ")) return (16); + if (strstr(name, "Adamantite ")) return (17); + } + + /* Assume nothing */ + return (0); +} + +/* + * This routine handles the production of corpses/skeletons/heads/skulls + * when a monster is killed. + */ +void place_corpse(monster_type *m_ptr) +{ + const int x = m_ptr->fx; + const int y = m_ptr->fy; + + /* Get local object */ + object_type object_type_body; + object_type *i_ptr = &object_type_body; + + /* Get the race information */ + auto const r_ptr = m_ptr->race(); + + /* It has a physical form */ + if (r_ptr->flags & RF_DROP_CORPSE) + { + /* Wipe the object */ + object_prep(i_ptr, lookup_kind(TV_CORPSE, SV_CORPSE_CORPSE)); + + /* Unique corpses are unique */ + if (r_ptr->flags & RF_UNIQUE) + { + object_aware(i_ptr); + i_ptr->name1 = 201; + } + + /* Calculate length of time before decay */ + i_ptr->pval = r_ptr->weight + rand_int(r_ptr->weight); + + /* Set weight */ + i_ptr->weight = (r_ptr->weight + rand_int(r_ptr->weight) / 10) + 1; + + /* Remember what we are */ + i_ptr->pval2 = m_ptr->r_idx; + + /* Some hp */ + i_ptr->pval3 = ((maxroll(r_ptr->hdice, r_ptr->hside) + p_ptr->mhp) / 2); + i_ptr->pval3 -= randint(i_ptr->pval3) / 3; + + i_ptr->found = OBJ_FOUND_MONSTER; + i_ptr->found_aux1 = m_ptr->r_idx; + i_ptr->found_aux2 = m_ptr->ego; + i_ptr->found_aux3 = dungeon_type; + i_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(i_ptr, -1, y, x); + } + + /* The creature is an animated skeleton. */ + if (!(r_ptr->flags & RF_DROP_CORPSE) && (r_ptr->flags & RF_DROP_SKELETON)) + { + /* Wipe the object */ + object_prep(i_ptr, lookup_kind(TV_CORPSE, SV_CORPSE_SKELETON)); + + /* Unique corpses are unique */ + if (r_ptr->flags & RF_UNIQUE) + { + object_aware(i_ptr); + i_ptr->name1 = 201; + } + + i_ptr->pval = 0; + + /* Set weight */ + i_ptr->weight = (r_ptr->weight / 4 + rand_int(r_ptr->weight) / 40) + 1; + + /* Remember what we are */ + i_ptr->pval2 = m_ptr->r_idx; + + i_ptr->found = OBJ_FOUND_MONSTER; + i_ptr->found_aux1 = m_ptr->r_idx; + i_ptr->found_aux2 = m_ptr->ego; + i_ptr->found_aux3 = dungeon_type; + i_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(i_ptr, -1, y, x); + } + +} + + +/* + * Check if monster race is in a given list. The list + * must be NULL-terminated. + */ +static bool_ monster_race_in_list_p(monster_type *m_ptr, cptr races[]) +{ + int i=0; + for (i=0; races[i] != NULL; i++) + { + if (m_ptr->r_idx == test_monster_name(races[i])) { + return TRUE; + } + } + /* Not found */ + return FALSE; +} + +/* + * Handle the "death" of a monster (Gods) + */ +static void monster_death_gods(int m_idx, monster_type *m_ptr) +{ + if (p_ptr->pgod == GOD_AULE) + { + /* TODO: This should really be a racial flag + which can be added to the r_info file. */ + cptr DWARVES[] = { + "Petty-dwarf", + "Petty-dwarf mage", + "Dark dwarven warrior", + "Dark dwarven smith", + "Dark dwarven lord", + "Dark dwarven priest", + "Dwarven warrior", + NULL, + }; + cptr UNIQUE_DWARVES[] = { + "Nar, the Dwarf", + "Naugladur, Lord of Nogrod", + "Telchar the Smith", + "Fundin Bluecloak", + "Khim, Son of Mim", + "Ibun, Son of Mim", + "Mim, Betrayer of Turin", + NULL, + }; + + /* Aule dislikes the killing of dwarves */ + if (monster_race_in_list_p(m_ptr, DWARVES)) + { + inc_piety(GOD_ALL, -20); + } + + /* ... and UNIQUE dwarves */ + if (monster_race_in_list_p(m_ptr, UNIQUE_DWARVES)) + { + inc_piety(GOD_ALL, -500); + } + } + + if (p_ptr->pgod == GOD_ULMO) + { + /* He doesn't like it if you kill these monsters */ + cptr MINOR_RACES[] = { + "Swordfish", + "Barracuda", + "Globefish", + "Aquatic bear", + "Pike", + "Electric eel", + "Giant crayfish", + "Mermaid", + "Leviathan", + "Box jellyfish", + "Giant piranha", + "Piranha", + "Ocean naga", + "Whale", + "Octopus", + "Giant octopus", + "Drowned soul", + "Tiger shark", + "Hammerhead shark", + "Great white shark", + "White shark", + "Stargazer", + "Flounder", + "Giant turtle", + "Killer whale", + "Water naga", + "Behemoth", + NULL, + }; + /* These monsters earn higher penalties */ + cptr MAJOR_RACES[] = { + "Seahorse", + "Aquatic elven warrior", + "Aquatic elven mage", + "Wavelord", + "The Watcher in the Water", + NULL, + }; + + if (monster_race_in_list_p(m_ptr, MINOR_RACES)) + { + inc_piety(GOD_ALL, -20); + } + + if (monster_race_in_list_p(m_ptr, MAJOR_RACES)) + { + inc_piety(GOD_ALL, -500); + } + } + + if (p_ptr->pgod == GOD_MANDOS) + { + cptr MINOR_BONUS_RACES[] = { + "Vampire", + "Master vampire", + "Oriental vampire", + "Vampire lord", + "Vampire orc", + "Vampire yeek", + "Vampire ogre", + "Vampire troll", + "Vampire dwarf", + "Vampire gnome", + "Elder vampire", + NULL, + }; + cptr MAJOR_BONUS_RACES[] = { + "Vampire elf", + "Thuringwethil, the Vampire Messenger", + NULL, + }; + cptr MINOR_PENALTY[] = { + "Dark elf", + "Dark elven druid", + "Eol, the Dark Elf", + "Maeglin, the Traitor of Gondolin", + "Dark elven mage", + "Dark elven warrior", + "Dark elven priest", + "Dark elven lord", + "Dark elven warlock", + "Dark elven sorcerer", + NULL, + }; + cptr MEDIUM_PENALTY[] = { + "Glorfindel of Rivendell", + "Finrod Felagund", + "Thranduil, King of the Wood Elves", + "Aquatic elven warrior", + "Aquatic elven mage", + "High-elven ranger", + "Elven archer", + NULL, + }; + cptr MAJOR_PENALTY[] = { + "Child spirit", + "Young spirit", + "Mature spirit", + "Experienced spirit", + "Wise spirit", + NULL, + }; + + if (monster_race_in_list_p(m_ptr, MINOR_BONUS_RACES)) + { + /* He really likes it if you kill Vampires + * (but not the adventurer kind :P) */ + inc_piety(GOD_ALL, 50); + } + + if (monster_race_in_list_p(m_ptr, MAJOR_BONUS_RACES)) + { + /* He *loves* it if you kill vampire Elves. He + * will also thank you extra kindly if you + * kill Thuringwethil */ + inc_piety(GOD_ALL, 200); + } + + if (monster_race_in_list_p(m_ptr, MINOR_PENALTY)) + { + /* He doesn't like it if you kill normal Elves + * (means more work for him :P) */ + inc_piety(GOD_ALL, -20); + } + + if (monster_race_in_list_p(m_ptr, MEDIUM_PENALTY)) + { + /* He hates it if you kill coaligned Elves */ + inc_piety(GOD_ALL, -200); + } + + if (monster_race_in_list_p(m_ptr, MAJOR_PENALTY)) + { + /* He *hates* it if you kill the coaligned Spirits */ + inc_piety(GOD_ALL, -1000); + } + } +} + +/* + * Handle the "death" of a monster. + * + * Disperse treasures centered at the monster location based on the + * various flags contained in the monster flags fields. + * + * Check for "Quest" completion when a quest monster is killed. + * + * Note that only the player can induce "monster_death()" on Uniques. + * Thus (for now) all Quest monsters should be Uniques. + * + * Note that monsters can now carry objects, and when a monster dies, + * it drops all of its objects, which may disappear in crowded rooms. + */ +void monster_death(int m_idx) +{ + auto const &d_info = game->edit_data.d_info; + auto const &f_info = game->edit_data.f_info; + auto &a_info = game->edit_data.a_info; + + monster_type *m_ptr = &m_list[m_idx]; + + auto const r_ptr = m_ptr->race(); + + bool_ create_stairs = FALSE; + int force_coin = get_coin_type(r_ptr); + + object_type forge; + object_type *q_ptr; + + /* Get the location */ + int y = m_ptr->fy; + int x = m_ptr->fx; + + /* Process the appropriate hooks */ + { + struct hook_monster_death_in in = { m_idx }; + process_hooks_new(HOOK_MONSTER_DEATH, &in, NULL); + } + + /* Per-god processing */ + monster_death_gods(m_idx, m_ptr); + + /* If companion dies, take note */ + if (m_ptr->status == MSTATUS_COMPANION) p_ptr->companion_killed++; + + /* Handle reviving if undead */ + if ((p_ptr->necro_extra & CLASS_UNDEAD) && p_ptr->necro_extra2) + { + p_ptr->necro_extra2--; + + if (!p_ptr->necro_extra2) + { + msg_print("Your death has been avenged -- you return to life."); + p_ptr->necro_extra &= ~CLASS_UNDEAD; + + /* Display the hitpoints */ + p_ptr->update |= (PU_HP); + p_ptr->redraw |= (PR_FRAME); + + /* Window stuff */ + p_ptr->window |= (PW_PLAYER); + } + else + { + msg_format("You still have to kill %d monster%s.", p_ptr->necro_extra2, (p_ptr->necro_extra2 == 1) ? "" : "s"); + } + } + + /* If the doppleganger die, the variable must be set accordingly */ + if (r_ptr->flags & RF_DOPPLEGANGER) doppleganger = 0; + + /* Need copy of object list since we're going to mutate it */ + auto const object_idxs(m_ptr->hold_o_idxs); + + /* Drop objects being carried */ + for (auto const this_o_idx: object_idxs) + { + /* Acquire object */ + object_type * o_ptr = &o_list[this_o_idx]; + + /* Paranoia */ + o_ptr->held_m_idx = 0; + + /* Get local object */ + q_ptr = &forge; + + /* Copy the object */ + object_copy(q_ptr, o_ptr); + + /* Delete the object */ + delete_object_idx(this_o_idx); + + /* Drop it */ + drop_near(q_ptr, -1, y, x); + } + + /* Forget objects */ + m_ptr->hold_o_idxs.clear(); + + /* Average dungeon and monster levels */ + object_level = (dun_level + m_ptr->level) / 2; + + /* Mega^2-hack -- destroying the Stormbringer gives it us! */ + if (strstr(r_ptr->name, "Stormbringer")) + { + /* Get local object */ + q_ptr = &forge; + + /* Prepare to make the Stormbringer */ + object_prep(q_ptr, lookup_kind(TV_SWORD, SV_BLADE_OF_CHAOS)); + + /* Mega-Hack -- Name the sword */ + + q_ptr->artifact_name = "'Stormbringer'"; + q_ptr->to_h = 16; + q_ptr->to_d = 16; + q_ptr->ds = 6; + q_ptr->dd = 6; + q_ptr->pval = 2; + + q_ptr->art_flags |= + TR_VAMPIRIC | + TR_STR | + TR_CON | + TR_BLOWS | + TR_FREE_ACT | + TR_HOLD_LIFE | + TR_RES_NEXUS | + TR_RES_CHAOS | + TR_RES_NETHER | + TR_RES_CONF | + TR_IGNORE_ACID | + TR_IGNORE_ELEC | + TR_IGNORE_FIRE | + TR_IGNORE_COLD | + TR_NO_TELE | + TR_CURSED | + TR_HEAVY_CURSE; + + q_ptr->ident |= IDENT_CURSED; + if (randint(2) == 1) + { + q_ptr->art_flags |= TR_DRAIN_EXP; + } + else + { + q_ptr->art_flags |= TR_AGGRAVATE; + } + + q_ptr->found = OBJ_FOUND_MONSTER; + q_ptr->found_aux1 = m_ptr->r_idx; + q_ptr->found_aux2 = m_ptr->ego; + q_ptr->found_aux3 = dungeon_type; + q_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + } + + /* + * Mega^3-hack: killing a 'Warrior of the Dawn' is likely to + * spawn another in the fallen one's place! + */ + else if (strstr(r_ptr->name, "the Dawn")) + { + if (!(randint(20) == 13)) + { + int wy = p_ptr->py, wx = p_ptr->px; + int attempts = 100; + + do + { + scatter(&wy, &wx, p_ptr->py, p_ptr->px, 20); + } + while (!(in_bounds(wy, wx) && cave_floor_bold(wy, wx)) && --attempts); + + if (attempts > 0) + { + if (is_friend(m_ptr) > 0) + { + if (summon_specific_friendly(wy, wx, 100, SUMMON_DAWN, FALSE)) + { + if (player_can_see_bold(wy, wx)) + msg_print ("A new warrior steps forth!"); + } + } + else + { + if (summon_specific(wy, wx, 100, SUMMON_DAWN)) + { + if (player_can_see_bold(wy, wx)) + msg_print ("A new warrior steps forth!"); + } + } + } + } + } + + /* One more ultra-hack: An Unmaker goes out with a big bang! */ + else if (strstr(r_ptr->name, "Unmaker")) + { + int flg = PROJECT_GRID | PROJECT_ITEM | PROJECT_KILL; + project(m_idx, 6, y, x, 100, GF_CHAOS, flg); + } + /* Pink horrors are replaced with 2 Blue horrors */ + else if (strstr(r_ptr->name, "ink horror")) + { + for (int i = 0; i < 2; i++) + { + int wy = p_ptr->py, wx = p_ptr->px; + int attempts = 100; + + do + { + scatter(&wy, &wx, p_ptr->py, p_ptr->px, 3); + } + while (!(in_bounds(wy, wx) && cave_floor_bold(wy, wx)) && --attempts); + + if (attempts > 0) + { + if (summon_specific(wy, wx, 100, SUMMON_BLUE_HORROR)) + { + if (player_can_see_bold(wy, wx)) + msg_print ("A blue horror appears!"); + } + } + } + } + + /* Mega-Hack -- drop "winner" treasures */ + else if (r_ptr->flags & RF_DROP_CHOSEN) + { + if (strstr(r_ptr->name, "Morgoth, Lord of Darkness")) + { + /* Get local object */ + q_ptr = &forge; + + /* Mega-Hack -- Prepare to make "Grond" */ + object_prep(q_ptr, lookup_kind(TV_HAFTED, SV_GROND)); + + /* Mega-Hack -- Mark this item as "Grond" */ + q_ptr->name1 = ART_GROND; + + /* Mega-Hack -- Actually create "Grond" */ + apply_magic(q_ptr, -1, TRUE, TRUE, TRUE); + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + + /* Get local object */ + q_ptr = &forge; + + /* Mega-Hack -- Prepare to make "Morgoth" */ + object_prep(q_ptr, lookup_kind(TV_CROWN, SV_MORGOTH)); + + /* Mega-Hack -- Mark this item as "Morgoth" */ + q_ptr->name1 = ART_MORGOTH; + + /* Mega-Hack -- Actually create "Morgoth" */ + apply_magic(q_ptr, -1, TRUE, TRUE, TRUE); + + q_ptr->found = OBJ_FOUND_MONSTER; + q_ptr->found_aux1 = m_ptr->r_idx; + q_ptr->found_aux2 = m_ptr->ego; + q_ptr->found_aux3 = dungeon_type; + q_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + } + else if (strstr(r_ptr->name, "Smeagol")) + { + /* Get local object */ + q_ptr = &forge; + + object_wipe(q_ptr); + + /* Mega-Hack -- Prepare to make a ring of invisibility */ + object_prep(q_ptr, lookup_kind(TV_RING, SV_RING_INVIS)); + q_ptr->number = 1; + + apply_magic(q_ptr, -1, TRUE, TRUE, FALSE); + + q_ptr->found = OBJ_FOUND_MONSTER; + q_ptr->found_aux1 = m_ptr->r_idx; + q_ptr->found_aux2 = m_ptr->ego; + q_ptr->found_aux3 = dungeon_type; + q_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + } + else if (r_ptr->flags & RF_NAZGUL) + { + /* Get local object */ + q_ptr = &forge; + + object_wipe(q_ptr); + + /* Mega-Hack -- Prepare to make a Ring of Power */ + object_prep(q_ptr, lookup_kind(TV_RING, SV_RING_SPECIAL)); + q_ptr->number = 1; + + apply_magic(q_ptr, -1, TRUE, TRUE, FALSE); + + /* Create a random artifact */ + create_artifact(q_ptr, TRUE, FALSE); + + /* Save the inscription */ + q_ptr->artifact_name = fmt::format("of {}", r_ptr->name); + + q_ptr->found = OBJ_FOUND_MONSTER; + q_ptr->found_aux1 = m_ptr->r_idx; + q_ptr->found_aux2 = m_ptr->ego; + q_ptr->found_aux3 = dungeon_type; + q_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop it in the dungeon */ + drop_near(q_ptr, -1, y, x); + } + else + { + byte a_idx = r_ptr->artifact_idx; + int chance = r_ptr->artifact_chance; + + if ((a_idx > 0) && ((randint(99) < chance) || (wizard))) + { + if (a_info[a_idx].cur_num == 0) + { + auto a_ptr = &a_info[a_idx]; + + /* Get local object */ + q_ptr = &forge; + + /* Wipe the object */ + object_wipe(q_ptr); + + /* Acquire the "kind" index */ + int I_kind = lookup_kind(a_ptr->tval, a_ptr->sval); + + /* Create the artifact */ + object_prep(q_ptr, I_kind); + + /* Save the name */ + q_ptr->name1 = a_idx; + + /* Extract the fields */ + q_ptr->pval = a_ptr->pval; + q_ptr->ac = a_ptr->ac; + q_ptr->dd = a_ptr->dd; + q_ptr->ds = a_ptr->ds; + q_ptr->to_a = a_ptr->to_a; + q_ptr->to_h = a_ptr->to_h; + q_ptr->to_d = a_ptr->to_d; + q_ptr->weight = a_ptr->weight; + + /* Hack -- acquire "cursed" flag */ + if (a_ptr->flags & TR_CURSED) q_ptr->ident |= (IDENT_CURSED); + + random_artifact_resistance(q_ptr); + + a_info[a_idx].cur_num = 1; + + q_ptr->found = OBJ_FOUND_MONSTER; + q_ptr->found_aux1 = m_ptr->r_idx; + q_ptr->found_aux2 = m_ptr->ego; + q_ptr->found_aux3 = dungeon_type; + q_ptr->found_aux4 = level_or_feat(dungeon_type, dun_level); + + /* Drop the artifact from heaven */ + drop_near(q_ptr, -1, y, x); + } + } + } + } + + /* Hack - the protected monsters must be advanged */ + else if (r_ptr->flags & RF_WYRM_PROTECT) + { + int xx = x, yy = y; + int attempts = 100; + + cmsg_print(TERM_VIOLET, "This monster was under the protection of a Great Wyrm of Power!"); + + do + { + scatter(&yy, &xx, y, x, 6); + } + while (!(in_bounds(yy, xx) && cave_floor_bold(yy, xx)) && --attempts); + + place_monster_aux(yy, xx, test_monster_name("Great Wyrm of Power"), FALSE, FALSE, m_ptr->status); + } + + /* Let monsters explode! */ + for (int i = 0; i < 4; i++) + { + if (m_ptr->blow[i].method == RBM_EXPLODE) + { + int flg = PROJECT_GRID | PROJECT_ITEM | PROJECT_KILL; + int typ = GF_MISSILE; + int d_dice = m_ptr->blow[i].d_dice; + int d_side = m_ptr->blow[i].d_side; + int damage = damroll(d_dice, d_side); + + switch (m_ptr->blow[i].effect) + { + case RBE_HURT: + typ = GF_MISSILE; + break; + case RBE_POISON: + typ = GF_POIS; + break; + case RBE_UN_BONUS: + typ = GF_DISENCHANT; + break; + case RBE_UN_POWER: + typ = GF_MISSILE; + break; /* ToDo: Apply the correct effects */ + case RBE_EAT_GOLD: + typ = GF_MISSILE; + break; + case RBE_EAT_ITEM: + typ = GF_MISSILE; + break; + case RBE_EAT_FOOD: + typ = GF_MISSILE; + break; + case RBE_EAT_LITE: + typ = GF_MISSILE; + break; + case RBE_ACID: + typ = GF_ACID; + break; + case RBE_ELEC: + typ = GF_ELEC; + break; + case RBE_FIRE: + typ = GF_FIRE; + break; + case RBE_COLD: + typ = GF_COLD; + break; + case RBE_BLIND: + typ = GF_MISSILE; + break; + case RBE_HALLU: + typ = GF_CONFUSION; + break; + case RBE_CONFUSE: + typ = GF_CONFUSION; + break; + case RBE_TERRIFY: + typ = GF_MISSILE; + break; + case RBE_PARALYZE: + typ = GF_MISSILE; + break; + case RBE_LOSE_STR: + typ = GF_MISSILE; + break; + case RBE_LOSE_DEX: + typ = GF_MISSILE; + break; + case RBE_LOSE_CON: + typ = GF_MISSILE; + break; + case RBE_LOSE_INT: + typ = GF_MISSILE; + break; + case RBE_LOSE_WIS: + typ = GF_MISSILE; + break; + case RBE_LOSE_CHR: + typ = GF_MISSILE; + break; + case RBE_LOSE_ALL: + typ = GF_MISSILE; + break; + case RBE_PARASITE: + typ = GF_MISSILE; + break; + case RBE_SHATTER: + typ = GF_ROCKET; + break; + case RBE_EXP_10: + typ = GF_MISSILE; + break; + case RBE_EXP_20: + typ = GF_MISSILE; + break; + case RBE_EXP_40: + typ = GF_MISSILE; + break; + case RBE_EXP_80: + typ = GF_MISSILE; + break; + case RBE_DISEASE: + typ = GF_POIS; + break; + case RBE_TIME: + typ = GF_TIME; + break; + case RBE_SANITY: + typ = GF_MISSILE; + break; + } + + project(m_idx, 3, y, x, damage, typ, flg); + break; + } + } + + if ((!force_coin) && (magik(10 + get_skill_scale(SKILL_PRESERVATION, 75))) && (!(m_ptr->mflag & MFLAG_NO_DROP))) + place_corpse(m_ptr); + + /* Create a magical staircase */ + if (create_stairs && (dun_level < d_info[dungeon_type].maxdepth)) + { + for (int i = -1; i <= 1; i++) + { + for (int j = -1; j <= 1; j++) + { + if (!(f_info[cave[y + j][x + i].feat].flags & FF_PERMANENT)) + { + cave_set_feat(y + j, x + i, d_info[dungeon_type].floor1); + } + } + } + + /* Stagger around */ + while (!cave_valid_bold(y, x)) + { + /* Pick a location */ + int ny, nx; + scatter(&ny, &nx, y, x, 1); + + /* Stagger */ + y = ny; + x = nx; + } + + /* Destroy any objects */ + delete_object(y, x); + + /* Explain the staircase */ + msg_print("A magical staircase appears..."); + + /* Create stairs down */ + cave_set_feat(y, x, FEAT_MORE); + + /* Remember to update everything */ + p_ptr->update |= (PU_VIEW | PU_FLOW | PU_MONSTERS); + } +} + + + + +/* + * Decreases monsters hit points, handling monster death. + * + * We return TRUE if the monster has been killed (and deleted). + * + * We announce monster death (using an optional "death message" + * if given, and a otherwise a generic killed/destroyed message). + * + * Only "physical attacks" can induce the "You have slain" message. + * Missile and Spell attacks will induce the "dies" message, or + * various "specialized" messages. Note that "You have destroyed" + * and "is destroyed" are synonyms for "You have slain" and "dies". + * + * Hack -- unseen monsters yield "You have killed it." message. + * + * Added fear (DGK) and check whether to print fear messages -CWS + * + * Genericized name, sex, and capitilization -BEN- + * + * Hack -- we "delay" fear messages by passing around a "fear" flag. + * + * XXX XXX XXX Consider decreasing monster experience over time, say, + * by using "(m_exp * m_lev * (m_lev)) / (p_lev * (m_lev + n_killed))" + * instead of simply "(m_exp * m_lev) / (p_lev)", to make the first + * monster worth more than subsequent monsters. This would also need + * to induce changes in the monster recall code. + */ +bool_ mon_take_hit(int m_idx, int dam, bool_ *fear, cptr note) +{ + monster_type *m_ptr = &m_list[m_idx]; + auto const r_idx = m_ptr->r_idx; + auto const r_ptr = m_ptr->race(); + + /* Redraw (later) if needed */ + if (health_who == m_idx) p_ptr->redraw |= (PR_FRAME); + + /* Some mosnters are immune to death */ + if (r_ptr->flags & RF_NO_DEATH) return FALSE; + + /* Wake it up */ + m_ptr->csleep = 0; + + /* Hurt it */ + m_ptr->hp -= dam; + + /* It is dead now */ + if (m_ptr->hp < 0) + { + char m_name[80]; + + /* Lets face it, you cannot get rid of a possessor that easily */ + if (m_ptr->possessor) + { + ai_deincarnate(m_idx); + + return FALSE; + } + + /* Extract monster name */ + monster_desc(m_name, m_ptr, 0); + + if ((r_ptr->flags & RF_DG_CURSE) && (randint(2) == 1)) + { + int curses = 2 + randint(5); + + cmsg_format(TERM_VIOLET, "%^s puts a terrible Morgothian curse on you!", m_name); + curse_equipment_dg(100, 50); + + do + { + activate_dg_curse(); + } + while (--curses); + } + + if (r_ptr->flags & RF_CAN_SPEAK) + { + char line_got[80]; + /* Dump a message */ + + get_rnd_line("mondeath.txt", line_got); + msg_format("%^s says: %s", m_name, line_got); + } + + /* Death by Missile/Spell attack */ + if (note) + { + cmsg_format(TERM_L_RED, "%^s%s", m_name, note); + } + + /* Death by physical attack -- invisible monster */ + else if (!m_ptr->ml) + { + cmsg_format(TERM_L_RED, "You have killed %s.", m_name); + } + + /* Death by Physical attack -- non-living monster */ + else if ((r_ptr->flags & RF_DEMON) || + (r_ptr->flags & RF_UNDEAD) || + (r_ptr->flags & RF_STUPID) || + (r_ptr->flags & RF_NONLIVING) || + (strchr("Evg", r_ptr->d_char))) + { + cmsg_format(TERM_L_RED, "You have destroyed %s.", m_name); + } + + /* Death by Physical attack -- living monster */ + else + { + cmsg_format(TERM_L_RED, "You have slain %s.", m_name); + } + + /* Maximum player level */ + const s32b div = p_ptr->max_plv; + + if (m_ptr->status < MSTATUS_FRIEND) + { + /* Give some experience for the kill */ + int new_exp = ((long)r_ptr->mexp * m_ptr->level) / div; + + /* Handle fractional experience */ + const s32b new_exp_frac = ((((long)r_ptr->mexp * m_ptr->level) % div) + * 0x10000L / div) + p_ptr->exp_frac; + + /* Keep track of experience */ + if (new_exp_frac >= 0x10000L) + { + new_exp++; + p_ptr->exp_frac = new_exp_frac - 0x10000L; + } + else + { + p_ptr->exp_frac = new_exp_frac; + } + + /* Gain experience */ + gain_exp(new_exp); + } + + if (!note) + { + /* Access the weapon */ + object_type *o_ptr = &p_ptr->inventory[INVEN_WIELD]; + auto const flags = object_flags(o_ptr); + + /* Can the weapon gain levels ? */ + if ((o_ptr->k_idx) && (flags & TR_LEVELS)) + { + /* Give some experience for the kill */ + const int new_exp = ((long)r_ptr->mexp * m_ptr->level) / (div * 2); + + /* Gain experience */ + o_ptr->exp += new_exp; + check_experience_obj(o_ptr); + } + } + + /* When the player kills a Unique, it stays dead */ + if (r_ptr->flags & RF_UNIQUE) + { + r_ptr->max_num = 0; + } + + /* Generate treasure */ + monster_death(m_idx); + + /* Eru doesn't appreciate good monster death */ + if (r_ptr->flags & RF_GOOD) + { + inc_piety(GOD_ERU, -7 * m_ptr->level); + inc_piety(GOD_MANWE, -10 * m_ptr->level); + inc_piety(GOD_MELKOR, 3 * m_ptr->level); + } + else + { + inc_piety(GOD_MELKOR, 1 + m_ptr->level / 2); + } + + /* Manwe appreciate evil monster death */ + if (r_ptr->flags & RF_EVIL) + { + int inc = std::max(1, m_ptr->level / 2); + + if (praying_to(GOD_MANWE)) + { + inc_piety(GOD_MANWE, inc); + } + + inc = std::max(2, inc); + + inc_piety(GOD_TULKAS, inc / 2); + + if (praying_to(GOD_TULKAS)) + { + inc_piety(GOD_TULKAS, inc / 2); + if (r_ptr->flags & RF_DEMON) + { + inc_piety(GOD_TULKAS, inc); + } + } + } + + /* Yavanna likes when corruption is destroyed */ + if ((r_ptr->flags & RF_NONLIVING) || (r_ptr->flags & RF_DEMON) || (r_ptr->flags & RF_UNDEAD)) + { + int inc = std::max(1, m_ptr->level / 2); + inc_piety(GOD_YAVANNA, inc); + } + + /* Yavanna doesnt like any killing in her name */ + if (praying_to(GOD_YAVANNA)) + { + int inc = std::max(1, m_ptr->level / 2); + inc_piety(GOD_YAVANNA, -inc); + + /* Killing animals in her name is a VERY bad idea */ + if (r_ptr->flags & RF_ANIMAL) + inc_piety(GOD_YAVANNA, -(inc * 3)); + } + + /* SHould we absorb its soul? */ + if (p_ptr->absorb_soul && (!(r_ptr->flags & RF_UNDEAD)) && (!(r_ptr->flags & RF_NONLIVING))) + { + msg_print("You absorb the life of the dying soul."); + hp_player(1 + (m_ptr->level / 2) + get_skill_scale(SKILL_NECROMANCY, 40)); + } + + /* + * XXX XXX XXX Mega-Hack -- Remove random quest rendered + * impossible + */ + if (r_ptr->flags & RF_UNIQUE) + { + int i; + + /* Search for all the random quests */ + for (i = 0; i < MAX_RANDOM_QUEST; i++) + { + random_quest *q_ptr = &random_quests[i]; + + /* Ignore invalid entries */ + if (q_ptr->type == 0) continue; + + /* It's done */ + if (q_ptr->done) continue; + + /* + * XXX XXX XXX Not yet completed quest is + * to kill a unique you've just killed + */ + if (r_idx == q_ptr->r_idx) + { + /* Invalidate it */ + q_ptr->type = 0; + } + } + } + + /* Make note of unique kills */ + if (r_ptr->flags & RF_UNIQUE) + { + char note[80]; + + /* Write note */ + sprintf(note, "Killed %s", r_ptr->name); + + add_note(note, 'U'); + } + + /* Recall even invisible uniques or winners */ + if (m_ptr->ml || (r_ptr->flags & RF_UNIQUE)) + { + /* Count kills this life */ + if (r_ptr->r_pkills < MAX_SHORT) r_ptr->r_pkills++; + + /* Hack -- Auto-recall */ + monster_race_track(m_ptr->r_idx, m_ptr->ego); + } + + /* Delete the monster */ + delete_monster_idx(m_idx); + + /* Not afraid */ + (*fear) = FALSE; + + /* Monster is dead */ + return (TRUE); + } + + /* Apply fear */ + mon_handle_fear(m_ptr, dam, fear); + + /* Not dead yet */ + return (FALSE); +} + + +/* + * Get term size and calculate screen size + */ +void get_screen_size(int *wid_p, int *hgt_p) +{ + Term_get_size(wid_p, hgt_p); + *hgt_p -= ROW_MAP + 1; + *wid_p -= COL_MAP + 1; +} + +/* + * Calculates current boundaries + * Called below. + */ +static void panel_bounds() +{ + int wid, hgt; + + /* Retrieve current screen size */ + get_screen_size(&wid, &hgt); + + /* + 24 - 1 - 2 = + 21 */ + panel_row_max = panel_row_min + hgt - 1; + panel_row_prt = panel_row_min - ROW_MAP; + + /* Paranoia -- Boundary check */ + if (panel_row_max > cur_hgt - 1) panel_row_max = cur_hgt - 1; + + panel_col_max = panel_col_min + wid - 1; + panel_col_prt = panel_col_min - COL_MAP; + + /* Paranoia -- Boundary check */ + if (panel_col_max > cur_wid - 1) panel_col_max = cur_wid - 1; +} + + +/* + * Handle a request to change the current panel + * + * Return TRUE if the panel was changed. + * + * Also used in do_cmd_locate() + */ +bool_ change_panel(int dy, int dx) +{ + int y, x; + int wid, hgt; + + /* Get size */ + get_screen_size(&wid, &hgt); + + /* Apply the motion */ + y = panel_row_min + dy * (hgt / 2); + x = panel_col_min + dx * (wid / 2); + + /* Calculate bounds */ + if (y > cur_hgt - hgt) y = cur_hgt - hgt; + if (y < 0) y = 0; + if (x > cur_wid - wid) x = cur_wid - wid; + if (x < 0) x = 0; + + /* Handle changes */ + if ((y != panel_row_min) || (x != panel_col_min)) + { + /* Save the new panel info */ + panel_row_min = y; + panel_col_min = x; + + /* Recalculate the boundaries */ + panel_bounds(); + + /* Update stuff */ + p_ptr->update |= (PU_MONSTERS); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Handle stuff */ + handle_stuff(); + + /* Success */ + return (TRUE); + } + + /* No changes */ + return (FALSE); +} + + +/* + * Given an row (y) and col (x), this routine detects when a move + * off the screen has occurred and figures new borders. -RAK- + * + * "Update" forces a "full update" to take place. + * + * The map is reprinted if necessary, and "TRUE" is returned. + */ +void verify_panel() +{ + int y = p_ptr->py; + int x = p_ptr->px; + + int wid, hgt; + + int prow_min; + int pcol_min; + + int panel_hgt, panel_wid; + + int max_prow_min; + int max_pcol_min; + + + /* + * Make sure panel_row/col_max have correct values -- now taken care of + * by the hook function below, which eliminates glitches, but does it + * in a very hackish way XXX XXX XXX + */ + /* panel_bounds(); */ + + /* Get size */ + get_screen_size(&wid, &hgt); + + /* Calculate scroll amount */ + panel_hgt = hgt / 2; + panel_wid = wid / 2; + + /* Upper boundary of panel_row/col_min */ + max_prow_min = cur_hgt - hgt; + max_pcol_min = cur_wid - wid; + + /* Boundary check */ + if (max_prow_min < 0) max_prow_min = 0; + if (max_pcol_min < 0) max_pcol_min = 0; + + /* An option: center on player */ + if (options->center_player) + { + /* Center vertically */ + prow_min = y - panel_hgt; + + /* Boundary check */ + if (prow_min < 0) prow_min = 0; + else if (prow_min > max_prow_min) prow_min = max_prow_min; + + /* Center horizontally */ + pcol_min = x - panel_wid; + + /* Boundary check */ + if (pcol_min < 0) pcol_min = 0; + else if (pcol_min > max_pcol_min) pcol_min = max_pcol_min; + } + + /* No centering */ + else + { + prow_min = panel_row_min; + pcol_min = panel_col_min; + + /* Scroll screen when 2 grids from top/bottom edge */ + if (y > panel_row_max - 2) + { + while (y > prow_min + hgt - 2) + { + prow_min += panel_hgt; + } + + if (prow_min > max_prow_min) prow_min = max_prow_min; + } + + if (y < panel_row_min + 2) + { + while (y < prow_min + 2) + { + prow_min -= panel_hgt; + } + + if (prow_min < 0) prow_min = 0; + } + + /* Scroll screen when 4 grids from left/right edge */ + if (x > panel_col_max - 4) + { + while (x > pcol_min + wid - 4) + { + pcol_min += panel_wid; + } + + if (pcol_min > max_pcol_min) pcol_min = max_pcol_min; + } + + if (x < panel_col_min + 4) + { + while (x < pcol_min + 4) + { + pcol_min -= panel_wid; + } + + if (pcol_min < 0) pcol_min = 0; + } + } + + /* Check for "no change" */ + if ((prow_min == panel_row_min) && (pcol_min == panel_col_min)) return; + + /* Save the new panel info */ + panel_row_min = prow_min; + panel_col_min = pcol_min; + + /* Hack -- optional disturb on "panel change" */ + if (options->disturb_panel && !options->center_player) + { + disturb(); + } + + /* Recalculate the boundaries */ + panel_bounds(); + + /* Update stuff */ + p_ptr->update |= (PU_MONSTERS); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); +} + + +/* + * Map resizing whenever the main term changes size + */ +void resize_map() +{ + /* Only if the dungeon exists */ + if (!character_dungeon) return; + + /* Mega-Hack -- No panel yet, assume illegal panel */ + panel_row_min = cur_hgt; + panel_row_max = 0; + panel_col_min = cur_wid; + panel_col_max = 0; + + /* Select player panel */ + verify_panel(); + + /* + * The following should be the same as the main window code + * in the do_cmd_redraw() + */ + + /* Combine and reorder the pack (later) */ + p_ptr->notice |= (PN_COMBINE | PN_REORDER); + + /* Update torch */ + p_ptr->update |= (PU_TORCH); + + /* Update stuff */ + p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_POWERS | + PU_SANITY | PU_BODY); + + /* Forget and update view */ + p_ptr->update |= (PU_UN_VIEW | PU_VIEW | PU_MONSTERS | PU_MON_LITE); + + /* Redraw everything */ + p_ptr->redraw |= (PR_WIPE | PR_FRAME | PR_MAP); + + /* Hack -- update */ + handle_stuff(); + + /* Redraw */ + Term_redraw(); + + /* Refresh */ + Term_fresh(); +} + + +/* + * Redraw a term when it is resized + */ +void resize_window() +{ + /* Only if the dungeon exists */ + if (!character_dungeon) return; + + /* Hack -- Activate the Angband window for the redraw */ + Term_activate(&term_screen[0]); + + /* Hack -- react to changes */ + Term_xtra(TERM_XTRA_REACT, 0); + + /* Window stuff */ + p_ptr->window |= (PW_INVEN | PW_EQUIP | PW_PLAYER); + + /* Window stuff */ + p_ptr->window |= (PW_M_LIST | PW_MESSAGE | PW_OVERHEAD | + PW_MONSTER | PW_OBJECT); + + + /* Hack -- update */ + handle_stuff(); + + /* Refresh */ + Term_fresh(); +} + + + + +/* + * Monster health description + */ +static cptr look_mon_desc(int m_idx) +{ + bool_ living = TRUE; + + /* Determine if the monster is "living" (vs "undead") */ + monster_type *m_ptr = &m_list[m_idx]; + auto const r_ptr = m_ptr->race(); + if (r_ptr->flags & RF_UNDEAD) living = FALSE; + if (r_ptr->flags & RF_DEMON) living = FALSE; + if (r_ptr->flags & RF_NONLIVING) living = FALSE; + if (strchr("Egv", r_ptr->d_char)) living = FALSE; + + + /* Healthy monsters */ + if (m_ptr->hp >= m_ptr->maxhp) + { + /* No damage */ + return (living ? "unhurt" : "undamaged"); + } + + + /* Calculate a health "percentage" */ + const int perc = 100L * m_ptr->hp / m_ptr->maxhp; + + if (perc >= 60) + { + return (living ? "somewhat wounded" : "somewhat damaged"); + } + + if (perc >= 25) + { + return (living ? "wounded" : "damaged"); + } + + if (perc >= 10) + { + return (living ? "badly wounded" : "badly damaged"); + } + + return (living ? "almost dead" : "almost destroyed"); +} + + + + + +/*** Targetting Code ***/ + + +/* + * Determine is a monster makes a reasonable target + * + * The concept of "targetting" was stolen from "Morgul" (?) + * + * The player can target any location, or any "target-able" monster. + * + * Currently, a monster is "target_able" if it is visible, and if + * the player can hit it with a projection, and the player is not + * hallucinating. This allows use of "use closest target" macros. + * + * Future versions may restrict the ability to target "trappers" + * and "mimics", but the semantics is a little bit weird. + */ +static bool target_able(int m_idx) +{ + auto const &r_info = game->edit_data.r_info; + + monster_type *m_ptr = &m_list[m_idx]; + + /* Monster must be alive */ + if (!m_ptr->r_idx) return (FALSE); + + /* Monster must be visible */ + if (!m_ptr->ml) return (FALSE); + + /* Monster must be projectable */ + if (!projectable(p_ptr->py, p_ptr->px, m_ptr->fy, m_ptr->fx)) return (FALSE); + + /* Hack -- no targeting hallucinations */ + if (p_ptr->image) return (FALSE); + + /* Dont target pets */ + if (is_friend(m_ptr) > 0) return (FALSE); + + /* Honor flag */ + if (r_info[m_ptr->r_idx].flags & RF_NO_TARGET) return (FALSE); + + /* XXX XXX XXX Hack -- Never target trappers */ + /* if (CLEAR_ATTR && (CLEAR_CHAR)) return (FALSE); */ + + /* Assume okay */ + return (TRUE); +} + + + + +/* + * Update (if necessary) and verify (if possible) the target. + * + * We return TRUE if the target is "okay" and FALSE otherwise. + */ +bool_ target_okay() +{ + /* Accept stationary targets */ + if (target_who < 0) return (TRUE); + + /* Check moving targets */ + if (target_who > 0) + { + /* Accept reasonable targets */ + if (target_able(target_who)) + { + monster_type *m_ptr = &m_list[target_who]; + + /* Acquire monster location */ + target_row = m_ptr->fy; + target_col = m_ptr->fx; + + /* Good target */ + return (TRUE); + } + } + + /* Assume no target */ + return (FALSE); +} + + + +/* + * Hack -- help "select" a location (see below) + */ +static s16b target_pick(point p, int dy, int dx, std::vector<point> const &points) +{ + int b_i = -1, b_v = 9999; + + /* Scan the locations */ + for (std::size_t i = 0; i < points.size(); i++) + { + /* Point 2 */ + int x2 = points[i].x(); + int y2 = points[i].y(); + + /* Directed distance */ + int x3 = (x2 - p.x()); + int y3 = (y2 - p.y()); + + /* Verify quadrant */ + if (dx && (x3 * dx <= 0)) continue; + if (dy && (y3 * dy <= 0)) continue; + + /* Absolute distance */ + int x4 = ABS(x3); + int y4 = ABS(y3); + + /* Verify quadrant */ + if (dy && !dx && (x4 > y4)) continue; + if (dx && !dy && (y4 > x4)) continue; + + /* Approximate Double Distance */ + int v = ((x4 > y4) ? (x4 + x4 + y4) : (y4 + y4 + x4)); + + /* XXX XXX XXX Penalize location */ + + /* Track best */ + if ((b_i >= 0) && (v >= b_v)) continue; + + /* Track best */ + b_i = i; + b_v = v; + } + + /* Result */ + return (b_i); +} + + +/* + * Hack -- determine if a given location is "interesting" + */ +static bool_ target_set_accept(int y, int x) +{ + auto const &r_info = game->edit_data.r_info; + auto const &f_info = game->edit_data.f_info; + + /* Player grid is always interesting */ + if ((y == p_ptr->py) && (x == p_ptr->px)) return (TRUE); + + + /* Handle hallucination */ + if (p_ptr->image) return (FALSE); + + + /* Examine the grid */ + cave_type *c_ptr = &cave[y][x]; + + /* Visible monsters */ + if (c_ptr->m_idx && c_ptr->m_idx < static_cast<int>(r_info.size())) + { + + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + /* Visible monsters */ + if (m_ptr->ml) return (TRUE); + } + + /* Scan all objects in the grid */ + for (auto const this_o_idx: c_ptr->o_idxs) + { + /* Acquire object */ + object_type *o_ptr = &o_list[this_o_idx]; + + /* Memorized object */ + if (o_ptr->marked) + { + return (TRUE); + } + } + + /* Interesting memorized features */ + if (c_ptr->info & (CAVE_MARK)) + { + /* Hack -- Doors are boring */ + if (c_ptr->feat == FEAT_OPEN) return (FALSE); + if (c_ptr->feat == FEAT_BROKEN) return (FALSE); + if ((c_ptr->feat >= FEAT_DOOR_HEAD) && + (c_ptr->feat <= FEAT_DOOR_TAIL)) return (FALSE); + + /* Accept 'naturally' interesting features */ + if (f_info[c_ptr->feat].flags & FF_NOTICE) return (TRUE); + } + + /* Nope */ + return (FALSE); +} + + +/* + * Prepare the "temp" array for "target_set" + * + * Return the number of target_able monsters in the set. + */ +static std::vector<point> target_set_prepare(int mode) +{ + std::vector<point> points; + + // Scan the current panel + for (int y = panel_row_min; y <= panel_row_max; y++) + { + for (int x = panel_col_min; x <= panel_col_max; x++) + { + cave_type *c_ptr = &cave[y][x]; + + // Require "interesting" contents + if (!target_set_accept(y, x)) continue; + + // Require target_able monsters for "TARGET_KILL" + if ((mode & (TARGET_KILL)) && !target_able(c_ptr->m_idx)) continue; + + // Save the location + points.push_back(point(x,y)); + } + } + + // Sort the points by distance to player; we'll + // use a stable sort to avoid equidistant targets + // "swapping" priorities. + std::stable_sort( + std::begin(points), + std::end(points), + [](point i, point j) -> bool { + auto di = distance(p_ptr->py, p_ptr->px, i.y(), i.x()); + auto dj = distance(p_ptr->py, p_ptr->px, j.y(), j.x()); + return di < dj; + } + ); + + return points; +} + + +bool_ target_object(int y, int x, int mode, cptr info, bool_ *boring, + object_type *o_ptr, char *out_val, cptr *s1, cptr *s2, cptr *s3, + int *query) +{ + char o_name[80]; + + /* Not boring */ + *boring = FALSE; + + /* Obtain an object description */ + object_desc(o_name, o_ptr, TRUE, 3); + + /* Describe the object */ + sprintf(out_val, "%s%s%s%s [%s]", *s1, *s2, *s3, o_name, info); + prt(out_val, 0, 0); + move_cursor_relative(y, x); + *query = inkey(); + + /* Always stop at "normal" keys */ + if ((*query != '\r') && (*query != '\n') && (*query != ' ')) return (TRUE); + + /* Sometimes stop at "space" key */ + if ((*query == ' ') && !(mode & (TARGET_LOOK))) return (TRUE); + + /* Change the intro */ + *s1 = "It is "; + + /* Plurals */ + if (o_ptr->number != 1) *s1 = "They are "; + + /* Preposition */ + *s2 = "on "; + return (FALSE); +} + +/* + * Examine a grid, return a keypress. + * + * The "mode" argument contains the "TARGET_LOOK" bit flag, which + * indicates that the "space" key should scan through the contents + * of the grid, instead of simply returning immediately. This lets + * the "look" command get complete information, without making the + * "target" command annoying. + * + * The "info" argument contains the "commands" which should be shown + * inside the "[xxx]" text. This string must never be empty, or grids + * containing monsters will be displayed with an extra comma. + * + * Note that if a monster is in the grid, we update both the monster + * recall info and the health bar info to track that monster. + * + * Eventually, we may allow multiple objects per grid, or objects + * and terrain features in the same grid. XXX XXX XXX + * + * This function must handle blindness/hallucination. + */ +static int target_set_aux(int y, int x, int mode, cptr info) +{ + auto const &d_info = game->edit_data.d_info; + auto const &st_info = game->edit_data.st_info; + auto const &wf_info = game->edit_data.wf_info; + auto const &f_info = game->edit_data.f_info; + auto const &k_info = game->edit_data.k_info; + + cave_type *c_ptr = &cave[y][x]; + + cptr s1, s2, s3; + + bool_ boring; + + int feat; + + int query; + + char out_val[160]; + + + /* Repeat forever */ + while (1) + { + /* Paranoia */ + query = ' '; + + /* Assume boring */ + boring = TRUE; + + /* Default */ + s1 = "You see "; + s2 = ""; + s3 = ""; + + /* Hack -- under the player */ + if ((y == p_ptr->py) && (x == p_ptr->px)) + { + /* Description */ + s1 = "You are "; + + /* Preposition */ + s2 = "on "; + } + + + /* Hack -- hallucination */ + if (p_ptr->image) + { + cptr name = "something strange"; + + /* Display a message */ + sprintf(out_val, "%s%s%s%s [%s]", s1, s2, s3, name, info); + prt(out_val, 0, 0); + move_cursor_relative(y, x); + query = inkey(); + + /* Stop on everything but "return" */ + if ((query != '\r') && (query != '\n')) break; + + /* Repeat forever */ + continue; + } + + + /* Actual monsters */ + if (c_ptr->m_idx) + { + monster_type *m_ptr = &m_list[c_ptr->m_idx]; + auto const r_ptr = m_ptr->race(); + + /* Mimics special treatment -- looks like an object */ + if ((r_ptr->flags & RF_MIMIC) && (m_ptr->csleep)) + { + /* Acquire object */ + object_type *o_ptr = &o_list[m_ptr->mimic_o_idx()]; + + if (o_ptr->marked) + { + if (target_object(y, x, mode, info, &boring, o_ptr, out_val, &s1, &s2, &s3, &query)) + { + break; + } + } + } + else + { + /* Visible */ + if (m_ptr->ml) + { + bool_ recall = FALSE; + + char m_name[80]; + + /* Not boring */ + boring = FALSE; + + /* Get the monster name ("a kobold") */ + monster_desc(m_name, m_ptr, 0x08); + + /* Hack -- track this monster race */ + monster_race_track(m_ptr->r_idx, m_ptr->ego); + + /* Hack -- health bar for this monster */ + health_track(c_ptr->m_idx); + + /* Hack -- handle stuff */ + handle_stuff(); + + /* Interact */ + while (1) + { + /* Recall */ + if (recall) + { + /* Save */ + character_icky = TRUE; + Term_save(); + + /* Recall on screen */ + screen_roff(m_ptr->r_idx, m_ptr->ego); + + /* Hack -- Complete the prompt (again) */ + Term_addstr( -1, TERM_WHITE, format(" [r,%s]", info)); + + /* Command */ + query = inkey(); + + /* Restore */ + Term_load(); + character_icky = FALSE; + } + + /* Normal */ + else + { + cptr mstat; + + switch (m_ptr->status) + { + case MSTATUS_NEUTRAL: + case MSTATUS_NEUTRAL_M: + case MSTATUS_NEUTRAL_P: + mstat = " (neutral) "; + break; + case MSTATUS_PET: + mstat = " (pet) "; + break; + case MSTATUS_FRIEND: + mstat = " (coaligned) "; + break; + case MSTATUS_COMPANION: + mstat = " (companion) "; + break; + default: + mstat = " "; + break; + } + if (m_ptr->mflag & MFLAG_PARTIAL) mstat = " (partial) "; + + /* Describe, and prompt for recall */ + sprintf(out_val, "%s%s%s%s (level %d, %s%s%s)%s%s[r,%s]", + s1, s2, s3, m_name, + m_ptr->level, look_mon_desc(c_ptr->m_idx), + (m_ptr->csleep) ? ", asleep" : "", + (m_ptr->mflag & MFLAG_QUEST) ? ", quest" : "", + (m_ptr->smart & SM_CLONED ? " (clone)" : ""), + (mstat), info); + + prt(out_val, 0, 0); + + /* Place cursor */ + move_cursor_relative(y, x); + + /* Command */ + query = inkey(); + } + + /* Normal commands */ + if (query != 'r') break; + + /* Toggle recall */ + recall = !recall; + } + + /* Always stop at "normal" keys */ + if ((query != '\r') && (query != '\n') && (query != ' ')) break; + + /* Sometimes stop at "space" key */ + if ((query == ' ') && !(mode & (TARGET_LOOK))) break; + + /* Change the intro */ + s1 = "It is "; + + /* Hack -- take account of gender */ + if (r_ptr->flags & RF_FEMALE) s1 = "She is "; + else if (r_ptr->flags & RF_MALE) s1 = "He is "; + + /* Use a preposition */ + s2 = "carrying "; + + /* Scan all objects being carried */ + std::size_t i = 0; + for (; i < m_ptr->hold_o_idxs.size(); i++) + { + /* Acquire object */ + auto this_o_idx = m_ptr->hold_o_idxs.at(i); + object_type *o_ptr = &o_list[this_o_idx]; + + /* Obtain an object description */ + char o_name[80]; + object_desc(o_name, o_ptr, TRUE, 3); + + /* Describe the object */ + sprintf(out_val, "%s%s%s%s [%s]", s1, s2, s3, o_name, info); + prt(out_val, 0, 0); + move_cursor_relative(y, x); + query = inkey(); + + /* Always stop at "normal" keys */ + if ((query != '\r') && (query != '\n') && (query != ' ')) + { + break; + } + + /* Sometimes stop at "space" key */ + if ((query == ' ') && !(mode & (TARGET_LOOK))) + { + break; + } + + /* Change the intro */ + s2 = "also carrying "; + } + + /* Double break? */ + if (i != m_ptr->hold_o_idxs.size()) + { + break; + } + + /* Use a preposition */ + s2 = "on "; + } + } + } + + /* Scan all objects in the grid */ + { + std::size_t i = 0; + for (; i < c_ptr->o_idxs.size(); i++) + { + /* Acquire object */ + auto this_o_idx = c_ptr->o_idxs.at(i); + object_type *o_ptr = &o_list[this_o_idx]; + + /* Describe it */ + if (o_ptr->marked && target_object(y, x, mode, info, &boring, o_ptr, out_val, &s1, &s2, &s3, &query)) + { + break; + } + } + + /* Double break? */ + if (i != c_ptr->o_idxs.size()) + { + break; + } + } + + /* Feature (apply "mimic") */ + if (c_ptr->mimic) + { + feat = c_ptr->mimic; + } + else + { + feat = f_info[c_ptr->feat].mimic; + } + + /* Require knowledge about grid, or ability to see grid */ + if (!(c_ptr->info & (CAVE_MARK)) && !player_can_see_bold(y, x)) + { + /* Forget feature */ + feat = FEAT_NONE; + } + + /* Terrain feature if needed */ + if (boring || (feat >= FEAT_GLYPH)) + { + std::string name; + + /* Hack -- special handling for building doors */ + if (feat == FEAT_SHOP) + { + name = st_info[c_ptr->special].name; + } + else + { + name = f_info[feat].name; + } + + /* Hack -- handle unknown grids */ + if (feat == FEAT_NONE) name = "unknown grid"; + + /* Pick a prefix */ + if (*s2 && + (((feat >= FEAT_MINOR_GLYPH) && + (feat <= FEAT_PATTERN_XTRA2)) || + (feat == FEAT_DIRT) || + (feat == FEAT_GRASS) || + (feat == FEAT_FLOWER))) s2 = "on "; + else if (*s2 && (feat == FEAT_SMALL_TREES)) s2 = "by "; + else if (*s2 && (feat >= FEAT_DOOR_HEAD)) s2 = "in "; + + /* Pick proper indefinite article */ + s3 = (is_a_vowel(name[0])) ? "an " : "a "; + + /* Hack -- special introduction for store & building doors */ + if (feat == FEAT_SHOP) + { + s3 = "the entrance to the "; + } + + if ((feat == FEAT_MORE) && c_ptr->special) + { + s3 = ""; + name = d_info[c_ptr->special].text; + } + + if (p_ptr->wild_mode && (feat == FEAT_TOWN)) + { + auto const &wilderness = game->wilderness; + auto const &wf = wf_info[wilderness(x, y).feat]; + + s3 = ""; + name = fmt::format("{}({})", wf.name, wf.text); + } + + if ((feat == FEAT_FOUNTAIN) && (c_ptr->info & CAVE_IDNT)) + { + int tv, sv; + + if (c_ptr->special <= SV_POTION_LAST) + { + tv = TV_POTION; + sv = c_ptr->special; + } + else + { + tv = TV_POTION2; + sv = c_ptr->special - SV_POTION_LAST; + } + + info = k_info[lookup_kind(tv, sv)].name; + } + + /* Display a message */ + if (!wizard) + { + sprintf(out_val, "%s%s%s%s [%s]", s1, s2, s3, name.c_str(), info); + } + else + { + sprintf(out_val, "%s%s%s%s [%s] (%d:%d:%d)", + s1, s2, s3, name.c_str(), info, + c_ptr->feat, c_ptr->mimic, c_ptr->special); + } + prt(out_val, 0, 0); + move_cursor_relative(y, x); + query = inkey(); + + /* Always stop at "normal" keys */ + if ((query != '\r') && (query != '\n') && (query != ' ')) break; + } + + /* Stop on everything but "return" */ + if ((query != '\r') && (query != '\n')) break; + } + + /* Keep going */ + return (query); +} + + + + +/* + * Handle "target" and "look". + * + * Note that this code can be called from "get_aim_dir()". + * + * All locations must be on the current panel. Consider the use of + * "panel_bounds()" to allow "off-panel" targets, perhaps by using + * some form of "scrolling" the map around the cursor. XXX XXX XXX + * That is, consider the possibility of "auto-scrolling" the screen + * while the cursor moves around. This may require changes in the + * "update_mon()" code to allow "visibility" even if off panel, and + * may require dynamic recalculation of the "temp" grid set. + * + * Hack -- targetting/observing an "outer border grid" may induce + * problems, so this is not currently allowed. + * + * The player can use the direction keys to move among "interesting" + * grids in a heuristic manner, or the "space", "+", and "-" keys to + * move through the "interesting" grids in a sequential manner, or + * can enter "location" mode, and use the direction keys to move one + * grid at a time in any direction. The "t" (set target) command will + * only target a monster (as opposed to a location) if the monster is + * target_able and the "interesting" mode is being used. + * + * The current grid is described using the "look" method above, and + * a new command may be entered at any time, but note that if the + * "TARGET_LOOK" bit flag is set (or if we are in "location" mode, + * where "space" has no obvious meaning) then "space" will scan + * through the description of the current grid until done, instead + * of immediately jumping to the next "interesting" grid. This + * allows the "target" command to retain its old semantics. + * + * The "*", "+", and "-" keys may always be used to jump immediately + * to the next (or previous) interesting grid, in the proper mode. + * + * The "return" key may always be used to scan through a complete + * grid description (forever). + * + * This command will cancel any old target, even if used from + * inside the "look" command. + */ +bool_ target_set(int mode) +{ + int i, d, m; + int y = p_ptr->py; + int x = p_ptr->px; + + bool_ done = FALSE; + + bool_ flag = TRUE; + + char query; + + char info[80]; + + int screen_wid, screen_hgt; + int panel_wid, panel_hgt; + + /* Get size */ + get_screen_size(&screen_wid, &screen_hgt); + + /* Calculate the amount of panel movement */ + panel_hgt = screen_hgt / 2; + panel_wid = screen_wid / 2; + + /* Cancel target */ + target_who = 0; + + + /* Cancel tracking */ + /* health_track(0); */ + + + /* Prepare the "temp" array */ + std::vector<point> points = target_set_prepare(mode); + + /* Start near the player */ + m = 0; + + /* Interact */ + while (!done) + { + /* Interesting grids */ + if (flag && points.size()) + { + y = points[m].y(); + x = points[m].x(); + + /* Access */ + cave_type *c_ptr = &cave[y][x]; + + /* Allow target */ + if (target_able(c_ptr->m_idx)) + { + strcpy(info, "q,t,p,o,+,-,'dir'"); + } + + /* Dis-allow target */ + else + { + strcpy(info, "q,p,o,+,-,'dir'"); + } + + /* Describe and Prompt */ + query = target_set_aux(y, x, mode, info); + + /* Cancel tracking */ + /* health_track(0); */ + + /* Assume no "direction" */ + d = 0; + + /* Analyze */ + switch (query) + { + case ESCAPE: + case 'q': + { + done = TRUE; + break; + } + + case 't': + case '.': + case '5': + case '0': + { + if (target_able(c_ptr->m_idx)) + { + health_track(c_ptr->m_idx); + target_who = c_ptr->m_idx; + target_row = y; + target_col = x; + done = TRUE; + } + else + { + bell(); + } + break; + } + + case ' ': + case '*': + case '+': + { + if (++m == points.size()) + { + m = 0; + } + break; + } + + case '-': + { + if (m-- == 0) + { + m = points.size() - 1; + } + break; + } + + case 'p': + { + /* Recenter the map around the player */ + verify_panel(); + + /* Update stuff */ + p_ptr->update |= (PU_MONSTERS); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + + /* Recalculate interesting grids */ + target_set_prepare(mode); + + y = p_ptr->py; + x = p_ptr->px; + + /* Fall through... */ + } + + case 'o': + { + flag = FALSE; + break; + } + + case 'm': + { + break; + } + + default: + { + /* Extract the action (if any) */ + d = get_keymap_dir(query); + + if (!d) bell(); + break; + } + } + + /* Hack -- move around */ + if (d) + { + /* Find a new monster */ + i = target_pick(points[m], ddy[d], ddx[d], points); + + /* Scroll to find interesting grid */ + if (i < 0) + { + int dy; + int dx; + + dy = ddy[d]; + dx = ddx[d]; + + /* Note panel change */ + if (change_panel(dy, dx)) + { + auto const t = points[m]; + + /* Recalculate interesting grids */ + target_set_prepare(mode); + + /* Find a new monster */ + i = target_pick(t, dy, dx, points); + + /* Restore panel if needed */ + if (i < 0) + { + /* Restore panel */ + change_panel( -dy, -dx); + + /* Recalculate interesting grids */ + target_set_prepare(mode); + } + } + } + + /* Use that grids */ + if (i >= 0) m = i; + } + } + + /* Arbitrary grids */ + else + { + /* Default prompt */ + strcpy(info, "q,t,p,m,+,-,'dir'"); + + /* Describe and Prompt (enable "TARGET_LOOK") */ + query = target_set_aux(y, x, mode | TARGET_LOOK, info); + + /* Cancel tracking */ + /* health_track(0); */ + + /* Assume no direction */ + d = 0; + + /* Analyze the keypress */ + switch (query) + { + case ESCAPE: + case 'q': + { + done = TRUE; + break; + } + + case 't': + case '.': + case '5': + case '0': + { + target_who = -1; + target_row = y; + target_col = x; + done = TRUE; + break; + } + + case ' ': + case '*': + case '+': + case '-': + { + break; + } + + case 'p': + { + y = p_ptr->py; + x = p_ptr->px; + } + + case 'o': + { + break; + } + + case 'm': + { + flag = TRUE; + break; + } + + default: + { + /* Extract the action (if any) */ + d = get_keymap_dir(query); + + if (!d) bell(); + break; + } + } + + /* Handle "direction" */ + if (d) + { + int dy = ddy[d]; + int dx = ddx[d]; + + /* Move */ + y += dy; + x += dx; + + /* Do not move horizontally if unnecessary */ + if (((x < panel_col_min + panel_wid) && (dx > 0)) || + ((x > panel_col_min + panel_wid) && (dx < 0))) + { + dx = 0; + } + + /* Do not move vertically if unnecessary */ + if (((y < panel_row_min + panel_hgt) && (dy > 0)) || + ((y > panel_row_min + panel_hgt) && (dy < 0))) + { + dy = 0; + } + /* Apply the motion */ + if ((y >= panel_row_min + screen_hgt) || + (y < panel_row_min) || + (x > panel_col_min + screen_wid) || + (x < panel_col_min)) + { + /* Change panel and recalculate interesting grids */ + if (change_panel(dy, dx)) target_set_prepare(mode); + } + + /* Boundary checks */ + if (!wizard) + { + /* Hack -- Verify y */ + if (y <= 0) y = 1; + else if (y >= cur_hgt - 1) y = cur_hgt - 2; + + /* Hack -- Verify x */ + if (x <= 0) x = 1; + else if (x >= cur_wid - 1) x = cur_wid - 2; + } + else + { + /* Hack -- Verify y */ + if (y < 0) y = 0; + else if (y > cur_hgt - 1) y = cur_hgt - 1; + + /* Hack -- Verify x */ + if (x < 0) x = 0; + else if (x > cur_wid - 1) x = cur_wid - 1; + } + } + } + } + + /* Clear the top line */ + prt("", 0, 0); + + /* Recenter the map around the player */ + verify_panel(); + + /* Update stuff */ + p_ptr->update |= (PU_MONSTERS); + + /* Redraw map */ + p_ptr->redraw |= (PR_MAP); + + /* Window stuff */ + p_ptr->window |= (PW_OVERHEAD); + + /* Handle stuff */ + handle_stuff(); + + /* Failure to set target */ + if (!target_who) return (FALSE); + + /* Success */ + return (TRUE); +} + + + +/* + * Get an "aiming direction" from the user. + * + * The "dir" is loaded with 1,2,3,4,6,7,8,9 for "actual direction", and + * "0" for "current target", and "-1" for "entry aborted". + * + * Note that "Force Target", if set, will pre-empt user interaction, + * if there is a usable target already set. + * + * Note that confusion over-rides any (explicit?) user choice. + */ +bool_ get_aim_dir(int *dp) +{ + int dir; + + char command; + + cptr p; + + if (repeat_pull(dp)) + { + /* Confusion? */ + + /* Verify */ + if (!(*dp == 5 && !target_okay())) + { + return (TRUE); + } + } + + /* Initialize */ + (*dp) = 0; + + /* Global direction */ + dir = command_dir; + + /* Hack -- auto-target if requested */ + if (options->use_old_target && target_okay()) + { + dir = 5; + } + + /* Ask until satisfied */ + while (!dir) + { + /* Choose a prompt */ + if (!target_okay()) + { + p = "Direction ('*' to choose a target, Escape to cancel)? "; + } + else + { + p = "Direction ('5' for target, '*' to re-target, Escape to cancel)? "; + } + + /* Get a command (or Cancel) */ + if (!get_com(p, &command)) break; + + /* Convert various keys to "standard" keys */ + switch (command) + { + /* Use current target */ + case 'T': + case 't': + case '.': + case '5': + case '0': + { + dir = 5; + break; + } + + /* Set new target */ + case '*': + { + if (target_set(TARGET_KILL)) dir = 5; + break; + } + + default: + { + /* Extract the action (if any) */ + dir = get_keymap_dir(command); + + break; + } + } + + /* Verify requested targets */ + if ((dir == 5) && !target_okay()) dir = 0; + + /* Error */ + if (!dir) bell(); + } + + /* No direction */ + if (!dir) return (FALSE); + + /* Save the direction */ + command_dir = dir; + + /* Check for confusion */ + if (p_ptr->confused) + { + /* XXX XXX XXX */ + /* Random direction */ + dir = ddd[rand_int(8)]; + } + + /* Notice confusion */ + if (command_dir != dir) + { + /* Warn the user */ + msg_print("You are confused."); + } + + /* Save direction */ + (*dp) = dir; + + + repeat_push(dir); + + /* A "valid" direction was entered */ + return (TRUE); +} + + + +/* + * Request a "movement" direction (1,2,3,4,6,7,8,9) from the user, + * and place it into "command_dir", unless we already have one. + * + * This function should be used for all "repeatable" commands, such as + * run, walk, open, close, bash, disarm, spike, tunnel, etc, as well + * as all commands which must reference a grid adjacent to the player, + * and which may not reference the grid under the player. Note that, + * for example, it is no longer possible to "disarm" or "open" chests + * in the same grid as the player. + * + * Direction "5" is illegal and will (cleanly) abort the command. + * + * This function tracks and uses the "global direction", and uses + * that as the "desired direction", to which "confusion" is applied. + */ +bool_ get_rep_dir(int *dp) +{ + int dir; + + if (repeat_pull(dp)) + { + return (TRUE); + } + + /* Initialize */ + (*dp) = 0; + + /* Global direction */ + dir = command_dir; + + /* Get a direction */ + while (!dir) + { + char ch; + + /* Get a command (or Cancel) */ + if (!get_com("Direction (Escape to cancel)? ", &ch)) break; + + /* Look up the direction */ + dir = get_keymap_dir(ch); + + /* Oops */ + if (!dir) bell(); + } + + /* Prevent weirdness */ + if (dir == 5) dir = 0; + + /* Aborted */ + if (!dir) return (FALSE); + + /* Save desired direction */ + command_dir = dir; + + /* Apply "confusion" */ + if (p_ptr->confused) + { + /* Standard confusion */ + if (rand_int(100) < 75) + { + /* Random direction */ + dir = ddd[rand_int(8)]; + } + } + + /* Notice confusion */ + if (command_dir != dir) + { + /* Warn the user */ + msg_print("You are confused."); + } + + /* Save direction */ + (*dp) = dir; + + + repeat_push(dir); + + /* Success */ + return (TRUE); +} + + +/* + * old -- from PsiAngband. + */ +bool_ tgt_pt(int *x, int *y) +{ + char ch = 0; + int d, cu, cv; + int screen_wid, screen_hgt; + bool_ success = FALSE; + + *x = p_ptr->px; + *y = p_ptr->py; + + /* Get size */ + get_screen_size(&screen_wid, &screen_hgt); + + cu = Term->scr->cu; + cv = Term->scr->cv; + Term->scr->cu = 0; + Term->scr->cv = 1; + msg_print("Select a point and press space."); + + while ((ch != 27) && (ch != ' ')) + { + move_cursor_relative(*y, *x); + ch = inkey(); + switch (ch) + { + case 27: + break; + case ' ': + success = TRUE; + break; + default: + /* Look up the direction */ + d = get_keymap_dir(ch); + + if (!d) break; + + *x += ddx[d]; + *y += ddy[d]; + + /* Hack -- Verify x */ + if ((*x >= cur_wid - 1) || (*x >= panel_col_min + screen_wid)) (*x)--; + else if ((*x <= 0) || (*x <= panel_col_min)) (*x)++; + + /* Hack -- Verify y */ + if ((*y >= cur_hgt - 1) || (*y >= panel_row_min + screen_hgt)) (*y)--; + else if ((*y <= 0) || (*y <= panel_row_min)) (*y)++; + + break; + } + } + + Term->scr->cu = cu; + Term->scr->cv = cv; + Term_fresh(); + return success; +} + +/* + * Set "p_ptr->grace", notice observable changes + */ +void set_grace(s32b v) +{ + if (v < -300000) v = -300000; + if (v > 300000) v = 300000; + p_ptr->grace = v; + p_ptr->update |= PU_BONUS; + p_ptr->redraw |= (PR_FRAME); + handle_stuff(); +} + +static bool_ test_object_wish(char *name, object_type *o_ptr, object_type *forge, const char *what) +{ + auto &k_info = game->edit_data.k_info; + auto const &e_info = game->edit_data.e_info; + + int save_aware; + char buf[200]; + + /* try all objects, this *IS* a very ugly and slow method :( */ + for (std::size_t i = 0; i < k_info.size(); i++) + { + auto k_ptr = &k_info[i]; + + o_ptr = forge; + + if (!k_ptr->name) continue; + if (k_ptr->flags & TR_NORM_ART) continue; + if (k_ptr->flags & TR_INSTA_ART) continue; + if (k_ptr->tval == TV_GOLD) continue; + + object_prep(o_ptr, i); + o_ptr->name1 = 0; + o_ptr->name2 = 0; + apply_magic(o_ptr, dun_level, FALSE, FALSE, FALSE); + /* Hack : aware status must be restored after describing the item name */ + save_aware = k_ptr->aware; + object_aware(o_ptr); + object_known(o_ptr); + object_desc(buf, o_ptr, FALSE, 0); + strlower(buf); + k_ptr->aware = save_aware; + + if (strstr(name, buf) || + /* Hack hack hackery */ + (o_ptr->tval == TV_ROD_MAIN && strstr(name, "rod of"))) + { + /* try all ego */ + for (std::size_t j = 0; j < e_info.size(); j++) + { + auto e_ptr = &e_info[j]; + bool_ ok = FALSE; + + if (j && !e_ptr->name) continue; + + /* Must have the correct fields */ + if (j) + { + int z; + + for (z = 0; z < 6; z++) + { + if (e_ptr->tval[z] == k_ptr->tval) + { + if ((e_ptr->min_sval[z] <= k_ptr->sval) && + (e_ptr->max_sval[z] >= k_ptr->sval)) ok = TRUE; + } + if (ok) break; + } + if (!ok) + { + continue; + } + } + + /* try all ego */ + for (std::size_t jb = 0; jb < e_info.size(); jb++) + { + auto eb_ptr = &e_info[jb]; + bool_ ok = FALSE; + + if (jb && !eb_ptr->name) continue; + + if (j && jb && (e_ptr->before == eb_ptr->before)) continue; + + /* Must have the correct fields */ + if (jb) + { + int z; + + for (z = 0; z < 6; z++) + { + if (eb_ptr->tval[z] == k_ptr->tval) + { + if ((eb_ptr->min_sval[z] <= k_ptr->sval) && + (eb_ptr->max_sval[z] >= k_ptr->sval)) ok = TRUE; + } + if (ok) break; + } + if (!ok) + { + continue; + } + } + + object_prep(o_ptr, i); + o_ptr->name1 = 0; + o_ptr->name2 = j; + o_ptr->name2b = jb; + apply_magic(o_ptr, dun_level, FALSE, FALSE, FALSE); + object_aware(o_ptr); + object_known(o_ptr); + object_desc(buf, o_ptr, FALSE, 0); + strlower(buf); + + if (iequals(buf, name)) + { + /* Don't search any more */ + return TRUE; + } + else + { + /* Restore again the aware status */ + k_ptr->aware = save_aware; + } + } + } + } + } + return FALSE; +} + +static void clean_wish_name(char *buf, char *name) +{ + char *p; + int i, j; + + /* Lowercase the wish */ + strlower(buf); + + /* Nuke uneccesary spaces */ + p = buf; + while (*p == ' ') p++; + i = 0; + j = 0; + while (p[i]) + { + if ((p[i] == ' ') && (p[i + 1] == ' ')) + { + i++; + continue; + } + name[j++] = p[i++]; + } + name[j++] = '\0'; + if (j) + { + j--; + while (j && (name[j] == ' ')) + { + name[j] = '\0'; + j--; + } + } +} + +/* + * Allow the player to make a wish + */ +void make_wish() +{ + auto const &re_info = game->edit_data.re_info; + auto const &r_info = game->edit_data.r_info; + + char name[200], *mname; + int mstatus = MSTATUS_ENEMY; + object_type forge, *o_ptr = &forge; + + /* Make an empty string */ + char buf[200]; + buf[0] = 0; + + /* Ask for the wish */ + if (!get_string("Wish for what? ", buf, 80)) return; + + clean_wish_name(buf, name); + + /* You can't wish for a wish! */ + if (strstr(name, "wish")) + { + msg_print("You can't wish for a wish!"); + return; + } + + if (test_object_wish(name, o_ptr, &forge, "wish")) + { + msg_print("Your wish becomes truth!"); + + /* Give it to the player */ + drop_near(o_ptr, -1, p_ptr->py, p_ptr->px); + + return; + } + + /* try monsters */ + if (prefix(name, "enemy ")) + { + mstatus = MSTATUS_ENEMY; + mname = name + 6; + } + else if (prefix(name, "neutral ")) + { + mstatus = MSTATUS_NEUTRAL; + mname = name + 8; + } + else if (prefix(name, "friendly ")) + { + mstatus = MSTATUS_FRIEND; + mname = name + 9; + } + else if (prefix(name, "pet ")) + { + mstatus = MSTATUS_PET; + mname = name + 4; + } + else if (prefix(name, "companion ")) + { + if (can_create_companion()) mstatus = MSTATUS_COMPANION; + else mstatus = MSTATUS_PET; + mname = name + 10; + } + else + { + mname = name; + } + + for (std::size_t i = 1; i < r_info.size(); i++) + { + auto r_ptr = &r_info[i]; + + if (!r_ptr->name) continue; + + if (r_ptr->flags & RF_SPECIAL_GENE) continue; + if (r_ptr->flags & RF_NEVER_GENE) continue; + if (r_ptr->flags & RF_UNIQUE) continue; + + sprintf(buf, "%s", r_ptr->name); + strlower(buf); + + if (strstr(mname, buf)) + { + /* try all ego */ + for (std::size_t j = 0; j < re_info.size(); j++) + { + auto re_ptr = &re_info[j]; + + if (j && !re_ptr->name) continue; + + if (!mego_ok(r_ptr, j)) continue; + + if (j) + { + if (re_ptr->before) + { + sprintf(buf, "%s %s", re_ptr->name, r_ptr->name); + } + else + { + sprintf(buf, "%s %s", r_ptr->name, re_ptr->name); + } + } + else + { + sprintf(buf, "%s", r_ptr->name); + } + + strlower(buf); + + if (iequals(mname, buf)) + { + int wy = p_ptr->py, wx = p_ptr->px; + int attempts = 100; + + do + { + scatter(&wy, &wx, p_ptr->py, p_ptr->px, 5); + } + while (!(in_bounds(wy, wx) && cave_floor_bold(wy, wx)) && --attempts); + + /* Create the monster */ + if (place_monster_one(wy, wx, i, j, FALSE, mstatus)) + { + msg_print("Your wish becomes truth!"); + } + + /* Don't search any more */ + return; + } + } + } + } +} + + +/* + * Corrupted have a 1/3 chance of losing a mutation each time this is called, + * assuming they have any in the first place + */ +static void corrupt_corrupted() +{ + if (magik(45)) + { + lose_corruption(); + } + else + { + gain_random_corruption(); + } + + /* We are done. */ + return; +} + +/* + * Change to an other subrace + */ +void switch_subrace(std::size_t racem, bool_ copy_old) +{ + auto &race_mod_info = game->edit_data.race_mod_info; + + assert(racem < race_mod_info.size()); + + /* If we switch to the saved subrace, we copy over the old subrace data */ + if (copy_old && (racem == SUBRACE_SAVE)) + { + // Keep old description + auto old_desc = race_mod_info[SUBRACE_SAVE].description; + // Copy everything + race_mod_info[SUBRACE_SAVE] = race_mod_info[p_ptr->pracem]; + // Reinstate description + race_mod_info[SUBRACE_SAVE].description = old_desc; + } + + p_ptr->pracem = racem; + rmp_ptr = &race_mod_info[p_ptr->pracem]; +} + +/* + * Rebirth, recalc hp & exp/level + */ +void do_rebirth() +{ + /* Experience factor */ + p_ptr->expfact = rp_ptr->ps.exp + rmp_ptr->ps.exp + cp_ptr->ps.exp; + + /* Hitdice */ + p_ptr->hitdie = rp_ptr->ps.mhp + rmp_ptr->ps.mhp + cp_ptr->ps.mhp; + + /* Recalc HP */ + do_cmd_rerate(); + + /* Change the level if needed */ + check_experience(); + p_ptr->max_plv = p_ptr->lev; + + /* Redraw/calc stuff */ + p_ptr->redraw |= (PR_FRAME); + p_ptr->update |= (PU_BONUS); + handle_stuff(); + + lite_spot(p_ptr->py, p_ptr->px); +} |