/** * @file * @brief Misc monster poly/mimic functions. **/ #include "AppHdr.h" #include "mon-poly.h" #include "artefact.h" #include "attitude-change.h" #include "butcher.h" #include "delay.h" #include "describe.h" #include "dgn-overview.h" #include "dungeon.h" #include "env.h" #include "exclude.h" #include "godconduct.h" #include "hints.h" #include "itemprop.h" #include "items.h" #include "libutil.h" #include "monster.h" #include "mon-flags.h" #include "mon-death.h" #include "mon-message.h" #include "mon-place.h" #include "mon-util.h" #include "notes.h" #include "player.h" #include "religion.h" #include "state.h" #include "strings.h" #include "terrain.h" #include "traps.h" #include "xom.h" dungeon_feature_type get_mimic_feat(const monster* mimic) { if (mimic->props.exists("feat_type")) return static_cast(mimic->props["feat_type"].get_short()); else return DNGN_FLOOR; } bool feature_mimic_at(const coord_def &c) { return map_masked(c, MMT_MIMIC); } item_def* item_mimic_at(const coord_def &c) { for (stack_iterator si(c); si; ++si) if (si->flags & ISFLAG_MIMIC) return &*si; return NULL; } bool mimic_at(const coord_def &c) { return feature_mimic_at(c) || item_mimic_at(c); } // The default suitable() function for monster_drop_things(). bool is_any_item(const item_def& item) { return true; } void monster_drop_things(monster* mons, bool mark_item_origins, bool (*suitable)(const item_def& item), int owner_id) { // Drop weapons and missiles last (i.e., on top), so others pick up. for (int i = NUM_MONSTER_SLOTS - 1; i >= 0; --i) { int item = mons->inv[i]; if (item != NON_ITEM && suitable(mitm[item])) { const bool summoned_item = testbits(mitm[item].flags, ISFLAG_SUMMONED); if (summoned_item) { item_was_destroyed(mitm[item], mons->mindex()); destroy_item(item); } else { if (mark_item_origins && mitm[item].defined()) origin_set_monster(mitm[item], mons); if (mitm[item].props.exists("autoinscribe")) { add_inscription(mitm[item], mitm[item].props["autoinscribe"].get_string()); mitm[item].props.erase("autoinscribe"); } // Unrands held by fixed monsters would give awfully redundant // messages ("Cerebov hits you with the Sword of Cerebov."), // thus delay identification until drop/death. autoid_unrand(mitm[item]); // If a monster is swimming, the items are ALREADY // underwater. move_item_to_grid(&item, mons->pos(), mons->swimming()); } mons->inv[i] = NON_ITEM; } } } static bool _valid_morph(monster* mons, monster_type new_mclass) { const dungeon_feature_type current_tile = grd(mons->pos()); monster_type old_mclass = mons_base_type(mons); // Shapeshifters cannot polymorph into glowing shapeshifters or // vice versa. if ((new_mclass == MONS_GLOWING_SHAPESHIFTER && mons->has_ench(ENCH_SHAPESHIFTER)) || (new_mclass == MONS_SHAPESHIFTER && mons->has_ench(ENCH_GLOWING_SHAPESHIFTER))) { return false; } // Various inappropriate polymorph targets. if (mons_class_holiness(new_mclass) != mons_class_holiness(old_mclass) || mons_class_flag(new_mclass, M_UNFINISHED) // no unfinished monsters || mons_class_flag(new_mclass, M_CANT_SPAWN) // no dummy monsters || mons_class_flag(new_mclass, M_NO_POLY_TO) // explicitly disallowed || mons_class_flag(new_mclass, M_UNIQUE) // no uniques || mons_class_flag(new_mclass, M_NO_EXP_GAIN) // not helpless || new_mclass == MONS_PROGRAM_BUG // 'morph targets are _always_ "base" classes, not derived ones. || new_mclass != mons_species(new_mclass) || new_mclass == mons_species(old_mclass) // They act as separate polymorph classes on their own. || mons_class_is_zombified(new_mclass) || mons_is_zombified(mons) && !mons_zombie_size(new_mclass) // Currently unused (no zombie shapeshifters, no polymorph). || mons->type == MONS_SKELETON && !mons_skeleton(new_mclass) || mons->type == MONS_ZOMBIE && !mons_zombifiable(new_mclass) // These require manual setting of the ghost demon struct to // indicate their characteristics, which we currently aren't // smart enough to handle. || mons_is_ghost_demon(new_mclass) // Other poly-unsuitable things. || mons_is_statue(new_mclass) || mons_is_projectile(new_mclass) || mons_is_tentacle_or_tentacle_segment(new_mclass) // Don't polymorph things without Gods into priests. || (mons_class_flag(new_mclass, MF_PRIEST) && mons->god == GOD_NO_GOD) // The spell on Prince Ribbit can't be broken so easily. || (new_mclass == MONS_HUMAN && (mons->type == MONS_PRINCE_RIBBIT || mons->mname == "Prince Ribbit"))) { return false; } // Determine if the monster is happy on current tile. return monster_habitable_grid(new_mclass, current_tile); } static bool _is_poly_power_unsuitable(poly_power_type power, int src_pow, int tgt_pow, int relax) { switch (power) { case PPT_LESS: return tgt_pow > src_pow - 3 + relax * 3 / 2 || (power == PPT_LESS && tgt_pow < src_pow - relax / 2); case PPT_MORE: return tgt_pow < src_pow + 2 - relax || (power == PPT_MORE && tgt_pow > src_pow + relax); default: case PPT_SAME: return tgt_pow < src_pow - relax || tgt_pow > src_pow + relax * 3 / 2; } } static bool _jiyva_slime_target(monster_type targetc) { return you_worship(GOD_JIYVA) && (targetc == MONS_DEATH_OOZE || targetc == MONS_OOZE || targetc == MONS_JELLY || targetc == MONS_SLIME_CREATURE || targetc == MONS_ACID_BLOB || targetc == MONS_AZURE_JELLY); } void change_monster_type(monster* mons, monster_type targetc) { bool could_see = you.can_see(mons); bool slimified = _jiyva_slime_target(targetc); // Quietly remove the old monster's invisibility before transforming // it. If we don't do this, it'll stay invisible even after losing // the invisibility enchantment below. mons->del_ench(ENCH_INVIS, false, false); // Remove replacement tile, since it probably doesn't work for the // new monster. mons->props.erase("monster_tile_name"); mons->props.erase("monster_tile"); // Even if the monster transforms from one type that can behold the // player into a different type which can also behold the player, // the polymorph disrupts the beholding process. Do this before // changing mons->type, since unbeholding can only happen while // the monster is still a mermaid/siren. you.remove_beholder(mons); you.remove_fearmonger(mons); if (mons_is_tentacle_head(mons_base_type(mons))) destroy_tentacles(mons); // Inform listeners that the original monster is gone. fire_monster_death_event(mons, KILL_MISC, NON_MONSTER, true); // the actual polymorphing: uint64_t flags = mons->flags & ~(MF_INTERESTING | MF_SEEN | MF_ATT_CHANGE_ATTEMPT | MF_WAS_IN_VIEW | MF_BAND_MEMBER | MF_KNOWN_SHIFTER | MF_MELEE_MASK | MF_SPELL_MASK); flags |= MF_POLYMORPHED; string name; // Preserve the names of uniques and named monsters. if (mons->type == MONS_ROYAL_JELLY || mons->mname == "shaped Royal Jelly") { name = "shaped Royal Jelly"; flags |= MF_INTERESTING | MF_NAME_SUFFIX; } else if (mons->type == MONS_LERNAEAN_HYDRA || mons->mname == "shaped Lernaean hydra") { name = "shaped Lernaean hydra"; flags |= MF_INTERESTING | MF_NAME_SUFFIX; } else if (mons->mons_species() == MONS_SERPENT_OF_HELL || mons->mname == "shaped Serpent of Hell") { name = "shaped Serpent of Hell"; flags |= MF_INTERESTING | MF_NAME_SUFFIX; } else if (!mons->mname.empty()) { if (flags & MF_NAME_MASK) { // Remove the replacement name from the new monster flags = flags & ~(MF_NAME_MASK | MF_NAME_DESCRIPTOR | MF_NAME_DEFINITE | MF_NAME_SPECIES | MF_NAME_ZOMBIE | MF_NAME_NOCORPSE); } else name = mons->mname; } else if (mons_is_unique(mons->type)) { flags |= MF_INTERESTING; name = mons->name(DESC_PLAIN, true); // "Blork the orc" and similar. const size_t the_pos = name.find(" the "); if (the_pos != string::npos) name = name.substr(0, the_pos); } const monster_type real_targetc = (mons->has_ench(ENCH_GLOWING_SHAPESHIFTER)) ? MONS_GLOWING_SHAPESHIFTER : (mons->has_ench(ENCH_SHAPESHIFTER)) ? MONS_SHAPESHIFTER : targetc; const god_type god = (player_will_anger_monster(real_targetc) || (you_worship(GOD_BEOGH) && mons_genus(real_targetc) != MONS_ORC)) ? GOD_NO_GOD : mons->god; if (god == GOD_NO_GOD) flags &= ~MF_GOD_GIFT; const int old_hp = mons->hit_points; const int old_hp_max = mons->max_hit_points; const bool old_mon_caught = mons->caught(); const char old_ench_countdown = mons->ench_countdown; // XXX: mons_is_unique should be converted to monster::is_unique, and that // function should be testing the value of props["original_was_unique"] // which would make things a lot simpler. // See also record_monster_defeat. bool old_mon_unique = mons_is_unique(mons->type); if (mons->props.exists("original_was_unique") && mons->props["original_was_unique"].get_bool()) { old_mon_unique = true; } mon_enchant abj = mons->get_ench(ENCH_ABJ); mon_enchant fabj = mons->get_ench(ENCH_FAKE_ABJURATION); mon_enchant charm = mons->get_ench(ENCH_CHARM); mon_enchant shifter = mons->get_ench(ENCH_GLOWING_SHAPESHIFTER, ENCH_SHAPESHIFTER); mon_enchant sub = mons->get_ench(ENCH_SUBMERGED); mon_enchant summon = mons->get_ench(ENCH_SUMMON); mon_enchant tp = mons->get_ench(ENCH_TP); mon_enchant vines = mons->get_ench(ENCH_AWAKEN_VINES); mon_enchant forest = mons->get_ench(ENCH_AWAKEN_FOREST); monster_spells spl = mons->spells; const bool need_save_spells = (!name.empty() && (!mons->can_use_spells() || mons->is_actual_spellcaster()) && !slimified); mons->number = 0; // Note: define_monster() will clear out all enchantments! - bwr if (mons_is_zombified(mons)) define_zombie(mons, targetc, mons->type); else { mons->type = targetc; mons->base_monster = MONS_NO_MONSTER; define_monster(mons); } mons->mname = name; mons->props["original_name"] = name; mons->props["original_was_unique"] = old_mon_unique; mons->god = god; mons->props.erase("dbname"); mons->flags = flags; // Line above might clear melee and/or spell flags; restore. mons->bind_melee_flags(); mons->bind_spell_flags(); // Forget various speech/shout Lua functions. mons->props.erase("speech_prefix"); // Don't allow polymorphing monsters for hides. mons->props[NEVER_HIDE_KEY] = true; // Keep spells for named monsters, but don't override innate ones // for dragons and the like. This means that Sigmund polymorphed // into a goblin will still cast spells, but if he ends up as a // swamp drake he'll breathe fumes and, if polymorphed further, // won't remember his spells anymore. if (need_save_spells && (!mons->can_use_spells() || mons->is_actual_spellcaster())) { mons->spells = spl; } mons->add_ench(abj); mons->add_ench(fabj); mons->add_ench(charm); mons->add_ench(shifter); mons->add_ench(sub); mons->add_ench(summon); mons->add_ench(tp); mons->add_ench(vines); mons->add_ench(forest); // Allows for handling of submerged monsters which polymorph into // monsters that can't submerge on this square. if (mons->has_ench(ENCH_SUBMERGED) && !monster_can_submerge(mons, grd(mons->pos()))) { mons->del_ench(ENCH_SUBMERGED); } mons->ench_countdown = old_ench_countdown; if (mons_class_flag(mons->type, M_INVIS)) mons->add_ench(ENCH_INVIS); mons->hit_points = mons->max_hit_points * old_hp / old_hp_max + random2(mons->max_hit_points); mons->hit_points = min(mons->max_hit_points, mons->hit_points); // Don't kill it. mons->hit_points = max(mons->hit_points, 1); mons->speed_increment = 67 + random2(6); monster_drop_things(mons); // New monster type might be interesting. mark_interesting_monst(mons); // If new monster is visible to player, then we've seen it. if (you.can_see(mons)) { seen_monster(mons); // If the player saw both the beginning and end results of a // shifter changing, then s/he knows it must be a shifter. if (could_see && shifter.ench != ENCH_NONE) discover_shifter(mons); } if (old_mon_caught) check_net_will_hold_monster(mons); // Even if the new form can constrict, it might be with a different // body part. Likewise, the new form might be too large for its // current constrictor. Rather than trying to handle these as special // cases, just stop the constriction entirely. The usual message about // evaporating and reforming justifies this behaviour. mons->stop_constricting_all(false); mons->stop_being_constricted(); mons->check_clinging(false); } // If targetc == RANDOM_MONSTER, then relpower indicates the desired // power of the new monster, relative to the current monster. // Relaxation still takes effect when needed, no matter what relpower // says. bool monster_polymorph(monster* mons, monster_type targetc, poly_power_type power, bool force_beh) { // Don't attempt to polymorph a monster that is busy using the stairs. if (mons->flags & MF_TAKING_STAIRS) return false; ASSERT(!(mons->flags & MF_BANISHED) || player_in_branch(BRANCH_ABYSS)); int source_power, target_power, relax; int source_tier, target_tier; int tries = 1000; // Used to be mons_power, but that just returns hit_dice // for the monster class. By using the current hit dice // the player gets the opportunity to use draining more // effectively against shapeshifters. - bwr source_power = mons->get_hit_dice(); source_tier = mons_demon_tier(mons->type); // There's not a single valid target on the '&' demon tier, so unless we // make one, let's ban this outright. if (source_tier == -1) { return simple_monster_message(mons, "'s appearance momentarily alters."); } relax = 1; if (targetc == RANDOM_MONSTER) { do { // Pick a monster species that's guaranteed happy at this grid. targetc = random_monster_at_grid(mons->pos(), true); target_power = mons_power(targetc); // Can't compare tiers in valid_morph, since we want to affect only // random polymorphs, and not absolutely, too. target_tier = mons_demon_tier(targetc); if (one_chance_in(200)) relax++; if (relax > 50) return simple_monster_message(mons, " shudders."); } while (tries-- && (!_valid_morph(mons, targetc) || source_tier != target_tier && !x_chance_in_y(relax, 200) || _is_poly_power_unsuitable(power, source_power, target_power, relax))); } bool could_see = you.can_see(mons); bool need_note = (could_see && MONST_INTERESTING(mons)); string old_name_a = mons->full_name(DESC_A); string old_name_the = mons->full_name(DESC_THE); monster_type oldc = mons->type; if (targetc == RANDOM_TOUGHER_MONSTER) { vector target_types; for (int mc = 0; mc < NUM_MONSTERS; ++mc) { const monsterentry *me = get_monster_data((monster_type) mc); int delta = (int) me->hpdice[0] - mons->get_hit_dice(); if (delta != 1) continue; if (!_valid_morph(mons, (monster_type) mc)) continue; target_types.push_back((monster_type) mc); } if (target_types.empty()) return false; shuffle_array(target_types); targetc = target_types[0]; } if (!_valid_morph(mons, targetc)) return simple_monster_message(mons, " looks momentarily different."); change_monster_type(mons, targetc); bool can_see = you.can_see(mons); // Messaging bool player_messaged = true; if (could_see) { string verb = ""; string obj = ""; obj = can_see ? mons_type_name(targetc, DESC_A) : "something you cannot see"; if (oldc == MONS_OGRE && targetc == MONS_TWO_HEADED_OGRE) { verb = "grows a second head"; obj = ""; } else if (mons->is_shapeshifter()) verb = "changes into "; else if (_jiyva_slime_target(targetc)) verb = "quivers uncontrollably and liquefies into "; else verb = "evaporates and reforms as "; mprf("%s %s%s!", old_name_the.c_str(), verb.c_str(), obj.c_str()); } else if (can_see) { mprf("%s appears out of thin air!", mons->name(DESC_A).c_str()); autotoggle_autopickup(false); } else player_messaged = false; if (need_note || could_see && can_see && MONST_INTERESTING(mons)) { string new_name = can_see ? mons->full_name(DESC_A) : "something unseen"; take_note(Note(NOTE_POLY_MONSTER, 0, 0, old_name_a.c_str(), new_name.c_str())); if (can_see) mons->flags |= MF_SEEN; } if (!force_beh) player_angers_monster(mons); // Xom likes watching monsters being polymorphed. if (can_see) { xom_is_stimulated(mons->is_shapeshifter() ? 12 : power == PPT_LESS || mons->friendly() ? 25 : power == PPT_SAME ? 50 : 100); } return player_messaged; } bool mon_can_be_slimified(monster* mons) { const mon_holy_type holi = mons->holiness(); return !mons->is_insubstantial() && !mons_is_tentacle_or_tentacle_segment(mons->type) && (holi == MH_UNDEAD || holi == MH_NATURAL && !mons_is_slime(mons)); } void slimify_monster(monster* mon, bool hostile) { monster_type target = MONS_JELLY; const int x = mon->get_hit_dice() + (coinflip() ? 1 : -1) * random2(5); if (x < 3) target = MONS_OOZE; else if (x >= 3 && x < 5) target = MONS_JELLY; else if (x >= 5 && x <= 11) target = MONS_SLIME_CREATURE; else { if (coinflip()) target = MONS_ACID_BLOB; else target = MONS_AZURE_JELLY; } if (feat_is_water(grd(mon->pos()))) // Pick something amphibious. target = (x < 7) ? MONS_JELLY : MONS_SLIME_CREATURE; if (mon->holiness() == MH_UNDEAD) target = MONS_DEATH_OOZE; // Bail out if jellies can't live here. if (!monster_habitable_grid(target, grd(mon->pos()))) { simple_monster_message(mon, " quivers momentarily."); return; } monster_polymorph(mon, target); if (!mons_eats_items(mon)) mon->add_ench(ENCH_EAT_ITEMS); if (!hostile) mon->attitude = ATT_STRICT_NEUTRAL; else mon->attitude = ATT_HOSTILE; mons_make_god_gift(mon, GOD_JIYVA); // Don't want shape-shifters to shift into non-slimes. mon->del_ench(ENCH_GLOWING_SHAPESHIFTER); mon->del_ench(ENCH_SHAPESHIFTER); mons_att_changed(mon); } void seen_monster(monster* mons) { // If the monster is in the auto_exclude list, automatically // set an exclusion. set_auto_exclude(mons); set_unique_annotation(mons); item_def* weapon = mons->weapon(); if (weapon && is_range_weapon(*weapon)) mons->flags |= MF_SEEN_RANGED; // Monster was viewed this turn mons->flags |= MF_WAS_IN_VIEW; // mark items as seen. for (int slot = MSLOT_WEAPON; slot <= MSLOT_LAST_VISIBLE_SLOT; slot++) { int item_id = mons->inv[slot]; if (item_id != NON_ITEM) mitm[item_id].flags |= ISFLAG_SEEN; } if (mons->flags & MF_SEEN) return; // First time we've seen this particular monster. mons->flags |= MF_SEEN; if (!mons_is_mimic(mons->type)) { if (crawl_state.game_is_hints()) hints_monster_seen(*mons); if (MONST_INTERESTING(mons)) { string name = mons->name(DESC_A, true); if (mons->type == MONS_PLAYER_GHOST) { name += make_stringf(" (%s)", short_ghost_description(mons, true).c_str()); } take_note( Note(NOTE_SEEN_MONSTER, mons->type, 0, name.c_str())); } } if (!(mons->flags & MF_TSO_SEEN)) { if (!mons->has_ench(ENCH_ABJ) && !mons->has_ench(ENCH_FAKE_ABJURATION) && !testbits(mons->flags, MF_NO_REWARD) && !mons_class_flag(mons->type, M_NO_EXP_GAIN) && !crawl_state.game_is_arena()) { did_god_conduct(DID_SEE_MONSTER, mons->get_experience_level(), true, mons); } mons->flags |= MF_TSO_SEEN; } if (mons_allows_beogh(mons)) env.level_state |= LSTATE_BEOGH; }