/*
* File: ghost.cc
* Summary: Player ghost and random Pandemonium demon handling.
*
* Created for Dungeon Crawl Reference by dshaligram on
* Thu Mar 15 20:10:20 2007 UTC.
*/
#include "AppHdr.h"
#include "ghost.h"
#include "artefact.h"
#include "colour.h"
#include "env.h"
#include "externs.h"
#include "itemname.h"
#include "itemprop.h"
#include "mon-iter.h"
#include "ng-input.h"
#include "random.h"
#include "skills2.h"
#include "mon-util.h"
#include "mon-transit.h"
#include "place.h"
#include "player.h"
#include "religion.h"
#include <vector>
#define MAX_GHOST_DAMAGE 50
#define MAX_GHOST_HP 400
#define MAX_GHOST_EVASION 60
#define MIN_GHOST_SPEED 6
#define MAX_GHOST_SPEED 13
std::vector<ghost_demon> ghosts;
// Order for looking for conjurations for the 1st & 2nd spell slots,
// when finding spells to be remembered by a player's ghost.
static spell_type search_order_conj[] = {
SPELL_LEHUDIBS_CRYSTAL_SPEAR,
SPELL_FIRE_STORM,
SPELL_ICE_STORM,
SPELL_CHAIN_LIGHTNING,
SPELL_BOLT_OF_DRAINING,
SPELL_AGONY,
SPELL_DISINTEGRATE,
SPELL_LIGHTNING_BOLT,
SPELL_AIRSTRIKE,
SPELL_STICKY_FLAME,
SPELL_ISKENDERUNS_MYSTIC_BLAST,
SPELL_IOOD,
SPELL_BOLT_OF_MAGMA,
SPELL_THROW_ICICLE,
SPELL_BOLT_OF_FIRE,
SPELL_BOLT_OF_COLD,
SPELL_FIREBALL,
SPELL_DELAYED_FIREBALL,
SPELL_VENOM_BOLT,
SPELL_IRON_SHOT,
SPELL_STONE_ARROW,
SPELL_THROW_FLAME,
SPELL_THROW_FROST,
SPELL_PAIN,
SPELL_STING,
SPELL_SHOCK,
SPELL_SANDBLAST,
SPELL_MAGIC_DART,
SPELL_HIBERNATION,
SPELL_CORONA,
SPELL_NO_SPELL, // end search
};
// Order for looking for summonings and self-enchants for the 3rd spell
// slot.
static spell_type search_order_third[] = {
// 0
SPELL_SYMBOL_OF_TORMENT,
SPELL_SUMMON_GREATER_DEMON,
SPELL_SUMMON_HORRIBLE_THINGS,
SPELL_HAUNT,
SPELL_SUMMON_DEMON,
SPELL_DEMONIC_HORDE,
SPELL_HASTE,
SPELL_SUMMON_SWARM,
SPELL_SUMMON_UGLY_THING,
SPELL_SWIFTNESS,
// 10
SPELL_SUMMON_ICE_BEAST,
SPELL_ANIMATE_DEAD,
SPELL_INVISIBILITY,
SPELL_SUMMON_SCORPIONS,
SPELL_CALL_IMP,
SPELL_SUMMON_SMALL_MAMMALS,
SPELL_CONTROLLED_BLINK,
SPELL_BLINK,
SPELL_NO_SPELL, // end search
};
// Order for looking for enchants for the 4th & 5th spell slots. If
// this fails, go through conjurations. Note: Dig must be in misc2
// (5th) position to work.
static spell_type search_order_misc[] = {
// 0
SPELL_AGONY,
SPELL_BANISHMENT,
SPELL_FREEZING_CLOUD,
SPELL_DISPEL_UNDEAD,
SPELL_PARALYSE,
SPELL_CONFUSE,
SPELL_MEPHITIC_CLOUD,
SPELL_SLOW,
SPELL_POLYMORPH_OTHER,
SPELL_TELEPORT_OTHER,
// 10
SPELL_DIG,
SPELL_CORONA,
SPELL_NO_SPELL, // end search
};
// Last slot (emergency) can only be Teleport Self or Blink.
ghost_demon::ghost_demon()
{
reset();
}
void ghost_demon::reset()
{
name.clear();
species = SP_UNKNOWN;
job = JOB_UNKNOWN;
religion = GOD_NO_GOD;
best_skill = SK_FIGHTING;
best_skill_level = 0;
xl = 0;
max_hp = 0;
ev = 0;
ac = 0;
damage = 0;
speed = 10;
see_invis = false;
brand = SPWPN_NORMAL;
att_type = AT_HIT;
att_flav = AF_PLAIN;
resists = mon_resist_def();
spellcaster = false;
cycle_colours = false;
colour = BLACK;
fly = FL_NONE;
}
void ghost_demon::init_random_demon()
{
name = make_name(random_int(), false);
// hp - could be defined below (as could ev, AC, etc.). Oh well, too late:
max_hp = 100 + roll_dice(3, 50);
ev = 5 + random2(20);
ac = 5 + random2(20);
see_invis = !one_chance_in(10);
if (!one_chance_in(3))
resists.fire = random_range(1, 2);
else
{
resists.fire = 0; // res_fire
if (one_chance_in(10))
resists.fire = -1;
}
if (!one_chance_in(3))
resists.cold = random_range(1, 2);
else
{
resists.cold = 0;
if (one_chance_in(10))
resists.cold = -1;
}
// Demons, like ghosts, automatically get poison res. and life prot.
// resist electricity:
resists.elec = one_chance_in(3);
// HTH damage:
damage = 20 + roll_dice(2, 20);
// special attack type (uses weapon brand code):
brand = SPWPN_NORMAL;
if (!one_chance_in(3))
{
do
{
brand = static_cast<brand_type>(random2(MAX_PAN_LORD_BRANDS));
// some brands inappropriate (e.g. holy wrath)
}
while (brand == SPWPN_HOLY_WRATH
|| (brand == SPWPN_ORC_SLAYING
&& you.mons_species() != MONS_ORC)
|| (brand == SPWPN_DRAGON_SLAYING
&& you.mons_species() != MONS_DRACONIAN)
|| brand == SPWPN_PROTECTION
|| brand == SPWPN_FLAME
|| brand == SPWPN_FROST);
}
// Is demon a spellcaster?
// Upped from one_chance_in(3)... spellcasters are more interesting
// and I expect named demons to typically have a trick or two. - bwr
spellcaster = !one_chance_in(10);
// Does demon fly?
fly = (one_chance_in(3) ? FL_NONE :
one_chance_in(5) ? FL_LEVITATE
: FL_FLY);
// hit dice:
xl = 10 + roll_dice(2, 10);
// Does demon cycle colours?
cycle_colours = one_chance_in(10);
colour = random_colour();
spells.init(SPELL_NO_SPELL);
// This bit uses the list of player spells to find appropriate
// spells for the demon, then converts those spells to the monster
// spell indices. Some special monster-only spells are at the end.
if (spellcaster)
{
if (coinflip())
spells[0] = RANDOM_ELEMENT(search_order_conj);
// Might duplicate the first spell, but that isn't a problem.
if (coinflip())
spells[1] = RANDOM_ELEMENT(search_order_conj);
if (!one_chance_in(4))
spells[2] = RANDOM_ELEMENT(search_order_third);
if (coinflip())
{
spells[3] = RANDOM_ELEMENT(search_order_misc);
if (spells[3] == SPELL_DIG)
spells[3] = SPELL_NO_SPELL;
}
if (coinflip())
spells[4] = RANDOM_ELEMENT(search_order_misc);
if (coinflip())
spells[5] = SPELL_BLINK;
if (coinflip())
spells[5] = SPELL_TELEPORT_SELF;
// Convert the player spell indices to monster spell ones.
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
spells[i] = translate_spell(spells[i]);
// Give demon a chance for some monster-only spells.
// Demon-summoning should be fairly common.
if (one_chance_in(25))
spells[0] = SPELL_HELLFIRE_BURST;
if (one_chance_in(25))
spells[0] = SPELL_FIRE_STORM;
if (one_chance_in(25))
spells[0] = SPELL_ICE_STORM;
if (one_chance_in(25))
spells[0] = SPELL_METAL_SPLINTERS;
if (one_chance_in(25))
spells[0] = SPELL_ENERGY_BOLT; // eye of devastation
if (one_chance_in(25))
spells[1] = SPELL_STEAM_BALL;
if (one_chance_in(25))
spells[1] = SPELL_ISKENDERUNS_MYSTIC_BLAST;
if (one_chance_in(25))
spells[1] = SPELL_HELLFIRE;
if (one_chance_in(25))
spells[1] = SPELL_IOOD;
if (one_chance_in(25))
spells[2] = SPELL_SMITING;
if (one_chance_in(25))
spells[2] = SPELL_HELLFIRE_BURST;
if (one_chance_in(12))
spells[2] = SPELL_SUMMON_GREATER_DEMON;
if (one_chance_in(12))
spells[2] = SPELL_SUMMON_DEMON;
if (one_chance_in(10))
spells[2] = SPELL_SUMMON_EYEBALLS;
if (one_chance_in(20))
spells[3] = SPELL_SUMMON_GREATER_DEMON;
if (one_chance_in(20))
spells[3] = SPELL_SUMMON_DEMON;
// At least they can summon demons.
if (spells[3] == SPELL_NO_SPELL)
spells[3] = SPELL_SUMMON_DEMON;
if (one_chance_in(15))
spells[4] = SPELL_DIG;
}
}
// Returns the movement speed for a player ghost. Note that this is a
// real speed, not a movement cost, so higher is better.
static int _player_ghost_base_movement_speed()
{
int speed = (you.species == SP_NAGA ? 8 : 10);
if (player_mutation_level(MUT_FAST))
speed += player_mutation_level(MUT_FAST) + 1;
if (player_equip_ego_type(EQ_BOOTS, SPARM_RUNNING))
speed += 2;
// Cap speeds.
if (speed < MIN_GHOST_SPEED)
speed = MIN_GHOST_SPEED;
else if (speed > MAX_GHOST_SPEED)
speed = MAX_GHOST_SPEED;
return (speed);
}
void ghost_demon::init_player_ghost()
{
name = you.your_name;
max_hp = ((you.hp_max >= MAX_GHOST_HP) ? MAX_GHOST_HP : you.hp_max);
ev = player_evasion();
ac = you.armour_class();
if (ev > MAX_GHOST_EVASION)
ev = MAX_GHOST_EVASION;
see_invis = you.can_see_invisible();
resists.fire = player_res_fire();
resists.cold = player_res_cold();
resists.elec = player_res_electricity();
speed = _player_ghost_base_movement_speed();
damage = 4;
brand = SPWPN_NORMAL;
if (you.weapon())
{
const item_def& weapon = *you.weapon();
if (weapon.base_type == OBJ_WEAPONS || weapon.base_type == OBJ_STAVES)
{
damage = property(weapon, PWPN_DAMAGE);
damage *= 25 + you.skills[weapon_skill(weapon)];
damage /= 25;
if (weapon.base_type == OBJ_WEAPONS)
{
brand = static_cast<brand_type>(get_weapon_brand(weapon));
// Ghosts can't get holy wrath, but they get to keep
// the weapon.
if (brand == SPWPN_HOLY_WRATH)
brand = SPWPN_NORMAL;
}
}
}
else
{
// Unarmed combat.
if (you.species == SP_TROLL)
damage += you.experience_level;
damage += you.skills[SK_UNARMED_COMBAT];
}
damage *= 30 + you.skills[SK_FIGHTING];
damage /= 30;
damage += you.strength / 4;
if (damage > MAX_GHOST_DAMAGE)
damage = MAX_GHOST_DAMAGE;
species = you.species;
job = you.char_class;
// Ghosts can't worship good gods.
if (!is_good_god(you.religion))
religion = you.religion;
best_skill = ::best_skill(SK_FIGHTING, (NUM_SKILLS - 1), 99);
best_skill_level = you.skills[best_skill];
xl = you.experience_level;
// These are the same as in mon-data.h.
colour = WHITE;
fly = FL_LEVITATE;
add_spells();
}
static unsigned char _ugly_thing_assign_colour(unsigned char force_colour,
unsigned char force_not_colour)
{
unsigned char colour;
if (force_colour != BLACK)
colour = force_colour;
else
{
do
colour = ugly_thing_random_colour();
while (force_not_colour != BLACK && colour == force_not_colour);
}
return (colour);
}
static mon_attack_flavour _ugly_thing_colour_to_flavour(unsigned char u_colour)
{
mon_attack_flavour u_att_flav = AF_PLAIN;
switch (u_colour)
{
case RED:
u_att_flav = AF_FIRE;
break;
case BROWN:
u_att_flav = AF_ACID;
break;
case GREEN:
u_att_flav = AF_POISON_NASTY;
break;
case CYAN:
u_att_flav = AF_ELEC;
break;
case MAGENTA:
u_att_flav = AF_DISEASE;
break;
case LIGHTGREY:
u_att_flav = AF_COLD;
break;
default:
break;
}
return (u_att_flav);
}
static mon_attack_flavour _very_ugly_thing_flavour_upgrade(mon_attack_flavour u_att_flav)
{
switch (u_att_flav)
{
case AF_FIRE:
u_att_flav = AF_NAPALM;
break;
case AF_POISON_NASTY:
u_att_flav = AF_POISON_MEDIUM;
break;
case AF_DISEASE:
u_att_flav = AF_ROT;
break;
default:
break;
}
return (u_att_flav);
}
void ghost_demon::init_ugly_thing(bool very_ugly, bool only_mutate,
unsigned char force_colour)
{
// Move speed: 11, the same as in mon-data.h.
speed = 11;
// Midpoint: 10, as in mon-data.h.
ev = 9 + random2(3);
// Midpoint: 3, as in mon-data.h.
ac = 2 + random2(3);
// Midpoint: 12, as in mon-data.h.
damage = 11 + random2(3);
// If we're mutating an ugly thing, leave its experience level, hit
// dice and maximum hit points as they are.
if (!only_mutate)
{
// Experience level: 8, the same as in mon-data.h.
xl = 8;
// Hit dice: {8, 3, 5, 0}, the same as in mon-data.h.
max_hp = hit_points(xl, 3, 5);
}
const mon_attack_type att_types[] =
{
AT_BITE, AT_STING, AT_CLAW, AT_PECK, AT_HEADBUTT, AT_PUNCH, AT_KICK,
AT_TENTACLE_SLAP, AT_TAIL_SLAP, AT_GORE
};
att_type = RANDOM_ELEMENT(att_types);
// An ugly thing always gets a low-intensity colour. If we're
// mutating it, it always gets a different colour from what it had
// before.
colour = _ugly_thing_assign_colour(make_low_colour(force_colour),
only_mutate ? make_low_colour(colour)
: BLACK);
// Pick a compatible attack flavour for this colour.
att_flav = _ugly_thing_colour_to_flavour(colour);
// Pick a compatible resistance for this attack flavour.
ugly_thing_add_resistance(false, att_flav);
// If this is a very ugly thing, upgrade it properly.
if (very_ugly)
ugly_thing_to_very_ugly_thing();
}
void ghost_demon::ugly_thing_to_very_ugly_thing()
{
// Midpoint when added to an ugly thing: 4, as in mon-data.h.
ac++;
// Midpoint when added to an ugly thing: 17, as in mon-data.h.
damage += 5;
// Experience level when added to an ugly thing: 12, the same as in
// mon-data.h.
xl += 4;
// Hit dice when added to an ugly thing: {12, 3, 5, 0}, the same as
// in mon-data.h.
max_hp += hit_points(4, 3, 5);
// A very ugly thing always gets a high-intensity colour.
colour = make_high_colour(colour);
// A very ugly thing sometimes gets an upgraded attack flavour.
att_flav = _very_ugly_thing_flavour_upgrade(att_flav);
// Pick a compatible resistance for this attack flavour.
ugly_thing_add_resistance(true, att_flav);
}
void ghost_demon::ugly_thing_add_resistance(bool very_ugly,
mon_attack_flavour u_att_flav)
{
resists.elec = 0;
resists.poison = 0;
resists.fire = 0;
resists.sticky_flame = false;
resists.cold = 0;
resists.acid = 0;
resists.rotting = false;
switch (u_att_flav)
{
case AF_FIRE:
case AF_NAPALM:
resists.fire = (very_ugly ? 2 : 1);
resists.sticky_flame = true;
break;
case AF_ACID:
resists.acid = (very_ugly ? 2 : 1);
break;
case AF_POISON_NASTY:
case AF_POISON_MEDIUM:
resists.poison = (very_ugly ? 2 : 1);
break;
case AF_ELEC:
resists.elec = (very_ugly ? 2 : 1);
break;
case AF_DISEASE:
case AF_ROT:
resists.rotting = true;
break;
case AF_COLD:
resists.cold = (very_ugly ? 2 : 1);
break;
default:
break;
}
}
void ghost_demon::init_dancing_weapon(const item_def& weapon, int power)
{
int mass = item_mass(weapon);
int delay = property(weapon, PWPN_SPEED);
int damg = property(weapon, PWPN_DAMAGE);
if (power > 150)
power = 150;
resists.poison = 1;
resists.fire = 1;
resists.cold = 1;
resists.elec = 1;
colour = weapon.colour;
fly = FL_LEVITATE;
// We want Tukima to reward characters who invest heavily in both
// carrying capacity and enchantments skill. Therefore, heavy
// weapons are a bit stronger when animated, and benefit much more
// from very high skill.
// First set up what the monsters will look like at very high skill.
// Daggers are weak here! In the table, "44+22" means d44+d22 with
// d22 being base damage and d44 coming from power.
// Giant spiked club: speed 12, 44+22 damage, 35 AC, 70 HP, 16 EV
// Bardiche: speed 10, 40+20 damage, 20 AC, 40 HP, 15 EV
// Katana: speed 17, 26+13 damage, 16 AC, 32 HP, 18 EV
// Dagger: speed 20, 8+ 4 damage, 2 AC, 4 HP, 20 EV
// Quick blade: speed 23, 10+ 5 damage, 5 AC, 10 HP, 22 EV
// Sabre: speed 18, 14+ 7 damage, 9 AC, 18 HP, 19 EV
xl = 15;
speed = 30 - delay;
ev = 25 - delay / 2;
ac = mass / 10;
damage = 2 * damg;
max_hp = mass / 5;
// If you aren't an awesome spellcaster, nerf the weapons. Do it in
// a way that lays most of the penalty on heavy weapons.
speed = std::max(3, speed - (10 - power / 15));
ev = std::max(3, ev - (10 - power / 15));
ac = ac * power / 200;
max_hp = std::max(5, max_hp * power / 150);
damage = std::max(1, damage * power / 150);
// For a spellpower 75 character (typical late midgame mage with no Ench
// focus), we have:
// Giant spiked club: speed 7, 22+22 damage, 17 AC, 35 HP, 11 EV
// Bardiche: speed 5, 20+20 damage, 10 AC, 20 HP, 10 EV
// Katana: speed 12, 13+13 damage, 8 AC, 16 HP, 13 EV
// Dagger: speed 15, 4+4 damage, 1 AC, 5 HP, 15 EV
// Quick blade: speed 18, 5+5 damage, 2 AC, 5 HP, 17 EV
// Sabre: speed 13, 7+7 damage, 4 AC, 9 HP, 14 EV
// At spellpower 37 (early game character with focus on Ench):
// Giant spiked club: speed 5, 11+22 damage, 8 AC, 17 HP, 9 EV
// Bardiche: speed 3, 10+20 damage, 5 AC, 10 HP, 8 EV
// Katana: speed 10, 6+13 damage, 4 AC, 8 HP, 11 EV
// Dagger: speed 13, 2+4 damage, 0 AC, 5 HP, 13 EV
// Quick blade: speed 16, 2+5 damage, 1 AC, 5 HP, 15 EV
// Sabre: speed 11, 3+7 damage, 2 AC, 5 HP, 12 EV
}
static spell_type search_first_list(int ignore_spell)
{
for (unsigned i = 0;
i < sizeof(search_order_conj) / sizeof(*search_order_conj); ++i)
{
if (search_order_conj[i] == SPELL_NO_SPELL)
return (SPELL_NO_SPELL);
if (search_order_conj[i] == ignore_spell)
continue;
if (you.has_spell(search_order_conj[i]))
return (search_order_conj[i]);
}
return (SPELL_NO_SPELL);
}
static spell_type search_second_list(int ignore_spell)
{
for (unsigned i = 0;
i < sizeof(search_order_third) / sizeof(*search_order_third); ++i)
{
if (search_order_third[i] == SPELL_NO_SPELL)
return (SPELL_NO_SPELL);
if (search_order_third[i] == ignore_spell)
continue;
if (you.has_spell(search_order_third[i]))
return (search_order_third[i]);
}
return (SPELL_NO_SPELL);
}
static spell_type search_third_list(int ignore_spell)
{
for (unsigned i = 0;
i < sizeof(search_order_misc) / sizeof(*search_order_misc); ++i)
{
if (search_order_misc[i] == SPELL_NO_SPELL)
return (SPELL_NO_SPELL);
if (search_order_misc[i] == ignore_spell)
continue;
if (you.has_spell(search_order_misc[i]))
return (search_order_misc[i]);
}
return (SPELL_NO_SPELL);
}
// Used when creating ghosts: goes through and finds spells for the
// ghost to cast. Death is a traumatic experience, so ghosts only
// remember a few spells.
void ghost_demon::add_spells()
{
spells.init(SPELL_NO_SPELL);
spells[0] = search_first_list(SPELL_NO_SPELL);
spells[1] = search_first_list(spells[0]);
spells[2] = search_second_list(SPELL_NO_SPELL);
spells[3] = search_third_list(SPELL_DIG);
if (spells[3] == SPELL_NO_SPELL)
spells[3] = search_first_list(SPELL_NO_SPELL);
spells[4] = search_third_list(spells[3]);
if (spells[4] == SPELL_NO_SPELL)
spells[4] = search_first_list(spells[3]);
if (you.has_spell(SPELL_DIG))
spells[4] = SPELL_DIG;
// Look for Blink or Teleport Self for the emergency slot.
if (you.has_spell(SPELL_CONTROLLED_BLINK)
|| you.has_spell(SPELL_BLINK))
{
spells[5] = SPELL_CONTROLLED_BLINK;
}
if (you.has_spell(SPELL_TELEPORT_SELF))
spells[5] = SPELL_TELEPORT_SELF;
for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i)
spells[i] = translate_spell(spells[i]);
}
// When passed the number for a player spell, returns the equivalent
// monster spell. Returns SPELL_NO_SPELL on failure (no equivalent).
spell_type ghost_demon::translate_spell(spell_type spel) const
{
switch (spel)
{
case SPELL_CONTROLLED_BLINK:
return (SPELL_BLINK); // approximate
case SPELL_DEMONIC_HORDE:
return (SPELL_CALL_IMP);
case SPELL_AGONY:
case SPELL_SYMBOL_OF_TORMENT:
// Too powerful to give ghosts Torment for Agony? Nah.
return (SPELL_SYMBOL_OF_TORMENT);
case SPELL_DELAYED_FIREBALL:
return (SPELL_FIREBALL);
case SPELL_PETRIFY:
return (SPELL_PARALYSE);
default:
break;
}
return (spel);
}
std::vector<ghost_demon> ghost_demon::find_ghosts()
{
std::vector<ghost_demon> gs;
if (!you.is_undead)
{
ghost_demon player;
player.init_player_ghost();
announce_ghost(player);
gs.push_back(player);
}
// Pick up any other ghosts that happen to be on the level if we
// have space. If the player is undead, add one to the ghost quota
// for the level.
find_extra_ghosts(gs, n_extra_ghosts() + 1 - gs.size());
return (gs);
}
void ghost_demon::find_transiting_ghosts(
std::vector<ghost_demon> &gs, int n)
{
if (n <= 0)
return;
const m_transit_list *mt = get_transit_list(level_id::current());
if (mt)
{
for (m_transit_list::const_iterator i = mt->begin();
i != mt->end() && n > 0; ++i)
{
if (i->mons.type == MONS_PLAYER_GHOST)
{
const monsters &m = i->mons;
if (m.ghost.get())
{
announce_ghost(*m.ghost);
gs.push_back(*m.ghost);
--n;
}
}
}
}
}
void ghost_demon::announce_ghost(const ghost_demon &g)
{
#if DEBUG_BONES | DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Saving ghost: %s", g.name.c_str());
#endif
}
void ghost_demon::find_extra_ghosts( std::vector<ghost_demon> &gs, int n )
{
for (monster_iterator mi; mi && n > 0; ++mi)
{
if (mi->type == MONS_PLAYER_GHOST && mi->ghost.get())
{
// Bingo!
announce_ghost(*(mi->ghost));
gs.push_back(*(mi->ghost));
--n;
}
}
// Check the transit list for the current level.
find_transiting_ghosts(gs, n);
}
// Returns the number of extra ghosts allowed on the level.
int ghost_demon::n_extra_ghosts()
{
const int lev = you.your_level + 1;
const int subdepth = subdungeon_depth(you.where_are_you, you.your_level);
if (you.level_type == LEVEL_PANDEMONIUM
|| you.level_type == LEVEL_ABYSS
|| (you.level_type == LEVEL_DUNGEON
&& (you.where_are_you == BRANCH_CRYPT
|| you.where_are_you == BRANCH_TOMB
|| you.where_are_you == BRANCH_HALL_OF_ZOT
|| player_in_hell()))
|| lev > 22)
{
return (MAX_GHOSTS - 1);
}
if (you.where_are_you == BRANCH_ECUMENICAL_TEMPLE)
return (0);
// No multiple ghosts until level 9 of the main dungeon.
if (lev < 9 && you.where_are_you == BRANCH_MAIN_DUNGEON
|| subdepth < 2 && you.where_are_you == BRANCH_LAIR
|| subdepth < 2 && you.where_are_you == BRANCH_ORCISH_MINES)
{
return (0);
}
if (you.where_are_you == BRANCH_LAIR
|| you.where_are_you == BRANCH_ORCISH_MINES
|| you.where_are_you == BRANCH_MAIN_DUNGEON && lev < 15)
{
return (1);
}
return (1 + x_chance_in_y(lev, 20) + x_chance_in_y(lev, 40));
}
// Sanity checks for some ghost values.
bool debug_check_ghosts()
{
for (unsigned int k = 0; k < ghosts.size(); ++k)
{
ghost_demon ghost = ghosts[k];
// Values greater than the allowed maximum or less then the
// allowed minimum signalise bugginess.
if (ghost.damage < 0 || ghost.damage > MAX_GHOST_DAMAGE)
return (false);
if (ghost.max_hp < 1 || ghost.max_hp > MAX_GHOST_HP)
return (false);
if (ghost.xl < 1 || ghost.xl > 27)
return (false);
if (ghost.ev > MAX_GHOST_EVASION)
return (false);
if (ghost.speed < MIN_GHOST_SPEED || ghost.speed > MAX_GHOST_SPEED)
return (false);
if (ghost.resists.fire < -3 || ghost.resists.fire > 3)
return (false);
if (ghost.resists.cold < -3 || ghost.resists.cold > 3)
return (false);
if (ghost.resists.elec < 0)
return (false);
if (ghost.brand < SPWPN_NORMAL || ghost.brand > MAX_PAN_LORD_BRANDS)
return (false);
if (ghost.species < 0 || ghost.species >= NUM_SPECIES)
return (false);
if (ghost.job < JOB_FIGHTER || ghost.job >= NUM_JOBS)
return (false);
if (ghost.best_skill < SK_FIGHTING || ghost.best_skill >= NUM_SKILLS)
return (false);
if (ghost.best_skill_level < 0 || ghost.best_skill_level > 27)
return (false);
if (ghost.religion < GOD_NO_GOD || ghost.religion >= NUM_GODS)
return (false);
if (ghost.brand == SPWPN_HOLY_WRATH || is_good_god(ghost.religion))
return (false);
// Only (very) ugly things get non-plain attack types and
// flavours.
if (ghost.att_type != AT_HIT || ghost.att_flav != AF_PLAIN)
return (false);
// Only Pandemonium lords cycle colours.
if (ghost.cycle_colours)
return (false);
// Name validation.
if (!validate_player_name(ghost.name, false))
return (false);
if (ghost.name.length() > (kNameLen - 1) || ghost.name.length() == 0)
return (false);
if (ghost.name != trimmed_string(ghost.name))
return (false);
// Check for non-existing spells.
for (int sp = 0; sp < NUM_MONSTER_SPELL_SLOTS; ++sp)
if (ghost.spells[sp] < 0 || ghost.spells[sp] >= NUM_SPELLS)
return (false);
}
return (true);
}