2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Mission/MissionMessage.cpp $
15 * Controls messaging to player during the mission
18 * Revision 1.7 2005/08/12 08:55:13 taylor
19 * sync up talking head fixes from FS2_Open code base (still not 100%)
21 * Revision 1.6 2004/07/04 11:42:56 taylor
22 * cleanup talking head code a little, fix anim free to work better and prevent crashes, fix test for old anim
24 * Revision 1.5 2003/06/11 18:30:33 taylor
27 * Revision 1.4 2003/05/25 02:30:43 taylor
30 * Revision 1.3 2002/06/09 04:41:22 relnev
31 * added copyright header
33 * Revision 1.2 2002/05/07 03:16:46 theoddone33
34 * The Great Newline Fix
36 * Revision 1.1.1.1 2002/05/03 03:28:10 root
40 * 32 9/12/99 8:09p Dave
41 * Fixed problem where skip-training button would cause mission messages
42 * not to get paged out for the current mission.
44 * 31 9/01/99 2:52p Andsager
45 * Add new heads to FRED and some debug code for playing heads
47 * 30 8/28/99 7:29p Dave
48 * Fixed wingmen persona messaging. Make sure locked turrets don't count
49 * towards the # attacking a player.
51 * 29 8/26/99 8:51p Dave
52 * Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
54 * 28 8/23/99 5:04p Jefff
55 * Added new mission flag to disable built-in messages from playing.
56 * Added fred support as well.
58 * 27 8/19/99 10:12a Alanl
59 * preload mission-specific messages on machines greater than 48MB
61 * 26 8/18/99 12:09p Andsager
62 * Add debug if message has no anim for message. Make messages come from
65 * 25 7/31/99 2:30p Dave
66 * Added nifty mission message debug viewing keys.
68 * 24 7/24/99 2:19p Dave
71 * 23 7/23/99 5:44p Andsager
72 * make personas consistently choose same ship
74 * 22 7/15/99 9:20a Andsager
75 * FS2_DEMO initial checkin
77 * 21 7/14/99 4:27p Andsager
78 * Added multiple message debug check
80 * 20 7/06/99 10:41a Andsager
81 * Add AWACS need help messages
83 * 19 7/02/99 11:16a Andsager
84 * Removed mult message debug check.
86 * 18 7/02/99 11:13a Andsager
89 * 17 6/16/99 10:20a Dave
90 * Added send-message-list sexpression.
92 * 16 6/14/99 5:53p Dave
93 * Removed duplicate message check temporarily.
95 * 15 6/10/99 3:43p Dave
96 * Do a better job of syncing text colors to HUD gauges.
98 * 14 6/09/99 2:56p Andsager
99 * Check all messages for repeat. Allow multiple versions of same message
100 * if queued > 20 apart.
102 * 13 6/07/99 11:33a Anoop
103 * Get rid of erroneous Int3() in multiple message check.
105 * 12 6/07/99 10:31a Andsager
106 * Get rid of false multiplayer multiple messages catch.
108 * 11 6/03/99 2:56p Andsager
111 * 10 6/03/99 2:44p Andsager
112 * Fix stupid bug in debug code.
114 * 9 6/03/99 2:08p Andsager
115 * Put in debug code to find multiple mission messages.
117 * 8 3/29/99 6:17p Dave
118 * More work on demo system. Got just about everything in except for
119 * blowing ships up, secondary weapons and player death/warpout.
121 * 7 1/28/99 12:19a Dave
122 * Fixed a dumb debug build unhandled exception.
124 * 6 1/07/99 10:08a Jasen
127 * 5 1/07/99 9:24a Dave
128 * Put in hi-res coord support for head anim.
130 * 4 11/05/98 4:18p Dave
131 * First run nebula support. Beefed up localization a bit. Removed all
132 * conditional compiles for foreign versions. Modified mission file
135 * 3 10/23/98 3:51p Dave
136 * Full support for tstrings.tbl and foreign languages. All that remains
137 * is to make it active in Fred.
139 * 2 10/07/98 10:53a Dave
142 * 1 10/07/98 10:49a Dave
144 * 127 8/25/98 1:48p Dave
145 * First rev of EMP effect. Player side stuff basically done. Next comes
148 * 126 6/01/98 11:43a John
149 * JAS & MK: Classified all strings for localization.
151 * 125 5/24/98 12:55a Mike
152 * Fix bug with scream from Installation. Should also fix bug with double
153 * screams from some ships.
155 * 124 5/18/98 6:06p Lawrance
156 * Don't play messages or auto-target on first frame
158 * 123 5/15/98 8:36p Lawrance
159 * Add 'target ship that last sent transmission' target key
161 * 122 5/09/98 10:00p Allender
162 * make vasudan persona for support use terran support persona
164 * 121 5/08/98 11:21a Allender
165 * fix ingame join trouble. Small messaging fix. Enable collisions for
168 * 120 5/06/98 12:19p Lawrance
169 * Fix typo for 'Stray Warning Final'
171 * 119 5/05/98 9:12p Allender
172 * fix large problem introduced last checkin when changiing SDL_assert to if
174 * 118 5/05/98 4:12p Chad
175 * changed SDL_assert info if statement when removing messages from queue when
178 * 117 5/01/98 12:34p John
179 * Added code to force FreeSpace to run in the same dir as exe and made
180 * all the parse error messages a little nicer.
182 * 116 4/27/98 9:00p Allender
183 * mission specific messages from #<someone> are now sourced to terran
186 * 115 4/26/98 11:35a Allender
187 * make traitor message play by iteself in all cases
189 * 114 4/25/98 11:49p Lawrance
190 * Add Terran Command stray messages
192 * 113 4/22/98 9:17a Allender
193 * be sure that builtin command messages play with the correct hud source.
194 * Also be sure that messages which get converted to Terran command to the
197 * 112 4/20/98 1:30a Lawrance
198 * Don't load head animations if talking head gauge is disabled.
200 * 111 4/17/98 11:03a Allender
201 * some rearm message being played too often and sent with incorrect
204 * 110 4/13/98 5:06p Lawrance
205 * Cut off talking head about 250ms before wave ends
207 * 109 4/10/98 9:14a Lawrance
208 * fix up persona code for the demo
210 * 108 4/09/98 2:15p Allender
211 * fixed compiler warnings
213 * 107 4/09/98 12:36p Allender
214 * don't allow the same ship to have messages overlapping. Put in code to
215 * check for ship's existence (wingman only) before actually playing
218 * 106 4/09/98 12:32a Lawrance
219 * Fix bugs related to multiple screams from same ship, builtin messages
220 * playing after screams, or praising while severly damaged.
222 * 105 4/08/98 3:45p Allender
223 * mission message overhaul. Make message from any wingman mean any
224 * wingman with that persona. Terran command wave and ani's for dead
225 * ships now play correctly.
227 * 104 4/07/98 8:09p Lawrance
228 * don't play talking heads in the demo
230 * 103 4/07/98 5:30p Lawrance
231 * Player can't send/receive messages when comm is destroyed. Garble
232 * messages when comm is damaged.
234 * 102 4/07/98 5:26p Allender
235 * low priority mission specific messages won't interrupt anything.
237 * 101 4/07/98 10:51a Allender
238 * remove any allied from message senders. Make heads for mission
239 * specific messages play appropriately
241 * 100 4/07/98 12:04a Mike
242 * New system for instructor chastising player if he fires at instructor.
244 * 99 4/03/98 11:39a Lawrance
245 * only allow 1 wingman persona in demo
247 * 98 4/02/98 1:09p Allender
248 * don't process messages before player "enters" mission (i.e. due to
249 * player entry delay)
251 * 97 4/02/98 10:06a Allender
252 * wing arrival message for delta and epsilon wings
254 * 96 4/01/98 10:47p Lawrance
255 * Supporting builtin messages for rearm and repair requests
257 * 95 3/25/98 8:43p Hoffoss
258 * Changed anim_play() to not be so damn complex when you try and call it.
260 * 94 3/24/98 12:46p Allender
261 * save shipnum before killing currently playing message in preparation
262 * for playing death scream.
264 * 93 3/22/98 3:54p Andsager
265 * AL: Prevent -1 index into Ships[] array when playing a scream
267 * 92 3/18/98 10:20p Allender
268 * force wingman scream when he's talking and then dies
270 * 91 3/18/98 12:03p John
271 * Marked all the new strings as externalized or not.
273 * 90 3/17/98 4:01p Hoffoss
274 * Added HUD_SOURCE_TERRAN_CMD and changed code to utilize it when a
275 * message is being sent from Terran Command.
277 * 89 3/05/98 10:18p Lawrance
278 * Play voice cue sound when there is no voice file present
280 * 88 3/02/98 5:42p John
281 * Removed WinAVI stuff from Freespace. Made all HUD gauges wriggle from
282 * afterburner. Made gr_set_clip work good with negative x &y. Made
283 * model_caching be on by default. Made each cached model have it's own
284 * bitmap id. Made asteroids not rotate when model_caching is on.
286 * 87 3/02/98 9:34a Allender
287 * don't allow mission specific messages to timeout. SDL_assert when trying
288 * to remove a mission specific messages from the queue. Print out in the
289 * log file if the voice didn't play.
291 * 86 2/23/98 8:45a John
292 * Externalized Strings
294 * 85 2/20/98 8:33p Lawrance
295 * Add the 'All Alone' message
297 * 84 2/16/98 2:20p Allender
298 * make death scream kill any other messages from that ship
300 * 83 2/12/98 4:58p Lawrance
301 * Add support for 'All Clear' radio message
303 * 82 2/11/98 9:44p Allender
304 * rearm repair code fixes. hud support view shows abort status. New
305 * support ship killed message. More network stats
307 * 81 2/04/98 10:44p Allender
308 * mark personas as not used between missions. Don't ever randomly
309 * choose a Vasudan persona
311 * 80 1/29/98 11:38a Allender
312 * support for Vasudan personas
314 * 79 1/25/98 10:04p Lawrance
315 * Fix nasty divide-by-zero bug in message_calc_anim_start_frame().
317 * 78 1/24/98 4:46p Lawrance
318 * add in support for new voice messasges
320 * 77 1/22/98 5:13p Lawrance
321 * pick useful starting frame when playing animation
323 * 76 1/21/98 7:20p Lawrance
324 * Make subsystem locking only work with line-of-sight, cleaned up locking
325 * code, moved globals to player struct.
327 * 75 1/21/98 11:54a Duncan
328 * Commended out assert for the moment to allow Fred to run. Mark can fix
329 * this properly later, since he understands it.
331 * 74 1/21/98 10:33a Allender
332 * fixed up messaging code to play a random head when playing a builtin
335 * 73 1/20/98 6:21p Lawrance
336 * Stop animation from playing when voice clip ends early.
338 * 72 1/20/98 3:43p Allender
339 * don't queue messages when player becomes traitor
341 * 71 1/20/98 12:52p Lawrance
342 * Draw talking head as alpha-color bitmap, black out region behind
345 * 70 1/20/98 10:20a Lawrance
346 * Draw head animation as alpha-colored.
348 * 69 1/18/98 9:51p Lawrance
349 * Add support for 'Player Died' messages.
351 * 68 1/14/98 9:49p Allender
352 * removed 3'oclock and 9'oclock messages
354 * 67 1/13/98 3:11p Allender
355 * new messages for disable/disarm
357 * 66 1/12/98 11:16p Lawrance
358 * Wonderful HUD config.
360 * 65 1/07/98 4:41p Allender
361 * minor modification to special messages. Fixed cargo_revealed problem
362 * for multiplayer and problem with is-cargo-known sexpression
364 * 64 12/15/97 12:14p Allender
365 * implemented overlapping messages
367 * 63 12/12/97 4:58p Allender
368 * make messages interruptable. Put in code to support multiple messages
369 * at once, although this feature not fully implemented yet.
371 * 62 12/04/97 9:37p Dave
372 * Fixed a bunch of multiplayer messaging bugs.
374 * 61 12/02/97 2:37p Allender
375 * added asserts to be sure that an actual ship (i.e. not cargo or
376 * otherwise) is the ship sending a message to the player
381 #include "linklist.h"
382 #include "missionmessage.h"
383 #include "missiontraining.h"
384 #include "hudmessage.h"
385 #include "hudtarget.h"
391 #include "freespace.h"
393 #include "multimsgs.h"
394 #include "gamesequence.h"
395 #include "animplay.h"
396 #include "controlsconfig.h"
397 #include "audiostr.h"
398 #include "hudsquadmsg.h"
399 #include "multiutil.h"
401 #include "subsysdamage.h"
403 #include "localize.h"
405 #include "hudconfig.h"
407 // here is a text list of the builtin message names. These names are used to match against
408 // names read in for builtin message radio bits to see what message to play. These are
409 // generic names, meaning that there will be the same message type for a number of different
411 const char *Builtin_message_types[MAX_BUILTIN_MESSAGE_TYPES] =
426 "Permission", // AL: no code support yet
427 "Stray", // DA: no code support
434 "Docking Start", // AL: no message seems to exist for this
444 "All Clear", // DA: no code support
452 "Stray Warning Final",
458 MMessage Messages[MAX_MISSION_MESSAGES];
459 int Message_times[MAX_MISSION_MESSAGES];
461 int Num_messages, Num_message_avis, Num_message_waves;
462 int Num_builtin_messages, Num_builtin_avis, Num_builtin_waves;
464 int Message_debug_index = -1;
466 message_extra Message_avis[MAX_MESSAGE_AVIS];
467 message_extra Message_waves[MAX_MESSAGE_WAVES];
469 #define MAX_PLAYING_MESSAGES 2
471 #if defined(FS2_DEMO) || defined(FS1_DEMO)
472 #define MAX_WINGMAN_HEADS 1
473 #define MAX_COMMAND_HEADS 1
475 #define MAX_WINGMAN_HEADS 2
476 #define MAX_COMMAND_HEADS 3
480 #define HEAD_PREFIX_STRING "head-"
481 #define COMMAND_HEAD_PREFIX "head-cm1"
482 #define COMMAND_WAVE_PREFIX "TC_"
483 #define SUPPORT_NAME "Support"
486 // variables to keep track of messages that are currently playing
487 int Num_messages_playing; // number of is a message currently playing?
489 typedef struct pmessage {
490 anim_instance *anim; // handle of anim currently playing
491 int wave; // handle of wave currently playing
492 int id; // id of message currently playing
493 int priority; // priority of message currently playing
494 int shipnum; // shipnum of ship sending this message, -1 if from Terran command
495 int builtin_type; // if a builtin message, type of the message
498 static pmessage Playing_messages[MAX_PLAYING_MESSAGES];
500 int Message_shipnum; // ship number of who is sending message to player -- used outside this module
502 // variables to control message queuing. All new messages to the player are queued. The array
503 // will be ordered by priority, then time submitted.
505 #define MQF_CONVERT_TO_COMMAND (1<<0) // convert this queued message to terran command
506 #define MQF_CHECK_ALIVE (1<<1) // check for the existence of who_from before sending
508 typedef struct message_q {
509 fix time_added; // time at which this entry was added
510 int window_timestamp; // timestamp which will tell us how long we have to play the message
511 int priority; // priority of the message
512 int message_num; // index into the Messages[] array
513 char who_from[NAME_LENGTH]; // who this message is from
514 int source; // who the source of the message is (HUD_SOURCE_* type)
515 int builtin_type; // type of builtin message (-1 if mission message)
516 int flags; // should this message entry be converted to Terran Command head/wave file
517 int min_delay_stamp; // minimum delay before this message will start playing
518 int group; // message is part of a group, don't time it out
521 #define MAX_MESSAGE_Q 30
522 #define MAX_MESSAGE_LIFE F1_0*30 // After being queued for 30 seconds, don't play it
523 #define DEFAULT_MESSAGE_LENGTH 3000 // default number of milliseconds to display message indicator on hud
524 message_q MessageQ[MAX_MESSAGE_Q];
525 int MessageQ_num; // keeps track of number of entries on the queue.
527 #define MESSAGE_IMMEDIATE_TIMESTAMP 1000 // immediate messages must play within 1 second
528 #define MESSAGE_SOON_TIMESTAMP 5000 // "soon" messages must play within 5 seconds
529 #define MESSAGE_ANYTIME_TIMESTAMP -1 // anytime timestamps are invalid
531 // Persona information
533 Persona Personas[MAX_PERSONAS];
535 const char *Persona_type_names[MAX_PERSONA_TYPES] =
547 ///////////////////////////////////////////////////////////////////
548 // used to distort incoming messages when comms are damaged
549 ///////////////////////////////////////////////////////////////////
550 static int Message_wave_muted;
551 static int Message_wave_duration;
552 static int Next_mute_time;
554 #define MAX_DISTORT_PATTERNS 2
555 #define MAX_DISTORT_LEVELS 6
556 static float Distort_patterns[MAX_DISTORT_PATTERNS][MAX_DISTORT_LEVELS] =
558 {0.20f, 0.20f, 0.20f, 0.20f, 0.20f, 0.20f},
559 {0.10f, 0.20f, 0.25f, 0.25f, 0.05f, 0.15f}
562 static int Distort_num; // which distort pattern is being used
563 static int Distort_next; // which section of distort pattern is next
565 int Head_coords[GR_NUM_RESOLUTIONS][2] = {
574 // forward declaration
575 void message_maybe_distort_text(char *text);
577 // following functions to parse messages.tbl -- code pretty much ripped from weapon/ship table parsing code
579 // functions to deal with parsing personas. Personas are just a list of names that give someone
580 // sending a message an identity which spans the life of the mission
584 char type[NAME_LENGTH];
586 SDL_assert ( Num_personas < MAX_PERSONAS );
588 Personas[Num_personas].flags = 0;
589 required_string("$Persona:");
590 stuff_string(Personas[Num_personas].name, F_NAME, NULL);
592 // get the type name and set the appropriate flag
593 required_string("$Type:");
594 stuff_string( type, F_NAME, NULL );
595 for ( i = 0; i < MAX_PERSONA_TYPES; i++ ) {
596 if ( !SDL_strcasecmp( type, Persona_type_names[i]) ) {
598 Personas[Num_personas].flags |= (1<<i);
600 // save the Terran Command persona in a global
601 if ( Personas[Num_personas].flags & PERSONA_FLAG_COMMAND ) {
602 // SDL_assert ( Command_persona == -1 );
603 Command_persona = Num_personas;
610 if ( optional_string("+Vasudan") )
611 Personas[Num_personas].flags |= PERSONA_FLAG_VASUDAN;
613 if ( i == MAX_PERSONA_TYPES )
614 Error(LOCATION, "Unknown persona type in messages.tbl -- %s\n", type );
620 // two functions to add avi/wave names into a table
621 int add_avi( const char *avi_name )
625 SDL_assert ( Num_message_avis < MAX_MESSAGE_AVIS );
626 SDL_assert (strlen(avi_name) < MAX_FILENAME_LEN );
628 // check to see if there is an existing avi being used here
629 for ( i = 0; i < Num_message_avis; i++ ) {
630 if ( !SDL_strcasecmp(Message_avis[i].name, avi_name) )
634 // would have returned if a slot existed.
635 SDL_strlcpy( Message_avis[Num_message_avis].name, avi_name, MAX_FILENAME_LEN );
636 Message_avis[Num_message_avis].num = -1;
638 return (Num_message_avis - 1);
641 int add_wave( const char *wave_name )
645 SDL_assert ( Num_message_waves < MAX_MESSAGE_WAVES );
646 SDL_assert (strlen(wave_name) < MAX_FILENAME_LEN );
648 // check to see if there is an existing wave being used here
649 for ( i = 0; i < Num_message_waves; i++ ) {
650 if ( !SDL_strcasecmp(Message_waves[i].name, wave_name) )
654 SDL_strlcpy( Message_waves[Num_message_waves].name, wave_name, MAX_FILENAME_LEN );
655 Message_waves[Num_message_waves].num = -1;
657 return (Num_message_waves - 1);
660 // parses an individual message
661 void message_parse( )
663 MissionMessage *msgp;
664 char persona_name[NAME_LENGTH];
666 SDL_assert ( Num_messages < MAX_MISSION_MESSAGES );
667 msgp = &Messages[Num_messages];
669 required_string("$Name:");
670 stuff_string(msgp->name, F_NAME, NULL);
673 msgp->multi_team = -1;
674 if(optional_string("$Team:")){
679 if((mt < 0) || (mt >= 2)){
683 // only bother with filters if multiplayer and TvT
684 if(Fred_running || ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)) ){
685 msgp->multi_team = mt;
689 // backwards compatibility for old fred missions - all new ones should use $MessageNew
690 if(optional_string("$Message:")){
691 stuff_string(msgp->message, F_MESSAGE, NULL);
693 required_string("$MessageNew:");
694 stuff_string(msgp->message, F_MULTITEXT, NULL);
697 msgp->persona_index = -1;
698 if ( optional_string("+Persona:") ) {
699 stuff_string(persona_name, F_NAME, NULL);
700 msgp->persona_index = message_persona_name_lookup( persona_name );
704 msgp->avi_info.index = -1;
706 msgp->avi_info.name = NULL;
708 if ( optional_string("+AVI Name:") ) {
709 char avi_name[MAX_FILENAME_LEN];
711 stuff_string(avi_name, F_NAME, NULL);
712 if ( !Fred_running ) {
713 msgp->avi_info.index = add_avi(avi_name);
715 msgp->avi_info.name = strdup(avi_name);
720 msgp->wave_info.index = -1;
722 msgp->wave_info.name = NULL;
724 if ( optional_string("+Wave Name:") ) {
725 char wave_name[MAX_FILENAME_LEN];
727 stuff_string(wave_name, F_NAME, NULL);
728 if ( !Fred_running ) {
729 msgp->wave_info.index = add_wave(wave_name);
731 msgp->wave_info.name = strdup(wave_name);
743 read_file_text("messages.tbl");
748 required_string("#Personas");
749 while ( required_string_either("#Messages", "$Persona:")){
753 required_string("#Messages");
754 while (required_string_either("#End", "$Name:")){
758 required_string("#End");
760 // save the number of builting message things -- make initing between missions easier
761 Num_builtin_messages = Num_messages;
762 Num_builtin_avis = Num_message_avis;
763 Num_builtin_waves = Num_message_waves;
765 // close localization
769 // this is called at the start of each level
773 static int table_read = 0;
776 Command_persona = -1;
777 if ((rval = setjmp(parse_abort)) != 0) {
778 Error(LOCATION, "Error parsing '%s'\r\nError code = %i.\r\n", "messages.tbl", rval);
786 // reset the number of messages that we have for this mission
787 Num_messages = Num_builtin_messages;
788 Message_debug_index = Num_builtin_messages - 1;
789 Num_message_avis = Num_builtin_avis;
790 Num_message_waves = Num_builtin_waves;
792 // initialize the stuff for the linked lists of messages
794 for (i = 0; i < MAX_MESSAGE_Q; i++) {
795 MessageQ[i].priority = -1;
796 MessageQ[i].time_added = -1;
797 MessageQ[i].message_num = -1;
798 MessageQ[i].builtin_type = -1;
799 MessageQ[i].min_delay_stamp = -1;
800 MessageQ[i].group = 0;
803 // this forces a reload of the AVI's and waves for builtin messages. Needed because the flic and
804 // sound system also get reset between missions!
805 for (i = 0; i < Num_builtin_avis; i++ ) {
806 Message_avis[i].anim_data = NULL;
809 for (i = 0; i < Num_builtin_waves; i++ ){
810 Message_waves[i].num = -1;
813 Message_shipnum = -1;
814 Num_messages_playing = 0;
815 for ( i = 0; i < MAX_PLAYING_MESSAGES; i++ ) {
816 Playing_messages[i].anim = NULL;
817 Playing_messages[i].wave = -1;
818 Playing_messages[i].id = -1;
819 Playing_messages[i].priority = -1;
820 Playing_messages[i].shipnum = -1;
821 Playing_messages[i].builtin_type = -1;
824 // reinitialize the personas. mark them all as not used
825 for ( i = 0; i < Num_personas; i++ ){
826 Personas[i].flags &= ~PERSONA_FLAG_USED;
829 Message_wave_muted = 0;
832 memset(Message_times, 0, sizeof(int)*MAX_MISSION_MESSAGES);
836 void message_mission_free_avi(int m_index)
838 int rc = 0, try_count = 0;
840 // check for bogus index
841 if ( (m_index < 0) || (m_index >= Num_message_avis) )
844 // Make sure this code doesn't get run if the talking head guage is off
845 // helps prevent a crash on jump out if this code doesn't work right
846 if ( !hud_gauge_active(HUD_TALKING_HEAD) )
849 if (Message_avis[m_index].anim_data != NULL) {
851 rc = anim_free( Message_avis[m_index].anim_data );
854 // -2 is to catch a point where the data isn't valid and we want
855 // to just abort right now rather than to keep trying
859 // stop at 25 tries to avoid a possible endless loop
860 } while ( rc && (try_count < 25) );
862 Message_avis[m_index].anim_data = NULL;
866 // called to do cleanup when leaving a mission
867 void message_mission_shutdown()
871 mprintf(("Unloading in mission messages\n"));
873 training_mission_shutdown();
875 // kill/stop all playing messages sounds and animations if we need to
876 if (Num_messages_playing) {
880 // remove the wave sounds from memory
881 for (i = 0; i < Num_message_waves; i++ ) {
882 if ( Message_waves[i].num != -1 ){
883 snd_unload( Message_waves[i].num );
887 // free up remaining anim data
888 for ( i = 0; i<Num_message_avis; i++ ) {
889 message_mission_free_avi(i);
893 // functions to deal with queuing messages to the message system.
895 // Compare function for system qsort() for sorting message queue entries based on priority.
896 // Return values set to sort array in _decreasing_ order. If priorities equal, sort based
897 // on time added into queue
898 int message_queue_priority_compare(const void *a, const void *b)
902 ma = (message_q *) a;
903 mb = (message_q *) b;
905 if (ma->priority > mb->priority) {
907 } else if (ma->priority < mb->priority) {
909 } else if (ma->time_added < mb->time_added) {
911 } else if (ma->time_added > mb->time_added) {
918 // function to kill all currently playing messages. kill_all parameter tells us to
919 // kill only the animations that are playing, or wave files too
920 void message_kill_all( int kill_all )
924 SDL_assert( Num_messages_playing );
926 // kill sounds for all voices currently playing
927 for ( i = 0; i < Num_messages_playing; i++ ) {
928 if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) ) {
929 anim_stop_playing( Playing_messages[i].anim );
930 Playing_messages[i].anim=NULL;
934 if ( (Playing_messages[i].wave != -1 ) && snd_is_playing(Playing_messages[i].wave) ){
935 snd_stop( Playing_messages[i].wave );
938 Playing_messages[i].shipnum = -1;
943 Num_messages_playing = 0;
947 // function to kill nth playing message
948 void message_kill_playing( int message_num )
950 SDL_assert( message_num < Num_messages_playing );
952 if ( (Playing_messages[message_num].anim != NULL) && anim_playing(Playing_messages[message_num].anim) ) {
953 anim_stop_playing( Playing_messages[message_num].anim );
954 Playing_messages[message_num].anim=NULL;
956 if ( (Playing_messages[message_num].wave != -1 ) && snd_is_playing(Playing_messages[message_num].wave) )
957 snd_stop( Playing_messages[message_num].wave );
959 Playing_messages[message_num].shipnum = -1;
963 // returns true if all messages currently playing are builtin messages
964 int message_playing_builtin()
968 for ( i = 0; i < Num_messages_playing; i++ ) {
969 if ( Playing_messages[i].id >= Num_builtin_messages ){
974 // if we got through the list without breaking, all playing messages are builtin messages
975 if ( i == Num_messages_playing ){
982 // returns true in any playing message is of the specific builtin type
983 int message_playing_specific_builtin( int builtin_type )
987 for (i = 0; i < Num_messages_playing; i++ ) {
988 if ( (Playing_messages[i].id < Num_builtin_messages) && (Playing_messages[i].builtin_type == builtin_type) ){
996 // returns true if all messages current playing are unique messages
997 int message_playing_unique()
1001 for ( i = 0; i < Num_messages_playing; i++ ) {
1002 if ( Playing_messages[i].id < Num_builtin_messages ){
1007 // if we got through the list without breaking, all playing messages are builtin messages
1008 if ( i == Num_messages_playing ){
1016 // returns the highest priority of the currently playing messages
1017 #define MESSAGE_GET_HIGHEST 1
1018 #define MESSAGE_GET_LOWEST 2
1019 int message_get_priority(int which)
1024 if ( which == MESSAGE_GET_HIGHEST ){
1025 priority = MESSAGE_PRIORITY_LOW;
1027 priority = MESSAGE_PRIORITY_HIGH;
1030 for ( i = 0; i < Num_messages_playing; i++ ) {
1031 if ( (which == MESSAGE_GET_HIGHEST) && (Playing_messages[i].priority > priority) ){
1032 priority = Playing_messages[i].priority;
1033 } else if ( (which == MESSAGE_GET_LOWEST) && (Playing_messages[i].priority < priority) ){
1034 priority = Playing_messages[i].priority;
1042 // removes current message from the queue
1043 void message_remove_from_queue(message_q *q)
1045 // quick out if nothing to do.
1046 if ( MessageQ_num <= 0 ) {
1053 q->message_num = -1;
1054 q->builtin_type = -1;
1055 q->min_delay_stamp = -1;
1058 if ( MessageQ_num > 0 ) {
1059 qsort(MessageQ, MAX_MESSAGE_Q, sizeof(message_q), message_queue_priority_compare);
1063 // Load in the sound data for a message.
1065 // index - index into the Message_waves[] array
1067 void message_load_wave(int index, const char *filename)
1074 if ( Message_waves[index].num >= 0) {
1079 memset(&tmp_gs, 0, sizeof(game_snd));
1080 SDL_strlcpy( tmp_gs.filename, filename, sizeof(tmp_gs.filename) );
1081 Message_waves[index].num = snd_load( &tmp_gs );
1082 if ( Message_waves[index].num == -1 ) {
1083 nprintf (("messaging", "Cannot load message wave: %s. Will not play\n", Message_waves[index].name ));
1087 // Play wave file associated with message
1088 // input: m => pointer to message description
1090 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1091 void message_play_wave( message_q *q )
1095 char filename[MAX_FILENAME_LEN];
1097 // check for multiple messages playing. don't check builtin messages.
1098 if (q->message_num >= Num_builtin_messages) {
1099 if ( (f2fl(Missiontime - Message_times[q->message_num]) < 10) && (f2fl(Missiontime) > 10) ) {
1100 // Int3(); // Get Andsager
1102 Message_times[q->message_num] = Missiontime;
1105 m = &Messages[q->message_num];
1107 if ( m->wave_info.index != -1 ) {
1108 index = m->wave_info.index;
1111 SDL_assert( index != -1 );
1116 // if we need to bash the wave name because of "conversion" to terran command, do it here
1117 SDL_strlcpy( filename, Message_waves[index].name, sizeof(filename) );
1118 if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1119 char *p, new_filename[MAX_FILENAME_LEN];
1121 Message_waves[index].num = -1; // forces us to reload the message
1123 // bash the filename here. Look for "[1-6]_" at the front of the message. If found, then
1125 p = SDL_strchr(filename, '_' );
1127 mprintf(("Cannot convert %s to terran command wave -- find Sandeep or Allender\n", Message_waves[index].name));
1131 // prepend the command name, and then the rest of the filename.
1133 SDL_strlcpy( new_filename, COMMAND_WAVE_PREFIX, sizeof(new_filename) );
1134 SDL_strlcat( new_filename, p, sizeof(new_filename) );
1135 SDL_strlcpy( filename, new_filename, sizeof(filename) );
1138 // load the sound file into memory
1139 message_load_wave(index, filename);
1140 if ( Message_waves[index].num == -1 ) {
1141 m->wave_info.index = -1;
1144 if ( m->wave_info.index >= 0 ) {
1145 // this call relies on the fact that snd_play returns -1 if the sound cannot be played
1146 Message_wave_duration = snd_get_duration(Message_waves[index].num);
1147 Playing_messages[Num_messages_playing].wave = snd_play_raw( Message_waves[index].num, 0.0f );
1152 // Determine the starting frame for the animation
1153 // input: time => time of voice clip, in ms
1154 // ani => pointer to anim data
1155 // reverse => flag to indicate that the start should be time ms from the end (used for death screams)
1156 int message_calc_anim_start_frame(int time, anim *ani, int reverse)
1158 float wave_time, anim_time;
1163 // If no voice clip exists, start from beginning of anim
1168 // convert time to seconds
1169 wave_time = time/1000.0f;
1170 anim_time = ani->time;
1172 // If voice clip is longer than anim, start from beginning of anim
1173 if ( wave_time >= (anim_time) ) {
1178 start_frame = (ani->total_frames-1) - fl2i(ani->fps * wave_time + 0.5f);
1180 int num_frames_extra;
1181 num_frames_extra = fl2i(ani->fps * (anim_time - wave_time) + 0.5f);
1182 if ( num_frames_extra > 0 ) {
1183 start_frame=rand()%num_frames_extra;
1187 if ( start_frame < 0 ) {
1195 // Play animation associated with message
1196 // input: m => pointer to message description
1197 // q => message queue data
1199 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1200 void message_play_anim( message_q *q )
1202 message_extra *anim_info;
1203 int is_death_scream=0, persona_index=-1, rand_index=-1;
1204 char ani_name[MAX_FILENAME_LEN], *p;
1207 // don't even bother with this stuff if the gauge is disabled
1208 if ( !hud_gauge_active(HUD_TALKING_HEAD) ) {
1212 m = &Messages[q->message_num];
1214 // check to see if the avi_index is valid -- try and load/play the avi if so.
1215 if ( m->avi_info.index < 0 ) {
1219 anim_info = &Message_avis[m->avi_info.index];
1221 // get the filename. Strip off the extension since we won't need it anyway
1222 SDL_strlcpy(ani_name, anim_info->name, sizeof(ani_name));
1223 p = SDL_strchr(ani_name, '.'); // gets us to the extension
1228 // builtin messages are given a base ani which we should add a suffix on before trying
1229 // to load the animation. See if this message is a builtin message which has a persona
1230 // attached to it. Deal with munging the name
1232 // support ships use a wingman head.
1233 // terran command uses it's own set of heads.
1234 int subhead_selected = FALSE;
1235 if ( (q->message_num < Num_builtin_messages) || !(SDL_strncasecmp(HEAD_PREFIX_STRING, ani_name, strlen(HEAD_PREFIX_STRING)-1)) ) {
1236 persona_index = m->persona_index;
1238 // if this ani should be converted to a terran command, set the persona to the command persona
1239 // so the correct head plays.
1240 if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1241 persona_index = Command_persona;
1242 SDL_strlcpy( ani_name, COMMAND_HEAD_PREFIX, sizeof(ani_name) );
1245 if ( Personas[persona_index].flags & (PERSONA_FLAG_WINGMAN | PERSONA_FLAG_SUPPORT) ) {
1246 // get a random head -- it's one of two.
1247 if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1248 rand_index=2; // 3rd version is always death animation
1251 rand_index = (Missiontime % MAX_WINGMAN_HEADS);
1253 SDL_snprintf(ani_name, sizeof(ani_name), "%s%c", ani_name, 'a'+rand_index);
1254 subhead_selected = TRUE;
1255 } else if ( Personas[persona_index].flags & (PERSONA_FLAG_COMMAND | PERSONA_FLAG_LARGE) ) {
1256 // get a random head -- it's one of two.
1257 rand_index = (Missiontime % MAX_COMMAND_HEADS);
1258 SDL_snprintf(ani_name, sizeof(ani_name), "%s%c", ani_name, 'a'+rand_index);
1259 subhead_selected = TRUE;
1261 if (!subhead_selected) {
1262 // choose between a and b
1263 rand_index = (Missiontime % MAX_WINGMAN_HEADS);
1264 SDL_snprintf(ani_name, sizeof(ani_name), "%s%c", ani_name, 'a'+rand_index);
1265 mprintf(("message '%s' with invalid head. Fix by assigning persona to the message.\n", m->name));
1267 nprintf(("Messaging", "playing head %s for %s\n", ani_name, q->who_from));
1270 // check to see if the avi has been loaded. If not, then load the AVI. On an error loading
1271 // the avi, set the top level index to -1 to avoid multiple tries at loading the flick.
1273 // if there is something already here that's not this same file then go ahead a let go of it
1274 if ( (anim_info->anim_data != NULL) && !strstr(anim_info->anim_data->name, ani_name) )
1275 message_mission_free_avi( m->avi_info.index );
1277 anim_info->anim_data = anim_load( ani_name );
1279 if ( anim_info->anim_data == NULL ) {
1280 nprintf (("messaging", "Cannot load message avi %s. Will not play.\n", ani_name));
1281 m->avi_info.index = -1; // if cannot load the avi -- set this index to -1 to avoid trying to load multiple times
1284 if ( m->avi_info.index >= 0 ) {
1285 // This call relies on the fact that AVI_play will return -1 if the AVI cannot be played
1286 // if any messages are already playing, kill off any head anims that are currently playing. We will
1287 // only play a head anim of the newest messages being played
1288 if ( Num_messages_playing > 0 ) {
1289 nprintf(("messaging", "killing off any currently playing head animations\n"));
1290 message_kill_all( 0 );
1293 int anim_start_frame;
1294 anim_play_struct aps;
1296 // figure out anim start frame
1297 anim_start_frame = message_calc_anim_start_frame(Message_wave_duration, anim_info->anim_data, is_death_scream);
1298 anim_play_init(&aps, anim_info->anim_data, Head_coords[gr_screen.res][0], Head_coords[gr_screen.res][1]);
1299 aps.start_at = anim_start_frame;
1301 // aps.color = &HUD_color_defaults[HUD_color_alpha];
1302 aps.color = &HUD_config.clr[HUD_TALKING_HEAD];
1304 Playing_messages[Num_messages_playing].anim = anim_play(&aps);
1308 // process the message queue -- called once a frame
1309 void message_queue_process()
1316 // Don't play messages until first frame has been rendered
1317 if ( Framecount < 2 ) {
1321 // determine if all playing messages (if any) are done playing. If any are done, remove their
1322 // entries collapsing the Playing_messages array if necessary
1323 if ( Num_messages_playing > 0 ) {
1325 // for each message playing, determine if it is done.
1327 while ( i < Num_messages_playing ) {
1328 int ani_done, wave_done, j;
1331 if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) )
1336 // if ( (Playing_messages[i].wave != -1) && snd_is_playing(Playing_messages[i].wave) )
1337 if ( (Playing_messages[i].wave != -1) && (snd_time_remaining(Playing_messages[i].wave) > 250) )
1340 // AL 1-20-98: If voice message is done, kill the animation early
1341 if ( (Playing_messages[i].wave != -1) && wave_done ) {
1343 anim_stop_playing( Playing_messages[i].anim );
1347 // see if the ship sending this message is dying. If do, kill wave and anim
1348 if ( Playing_messages[i].shipnum != -1 ) {
1349 if ( (Ships[Playing_messages[i].shipnum].flags & SF_DYING) && (Playing_messages[i].builtin_type != MESSAGE_WINGMAN_SCREAM) ) {
1352 shipnum = Playing_messages[i].shipnum;
1353 message_kill_playing( i );
1354 // force this guy to scream
1355 // AL 22-2-98: Ensure don't use -1 to index into ships array. Mark, something is incorrect
1356 // here, since message_kill_playing() seems to always set Playing_messages[i].shipnum to -1
1357 // MWA 3/24/98 -- save shipnum before killing message
1359 SDL_assert( shipnum >= 0 );
1360 if ( !(Ships[shipnum].flags & SF_SHIP_HAS_SCREAMED) ) {
1361 ship_scream( &Ships[shipnum] );
1363 continue; // this should keep us in the while() loop with same value of i.
1364 } // we should enter the next 'if' statement during next pass
1367 // if both ani and wave are done, mark internal variable so we can do next message on queue, and
1368 // global variable to clear voice brackets on hud
1369 if ( wave_done && ani_done ) {
1370 nprintf(("messaging", "Message %d is done playing\n", i));
1371 Message_shipnum = -1;
1372 Num_messages_playing--;
1373 if ( Num_messages_playing == 0 )
1376 // there is still another message playing. Collapse the playing_message array
1377 nprintf(("messaging", "Collapsing playing message stack\n"));
1378 for ( j = i+1; j < Num_messages_playing + 1; j++ ) {
1379 Playing_messages[j-1] = Playing_messages[j];
1382 // messages is not done playing -- move to next message
1388 // preprocess message queue and remove anything on the queue that is too old. If next message on
1389 // the queue can be played, then break out of the loop. Otherwise, loop until nothing on the queue
1390 while ( MessageQ_num > 0 ) {
1392 if ( timestamp_valid(q->window_timestamp) && timestamp_elapsed(q->window_timestamp) && !q->group) {
1393 // remove message from queue and see if more to remove
1394 nprintf(("messaging", "Message %s didn't play because it didn't fit into time window.\n", Messages[q->message_num].name));
1395 if ( q->message_num < Num_builtin_messages ){ // we should only ever remove builtin messages this way
1396 message_remove_from_queue(q);
1405 // no need to process anything if there isn't anything on the queue
1406 if ( MessageQ_num <= 0 ){
1410 // get a pointer to an item on the queue
1413 while((found == -1) && (idx < MessageQ_num)){
1414 // if this guy has no min delay timestamp, or it has expired, select him
1415 if((MessageQ[idx].min_delay_stamp == -1) || timestamp_elapsed(MessageQ[idx].min_delay_stamp)){
1424 // if we didn't find anything, bail
1428 // if this is not the first item on the queue, make it the first item
1433 memcpy(&temp, &MessageQ[found], sizeof(message_q));
1435 // move all other entries up
1436 for(idx=found; idx>0; idx--){
1437 memcpy(&MessageQ[idx], &MessageQ[idx-1], sizeof(message_q));
1440 // plop the entry down as being first
1441 memcpy(&MessageQ[0], &temp, sizeof(message_q));
1445 SDL_assert ( q->message_num != -1 );
1446 SDL_assert ( q->priority != -1 );
1447 SDL_assert ( q->time_added != -1 );
1449 if ( Num_messages_playing ) {
1450 // peek at the first message on the queue to see if it should interrupt, or overlap a currently
1451 // playing message. Mission specific messages will always interrupt builtin messages. They
1452 // will never interrupt other mission specific messages.
1454 // Builtin message might interrupt other builtin messages, or overlap them, all depending on
1455 // message priority.
1457 if ( q->builtin_type == MESSAGE_HAMMER_SWINE ) {
1458 message_kill_all(1);
1459 } else if ( message_playing_specific_builtin(MESSAGE_HAMMER_SWINE) ) {
1462 } else if ( message_playing_builtin() && ( q->message_num >= Num_builtin_messages) && (q->priority > MESSAGE_PRIORITY_LOW) ) {
1463 // builtin is playing and we have a unique message to play. Kill currently playing message
1464 // so unique can play uninterrupted. Only unique messages higher than low priority will interrupt
1466 message_kill_all(1);
1467 nprintf(("messaging", "Killing all currently playing messages to play unique message\n"));
1468 } else if ( message_playing_builtin() && (q->message_num < Num_builtin_messages) ) {
1469 // when a builtin message is queued, we might either overlap or interrupt the currently
1472 // we have to check for num_messages_playing (again), since code for death scream might
1473 // kill all messages.
1474 if ( Num_messages_playing ) {
1475 if ( message_get_priority(MESSAGE_GET_HIGHEST) < q->priority ) {
1476 // lower priority message playing -- kill it.
1477 message_kill_all(1);
1478 nprintf(("messaging", "Killing all currently playing messages to play high priority builtin\n"));
1479 } else if ( message_get_priority(MESSAGE_GET_LOWEST) > q->priority ) {
1480 // queued message is a lower priority, so wait it out
1483 // if we get here, then queued messages is a builtin message with the same priority
1484 // as the currently playing messages. This state will cause messages to overlap.
1485 nprintf(("messaging", "playing builtin message (overlap) because priorities match\n"));
1488 } else if ( message_playing_unique() && (q->message_num < Num_builtin_messages) ) {
1489 // code messages can kill any low priority mission specific messages
1490 if ( Num_messages_playing ) {
1491 if ( message_get_priority(MESSAGE_GET_HIGHEST) == MESSAGE_PRIORITY_LOW ) {
1492 message_kill_all(1);
1493 nprintf(("messaging", "Killing low priority unique messages to play code message\n"));
1495 return; // do nothing.
1503 // if we are playing the maximum number of voices, then return. Make the check here since the above
1504 // code might kill of currently playing messages
1505 if ( Num_messages_playing == MAX_PLAYING_MESSAGES )
1508 Message_shipnum = ship_name_lookup( q->who_from );
1510 // see if we need to check if sending ship is alive
1511 if ( q->flags & MQF_CHECK_ALIVE ) {
1512 if ( Message_shipnum == -1 ) {
1517 // if this is a ship, then don't play anything if this ship is already talking
1518 if ( Message_shipnum != -1 ) {
1519 for ( i = 0; i < Num_messages_playing; i++ ) {
1520 if ( (Playing_messages[i].shipnum != -1) && (Playing_messages[i].shipnum == Message_shipnum) ){
1526 // set up module globals for this message
1527 m = &Messages[q->message_num];
1528 Playing_messages[Num_messages_playing].anim = NULL;
1529 Playing_messages[Num_messages_playing].wave = -1;
1530 Playing_messages[Num_messages_playing].id = q->message_num;
1531 Playing_messages[Num_messages_playing].priority = q->priority;
1532 Playing_messages[Num_messages_playing].shipnum = Message_shipnum;
1533 Playing_messages[Num_messages_playing].builtin_type = q->builtin_type;
1535 Message_wave_duration = 0;
1537 // translate tokens in message to the real things
1538 message_translate_tokens(buf, sizeof(buf), m->message);
1540 // AL: added 07/14/97.. only play avi/sound if in gameplay
1541 if ( gameseq_get_state() != GS_STATE_GAME_PLAY )
1544 // AL 4-7-98: Can't receive messages if comm is destroyed
1545 if ( hud_communications_state(Player_ship) == COMM_DESTROYED ) {
1549 // Don't play death scream unless a small ship.
1550 if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1551 int t = Ship_info[Ships[Message_shipnum].ship_info_index].flags;
1552 int t2 = SIF_SMALL_SHIP;
1559 // play wave first, since need to know duration for picking anim start frame
1560 message_play_wave(q);
1562 // play animation for head
1563 #ifndef DEMO // do we want this for FS2_DEMO
1564 message_play_anim(q);
1567 // distort the message if comms system is damaged
1568 message_maybe_distort_text(buf);
1571 // debug only -- if the message is a builtin message, put in parens whether or not the voice played
1572 if ( Playing_messages[Num_messages_playing].wave == -1 ) {
1573 SDL_strlcat( buf, NOX("..(no wavefile for voice)"), sizeof(buf));
1574 snd_play(&Snds[SND_CUE_VOICE]);
1578 HUD_sourced_printf( q->source, NOX("%s: %s"), q->who_from, buf );
1580 if ( Message_shipnum >= 0 ) {
1581 hud_target_last_transmit_add(Message_shipnum);
1585 Num_messages_playing++;
1586 message_remove_from_queue( q );
1589 // queues up a message to display to the player
1590 void message_queue_message( int message_num, int priority, int timing, const char *who_from, int source, int group, int delay, int builtin_type )
1594 if ( message_num < 0 ) return;
1596 // some messages can get queued quickly. Try to filter out certain types of messages before
1597 // they get queued if there are other messages of the same type already queued
1598 if ( (builtin_type == MESSAGE_REARM_ON_WAY) || (builtin_type == MESSAGE_OOPS) ) {
1599 // if it is already playing, then don't play it
1600 if ( message_playing_specific_builtin(builtin_type) )
1603 for ( i = 0; i < MessageQ_num; i++ ) {
1604 // if one of these messages is already queued, the don't play
1605 if ( (MessageQ[i].message_num == message_num) && (MessageQ[i].builtin_type == builtin_type) )
1611 // check to be sure that we haven't reached our max limit on these messages yet.
1612 if ( MessageQ_num == MAX_MESSAGE_Q ) {
1617 // if player is a traitor, no messages for him!!!
1618 if ( Player_ship->team == TEAM_TRAITOR ) {
1622 m_persona = Messages[message_num].persona_index;
1624 // put the message into a slot
1626 MessageQ[i].time_added = Missiontime;
1627 MessageQ[i].priority = priority;
1628 MessageQ[i].message_num = message_num;
1629 MessageQ[i].source = source;
1630 MessageQ[i].builtin_type = builtin_type;
1631 MessageQ[i].min_delay_stamp = timestamp(delay);
1632 MessageQ[i].group = group;
1633 SDL_strlcpy(MessageQ[i].who_from, who_from, NAME_LENGTH);
1635 // SPECIAL HACK -- if the who_from is terran command, and there is a wingman persona attached
1636 // to this message, then set a bit to tell the wave/anim playing code to play the command version
1637 // of the wave and head
1638 MessageQ[i].flags = 0;
1639 if ( !SDL_strcasecmp(who_from, TERRAN_COMMAND) && (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1640 MessageQ[i].flags |= MQF_CONVERT_TO_COMMAND;
1641 MessageQ[i].source = HUD_SOURCE_TERRAN_CMD;
1644 if ( (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1645 if ( !strstr(who_from, ".wav") ) {
1646 MessageQ[i].flags |= MQF_CHECK_ALIVE;
1650 // set the timestamp of when to play this message based on the 'timing' value
1651 if ( timing == MESSAGE_TIME_IMMEDIATE )
1652 MessageQ[i].window_timestamp = timestamp(MESSAGE_IMMEDIATE_TIMESTAMP);
1653 else if ( timing == MESSAGE_TIME_SOON )
1654 MessageQ[i].window_timestamp = timestamp(MESSAGE_SOON_TIMESTAMP);
1656 MessageQ[i].window_timestamp = timestamp(MESSAGE_ANYTIME_TIMESTAMP); // make invalid
1659 qsort(MessageQ, MAX_MESSAGE_Q, sizeof(message_q), message_queue_priority_compare);
1662 // MWA -- called every frame from game loop
1663 //message_queue_process();
1666 // function to return the persona index of the given ship. If it isn't assigned, it will be
1667 // in this function. persona_type could be a wingman, Terran Command, or other generic ship
1668 // type personas. ship is the ship we should assign a persona to
1669 int message_get_persona( ship *shipp )
1671 int i, ship_type, slist[MAX_PERSONAS], count;
1673 if ( shipp != NULL ) {
1674 // see if this ship has a persona
1675 if ( shipp->persona_index != -1 )
1676 return shipp->persona_index;
1678 // get the type of ship (i.e. support, fighter/bomber, etc)
1679 ship_type = Ship_info[shipp->ship_info_index].flags;
1681 // shorcut for Vasudan personas. All vasudan fighters/bombers use the same persona. All Vasudan
1682 // large ships will use the same persona
1683 if ( Ship_info[shipp->ship_info_index].species == SPECIES_VASUDAN ) {
1686 if ( ship_type & (SIF_FIGHTER|SIF_BOMBER) ) {
1687 persona_needed = PERSONA_FLAG_WINGMAN;
1688 } else if ( ship_type & SIF_SUPPORT ) {
1689 persona_needed = PERSONA_FLAG_SUPPORT;
1691 persona_needed = PERSONA_FLAG_LARGE;
1694 // iternate through the persona list finding the one that we need
1695 for ( i = 0; i < Num_personas; i++ ) {
1696 if ( (Personas[i].flags & persona_needed) && (Personas[i].flags & PERSONA_FLAG_VASUDAN) ) {
1697 nprintf(("messaging", "assigning vasudan persona %s to %s\n", Personas[i].name, shipp->ship_name));
1702 // make support personas use the terran persona by not returning here when looking for
1704 if ( persona_needed != PERSONA_FLAG_SUPPORT )
1705 return -1; // shouldn't get here eventually, but return -1 for now to deal with missing persona
1708 // iterate through the persona list looking for one not used. Look at the type of persona
1709 // and try to determine appropriate personas to use.
1710 for ( i = 0; i < Num_personas; i++ ) {
1712 // if this is a vasudan persona -- skip it
1713 if ( Personas[i].flags & PERSONA_FLAG_VASUDAN )
1716 // check the ship types, and don't try to assign those which don't type match
1717 if ( (ship_type & SIF_SUPPORT) && !(Personas[i].flags & PERSONA_FLAG_SUPPORT) )
1719 else if ( (ship_type & (SIF_FIGHTER|SIF_BOMBER)) && !(Personas[i].flags & PERSONA_FLAG_WINGMAN) )
1721 else if ( !(ship_type & (SIF_FIGHTER|SIF_BOMBER|SIF_SUPPORT)) && !(Personas[i].flags & PERSONA_FLAG_LARGE) )
1724 if ( !(Personas[i].flags & PERSONA_FLAG_USED) ) {
1725 nprintf(("messaging", "assigning persona %s to %s\n", Personas[i].name, shipp->ship_name));
1726 Personas[i].flags |= PERSONA_FLAG_USED;
1731 // grab a random one, and reuse it (staying within type specifications)
1733 for ( i = 0; i < Num_personas; i++ ) {
1735 // see if ship meets our criterea
1736 if ( (ship_type & SIF_SUPPORT) && !(Personas[i].flags & PERSONA_FLAG_SUPPORT) )
1738 else if ( (ship_type & (SIF_FIGHTER|SIF_BOMBER)) && !(Personas[i].flags & PERSONA_FLAG_WINGMAN) )
1740 else if ( !(ship_type & (SIF_FIGHTER|SIF_BOMBER|SIF_SUPPORT)) && !(Personas[i].flags & PERSONA_FLAG_LARGE) )
1742 else if ( Personas[i].flags & PERSONA_FLAG_VASUDAN ) // don't use any vasudan persona
1749 // couldn't find appropriate persona type
1753 // now get a random one from the list
1754 i = (rand() % count);
1757 nprintf(("messaging", "Couldn't find a new persona for ship %s, reusing persona %s\n", shipp->ship_name, Personas[i].name));
1762 // for now -- we don't support other types of personas (non-wingman personas)
1767 // given a message id#, should it be filtered for me?
1768 int message_filter_multi(int id)
1771 if(!(Game_mode & GM_MULTIPLAYER)){
1776 if((id < 0) || (id >= Num_messages)){
1777 mprintf(("Filtering bogus mission message!\n"));
1782 if(id < Num_builtin_messages){
1784 // mission-specific messages
1786 // not team filtered
1787 if(Messages[id].multi_team < 0){
1792 if(!(Netgame.type_flags & NG_TYPE_TEAM)){
1796 // is this for my team?
1797 if((Net_player != NULL) && (Net_player->p_info.team != Messages[id].multi_team)){
1798 mprintf(("Filtering team-based mission message!\n"));
1806 // send_unique_to_player sends a mission unique (specific) message to the player (possibly a multiplayer
1807 // person). These messages are *not* the builtin messages
1808 void message_send_unique_to_player( char *id, void *data, int m_source, int priority, int group, int delay )
1811 const char *who_from;
1815 for (i=0; i<Num_messages; i++) {
1817 if ( !SDL_strcasecmp(id, Messages[i].name) ) {
1819 // if the ship pointer and special_who are both NULL then this is from generic "Terran Command"
1820 // if the ship is NULL and special_who is not NULL, then this is from special_who
1821 // otherwise, message is from ship.
1822 if ( m_source == MESSAGE_SOURCE_COMMAND ) {
1823 who_from = TERRAN_COMMAND;
1824 source = HUD_SOURCE_TERRAN_CMD;
1825 } else if ( m_source == MESSAGE_SOURCE_SPECIAL ) {
1826 who_from = (const char *)data;
1827 source = HUD_SOURCE_TERRAN_CMD;
1828 } else if ( m_source == MESSAGE_SOURCE_WINGMAN ) {
1829 int m_persona, ship_index;
1831 // find a wingman with the same persona as this message. If the message's persona doesn't
1832 // exist, we will use Terran command
1833 m_persona = Messages[i].persona_index;
1834 if ( m_persona == -1 ) {
1835 mprintf(("Warning: Message %d has no persona assigned.\n", i));
1839 ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS, 0.0f, m_persona, 1, Messages[i].multi_team);
1841 // if the ship_index is -1, then make the message come from Terran command
1842 if ( ship_index == -1 ) {
1843 who_from = TERRAN_COMMAND;
1844 source = HUD_SOURCE_TERRAN_CMD;
1846 who_from = Ships[ship_index].ship_name;
1847 source = HUD_get_team_source(Ships[ship_index].team);
1850 } else if ( m_source == MESSAGE_SOURCE_SHIP ) {
1853 shipp = (ship *)data;
1854 who_from = shipp->ship_name;
1855 source = HUD_get_team_source(shipp->team);
1857 // be sure that this ship can actually send a message!!! (i.e. not-not-flyable -- get it!)
1858 SDL_assert( !(Ship_info[shipp->ship_info_index].flags & SIF_NOT_FLYABLE) ); // get allender or alan
1861 // not multiplayer or this message is for me, then queue it
1862 // if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
1864 // maybe filter it out altogether
1865 if(!message_filter_multi(i)){
1866 message_queue_message( i, priority, MESSAGE_TIME_ANYTIME, who_from, source, group, delay );
1869 // record to the demo if necessary
1870 if(Game_mode & GM_DEMO_RECORD){
1871 demo_POST_unique_message(id, who_from, m_source, priority);
1875 // send a message packet to a player if destined for everyone or only a specific person
1876 if ( MULTIPLAYER_MASTER ){
1877 send_mission_message_packet( i, who_from, priority, MESSAGE_TIME_SOON, source, -1, -1, -1);
1880 return; // all done with displaying
1883 nprintf (("messaging", "Couldn't find message id %s to send to player!\n", id ));
1886 // send builtin_to_player sends a message (from messages.tbl) to the player. These messages are
1887 // the generic infomrational type messages. The have priorities like misison specific messages,
1888 // and use a timing to tell how long we should wait before playing this message
1889 void message_send_builtin_to_player( int type, ship *shipp, int priority, int timing, int group, int delay, int multi_target, int multi_team_filter )
1891 int i, persona_index;
1894 // if we aren't showing builtin msgs, bail
1895 if (The_mission.flags & MISSION_FLAG_NO_BUILTIN_MSGS) {
1899 // see if there is a persona assigned to this ship. If not, then try to assign one!!!
1901 if ( shipp->persona_index == -1 ){
1902 shipp->persona_index = message_get_persona( shipp );
1905 persona_index = shipp->persona_index;
1906 if ( persona_index == -1 ) {
1907 nprintf(("messaging", "Couldn't find persona for %s\n", shipp->ship_name ));
1910 // be sure that this ship can actually send a message!!! (i.e. not-not-flyable -- get it!)
1911 SDL_assert( !(Ship_info[shipp->ship_info_index].flags & SIF_NOT_FLYABLE) ); // get allender or alan
1913 persona_index = Command_persona; // use the terran command persona
1916 // try to find a builtin message with the given type for the given persona
1917 // make a loop out of this routne since we may try to play a message in the wrong
1918 // persona if we can't find the right message for the given persona
1920 for ( i = 0; i < Num_builtin_messages; i++ ) {
1921 const char *name, *who_from;
1923 name = Builtin_message_types[type];
1925 // see if the have the type of message
1926 if ( SDL_strcasecmp(Messages[i].name, name) ){
1930 // must have the correct persona. persona_index of -1 means find the first
1931 // message possibly of the correct type
1932 if ( (persona_index != -1 ) && (Messages[i].persona_index != persona_index) ){
1936 // get who this message is from -- kind of a hack since we assume Terran Command in the
1937 // absense of a ship. This will be fixed later
1939 source = HUD_get_team_source( shipp->team );
1940 who_from = shipp->ship_name;
1942 source = HUD_SOURCE_TERRAN_CMD;
1943 who_from = TERRAN_COMMAND;
1946 // maybe change the who from here for special rearm cases (always seems like that is the case :-) )
1947 if ( !SDL_strcasecmp(who_from, TERRAN_COMMAND) && (type == MESSAGE_REARM_ON_WAY) ){
1948 who_from = SUPPORT_NAME;
1951 // determine what we should actually do with this dang message. In multiplayer, we must
1952 // deal with the fact that this message might not get played on my machine if I am a server
1954 // not multiplayer or this message is for me, then queue it
1955 if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
1957 // if this filter matches mine
1958 if( (multi_team_filter < 0) || !(Netgame.type_flags & NG_TYPE_TEAM) || ((Net_player != NULL) && (Net_player->p_info.team == multi_team_filter)) ){
1959 message_queue_message( i, priority, timing, who_from, source, group, delay, type );
1961 // post a builtin message
1962 if(Game_mode & GM_DEMO_RECORD){
1963 demo_POST_builtin_message(type, shipp, priority, timing);
1968 // send a message packet to a player if destined for everyone or only a specific person
1969 if ( MULTIPLAYER_MASTER ) {
1970 // only send a message if it is of a particular type
1971 if(multi_target == -1){
1972 if(multi_message_should_broadcast(type)){
1973 send_mission_message_packet( i, who_from, priority, timing, source, type, -1, multi_team_filter );
1976 send_mission_message_packet( i, who_from, priority, timing, source, type, multi_target, multi_team_filter );
1980 return; // all done with displaying
1983 if ( persona_index >= 0 ) {
1984 nprintf(("messaging", "Couldn't find builtin message %s for persona %d\n", Builtin_message_types[type], persona_index ));
1985 nprintf(("messaging", "looking for message for any persona\n"));
1988 persona_index = -999; // used here and the next line only -- hard code bad, but I'm lazy
1990 } while ( persona_index != -999 );
1993 // message_is_playing()
1995 // Return the Message_playing flag. Message_playing is local to MissionMessage.cpp, but
1996 // this info is needed by code in HUDsquadmsg.cpp
1998 int message_is_playing()
2000 return Num_messages_playing?1:0;
2003 // Functions below pertain only to personas!!!!
2005 // given a character string, try to find the persona index
2006 int message_persona_name_lookup( const char *name )
2010 for (i = 0; i < Num_personas; i++ ) {
2011 if ( !SDL_strcasecmp(Personas[i].name, name) )
2019 // Blank out portions of the audio playback for the sound identified by Message_wave
2020 // This works by using the same Distort_pattern[][] that was used to distort the associated text
2021 void message_maybe_distort()
2026 if ( Num_messages_playing == 0 )
2029 for ( i = 0; i < Num_messages_playing; i++ ) {
2030 if ( !snd_is_playing(Playing_messages[i].wave) )
2034 // distort the number of voices currently playing
2035 for ( i = 0; i < Num_messages_playing; i++ ) {
2036 SDL_assert(Playing_messages[i].wave >= 0 );
2040 // added check to see if EMP effect was active
2042 if ( (hud_communications_state(Player_ship) != COMM_OK) || emp_active_local() ) {
2043 was_muted = Message_wave_muted;
2044 if ( timestamp_elapsed(Next_mute_time) ) {
2045 Next_mute_time = fl2i(Distort_patterns[Distort_num][Distort_next++] * Message_wave_duration);
2046 if ( Distort_next >= MAX_DISTORT_LEVELS )
2049 Message_wave_muted ^= 1;
2052 if ( Message_wave_muted ) {
2054 snd_set_volume(Playing_messages[i].wave, 0.0f);
2057 snd_set_volume(Playing_messages[i].wave, Master_sound_volume);
2064 // if the player communications systems are heavily damaged, distort incoming messages.
2066 // first case: Message_wave_duration == 0 (this occurs when there is no associated voice playback)
2067 // Blank out random runs of characters in the message
2069 // second case: Message_wave_duration > 0 (occurs when voice playback accompainies message)
2070 // Blank out portions of the sound based on Distort_num, this this is that same
2071 // data that will be used to blank out portions of the audio playback
2073 void message_maybe_distort_text(char *text)
2075 int i, j, len, run, curr_offset, voice_duration, next_distort;
2077 if ( (hud_communications_state(Player_ship) == COMM_OK) && !emp_active_local() ) {
2082 if ( Message_wave_duration == 0 ) {
2083 next_distort = 5+myrand()%5;
2084 for ( i = 0; i < len; i++ ) {
2085 if ( i == next_distort ) {
2089 for ( j = 0; j < run; j++) {
2094 next_distort = i + (5+myrand()%5);
2100 voice_duration = Message_wave_duration;
2103 Distort_num = myrand()%MAX_DISTORT_PATTERNS;
2106 while (voice_duration > 0) {
2107 run = fl2i(Distort_patterns[Distort_num][Distort_next] * len);
2108 if (Distort_next & 1) {
2109 for ( i = curr_offset; i < min(len, curr_offset+run); i++ ) {
2110 if ( text[i] != ' ' )
2120 voice_duration -= fl2i(Distort_patterns[Distort_num][Distort_next]*Message_wave_duration);
2122 if ( Distort_next >= MAX_DISTORT_LEVELS )
2129 // return 1 if a talking head animation is playing, otherwise return 0
2130 int message_anim_is_playing()
2134 for (i = 0; i < Num_messages_playing; i++ ) {
2135 if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) )
2142 // Load mission messages (this is called by the level paging code when running with low memory)
2143 void message_pagein_mission_messages()
2147 mprintf(("Paging in mission messages\n"));
2149 if (Num_messages <= Num_builtin_messages) {
2153 char *sound_filename;
2155 for (i=Num_builtin_messages; i<Num_messages; i++) {
2156 if (Messages[i].wave_info.index != -1) {
2157 sound_filename = Message_waves[Messages[i].wave_info.index].name;
2158 message_load_wave(Messages[i].wave_info.index, sound_filename);