#include "AppHdr.h"
#include "spells2.h"
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <algorithm>
#include <queue>
#include "externs.h"
#include "artefact.h"
#include "beam.h"
#include "cloud.h"
#include "delay.h"
#include "directn.h"
#include "dungeon.h"
#include "effects.h"
#include "goditem.h"
#include "invent.h"
#include "itemname.h"
#include "itemprop.h"
#include "items.h"
#include "it_use2.h"
#include "los.h"
#include "message.h"
#include "misc.h"
#include "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
#include "ouch.h"
#include "player.h"
#include "religion.h"
#include "spells4.h"
#include "spl-mis.h"
#include "stuff.h"
#include "tiles.h"
#include "terrain.h"
#include "traps.h"
#include "view.h"
int detect_traps(int pow)
{
pow = std::min(50, pow);
int traps_found = 0;
const int range = 8 + random2(8) + pow;
for (int i = 0; i < MAX_TRAPS; i++)
{
trap_def& trap = env.trap[i];
if (!trap.active())
continue;
if (grid_distance(you.pos(), trap.pos) < range && !trap.is_known())
{
traps_found++;
trap.reveal();
set_envmap_obj(trap.pos, grd(trap.pos));
set_terrain_mapped(trap.pos);
}
}
return (traps_found);
}
int detect_items(int pow)
{
int items_found = 0;
const int map_radius = 8 + random2(8) + pow;
for (radius_iterator ri(you.pos(), map_radius, true, false); ri; ++ri)
{
if (is_terrain_changed(*ri))
continue;
if (igrd(*ri) != NON_ITEM
&& (!get_envmap_obj(*ri) || !is_envmap_item(*ri)))
{
items_found++;
set_envmap_obj(*ri, DNGN_ITEM_DETECTED);
set_envmap_detected_item(*ri);
#ifdef USE_TILE
if (!is_terrain_seen(*ri) && !is_terrain_mapped(*ri))
env.tile_bk_fg[ri->x][ri->y] = TILE_UNSEEN_ITEM;
#endif
}
}
return (items_found);
}
static void _fuzz_detect_creatures(int pow, int *fuzz_radius, int *fuzz_chance)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "dc_fuzz: Power is %d", pow);
#endif
pow = std::max(1, pow);
*fuzz_radius = pow >= 50 ? 1 : 2;
*fuzz_chance = 100 - 90 * (pow - 1) / 59;
if (*fuzz_chance < 10)
*fuzz_chance = 10;
}
static bool _mark_detected_creature(coord_def where, const monsters *mon,
int fuzz_chance, int fuzz_radius)
{
#ifdef USE_TILE
int idx = mgrd(where);
#endif
bool found_good = false;
if (fuzz_radius && x_chance_in_y(fuzz_chance, 100))
{
const int fuzz_diam = 2 * fuzz_radius + 1;
coord_def place;
for (int itry = 0; itry < 5; ++itry)
{
place.set(where.x + random2(fuzz_diam) - fuzz_radius,
where.y + random2(fuzz_diam) - fuzz_radius);
if (see_cell(place))
continue;
if (is_envmap_detected_mons(place))
continue;
if (map_bounds(place) && !is_terrain_changed(place)
&& mon->can_pass_through_feat(grd(place)))
{
found_good = true;
break;
}
}
if (found_good)
where = place;
}
set_envmap_obj(where, mon->type + DNGN_START_OF_MONSTERS);
set_envmap_detected_mons(where);
#ifdef USE_TILE
tile_place_monster(where.x, where.y, idx, false, true);
#endif
return (found_good);
}
int detect_creatures(int pow, bool telepathic)
{
int fuzz_radius = 0, fuzz_chance = 0;
if (!telepathic)
_fuzz_detect_creatures(pow, &fuzz_radius, &fuzz_chance);
int creatures_found = 0;
const int map_radius = 8 + random2(8) + pow;
if (!telepathic)
clear_map(false);
for (radius_iterator ri(you.pos(), map_radius, true, false); ri; ++ri)
{
if (monsters *mon = monster_at(*ri))
{
if (!mons_near(mon) || !mon->visible_to(&you))
{
creatures_found++;
_mark_detected_creature(*ri, mon, fuzz_chance, fuzz_radius);
}
if (mons_intel(mon) == I_HIGH
&& mons_class_flag(mon->type, M_SPELLCASTER))
{
behaviour_event(mon, ME_DISTURB, MHITYOU, you.pos());
}
}
}
return (creatures_found);
}
void corpse_rot()
{
for (radius_iterator ri(you.pos(), 6); ri; ++ri)
{
if (see_cell_no_trans(*ri) && !is_sanctuary(*ri)
&& env.cgrid(*ri) == EMPTY_CLOUD)
{
for (stack_iterator si(*ri); si; ++si)
{
if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY)
{
if (!mons_skeleton(si->plus))
destroy_item(si->index());
else
turn_corpse_into_skeleton(*si);
place_cloud(CLOUD_MIASMA, *ri, 4+random2avg(16, 3), KC_YOU);
break;
}
}
}
}
if (player_can_smell())
mpr("You smell decay.");
}
bool brand_weapon(brand_type which_brand, int power)
{
if (!you.weapon())
return (false);
const bool temp_brand = you.duration[DUR_WEAPON_BRAND];
item_def& weapon = *you.weapon();
if (weapon.base_type != OBJ_WEAPONS || is_range_weapon(weapon))
return (false);
if (is_artefact(weapon))
return (false);
if (!temp_brand && get_weapon_brand(weapon) != SPWPN_NORMAL)
return (false);
if (temp_brand && (get_weapon_brand(weapon) != which_brand))
return (false);
std::string msg = weapon.name(DESC_CAP_YOUR);
const int wpn_type = get_vorpal_type(weapon);
bool emit_special_message = !temp_brand;
int duration_affected = 0;
switch (which_brand)
{
case SPWPN_FLAMING:
msg += " bursts into flame!";
duration_affected = 7;
break;
case SPWPN_FREEZING:
msg += " glows blue.";
duration_affected = 7;
break;
case SPWPN_VENOM:
if (wpn_type == DVORP_CRUSHING)
return (false);
msg += " starts dripping with poison.";
duration_affected = 15;
break;
case SPWPN_DRAINING:
msg += " crackles with unholy energy.";
duration_affected = 12;
break;
case SPWPN_VORPAL:
if (wpn_type != DVORP_SLICING)
return (false);
msg += " glows silver and looks extremely sharp.";
duration_affected = 10;
break;
case SPWPN_DISTORTION: msg += " seems to ";
msg += random_choose_string("twist", "bend", "vibrate",
"flex", "wobble", "twang", NULL);
msg += (coinflip() ? " oddly." : " strangely.");
duration_affected = 5;
power = 2;
break;
case SPWPN_PAIN:
msg += " shrieks in agony.";
noisy(15, you.pos());
duration_affected = 8;
emit_special_message = true;
break;
case SPWPN_DUMMY_CRUSHING: if (wpn_type != DVORP_CRUSHING)
return (false);
which_brand = SPWPN_VORPAL;
msg += " glows silver and feels heavier.";
duration_affected = 7;
break;
default:
break;
}
if (!temp_brand)
{
set_item_ego_type(weapon, OBJ_WEAPONS, which_brand);
you.wield_change = true;
}
if (emit_special_message)
mpr(msg.c_str());
else
mprf("%s flashes.", weapon.name(DESC_CAP_YOUR).c_str());
const int dur_change = duration_affected + roll_dice(2, power);
you.duration[DUR_WEAPON_BRAND] += dur_change;
if (you.duration[DUR_WEAPON_BRAND] > 50)
you.duration[DUR_WEAPON_BRAND] = 50;
return (true);
}
bool restore_stat(unsigned char which_stat, unsigned char stat_gain,
bool suppress_msg, bool recovery)
{
bool stat_restored = false;
if (which_stat == STAT_ALL)
{
for (unsigned char loopy = STAT_STRENGTH; loopy < NUM_STATS; ++loopy)
if (restore_stat(loopy, stat_gain, suppress_msg))
stat_restored = true;
return (stat_restored); }
char *ptr_stat = NULL;
char *ptr_stat_max = NULL;
bool *ptr_redraw = NULL;
std::string msg = "You feel your ";
if (which_stat == STAT_RANDOM)
which_stat = random2(NUM_STATS);
switch (which_stat)
{
case STAT_STRENGTH:
msg += "strength";
ptr_stat = &you.strength;
ptr_stat_max = &you.max_strength;
ptr_redraw = &you.redraw_strength;
break;
case STAT_INTELLIGENCE:
msg += "intelligence";
ptr_stat = &you.intel;
ptr_stat_max = &you.max_intel;
ptr_redraw = &you.redraw_intelligence;
break;
case STAT_DEXTERITY:
msg += "dexterity";
ptr_stat = &you.dex;
ptr_stat_max = &you.max_dex;
ptr_redraw = &you.redraw_dexterity;
break;
}
if (*ptr_stat < *ptr_stat_max)
{
msg += " returning.";
if (!suppress_msg)
mpr(msg.c_str(), (recovery) ? MSGCH_RECOVERY : MSGCH_PLAIN);
if (stat_gain == 0 || *ptr_stat + stat_gain > *ptr_stat_max)
stat_gain = *ptr_stat_max - *ptr_stat;
if (stat_gain != 0)
{
*ptr_stat += stat_gain;
*ptr_redraw = true;
stat_restored = true;
if (ptr_stat == &you.strength)
burden_change();
}
}
return (stat_restored);
}
typedef std::pair<const monsters*,int> counted_monster;
typedef std::vector<counted_monster> counted_monster_list;
static void _record_monster_by_name(counted_monster_list &list,
const monsters *mons)
{
const std::string name = mons->name(DESC_PLAIN);
for (counted_monster_list::iterator i = list.begin(); i != list.end(); ++i)
{
if (i->first->name(DESC_PLAIN) == name)
{
i->second++;
return;
}
}
list.push_back(counted_monster(mons, 1));
}
static int _monster_count(const counted_monster_list &list)
{
int nmons = 0;
for (counted_monster_list::const_iterator i = list.begin();
i != list.end(); ++i)
{
nmons += i->second;
}
return (nmons);
}
static std::string _describe_monsters(const counted_monster_list &list)
{
std::ostringstream out;
description_level_type desc = DESC_CAP_THE;
for (counted_monster_list::const_iterator i = list.begin();
i != list.end(); desc = DESC_NOCAP_THE)
{
const counted_monster &cm(*i);
if (i != list.begin())
{
++i;
out << (i == list.end() ? " and " : ", ");
}
else
++i;
const std::string name =
cm.second > 1 ? pluralise(cm.first->name(desc))
: cm.first->name(desc);
out << name;
}
return (out.str());
}
void cast_toxic_radiance()
{
mpr("You radiate a sickly green light!");
you.flash_colour = GREEN;
viewwindow(true, false);
more();
mesclr();
if (you.duration[DUR_INVIS])
{
mpr("The light passes straight through your body.");
}
else if (!player_res_poison())
{
mpr("You feel rather sick.");
poison_player(2);
}
counted_monster_list affected_monsters;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters* const monster = &menv[i];
if (monster->alive() && mons_near(monster) && !monster->submerged())
{
if (!monster->has_ench(ENCH_INVIS))
{
bool affected =
poison_monster(monster, KC_YOU, 1, false, false);
if (coinflip() && poison_monster(monster, KC_YOU, false, false))
affected = true;
if (affected)
_record_monster_by_name(affected_monsters, monster);
}
else if (you.can_see_invisible())
{
mprf("The light passes through %s.",
monster->name(DESC_NOCAP_THE).c_str());
}
}
}
if (!affected_monsters.empty())
{
const std::string message =
make_stringf("%s %s poisoned.",
_describe_monsters(affected_monsters).c_str(),
_monster_count(affected_monsters) == 1? "is" : "are");
if (static_cast<int>(message.length()) < get_number_of_cols() - 2)
mpr(message.c_str());
else
{
mpr("The monsters around you are poisoned!");
}
}
}
void cast_refrigeration(int pow)
{
mpr("The heat is drained from your surroundings.");
you.flash_colour = LIGHTCYAN;
viewwindow(true, false);
more();
mesclr();
const dice_def dam_dice(3, 5 + pow / 10);
const int hurted = check_your_resists(dam_dice.roll(), BEAM_COLD);
if (hurted > 0)
{
mpr("You feel very cold.");
ouch(hurted, NON_MONSTER, KILLED_BY_FREEZING);
expose_player_to_element(BEAM_COLD, 5);
}
counted_monster_list affected_monsters;
for (int i = 0; i < MAX_MONSTERS; i++)
{
const monsters* const monster = &menv[i];
if (monster->alive() && you.can_see(monster))
_record_monster_by_name(affected_monsters, monster);
}
if (!affected_monsters.empty())
{
const std::string message =
make_stringf("%s %s frozen.",
_describe_monsters(affected_monsters).c_str(),
_monster_count(affected_monsters) == 1? "is" : "are");
if (static_cast<int>(message.length()) < get_number_of_cols() - 2)
mpr(message.c_str());
else
{
mpr("The monsters around you are frozen!");
}
}
bolt beam;
beam.flavour = BEAM_COLD;
beam.thrower = KILL_YOU;
for (int i = 0; i < MAX_MONSTERS; i++)
{
monsters* const monster = &menv[i];
if (monster->alive() && mons_near(monster))
{
int hurt = mons_adjust_flavoured(monster, beam, dam_dice.roll());
monster->hurt(&you, hurt, BEAM_COLD);
if (monster->alive()
&& mons_class_flag(monster->type, M_COLD_BLOOD)
&& coinflip())
{
monster->add_ench(ENCH_SLOW);
}
}
}
}
void drain_life(int pow)
{
mpr("You draw life from your surroundings.");
ASSERT(pow <= 27);
you.flash_colour = DARKGREY;
viewwindow(true, false);
more();
mesclr();
int hp_gain = 0;
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters* monster = &menv[i];
if (!monster->alive()
|| monster->holiness() != MH_NATURAL
|| monster->res_negative_energy())
{
continue;
}
if (mons_near(monster))
{
mprf("You draw life from %s.",
monster->name(DESC_NOCAP_THE).c_str());
const int hurted = 3 + random2(7) + random2(pow);
behaviour_event(monster, ME_WHACK, MHITYOU, you.pos());
if (!mons_is_summoned(monster))
hp_gain += hurted;
monster->hurt(&you, hurted);
if (monster->alive())
print_wounds(monster);
}
}
hp_gain /= 2;
hp_gain = std::min(pow * 2, hp_gain);
if (hp_gain)
{
mpr("You feel life flooding into your body.");
inc_hp(hp_gain, false);
}
}
bool vampiric_drain(int pow, const dist &vmove)
{
monsters *monster = monster_at(you.pos() + vmove.delta);
if (monster == NULL)
{
mpr("There isn't anything there!");
return (false);
}
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
const bool success = !stop_attack_prompt(monster, false, you.pos());
if (success)
{
set_attack_conducts(conducts, monster);
behaviour_event(monster, ME_WHACK, MHITYOU, you.pos());
}
enable_attack_conducts(conducts);
if (success)
{
if (!monster->alive())
{
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
if (monster->is_unholy())
{
mpr("Aaaarggghhhhh!");
dec_hp(random2avg(39, 2) + 10, false, "vampiric drain backlash");
return (false);
}
if (monster->holiness() != MH_NATURAL
|| monster->res_negative_energy())
{
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
int hp_gain = 3 + random2avg(9, 2) + random2(pow) / 7;
hp_gain = std::min(monster->hit_points, hp_gain);
hp_gain = std::min(you.hp_max - you.hp, hp_gain);
if (!hp_gain)
{
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
const bool mons_was_summoned = mons_is_summoned(monster);
monster->hurt(&you, hp_gain);
if (monster->alive())
print_wounds(monster);
hp_gain /= 2;
if (hp_gain && !mons_was_summoned)
{
mpr("You feel life coursing into your body.");
inc_hp(hp_gain, false);
}
}
return (success);
}
bool burn_freeze(int pow, beam_type flavour, monsters *monster)
{
pow = std::min(25, pow);
if (monster == NULL)
{
mpr("There isn't anything close enough!");
return (true);
}
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
const bool success = !stop_attack_prompt(monster, false, you.pos());
if (success)
{
set_attack_conducts(conducts, monster);
mprf("You %s %s.",
(flavour == BEAM_FIRE) ? "burn" :
(flavour == BEAM_COLD) ? "freeze" :
(flavour == BEAM_MISSILE) ? "crush" :
(flavour == BEAM_ELECTRICITY) ? "zap"
: "______",
monster->name(DESC_NOCAP_THE).c_str());
behaviour_event(monster, ME_ANNOY, MHITYOU);
}
enable_attack_conducts(conducts);
if (success)
{
bolt beam;
beam.flavour = flavour;
beam.thrower = KILL_YOU;
const int orig_hurted = roll_dice(1, 3 + pow / 3);
int hurted = mons_adjust_flavoured(monster, beam, orig_hurted);
monster->hurt(&you, hurted);
if (monster->alive())
{
monster->expose_to_element(flavour, orig_hurted);
print_wounds(monster);
if (flavour == BEAM_COLD)
{
const int cold_res = monster->res_cold();
if (cold_res <= 0)
{
const int stun = (1 - cold_res) * random2(2 + pow/5);
monster->speed_increment -= stun;
}
}
}
}
return (success);
}
bool summon_animals(int pow)
{
bool success = false;
const monster_type animals[] = {
MONS_BUMBLEBEE, MONS_WAR_DOG, MONS_SHEEP, MONS_YAK,
MONS_HOG, MONS_SOLDIER_ANT, MONS_WOLF,
MONS_GRIZZLY_BEAR, MONS_POLAR_BEAR, MONS_BLACK_BEAR,
MONS_GIANT_SNAIL, MONS_BORING_BEETLE, MONS_GILA_MONSTER,
MONS_KOMODO_DRAGON, MONS_SPINY_FROG, MONS_HOUND
};
int count = 0;
const int count_max = 8;
int pow_left = pow + 1;
const bool varied = coinflip();
monster_type mon = MONS_PROGRAM_BUG;
while (pow_left >= 0 && count < count_max)
{
if (varied || count == 0)
mon = RANDOM_ELEMENT(animals);
const int pow_spent = mons_power(mon) * 3;
if (pow_spent >= pow_left * 2 && count >= 2)
break;
pow_left -= pow_spent;
count++;
const bool friendly = (random2(pow) > 4);
if (create_monster(
mgen_data(mon,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
4, 0, you.pos(), MHITYOU)) != -1)
{
success = true;
}
}
return (success);
}
bool cast_summon_butterflies(int pow, god_type god)
{
bool success = false;
const int how_many = std::max(15, 4 + random2(3) + random2(pow) / 10);
for (int i = 0; i < how_many; ++i)
{
if (create_monster(
mgen_data(MONS_BUTTERFLY, BEH_FRIENDLY,
3, SPELL_SUMMON_BUTTERFLIES,
you.pos(), MHITYOU,
0, god)) != -1)
{
success = true;
}
}
if (!success)
canned_msg(MSG_NOTHING_HAPPENS);
return (success);
}
bool cast_summon_small_mammals(int pow, god_type god)
{
bool success = false;
monster_type mon = MONS_PROGRAM_BUG;
int count = 0;
const int count_max = 2;
do
{
switch (random2(pow+1))
{
case 75: case 74: case 38:
mon = MONS_ORANGE_RAT;
break;
case 65: case 64: case 63: case 27: case 26: case 25:
mon = MONS_GREEN_RAT;
break;
case 57: case 56: case 55: case 54: case 53: case 52:
case 20: case 18: case 16: case 14: case 12: case 10:
mon = (coinflip()) ? MONS_QUOKKA : MONS_GREY_RAT;
break;
default:
mon = (coinflip()) ? MONS_GIANT_BAT : MONS_RAT;
break;
}
if (player_will_anger_monster(mon))
mon = MONS_GREEN_RAT;
count++;
if (create_monster(
mgen_data(mon, BEH_FRIENDLY,
3, SPELL_SUMMON_SMALL_MAMMALS,
you.pos(), MHITYOU,
0, god)) != -1)
{
success = true;
}
}
while (random2(pow+1) > 32 && count < count_max);
return (success);
}
bool cast_sticks_to_snakes(int pow, god_type god)
{
if (!you.weapon())
{
mprf("Your %s feel slithery!", your_hand(true).c_str());
return (false);
}
const item_def& wpn = *you.weapon();
if (!check_warning_inscriptions(wpn, OPER_DESTROY))
{
mprf("%s feel%s slithery for a moment!",
wpn.name(DESC_CAP_YOUR).c_str(),
wpn.quantity > 1 ? "s" : "");
return (false);
}
monster_type mon = MONS_PROGRAM_BUG;
const int dur = std::min(3 + random2(pow) / 20, 5);
int how_many_max = 1 + random2(1 + you.skills[SK_TRANSMUTATIONS]) / 4;
const bool friendly = (!item_cursed(wpn));
const beh_type beha = (friendly) ? BEH_FRIENDLY : BEH_HOSTILE;
int count = 0;
if (wpn.base_type == OBJ_MISSILES && wpn.sub_type == MI_ARROW)
{
if (wpn.quantity < how_many_max)
how_many_max = wpn.quantity;
for (int i = 0; i <= how_many_max; i++)
{
if (one_chance_in(5 - std::min(4, div_rand_round(pow * 2, 25)))
|| get_ammo_brand(wpn) == SPMSL_POISONED)
{
mon = x_chance_in_y(pow / 3, 100) ? MONS_WATER_MOCCASIN
: MONS_SNAKE;
}
else
mon = MONS_SMALL_SNAKE;
if (create_monster(
mgen_data(mon, beha,
dur, SPELL_STICKS_TO_SNAKES,
you.pos(), MHITYOU,
0, god)) != -1)
{
count++;
}
}
}
if (wpn.base_type == OBJ_WEAPONS
&& (wpn.sub_type == WPN_CLUB
|| wpn.sub_type == WPN_SPEAR
|| wpn.sub_type == WPN_QUARTERSTAFF
|| wpn.sub_type == WPN_SCYTHE
|| wpn.sub_type == WPN_GIANT_CLUB
|| wpn.sub_type == WPN_GIANT_SPIKED_CLUB
|| wpn.sub_type == WPN_BOW
|| wpn.sub_type == WPN_LONGBOW
|| wpn.sub_type == WPN_ANKUS
|| wpn.sub_type == WPN_HALBERD
|| wpn.sub_type == WPN_GLAIVE
|| wpn.sub_type == WPN_BLOWGUN))
{
if (item_mass(wpn) < 300)
mon = MONS_SNAKE;
else
mon = MONS_WATER_MOCCASIN;
if (pow > 20 && one_chance_in(3))
mon = MONS_WATER_MOCCASIN;
if (pow > 40 && one_chance_in(3))
mon = MONS_VIPER;
if (pow > 70 && one_chance_in(3))
mon = MONS_BLACK_MAMBA;
if (pow > 90 && one_chance_in(3))
mon = MONS_GREY_SNAKE;
if (create_monster(
mgen_data(mon, beha,
dur, SPELL_STICKS_TO_SNAKES,
you.pos(), MHITYOU,
0, god)) != -1)
{
count++;
}
}
if (wpn.quantity < count)
count = wpn.quantity;
if (count > 0)
{
dec_inv_item_quantity(you.equip[EQ_WEAPON], count);
mprf("You create %s snake%s!",
count > 1 ? "some" : "a",
count > 1 ? "s" : "");
return (true);
}
mprf("Your %s feel slithery!", your_hand(true).c_str());
return (false);
}
bool cast_summon_scorpions(int pow, god_type god)
{
bool success = false;
const int how_many = stepdown_value(1 + random2(pow)/10 + random2(pow)/10,
2, 2, 6, 8);
for (int i = 0; i < how_many; ++i)
{
bool friendly = (random2(pow) > 3);
if (create_monster(
mgen_data(MONS_SCORPION,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
3, SPELL_SUMMON_SCORPIONS,
you.pos(), MHITYOU,
0, god)) != -1)
{
success = true;
}
}
if (!success)
canned_msg(MSG_NOTHING_HAPPENS);
return (success);
}
bool cast_summon_swarm(int pow, god_type god,
bool force_hostile,
bool permanent)
{
bool success = false;
const int dur = permanent ? 0 : std::min(2 + (random2(pow) / 4), 6);
const int how_many = stepdown_value(2 + random2(pow)/10 + random2(pow)/25,
2, 2, 6, 8);
for (int i = 0; i < how_many; ++i)
{
const monster_type swarmers[] = {
MONS_KILLER_BEE, MONS_KILLER_BEE, MONS_KILLER_BEE,
MONS_SCORPION, MONS_WORM, MONS_GIANT_MOSQUITO,
MONS_GIANT_BEETLE, MONS_GIANT_BLOWFLY, MONS_WOLF_SPIDER,
MONS_BUTTERFLY, MONS_YELLOW_WASP, MONS_GIANT_ANT,
MONS_GIANT_ANT, MONS_GIANT_ANT
};
const monster_type mon = RANDOM_ELEMENT(swarmers);
const bool friendly = force_hostile ? false : (random2(pow) > 7);
if (create_monster(
mgen_data(mon,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
dur, !permanent ? SPELL_SUMMON_SWARM : 0,
you.pos(),
MHITYOU,
0, god)) != -1)
{
success = true;
}
}
if (!success)
canned_msg(MSG_NOTHING_HAPPENS);
return (success);
}
bool cast_call_canine_familiar(int pow, god_type god)
{
bool success = false;
monster_type mon = MONS_PROGRAM_BUG;
const int chance = random2(pow);
if (chance < 10)
mon = MONS_JACKAL;
else if (chance < 15)
mon = MONS_HOUND;
else
{
switch (chance % 7)
{
case 0:
if (one_chance_in(you.species == SP_HILL_ORC ? 3 : 6))
mon = MONS_WARG;
else
mon = MONS_WOLF;
break;
case 1:
case 2:
mon = MONS_WAR_DOG;
break;
case 3:
case 4:
mon = MONS_HOUND;
break;
default:
mon = MONS_JACKAL;
break;
}
}
const int dur = std::min(2 + (random2(pow) / 4), 6);
if (create_monster(
mgen_data(mon, BEH_FRIENDLY,
dur, SPELL_CALL_CANINE_FAMILIAR,
you.pos(),
MHITYOU,
0, god)) != -1)
{
success = true;
mpr("A canine appears!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
return (success);
}
bool cast_summon_elemental(int pow, god_type god,
monster_type restricted_type,
int unfriendly)
{
monster_type mon = MONS_PROGRAM_BUG;
coord_def targ;
dist smove;
const int dur = std::min(2 + (random2(pow) / 5), 6);
const bool any_elemental = (restricted_type == MONS_NO_MONSTER);
while (true)
{
mpr("Summon from material in which direction? ", MSGCH_PROMPT);
direction(smove, DIR_DIR, TARG_ANY);
if (!smove.isValid)
{
canned_msg(MSG_OK);
return (false);
}
targ = you.pos() + smove.delta;
if (const monsters *m = monster_at(targ))
{
if (you.can_see(m))
mpr("There's something there already!");
else
{
mpr("Something seems to disrupt your summoning.");
return (false);
}
}
else if (smove.delta.origin())
mpr("You can't summon an elemental from yourself!");
else if ((any_elemental || restricted_type == MONS_EARTH_ELEMENTAL)
&& (grd(targ) == DNGN_ROCK_WALL
|| grd(targ) == DNGN_CLEAR_ROCK_WALL))
{
if (!in_bounds(targ))
{
mpr("That wall won't yield to your beckoning.");
}
else
{
mon = MONS_EARTH_ELEMENTAL;
break;
}
}
else
break;
}
if (mon == MONS_EARTH_ELEMENTAL)
{
grd(targ) = DNGN_FLOOR;
}
else if (env.cgrid(targ) != EMPTY_CLOUD
&& env.cloud[env.cgrid(targ)].type == CLOUD_FIRE
&& (any_elemental || restricted_type == MONS_FIRE_ELEMENTAL))
{
mon = MONS_FIRE_ELEMENTAL;
delete_cloud(env.cgrid(targ));
}
else if (grd(targ) == DNGN_LAVA
&& (any_elemental || restricted_type == MONS_FIRE_ELEMENTAL))
{
mon = MONS_FIRE_ELEMENTAL;
}
else if (feat_is_watery(grd(targ))
&& (any_elemental || restricted_type == MONS_WATER_ELEMENTAL))
{
mon = MONS_WATER_ELEMENTAL;
}
else if (grd(targ) >= DNGN_FLOOR
&& env.cgrid(targ) == EMPTY_CLOUD
&& (any_elemental || restricted_type == MONS_AIR_ELEMENTAL))
{
mon = MONS_AIR_ELEMENTAL;
}
if (mon == MONS_PROGRAM_BUG)
{
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
const bool friendly = ((mon != MONS_FIRE_ELEMENTAL
|| x_chance_in_y(you.skills[SK_FIRE_MAGIC], 10))
&& (mon != MONS_WATER_ELEMENTAL
|| x_chance_in_y(you.skills[SK_ICE_MAGIC],
(you.species == SP_MERFOLK) ? 5
: 15))
&& (mon != MONS_AIR_ELEMENTAL
|| x_chance_in_y(you.skills[SK_AIR_MAGIC], 15))
&& (mon != MONS_EARTH_ELEMENTAL
|| x_chance_in_y(you.skills[SK_EARTH_MAGIC], 5))
&& random2(100) >= unfriendly);
if (create_monster(
mgen_data(mon,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
dur, SPELL_SUMMON_ELEMENTAL,
targ,
MHITYOU,
0, god)) == -1)
{
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
mpr("An elemental appears!");
if (!friendly)
mpr("It doesn't seem to appreciate being summoned.");
return (true);
}
bool cast_summon_ice_beast(int pow, god_type god)
{
monster_type mon = MONS_ICE_BEAST;
const int dur = std::min(2 + (random2(pow) / 4), 6);
if (create_monster(
mgen_data(mon, BEH_FRIENDLY,
dur, SPELL_SUMMON_ICE_BEAST,
you.pos(), MHITYOU,
0, god)) != -1)
{
mpr("A chill wind blows around you.");
return (true);
}
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
bool cast_summon_ugly_thing(int pow, god_type god)
{
const int chance = std::max(6 - (pow / 12), 1);
monster_type mon = (one_chance_in(chance)) ? MONS_VERY_UGLY_THING
: MONS_UGLY_THING;
const int dur = std::min(2 + (random2(pow) / 4), 6);
const bool friendly = (random2(pow) > 3);
if (create_monster(
mgen_data(mon,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
dur, SPELL_SUMMON_UGLY_THING,
you.pos(),
MHITYOU,
0, god)) != -1)
{
mpr((mon == MONS_VERY_UGLY_THING) ? "A very ugly thing appears."
: "An ugly thing appears.");
if (!friendly)
mpr("It doesn't look very happy.");
return (true);
}
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
bool cast_summon_dragon(int pow, god_type god)
{
const bool friendly = (random2(pow) > 5);
if (create_monster(
mgen_data(MONS_DRAGON,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
3, SPELL_SUMMON_DRAGON,
you.pos(),
MHITYOU,
0, god)) != -1)
{
mpr("A dragon appears.");
if (!friendly)
mpr("It doesn't look very happy.");
return (true);
}
canned_msg(MSG_NOTHING_HAPPENS);
return (false);
}
bool summon_berserker(int pow, god_type god, int spell,
bool force_hostile)
{
monster_type mon = MONS_PROGRAM_BUG;
int dur = std::min(2 + (random2(pow) / 4), 6);
if (pow <= 100)
{
mon = (coinflip()) ? MONS_BLACK_BEAR : MONS_GRIZZLY_BEAR;
}
else if (pow <= 140)
{
if (one_chance_in(3))
mon = MONS_TWO_HEADED_OGRE;
else
mon = MONS_OGRE;
}
else if (pow <= 180)
{
switch (random2(8))
{
case 0:
mon = MONS_DEEP_TROLL;
break;
case 1:
case 2:
mon = MONS_IRON_TROLL;
break;
case 3:
case 4:
mon = MONS_ROCK_TROLL;
break;
default:
mon = MONS_TROLL;
break;
}
}
else
{
mon = (coinflip()) ? MONS_HILL_GIANT : MONS_STONE_GIANT;
}
int monster =
create_monster(
mgen_data(mon,
!force_hostile ? BEH_FRIENDLY : BEH_HOSTILE,
dur, spell, you.pos(), MHITYOU, 0, god));
if (monster == -1)
return (false);
monsters *summon = &menv[monster];
summon->go_berserk(false);
mon_enchant berserk = summon->get_ench(ENCH_BERSERK);
mon_enchant abj = summon->get_ench(ENCH_ABJ);
berserk.duration = berserk.duration * 3 / 2;
berserk.maxduration = berserk.duration;
abj.duration = abj.maxduration = berserk.duration;
summon->update_ench(berserk);
summon->update_ench(abj);
player_angers_monster(&menv[monster]);
return (true);
}
static bool _summon_holy_being_wrapper(int pow, god_type god, int spell,
monster_type mon, int dur, bool friendly,
bool quiet)
{
UNUSED(pow);
const int monster =
create_monster(
mgen_data(mon,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
dur, spell,
you.pos(),
MHITYOU,
MG_FORCE_BEH, god));
if (monster == -1)
return (false);
monsters *summon = &menv[monster];
summon->flags |= MF_ATT_CHANGE_ATTEMPT;
if (!quiet)
{
mprf("You are momentarily dazzled by a brilliant %slight.",
(mon == MONS_DAEVA) ? "golden " :
(mon == MONS_ANGEL) ? "white "
: "");
}
player_angers_monster(&menv[monster]);
return (true);
}
static bool _summon_holy_being_wrapper(int pow, god_type god, int spell,
holy_being_class_type hbct, int dur,
bool friendly, bool quiet)
{
monster_type mon = summon_any_holy_being(hbct);
return _summon_holy_being_wrapper(pow, god, spell, mon, dur, friendly,
quiet);
}
bool summon_holy_warrior(int pow, god_type god, int spell,
bool force_hostile, bool permanent,
bool quiet)
{
return _summon_holy_being_wrapper(pow, god, spell, HOLY_BEING_WARRIOR,
!permanent ?
std::min(2 + (random2(pow) / 4), 6) :
0,
!force_hostile, quiet);
}
bool cast_tukimas_dance(int pow, god_type god, bool force_hostile)
{
bool success = true;
conduct_type why;
const int dur = std::min(2 + (random2(pow) / 5), 6);
const int wpn = you.equip[EQ_WEAPON];
if (wpn == -1
|| you.inv[wpn].base_type != OBJ_WEAPONS
|| is_range_weapon(you.inv[wpn])
|| is_special_unrandom_artefact(you.inv[wpn]))
{
success = false;
}
const int i = get_item_slot();
if (i == NON_ITEM)
success = false;
else if (success)
{
mitm[i] = you.inv[wpn];
}
int monster;
if (success)
{
const bool friendly = (!force_hostile && !item_cursed(you.inv[wpn]));
monster =
create_monster(
mgen_data(MONS_DANCING_WEAPON,
friendly ? BEH_FRIENDLY : BEH_HOSTILE,
dur, SPELL_TUKIMAS_DANCE,
you.pos(),
MHITYOU,
0, god));
if (monster == -1)
{
mitm[i].clear();
success = false;
}
}
if (!success)
{
destroy_item(i);
if (wpn != -1)
{
mprf("%s vibrates crazily for a second.",
you.inv[wpn].name(DESC_CAP_YOUR).c_str());
}
else
mprf("Your %s twitch.", your_hand(true).c_str());
return (false);
}
unwield_item();
mitm[i] = you.inv[wpn];
mitm[i].quantity = 1;
mitm[i].pos.set(-2, -2);
mitm[i].link = NON_ITEM + 1 + monster;
mitm[i].flags |= ISFLAG_THROWN;
mprf("%s dances into the air!", you.inv[wpn].name(DESC_CAP_YOUR).c_str());
you.inv[wpn].quantity = 0;
destroy_item(menv[monster].inv[MSLOT_WEAPON]);
menv[monster].inv[MSLOT_WEAPON] = i;
menv[monster].colour = mitm[i].colour;
burden_change();
if ((why = good_god_hates_item_handling(you.inv[wpn]))
|| (why = god_hates_item_handling(you.inv[wpn])))
{
simple_god_message(" booms: How dare you animate that foul thing!");
did_god_conduct(why, 10, true, &menv[monster]);
}
return (true);
}
bool cast_conjure_ball_lightning(int pow, god_type god)
{
bool success = false;
const int how_many = std::min(8, 3 + random2(2 + pow / 50));
for (int i = 0; i < how_many; ++i)
{
coord_def target;
bool found = false;
for (int j = 0; j < 10; ++j)
{
if (random_near_space(you.pos(), target, true, true, false)
&& distance(you.pos(), target) <= 5)
{
found = true;
break;
}
}
if (!found)
target = you.pos();
int monster =
mons_place(
mgen_data(MONS_BALL_LIGHTNING, BEH_FRIENDLY,
0, SPELL_CONJURE_BALL_LIGHTNING,
target, MHITNOT, 0, god));
if (monster != -1)
{
success = true;
menv[monster].add_ench(ENCH_SHORT_LIVED);
}
}
if (success)
mpr("You create some ball lightning!");
else
canned_msg(MSG_NOTHING_HAPPENS);
return (success);
}
int fungal_bloom()
{
int seen_mushrooms = 0;
int seen_corpses = 0;
int processed_count = 0;
bool kills = false;
for (radius_iterator i(you.position, LOS_RADIUS); i; ++i)
{
actor *target = actor_at(*i);
if (target && (target->atype() == ACT_PLAYER
|| target->is_summoned()))
{
continue;
}
monsters * mons = monster_at(*i);
if (mons && mons->mons_species() != MONS_TOADSTOOL)
{
switch (mons_genus(mons->mons_species()))
{
case MONS_ZOMBIE_SMALL:
if (mons_skeleton(mons_zombie_base(mons)))
{
processed_count++;
monster_type skele_type = MONS_SKELETON_LARGE;
if (mons_zombie_size(mons_zombie_base(mons)) == Z_SMALL)
skele_type = MONS_SKELETON_SMALL;
simple_monster_message(mons, "'s flesh rots away.");
mons->upgrade_type(skele_type, true, true);
behaviour_event(mons, ME_ALERT, MHITYOU);
continue;
}
case MONS_GHOUL:
{
simple_monster_message(mons, " rots away and dies.");
coord_def pos = mons->pos();
int colour = mons->colour;
int corpse = monster_die(mons, KILL_MISC, NON_MONSTER, true);
kills = true;
if (corpse < 0)
{
const int mushroom = create_monster(
mgen_data(MONS_TOADSTOOL,
BEH_HOSTILE,
0,
0,
pos,
MHITNOT,
MG_FORCE_PLACE,
GOD_NO_GOD,
MONS_NO_MONSTER,
0,
colour),
false);
if (mushroom != -1)
seen_mushrooms++;
processed_count++;
continue;
}
break;
}
default:
continue;
}
}
for (stack_iterator j(*i); j; ++j)
{
bool corpse_on_pos = false;
if (j->base_type == OBJ_CORPSES && j->sub_type == CORPSE_BODY)
{
corpse_on_pos = true;
int trial_prob = mushroom_prob(*j);
processed_count++;
int target_count = 1 + binomial_generator(20, trial_prob);
int seen_per;
spawn_corpse_mushrooms(*j, target_count, seen_per, true);
seen_mushrooms += seen_per;
if (mons_skeleton(j->plus))
turn_corpse_into_skeleton(*j);
else
destroy_item(j->index());
}
if (corpse_on_pos && see_cell(*i))
seen_corpses++;
}
}
if (seen_mushrooms > 0)
{
mushroom_spawn_message(seen_mushrooms, seen_corpses);
}
if (kills)
mprf("That felt like a moral victory.");
return (processed_count);
}
int create_plant(coord_def & target)
{
if (actor_at(target) || !mons_class_can_pass(MONS_PLANT, grd(target)))
return (0);
const int plant = create_monster(mgen_data
(MONS_PLANT,
BEH_FRIENDLY,
0,
0,
target,
MHITNOT,
MG_FORCE_PLACE, GOD_FEAWN));
if (plant != -1)
{
env.mons[plant].flags |= MF_ATT_CHANGE_ATTEMPT;
if (see_cell(target))
mpr("A plant grows up from the ground.");
}
return (plant != -1);
}
bool sunlight()
{
int c_size = 5;
int x_offset[] = {-1, 0, 0, 0, 1};
int y_offset[] = { 0,-1, 0, 1, 0};
dist spelld;
bolt temp_bolt;
temp_bolt.colour = YELLOW;
direction(spelld, DIR_TARGET, TARG_HOSTILE, LOS_RADIUS, false, false,
false, true, "Select sunlight destination", NULL,
true);
if (!spelld.isValid)
return (false);
coord_def base = spelld.target;
int evap_count = 0;
int plant_count = 0;
for (int i = 0;i < c_size; ++i)
{
coord_def target = base;
target.x += x_offset[i];
target.y += y_offset[i];
if (!in_bounds(target) || feat_is_solid(grd(target)))
continue;
temp_bolt.explosion_draw_cell(target);
actor *victim = actor_at(target);
dungeon_feature_type ftype = grd(target);
switch (ftype)
{
case DNGN_SHALLOW_WATER:
ftype = DNGN_FLOOR;
break;
case DNGN_DEEP_WATER:
ftype = DNGN_SHALLOW_WATER;
break;
default:
break;
}
if (grd(target) != ftype)
{
grd(target) = ftype;
if (see_cell(target))
evap_count++;
}
monsters *mons = monster_at(target);
if (mons && mons_habitat(mons) == HT_WATER)
{
mons->del_ench(ENCH_SUBMERGED);
if (ftype == DNGN_FLOOR)
mons->add_ench(mon_enchant(ENCH_AQUATIC_LAND, 0, KC_YOU));
}
if (victim)
{
if (!mons)
you.backlight();
else
backlight_monsters(target, 1, 0);
}
else if (one_chance_in(100)
&& ftype >= DNGN_FLOOR_MIN
&& ftype <= DNGN_FLOOR_MAX)
{
const int plant = create_monster(mgen_data(MONS_PLANT,
BEH_HOSTILE,
0,
0,
target,
MHITNOT,
MG_FORCE_PLACE,
GOD_FEAWN));
if (plant != -1 && see_cell(target))
plant_count++;
}
}
delay(50);
update_screen();
if (plant_count)
{
mprf("%s grow%s in the sunlight.",
(plant_count > 1 ? "Some plants": "A plant"),
(plant_count > 1 ? "": "s"));
}
if (evap_count)
mprf("Some water evaporates in the bright sunlight.");
return (true);
}
template<typename T>
bool less_second(const T & left, const T & right)
{
return left.second < right.second;
}
typedef std::pair<coord_def, int> point_distance;
void path_distance(coord_def & origin,
std::vector<coord_def> & targets,
std::vector<int> & distances)
{
std::set<unsigned> exclusion;
std::queue<point_distance> fringe;
fringe.push(point_distance(origin,0));
int idx = origin.x + origin.y * X_WIDTH;
exclusion.insert(idx);
while (!fringe.empty())
{
point_distance current = fringe.front();
fringe.pop();
for (unsigned i = 0; i < targets.size(); ++i)
{
if (current.first == targets[i])
{
distances[i] = current.second;
break;
}
}
for (adjacent_iterator adj_it(current.first); adj_it; ++adj_it)
{
idx = adj_it->x + adj_it->y * X_WIDTH;
if (see_cell(*adj_it)
&& !feat_is_solid(env.grid(*adj_it))
&& exclusion.insert(idx).second)
{
monsters * temp = monster_at(*adj_it);
if (!temp || (temp->attitude == ATT_HOSTILE
&& temp->mons_species() != MONS_PLANT
&& temp->mons_species() != MONS_TOADSTOOL
&& temp->mons_species() != MONS_FUNGUS
&& temp->mons_species() != MONS_BALLISTOMYCETE))
{
fringe.push(point_distance(*adj_it, current.second+1));
}
}
}
}
}
void point_point(std::vector<coord_def> & origins,
std::vector<coord_def> & targets,
bool origin_to_target,
std::vector<int> & distances)
{
distances.clear();
if (origin_to_target)
distances.resize(origins.size(), INT_MAX);
else
distances.resize(targets.size(), INT_MAX);
std::vector<int> current_distances(targets.size(), 0);
for (unsigned i = 0; i < origins.size(); ++i)
{
for (unsigned j = 0; j < current_distances.size(); ++j)
current_distances[j] = INT_MAX;
path_distance(origins[i], targets, current_distances);
if (origin_to_target)
{
int min_dist = current_distances[0];
for (unsigned j = 1; i < current_distances.size(); ++i)
if (current_distances[j] < min_dist)
min_dist = current_distances[j];
distances[i] = min_dist;
}
else
{
for (unsigned j = 0; j < targets.size(); ++j)
{
if (i == 0)
distances[j] = current_distances[j];
else if (current_distances[j] < distances[j])
distances[j] = current_distances[j];
}
}
}
}
bool prioritise_adjacent(coord_def & target, std::vector<coord_def> & candidates)
{
radius_iterator los_it(target, LOS_RADIUS, true, true, true);
std::vector<coord_def> mons_positions;
for ( ; los_it; ++los_it)
{
monsters * hostile = monster_at(*los_it);
if (hostile && hostile->attitude == ATT_HOSTILE)
mons_positions.push_back(hostile->pos());
}
if (mons_positions.empty())
{
std::random_shuffle(candidates.begin(), candidates.end());
return (true);
}
bool squares_to_monsters = mons_positions.size() > candidates.size();
std::vector<int> distances;
if (squares_to_monsters)
point_point(candidates, mons_positions, squares_to_monsters, distances);
else
point_point(mons_positions, candidates, squares_to_monsters, distances);
std::vector<point_distance> possible_moves(candidates.size());
for (unsigned i = 0; i < possible_moves.size(); ++i)
{
possible_moves[i].first = candidates[i];
possible_moves[i].second = distances[i];
}
std::sort(possible_moves.begin(), possible_moves.end(),
less_second<point_distance>);
for (unsigned i = 0; i < candidates.size(); ++i)
candidates[i] = possible_moves[i].first;
return (true);
}
int _prompt_for_fruit(int & count, const char * prompt_string)
{
int rc = prompt_invent_item(prompt_string,
MT_INVLIST,
OSEL_FRUIT,
true,
true,
true,
'\0',
-1,
&count);
if (prompt_failed(rc))
return (rc);
if (!is_fruit(you.inv[rc]))
return (PROMPT_INAPPROPRIATE);
if (count > you.inv[rc].quantity)
count = you.inv[rc].quantity;
return (rc);
}
bool plant_ring_from_fruit()
{
int possible_count;
int created_count = 0;
int rc = _prompt_for_fruit(possible_count, "Use which fruit?");
if (rc < 0)
return (false);
std::vector<coord_def> adjacent;
for (adjacent_iterator adj_it(you.pos()); adj_it; ++adj_it)
{
if (mons_class_can_pass(MONS_PLANT, env.grid(*adj_it))
&& !actor_at(*adj_it))
{
adjacent.push_back(*adj_it);
}
}
if ((int)adjacent.size() > possible_count)
{
prioritise_adjacent(you.pos(), adjacent);
}
unsigned target_count =
(possible_count < (int)adjacent.size()) ? possible_count
: adjacent.size();
for (unsigned i = 0; i < target_count; ++i)
{
if (create_plant(adjacent[i]))
created_count++;
}
dec_inv_item_quantity(rc, created_count);
return (created_count);
}
int rain(coord_def & target)
{
int spawned_count = 0;
for (radius_iterator rad(target, LOS_RADIUS, true, true, true); rad; ++rad)
{
int rain_thresh = 6;
coord_def local = *rad - target;
dungeon_feature_type ftype = grd(*rad);
if (local.abs() > rain_thresh)
{
if (x_chance_in_y(5, 192)
&& !actor_at(*rad)
&& ftype >= DNGN_FLOOR_MIN
&& ftype <= DNGN_FLOOR_MAX)
{
const int plant = create_monster(mgen_data
(coinflip() ? MONS_PLANT : MONS_FUNGUS,
BEH_GOOD_NEUTRAL,
0,
0,
*rad,
MHITNOT,
MG_FORCE_PLACE, GOD_FEAWN));
if (plant != -1)
spawned_count++;
}
continue;
}
if (ftype >= DNGN_FLOOR_MIN && ftype <= DNGN_FLOOR_MAX)
{
grd(*rad) = DNGN_SHALLOW_WATER;
env.map(*rad).property &= ~(FPROP_BLOODY);
monsters *mon = monster_at(*rad);
if (mon && mon->has_ench(ENCH_AQUATIC_LAND))
mon->del_ench(ENCH_AQUATIC_LAND);
}
else if (!actor_at(*rad)
&& igrd(*rad) == NON_ITEM
&& ftype == DNGN_SHALLOW_WATER)
{
grd(*rad) = DNGN_DEEP_WATER;
}
if (ftype >= DNGN_MINMOVE)
{
int max_expected = 5;
int expected = div_rand_round(max_expected
* you.skills[SK_INVOCATIONS], 27);
if (x_chance_in_y(expected, 20))
place_cloud(CLOUD_RAIN, *rad, 10, KC_YOU);
}
}
if (spawned_count > 0)
{
mprf("%s grow%s in the rain.",
(spawned_count > 1 ? "Some plants" : "A plant"),
(spawned_count > 1 ? "" : "s"));
}
return (spawned_count);
}
int corpse_spores(beh_type behavior)
{
int count = 0;
for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true); rad;
++rad)
{
for (stack_iterator stack_it(*rad); stack_it; ++stack_it)
{
if (stack_it->base_type == OBJ_CORPSES
&& stack_it->sub_type == CORPSE_BODY)
{
count++;
int rc = create_monster(mgen_data(MONS_GIANT_SPORE,
behavior,
0,
0,
*rad,
MHITNOT,
MG_FORCE_PLACE));
if (rc!=-1)
env.mons[rc].flags |= MF_ATT_CHANGE_ATTEMPT;
if (mons_skeleton(stack_it->plus))
turn_corpse_into_skeleton(*stack_it);
else
destroy_item(stack_it->index());
break;
}
}
}
return (count);
}
struct monster_conversion
{
monsters * base_monster;
int cost;
monster_type new_type;
};
bool operator<(const monster_conversion & left,
const monster_conversion & right)
{
if (left.cost == right.cost)
return (coinflip());
return (left.cost < right.cost);
}
bool _possible_evolution(monsters * input,
monster_conversion & possible_monster)
{
int plant_cost = 10;
int toadstool_cost = 1;
int fungus_cost = 5;
possible_monster.base_monster = input;
switch (input->mons_species())
{
case MONS_PLANT:
possible_monster.cost = plant_cost;
possible_monster.new_type = MONS_OKLOB_PLANT;
break;
case MONS_FUNGUS:
case MONS_BALLISTOMYCETE:
possible_monster.cost = fungus_cost;
possible_monster.new_type = MONS_WANDERING_MUSHROOM;
break;
case MONS_TOADSTOOL:
possible_monster.cost = toadstool_cost;
possible_monster.new_type = MONS_BALLISTOMYCETE;
break;
default:
return (false);
}
return (true);
}
bool evolve_flora()
{
int rc;
int available_count;
int points_per_fruit = 8;
int oklob_cost = 10;
float approx_oklob_rate = float(points_per_fruit)/float(oklob_cost);
char prompt_string[100];
memset(prompt_string,0,100);
sprintf(prompt_string,"Use which fruit (%1.1f oklob plants per fruit)?",
approx_oklob_rate);
rc = _prompt_for_fruit(available_count, prompt_string);
if (rc < 0)
return (false);
int points = points_per_fruit * available_count;
int starting_points = points;
std::priority_queue<monster_conversion> available_targets;
monster_conversion temp_conversion;
for (radius_iterator rad(you.pos(), LOS_RADIUS, true, true, true);
rad; ++rad)
{
monsters * target = monster_at(*rad);
if (!target)
continue;
if (_possible_evolution(target, temp_conversion))
available_targets.push(temp_conversion);
}
if (available_targets.empty())
{
mpr("No flora in sight can be evolved.");
return (false);
}
int plants_evolved = 0;
int toadstools_evolved = 0;
int fungi_evolved = 0;
while (!available_targets.empty() && points > 0)
{
monster_conversion current_target = available_targets.top();
monsters * current_plant = current_target.base_monster;
available_targets.pop();
if (current_target.cost > points)
continue;
points -= current_target.cost;
switch (current_plant->mons_species())
{
case MONS_PLANT:
plants_evolved++;
break;
case MONS_FUNGUS:
case MONS_BALLISTOMYCETE:
fungi_evolved++;
break;
case MONS_TOADSTOOL:
toadstools_evolved++;
break;
};
current_plant->upgrade_type(current_target.new_type, true, true);
current_plant->god = GOD_FEAWN;
current_plant->attitude = ATT_FRIENDLY;
current_plant->flags |= MF_CREATED_FRIENDLY;
current_plant->flags |= MF_ATT_CHANGE_ATTEMPT;
current_plant->del_ench(ENCH_SLOWLY_DYING);
current_plant->del_ench(ENCH_SPORE_PRODUCTION);
if (current_plant->mons_species() == MONS_BALLISTOMYCETE)
current_plant->add_ench(ENCH_SPORE_PRODUCTION);
if (_possible_evolution(current_plant, temp_conversion)
&& temp_conversion.cost <= points)
{
available_targets.push(temp_conversion);
}
}
int points_used = starting_points - points;
int fruit_used = points_used / points_per_fruit;
if (points_used % points_per_fruit)
fruit_used++;
if (!fruit_used)
{
mpr("Not enough fruit to cause evolution.");
return (false);
}
dec_inv_item_quantity(rc, fruit_used);
if (fruit_used > 1)
mprf("%d pieces of fruit are consumed!", fruit_used);
else
mpr("A piece of fruit is consumed!");
if (plants_evolved > 1)
mprf("%d plants can now spit acid.", plants_evolved);
else if (plants_evolved == 1)
mpr("A plant can now spit acid.");
if (toadstools_evolved > 1)
mprf("%d toadstools gained stability.", toadstools_evolved);
else if (toadstools_evolved == 1)
mpr("A toadstool gained stability.");
if (fungi_evolved > 1)
{
mprf("%d fungal colonies can now pick up their mycelia and move.",
fungi_evolved);
}
else if (fungi_evolved == 1)
mpr("A fungal colony can now pick up its mycelia and move.");
return (true);
}