1 /**
2 * \file mon-msg.c
3 * \brief Monster message code.
4 *
5 * Copyright (c) 1997-2016 Jeff Greene, Andi Sidwell
6 *
7 * This work is free software; you can redistribute it and/or modify it
8 * under the terms of either:
9 *
10 * a) the GNU General Public License as published by the Free Software
11 * Foundation, version 2, or
12 *
13 * b) the "Angband licence":
14 * This software may be copied and distributed for educational, research,
15 * and not for profit purposes provided that this copyright and statement
16 * are included in all such copies. Other copyrights may also apply.
17 */
18
19 #include "angband.h"
20 #include "mon-desc.h"
21 #include "mon-msg.h"
22 #include "mon-predicate.h"
23 #include "mon-util.h"
24 #include "game-input.h"
25 #include "player-calcs.h"
26
27 /**
28 * Maxinum number of stacked monster messages
29 */
30 #define MAX_STORED_MON_MSG 200
31 #define MAX_STORED_MON_CODES 400
32
33 /**
34 * Flags for whether monsters are offscreen or invisible
35 */
36 #define MON_MSG_FLAG_OFFSCREEN 0x01
37 #define MON_MSG_FLAG_INVISIBLE 0x02
38
39 /**
40 * A stacked monster message entry
41 */
42 struct monster_race_message {
43 struct monster_race *race; /* The race of the monster */
44 int flags; /* Flags */
45 int msg_code; /* The coded message */
46 int count; /* How many monsters triggered this message */
47 int delay; /* messages will be processed in this order: delay = 0, 1, 2 */
48 };
49
50 /**
51 * A (monster, message type) pair used for duplicate checking
52 */
53 struct monster_message_history {
54 struct monster *mon; /* The monster */
55 int message_code; /* The coded message */
56 };
57
58 static int size_mon_hist = 0;
59 static int size_mon_msg = 0;
60 static struct monster_race_message mon_msg[MAX_STORED_MON_MSG];
61 static struct monster_message_history mon_message_hist[MAX_STORED_MON_CODES];
62
63 /**
64 * An array of monster messages in order of monster message type.
65 *
66 * Singular and plural modifiers are encoded in the same string. Example:
67 * "[is|are] hurt" is expanded to "is hurt" if you request the singular form.
68 * The string is expanded to "are hurt" if the plural form is requested.
69 *
70 * The singular and plural parts are optional. Example:
71 * "rear[s] up in anger" only includes a modifier for the singular form.
72 *
73 * Any of these strings can start with "~", in which case we consider that
74 * string as a whole message, not as a part of a larger message. This
75 * is useful to display Moria-like death messages.
76 */
77 static const struct {
78 const char *msg;
79 bool omit_subject;
80 int type;
81 } msg_repository[] = {
82 #define MON_MSG(x, t, o, s) { s, o, t },
83 #include "list-mon-message.h"
84 #undef MON_MSG
85 };
86
87 /**
88 * Adds to the message queue a message describing a monster's reaction
89 * to damage.
90 */
message_pain(struct monster * mon,int dam)91 void message_pain(struct monster *mon, int dam)
92 {
93 int msg_code = MON_MSG_UNHARMED;
94
95 /* Calculate damage levels */
96 if (dam > 0) {
97 /* Note -- subtle fix -CFT */
98 long newhp = (long)(mon->hp);
99 long oldhp = newhp + (long)(dam);
100 long tmp = (newhp * 100L) / oldhp;
101 int percentage = (int)(tmp);
102
103 if (percentage > 95) msg_code = MON_MSG_95;
104 else if (percentage > 75) msg_code = MON_MSG_75;
105 else if (percentage > 50) msg_code = MON_MSG_50;
106 else if (percentage > 35) msg_code = MON_MSG_35;
107 else if (percentage > 20) msg_code = MON_MSG_20;
108 else if (percentage > 10) msg_code = MON_MSG_10;
109 else msg_code = MON_MSG_0;
110 }
111
112 add_monster_message(mon, msg_code, false);
113 }
114
115 /**
116 * Tracks which monster has had which pain message stored, so redundant
117 * messages don't happen due to monster attacks hitting other monsters.
118 * Returns true if the message is redundant.
119 */
redundant_monster_message(struct monster * mon,int msg_code)120 static bool redundant_monster_message(struct monster *mon, int msg_code)
121 {
122 assert(mon);
123 assert(msg_code >= 0);
124 assert(msg_code < MON_MSG_MAX);
125
126 for (int i = 0; i < size_mon_hist; i++) {
127 /* Check for a matched monster & monster code */
128 if (mon == mon_message_hist[i].mon &&
129 msg_code == mon_message_hist[i].message_code) {
130 return true;
131 }
132 }
133
134 return false;
135 }
136
137 /**
138 * Work out what flags a message should have from a monster
139 */
message_flags(const struct monster * mon)140 static int message_flags(const struct monster *mon)
141 {
142 int flags = 0;
143
144 if (!panel_contains(mon->grid.y, mon->grid.x)) {
145 flags |= MON_MSG_FLAG_OFFSCREEN;
146 }
147
148 if (!monster_is_visible(mon)) {
149 flags |= MON_MSG_FLAG_INVISIBLE;
150 }
151
152 return flags;
153 }
154
155 /**
156 * Store the monster in the monster history for duplicate checking later
157 */
store_monster(struct monster * mon,int msg_code)158 static void store_monster(struct monster *mon, int msg_code)
159 {
160 /* Record which monster had this message stored */
161 if (size_mon_hist < MAX_STORED_MON_CODES) {
162 mon_message_hist[size_mon_hist].mon = mon;
163 mon_message_hist[size_mon_hist].message_code = msg_code;
164 size_mon_hist++;
165 }
166 }
167
168 /**
169 * Try to stack a message on top of existing ones
170 *
171 * \returns true if successful, false if failed
172 */
stack_message(struct monster * mon,int msg_code,int flags)173 static bool stack_message(struct monster *mon, int msg_code, int flags)
174 {
175 int i;
176
177 for (i = 0; i < size_mon_msg; i++) {
178 /* We found the race and the message code */
179 if (mon_msg[i].race == mon->race &&
180 mon_msg[i].flags == flags &&
181 mon_msg[i].msg_code == msg_code) {
182 mon_msg[i].count++;
183 store_monster(mon, msg_code);
184 return true;
185 }
186 }
187
188 return false;
189 }
190
what_delay(int msg_code,int delay)191 static int what_delay(int msg_code, int delay)
192 {
193 if (msg_code == MON_MSG_DIE || msg_code == MON_MSG_DESTROYED) {
194 return 2;
195 } else {
196 return delay ? 1 : 0;
197 }
198 }
199
200 /**
201 * Stack a codified message for the given monster race.
202 *
203 * Return true on success.
204 */
add_monster_message(struct monster * mon,int msg_code,bool delay)205 bool add_monster_message(struct monster *mon, int msg_code, bool delay)
206 {
207 assert(msg_code >= 0);
208 assert(msg_code < MON_MSG_MAX);
209
210 int flags = message_flags(mon);
211
212 /* Try to stack the message on top of older messages if it isn't redunant */
213 /* If not possible, check we have storage space for more messages and add */
214 if (!redundant_monster_message(mon, msg_code) &&
215 !stack_message(mon, msg_code, flags) &&
216 size_mon_msg < MAX_STORED_MON_MSG) {
217 mon_msg[size_mon_msg].race = mon->race;
218 mon_msg[size_mon_msg].flags = flags;
219 mon_msg[size_mon_msg].msg_code = msg_code;
220 mon_msg[size_mon_msg].count = 1;
221 mon_msg[size_mon_msg].delay = what_delay(msg_code, delay);
222 size_mon_msg++;
223
224 store_monster(mon, msg_code);
225
226 player->upkeep->notice |= PN_MON_MESSAGE;
227
228 return true;
229 } else {
230 return false;
231 }
232 }
233
234 /**
235 * Create the subject of the sentence for monster messages
236 */
get_subject(char * buf,size_t buflen,struct monster_race * race,int count,bool invisible,bool offscreen)237 static void get_subject(char *buf, size_t buflen,
238 struct monster_race *race,
239 int count,
240 bool invisible,
241 bool offscreen)
242 {
243 if (invisible) {
244 if (count == 1) {
245 my_strcpy(buf, "It", buflen);
246 } else {
247 strnfmt(buf, buflen, "%d monsters", count);
248 }
249 } else {
250 /* Uniques, multiple monsters, or just one */
251 if (rf_has(race->flags, RF_UNIQUE)) {
252 my_strcpy(buf, race->name, buflen);
253 } else if (count == 1) {
254 strnfmt(buf, buflen, "The %s", race->name);
255 } else {
256 /* Get the plural of the race name */
257 if (race->plural != NULL) {
258 strnfmt(buf, buflen, "%d %s", count, race->plural);
259 } else {
260 strnfmt(buf, buflen, "%d %s", count, race->name);
261 plural_aux(buf, buflen);
262 }
263 }
264 }
265
266 if (offscreen)
267 my_strcat(buf, " (offscreen)", buflen);
268
269 /* Add a separator */
270 my_strcat(buf, " ", buflen);
271 }
272
273 /* State machine constants for get_message_text() */
274 #define MSG_PARSE_NORMAL 0
275 #define MSG_PARSE_SINGLE 1
276 #define MSG_PARSE_PLURAL 2
277
278 /**
279 * Formats a message based on the given message code and the plural flag.
280 *
281 * \param pos the position in buf to start writing the message into
282 */
get_message_text(char * buf,size_t buflen,int msg_code,const struct monster_race * race,bool do_plural)283 static void get_message_text(char *buf, size_t buflen,
284 int msg_code,
285 const struct monster_race *race,
286 bool do_plural)
287 {
288 assert(msg_code < MON_MSG_MAX);
289 assert(race != NULL);
290 assert(race->base != NULL);
291 assert(race->base->pain != NULL);
292
293 /* Find the appropriate message */
294 const char *source = msg_repository[msg_code].msg;
295 switch (msg_code) {
296 case MON_MSG_95: source = race->base->pain->messages[0]; break;
297 case MON_MSG_75: source = race->base->pain->messages[1]; break;
298 case MON_MSG_50: source = race->base->pain->messages[2]; break;
299 case MON_MSG_35: source = race->base->pain->messages[3]; break;
300 case MON_MSG_20: source = race->base->pain->messages[4]; break;
301 case MON_MSG_10: source = race->base->pain->messages[5]; break;
302 case MON_MSG_0: source = race->base->pain->messages[6]; break;
303 }
304
305 int state = MSG_PARSE_NORMAL;
306 size_t maxlen = strlen(source);
307 size_t pos = 0;
308
309 /* Put the message characters in the buffer */
310 /* XXX This logic should be used everywhere for pluralising strings */
311 for (size_t i = 0; i < maxlen && pos < buflen - 1; i++) {
312 char cur = source[i];
313
314 /*
315 * The characters '[|]' switch parsing mode and are never output.
316 * The syntax is [singular|plural]
317 */
318 if (state == MSG_PARSE_NORMAL && cur == '[') {
319 state = MSG_PARSE_SINGLE;
320 } else if (state == MSG_PARSE_SINGLE && cur == '|') {
321 state = MSG_PARSE_PLURAL;
322 } else if (state != MSG_PARSE_NORMAL && cur == ']') {
323 state = MSG_PARSE_NORMAL;
324 } else if (state == MSG_PARSE_NORMAL ||
325 (state == MSG_PARSE_SINGLE && do_plural == false) ||
326 (state == MSG_PARSE_PLURAL && do_plural == true)) {
327 /* Copy the characters according to the mode */
328 buf[pos++] = cur;
329 }
330 }
331
332 /* We should always return to the normal state */
333 assert(state == MSG_PARSE_NORMAL);
334
335 /* Terminate the buffer */
336 buf[pos] = 0;
337 }
338
339 #undef MSG_PARSE_NORMAL
340 #undef MSG_PARSE_SINGLE
341 #undef MSG_PARSE_PLURAL
342
343 /**
344 * Accessor function - should we skip the monster name for this message type?
345 */
skip_subject(int msg_code)346 static bool skip_subject(int msg_code)
347 {
348 assert(msg_code >= 0);
349 assert(msg_code < MON_MSG_MAX);
350
351 return msg_repository[msg_code].omit_subject;
352 }
353
354 /**
355 * Return a MSG_ type for the given message code (and monster)
356 */
get_message_type(int msg_code,const struct monster_race * race)357 static int get_message_type(int msg_code, const struct monster_race *race)
358 {
359 int type = msg_repository[msg_code].type;
360
361 if (type == MSG_KILL) {
362 /* Play a special sound if the monster was unique */
363 if (rf_has(race->flags, RF_UNIQUE)) {
364 if (race->base == lookup_monster_base("Morgoth")) {
365 type = MSG_KILL_KING;
366 } else {
367 type = MSG_KILL_UNIQUE;
368 }
369 }
370 }
371
372 return type;
373 }
374
375 /**
376 * Show the given monster message.
377 */
show_message(struct monster_race_message * msg)378 static void show_message(struct monster_race_message *msg)
379 {
380 char subject[60] = "";
381 char body[60];
382
383 /* Some messages don't require a monster name */
384 if (!skip_subject(msg->msg_code)) {
385 /* Get 'it ' or '3 monsters (offscreen) ' or '15000 snakes ' etc */
386 get_subject(subject, sizeof(subject),
387 msg->race,
388 msg->count,
389 msg->flags & MON_MSG_FLAG_INVISIBLE,
390 msg->flags & MON_MSG_FLAG_OFFSCREEN);
391 }
392
393 /* Get the message proper, corrected for singular/plural etc. */
394 get_message_text(body, sizeof(body),
395 msg->msg_code,
396 msg->race,
397 msg->count > 1);
398
399 /* Show the message */
400 msgt(get_message_type(msg->msg_code, msg->race),
401 "%s%s",
402 subject,
403 body);
404 }
405
406 /**
407 * Show and then cler all stacked monster messages.
408 */
show_monster_messages(void)409 void show_monster_messages(void)
410 {
411 for (int delay = 0; delay < 3; delay++) {
412 for (int i = 0; i < size_mon_msg; i++) {
413 struct monster_race_message *msg = &mon_msg[i];
414
415 /* Skip irrelevant entries */
416 if (msg->delay == delay) {
417 show_message(msg);
418 }
419 }
420 }
421
422 /* Delete all the stacked messages and history */
423 size_mon_msg = size_mon_hist = 0;
424 }
425