diff options
author | Robert Vollmert <rvollmert@gmx.net> | 2009-11-10 12:42:34 +0100 |
---|---|---|
committer | Robert Vollmert <rvollmert@gmx.net> | 2009-11-10 12:44:19 +0100 |
commit | 3977b123c4b05ea20323805fa901a213ecaeac3e (patch) | |
tree | a18491061f515ba43c35aaff1e51366cc5f7af3b /crawl-ref/source/shout.cc | |
parent | ba3b7dbaad47d6e41d3763ec7dd6215bd74cfb5f (diff) | |
download | crawl-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.cc | 548 |
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!"); + } + } + } + } + } +} |