summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/mon-speak.cc
diff options
context:
space:
mode:
authorJude Brown <bookofjude@users.sourceforge.net>2009-11-12 09:01:44 +1000
committerJude Brown <bookofjude@users.sourceforge.net>2009-11-12 09:10:40 +1000
commitc5950794e9a3e338fc2568e6f0e827868c821885 (patch)
tree6f48a7f9d0d67370afadf8d3294399267be498be /crawl-ref/source/mon-speak.cc
parentf0b1c25fa937388e6fc235e181f430ccac601435 (diff)
downloadcrawl-ref-c5950794e9a3e338fc2568e6f0e827868c821885.tar.gz
crawl-ref-c5950794e9a3e338fc2568e6f0e827868c821885.zip
Rename monspeak/monplace/monstuff to mon-speak/place/stuff.
I'm pretty sure I've managed to replace every instance of a reference to these files, so I hopefully haven't missed anything.
Diffstat (limited to 'crawl-ref/source/mon-speak.cc')
-rw-r--r--crawl-ref/source/mon-speak.cc860
1 files changed, 860 insertions, 0 deletions
diff --git a/crawl-ref/source/mon-speak.cc b/crawl-ref/source/mon-speak.cc
new file mode 100644
index 0000000000..df2484850d
--- /dev/null
+++ b/crawl-ref/source/mon-speak.cc
@@ -0,0 +1,860 @@
+/*
+ * File: mon-speak.cc
+ * Summary: Functions to handle speaking monsters
+ */
+
+#include "AppHdr.h"
+
+#include "mon-speak.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <algorithm>
+
+#ifdef TARGET_OS_DOS
+#include <conio.h>
+#endif
+
+#include "externs.h"
+
+#include "beam.h"
+#include "database.h"
+#include "debug.h"
+#include "ghost.h"
+#include "message.h"
+#include "mon-stuff.h"
+#include "mon-util.h"
+#include "jobs.h"
+#include "player.h"
+#include "religion.h"
+#include "state.h"
+#include "stuff.h"
+#include "view.h"
+
+// Try the exact key lookup along with the entire prefix list.
+// If that fails, start ignoring hostile/religion/silence, in that order,
+// first skipping hostile, then hostile *and* religion, then all three.
+static std::string __try_exact_string(const std::vector<std::string> &prefixes,
+ const std::string &key,
+ bool ignore_hostile = false,
+ bool ignore_related = false,
+ bool ignore_religion = false,
+ bool ignore_silenced = false)
+{
+ bool hostile = false;
+ bool related = false;
+ bool religion = false;
+ bool silenced = false;
+
+ std::string prefix = "";
+ std::string msg = "";
+ const int size = prefixes.size();
+ for (int i = 0; i < size; i++)
+ {
+ if (prefixes[i] == "hostile")
+ {
+ if (ignore_hostile)
+ continue;
+ hostile = true;
+ }
+ else if (prefixes[i] == "related")
+ {
+ if (ignore_related)
+ continue;
+ related = true;
+ }
+ else if (prefixes[i] == "silenced")
+ {
+ if (ignore_silenced)
+ continue;
+ silenced = true;
+ }
+ else if (prefixes[i] == "beogh" || prefixes[i] == "good god"
+ || prefixes[i] == "evil god")
+ {
+ if (ignore_religion)
+ continue;
+ religion = true;
+ }
+ prefix += prefixes[i];
+ prefix += " ";
+ }
+ msg = getSpeakString(prefix + key);
+
+ if (msg.empty())
+ {
+ if (hostile) // skip hostile
+ msg = __try_exact_string(prefixes, key, true);
+ else if (related)
+ {
+ if (religion) // skip hostile and religion
+ msg = __try_exact_string(prefixes, key, true, false, true);
+ else // skip hostile and related
+ msg = __try_exact_string(prefixes, key, true, true);
+ }
+ else if (religion) // skip hostile, related and religion
+ msg = __try_exact_string(prefixes, key, true, true, true);
+ // 50% use non-verbal monster speech,
+ // 50% try for more general silenced monster message instead
+ else if (silenced && coinflip()) // skip all
+ msg = __try_exact_string(prefixes, key, true, true, true, true);
+ }
+ return msg;
+}
+
+static bool _invalid_msg(const std::string &msg, bool no_player, bool no_foe,
+ bool no_foe_name, bool no_god, bool unseen)
+{
+ if (no_player
+ && (msg.find("@player") != std::string::npos
+ || msg.find("@Player") != std::string::npos
+ || msg.find(":You") != std::string::npos))
+ {
+ return (true);
+ }
+
+ if (no_player)
+ {
+ std::vector<std::string> lines = split_string("\n", msg);
+ for (unsigned int i = 0; i < lines.size(); i++)
+ {
+ if (starts_with(lines[i], "You")
+ || ends_with(lines[i], "you."))
+ {
+ return (true);
+ }
+ }
+ }
+
+ if (no_foe && (msg.find("@foe") != std::string::npos
+ || msg.find("@Foe") != std::string::npos
+ || msg.find("foe@") != std::string::npos
+ || msg.find("@species") != std::string::npos))
+ {
+ return (true);
+ }
+
+ if (no_god && (msg.find("_god@") != std::string::npos
+ || msg.find("@god_") != std::string::npos))
+ {
+ return (true);
+ }
+
+ if (no_foe_name && msg.find("@foe_name@") != std::string::npos)
+ return (true);
+
+ if (unseen && msg.find("VISUAL") != std::string::npos)
+ return (true);
+
+ return (false);
+}
+
+static std::string _try_exact_string(const std::vector<std::string> &prefixes,
+ const std::string &key,
+ bool no_player, bool no_foe,
+ bool no_foe_name, bool no_god,
+ bool unseen,
+ bool ignore_hostile = false,
+ bool ignore_related = false,
+ bool ignore_religion = false,
+ bool ignore_silenced = false)
+{
+ std::string msg;
+ for (int tries = 0; tries < 10; tries++)
+ {
+ msg =
+ __try_exact_string(prefixes, key, ignore_hostile, ignore_related,
+ ignore_religion, ignore_silenced);
+
+ // If the first message was non-empty and discarded then discard
+ // all subsequent empty messages, so as to not replace an
+ // invalid non-empty message with an empty one.
+ if (msg.empty())
+ {
+ if (tries == 0)
+ return (msg);
+ else
+ {
+ tries--;
+ continue;
+ }
+ }
+
+ if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen))
+ {
+ msg = "";
+ continue;
+ }
+ break;
+ }
+
+ return (msg);
+}
+
+static std::string __get_speak_string(const std::vector<std::string> &prefixes,
+ const std::string &key,
+ const monsters *monster,
+ bool no_player, bool no_foe,
+ bool no_foe_name, bool no_god,
+ bool unseen)
+{
+ std::string msg = _try_exact_string(prefixes, key, no_player, no_foe,
+ no_foe_name, no_god, unseen);
+
+ if (!msg.empty())
+ return msg;
+
+ // Combinations of prefixes by threes
+ const int size = prefixes.size();
+ std::string prefix = "";
+ if (size >= 3)
+ {
+ for (int i = 0; i < (size - 2); i++)
+ for (int j = i + 1; j < (size - 1); j++)
+ for (int k = j + 1; k < size; k++)
+ {
+ prefix = prefixes[i] + " ";
+ prefix += prefixes[j] + " ";
+ prefix += prefixes[k] + " ";
+
+ msg = getSpeakString("default " + prefix + key);
+
+ if (!msg.empty())
+ return msg;
+ }
+ }
+
+ // Combinations of prefixes by twos
+ if (size >= 2)
+ {
+ for (int i = 0; i < (size - 1); i++)
+ for (int j = i + 1; j < size; j++)
+ {
+ prefix = prefixes[i] + " ";
+ prefix += prefixes[j] + " ";
+
+ msg = getSpeakString("default " + prefix + key);
+
+ if (!msg.empty())
+ return msg;
+ }
+ }
+
+ // Prefixes singly
+ if (size >= 1)
+ {
+ for (int i = 0; i < size; i++)
+ {
+ prefix = prefixes[i] + " ";
+
+ msg = getSpeakString("default " + prefix + key);
+
+ if (!msg.empty())
+ return msg;
+ }
+ }
+
+ // No prefixes
+ msg = getSpeakString("default " + key);
+
+ return msg;
+}
+
+static std::string _get_speak_string(const std::vector<std::string> &prefixes,
+ std::string key,
+ const monsters *monster,
+ bool no_player, bool no_foe,
+ bool no_foe_name, bool no_god,
+ bool unseen)
+{
+ int duration = 1;
+ if (monster->hit_points <= 0)
+ key += " killed";
+ else if ((monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS)
+ key += " banished";
+ else if (monster->is_summoned(&duration) && duration <= 0)
+ key += " unsummoned";
+
+ std::string msg;
+ for (int tries = 0; tries < 10; tries++)
+ {
+ msg =
+ __get_speak_string(prefixes, key, monster, no_player, no_foe,
+ no_foe_name, no_god, unseen);
+
+ // If the first message was non-empty and discarded then discard
+ // all subsequent empty messages, so as to not replace an
+ // invalid non-empty message with an empty one.
+ if (msg.empty())
+ {
+ if (tries == 0)
+ return (msg);
+ else
+ {
+ tries--;
+ continue;
+ }
+ }
+
+ if (_invalid_msg(msg, no_player, no_foe, no_foe_name, no_god, unseen))
+ {
+ msg = "";
+ continue;
+ }
+
+ break;
+ }
+
+ return (msg);
+}
+
+// Player ghosts with different classes can potentially speak different
+// things.
+static std::string _player_ghost_speak_str(const monsters *monster,
+ const std::vector<std::string> prefixes)
+{
+ const ghost_demon &ghost = *(monster->ghost);
+ std::string ghost_class = get_class_name(ghost.job);
+
+ std::string prefix = "";
+ for (int i = 0, size = prefixes.size(); i < size; i++)
+ {
+ prefix += prefixes[i];
+ prefix += " ";
+ }
+
+ // first try together with class name
+ std::string msg = getSpeakString(prefix + ghost_class + " player ghost");
+
+ // else try without class name
+ if (msg.empty() || msg == "__NEXT")
+ msg = getSpeakString(prefix + "player ghost");
+
+ return msg;
+}
+
+// If the monster was originally a unique which has been polymorphed into
+// a non-unique, is its current monter type capable of using its old
+// speech?
+static bool _polyd_can_speak(const monsters* monster)
+{
+ // Priest and wizard monsters can always speak.
+ if (monster->is_priest() || monster->is_actual_spellcaster())
+ return (true);
+
+ // Silent or non-sentient monsters can't use the original speech.
+ if (mons_intel(monster) < I_NORMAL
+ || mons_shouts(monster->type) == S_SILENT)
+ {
+ return (false);
+ }
+
+ // Does it have the proper vocal equipment?
+ const mon_body_shape shape = get_mon_shape(monster);
+ return (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA);
+}
+
+// Returns true if the monster did speak, false otherwise.
+// Maybe monsters will speak!
+void maybe_mons_speaks (monsters *monster)
+{
+#define MON_SPEAK_CHANCE 21
+
+ if (monster->is_patrolling() || mons_is_wandering(monster)
+ || monster->attitude == ATT_NEUTRAL)
+ {
+ // Very fast wandering/patrolling monsters might, in one monster turn,
+ // move into the player's LOS and then back out (or the player
+ // might move into their LOS and the monster move back out before
+ // the player's view has a chance to update) so prevent them
+ // from speaking.
+ ;
+ }
+ else if ((mons_class_flag(monster->type, M_SPEAKS)
+ || !monster->mname.empty())
+ && one_chance_in(MON_SPEAK_CHANCE))
+ {
+ mons_speaks(monster);
+ }
+ else if (monster->type == MONS_CRAZY_YIUF
+ && one_chance_in(MON_SPEAK_CHANCE / 3))
+ {
+ // Yiuf gets an extra chance to speak!
+ mons_speaks(monster);
+ }
+ else if (get_mon_shape(monster) >= MON_SHAPE_QUADRUPED)
+ {
+ // Non-humanoid-ish monsters have a low chance of speaking
+ // without the M_SPEAKS flag, to give the dungeon some
+ // atmosphere/flavour.
+ int chance = MON_SPEAK_CHANCE * 4;
+
+ // Band members are a lot less likely to speak, since there's
+ // a lot of them.
+ if (testbits(monster->flags, MF_BAND_MEMBER))
+ chance *= 10;
+
+ // However, confused and fleeing monsters are more interesting.
+ if (mons_is_fleeing(monster))
+ chance /= 2;
+ if (monster->has_ench(ENCH_CONFUSION))
+ chance /= 2;
+
+ if (one_chance_in(chance))
+ mons_speaks(monster);
+ }
+ // Okay then, don't speak.
+}
+
+
+// Returns true if something is said.
+bool mons_speaks(monsters *monster)
+{
+ ASSERT(!invalid_monster_type(monster->type));
+
+ // Monsters always talk on death, even if invisible/silenced/etc.
+ int duration = 1;
+ const bool force_speak = !monster->alive()
+ || (monster->flags & MF_BANISHED) && you.level_type != LEVEL_ABYSS
+ || (monster->is_summoned(&duration) && duration <= 0)
+ || crawl_state.prev_cmd == CMD_LOOK_AROUND; // Wizard testing
+
+ const bool unseen = !you.can_see(monster);
+ const bool confused = monster->confused();
+
+ if (!force_speak)
+ {
+ // Invisible monster tries to remain unnoticed. Unless they're
+ // confused, since then they're too confused to realise they
+ // should stay silent, but only if the player can see them, so as
+ // to not have to deal with cases of speaking monsters which the
+ // player can't see.
+ if (unseen && !confused)
+ return (false);
+
+ // Silenced monsters only "speak" 1/3 as often as non-silenced,
+ // unless they're normally silent (S_SILENT). Use
+ // get_monster_data(monster->type) to bypass mon_shouts()
+ // replacing S_RANDOM with a random value.
+ if (silenced(monster->pos())
+ && get_monster_data(monster->type)->shouts != S_SILENT)
+ {
+ if (!one_chance_in(3))
+ return (false);
+ }
+
+ // Berserk monsters just want your hide.
+ if (monster->berserk())
+ return (false);
+
+ // Monsters in a battle frenzy are likewise occupied.
+ if (monster->has_ench(ENCH_BATTLE_FRENZY) && !one_chance_in(3))
+ return (false);
+
+ // Charmed monsters aren't too expressive.
+ if (monster->has_ench(ENCH_CHARM) && !one_chance_in(3))
+ return (false);
+ }
+
+ std::vector<std::string> prefixes;
+ if (monster->neutral())
+ {
+ if (!force_speak && coinflip()) // Neutrals speak half as often.
+ return (false);
+
+ prefixes.push_back("neutral");
+ }
+ else if (monster->friendly() && !crawl_state.arena)
+ prefixes.push_back("friendly");
+ else
+ prefixes.push_back("hostile");
+
+ if (mons_is_fleeing(monster))
+ prefixes.push_back("fleeing");
+
+ bool silence = silenced(you.pos());
+ if (silenced(monster->pos()))
+ {
+ silence = true;
+ prefixes.push_back("silenced");
+ }
+
+ if (confused)
+ prefixes.push_back("confused");
+
+ const actor* foe = (!crawl_state.arena && monster->wont_attack()
+ && invalid_monster_index(monster->foe)) ?
+ &you : monster->get_foe();
+ const monsters* m_foe = (foe && foe->atype() == ACT_MONSTER) ?
+ dynamic_cast<const monsters*>(foe) : NULL;
+
+ // animals only look at the current player form, smart monsters at the
+ // actual player genus
+ if (!foe || foe->atype() == ACT_PLAYER)
+ {
+ if (is_player_same_species(monster->type,
+ mons_intel(monster) <= I_ANIMAL))
+ {
+ prefixes.push_back("related"); // maybe overkill for Beogh?
+ }
+ }
+ else
+ {
+ if (mons_genus(monster->mons_species()) ==
+ mons_genus(foe->mons_species()))
+ {
+ prefixes.push_back("related");
+ }
+ }
+
+ const god_type god = foe ? foe->deity() :
+ crawl_state.arena ? GOD_NO_GOD :
+ you.religion;
+
+ // Add Beogh to list of prefixes for orcs (hostile and friendly) if you
+ // worship Beogh. (This assumes your being a Hill Orc, so might have odd
+ // results in wizard mode.) Don't count charmed or summoned orcs.
+ if (you.religion == GOD_BEOGH && mons_genus(monster->type) == MONS_ORC
+ && !monster->has_ench(ENCH_CHARM) && !monster->is_summoned())
+ {
+ if (monster->god == GOD_BEOGH)
+ prefixes.push_back("beogh");
+ else
+ prefixes.push_back("unbeliever");
+ }
+ else
+ {
+ if (is_good_god(god))
+ prefixes.push_back("good god");
+ else if (is_evil_god(god))
+ prefixes.push_back("evil god");
+ }
+
+#ifdef DEBUG_MONSPEAK
+ {
+ std::string prefix;
+ const int size = prefixes.size();
+ for (int i = 0; i < size; i++)
+ {
+ prefix += prefixes[i];
+ prefix += " ";
+ }
+ mprf(MSGCH_DIAGNOSTICS, "monster speech lookup for %s: prefix = %s",
+ monster->name(DESC_PLAIN).c_str(), prefix.c_str());
+ }
+#endif
+
+ const bool no_foe = (foe == NULL);
+ const bool no_player = crawl_state.arena
+ || (!monster->wont_attack()
+ && (!foe || foe->atype() != ACT_PLAYER));
+ const bool mon_foe = (m_foe != NULL);
+ const bool no_god = no_foe || (mon_foe && foe->deity() == GOD_NO_GOD);
+ const bool named_foe = !no_foe
+ && (!mon_foe || (m_foe->is_named()
+ && m_foe->type != MONS_ROYAL_JELLY));
+ const bool no_foe_name = !named_foe
+ || (mon_foe && (m_foe->flags & MF_NAME_MASK));
+
+ std::string msg;
+
+ // First, try its exact name.
+ if (monster->type == MONS_PLAYER_GHOST)
+ {
+ // Player ghosts are treated differently.
+ msg = _player_ghost_speak_str(monster, prefixes);
+ }
+ else if (monster->type == MONS_PANDEMONIUM_DEMON)
+ {
+ // Pandemonium demons have randomly generated names, so use
+ // "pandemonium lord" instead.
+ msg = _get_speak_string(prefixes, "pandemonium lord", monster,
+ no_player, no_foe, no_foe_name, no_god,
+ unseen);
+ }
+ else
+ {
+ if (!monster->mname.empty() && _polyd_can_speak(monster))
+ {
+ msg = _get_speak_string(prefixes, monster->name(DESC_PLAIN),
+ monster, no_player, no_foe, no_foe_name,
+ no_god, unseen);
+ }
+
+ if (msg.empty())
+ {
+ msg = _get_speak_string(prefixes, monster->base_name(DESC_PLAIN),
+ monster, no_player, no_foe, no_foe_name,
+ no_god, unseen);
+ }
+ }
+
+ // The exact name brought no results, try monster genus.
+ if ((msg.empty() || msg == "__NEXT")
+ && mons_genus(monster->type) != monster->type)
+ {
+ msg = _get_speak_string(prefixes,
+ mons_type_name(mons_genus(monster->type), DESC_PLAIN),
+ monster, no_player, no_foe, no_foe_name, no_god,
+ unseen);
+ }
+
+ // __NONE means to be silent, and __NEXT means to try the next,
+ // less exact method of describing the monster to find a speech
+ // string.
+
+ if (msg == "__NONE")
+ {
+#ifdef DEBUG_MONSPEAK
+ mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
+#endif
+ return (false);
+ }
+
+ // Now that we're not dealing with a specific monster name, include
+ // whether or not it can move in the prefix.
+ if (mons_is_stationary(monster))
+ prefixes.insert(prefixes.begin(), "stationary");
+
+ // Names for the exact monster name and its genus have failed,
+ // so try the monster's glyph/symbol.
+ if (msg.empty() || msg == "__NEXT")
+ {
+ std::string key = "'";
+
+ // Database keys are case-insensitve.
+ if (isupper(mons_char(monster->type)))
+ key += "cap-";
+
+ key += mons_char(monster->type);
+ key += "'";
+ msg = _get_speak_string(prefixes, key, monster, no_player, no_foe,
+ no_foe_name, no_god, unseen);
+ }
+
+ if (msg == "__NONE")
+ {
+#ifdef DEBUG_MONSPEAK
+ mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
+#endif
+ return (false);
+ }
+
+ // Monster symbol didn't work, try monster shape. Since we're
+ // dealing with just the monster shape, change the prefix to
+ // include info on if the monster's intelligence is at odds with
+ // its shape.
+ mon_body_shape shape = get_mon_shape(monster);
+ mon_intel_type intel = mons_intel(monster);
+ if (shape >= MON_SHAPE_HUMANOID && shape <= MON_SHAPE_NAGA
+ && intel < I_NORMAL)
+ {
+ prefixes.insert(prefixes.begin(), "stupid");
+ }
+ else if (shape >= MON_SHAPE_QUADRUPED && shape <= MON_SHAPE_FISH)
+ {
+ if (mons_char(monster->type) == 'w')
+ {
+ if (intel > I_INSECT)
+ prefixes.insert(prefixes.begin(), "smart");
+ else if (intel < I_INSECT)
+ prefixes.insert(prefixes.begin(), "stupid");
+ }
+ else
+ {
+ if (intel > I_ANIMAL)
+ prefixes.insert(prefixes.begin(), "smart");
+ else if (intel < I_ANIMAL)
+ prefixes.insert(prefixes.begin(), "stupid");
+ }
+ }
+ else if (shape >= MON_SHAPE_INSECT && shape <= MON_SHAPE_SNAIL)
+ {
+ if (intel > I_INSECT)
+ prefixes.insert(prefixes.begin(), "smart");
+ else if (intel < I_INSECT)
+ prefixes.insert(prefixes.begin(), "stupid");
+ }
+ else if (shape >= MON_SHAPE_PLANT && shape <= MON_SHAPE_BLOB
+ && intel > I_PLANT)
+ {
+ prefixes.insert(prefixes.begin(), "smart");
+ }
+
+ if (msg.empty() || msg == "__NEXT")
+ {
+ msg = _get_speak_string(prefixes, get_mon_shape_str(shape), monster,
+ no_player, no_foe, no_foe_name, no_god,
+ unseen);
+ }
+
+ if (msg == "__NONE")
+ {
+#ifdef DEBUG_MONSPEAK
+ mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
+#endif
+ return (false);
+ }
+
+ // If we failed to get a message with a winged or tailed humanoid,
+ // or a naga or centaur, try moving closer to plain humanoid.
+ if ((msg.empty() || msg == "__NEXT") && shape > MON_SHAPE_HUMANOID
+ && shape <= MON_SHAPE_NAGA)
+ {
+ // If a humanoid monster has both wings and a tail, try removing
+ // one and then the other to see if we get any results.
+ if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED)
+ {
+ shape = MON_SHAPE_HUMANOID_TAILED;
+ msg = _get_speak_string(prefixes, get_mon_shape_str(shape),
+ monster, no_player, no_foe, no_foe_name,
+ no_god, unseen);
+
+ // Only be silent if both tailed and winged return __NONE.
+ if (msg.empty() || msg == "__NONE" || msg == "__NEXT")
+ {
+ shape = MON_SHAPE_HUMANOID_WINGED;
+ std::string msg2;
+ msg2 = _get_speak_string(prefixes, get_mon_shape_str(shape),
+ monster, no_player, no_foe,
+ no_foe_name, no_god, unseen);
+
+ if (msg == "__NONE" && msg2 == "__NONE")
+ {
+#ifdef DEBUG_MONSPEAK
+ mpr("result: \"__NONE\"!", MSGCH_DIAGNOSTICS);
+#endif
+ return (false);
+ }
+
+ if (msg2 == "__NONE")
+ msg2 = "";
+
+ msg = msg2;
+ }
+ } // if (shape == MON_SHAPE_HUMANOID_WINGED_TAILED)
+ if (msg.empty() || msg == "__NONE" || msg == "__NEXT")
+ {
+ shape = MON_SHAPE_HUMANOID;
+ msg = _get_speak_string(prefixes, get_mon_shape_str(shape),
+ monster, no_player, no_foe, no_foe_name,
+ no_god, unseen);
+ }
+ }
+ if (msg.empty() || msg == "__NONE")
+ {
+#ifdef DEBUG_MONSPEAK
+ mprf(MSGCH_DIAGNOSTICS, "final result: %s!",
+ (msg.empty() ? "empty" : "\"__NONE\""));
+#endif
+ return (false);
+ }
+
+ if (msg == "__NEXT")
+ {
+ msg::streams(MSGCH_DIAGNOSTICS)
+ << "__NEXT used by shape-based speech string for monster '"
+ << monster->name(DESC_PLAIN) << "'" << std::endl;
+ return (false);
+ }
+
+ if (foe == NULL)
+ msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS");
+ else if (foe->atype() == ACT_MONSTER)
+ {
+ if (you.can_see(foe))
+ msg = replace_all(msg, "__YOU_RESIST", "@The_monster@ resists.");
+ else
+ msg = replace_all(msg, "__YOU_RESIST", "__NOTHING_HAPPENS");
+ }
+
+ return (mons_speaks_msg(monster, msg, MSGCH_TALK, silence));
+}
+
+bool mons_speaks_msg(monsters *monster, const std::string &msg,
+ const msg_channel_type def_chan, const bool silence)
+{
+ if (!mons_near(monster))
+ return (false);
+
+ mon_acting mact(monster);
+
+ // We have a speech string, now parse and act on it.
+ const std::string _msg = do_mon_str_replacements(msg, monster);
+ const std::vector<std::string> lines = split_string("\n", _msg);
+
+ bool noticed = false; // Any messages actually printed?
+
+ for (int i = 0, size = lines.size(); i < size; ++i)
+ {
+ std::string line = lines[i];
+
+ // This function is a little bit of a problem for the message
+ // channels since some of the messages it generates are "fake"
+ // warning to scare the player. In order to accomodate this
+ // intent, we're falsely categorizing various things in the
+ // function as spells and danger warning... everything else
+ // just goes into the talk channel -- bwr
+ // [jpeg] Added MSGCH_TALK_VISUAL for silent "chatter".
+ msg_channel_type msg_type = def_chan;
+
+ std::string param = "";
+ std::string::size_type pos = line.find(":");
+
+ if (pos != std::string::npos)
+ param = line.substr(0, pos);
+
+ if (!param.empty())
+ {
+ bool match = true;
+
+ if (param == "DANGER")
+ msg_type = MSGCH_DANGER;
+ else if (param == "WARN" && !silence || param == "VISUAL WARN")
+ msg_type = MSGCH_WARN;
+ else if (param == "SOUND")
+ msg_type = MSGCH_SOUND;
+ else if (param == "VISUAL")
+ msg_type = MSGCH_TALK_VISUAL;
+ else if (param == "SPELL" && !silence || param == "VISUAL SPELL")
+ {
+ msg_type = monster->friendly() ? MSGCH_FRIEND_SPELL
+ : MSGCH_MONSTER_SPELL;
+ }
+ else if (param == "ENCHANT" && !silence
+ || param == "VISUAL ENCHANT")
+ {
+ msg_type = monster->friendly() ? MSGCH_FRIEND_ENCHANT
+ : MSGCH_MONSTER_ENCHANT;
+ }
+ else if (param == "PLAIN")
+ msg_type = MSGCH_PLAIN;
+ else
+ match = false;
+
+ if (match)
+ line = line.substr(pos + 1);
+ }
+
+ const bool old_noticed = noticed;
+ noticed = true; // Only one case is different.
+
+ // Except for VISUAL, none of the above influence these.
+ if (line == "__YOU_RESIST" && (!silence || param == "VISUAL"))
+ canned_msg( MSG_YOU_RESIST );
+ else if (line == "__NOTHING_HAPPENS" && (!silence || param == "VISUAL"))
+ canned_msg( MSG_NOTHING_HAPPENS );
+ else if (line == "__MORE" && (!silence || param == "VISUAL"))
+ more();
+ else if (msg_type == MSGCH_TALK_VISUAL && !you.can_see(monster))
+ noticed = old_noticed;
+ else
+ {
+ if (you.can_see(monster))
+ handle_seen_interrupt(monster);
+ mpr(line.c_str(), msg_type);
+ }
+ }
+ return (noticed);
+}