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