1 /**
2  * @file
3  * @brief Stealth, noise, shouting.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "shout.h"
9 
10 #include <sstream>
11 
12 #include "act-iter.h"
13 #include "areas.h"
14 #include "artefact.h"
15 #include "branch.h"
16 #include "database.h"
17 #include "directn.h"
18 #include "english.h"
19 #include "env.h"
20 #include "exercise.h"
21 #include "ghost.h"
22 #include "hints.h"
23 #include "item-status-flag-type.h"
24 #include "jobs.h"
25 #include "libutil.h"
26 #include "macro.h"
27 #include "message.h"
28 #include "mon-behv.h"
29 #include "mon-place.h"
30 #include "mon-poly.h"
31 #include "prompt.h"
32 #include "religion.h"
33 #include "state.h"
34 #include "stringutil.h"
35 #include "terrain.h"
36 #include "view.h"
37 #include "viewchar.h"
38 
39 static noise_grid _noise_grid;
40 static void _actor_apply_noise(actor *act,
41                                const coord_def &apparent_source,
42                                int noise_intensity_millis);
43 
44 /// By default, what databse lookup key corresponds to each shout type?
45 static const map<shout_type, string> default_msg_keys = {
46     { S_SILENT,         "" },
47     { S_SHOUT,          "__SHOUT" },
48     { S_BARK,           "__BARK" },
49     { S_HOWL,           "__HOWL" },
50     { S_SHOUT2,         "__TWO_SHOUTS" },
51     { S_ROAR,           "__ROAR" },
52     { S_SCREAM,         "__SCREAM" },
53     { S_BELLOW,         "__BELLOW" },
54     { S_BLEAT,          "__BLEAT" },
55     { S_TRUMPET,        "__TRUMPET" },
56     { S_SCREECH,        "__SCREECH" },
57     { S_BUZZ,           "__BUZZ" },
58     { S_MOAN,           "__MOAN" },
59     { S_GURGLE,         "__GURGLE" },
60     { S_CROAK,          "__CROAK" },
61     { S_GROWL,          "__GROWL" },
62     { S_HISS,           "__HISS" },
63     { S_DEMON_TAUNT,    "__DEMON_TAUNT" },
64     { S_CHERUB,         "__CHERUB" },
65     { S_SQUEAL,         "__SQUEAL" },
66     { S_LOUD_ROAR,      "__LOUD_ROAR" },
67 };
68 
69 /**
70  * What's the appropriate DB lookup key for a given monster's shouts?
71  *
72  * @param mons      The monster in question.
73  * @return          A name for the monster; e.g. "orc", "Kirke", "pandemonium
74  *                  lord", "Fire Elementalist player ghost".
75  */
_shout_key(const monster & mons)76 static string _shout_key(const monster &mons)
77 {
78     // Pandemonium demons have random names, so use "pandemonium lord"
79     if (mons.type == MONS_PANDEMONIUM_LORD)
80         return "pandemonium lord";
81 
82     // Search for player ghost shout by the ghost's job.
83     if (mons.type == MONS_PLAYER_GHOST)
84     {
85         const ghost_demon &ghost = *(mons.ghost);
86         const string ghost_job         = get_job_name(ghost.job);
87         return ghost_job + " player ghost";
88     }
89 
90     // everything else just goes by name.
91     return mons_type_name(mons.type, DESC_PLAIN);
92 }
93 
94 /**
95  * Let a monster consider whether or not it wants to shout, and, if so, shout.
96  *
97  * @param mon       The monster in question.
98  */
monster_consider_shouting(monster & mon)99 void monster_consider_shouting(monster &mon)
100 {
101     if (one_chance_in(5))
102         return;
103 
104     // Friendly or neutral monsters don't shout.
105     // XXX: redundant with one of two uses (mon-behv.cc)
106     if (mon.friendly() || mon.neutral())
107         return;
108 
109     monster_attempt_shout(mon);
110 }
111 
112 /**
113  * If it's at all possible for a monster to shout, have it do so.
114  *
115  * @param mon       The monster in question.
116  * @return          Whether a shout occurred.
117  */
monster_attempt_shout(monster & mon)118 bool monster_attempt_shout(monster &mon)
119 {
120     if (mon.cannot_move() || mon.asleep() || mon.has_ench(ENCH_DUMB))
121         return false;
122 
123     const shout_type shout = mons_shouts(mon.type, false);
124 
125     // Silent monsters can give noiseless "visual shouts" if the
126     // player can see them, in which case silence isn't checked for.
127     // Muted & silenced monsters can't shout at all.
128     if (shout == S_SILENT && !mon.visible_to(&you)
129         || shout != S_SILENT && mon.is_silenced())
130     {
131         return false;
132     }
133 
134     monster_shout(&mon, shout);
135     return true;
136 }
137 
138 
139 /**
140  * Have a monster perform a specific shout.
141  *
142  * @param mons      The monster in question.
143  *                  TODO: use a reference, not a pointer
144  * @param shout    The shout_type to use.
145  */
monster_shout(monster * mons,int shout)146 void monster_shout(monster* mons, int shout)
147 {
148     shout_type s_type = static_cast<shout_type>(shout);
149     mon_acting mact(mons);
150 
151     // less specific, more specific.
152     const string default_msg_key
153         = mons->type == MONS_PLAYER_GHOST ?
154                  "player ghost" :
155                  lookup(default_msg_keys, s_type, "__BUGGY");
156     const string key = _shout_key(*mons);
157 
158     // Now that we have the message key, get a random verb and noise level
159     // for pandemonium lords.
160     if (s_type == S_DEMON_TAUNT)
161         s_type = mons_shouts(mons->type, true);
162 
163     // Tries to find an entry for "name seen" or "name unseen",
164     // and if no such entry exists then looks simply for "name".
165     const string suffix = you.can_see(*mons) ? " seen" : " unseen";
166     string message = getShoutString(key, suffix);
167 
168     if (message == "__DEFAULT" || message == "__NEXT")
169         message = getShoutString(default_msg_key, suffix);
170     else if (message.empty())
171     {
172         char mchar = mons_base_char(mons->type);
173 
174         // See if there's a shout for all monsters using the
175         // same glyph/symbol
176         string glyph_key = "'";
177 
178         // Database keys are case-insensitve.
179         if (isaupper(mchar))
180             glyph_key += "cap-";
181 
182         glyph_key += mchar;
183         glyph_key += "'";
184         message = getShoutString(glyph_key, suffix);
185 
186         if (message.empty() || message == "__DEFAULT")
187             message = getShoutString(default_msg_key, suffix);
188     }
189 
190     if (default_msg_key == "__BUGGY")
191     {
192         msg::streams(MSGCH_SOUND) << "You hear something buggy!"
193                                   << endl;
194     }
195     else if (s_type == S_SILENT && (message.empty() || message == "__NONE"))
196         ; // No "visual shout" defined for silent monster, do nothing.
197     else if (message.empty()) // Still nothing found?
198     {
199         msg::streams(MSGCH_DIAGNOSTICS)
200             << "No shout entry for default shout type '"
201             << default_msg_key << "'" << endl;
202 
203         msg::streams(MSGCH_SOUND) << "You hear something buggy!"
204                                   << endl;
205     }
206     else if (message == "__NONE")
207     {
208         msg::streams(MSGCH_DIAGNOSTICS)
209             << "__NONE returned as shout for non-silent monster '"
210             << default_msg_key << "'" << endl;
211         msg::streams(MSGCH_SOUND) << "You hear something buggy!"
212                                   << endl;
213     }
214     else if (s_type == S_SILENT || !silenced(you.pos()))
215     {
216         msg_channel_type channel = MSGCH_TALK;
217         if (s_type == S_SILENT)
218             channel = MSGCH_TALK_VISUAL;
219 
220         strip_channel_prefix(message, channel);
221 
222         // Monster must come up from being submerged if it wants to shout.
223         // XXX: this code is probably unreachable now?
224         if (mons->submerged())
225         {
226             if (!mons->del_ench(ENCH_SUBMERGED))
227             {
228                 // Couldn't unsubmerge.
229                 return;
230             }
231 
232             if (you.can_see(*mons))
233             {
234                 mons->seen_context = SC_FISH_SURFACES;
235 
236                 // Give interrupt message before shout message.
237                 handle_seen_interrupt(mons);
238             }
239         }
240 
241         if (channel != MSGCH_TALK_VISUAL || you.can_see(*mons))
242         {
243             // Otherwise it can move away with no feedback.
244             if (you.can_see(*mons))
245             {
246                 if (!(mons->flags & MF_WAS_IN_VIEW))
247                     handle_seen_interrupt(mons);
248                 seen_monster(mons);
249             }
250 
251             message = do_mon_str_replacements(message, *mons, s_type);
252             msg::streams(channel) << message << endl;
253         }
254     }
255 
256     const int  noise_level = get_shout_noise_level(s_type);
257     const bool heard       = noisy(noise_level, mons->pos(), mons->mid);
258 
259     if (crawl_state.game_is_hints() && (heard || you.can_see(*mons)))
260         learned_something_new(HINT_MONSTER_SHOUT, mons->pos());
261 }
262 
check_awaken(monster * mons,int stealth)263 bool check_awaken(monster* mons, int stealth)
264 {
265     // Usually redundant because we iterate over player LOS,
266     // but e.g. for passive_t::xray_vision.
267     if (!mons->see_cell(you.pos()))
268         return false;
269 
270     // Monsters put to sleep by ensorcelled hibernation will sleep
271     // at least one turn.
272     if (mons_just_slept(*mons))
273         return false;
274 
275     // Berserkers aren't really concerned about stealth.
276     if (you.berserk())
277         return true;
278 
279     // If you've sacrificed stealth, you always alert monsters.
280     if (you.get_mutation_level(MUT_NO_STEALTH))
281         return true;
282 
283 
284     int mons_perc = 10 + (mons_intel(*mons) * 4) + mons->get_hit_dice();
285 
286     bool unnatural_stealthy = false; // "stealthy" only because of invisibility?
287 
288     // Critters that are wandering but still have MHITYOU as their foe are
289     // still actively on guard for the player, even if they can't see you.
290     // Give them a large bonus -- handle_behaviour() will nuke 'foe' after
291     // a while, removing this bonus.
292     if (mons_is_wandering(*mons) && mons->foe == MHITYOU)
293         mons_perc += 15;
294 
295     if (!you.visible_to(mons))
296     {
297         mons_perc -= 75;
298         unnatural_stealthy = true;
299     }
300 
301     if (mons->asleep())
302     {
303         if (mons->holiness() & MH_NATURAL)
304         {
305             // Monster is "hibernating"... reduce chance of waking.
306             if (mons->has_ench(ENCH_SLEEP_WARY))
307                 mons_perc -= 10;
308         }
309         else // unnatural creature
310         {
311             // Unnatural monsters don't actually "sleep", they just
312             // haven't noticed an intruder yet... we'll assume that
313             // they're diligently on guard.
314             mons_perc += 10;
315         }
316     }
317 
318     if (mons_perc < 4)
319         mons_perc = 4;
320 
321     if (x_chance_in_y(mons_perc + 1, stealth))
322         return true; // Oops, the monster wakes up!
323 
324     // You didn't wake the monster!
325     if (you.can_see(*mons) // to avoid leaking information
326         && !mons->wont_attack()
327         && !mons->neutral() // include pacified monsters
328         && mons_class_gives_xp(mons->type))
329     {
330         practise_sneaking(unnatural_stealthy);
331     }
332 
333     return false;
334 }
335 
item_noise(const item_def & item,actor & act,string msg,int loudness)336 void item_noise(const item_def &item, actor &act, string msg, int loudness)
337 {
338     // TODO: messaging for cases where act != you. (This doesn't come up right
339     // now.)
340     if (is_unrandom_artefact(item))
341     {
342         // "Your Singing Sword" sounds disrespectful
343         // (as if there could be more than one!)
344         msg = replace_all(msg, "@Your_weapon@", "@The_weapon@");
345         msg = replace_all(msg, "@your_weapon@", "@the_weapon@");
346     }
347 
348     // Set appropriate channel (will usually be TALK).
349     msg_channel_type channel = MSGCH_TALK;
350 
351     string param;
352     const string::size_type pos = msg.find(":");
353 
354     if (pos != string::npos)
355         param = msg.substr(0, pos);
356 
357     if (!param.empty())
358     {
359         bool match = true;
360 
361         if (param == "DANGER")
362             channel = MSGCH_DANGER;
363         else if (param == "WARN")
364             channel = MSGCH_WARN;
365         else if (param == "SOUND")
366             channel = MSGCH_SOUND;
367         else if (param == "PLAIN")
368             channel = MSGCH_PLAIN;
369         else if (param == "SPELL")
370             channel = MSGCH_FRIEND_SPELL;
371         else if (param == "ENCHANT")
372             channel = MSGCH_FRIEND_ENCHANT;
373         else if (param == "VISUAL")
374             channel = MSGCH_TALK_VISUAL;
375         else if (param != "TALK")
376             match = false;
377 
378         if (match)
379             msg = msg.substr(pos + 1);
380     }
381 
382     if (msg.empty()) // give default noises
383     {
384         channel = MSGCH_SOUND;
385         msg = "You hear a strange noise.";
386     }
387 
388     // Replace weapon references. Can't use DESC_THE because that includes
389     // pluses etc. and we want just the basename.
390     msg = replace_all(msg, "@The_weapon@", "The @weapon@");
391     msg = replace_all(msg, "@the_weapon@", "the @weapon@");
392     msg = replace_all(msg, "@Your_weapon@", "Your @weapon@");
393     msg = replace_all(msg, "@your_weapon@", "your @weapon@");
394     msg = replace_all(msg, "@weapon@", item.name(DESC_BASENAME));
395 
396     // replace references to player name and god
397     msg = replace_all(msg, "@player_name@", you.your_name);
398     msg = replace_all(msg, "@player_god@",
399                       you_worship(GOD_NO_GOD) ? "atheism"
400                       : god_name(you.religion, coinflip()));
401     msg = replace_all(msg, "@player_genus@",
402                 species::name(you.species, species::SPNAME_GENUS));
403     msg = replace_all(msg, "@a_player_genus@",
404                 article_a(species::name(you.species, species::SPNAME_GENUS)));
405     msg = replace_all(msg, "@player_genus_plural@",
406                 pluralise(species::name(you.species, species::SPNAME_GENUS)));
407 
408     msg = maybe_pick_random_substring(msg);
409     msg = maybe_capitalise_substring(msg);
410 
411     mprf(channel, "%s", msg.c_str());
412 
413     if (channel != MSGCH_TALK_VISUAL)
414         noisy(loudness, act.pos());
415 }
416 
417 // TODO: Let artefacts besides weapons generate noise.
noisy_equipment()418 void noisy_equipment()
419 {
420     if (silenced(you.pos()) || !one_chance_in(20))
421         return;
422 
423     string msg;
424 
425     const item_def* weapon = you.weapon();
426     if (!weapon)
427         return;
428 
429     if (is_unrandom_artefact(*weapon))
430     {
431         string name = weapon->name(DESC_PLAIN, false, true, false, false,
432                                    ISFLAG_IDENT_MASK);
433         msg = getSpeakString(name);
434         if (msg == "NONE")
435             return;
436     }
437 
438     if (msg.empty())
439         msg = getSpeakString("noisy weapon");
440 
441     item_noise(*weapon, you, msg, 20);
442 }
443 
444 // Berserking monsters cannot be ordered around.
_follows_orders(monster * mon)445 static bool _follows_orders(monster* mon)
446 {
447     return mon->friendly()
448            && mon->type != MONS_BALLISTOMYCETE_SPORE
449            && !mon->berserk_or_insane()
450            && !mons_is_conjured(mon->type)
451            && !mon->has_ench(ENCH_HAUNTING);
452 }
453 
454 // Sets foe target of friendly monsters.
455 // If allow_patrol is true, patrolling monsters get MHITNOT instead.
_set_friendly_foes(bool allow_patrol=false)456 static void _set_friendly_foes(bool allow_patrol = false)
457 {
458     for (monster_near_iterator mi(you.pos()); mi; ++mi)
459     {
460         if (!_follows_orders(*mi))
461             continue;
462         if (you.pet_target != MHITNOT && mi->behaviour == BEH_WITHDRAW)
463         {
464             mi->behaviour = BEH_SEEK;
465             mi->patrol_point = coord_def(0, 0);
466         }
467         mi->foe = (allow_patrol && mi->is_patrolling() ? int{MHITNOT}
468                                                        : you.pet_target);
469     }
470 }
471 
_set_allies_patrol_point(bool clear=false)472 static void _set_allies_patrol_point(bool clear = false)
473 {
474     for (monster_near_iterator mi(you.pos()); mi; ++mi)
475     {
476         if (!_follows_orders(*mi))
477             continue;
478         mi->patrol_point = (clear ? coord_def(0, 0) : mi->pos());
479         if (!clear)
480             mi->behaviour = BEH_WANDER;
481         else
482             mi->behaviour = BEH_SEEK;
483     }
484 }
485 
_set_allies_withdraw(const coord_def & target)486 static void _set_allies_withdraw(const coord_def &target)
487 {
488     coord_def delta = target - you.pos();
489     float mult = float(LOS_DEFAULT_RANGE * 2) / (float)max(abs(delta.x), abs(delta.y));
490     coord_def rally_point = clamp_in_bounds(coord_def(delta.x * mult, delta.y * mult) + you.pos());
491 
492     for (monster_near_iterator mi(you.pos()); mi; ++mi)
493     {
494         if (!_follows_orders(*mi))
495             continue;
496         if (mons_class_flag(mi->type, M_STATIONARY))
497             continue;
498         mi->behaviour = BEH_WITHDRAW;
499         mi->target = clamp_in_bounds(target);
500         mi->patrol_point = rally_point;
501         mi->foe = MHITNOT;
502 
503         mi->props.erase("last_pos");
504         mi->props.erase("idle_point");
505         mi->props.erase("idle_deadline");
506         mi->props.erase("blocked_deadline");
507     }
508 }
509 
510 /// Does the player have a 'previous target' to issue targeting orders at?
_can_target_prev()511 static bool _can_target_prev()
512 {
513     return !(you.prev_targ == MHITNOT || you.prev_targ == MHITYOU);
514 }
515 
516 /// Prompt the player to issue orders. Returns the key pressed.
_issue_orders_prompt()517 static int _issue_orders_prompt()
518 {
519     mprf(MSGCH_PROMPT, "What are your orders?");
520     if (!you.cannot_speak())
521     {
522         string cap_shout = you.shout_verb(false);
523         cap_shout[0] = toupper_safe(cap_shout[0]);
524         mprf(" t - %s!", cap_shout.c_str());
525     }
526 
527     if (!you.berserk() && !you.confused())
528     {
529         string previous;
530         if (_can_target_prev())
531         {
532             const monster* target = &env.mons[you.prev_targ];
533             if (target->alive() && you.can_see(*target))
534                 previous = "   p - Attack previous target.";
535         }
536 
537         mprf("Orders for allies: a - Attack new target.%s", previous.c_str());
538         mpr("                   r - Retreat!             s - Stop attacking.");
539         mpr("                   g - Guard the area.      f - Follow me.");
540     }
541     mpr(" Anything else - Cancel.");
542 
543     flush_prev_message(); // buffer doesn't get flushed otherwise
544 
545     const int keyn = get_ch();
546     clear_messages();
547     return keyn;
548 }
549 
550 /**
551  * Issue the order specified by the given key.
552  *
553  * @param keyn              The key the player just pressed.
554  * @param mons_targd[out]   Who the player's allies should be targetting as a
555  *                          result of this command.
556  * @return                  Whether a command actually executed (and the value
557  *                          of mons_targd should be used).
558  */
_issue_order(int keyn,int & mons_targd)559 static bool _issue_order(int keyn, int &mons_targd)
560 {
561     if (you.berserk() || you.confused())
562     {
563         canned_msg(MSG_OK);
564         return false;
565     }
566 
567     switch (keyn)
568     {
569         case 'f':
570         case 's':
571             mons_targd = MHITYOU;
572             if (keyn == 'f')
573             {
574                 // Don't reset patrol points for 'Stop fighting!'
575                 _set_allies_patrol_point(true);
576                 mpr("Follow me!");
577             }
578             else
579                 mpr("Stop fighting!");
580             break;
581 
582         case 'w':
583         case 'g':
584             mpr("Guard this area!");
585             mons_targd = MHITNOT;
586             _set_allies_patrol_point();
587             break;
588 
589         case 'p':
590 
591             if (_can_target_prev())
592             {
593                 mons_targd = you.prev_targ;
594                 break;
595             }
596 
597             // fall through
598         case 'a':
599             if (env.sanctuary_time > 0)
600             {
601                 if (!yesno("An ally attacking under your orders might violate "
602                            "sanctuary; order anyway?", false, 'n'))
603                 {
604                     canned_msg(MSG_OK);
605                     return false;
606                 }
607             }
608 
609         {
610             direction_chooser_args args;
611             args.restricts = DIR_TARGET;
612             args.mode = TARG_HOSTILE;
613             args.needs_path = false;
614             args.top_prompt = "Gang up on whom?";
615             dist targ;
616             direction(targ, args);
617 
618             if (targ.isCancel)
619             {
620                 canned_msg(MSG_OK);
621                 return false;
622             }
623 
624             bool cancel = !targ.isValid;
625             if (!cancel)
626             {
627                 const monster* m = monster_at(targ.target);
628                 cancel = (m == nullptr || !you.can_see(*m));
629                 if (!cancel)
630                     mons_targd = m->mindex();
631             }
632 
633             if (cancel)
634             {
635                 canned_msg(MSG_NOTHING_THERE);
636                 return false;
637             }
638         }
639             break;
640 
641         case 'r':
642         {
643             direction_chooser_args args;
644             args.restricts = DIR_TARGET;
645             args.mode = TARG_ANY;
646             args.needs_path = false;
647             args.top_prompt = "Retreat in which direction?";
648             dist targ;
649             direction(targ, args);
650 
651             if (targ.isCancel)
652             {
653                 canned_msg(MSG_OK);
654                 return false;
655             }
656 
657             if (targ.isValid)
658             {
659                 mpr("Fall back!");
660                 mons_targd = MHITNOT;
661             }
662 
663             _set_allies_withdraw(targ.target);
664         }
665             break;
666 
667         default:
668             canned_msg(MSG_OK);
669             return false;
670     }
671 
672     return true;
673 }
674 
675 /**
676  * Prompt the player to either change their allies' orders or to shout.
677  *
678  * XXX: it'd be nice if shouting was a separate command.
679  * XXX: this should maybe be in another file.
680  */
issue_orders()681 void issue_orders()
682 {
683     ASSERT(!crawl_state.game_is_arena());
684 
685     if (you.cannot_speak() && you.berserk())
686     {
687         mpr("You're too berserk to give orders, and you can't shout!");
688         return;
689     }
690 
691     const int keyn = _issue_orders_prompt();
692     if (keyn == '!' || keyn == 't') // '!' for [very] old keyset
693     {
694         yell();
695         you.turn_is_over = true;
696         return;
697     }
698 
699     int mons_targd = MHITNOT; // XXX: just use you.pet_target directly?
700     if (!_issue_order(keyn, mons_targd))
701         return;
702 
703     you.turn_is_over = true;
704     you.pet_target = mons_targd;
705     // Allow patrolling for "Stop fighting!" and "Wait here!"
706     _set_friendly_foes(keyn == 's' || keyn == 'w');
707 
708     if (mons_targd != MHITNOT && mons_targd != MHITYOU)
709         mpr("Attack!");
710 }
711 
712 /**
713  * Make the player yell, either at a monster or at nothing in particular.
714  *
715  * @mon     The monster to yell at; may be null.
716  */
yell(const actor * mon)717 void yell(const actor* mon)
718 {
719     ASSERT(!crawl_state.game_is_arena());
720 
721     const string shout_verb = you.shout_verb(mon != nullptr);
722     const int noise_level = you.shout_volume();
723 
724     if (you.cannot_speak())
725     {
726         if (mon)
727         {
728             if (you.paralysed() || you.duration[DUR_WATER_HOLD])
729             {
730                 mprf("You feel a strong urge to %s, but "
731                      "you are unable to make a sound!",
732                      shout_verb.c_str());
733             }
734             else
735             {
736                 mprf("You feel a %s rip itself from your throat, "
737                      "but you make no sound!",
738                      shout_verb.c_str());
739             }
740         }
741         else
742             mpr("You are unable to make a sound!");
743 
744         return;
745     }
746 
747     if (mon)
748     {
749         mprf("You %s%s at %s!",
750              shout_verb.c_str(),
751              you.duration[DUR_RECITE] ? " your recitation" : "",
752              mon->name(DESC_THE).c_str());
753     }
754     else
755     {
756         mprf(MSGCH_SOUND, "You %s%s!",
757              shout_verb.c_str(),
758              you.berserk() ? " wildly" : " for attention");
759     }
760 
761     noisy(noise_level, you.pos());
762 }
763 
apply_noises()764 void apply_noises()
765 {
766     // [ds] This copying isn't awesome, but we cannot otherwise handle
767     // the case where one set of noises wakes up monsters who then let
768     // out yips of their own, modifying _noise_grid while it is in the
769     // middle of propagate_noise().
770     if (_noise_grid.dirty())
771     {
772         noise_grid copy = _noise_grid;
773         // Reset the main grid.
774         _noise_grid.reset();
775         copy.propagate_noise();
776     }
777 }
778 
779 // noisy() has a messaging service for giving messages to the player
780 // as appropriate.
noisy(int original_loudness,const coord_def & where,const char * msg,mid_t who,bool fake_noise)781 bool noisy(int original_loudness, const coord_def& where,
782            const char *msg, mid_t who, bool fake_noise)
783 {
784     ASSERT_IN_BOUNDS(where);
785 
786     if (original_loudness <= 0)
787         return false;
788 
789     // high ambient noise makes sounds harder to hear
790     const int ambient = ambient_noise();
791     const int loudness =
792         ambient < 0 ? original_loudness + random2avg(abs(ambient), 3)
793                     : original_loudness - random2avg(abs(ambient), 3);
794 
795     const int adj_loudness = you.has_mutation(MUT_NOISE_DAMPENING)
796                 && you.see_cell(where) ? div_rand_round(loudness, 2) : loudness;
797 
798     dprf(DIAG_NOISE, "Noise %d (orig: %d; ambient: %d) at pos(%d,%d)",
799          adj_loudness, original_loudness, ambient, where.x, where.y);
800 
801     if (adj_loudness <= 0)
802         return false;
803 
804     // If the origin is silenced there is no noise, unless we're
805     // faking it.
806     if (silenced(where) && !fake_noise)
807         return false;
808 
809     // [ds] Reduce noise propagation for Sprint.
810     const int scaled_loudness =
811         crawl_state.game_is_sprint()? max(1, div_rand_round(adj_loudness, 2))
812                                     : adj_loudness;
813 
814     // The multiplier converts to milli-auns which are used internally
815     // by noise propagation.
816     const int multiplier = 1000;
817 
818     // Add +1 to scaled_loudness so that all squares adjacent to a
819     // sound of loudness 1 will hear the sound.
820     const string noise_msg(msg? msg : "");
821     _noise_grid.register_noise(
822         noise_t(where, noise_msg, (scaled_loudness + 1) * multiplier, who));
823 
824     // Some users of noisy() want an immediate answer to whether the
825     // player heard the noise. The deferred noise system also means
826     // that soft noises can be drowned out by loud noises. For both
827     // these reasons, use the simple old noise system to check if the
828     // player heard the noise:
829     const int dist = loudness;
830     const int player_distance = grid_distance(you.pos(), where);
831 
832     // Message the player.
833     if (player_distance <= dist && player_can_hear(where))
834     {
835         if (msg && !fake_noise)
836             mprf(MSGCH_SOUND, "%s", msg);
837         return true;
838     }
839     return false;
840 }
841 
noisy(int loudness,const coord_def & where,mid_t who)842 bool noisy(int loudness, const coord_def& where, mid_t who)
843 {
844     return noisy(loudness, where, nullptr, who);
845 }
846 
847 // This fakes noise even through silence.
fake_noisy(int loudness,const coord_def & where)848 bool fake_noisy(int loudness, const coord_def& where)
849 {
850     return noisy(loudness, where, nullptr, MID_NOBODY, true);
851 }
852 
853 //////////////////////////////////////////////////////////////////////////////
854 // noise machinery
855 
856 // Currently noise attenuation depends solely on the feature in question.
857 // Permarock walls are assumed to completely kill noise.
_noise_attenuation_millis(const coord_def & pos)858 static int _noise_attenuation_millis(const coord_def &pos)
859 {
860     const dungeon_feature_type feat = env.grid(pos);
861 
862     if (feat_is_permarock(feat))
863         return NOISE_ATTENUATION_COMPLETE;
864 
865     return BASE_NOISE_ATTENUATION_MILLIS *
866             (feat_is_wall(feat)        ? 12 :
867              feat_is_closed_door(feat) ?  8 :
868              feat_is_tree(feat)        ?  3 :
869              feat_is_statuelike(feat)  ?  2 :
870                                           1);
871 }
872 
noise_cell()873 noise_cell::noise_cell()
874     : neighbour_delta(0, 0), noise_id(-1), noise_intensity_millis(0),
875       noise_travel_distance(0)
876 {
877 }
878 
can_apply_noise(int _noise_intensity_millis) const879 bool noise_cell::can_apply_noise(int _noise_intensity_millis) const
880 {
881     return noise_intensity_millis < _noise_intensity_millis;
882 }
883 
apply_noise(int _noise_intensity_millis,int _noise_id,int _noise_travel_distance,const coord_def & _neighbour_delta)884 bool noise_cell::apply_noise(int _noise_intensity_millis,
885                              int _noise_id,
886                              int _noise_travel_distance,
887                              const coord_def &_neighbour_delta)
888 {
889     if (can_apply_noise(_noise_intensity_millis))
890     {
891         noise_id = _noise_id;
892         noise_intensity_millis = _noise_intensity_millis;
893         noise_travel_distance = _noise_travel_distance;
894         neighbour_delta = _neighbour_delta;
895         return true;
896     }
897     return false;
898 }
899 
turn_angle(const coord_def & next_delta) const900 int noise_cell::turn_angle(const coord_def &next_delta) const
901 {
902     if (neighbour_delta.origin())
903         return 0;
904 
905     // Going in reverse?
906     if (next_delta.x == -neighbour_delta.x
907         && next_delta.y == -neighbour_delta.y)
908     {
909         return 4;
910     }
911 
912     const int xdiff = abs(neighbour_delta.x - next_delta.x);
913     const int ydiff = abs(neighbour_delta.y - next_delta.y);
914     return xdiff + ydiff;
915 }
916 
noise_grid()917 noise_grid::noise_grid()
918     : cells(), noises(), affected_actor_count(0)
919 {
920 }
921 
reset()922 void noise_grid::reset()
923 {
924     cells.init(noise_cell());
925     noises.clear();
926     affected_actor_count = 0;
927 }
928 
register_noise(const noise_t & noise)929 void noise_grid::register_noise(const noise_t &noise)
930 {
931     noise_cell &target_cell(cells(noise.noise_source));
932     if (target_cell.can_apply_noise(noise.noise_intensity_millis))
933     {
934         const int noise_index = noises.size();
935         noises.push_back(noise);
936         noises[noise_index].noise_id = noise_index;
937         cells(noise.noise_source).apply_noise(noise.noise_intensity_millis,
938                                               noise_index,
939                                               0,
940                                               coord_def(0, 0));
941     }
942 }
943 
propagate_noise()944 void noise_grid::propagate_noise()
945 {
946     if (noises.empty())
947         return;
948 
949 #ifdef DEBUG_NOISE_PROPAGATION
950     dprf(DIAG_NOISE, "noise_grid: %u noises to apply",
951          (unsigned int)noises.size());
952 #endif
953     vector<coord_def> noise_perimeter[2];
954     int circ_index = 0;
955 
956     for (const noise_t &noise : noises)
957         noise_perimeter[circ_index].push_back(noise.noise_source);
958 
959     int travel_distance = 0;
960     while (!noise_perimeter[circ_index].empty())
961     {
962         const vector<coord_def> &perimeter(noise_perimeter[circ_index]);
963         vector<coord_def> &next_perimeter(noise_perimeter[!circ_index]);
964         ++travel_distance;
965         for (const coord_def &p : perimeter)
966         {
967             const noise_cell &cell(cells(p));
968 
969             if (!cell.silent())
970             {
971                 apply_noise_effects(p,
972                                     cell.noise_intensity_millis,
973                                     noises[cell.noise_id]);
974 
975                 const int attenuation = _noise_attenuation_millis(p);
976                 // If the base noise attenuation kills the noise, go no farther:
977                 if (noise_is_audible(cell.noise_intensity_millis - attenuation))
978                 {
979                     // [ds] Not using adjacent iterator which has
980                     // unnecessary overhead for the tight loop here.
981                     for (int xi = -1; xi <= 1; ++xi)
982                     {
983                         for (int yi = -1; yi <= 1; ++yi)
984                         {
985                             if (xi || yi)
986                             {
987                                 const coord_def next_position(p.x + xi,
988                                                               p.y + yi);
989                                 if (in_bounds(next_position)
990                                     && !silenced(next_position))
991                                 {
992                                     if (propagate_noise_to_neighbour(
993                                             attenuation,
994                                             travel_distance,
995                                             cell, p,
996                                             next_position))
997                                     {
998                                         next_perimeter.push_back(next_position);
999                                     }
1000                                 }
1001                             }
1002                         }
1003                     }
1004                 }
1005             }
1006         }
1007 
1008         noise_perimeter[circ_index].clear();
1009         circ_index = !circ_index;
1010     }
1011 
1012 #ifdef DEBUG_NOISE_PROPAGATION
1013     if (affected_actor_count)
1014     {
1015         mprf(MSGCH_WARN, "Writing noise grid with %d noise sources",
1016              (int) noises.size());
1017         dump_noise_grid("noise-grid.html");
1018     }
1019 #endif
1020 }
1021 
propagate_noise_to_neighbour(int base_attenuation,int travel_distance,const noise_cell & cell,const coord_def & current_pos,const coord_def & next_pos)1022 bool noise_grid::propagate_noise_to_neighbour(int base_attenuation,
1023                                               int travel_distance,
1024                                               const noise_cell &cell,
1025                                               const coord_def &current_pos,
1026                                               const coord_def &next_pos)
1027 {
1028     noise_cell &neighbour(cells(next_pos));
1029     if (!neighbour.can_apply_noise(cell.noise_intensity_millis
1030                                    - base_attenuation))
1031     {
1032         return false;
1033     }
1034 
1035     const int noise_turn_angle = cell.turn_angle(next_pos - current_pos);
1036     const int turn_attenuation =
1037         noise_turn_angle? (base_attenuation * (100 + noise_turn_angle * 25)
1038                            / 100)
1039         : base_attenuation;
1040     const int attenuated_noise_intensity =
1041         cell.noise_intensity_millis - turn_attenuation;
1042     if (noise_is_audible(attenuated_noise_intensity))
1043     {
1044         const int neighbour_old_distance = neighbour.noise_travel_distance;
1045         if (neighbour.apply_noise(attenuated_noise_intensity,
1046                                   cell.noise_id,
1047                                   travel_distance,
1048                                   next_pos - current_pos))
1049             // Return true only if we hadn't already registered this
1050             // cell as a neighbour (presumably with a lower volume).
1051             return neighbour_old_distance != travel_distance;
1052     }
1053     return false;
1054 }
1055 
apply_noise_effects(const coord_def & pos,int noise_intensity_millis,const noise_t & noise)1056 void noise_grid::apply_noise_effects(const coord_def &pos,
1057                                      int noise_intensity_millis,
1058                                      const noise_t &noise)
1059 {
1060     if (you.pos() == pos)
1061     {
1062         // The bizarre arrangement of those code into two functions that each
1063         // check whether the actor is the player, but work at different types,
1064         // probably ought to be refactored. I put the noise updating code here
1065         // because the type is correct here.
1066 
1067         _actor_apply_noise(&you, noise.noise_source,
1068                            noise_intensity_millis);
1069 
1070         // The next bit stores noise heard at the player's position for
1071         // display in the HUD. A more interesting (and much more complicated)
1072         // way of doing this might be to sample from the noise grid at
1073         // selected distances from the player. Dealing with terrain is a bit
1074         // nightmarish for this alternative, though, so I'm going to keep it
1075         // simple.
1076         you.los_noise_level = you.asleep()
1077                             // noise may awaken the player but this should be
1078                             // dealt with in `_actor_apply_noise`. We want only
1079                             // noises after awakening (or the awakening noise)
1080                             // to be shown.
1081                             ? 0
1082                             : max(you.los_noise_level, noise_intensity_millis);
1083         ++affected_actor_count;
1084     }
1085 
1086     if (monster *mons = monster_at(pos))
1087     {
1088         if (mons->alive()
1089             && !mons_just_slept(*mons)
1090             && mons->mid != noise.noise_producer_mid)
1091         {
1092             const coord_def perceived_position =
1093                 noise_perceived_position(mons, pos, noise);
1094             _actor_apply_noise(mons, perceived_position,
1095                                noise_intensity_millis);
1096             ++affected_actor_count;
1097         }
1098     }
1099 }
1100 
1101 // Given an actor at affected_pos and a given noise, calculates where
1102 // the actor might think the noise originated.
1103 //
1104 // [ds] Let's keep this brutally simple, since the player will
1105 // probably not notice even if we get very clever:
1106 //
1107 //  - If the cells can see each other, return the actual source position.
1108 //
1109 //  - If the cells cannot see each other, calculate a noise source as follows:
1110 //
1111 //    Calculate a noise centroid between the noise source and the observer,
1112 //    weighted to the noise source if the noise has travelled in a straight line,
1113 //    weighted toward the observer the more the noise has deviated from a
1114 //    straight line.
1115 //
1116 //    Fuzz the centroid by the extra distance the noise has travelled over
1117 //    the straight line distance. This is where the observer will think the
1118 //    noise originated.
1119 //
1120 //    Thus, if the noise has travelled in a straight line, the observer
1121 //    will know the exact origin, 100% of the time, even if the
1122 //    observer is all the way across the level.
noise_perceived_position(actor * act,const coord_def & affected_pos,const noise_t & noise) const1123 coord_def noise_grid::noise_perceived_position(actor *act,
1124                                                const coord_def &affected_pos,
1125                                                const noise_t &noise) const
1126 {
1127     const int noise_travel_distance = cells(affected_pos).noise_travel_distance;
1128     if (!noise_travel_distance)
1129         return noise.noise_source;
1130 
1131     const int cell_grid_distance =
1132         grid_distance(affected_pos, noise.noise_source);
1133 
1134     if (cell_grid_distance <= LOS_RADIUS)
1135     {
1136         if (act->see_cell(noise.noise_source))
1137             return noise.noise_source;
1138     }
1139 
1140     const int extra_distance_covered =
1141         noise_travel_distance - cell_grid_distance;
1142 
1143     const int source_weight = 200;
1144     const int target_weight =
1145         extra_distance_covered?
1146         75 * extra_distance_covered / cell_grid_distance
1147         : 0;
1148 
1149     const coord_def noise_centroid =
1150         target_weight?
1151         (noise.noise_source * source_weight + affected_pos * target_weight)
1152         / (source_weight + target_weight)
1153         : noise.noise_source;
1154 
1155     const int fuzz = extra_distance_covered;
1156     coord_def fuzz_rnd;
1157     fuzz_rnd.x = random_range(-fuzz, fuzz, 2);
1158     fuzz_rnd.y = random_range(-fuzz, fuzz, 2);
1159     const coord_def perceived_point =
1160         fuzz?
1161         noise_centroid + fuzz_rnd
1162         : noise_centroid;
1163 
1164     const coord_def final_perceived_point =
1165         !in_bounds(perceived_point)?
1166         clamp_in_bounds(perceived_point)
1167         : perceived_point;
1168 
1169 #ifdef DEBUG_NOISE_PROPAGATION
1170     dprf(DIAG_NOISE, "[NOISE] Noise perceived by %s at (%d,%d) "
1171          "centroid (%d,%d) source (%d,%d) "
1172          "heard at (%d,%d), distance: %d (travelled %d)",
1173          act->name(DESC_PLAIN, true).c_str(),
1174          final_perceived_point.x, final_perceived_point.y,
1175          noise_centroid.x, noise_centroid.y,
1176          noise.noise_source.x, noise.noise_source.y,
1177          affected_pos.x, affected_pos.y,
1178          cell_grid_distance, noise_travel_distance);
1179 #endif
1180     return final_perceived_point;
1181 }
1182 
1183 #ifdef DEBUG_NOISE_PROPAGATION
1184 
1185 #include <cmath>
1186 
1187 // Return HTML RGB triple given a hue and assuming chroma of 0.86 (220)
_hue_rgb(int hue)1188 static string _hue_rgb(int hue)
1189 {
1190     const int chroma = 220;
1191     const double hue2 = hue / 60.0;
1192     const int x = chroma * (1.0 - fabs(hue2 - floor(hue2 / 2) * 2 - 1));
1193     int red = 0, green = 0, blue = 0;
1194     if (hue2 < 1)
1195         red = chroma, green = x;
1196     else if (hue2 < 2)
1197         red = x, green = chroma;
1198     else if (hue2 < 3)
1199         green = chroma, blue = x;
1200     else if (hue2 < 4)
1201         green = x, blue = chroma;
1202     else
1203         red = x, blue = chroma;
1204     // Other hues are not generated, so skip them.
1205     return make_stringf("%02x%02x%02x", red, green, blue);
1206 }
1207 
_noise_intensity_styles()1208 static string _noise_intensity_styles()
1209 {
1210     // Hi-intensity sound will be red (HSV 0), low intensity blue (HSV 240).
1211     const int hi_hue = 0;
1212     const int lo_hue = 240;
1213     const int huespan = lo_hue - hi_hue;
1214 
1215     const int max_intensity = 25;
1216     string styles;
1217     for (int intensity = 1; intensity <= max_intensity; ++intensity)
1218     {
1219         const int hue = lo_hue - intensity * huespan / max_intensity;
1220         styles += make_stringf(".i%d { background: #%s }\n",
1221                                intensity, _hue_rgb(hue).c_str());
1222 
1223     }
1224     return styles;
1225 }
1226 
_write_noise_grid_css(FILE * outf)1227 static void _write_noise_grid_css(FILE *outf)
1228 {
1229     fprintf(outf,
1230             "<style type='text/css'>\n"
1231             "body { font-family: monospace; padding: 0; margin: 0; "
1232             "line-height: 100%% }\n"
1233             "%s\n"
1234             "</style>",
1235             _noise_intensity_styles().c_str());
1236 }
1237 
write_cell(FILE * outf,coord_def p,int ch) const1238 void noise_grid::write_cell(FILE *outf, coord_def p, int ch) const
1239 {
1240     const int intensity = min(25, cells(p).noise_intensity_millis / 1000);
1241     if (intensity)
1242         fprintf(outf, "<span class='i%d'>&#%d;</span>", intensity, ch);
1243     else
1244         fprintf(outf, "<span>&#%d;</span>", ch);
1245 }
1246 
write_noise_grid(FILE * outf) const1247 void noise_grid::write_noise_grid(FILE *outf) const
1248 {
1249     // Duplicate the screenshot() trick.
1250     FixedVector<char32_t, NUM_DCHAR_TYPES> char_table_bk;
1251     char_table_bk = Options.char_table;
1252 
1253     init_char_table(CSET_ASCII);
1254     init_show_table();
1255 
1256     fprintf(outf, "<div>\n");
1257     // Write the whole map out without checking for mappedness. Handy
1258     // for debugging level-generation issues.
1259     for (int y = 0; y < GYM; ++y)
1260     {
1261         for (int x = 0; x < GXM; ++x)
1262         {
1263             coord_def p(x, y);
1264             if (you.pos() == coord_def(x, y))
1265                 write_cell(outf, p, '@');
1266             else
1267                 write_cell(outf, p, get_feature_def(env.grid[x][y]).symbol());
1268         }
1269         fprintf(outf, "<br>\n");
1270     }
1271     fprintf(outf, "</div>\n");
1272 }
1273 
dump_noise_grid(const string & filename) const1274 void noise_grid::dump_noise_grid(const string &filename) const
1275 {
1276     FILE *outf = fopen(filename.c_str(), "w");
1277     fprintf(outf, "<!DOCTYPE html><html><head>");
1278     _write_noise_grid_css(outf);
1279     fprintf(outf, "</head>\n<body>\n");
1280     write_noise_grid(outf);
1281     fprintf(outf, "</body></html>\n");
1282     fclose(outf);
1283 }
1284 #endif
1285 
_actor_apply_noise(actor * act,const coord_def & apparent_source,int noise_intensity_millis)1286 static void _actor_apply_noise(actor *act,
1287                                const coord_def &apparent_source,
1288                                int noise_intensity_millis)
1289 {
1290 #ifdef DEBUG_NOISE_PROPAGATION
1291     dprf(DIAG_NOISE, "[NOISE] Actor %s (%d,%d) perceives noise (%d) "
1292          "from (%d,%d), real source (%d,%d), distance: %d, noise travelled: %d",
1293          act->name(DESC_PLAIN, true).c_str(),
1294          act->pos().x, act->pos().y,
1295          noise_intensity_millis,
1296          apparent_source.x, apparent_source.y,
1297          noise.noise_source.x, noise.noise_source.y,
1298          grid_distance(act->pos(), noise.noise_source),
1299          noise_travel_distance);
1300 #endif
1301 
1302     const bool player = act->is_player();
1303     if (player)
1304     {
1305         const int loudness = div_rand_round(noise_intensity_millis, 1000);
1306         act->check_awaken(loudness);
1307     }
1308     else
1309     {
1310         monster *mons = act->as_monster();
1311         // If the perceived noise came from within the player's LOS, any
1312         // monster that hears it will have a chance to guess that the
1313         // noise was triggered by the player, depending on how close it was to
1314         // the player. This happens independently of actual noise source.
1315         //
1316         // The probability is calculated as p(x) = (-90/R)x + 100, which is
1317         // linear from p(0) = 100 to p(R) = 10. This replaces a version that
1318         // was 100% from 0 to 3, and 0% outward.
1319         //
1320         // behaviour around the old breakpoint for R=8: p(3) = 66, p(4) = 55.
1321 
1322         const int player_distance = grid_distance(apparent_source, you.pos());
1323         const int alert_prob = max(player_distance * -90 / LOS_RADIUS + 100, 0);
1324 
1325         if (x_chance_in_y(alert_prob, 100))
1326             behaviour_event(mons, ME_ALERT, &you, apparent_source); // shout + set you as foe
1327         else
1328             behaviour_event(mons, ME_DISTURB, 0, apparent_source); // wake
1329     }
1330 }
1331