From c5950794e9a3e338fc2568e6f0e827868c821885 Mon Sep 17 00:00:00 2001 From: Jude Brown Date: Thu, 12 Nov 2009 09:01:44 +1000 Subject: Rename monspeak/monplace/monstuff to mon-speak/place/stuff. I'm pretty sure I've managed to replace every instance of a reference to these files, so I hopefully haven't missed anything. --- crawl-ref/source/mon-place.cc | 3525 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3525 insertions(+) create mode 100644 crawl-ref/source/mon-place.cc (limited to 'crawl-ref/source/mon-place.cc') diff --git a/crawl-ref/source/mon-place.cc b/crawl-ref/source/mon-place.cc new file mode 100644 index 0000000000..15c805455c --- /dev/null +++ b/crawl-ref/source/mon-place.cc @@ -0,0 +1,3525 @@ +/* + * File: monplace.cc + * Summary: Functions used when placing monsters in the dungeon. + * Written by: Linley Henzell + */ + +#include "AppHdr.h" + +#include + +#include "mon-place.h" + +#include "arena.h" +#include "branch.h" +#include "directn.h" // for the Compass +#include "fprop.h" +#include "externs.h" +#include "options.h" +#include "ghost.h" +#include "lev-pand.h" +#include "los.h" +#include "makeitem.h" +#include "message.h" +#include "mon-behv.h" +#include "mon-gear.h" +#include "mon-pick.h" +#include "mon-util.h" +#include "mon-stuff.h" +#include "player.h" +#include "random.h" +#include "religion.h" +#include "state.h" +#include "stuff.h" +#include "terrain.h" +#include "traps.h" +#include "view.h" + +static std::vector vault_mon_types; +static std::vector vault_mon_bases; +static std::vector vault_mon_weights; + +#define VAULT_MON_TYPES_KEY "vault_mon_types" +#define VAULT_MON_BASES_KEY "vault_mon_bases" +#define VAULT_MON_WEIGHTS_KEY "vault_mon_weights" + +// NEW place_monster -- note that power should be set to: +// 51 for abyss +// 52 for pandemonium +// x otherwise + +// proximity is the same as for mons_place: +// 0 is no restrictions +// 1 attempts to place near player +// 2 attempts to avoid player LOS + +#define BIG_BAND 20 + +static monster_type _resolve_monster_type(monster_type mon_type, + proximity_type proximity, + monster_type &base_type, + coord_def &pos, + unsigned mmask, + dungeon_char_type *stair_type, + int *lev_mons); + +static void _define_zombie(int mid, monster_type ztype, monster_type cs, + int power, coord_def pos); +static monster_type _band_member(band_type band, int power); +static band_type _choose_band(int mon_type, int power, int &band_size); +// static int _place_monster_aux(int mon_type, beh_type behaviour, int target, +// int px, int py, int power, int extra, +// bool first_band_member, int dur = 0); + +static int _place_monster_aux(const mgen_data &mg, bool first_band_member, + bool force_pos = false); + +// Returns whether actual_feat is compatible with feat_wanted for monster +// movement and generation. +bool feat_compatible(dungeon_feature_type feat_wanted, + dungeon_feature_type actual_feat) +{ + if (feat_wanted == DNGN_FLOOR) + { + return (actual_feat >= DNGN_FLOOR + && actual_feat != DNGN_BUILDER_SPECIAL_WALL + || actual_feat == DNGN_SHALLOW_WATER); + } + + if (feat_wanted >= DNGN_ROCK_WALL + && feat_wanted <= DNGN_CLEAR_PERMAROCK_WALL) + { + // A monster can only move through or inhabit permanent rock if that's + // exactly what it's asking for. + if (actual_feat == DNGN_PERMAROCK_WALL + || actual_feat == DNGN_CLEAR_PERMAROCK_WALL) + { + return (feat_wanted == DNGN_PERMAROCK_WALL + || feat_wanted == DNGN_CLEAR_PERMAROCK_WALL); + } + + return (actual_feat >= DNGN_ROCK_WALL + && actual_feat <= DNGN_CLEAR_PERMAROCK_WALL); + } + + return (feat_wanted == actual_feat + || (feat_wanted == DNGN_DEEP_WATER + && (actual_feat == DNGN_SHALLOW_WATER + || actual_feat == DNGN_FOUNTAIN_BLUE))); +} + +// Can this monster survive on actual_grid? +// +// If you have an actual monster, use this instead of the overloaded function +// that uses only the monster class to make decisions. +bool monster_habitable_grid(const monsters *m, + dungeon_feature_type actual_grid) +{ + // Zombified monsters enjoy the same habitat as their original. + const monster_type montype = mons_is_zombified(m) ? mons_zombie_base(m) + : m->type; + + return (monster_habitable_grid(montype, actual_grid, mons_flies(m), + m->cannot_move())); +} + +inline static bool _mons_airborne(int mcls, int flies, bool paralysed) +{ + if (flies == -1) + flies = mons_class_flies(mcls); + + return (paralysed ? flies == FL_LEVITATE : flies != FL_NONE); +} + +// Can monsters of class monster_class live happily on actual_grid? +// Use flies == true to pretend the monster can fly. +// +// [dshaligram] We're trying to harmonise the checks from various places into +// one check, so we no longer care if a water elemental springs into existence +// on dry land, because they're supposed to be able to move onto dry land +// anyway. +bool monster_habitable_grid(monster_type montype, + dungeon_feature_type actual_grid, + int flies, bool paralysed) +{ + // No monster may be placed on open sea. + if (actual_grid == DNGN_OPEN_SEA) + return (false); + + const dungeon_feature_type feat_preferred = + habitat2grid(mons_class_primary_habitat(montype)); + const dungeon_feature_type feat_nonpreferred = + habitat2grid(mons_class_secondary_habitat(montype)); + + // Special check for fire elementals since their habitat is floor which + // is generally considered compatible with shallow water. + if (montype == MONS_FIRE_ELEMENTAL && feat_is_watery(actual_grid)) + return (false); + + // Krakens are too large for shallow water. + if (montype == MONS_KRAKEN && actual_grid == DNGN_SHALLOW_WATER) + return (false); + + if (feat_compatible(feat_preferred, actual_grid) + || (feat_nonpreferred != feat_preferred + && feat_compatible(feat_nonpreferred, actual_grid))) + { + return (true); + } + + // [dshaligram] Flying creatures are all DNGN_FLOOR, so we + // only have to check for the additional valid grids of deep + // water and lava. + if (_mons_airborne(montype, flies, paralysed) + && (actual_grid == DNGN_LAVA || actual_grid == DNGN_DEEP_WATER)) + { + return (true); + } + + return (false); +} + +// Returns true if the monster can submerge in the given grid. +bool monster_can_submerge(const monsters *mons, dungeon_feature_type grid) +{ + if (mons->type == MONS_TRAPDOOR_SPIDER && grid == DNGN_FLOOR) + return (!find_trap(mons->pos())); + + switch (mons_primary_habitat(mons)) + { + case HT_WATER: + // Monsters can submerge in shallow water - this is intentional. + return (feat_is_watery(grid)); + + case HT_LAVA: + return (grid == DNGN_LAVA); + + default: + return (false); + } +} + +static bool _need_moderate_ood(int lev_mons) +{ + return (env.turns_on_level > 700 - lev_mons * 117); +} + +static bool _need_super_ood(int lev_mons) +{ + return (env.turns_on_level > 1400 - lev_mons * 117 + && one_chance_in(5000)); +} + +static int _fuzz_mons_level(int level) +{ + if (one_chance_in(7)) + { + const int fuzz = random2avg(9, 2); + return (fuzz > 4? level + fuzz - 4 : level); + } + return (level); +} + +static void _hell_spawn_random_monsters() +{ + // Monster generation in the Vestibule drops off quickly. + const int taper_off_turn = 500; + int genodds = 240; + // genodds increases once you've spent more than 500 turns in Hell. + if (env.turns_on_level > taper_off_turn) + { + genodds += (env.turns_on_level - taper_off_turn); + genodds = (genodds < 0 ? 20000 : std::min(genodds, 20000)); + } + + if (x_chance_in_y(5, genodds)) + { + mgen_data mg(WANDERING_MONSTER); + mg.proximity = (one_chance_in(10) ? PROX_NEAR_STAIRS + : PROX_AWAY_FROM_PLAYER); + mons_place(mg); + viewwindow(false); + } +} + +//#define DEBUG_MON_CREATION + +// This function is now only called about once every 5 turns. (Used to be +// every turn independent of how much time an action took, which was not ideal.) +// To arrive at spawning rates close to how they used to be, replace the +// one_chance_in(value) checks with the new x_chance_in_y(5, value). (jpeg) +void spawn_random_monsters() +{ + if (crawl_state.arena) + return; + +#ifdef DEBUG_MON_CREATION + mpr("in spawn_random_monsters()", MSGCH_DIAGNOSTICS); +#endif + if (player_in_branch(BRANCH_VESTIBULE_OF_HELL)) + { + _hell_spawn_random_monsters(); + return; + } + + if (env.spawn_random_rate == 0) + { +#ifdef DEBUG_MON_CREATION + mpr("random monster gen turned off", MSGCH_DIAGNOSTICS); +#endif + return; + } + + const int rate = (you.char_direction == GDT_DESCENDING) ? + env.spawn_random_rate : 8; + + // Place normal dungeon monsters, but not in player LOS. + if (you.level_type == LEVEL_DUNGEON && x_chance_in_y(5, rate)) + { +#ifdef DEBUG_MON_CREATION + mpr("Create wandering monster...", MSGCH_DIAGNOSTICS); +#endif + proximity_type prox = (one_chance_in(10) ? PROX_NEAR_STAIRS + : PROX_AWAY_FROM_PLAYER); + + // The rules change once the player has picked up the Orb... + if (you.char_direction == GDT_ASCENDING) + prox = (one_chance_in(6) ? PROX_CLOSE_TO_PLAYER : PROX_ANYWHERE); + + mgen_data mg(WANDERING_MONSTER); + mg.proximity = prox; + mons_place(mg); + viewwindow(false); + return; + } + + // Place Abyss monsters. (Now happens regularly every 5 turns which might + // look a bit strange for a place as chaotic as the Abyss. Then again, + // the player is unlikely to meet all of them and notice this.) + if (you.level_type == LEVEL_ABYSS + && (you.char_direction != GDT_GAME_START + || x_chance_in_y(5, rate)) + && (you.religion != GOD_CHEIBRIADOS || coinflip())) + { + mons_place(mgen_data(WANDERING_MONSTER)); + viewwindow(false); + return; + } + + // Place Pandemonium monsters. + if (you.level_type == LEVEL_PANDEMONIUM && x_chance_in_y(5, rate)) + { + pandemonium_mons(); + viewwindow(false); + return; + } + + // A portal vault *might* decide to turn on random monster spawning, + // but it's off by default. + if (you.level_type == LEVEL_PORTAL_VAULT && x_chance_in_y(5, rate)) + { + mons_place(mgen_data(WANDERING_MONSTER)); + viewwindow(false); + } + + // No random monsters in the Labyrinth. +} + +monster_type pick_random_monster(const level_id &place) +{ + int level; + if (place.level_type == LEVEL_PORTAL_VAULT) + level = you.your_level; + else + level = place.absdepth(); + return pick_random_monster(place, level, level); +} + +monster_type pick_random_monster(const level_id &place, int power, + int &lev_mons) +{ + if (crawl_state.arena) + { + monster_type type = arena_pick_random_monster(place, power, lev_mons); + if (type != RANDOM_MONSTER) + return (type); + } + + if (place.level_type == LEVEL_LABYRINTH) + return (MONS_PROGRAM_BUG); + + if (place == BRANCH_ECUMENICAL_TEMPLE) + return (MONS_PROGRAM_BUG); + + if (place.level_type == LEVEL_PORTAL_VAULT) + { + monster_type base_type = (monster_type) 0; + coord_def dummy1; + dungeon_char_type dummy2; + monster_type type = + _resolve_monster_type(RANDOM_MONSTER, PROX_ANYWHERE, base_type, + dummy1, 0, &dummy2, &lev_mons); + +#if DEBUG || DEBUG_DIAGNOSTICS + if (base_type != 0 && base_type != MONS_PROGRAM_BUG) + mpr("Random portal vault mon discarding base type.", + MSGCH_ERROR); +#endif + return (type); + } + + monster_type mon_type = MONS_PROGRAM_BUG; + + lev_mons = power; + + if (place == BRANCH_MAIN_DUNGEON + && lev_mons != 51 && one_chance_in(4)) + { + lev_mons = random2(power); + } + + if (place == BRANCH_MAIN_DUNGEON + && lev_mons <= 27) + { + // If on D:1, allow moderately out-of-depth monsters only after + // a certain elapsed turn count on the level (currently 700 turns). + if (lev_mons || _need_moderate_ood(lev_mons)) + lev_mons = _fuzz_mons_level(lev_mons); + + // Potentially nasty surprise, but very rare. + if (_need_super_ood(lev_mons)) + lev_mons += random2(12); + + // Slightly out of depth monsters are more common: + // [ds] Replaced with a fuzz above for a more varied mix. + //if (need_moderate_ood(lev_mons)) + // lev_mons += random2(5); + + lev_mons = std::min(27, lev_mons); + } + + // Abyss or Pandemonium. Almost never called from Pan; probably only + // if a random demon gets summon anything spell. + if (lev_mons == 51 + || place.level_type == LEVEL_PANDEMONIUM + || place.level_type == LEVEL_ABYSS) + { + do + { + int count = 0; + + do + { + // was: random2(400) {dlb} + mon_type = static_cast( random2(NUM_MONSTERS) ); + count++; + } + while (mons_abyss(mon_type) == 0 && count < 2000); + + if (count == 2000) + return (MONS_PROGRAM_BUG); + if (crawl_state.arena && arena_veto_random_monster(mon_type)) + continue; + } + while (random2avg(100, 2) > mons_rare_abyss(mon_type) + && !one_chance_in(100)); + } + else + { + int level, diff, chance; + + lev_mons = std::min(30, lev_mons); + + int i; + for (i = 0; i < 10000; ++i) + { + int count = 0; + + do + { + mon_type = static_cast(random2(NUM_MONSTERS)); + count++; + } + while (mons_rarity(mon_type, place) == 0 && count < 2000); + + if (count == 2000) + return (MONS_PROGRAM_BUG); + + if (crawl_state.arena && arena_veto_random_monster(mon_type)) + continue; + + level = mons_level(mon_type, place); + diff = level - lev_mons; + chance = mons_rarity(mon_type, place) - (diff * diff); + + if ((lev_mons >= level - 5 && lev_mons <= level + 5) + && random2avg(100, 2) <= chance) + { + break; + } + } + + if (i == 10000) + return (MONS_PROGRAM_BUG); + } + + return (mon_type); +} + +static bool _can_place_on_trap(int mon_type, trap_type trap) +{ + if (trap == TRAP_TELEPORT) + return (false); + + if (trap == TRAP_SHAFT) + { + if (mon_type == RANDOM_MONSTER) + return (false); + + return (mons_class_flies(mon_type) != FL_NONE + || get_monster_data(mon_type)->size == SIZE_TINY); + } + + return (true); +} + +bool drac_colour_incompatible(int drac, int colour) +{ + return (drac == MONS_DRACONIAN_SCORCHER && colour == MONS_WHITE_DRACONIAN); +} + +static monster_type _resolve_monster_type(monster_type mon_type, + proximity_type proximity, + monster_type &base_type, + coord_def &pos, + unsigned mmask, + dungeon_char_type *stair_type, + int *lev_mons) +{ + if (mon_type == RANDOM_DRACONIAN) + { + // Pick any random drac, constrained by colour if requested. + do + { + mon_type = + static_cast( + random_range(MONS_BLACK_DRACONIAN, + MONS_DRACONIAN_SCORCHER)); + } + while (base_type != MONS_PROGRAM_BUG + && mon_type != base_type + && (mons_species(mon_type) == mon_type + || drac_colour_incompatible(mon_type, base_type))); + } + else if (mon_type == RANDOM_BASE_DRACONIAN) + mon_type = random_draconian_monster_species(); + else if (mon_type == RANDOM_NONBASE_DRACONIAN) + { + mon_type = + static_cast( + random_range(MONS_DRACONIAN_CALLER, MONS_DRACONIAN_SCORCHER)); + } + + // (2) Take care of non-draconian random monsters. + else if (mon_type == RANDOM_MONSTER) + { + level_id place = level_id::current(); + + // Respect destination level for staircases. + if (proximity == PROX_NEAR_STAIRS) + { + int tries = 0; + int pval = 0; + while (++tries <= 320) + { + pos = random_in_bounds(); + + if (actor_at(pos)) + continue; + + // Is the grid verboten? + if (!unforbidden( pos, mmask )) + continue; + + // Don't generate monsters on top of teleport traps. + const trap_def* ptrap = find_trap(pos); + if (ptrap && !_can_place_on_trap(mon_type, ptrap->type)) + continue; + + // Check whether there's a stair + // and whether it leads to another branch. + pval = near_stairs(pos, 1, *stair_type, place.branch); + + // No monsters spawned in the Temple. + if (branches[place.branch].id == BRANCH_ECUMENICAL_TEMPLE) + continue; + + // Found a position near the stairs! + if (pval > 0) + break; + } + + if (tries > 320) + { + // Give up and try somewhere else. + proximity = PROX_AWAY_FROM_PLAYER; + } + else + { + if (*stair_type == DCHAR_STAIRS_DOWN) // deeper level + ++*lev_mons; + else if (*stair_type == DCHAR_STAIRS_UP) // higher level + { + // Monsters don't come from outside the dungeon. + if (*lev_mons <= 0) + { + proximity = PROX_AWAY_FROM_PLAYER; + // In that case lev_mons stays as it is. + } + else + --*lev_mons; + } + } + } // end proximity check + + if (place == BRANCH_HALL_OF_BLADES) + mon_type = MONS_DANCING_WEAPON; + else + { + if (you.level_type == LEVEL_PORTAL_VAULT) + { + if (vault_mon_types.size() == 0) + return (MONS_PROGRAM_BUG); + + int i = choose_random_weighted(vault_mon_weights.begin(), + vault_mon_weights.end()); + int type = vault_mon_types[i]; + int base = vault_mon_bases[i]; + + if (type == -1) + { + place = level_id::from_packed_place(base); + // If lev_mons is set to you.your_level, it was probably + // set as a default meaning "the current dungeon depth", + // which for a portal vault using its own definition + // of random monsters means "the depth of whatever place + // we're using for picking the random monster". + if (*lev_mons == you.your_level) + *lev_mons = place.absdepth(); + // pick_random_monster() is called below + } + else + { + base_type = (monster_type) base; + mon_type = (monster_type) type; + if (mon_type == RANDOM_DRACONIAN + || mon_type == RANDOM_BASE_DRACONIAN + || mon_type == RANDOM_NONBASE_DRACONIAN) + { + mon_type = + _resolve_monster_type(mon_type, proximity, + base_type, pos, mmask, + stair_type, lev_mons); + } + return (mon_type); + } + } + + int tries = 0; + while (tries++ < 300) + { + // Now pick a monster of the given branch and level. + mon_type = pick_random_monster(place, *lev_mons, *lev_mons); + + // Don't allow monsters too stupid to use stairs (e.g. + // non-spectral zombified undead) to be placed near + // stairs. + if (proximity != PROX_NEAR_STAIRS + || mons_class_can_use_stairs(mon_type)) + { + break; + } + } + + if (proximity == PROX_NEAR_STAIRS && tries >= 300) + { + proximity = PROX_AWAY_FROM_PLAYER; + + // Reset target level. + if (*stair_type == DCHAR_STAIRS_DOWN) + --*lev_mons; + else if (*stair_type == DCHAR_STAIRS_UP) + ++*lev_mons; + + mon_type = pick_random_monster(place, *lev_mons, *lev_mons); + } + } + } + return (mon_type); +} + +// A short function to check the results of near_stairs(). +// Returns 0 if the point is not near stairs. +// Returns 1 if the point is near unoccupied stairs. +// Returns 2 if the point is near player-occupied stairs. +static int _is_near_stairs(coord_def &p) +{ + int result = 0; + + for (int i = -1; i <= 1; ++i) + for (int j = -1; j <= 1; ++j) + { + if (!in_bounds(p)) + continue; + + const dungeon_feature_type feat = grd(p); + if (feat_is_stair(feat)) + { + // Shouldn't matter for escape hatches. + if (feat_is_escape_hatch(feat)) + continue; + + // Should there be several stairs, don't overwrite the + // player on stairs info. + if (result < 2) + result = (p == you.pos()) ? 2 : 1; + } + } + + return (result); +} + +// Checks if the monster is ok to place at mg_pos. If force_location +// is true, then we'll be less rigorous in our checks, in particular +// allowing land monsters to be placed in shallow water and water +// creatures in fountains. +static bool _valid_monster_generation_location( + const mgen_data &mg, + const coord_def &mg_pos) +{ + if (!in_bounds(mg_pos) || actor_at(mg_pos)) + return (false); + + const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + if (!monster_habitable_grid(montype, grd(mg_pos), + mons_class_flies(montype), false) + || (mg.behaviour != BEH_FRIENDLY && is_sanctuary(mg_pos))) + { + return (false); + } + + // Don't generate monsters on top of teleport traps. + // (How did they get there?) + const trap_def* ptrap = find_trap(mg_pos); + if (ptrap && !_can_place_on_trap(mg.cls, ptrap->type)) + return (false); + + return (true); +} + +static bool _valid_monster_generation_location(mgen_data &mg) +{ + return _valid_monster_generation_location(mg, mg.pos); +} + +int place_monster(mgen_data mg, bool force_pos) +{ +#ifdef DEBUG_MON_CREATION + mpr("in place_monster()", MSGCH_DIAGNOSTICS); +#endif + + int tries = 0; + dungeon_char_type stair_type = NUM_DCHAR_TYPES; + int id = -1; + + // (1) Early out (summoned to occupied grid). + if (mg.use_position() && monster_at(mg.pos)) + return (-1); + + mg.cls = _resolve_monster_type(mg.cls, mg.proximity, mg.base_type, + mg.pos, mg.map_mask, + &stair_type, &mg.power); + + if (mg.cls == MONS_NO_MONSTER) + return (-1); + + // (3) Decide on banding (good lord!) + int band_size = 1; + monster_type band_monsters[BIG_BAND]; // band monster types + band_monsters[0] = mg.cls; + + // The (very) ugly thing band colour. + static unsigned char ugly_colour = BLACK; + + if (mg.permit_bands()) + { +#ifdef DEBUG_MON_CREATION + mpr("Choose band members...", MSGCH_DIAGNOSTICS); +#endif + const band_type band = _choose_band(mg.cls, mg.power, band_size); + band_size++; + + for (int i = 1; i < band_size; ++i) + { + band_monsters[i] = _band_member(band, mg.power); + + // Get the (very) ugly thing band colour, so that all (very) + // ugly things in a band will start with it. + if ((band_monsters[i] == MONS_UGLY_THING + || band_monsters[i] == MONS_VERY_UGLY_THING) + && ugly_colour == BLACK) + { + ugly_colour = ugly_thing_random_colour(); + } + } + } + + // Set the (very) ugly thing band colour. + if (ugly_colour != BLACK) + mg.colour = ugly_colour; + + // Returns 2 if the monster is placed near player-occupied stairs. + int pval = _is_near_stairs(mg.pos); + if (mg.proximity == PROX_NEAR_STAIRS) + { + // For some cases disallow monsters on stairs. + if (mons_class_is_stationary(mg.cls) + || (pval == 2 // Stairs occupied by player. + && (mons_class_base_speed(mg.cls) == 0 + || grd(mg.pos) == DNGN_LAVA + || grd(mg.pos) == DNGN_DEEP_WATER))) + { + mg.proximity = PROX_AWAY_FROM_PLAYER; + } + } + + // (4) For first monster, choose location. This is pretty intensive. + bool proxOK; + bool close_to_player; + + // Player shoved out of the way? + bool shoved = false; + + if (!mg.use_position()) + { + tries = 0; + + // Try to pick a position that is + // a) not occupied + // b) compatible + // c) in the 'correct' proximity to the player + + while (true) + { + // Dropped number of tries from 60. + if (tries++ >= 45) + return (-1); + + // Placement already decided for PROX_NEAR_STAIRS. + // Else choose a random point on the map. + if (mg.proximity != PROX_NEAR_STAIRS) + mg.pos = random_in_bounds(); + + if (!_valid_monster_generation_location(mg)) + continue; + + // Is the grid verboten? + if (!unforbidden(mg.pos, mg.map_mask)) + continue; + + // Let's recheck these even for PROX_NEAR_STAIRS, just in case. + // Check proximity to player. + proxOK = true; + + switch (mg.proximity) + { + case PROX_ANYWHERE: + if (grid_distance( you.pos(), mg.pos ) < 2 + random2(3)) + proxOK = false; + break; + + case PROX_CLOSE_TO_PLAYER: + case PROX_AWAY_FROM_PLAYER: + // If this is supposed to measure los vs not los, + // then see_cell(mg.pos) should be used instead. (jpeg) + close_to_player = (distance(you.pos(), mg.pos) < 64); + + if (mg.proximity == PROX_CLOSE_TO_PLAYER && !close_to_player + || mg.proximity == PROX_AWAY_FROM_PLAYER && close_to_player) + { + proxOK = false; + } + break; + + case PROX_NEAR_STAIRS: + if (pval == 2) // player on stairs + { + if (mons_class_base_speed(mg.cls) == 0) + { + proxOK = false; + break; + } + // Swap the monster and the player spots, unless the + // monster was generated in lava or deep water. + if (grd(mg.pos) == DNGN_LAVA + || grd(mg.pos) == DNGN_DEEP_WATER) + { + proxOK = false; + break; + } + + // You can't be shoved if you're caught in a net. + if (you.caught()) + { + proxOK = false; + break; + } + + shoved = true; + coord_def mpos = mg.pos; + mg.pos = you.pos(); + you.moveto(mpos); + } + proxOK = (pval > 0); + break; + } + + if (!proxOK) + continue; + + // Cool.. passes all tests. + break; + } // end while... place first monster + } + else if (!_valid_monster_generation_location(mg)) + { + // Sanity check that the specified position is valid. + return (-1); + } + + id = _place_monster_aux(mg, true, force_pos); + + // Reset the (very) ugly thing band colour. + if (ugly_colour != BLACK) + ugly_colour = BLACK; + + // Bail out now if we failed. + if (id == -1) + return (-1); + + monsters *mon = &menv[id]; + if (mg.needs_patrol_point()) + { + mon->patrol_point = mon->pos(); +#ifdef DEBUG_PATHFIND + mprf("Monster %s is patrolling around (%d, %d).", + mon->name(DESC_PLAIN).c_str(), mon->pos().x, mon->pos().y); +#endif + } + + // Message to player from stairwell/gate appearance. + if (observe_cell(mg.pos) && mg.proximity == PROX_NEAR_STAIRS) + { + std::string msg; + + if (menv[id].visible_to(&you)) + msg = menv[id].name(DESC_CAP_A); + else if (shoved) + msg = "Something"; + + if (shoved) + { + msg += " shoves you out of the "; + if (stair_type == DCHAR_ARCH) + msg += "gateway!"; + else + msg += "stairwell!"; + mpr(msg.c_str()); + } + else if (!msg.empty()) + { + if (stair_type == DCHAR_STAIRS_DOWN) + msg += " comes up the stairs."; + else if (stair_type == DCHAR_STAIRS_UP) + msg += " comes down the stairs."; + else if (stair_type == DCHAR_ARCH) + msg += " comes through the gate."; + else + msg = ""; + + if (!msg.empty()) + mpr(msg.c_str()); + } + + // Special case: must update the view for monsters created + // in player LOS. + viewwindow(false); + } + + // Now, forget about banding if the first placement failed, or there are + // too many monsters already, or we successfully placed by stairs. + if (id >= MAX_MONSTERS - 30 || mg.proximity == PROX_NEAR_STAIRS) + return (id); + + // Not PROX_NEAR_STAIRS, so it will be part of a band, if there is any. + if (band_size > 1) + menv[id].flags |= MF_BAND_MEMBER; + + const bool priest = mon->is_priest(); + + mgen_data band_template = mg; + // (5) For each band monster, loop call to place_monster_aux(). + for (int i = 1; i < band_size; i++) + { + if (band_monsters[i] == MONS_NO_MONSTER) + break; + + band_template.cls = band_monsters[i]; + + // We don't want to place a unique that has already been + // generated. + if (mons_is_unique(band_template.cls) + && you.unique_creatures[band_template.cls]) + { + continue; + } + + const int band_id = _place_monster_aux(band_template, false); + if (band_id != -1 && band_id != NON_MONSTER) + { + menv[band_id].flags |= MF_BAND_MEMBER; + // Priestly band leaders should have an entourage of the + // same religion. + if (priest) + menv[band_id].god = mon->god; + } + } + + // Placement of first monster, at least, was a success. + return (id); +} + +static int _place_monster_aux(const mgen_data &mg, + bool first_band_member, bool force_pos) +{ + int id = -1; + coord_def fpos; + + // Gotta be able to pick an ID. + for (id = 0; id < MAX_MONSTERS; id++) + if (menv[id].type == MONS_NO_MONSTER) + break; + + // Too many monsters on level? + if (id == MAX_MONSTERS) + return (-1); + + menv[id].reset(); + + const monster_type montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + + // Setup habitat and placement. + // If the space is occupied, try some neighbouring square instead. + if (first_band_member && in_bounds(mg.pos) + && (mg.behaviour == BEH_FRIENDLY || !is_sanctuary(mg.pos)) + && actor_at(mg.pos) == NULL + && (force_pos || monster_habitable_grid(montype, grd(mg.pos)))) + { + fpos = mg.pos; + } + else + { + int i; + // We'll try 1000 times for a good spot. + for (i = 0; i < 1000; ++i) + { + fpos = mg.pos + coord_def( random_range(-3, 3), + random_range(-3, 3) ); + + if (_valid_monster_generation_location(mg, fpos)) + break; + } + + // Did we really try 1000 times? + if (i == 1000) + return (-1); + } + + ASSERT(!monster_at(fpos)); + + if (crawl_state.arena + && arena_veto_place_monster(mg, first_band_member, fpos)) + { + return (-1); + } + + monsters &mons(menv[id]); + // Now, actually create the monster. (Wheeee!) + mons.type = mg.cls; + mons.base_monster = mg.base_type; + mons.number = mg.number; + + mons.moveto(fpos); + + // Link monster into monster grid. + mgrd(fpos) = id; + + // Generate a brand shiny new monster, or zombie. + if (mons_class_is_zombified(mg.cls)) + _define_zombie(id, mg.base_type, mg.cls, mg.power, fpos); + else + define_monster(id); + + // Is it a god gift? + if (mg.god != GOD_NO_GOD) + { + mons.god = mg.god; + mons.flags |= MF_GOD_GIFT; + } + // Not a god gift, give priestly monsters a god. + else if (mons_class_flag(mg.cls, M_PRIEST)) + { + switch (mons_genus(mg.cls)) + { + case MONS_ORC: + mons.god = GOD_BEOGH; + break; + case MONS_JELLY: + mons.god = GOD_JIYVA; + break; + case MONS_MUMMY: + case MONS_DRACONIAN: + case MONS_ELF: + // [ds] Vault defs can request priest monsters of unusual types. + default: + mons.god = GOD_NAMELESS; + break; + } + } + // 1 out of 7 non-priestly orcs are unbelievers. + else if (mons_genus(mg.cls) == MONS_ORC) + { + if (!one_chance_in(7)) + mons.god = GOD_BEOGH; + } + // The royal jelly belongs to Jiyva. + else if (mg.cls == MONS_ROYAL_JELLY) + mons.god = GOD_JIYVA; + // Angels and Daevas belong to TSO, but 1 out of 7 in the Abyss are + // adopted by Xom. + else if (mons_class_holiness(mg.cls) == MH_HOLY) + { + if (mg.level_type != LEVEL_ABYSS || !one_chance_in(7)) + mons.god = GOD_SHINING_ONE; + else + mons.god = GOD_XOM; + } + // 6 out of 7 demons in the Abyss belong to Lugonu. + else if (mons_class_holiness(mg.cls) == MH_DEMONIC) + { + if (mg.level_type == LEVEL_ABYSS && !one_chance_in(7)) + mons.god = GOD_LUGONU; + } + + // If the caller requested a specific colour for this monster, apply + // it now. + if (mg.colour != BLACK) + mons.colour = mg.colour; + + if (mg.mname != "") + mons.mname = mg.mname; + + // The return of Boris is now handled in monster_die(). Not setting + // this for Boris here allows for multiple Borises in the dungeon at + // the same time. - bwr + if (mons_is_unique(mg.cls)) + you.unique_creatures[mg.cls] = true; + + if (mons_class_flag(mg.cls, M_INVIS)) + mons.add_ench(ENCH_INVIS); + + if (mons_class_flag(mg.cls, M_CONFUSED)) + mons.add_ench(ENCH_CONFUSION); + + if (mg.cls == MONS_SHAPESHIFTER) + mons.add_ench(ENCH_SHAPESHIFTER); + + if (mg.cls == MONS_GLOWING_SHAPESHIFTER) + mons.add_ench(ENCH_GLOWING_SHAPESHIFTER); + + if (mg.cls == MONS_TOADSTOOL) + { + // This enchantment is a timer that counts down until death. + // It should last longer than the lifespan of a corpse, to avoid + // spawning mushrooms in the same place over and over. Aside + // from that, the value is slightly randomised to avoid + // simultaneous die-offs of mushroom rings. + mons.add_ench(ENCH_SLOWLY_DYING); + } + + if (mg.cls == MONS_BALLISTOMYCETE) + { + // This enchantment causes giant spore production. + mons.add_ench(ENCH_SPORE_PRODUCTION); + } + + if (monster_can_submerge(&mons, grd(fpos)) && !one_chance_in(5)) + mons.add_ench(ENCH_SUBMERGED); + + mons.flags |= MF_JUST_SUMMONED; + + // Don't leave shifters in their starting shape. + if (mg.cls == MONS_SHAPESHIFTER || mg.cls == MONS_GLOWING_SHAPESHIFTER) + { + no_messages nm; + monster_polymorph(&mons, RANDOM_MONSTER); + + // It's not actually a known shapeshifter if it happened to be + // placed in LOS of the player. + mons.flags &= ~MF_KNOWN_MIMIC; + } + + // dur should always be 1-6 for monsters that can be abjured. + const bool summoned = mg.abjuration_duration >= 1 + && mg.abjuration_duration <= 6; + + if (mg.cls == MONS_DANCING_WEAPON) + { + give_item(id, mg.power, summoned); + + // Dancing weapons *always* have a weapon. Fail to create them + // otherwise. + const item_def* wpn = mons.mslot_item(MSLOT_WEAPON); + if (!wpn) + { + mons.destroy_inventory(); + mons.reset(); + mgrd(fpos) = NON_MONSTER; + return (-1); + } + else + mons.colour = wpn->colour; + } + else if (mons_class_itemuse(mg.cls) >= MONUSE_STARTING_EQUIPMENT) + { + give_item(id, mg.power, summoned); + // Give these monsters a second weapon -- bwr + if (mons_class_wields_two_weapons(mg.cls)) + give_item(id, mg.power, summoned); + + unwind_var save_speedinc(mons.speed_increment); + mons.wield_melee_weapon(false); + } + + // Give manticores 8 to 16 spike volleys. + if (mg.cls == MONS_MANTICORE) + mons.number = 8 + random2(9); + + if (mg.cls == MONS_SLIME_CREATURE) + { + if (mg.number > 1) + { + // Boost HP to what it would have been if it had grown this + // big by merging. + mons.hit_points *= mg.number; + mons.max_hit_points *= mg.number; + } + } + + // Set attitude, behaviour and target. + mons.attitude = ATT_HOSTILE; + mons.behaviour = mg.behaviour; + + // Statues cannot sleep (nor wander but it means they are a bit + // more aware of the player than they'd be otherwise). + if (mons_is_statue(mg.cls)) + mons.behaviour = BEH_WANDER; + + mons.foe_memory = 0; + + // Setting attitude will always make the monster wander... + // If you want sleeping hostiles, use BEH_SLEEP since the default + // attitude is hostile. + if (mg.behaviour > NUM_BEHAVIOURS) + { + if (mg.behaviour == BEH_FRIENDLY) + mons.attitude = ATT_FRIENDLY; + + if (mg.behaviour == BEH_GOOD_NEUTRAL) + mons.attitude = ATT_GOOD_NEUTRAL; + + if (mg.behaviour == BEH_NEUTRAL) + mons.attitude = ATT_NEUTRAL; + + if (mg.behaviour == BEH_STRICT_NEUTRAL) + mons.attitude = ATT_STRICT_NEUTRAL; + + mons.behaviour = BEH_WANDER; + } + + if (summoned) + { + mons.mark_summoned(mg.abjuration_duration, true, + mg.summon_type); + } + mons.foe = mg.foe; + + // Initialise (very) ugly things and pandemonium demons. + if (mons.type == MONS_UGLY_THING + || mons.type == MONS_VERY_UGLY_THING) + { + ghost_demon ghost; + ghost.init_ugly_thing(mons.type == MONS_VERY_UGLY_THING, false, + mg.colour); + mons.set_ghost(ghost, false); + mons.uglything_init(); + } + else if (mons.type == MONS_PANDEMONIUM_DEMON) + { + ghost_demon ghost; + ghost.init_random_demon(); + mons.set_ghost(ghost); + mons.pandemon_init(); + } + else if (mons.type == MONS_DANCING_WEAPON) + { + ghost_demon ghost; + // We can't use monsters::weapon here because it wants to look + // at attack types, which are in the ghost structure we're + // building. + ASSERT( mons.mslot_item(MSLOT_WEAPON) ); + // Dancing weapons are placed at pretty high power. Remember, the + // player is fighting them one-on-one, while he will often summon + // several. + ghost.init_dancing_weapon(*(mons.mslot_item(MSLOT_WEAPON)), 180); + mons.set_ghost(ghost); + mons.dancing_weapon_init(); + } + + mark_interesting_monst(&mons, mg.behaviour); + + if (you.can_see(&mons)) + handle_seen_interrupt(&mons); + + if (crawl_state.arena) + arena_placed_monster(&mons); + + return (id); +} + +monster_type pick_random_zombie() +{ + static std::vector zombifiable; + if (zombifiable.empty()) + { + for (int i = 0; i < NUM_MONSTERS; ++i) + { + if (mons_species(i) != i || i == MONS_PROGRAM_BUG) + continue; + + const monster_type mcls = static_cast(i); + if (!mons_zombie_size(mcls)) + continue; + + zombifiable.push_back(mcls); + } + } + return (zombifiable[random2(zombifiable.size())]); +} + +monster_type pick_local_zombifiable_monster_type(int power) +{ + // Generates a dummy zombie likely to be found. + // Ripped wholly from _define_zombie(). + + power = std::min(27, power); + // How OOD this zombie can be. + int relax = 5; + + // Pick an appropriate creature to make a zombie out of, levelwise. + // The old code was generating absolutely incredible OOD zombies. + int cls; + while (true) + { + cls = pick_random_zombie(); + + bool ignore_rarity = false; + // On certain branches, zombie creation will fail if we use the + // mons_rarity() functions, because (for example) there are NO + // zombifiable "native" abyss creatures. Other branches where + // this is a problem are Hell levels and the Crypt. We have to + // watch for summoned zombies on other levels, too, such as the + // Temple, the Hall of Blades and the Slime Pits. + if (you.level_type != LEVEL_DUNGEON + || player_in_hell() + || player_in_branch(BRANCH_HALL_OF_ZOT) + || player_in_branch(BRANCH_VESTIBULE_OF_HELL) + || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) + || player_in_branch(BRANCH_CRYPT) + || player_in_branch(BRANCH_TOMB) + || player_in_branch(BRANCH_HALL_OF_BLADES) + || player_in_branch(BRANCH_SNAKE_PIT) + || player_in_branch(BRANCH_SLIME_PITS) + || one_chance_in(1000)) + { + ignore_rarity = true; + } + + // Don't make out-of-rarity zombies when we don't have to. + if (!ignore_rarity && mons_rarity(cls) == 0) + continue; + + // Check for rarity.. and OOD - identical to mons_place(). + int level, diff, chance; + + level = mons_level(cls) - 4; + diff = level - power; + + chance = (ignore_rarity) ? 100 + : mons_rarity(cls) - (diff * diff) / 2; + + if (power > level - relax && power < level + relax + && random2avg(100, 2) <= chance) + { + break; + } + + // Every so often, we'll relax the OOD restrictions. This + // avoids infinite loops. If we don't do this, things like + // creating a large skeleton on level 1 may hang the game! + if (one_chance_in(5)) + relax++; + } + + return (monster_type)cls; +} + +static void _define_zombie(int mid, monster_type ztype, monster_type cs, + int power, coord_def pos) +{ + ASSERT(mons_class_is_zombified(cs)); + + monster_type cls = MONS_PROGRAM_BUG; + monster_type mons_sec2 = MONS_PROGRAM_BUG; + zombie_size_type zombie_size = Z_NOZOMBIE; + bool ignore_rarity = false; + + power = std::min(27, power); + + // Set size based on zombie class (cs). + switch (cs) + { + case MONS_ZOMBIE_SMALL: + case MONS_SIMULACRUM_SMALL: + case MONS_SKELETON_SMALL: + zombie_size = Z_SMALL; + break; + + case MONS_ZOMBIE_LARGE: + case MONS_SIMULACRUM_LARGE: + case MONS_SKELETON_LARGE: + zombie_size = Z_BIG; + break; + + case MONS_SPECTRAL_THING: + break; + + default: + break; + } + + // That is, random creature from which to fashion undead. + if (ztype == MONS_NO_MONSTER) + { + // How OOD this zombie can be. + int relax = 5; + + // Pick an appropriate creature to make a zombie out of, + // levelwise. The old code was generating absolutely + // incredible OOD zombies. + while (true) + { + cls = pick_random_zombie(); + + // Actually pick a monster that is happy where we want to put it. + // Fish zombies on land are helpless and uncool. + if (!monster_habitable_grid(cls, grd(pos))) + continue; + + // On certain branches, zombie creation will fail if we use + // the mons_rarity() functions, because (for example) there + // are NO zombifiable "native" abyss creatures. Other branches + // where this is a problem are hell levels and the crypt. + // we have to watch for summoned zombies on other levels, too, + // such as the Temple, HoB, and Slime Pits. + if (you.level_type != LEVEL_DUNGEON + || player_in_hell() + || player_in_branch(BRANCH_HALL_OF_ZOT) + || player_in_branch(BRANCH_VESTIBULE_OF_HELL) + || player_in_branch(BRANCH_ECUMENICAL_TEMPLE) + || player_in_branch(BRANCH_CRYPT) + || player_in_branch(BRANCH_TOMB) + || player_in_branch(BRANCH_HALL_OF_BLADES) + || player_in_branch(BRANCH_SNAKE_PIT) + || player_in_branch(BRANCH_SLIME_PITS) + || one_chance_in(1000)) + { + ignore_rarity = true; + } + + // Don't make out-of-rarity zombies when we don't have to. + if (!ignore_rarity && mons_rarity(cls) == 0) + continue; + + // If skeleton, monster must have a skeleton. + if ((cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) + && !mons_skeleton(cls)) + { + continue; + } + + // Size must match, but you can make a spectral thing out + // of anything. + if (cs != MONS_SPECTRAL_THING + && mons_zombie_size(cls) != zombie_size) + { + continue; + } + + if (cs == MONS_SKELETON_SMALL || cs == MONS_SIMULACRUM_SMALL) + { + // Skeletal or icy draconians shouldn't be coloured. + // How could you tell? + if (mons_genus(cls) == MONS_DRACONIAN + && cls != MONS_DRACONIAN) + { + cls = MONS_DRACONIAN; + } + // The same goes for rats. + else if (mons_genus(cls) == MONS_RAT + && cls != MONS_RAT) + { + cls = MONS_RAT; + } + } + + // Hack -- non-dungeon zombies are always made out of nastier + // monsters. + if (you.level_type != LEVEL_DUNGEON && mons_power(cls) > 8) + break; + + // Check for rarity.. and OOD - identical to mons_place() + int level, diff, chance; + + level = mons_level(cls) - 4; + diff = level - power; + + chance = (ignore_rarity) ? 100 + : mons_rarity(cls) - (diff * diff) / 2; + + if (power > level - relax && power < level + relax + && random2avg(100, 2) <= chance) + { + break; + } + + // Every so often, we'll relax the OOD restrictions. Avoids + // infinite loops (if we don't do this, things like creating + // a large skeleton on level 1 may hang the game!). + if (one_chance_in(5)) + relax++; + } + + // Set type and secondary appropriately. + menv[mid].base_monster = cls; + mons_sec2 = cls; + } + else + { + menv[mid].base_monster = mons_species(ztype); + mons_sec2 = menv[mid].base_monster; + } + + // Set type to the base type to calculate appropriate stats. + menv[mid].type = menv[mid].base_monster; + + define_monster(mid); + + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 6, 5); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac -= 2; + menv[mid].ac = std::max(0, menv[mid].ac); + + menv[mid].ev -= 5; + menv[mid].ev = std::max(0, menv[mid].ev); + + menv[mid].speed = mons_class_zombie_base_speed(menv[mid].base_monster); + + // Now override type with the required type. + if (cs == MONS_ZOMBIE_SMALL || cs == MONS_ZOMBIE_LARGE) + { + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_ZOMBIE_LARGE : MONS_ZOMBIE_SMALL); + } + else if (cs == MONS_SKELETON_SMALL || cs == MONS_SKELETON_LARGE) + { + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 5, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac -= 4; + menv[mid].ac = std::max(0, menv[mid].ac); + + menv[mid].ev -= 2; + menv[mid].ev = std::max(0, menv[mid].ev); + + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_SKELETON_LARGE : MONS_SKELETON_SMALL); + } + else if (cs == MONS_SIMULACRUM_SMALL || cs == MONS_SIMULACRUM_LARGE) + { + // Simulacra aren't tough, but you can create piles of them. - bwr + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 1, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].type = ((mons_zombie_size(menv[mid].base_monster) == Z_BIG) ? + MONS_SIMULACRUM_LARGE : MONS_SIMULACRUM_SMALL); + } + else if (cs == MONS_SPECTRAL_THING) + { + menv[mid].hit_points = hit_points(menv[mid].hit_dice, 4, 4); + menv[mid].max_hit_points = menv[mid].hit_points; + + menv[mid].ac += 4; + + menv[mid].type = MONS_SPECTRAL_THING; + } + + menv[mid].base_monster = mons_sec2; + menv[mid].colour = mons_class_colour(cs); +} + +static band_type _choose_band(int mon_type, int power, int &band_size) +{ +#ifdef DEBUG_MON_CREATION + mpr("in _choose_band()", MSGCH_DIAGNOSTICS); +#endif + // Band size describes the number of monsters in addition to + // the band leader. + band_size = 0; // Single monster, no band. + band_type band = BAND_NO_BAND; + + switch (mon_type) + { + case MONS_ORC: + if (coinflip()) + break; + // intentional fall-through {dlb} + case MONS_ORC_WIZARD: + band = BAND_ORCS; + band_size = 2 + random2(3); + break; + + case MONS_ORC_PRIEST: + case MONS_ORC_WARRIOR: + band = BAND_ORC_WARRIOR; + band_size = 2 + random2(3); + break; + + case MONS_ORC_WARLORD: + case MONS_SAINT_ROKA: + band_size = 5 + random2(5); // warlords have large bands + // intentional fall through + case MONS_ORC_KNIGHT: + band = BAND_ORC_KNIGHT; // orcs + knight + band_size += 3 + random2(4); + break; + + case MONS_ORC_HIGH_PRIEST: + band = BAND_ORC_HIGH_PRIEST; + band_size = 4 + random2(4); + break; + + case MONS_BIG_KOBOLD: + if (power > 3) + { + band = BAND_KOBOLDS; + band_size = 2 + random2(6); + } + break; + + case MONS_KILLER_BEE: + band = BAND_KILLER_BEES; + band_size = 2 + random2(4); + break; + + case MONS_FLYING_SKULL: + band = BAND_FLYING_SKULLS; + band_size = 2 + random2(4); + break; + case MONS_SLIME_CREATURE: + band = BAND_SLIME_CREATURES; + band_size = 2 + random2(4); + break; + case MONS_YAK: + band = BAND_YAKS; + band_size = 2 + random2(4); + break; + case MONS_UGLY_THING: + case MONS_VERY_UGLY_THING: + band = BAND_UGLY_THINGS; + band_size = 2 + random2(4); + break; + case MONS_HELL_HOUND: + band = BAND_HELL_HOUNDS; + band_size = 2 + random2(3); + break; + case MONS_JACKAL: + band = BAND_JACKALS; + band_size = 1 + random2(3); + break; + case MONS_HELL_KNIGHT: + case MONS_MARGERY: + band = BAND_HELL_KNIGHTS; + band_size = 4 + random2(4); + break; + case MONS_JOSEPHINE: + case MONS_NECROMANCER: + case MONS_VAMPIRE_MAGE: + band = BAND_NECROMANCER; + band_size = 4 + random2(4); + break; + case MONS_GNOLL: + band = BAND_GNOLLS; + band_size = (coinflip() ? 3 : 2); + break; + case MONS_GRUM: + band = BAND_WAR_DOGS; + band_size = 2 + random2(3); + break; + case MONS_BUMBLEBEE: + band = BAND_BUMBLEBEES; + band_size = 2 + random2(4); + break; + case MONS_CENTAUR: + case MONS_CENTAUR_WARRIOR: + if (power > 9 && one_chance_in(3)) + { + band = BAND_CENTAURS; + band_size = 2 + random2(4); + } + break; + + case MONS_YAKTAUR: + case MONS_YAKTAUR_CAPTAIN: + if (coinflip()) + { + band = BAND_YAKTAURS; + band_size = 2 + random2(3); + } + break; + + case MONS_DEATH_YAK: + band = BAND_DEATH_YAKS; + band_size = 2 + random2(4); + break; + case MONS_INSUBSTANTIAL_WISP: + band = BAND_INSUBSTANTIAL_WISPS; + band_size = 4 + random2(5); + break; + case MONS_OGRE_MAGE: + band = BAND_OGRE_MAGE; + band_size = 4 + random2(4); + break; + case MONS_BALRUG: + band = BAND_BALRUG; // RED gr demon + band_size = 2 + random2(3); + break; + case MONS_CACODEMON: + band = BAND_CACODEMON; // BROWN gr demon + band_size = 1 + random2(3); + break; + + case MONS_EXECUTIONER: + if (coinflip()) + { + band = BAND_EXECUTIONER; // DARKGREY gr demon + band_size = 1 + random2(3); + } + break; + + case MONS_PANDEMONIUM_DEMON: + band = BAND_PANDEMONIUM_DEMON; + band_size = random_range(1, 3); + break; + + case MONS_HELLWING: + if (coinflip()) + { + band = BAND_HELLWING; // LIGHTGREY gr demon + band_size = 1 + random2(4); + } + break; + + case MONS_DEEP_ELF_FIGHTER: + if (coinflip()) + { + band = BAND_DEEP_ELF_FIGHTER; + band_size = 3 + random2(4); + } + break; + + case MONS_DEEP_ELF_KNIGHT: + if (coinflip()) + { + band = BAND_DEEP_ELF_KNIGHT; + band_size = 3 + random2(4); + } + break; + + case MONS_DEEP_ELF_HIGH_PRIEST: + if (coinflip()) + { + band = BAND_DEEP_ELF_HIGH_PRIEST; + band_size = 3 + random2(4); + } + break; + + case MONS_KOBOLD_DEMONOLOGIST: + if (coinflip()) + { + band = BAND_KOBOLD_DEMONOLOGIST; + band_size = 3 + random2(6); + } + break; + + case MONS_NAGA_MAGE: + case MONS_NAGA_WARRIOR: + band = BAND_NAGAS; + band_size = 3 + random2(4); + break; + + case MONS_WAR_DOG: + band = BAND_WAR_DOGS; + band_size = 2 + random2(4); + break; + + case MONS_GREY_RAT: + band = BAND_GREY_RATS; + band_size = 4 + random2(6); + break; + + case MONS_GREEN_RAT: + band = BAND_GREEN_RATS; + band_size = 4 + random2(6); + break; + + case MONS_ORANGE_RAT: + band = BAND_ORANGE_RATS; + band_size = 3 + random2(4); + break; + + case MONS_SHEEP: + band = BAND_SHEEP; + band_size = 3 + random2(5); + break; + + case MONS_GHOUL: + band = BAND_GHOULS; + band_size = 2 + random2(3); + break; + + case MONS_KIRKE: + band_size = 2 + random2(3); + case MONS_HOG: + band = BAND_HOGS; + band_size += 1 + random2(3); + break; + + case MONS_GIANT_MOSQUITO: + band = BAND_GIANT_MOSQUITOES; + band_size = 1 + random2(3); + break; + + case MONS_DEEP_TROLL: + band = BAND_DEEP_TROLLS; + band_size = 3 + random2(3); + break; + + case MONS_HELL_HOG: + band = BAND_HELL_HOGS; + band_size = 1 + random2(3); + break; + + case MONS_BOGGART: + band = BAND_BOGGARTS; + band_size = 2 + random2(3); + break; + + case MONS_BLINK_FROG: + band = BAND_BLINK_FROGS; + band_size = 2 + random2(3); + break; + + case MONS_SKELETAL_WARRIOR: + band = BAND_SKELETAL_WARRIORS; + band_size = 2 + random2(3); + break; + + case MONS_CYCLOPS: + if (one_chance_in(5) || player_in_branch(BRANCH_SHOALS)) + { + band = BAND_SHEEP; // Odyssey reference + band_size = 2 + random2(3); + } + break; + + case MONS_POLYPHEMUS: + band = BAND_DEATH_YAKS; + band_size = 3 + random2(3); + break; + + case MONS_HARPY: + band = BAND_HARPIES; + band_size = 2 + random2(3); + break; + + // Journey -- Added Draconian Packs + case MONS_WHITE_DRACONIAN: + case MONS_RED_DRACONIAN: + case MONS_PURPLE_DRACONIAN: + case MONS_MOTTLED_DRACONIAN: + case MONS_YELLOW_DRACONIAN: + case MONS_BLACK_DRACONIAN: + case MONS_GREEN_DRACONIAN: + case MONS_PALE_DRACONIAN: + if (power > 18 && one_chance_in(3) && you.level_type == LEVEL_DUNGEON) + { + band = BAND_DRACONIAN; + band_size = random_range(2, 4); + } + break; + + case MONS_DRACONIAN_CALLER: + case MONS_DRACONIAN_MONK: + case MONS_DRACONIAN_SCORCHER: + case MONS_DRACONIAN_KNIGHT: + case MONS_DRACONIAN_ANNIHILATOR: + case MONS_DRACONIAN_ZEALOT: + case MONS_DRACONIAN_SHIFTER: + if (power > 20 && you.level_type == LEVEL_DUNGEON) + { + band = BAND_DRACONIAN; + band_size = random_range(3, 6); + } + break; + + case MONS_TIAMAT: + band = BAND_DRACONIAN; + // yup, scary + band_size = random_range(3,6) + random_range(3,6) + 2; + break; + + case MONS_ILSUIW: + band = BAND_ILSUIW; + band_size = 3 + random2(3); + break; + + case MONS_AZRAEL: + band = BAND_AZRAEL; + band_size = 4 + random2(5); + break; + + case MONS_DUVESSA: + band = BAND_DUVESSA; + band_size = 1; + break; + + case MONS_KHUFU: + band = BAND_KHUFU; + band_size = 3; + break; + + case MONS_GOLDEN_EYE: + band = BAND_GOLDEN_EYE; + band_size = 1 + random2(5); + break; + + case MONS_PIKEL: + band = BAND_PIKEL; + band_size = 1 + random2(3); + break; + + } // end switch + + if (band != BAND_NO_BAND && band_size == 0) + band = BAND_NO_BAND; + + if (band_size >= BIG_BAND) + band_size = BIG_BAND - 1; + + return (band); +} + +static monster_type _band_member(band_type band, int power) +{ + monster_type mon_type = MONS_PROGRAM_BUG; + int temp_rand; + + if (band == BAND_NO_BAND) + return (MONS_PROGRAM_BUG); + + switch (band) + { + case BAND_KOBOLDS: + mon_type = MONS_KOBOLD; + break; + + case BAND_ORCS: + mon_type = MONS_ORC; + if (one_chance_in(6)) + mon_type = MONS_ORC_WIZARD; + if (one_chance_in(8)) + mon_type = MONS_ORC_PRIEST; + break; + + case BAND_ORC_WARRIOR: + mon_type = MONS_ORC; + if (one_chance_in(5)) + mon_type = MONS_ORC_WIZARD; + if (one_chance_in(7)) + mon_type = MONS_ORC_PRIEST; + break; + + case BAND_ORC_KNIGHT: + case BAND_ORC_HIGH_PRIEST: + // XXX: For Beogh punishment, ogres and trolls look out of place... + // (For normal generation, they're okay, of course.) + temp_rand = random2(30); + mon_type = ((temp_rand > 17) ? MONS_ORC : // 12 in 30 + (temp_rand > 8) ? MONS_ORC_WARRIOR : // 9 in 30 + (temp_rand > 6) ? MONS_WARG : // 2 in 30 + (temp_rand > 4) ? MONS_ORC_WIZARD : // 2 in 30 + (temp_rand > 2) ? MONS_ORC_PRIEST : // 2 in 30 + (temp_rand > 1) ? MONS_OGRE : // 1 in 30 + (temp_rand > 0) ? MONS_TROLL // 1 in 30 + : MONS_ORC_SORCERER); // 1 in 30 + break; + + case BAND_KILLER_BEES: + mon_type = MONS_KILLER_BEE; + break; + + case BAND_FLYING_SKULLS: + mon_type = MONS_FLYING_SKULL; + break; + + case BAND_SLIME_CREATURES: + mon_type = MONS_SLIME_CREATURE; + break; + + case BAND_YAKS: + mon_type = MONS_YAK; + break; + + case BAND_HARPIES: + mon_type = MONS_HARPY; + break; + + case BAND_UGLY_THINGS: + mon_type = ((power > 21 && one_chance_in(4)) ? + MONS_VERY_UGLY_THING : MONS_UGLY_THING); + break; + + case BAND_HELL_HOUNDS: + mon_type = MONS_HELL_HOUND; + break; + + case BAND_JACKALS: + mon_type = MONS_JACKAL; + break; + + case BAND_GNOLLS: + mon_type = MONS_GNOLL; + break; + + case BAND_BUMBLEBEES: + mon_type = MONS_BUMBLEBEE; + break; + + case BAND_CENTAURS: + mon_type = MONS_CENTAUR; + break; + + case BAND_YAKTAURS: + mon_type = MONS_YAKTAUR; + break; + + case BAND_INSUBSTANTIAL_WISPS: + mon_type = MONS_INSUBSTANTIAL_WISP; + break; + + case BAND_DEATH_YAKS: + mon_type = MONS_DEATH_YAK; + break; + + case BAND_NECROMANCER: // necromancer + temp_rand = random2(13); + mon_type = ((temp_rand > 9) ? MONS_ZOMBIE_SMALL : // 3 in 13 + (temp_rand > 6) ? MONS_ZOMBIE_LARGE : // 3 in 13 + (temp_rand > 3) ? MONS_SKELETON_SMALL : // 3 in 13 + (temp_rand > 0) ? MONS_SKELETON_LARGE // 3 in 13 + : MONS_NECROPHAGE); // 1 in 13 + break; + + case BAND_BALRUG: + mon_type = (coinflip() ? MONS_NEQOXEC : MONS_ORANGE_DEMON); + break; + + case BAND_CACODEMON: + mon_type = MONS_LEMURE; + break; + + case BAND_EXECUTIONER: + mon_type = (coinflip() ? MONS_ABOMINATION_SMALL + : MONS_ABOMINATION_LARGE); + break; + + case BAND_PANDEMONIUM_DEMON: + if (one_chance_in(7)) + { + mon_type = static_cast( + random_choose_weighted(50, MONS_LICH, + 10, MONS_ANCIENT_LICH, + 0)); + } + else if (one_chance_in(6)) + { + mon_type = static_cast( + random_choose_weighted(50, MONS_ABOMINATION_SMALL, + 40, MONS_ABOMINATION_LARGE, + 10, MONS_TENTACLED_MONSTROSITY, + 0)); + } + else + { + mon_type = + summon_any_demon( + static_cast( + random_choose_weighted(50, DEMON_COMMON, + 20, DEMON_GREATER, + 10, DEMON_RANDOM, + 0))); + } + break; + + case BAND_HELLWING: + mon_type = (coinflip() ? MONS_HELLWING : MONS_SMOKE_DEMON); + break; + + case BAND_DEEP_ELF_FIGHTER: // deep elf fighter + temp_rand = random2(11); + mon_type = ((temp_rand > 4) ? MONS_DEEP_ELF_SOLDIER : // 6 in 11 + (temp_rand == 4) ? MONS_DEEP_ELF_FIGHTER : // 1 in 11 + (temp_rand == 3) ? MONS_DEEP_ELF_KNIGHT : // 1 in 11 + (temp_rand == 2) ? MONS_DEEP_ELF_CONJURER :// 1 in 11 + (temp_rand == 1) ? MONS_DEEP_ELF_MAGE // 1 in 11 + : MONS_DEEP_ELF_PRIEST); // 1 in 11 + break; + + case BAND_DEEP_ELF_KNIGHT: // deep elf knight + temp_rand = random2(208); + mon_type = + ((temp_rand > 159) ? MONS_DEEP_ELF_SOLDIER : // 23.08% + (temp_rand > 111) ? MONS_DEEP_ELF_FIGHTER : // 23.08% + (temp_rand > 79) ? MONS_DEEP_ELF_KNIGHT : // 15.38% + (temp_rand > 51) ? MONS_DEEP_ELF_MAGE : // 13.46% + (temp_rand > 35) ? MONS_DEEP_ELF_PRIEST : // 7.69% + (temp_rand > 19) ? MONS_DEEP_ELF_CONJURER : // 7.69% + (temp_rand > 3) ? MONS_DEEP_ELF_SUMMONER : // 7.69% + (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 0.48% + (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 0.48% + (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 0.48% + : MONS_DEEP_ELF_DEATH_MAGE); // 0.48% + break; + + case BAND_DEEP_ELF_HIGH_PRIEST: // deep elf high priest + temp_rand = random2(16); + mon_type = + ((temp_rand > 12) ? MONS_DEEP_ELF_SOLDIER : // 3 in 16 + (temp_rand > 9) ? MONS_DEEP_ELF_FIGHTER : // 3 in 16 + (temp_rand > 6) ? MONS_DEEP_ELF_PRIEST : // 3 in 16 + (temp_rand == 6) ? MONS_DEEP_ELF_MAGE : // 1 in 16 + (temp_rand == 5) ? MONS_DEEP_ELF_SUMMONER : // 1 in 16 + (temp_rand == 4) ? MONS_DEEP_ELF_CONJURER : // 1 in 16 + (temp_rand == 3) ? MONS_DEEP_ELF_DEMONOLOGIST :// 1 in 16 + (temp_rand == 2) ? MONS_DEEP_ELF_ANNIHILATOR : // 1 in 16 + (temp_rand == 1) ? MONS_DEEP_ELF_SORCERER // 1 in 16 + : MONS_DEEP_ELF_DEATH_MAGE); // 1 in 16 + break; + + case BAND_HELL_KNIGHTS: + mon_type = MONS_HELL_KNIGHT; + if (one_chance_in(4)) + mon_type = MONS_NECROMANCER; + break; + + case BAND_OGRE_MAGE: + mon_type = MONS_OGRE; + if (one_chance_in(3)) + mon_type = MONS_TWO_HEADED_OGRE; + break; // ogre mage + + case BAND_KOBOLD_DEMONOLOGIST: + temp_rand = random2(13); + mon_type = ((temp_rand > 4) ? MONS_KOBOLD : // 8 in 13 + (temp_rand > 0) ? MONS_BIG_KOBOLD // 4 in 13 + : MONS_KOBOLD_DEMONOLOGIST);// 1 in 13 + break; + + case BAND_NAGAS: + mon_type = MONS_NAGA; + break; + case BAND_WAR_DOGS: + mon_type = MONS_WAR_DOG; + break; + case BAND_GREY_RATS: + mon_type = MONS_GREY_RAT; + break; + case BAND_GREEN_RATS: + mon_type = MONS_GREEN_RAT; + break; + case BAND_ORANGE_RATS: + mon_type = MONS_ORANGE_RAT; + break; + case BAND_SHEEP: + mon_type = MONS_SHEEP; + break; + case BAND_GHOULS: + mon_type = (coinflip() ? MONS_GHOUL : MONS_NECROPHAGE); + break; + case BAND_DEEP_TROLLS: + mon_type = MONS_DEEP_TROLL; + break; + case BAND_HOGS: + mon_type = MONS_HOG; + break; + case BAND_HELL_HOGS: + mon_type = MONS_HELL_HOG; + break; + case BAND_GIANT_MOSQUITOES: + mon_type = MONS_GIANT_MOSQUITO; + break; + case BAND_BOGGARTS: + mon_type = MONS_BOGGART; + break; + case BAND_BLINK_FROGS: + mon_type = MONS_BLINK_FROG; + break; + case BAND_SKELETAL_WARRIORS: + mon_type = MONS_SKELETAL_WARRIOR; + break; + case BAND_DRACONIAN: + { + temp_rand = random2( (power < 24) ? 24 : 37 ); + mon_type = + ((temp_rand > 35) ? MONS_DRACONIAN_CALLER : // 1 in 34 + (temp_rand > 33) ? MONS_DRACONIAN_KNIGHT : // 2 in 34 + (temp_rand > 31) ? MONS_DRACONIAN_MONK : // 2 in 34 + (temp_rand > 29) ? MONS_DRACONIAN_SHIFTER : // 2 in 34 + (temp_rand > 27) ? MONS_DRACONIAN_ANNIHILATOR :// 2 in 34 + (temp_rand > 25) ? MONS_DRACONIAN_SCORCHER : // 2 in 34 + (temp_rand > 23) ? MONS_DRACONIAN_ZEALOT : // 2 in 34 + (temp_rand > 20) ? MONS_YELLOW_DRACONIAN : // 3 in 34 + (temp_rand > 17) ? MONS_GREEN_DRACONIAN : // 3 in 34 + (temp_rand > 14) ? MONS_BLACK_DRACONIAN : // 3 in 34 + (temp_rand > 11) ? MONS_WHITE_DRACONIAN : // 3 in 34 + (temp_rand > 8) ? MONS_PALE_DRACONIAN : // 3 in 34 + (temp_rand > 5) ? MONS_PURPLE_DRACONIAN : // 3 in 34 + (temp_rand > 2) ? MONS_MOTTLED_DRACONIAN : // 3 in 34 + MONS_RED_DRACONIAN ); // 3 in 34 + break; + } + case BAND_ILSUIW: + mon_type = coinflip()? MONS_MERFOLK : MONS_MERMAID; + break; + + case BAND_AZRAEL: + mon_type = coinflip()? MONS_FIRE_ELEMENTAL : MONS_HELL_HOUND; + break; + + case BAND_DUVESSA: + mon_type = MONS_DOWAN; + break; + + case BAND_KHUFU: + mon_type = coinflip()? MONS_GREATER_MUMMY : MONS_MUMMY; + break; + + case BAND_GOLDEN_EYE: + mon_type = MONS_GOLDEN_EYE; + break; + + case BAND_PIKEL: + mon_type = MONS_HUMAN; + break; + + default: + break; + } + + return (mon_type); +} + +static int _ood_limit() +{ + return Options.ood_interesting; +} + +void mark_interesting_monst(struct monsters* monster, beh_type behaviour) +{ + if (crawl_state.arena) + return; + + bool interesting = false; + + // Unique monsters are always intersting + if (mons_is_unique(monster->type)) + interesting = true; + // If it's never going to attack us, then not interesting + else if (behaviour == BEH_FRIENDLY) + interesting = false; + else if (you.where_are_you == BRANCH_MAIN_DUNGEON + && you.level_type == LEVEL_DUNGEON + && mons_level(monster->type) >= you.your_level + _ood_limit() + && mons_level(monster->type) < 99 + && !(monster->type >= MONS_EARTH_ELEMENTAL + && monster->type <= MONS_AIR_ELEMENTAL) + && !mons_class_flag( monster->type, M_NO_EXP_GAIN )) + { + interesting = true; + } + else if ((you.level_type == LEVEL_DUNGEON + || you.level_type == LEVEL_ABYSS) + && mons_rarity(monster->type) <= Options.rare_interesting + && monster->hit_dice > 2 // Don't note the really low-hd monsters. + && mons_rarity(monster->type) > 0) + { + interesting = true; + } + // Don't waste time on moname() if user isn't using this option + else if (Options.note_monsters.size() > 0) + { + const std::string iname = mons_type_name(monster->type, DESC_NOCAP_A); + for (unsigned i = 0; i < Options.note_monsters.size(); ++i) + { + if (Options.note_monsters[i].matches(iname)) + { + interesting = true; + break; + } + } + } + + if (interesting) + monster->flags |= MF_INTERESTING; +} + +// PUBLIC FUNCTION -- mons_place(). + +static monster_type _pick_zot_exit_defender() +{ + if (one_chance_in(11)) + { +#ifdef DEBUG_MON_CREATION + mpr("Create a pandemonium demon!", MSGCH_DIAGNOSTICS); +#endif + return (MONS_PANDEMONIUM_DEMON); + } + + const int temp_rand = random2(276); + const int mon_type = + ((temp_rand > 184) ? MONS_WHITE_IMP + random2(15) : // 33.33% + (temp_rand > 104) ? MONS_HELLION + random2(10) : // 28.99% + (temp_rand > 78) ? MONS_HELL_HOUND : // 9.06% + (temp_rand > 54) ? MONS_ABOMINATION_LARGE : // 8.70% + (temp_rand > 33) ? MONS_ABOMINATION_SMALL : // 7.61% + (temp_rand > 13) ? MONS_RED_DEVIL // 7.25% + : MONS_PIT_FIEND); // 5.07% + + return static_cast(mon_type); +} + +int mons_place(mgen_data mg) +{ +#ifdef DEBUG_MON_CREATION + mpr("in mons_place()", MSGCH_DIAGNOSTICS); +#endif + int mon_count = 0; + for (int il = 0; il < MAX_MONSTERS; il++) + if (menv[il].type != MONS_NO_MONSTER) + mon_count++; + + if (mg.cls == WANDERING_MONSTER) + { + if (mon_count > MAX_MONSTERS - 50) + return (-1); + +#ifdef DEBUG_MON_CREATION + mpr("Set class RANDOM_MONSTER", MSGCH_DIAGNOSTICS); +#endif + mg.cls = RANDOM_MONSTER; + } + + // All monsters have been assigned? {dlb} + if (mon_count >= MAX_MONSTERS - 1) + return (-1); + + // This gives a slight challenge to the player as they ascend the + // dungeon with the Orb. + if (you.char_direction == GDT_ASCENDING && mg.cls == RANDOM_MONSTER + && you.level_type == LEVEL_DUNGEON && !mg.summoned()) + { +#ifdef DEBUG_MON_CREATION + mpr("Call _pick_zot_exit_defender()", MSGCH_DIAGNOSTICS); +#endif + mg.cls = _pick_zot_exit_defender(); + mg.flags |= MG_PERMIT_BANDS; + } + else if (mg.cls == RANDOM_MONSTER || mg.level_type == LEVEL_PANDEMONIUM) + mg.flags |= MG_PERMIT_BANDS; + + // Translate level_type. + switch (mg.level_type) + { + case LEVEL_PANDEMONIUM: + case LEVEL_ABYSS: + mg.power = level_id(mg.level_type).absdepth(); + break; + case LEVEL_DUNGEON: + default: + mg.power = you.your_level; + break; + } + + int mid = place_monster(mg); + if (mid == -1) + return (-1); + + monsters *creation = &menv[mid]; + + // Look at special cases: CHARMED, FRIENDLY, NEUTRAL, GOOD_NEUTRAL, + // HOSTILE. + if (mg.behaviour > NUM_BEHAVIOURS) + { + if (mg.behaviour == BEH_FRIENDLY) + creation->flags |= MF_CREATED_FRIENDLY; + + if (mg.behaviour == BEH_NEUTRAL || mg.behaviour == BEH_GOOD_NEUTRAL + || mg.behaviour == BEH_STRICT_NEUTRAL) + { + creation->flags |= MF_WAS_NEUTRAL; + } + + if (mg.behaviour == BEH_CHARMED) + { + creation->attitude = ATT_HOSTILE; + creation->add_ench(ENCH_CHARM); + } + + if (creation->type == MONS_RAKSHASA_FAKE && !one_chance_in(3)) + creation->add_ench(ENCH_INVIS); + + if (!(mg.flags & MG_FORCE_BEH) && !crawl_state.arena) + player_angers_monster(creation); + + if (crawl_state.arena) + behaviour_event(creation, ME_EVAL); + else + // Make summoned being aware of player's presence. + behaviour_event(creation, ME_ALERT, MHITYOU); + } + + return (mid); +} + +static dungeon_feature_type _monster_primary_habitat_feature(int mc) +{ + if (mc == RANDOM_MONSTER) + return (DNGN_FLOOR); + return (habitat2grid(mons_class_primary_habitat(mc))); +} + +static dungeon_feature_type _monster_secondary_habitat_feature(int mc) +{ + if (mc == RANDOM_MONSTER) + return (DNGN_FLOOR); + return (habitat2grid(mons_class_secondary_habitat(mc))); +} + +class newmons_square_find : public travel_pathfind +{ +private: + dungeon_feature_type feat_wanted; + coord_def start; + int maxdistance; + + int best_distance; + int nfound; +public: + // Terrain that we can't spawn on, but that we can skip through. + std::set passable; +public: + newmons_square_find(dungeon_feature_type grdw, + const coord_def &pos, + int maxdist = 0) + : feat_wanted(grdw), start(pos), maxdistance(maxdist), + best_distance(0), nfound(0) + { + } + + coord_def pathfind() + { + set_floodseed(start); + return travel_pathfind::pathfind(RMODE_EXPLORE); + } + + bool path_flood(const coord_def &c, const coord_def &dc) + { + if (best_distance && traveled_distance > best_distance) + return (true); + + if (!in_bounds(dc) + || (maxdistance > 0 && traveled_distance > maxdistance)) + { + return (false); + } + if (!feat_compatible(feat_wanted, grd(dc))) + { + if (passable.find(grd(dc)) != passable.end()) + good_square(dc); + return (false); + } + if (actor_at(dc) == NULL && one_chance_in(++nfound)) + { + greedy_dist = traveled_distance; + greedy_place = dc; + best_distance = traveled_distance; + } + else + { + good_square(dc); + } + return (false); + } +}; + +// Finds a square for a monster of the given class, pathfinding +// through only contiguous squares of habitable terrain. +coord_def find_newmons_square_contiguous(monster_type mons_class, + const coord_def &start, + int distance) +{ + coord_def p; + + const dungeon_feature_type feat_preferred = + _monster_primary_habitat_feature(mons_class); + const dungeon_feature_type feat_nonpreferred = + _monster_secondary_habitat_feature(mons_class); + + newmons_square_find nmpfind(feat_preferred, start, distance); + const coord_def pp = nmpfind.pathfind(); + p = pp; + + if (feat_nonpreferred != feat_preferred && !in_bounds(pp)) + { + newmons_square_find nmsfind(feat_nonpreferred, start, distance); + const coord_def ps = nmsfind.pathfind(); + p = ps; + } + + return (in_bounds(p) ? p : coord_def(-1, -1)); +} + +coord_def find_newmons_square(int mons_class, const coord_def &p) +{ + coord_def empty; + coord_def pos(-1, -1); + + if (mons_class == WANDERING_MONSTER) + mons_class = RANDOM_MONSTER; + + const dungeon_feature_type feat_preferred = + _monster_primary_habitat_feature(mons_class); + const dungeon_feature_type feat_nonpreferred = + _monster_secondary_habitat_feature(mons_class); + + // Might be better if we chose a space and tried to match the monster + // to it in the case of RANDOM_MONSTER, that way if the target square + // is surrounded by water or lava this function would work. -- bwr + if (empty_surrounds(p, feat_preferred, 2, true, empty)) + pos = empty; + + if (feat_nonpreferred != feat_preferred && !in_bounds(pos) + && empty_surrounds(p, feat_nonpreferred, 2, true, empty)) + { + pos = empty; + } + + return (pos); +} + +bool player_will_anger_monster(monster_type type, bool *holy, + bool *unholy, bool *lawful, + bool *antimagical) +{ + monsters dummy; + dummy.type = type; + + return (player_will_anger_monster(&dummy, holy, unholy, lawful, + antimagical)); +} + +bool player_will_anger_monster(monsters *mon, bool *holy, + bool *unholy, bool *lawful, + bool *antimagical) +{ + const bool isHoly = + (is_good_god(you.religion) && mon->is_evil()); + const bool isUnholy = + (is_evil_god(you.religion) && mon->is_holy()); + const bool isLawful = + (you.religion == GOD_ZIN && mon->is_chaotic()); + const bool isAntimagical = + (you.religion == GOD_TROG && mon->is_actual_spellcaster()); + + if (holy) + *holy = isHoly; + if (unholy) + *unholy = isUnholy; + if (lawful) + *lawful = isLawful; + if (antimagical) + *antimagical = isAntimagical; + + return (isHoly || isUnholy || isLawful || isAntimagical); +} + +bool player_angers_monster(monsters *mon) +{ + bool holy; + bool unholy; + bool lawful; + bool antimagical; + + // Get the drawbacks, not the benefits... (to prevent e.g. demon-scumming). + if (player_will_anger_monster(mon, &holy, &unholy, &lawful, &antimagical) + && mon->wont_attack()) + { + mon->attitude = ATT_HOSTILE; + mon->del_ench(ENCH_CHARM); + behaviour_event(mon, ME_ALERT, MHITYOU); + + if (you.can_see(mon)) + { + std::string aura; + + if (holy) + aura = "holy"; + else if (unholy) + aura = "unholy"; + else if (lawful) + aura = "lawful"; + else if (antimagical) + aura = "anti-magical"; + + mprf("%s is enraged by your %s aura!", + mon->name(DESC_CAP_THE).c_str(), aura.c_str()); + } + + return (true); + } + + return (false); +} + +int create_monster(mgen_data mg, bool fail_msg) +{ + const int montype = (mons_class_is_zombified(mg.cls) ? mg.base_type + : mg.cls); + + int summd = -1; + + if (!mg.force_place() + || !in_bounds(mg.pos) + || actor_at(mg.pos) + || !mons_class_can_pass(montype, grd(mg.pos))) + { + mg.pos = find_newmons_square(montype, mg.pos); + + // Gods other than Xom will try to avoid placing their monsters + // directly in harm's way. + if (mg.god != GOD_NO_GOD && mg.god != GOD_XOM) + { + monsters dummy; + // If the type isn't known yet assume no resists or anything. + dummy.type = (mg.cls == RANDOM_MONSTER) ? MONS_HUMAN + : mg.cls; + dummy.base_monster = mg.base_type; + dummy.god = mg.god; + + int tries = 0; + while (tries++ < 50 + && (!in_bounds(mg.pos) + || mons_avoids_cloud(&dummy, env.cgrid(mg.pos), + NULL, true))) + { + mg.pos = find_newmons_square(montype, mg.pos); + } + if (!in_bounds(mg.pos)) + return (-1); + + const int cloud_num = env.cgrid(mg.pos); + // Don't place friendly god gift in a damaging cloud created by + // you if that would anger the god. + if (mons_avoids_cloud(&dummy, cloud_num, NULL, true) + && mg.behaviour == BEH_FRIENDLY + && god_hates_attacking_friend(you.religion, &dummy) + && YOU_KILL(env.cloud[cloud_num].killer)) + { + return (-1); + } + } + } + + if (in_bounds(mg.pos)) + { + summd = mons_place(mg); + // If the arena vetoed the placement then give no fail message. + if (crawl_state.arena) + fail_msg = false; + } + + // Determine whether creating a monster is successful (summd != -1) {dlb}: + // then handle the outcome. {dlb}: + if (fail_msg && summd == -1 && observe_cell(mg.pos)) + mpr("You see a puff of smoke."); + + // The return value is either -1 (failure of some sort) + // or the index of the monster placed (if I read things right). {dlb} + return (summd); +} + +bool empty_surrounds(const coord_def& where, dungeon_feature_type spc_wanted, + int radius, bool allow_centre, coord_def& empty) +{ + // Assume all player summoning originates from player x,y. + bool playerSummon = (where == you.pos()); + + int good_count = 0; + + for (radius_iterator ri(where, radius, true, false, !allow_centre); + ri; ++ri) + { + bool success = false; + + if (actor_at(*ri)) + continue; + + // Players won't summon out of LOS, or past transparent walls. + if (!you.see_cell_no_trans(*ri) && playerSummon) + continue; + + success = + (grd(*ri) == spc_wanted) || feat_compatible(spc_wanted, grd(*ri)); + + if (success && one_chance_in(++good_count)) + empty = *ri; + } + + return (good_count > 0); +} + +monster_type summon_any_demon(demon_class_type dct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + if (dct == DEMON_RANDOM) + dct = static_cast(random2(DEMON_RANDOM)); + + int temp_rand; // probability determination {dlb} + + switch (dct) + { + case DEMON_LESSER: + temp_rand = random2(60); + mon = ((temp_rand > 49) ? MONS_IMP : // 10 in 60 + (temp_rand > 40) ? MONS_WHITE_IMP : // 9 in 60 + (temp_rand > 31) ? MONS_LEMURE : // 9 in 60 + (temp_rand > 22) ? MONS_UFETUBUS : // 9 in 60 + (temp_rand > 13) ? MONS_MANES : // 9 in 60 + (temp_rand > 4) ? MONS_MIDGE // 9 in 60 + : MONS_SHADOW_IMP); // 5 in 60 + break; + + case DEMON_COMMON: + temp_rand = random2(3948); + mon = ((temp_rand > 3367) ? MONS_NEQOXEC : // 14.69% + (temp_rand > 2787) ? MONS_ORANGE_DEMON : // 14.69% + (temp_rand > 2207) ? MONS_HELLWING : // 14.69% + (temp_rand > 1627) ? MONS_SMOKE_DEMON : // 14.69% + (temp_rand > 1047) ? MONS_YNOXINUL : // 14.69% + (temp_rand > 889) ? MONS_RED_DEVIL : // 4.00% + (temp_rand > 810) ? MONS_HELLION : // 2.00% + (temp_rand > 731) ? MONS_ROTTING_DEVIL : // 2.00% + (temp_rand > 652) ? MONS_TORMENTOR : // 2.00% + (temp_rand > 573) ? MONS_REAPER : // 2.00% + (temp_rand > 494) ? MONS_SOUL_EATER : // 2.00% + (temp_rand > 415) ? MONS_HAIRY_DEVIL : // 2.00% + (temp_rand > 336) ? MONS_ICE_DEVIL : // 2.00% + (temp_rand > 257) ? MONS_BLUE_DEVIL : // 2.00% + (temp_rand > 178) ? MONS_BEAST : // 2.00% + (temp_rand > 99) ? MONS_IRON_DEVIL : // 2.00% + (temp_rand > 49) ? MONS_SUN_DEMON // 1.26% + : MONS_SHADOW_IMP); // 1.26% + break; + + case DEMON_GREATER: + temp_rand = random2(1000); + mon = ((temp_rand > 868) ? MONS_CACODEMON : // 13.1% + (temp_rand > 737) ? MONS_BALRUG : // 13.1% + (temp_rand > 606) ? MONS_BLUE_DEATH : // 13.1% + (temp_rand > 475) ? MONS_GREEN_DEATH : // 13.1% + (temp_rand > 344) ? MONS_EXECUTIONER : // 13.1% + (temp_rand > 244) ? MONS_FIEND : // 10.0% + (temp_rand > 154) ? MONS_ICE_FIEND : // 9.0% + (temp_rand > 73) ? MONS_SHADOW_FIEND // 8.1% + : MONS_PIT_FIEND); // 7.4% + break; + + default: + break; + } + + return (mon); +} + +monster_type summon_any_holy_being(holy_being_class_type hbct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + switch (hbct) + { + case HOLY_BEING_WARRIOR: + mon = coinflip() ? MONS_DAEVA : MONS_ANGEL; + break; + + default: + break; + } + + return (mon); +} + +monster_type summon_any_dragon(dragon_class_type dct) +{ + monster_type mon = MONS_PROGRAM_BUG; + + int temp_rand; + + switch (dct) + { + case DRAGON_LIZARD: + temp_rand = random2(100); + mon = ((temp_rand > 80) ? MONS_SWAMP_DRAKE : + (temp_rand > 59) ? MONS_KOMODO_DRAGON : + (temp_rand > 34) ? MONS_FIREDRAKE : + (temp_rand > 11) ? MONS_DEATH_DRAKE : + MONS_DRAGON); + break; + + case DRAGON_DRACONIAN: + temp_rand = random2(70); + mon = ((temp_rand > 60) ? MONS_YELLOW_DRACONIAN : + (temp_rand > 50) ? MONS_BLACK_DRACONIAN : + (temp_rand > 40) ? MONS_PALE_DRACONIAN : + (temp_rand > 30) ? MONS_GREEN_DRACONIAN : + (temp_rand > 20) ? MONS_PURPLE_DRACONIAN : + (temp_rand > 10) ? MONS_RED_DRACONIAN + : MONS_WHITE_DRACONIAN); + break; + + case DRAGON_DRAGON: + temp_rand = random2(90); + mon = ((temp_rand > 80) ? MONS_MOTTLED_DRAGON : + (temp_rand > 70) ? MONS_LINDWURM : + (temp_rand > 60) ? MONS_STORM_DRAGON : + (temp_rand > 50) ? MONS_MOTTLED_DRAGON : + (temp_rand > 40) ? MONS_STEAM_DRAGON : + (temp_rand > 30) ? MONS_DRAGON : + (temp_rand > 20) ? MONS_ICE_DRAGON : + (temp_rand > 10) ? MONS_SWAMP_DRAGON + : MONS_SHADOW_DRAGON); + break; + + default: + break; + } + + return (mon); +} + +///////////////////////////////////////////////////////////////////////////// +// monster_pathfind + +// The pathfinding is an implementation of the A* algorithm. Beginning at the +// destination square we check all neighbours of a given grid, estimate the +// distance needed for any shortest path including this grid and push the +// result into a hash. We can then easily access all points with the shortest +// distance estimates and then check _their_ neighbours and so on. +// The algorithm terminates once we reach the monster position since - because +// of the sorting of grids by shortest distance in the hash - there can be no +// path between start and target that is shorter than the current one. There +// could be other paths that have the same length but that has no real impact. +// If the hash has been cleared and the start grid has not been encountered, +// then there's no path that matches the requirements fed into monster_pathfind. +// (These requirements are usually preference of habitat of a specific monster +// or a limit of the distance between start and any grid on the path.) + +int mons_tracking_range(const monsters *mon) +{ + + int range = 0; + switch (mons_intel(mon)) + { + case I_PLANT: + range = 2; + break; + case I_INSECT: + range = 4; + break; + case I_ANIMAL: + range = 5; + break; + case I_NORMAL: + range = LOS_RADIUS; + break; + default: + // Highly intelligent monsters can find their way + // anywhere. (range == 0 means no restriction.) + break; + } + + if (range) + { + if (mons_is_native_in_branch(mon)) + range += 3; + else if (mons_class_flag(mon->type, M_BLOOD_SCENT)) + range++; + } + + return (range); +} + +//#define DEBUG_PATHFIND +monster_pathfind::monster_pathfind() + : mons(), target(), range(0), min_length(0), max_length(0), dist(), prev() +{ +} + +monster_pathfind::~monster_pathfind() +{ +} + +void monster_pathfind::set_range(int r) +{ + if (r >= 0) + range = r; +} + +coord_def monster_pathfind::next_pos(const coord_def &c) const +{ + return c + Compass[prev[c.x][c.y]]; +} + +// The main method in the monster_pathfind class. +// Returns true if a path was found, else false. +bool monster_pathfind::init_pathfind(const monsters *mon, coord_def dest, + bool diag, bool msg, bool pass_unmapped) +{ + mons = mon; + + // We're doing a reverse search from target to monster. + start = dest; + target = mon->pos(); + pos = start; + allow_diagonals = diag; + traverse_unmapped = pass_unmapped; + + // Easy enough. :P + if (start == target) + { + if (msg) + mpr("The monster is already there!"); + + return (true); + } + + return start_pathfind(msg); +} + +bool monster_pathfind::init_pathfind(coord_def src, coord_def dest, bool diag, + bool msg) +{ + start = src; + target = dest; + pos = start; + allow_diagonals = diag; + + // Easy enough. :P + if (start == target) + return (true); + + return start_pathfind(msg); +} + +bool monster_pathfind::start_pathfind(bool msg) +{ + // NOTE: We never do any traversable() check for the starting square + // (target). This means that even if the target cannot be reached + // we may still find a path leading adjacent to this position, which + // is desirable if e.g. the player is hovering over deep water + // surrounded by shallow water or floor, or if a foe is hiding in + // a wall. + // If the surrounding squares also are not traversable, we return + // early that no path could be found. + + max_length = min_length = grid_distance(pos.x, pos.y, target.x, target.y); + for (int i = 0; i < GXM; i++) + for (int j = 0; j < GYM; j++) + dist[i][j] = INFINITE_DISTANCE; + + dist[pos.x][pos.y] = 0; + + bool success = false; + do + { + // Calculate the distance to all neighbours of the current position, + // and add them to the hash, if they haven't already been looked at. + success = calc_path_to_neighbours(); + if (success) + return (true); + + // Pull the position with shortest distance estimate to our target grid. + success = get_best_position(); + + if (!success) + { + if (msg) + { + mprf("Couldn't find a path from (%d,%d) to (%d,%d).", + target.x, target.y, start.x, start.y); + } + return (false); + } + } + while (true); +} + +// Returns true as soon as we encounter the target. +bool monster_pathfind::calc_path_to_neighbours() +{ + coord_def npos; + int distance, old_dist, total; + + // For each point, we look at all neighbour points. Check the orthogonals + // last, so that, should an orthogonal and a diagonal direction have the + // same total travel cost, the orthogonal will be picked first, and thus + // zigzagging will be significantly reduced. + // + // 1 0 3 This means directions are looked at, in order, + // \ | / 1, 3, 5, 7 (diagonals) followed by 0, 2, 4, 6 + // 6--.--2 (orthogonals). This is achieved by the assignment + // / | \ of (dir = 0) once dir has passed 7. + // 7 4 5 + // + for (int dir = 1; dir < 8; (dir += 2) == 9 && (dir = 0)) + { + // Skip diagonal movement. + if (!allow_diagonals && (dir % 2)) + continue; + + npos = pos + Compass[dir]; + +#ifdef DEBUG_PATHFIND + mprf("Looking at neighbour (%d,%d)", npos.x, npos.y); +#endif + if (!in_bounds(npos)) + continue; + + if (!traversable(npos)) + continue; + + // Ignore this grid if it takes us above the allowed distance. + if (range && estimated_cost(npos) > range) + continue; + + distance = dist[pos.x][pos.y] + travel_cost(npos); + old_dist = dist[npos.x][npos.y]; +#ifdef DEBUG_PATHFIND + mprf("old dist: %d, new dist: %d, infinite: %d", old_dist, distance, + INFINITE_DISTANCE); +#endif + // If the new distance is better than the old one (initialised with + // INFINITE), update the position. + if (distance < old_dist) + { + // Calculate new total path length. + total = distance + estimated_cost(npos); + if (old_dist == INFINITE_DISTANCE) + { +#ifdef DEBUG_PATHFIND + mprf("Adding (%d,%d) to hash (total dist = %d)", + npos.x, npos.y, total); +#endif + add_new_pos(npos, total); + if (total > max_length) + max_length = total; + } + else + { +#ifdef DEBUG_PATHFIND + mprf("Improving (%d,%d) to total dist %d", + npos.x, npos.y, total); +#endif + + update_pos(npos, total); + } + + // Update distance start->pos. + dist[npos.x][npos.y] = distance; + + // Set backtracking information. + // Converts the Compass direction to its counterpart. + // 0 1 2 4 5 6 + // 7 . 3 ==> 3 . 7 e.g. (3 + 4) % 8 = 7 + // 6 5 4 2 1 0 (7 + 4) % 8 = 11 % 8 = 3 + + prev[npos.x][npos.y] = (dir + 4) % 8; + + // Are we finished? + if (npos == target) + { +#ifdef DEBUG_PATHFIND + mpr("Arrived at target."); +#endif + return (true); + } + } + } + return (false); +} + +// Starting at known min_length (minimum total estimated path distance), check +// the hash for existing vectors, then pick the last entry of the first vector +// that matches. Update min_length, if necessary. +bool monster_pathfind::get_best_position() +{ + for (int i = min_length; i <= max_length; i++) + { + if (!hash[i].empty()) + { + if (i > min_length) + min_length = i; + + std::vector &vec = hash[i]; + // Pick the last position pushed into the vector as it's most + // likely to be close to the target. + pos = vec[vec.size()-1]; + vec.pop_back(); + +#ifdef DEBUG_PATHFIND + mprf("Returning (%d, %d) as best pos with total dist %d.", + pos.x, pos.y, min_length); +#endif + + return (true); + } +#ifdef DEBUG_PATHFIND + mprf("No positions for path length %d.", i); +#endif + } + + // Nothing found? Then there's no path! :( + return (false); +} + +// Using the prev vector backtrack from start to target to find all steps to +// take along the shortest path. +std::vector monster_pathfind::backtrack() +{ +#ifdef DEBUG_PATHFIND + mpr("Backtracking..."); +#endif + std::vector path; + pos = target; + path.push_back(pos); + + if (pos == start) + return path; + + int dir; + do + { + dir = prev[pos.x][pos.y]; + pos = pos + Compass[dir]; + ASSERT(in_bounds(pos)); +#ifdef DEBUG_PATHFIND + mprf("prev: (%d, %d), pos: (%d, %d)", Compass[dir].x, Compass[dir].y, + pos.x, pos.y); +#endif + path.push_back(pos); + + if (pos.x == 0 && pos.y == 0) + break; + } + while (pos != start); + ASSERT(pos == start); + + return (path); +} + +// Reduces the path coordinates to only a couple of key waypoints needed +// to reach the target. Waypoints are chosen such that from one waypoint you +// can see (and, more importantly, reach) the next one. Note that +// can_go_straight() is probably rather too conservative in these estimates. +// This is done because Crawl's pathfinding - once a target is in sight and easy +// reach - is both very robust and natural, especially if we want to flexibly +// avoid plants and other monsters in the way. +std::vector monster_pathfind::calc_waypoints() +{ + std::vector path = backtrack(); + + // If no path found, nothing to be done. + if (path.empty()) + return path; + + dungeon_feature_type can_move; + if (mons_amphibious(mons)) + can_move = DNGN_DEEP_WATER; + else + can_move = DNGN_SHALLOW_WATER; + + std::vector waypoints; + pos = path[0]; + +#ifdef DEBUG_PATHFIND + mpr(EOL "Waypoints:"); +#endif + for (unsigned int i = 1; i < path.size(); i++) + { + if (can_go_straight(pos, path[i], can_move)) + continue; + else + { + pos = path[i-1]; + waypoints.push_back(pos); +#ifdef DEBUG_PATHFIND + mprf("waypoint: (%d, %d)", pos.x, pos.y); +#endif + } + } + + // Add the actual target to the list of waypoints, so we can later check + // whether a tracked enemy has moved too much, in case we have to update + // the path. + if (pos != path[path.size() - 1]) + waypoints.push_back(path[path.size() - 1]); + + return (waypoints); +} + +bool monster_pathfind::traversable(const coord_def p) +{ + if (traverse_unmapped && grd(p) == DNGN_UNSEEN) + return (true); + + if (mons) + return mons_traversable(p); + + return (!feat_is_solid(grd(p)) && !feat_destroys_items(grd(p))); +} + +// Checks whether a given monster can pass over a certain position, respecting +// its preferred habit and capability of flight or opening doors. +bool monster_pathfind::mons_traversable(const coord_def p) +{ + const monster_type montype = mons_is_zombified(mons) ? mons_zombie_base(mons) + : mons->type; + + if (!monster_habitable_grid(montype, grd(p))) + return (false); + + // Monsters that can't open doors won't be able to pass them. + if (feat_is_closed_door(grd(p)) || grd(p) == DNGN_SECRET_DOOR) + { + if (mons_is_zombified(mons)) + { + if (mons_class_itemuse(montype) < MONUSE_OPEN_DOORS) + return (false); + } + else if (mons_itemuse(mons) < MONUSE_OPEN_DOORS) + return (false); + } + + // Your friends only know about doors you know about, unless they feel + // at home in this branch. + if (grd(p) == DNGN_SECRET_DOOR && mons->friendly() + && (mons_intel(mons) < I_NORMAL || !mons_is_native_in_branch(mons))) + { + return (false); + } + + const trap_def* ptrap = find_trap(p); + if (ptrap) + { + const trap_type tt = ptrap->type; + + // Don't allow allies to pass over known (to them) Zot traps. + if (tt == TRAP_ZOT + && ptrap->is_known(mons) + && mons->friendly()) + { + return (false); + } + + // Monsters cannot travel over teleport traps. + if (!_can_place_on_trap(montype, tt)) + return (false); + } + + return (true); +} + +int monster_pathfind::travel_cost(coord_def npos) +{ + if (mons) + return mons_travel_cost(npos); + + return (1); +} + +// Assumes that grids that really cannot be entered don't even get here. +// (Checked by traversable().) +int monster_pathfind::mons_travel_cost(coord_def npos) +{ + ASSERT(grid_distance(pos, npos) <= 1); + + // Doors need to be opened. + if (feat_is_closed_door(grd(npos)) || grd(npos) == DNGN_SECRET_DOOR) + return 2; + + const int montype = mons_is_zombified(mons) ? mons_zombie_base(mons) + : mons->type; + + const bool airborne = _mons_airborne(montype, -1, false); + + // Travelling through water, entering or leaving water is more expensive + // for non-amphibious monsters, so they'll avoid it where possible. + // (The resulting path might not be optimal but it will lead to a path + // a monster of such habits is likely to prefer.) + // Only tested for shallow water since they can't enter deep water anyway. + if (!airborne && !mons_class_amphibious(montype) + && (grd(pos) == DNGN_SHALLOW_WATER || grd(npos) == DNGN_SHALLOW_WATER)) + { + return 2; + } + + // Try to avoid (known) traps. + const trap_def* ptrap = find_trap(npos); + if (ptrap) + { + const bool knows_trap = ptrap->is_known(mons); + const trap_type tt = ptrap->type; + if (tt == TRAP_ALARM || tt == TRAP_ZOT) + { + // Your allies take extra precautions to avoid known alarm traps. + // Zot traps are considered intraversable. + if (knows_trap && mons->friendly()) + return (3); + + // To hostile monsters, these traps are completely harmless. + return 1; + } + + // Mechanical traps can be avoided by flying, as can shafts, and + // tele traps are never traversable anyway. + if (knows_trap && !airborne) + return 2; + } + + return 1; +} + +// The estimated cost to reach a grid is simply max(dx, dy). +int monster_pathfind::estimated_cost(coord_def p) +{ + return (grid_distance(p, target)); +} + +void monster_pathfind::add_new_pos(coord_def npos, int total) +{ + hash[total].push_back(npos); +} + +void monster_pathfind::update_pos(coord_def npos, int total) +{ + // Find hash position of old distance and delete it, + // then call_add_new_pos. + int old_total = dist[npos.x][npos.y] + estimated_cost(npos); + + std::vector &vec = hash[old_total]; + for (unsigned int i = 0; i < vec.size(); i++) + { + if (vec[i] == npos) + { + vec.erase(vec.begin() + i); + break; + } + } + + add_new_pos(npos, total); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Random monsters for portal vaults. +// +///////////////////////////////////////////////////////////////////////////// + +void set_vault_mon_list(const std::vector &list) +{ + CrawlHashTable &props = env.properties; + + props.erase(VAULT_MON_TYPES_KEY); + props.erase(VAULT_MON_BASES_KEY); + props.erase(VAULT_MON_WEIGHTS_KEY); + + unsigned int size = list.size(); + if (size == 0) + { + setup_vault_mon_list(); + return; + } + + props[VAULT_MON_TYPES_KEY].new_vector(SV_LONG).resize(size); + props[VAULT_MON_BASES_KEY].new_vector(SV_LONG).resize(size); + props[VAULT_MON_WEIGHTS_KEY].new_vector(SV_LONG).resize(size); + + CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); + CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); + CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); + + for (unsigned int i = 0; i < size; i++) + { + const mons_spec &spec = list[i]; + + if (spec.place.is_valid()) + { + ASSERT(spec.place.level_type != LEVEL_LABYRINTH + && spec.place.level_type != LEVEL_PORTAL_VAULT); + type_vec[i] = (long) -1; + base_vec[i] = (long) spec.place.packed_place(); + } + else + { + ASSERT(spec.mid != RANDOM_MONSTER + && spec.monbase != RANDOM_MONSTER); + type_vec[i] = (long) spec.mid; + base_vec[i] = (long) spec.monbase; + } + weight_vec[i] = (long) spec.genweight; + } + + setup_vault_mon_list(); +} + +void get_vault_mon_list(std::vector &list) +{ + list.clear(); + + CrawlHashTable &props = env.properties; + + if (!props.exists(VAULT_MON_TYPES_KEY)) + return; + + ASSERT(props.exists(VAULT_MON_BASES_KEY)); + ASSERT(props.exists(VAULT_MON_WEIGHTS_KEY)); + + CrawlVector &type_vec = props[VAULT_MON_TYPES_KEY].get_vector(); + CrawlVector &base_vec = props[VAULT_MON_BASES_KEY].get_vector(); + CrawlVector &weight_vec = props[VAULT_MON_WEIGHTS_KEY].get_vector(); + + ASSERT(type_vec.size() == base_vec.size()); + ASSERT(type_vec.size() == weight_vec.size()); + + unsigned int size = type_vec.size(); + for (unsigned int i = 0; i < size; i++) + { + int type = (long) type_vec[i]; + int base = (long) base_vec[i]; + + mons_spec spec; + + if (type == -1) + { + spec.place = level_id::from_packed_place(base); + ASSERT(spec.place.is_valid()); + ASSERT(spec.place.level_type != LEVEL_LABYRINTH + && spec.place.level_type != LEVEL_PORTAL_VAULT); + } + else + { + spec.mid = type; + spec.monbase = (monster_type) base; + ASSERT(spec.mid != RANDOM_MONSTER + && spec.monbase != RANDOM_MONSTER); + } + spec.genweight = (long) weight_vec[i]; + + list.push_back(spec); + } +} + +void setup_vault_mon_list() +{ + vault_mon_types.clear(); + vault_mon_bases.clear(); + vault_mon_weights.clear(); + + std::vector list; + get_vault_mon_list(list); + + unsigned int size = list.size(); + + vault_mon_types.resize(size); + vault_mon_bases.resize(size); + vault_mon_weights.resize(size); + + for (unsigned int i = 0; i < size; i++) + { + if (list[i].place.is_valid()) + { + vault_mon_types[i] = -1; + vault_mon_bases[i] = list[i].place.packed_place(); + } + else + { + vault_mon_types[i] = list[i].mid; + vault_mon_bases[i] = list[i].monbase; + } + vault_mon_weights[i] = list[i].genweight; + } +} -- cgit v1.2.3-54-g00ecf