diff options
Diffstat (limited to 'crawl-ref/source/mon-util.cc')
-rw-r--r-- | crawl-ref/source/mon-util.cc | 1000 |
1 files changed, 706 insertions, 294 deletions
diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 4df429aab7..f4ef56fb8d 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -30,6 +30,8 @@ #include "debug.h" #include "itemname.h" +#include "itemprop.h" +#include "monplace.h" #include "mstuff2.h" #include "player.h" #include "randart.h" @@ -42,6 +44,20 @@ static FixedVector < int, NUM_MONSTERS > mon_entry; // really important extern -- screen redraws suck w/o it {dlb} FixedVector < unsigned short, 1000 > mcolour; +enum habitat_type +{ + // Flying monsters will appear in all categories + HT_NORMAL, // Normal critters + HT_SHALLOW_WATER, // Union of normal + water + HT_DEEP_WATER, // Water critters + HT_LAVA, // Lava critters + + NUM_HABITATS +}; + +static bool initialized_randmons = false; +static std::vector<int> monsters_by_habitat[NUM_HABITATS]; + static struct monsterentry mondata[] = { #include "mon-data.h" }; @@ -59,8 +75,8 @@ static const char *monster_spell_name[] = { "Throw Frost", "Paralysis", "Slow", - "Haste", - "Confuse", + "Haste", + "Confuse", "Venom Bolt", "Fire Bolt", "Cold Bolt", @@ -79,45 +95,153 @@ static const char *monster_spell_name[] = { "Orb Energy", "Brain Feed", "Level Summon", - "Fake Rakshasa Summon", + "Fake Rakshasa Summon", "Steam Ball", "Summon Demon", "Animate Dead", "Pain", - "Smite", + "Smite", "Sticky Flame", "Poison Blast", "Summon Demon Lesser", "Summon Ufetubus", - "Purple Blast", + "Purple Blast", "Summon Beast", "Energy Bolt", "Sting", "Iron Bolt", - "Stone Arrow", + "Stone Arrow", "Poison Splash", "Summon Undead", - "Mutation", + "Mutation", "Cantrip", "Disintegrate", "Marsh Gas", "Quicksilver Bolt", "Torment", "Hellfire", - "Metal Splinters", + "Metal Splinters", "Summon Demon Greater", "Banishment", + "Controlled Blink", + "Control Undead", + "Miasma", + "Summon Lizards", + "Blink Other", + "Dispel Undead", + "Hellfrost", + "Poison Arrow", + "Summon Small Mammals", + "Summon Mushrooms", }; #endif static int mons_exp_mod(int mclass); -static monsterentry *seekmonster(int *p_monsterid); +static monsterentry *seekmonster(int p_monsterid); // macro that saves some typing, nothing more -#define smc seekmonster(&mc) +#define smc seekmonster(mc) /* ******************** BEGIN PUBLIC FUNCTIONS ******************** */ -void mons_init(FixedVector < unsigned short, 1000 > &colour) + +habitat_type grid2habitat(int grid) +{ + switch (grid) + { + case DNGN_DEEP_WATER: + return (HT_DEEP_WATER); + case DNGN_SHALLOW_WATER: + return (HT_SHALLOW_WATER); + case DNGN_LAVA: + return (HT_LAVA); + default: + return (HT_NORMAL); + } +} + +int habitat2grid(habitat_type ht) +{ + switch (ht) + { + case HT_DEEP_WATER: + return (DNGN_DEEP_WATER); + case HT_SHALLOW_WATER: + return (DNGN_SHALLOW_WATER); + case HT_LAVA: + return (DNGN_LAVA); + case HT_NORMAL: + default: + return (DNGN_FLOOR); + } +} + +static void initialize_randmons() +{ + for (int i = 0; i < NUM_HABITATS; ++i) + { + int grid = habitat2grid( habitat_type(i) ); + + for (int m = 0; m < NUM_MONSTERS; ++m) + { + if (invalid_monster_class(m)) + continue; + if (monster_habitable_grid(m, grid)) + monsters_by_habitat[i].push_back(m); + } + } + initialized_randmons = true; +} + +monster_type random_monster_at_grid(int x, int y) +{ + return (random_monster_at_grid(grd[x][y])); +} + +monster_type random_monster_at_grid(int grid) +{ + if (!initialized_randmons) + initialize_randmons(); + + const habitat_type ht = grid2habitat(grid); + const std::vector<int> &valid_mons = monsters_by_habitat[ht]; + ASSERT(!valid_mons.empty()); + return valid_mons.empty()? MONS_PROGRAM_BUG + : monster_type(valid_mons[ random2(valid_mons.size()) ]); +} + +monster_type get_monster_by_name(std::string name, bool exact) +{ + lowercase(name); + + monster_type mon = MONS_PROGRAM_BUG; + for (unsigned i = 0; i < sizeof(mondata) / sizeof(*mondata); ++i) + { + std::string candidate = mondata[i].name; + lowercase(candidate); + + const int mtype = mondata[i].mc; + + if (exact) + { + if (name == candidate) + return monster_type(mtype); + + continue; + } + + const std::string::size_type match = candidate.find(name); + if (match == std::string::npos) + continue; + + mon = monster_type(mtype); + // we prefer prefixes over partial matches + if (match == 0) + break; + } + return (mon); +} + +void init_monsters(FixedVector < unsigned short, 1000 > &colour) { unsigned int x; // must be unsigned to match size_t {dlb} @@ -143,13 +267,40 @@ void mons_init(FixedVector < unsigned short, 1000 > &colour) //return (monsterentry *) 0; // return value should not matter here {dlb} } // end mons_init() +unsigned long get_mons_class_resists(int mc) +{ + return (smc->resists); +} -int mons_flag(int mc, int bf) +unsigned long get_mons_resists(const monsters *mon) { - return ((smc->bitfields & bf) != 0); -} // end mons_flag() + unsigned long resists = get_mons_class_resists(mon->type); + if (mons_genus(mon->type) == MONS_DRACONIAN + && mon->type != MONS_DRACONIAN) + { + monster_type draco_species = draco_subspecies(mon); + if (draco_species != mon->type) + resists |= get_mons_class_resists(draco_species); + } + return (resists); +} + +unsigned long mons_resist(const monsters *mon, unsigned long flags) +{ + return (get_mons_resists(mon) & flags); +} + +bool mons_class_flag(int mc, int bf) +{ + const monsterentry *me = smc; + + if (!me) + return (false); -static int scan_mon_inv_randarts( struct monsters *mon, int ra_prop ) + return ((me->bitfields & bf) != 0); +} // end mons_class_flag() + +static int scan_mon_inv_randarts( const monsters *mon, int ra_prop ) { int ret = 0; @@ -181,15 +332,52 @@ static int scan_mon_inv_randarts( struct monsters *mon, int ra_prop ) return (ret); } +mon_holy_type mons_holiness(const monsters *mon) +{ + return (mons_class_holiness(mon->type)); +} -int mons_holiness(int mc) +mon_holy_type mons_class_holiness(int mc) { return (smc->holiness); } // end mons_holiness() +bool mons_class_is_stationary(int type) +{ + return (type == MONS_OKLOB_PLANT + || type == MONS_PLANT + || type == MONS_FUNGUS + || type == MONS_CURSE_SKULL + || mons_is_statue(type) + || mons_is_mimic(type)); +} + +bool mons_is_stationary(const monsters *mons) +{ + return (mons_class_is_stationary(mons->type)); +} + +bool invalid_monster(const monsters *mons) +{ + return (!mons || mons->type == -1); +} + +bool invalid_monster_class(int mclass) +{ + return (mclass < 0 + || mclass >= NUM_MONSTERS + || mon_entry[mclass] == -1 + || mon_entry[mclass] == MONS_PROGRAM_BUG); +} + +bool mons_is_statue(int mc) +{ + return (mc == MONS_ORANGE_STATUE || mc == MONS_SILVER_STATUE); +} + bool mons_is_mimic( int mc ) { - return (mons_charclass( mc ) == MONS_GOLD_MIMIC); + return (mons_species( mc ) == MONS_GOLD_MIMIC); } bool mons_is_demon( int mc ) @@ -197,7 +385,7 @@ bool mons_is_demon( int mc ) const int show_char = mons_char( mc ); // Not every demonic monster is a demon (ie hell hog, hell hound) - if (mons_holiness( mc ) == MH_DEMONIC + if (mons_class_holiness( mc ) == MH_DEMONIC && (isdigit( show_char ) || show_char == '&')) { return (true); @@ -248,18 +436,34 @@ int mons_weight(int mc) return (smc->weight); } // end mons_weight() - -int mons_corpse_thingy(int mc) +corpse_effect_type mons_corpse_effect(int mc) { return (smc->corpse_thingy); -} // end mons_corpse_thingy() +} // end mons_corpse_effect() + + +monster_type mons_species( int mc ) +{ + const monsterentry *me = seekmonster(mc); + return (me? me->species : MONS_PROGRAM_BUG); +} // end mons_species() +monster_type mons_genus( int mc ) +{ + return (smc->genus); +} -int mons_charclass(int mc) +monster_type draco_subspecies( const monsters *mon ) { - return (smc->charclass); -} // end mons_charclass() + ASSERT( mons_genus( mon->type ) == MONS_DRACONIAN ); + + monster_type ret = mons_species( mon->type ); + if (ret == MONS_DRACONIAN && mon->type != MONS_DRACONIAN) + ret = static_cast<monster_type>( mon->number ); + + return (ret); +} int mons_shouts(int mc) { @@ -273,22 +477,14 @@ int mons_shouts(int mc) bool mons_is_unique( int mc ) { - if (mc <= MONS_PROGRAM_BUG - || (mc >= MONS_NAGA_MAGE && mc <= MONS_ROYAL_JELLY) - || (mc >= MONS_ANCIENT_LICH - && (mc != MONS_PLAYER_GHOST && mc != MONS_PANDEMONIUM_DEMON))) - { - return (false); - } - - return (true); + return (mons_class_flag(mc, M_UNIQUE)); } char mons_see_invis( struct monsters *mon ) { if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON) return (ghost.values[ GVAL_SEE_INVIS ]); - else if (((seekmonster(&mon->type))->bitfields & M_SEE_INVIS) != 0) + else if (((seekmonster(mon->type))->bitfields & M_SEE_INVIS) != 0) return (1); else if (scan_mon_inv_randarts( mon, RAP_EYESIGHT ) > 0) return (1); @@ -339,11 +535,20 @@ char mons_itemuse(int mc) return (smc->gmon_use); } // end mons_itemuse() -unsigned char mons_colour(int mc) +int mons_class_colour(int mc) { - return (smc->colour); + const monsterentry *m = smc; + if (!m) + return (BLACK); + + const int class_colour = m->colour; + return (class_colour); } // end mons_colour() +int mons_colour(const monsters *monster) +{ + return (monster->colour); +} int mons_damage(int mc, int rt) { @@ -359,9 +564,9 @@ int mons_damage(int mc, int rt) return (smc->damage[rt]); } // end mons_damage() -int mons_resist_magic( struct monsters *mon ) +int mons_resist_magic( const monsters *mon ) { - int u = (seekmonster(&mon->type))->resist_magic; + int u = (seekmonster(mon->type))->resist_magic; // negative values get multiplied with mhd if (u < 0) @@ -384,7 +589,7 @@ int mons_resist_magic( struct monsters *mon ) // Returns true if the monster made its save against hostile // enchantments/some other magics. -bool check_mons_resist_magic( struct monsters *monster, int pow ) +bool check_mons_resist_magic( const monsters *monster, int pow ) { int mrs = mons_resist_magic(monster); @@ -422,7 +627,7 @@ bool check_mons_resist_magic( struct monsters *monster, int pow ) } // end check_mons_resist_magic() -int mons_res_elec( struct monsters *mon ) +int mons_res_elec( const monsters *mon ) { int mc = mon->type; @@ -430,11 +635,10 @@ int mons_res_elec( struct monsters *mon ) return (ghost.values[ GVAL_RES_ELEC ]); /* this is a variable, not a player_xx() function, so can be above 1 */ - int u = 0, f = (seekmonster(&mc))->bitfields; + int u = 0; - // of course it makes no sense setting them both :) - if (f & M_RES_ELEC) - u++; //if(f&M_ED_ELEC) u--; + if (mons_resist(mon, MR_RES_ELEC)) + u++; // don't bother checking equipment if the monster can't use it if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) @@ -453,17 +657,26 @@ int mons_res_elec( struct monsters *mon ) return (u); } // end mons_res_elec() +bool mons_res_asphyx( const monsters *mon ) +{ + const int holiness = mons_holiness( mon ); + return (holiness == MH_UNDEAD + || holiness == MH_DEMONIC + || holiness == MH_NONLIVING + || holiness == MH_PLANT + || mons_resist(mon, MR_RES_ASPHYX)); +} -int mons_res_poison( struct monsters *mon ) +int mons_res_poison( const monsters *mon ) { int mc = mon->type; - int u = 0, f = (seekmonster(&mc))->bitfields; + int u = 0, f = get_mons_resists(mon); - if (f & M_RES_POISON) + if (f & MR_RES_POISON) u++; - if (f & M_ED_POISON) + if (f & MR_VUL_POISON) u--; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) @@ -491,25 +704,25 @@ int mons_res_poison( struct monsters *mon ) } // end mons_res_poison() -int mons_res_fire( struct monsters *mon ) +int mons_res_fire( const monsters *mon ) { int mc = mon->type; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) return (ghost.values[ GVAL_RES_FIRE ]); - int u = 0, f = (seekmonster(&mc))->bitfields; + int u = 0, f = get_mons_resists(mon); // no Big Prize (tm) here either if you set all three flags. It's a pity uh? // // Note that natural monster resistance is two levels, this is duplicate // the fact that having this flag used to be a lot better than armour // for monsters (it used to make them immune in a lot of cases) -- bwr - if (f & M_RES_HELLFIRE) + if (f & MR_RES_HELLFIRE) u += 3; - else if (f & M_RES_FIRE) + else if (f & MR_RES_FIRE) u += 2; - else if (f & M_ED_FIRE) + else if (f & MR_VUL_FIRE) u--; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) @@ -539,21 +752,21 @@ int mons_res_fire( struct monsters *mon ) } // end mons_res_fire() -int mons_res_cold( struct monsters *mon ) +int mons_res_cold( const monsters *mon ) { int mc = mon->type; if (mc == MONS_PLAYER_GHOST || mc == MONS_PANDEMONIUM_DEMON) return (ghost.values[ GVAL_RES_COLD ]); - int u = 0, f = (seekmonster(&mc))->bitfields; + int u = 0, f = get_mons_resists(mon); // Note that natural monster resistance is two levels, this is duplicate // the fact that having this flag used to be a lot better than armour // for monsters (it used to make them immune in a lot of cases) -- bwr - if (f & M_RES_COLD) + if (f & MR_RES_COLD) u += 2; - else if (f & M_ED_COLD) + else if (f & MR_VUL_COLD) u--; if (mons_itemuse(mc) >= MONUSE_STARTING_EQUIPMENT) @@ -582,15 +795,16 @@ int mons_res_cold( struct monsters *mon ) return (u); } // end mons_res_cold() -int mons_res_negative_energy( struct monsters *mon ) +int mons_res_negative_energy( const monsters *mon ) { int mc = mon->type; - if (mons_holiness( mon->type ) == MH_UNDEAD - || mons_holiness( mon->type ) == MH_DEMONIC - || mons_holiness( mon->type ) == MH_NONLIVING - || mons_holiness( mon->type ) == MH_PLANT - || mon->type == MONS_SHADOW_DRAGON) + if (mons_holiness(mon) == MH_UNDEAD + || mons_holiness(mon) == MH_DEMONIC + || mons_holiness(mon) == MH_NONLIVING + || mons_holiness(mon) == MH_PLANT + || mon->type == MONS_SHADOW_DRAGON + || mon->type == MONS_DEATH_DRAKE) { return (3); // to match the value for players } @@ -613,6 +827,26 @@ int mons_res_negative_energy( struct monsters *mon ) return (u); } // end mons_res_negative_energy() +bool mons_is_evil( const monsters *mon ) +{ + return (mons_class_flag( mon->type, M_EVIL )); +} + +bool mons_is_unholy( const monsters *mon ) +{ + const mon_holy_type holy = mons_holiness( mon ); + + return (holy == MH_UNDEAD || holy == MH_DEMONIC); +} + +bool mons_has_lifeforce( const monsters *mon ) +{ + const int holy = mons_holiness( mon ); + + return (holy == MH_NATURAL || holy == MH_PLANT); + // && !mons_has_ench( mon, ENCH_PETRIFY )); +} + int mons_skeleton(int mc) { if (mons_zombie_size(mc) == 0 @@ -624,7 +858,7 @@ int mons_skeleton(int mc) return (1); } // end mons_skeleton() -char mons_class_flies(int mc) +int mons_class_flies(int mc) { if (mc == MONS_PANDEMONIUM_DEMON) return (ghost.values[ GVAL_DEMONLORD_FLY ]); @@ -640,10 +874,9 @@ char mons_class_flies(int mc) return (0); } -char mons_flies( struct monsters *mon ) +int mons_flies( const monsters *mon ) { - char ret = mons_class_flies( mon->type ); - + int ret = mons_class_flies( mon->type ); return (ret ? ret : (scan_mon_inv_randarts(mon, RAP_LEVITATE) > 0) ? 2 : 0); } // end mons_flies() @@ -666,7 +899,7 @@ int hit_points(int hit_dice, int min_hp, int rand_hp) // of monster, not a pacticular monsters current hit dice. -- bwr int mons_type_hit_dice( int type ) { - struct monsterentry *mon_class = seekmonster( &type ); + struct monsterentry *mon_class = seekmonster( type ); if (mon_class) return (mon_class->hpdice[0]); @@ -675,7 +908,7 @@ int mons_type_hit_dice( int type ) } -int exper_value( struct monsters *monster ) +int exper_value( const struct monsters *monster ) { long x_val = 0; @@ -690,10 +923,15 @@ int exper_value( struct monsters *monster ) const int item_usage = mons_itemuse(mclass); // XXX: shapeshifters can qualify here, even though they can't cast: - const bool spellcaster = mons_flag( mclass, M_SPELLCASTER ); + const bool spellcaster = mons_class_flag( mclass, M_SPELLCASTER ); // early out for no XP monsters - if (mons_flag(mclass, M_NO_EXP_GAIN)) + if (mons_class_flag(mclass, M_NO_EXP_GAIN)) + return (0); + + // no experience for destroying furniture, even if the furniture started + // the fight. + if (mons_is_statue(mclass)) return (0); // These undead take damage to maxhp, so we use only HD. -- bwr @@ -718,14 +956,7 @@ int exper_value( struct monsters *monster ) // Let's look for big spells: if (spellcaster) { - const int msecc = ((mclass == MONS_HELLION) ? MST_BURNING_DEVIL : - (mclass == MONS_PANDEMONIUM_DEMON) ? MST_GHOST - : monster->number); - - int hspell_pass[6] = { MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL, - MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL }; - - mons_spell_list( msecc, hspell_pass ); + const monster_spells &hspell_pass = monster->spells; for (int i = 0; i < 6; i++) { @@ -831,35 +1062,58 @@ int exper_value( struct monsters *monster ) return (x_val); } // end exper_value() +int obsolete_mons_spell_template_index(const monsters *mon) +{ + return (mons_class_flag(mon->type, M_SPELLCASTER)? + mon->number + : MST_NO_SPELLS); +} -// this needs to be rewritten a la the monsterseek rewrite {dlb}: -void mons_spell_list( unsigned char sec, int splist[6] ) +void mons_load_spells( monsters *mon, int book ) { - unsigned int x; + int x, y; - for (x = 0; x < NUM_MSTYPES; x++) + if (book == MST_NO_SPELLS) { - if (mspell_list[x][0] == sec) - break; + for (y = 0; y < NUM_MONSTER_SPELL_SLOTS; y++) + mon->spells[y] = MS_NO_SPELL; + return; } - if (x >= NUM_MSTYPES) - return; +#if DEBUG_DIAGNOSTICS + mprf( MSGCH_DIAGNOSTICS, "%s: loading spellbook #%d", + ptr_monam( mon, DESC_PLAIN ), book ); +#endif - // I *KNOW* this can easily be done in a loop - splist[0] = mspell_list[x][1]; // bolt spell - splist[1] = mspell_list[x][2]; // enchantment - splist[2] = mspell_list[x][3]; // self_ench - splist[3] = mspell_list[x][4]; // misc - splist[4] = mspell_list[x][5]; // misc2 - splist[5] = mspell_list[x][6]; // emergency + for (x = 0; x < 6; x++) + mon->spells[x] = MS_NO_SPELL; - if (sec == MST_GHOST) /* ghost */ + if (book == MST_GHOST) + { + for (y = 0; y < NUM_MONSTER_SPELL_SLOTS; y++) + { + mon->spells[y] = ghost.values[ GVAL_SPELL_1 + y ]; +#if DEBUG_DIAGNOSTICS + mprf( MSGCH_DIAGNOSTICS, "spell #%d: %d", y, mon->spells[y] ); +#endif + } + } + else { - for (x = 0; x < 6; x++) - splist[x] = ghost.values[ GVAL_SPELL_1 + x ]; + // this needs to be rewritten a la the monsterseek rewrite {dlb}: + for (x = 0; x < NUM_MSTYPES; x++) + { + if (mspell_list[x][0] == book) + break; + } + + if (x < NUM_MSTYPES) + { + for (y = 0; y < 6; y++) + mon->spells[y] = mspell_list[x][y + 1]; + } } -} // end mons_spell_list() +} #if DEBUG_DIAGNOSTICS const char *mons_spell_name( int spell ) @@ -872,152 +1126,200 @@ const char *mons_spell_name( int spell ) #endif // generate a shiny new and unscarred monster -void define_monster(int k) +void define_monster(int index) { + monsters &mons = menv[index]; + int temp_rand = 0; // probability determination {dlb} - int m2_class = menv[k].type; - int m2_HD, m2_hp, m2_hp_max, m2_AC, m2_ev, m2_speed; - int m2_sec = menv[k].number; - struct monsterentry *m = seekmonster(&m2_class); + int mcls = mons.type; + int hd, hp, hp_max, ac, ev, speed; + int monnumber = mons.number; + const monsterentry *m = seekmonster(mcls); + int col = mons_class_colour(mons.type); + int spells = MST_NO_SPELLS; - m2_HD = m->hpdice[0]; + hd = m->hpdice[0]; // misc - m2_AC = m->AC; - m2_ev = m->ev; + ac = m->AC; + ev = m->ev; - // speed - m2_speed = m->speed; + speed = m->speed; - // some monsters are randomized: - // did I get them all? // I don't think so {dlb} - if (mons_is_mimic( m2_class )) - m2_sec = get_mimic_colour( &menv[k] ); - else + switch (mcls) { - switch (m2_class) - { - case MONS_ABOMINATION_SMALL: - m2_HD = 4 + random2(4); - m2_AC = 3 + random2(7); - m2_ev = 7 + random2(6); - m2_speed = 7 + random2avg(9, 2); - - if (m2_sec == 250) - m2_sec = random_colour(); - break; + case MONS_ABOMINATION_SMALL: + hd = 4 + random2(4); + ac = 3 + random2(7); + ev = 7 + random2(6); + speed = 7 + random2avg(9, 2); + + if (monnumber == 250) + col = random_colour(); + break; - case MONS_ZOMBIE_SMALL: - m2_HD = (coinflip() ? 1 : 2); - break; + case MONS_ZOMBIE_SMALL: + hd = (coinflip() ? 1 : 2); + break; - case MONS_ZOMBIE_LARGE: - m2_HD = 3 + random2(5); - break; + case MONS_ZOMBIE_LARGE: + hd = 3 + random2(5); + break; - case MONS_ABOMINATION_LARGE: - m2_HD = 8 + random2(4); - m2_AC = 5 + random2avg(9, 2); - m2_ev = 3 + random2(5); - m2_speed = 6 + random2avg(7, 2); + case MONS_ABOMINATION_LARGE: + hd = 8 + random2(4); + ac = 5 + random2avg(9, 2); + ev = 3 + random2(5); + speed = 6 + random2avg(7, 2); - if (m2_sec == 250) - m2_sec = random_colour(); - break; + if (monnumber == 250) + col = random_colour(); + break; - case MONS_BEAST: - m2_HD = 4 + random2(4); - m2_AC = 2 + random2(5); - m2_ev = 7 + random2(5); - m2_speed = 8 + random2(5); - break; + case MONS_BEAST: + hd = 4 + random2(4); + ac = 2 + random2(5); + ev = 7 + random2(5); + speed = 8 + random2(5); + break; - case MONS_HYDRA: - m2_sec = 4 + random2(5); - break; + case MONS_HYDRA: + monnumber = random_range(4, 8); + break; - case MONS_DEEP_ELF_FIGHTER: - case MONS_DEEP_ELF_KNIGHT: - case MONS_DEEP_ELF_SOLDIER: - case MONS_ORC_WIZARD: - m2_sec = MST_ORC_WIZARD_I + random2(3); - break; + case MONS_DEEP_ELF_FIGHTER: + case MONS_DEEP_ELF_KNIGHT: + case MONS_DEEP_ELF_SOLDIER: + case MONS_ORC_WIZARD: + spells = MST_ORC_WIZARD_I + random2(3); + break; - case MONS_LICH: - case MONS_ANCIENT_LICH: - m2_sec = MST_LICH_I + random2(4); - break; + case MONS_LICH: + case MONS_ANCIENT_LICH: + spells = MST_LICH_I + random2(4); + break; - case MONS_HELL_KNIGHT: - m2_sec = (coinflip() ? MST_HELL_KNIGHT_I : MST_HELL_KNIGHT_II); - break; + case MONS_HELL_KNIGHT: + spells = (coinflip() ? MST_HELL_KNIGHT_I : MST_HELL_KNIGHT_II); + break; - case MONS_NECROMANCER: - m2_sec = (coinflip() ? MST_NECROMANCER_I : MST_NECROMANCER_II); - break; + case MONS_NECROMANCER: + spells = (coinflip() ? MST_NECROMANCER_I : MST_NECROMANCER_II); + break; - case MONS_WIZARD: - case MONS_OGRE_MAGE: - case MONS_EROLCHA: - case MONS_DEEP_ELF_MAGE: - m2_sec = MST_WIZARD_I + random2(5); - break; + case MONS_WIZARD: + case MONS_OGRE_MAGE: + case MONS_EROLCHA: + case MONS_DEEP_ELF_MAGE: + spells = MST_WIZARD_I + random2(5); + break; - case MONS_DEEP_ELF_CONJURER: - m2_sec = (coinflip()? MST_DEEP_ELF_CONJURER_I : MST_DEEP_ELF_CONJURER_II); - break; + case MONS_DEEP_ELF_CONJURER: + spells = + (coinflip()? MST_DEEP_ELF_CONJURER_I : MST_DEEP_ELF_CONJURER_II); + break; - case MONS_BUTTERFLY: - case MONS_SPATIAL_VORTEX: - case MONS_KILLER_KLOWN: - m2_sec = random_colour(); - break; + case MONS_BUTTERFLY: + case MONS_SPATIAL_VORTEX: + case MONS_KILLER_KLOWN: + col = random_colour(); + break; - case MONS_GILA_MONSTER: - temp_rand = random2(7); + case MONS_GILA_MONSTER: + temp_rand = random2(7); - m2_sec = (temp_rand >= 5 ? LIGHTRED : // 2/7 - temp_rand >= 3 ? LIGHTMAGENTA : // 2/7 - temp_rand == 2 ? RED : // 1/7 - temp_rand == 1 ? MAGENTA // 1/7 - : YELLOW); // 1/7 - break; + col = (temp_rand >= 5 ? LIGHTRED : // 2/7 + temp_rand >= 3 ? LIGHTMAGENTA : // 2/7 + temp_rand == 2 ? RED : // 1/7 + temp_rand == 1 ? MAGENTA // 1/7 + : YELLOW); // 1/7 + break; - case MONS_HUMAN: - case MONS_ELF: - // these are supposed to only be created by polymorph - m2_HD += random2(10); - m2_AC += random2(5); - m2_ev += random2(5); - break; + case MONS_DRACONIAN: + // these are supposed to only be created by polymorph + hd += random2(10); + ac += random2(5); + ev += random2(5); - default: - break; + if (hd >= 7) + { + mons.type = MONS_BLACK_DRACONIAN + random2(8); + col = mons_class_colour( mons.type ); } + break; + + case MONS_DRACONIAN_CALLER: + case MONS_DRACONIAN_MONK: + case MONS_DRACONIAN_ZEALOT: + case MONS_DRACONIAN_SHIFTER: + case MONS_DRACONIAN_ANNIHILATOR: + case MONS_DRACONIAN_SCORCHER: + { + // Professional draconians still have a base draconian type. + // White draconians will never be draconian scorchers, but + // apart from that, anything goes. + do + monnumber = MONS_BLACK_DRACONIAN + random2(8); + while (monnumber == MONS_WHITE_DRACONIAN + && mcls == MONS_DRACONIAN_SCORCHER); + break; + } + case MONS_DRACONIAN_KNIGHT: + { + temp_rand = random2(10); + // hell knight, death knight, chaos knight... + if (temp_rand < 6) + spells = (coinflip() ? MST_HELL_KNIGHT_I : MST_HELL_KNIGHT_II); + else if (temp_rand < 9) + spells = (coinflip() ? MST_NECROMANCER_I : MST_NECROMANCER_II); + else + spells = (coinflip() ? MST_DEEP_ELF_CONJURER_I + : MST_DEEP_ELF_CONJURER_II); + + monnumber = MONS_BLACK_DRACONIAN + random2(8); + break; } + case MONS_HUMAN: + case MONS_ELF: + // these are supposed to only be created by polymorph + hd += random2(10); + ac += random2(5); + ev += random2(5); + break; + + default: + if (mons_is_mimic( mcls )) + col = get_mimic_colour( &mons ); + break; + } + + if (spells == MST_NO_SPELLS && mons_class_flag(mons.type, M_SPELLCASTER)) + spells = m->sec; // some calculations - m2_hp = hit_points(m2_HD, m->hpdice[1], m->hpdice[2]); - m2_hp += m->hpdice[3]; - m2_hp_max = m2_hp; + hp = hit_points(hd, m->hpdice[1], m->hpdice[2]); + hp += m->hpdice[3]; + hp_max = hp; - if (m2_sec == 250) - m2_sec = m->sec; + if (monnumber == 250) + monnumber = m->sec; // so let it be written, so let it be done - menv[k].hit_dice = m2_HD; - menv[k].hit_points = m2_hp; - menv[k].max_hit_points = m2_hp_max; - menv[k].armour_class = m2_AC; - menv[k].evasion = m2_ev; - menv[k].speed = m2_speed; - menv[k].speed_increment = 70; - menv[k].number = m2_sec; - menv[k].flags = 0; + mons.hit_dice = hd; + mons.hit_points = hp; + mons.max_hit_points = hp_max; + mons.armour_class = ac; + mons.evasion = ev; + mons.speed = speed; + mons.speed_increment = 70; + mons.number = monnumber; + mons.flags = 0; + mons.colour = col; + mons_load_spells( &mons, spells ); // reset monster enchantments for (int i = 0; i < NUM_MON_ENCHANTS; i++) - menv[k].enchantment[i] = ENCH_NONE; + mons.enchantment[i] = ENCH_NONE; } // end define_monster() @@ -1048,7 +1350,7 @@ const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) static char gmo_n[ ITEMNAME_SIZE ]; char gmo_n2[ ITEMNAME_SIZE ] = ""; - gmo_n[0] = '\0'; + gmo_n[0] = 0; // If you can't see the critter, let moname() print [Ii]t. if (!vis) @@ -1059,7 +1361,7 @@ const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) // These need their description level handled here instead of // in monam(). - if (mons == MONS_SPECTRAL_THING) + if (mons == MONS_SPECTRAL_THING || mons_genus(mons) == MONS_DRACONIAN) { switch (desc) { @@ -1107,6 +1409,33 @@ const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) strcat(gmo_n, gmo_n2); break; + case MONS_DRACONIAN_CALLER: + case MONS_DRACONIAN_MONK: + case MONS_DRACONIAN_ZEALOT: + case MONS_DRACONIAN_SHIFTER: + case MONS_DRACONIAN_ANNIHILATOR: + case MONS_DRACONIAN_KNIGHT: + case MONS_DRACONIAN_SCORCHER: + if (desc != DESC_PLAIN) + strcat( gmo_n, " " ); + + switch (mons_num) + { + default: break; + case MONS_BLACK_DRACONIAN: strcat(gmo_n, "black "); break; + case MONS_MOTTLED_DRACONIAN: strcat(gmo_n, "mottled "); break; + case MONS_YELLOW_DRACONIAN: strcat(gmo_n, "yellow "); break; + case MONS_GREEN_DRACONIAN: strcat(gmo_n, "green "); break; + case MONS_PURPLE_DRACONIAN: strcat(gmo_n, "purple "); break; + case MONS_RED_DRACONIAN: strcat(gmo_n, "red "); break; + case MONS_WHITE_DRACONIAN: strcat(gmo_n, "white "); break; + case MONS_PALE_DRACONIAN: strcat(gmo_n, "pale "); break; + } + + moname( mons, vis, DESC_PLAIN, gmo_n2 ); + strcat( gmo_n, gmo_n2 ); + break; + case MONS_DANCING_WEAPON: // safety check -- if we don't have/know the weapon use default name if (mons_wpn == NON_ITEM) @@ -1136,12 +1465,12 @@ const char *monam( int mons_num, int mons, bool vis, char desc, int mons_wpn ) return (gmo_n); } // end monam() -void moname(int mons_num, bool vis, char descrip, char glog[ ITEMNAME_SIZE ]) +const char *moname(int mons_num, bool vis, char descrip, char glog[ ITEMNAME_SIZE ]) { - glog[0] = '\0'; + glog[0] = 0; char gmon_name[ ITEMNAME_SIZE ] = ""; - strcpy( gmon_name, seekmonster( &mons_num )->name ); + strcpy( gmon_name, seekmonster( mons_num )->name ); if (!vis) { @@ -1159,7 +1488,7 @@ void moname(int mons_num, bool vis, char descrip, char glog[ ITEMNAME_SIZE ]) } strcpy(gmon_name, glog); - return; + return (glog); } if (!mons_is_unique( mons_num )) @@ -1203,19 +1532,19 @@ void moname(int mons_num, bool vis, char descrip, char glog[ ITEMNAME_SIZE ]) } strcat(glog, gmon_name); + + return (glog); } // end moname() /* ********************* END PUBLIC FUNCTIONS ********************* */ // see mons_init for initialization of mon_entry array. -static struct monsterentry *seekmonster(int *p_monsterid) +static monsterentry *seekmonster(int p_monsterid) { - ASSERT(p_monsterid != 0); - - int me = mon_entry[(*p_monsterid)]; + int me = p_monsterid != -1? mon_entry[p_monsterid] : -1; if (me >= 0) // PARANOIA - return (&mondata[mon_entry[(*p_monsterid)]]); + return (&mondata[me]); else return (NULL); } // end seekmonster() @@ -1261,7 +1590,7 @@ int mons_intel_type(int mc) //jmf: new, used by my spells int mons_power(int mc) { - // for now, just return monster hit dice. + // for now, just return monster hit dice. return (smc->hpdice[0]); } @@ -1292,79 +1621,67 @@ bool mons_aligned(int m1, int m2) return (fr1 == fr2); } -bool mons_friendly(struct monsters *m) +bool mons_friendly(const monsters *m) { return (m->attitude == ATT_FRIENDLY || mons_has_ench(m, ENCH_CHARM)); } -bool mons_is_stabbable(struct monsters *m) +bool mons_is_submerged( struct monsters *mon ) { - // Make sure oklob plants are never highlighted. That'll defeat the - // point of making them look like normal plants. - return (!mons_flag(m->type, M_NO_EXP_GAIN) - && m->type != MONS_OKLOB_PLANT - && !mons_friendly(m) - && m->behaviour == BEH_SLEEP); + // FIXME, switch to 4.1's MF_SUBMERGED system which is much cleaner. + return (mons_has_ench( mon, ENCH_SUBMERGED )); } -bool mons_maybe_stabbable(struct monsters *m) +bool mons_is_paralysed(const monsters *m) { - return (!mons_flag(m->type, M_NO_EXP_GAIN) - && m->type != MONS_OKLOB_PLANT - && !mons_friendly(m) - && ((m->foe != MHITYOU && !testbits(m->flags, MF_BATTY)) - || (mons_has_ench(m, ENCH_CONFUSION) && - !mons_flag(m->type, M_CONFUSED)) - || m->behaviour == BEH_FLEE)); + // maybe this should be 70 + return (m->speed_increment <= 60); } -/* ****************************************************************** - -// In the name of England, I declare this function wasteful! {dlb} - -static monsterentry *seekmonster( int mc ) +bool mons_is_confused(const monsters *m) { + return (mons_has_ench(m, ENCH_CONFUSION) && + !mons_class_flag(m->type, M_CONFUSED)); +} - ASSERT(mc >= 0); - - int x = 0; - - while (x < mondatasize) - { - if (mondata[x].mc == mc) - return &mondata[x]; - - x++; - } - - ASSERT(false); - - return seekmonster(MONS_PROGRAM_BUG); // see the disasters coming if there is no 250? - -} // end seekmonster() -****************************************************************** */ - - -/* ****************************************************************** - -// only used once, and internal to this file, to boot {dlb}: - -// These are easy to implement here. The difficult (dull!) work of converting -// the data structures is finally finished now! -inline char *mons_name( int mc ) +bool mons_is_fleeing(const monsters *m) { + return (m->behaviour == BEH_FLEE); +} - return smc->name; - -} // end mons_name() -****************************************************************** */ +bool mons_is_sleeping(const monsters *m) +{ + return (m->behaviour == BEH_SLEEP); +} -/***************************************************************** +bool mons_is_batty(const monsters *m) +{ + return testbits(m->flags, MF_BATTY); +} - Used to determine whether or not a monster should fire a beam (MUST be - called _after_ fire_tracer() for meaningful result. +bool mons_looks_stabbable(const monsters *m) +{ + // Make sure oklob plants are never highlighted. That'll defeat the + // point of making them look like normal plants. + return (!mons_class_flag(m->type, M_NO_EXP_GAIN) + && m->type != MONS_OKLOB_PLANT + && !mons_is_mimic(m->type) + && !mons_is_statue(m->type) + && !mons_friendly(m) + && mons_is_sleeping(m)); +} -*/ +bool mons_looks_distracted(const monsters *m) +{ + return (!mons_class_flag(m->type, M_NO_EXP_GAIN) + && m->type != MONS_OKLOB_PLANT + && !mons_is_mimic(m->type) + && !mons_is_statue(m->type) + && !mons_friendly(m) + && ((m->foe != MHITYOU && !mons_is_batty(m)) + || mons_is_confused(m) + || mons_is_fleeing(m))); +} bool mons_should_fire(struct bolt &beam) { @@ -1380,19 +1697,19 @@ bool mons_should_fire(struct bolt &beam) return (false); // if we either hit no friends, or monster too dumb to care - if (beam.fr_count == 0 || !beam.smartMonster) + if (beam.fr_count == 0 || !beam.smart_monster) return (true); // only fire if they do acceptably low collateral damage // the default for this is 50%; in other words, don't // hit a foe unless you hit 2 or fewer friends. - if (beam.foe_power >= (beam.foeRatio * beam.fr_power) / 100) + if (beam.foe_power >= (beam.foe_ratio * beam.fr_power) / 100) return (true); return (false); } -int mons_has_ench(struct monsters *mon, unsigned int ench, unsigned int ench2) +int mons_has_ench(const monsters *mon, unsigned int ench, unsigned int ench2) { // silliness if (ench == ENCH_NONE) @@ -1474,7 +1791,7 @@ int mons_del_ench( struct monsters *mon, unsigned int ench, unsigned int ench2, if (ench == ENCH_INVIS) { // invisible monsters stay invisible - if (mons_flag(mon->type, M_INVIS)) + if (mons_class_flag(mon->type, M_INVIS)) { mon->enchantment[p] = ENCH_INVIS; } @@ -1487,6 +1804,7 @@ int mons_del_ench( struct monsters *mon, unsigned int ench, unsigned int ench2, strcat( info, " appears!" ); mpr( info ); } + seen_monster(mon); } } @@ -1598,6 +1916,7 @@ bool ms_requires_tracer(int monspell) case MS_IRON_BOLT: case MS_LIGHTNING_BOLT: case MS_MARSH_GAS: + case MS_MIASMA: case MS_METAL_SPLINTERS: case MS_MMISSILE: case MS_NEGATIVE_BOLT: @@ -1605,6 +1924,7 @@ bool ms_requires_tracer(int monspell) case MS_PAIN: case MS_PARALYSIS: case MS_POISON_BLAST: + case MS_POISON_ARROW: case MS_POISON_SPLASH: case MS_QUICKSILVER_BOLT: case MS_SLOW: @@ -1637,6 +1957,7 @@ bool ms_requires_tracer(int monspell) case MS_SUMMON_UFETUBUS: case MS_TELEPORT: case MS_TORMENT: + case MS_SUMMON_SMALL_MAMMALS: case MS_VAMPIRE_SUMMON: case MS_CANTRIP: @@ -1677,6 +1998,7 @@ bool ms_direct_nasty(int monspell) case MS_SUMMON_DEMON_GREATER: case MS_SUMMON_UFETUBUS: case MS_TELEPORT: + case MS_SUMMON_SMALL_MAMMALS: case MS_VAMPIRE_SUMMON: nasty = false; break; @@ -1714,6 +2036,7 @@ bool ms_useful_fleeing_out_of_sight( struct monsters *mon, int monspell ) case MS_ANIMATE_DEAD: return (true); + case MS_SUMMON_SMALL_MAMMALS: case MS_VAMPIRE_SUMMON: case MS_SUMMON_UFETUBUS: case MS_FAKE_RAKSHASA_SUMMON: @@ -1722,6 +2045,7 @@ bool ms_useful_fleeing_out_of_sight( struct monsters *mon, int monspell ) case MS_SUMMON_DEMON_LESSER: case MS_SUMMON_BEAST: case MS_SUMMON_UNDEAD: + case MS_SUMMON_MUSHROOMS: case MS_SUMMON_DEMON_GREATER: if (one_chance_in(10)) // only summon friends some of the time return (true); @@ -1763,6 +2087,7 @@ bool ms_low_hitpoint_cast( struct monsters *mon, int monspell ) ret = true; break; + case MS_SUMMON_SMALL_MAMMALS: case MS_VAMPIRE_SUMMON: case MS_SUMMON_UFETUBUS: case MS_FAKE_RAKSHASA_SUMMON: @@ -1793,7 +2118,8 @@ bool ms_waste_of_time( struct monsters *mon, int monspell ) break; case MS_INVIS: - if (mons_has_ench( mon, ENCH_INVIS )) + if (mons_has_ench( mon, ENCH_INVIS ) || + (mons_friendly(mon) && !player_see_invis(false))) ret = true; break; @@ -1902,20 +2228,11 @@ bool mons_has_ranged_spell( struct monsters *mon ) { const int mclass = mon->type; - if (mons_flag( mclass, M_SPELLCASTER )) + if (mons_class_flag( mclass, M_SPELLCASTER )) { - const int msecc = ((mclass == MONS_HELLION) ? MST_BURNING_DEVIL : - (mclass == MONS_PANDEMONIUM_DEMON) ? MST_GHOST - : mon->number); - - int hspell_pass[6] = { MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL, - MS_NO_SPELL, MS_NO_SPELL, MS_NO_SPELL }; - - mons_spell_list( msecc, hspell_pass ); - - for (int i = 0; i < 6; i++) + for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; i++) { - if (ms_ranged_spell( hspell_pass[i] )) + if (ms_ranged_spell( mon->spells[i] )) return (true); } } @@ -2004,3 +2321,98 @@ const char *mons_pronoun(int mon_type, int variant) return (""); } + +/* + * Checks if the monster can use smiting/torment to attack without unimpeded + * LOS to the player. + */ +static bool mons_can_smite(const monsters *monster) +{ + if (monster->type == MONS_FIEND) + return (true); + + const monster_spells &hspell_pass = monster->spells; + for (unsigned i = 0; i < hspell_pass.size(); ++i) + if (hspell_pass[i] == MS_TORMENT || hspell_pass[i] == MS_SMITE) + return (true); + + return (false); +} + +/* + * Determines if a monster is smart and pushy enough to displace other monsters. + * A shover should not cause damage to the shovee by displacing it, so monsters + * that trail clouds of badness are ineligible. The shover should also benefit + * from shoving, so monsters that can smite/torment are ineligible. + * + * (Smiters would be eligible for shoving when fleeing if the AI allowed for + * smart monsters to flee.) + */ +bool monster_shover(const monsters *m) +{ + const monsterentry *me = seekmonster(m->type); + if (!me) + return (false); + + // Efreet and fire elementals are disqualified because they leave behind + // clouds of flame. Rotting devils trail clouds of miasma. + if (m->type == MONS_EFREET || m->type == MONS_FIRE_ELEMENTAL + || m->type == MONS_ROTTING_DEVIL + || m->type == MONS_CURSE_TOE) + return (false); + + // Smiters profit from staying back and smiting. + if (mons_can_smite(m)) + return (false); + + int mchar = me->showchar; + // Somewhat arbitrary: giants and dragons are too big to get past anything, + // beetles are too dumb (arguable), dancing weapons can't communicate, eyes + // aren't pushers and shovers, zombies are zombies. Worms and elementals + // are on the list because all 'w' are currently unrelated. + return (mchar != 'C' && mchar != 'B' && mchar != '(' && mchar != 'D' + && mchar != 'G' && mchar != 'Z' && mchar != 'z' + && mchar != 'w' && mchar != '#'); +} + +// Returns true if m1 and m2 are related, and m1 is higher up the totem pole +// than m2. The criteria for being related are somewhat loose, as you can see +// below. +bool monster_senior(const monsters *m1, const monsters *m2) +{ + const monsterentry *me1 = seekmonster(m1->type), + *me2 = seekmonster(m2->type); + + if (!me1 || !me2) + return (false); + + int mchar1 = me1->showchar, + mchar2 = me2->showchar; + + // If both are demons, the smaller number is the nastier demon. + if (isdigit(mchar1) && isdigit(mchar2)) + return (mchar1 < mchar2); + + // &s are the evillest demons of all, well apart from Geryon, who really + // profits from *not* pushing past beasts. + if (mchar1 == '&' && isdigit(mchar2) && m1->type != MONS_GERYON) + return (m1->hit_dice > m2->hit_dice); + + // Skeletal warriors can push past zombies large and small. + if (m1->type == MONS_SKELETAL_WARRIOR && (mchar2 == 'z' || mchar2 == 'Z')) + return (m1->hit_dice > m2->hit_dice); + + if (m1->type == MONS_QUEEN_BEE + && (m2->type == MONS_KILLER_BEE + || m2->type == MONS_KILLER_BEE_LARVA)) + return (true); + + if (m1->type == MONS_KILLER_BEE && m2->type == MONS_KILLER_BEE_LARVA) + return (true); + + // Special-case gnolls so they can't get past (hob)goblins + if (m1->type == MONS_GNOLL && m2->type != MONS_GNOLL) + return (false); + + return (mchar1 == mchar2 && m1->hit_dice > m2->hit_dice); +} |