/*
* File: effects.cc
* Summary: Misc stuff.
* Written by: Linley Henzell
*
* Modified for Crawl Reference by $Author$ on $Date$
*
* Change History (most recent first):
*
* <1> -/--/-- LRH Created
*/
#include "AppHdr.h"
#include "effects.h"
#include <cstdlib>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include "externs.h"
#include "beam.h"
#include "cloud.h"
#include "decks.h"
#include "delay.h"
#include "describe.h"
#include "directn.h"
#include "dgnevent.h"
#include "food.h"
#include "hiscores.h"
#include "invent.h"
#include "it_use2.h"
#include "item_use.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "makeitem.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
#include "mutation.h"
#include "newgame.h"
#include "notes.h"
#include "ouch.h"
#include "player.h"
#include "randart.h"
#include "religion.h"
#include "skills.h"
#include "skills2.h"
#include "spells2.h"
#include "spells3.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
#include "terrain.h"
#include "traps.h"
#include "tutorial.h"
#include "view.h"
#include "xom.h"
int holy_word_player(int pow, int caster)
{
if (!you.is_undead && you.species != SP_DEMONSPAWN)
return 0;
int hploss = std::max(0, you.hp / 2 - 1);
if (!hploss)
return 0;
mpr("You are blasted by holy energy!");
const char *aux = "holy word";
if (caster < 0)
{
switch (caster)
{
case HOLY_WORD_SCROLL:
aux = "scroll of holy word";
break;
case HOLY_WORD_ZIN:
aux = "Zin's holy word";
break;
case HOLY_WORD_SHINING_ONE:
aux = "The Shining One's holy word";
break;
}
caster = HOLY_WORD_GENERIC;
}
ouch(hploss, caster,
(caster != HOLY_WORD_GENERIC) ? KILLED_BY_MONSTER
: KILLED_BY_SOMETHING,
aux);
return 1;
}
int holy_word_monsters(int x, int y, int pow, int caster)
{
int retval = 0;
// doubt this will ever happen, but it's here as a safety -- bwr
pow = std::min(300, pow);
// Is the player in this cell?
if (x == you.x_pos && y == you.y_pos)
retval = holy_word_player(pow, caster);
// Is a monster in this cell?
int mon = mgrd[x][y];
if (mon == NON_MONSTER)
return retval;
monsters *monster = &menv[mon];
if (invalid_monster(monster) || !mons_is_unholy(monster)
|| (is_good_god(you.religion)
&& (is_follower(monster) || mons_neutral(monster))))
{
return retval;
}
int hploss = std::max(0, roll_dice(2, 15) + (random2(pow) / 3));
// Currently, holy word annoys the monsters it affects because it
// can kill them, and because hostile monsters don't use it.
behaviour_event(monster, ME_ANNOY, MHITYOU);
hurt_monster(monster, hploss);
if (hploss)
{
retval = 1;
if (!monster->alive())
{
monster_die(monster, KILL_YOU, 0);
return retval;
}
}
simple_monster_message(monster, " convulses!");
if (monster->speed_increment >= 25)
{
retval = 1;
monster->speed_increment -= 20;
}
if (monster->add_ench(ENCH_FEAR))
retval = 1;
return retval;
}
int holy_word(int pow, int caster, int x, int y, bool silent)
{
if (!silent)
mpr("You speak a Word of immense power!");
return apply_area_within_radius(holy_word_monsters, x, y, pow, 8, caster);
}
int torment_player(int pow, int caster)
{
UNUSED(pow);
// [dshaligram] Switched to using ouch() instead of dec_hp() so that
// notes can also track torment and activities can be interrupted
// correctly.
int hploss = 0;
if (!player_res_torment())
{
// Negative energy resistance can alleviate torment.
hploss = std::max(0, you.hp * (50 - player_prot_life() * 5) / 100 - 1);
}
if (!hploss)
{
mpr("You feel a surge of unholy energy.");
return 0;
}
mpr("Your body is wracked with pain!");
const char *aux = "torment";
if (caster < 0)
{
switch (caster)
{
case TORMENT_CARDS:
case TORMENT_SPELL:
aux = "Symbol of Torment";
break;
case TORMENT_SPWLD:
// XXX: If we ever make any other weapon / randart eligible
// to torment, this will be incorrect.
aux = "Sceptre of Torment";
break;
case TORMENT_SCROLL:
aux = "scroll of torment";
break;
case TORMENT_XOM:
aux = "Xom's torment";
break;
}
caster = TORMENT_GENERIC;
}
ouch(hploss, caster, (caster != TORMENT_GENERIC) ? KILLED_BY_MONSTER
: KILLED_BY_SOMETHING,
aux);
return 1;
}
// torment_monsters() is called with power 0 because torment is
// UNRESISTABLE except for having torment resistance! Even if we used
// maximum power of 1000, high level monsters and characters would save
// too often. (GDL)
int torment_monsters(int x, int y, int pow, int caster)
{
UNUSED(pow);
int retval = 0;
// Is the player in this cell?
if (x == you.x_pos && y == you.y_pos)
retval = torment_player(0, caster);
// Is a monster in this cell?
int mon = mgrd[x][y];
if (mon == NON_MONSTER)
return retval;
monsters *monster = &menv[mon];
if (invalid_monster(monster) || mons_res_negative_energy(monster) == 3)
return retval;
int hploss = std::max(0, monster->hit_points / 2 - 1);
// Currently, torment doesn't annoy the monsters it affects because
// it can't kill them, and because hostile monsters use it.
hurt_monster(monster, hploss);
if (hploss)
{
retval = 1;
if (!monster->alive())
{
monster_die(monster, KILL_YOU, 0);
return retval;
}
}
simple_monster_message(monster, " convulses!");
return retval;
}
int torment(int caster, int x, int y)
{
return apply_area_within_radius(torment_monsters, x, y, 0, 8, caster);
}
static std::string _who_banished(const std::string &who)
{
return (who.empty()? who : " (" + who + ")");
}
void banished(dungeon_feature_type gate_type, const std::string &who)
{
#ifdef DGL_MILESTONES
if (gate_type == DNGN_ENTER_ABYSS)
{
mark_milestone("abyss.enter",
"is cast into the Abyss!" + _who_banished(who));
}
else if (gate_type == DNGN_EXIT_ABYSS)
{
mark_milestone("abyss.exit",
"escaped from the Abyss!" + _who_banished(who));
}
#endif
std::string cast_into;
switch (gate_type)
{
case DNGN_ENTER_ABYSS:
if (you.level_type == LEVEL_ABYSS)
{
mpr("You feel trapped.");
return;
}
cast_into = "the Abyss";
break;
case DNGN_EXIT_ABYSS:
if (you.level_type != LEVEL_ABYSS)
{
mpr("You feel dizzy for a moment.");
return;
}
break;
case DNGN_ENTER_PANDEMONIUM:
if (you.level_type == LEVEL_PANDEMONIUM)
{
mpr("You feel trapped.");
return;
}
cast_into = "Pandemonium";
break;
case DNGN_TRANSIT_PANDEMONIUM:
if (you.level_type != LEVEL_PANDEMONIUM)
{
banished(DNGN_ENTER_PANDEMONIUM, who);
return;
}
break;
case DNGN_EXIT_PANDEMONIUM:
if (you.level_type != LEVEL_PANDEMONIUM)
{
mpr("You feel dizzy for a moment.");
return;
}
break;
case DNGN_ENTER_LABYRINTH:
if (you.level_type == LEVEL_LABYRINTH)
{
mpr("You feel trapped.");
return;
}
cast_into = "a Labyrinth";
break;
case DNGN_ENTER_HELL:
case DNGN_ENTER_DIS:
case DNGN_ENTER_GEHENNA:
case DNGN_ENTER_COCYTUS:
case DNGN_ENTER_TARTARUS:
if (player_in_hell() || player_in_branch(BRANCH_VESTIBULE_OF_HELL))
{
mpr("You feel dizzy for a moment.");
return;
}
cast_into = "Hell";
break;
default:
mprf(MSGCH_DIAGNOSTICS, "Invalid banished() gateway %d",
static_cast<int>(gate_type));
ASSERT(false);
}
// Now figure out how we got here.
if (crawl_state.is_god_acting())
{
// down_stairs() will take care of setting things.
you.entry_cause = EC_UNKNOWN;
}
else if (who.find("self") != std::string::npos || who == you.your_name
|| who == "you" || who == "You")
{
you.entry_cause = EC_SELF_EXPLICIT;
}
else if (who.find("distortion") != std::string::npos)
{
if (who.find("wield") != std::string::npos)
{
if (who.find("unknowing") != std::string::npos)
you.entry_cause = EC_SELF_ACCIDENT;
else
you.entry_cause = EC_SELF_RISKY;
}
else if (who.find("affixation") != std::string::npos)
you.entry_cause = EC_SELF_ACCIDENT;
else if (who.find("branding") != std::string::npos)
you.entry_cause = EC_SELF_RISKY;
else
you.entry_cause = EC_MONSTER;
}
else if (who == "drawing a card")
you.entry_cause = EC_SELF_RISKY;
else if (who.find("miscast") != std::string::npos)
you.entry_cause = EC_MISCAST;
else if (who == "wizard command")
you.entry_cause = EC_SELF_EXPLICIT;
else
you.entry_cause = EC_MONSTER;
if (!crawl_state.is_god_acting())
you.entry_cause_god = GOD_NO_GOD;
if (!cast_into.empty() && you.entry_cause != EC_SELF_EXPLICIT)
{
const std::string what = "Cast into " + cast_into + _who_banished(who);
take_note(Note(NOTE_MESSAGE, 0, 0, what.c_str()), true);
}
// No longer held in net.
clear_trapping_net();
down_stairs(you.your_level, gate_type, you.entry_cause); // heh heh
}
bool forget_spell(void)
{
if (!you.spell_no)
return (false);
// find a random spell to forget:
int slot = -1;
int num = 0;
for (int i = 0; i < 25; i++)
{
if (you.spells[i] != SPELL_NO_SPELL)
{
num++;
if (one_chance_in( num ))
slot = i;
}
}
if (slot == -1) // should never happen though
return (false);
del_spell_from_memory_by_slot( slot );
return (true);
} // end forget_spell()
// use player::decrease_stats() instead iff:
// (a) player_sust_abil() should not factor in; and
// (b) there is no floor to the final stat values {dlb}
bool lose_stat(unsigned char which_stat, unsigned char stat_loss, bool force,
const char *cause, bool see_source)
{
bool statLowered = false; // must initialize to false {dlb}
char *ptr_stat = NULL;
bool *ptr_redraw = NULL;
char newValue = 0; // holds new value, for comparison to old {dlb}
kill_method_type kill_type = NUM_KILLBY;
// begin outputing message: {dlb}
std::string msg = "You feel ";
// set pointers to appropriate variables: {dlb}
if (which_stat == STAT_RANDOM)
which_stat = random2(NUM_STATS);
switch (which_stat)
{
case STAT_STRENGTH:
msg += "weakened";
ptr_stat = &you.strength;
ptr_redraw = &you.redraw_strength;
kill_type = KILLED_BY_WEAKNESS;
break;
case STAT_DEXTERITY:
msg += "clumsy";
ptr_stat = &you.dex;
ptr_redraw = &you.redraw_dexterity;
kill_type = KILLED_BY_CLUMSINESS;
break;
case STAT_INTELLIGENCE:
msg += "dopey";
ptr_stat = &you.intel;
ptr_redraw = &you.redraw_intelligence;
kill_type = KILLED_BY_STUPIDITY;
break;
}
// scale modifier by player_sust_abil() - right-shift
// permissible because stat_loss is unsigned: {dlb}
if (!force)
stat_loss >>= player_sust_abil();
// newValue is current value less modifier: {dlb}
newValue = *ptr_stat - stat_loss;
// conceivable that stat was already *at* three
// or stat_loss zeroed by player_sust_abil(): {dlb}
//
// Actually, that code was somewhat flawed. Several race-class combos
// can start with a stat lower than three, and this block (which
// used to say '!=' would actually cause stat gain with the '< 3'
// check that used to be above. Crawl has stat-death code and I
// don't see why we shouldn't be using it here. -- bwr
if (newValue < *ptr_stat)
{
*ptr_stat = newValue;
*ptr_redraw = 1;
// handle burden change, where appropriate: {dlb}
if (ptr_stat == &you.strength)
burden_change();
statLowered = true; // that is, stat was lowered (not just changed)
}
// a warning to player that s/he cut it close: {dlb}
if (!statLowered)
msg += " for a moment";
// finish outputting message: {dlb}
msg += ".";
mpr(msg.c_str());
if (newValue < 1)
{
if (cause == NULL)
ouch(INSTANT_DEATH, 0, kill_type);
else
ouch(INSTANT_DEATH, 0, kill_type, cause, see_source);
}
return (statLowered);
} // end lose_stat()
bool lose_stat(unsigned char which_stat, unsigned char stat_loss, bool force,
const std::string cause, bool see_source)
{
return lose_stat(which_stat, stat_loss, force, cause.c_str(), see_source);
}
bool lose_stat(unsigned char which_stat, unsigned char stat_loss,
const monsters* cause, bool force)
{
if (cause == NULL || invalid_monster(cause))
return lose_stat(which_stat, stat_loss, force, NULL, true);
bool vis = mons_near(cause) && player_monster_visible(cause);
std::string name = cause->name(DESC_NOCAP_A, true);
if (cause->has_ench(ENCH_SHAPESHIFTER))
name += " (shapeshifter)";
else if (cause->has_ench(ENCH_GLOWING_SHAPESHIFTER))
name += " (glowing shapeshifter)";
return lose_stat(which_stat, stat_loss, force, name, vis);
}
bool lose_stat(unsigned char which_stat, unsigned char stat_loss,
const item_def &cause, bool removed, bool force)
{
std::string name = cause.name(DESC_NOCAP_THE, false, true, false, false,
ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
std::string verb;
switch(cause.base_type)
{
case OBJ_ARMOUR:
case OBJ_JEWELLERY:
if (removed)
verb = "removing";
else
verb = "wearing";
break;
case OBJ_WEAPONS:
case OBJ_STAVES:
if (removed)
verb = "unwielding";
else
verb = "wielding";
break;
case OBJ_WANDS: verb = "zapping"; break;
case OBJ_FOOD: verb = "eating"; break;
case OBJ_SCROLLS: verb = "reading"; break;
case OBJ_POTIONS: verb = "drinking"; break;
default: verb = "using";
}
return lose_stat(which_stat, stat_loss, force, verb + " " + name, true);
}
void direct_effect(struct bolt &pbolt)
{
int damage_taken = 0;
monsters* source = NULL;
if (pbolt.beam_source != NON_MONSTER)
source = &menv[pbolt.beam_source];
switch (pbolt.type)
{
case DMNBM_HELLFIRE:
pbolt.aux_source = "burst of hellfire";
pbolt.name = "hellfire";
pbolt.ex_size = 1;
pbolt.flavour = BEAM_HELLFIRE;
pbolt.is_explosion = true;
pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP);
pbolt.colour = RED;
pbolt.thrower = KILL_MON_MISSILE;
pbolt.aux_source.clear();
pbolt.is_beam = false;
pbolt.is_tracer = false;
pbolt.hit = 20;
pbolt.damage = dice_def( 3, 20 );
explosion( pbolt );
break;
case DMNBM_SMITING:
mpr( "Something smites you!" );
pbolt.name = "smiting";
pbolt.aux_source = "by divine providence";
damage_taken = 7 + random2avg(11, 2);
break;
case DMNBM_BRAIN_FEED:
// lose_stat() must come last {dlb}
if (one_chance_in(3)
&& lose_stat(STAT_INTELLIGENCE, 1, source))
{
mpr("Something feeds on your intellect!");
xom_is_stimulated(50);
}
else
mpr("Something tries to feed on your intellect!");
break;
}
// apply damage and handle death, where appropriate {dlb}
if (damage_taken > 0)
{
ouch(damage_taken, pbolt.beam_source, KILLED_BY_BEAM,
pbolt.aux_source.c_str());
}
return;
} // end direct_effect()
// monster-to-monster
void mons_direct_effect(struct bolt &pbolt, int i)
{
// note the translation here - important {dlb}
int o = menv[i].foe;
monsters *monster = &menv[o];
int damage_taken = 0;
// annoy the target
behaviour_event(monster, ME_ANNOY, i);
switch (pbolt.type)
{
case DMNBM_HELLFIRE:
simple_monster_message(monster, " is engulfed in hellfire.");
pbolt.name = "hellfire";
pbolt.flavour = BEAM_LAVA;
damage_taken = 5 + random2(10) + random2(5);
damage_taken = mons_adjust_flavoured(monster, pbolt, damage_taken);
break;
case DMNBM_SMITING:
simple_monster_message(monster, " is smitten.");
pbolt.name = "smiting";
pbolt.flavour = BEAM_MISSILE;
damage_taken += 7 + random2avg(11, 2);
break;
case DMNBM_BRAIN_FEED: // Not implemented here (nor, probably, can be).
break;
case DMNBM_MUTATION:
if (mons_holiness(monster) != MH_NATURAL
|| mons_immune_magic(monster))
{
simple_monster_message(monster, " is unaffected.");
}
else if (check_mons_resist_magic( monster, pbolt.ench_power ))
simple_monster_message(monster, " resists.");
else
monster_polymorph(monster, RANDOM_MONSTER);
break;
}
// Apply damage and handle death, where appropriate {dlb}
if (damage_taken > 0)
{
hurt_monster(monster, damage_taken);
if (monster->hit_points < 1)
monster_die(monster, KILL_MON_MISSILE, i);
}
return;
}
void random_uselessness(int scroll_slot)
{
int temp_rand = random2(8);
// If this isn't from a scroll, skip the first two possibilities.
if (scroll_slot == -1)
temp_rand = 2 + random2(6);
switch (temp_rand)
{
case 0:
mprf("The dust glows %s!", weird_glowing_colour().c_str());
break;
case 1:
mpr("The scroll reassembles itself in your hand!");
inc_inv_item_quantity(scroll_slot, 1);
break;
case 2:
if (you.equip[EQ_WEAPON] != -1)
{
mprf("%s glows %s for a moment.",
you.inv[you.equip[EQ_WEAPON]].name(DESC_CAP_YOUR).c_str(),
weird_glowing_colour().c_str());
}
else
{
mprf("Your %s glow %s for a moment.",
your_hand(true).c_str(), weird_glowing_colour().c_str());
}
break;
case 3:
if (player_can_smell())
mprf("You smell %s.", weird_smell().c_str());
else if (you.species == SP_MUMMY)
mpr("Your bandages flutter.");
else
canned_msg(MSG_NOTHING_HAPPENS);
break;
case 4:
mpr("You experience a momentary feeling of inescapable doom!");
break;
case 5:
temp_rand = random2(3);
mprf("Your %s",
(temp_rand == 0) ? "ears itch." :
(temp_rand == 1) ? "brain hurts!"
: "nose twitches suddenly!");
break;
case 6:
mpr("You hear the tinkle of a tiny bell.", MSGCH_SOUND);
cast_summon_butterflies(100);
break;
case 7:
mprf(MSGCH_SOUND, "You hear %s.", weird_sound().c_str());
break;
}
}
static armour_type _random_nonbody_armour_type()
{
const armour_type at =
static_cast<armour_type>(
random_choose(ARM_SHIELD, ARM_CLOAK, ARM_HELMET,
ARM_GLOVES, ARM_BOOTS, -1));
return (at);
}
static int _find_acquirement_subtype(object_class_type class_wanted,
int &quantity)
{
ASSERT(class_wanted != OBJ_RANDOM);
int type_wanted = OBJ_RANDOM;
int iteration = 0;
const int max_has_value = 100;
FixedVector< int, max_has_value > already_has;
skill_type best_spell = SK_NONE;
skill_type best_any = SK_NONE;
already_has.init(0);
int spell_skills = 0;
for (int i = SK_SPELLCASTING; i <= SK_POISON_MAGIC; i++)
spell_skills += you.skills[i];
for (int acqc = 0; acqc < ENDOFPACK; acqc++)
{
if (is_valid_item( you.inv[acqc] )
&& you.inv[acqc].base_type == class_wanted)
{
ASSERT( you.inv[acqc].sub_type < max_has_value );
already_has[you.inv[acqc].sub_type] += you.inv[acqc].quantity;
}
}
if (class_wanted == OBJ_FOOD)
{
// food is a little less predictable now -- bwr
if (you.species == SP_GHOUL)
{
type_wanted = one_chance_in(10) ? FOOD_ROYAL_JELLY
: FOOD_CHUNK;
}
else if (you.species == SP_VAMPIRE)
{
// Vampires really don't want any OBJ_FOOD but OBJ_CORPSES
// but it's easier to just give them a potion of blood
// class type is set elsewhere
type_wanted = POT_BLOOD;
quantity = 2 + random2(4);
}
else
{
// Meat is better than bread (except for herbivores), and
// by choosing it as the default we don't have to worry
// about special cases for carnivorous races (e.g. kobolds)
type_wanted = FOOD_MEAT_RATION;
if (player_mutation_level(MUT_HERBIVOROUS))
type_wanted = FOOD_BREAD_RATION;
// If we have some regular rations, then we're probably more
// interested in faster foods (especially royal jelly)...
// otherwise the regular rations should be a good enough offer.
if (already_has[FOOD_MEAT_RATION]
+ already_has[FOOD_BREAD_RATION] >= 2 || coinflip())
{
type_wanted = one_chance_in(5) ? FOOD_HONEYCOMB
: FOOD_ROYAL_JELLY;
}
}
quantity = 3 + random2(5);
// giving more of the lower food value items
if (type_wanted == FOOD_HONEYCOMB || type_wanted == FOOD_CHUNK)
{
quantity += random2avg(10, 2);
}
}
else if (class_wanted == OBJ_WEAPONS)
{
// Now asking for a weapon is biased towards your skills,
// although launchers are right out for now. -- bwr
int count = 0;
int skill = SK_FIGHTING;
// Can't do much with launchers, so we'll avoid them for now -- bwr
for (int i = SK_SHORT_BLADES; i < SK_DARTS; i++)
{
if (i == SK_UNUSED_1)
continue;
// Adding a small constant allows for the occasional
// weapon in an untrained skill.
const int weight = you.skills[i] + 1;
count += weight;
if (x_chance_in_y(weight, count))
skill = i;
}
count = 0;
item_def item_considered;
item_considered.base_type = OBJ_WEAPONS;
for (int i = 0; i < NUM_WEAPONS; ++i)
{
item_considered.sub_type = i;
const int acqweight = property(item_considered, PWPN_ACQ_WEIGHT);
if (!acqweight)
continue;
int wskill = range_skill(OBJ_WEAPONS, i);
if (wskill == SK_THROWING)
wskill = weapon_skill(OBJ_WEAPONS, i);
if (wskill == skill && x_chance_in_y(acqweight, count += acqweight))
type_wanted = i;
}
}
else if (class_wanted == OBJ_MISSILES)
{
int count = 0;
int skill = SK_THROWING;
for (int i = SK_SLINGS; i <= SK_DARTS; i++)
{
if (you.skills[i])
{
count += you.skills[i];
if (x_chance_in_y(you.skills[i], count))
skill = i;
}
}
switch (skill)
{
case SK_SLINGS:
type_wanted = MI_STONE;
break;
case SK_BOWS:
type_wanted = MI_ARROW;
break;
case SK_CROSSBOWS:
type_wanted = MI_DART;
for (int i = 0; i < ENDOFPACK; i++)
{
// Assuming that crossbow in inventory means that they
// want bolts for it (not darts for a hand crossbow)...
// perhaps we should check for both and compare ammo
// amounts on hand?
if (is_valid_item( you.inv[i] )
&& you.inv[i].base_type == OBJ_WEAPONS
&& you.inv[i].sub_type == WPN_CROSSBOW)
{
type_wanted = MI_BOLT;
break;
}
}
break;
case SK_DARTS:
type_wanted = MI_DART;
for (int i = 0; i < ENDOFPACK; i++)
{
if (is_valid_item( you.inv[i] )
&& you.inv[i].base_type == OBJ_WEAPONS
&& you.inv[i].sub_type == WPN_BLOWGUN)
{
// Assuming that blowgun in inventory means that they
// may want needles for it (but darts might also be
// wanted). Maybe expand this... see above comment.
if (coinflip())
type_wanted = MI_NEEDLE;
break;
}
}
break;
default:
type_wanted = MI_DART;
break;
}
}
else if (class_wanted == OBJ_ARMOUR)
{
// Increasing the representation of the non-body armour
// slots here to make up for the fact that there's one
// one type of item for most of them. -- bwr
//
// OBJ_RANDOM is body armour and handled below
type_wanted = (coinflip())? OBJ_RANDOM :
static_cast<int>(_random_nonbody_armour_type());
// Some species specific fitting problems.
switch (you.species)
{
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_TROLL:
case SP_RED_DRACONIAN:
case SP_WHITE_DRACONIAN:
case SP_GREEN_DRACONIAN:
case SP_YELLOW_DRACONIAN:
case SP_GREY_DRACONIAN:
case SP_BLACK_DRACONIAN:
case SP_PURPLE_DRACONIAN:
case SP_MOTTLED_DRACONIAN:
case SP_PALE_DRACONIAN:
case SP_BASE_DRACONIAN:
case SP_SPRIGGAN:
if (type_wanted == ARM_GLOVES || type_wanted == ARM_BOOTS
|| type_wanted == ARM_CENTAUR_BARDING
|| type_wanted == ARM_NAGA_BARDING)
{
type_wanted = ARM_ROBE; // no heavy armour
}
else if (type_wanted == ARM_SHIELD)
{
if (you.species == SP_SPRIGGAN)
type_wanted = ARM_BUCKLER;
else if (coinflip()) // giant races: 50/50 shield/large shield
type_wanted = ARM_LARGE_SHIELD;
}
else if (type_wanted == OBJ_RANDOM)
{
type_wanted = ARM_ROBE; // no heavy armour, see below
}
break;
case SP_NAGA:
if (type_wanted == ARM_BOOTS || type_wanted == ARM_CENTAUR_BARDING)
type_wanted = ARM_NAGA_BARDING;
break;
case SP_CENTAUR:
if (type_wanted == ARM_BOOTS || type_wanted == ARM_NAGA_BARDING)
type_wanted = ARM_CENTAUR_BARDING;
break;
default:
if (type_wanted == ARM_CENTAUR_BARDING
|| type_wanted == ARM_NAGA_BARDING)
{
type_wanted = ARM_BOOTS;
}
break;
}
// Mutation specific problems (horns allow caps).
if (type_wanted == ARM_BOOTS && !player_has_feet()
|| type_wanted == ARM_GLOVES && you.has_claws(false) >= 3)
{
type_wanted = OBJ_RANDOM;
}
// Do this here, before acquirement()'s call to can_wear_armour(),
// so that caps will be just as common as helmets for those
// that can't wear helmets.
// We could use player_mutation_level for the horns, but let's just
// check for the mutation directly to avoid acquirement fiddles.
if (type_wanted == ARM_HELMET
&& ((you.species >= SP_OGRE && you.species <= SP_OGRE_MAGE)
|| player_genus(GENPC_DRACONIAN)
|| you.species == SP_KENKU
|| you.species == SP_SPRIGGAN
|| you.mutation[MUT_HORNS]))
{
type_wanted = coinflip()? ARM_CAP : ARM_WIZARD_HAT;
}
// Now we'll randomly pick a body armour (light only in the
// case of ARM_ROBE). Unlike before, now we're only giving
// out the finished products here, never the hides. -- bwr
if (type_wanted == OBJ_RANDOM || type_wanted == ARM_ROBE)
{
// start with normal base armour
if (type_wanted == ARM_ROBE)
type_wanted = coinflip() ? ARM_ROBE : ARM_ANIMAL_SKIN;
else
{
type_wanted = ARM_ROBE + random2(8);
if (one_chance_in(10) && you.skills[SK_ARMOUR] >= 10)
type_wanted = ARM_CRYSTAL_PLATE_MAIL;
if (one_chance_in(10))
type_wanted = ARM_ANIMAL_SKIN;
}
// everyone can wear things made from hides
if (one_chance_in(20))
{
int rnd = random2(20);
type_wanted = (rnd < 4) ? ARM_TROLL_LEATHER_ARMOUR : // 20%
(rnd < 8) ? ARM_STEAM_DRAGON_ARMOUR : // 20%
(rnd < 11) ? ARM_MOTTLED_DRAGON_ARMOUR : // 15%
(rnd < 14) ? ARM_SWAMP_DRAGON_ARMOUR : // 15%
(rnd < 16) ? ARM_DRAGON_ARMOUR : // 10%
(rnd < 18) ? ARM_ICE_DRAGON_ARMOUR : // 10%
(rnd < 19) ? ARM_STORM_DRAGON_ARMOUR // 5%
: ARM_GOLD_DRAGON_ARMOUR; // 5%
}
}
}
else if (class_wanted != OBJ_GOLD)
{
do
{
unsigned char i;
switch (class_wanted)
{
case OBJ_JEWELLERY:
// Try for a base type the player hasn't identified
for (i = 0; i < 10; i++)
{
type_wanted = random2(24);
if (one_chance_in(3))
type_wanted = AMU_RAGE + random2(10);
if (get_ident_type(OBJ_JEWELLERY, type_wanted) ==
ID_UNKNOWN_TYPE)
{
break;
}
}
break;
case OBJ_BOOKS:
// Remember, put rarer books higher in the list.
iteration = 1;
type_wanted = NUM_BOOKS;
best_spell = best_skill( SK_SPELLCASTING, (NUM_SKILLS - 1),
best_spell );
which_book:
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"acquirement: iteration = %d, best_spell = %d",
iteration, best_spell );
#endif
switch (best_spell)
{
default:
case SK_SPELLCASTING:
if (you.skills[SK_SPELLCASTING] <= 3
&& !you.had_book[BOOK_CANTRIPS])
{
// Handful of level one spells, very useful for the
// new spellcaster who's asking for a book -- bwr
type_wanted = BOOK_CANTRIPS;
}
else if (!you.had_book[BOOK_MINOR_MAGIC_I])
type_wanted = BOOK_MINOR_MAGIC_I + random2(3);
else if (!you.had_book[BOOK_WIZARDRY])
type_wanted = BOOK_WIZARDRY;
else if (!you.had_book[BOOK_CONTROL])
type_wanted = BOOK_CONTROL;
else if (!you.had_book[BOOK_POWER])
type_wanted = BOOK_POWER;
break;
case SK_POISON_MAGIC:
if (!you.had_book[BOOK_YOUNG_POISONERS])
type_wanted = BOOK_YOUNG_POISONERS;
else if (!you.had_book[BOOK_ENVENOMATIONS])
type_wanted = BOOK_ENVENOMATIONS;
break;
case SK_EARTH_MAGIC:
if (!you.had_book[BOOK_GEOMANCY])
type_wanted = BOOK_GEOMANCY;
else if (!you.had_book[BOOK_EARTH])
type_wanted = BOOK_EARTH;
break;
case SK_AIR_MAGIC:
// removed the book of clouds... all the other elements
// (and most other spell skills) only get two.
if (!you.had_book[BOOK_AIR])
type_wanted = BOOK_AIR;
else if (!you.had_book[BOOK_SKY])
type_wanted = BOOK_SKY;
break;
case SK_ICE_MAGIC:
if (!you.had_book[BOOK_FROST])
type_wanted = BOOK_FROST;
else if (!you.had_book[BOOK_ICE])
type_wanted = BOOK_ICE;
break;
case SK_FIRE_MAGIC:
if (!you.had_book[BOOK_FLAMES])
type_wanted = BOOK_FLAMES;
else if (!you.had_book[BOOK_FIRE])
type_wanted = BOOK_FIRE;
break;
case SK_SUMMONINGS:
if (!you.had_book[BOOK_CALLINGS])
type_wanted = BOOK_CALLINGS;
else if (!you.had_book[BOOK_SUMMONINGS])
type_wanted = BOOK_SUMMONINGS;
// now a Vehumet special -- bwr
// else if (!you.had_book[BOOK_DEMONOLOGY])
// type_wanted = BOOK_DEMONOLOGY;
break;
case SK_ENCHANTMENTS:
best_any = best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99);
// So many enchantment books! I really can't feel
// guilty at all for dividing out the fighting
// books and forcing the player to raise a fighting
// skill (or enchantments in the case of Crusaders)
// to get the remaining books... enchantments are
// much too good (most spells, lots of books here,
// id wand charges, gives magic resistance),
// something will eventually have to be done. -- bwr
if (best_any >= SK_FIGHTING
&& best_any <= SK_STAVES)
{
// Fighter mages get the fighting enchantment books
if (!you.had_book[BOOK_WAR_CHANTS])
type_wanted = BOOK_WAR_CHANTS;
else if (!you.had_book[BOOK_TUKIMA])
type_wanted = BOOK_TUKIMA;
}
else if (!you.had_book[BOOK_CHARMS])
type_wanted = BOOK_CHARMS;
else if (!you.had_book[BOOK_HINDERANCE])
type_wanted = BOOK_HINDERANCE;
else if (!you.had_book[BOOK_ENCHANTMENTS])
type_wanted = BOOK_ENCHANTMENTS;
break;
case SK_CONJURATIONS:
if (!you.had_book[BOOK_CONJURATIONS_I])
type_wanted = give_first_conjuration_book();
else if (!you.had_book[BOOK_TEMPESTS])
type_wanted = BOOK_TEMPESTS;
// now a Vehumet special -- bwr
// else if (!you.had_book[BOOK_ANNIHILATIONS])
// type_wanted = BOOK_ANNIHILATIONS;
break;
case SK_NECROMANCY:
if (!you.had_book[BOOK_NECROMANCY])
type_wanted = BOOK_NECROMANCY;
else if (!you.had_book[BOOK_DEATH])
type_wanted = BOOK_DEATH;
else if (!you.had_book[BOOK_UNLIFE])
type_wanted = BOOK_UNLIFE;
// now a Kikubaaqudgha special -- bwr
// else if (!you.had_book[BOOK_NECRONOMICON])
// type_wanted = BOOK_NECRONOMICON;
break;
case SK_TRANSLOCATIONS:
if (!you.had_book[BOOK_SPATIAL_TRANSLOCATIONS])
type_wanted = BOOK_SPATIAL_TRANSLOCATIONS;
else if (!you.had_book[BOOK_WARP])
type_wanted = BOOK_WARP;
break;
case SK_TRANSMIGRATION:
if (!you.had_book[BOOK_CHANGES])
type_wanted = BOOK_CHANGES;
else if (!you.had_book[BOOK_TRANSFIGURATIONS])
type_wanted = BOOK_TRANSFIGURATIONS;
else if (!you.had_book[BOOK_MUTATIONS])
type_wanted = BOOK_MUTATIONS;
break;
case SK_DIVINATIONS: //jmf: added 24mar2000
if (!you.had_book[BOOK_SURVEYANCES])
type_wanted = BOOK_SURVEYANCES;
else if (!you.had_book[BOOK_DIVINATIONS])
type_wanted = BOOK_DIVINATIONS;
break;
}
/*
if (type_wanted == 99 && glof == best_skill(SK_SPELLCASTING, (NUM_SKILLS - 1), 99))
*/
if (type_wanted == NUM_BOOKS && iteration == 1)
{
best_spell = best_skill( SK_SPELLCASTING, NUM_SKILLS - 1,
best_skill(SK_SPELLCASTING,
NUM_SKILLS - 1, 99) );
iteration++;
goto which_book;
}
// If we don't have a book, try and get a new one.
if (type_wanted == NUM_BOOKS)
{
do
{
type_wanted = random2(NUM_BOOKS);
if (one_chance_in(500))
break;
}
while (you.had_book[type_wanted]);
}
// If the book is invalid find any valid one.
while (book_rarity(type_wanted) == 100
|| type_wanted == BOOK_DESTRUCTION
|| type_wanted == BOOK_MANUAL)
{
type_wanted = random2(NUM_BOOKS);
}
break;
case OBJ_STAVES:
type_wanted = random2(13);
if (type_wanted >= 10)
type_wanted = STAFF_AIR + type_wanted - 10;
// Elemental preferences -- bwr
if (type_wanted == STAFF_FIRE || type_wanted == STAFF_COLD)
{
if (you.skills[SK_FIRE_MAGIC] > you.skills[SK_ICE_MAGIC])
type_wanted = STAFF_FIRE;
else if (you.skills[SK_FIRE_MAGIC] != you.skills[SK_ICE_MAGIC])
type_wanted = STAFF_COLD;
}
else if (type_wanted == STAFF_AIR || type_wanted == STAFF_EARTH)
{
if (you.skills[SK_AIR_MAGIC] > you.skills[SK_EARTH_MAGIC])
type_wanted = STAFF_AIR;
else if (you.skills[SK_AIR_MAGIC] != you.skills[SK_EARTH_MAGIC])
type_wanted = STAFF_EARTH;
}
best_spell = best_skill( SK_SPELLCASTING, (NUM_SKILLS-1), 99 );
// If we're going to give out an enhancer staff,
// we should at least bias things towards the
// best spell skill. -- bwr
switch (best_spell)
{
case SK_FIRE_MAGIC:
if (!already_has[STAFF_FIRE])
type_wanted = STAFF_FIRE;
break;
case SK_ICE_MAGIC:
if (!already_has[STAFF_COLD])
type_wanted = STAFF_COLD;
break;
case SK_AIR_MAGIC:
if (!already_has[STAFF_AIR])
type_wanted = STAFF_AIR;
break;
case SK_EARTH_MAGIC:
if (!already_has[STAFF_EARTH])
type_wanted = STAFF_EARTH;
break;
case SK_POISON_MAGIC:
if (!already_has[STAFF_POISON])
type_wanted = STAFF_POISON;
break;
case SK_NECROMANCY:
if (!already_has[STAFF_DEATH])
type_wanted = STAFF_DEATH;
break;
case SK_CONJURATIONS:
if (!already_has[STAFF_CONJURATION])
type_wanted = STAFF_CONJURATION;
break;
case SK_ENCHANTMENTS:
if (!already_has[STAFF_ENCHANTMENT])
type_wanted = STAFF_ENCHANTMENT;
break;
case SK_SUMMONINGS:
if (!already_has[STAFF_SUMMONING])
type_wanted = STAFF_SUMMONING;
break;
case SK_EVOCATIONS:
if (!one_chance_in(4))
type_wanted = random_rod_subtype();
break;
default: // Invocations and leftover spell schools.
switch (random2(5))
{
case 0:
type_wanted = STAFF_WIZARDRY;
break;
case 1:
type_wanted = STAFF_POWER;
break;
case 2:
type_wanted = STAFF_ENERGY;
break;
case 3:
type_wanted = STAFF_CHANNELING;
break;
case 4:
break;
}
break;
}
// Increased chance of getting a rod for new or
// non-spellcasters. -- bwr
if (one_chance_in(20)
|| (spell_skills <= 1 // short on spells
&& type_wanted < STAFF_SMITING
&& !one_chance_in(4)))
{
type_wanted = coinflip() ? STAFF_STRIKING :
random_rod_subtype();
}
break;
case OBJ_MISCELLANY:
do
{
type_wanted = random2(NUM_MISCELLANY);
}
while (type_wanted == MISC_HORN_OF_GERYON
|| type_wanted == MISC_RUNE_OF_ZOT
|| type_wanted == MISC_CRYSTAL_BALL_OF_FIXATION
|| type_wanted == MISC_EMPTY_EBONY_CASKET);
break;
default:
break;
}
ASSERT( type_wanted < max_has_value );
}
while (already_has[type_wanted] && !one_chance_in(200));
}
return (type_wanted);
}
bool acquirement(object_class_type class_wanted, int agent,
bool quiet, int* item_index)
{
int thing_created = NON_ITEM;
if (item_index == NULL)
item_index = &thing_created;
*item_index = NON_ITEM;
while (class_wanted == OBJ_RANDOM)
{
ASSERT(!quiet);
mesclr();
mpr( "[a] Weapon [b] Armour [c] Jewellery [d] Book" );
mpr( "[e] Staff [f] Food [g] Miscellaneous [h] Gold" );
mpr("What kind of item would you like to acquire? ", MSGCH_PROMPT);
const int keyin = tolower( get_ch() );
switch (keyin)
{
case 'a': class_wanted = OBJ_WEAPONS; break;
case 'b': class_wanted = OBJ_ARMOUR; break;
case 'c': class_wanted = OBJ_JEWELLERY; break;
case 'd': class_wanted = OBJ_BOOKS; break;
case 'e': class_wanted = OBJ_STAVES; break;
case 'f': class_wanted = OBJ_FOOD; break;
case 'g': class_wanted = OBJ_MISCELLANY; break;
case 'h': class_wanted = OBJ_GOLD; break;
default: break;
}
}
if (grid_destroys_items(grd(you.pos())))
{
// How sad (and stupid).
if (!silenced(you.pos()) && !quiet)
mprf(MSGCH_SOUND, grid_item_destruction_message(grd(you.pos())));
item_was_destroyed(mitm[igrd(you.pos())], NON_MONSTER);
*item_index = NON_ITEM;
}
else
{
int quant = 1;
for (int item_tries = 0; item_tries < 40; item_tries++)
{
int type_wanted = _find_acquirement_subtype(class_wanted, quant);
// Clobber class_wanted for vampires.
if (you.species == SP_VAMPIRE && class_wanted == OBJ_FOOD)
class_wanted = OBJ_POTIONS;
thing_created = items( 1, class_wanted, type_wanted, true,
MAKE_GOOD_ITEM, MAKE_ITEM_RANDOM_RACE );
if (thing_created == NON_ITEM)
continue;
const item_def &doodad(mitm[thing_created]);
if (doodad.base_type == OBJ_WEAPONS
&& !can_wield(&doodad, false, true)
|| doodad.base_type == OBJ_ARMOUR
&& !can_wear_armour(doodad, false, true))
{
destroy_item(thing_created, true);
thing_created = NON_ITEM;
continue;
}
// Only TSO gifts blessed blades, and currently not through
// acquirement, but make sure of this anyway.
if (agent != GOD_SHINING_ONE && is_blessed_blade(doodad))
{
destroy_item(thing_created, true);
thing_created = NON_ITEM;
continue;
}
// Trog does not gift the Wrath of Trog, nor weapons of pain
// (which work together with Necromantic magic).
if (agent == GOD_TROG)
{
int brand = get_weapon_brand(doodad);
if (brand == SPWPN_PAIN
|| is_fixed_artefact(doodad)
&& (doodad.special == SPWPN_WRATH_OF_TROG
|| doodad.special == SPWPN_STAFF_OF_WUCAD_MU))
{
destroy_item(thing_created, true);
thing_created = NON_ITEM;
continue;
}
}
// MT - Check: god-gifted weapons and armor shouldn't kill you.
// Except Xom.
if ((agent == GOD_TROG || agent == GOD_OKAWARU)
&& is_random_artefact(doodad))
{
randart_properties_t proprt;
randart_wpn_properties( doodad, proprt );
// Check vs stats. positive stats will automatically fall
// through. As will negative stats that won't kill you.
if (-proprt[RAP_STRENGTH] >= you.strength
|| -proprt[RAP_INTELLIGENCE] >= you.intel
|| -proprt[RAP_DEXTERITY] >= you.dex)
{
// Try again.
destroy_item(thing_created);
thing_created = NON_ITEM;
continue;
}
}
break;
}
if (thing_created == NON_ITEM)
{
if (!quiet)
mpr("The demon of the infinite void smiles upon you.");
*item_index = NON_ITEM;
return (false);
}
// Easier to read this way.
item_def& thing(mitm[thing_created]);
// Give some more gold.
if (class_wanted == OBJ_GOLD)
thing.quantity += 150;
else if (quant > 1)
thing.quantity = quant;
if (is_blood_potion(thing))
init_stack_blood_potions(thing);
// Remove curse flag from item.
do_uncurse_item( thing );
if (thing.base_type == OBJ_BOOKS)
{
mark_had_book(thing.sub_type);
}
else if (thing.base_type == OBJ_JEWELLERY)
{
switch (thing.sub_type)
{
case RING_SLAYING:
// Make sure plus to damage is >= 1.
thing.plus2 = abs( thing.plus2 );
if (thing.plus2 == 0)
thing.plus2 = 1;
// fall through...
case RING_PROTECTION:
case RING_STRENGTH:
case RING_INTELLIGENCE:
case RING_DEXTERITY:
case RING_EVASION:
// Make sure plus is >= 1.
thing.plus = abs( thing.plus );
if (thing.plus == 0)
thing.plus = 1;
break;
case RING_HUNGER:
case AMU_INACCURACY:
// These are the only truly bad pieces of jewellery.
if (!one_chance_in(9))
make_item_randart( thing );
break;
default:
break;
}
}
else if (thing.base_type == OBJ_WEAPONS
&& !is_fixed_artefact( thing )
&& !is_unrandom_artefact( thing ))
{
// HACK: Make unwieldable weapons wieldable.
// Note: messing with fixed artefacts is probably very bad.
switch (you.species)
{
case SP_DEMONSPAWN:
case SP_MUMMY:
case SP_GHOUL:
case SP_VAMPIRE:
{
int brand = get_weapon_brand( thing );
if (brand == SPWPN_HOLY_WRATH)
{
if (!is_random_artefact( thing ))
{
set_item_ego_type( thing,
OBJ_WEAPONS, SPWPN_VORPAL );
}
else
{
// Keep resetting seed until it's good.
for (; brand == SPWPN_HOLY_WRATH;
brand = get_weapon_brand(thing))
{
make_item_randart( thing );
}
}
}
}
break;
case SP_HALFLING:
case SP_GNOME:
case SP_KOBOLD:
case SP_SPRIGGAN:
switch (thing.sub_type)
{
case WPN_LONGBOW:
thing.sub_type = WPN_BOW;
break;
case WPN_GREAT_SWORD:
case WPN_TRIPLE_SWORD:
thing.sub_type = (coinflip() ? WPN_FALCHION : WPN_LONG_SWORD);
break;
case WPN_GREAT_MACE:
case WPN_DIRE_FLAIL:
thing.sub_type = (coinflip() ? WPN_MACE : WPN_FLAIL);
break;
case WPN_BATTLEAXE:
case WPN_EXECUTIONERS_AXE:
thing.sub_type = (coinflip() ? WPN_HAND_AXE : WPN_WAR_AXE);
break;
case WPN_HALBERD:
case WPN_GLAIVE:
case WPN_SCYTHE:
case WPN_BARDICHE:
thing.sub_type = (coinflip() ? WPN_SPEAR : WPN_TRIDENT);
break;
}
break;
default:
break;
}
int plusmod = random2(4);
if (agent == GOD_TROG)
{
// More damage, less accuracy.
thing.plus -= plusmod;
thing.plus2 += plusmod;
if (!is_random_artefact(thing))
thing.plus = std::max(static_cast<int>(thing.plus), 0);
}
else if (agent == GOD_OKAWARU)
{
// More accuracy, less damage.
thing.plus += plusmod;
thing.plus2 -= plusmod;
if (!is_random_artefact(thing))
thing.plus2 = std::max(static_cast<int>(thing.plus2), 0);
}
}
if (agent > AQ_SCROLL && agent == you.religion)
{
thing.inscription = "god gift";
if (is_random_artefact(thing))
{
origin_acquired(mitm[thing_created], agent);
// give another name that takes god gift into account
thing.props["randart_name"].get_string() =
randart_name(thing, false);
}
}
move_item_to_grid( &thing_created, you.x_pos, you.y_pos );
// This should never actually be NON_ITEM because of the way
// move_item_to_grid works (doesn't create a new item ever),
// but we're checking it anyways. -- bwr
if (thing_created != NON_ITEM)
{
if (!quiet)
canned_msg(MSG_SOMETHING_APPEARS);
origin_acquired(mitm[thing_created], agent);
}
*item_index = thing_created;
}
// Well, the item may have fallen in the drink, but the intent is
// that acquirement happened. -- bwr
return (true);
} // end acquirement()
bool recharge_wand(int item_slot)
{
if (item_slot == -1)
{
item_slot = prompt_invent_item( "Charge which item?", MT_INVLIST,
OSEL_RECHARGE, true, true, false );
}
if (prompt_failed(item_slot))
return (false);
item_def &wand = you.inv[ item_slot ];
// Weapons of electrocution can be "charged", i.e. gain +1 damage.
if (wand.base_type == OBJ_WEAPONS
&& get_weapon_brand(wand) == SPWPN_ELECTROCUTION)
{
// Might fail because of already high enchantment.
if (enchant_weapon( ENCHANT_TO_DAM, false, wand ))
{
you.wield_change = true;
if (!item_ident(wand, ISFLAG_KNOW_TYPE))
set_ident_flags(wand, ISFLAG_KNOW_TYPE);
return (true);
}
return (false);
}
if (wand.base_type != OBJ_WANDS && !item_is_rod(wand))
return (false);
int charge_gain = 0;
if (wand.base_type == OBJ_WANDS)
{
switch (wand.sub_type)
{
case WAND_INVISIBILITY:
case WAND_FIREBALL:
case WAND_TELEPORTATION:
case WAND_HEALING:
case WAND_HASTING:
charge_gain = 3;
break;
case WAND_LIGHTNING:
case WAND_DRAINING:
charge_gain = 4;
break;
case WAND_FIRE:
case WAND_COLD:
charge_gain = 5;
break;
default:
charge_gain = 8;
break;
}
// Don't display zap counts any more.
wand.plus2 = ZAPCOUNT_UNKNOWN;
const int new_charges =
std::max<int>(
wand.plus,
std::min(charge_gain * 3,
wand.plus +
1 + random2avg( ((charge_gain - 1) * 3) + 1, 3 )));
const bool charged = new_charges > wand.plus;
mprf("%s %s for a moment.",
wand.name(DESC_CAP_YOUR).c_str(),
charged? "glows" : "flickers");
wand.plus = new_charges;
}
else // It's a rod.
{
bool work = false;
if (wand.plus2 <= MAX_ROD_CHARGE * ROD_CHARGE_MULT)
{
wand.plus2 += ROD_CHARGE_MULT;
if (wand.plus2 > MAX_ROD_CHARGE * ROD_CHARGE_MULT)
wand.plus2 = MAX_ROD_CHARGE * ROD_CHARGE_MULT;
work = true;
}
if (wand.plus < wand.plus2)
{
wand.plus = wand.plus2;
work = true;
}
if (!work)
return (false);
mprf("%s glows for a moment.", wand.name(DESC_CAP_YOUR).c_str());
}
you.wield_change = true;
return (true);
} // end recharge_wand()
// Sets foe target of friendly monsters.
// If allow_patrol is true, patrolling monsters get MHITNOT instead.
static void _set_friendly_foes(bool allow_patrol = false)
{
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *mon(&menv[i]);
if (!mon->alive() || !mons_near(mon) || !mons_friendly(mon))
continue;
mon->foe = (allow_patrol && mon->is_patrolling() ? MHITNOT
: you.pet_target);
}
}
static void _set_allies_patrol_point(bool clear = false)
{
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters *mon(&menv[i]);
if (!mon->alive() || !mons_near(mon) || !mons_friendly(mon))
continue;
mon->patrol_point = (clear ? coord_def(0, 0)
: coord_def(mon->x, mon->y));
if (!clear)
mon->behaviour = BEH_WANDER;
}
}
void yell(bool force)
{
bool targ_prev = false;
int mons_targd = MHITNOT;
struct dist targ;
const std::string shout_verb = you.shout_verb();
std::string cap_shout = shout_verb;
cap_shout[0] = toupper(cap_shout[0]);
int noise_level = 12; // "shout"
// Tweak volume for different kinds of vocalisation.
if (shout_verb == "roar")
noise_level = 18;
else if (shout_verb == "hiss")
noise_level = 8;
else if (shout_verb == "squeak")
noise_level = 4;
else if (shout_verb == "__NONE")
noise_level = 0;
else if (shout_verb == "yell")
noise_level = 14;
else if (shout_verb == "scream")
noise_level = 16;
if (silenced(you.x_pos, you.y_pos) || you.cannot_speak())
noise_level = 0;
if (noise_level == 0)
{
if (force)
{
if (shout_verb == "__NONE" || you.paralysed())
{
mprf("You feel a strong urge to %s, but "
"you are unable to make a sound!",
shout_verb == "__NONE" ? "scream"
: shout_verb.c_str());
}
else
{
mprf("You feel a %s rip itself from your throat, "
"but you make no sound!",
shout_verb.c_str());
}
}
else
mpr("You are unable to make a sound!");
return;
}
if (force)
{
mprf("A %s rips itself from your throat!", shout_verb.c_str());
noisy(noise_level, you.x_pos, you.y_pos);
return;
}
mpr("What do you say?", MSGCH_PROMPT);
mprf(" t - %s!", cap_shout.c_str());
if (!you.duration[DUR_BERSERKER])
{
std::string previous = "";
if (!(you.prev_targ == MHITNOT || you.prev_targ == MHITYOU))
{
monsters *target = &menv[you.prev_targ];
if (target->alive() && mons_near(target)
&& player_monster_visible(target))
{
previous = " p - Attack previous target.";
targ_prev = true;
}
}
mprf("Orders for allies: a - Attack new target.%s", previous.c_str());
mpr( " s - Stop attacking.");
mpr( " w - Wait here. f - Follow me.");
}
mprf(" Anything else - Stay silent%s.",
one_chance_in(20)? " (and be thought a fool)" : "");
unsigned char keyn = get_ch();
mesclr();
switch (keyn)
{
case '!': // for players using the old keyset
case 't':
mprf(MSGCH_SOUND, "You %s for attention!", shout_verb.c_str());
noisy(noise_level, you.x_pos, you.y_pos);
you.turn_is_over = true;
return;
case 'f':
case 's':
mons_targd = MHITYOU;
if (keyn == 'f')
{
// Don't reset patrol points for 'Stop fighting!'
_set_allies_patrol_point(true);
mpr("Follow me!");
}
else
mpr("Stop fighting!");
break;
case 'w':
mpr("Wait here!");
mons_targd = MHITNOT;
_set_allies_patrol_point();
break;
case 'p':
if (you.duration[DUR_BERSERKER])
{
canned_msg(MSG_TOO_BERSERK);
return;
}
if (targ_prev)
{
mons_targd = you.prev_targ;
break;
}
// fall through
case 'a':
if (you.duration[DUR_BERSERKER])
{
canned_msg(MSG_TOO_BERSERK);
return;
}
if (env.sanctuary_time > 0)
{
if (!yesno("An ally attacking under your orders might violate "
"sanctuary; order anyway?", false, 'n'))
{
canned_msg(MSG_OK);
return;
}
}
mpr("Gang up on whom?", MSGCH_PROMPT);
direction( targ, DIR_TARGET, TARG_ENEMY, -1, false, false );
if (targ.isCancel)
{
canned_msg(MSG_OK);
return;
}
if (!targ.isValid || mgrd[targ.tx][targ.ty] == NON_MONSTER
|| !player_monster_visible(&env.mons[mgrd[targ.tx][targ.ty]]))
{
mpr("Yeah, whatever.");
return;
}
mons_targd = mgrd[targ.tx][targ.ty];
break;
default:
mpr("Okely-dokely.");
return;
}
you.pet_target = mons_targd;
// Allow patrolling for "Stop fighting!" and "Wait here!"
_set_friendly_foes(keyn == 's' || keyn == 'w');
if (mons_targd != MHITNOT && mons_targd != MHITYOU)
mpr("Attack!");
noisy(10, you.x_pos, you.y_pos);
} // end yell()
bool forget_inventory(bool quiet)
{
int items_forgotten = 0;
for (int i = 0; i < ENDOFPACK; i++)
{
item_def& item(you.inv[i]);
if (!is_valid_item(item) || item_is_equipped(item))
continue;
unsigned long orig_flags = item.flags;
unset_ident_flags(item, ISFLAG_KNOW_CURSE);
// Don't forget times used or uses left for wands or decks.
if (item.base_type != OBJ_WANDS && item.base_type != OBJ_MISCELLANY)
unset_ident_flags(item, ISFLAG_KNOW_PLUSES);
if (!is_artefact(item))
{
switch (item.base_type)
{
case OBJ_WEAPONS:
case OBJ_ARMOUR:
case OBJ_BOOKS:
case OBJ_STAVES:
case OBJ_MISCELLANY:
// Don't forget identity of decks if the player has
// used any of its cards, or knows how many are left.
if (!is_deck(item) || item.plus2 == 0)
unset_ident_flags(item, ISFLAG_KNOW_TYPE);
break;
default:
break;
}
}
// Non-jewellery artefacts can easily be re-identified by
// equipping them.
else if (item.base_type != OBJ_JEWELLERY)
unset_ident_flags(item, ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES);
if (item.flags != orig_flags)
items_forgotten++;
}
if (items_forgotten > 0)
mpr("Wait, did you forget something?");
return (items_forgotten > 0);
}
// Returns true if there was a visible change.
bool vitrify_area(int radius)
{
if (radius < 2)
return (false);
const int radius2 = radius * radius;
// This hinges on clear wall types having the same order as non-clear ones!
const int clear_plus = DNGN_CLEAR_ROCK_WALL - DNGN_ROCK_WALL;
bool something_happened = false;
for (int x = X_BOUND_1; x <= X_BOUND_2; ++x)
for (int y = Y_BOUND_1; y <= Y_BOUND_2; ++y)
{
if (distance(x,y,you.x_pos,you.y_pos) < radius2)
{
dungeon_feature_type grid = grd[x][y];
if (grid == DNGN_ROCK_WALL
|| grid == DNGN_STONE_WALL
|| grid == DNGN_PERMAROCK_WALL )
{
grd[x][y]
= static_cast<dungeon_feature_type>(grid + clear_plus);
something_happened = true;
}
}
}
return (something_happened);
}
static void _hell_effects()
{
if (is_sanctuary(you.x_pos, you.y_pos))
{
mpr("Zin's power protects you from Hell's scourges!", MSGCH_GOD);
return;
}
int temp_rand = random2(17);
spschool_flag_type which_miscast = SPTYP_RANDOM;
bool summon_instead = false;
monster_type which_beastie = MONS_PROGRAM_BUG;
mpr((temp_rand == 0) ? "\"You will not leave this place.\"" :
(temp_rand == 1) ? "\"Die, mortal!\"" :
(temp_rand == 2) ? "\"We do not forgive those who trespass against us!\"" :
(temp_rand == 3) ? "\"Trespassers are not welcome here!\"" :
(temp_rand == 4) ? "\"You do not belong in this place!\"" :
(temp_rand == 5) ? "\"Leave now, before it is too late!\"" :
(temp_rand == 6) ? "\"We have you now!\"" :
// plain messages
(temp_rand == 7) ? (player_can_smell()) ? "You smell brimstone."
: "Brimstone rains from above." :
(temp_rand == 8) ? "You feel lost and a long, long way from home..." :
(temp_rand == 9) ? "You shiver with fear." :
// warning
(temp_rand == 10) ? "You feel a terrible foreboding..." :
(temp_rand == 11) ? "Something frightening happens." :
(temp_rand == 12) ? "You sense an ancient evil watching you..." :
(temp_rand == 13) ? "You suddenly feel all small and vulnerable." :
(temp_rand == 14) ? "You sense a hostile presence." :
// sounds
(temp_rand == 15) ? "A gut-wrenching scream fills the air!" :
(temp_rand == 16) ? "You hear words spoken in a strange and terrible language..."
: "You hear diabolical laughter!",
(temp_rand < 7 ? MSGCH_TALK :
temp_rand < 10 ? MSGCH_PLAIN :
temp_rand < 15 ? MSGCH_WARN
: MSGCH_SOUND));
temp_rand = random2(27);
if (temp_rand > 17) // 9 in 27 odds {dlb}
{
temp_rand = random2(8);
if (temp_rand > 3) // 4 in 8 odds {dlb}
which_miscast = SPTYP_NECROMANCY;
else if (temp_rand > 1) // 2 in 8 odds {dlb}
which_miscast = SPTYP_SUMMONING;
else if (temp_rand > 0) // 1 in 8 odds {dlb}
which_miscast = SPTYP_CONJURATION;
else // 1 in 8 odds {dlb}
which_miscast = SPTYP_ENCHANTMENT;
miscast_effect(which_miscast, 4 + random2(6), random2avg(97, 3),
100, "the effects of Hell");
}
else if (temp_rand > 7) // 10 in 27 odds {dlb}
{
// 60:40 miscast:summon split {dlb}
summon_instead = x_chance_in_y(2, 5);
switch (you.where_are_you)
{
case BRANCH_DIS:
if (summon_instead)
which_beastie = summon_any_demon(DEMON_GREATER);
else
which_miscast = SPTYP_EARTH;
break;
case BRANCH_GEHENNA:
if (summon_instead)
which_beastie = MONS_FIEND;
else
which_miscast = SPTYP_FIRE;
break;
case BRANCH_COCYTUS:
if (summon_instead)
which_beastie = MONS_ICE_FIEND;
else
which_miscast = SPTYP_ICE;
break;
case BRANCH_TARTARUS:
if (summon_instead)
which_beastie = MONS_SHADOW_FIEND;
else
which_miscast = SPTYP_NECROMANCY;
break;
default:
// This is to silence gcc compiler warnings. {dlb}
if (summon_instead)
which_beastie = MONS_FIEND;
else
which_miscast = SPTYP_NECROMANCY;
break;
}
if (summon_instead)
{
create_monster(
mgen_data::hostile_at(which_beastie,
you.pos(), 0, 0, true));
}
else
{
miscast_effect(which_miscast, 4 + random2(6),
random2avg(97, 3), 100, "the effects of Hell");
}
}
// NB: No "else" - 8 in 27 odds that nothing happens through
// first chain. {dlb}
// Also note that the following is distinct from and in
// addition to the above chain.
// Try to summon at least one and up to five random monsters. {dlb}
if (one_chance_in(3))
{
mgen_data mg;
mg.pos = you.pos();
mg.foe = MHITYOU;
create_monster(mg);
for (int i = 0; i < 4; ++i)
if (one_chance_in(3))
create_monster(mg);
}
}
static bool _food_item_needs_time_check(item_def &item)
{
if (!is_valid_item(item))
return (false);
if (item.base_type != OBJ_CORPSES
&& item.base_type != OBJ_FOOD
&& item.base_type != OBJ_POTIONS)
{
return (false);
}
if (item.base_type == OBJ_CORPSES
&& item.sub_type > CORPSE_SKELETON)
{
return (false);
}
if (item.base_type == OBJ_FOOD && item.sub_type != FOOD_CHUNK)
return (false);
if (item.base_type == OBJ_POTIONS && !is_blood_potion(item))
return (false);
return (true);
}
static void _rot_inventory_food(long time_delta)
{
// Update all of the corpses and food chunks in the player's
// inventory. {should be moved elsewhere - dlb}
bool burden_changed_by_rot = false;
std::vector<char> rotten_items;
for (int i = 0; i < ENDOFPACK; i++)
{
if (you.inv[i].quantity < 1)
continue;
if (!_food_item_needs_time_check(you.inv[i]))
continue;
if (you.inv[i].base_type == OBJ_POTIONS)
{
// also handles messaging
if (maybe_coagulate_blood_potions_inv(you.inv[i]))
burden_changed_by_rot = true;
continue;
}
// food item timed out -> make it disappear
if ((time_delta / 20) >= you.inv[i].special)
{
if (you.inv[i].base_type == OBJ_FOOD)
{
if (you.equip[EQ_WEAPON] == i)
unwield_item();
destroy_item(you.inv[i]);
burden_changed_by_rot = true;
continue;
}
// Carried skeletons are not destroyed.
if (you.inv[i].sub_type == CORPSE_SKELETON)
continue;
if (!mons_skeleton( you.inv[i].plus ))
{
if (you.equip[EQ_WEAPON] == i)
unwield_item();
destroy_item(you.inv[i]);
burden_changed_by_rot = true;
continue;
}
turn_corpse_into_skeleton(you.inv[i]);
you.wield_change = true;
burden_changed_by_rot = true;
continue;
}
// if it hasn't disappeared, reduce the rotting timer
you.inv[i].special -= (time_delta / 20);
if (food_is_rotten(you.inv[i])
&& (you.inv[i].special + (time_delta / 20) >= 100 ))
{
rotten_items.push_back(index_to_letter( i ));
}
}
//mv: messages when chunks/corpses become rotten
if (!rotten_items.empty())
{
std::string msg = "";
// Races that can't smell don't care, and trolls are stupid and
// don't care.
if (player_can_smell() && you.species != SP_TROLL)
{
int temp_rand = 0; // Grr.
int level = player_mutation_level(MUT_SAPROVOROUS);
if (!level && you.species == SP_VAMPIRE)
level = 1;
switch (level)
{
// level 1 and level 2 saprovores, as well as vampires, aren't so touchy
case 1:
case 2:
temp_rand = random2(8);
msg = (temp_rand < 5) ? "You smell something rotten." :
(temp_rand == 5) ? "You smell rotting flesh." :
(temp_rand == 6) ? "You smell decay."
: "There is something rotten in your inventory.";
break;
// level 3 saprovores like it
case 3:
temp_rand = random2(8);
msg = (temp_rand < 5) ? "You smell something rotten." :
(temp_rand == 5) ? "The smell of rotting flesh makes you hungry." :
(temp_rand == 6) ? "You smell decay. Yum-yum."
: "Wow! There is something tasty in your inventory.";
break;
default:
temp_rand = random2(8);
msg = (temp_rand < 5) ? "You smell something rotten." :
(temp_rand == 5) ? "The smell of rotting flesh makes you sick." :
(temp_rand == 6) ? "You smell decay. Yuck!"
: "Ugh! There is something really disgusting in your inventory.";
break;
}
}
else if (Options.list_rotten)
msg = "Something in your inventory has become rotten.";
if (Options.list_rotten)
{
mprf(MSGCH_ROTTEN_MEAT, "%s (slot%s %s)",
msg.c_str(),
rotten_items.size() > 1 ? "s" : "",
comma_separated_line(rotten_items.begin(),
rotten_items.end()).c_str());
}
else if (!msg.empty())
mpr(msg.c_str(), MSGCH_ROTTEN_MEAT);
learned_something_new(TUT_ROTTEN_FOOD);
}
if (burden_changed_by_rot)
{
mpr("Your equipment suddenly weighs less.", MSGCH_ROTTEN_MEAT);
burden_change();
}
}
// Do various time related actions...
// This function is called about every 20 turns.
void handle_time(long time_delta)
{
// Nasty things happen to people who spend too long in Hell.
if (player_in_hell() && coinflip())
_hell_effects();
// Adjust the player's stats if s/he's diseased (or recovering).
if (!you.disease)
{
if (you.strength < you.max_strength && one_chance_in(100))
restore_stat(STAT_STRENGTH, 0, false, true);
if (you.intel < you.max_intel && one_chance_in(100))
restore_stat(STAT_INTELLIGENCE, 0, false, true);
if (you.dex < you.max_dex && one_chance_in(100))
restore_stat(STAT_DEXTERITY, 0, false, true);
}
else
{
if (one_chance_in(30))
{
mpr("Your disease is taking its toll.", MSGCH_WARN);
lose_stat(STAT_RANDOM, 1, false, "disease");
}
}
// Adjust the player's stats if s/he has the deterioration mutation.
if (player_mutation_level(MUT_DETERIORATION)
&& x_chance_in_y(player_mutation_level(MUT_DETERIORATION) * 5 - 1, 200))
{
lose_stat(STAT_RANDOM, 1, false, "deterioration mutation");
}
int added_contamination = 0;
// Account for mutagenic radiation. Invis and haste will give the
// player about .1 points per turn, mutagenic randarts will give
// about 1.5 points on average, so they can corrupt the player
// quite quickly. Wielding one for a short battle is OK, which is
// as things should be. -- GDL
if (you.duration[DUR_INVIS] && x_chance_in_y(6, 10))
added_contamination++;
if (you.duration[DUR_HASTE] && !you.duration[DUR_BERSERKER]
&& x_chance_in_y(6, 10))
{
added_contamination++;
}
bool mutagenic_randart = false;
if (const int randart_glow = scan_randarts(RAP_MUTAGENIC))
{
// Reduced randart glow. Note that one randart will contribute
// 2 - 5 units of glow to randart_glow. A randart with a mutagen
// index of 2 does about 0.58 points of contamination per turn.
// A randart with a mutagen index of 5 does about 0.7 points of
// contamination per turn.
const int mean_glow = 500 + randart_glow * 40;
const int actual_glow = mean_glow / 2 + random2(mean_glow);
added_contamination += div_rand_round(actual_glow, 1000);
mutagenic_randart = true;
}
// we take off about .5 points per turn
if (!you.duration[DUR_INVIS] && !you.duration[DUR_HASTE] && coinflip())
added_contamination--;
// only punish if contamination caused by mutagenic randarts
// (haste and invisibility already penalized earlier)
contaminate_player( added_contamination, mutagenic_randart );
// only check for badness once every other turn
if (coinflip())
{
// [ds] Be less harsh with glow mutation; Brent and Mark Mackey note
// that the commented out random2(X) <= MC check was a bug. I've
// uncommented it but dropped the roll sharply from 150. (Brent used
// the original roll of 150 for 4.1.2, but I think players are
// sufficiently used to beta 26's unkindness that we can use a lower
// roll.)
if (is_sanctuary(you.x_pos, you.y_pos)
&& you.magic_contamination >= 5
&& x_chance_in_y(you.magic_contamination + 1, 25))
{
mpr("Your body momentarily shudders from a surge of wild "
"energies until Zin's power calms it.", MSGCH_GOD);
}
else if (you.magic_contamination >= 5
&& x_chance_in_y(you.magic_contamination + 1, 25))
{
mpr("Your body shudders with the violent release "
"of wild energies!", MSGCH_WARN);
// For particularly violent releases, make a little boom.
if (you.magic_contamination >= 10 && coinflip())
{
struct bolt boom;
boom.type = dchar_glyph(DCHAR_FIRED_BURST);
boom.colour = BLACK;
boom.flavour = BEAM_RANDOM;
boom.target_x = you.x_pos;
boom.target_y = you.y_pos;
// Undead enjoy extra contamination explosion damage because
// the magical contamination has a harder time dissipating
// through non-living flesh. :-)
boom.damage = dice_def(3, you.magic_contamination
* (you.is_undead ? 4 : 2) / 4);
boom.thrower = KILL_MISC;
boom.aux_source = "a magical explosion";
boom.beam_source = NON_MONSTER;
boom.is_beam = false;
boom.is_tracer = false;
boom.is_explosion = true;
boom.name = "magical storm";
boom.ench_power = (you.magic_contamination * 5);
boom.ex_size = std::min(9, you.magic_contamination / 15);
explosion(boom);
}
// we want to warp the player, not do good stuff!
if (one_chance_in(5))
mutate(RANDOM_MUTATION);
else
give_bad_mutation(true, coinflip());
// we're meaner now, what with explosions and whatnot, but
// we dial down the contamination a little faster if its actually
// mutating you. -- GDL
contaminate_player(-(random2(you.magic_contamination / 4) + 1));
}
}
// Random chance to identify staff in hand based off of Spellcasting
// and an appropriate other spell skill... is 1/20 too fast?
if (you.equip[EQ_WEAPON] != -1
&& you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_STAVES
&& !item_type_known( you.inv[you.equip[EQ_WEAPON]] )
&& one_chance_in(20))
{
int total_skill = you.skills[SK_SPELLCASTING];
switch (you.inv[you.equip[EQ_WEAPON]].sub_type)
{
case STAFF_WIZARDRY:
case STAFF_ENERGY:
total_skill += you.skills[SK_SPELLCASTING];
break;
case STAFF_FIRE:
if (you.skills[SK_FIRE_MAGIC] > you.skills[SK_ICE_MAGIC])
total_skill += you.skills[SK_FIRE_MAGIC];
else
total_skill += you.skills[SK_ICE_MAGIC];
break;
case STAFF_COLD:
if (you.skills[SK_ICE_MAGIC] > you.skills[SK_FIRE_MAGIC])
total_skill += you.skills[SK_ICE_MAGIC];
else
total_skill += you.skills[SK_FIRE_MAGIC];
break;
case STAFF_AIR:
if (you.skills[SK_AIR_MAGIC] > you.skills[SK_EARTH_MAGIC])
total_skill += you.skills[SK_AIR_MAGIC];
else
total_skill += you.skills[SK_EARTH_MAGIC];
break;
case STAFF_EARTH:
if (you.skills[SK_EARTH_MAGIC] > you.skills[SK_AIR_MAGIC])
total_skill += you.skills[SK_EARTH_MAGIC];
else
total_skill += you.skills[SK_AIR_MAGIC];
break;
case STAFF_POISON:
total_skill += you.skills[SK_POISON_MAGIC];
break;
case STAFF_DEATH:
total_skill += you.skills[SK_NECROMANCY];
break;
case STAFF_CONJURATION:
total_skill += you.skills[SK_CONJURATIONS];
break;
case STAFF_ENCHANTMENT:
total_skill += you.skills[SK_ENCHANTMENTS];
break;
case STAFF_SUMMONING:
total_skill += you.skills[SK_SUMMONINGS];
break;
}
if (x_chance_in_y(total_skill, 100))
{
item_def& item = you.inv[you.equip[EQ_WEAPON]];
set_ident_type( OBJ_STAVES, item.sub_type, ID_KNOWN_TYPE );
set_ident_flags( item, ISFLAG_IDENT_MASK );
mprf("You are wielding %s.", item.name(DESC_NOCAP_A).c_str());
more();
you.wield_change = true;
}
}
// Check to see if an upset god wants to do something to the player.
handle_god_time();
if (player_mutation_level(MUT_SCREAM)
&& x_chance_in_y(3 + player_mutation_level(MUT_SCREAM) * 3, 100))
{
yell(true);
}
// Update all of the corpses, food chunks and potions of blood on
// the floor.
update_corpses(time_delta);
_rot_inventory_food(time_delta);
// Exercise armour *xor* stealth skill: {dlb}
if (!player_light_armour(true))
{
// lowered random roll from 7 to 6 -- bwross
if (random2(1000) > item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]])
&& one_chance_in(6))
{
exercise(SK_ARMOUR, 1);
}
}
// Exercise stealth skill:
else if (you.burden_state == BS_UNENCUMBERED
&& !you.duration[DUR_BERSERKER]
&& you.special_wield != SPWLD_SHADOW)
{
// Diminishing returns for stealth training by waiting.
if ((you.equip[EQ_BODY_ARMOUR] == -1
|| you.equip[EQ_BODY_ARMOUR] != -1
&& random2(item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]])) < 100)
&& you.skills[SK_STEALTH] <= 2 + random2(3) && one_chance_in(18))
{
exercise(SK_STEALTH, 1);
}
}
spawn_random_monsters();
}
// Move monsters around to fake them walking around while player was
// off-level.
static void _catchup_monster_moves(monsters *mon, int turns)
{
// Summoned monsters might have disappeared.
if (!mon->alive())
return;
// Don't move non-land or stationary monsters around.
if (mons_habitat(mon) != HT_LAND
|| mons_is_zombified(mon)
&& mons_habitat_by_type(mon->base_monster) != HT_LAND
|| mons_is_stationary(mon))
{
return;
}
// Let sleeping monsters lie.
if (mons_is_sleeping(mon) || mons_is_paralysed(mon))
return;
const int range = (turns * mon->speed) / 10;
const int moves = (range > 50) ? 50 : range;
// const bool short_time = (range >= 5 + random2(10));
const bool long_time = (range >= (500 + roll_dice( 2, 500 )));
const bool ranged_attack = (mons_has_ranged_spell( mon )
|| mons_has_ranged_attack( mon ));
#if DEBUG_DIAGNOSTICS
// probably too annoying even for DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"mon #%d: range %d; long %d; "
"pos (%d,%d); targ %d(%d,%d); flags %ld",
monster_index(mon), range, long_time, mon->x, mon->y,
mon->foe, mon->target_x, mon->target_y, mon->flags );
#endif
if (range <= 0)
return;
if (long_time
&& (mons_is_fleeing(mon)
|| mons_is_cornered(mon)
|| mons_is_batty(mon)
|| ranged_attack
|| coinflip()))
{
if (!mons_is_wandering(mon))
{
mon->behaviour = BEH_WANDER;
mon->foe = MHITNOT;
mon->target_x = 10 + random2( GXM - 10 );
mon->target_y = 10 + random2( GYM - 10 );
}
else
{
// monster will be sleeping after we move it
mon->behaviour = BEH_SLEEP;
}
}
else if (ranged_attack)
{
// If we're doing short time movement and the monster has a
// ranged attack (missile or spell), then the monster will
// flee to gain distance if its "too close", else it will
// just shift its position rather than charge the player. -- bwr
if (grid_distance(mon->x, mon->y, mon->target_x, mon->target_y) < 3)
{
mon->behaviour = BEH_FLEE;
// If the monster is on the target square, fleeing won't work.
if (mon->x == mon->target_x && mon->y == mon->target_y)
{
if (you.x_pos != mon->x || you.y_pos != mon->y)
{
// Flee from player's position if different.
mon->target_x = you.x_pos;
mon->target_y = you.y_pos;
}
else
{
// Randomize the target so we have a direction to flee.
mon->target_x += (random2(3) - 1);
mon->target_y += (random2(3) - 1);
}
}
#if DEBUG_DIAGNOSTICS
mpr( "backing off...", MSGCH_DIAGNOSTICS );
#endif
}
else
{
shift_monster( mon, mon->x, mon->y );
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "shifted to (%d,%d)", mon->x, mon->y);
#endif
return;
}
}
coord_def pos(mon->pos());
// dirt simple movement:
for (int i = 0; i < moves; i++)
{
coord_def inc(mon->target_pos() - pos);
inc = coord_def(sgn(inc.x), sgn(inc.y));
if (mons_is_fleeing(mon))
inc *= -1;
if (pos.x + inc.x < 0 || pos.x + inc.x >= GXM)
inc.x = 0;
if (pos.y + inc.y < 0 || pos.y + inc.y >= GYM)
inc.y = 0;
if (inc.origin())
break;
const coord_def next(pos + inc);
const dungeon_feature_type feat = grd(next);
if (grid_is_solid(feat)
|| mgrd(next) != NON_MONSTER
|| !monster_habitable_grid(mon, feat))
break;
pos = next;
}
if (!shift_monster( mon, pos.x, pos.y ))
shift_monster( mon, mon->x, mon->y );
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "moved to (%d,%d)", mon->x, mon->y );
#endif
}
//---------------------------------------------------------------
//
// update_level
//
// Update the level when the player returns to it.
//
//---------------------------------------------------------------
void update_level(double elapsedTime)
{
const int turns = static_cast<int>(elapsedTime / 10.0);
#if DEBUG_DIAGNOSTICS
int mons_total = 0;
mprf(MSGCH_DIAGNOSTICS, "turns: %d", turns );
#endif
update_corpses(elapsedTime);
if (env.sanctuary_time)
{
if (turns >= env.sanctuary_time)
remove_sanctuary();
else
env.sanctuary_time -= turns;
}
dungeon_events.fire_event(
dgn_event(DET_TURN_ELAPSED, coord_def(0, 0), turns * 10));
for (int m = 0; m < MAX_MONSTERS; m++)
{
monsters *mon = &menv[m];
if (!mon->alive())
continue;
#if DEBUG_DIAGNOSTICS
mons_total++;
#endif
// Pacified monsters often leave the level now.
if (mons_is_pacified(mon) && turns > random2(40) + 21)
{
make_mons_leave_level(mon);
continue;
}
// Following monsters don't get movement.
if (mon->flags & MF_JUST_SUMMONED)
continue;
// XXX: Allow some spellcasting (like Healing and Teleport)? -- bwr
// const bool healthy = (mon->hit_points * 2 > mon->max_hit_points);
// This is the monster healing code, moved here from tag.cc:
if (monster_descriptor(mon->type, MDSC_REGENERATES)
|| mon->type == MONS_PLAYER_GHOST)
{
heal_monster(mon, turns, false);
}
else if (!mons_class_flag(mon->type, M_NO_REGEN))
{
// Set a lower ceiling of 0.1 on the regen rate.
const int regen_rate =
std::max(mons_natural_regen_rate(mon) * 2, 5);
heal_monster(mon, div_rand_round(turns * regen_rate, 50),
false);
}
_catchup_monster_moves(mon, turns);
if (turns >= 10 && mon->alive())
mon->timeout_enchantments(turns / 10);
}
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "total monsters on level = %d", mons_total );
#endif
for (int i = 0; i < MAX_CLOUDS; i++)
delete_cloud(i);
}
static void _maybe_restart_fountain_flow(const int x, const int y,
const int tries)
{
dungeon_feature_type grid = grd[x][y];
if (grid < DNGN_DRY_FOUNTAIN_BLUE || grid > DNGN_DRY_FOUNTAIN_BLOOD)
return;
int t = 0;
while (tries > t++)
{
if (!one_chance_in(100))
continue;
// Make it start flowing again.
grd[x][y] = static_cast<dungeon_feature_type> (grid
- (DNGN_DRY_FOUNTAIN_BLUE - DNGN_FOUNTAIN_BLUE));
if (is_terrain_seen(coord_def(x,y)))
set_envmap_obj(x, y, grd[x][y]);
// Clean bloody floor.
if (is_bloodcovered(x,y))
env.map[x][y].property = FPROP_NONE;
// Chance of cleaning adjacent squares.
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
if (is_bloodcovered(x+i,y+j)
&& one_chance_in(5))
{
env.map[x+i][y+j].property = FPROP_NONE;
}
}
return;
}
}
//---------------------------------------------------------------
//
// update_corpses
//
// Update all of the corpses and food chunks on the floor. (The
// elapsed time is a double because this is called when we re-
// enter a level and a *long* time may have elapsed).
//
//---------------------------------------------------------------
void update_corpses(double elapsedTime)
{
int cx, cy;
if (elapsedTime <= 0.0)
return;
const long rot_time = static_cast<long>(elapsedTime / 20.0);
for (int c = 0; c < MAX_ITEMS; c++)
{
item_def &it = mitm[c];
if (!_food_item_needs_time_check(it))
continue;
if (it.base_type == OBJ_POTIONS)
{
maybe_coagulate_blood_potions_floor(c);
continue;
}
if (rot_time >= it.special && !is_being_butchered(it))
{
if (it.base_type == OBJ_FOOD)
{
destroy_item(c);
}
else
{
if (it.sub_type == CORPSE_SKELETON
|| !mons_skeleton( it.plus ))
{
destroy_item(c);
}
else
turn_corpse_into_skeleton(it);
}
}
else
{
it.special -= rot_time;
}
}
int fountain_checks = static_cast<int>(elapsedTime / 1000.0);
if (x_chance_in_y(static_cast<int>(elapsedTime) % 1000, 1000))
fountain_checks += 1;
// dry fountains may start flowing again
if (fountain_checks > 0)
{
for (cx = 0; cx < GXM; cx++)
for (cy = 0; cy < GYM; cy++)
{
if (grd[cx][cy] >= DNGN_DRY_FOUNTAIN_BLUE
&& grd[cx][cy] < DNGN_PERMADRY_FOUNTAIN)
{
_maybe_restart_fountain_flow(cx, cy, fountain_checks);
}
}
}
}