/* * 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 "mgen_data.h" #include "arena.h" #include "branch.h" #include "coord.h" #include "coordit.h" #include "directn.h" #include "dungeon.h" #include "fprop.h" #include "godabil.h" #include "externs.h" #include "options.h" #include "ghost.h" #include "lev-pand.h" #include "message.h" #include "mislead.h" #include "mon-behv.h" #include "mon-gear.h" #include "mon-iter.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 "env.h" #include "terrain.h" #include "traps.h" #include "travel.h" #include "view.h" #ifdef USE_TILE #include "tiledef-player.h" #endif band_type active_monster_band = BAND_NO_BAND; 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, bool& natural_leader); // 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())); } 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) && mons_genus(mons_base_type(mons)) != MONS_MERFOLK); 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; mg.foe = (you.char_direction == GDT_ASCENDING) ? MHITYOU : MHITNOT; 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); } 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 && vault_mon_types.size() > 0) { 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); } } else if (you.level_type == LEVEL_PORTAL_VAULT) { // XXX: We don't have a random monster list here, so pick one // from where we were. place.level_type = LEVEL_DUNGEON; *lev_mons = place.absdepth(); } 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) || monster_at(mg_pos) || you.pos() == mg_pos && !fedhas_passthrough_class(mg.cls)) 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); } // Check player proximity to avoid band members being placed // close to the player erroneously. // XXX: This is a little redundant with proximity checks in // place_monster. if (mg.proximity == PROX_AWAY_FROM_PLAYER && distance(you.pos(), mg_pos) <= LOS_RADIUS_SQ) { 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 || mg.cls == MONS_PROGRAM_BUG) return (-1); // (3) Decide on banding (good lord!) int band_size = 1; bool leader = false; monster_type band_monsters[BIG_BAND]; // band monster types band_type band = BAND_NO_BAND; 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 band = _choose_band(mg.cls, mg.power, band_size, leader); 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) <= LOS_RADIUS_SQ); 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 (you.see_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; if (leader && !mg.summoner) { band_template.summoner = &menv[id]; band_template.flags |= MG_BAND_MINION; } unwind_var current_band(active_monster_band, band); // (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; if (mon->type == MONS_PIKEL) { // Don't give XP for the slaves to discourage hunting. Pikel // has an artificially large XP modifier to compensate for // this. menv[band_id].flags |= MF_NO_REWARD; menv[band_id].props["pikel_band"] = true; } } } // Placement of first monster, at least, was a success. return (id); } monsters* get_free_monster() { for (int i = 0; i < MAX_MONSTERS; ++i) if (env.mons[i].type == MONS_NO_MONSTER) { env.mons[i].reset(); return (&env.mons[i]); } return (NULL); } #ifdef USE_TILE // For some tiles, always use the fixed same variant out of a set // of tiles. (Where this is not handled by number or colour already.) static void _maybe_init_tilenum_props(monsters *mon) { // Not necessary. if (mon->props.exists("monster_tile") || mon->props.exists("tile_num")) return; // Special-case for monsters masquerading as items. if (mon->type == MONS_DANCING_WEAPON || mons_is_mimic(mon->type)) return; // Only add the property for tiles that have several variants. const int base_tile = tileidx_monster_base(mon); if (tile_player_count(base_tile) > 1) mon->props["tile_num"] = short(random2(256)); } #endif static int _place_monster_aux(const mgen_data &mg, bool first_band_member, bool force_pos) { coord_def fpos; const monsterentry *m_ent = get_monster_data(mg.cls); monsters* mon = get_free_monster(); if (!mon) return (-1); 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)) && !monster_at(mg.pos) && (you.pos() != mg.pos || fedhas_passthrough_class(mg.cls)) && (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); } // Now, actually create the monster. (Wheeee!) mon->type = mg.cls; mon->base_monster = mg.base_type; mon->number = mg.number; mon->moveto(fpos); // Link monster into monster grid. int id = mon->mindex(); env.mgrid(fpos) = id; if (mons_is_mimic(mg.cls)) { // Mimics who mimic thin air get the axe. if (!give_mimic_item(mon)) { mon->reset(); mgrd(fpos) = NON_MONSTER; return (-1); } } else if (mon->type == MONS_MERGED_SLIME_CREATURE) // shouldn't ever happen mon->type = MONS_SLIME_CREATURE; #ifdef USE_TILE else _maybe_init_tilenum_props(mon); #endif // 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) { mon->god = mg.god; mon->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: mon->god = GOD_BEOGH; break; case MONS_JELLY: mon->god = GOD_JIYVA; break; case MONS_MUMMY: case MONS_DRACONIAN: case MONS_ELF: // [ds] Vault defs can request priest monsters of unusual types. default: mon->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)) mon->god = GOD_BEOGH; } // The royal jelly belongs to Jiyva. else if (mg.cls == MONS_ROYAL_JELLY) mon->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)) mon->god = GOD_SHINING_ONE; else mon->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)) mon->god = GOD_LUGONU; } // If the caller requested a specific colour for this monster, apply // it now. if (mg.colour != BLACK) mon->colour = mg.colour; if (mg.mname != "") mon->mname = mg.mname; if (mg.hd != 0) { mon->hit_dice = mg.hd; // Re-roll HP. int hp = hit_points(mg.hd, m_ent->hpdice[1], m_ent->hpdice[2]); // But only for monsters with random HP. if (hp > 0) { mon->max_hit_points = hp; mon->hit_points = hp; } } if (mg.hp != 0) { mon->max_hit_points = mg.hp; mon->hit_points = mg.hp; } // Store the extra flags here. mon->flags |= mg.extra_flags; // 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)) mon->add_ench(ENCH_INVIS); if (mons_class_flag(mg.cls, M_CONFUSED)) mon->add_ench(ENCH_CONFUSION); if (mg.cls == MONS_SHAPESHIFTER) mon->add_ench(ENCH_SHAPESHIFTER); if (mg.cls == MONS_GLOWING_SHAPESHIFTER) mon->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. mon->add_ench(ENCH_SLOWLY_DYING); } if (!crawl_state.arena && you.misled()) update_mislead_monster(mon); if (monster_can_submerge(mon, grd(fpos)) && !one_chance_in(5)) mon->add_ench(ENCH_SUBMERGED); mon->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(mon, RANDOM_MONSTER); // It's not actually a known shapeshifter if it happened to be // placed in LOS of the player. mon->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 = mon->mslot_item(MSLOT_WEAPON); if (!wpn) { mon->destroy_inventory(); mon->reset(); mgrd(fpos) = NON_MONSTER; return (-1); } else mon->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(mon->speed_increment); mon->wield_melee_weapon(false); } 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. mon->hit_points *= mg.number; mon->max_hit_points *= mg.number; } } // Set attitude, behaviour and target. mon->attitude = ATT_HOSTILE; mon->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)) mon->behaviour = BEH_WANDER; mon->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) mon->attitude = ATT_FRIENDLY; if (mg.behaviour == BEH_GOOD_NEUTRAL) mon->attitude = ATT_GOOD_NEUTRAL; if (mg.behaviour == BEH_NEUTRAL) mon->attitude = ATT_NEUTRAL; if (mg.behaviour == BEH_STRICT_NEUTRAL) mon->attitude = ATT_STRICT_NEUTRAL; mon->behaviour = BEH_WANDER; } if (summoned) { mon->mark_summoned(mg.abjuration_duration, true, mg.summon_type); } mon->foe = mg.foe; std::string blame_prefix; if (mg.flags & MG_BAND_MINION) { blame_prefix = "led by "; } else if (mg.abjuration_duration > 0) { blame_prefix = "summoned by "; } else if (mons_class_is_zombified(mg.cls)) { blame_prefix = "animated by "; } else { blame_prefix = "created by "; } if (!mg.non_actor_summoner.empty()) { CrawlStoreValue& blame = mon->props["blame"]; blame.new_vector(SV_STR, SFLAG_CONST_TYPE); blame.get_vector().push_back(blame_prefix + mg.non_actor_summoner); } // NOTE: The summoner might be dead if the summoned is placed by a // beam which killed the summoner first (like fire vortexes placed // by the Fire Storm spell). else if (mg.summoner != NULL && mg.summoner->alive()) { ASSERT(mg.summoner->alive()); CrawlStoreValue& blame = mon->props["blame"]; blame.new_vector(SV_STR, SFLAG_CONST_TYPE); if (mg.summoner->atype() == ACT_PLAYER) { blame.get_vector().push_back(blame_prefix + "the player character"); } else { monsters* sum = dynamic_cast(mg.summoner); blame.get_vector().push_back(blame_prefix + sum->full_name(DESC_NOCAP_A, true)); if (sum->props.exists("blame")) { CrawlVector& oldblame = sum->props["blame"].get_vector(); for (CrawlVector::iterator i = oldblame.begin(); i != oldblame.end(); ++i) { blame.get_vector().push_back(*i); } } } } // Initialise (very) ugly things and pandemonium demons. if (mon->type == MONS_UGLY_THING || mon->type == MONS_VERY_UGLY_THING) { ghost_demon ghost; ghost.init_ugly_thing(mon->type == MONS_VERY_UGLY_THING, false, mg.colour); mon->set_ghost(ghost, false); mon->uglything_init(); } else if (mon->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(mon->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(*(mon->mslot_item(MSLOT_WEAPON)), 180); mon->set_ghost(ghost); mon->dancing_weapon_init(); } mark_interesting_monst(mon, mg.behaviour); if (!Generating_Level && you.can_see(mon)) handle_seen_interrupt(mon); if (crawl_state.arena) arena_placed_monster(mon); 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); // Turn off all spellcasting flags. // Hack - kraken get to keep their spell-like ability. if (menv[mid].base_monster != MONS_KRAKEN) menv[mid].flags &= ~MF_SPELLCASTER & ~MF_ACTUAL_SPELLS & ~MF_PRIEST; 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, bool &natural_leader) { #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. natural_leader = false; 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); natural_leader = true; break; case MONS_ORC_HIGH_PRIEST: band = BAND_ORC_HIGH_PRIEST; band_size = 4 + random2(4); natural_leader = true; 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_MARGERY: natural_leader = true; case MONS_HELL_KNIGHT: band = BAND_HELL_KNIGHTS; band_size = 4 + random2(4); break; case MONS_JOSEPHINE: case MONS_NECROMANCER: case MONS_VAMPIRE_MAGE: natural_leader = true; band = BAND_NECROMANCER; band_size = 4 + random2(4); break; case MONS_GNOLL: band = BAND_GNOLLS; band_size = (coinflip() ? 3 : 2); break; case MONS_GRUM: natural_leader = true; 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_WARRIOR: natural_leader = true; case MONS_CENTAUR: if (power > 9 && one_chance_in(3) && you.where_are_you != BRANCH_SHOALS) { band = BAND_CENTAURS; band_size = 2 + random2(4); } break; case MONS_YAKTAUR_CAPTAIN: natural_leader = true; case MONS_YAKTAUR: 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: natural_leader = true; band = BAND_OGRE_MAGE; band_size = 4 + random2(4); break; case MONS_BALRUG: natural_leader = true; band = BAND_BALRUG; // RED gr demon band_size = 2 + random2(3); break; case MONS_CACODEMON: natural_leader = true; band = BAND_CACODEMON; // BROWN gr demon band_size = 1 + random2(3); break; case MONS_EXECUTIONER: if (coinflip()) { natural_leader = true; band = BAND_EXECUTIONER; // DARKGREY gr demon band_size = 1 + random2(3); } break; case MONS_PANDEMONIUM_DEMON: natural_leader = true; 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()) { natural_leader = true; 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); natural_leader = true; 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)) { natural_leader = true; band = BAND_SHEEP; // Odyssey reference band_size = 2 + random2(3); } break; case MONS_POLYPHEMUS: natural_leader = true; 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: natural_leader = true; band = BAND_DRACONIAN; // yup, scary band_size = random_range(3,6) + random_range(3,6) + 2; break; case MONS_ILSUIW: natural_leader = true; band = BAND_ILSUIW; band_size = 3 + random2(3); break; case MONS_AZRAEL: natural_leader = true; band = BAND_AZRAEL; band_size = 4 + random2(5); break; case MONS_DUVESSA: // no natural_leader since this band is supposed to be symmetric band = BAND_DUVESSA; band_size = 1; break; case MONS_KHUFU: natural_leader = true; band = BAND_KHUFU; band_size = 3; break; case MONS_GOLDEN_EYE: band = BAND_GOLDEN_EYE; band_size = 1 + random2(5); break; case MONS_PIKEL: natural_leader = true; band = BAND_PIKEL; band_size = 4; break; case MONS_MERFOLK_AQUAMANCER: natural_leader = true; band = BAND_MERFOLK_AQUAMANCER; band_size = random_range(3, 6); break; case MONS_MERFOLK_JAVELINEER: natural_leader = true; band = BAND_MERFOLK_JAVELINEER; band_size = random_range(3, 5); break; case MONS_MERFOLK_IMPALER: natural_leader = true; band = BAND_MERFOLK_IMPALER; band_size = random_range(3, 5); 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 = static_cast( random_choose_weighted(30, MONS_MERMAID, 15, MONS_MERFOLK, 10, MONS_MERFOLK_JAVELINEER, 10, MONS_MERFOLK_IMPALER, 0)); 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_SLAVE; break; case BAND_MERFOLK_AQUAMANCER: mon_type = static_cast( random_choose_weighted(8, MONS_MERFOLK, 10, MONS_ICE_BEAST, 0)); break; case BAND_MERFOLK_IMPALER: case BAND_MERFOLK_JAVELINEER: mon_type = MONS_MERFOLK; 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; } if (mg.behaviour == BEH_COPY) { mg.behaviour = (mg.summoner == &you) ? BEH_FRIENDLY : SAME_ATTITUDE((&menv[mg.summoner->mindex()])); } 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_NO_REWARD; 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); behaviour_event(creation, ME_EVAL); } 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_unholy() || mon->is_evil())); const bool isUnholy = (is_evil_god(you.religion) && mon->is_holy()); const bool isLawful = (you.religion == GOD_ZIN && (mon->is_unclean() || 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) || monster_at(mg.pos) || you.pos() == mg.pos && !fedhas_passthrough_class(mg.cls) || !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; // FIXME: resistence checks use the ghost_demon member for // monster types that use it, so a call to mons_avoids_cloud() // will crash for dummy monsters which should have a // ghost_demon setup. if (mons_is_ghost_demon(dummy.type)) return (-1); 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 && you.see_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(4066); mon = ((temp_rand > 3947) ? MONS_SIXFIRHY : // 3.00% (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_FIRE_DRAKE : (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); } ///////////////////////////////////////////////////////////////////////////// // // 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; } }