summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/shout.cc
diff options
context:
space:
mode:
authorRobert Vollmert <rvollmert@gmx.net>2009-11-10 12:42:34 +0100
committerRobert Vollmert <rvollmert@gmx.net>2009-11-10 12:44:19 +0100
commit3977b123c4b05ea20323805fa901a213ecaeac3e (patch)
treea18491061f515ba43c35aaff1e51366cc5f7af3b /crawl-ref/source/shout.cc
parentba3b7dbaad47d6e41d3763ec7dd6215bd74cfb5f (diff)
downloadcrawl-ref-3977b123c4b05ea20323805fa901a213ecaeac3e.tar.gz
crawl-ref-3977b123c4b05ea20323805fa901a213ecaeac3e.zip
Split shouting and stealth code from view.cc.
Diffstat (limited to 'crawl-ref/source/shout.cc')
-rw-r--r--crawl-ref/source/shout.cc548
1 files changed, 548 insertions, 0 deletions
diff --git a/crawl-ref/source/shout.cc b/crawl-ref/source/shout.cc
new file mode 100644
index 0000000000..1ecc0600f5
--- /dev/null
+++ b/crawl-ref/source/shout.cc
@@ -0,0 +1,548 @@
+/*
+ * File: shout.cc
+ * Summary: Stealth, noise, shouting.
+ */
+
+#include "AppHdr.h"
+
+#include "shout.h"
+
+#include "coord.h"
+#include "database.h"
+#include "env.h"
+#include "ghost.h"
+#include "jobs.h"
+#include "message.h"
+#include "misc.h"
+#include "mon-behv.h"
+#include "monplace.h"
+#include "monster.h"
+#include "monstuff.h"
+#include "options.h"
+#include "player.h"
+#include "random.h"
+#include "skills.h"
+#include "state.h"
+#include "stuff.h"
+#include "tutorial.h"
+#include "view.h"
+
+#include <sstream>
+
+extern int stealth; // defined in acr.cc
+
+void handle_monster_shouts(monsters* monster, bool force)
+{
+ if (!force && x_chance_in_y(you.skills[SK_STEALTH], 30))
+ return;
+
+ // Friendly or neutral monsters don't shout.
+ if (!force && (monster->friendly() || monster->neutral()))
+ return;
+
+ // Get it once, since monster might be S_RANDOM, in which case
+ // mons_shouts() will return a different value every time.
+ // Demon lords will insult you as a greeting, but later we'll
+ // choose a random verb and loudness for them.
+ shout_type s_type = mons_shouts(monster->type, false);
+
+ // Silent monsters can give noiseless "visual shouts" if the
+ // player can see them, in which case silence isn't checked for.
+ if (s_type == S_SILENT && !monster->visible_to(&you)
+ || s_type != S_SILENT && !player_can_hear(monster->pos()))
+ {
+ return;
+ }
+
+ mon_acting mact(monster);
+
+ std::string default_msg_key = "";
+
+ switch (s_type)
+ {
+ case S_SILENT:
+ // No default message.
+ break;
+ case S_SHOUT:
+ default_msg_key = "__SHOUT";
+ break;
+ case S_BARK:
+ default_msg_key = "__BARK";
+ break;
+ case S_SHOUT2:
+ default_msg_key = "__TWO_SHOUTS";
+ break;
+ case S_ROAR:
+ default_msg_key = "__ROAR";
+ break;
+ case S_SCREAM:
+ default_msg_key = "__SCREAM";
+ break;
+ case S_BELLOW:
+ default_msg_key = "__BELLOW";
+ break;
+ case S_SCREECH:
+ default_msg_key = "__SCREECH";
+ break;
+ case S_BUZZ:
+ default_msg_key = "__BUZZ";
+ break;
+ case S_MOAN:
+ default_msg_key = "__MOAN";
+ break;
+ case S_GURGLE:
+ default_msg_key = "__GURGLE";
+ break;
+ case S_WHINE:
+ default_msg_key = "__WHINE";
+ break;
+ case S_CROAK:
+ default_msg_key = "__CROAK";
+ break;
+ case S_GROWL:
+ default_msg_key = "__GROWL";
+ break;
+ case S_HISS:
+ default_msg_key = "__HISS";
+ break;
+ case S_DEMON_TAUNT:
+ default_msg_key = "__DEMON_TAUNT";
+ break;
+ default:
+ default_msg_key = "__BUGGY"; // S_LOUD, S_VERY_SOFT, etc. (loudness)
+ }
+
+ // Now that we have the message key, get a random verb and noise level
+ // for pandemonium lords.
+ if (s_type == S_DEMON_TAUNT)
+ s_type = mons_shouts(monster->type, true);
+
+ std::string msg, suffix;
+ std::string key = mons_type_name(monster->type, DESC_PLAIN);
+
+ // Pandemonium demons have random names, so use "pandemonium lord"
+ if (monster->type == MONS_PANDEMONIUM_DEMON)
+ key = "pandemonium lord";
+ // Search for player ghost shout by the ghost's class.
+ else if (monster->type == MONS_PLAYER_GHOST)
+ {
+ const ghost_demon &ghost = *(monster->ghost);
+ std::string ghost_class = get_class_name(ghost.job);
+
+ key = ghost_class + " player ghost";
+
+ default_msg_key = "player ghost";
+ }
+
+ // Tries to find an entry for "name seen" or "name unseen",
+ // and if no such entry exists then looks simply for "name".
+ // We don't use "you.can_see(monster)" here since that would return
+ // false for submerged monsters, but submerged monsters will be forced
+ // to surface before they shout, thus removing that source of
+ // non-visibility.
+ if (mons_near(monster) && (!monster->invisible() || you.can_see_invisible()))
+ suffix = " seen";
+ else
+ suffix = " unseen";
+
+ msg = getShoutString(key, suffix);
+
+ if (msg == "__DEFAULT" || msg == "__NEXT")
+ msg = getShoutString(default_msg_key, suffix);
+ else if (msg.empty())
+ {
+ // NOTE: Use the hardcoded glyph rather than that returned
+ // by mons_char(), since the result of mons_char() can be
+ // changed by user settings.
+ char mchar = get_monster_data(monster->type)->showchar;
+
+ // See if there's a shout for all monsters using the
+ // same glyph/symbol
+ std::string glyph_key = "'";
+
+ // Database keys are case-insensitve.
+ if (isupper(mchar))
+ glyph_key += "cap-";
+
+ glyph_key += mchar;
+ glyph_key += "'";
+ msg = getShoutString(glyph_key, suffix);
+
+ if (msg.empty() || msg == "__DEFAULT")
+ msg = getShoutString(default_msg_key, suffix);
+ }
+
+ if (default_msg_key == "__BUGGY")
+ {
+ msg::streams(MSGCH_SOUND) << "You hear something buggy!"
+ << std::endl;
+ }
+ else if (s_type == S_SILENT && (msg.empty() || msg == "__NONE"))
+ {
+ ; // No "visual shout" defined for silent monster, do nothing.
+ }
+ else if (msg.empty()) // Still nothing found?
+ {
+ msg::streams(MSGCH_DIAGNOSTICS)
+ << "No shout entry for default shout type '"
+ << default_msg_key << "'" << std::endl;
+
+ msg::streams(MSGCH_SOUND) << "You hear something buggy!"
+ << std::endl;
+ }
+ else if (msg == "__NONE")
+ {
+ msg::streams(MSGCH_DIAGNOSTICS)
+ << "__NONE returned as shout for non-silent monster '"
+ << default_msg_key << "'" << std::endl;
+ msg::streams(MSGCH_SOUND) << "You hear something buggy!"
+ << std::endl;
+ }
+ else
+ {
+ msg_channel_type channel = MSGCH_TALK;
+
+ std::string param = "";
+ std::string::size_type pos = msg.find(":");
+
+ if (pos != std::string::npos)
+ {
+ param = msg.substr(0, pos);
+ msg = msg.substr(pos + 1);
+ }
+
+ if (s_type == S_SILENT || param == "VISUAL")
+ channel = MSGCH_TALK_VISUAL;
+ else if (param == "SOUND")
+ channel = MSGCH_SOUND;
+
+ // Monster must come up from being submerged if it wants to shout.
+ if (monster->submerged())
+ {
+ if (!monster->del_ench(ENCH_SUBMERGED))
+ {
+ // Couldn't unsubmerge.
+ return;
+ }
+
+ if (you.can_see(monster))
+ {
+ if (monster->type == MONS_AIR_ELEMENTAL)
+ monster->seen_context = "thin air";
+ else if (monster->type == MONS_TRAPDOOR_SPIDER)
+ monster->seen_context = "leaps out";
+ else if (!monster_habitable_grid(monster, DNGN_FLOOR))
+ monster->seen_context = "bursts forth shouting";
+ else
+ monster->seen_context = "surfaces";
+
+ // Give interrupt message before shout message.
+ handle_seen_interrupt(monster);
+ }
+ }
+
+ if (channel != MSGCH_TALK_VISUAL || you.can_see(monster))
+ {
+ msg = do_mon_str_replacements(msg, monster, s_type);
+ msg::streams(channel) << msg << std::endl;
+
+ // Otherwise it can move away with no feedback.
+ if (you.can_see(monster))
+ {
+ if (!(monster->flags & MF_WAS_IN_VIEW))
+ handle_seen_interrupt(monster);
+ seen_monster(monster);
+ }
+ }
+ }
+
+ const int noise_level = get_shout_noise_level(s_type);
+ const bool heard = noisy(noise_level, monster->pos(), monster->mindex());
+
+ if (Options.tutorial_left && (heard || you.can_see(monster)))
+ learned_something_new(TUT_MONSTER_SHOUT, monster->pos());
+}
+
+#ifdef WIZARD
+void force_monster_shout(monsters* monster)
+{
+ handle_monster_shouts(monster, true);
+}
+#endif
+
+
+bool check_awaken(monsters* monster)
+{
+ // Monsters put to sleep by ensorcelled hibernation will sleep
+ // at least one turn.
+ if (monster->has_ench(ENCH_SLEEPY))
+ return (false);
+
+ // Berserkers aren't really concerned about stealth.
+ if (you.berserk())
+ return (true);
+
+ // I assume that creatures who can sense invisible are very perceptive.
+ int mons_perc = 10 + (mons_intel(monster) * 4) + monster->hit_dice
+ + mons_sense_invis(monster) * 5;
+
+ bool unnatural_stealthy = false; // "stealthy" only because of invisibility?
+
+ // Critters that are wandering but still have MHITYOU as their foe are
+ // still actively on guard for the player, even if they can't see you.
+ // Give them a large bonus -- handle_behaviour() will nuke 'foe' after
+ // a while, removing this bonus.
+ if (mons_is_wandering(monster) && monster->foe == MHITYOU)
+ mons_perc += 15;
+
+ if (!you.visible_to(monster))
+ {
+ mons_perc -= 75;
+ unnatural_stealthy = true;
+ }
+
+ if (monster->asleep())
+ {
+ if (monster->holiness() == MH_NATURAL)
+ {
+ // Monster is "hibernating"... reduce chance of waking.
+ if (monster->has_ench(ENCH_SLEEP_WARY))
+ mons_perc -= 10;
+ }
+ else // unnatural creature
+ {
+ // Unnatural monsters don't actually "sleep", they just
+ // haven't noticed an intruder yet... we'll assume that
+ // they're diligently on guard.
+ mons_perc += 10;
+ }
+ }
+
+ // If you've been tagged with Corona or are Glowing, the glow
+ // makes you extremely unstealthy.
+ if (you.backlit() && you.visible_to(monster))
+ mons_perc += 50;
+
+ if (mons_perc < 0)
+ mons_perc = 0;
+
+ if (x_chance_in_y(mons_perc + 1, stealth))
+ return (true); // Oops, the monster wakes up!
+
+ // You didn't wake the monster!
+ if (player_light_armour(true)
+ && you.can_see(monster) // to avoid leaking information
+ && you.burden_state == BS_UNENCUMBERED
+ && !you.attribute[ATTR_SHADOWS]
+ && !monster->wont_attack()
+ && !mons_class_flag(monster->type, M_NO_EXP_GAIN)
+ // If invisible, training happens much more rarely.
+ && (!unnatural_stealthy && one_chance_in(25) || one_chance_in(100)))
+ {
+ exercise(SK_STEALTH, 1);
+ }
+
+ return (false);
+}
+
+// Noisy now has a messenging service for giving messages to the
+// player is appropriate.
+//
+// Returns true if the PC heard the noise.
+bool noisy(int loudness, const coord_def& where, const char *msg, int who,
+ bool mermaid)
+{
+ bool ret = false;
+
+ if (loudness <= 0)
+ return (false);
+
+ // If the origin is silenced there is no noise.
+ if (silenced(where))
+ return (false);
+
+ const int dist = loudness * loudness;
+ const int player_distance = distance( you.pos(), where );
+
+ // Message the player.
+ if (player_distance <= dist && player_can_hear( where ))
+ {
+ if (msg)
+ mpr( msg, MSGCH_SOUND );
+
+ you.check_awaken(dist - player_distance);
+
+ if (!mermaid)
+ you.beholders_check_noise(loudness);
+
+ ret = true;
+ }
+
+ for (int p = 0; p < MAX_MONSTERS; p++)
+ {
+ monsters* monster = &menv[p];
+
+ if (!monster->alive())
+ continue;
+
+ // Monsters arent' affected by their own noise. We don't check
+ // where == monster->pos() since it might be caused by the
+ // Projected Noise spell.
+ if (p == who)
+ continue;
+
+ if (distance(monster->pos(), where) <= dist
+ && !silenced(monster->pos()))
+ {
+ // If the noise came from the character, any nearby monster
+ // will be jumping on top of them.
+ if (where == you.pos())
+ behaviour_event( monster, ME_ALERT, MHITYOU, you.pos() );
+ else if (mermaid && mons_primary_habitat(monster) == HT_WATER
+ && !monster->friendly())
+ {
+ // Mermaids/sirens call (hostile) aquatic monsters.
+ behaviour_event( monster, ME_ALERT, MHITNOT, where );
+ }
+ else
+ behaviour_event( monster, ME_DISTURB, MHITNOT, where );
+ }
+ }
+
+ return (ret);
+}
+
+bool noisy(int loudness, const coord_def& where, int who,
+ bool mermaid)
+{
+ return noisy(loudness, where, NULL, who, mermaid);
+}
+
+static const char* _player_vampire_smells_blood(int dist)
+{
+ // non-thirsty vampires get no clear indication of how close the
+ // smell is
+ if (you.hunger_state >= HS_SATIATED)
+ return "";
+
+ if (dist < 16) // 4*4
+ return " near-by";
+
+ if (you.hunger_state <= HS_NEAR_STARVING && dist > get_los_radius_sq())
+ return " in the distance";
+
+ return "";
+}
+
+void blood_smell( int strength, const coord_def& where )
+{
+ monsters *monster = NULL;
+
+ const int range = strength * strength;
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS,
+ "blood stain at (%d, %d), range of smell = %d",
+ where.x, where.y, range);
+#endif
+
+ // Of the player species, only Vampires can smell blood.
+ if (you.species == SP_VAMPIRE)
+ {
+ // Whether they actually do so, depends on their hunger state.
+ int vamp_strength = strength - 2 * (you.hunger_state - 1);
+ if (vamp_strength > 0)
+ {
+ int vamp_range = vamp_strength * vamp_strength;
+
+ const int player_distance = distance( you.pos(), where );
+
+ if (player_distance <= vamp_range)
+ {
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS,
+ "Player smells blood, pos: (%d, %d), dist = %d)",
+ you.pos().x, you.pos().y, player_distance);
+#endif
+ you.check_awaken(range - player_distance);
+ // Don't message if you can see the square.
+ if (!you.see_cell(where))
+ {
+ mprf("You smell fresh blood%s.",
+ _player_vampire_smells_blood(player_distance));
+ }
+ }
+ }
+ }
+
+ for (int p = 0; p < MAX_MONSTERS; p++)
+ {
+ monster = &menv[p];
+
+ if (monster->type < 0)
+ continue;
+
+ if (!mons_class_flag(monster->type, M_BLOOD_SCENT))
+ continue;
+
+ if (distance(monster->pos(), where) <= range)
+ {
+ // Let sleeping hounds lie.
+ if (monster->asleep()
+ && mons_species(monster->type) != MONS_VAMPIRE
+ && monster->type != MONS_SHARK)
+ {
+ // 33% chance of sleeping on
+ // 33% of being disturbed (start BEH_WANDER)
+ // 33% of being alerted (start BEH_SEEK)
+ if (!one_chance_in(3))
+ {
+ if (coinflip())
+ {
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS, "disturbing %s (%d, %d)",
+ monster->name(DESC_PLAIN).c_str(),
+ monster->pos().x, monster->pos().y);
+#endif
+ behaviour_event(monster, ME_DISTURB, MHITNOT, where);
+ }
+ continue;
+ }
+ }
+#ifdef DEBUG_DIAGNOSTICS
+ mprf(MSGCH_DIAGNOSTICS, "alerting %s (%d, %d)",
+ monster->name(DESC_PLAIN).c_str(),
+ monster->pos().x, monster->pos().y);
+#endif
+ behaviour_event( monster, ME_ALERT, MHITNOT, where );
+
+ if (monster->type == MONS_SHARK)
+ {
+ // Sharks go into a battle frenzy if they smell blood.
+ monster_pathfind mp;
+ if (mp.init_pathfind(monster, where))
+ {
+ mon_enchant ench = monster->get_ench(ENCH_BATTLE_FRENZY);
+ const int dist = 15 - (monster->pos() - where).rdist();
+ const int dur = random_range(dist, dist*2)
+ * speed_to_duration(monster->speed);
+
+ if (ench.ench != ENCH_NONE)
+ {
+ int level = ench.degree;
+ if (level < 4 && one_chance_in(2*level))
+ ench.degree++;
+ ench.duration = std::max(ench.duration, dur);
+ monster->update_ench(ench);
+ }
+ else
+ {
+ monster->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, 1,
+ KC_OTHER, dur));
+ simple_monster_message(monster, " is consumed with "
+ "blood-lust!");
+ }
+ }
+ }
+ }
+ }
+}