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 ¤t_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