#include "AppHdr.h"
#include <algorithm>
#include "artefact.h"
#include "beam.h"
#include "branch.h"
#include "database.h"
#include "delay.h"
#include "directn.h"
#include "effects.h"
#include "goditem.h"
#include "it_use2.h"
#include "items.h"
#include "kills.h"
#include "los.h"
#include "makeitem.h"
#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "monplace.h"
#include "monstuff.h"
#include "mutation.h"
#include "notes.h"
#include "ouch.h"
#include "item_use.h"
#include "output.h"
#include "quiver.h"
#include "player.h"
#include "religion.h"
#include "spells2.h"
#include "spells3.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-mis.h"
#include "spl-util.h"
#include "stash.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "transfor.h"
#include "traps.h"
#include "view.h"
#include "xom.h"
#ifdef DEBUG_XOM
# define DEBUG_RELIGION 1
# define NOTE_DEBUG_XOM 1
#endif
#ifdef DEBUG_RELIGION
# define DEBUG_DIAGNOSTICS 1
# define DEBUG_GIFTS 1
#endif
#define FAKE_SPELL_MAGIC_MAPPING SPELL_NO_SPELL
static const spell_type _xom_nontension_spells[] =
{
FAKE_SPELL_MAGIC_MAPPING, SPELL_DETECT_ITEMS, SPELL_SUMMON_BUTTERFLIES,
SPELL_DETECT_CREATURES, SPELL_FLY, SPELL_SPIDER_FORM, SPELL_STATUE_FORM,
SPELL_ICE_FORM, SPELL_DRAGON_FORM, SPELL_NECROMUTATION
};
static const spell_type _xom_tension_spells[] =
{
SPELL_BLINK, SPELL_CONFUSING_TOUCH, SPELL_CAUSE_FEAR, SPELL_MASS_SLEEP,
SPELL_DISPERSAL, SPELL_STONESKIN, SPELL_RING_OF_FLAMES,
SPELL_OLGREBS_TOXIC_RADIANCE, SPELL_TUKIMAS_VORPAL_BLADE,
SPELL_MAXWELLS_SILVER_HAMMER, SPELL_FIRE_BRAND, SPELL_FREEZING_AURA,
SPELL_POISON_WEAPON, SPELL_STONEMAIL, SPELL_LETHAL_INFUSION,
SPELL_EXCRUCIATING_WOUNDS, SPELL_WARP_BRAND, SPELL_TUKIMAS_DANCE,
SPELL_RECALL, SPELL_SUMMON_BUTTERFLIES, SPELL_SUMMON_SMALL_MAMMALS,
SPELL_SUMMON_SCORPIONS, SPELL_SUMMON_SWARM, SPELL_FLY, SPELL_SPIDER_FORM,
SPELL_STATUE_FORM, SPELL_ICE_FORM, SPELL_DRAGON_FORM, SPELL_ANIMATE_DEAD,
SPELL_SHADOW_CREATURES, SPELL_SUMMON_HORRIBLE_THINGS,
SPELL_CALL_CANINE_FAMILIAR, SPELL_SUMMON_ICE_BEAST, SPELL_SUMMON_UGLY_THING,
SPELL_CONJURE_BALL_LIGHTNING, SPELL_SUMMON_DRAGON, SPELL_DEATH_CHANNEL,
SPELL_NECROMUTATION
};
static const char *_xom_message_arrays[NUM_XOM_MESSAGE_TYPES][6] =
{
{
"Xom is interested.",
"Xom is mildly amused.",
"Xom is amused.",
"Xom is highly amused!",
"Xom thinks this is hilarious!",
"Xom roars with laughter!"
},
{
"Xom is interested.",
"Xom is very interested.",
"Xom is extremely interested.",
"Xom is intrigued!",
"Xom is very intrigued!",
"Xom is fascinated!"
}
};
static const char *describe_xom_mood()
{
return (you.piety > 180) ? "Xom's teddy bear." :
(you.piety > 150) ? "a beloved toy of Xom." :
(you.piety > 120) ? "a favourite toy of Xom." :
(you.piety > 80) ? "a toy of Xom." :
(you.piety > 50) ? "a plaything of Xom." :
(you.piety > 20) ? "a special plaything of Xom."
: "a very special plaything of Xom.";
}
const char *describe_xom_favour(bool upper)
{
std::string favour;
if (you.religion != GOD_XOM)
favour = "a very buggy toy of Xom.";
else if (you.gift_timeout < 1)
favour = "a BORING thing.";
else
favour = describe_xom_mood();
if (upper)
favour = uppercase_first(favour);
return (favour.c_str());
}
static std::string _get_xom_speech(const std::string key)
{
std::string result = getSpeakString("Xom " + key);
if (result.empty())
result = getSpeakString("Xom general effect");
if (result.empty())
return ("Xom makes something happen.");
return (result);
}
static bool _xom_is_bored()
{
return (you.religion == GOD_XOM && you.gift_timeout == 0);
}
static bool _xom_feels_nasty()
{
return (you.penance[GOD_XOM] || _xom_is_bored());
}
bool xom_is_nice(int tension)
{
if (you.penance[GOD_XOM])
return (false);
if (you.religion == GOD_XOM)
{
if (you.gift_timeout == 0)
return (false);
const int tension_bonus
= (tension == -1 ? 0 :
tension == 0 ? -std::min(abs(HALF_MAX_PIETY - you.piety) / 2,
you.piety / 10)
: std::min((MAX_PIETY - you.piety) / 2,
random2(tension)));
const int effective_piety = you.piety + tension_bonus;
ASSERT(effective_piety >= 0 && effective_piety <= MAX_PIETY);
#ifdef DEBUG_XOM
mprf(MSGCH_DIAGNOSTICS,
"Xom: tension: %d, piety: %d -> tension bonus = %d, eff. piety: %d",
tension, you.piety, tension_bonus, effective_piety);
#endif
return (x_chance_in_y(effective_piety, MAX_PIETY));
}
else return coinflip();
}
static void _xom_is_stimulated(int maxinterestingness,
const char *message_array[],
bool force_message)
{
if (you.religion != GOD_XOM || maxinterestingness <= 0)
return;
if (crawl_state.which_god_acting() == GOD_XOM)
return;
int interestingness = random2(maxinterestingness);
interestingness = std::min(255, interestingness);
#if defined(DEBUG_RELIGION) || defined(DEBUG_GIFTS) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS,
"Xom: gift_timeout: %d, maxinterestingness = %d, interestingness = %d",
you.gift_timeout, maxinterestingness, interestingness);
#endif
bool was_stimulated = false;
if (interestingness > you.gift_timeout && interestingness >= 12)
{
you.gift_timeout = interestingness;
was_stimulated = true;
}
if (was_stimulated || force_message)
{
god_speaks(GOD_XOM,
((interestingness > 200) ? message_array[5] :
(interestingness > 100) ? message_array[4] :
(interestingness > 75) ? message_array[3] :
(interestingness > 50) ? message_array[2] :
(interestingness > 25) ? message_array[1]
: message_array[0]));
}
}
void xom_is_stimulated(int maxinterestingness, xom_message_type message_type,
bool force_message)
{
_xom_is_stimulated(maxinterestingness, _xom_message_arrays[message_type],
force_message);
}
void xom_is_stimulated(int maxinterestingness, const std::string& message,
bool force_message)
{
if (you.religion != GOD_XOM)
return;
const char *message_array[6];
for (int i = 0; i < 6; ++i)
message_array[i] = message.c_str();
_xom_is_stimulated(maxinterestingness, message_array, force_message);
}
void xom_tick()
{
const std::string old_xom_favour = describe_xom_favour();
const bool good = (you.piety == HALF_MAX_PIETY? coinflip()
: you.piety > HALF_MAX_PIETY);
int size = abs(you.piety - HALF_MAX_PIETY);
int delta = (x_chance_in_y(511, 1000) ? 1 : -1);
size += delta;
if (size > HALF_MAX_PIETY)
size = HALF_MAX_PIETY;
you.piety = HALF_MAX_PIETY + (good ? size : -size);
std::string new_xom_favour = describe_xom_favour();
if (old_xom_favour != new_xom_favour)
{
size += delta * 8;
if (size > HALF_MAX_PIETY)
size = HALF_MAX_PIETY;
you.piety = HALF_MAX_PIETY + (good ? size : -size);
}
#ifdef DEBUG_DIAGNOSTICS
snprintf(info, INFO_SIZE, "xom_tick(), delta: %d, piety: %d",
delta, you.piety);
take_note(Note(NOTE_MESSAGE, 0, 0, info), true);
#endif
if (you.gift_timeout > 0 && coinflip())
you.gift_timeout--;
new_xom_favour = describe_xom_favour();
if (old_xom_favour != new_xom_favour)
{
const std::string msg = "You are now " + new_xom_favour;
god_speaks(you.religion, msg.c_str());
}
if (you.gift_timeout == 1)
simple_god_message(" is getting BORED.");
if (one_chance_in(3))
{
const int tension = get_tension(GOD_XOM);
const int chance = (tension == 0 ? 1 :
tension <= 5 ? 2 :
tension <= 10 ? 3 :
tension <= 20 ? 4
: 5);
if (you.gift_timeout == 0 && x_chance_in_y(5-chance,5))
{
xom_acts(abs(you.piety - HALF_MAX_PIETY), tension);
return;
}
else if (you.gift_timeout <= 1 && chance > 0
&& x_chance_in_y(chance-1, 4))
{
const int interest = random2(chance*15);
if (interest > 0)
{
if (interest < 25)
simple_god_message(" is interested.");
else
simple_god_message(" is intrigued.");
you.gift_timeout += interest;
#if defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS,
"tension %d (chance: %d) -> increase interest to %d",
tension, chance, you.gift_timeout);
#endif
}
}
if (x_chance_in_y(chance, 5))
xom_acts(abs(you.piety - HALF_MAX_PIETY), tension);
}
}
static int _exploration_estimate(bool seen_only = false)
{
int seen = 0;
int total = 0;
int tries = 0;
do
{
tries++;
coord_def pos = random_in_bounds();
if (!seen_only && is_terrain_known(pos) || is_terrain_seen(pos))
{
seen++;
total++;
continue;
}
bool open = true;
if (feat_is_solid(grd(pos)) && !feat_is_closed_door(grd(pos)))
{
open = false;
for (adjacent_iterator ai(pos); ai; ++ai)
{
if (map_bounds(*ai) && (!feat_is_opaque(grd(*ai))
|| feat_is_closed_door(grd(*ai))))
{
open = true;
break;
}
}
}
if (open)
total++;
}
while (total < 100 && tries < 1000);
#ifdef DEBUG_XOM
mprf(MSGCH_DIAGNOSTICS,
"exploration estimate (%s): %d out of %d grids seen",
seen_only ? "explored" : "mapped", seen, total);
#endif
if (total == 0)
return (100);
if (total < 100)
seen *= 100 / total;
return (seen);
}
static bool _spell_weapon_check(const spell_type spell)
{
switch (spell)
{
case SPELL_TUKIMAS_DANCE:
return (player_weapon_wielded());
case SPELL_TUKIMAS_VORPAL_BLADE:
case SPELL_MAXWELLS_SILVER_HAMMER:
case SPELL_FIRE_BRAND:
case SPELL_FREEZING_AURA:
case SPELL_POISON_WEAPON:
case SPELL_LETHAL_INFUSION:
case SPELL_EXCRUCIATING_WOUNDS:
case SPELL_WARP_BRAND:
{
if (!player_weapon_wielded())
return (false);
const item_def& weapon = *you.weapon();
return (!is_artefact(weapon) && !is_range_weapon(weapon)
&& get_weapon_brand(weapon) == SPWPN_NORMAL);
}
default:
return (true);
}
}
static bool _teleportation_check(const spell_type spell = SPELL_TELEPORT_SELF)
{
switch (spell)
{
case SPELL_BLINK:
case SPELL_TELEPORT_SELF:
return (!scan_artefacts(ARTP_PREVENT_TELEPORTATION));
default:
return (true);
}
}
static bool _transformation_check(const spell_type spell)
{
transformation_type tran = TRAN_NONE;
switch (spell)
{
case SPELL_SPIDER_FORM:
tran = TRAN_SPIDER;
break;
case SPELL_STATUE_FORM:
tran = TRAN_STATUE;
break;
case SPELL_ICE_FORM:
tran = TRAN_ICE_BEAST;
break;
case SPELL_DRAGON_FORM:
tran = TRAN_DRAGON;
break;
case SPELL_NECROMUTATION:
tran = TRAN_LICH;
break;
default:
break;
}
if (tran == TRAN_NONE)
return (true);
return (transform(0, tran, true, true));
}
static int _xom_makes_you_cast_random_spell(int sever, int tension,
bool debug = false)
{
int spellenum = std::max(1, sever);
god_acting gdact(GOD_XOM);
spell_type spell;
if (tension > 0)
{
const int nxomspells = ARRAYSZ(_xom_tension_spells);
spellenum = std::min(nxomspells, spellenum);
spell = _xom_tension_spells[random2(spellenum)];
if (!_spell_weapon_check(spell))
return (XOM_DID_NOTHING);
}
else
{
const int nxomspells = ARRAYSZ(_xom_nontension_spells);
spellenum = std::min(nxomspells, std::max(3 + coinflip(), spellenum));
spell = _xom_nontension_spells[random2(spellenum)];
if (spell == FAKE_SPELL_MAGIC_MAPPING || spell == SPELL_DETECT_ITEMS)
{
const int explored = _exploration_estimate();
if (explored > 80 && x_chance_in_y(explored, 100))
return (XOM_DID_NOTHING);
}
}
if (spell == FAKE_SPELL_MAGIC_MAPPING)
{
if (you.level_type == LEVEL_PANDEMONIUM)
return (XOM_DID_NOTHING);
if (debug)
return (XOM_GOOD_MAPPING);
god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str());
#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS,
"_xom_makes_you_cast_random_spell(); spell: %d, spellenum: %d",
spell, spellenum);
#endif
take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, "magic mapping"),
true);
const int power = stepdown_value( sever, 10, 10, 40, 45 );
magic_mapping(5 + power, 50 + random2avg(power * 2, 2), false);
return (XOM_GOOD_MAPPING);
}
if (you_cannot_memorise(spell))
return (XOM_DID_NOTHING);
if (!_teleportation_check(spell))
return (XOM_DID_NOTHING);
if (!_transformation_check(spell))
return (XOM_DID_NOTHING);
const int result = (tension > 0 ? XOM_GOOD_SPELL_TENSION
: XOM_GOOD_SPELL_CALM);
if (debug)
return (result);
god_speaks(GOD_XOM, _get_xom_speech("spell effect").c_str());
#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS,
"_xom_makes_you_cast_random_spell(); spell: %d, spellenum: %d",
spell, spellenum);
#endif
static char spell_buf[100];
snprintf(spell_buf, sizeof(spell_buf), "cast spell '%s'",
spell_title(spell));
take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, spell_buf), true);
your_spells(spell, sever, false);
return (result);
}
static void _try_brand_switch(const int item_index)
{
if (item_index == NON_ITEM)
return;
item_def &item(mitm[item_index]);
if (item.base_type != OBJ_WEAPONS || is_range_weapon(item))
return;
if (is_unrandom_artefact(item))
return;
if (one_chance_in(5))
return;
int brand;
if (item.base_type == OBJ_WEAPONS)
{
if (get_weapon_brand(item) == SPWPN_NORMAL)
return;
brand = (int) SPWPN_CHAOS;
}
else
{
if (get_ammo_brand(item) == SPWPN_NORMAL)
return;
brand = (int) SPMSL_CHAOS;
}
if (is_random_artefact(item))
artefact_set_property(item, ARTP_BRAND, brand);
else
item.special = brand;
}
static void _xom_make_item(object_class_type base, int subtype, int power)
{
god_acting gdact(GOD_XOM);
int thing_created =
items(true, base, subtype, true, power, MAKE_ITEM_RANDOM_RACE,
0, 0, GOD_XOM);
if (feat_destroys_items(grd(you.pos())))
{
if (!silenced(you.pos()))
mprf(MSGCH_SOUND, feat_item_destruction_message(grd(you.pos())));
simple_god_message(" snickers.", GOD_XOM);
destroy_item(thing_created, true);
thing_created = NON_ITEM;
}
if (thing_created == NON_ITEM)
{
god_speaks(GOD_XOM, "\"No, never mind.\"");
return;
}
_try_brand_switch(thing_created);
static char gift_buf[100];
snprintf(gift_buf, sizeof(gift_buf), "god gift: %s",
mitm[thing_created].name(DESC_PLAIN).c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, gift_buf), true);
move_item_to_grid(&thing_created, you.pos());
mitm[thing_created].inscription = "god gift";
canned_msg(MSG_SOMETHING_APPEARS);
stop_running();
}
static void _xom_acquirement(object_class_type force_class)
{
god_acting gdact(GOD_XOM);
int item_index = NON_ITEM;
if (!acquirement(force_class, GOD_XOM, false, &item_index)
|| item_index == NON_ITEM)
{
god_speaks(GOD_XOM, "\"No, never mind.\"");
return;
}
_try_brand_switch(item_index);
static char gift_buf[100];
snprintf(gift_buf, sizeof(gift_buf), "god gift: %s",
mitm[item_index].name(DESC_PLAIN).c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, gift_buf), true);
stop_running();
}
static object_class_type _get_unrelated_wield_class(object_class_type ref)
{
object_class_type objtype = OBJ_WEAPONS;
if (ref == OBJ_WEAPONS)
{
if (one_chance_in(10))
objtype = OBJ_MISCELLANY;
else
objtype = OBJ_STAVES;
}
else if (ref == OBJ_STAVES)
{
if (one_chance_in(10))
objtype = OBJ_MISCELLANY;
else
objtype = OBJ_WEAPONS;
}
else
{
const int temp_rand = random2(3);
objtype = (temp_rand == 0) ? OBJ_WEAPONS :
(temp_rand == 1) ? OBJ_STAVES
: OBJ_MISCELLANY;
}
return (objtype);
}
static bool _xom_annoyance_gift(int power, bool debug = false)
{
god_acting gdact(GOD_XOM);
if (coinflip() && player_in_a_dangerous_place())
{
const item_def *weapon = you.weapon();
if (coinflip() && weapon && weapon->cursed())
{
if (debug)
return (true);
god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str());
if (coinflip())
_xom_make_item(weapon->base_type, weapon->sub_type, power * 3);
else
_xom_acquirement(weapon->base_type);
return (true);
}
const item_def *gloves = you.slot_item(EQ_GLOVES);
if (coinflip() && gloves && gloves->cursed())
{
if (debug)
return (true);
god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str());
_xom_make_item(OBJ_JEWELLERY, get_random_ring_type(), power * 3);
return (true);
};
const item_def *amulet = you.slot_item(EQ_AMULET);
if (coinflip() && amulet && amulet->cursed())
{
if (debug)
return (true);
god_speaks(GOD_XOM, _get_xom_speech("cursed gift").c_str());
_xom_make_item(OBJ_JEWELLERY, get_random_amulet_type(), power * 3);
return (true);
};
const item_def *left_ring = you.slot_item(EQ_LEFT_RING);
const item_def *right_ring = you.slot_item(EQ_RIGHT_RING);
if (coinflip() && ((left_ring && left_ring->cursed())
|| (right_ring && right_ring->cursed())))
{
if (debug)
return (true);
god_speaks(GOD_XOM, _get_xom_speech("ring gift").c_str());
_xom_make_item(OBJ_JEWELLERY, get_random_ring_type(), power * 3);
return (true);
}
if (one_chance_in(5) && weapon)
{
if (debug)
return (true);
god_speaks(GOD_XOM, _get_xom_speech("weapon gift").c_str());
const object_class_type objtype =
_get_unrelated_wield_class(weapon->base_type);
if (x_chance_in_y(power, 256))
_xom_acquirement(objtype);
else
_xom_make_item(objtype, OBJ_RANDOM, power * 3);
return (true);
}
}
const item_def *cloak = you.slot_item(EQ_CLOAK);
if (coinflip() && cloak && cloak->cursed())
{
god_speaks(GOD_XOM, _get_xom_speech("armour gift").c_str());
_xom_make_item(OBJ_ARMOUR,
one_chance_in(10) ? ARM_CLOAK :
get_random_body_armour_type(power * 2),
power * 3);
return (true);
}
return (false);
}
static int _xom_give_item(int power, bool debug = false)
{
if (_xom_annoyance_gift(power, debug))
return (XOM_GOOD_ANNOYANCE_GIFT);
if (!debug)
god_speaks(GOD_XOM, _get_xom_speech("general gift").c_str());
if (x_chance_in_y(power, 256))
{
if (debug)
return (XOM_GOOD_ACQUIREMENT);
const object_class_type types[] = {
OBJ_WEAPONS, OBJ_ARMOUR, OBJ_JEWELLERY, OBJ_BOOKS,
OBJ_STAVES, OBJ_WANDS, OBJ_MISCELLANY, OBJ_FOOD, OBJ_GOLD
};
god_acting gdact(GOD_XOM);
_xom_acquirement(RANDOM_ELEMENT(types));
}
else
{
if (debug)
return (XOM_GOOD_RANDOM_ITEM);
_xom_make_item(OBJ_RANDOM, OBJ_RANDOM, power * 3);
}
more();
return (XOM_GOOD_RANDOM_ITEM);
}
static bool _choose_mutatable_monster(const monsters* mon)
{
return (mon->alive() && mon->can_safely_mutate()
&& !mon->submerged());
}
static bool _is_chaos_upgradeable(const item_def &item,
const monsters* mon)
{
if (is_unrandom_artefact(item))
return (false);
if (item.flags & ISFLAG_SUMMONED)
return (false);
if (is_blessed_blade(item))
return (false);
if (item.orig_monnum < 0)
{
god_type iorig = static_cast<god_type>(-item.orig_monnum);
if (iorig > GOD_NO_GOD && iorig < NUM_GODS
&& (is_good_god(iorig) || iorig == GOD_BEOGH))
{
return (false);
}
}
if (item.base_type == OBJ_MISSILES)
{
if (item.sub_type == MI_LARGE_ROCK
|| item.sub_type == MI_THROWING_NET)
{
return (false);
}
if (get_ammo_brand(item) == SPMSL_NORMAL)
return (true);
}
else
{
if (is_range_weapon(item)
&& (mon->inv[MSLOT_MISSILE] == NON_ITEM
|| !has_launcher(mitm[mon->inv[MSLOT_MISSILE]])))
{
return (false);
}
if (get_weapon_brand(item) == SPWPN_NORMAL)
return (true);
}
return (false);
}
static bool _choose_chaos_upgrade(const monsters* mon)
{
if (!mon->alive() || mons_attitude(mon) != ATT_HOSTILE
|| mons_is_fleeing(mon) || mons_is_panicking(mon))
{
return (false);
}
if (mons_itemuse(mon) < MONUSE_STARTING_EQUIPMENT)
return (false);
if (mons_is_holy(mon) && !is_chaotic_god(mon->god))
return (false);
if (is_good_god(mon->god))
return (false);
if (mons_species(mon->type) == MONS_ORC
&& (mons_class_flag(mon->type, M_PRIEST) || coinflip()))
{
return (false);
}
mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE};
bool special_launcher = false;
for (int i = 0; i < 3; ++i)
{
const mon_inv_type slot = slots[i];
const int midx = mon->inv[slot];
if (midx == NON_ITEM)
continue;
const item_def &item(mitm[midx]);
if (is_chaotic_item(item))
return (false);
if (_is_chaos_upgradeable(item, mon))
{
if (item.base_type != OBJ_MISSILES)
return (true);
if (!special_launcher || !has_launcher(item))
return (true);
}
if (is_range_weapon(item))
{
int brand = get_weapon_brand(item);
if (brand == SPWPN_FLAME || brand == SPWPN_FROST
|| brand == SPWPN_VENOM)
{
special_launcher = true;
}
}
}
return (false);
}
static void _do_chaos_upgrade(item_def &item, const monsters* mon)
{
ASSERT(item.base_type == OBJ_MISSILES
|| item.base_type == OBJ_WEAPONS);
ASSERT(!is_unrandom_artefact(item));
bool seen = false;
if (mon && you.can_see(mon) && item.base_type == OBJ_WEAPONS)
{
seen = true;
description_level_type desc = mons_friendly(mon) ? DESC_CAP_YOUR :
DESC_CAP_THE;
std::string msg = apostrophise(mon->name(desc));
msg += " ";
msg += item.name(DESC_PLAIN, false, false, false);
msg += " is briefly surrounded by a scintillating aura of "
"random colours.";
mpr(msg.c_str());
}
const int brand = (item.base_type == OBJ_WEAPONS) ? (int) SPWPN_CHAOS
: (int) SPMSL_CHAOS;
if (is_random_artefact(item))
{
artefact_set_property(item, ARTP_BRAND, brand);
if (seen)
artefact_wpn_learn_prop(item, ARTP_BRAND);
}
else
{
item.special = brand;
if (seen)
set_ident_flags(item, ISFLAG_KNOW_TYPE);
if (!(item.flags & ISFLAG_COSMETIC_MASK))
item.flags |= ISFLAG_GLOWING;
item.plus += random2(5);
item.plus2 += random2(5);
}
}
static monster_type _xom_random_demon(int sever, bool use_greater_demons = true)
{
const int roll = random2(1000 - (MAX_PIETY - sever) * 3);
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "_xom_random_demon(); sever = %d, roll: %d",
sever, roll);
#endif
const demon_class_type dct =
(roll >= 850) ? DEMON_GREATER :
(roll >= 340) ? DEMON_COMMON
: DEMON_LESSER;
monster_type demon = MONS_PROGRAM_BUG;
if (dct == DEMON_GREATER && coinflip())
demon = summon_any_holy_being(HOLY_BEING_WARRIOR);
else
{
const demon_class_type dct2 =
(!use_greater_demons && dct == DEMON_GREATER) ? DEMON_COMMON : dct;
if (dct2 == DEMON_COMMON && one_chance_in(10))
demon = MONS_CHAOS_SPAWN;
else
demon = summon_any_demon(dct2);
}
return (demon);
}
static bool _feat_is_deadly(dungeon_feature_type feat)
{
if (you.airborne())
return (false);
return (feat == DNGN_LAVA || feat == DNGN_DEEP_WATER && !you.can_swim());
}
static bool _player_is_dead()
{
return (you.hp <= 0 || you.strength <= 0 || you.dex <= 0 || you.intel <= 0
|| _feat_is_deadly(grd(you.pos()))
|| you.did_escape_death());
}
static int _xom_do_potion(bool debug = false)
{
if (debug)
return (XOM_GOOD_POTION);
potion_type pot = POT_HEALING;
while (true)
{
pot = static_cast<potion_type>(
random_choose(POT_HEALING, POT_HEAL_WOUNDS, POT_MAGIC,
POT_SPEED, POT_MIGHT, POT_AGILITY, POT_BRILLIANCE,
POT_INVISIBILITY, POT_BERSERK_RAGE,
POT_EXPERIENCE, -1));
if (pot == POT_EXPERIENCE && !one_chance_in(6))
pot = POT_BERSERK_RAGE;
bool has_effect = true;
switch (pot)
{
case POT_HEALING:
if (you.rotting || you.disease || you.duration[DUR_CONF]
|| you.duration[DUR_POISONING])
{
break;
}
case POT_HEAL_WOUNDS:
if (you.hp == you.hp_max && player_rotted() == 0)
has_effect = false;
break;
case POT_MAGIC:
if (you.magic_points == you.max_magic_points)
has_effect = false;
break;
case POT_BERSERK_RAGE:
if (!you.can_go_berserk(false))
has_effect = false;
break;
case POT_EXPERIENCE:
if (you.experience_level == 27)
has_effect = false;
break;
default:
break;
}
if (has_effect)
break;
}
god_speaks(GOD_XOM, _get_xom_speech("potion effect").c_str());
if (pot == POT_BERSERK_RAGE)
you.berserk_penalty = NO_BERSERK_PENALTY;
std::string potion_msg = "potion effect ";
switch (pot)
{
case POT_HEALING: potion_msg += "(healing)"; break;
case POT_HEAL_WOUNDS: potion_msg += "(heal wounds)"; break;
case POT_MAGIC: potion_msg += "(magic)"; break;
case POT_SPEED: potion_msg += "(speed)"; break;
case POT_MIGHT: potion_msg += "(might)"; break;
case POT_AGILITY: potion_msg += "(agility)"; break;
case POT_BRILLIANCE: potion_msg += "(brilliance)"; break;
case POT_INVISIBILITY: potion_msg += "(invisibility)"; break;
case POT_BERSERK_RAGE: potion_msg += "(berserk)"; break;
case POT_EXPERIENCE: potion_msg += "(experience)"; break;
default: potion_msg += "(other)"; break;
}
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, potion_msg.c_str()), true);
potion_effect(pot, 150);
return (XOM_GOOD_POTION);
}
static int _xom_confuse_monsters(int sever, bool debug = false)
{
bool rc = false;
monsters *monster;
for (unsigned i = 0; i < MAX_MONSTERS; ++i)
{
monster = &menv[i];
if (monster->type == MONS_NO_MONSTER || !mons_near(monster)
|| mons_wont_attack(monster)
|| !mons_class_is_confusable(monster->type)
|| one_chance_in(20))
{
continue;
}
if (debug)
return (XOM_GOOD_CONFUSION);
if (monster->add_ench(mon_enchant(ENCH_CONFUSION, 0,
KC_FRIENDLY, random2(sever))))
{
if (!rc)
god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str());
simple_monster_message(monster, " looks rather confused.");
rc = true;
}
}
if (rc)
{
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "confuse monster(s)"),
true);
return (XOM_GOOD_CONFUSION);
}
return (XOM_DID_NOTHING);
}
static int _xom_send_allies(int sever, bool debug = false)
{
if (debug)
return (XOM_GOOD_ALLIES);
int numdemons = sever;
for (int i = 0; i < 3; i++)
numdemons = random2(numdemons + 1);
numdemons = std::min(numdemons + 2, 16);
int numdifferent = 0;
int hostiletype = random_choose_weighted(3, 0, 4, 1, 4, 2, 1, 3, 0);
std::vector<bool> is_demonic(numdemons, false);
std::vector<int> summons(numdemons);
int num_actually_summoned = 0;
for (int i = 0; i < numdemons; ++i)
{
monster_type mon_type = _xom_random_demon(sever);
summons[i] =
create_monster(
mgen_data(mon_type, BEH_FRIENDLY,
3, MON_SUMM_AID,
you.pos(), MHITYOU,
MG_FORCE_BEH, GOD_XOM));
if (summons[i] != -1)
{
num_actually_summoned++;
is_demonic[i] = (mons_class_holiness(mon_type) == MH_DEMONIC);
if (!is_demonic[i])
numdifferent++;
}
}
if (num_actually_summoned)
{
const bool only_holy = (numdifferent == num_actually_summoned);
const bool only_demonic = (numdifferent == 0);
if (only_holy)
{
god_speaks(GOD_XOM,
_get_xom_speech("multiple holy summons").c_str());
}
else if (only_demonic)
{
god_speaks(GOD_XOM,
_get_xom_speech("multiple summons").c_str());
}
else
{
god_speaks(GOD_XOM,
_get_xom_speech("multiple mixed summons").c_str());
}
if (only_holy && one_chance_in(4))
hostiletype = 2;
else if (only_demonic)
hostiletype = 0;
for (int i = 0; i < numdemons; ++i)
{
if (summons[i] == -1)
continue;
monsters *mon = &menv[summons[i]];
if (hostiletype != 0)
{
if (hostiletype == 3
|| (is_demonic[i] && hostiletype == 1)
|| (!is_demonic[i] && hostiletype == 2))
{
mon->attitude = ATT_HOSTILE;
behaviour_event(mon, ME_ALERT, MHITYOU);
}
}
player_angers_monster(mon);
}
static char summ_buf[80];
snprintf(summ_buf, sizeof(summ_buf), "summons %d %s%s%s",
num_actually_summoned,
hostiletype == 0 ? "friendly " :
hostiletype == 3 ? "hostile " : "",
only_demonic ? "demon" : "monster",
num_actually_summoned > 1 ? "s" : "");
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true);
return (XOM_GOOD_ALLIES);
}
return (XOM_DID_NOTHING);
}
static int _xom_send_one_ally(int sever, bool debug = false)
{
if (debug)
return (XOM_GOOD_SINGLE_ALLY);
const monster_type mon_type = _xom_random_demon(sever);
const bool is_demonic = (mons_class_holiness(mon_type) == MH_DEMONIC);
bool different = !is_demonic;
beh_type beha = BEH_FRIENDLY;
if (different && one_chance_in(4))
beha = BEH_HOSTILE;
const int summons =
create_monster(
mgen_data(mon_type, beha, 6, MON_SUMM_AID, you.pos(), MHITYOU,
MG_FORCE_BEH, GOD_XOM));
if (summons != -1)
{
if (different)
god_speaks(GOD_XOM, _get_xom_speech("single holy summon").c_str());
else
god_speaks(GOD_XOM, _get_xom_speech("single summon").c_str());
player_angers_monster(&menv[summons]);
static char summ_buf[80];
snprintf(summ_buf, sizeof(summ_buf), "summons %s %s",
beha == BEH_FRIENDLY ? "friendly" : "hostile",
menv[summons].name(DESC_PLAIN).c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true);
return (XOM_GOOD_SINGLE_ALLY);
}
return (XOM_DID_NOTHING);
}
static int _xom_polymorph_nearby_monster(bool helpful, bool debug = false)
{
if (there_are_monsters_nearby(false, false))
{
monsters *mon = choose_random_nearby_monster(0,
_choose_mutatable_monster);
if (mon)
{
if (debug)
return (helpful ? XOM_GOOD_POLYMORPH : XOM_BAD_POLYMORPH);
const char* lookup = (helpful ? "good monster polymorph"
: "bad monster polymorph");
god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str());
bool see_old = you.can_see(mon);
std::string old_name = mon->full_name(DESC_PLAIN);
if (one_chance_in(8)
&& !mons_is_ghost_demon(mon->type)
&& !mons_is_shapeshifter(mon)
&& mon->holiness() == MH_NATURAL)
{
mon->add_ench(one_chance_in(3) ? ENCH_GLOWING_SHAPESHIFTER
: ENCH_SHAPESHIFTER);
}
const bool powerup = !(mons_wont_attack(mon) ^ helpful);
monster_polymorph(mon, RANDOM_MONSTER,
powerup ? PPT_MORE : PPT_LESS);
bool see_new = you.can_see(mon);
if (see_old || see_new)
{
std::string new_name = mon->full_name(DESC_PLAIN);
if (!see_old)
old_name = "something unseen";
else if (!see_new)
new_name = "something unseen";
static char poly_buf[120];
snprintf(poly_buf, sizeof(poly_buf), "polymorph %s -> %s",
old_name.c_str(), new_name.c_str());
std::string poly = poly_buf;
#ifdef NOTE_DEBUG_XOM
poly += " (";
poly += (powerup ? "upgrade" : "downgrade");
poly += ")";
#endif
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, poly.c_str()),
true);
}
return (helpful ? XOM_GOOD_POLYMORPH : XOM_BAD_POLYMORPH);
}
}
return (XOM_DID_NOTHING);
}
static void _confuse_monster(monsters mons, int sever)
{
if (!mons_class_is_confusable(mons.type))
return;
const bool was_confused = mons.confused();
if (mons.add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_FRIENDLY,
random2(sever))))
{
if (was_confused)
simple_monster_message(&mons, " looks rather more confused.");
else
simple_monster_message(&mons, " looks rather confused.");
}
}
static bool _swap_monsters(monsters *m1, monsters *m2)
{
monsters& mon1(*m1);
monsters& mon2(*m2);
const bool mon1_caught = mons_is_caught(&mon1);
const bool mon2_caught = mons_is_caught(&mon2);
const coord_def mon1_pos = mon1.pos();
const coord_def mon2_pos = mon2.pos();
if (!mon2.is_habitable(mon1_pos) || !mon1.is_habitable(mon2_pos))
return (false);
mon1.del_ench(ENCH_SUBMERGED);
mon2.del_ench(ENCH_SUBMERGED);
mgrd(mon1_pos) = mon2.mindex();
mon1.moveto(mon2_pos);
mgrd(mon2_pos) = mon1.mindex();
mon2.moveto(mon1_pos);
if (mon1_caught && !mon2_caught)
{
check_net_will_hold_monster(&mon2);
mon1.del_ench(ENCH_HELD, true);
}
else if (mon2_caught && !mon1_caught)
{
check_net_will_hold_monster(&mon1);
mon2.del_ench(ENCH_HELD, true);
}
return (true);
}
static bool _art_is_safe(item_def item)
{
int prop_str = artefact_wpn_property(item, ARTP_STRENGTH);
int prop_int = artefact_wpn_property(item, ARTP_INTELLIGENCE);
int prop_dex = artefact_wpn_property(item, ARTP_DEXTERITY);
return (prop_str >= 0 && prop_int >= 0 && prop_dex >= 0);
}
static int _xom_swap_weapons(bool debug = false)
{
if (player_stair_delay())
return (XOM_DID_NOTHING);
item_def *wpn = you.weapon();
if (!wpn)
return (XOM_DID_NOTHING);
if (you.duration[DUR_BERSERKER]
|| wpn->base_type != OBJ_WEAPONS
|| get_weapon_brand(*wpn) == SPWPN_DISTORTION
|| !safe_to_remove_or_wear(*wpn, true, true))
{
return (XOM_DID_NOTHING);
}
std::vector<monsters *> mons_wpn;
for (unsigned i = 0; i < MAX_MONSTERS; ++i)
{
monsters* m = &menv[i];
if (!m->alive())
continue;
if (!see_cell(m->pos()))
continue;
if (!wpn || mons_wont_attack(m) || mons_is_summoned(m)
|| mons_itemuse(m) < MONUSE_STARTING_EQUIPMENT
|| (m->flags & MF_HARD_RESET))
{
continue;
}
const int mweap = m->inv[MSLOT_WEAPON];
if (mweap == NON_ITEM)
continue;
const item_def weapon = mitm[mweap];
if (weapon.base_type == OBJ_WEAPONS
&& !(weapon.flags & ISFLAG_SUMMONED)
&& you.can_wield(weapon, true) && m->can_wield(*wpn, true)
&& !get_weapon_brand(weapon) != SPWPN_DISTORTION
&& (!is_artefact(weapon) || _art_is_safe(weapon)))
{
mons_wpn.push_back(m);
}
}
if (mons_wpn.empty())
return (XOM_DID_NOTHING);
if (debug)
return (XOM_BAD_SWAP_WEAPONS);
god_speaks(GOD_XOM, _get_xom_speech("swap weapons").c_str());
const int num_mons = mons_wpn.size();
monsters *mon = mons_wpn[random2(num_mons)];
int monwpn = mon->inv[MSLOT_WEAPON];
int mywpn = you.equip[EQ_WEAPON];
ASSERT (monwpn != NON_ITEM);
ASSERT (mywpn != -1);
unwield_item();
item_def &myweapon = you.inv[mywpn];
int index = get_item_slot(10);
if (index == NON_ITEM)
return (XOM_DID_NOTHING);
mon->unequip(*(mon->mslot_item(MSLOT_WEAPON)), MSLOT_WEAPON, 0, true);
mon->inv[MSLOT_WEAPON] = NON_ITEM;
mitm[index] = myweapon;
unwind_var<int> save_speedinc(mon->speed_increment);
if (!mon->pickup_item(mitm[index], false, true))
{
mpr("Monster wouldn't take item.", MSGCH_ERROR);
mon->inv[MSLOT_WEAPON] = monwpn;
mon->equip(mitm[monwpn], MSLOT_WEAPON, 0);
unlink_item(index);
destroy_item(myweapon);
return (XOM_DID_NOTHING);
}
mitm[index].flags |= ISFLAG_THROWN;
mprf("%s wields %s!",
mon->name(DESC_CAP_THE).c_str(),
myweapon.name(DESC_NOCAP_YOUR).c_str());
mon->equip(myweapon, MSLOT_WEAPON, 0);
dec_inv_item_quantity(mywpn, myweapon.quantity);
mitm[monwpn].pos.reset();
mitm[monwpn].link = NON_ITEM;
int freeslot = find_free_slot(mitm[monwpn]);
if (freeslot < 0 || freeslot >= ENDOFPACK
|| is_valid_item(you.inv[freeslot]))
{
return (XOM_DID_NOTHING);
}
item_def &myitem = you.inv[freeslot];
myitem = mitm[monwpn];
myitem.link = freeslot;
myitem.pos.set(-1, -1);
myitem.flags &= ~(ISFLAG_DROPPED_BY_ALLY);
if (!myitem.slot)
myitem.slot = index_to_letter(myitem.link);
origin_set_monster(myitem, mon);
note_inscribe_item(myitem);
dec_mitm_item_quantity(monwpn, myitem.quantity);
you.m_quiver->on_inv_quantity_changed(freeslot, myitem.quantity);
burden_change();
mprf("You wield %s %s!",
mon->name(DESC_NOCAP_ITS).c_str(),
you.inv[freeslot].name(DESC_PLAIN).c_str());
you.equip[EQ_WEAPON] = freeslot;
wield_effects(freeslot, true);
you.wield_change = true;
you.m_quiver->on_weapon_changed();
return (XOM_BAD_SWAP_WEAPONS);
}
static int _xom_rearrange_pieces(int sever, bool debug = false)
{
if (player_stair_delay())
return (XOM_DID_NOTHING);
std::vector<monsters *> mons;
for (unsigned i = 0; i < MAX_MONSTERS; ++i)
{
monsters* m = &menv[i];
if (!m->alive())
continue;
if (!see_cell(m->pos()))
continue;
mons.push_back(m);
}
if (mons.empty())
return (XOM_DID_NOTHING);
if (debug)
return (XOM_GOOD_SWAP_MONSTERS);
god_speaks(GOD_XOM, _get_xom_speech("rearrange the pieces").c_str());
const int num_mons = mons.size();
monsters *mon = mons[random2(num_mons)];
swap_with_monster(mon);
if (coinflip())
_confuse_monster(*mon, sever);
if (num_mons > 1 && x_chance_in_y(sever, 70))
{
bool did_message = false;
const int max_repeats = std::min(num_mons / 2, 8);
const int repeats = std::min(random2(sever / 10) + 1, max_repeats);
for (int i = 0; i < repeats; ++i)
{
const int mon1 = random2(num_mons);
int mon2 = mon1;
while (mon1 == mon2)
mon2 = random2(num_mons);
if (_swap_monsters(mons[mon1], mons[mon2]))
{
if (!did_message)
{
mpr("Some monsters swap places.");
did_message = true;
}
if (one_chance_in(3))
_confuse_monster(*mons[mon1], sever);
if (one_chance_in(3))
_confuse_monster(*mons[mon2], sever);
}
}
}
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "swap monsters"), true);
return (XOM_GOOD_SWAP_MONSTERS);
}
static int _xom_animate_monster_weapon(int sever, bool debug = false)
{
std::vector<monsters *> mons_wpn;
for (unsigned i = 0; i < MAX_MONSTERS; ++i)
{
monsters* m = &menv[i];
if (!m->alive())
continue;
if (!see_cell(m->pos()))
continue;
if (mons_wont_attack(m) || mons_is_summoned(m)
|| mons_itemuse(m) < MONUSE_STARTING_EQUIPMENT
|| (m->flags & MF_HARD_RESET))
{
continue;
}
const int mweap = m->inv[MSLOT_WEAPON];
if (mweap == NON_ITEM)
continue;
const item_def weapon = mitm[mweap];
if (weapon.base_type == OBJ_WEAPONS
&& !(weapon.flags & ISFLAG_SUMMONED)
&& weapon.quantity == 1
&& !is_range_weapon(weapon)
&& !is_special_unrandom_artefact(weapon)
&& !get_weapon_brand(weapon) != SPWPN_DISTORTION)
{
mons_wpn.push_back(m);
}
}
if (mons_wpn.empty())
return (XOM_DID_NOTHING);
if (debug)
return (XOM_GOOD_ANIMATE_MON_WPN);
god_speaks(GOD_XOM, _get_xom_speech("animate monster weapon").c_str());
const int num_mons = mons_wpn.size();
monsters *mon = mons_wpn[random2(num_mons)];
const int wpn = mon->inv[MSLOT_WEAPON];
ASSERT(wpn != NON_ITEM);
const int dur = std::min(2 + (random2(sever) / 5), 6);
const int monster = create_monster(
mgen_data(MONS_DANCING_WEAPON, BEH_FRIENDLY,
dur, SPELL_TUKIMAS_DANCE,
mon->pos(), mon->mindex(), 0, GOD_XOM));
if (monster == -1)
return (XOM_DID_NOTHING);
mon->unequip(*(mon->mslot_item(MSLOT_WEAPON)), MSLOT_WEAPON, 0, true);
mon->inv[MSLOT_WEAPON] = NON_ITEM;
mprf("%s %s dances into the air!",
apostrophise(mon->name(DESC_CAP_THE)).c_str(),
mitm[wpn].name(DESC_PLAIN).c_str());
destroy_item(menv[monster].inv[MSLOT_WEAPON]);
menv[monster].inv[MSLOT_WEAPON] = wpn;
mitm[wpn].set_holding_monster(monster);
menv[monster].colour = mitm[wpn].colour;
return (XOM_GOOD_ANIMATE_MON_WPN);
}
static int _xom_give_mutations(bool good, bool debug = false)
{
bool rc = false;
if (you.can_safely_mutate())
{
if (debug)
return (good ? XOM_GOOD_MUTATION : XOM_BAD_MUTATION);
const char* lookup = (good ? "good mutations" : "random mutations");
god_speaks(GOD_XOM, _get_xom_speech(lookup).c_str());
const int num_tries = random2(4) + 1;
static char mut_buf[80];
snprintf(mut_buf, sizeof(mut_buf), "give %smutation%s",
#ifdef NOTE_DEBUG_XOM
good ? "good " : "random ",
#else
"",
#endif
num_tries > 1 ? "s" : "");
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, mut_buf), true);
mpr("Your body is suffused with distortional energy.");
set_hp(1 + random2(you.hp), false);
deflate_hp(you.hp_max / 2, true);
bool failMsg = true;
for (int i = num_tries; i > 0; --i)
{
if (mutate(good ? RANDOM_GOOD_MUTATION : RANDOM_XOM_MUTATION,
failMsg, false, true, false, false,
good || !_xom_feels_nasty()))
{
rc = true;
}
else
failMsg = false;
}
}
if (rc)
return (good ? XOM_GOOD_MUTATION : XOM_BAD_MUTATION);
return (XOM_DID_NOTHING);
}
static int _xom_send_major_ally(int sever, bool debug = false)
{
if (debug)
return (XOM_GOOD_MAJOR_ALLY);
const monster_type mon_type = _xom_random_demon(sever);
const bool is_demonic = (mons_class_holiness(mon_type) == MH_DEMONIC);
beh_type beha = BEH_FRIENDLY;
if (!is_demonic && one_chance_in(4))
beha = BEH_HOSTILE;
const int summons =
create_monster(
mgen_data(_xom_random_demon(sever, one_chance_in(8)), beha,
0, 0, you.pos(), MHITYOU, MG_FORCE_BEH, GOD_XOM));
if (summons != -1)
{
if (is_demonic)
{
god_speaks(GOD_XOM,
_get_xom_speech("single major demon summon").c_str());
}
else
{
god_speaks(GOD_XOM,
_get_xom_speech("single major holy summon").c_str());
}
player_angers_monster(&menv[summons]);
static char summ_buf[80];
snprintf(summ_buf, sizeof(summ_buf), "sends permanent %s %s",
beha == BEH_FRIENDLY ? "friendly" : "hostile",
menv[summons].name(DESC_PLAIN).c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true);
return (XOM_GOOD_MAJOR_ALLY);
}
return (XOM_DID_NOTHING);
}
static int _xom_throw_divine_lightning(bool debug = false)
{
if (!player_in_a_dangerous_place())
return (XOM_DID_NOTHING);
bool found_hostile = false;
for (radius_iterator ri(you.pos(), 2, true, true, true); ri; ++ri)
{
if (monsters* mon = monster_at(*ri))
{
if (!mons_wont_attack(mon))
{
found_hostile = true;
break;
}
}
}
if (!found_hostile)
return (XOM_DID_NOTHING);
if (debug)
return (XOM_GOOD_LIGHTNING);
bool protection = false;
if (you.hp <= random2(201))
{
you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 1;
protection = true;
}
god_speaks(GOD_XOM, "The area is suffused with divine lightning!");
bolt beam;
beam.flavour = BEAM_ELECTRICITY;
beam.type = dchar_glyph(DCHAR_FIRED_BURST);
beam.damage = dice_def(3, 30);
beam.target = you.pos();
beam.name = "blast of lightning";
beam.colour = LIGHTCYAN;
beam.thrower = KILL_MISC;
beam.beam_source = NON_MONSTER;
beam.aux_source = "Xom's lightning strike";
beam.ex_size = 2;
beam.is_explosion = true;
beam.explode();
if (you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION])
{
mpr("Your divine protection wanes.");
you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 0;
}
if (you.escaped_death_cause == KILLED_BY_WILD_MAGIC
&& you.escaped_death_aux == "Xom's lightning strike")
{
you.hp = 1;
you.reset_escaped_death();
}
static char lightning_buf[80];
snprintf(lightning_buf, sizeof(lightning_buf),
"divine lightning%s", protection ? " (protected)" : "");
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, lightning_buf), true);
return (XOM_GOOD_LIGHTNING);
}
static int _xom_change_scenery(bool debug = false)
{
std::vector<coord_def> candidates;
for (radius_iterator ri(you.pos(), LOS_RADIUS, false, true); ri; ++ri)
{
if (!in_bounds(*ri) || !see_cell(*ri))
continue;
dungeon_feature_type feat = grd(*ri);
if (feat >= DNGN_FOUNTAIN_BLUE && feat <= DNGN_DRY_FOUNTAIN_BLOOD)
candidates.push_back(*ri);
else if (feat >= DNGN_CLOSED_DOOR && feat <= DNGN_SECRET_DOOR
|| feat == DNGN_OPEN_DOOR && !actor_at(*ri)
&& igrd(*ri) == NON_ITEM)
{
candidates.push_back(*ri);
}
}
const std::string speech = _get_xom_speech("scenery");
if (candidates.empty())
{
if (!one_chance_in(8))
return (XOM_DID_NOTHING);
coord_def place;
bool success = false;
const int max_altars = std::max(1, random2(random2(14)));
for (int tries = max_altars; tries > 0; --tries)
{
if ((random_near_space(you.pos(), place)
|| random_near_space(you.pos(), place, true))
&& grd(place) == DNGN_FLOOR && see_cell(place))
{
if (debug)
return (XOM_GOOD_SCENERY);
grd(place) = DNGN_ALTAR_XOM;
success = true;
}
}
if (success)
{
god_speaks(GOD_XOM, speech.c_str());
return (XOM_GOOD_SCENERY);
}
return (XOM_DID_NOTHING);
}
if (debug)
return (XOM_GOOD_SCENERY);
const int fountain_diff = (DNGN_DRY_FOUNTAIN_BLUE - DNGN_FOUNTAIN_BLUE);
std::random_shuffle(candidates.begin(), candidates.end());
int doors_open = 0;
int doors_close = 0;
int fountains_flow = 0;
int fountains_blood = 0;
for (unsigned int i = 0; i < candidates.size(); ++i)
{
coord_def pos = candidates[i];
switch (grd(pos))
{
case DNGN_CLOSED_DOOR:
case DNGN_DETECTED_SECRET_DOOR:
case DNGN_SECRET_DOOR:
grd(pos) = DNGN_OPEN_DOOR;
doors_open++;
break;
case DNGN_OPEN_DOOR:
grd(pos) = DNGN_CLOSED_DOOR;
doors_close++;
break;
case DNGN_DRY_FOUNTAIN_BLUE:
case DNGN_DRY_FOUNTAIN_SPARKLING:
case DNGN_DRY_FOUNTAIN_BLOOD:
{
if (x_chance_in_y(fountains_flow, 5))
continue;
grd(pos) = (dungeon_feature_type) (grd(pos) - fountain_diff);
fountains_flow++;
break;
}
case DNGN_FOUNTAIN_BLUE:
if (x_chance_in_y(fountains_blood, 3))
continue;
grd(pos) = DNGN_FOUNTAIN_BLOOD;
fountains_blood++;
break;
default:
break;
}
}
if (!doors_open && !doors_close && !fountains_flow && !fountains_blood)
return (XOM_DID_NOTHING);
god_speaks(GOD_XOM, speech.c_str());
std::vector<std::string> effects;
if (doors_open > 0)
{
snprintf(info, INFO_SIZE, "%s door%s burst%s open",
doors_open == 1 ? "A" :
doors_open == 2 ? "Two"
: "Several",
doors_open == 1 ? "" : "s",
doors_open == 1 ? "s" : "");
effects.push_back(info);
}
if (doors_close > 0)
{
snprintf(info, INFO_SIZE, "%s%s door%s slam%s shut",
doors_close == 1 ? "a" :
doors_close == 2 ? "two"
: "several",
doors_open > 0 ? (doors_close == 1 ? "nother" : " other")
: "",
doors_close == 1 ? "" : "s",
doors_close == 1 ? "s" : "");
std::string closed = info;
if (effects.empty())
closed = uppercase_first(closed);
effects.push_back(closed);
}
if (!effects.empty())
{
mprf("%s!",
comma_separated_line(effects.begin(), effects.end(),
", and ").c_str());
effects.clear();
}
if (fountains_flow > 0)
{
snprintf(info, INFO_SIZE,
"%s fountain%s start%s reflowing",
fountains_flow == 1 ? "A" : "Some",
fountains_flow == 1 ? "" : "s",
fountains_flow == 1 ? "s" : "");
effects.push_back(info);
}
if (fountains_blood > 0)
{
snprintf(info, INFO_SIZE,
"%s%s fountain%s start%s gushing blood",
fountains_blood == 1 ? "a" : "some",
fountains_flow > 0 ? (fountains_blood == 1 ? "nother"
: " other")
: "",
fountains_blood == 1 ? "" : "s",
fountains_blood == 1 ? "s" : "");
std::string fountains = info;
if (effects.empty())
fountains = uppercase_first(fountains);
effects.push_back(fountains);
}
if (!effects.empty())
{
mprf("%s!",
comma_separated_line(effects.begin(), effects.end(),
", and ").c_str());
}
return (XOM_GOOD_SCENERY);
}
static int _xom_is_good(int sever, int tension, bool debug = false)
{
int done = XOM_DID_NOTHING;
if (_player_is_dead())
return (XOM_PLAYER_DEAD);
god_acting gdact(GOD_XOM);
if (tension > random2(3) && x_chance_in_y(2, sever))
done = _xom_do_potion(debug);
else if (x_chance_in_y(3, sever))
{
if (tension > 0 || one_chance_in(3))
done = _xom_makes_you_cast_random_spell(sever, tension, debug);
}
else if (tension > 0 && x_chance_in_y(4, sever))
done = _xom_confuse_monsters(sever, debug);
else if (tension > random2(5) && x_chance_in_y(5, sever))
done = _xom_send_one_ally(sever, debug);
else if (tension < random2(5) && x_chance_in_y(6, sever))
done = _xom_change_scenery(debug);
else if (x_chance_in_y(7, sever))
done = _xom_give_item(sever, debug);
else if (tension > random2(10) && x_chance_in_y(8, sever))
done = _xom_send_allies(sever, debug);
else if (tension > random2(8) && x_chance_in_y(9, sever))
done = _xom_animate_monster_weapon(sever, debug);
else if (x_chance_in_y(10, sever))
done = _xom_polymorph_nearby_monster(true, debug);
else if (tension > 0 && x_chance_in_y(11, sever))
done = _xom_rearrange_pieces(sever, debug);
else if (random2(tension) < 15 && x_chance_in_y(12, sever))
done = _xom_give_item(sever, debug);
else if (x_chance_in_y(13, sever) && you.level_type != LEVEL_ABYSS)
{
if (!_teleportation_check())
return (XOM_DID_NOTHING);
const int explored = _exploration_estimate(true);
if (explored >= 80 && x_chance_in_y(explored, 120))
return (XOM_DID_NOTHING);
if (debug)
return (XOM_GOOD_TELEPORT);
god_speaks(GOD_XOM, _get_xom_speech("teleportation journey").c_str());
int count = 0;
do
{
count++;
you_teleport_now(false);
more();
if (one_chance_in(10) || count >= 7 + random2(5))
break;
}
while (x_chance_in_y(3, 4) || player_in_a_dangerous_place());
maybe_update_stashes();
static char tele_buf[80];
snprintf(tele_buf, sizeof(tele_buf),
"%d-stop teleportation journey%s", count,
#ifdef NOTE_DEBUG_XOM
player_in_a_dangerous_place() ? " (dangerous)" : #endif
"");
take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, tele_buf), true);
done = XOM_GOOD_TELEPORT;
}
else if (random2(tension) < 5 && x_chance_in_y(14, sever))
{
if (debug)
return (XOM_GOOD_VITRIFY);
if (vitrify_area(random2avg(sever/4, 2) + 1))
{
god_speaks(GOD_XOM, _get_xom_speech("vitrification").c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, tension,
"vitrification"), true);
done = XOM_GOOD_VITRIFY;
}
}
else if (random2(tension) < 5 && x_chance_in_y(15, sever)
&& x_chance_in_y(16, how_mutated()))
{
done = _xom_give_mutations(true, debug);
}
else if (tension > random2(15) && x_chance_in_y(16, sever))
done = _xom_send_major_ally(sever, debug);
else if (tension > 0 && x_chance_in_y(17, sever))
done = _xom_throw_divine_lightning(debug);
return (done);
}
static bool _could_wear_eq(equipment_type eq)
{
if (!you_tran_can_wear(eq, true))
return (false);
return (you.slot_item(eq) == NULL);
}
static item_def* _tran_get_eq(equipment_type eq)
{
if (you_tran_can_wear(eq, true))
return (you.slot_item(eq));
return (NULL);
}
static void _get_in_view(FixedVector<bool, NUM_REAL_FEATURES>& in_view)
{
in_view.init(false);
for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri)
in_view[grd(*ri)] = true;
}
static void _xom_zero_miscast()
{
std::vector<std::string> messages;
std::vector<std::string> priority;
std::vector<int> inv_items;
for (int i = 0; i < ENDOFPACK; ++i)
{
const item_def &item(you.inv[i]);
if (is_valid_item(item) && !item_is_equipped(item)
&& !item_is_critical(item))
{
inv_items.push_back(i);
}
}
messages.push_back("Nothing appears to happen... Ominous!");
FixedVector<bool, NUM_REAL_FEATURES> in_view;
_get_in_view(in_view);
if (in_view[DNGN_LAVA])
messages.push_back("The lava spits out sparks!");
if (in_view[DNGN_SHALLOW_WATER] || in_view[DNGN_DEEP_WATER])
{
messages.push_back("The water briefly bubbles.");
messages.push_back("The water briefly swirls.");
messages.push_back("The water briefly glows.");
}
if (in_view[DNGN_DEEP_WATER])
{
messages.push_back("From the corner of your eye you spot something "
"lurking in the deep water.");
}
if (in_view[DNGN_ORCISH_IDOL])
{
if (you.species == SP_HILL_ORC)
priority.push_back("The idol of Beogh turns to glare at you.");
else
priority.push_back("The orcish idol turns to glare at you.");
}
if (in_view[DNGN_GRANITE_STATUE])
priority.push_back("The granite statue turns to stare at you.");
if (in_view[DNGN_WAX_WALL])
priority.push_back("The wax wall pulsates ominously.");
if (in_view[DNGN_CLEAR_ROCK_WALL] || in_view[DNGN_CLEAR_STONE_WALL]
|| in_view[DNGN_CLEAR_PERMAROCK_WALL])
{
messages.push_back("Dim shapes swim through the translucent wall.");
}
if (in_view[DNGN_GREEN_CRYSTAL_WALL])
messages.push_back("Dim shapes swim through the green crystal wall.");
if (in_view[DNGN_METAL_WALL])
{
messages.push_back("Tendrils of electricity crawl over the metal "
"wall!");
}
if (in_view[DNGN_FOUNTAIN_BLUE] || in_view[DNGN_FOUNTAIN_SPARKLING])
{
priority.push_back("The water in the fountain briefly bubbles.");
priority.push_back("The water in the fountain briefly swirls.");
priority.push_back("The water in the fountain briefly glows.");
}
if (in_view[DNGN_DRY_FOUNTAIN_BLUE]
|| in_view[DNGN_DRY_FOUNTAIN_SPARKLING]
|| in_view[DNGN_PERMADRY_FOUNTAIN])
{
priority.push_back("Water briefly sprays from the dry fountain.");
priority.push_back("Dust puffs up from the dry fountain.");
}
if (in_view[DNGN_STONE_ARCH])
priority.push_back("The stone arch briefly shows a sunny meadow on "
"the other side.");
const dungeon_feature_type feat = grd(you.pos());
if (!feat_is_solid(feat) && feat_stair_direction(feat) == CMD_NO_CMD
&& !feat_is_trap(feat) && feat != DNGN_STONE_ARCH
&& feat != DNGN_OPEN_DOOR && feat != DNGN_ABANDONED_SHOP)
{
const std::string feat_name =
feature_description(you.pos(), false, DESC_CAP_THE, false);
if (you.airborne())
{
std::vector<std::string>* vec;
if (you.species == SP_KENKU)
vec = &messages;
else
vec = &priority;
vec->push_back(feat_name
+ " seems to fall away from under you!");
vec->push_back(feat_name
+ " seems to rush up at you!");
if (feat_is_water(feat))
{
priority.push_back("Something invisible splashes into the "
"water beneath you!");
}
}
else if (feat_is_water(feat))
{
priority.push_back("The water briefly recedes away from you.");
priority.push_back("Something invisible splashes into the water "
"beside you!");
}
}
if (!feat_destroys_items(feat) && !feat_is_solid(feat)
&& inv_items.size() > 0)
{
int idx = inv_items[random2(inv_items.size())];
const item_def &item(you.inv[idx]);
std::string name;
if (item.quantity == 1)
name = item.name(DESC_CAP_YOUR, false, false, false);
else
{
name = "One of ";
name += item.name(DESC_NOCAP_YOUR, false, false, false);
}
messages.push_back(name + " falls out of your pack, then "
"immediately jumps back in!");
}
const int transform = you.attribute[ATTR_TRANSFORMATION];
if (you.species == SP_MUMMY && you_tran_can_wear(EQ_BODY_ARMOUR))
{
messages.push_back("You briefly get tangled in your bandages.");
if (!you.airborne() && !you.swimming())
messages.push_back("You trip over your bandages.");
}
if (transform != TRAN_SPIDER)
{
std::string str = "A monocle briefly appears over your ";
str += coinflip() ? "right" : "left";
str += " eye.";
messages.push_back(str);
}
if (!player_genus(GENPC_DRACONIAN) && you.species != SP_MUMMY
&& (transform == TRAN_NONE || transform == TRAN_BLADE_HANDS))
{
messages.push_back("Your eyebrows briefly feel incredibly bushy.");
messages.push_back("Your eyebrows wriggle.");
}
if (you.species != SP_NAGA
&& (you.species != SP_MERFOLK || !player_is_swimming())
&& !you.airborne())
{
messages.push_back("You do an impromptu tapdance.");
}
item_def* item;
if (_could_wear_eq(EQ_WEAPON))
{
std::string str = "A fancy cane briefly appears in your ";
str += you.hand_name(false);
str += ".";
messages.push_back(str);
}
if (_tran_get_eq(EQ_CLOAK) != NULL)
messages.push_back("Your cloak billows in an unfelt wind.");
if ((item = _tran_get_eq(EQ_HELMET)))
{
std::string str = "Your ";
str += item->name(DESC_BASENAME, false, false, false);
str += " leaps into the air, briefly spins, then lands back on "
"your head!";
messages.push_back(str);
}
if ((item = _tran_get_eq(EQ_BOOTS)) && item->sub_type == ARM_BOOTS
&& !you.cannot_act())
{
std::string name = item->name(DESC_BASENAME, false, false, false);
name = replace_all(name, "pair of ", "");
std::string str = "You compulsively click the heels of your ";
str += name;
str += " together three times.";
}
if ((item = _tran_get_eq(EQ_SHIELD)))
{
std::string str = "Your ";
str += item->name(DESC_BASENAME, false, false, false);
str += " spins!";
messages.push_back(str);
str = "Your ";
str += item->name(DESC_BASENAME, false, false, false);
str += " briefly flashes a lurid colour!";
messages.push_back(str);
}
if ((item = _tran_get_eq(EQ_BODY_ARMOUR)))
{
std::string str;
std::string name = item->name(DESC_BASENAME, false, false, false);
if (name.find("dragon") != std::string::npos)
{
str = "The scales on your ";
str += name;
str += " wiggle briefly.";
}
else if (item->sub_type == ARM_ANIMAL_SKIN)
{
str = "The fur on your ";
str += name;
str += " grows longer at an alarming rate, then retracts back "
"to normal.";
}
else if (item->sub_type == ARM_LEATHER_ARMOUR)
{
str = "Your ";
str += name;
str += " briefly grows fur, then returns to normal.";
}
else if (item->sub_type == ARM_ROBE)
{
str = "You briefly become tangled in your ";
str += pluralise(name);
str += ".";
}
else if (item->sub_type >= ARM_RING_MAIL
&& item->sub_type <= ARM_PLATE_MAIL)
{
str = "Your ";
str += name;
str += " briefly appears rusty.";
}
if (!str.empty())
messages.push_back(str);
}
if (inv_items.size() > 0)
{
int idx = inv_items[random2(inv_items.size())];
item = &you.inv[idx];
std::string name = item->name(DESC_CAP_YOUR, false, false, false);
std::string verb = coinflip() ? "glow" : "vibrate";
if (item->quantity == 1)
verb += "s";
messages.push_back(name + " briefly " + verb + ".");
}
if (!priority.empty() && coinflip())
mpr(priority[random2(priority.size())].c_str());
else
mpr(messages[random2(messages.size())].c_str());
}
static void _get_hand_type(std::string &hand, bool &can_plural)
{
hand = "";
can_plural = true;
const int transform = you.attribute[ATTR_TRANSFORMATION];
std::vector<std::string> hand_vec;
std::vector<bool> plural_vec;
bool plural;
hand_vec.push_back(you.hand_name(false, &plural));
plural_vec.push_back(plural);
if (you.species != SP_NAGA || transform_changed_physiology())
{
item_def* item;
if ((item = _tran_get_eq(EQ_BOOTS)) && item->sub_type == ARM_BOOTS)
{
hand_vec.push_back("boot");
plural = true;
}
else
hand_vec.push_back(you.foot_name(false, &plural));
plural_vec.push_back(plural);
}
if (transform == TRAN_SPIDER)
{
hand_vec.push_back("mandible");
plural_vec.push_back(true);
}
else if (you.species != SP_MUMMY && !player_mutation_level(MUT_BEAK)
|| transform_changed_physiology())
{
hand_vec.push_back("nose");
plural_vec.push_back(false);
}
if (transform == TRAN_BAT
|| you.species != SP_MUMMY && !transform_changed_physiology())
{
hand_vec.push_back("ear");
plural_vec.push_back(true);
}
if (!transform_changed_physiology())
{
hand_vec.push_back("elbow");
plural_vec.push_back(true);
}
ASSERT(hand_vec.size() == plural_vec.size());
ASSERT(hand_vec.size() > 0);
const unsigned int choice = random2(hand_vec.size());
hand = hand_vec[choice];
can_plural = plural_vec[choice];
}
static int _xom_miscast(const int max_level, const bool nasty,
bool debug = false)
{
ASSERT(max_level >= 0 && max_level <= 3);
const char* speeches[4] = {
"zero miscast effect",
"minor miscast effect",
"medium miscast effect",
"major miscast effect"
};
const char* causes[4] = {
"the mischief of Xom",
"the capriciousness of Xom",
"the capriciousness of Xom",
"the severe capriciousness of Xom"
};
const char* speech_str = speeches[max_level];
const char* cause_str = causes[max_level];
const int level = (nasty ? 1 + random2(max_level)
: random2(max_level + 1));
if (debug)
{
switch (level)
{
case 0: return (XOM_BAD_MISCAST_PSEUDO);
case 1: return (XOM_BAD_MISCAST_MINOR);
case 2: return (XOM_BAD_MISCAST_MAJOR);
case 3: return (XOM_BAD_MISCAST_NASTY);
}
}
std::string desc = "miscast effect";
#ifdef NOTE_DEBUG_XOM
static char level_buf[20];
snprintf(level_buf, sizeof(level_buf), " level %d%s",
level, (nasty ? " (nasty)" : ""));
desc += level_buf;
#endif
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, desc.c_str()), true);
if (level == 0 && one_chance_in(20))
{
god_speaks(GOD_XOM, _get_xom_speech(speech_str).c_str());
_xom_zero_miscast();
return (XOM_BAD_MISCAST_PSEUDO);
}
std::string hand_str;
bool can_plural;
_get_hand_type(hand_str, can_plural);
const int lethality_margin = nasty ? 0 : random_range(1, 4);
god_speaks(GOD_XOM, _get_xom_speech(speech_str).c_str());
MiscastEffect(&you, -GOD_XOM, SPTYP_RANDOM, level, cause_str, NH_DEFAULT,
lethality_margin, hand_str, can_plural);
return (XOM_BAD_MISCAST_MAJOR);
}
static int _xom_lose_stats(bool debug = false)
{
if (debug)
return (XOM_BAD_STATLOSS);
stat_type stat = STAT_RANDOM;
int max = 3;
if (!_xom_feels_nasty())
{
char vals[3] =
{you.strength - (you.duration[DUR_MIGHT] ? 5 : 0),
you.dex - (you.duration[DUR_AGILITY] ? 5 : 0),
you.intel - (you.duration[DUR_BRILLIANCE] ? 5 : 0)};
stat_type types[3] = {STAT_STRENGTH, STAT_DEXTERITY,
STAT_INTELLIGENCE};
int tries = 0;
do
{
int idx = random2(3);
stat = types[idx];
max = std::min(3, vals[idx] - 1);
}
while (max < 2 && (++tries < 30));
if (tries >= 30)
return (XOM_DID_NOTHING);
}
god_speaks(GOD_XOM, _get_xom_speech("lose stats").c_str());
const int loss = 1 + random2(max);
lose_stat(stat, loss, true, "the vengeance of Xom" );
static char stat_buf[80];
snprintf(stat_buf, sizeof(stat_buf), "stat loss: -%d %s (%d/%d)",
loss, (stat == STAT_STRENGTH ? "Str" :
stat == STAT_DEXTERITY ? "Dex" : "Int"),
(stat == STAT_STRENGTH ? you.strength :
stat == STAT_DEXTERITY ? you.dex : you.intel),
(stat == STAT_STRENGTH ? you.max_strength :
stat == STAT_DEXTERITY ? you.max_dex : you.max_intel));
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, stat_buf), true);
return (XOM_BAD_STATLOSS);
}
static int _xom_chaos_upgrade_nearby_monster(bool debug = false)
{
monsters *mon = choose_random_nearby_monster(0, _choose_chaos_upgrade);
if (!mon)
return (XOM_DID_NOTHING);
if (debug)
return (XOM_BAD_CHAOS_UPGRADE);
god_speaks(GOD_XOM, _get_xom_speech("chaos upgrade").c_str());
mon_inv_type slots[] = {MSLOT_WEAPON, MSLOT_ALT_WEAPON, MSLOT_MISSILE};
bool rc = false;
for (int i = 0; i < 3 && !rc; ++i)
{
item_def* const item = mon->mslot_item(slots[i]);
if (item && _is_chaos_upgradeable(*item, mon))
{
_do_chaos_upgrade(*item, mon);
rc = true;
}
}
ASSERT(rc);
behaviour_event(mon, ME_ALERT, MHITYOU);
if (rc)
{
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "chaos upgrade"), true);
return (XOM_BAD_CHAOS_UPGRADE);
}
return (XOM_DID_NOTHING);
}
static int _xom_player_confusion_effect(int sever, bool debug = false)
{
if (!_xom_feels_nasty())
{
for (adjacent_iterator ai; ai; ++ai)
if (in_bounds(*ai) && is_feat_dangerous(grd(*ai)))
return (XOM_DID_NOTHING);
}
if (debug)
return (XOM_BAD_CONFUSION);
bool rc = false;
if (confuse_player(random2(sever) + 1, false))
{
god_speaks(GOD_XOM, _get_xom_speech("confusion").c_str());
rc = true;
bool mons_too = false;
if (coinflip())
{
for (unsigned i = 0; i < MAX_MONSTERS; ++i)
{
monsters* const monster = &menv[i];
if (!monster->alive()
|| !mons_near(monster)
|| !mons_class_is_confusable(monster->type)
|| one_chance_in(20))
{
continue;
}
if (monster->add_ench(mon_enchant(ENCH_CONFUSION, 0,
KC_FRIENDLY, random2(sever))))
{
simple_monster_message(monster,
" looks rather confused.");
}
mons_too = true;
}
}
std::string conf_msg = "confusion";
if (mons_too)
conf_msg += " (+ monsters)";
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, conf_msg.c_str()), true);
}
return (rc ? XOM_BAD_CONFUSION : XOM_DID_NOTHING);
}
static bool _valid_floor_grid(coord_def pos)
{
if (!in_bounds(pos))
return (false);
return (grd(pos) == DNGN_FLOOR);
}
bool move_stair(coord_def stair_pos, bool away, bool allow_under)
{
if (!allow_under)
ASSERT(stair_pos != you.pos());
dungeon_feature_type feat = grd(stair_pos);
ASSERT(feat_stair_direction(feat) != CMD_NO_CMD);
coord_def begin, towards;
bool stairs_moved = false;
if (away)
{
if (allow_under && stair_pos == you.pos())
{
coord_def new_pos(stair_pos);
for (int tries = 0; tries < 2; ++tries)
{
int adj_count = 0;
for (adjacent_iterator ai(stair_pos); ai; ++ai)
if (grd(*ai) == DNGN_FLOOR
&& (tries || _valid_floor_grid(*ai + *ai - stair_pos))
&& one_chance_in(++adj_count))
{
new_pos = *ai;
}
if (!tries && new_pos != stair_pos)
break;
}
if (new_pos == stair_pos)
return (false);
if (!slide_feature_over(stair_pos, new_pos))
return (false);
stair_pos = new_pos;
stairs_moved = true;
}
begin = you.pos();
towards = stair_pos;
}
else
{
if (adjacent(you.pos(), stair_pos))
return (false);
begin = stair_pos;
towards = you.pos();
}
ray_def ray;
if (!find_ray(begin, towards, ray))
{
mpr("Couldn't find ray between player and stairs.", MSGCH_ERROR);
return (stairs_moved);
}
if (away)
ray.advance();
bool found_stairs = false;
int past_stairs = 0;
while (in_bounds(ray.pos()) && see_cell(ray.pos())
&& !cell_is_solid(ray.pos()) && ray.pos() != you.pos())
{
if (ray.pos() == stair_pos)
found_stairs = true;
if (found_stairs)
past_stairs++;
ray.advance();
}
past_stairs--;
if (!away && cell_is_solid(ray.pos()))
{
return (stairs_moved);
}
if (away && !found_stairs)
{
if (cell_is_solid(ray.pos()))
{
return (stairs_moved);
}
mpr("Ray didn't cross stairs.", MSGCH_ERROR);
}
if (away && past_stairs <= 0)
{
return (stairs_moved);
}
if (!in_bounds(ray.pos()) || ray.pos() == you.pos())
ray.regress();
while (!see_cell(ray.pos()) || grd(ray.pos()) != DNGN_FLOOR)
{
ray.regress();
if (!in_bounds(ray.pos()) || ray.pos() == you.pos()
|| ray.pos() == stair_pos)
{
return (stairs_moved);
}
}
ASSERT(stair_pos != ray.pos());
std::string stair_str =
feature_description(stair_pos, false, DESC_CAP_THE, false);
mprf("%s slides %s you!", stair_str.c_str(),
away ? "away from" : "towards");
const feature_def &feat_def = get_feature_def(feat);
bolt beam;
beam.range = INFINITE_DISTANCE;
beam.flavour = BEAM_VISUAL;
beam.type = feat_def.symbol;
beam.colour = feat_def.colour;
beam.source = stair_pos;
beam.target = ray.pos();
beam.name = "STAIR BEAM";
beam.draw_delay = 50;
beam.aimed_at_spot = true;
beam.fire();
viewwindow(true, false);
if (!swap_features(stair_pos, ray.pos(), false, false))
{
mprf(MSGCH_ERROR, "_move_stair(): failed to move %s",
stair_str.c_str());
return (stairs_moved);
}
return (true);
}
static int _xom_repel_stairs(bool debug = false)
{
if (you.duration[DUR_REPEL_STAIRS_MOVE]
|| you.duration[DUR_REPEL_STAIRS_CLIMB])
{
return (XOM_DID_NOTHING);
}
std::vector<coord_def> stairs_avail;
bool real_stairs = false;
for (radius_iterator ri(you.pos(), LOS_RADIUS, false, true); ri; ++ri)
{
dungeon_feature_type feat = grd(*ri);
if (feat_stair_direction(feat) != CMD_NO_CMD
&& feat != DNGN_ENTER_SHOP)
{
stairs_avail.push_back(*ri);
if (feat_is_staircase(feat))
real_stairs = true;
}
}
if (stairs_avail.empty())
return (XOM_DID_NOTHING);
if (debug)
return (XOM_BAD_STAIRS);
std::string stair_msg = _get_xom_speech("repel stairs");
if (stair_msg.find("@staircase@") != std::string::npos)
{
std::string feat_name;
if (!real_stairs)
{
if (feat_is_escape_hatch(grd(stairs_avail[0])))
feat_name = "escape hatch";
else
feat_name = "gate";
}
else
feat_name = "staircase";
stair_msg = replace_all(stair_msg, "@staircase@", feat_name);
}
god_speaks(GOD_XOM, stair_msg.c_str());
you.duration[DUR_REPEL_STAIRS_MOVE] = 1000;
if (one_chance_in(5)
|| feat_stair_direction(grd(you.pos())) != CMD_NO_CMD
&& grd(you.pos()) != DNGN_ENTER_SHOP)
{
you.duration[DUR_REPEL_STAIRS_CLIMB] = 500;
}
std::random_shuffle(stairs_avail.begin(), stairs_avail.end());
int count_moved = 0;
for (unsigned int i = 0; i < stairs_avail.size(); i++)
if (move_stair(stairs_avail[i], true, true))
count_moved++;
if (!count_moved)
{
if (one_chance_in(8))
mpr("Nothing appears to happen... Ominous!");
else
canned_msg(MSG_NOTHING_HAPPENS);
}
else
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "repel stairs"), true);
return (XOM_BAD_STAIRS);
}
static int _xom_draining_torment_effect(int sever, bool debug = false)
{
const std::string speech = _get_xom_speech("draining or torment");
const bool nasty = _xom_feels_nasty();
int rc = XOM_DID_NOTHING;
if (one_chance_in(4))
{
if (player_prot_life() < 3 && (nasty || you.experience > 0))
{
if (debug)
return (XOM_BAD_DRAINING);
god_speaks(GOD_XOM, speech.c_str());
drain_exp();
if (random2(sever) > 3 && (nasty || you.experience > 0))
drain_exp();
if (random2(sever) > 3 && (nasty || you.experience > 0))
drain_exp();
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, "draining"), true);
return (XOM_BAD_DRAINING);
}
}
else
{
if (!player_res_torment())
{
if (debug)
return (XOM_BAD_TORMENT);
god_speaks(GOD_XOM, speech.c_str());
torment_player(0, TORMENT_XOM);
static char torment_buf[80];
snprintf(torment_buf, sizeof(torment_buf),
"torment (%d/%d hp)", you.hp, you.hp_max);
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, torment_buf), true);
return (XOM_BAD_TORMENT);
}
}
return (rc);
}
static int _xom_summon_hostiles(int sever, bool debug = false)
{
bool rc = false;
const std::string speech = _get_xom_speech("hostile monster");
int result = XOM_DID_NOTHING;
if (player_weapon_wielded() && one_chance_in(4))
{
if (debug)
return (XOM_BAD_ANIMATE_WPN);
const item_def& weapon = *you.weapon();
const std::string wep_name = weapon.name(DESC_PLAIN);
rc = cast_tukimas_dance(100, GOD_XOM, true);
if (rc)
{
static char wpn_buf[80];
snprintf(wpn_buf, sizeof(wpn_buf),
"animates weapon (%s)", wep_name.c_str());
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, wpn_buf), true);
result = XOM_BAD_ANIMATE_WPN;
}
}
else
{
if (debug)
return (XOM_BAD_SUMMON_DEMONS);
int numdemons = sever;
for (int i = 0; i < 3; ++i)
numdemons = random2(numdemons + 1);
numdemons = std::min(numdemons + 1, 14);
int num_summoned = 0;
for (int i = 0; i < numdemons; ++i)
{
if (create_monster(
mgen_data::hostile_at(
_xom_random_demon(sever),
you.pos(), 4, 0, true, GOD_XOM,
MON_SUMM_WRATH)) != -1)
{
num_summoned++;
}
}
if (num_summoned > 0)
{
static char summ_buf[80];
snprintf(summ_buf, sizeof(summ_buf),
"summons %d hostile demon%s",
num_summoned, num_summoned > 1 ? "s" : "");
take_note(Note(NOTE_XOM_EFFECT, you.piety, -1, summ_buf), true);
rc = true;
result = XOM_BAD_SUMMON_DEMONS;
}
}
if (rc)
god_speaks(GOD_XOM, speech.c_str());
return (result);
}
static int _xom_is_bad(int sever, int tension, bool debug = false)
{
int done = XOM_DID_NOTHING;
bool nasty = (sever >= 5 && _xom_feels_nasty());
god_acting gdact(GOD_XOM);
int badness = 1;
while (done == XOM_DID_NOTHING)
{
if (_player_is_dead())
return (XOM_PLAYER_DEAD);
if (!nasty && x_chance_in_y(3, sever))
done = _xom_miscast(0, nasty, debug);
else if (!nasty && x_chance_in_y(4, sever))
done = _xom_miscast(1, nasty, debug);
else if (tension > 0 && x_chance_in_y(5, sever))
{
done = _xom_player_confusion_effect(sever, debug);
badness = (random2(tension) > 5 ? 2 : 1);
}
else if (x_chance_in_y(6, sever))
{
done = _xom_lose_stats(debug);
badness = 2;
}
else if (tension > 0 && x_chance_in_y(7, sever))
done = _xom_swap_weapons(debug);
else if (x_chance_in_y(8, sever))
{
done = _xom_miscast(2, nasty, debug);
badness = 2;
}
else if (x_chance_in_y(9, sever))
{
done = _xom_chaos_upgrade_nearby_monster(debug);
badness = 2 + coinflip();
}
else if (x_chance_in_y(10, sever) && you.level_type != LEVEL_ABYSS)
{
if (!_teleportation_check())
return (XOM_DID_NOTHING);
const int explored = _exploration_estimate(true);
if (nasty && (explored >= 40 || tension > 10)
|| explored >= 60 + random2(40))
{
done = XOM_DID_NOTHING;
continue;
}
if (debug)
return (XOM_BAD_TELEPORT);
god_speaks(GOD_XOM,
_get_xom_speech("teleportation journey").c_str());
int count = 0;
do
{
you_teleport_now(false);
more();
if (count++ >= 7 + random2(5))
break;
}
while (x_chance_in_y(3, 4) && !player_in_a_dangerous_place());
maybe_update_stashes();
badness = player_in_a_dangerous_place() ? 3 : 1;
static char tele_buf[80];
snprintf(tele_buf, sizeof(tele_buf),
"%d-stop teleportation journey%s", count,
#ifdef NOTE_DEBUG_XOM
badness == 3 ? " (dangerous)" : "");
#else
"");
#endif
take_note(Note(NOTE_XOM_EFFECT, you.piety, tension, tele_buf),
true);
done = XOM_BAD_TELEPORT;
}
else if (x_chance_in_y(11, sever))
{
done = _xom_polymorph_nearby_monster(false, debug);
badness = 3;
}
else if (tension > 0 && x_chance_in_y(12, sever))
{
done = _xom_repel_stairs(debug);
badness = (you.duration[DUR_REPEL_STAIRS_CLIMB] ? 3 : 2);
}
else if (random2(tension) < 11 && x_chance_in_y(13, sever))
{
done = _xom_give_mutations(false, debug);
badness = 3;
}
else if (x_chance_in_y(14, sever))
{
done = _xom_draining_torment_effect(sever, debug);
badness = (random2(tension) > 5 ? 3 : 2);
}
else if (x_chance_in_y(15, sever))
{
done = _xom_summon_hostiles(sever, debug);
badness = 3 + coinflip();
}
else if (x_chance_in_y(16, sever))
{
done = _xom_miscast(3, nasty, debug);
badness = 4 + coinflip();
}
else if (one_chance_in(sever) && you.level_type != LEVEL_ABYSS)
{
if (!debug)
{
god_speaks(GOD_XOM, _get_xom_speech("banishment").c_str());
banished(DNGN_ENTER_ABYSS, "Xom");
badness = 5;
}
done = XOM_BAD_BANISHMENT;
}
}
if (done && !debug && _xom_is_bored())
{
const int interest = random2avg(badness*60, 2);
you.gift_timeout = std::min(interest, 255);
#if defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS, "badness: %d, new interest: %d",
badness, you.gift_timeout);
#endif
}
return (done);
}
static char* _stat_ptrs[3] = {&you.strength, &you.intel, &you.dex};
static void _handle_accidental_death(const int orig_hp,
const char orig_stats[],
const FixedVector<unsigned char, NUM_MUTATIONS> &orig_mutation)
{
if (!you.did_escape_death()
&& you.escaped_death_aux.empty()
&& !_player_is_dead())
{
return;
}
std::string speech_type = "accidental homicide";
const dungeon_feature_type feat = grd(you.pos());
switch (you.escaped_death_cause)
{
case NUM_KILLBY:
case KILLED_BY_LEAVING:
case KILLED_BY_WINNING:
case KILLED_BY_QUITTING:
speech_type = "weird death";
break;
case KILLED_BY_LAVA:
case KILLED_BY_WATER:
if (!_feat_is_deadly(feat))
speech_type = "weird death";
break;
case KILLED_BY_STUPIDITY:
if (you.intel > 0)
speech_type = "weird death";
break;
case KILLED_BY_WEAKNESS:
if (you.strength > 0)
speech_type = "weird death";
break;
case KILLED_BY_CLUMSINESS:
if (you.dex > 0)
speech_type = "weird death";
break;
default:
if (_feat_is_deadly(feat))
speech_type = "weird death";
if (you.strength <= 0 || you.intel <= 0 || you.dex <= 0)
speech_type = "weird death";
break;
}
mpr("You die...");
god_speaks(GOD_XOM, _get_xom_speech(speech_type).c_str());
god_speaks(GOD_XOM, _get_xom_speech("resurrection").c_str());
if (you.hp <= 0)
you.hp = std::min(orig_hp, you.hp_max);
mutation_type dex_muts[5] = {MUT_GREY2_SCALES, MUT_METALLIC_SCALES,
MUT_YELLOW_SCALES, MUT_RED2_SCALES,
MUT_STRONG_STIFF};
for (int i = 0; i < 5; ++i)
{
mutation_type bad = dex_muts[i];
while (you.dex <= 0 && you.mutation[bad] > orig_mutation[bad])
delete_mutation(bad, true, true, true);
}
while (you.dex <= 0
&& you.mutation[MUT_FLEXIBLE_WEAK] <
orig_mutation[MUT_FLEXIBLE_WEAK])
{
mutate(MUT_FLEXIBLE_WEAK, true, true, true);
}
while (you.strength <= 0
&& you.mutation[MUT_FLEXIBLE_WEAK] >
orig_mutation[MUT_FLEXIBLE_WEAK])
{
delete_mutation(MUT_FLEXIBLE_WEAK, true, true, true);
}
while (you.strength <= 0
&& you.mutation[MUT_STRONG_STIFF] <
orig_mutation[MUT_STRONG_STIFF])
{
mutate(MUT_STRONG_STIFF, true, true, true);
}
mutation_type bad_muts[3] = {MUT_WEAK, MUT_DOPEY, MUT_CLUMSY};
mutation_type good_muts[3] = {MUT_STRONG, MUT_CLEVER, MUT_AGILE};
for (int i = 0; i < 3; ++i)
{
while (*(_stat_ptrs[i]) <= 0)
{
mutation_type good = good_muts[i];
mutation_type bad = bad_muts[i];
if (you.mutation[bad] > orig_mutation[bad]
|| you.mutation[good] < orig_mutation[good])
{
mutate(good, true, true, true);
}
else
{
*(_stat_ptrs[i]) = orig_stats[i];
break;
}
}
}
you.max_strength = std::max(you.max_strength, you.strength);
you.max_intel = std::max(you.max_intel, you.intel);
you.max_dex = std::max(you.max_dex, you.dex);
if (_feat_is_deadly(feat))
you_teleport_now(false);
}
int xom_acts(bool niceness, int sever, int tension, bool debug)
{
#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_RELIGION) || defined(DEBUG_XOM)
mprf(MSGCH_DIAGNOSTICS, "xom_acts(%u, %d, %d); piety: %u, interest: %u\n",
niceness, sever, tension, you.piety, you.gift_timeout);
#endif
#ifdef WIZARD
if (_player_is_dead())
{
ASSERT(you.wizard && !you.did_escape_death());
if (_feat_is_deadly(grd(you.pos())))
{
mpr("Player is standing in deadly terrain, skipping Xom act.",
MSGCH_DIAGNOSTICS);
}
else
{
mpr("Player is already dead, skipping Xom act.",
MSGCH_DIAGNOSTICS);
}
return (XOM_PLAYER_DEAD);
}
#else
ASSERT(!_player_is_dead());
#endif
entry_cause_type old_entry_cause = you.entry_cause;
sever = std::max(1, sever);
god_type which_god = GOD_XOM;
if (crawl_state.is_god_acting()
&& crawl_state.which_god_acting() != GOD_XOM)
{
which_god = crawl_state.which_god_acting();
if (crawl_state.is_god_retribution())
{
niceness = false;
simple_god_message(" asks Xom for help in punishing you, and "
"Xom happily agrees.", which_god);
}
else
{
niceness = true;
simple_god_message(" calls in a favour from Xom.", which_god);
}
}
if (tension == -1)
tension = get_tension(which_god);
#if defined(DEBUG_RELIGION) || defined(DEBUG_XOM) || defined(DEBUG_TENSION)
mprf(MSGCH_DIAGNOSTICS, "Xom tension: %d", tension);
#endif
const int orig_hp = you.hp;
const char orig_stats[3] = {*(_stat_ptrs[0]), *(_stat_ptrs[1]),
*(_stat_ptrs[2])};
const FixedVector<unsigned char, NUM_MUTATIONS> orig_mutation
= you.mutation;
#ifdef NOTE_DEBUG_XOM
static char xom_buf[100];
snprintf(xom_buf, sizeof(xom_buf), "xom_acts(%s, %d, %d), mood: %d",
(niceness ? "true" : "false"), sever, tension, you.piety);
take_note(Note(NOTE_MESSAGE, 0, 0, xom_buf), true);
#endif
const bool was_bored = _xom_is_bored();
int result = XOM_DID_NOTHING;
if (niceness && !one_chance_in(20))
{
while (result == XOM_DID_NOTHING)
result = _xom_is_good(sever, tension, debug);
if (debug)
return (result);
}
else
{
if (!debug && was_bored && Options.note_xom_effects)
take_note(Note(NOTE_MESSAGE, 0, 0, "XOM is BORED!"), true);
#ifdef NOTE_DEBUG_XOM
else if (niceness)
{
take_note(Note(NOTE_MESSAGE, 0, 0, "good act randomly turned bad"),
true);
}
#endif
while (result == XOM_DID_NOTHING)
result = _xom_is_bad(sever, tension, debug);
if (debug)
return (result);
}
_handle_accidental_death(orig_hp, orig_stats, orig_mutation);
if (crawl_state.is_god_acting()
&& crawl_state.which_god_acting() != GOD_XOM)
{
if (old_entry_cause != you.entry_cause
&& you.entry_cause_god == GOD_XOM)
{
you.entry_cause_god = crawl_state.which_god_acting();
}
}
if (you.religion == GOD_XOM && one_chance_in(5))
{
const std::string old_xom_favour = describe_xom_favour();
you.piety = random2(MAX_PIETY + 1);
const std::string new_xom_favour = describe_xom_favour();
if (was_bored || old_xom_favour != new_xom_favour)
{
const std::string msg = "You are now " + new_xom_favour;
god_speaks(you.religion, msg.c_str());
}
#ifdef NOTE_DEBUG_XOM
snprintf(info, INFO_SIZE, "reroll piety: %d", you.piety);
take_note(Note(NOTE_MESSAGE, 0, 0, info), true);
#endif
}
else if (was_bored)
{
const std::string new_xom_favour = describe_xom_favour();
const std::string msg = "You are now " + new_xom_favour;
god_speaks(you.religion, msg.c_str());
}
return (result);
}
static void _xom_check_less_runes(int runes_gone)
{
if (you.opened_zot
|| player_in_branch(BRANCH_HALL_OF_ZOT)
|| !(branches[BRANCH_HALL_OF_ZOT].branch_flags & BFLAG_HAS_ORB))
{
return;
}
int runes_avail = you.attribute[ATTR_UNIQUE_RUNES]
+ you.attribute[ATTR_DEMONIC_RUNES]
+ you.attribute[ATTR_ABYSSAL_RUNES];
int was_avail = runes_avail + runes_gone;
if (was_avail >= NUMBER_OF_RUNES_NEEDED
&& runes_avail < NUMBER_OF_RUNES_NEEDED)
{
xom_is_stimulated(128, "Xom snickers.", true);
}
}
void xom_check_lost_item(const item_def& item)
{
if (item.base_type == OBJ_ORBS)
xom_is_stimulated(255, "Xom laughs nastily.", true);
else if (is_special_unrandom_artefact(item))
xom_is_stimulated(128, "Xom snickers.", true);
else if (is_rune(item))
{
if (item.flags & ISFLAG_BEEN_IN_INV)
_xom_check_less_runes(item.quantity);
if (is_unique_rune(item))
xom_is_stimulated(255, "Xom snickers loudly.", true);
else if (you.entry_cause == EC_SELF_EXPLICIT
&& !(item.flags & ISFLAG_BEEN_IN_INV))
{
if (item.plus == RUNE_ABYSSAL
&& you.attribute[ATTR_ABYSSAL_RUNES] == 0)
{
if (you.level_type != LEVEL_ABYSS)
{
xom_is_stimulated(128, "Xom snickers.", true);
}
}
else if (item.plus == RUNE_DEMONIC
&& you.attribute[ATTR_DEMONIC_RUNES] == 0)
{
xom_is_stimulated(64, "Xom snickers softly.", true);
}
}
}
}
void xom_check_destroyed_item(const item_def& item, int cause)
{
int amusement = 0;
if (item.base_type == OBJ_ORBS)
{
xom_is_stimulated(255, "Xom laughs nastily.", true);
return;
}
else if (is_special_unrandom_artefact(item))
xom_is_stimulated(128, "Xom snickers.", true);
else if (is_rune(item))
{
_xom_check_less_runes(item.quantity);
if (is_unique_rune(item) || item.plus == RUNE_ABYSSAL)
amusement = 255;
else
amusement = 64 * item.quantity;
}
xom_is_stimulated(amusement,
(amusement > 128) ? "Xom snickers loudly." :
(amusement > 64) ? "Xom snickers."
: "Xom snickers softly.",
true);
}
static bool _death_is_funny(const kill_method_type killed_by)
{
switch (killed_by)
{
case KILLED_BY_MONSTER:
case KILLED_BY_BEAM:
case KILLED_BY_CLOUD:
case KILLED_BY_FREEZING:
case KILLED_BY_BURNING:
case KILLED_BY_SELF_AIMED:
case KILLED_BY_SOMETHING:
case KILLED_BY_TRAP:
return (false);
default:
return (true);
}
}
void xom_death_message(const kill_method_type killed_by)
{
if (you.religion != GOD_XOM && (!you.worshipped[GOD_XOM] || coinflip()))
return;
const int death_tension = get_tension(GOD_XOM, false);
if (!_death_is_funny(killed_by) && you.hp >= -1 * random2(3)
&& death_tension <= random2(10))
{
god_speaks(GOD_XOM, _get_xom_speech("boring death").c_str());
}
else if (_death_is_funny(killed_by) || you.hp <= -10
|| death_tension >= 20)
{
god_speaks(GOD_XOM, _get_xom_speech("laughter").c_str());
}
}
#ifdef WIZARD
struct xom_effect_count
{
std::string effect;
int count;
xom_effect_count(std::string e, int c) : effect(e), count(c) {};
};
static bool _sort_xom_effects(const xom_effect_count &a,
const xom_effect_count &b)
{
if (a.count == b.count)
return (a.effect < b.effect);
return (a.count > b.count);
}
static const char* _xom_effect_to_name(int effect)
{
ASSERT(effect < XOM_PLAYER_DEAD);
static const char* _xom_effect_names[] =
{
"nothing",
"potion", "spell (tension)", "spell (no tension)", "mapping",
"confuse monsters", "single ally", "animate monster weapon",
"annoyance gift", "random item gift", "acquirement", "summon allies",
"polymorph", "swap monsters", "teleportation", "vitrification",
"mutation", "permanent ally", "lightning", "change scenery",
"miscast (pseudo)", "miscast (minor)", "miscast (major)",
"miscast (nasty)", "stat loss", "teleportation", "swap weapons",
"chaos upgrade", "mutation", "polymorph", "repel stairs", "confusion",
"draining", "torment", "animate weapon", "summon demons", "banishment"
};
std::string result = "";
if (effect > XOM_DID_NOTHING && effect < XOM_PLAYER_DEAD)
{
if (effect <= XOM_LAST_GOOD_ACT)
result = "GOOD: ";
else
result = "BAD: ";
}
result += _xom_effect_names[effect];
return (result.c_str());
}
void debug_xom_effects()
{
const int N = 10;
FILE *ostat = fopen("xom_debug.stat", "a");
if (!ostat)
{
mprf(MSGCH_ERROR, "Can't write 'xom_debug.stat'. Aborting.");
return;
}
const int real_piety = you.piety;
const god_type real_god = you.religion;
you.religion = GOD_XOM;
const int tension = get_tension(GOD_XOM);
fprintf(ostat, "---- STARTING XOM DEBUG TESTING ----\n");
fprintf(ostat, "%s\n", dump_overview_screen(false).c_str());
fprintf(ostat, "%s", screenshot().c_str());
fprintf(ostat, "\n%s\n", mpr_monster_list().c_str());
fprintf(ostat, " --> Tension: %d\n", tension);
if (you.penance[GOD_XOM])
fprintf(ostat, "You are under Xom's penance!\n");
else if (_xom_is_bored())
fprintf(ostat, "Xom is BORED.\n");
std::vector<int> mood_effects;
std::vector<std::vector<int> > all_effects;
std::vector<std::string> moods;
std::vector<int> mood_good_acts;
std::string old_mood = "";
std::string mood = "";
all_effects.push_back(mood_effects);
moods.push_back("total");
mood_good_acts.push_back(0);
int mood_good = 0;
for (int p = 0; p <= MAX_PIETY; ++p)
{
you.piety = p;
int sever = abs(p - HALF_MAX_PIETY);
mood = describe_xom_mood();
if (old_mood != mood)
{
if (old_mood != "")
{
all_effects.push_back(mood_effects);
mood_effects.clear();
mood_good_acts.push_back(mood_good);
mood_good_acts[0] += mood_good;
mood_good = 0;
}
moods.push_back(mood);
old_mood = mood;
}
for (int i = 0; i < N; ++i)
{
bool niceness = xom_is_nice(tension);
int result = xom_acts(niceness, sever, tension, true);
mood_effects.push_back(result);
all_effects[0].push_back(result);
if (result <= XOM_LAST_GOOD_ACT)
mood_good++;
}
}
all_effects.push_back(mood_effects);
mood_effects.clear();
mood_good_acts.push_back(mood_good);
mood_good_acts[0] += mood_good;
const int num_moods = moods.size();
std::vector<xom_effect_count> xom_ec_pairs;
for (int i = 0; i < num_moods; ++i)
{
mood_effects = all_effects[i];
const int total = mood_effects.size();
if (i == 0)
fprintf(ostat, "\nTotal effects (all piety ranges)\n");
else
fprintf(ostat, "\nMood: You are %s\n", moods[i].c_str());
fprintf(ostat, "GOOD%7.2f%%\n",
(100.0 * (float) mood_good_acts[i] / (float) total));
fprintf(ostat, "BAD %7.2f%%\n",
(100.0 * (float) (total - mood_good_acts[i]) / (float) total));
std::sort(mood_effects.begin(), mood_effects.end());
xom_ec_pairs.clear();
int old_effect = XOM_DID_NOTHING;
int count = 0;
for (int k = 0; k < total; ++k)
{
if (mood_effects[k] != old_effect)
{
if (count > 0)
{
std::string name = _xom_effect_to_name(old_effect);
xom_effect_count xec = xom_effect_count(name, count);
xom_ec_pairs.push_back(xec);
}
old_effect = mood_effects[k];
count = 1;
}
else
count++;
}
if (count > 0)
{
std::string name = _xom_effect_to_name(old_effect);
xom_effect_count xec = xom_effect_count(name, count);
xom_ec_pairs.push_back(xec);
}
std::sort(xom_ec_pairs.begin(), xom_ec_pairs.end(), _sort_xom_effects);
for (unsigned int k = 0; k < xom_ec_pairs.size(); ++k)
{
xom_effect_count xec = xom_ec_pairs[k];
fprintf(ostat, "%7.2f%% %s\n",
(100.0 * (float) xec.count / (float) total),
xec.effect.c_str());
}
}
fprintf(ostat, "---- FINISHED XOM DEBUG TESTING ----\n");
fclose(ostat);
mpr("Results written into 'xom_debug.stat'.");
you.piety = real_piety;
you.religion = real_god;
}
#endif