]> icculus.org git repositories - taylor/freespace2.git/blob - src/mission/missionmessage.cpp
added copyright header
[taylor/freespace2.git] / src / mission / missionmessage.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Mission/MissionMessage.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Controls messaging to player during the mission
16  *
17  * $Log$
18  * Revision 1.3  2002/06/09 04:41:22  relnev
19  * added copyright header
20  *
21  * Revision 1.2  2002/05/07 03:16:46  theoddone33
22  * The Great Newline Fix
23  *
24  * Revision 1.1.1.1  2002/05/03 03:28:10  root
25  * Initial import.
26  *
27  * 
28  * 32    9/12/99 8:09p Dave
29  * Fixed problem where skip-training button would cause mission messages
30  * not to get paged out for the current mission.
31  * 
32  * 31    9/01/99 2:52p Andsager
33  * Add new heads to FRED and some debug code for playing heads
34  * 
35  * 30    8/28/99 7:29p Dave
36  * Fixed wingmen persona messaging. Make sure locked turrets don't count
37  * towards the # attacking a player.
38  * 
39  * 29    8/26/99 8:51p Dave
40  * Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
41  * 
42  * 28    8/23/99 5:04p Jefff
43  * Added new mission flag to disable built-in messages from playing.
44  * Added fred support as well.
45  * 
46  * 27    8/19/99 10:12a Alanl
47  * preload mission-specific messages on machines greater than 48MB
48  * 
49  * 26    8/18/99 12:09p Andsager
50  * Add debug if message has no anim for message.  Make messages come from
51  * wing leader.
52  * 
53  * 25    7/31/99 2:30p Dave
54  * Added nifty mission message debug viewing keys.
55  * 
56  * 24    7/24/99 2:19p Dave
57  * Fixed broken build.
58  * 
59  * 23    7/23/99 5:44p Andsager
60  * make personas consistently choose same ship
61  * 
62  * 22    7/15/99 9:20a Andsager
63  * FS2_DEMO initial checkin
64  * 
65  * 21    7/14/99 4:27p Andsager
66  * Added multiple message debug check
67  * 
68  * 20    7/06/99 10:41a Andsager
69  * Add AWACS need help messages
70  * 
71  * 19    7/02/99 11:16a Andsager
72  * Removed mult message debug check.
73  * 
74  * 18    7/02/99 11:13a Andsager
75  * max debug version
76  * 
77  * 17    6/16/99 10:20a Dave
78  * Added send-message-list sexpression.
79  * 
80  * 16    6/14/99 5:53p Dave
81  * Removed duplicate message check temporarily.
82  * 
83  * 15    6/10/99 3:43p Dave
84  * Do a better job of syncing text colors to HUD gauges.
85  * 
86  * 14    6/09/99 2:56p Andsager
87  * Check all messages for repeat.  Allow multiple versions of same message
88  * if queued > 20 apart.
89  * 
90  * 13    6/07/99 11:33a Anoop
91  * Get rid of erroneous Int3() in multiple message check.
92  * 
93  * 12    6/07/99 10:31a Andsager
94  * Get rid of false multiplayer multiple messages catch.
95  * 
96  * 11    6/03/99 2:56p Andsager
97  * DOH!!
98  * 
99  * 10    6/03/99 2:44p Andsager
100  * Fix stupid bug in debug code.
101  * 
102  * 9     6/03/99 2:08p Andsager
103  * Put in debug code to find multiple mission messages.
104  * 
105  * 8     3/29/99 6:17p Dave
106  * More work on demo system. Got just about everything in except for
107  * blowing ships up, secondary weapons and player death/warpout.
108  * 
109  * 7     1/28/99 12:19a Dave
110  * Fixed a dumb debug build unhandled exception.
111  * 
112  * 6     1/07/99 10:08a Jasen
113  * coords
114  * 
115  * 5     1/07/99 9:24a Dave
116  * Put in hi-res coord support for head anim.
117  * 
118  * 4     11/05/98 4:18p Dave
119  * First run nebula support. Beefed up localization a bit. Removed all
120  * conditional compiles for foreign versions. Modified mission file
121  * format.
122  * 
123  * 3     10/23/98 3:51p Dave
124  * Full support for tstrings.tbl and foreign languages. All that remains
125  * is to make it active in Fred.
126  * 
127  * 2     10/07/98 10:53a Dave
128  * Initial checkin.
129  * 
130  * 1     10/07/98 10:49a Dave
131  * 
132  * 127   8/25/98 1:48p Dave
133  * First rev of EMP effect. Player side stuff basically done. Next comes
134  * AI code.
135  * 
136  * 126   6/01/98 11:43a John
137  * JAS & MK:  Classified all strings for localization.
138  * 
139  * 125   5/24/98 12:55a Mike
140  * Fix bug with scream from Installation.  Should also fix bug with double
141  * screams from some ships.
142  * 
143  * 124   5/18/98 6:06p Lawrance
144  * Don't play messages or auto-target on first frame
145  * 
146  * 123   5/15/98 8:36p Lawrance
147  * Add 'target ship that last sent transmission' target key
148  * 
149  * 122   5/09/98 10:00p Allender
150  * make vasudan persona for support use terran support persona
151  * 
152  * 121   5/08/98 11:21a Allender
153  * fix ingame join trouble.  Small messaging fix.  Enable collisions for
154  * friendlies again
155  * 
156  * 120   5/06/98 12:19p Lawrance
157  * Fix typo for 'Stray Warning Final'
158  * 
159  * 119   5/05/98 9:12p Allender
160  * fix large problem introduced last checkin when changiing Assert to if
161  * 
162  * 118   5/05/98 4:12p Chad
163  * changed Assert info if statement when removing messages from queue when
164  * too old
165  * 
166  * 117   5/01/98 12:34p John
167  * Added code to force FreeSpace to run in the same dir as exe and made
168  * all the parse error messages a little nicer.
169  * 
170  * 116   4/27/98 9:00p Allender
171  * mission specific messages from #<someone> are now sourced to terran
172  * command
173  * 
174  * 115   4/26/98 11:35a Allender
175  * make traitor message play by iteself in all cases
176  * 
177  * 114   4/25/98 11:49p Lawrance
178  * Add Terran Command stray messages
179  * 
180  * 113   4/22/98 9:17a Allender
181  * be sure that builtin command messages play with the correct hud source.
182  * Also be sure that messages which get converted to Terran command to the
183  * same
184  * 
185  * 112   4/20/98 1:30a Lawrance
186  * Don't load head animations if talking head gauge is disabled.
187  * 
188  * 111   4/17/98 11:03a Allender
189  * some rearm message being played too often and sent with incorrect
190  * 'who-from'
191  * 
192  * 110   4/13/98 5:06p Lawrance
193  * Cut off talking head about 250ms before wave ends
194  * 
195  * 109   4/10/98 9:14a Lawrance
196  * fix up persona code for the demo
197  * 
198  * 108   4/09/98 2:15p Allender
199  * fixed compiler warnings
200  * 
201  * 107   4/09/98 12:36p Allender
202  * don't allow the same ship to have messages overlapping.  Put in code to
203  * check for ship's existence (wingman only) before actually playing
204  * message
205  * 
206  * 106   4/09/98 12:32a Lawrance
207  * Fix bugs related to multiple screams from same ship, builtin messages
208  * playing after screams, or praising while severly damaged.
209  * 
210  * 105   4/08/98 3:45p Allender
211  * mission message overhaul.  Make message from any wingman mean any
212  * wingman with that persona.  Terran command wave and ani's for dead
213  * ships now play correctly.
214  * 
215  * 104   4/07/98 8:09p Lawrance
216  * don't play talking heads in the demo
217  * 
218  * 103   4/07/98 5:30p Lawrance
219  * Player can't send/receive messages when comm is destroyed.  Garble
220  * messages when comm is damaged.
221  * 
222  * 102   4/07/98 5:26p Allender
223  * low priority mission specific messages won't interrupt anything.
224  * 
225  * 101   4/07/98 10:51a Allender
226  * remove any allied from message senders.  Make heads for mission
227  * specific messages play appropriately
228  * 
229  * 100   4/07/98 12:04a Mike
230  * New system for instructor chastising player if he fires at instructor.
231  * 
232  * 99    4/03/98 11:39a Lawrance
233  * only allow 1 wingman persona in demo
234  * 
235  * 98    4/02/98 1:09p Allender
236  * don't process messages before player "enters" mission (i.e. due to
237  * player entry delay)
238  * 
239  * 97    4/02/98 10:06a Allender
240  * wing arrival message for delta and epsilon wings
241  * 
242  * 96    4/01/98 10:47p Lawrance
243  * Supporting builtin messages for rearm and repair requests
244  * 
245  * 95    3/25/98 8:43p Hoffoss
246  * Changed anim_play() to not be so damn complex when you try and call it.
247  * 
248  * 94    3/24/98 12:46p Allender
249  * save shipnum before killing currently playing message in preparation
250  * for playing death scream.
251  * 
252  * 93    3/22/98 3:54p Andsager
253  * AL: Prevent -1 index into Ships[] array when playing a scream
254  * 
255  * 92    3/18/98 10:20p Allender
256  * force wingman scream when he's talking and then dies
257  * 
258  * 91    3/18/98 12:03p John
259  * Marked all the new strings as externalized or not.
260  * 
261  * 90    3/17/98 4:01p Hoffoss
262  * Added HUD_SOURCE_TERRAN_CMD and changed code to utilize it when a
263  * message is being sent from Terran Command.
264  * 
265  * 89    3/05/98 10:18p Lawrance
266  * Play voice cue sound when there is no voice file present
267  * 
268  * 88    3/02/98 5:42p John
269  * Removed WinAVI stuff from Freespace.  Made all HUD gauges wriggle from
270  * afterburner.  Made gr_set_clip work good with negative x &y.  Made
271  * model_caching be on by default.  Made each cached model have it's own
272  * bitmap id.  Made asteroids not rotate when model_caching is on.  
273  * 
274  * 87    3/02/98 9:34a Allender
275  * don't allow mission specific messages to timeout.  Assert when trying
276  * to remove a mission specific messages from the queue.  Print out in the
277  * log file if the voice didn't play.
278  * 
279  * 86    2/23/98 8:45a John
280  * Externalized Strings
281  * 
282  * 85    2/20/98 8:33p Lawrance
283  * Add the 'All Alone' message
284  * 
285  * 84    2/16/98 2:20p Allender
286  * make death scream kill any other messages from that ship
287  * 
288  * 83    2/12/98 4:58p Lawrance
289  * Add support for 'All Clear' radio message
290  * 
291  * 82    2/11/98 9:44p Allender
292  * rearm repair code fixes.  hud support view shows abort status.  New
293  * support ship killed message.  More network stats
294  * 
295  * 81    2/04/98 10:44p Allender
296  * mark personas as not used between missions.   Don't ever randomly
297  * choose a Vasudan persona
298  * 
299  * 80    1/29/98 11:38a Allender
300  * support for Vasudan personas
301  * 
302  * 79    1/25/98 10:04p Lawrance
303  * Fix nasty divide-by-zero bug in message_calc_anim_start_frame().
304  * 
305  * 78    1/24/98 4:46p Lawrance
306  * add in support for new voice messasges
307  * 
308  * 77    1/22/98 5:13p Lawrance
309  * pick useful starting frame when playing animation
310  * 
311  * 76    1/21/98 7:20p Lawrance
312  * Make subsystem locking only work with line-of-sight, cleaned up locking
313  * code, moved globals to player struct.
314  * 
315  * 75    1/21/98 11:54a Duncan
316  * Commended out assert for the moment to allow Fred to run.  Mark can fix
317  * this properly later, since he understands it.
318  * 
319  * 74    1/21/98 10:33a Allender
320  * fixed up messaging code to play a random head when playing a builtin
321  * message
322  * 
323  * 73    1/20/98 6:21p Lawrance
324  * Stop animation from playing when voice clip ends early.
325  * 
326  * 72    1/20/98 3:43p Allender
327  * don't queue messages when player becomes traitor
328  * 
329  * 71    1/20/98 12:52p Lawrance
330  * Draw talking head as alpha-color bitmap, black out region behind
331  * animation.
332  * 
333  * 70    1/20/98 10:20a Lawrance
334  * Draw head animation as alpha-colored.
335  * 
336  * 69    1/18/98 9:51p Lawrance
337  * Add support for 'Player Died' messages.
338  * 
339  * 68    1/14/98 9:49p Allender
340  * removed 3'oclock and 9'oclock messages
341  * 
342  * 67    1/13/98 3:11p Allender
343  * new messages for disable/disarm
344  * 
345  * 66    1/12/98 11:16p Lawrance
346  * Wonderful HUD config.
347  * 
348  * 65    1/07/98 4:41p Allender
349  * minor modification to special messages.  Fixed cargo_revealed problem
350  * for multiplayer and problem with is-cargo-known sexpression
351  * 
352  * 64    12/15/97 12:14p Allender
353  * implemented overlapping messages
354  * 
355  * 63    12/12/97 4:58p Allender
356  * make messages interruptable.  Put in code to support multiple messages
357  * at once, although this feature not fully implemented yet.
358  * 
359  * 62    12/04/97 9:37p Dave
360  * Fixed a bunch of multiplayer messaging bugs.
361  * 
362  * 61    12/02/97 2:37p Allender
363  * added asserts to be sure that an actual ship (i.e. not cargo or
364  * otherwise) is the ship sending a message to the player
365  *
366  * $NoKeywords: $
367  */
368
369 #include "linklist.h"
370 #include "missionmessage.h"
371 #include "missiontraining.h"
372 #include "hudmessage.h"
373 #include "hudtarget.h"
374 #include "sexp.h"
375 #include "timer.h"
376 #include "parselo.h"
377 #include "gamesnd.h"
378 #include "sound.h"
379 #include "freespace.h"
380 #include "multi.h"
381 #include "multimsgs.h"
382 #include "gamesequence.h"
383 #include "animplay.h"
384 #include "controlsconfig.h"
385 #include "audiostr.h"
386 #include "hudsquadmsg.h"
387 #include "multiutil.h"
388 #include "hud.h"
389 #include "subsysdamage.h"
390 #include "emp.h"
391 #include "localize.h"
392 #include "demo.h"
393 #include "hudconfig.h"
394
395 // here is a text list of the builtin message names.  These names are used to match against
396 // names read in for builtin message radio bits to see what message to play.  These are
397 // generic names, meaning that there will be the same message type for a number of different
398 // personas
399 char *Builtin_message_types[MAX_BUILTIN_MESSAGE_TYPES] =
400 {
401 //XSTR:OFF
402         "Arrive Enemy",
403         "Attack Target",
404         "Beta Arrived",
405         "Check 6",
406         "Engage",
407         "Gamma Arrived",
408         "Help",
409         "Praise",
410         "Backup",
411         "Ignore Target",
412         "No",
413         "Oops 1",
414         "Permission",                   // AL: no code support yet
415         "Stray",                                        // DA: no code support
416         "Depart",
417         "yes",
418         "Rearm on Way",
419         "On way",
420         "Rearm warping in",
421         "No Target",
422         "Docking Start",                // AL: no message seems to exist for this
423         "Repair Done",
424         "Repair Aborted",
425         "Traitor",
426         "Rearm",
427         "Disable Target",
428         "Disarm Target",
429         "Player Dead",
430         "Death",
431         "Support Killed",
432         "All Clear",                    // DA: no code support
433         "All Alone",
434         "Repair",
435         "Delta Arrived",
436         "Epsilon Arrived",
437         "Instructor Hit",
438         "Instructor Attack",
439         "Stray Warning",
440         "Stray Warning Final",
441         "AWACS at 75",
442         "AWACS at 25"
443 //XSTR:ON
444 };
445
446 MMessage Messages[MAX_MISSION_MESSAGES];
447 int Message_times[MAX_MISSION_MESSAGES];
448
449 int Num_messages, Num_message_avis, Num_message_waves;
450 int Num_builtin_messages, Num_builtin_avis, Num_builtin_waves;
451
452 int Message_debug_index = -1;
453
454 message_extra Message_avis[MAX_MESSAGE_AVIS];
455 message_extra Message_waves[MAX_MESSAGE_WAVES];
456
457 #define MAX_PLAYING_MESSAGES            2
458
459 #ifdef FS2_DEMO
460         #define MAX_WINGMAN_HEADS                       1
461         #define MAX_COMMAND_HEADS                       1
462 #else
463 #define MAX_WINGMAN_HEADS                       2
464 #define MAX_COMMAND_HEADS                       3
465 #endif
466
467 //XSTR:OFF
468 #define HEAD_PREFIX_STRING                      "head-"
469 #define COMMAND_HEAD_PREFIX             "head-cm1"
470 #define COMMAND_WAVE_PREFIX             "TC_"
471 #define SUPPORT_NAME                                    "Support"
472 //XSTR:ON
473
474 // variables to keep track of messages that are currently playing
475 int Num_messages_playing;                                               // number of is a message currently playing?
476
477 typedef struct pmessage {
478         anim_instance *anim;            // handle of anim currently playing
479         int wave;                                       // handle of wave currently playing
480         int id;                                         // id of message currently playing
481         int priority;                           // priority of message currently playing
482         int shipnum;                            // shipnum of ship sending this message,  -1 if from Terran command
483         int builtin_type;                       // if a builtin message, type of the message
484 } pmessage;
485
486 LOCAL pmessage Playing_messages[MAX_PLAYING_MESSAGES];
487
488 int Message_shipnum;                                            // ship number of who is sending message to player -- used outside this module
489
490 // variables to control message queuing.  All new messages to the player are queued.  The array
491 // will be ordered by priority, then time submitted.
492
493 #define MQF_CONVERT_TO_COMMAND          (1<<0)                  // convert this queued message to terran command
494 #define MQF_CHECK_ALIVE                                 (1<<1)                  // check for the existence of who_from before sending
495
496 typedef struct message_q {
497         fix     time_added;                                     // time at which this entry was added
498         int     window_timestamp;                       // timestamp which will tell us how long we have to play the message
499         int     priority;                                       // priority of the message
500         int     message_num;                            // index into the Messages[] array
501         char    who_from[NAME_LENGTH];  // who this message is from
502         int     source;                                         // who the source of the message is (HUD_SOURCE_* type)
503         int     builtin_type;                           // type of builtin message (-1 if mission message)
504         int     flags;                                          // should this message entry be converted to Terran Command head/wave file
505         int     min_delay_stamp;                        // minimum delay before this message will start playing
506         int     group;                                          // message is part of a group, don't time it out
507 } message_q;
508
509 #define MAX_MESSAGE_Q                           30
510 #define MAX_MESSAGE_LIFE                        F1_0*30         // After being queued for 30 seconds, don't play it
511 #define DEFAULT_MESSAGE_LENGTH  3000                    // default number of milliseconds to display message indicator on hud
512 message_q       MessageQ[MAX_MESSAGE_Q];
513 int MessageQ_num;                       // keeps track of number of entries on the queue.
514
515 #define MESSAGE_IMMEDIATE_TIMESTAMP             1000            // immediate messages must play within 1 second
516 #define MESSAGE_SOON_TIMESTAMP                  5000            // "soon" messages must play within 5 seconds
517 #define MESSAGE_ANYTIME_TIMESTAMP               -1                      // anytime timestamps are invalid
518
519 // Persona information
520 int Num_personas;
521 Persona Personas[MAX_PERSONAS];
522
523 char *Persona_type_names[MAX_PERSONA_TYPES] = 
524 {
525 //XSTR:OFF
526         "wingman",
527         "support",
528         "large", 
529         "command",
530 //XSTR:ON
531 };
532
533 int Command_persona;
534
535 ///////////////////////////////////////////////////////////////////
536 // used to distort incoming messages when comms are damaged
537 ///////////////////////////////////////////////////////////////////
538 static int Message_wave_muted;
539 static int Message_wave_duration;
540 static int Next_mute_time;
541
542 #define MAX_DISTORT_PATTERNS    2
543 #define MAX_DISTORT_LEVELS              6
544 static float Distort_patterns[MAX_DISTORT_PATTERNS][MAX_DISTORT_LEVELS] = 
545 {
546         {0.20f, 0.20f, 0.20f, 0.20f, 0.20f, 0.20f},
547         {0.10f, 0.20f, 0.25f, 0.25f, 0.05f, 0.15f}
548 };
549
550 static int Distort_num;         // which distort pattern is being used
551 static int Distort_next;        // which section of distort pattern is next
552
553 int Head_coords[GR_NUM_RESOLUTIONS][2] = {
554         { // GR_640
555                 7, 45
556         },
557         { // GR_1024
558                 7, 66
559         }
560 };
561
562 // forward declaration
563 void message_maybe_distort_text(char *text);
564
565 // following functions to parse messages.tbl -- code pretty much ripped from weapon/ship table parsing code
566
567 // functions to deal with parsing personas.  Personas are just a list of names that give someone
568 // sending a message an identity which spans the life of the mission
569 void persona_parse()
570 {
571         int i;
572         char type[NAME_LENGTH];
573
574         Assert ( Num_personas < MAX_PERSONAS );
575
576         Personas[Num_personas].flags = 0;
577         required_string("$Persona:");
578         stuff_string(Personas[Num_personas].name, F_NAME, NULL);
579
580         // get the type name and set the appropriate flag
581         required_string("$Type:");
582         stuff_string( type, F_NAME, NULL );
583         for ( i = 0; i < MAX_PERSONA_TYPES; i++ ) {
584                 if ( !stricmp( type, Persona_type_names[i]) ) {
585
586                         Personas[Num_personas].flags |= (1<<i);
587
588                         // save the Terran Command persona in a global
589                         if ( Personas[Num_personas].flags & PERSONA_FLAG_COMMAND ) {
590 //                              Assert ( Command_persona == -1 );
591                                 Command_persona = Num_personas;
592                         }
593
594                         break;
595                 }
596         }
597
598         if ( optional_string("+Vasudan") )
599                 Personas[Num_personas].flags |= PERSONA_FLAG_VASUDAN;
600
601         if ( i == MAX_PERSONA_TYPES )
602                 Error(LOCATION, "Unknown persona type in messages.tbl -- %s\n", type );
603
604
605         Num_personas++;
606 }
607
608 // two functions to add avi/wave names into a table
609 int add_avi( char *avi_name )
610 {
611         int i;
612
613         Assert ( Num_message_avis < MAX_MESSAGE_AVIS );
614         Assert (strlen(avi_name) < MAX_FILENAME_LEN );
615
616         // check to see if there is an existing avi being used here
617         for ( i = 0; i < Num_message_avis; i++ ) {
618                 if ( !stricmp(Message_avis[i].name, avi_name) )
619                         return i;
620         }
621
622         // would have returned if a slot existed.
623         strcpy( Message_avis[Num_message_avis].name, avi_name );
624         Message_avis[Num_message_avis].num = -1;
625         Num_message_avis++;
626         return (Num_message_avis - 1);
627 }
628
629 int add_wave( char *wave_name )
630 {
631         int i;
632
633         Assert ( Num_message_waves < MAX_MESSAGE_WAVES );
634         Assert (strlen(wave_name) < MAX_FILENAME_LEN );
635
636         // check to see if there is an existing wave being used here
637         for ( i = 0; i < Num_message_waves; i++ ) {
638                 if ( !stricmp(Message_waves[i].name, wave_name) )
639                         return i;
640         }
641
642         strcpy( Message_waves[Num_message_waves].name, wave_name );
643         Message_waves[Num_message_waves].num = -1;
644         Num_message_waves++;
645         return (Num_message_waves - 1);
646 }
647
648 // parses an individual message
649 void message_parse( )
650 {
651         MissionMessage *msgp;
652         char persona_name[NAME_LENGTH];
653
654         Assert ( Num_messages < MAX_MISSION_MESSAGES );
655         msgp = &Messages[Num_messages];
656
657         required_string("$Name:");
658         stuff_string(msgp->name, F_NAME, NULL);
659
660         // team
661         msgp->multi_team = -1;
662         if(optional_string("$Team:")){
663                 int mt;
664                 stuff_int(&mt);
665
666                 // keep it real
667                 if((mt < 0) || (mt >= 2)){
668                         mt = -1;
669                 }
670
671                 // only bother with filters if multiplayer and TvT
672                 if(Fred_running || ((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM)) ){
673                         msgp->multi_team = mt;
674                 }
675         }
676
677         // backwards compatibility for old fred missions - all new ones should use $MessageNew
678         if(optional_string("$Message:")){
679                 stuff_string(msgp->message, F_MESSAGE, NULL);
680         } else {
681                 required_string("$MessageNew:");
682                 stuff_string(msgp->message, F_MULTITEXT, NULL);
683         }
684
685         msgp->persona_index = -1;
686         if ( optional_string("+Persona:") ) {
687                 stuff_string(persona_name, F_NAME, NULL);
688                 msgp->persona_index = message_persona_name_lookup( persona_name );
689         }
690
691         if ( !Fred_running)
692                 msgp->avi_info.index = -1;
693         else
694                 msgp->avi_info.name = NULL;
695
696         if ( optional_string("+AVI Name:") ) {
697                 char avi_name[MAX_FILENAME_LEN];
698
699                 stuff_string(avi_name, F_NAME, NULL);
700                 if ( !Fred_running ) {
701                         msgp->avi_info.index = add_avi(avi_name);
702                 } else {
703                         msgp->avi_info.name = strdup(avi_name);
704                 }
705         }
706
707         if ( !Fred_running )
708                 msgp->wave_info.index = -1;
709         else
710                 msgp->wave_info.name = NULL;
711
712         if ( optional_string("+Wave Name:") ) {
713                 char wave_name[MAX_FILENAME_LEN];
714
715                 stuff_string(wave_name, F_NAME, NULL);
716                 if ( !Fred_running ) {
717                         msgp->wave_info.index = add_wave(wave_name);
718                 } else {
719                         msgp->wave_info.name = strdup(wave_name);
720                 }
721         }
722
723         Num_messages++;
724 }
725
726 void parse_msgtbl()
727 {
728         // open localization
729         lcl_ext_open();
730
731         read_file_text("messages.tbl");
732         reset_parse();
733         Num_messages = 0;
734         Num_personas = 0;
735
736         required_string("#Personas");
737         while ( required_string_either("#Messages", "$Persona:")){
738                 persona_parse();
739         }
740
741         required_string("#Messages");
742         while (required_string_either("#End", "$Name:")){
743                 message_parse();
744         }
745
746         required_string("#End");
747
748         // save the number of builting message things -- make initing between missions easier
749         Num_builtin_messages = Num_messages;
750         Num_builtin_avis = Num_message_avis;
751         Num_builtin_waves = Num_message_waves;
752
753         // close localization
754         lcl_ext_close();
755 }
756
757 // this is called at the start of each level
758 void messages_init()
759 {
760         int rval, i;
761         static int table_read = 0;
762
763         if ( !table_read ) {
764                 Command_persona = -1;
765                 if ((rval = setjmp(parse_abort)) != 0) {
766                         Error(LOCATION, "Error parsing '%s'\r\nError code = %i.\r\n", "messages.tbl", rval);
767
768                 } else {                        
769                         parse_msgtbl();
770                         table_read = 1;
771                 }
772         }
773
774         // reset the number of messages that we have for this mission
775         Num_messages = Num_builtin_messages;
776         Message_debug_index = Num_builtin_messages - 1;
777         Num_message_avis = Num_builtin_avis;
778         Num_message_waves = Num_builtin_waves;
779
780         // initialize the stuff for the linked lists of messages
781         MessageQ_num = 0;
782         for (i = 0; i < MAX_MESSAGE_Q; i++) {
783                 MessageQ[i].priority = -1;
784                 MessageQ[i].time_added = -1;
785                 MessageQ[i].message_num = -1;
786                 MessageQ[i].builtin_type = -1;
787                 MessageQ[i].min_delay_stamp = -1;
788                 MessageQ[i].group = 0;
789         }
790         
791         // this forces a reload of the AVI's and waves for builtin messages.  Needed because the flic and
792         // sound system also get reset between missions!
793         for (i = 0; i < Num_builtin_avis; i++ ) {
794                 Message_avis[i].anim_data = NULL;
795         }
796
797         for (i = 0; i < Num_builtin_waves; i++ ){
798                 Message_waves[i].num = -1;
799         }
800
801         Message_shipnum = -1;
802         Num_messages_playing = 0;
803         for ( i = 0; i < MAX_PLAYING_MESSAGES; i++ ) {
804                 Playing_messages[i].anim = NULL;
805                 Playing_messages[i].wave = -1;
806                 Playing_messages[i].id = -1;
807                 Playing_messages[i].priority = -1;
808                 Playing_messages[i].shipnum = -1;
809                 Playing_messages[i].builtin_type = -1;
810         }
811
812         // reinitialize the personas.  mark them all as not used
813         for ( i = 0; i < Num_personas; i++ ){
814                 Personas[i].flags &= ~PERSONA_FLAG_USED;
815         }
816
817         Message_wave_muted = 0;
818         Next_mute_time = 1;
819
820         memset(Message_times, 0, sizeof(int)*MAX_MISSION_MESSAGES);
821 }
822
823 // called to do cleanup when leaving a mission
824 void message_mission_shutdown()
825 {
826         int i;
827
828         mprintf(("Unloading in mission messages\n"));
829
830         training_mission_shutdown();
831
832         // remove the wave sounds from memory
833         for (i = 0; i < Num_message_waves; i++ ) {
834                 if ( Message_waves[i].num != -1 ){
835                         snd_unload( Message_waves[i].num );
836                 }
837         }
838
839 }
840
841 // functions to deal with queuing messages to the message system.
842
843 //      Compare function for system qsort() for sorting message queue entries based on priority.
844 //      Return values set to sort array in _decreasing_ order.  If priorities equal, sort based
845 // on time added into queue
846 int message_queue_priority_compare(const void *a, const void *b)
847 {
848         message_q *ma, *mb;
849
850         ma = (message_q *) a;
851         mb = (message_q *) b;
852
853         if (ma->priority > mb->priority) {
854                 return -1;
855         } else if (ma->priority < mb->priority) {
856                 return 1;
857         } else if (ma->time_added < mb->time_added) {
858                 return -1;
859         } else if (ma->time_added > mb->time_added) {
860                 return 1;
861         } else {
862                 return 0;
863         }
864 }
865
866 // function to kill all currently playing messages.  kill_all parameter tells us to
867 // kill only the animations that are playing, or wave files too
868 void message_kill_all( int kill_all )
869 {
870         int i;
871
872         Assert( Num_messages_playing );
873
874         // kill sounds for all voices currently playing
875         for ( i = 0; i < Num_messages_playing; i++ ) {
876                 if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) ) {
877                         anim_stop_playing( Playing_messages[i].anim );
878                         Playing_messages[i].anim=NULL;
879                 }
880
881                 if ( kill_all ) {
882                         if ( (Playing_messages[i].wave != -1 ) && snd_is_playing(Playing_messages[i].wave) ){
883                                 snd_stop( Playing_messages[i].wave );
884                         }
885
886                         Playing_messages[i].shipnum = -1;
887                 }
888         }
889
890         if ( kill_all ) {
891                 Num_messages_playing = 0;
892         }
893 }
894
895 // function to kill nth playing message
896 void message_kill_playing( int message_num )
897 {
898         Assert( message_num < Num_messages_playing );
899
900         if ( (Playing_messages[message_num].anim != NULL) && anim_playing(Playing_messages[message_num].anim) ) {
901                 anim_stop_playing( Playing_messages[message_num].anim );
902                 Playing_messages[message_num].anim=NULL;
903         }
904         if ( (Playing_messages[message_num].wave != -1 ) && snd_is_playing(Playing_messages[message_num].wave) )
905                 snd_stop( Playing_messages[message_num].wave );
906
907         Playing_messages[message_num].shipnum = -1;
908 }
909
910
911 // returns true if all messages currently playing are builtin messages
912 int message_playing_builtin()
913 {
914         int i;
915
916         for ( i = 0; i < Num_messages_playing; i++ ) {
917                 if ( Playing_messages[i].id >= Num_builtin_messages ){
918                         break;
919                 }
920         }
921
922         // if we got through the list without breaking, all playing messages are builtin messages
923         if ( i == Num_messages_playing ){
924                 return 1;
925         } else {
926                 return 0;
927         }
928 }
929
930 // returns true in any playing message is of the specific builtin type
931 int message_playing_specific_builtin( int builtin_type )
932 {
933         int i;
934
935         for (i = 0; i < Num_messages_playing; i++ ) {
936                 if ( (Playing_messages[i].id < Num_builtin_messages) && (Playing_messages[i].builtin_type == builtin_type) ){
937                         return 1;
938                 }
939         }
940
941         return 0;
942 }
943
944 // returns true if all messages current playing are unique messages
945 int message_playing_unique()
946 {
947         int i;
948
949         for ( i = 0; i < Num_messages_playing; i++ ) {
950                 if ( Playing_messages[i].id < Num_builtin_messages ){
951                         break;
952                 }
953         }
954
955         // if we got through the list without breaking, all playing messages are builtin messages
956         if ( i == Num_messages_playing ){
957                 return 1;
958         } else {
959                 return 0;
960         }
961 }
962
963
964 // returns the highest priority of the currently playing messages
965 #define MESSAGE_GET_HIGHEST             1
966 #define MESSAGE_GET_LOWEST                      2
967 int message_get_priority(int which)
968 {
969         int i;
970         int priority;
971
972         if ( which == MESSAGE_GET_HIGHEST ){
973                 priority = MESSAGE_PRIORITY_LOW;
974         } else {
975                 priority = MESSAGE_PRIORITY_HIGH;
976         }
977
978         for ( i = 0; i < Num_messages_playing; i++ ) {
979                 if ( (which == MESSAGE_GET_HIGHEST) && (Playing_messages[i].priority > priority) ){
980                         priority = Playing_messages[i].priority;
981                 } else if ( (which == MESSAGE_GET_LOWEST) && (Playing_messages[i].priority < priority) ){
982                         priority = Playing_messages[i].priority;
983                 }
984         }
985
986         return priority;
987 }
988
989
990 // removes current message from the queue
991 void message_remove_from_queue(message_q *q)
992 {       
993         // quick out if nothing to do.
994         if ( MessageQ_num <= 0 ) {
995                 return;
996         }       
997
998         MessageQ_num--;
999         q->priority = -1;
1000         q->time_added = -1;
1001         q->message_num = -1;
1002         q->builtin_type = -1;
1003         q->min_delay_stamp = -1;
1004         q->group = 0;   
1005
1006         if ( MessageQ_num > 0 ) {
1007                 qsort(MessageQ, MAX_MESSAGE_Q, sizeof(message_q), message_queue_priority_compare);
1008         }
1009 }
1010
1011 // Load in the sound data for a message.
1012 //
1013 // index - index into the Message_waves[] array
1014 //
1015 void message_load_wave(int index, const char *filename)
1016 {
1017         if (index == -1) {
1018                 Int3();
1019                 return;
1020         }
1021
1022         if ( Message_waves[index].num >= 0) {
1023                 return;
1024         }
1025
1026         game_snd tmp_gs;
1027         memset(&tmp_gs, 0, sizeof(game_snd));
1028         strcpy( tmp_gs.filename, filename );
1029         Message_waves[index].num = snd_load( &tmp_gs );
1030         if ( Message_waves[index].num == -1 ) {
1031                 nprintf (("messaging", "Cannot load message wave: %s.  Will not play\n", Message_waves[index].name ));
1032         }
1033 }
1034
1035 // Play wave file associated with message
1036 // input: m             =>              pointer to message description
1037 //
1038 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1039 void message_play_wave( message_q *q )
1040 {
1041         int index;
1042         MissionMessage *m;
1043         char filename[MAX_FILENAME_LEN];
1044
1045         // check for multiple messages playing.  don't check builtin messages.
1046         if (q->message_num >= Num_builtin_messages) {
1047                 if ( (f2fl(Missiontime - Message_times[q->message_num]) < 10) && (f2fl(Missiontime) > 10) ) {
1048                         // Int3();  // Get Andsager
1049                 }
1050                 Message_times[q->message_num] = Missiontime;
1051         }
1052
1053         m = &Messages[q->message_num];
1054
1055         if ( m->wave_info.index != -1 ) {
1056                 index = m->wave_info.index;
1057
1058                 // sanity check
1059                 Assert( index != -1 );
1060                 if ( index == -1 ){
1061                         return;
1062                 }
1063
1064                 // if we need to bash the wave name because of "conversion" to terran command, do it here
1065                 strcpy( filename, Message_waves[index].name );
1066                 if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1067                         char *p, new_filename[MAX_FILENAME_LEN];
1068
1069                         Message_waves[index].num = -1;                                  // forces us to reload the message
1070
1071                         // bash the filename here. Look for "[1-6]_" at the front of the message.  If found, then
1072                         // convert to TC_*
1073                         p = strchr(filename, '_' );
1074                         if ( p == NULL ) {
1075                                 mprintf(("Cannot convert %s to terran command wave -- find Sandeep or Allender\n", Message_waves[index].name));
1076                                 return;
1077                         }
1078
1079                         // prepend the command name, and then the rest of the filename.
1080                         p++;
1081                         strcpy( new_filename, COMMAND_WAVE_PREFIX );
1082                         strcat( new_filename, p );
1083                         strcpy( filename, new_filename );
1084                 }
1085
1086                 // load the sound file into memory
1087                 message_load_wave(index, filename);
1088                 if ( Message_waves[index].num == -1 ) {
1089                         m->wave_info.index = -1;
1090                 }
1091
1092                 if ( m->wave_info.index >= 0 ) {
1093                         // this call relies on the fact that snd_play returns -1 if the sound cannot be played
1094                         Message_wave_duration = snd_get_duration(Message_waves[index].num);
1095                         Playing_messages[Num_messages_playing].wave = snd_play_raw( Message_waves[index].num, 0.0f );
1096                 }
1097         }
1098 }
1099
1100 // Determine the starting frame for the animation
1101 // input:       time    =>              time of voice clip, in ms
1102 //                              ani     =>              pointer to anim data
1103 //                              reverse =>      flag to indicate that the start should be time ms from the end (used for death screams)
1104 int message_calc_anim_start_frame(int time, anim *ani, int reverse)
1105 {
1106         float   wave_time, anim_time;
1107         int     start_frame;
1108
1109         start_frame=0;
1110
1111         // If no voice clip exists, start from beginning of anim
1112         if ( time <= 0 ) {
1113                 return start_frame;
1114         }
1115
1116         // convert time to seconds
1117         wave_time = time/1000.0f;
1118         anim_time = ani->time;
1119
1120         // If voice clip is longer than anim, start from beginning of anim
1121         if ( wave_time >= (anim_time) ) {
1122                 return start_frame;
1123         }
1124
1125         if ( reverse ) {
1126                 start_frame = (ani->total_frames-1) - fl2i(ani->fps * wave_time + 0.5f);
1127         } else {
1128                 int num_frames_extra;
1129                 num_frames_extra = fl2i(ani->fps * (anim_time - wave_time) + 0.5f);
1130                 if ( num_frames_extra > 0 ) {
1131                         start_frame=rand()%num_frames_extra;
1132                 }
1133         }
1134
1135         if ( start_frame < 0 ) {
1136                 Int3();
1137                 start_frame=0;
1138         }
1139
1140         return start_frame;
1141 }
1142
1143 // Play animation associated with message
1144 // input:       m               =>              pointer to message description
1145 //                              q               =>              message queue data
1146 //
1147 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1148 void message_play_anim( message_q *q )
1149 {
1150         message_extra   *anim_info;
1151         int                             is_death_scream=0, persona_index=-1, rand_index=-1;
1152         char                            ani_name[MAX_FILENAME_LEN], *p;
1153         MissionMessage  *m;
1154
1155         m = &Messages[q->message_num];
1156
1157         // check to see if the avi_index is valid -- try and load/play the avi if so.
1158         if ( m->avi_info.index < 0 ) {
1159                 return;
1160         }
1161
1162         anim_info = &Message_avis[m->avi_info.index];
1163
1164         // get the filename.  Strip off the extension since we won't need it anyway
1165         strcpy(ani_name, anim_info->name);
1166         p = strchr(ani_name, '.');                      // gets us to the extension
1167         if ( p ) {
1168                 *p = '\0';
1169         }
1170
1171         // builtin messages are given a base ani which we should add a suffix on before trying
1172         // to load the animation.  See if this message is a builtin message which has a persona
1173         // attached to it.  Deal with munging the name
1174
1175         // support ships use a wingman head.
1176         // terran command uses it's own set of heads.
1177         int subhead_selected = FALSE;
1178         if ( (q->message_num < Num_builtin_messages) || !(_strnicmp(HEAD_PREFIX_STRING, ani_name, strlen(HEAD_PREFIX_STRING)-1)) ) {
1179                 persona_index = m->persona_index;
1180                 
1181                 // if this ani should be converted to a terran command, set the persona to the command persona
1182                 // so the correct head plays.
1183                 if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1184                         persona_index = Command_persona;
1185                         strcpy( ani_name, COMMAND_HEAD_PREFIX );
1186                 }
1187
1188                 if ( Personas[persona_index].flags & (PERSONA_FLAG_WINGMAN | PERSONA_FLAG_SUPPORT) ) {
1189                         // get a random head -- it's one of two.
1190                         if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1191                                 rand_index=2;   // 3rd version is always death animation
1192                                 is_death_scream=1;
1193                         } else {
1194                                 rand_index = (Missiontime % MAX_WINGMAN_HEADS);
1195                         }
1196                         sprintf(ani_name, "%s%c", ani_name, 'a'+rand_index);
1197                         subhead_selected = TRUE;
1198                 } else if ( Personas[persona_index].flags & (PERSONA_FLAG_COMMAND | PERSONA_FLAG_LARGE) ) {
1199                         // get a random head -- it's one of two.
1200                         rand_index = (Missiontime % MAX_COMMAND_HEADS);
1201                         sprintf(ani_name, "%s%c", ani_name, 'a'+rand_index);
1202                         subhead_selected = TRUE;
1203                 }
1204                 if (!subhead_selected) {
1205                         // choose between a and b
1206                         rand_index = (Missiontime % MAX_WINGMAN_HEADS);
1207                         sprintf(ani_name, "%s%c", ani_name, 'a'+rand_index);
1208                         mprintf(("message '%s' with invalid head.  Fix by assigning persona to the message.\n", m->name));
1209                 }
1210                 nprintf(("Messaging", "playing head %s for %s\n", ani_name, q->who_from));
1211         }
1212
1213         // check to see if the avi has been loaded.  If not, then load the AVI.  On an error loading
1214         // the avi, set the top level index to -1 to avoid multiple tries at loading the flick.
1215         if ( hud_gauge_active(HUD_TALKING_HEAD) ) {
1216                 anim_info->anim_data = anim_load( ani_name, 0 );
1217         } else {
1218                 return;
1219         }
1220
1221         if ( anim_info->anim_data == NULL ) {
1222                 nprintf (("messaging", "Cannot load message avi %s.  Will not play.\n", ani_name));
1223                 m->avi_info.index = -1;                 // if cannot load the avi -- set this index to -1 to avoid trying to load multiple times
1224         }
1225
1226         if ( m->avi_info.index >= 0 ) {
1227                 // This call relies on the fact that AVI_play will return -1 if the AVI cannot be played
1228                 // if any messages are already playing, kill off any head anims that are currently playing.  We will
1229                 // only play a head anim of the newest messages being played
1230                 if ( Num_messages_playing > 0 ) {
1231                         nprintf(("messaging", "killing off any currently playing head animations\n"));
1232                         message_kill_all( 0 );
1233                 }
1234
1235                 if ( hud_gauge_active(HUD_TALKING_HEAD) ) {
1236                         int anim_start_frame;
1237                         anim_play_struct aps;
1238
1239                         // figure out anim start frame
1240                         anim_start_frame = message_calc_anim_start_frame(Message_wave_duration, anim_info->anim_data, is_death_scream);
1241                         anim_play_init(&aps, anim_info->anim_data, Head_coords[gr_screen.res][0], Head_coords[gr_screen.res][1]);
1242                         aps.start_at = anim_start_frame;
1243                         
1244                         // aps.color = &HUD_color_defaults[HUD_color_alpha];
1245                         aps.color = &HUD_config.clr[HUD_TALKING_HEAD];
1246
1247                         Playing_messages[Num_messages_playing].anim = anim_play(&aps);
1248                 }
1249         }
1250 }
1251
1252 // process the message queue -- called once a frame
1253 void message_queue_process()
1254 {       
1255         char    buf[4096];
1256         message_q *q;
1257         int i;
1258         MissionMessage *m;
1259
1260         // Don't play messages until first frame has been rendered
1261         if ( Framecount < 2 ) {
1262                 return;
1263         }
1264
1265         // determine if all playing messages (if any) are done playing.  If any are done, remove their
1266         // entries collapsing the Playing_messages array if necessary
1267         if ( Num_messages_playing > 0 ) {
1268
1269                 // for each message playing, determine if it is done.
1270                 i = 0;
1271                 while ( i < Num_messages_playing ) {
1272                         int ani_done, wave_done, j;
1273
1274                         ani_done = 1;
1275                         if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) )
1276                                 ani_done = 0;
1277
1278                         wave_done = 1;
1279
1280 //                      if ( (Playing_messages[i].wave != -1) && snd_is_playing(Playing_messages[i].wave) )
1281                         if ( (Playing_messages[i].wave != -1) && (snd_time_remaining(Playing_messages[i].wave) > 250) )
1282                                 wave_done = 0;
1283
1284                         // AL 1-20-98: If voice message is done, kill the animation early
1285                         if ( (Playing_messages[i].wave != -1) && wave_done ) {
1286                                 if ( !ani_done ) {
1287                                         anim_stop_playing( Playing_messages[i].anim );
1288                                 }
1289                         }
1290
1291                         // see if the ship sending this message is dying.  If do, kill wave and anim
1292                         if ( Playing_messages[i].shipnum != -1 ) {
1293                                 if ( (Ships[Playing_messages[i].shipnum].flags & SF_DYING) && (Playing_messages[i].builtin_type != MESSAGE_WINGMAN_SCREAM) ) {
1294                                         int shipnum;
1295
1296                                         shipnum = Playing_messages[i].shipnum;
1297                                         message_kill_playing( i );
1298                                         // force this guy to scream
1299                                         // AL 22-2-98: Ensure don't use -1 to index into ships array.  Mark, something is incorrect 
1300                                         //             here, since message_kill_playing() seems to always set Playing_messages[i].shipnum to -1
1301                                         // MWA 3/24/98 -- save shipnum before killing message
1302                                         // 
1303                                         Assert( shipnum >= 0 );
1304                                         if ( !(Ships[shipnum].flags & SF_SHIP_HAS_SCREAMED) ) {
1305                                                 ship_scream( &Ships[shipnum] );
1306                                         }
1307                                         continue;                                                       // this should keep us in the while() loop with same value of i.                                                                                                                
1308                                 }                                                                                       // we should enter the next 'if' statement during next pass
1309                         }
1310
1311                         // if both ani and wave are done, mark internal variable so we can do next message on queue, and
1312                         // global variable to clear voice brackets on hud
1313                         if ( wave_done && ani_done ) {
1314                                 nprintf(("messaging", "Message %d is done playing\n", i));
1315                                 Message_shipnum = -1;
1316                                 Num_messages_playing--;
1317                                 if ( Num_messages_playing == 0 )
1318                                         break;
1319
1320                                 // there is still another message playing.  Collapse the playing_message array
1321                                 nprintf(("messaging", "Collapsing playing message stack\n"));
1322                                 for ( j = i+1; j < Num_messages_playing + 1; j++ ) {
1323                                         Playing_messages[j-1] = Playing_messages[j];
1324                                 }
1325                         } else {
1326                                 // messages is not done playing -- move to next message
1327                                 i++;
1328                         }
1329                 }
1330         }
1331
1332         // preprocess message queue and remove anything on the queue that is too old.  If next message on
1333         // the queue can be played, then break out of the loop.  Otherwise, loop until nothing on the queue
1334         while ( MessageQ_num > 0 ) {
1335                 q = &MessageQ[0];               
1336                 if ( timestamp_valid(q->window_timestamp) && timestamp_elapsed(q->window_timestamp) && !q->group) {
1337                         // remove message from queue and see if more to remove
1338                         nprintf(("messaging", "Message %s didn't play because it didn't fit into time window.\n", Messages[q->message_num].name));
1339                         if ( q->message_num < Num_builtin_messages ){                   // we should only ever remove builtin messages this way
1340                                 message_remove_from_queue(q);
1341                         } else {
1342                                 break;
1343                         }
1344                 } else {
1345                         break;
1346                 }
1347         }
1348
1349         // no need to process anything if there isn't anything on the queue
1350         if ( MessageQ_num <= 0 ){
1351                 return;
1352         }
1353
1354         // get a pointer to an item on the queue
1355         int found = -1;
1356         int idx = 0;
1357         while((found == -1) && (idx < MessageQ_num)){
1358                 // if this guy has no min delay timestamp, or it has expired, select him
1359                 if((MessageQ[idx].min_delay_stamp == -1) || timestamp_elapsed(MessageQ[idx].min_delay_stamp)){
1360                         found = idx;
1361                         break;
1362                 }
1363
1364                 // next
1365                 idx++;
1366         }
1367
1368         // if we didn't find anything, bail
1369         if(found == -1){
1370                 return;
1371         }
1372         // if this is not the first item on the queue, make it the first item
1373         if(found != 0){
1374                 message_q temp;
1375
1376                 // store the entry
1377                 memcpy(&temp, &MessageQ[found], sizeof(message_q));
1378
1379                 // move all other entries up
1380                 for(idx=found; idx>0; idx--){
1381                         memcpy(&MessageQ[idx], &MessageQ[idx-1], sizeof(message_q));
1382                 }
1383
1384                 // plop the entry down as being first
1385                 memcpy(&MessageQ[0], &temp, sizeof(message_q));
1386         }
1387
1388         q = &MessageQ[0];
1389         Assert ( q->message_num != -1 );
1390         Assert ( q->priority != -1 );
1391         Assert ( q->time_added != -1 );
1392
1393         if ( Num_messages_playing ) {
1394                 // peek at the first message on the queue to see if it should interrupt, or overlap a currently
1395                 // playing message.  Mission specific messages will always interrupt builtin messages.  They
1396                 // will never interrupt other mission specific messages.
1397                 //
1398                 //  Builtin message might interrupt other builtin messages, or overlap them, all depending on
1399                 // message priority.
1400
1401                 if ( q->builtin_type == MESSAGE_HAMMER_SWINE ) {
1402                         message_kill_all(1);
1403                 } else if ( message_playing_specific_builtin(MESSAGE_HAMMER_SWINE) ) {
1404                         MessageQ_num = 0;
1405                         return;
1406                 } else if ( message_playing_builtin() && ( q->message_num >= Num_builtin_messages) && (q->priority > MESSAGE_PRIORITY_LOW) ) {
1407                         // builtin is playing and we have a unique message to play.  Kill currently playing message
1408                         // so unique can play uninterrupted.  Only unique messages higher than low priority will interrupt
1409                         // other messages.
1410                         message_kill_all(1);
1411                         nprintf(("messaging", "Killing all currently playing messages to play unique message\n"));
1412                 } else if ( message_playing_builtin() && (q->message_num < Num_builtin_messages) ) {
1413                         // when a builtin message is queued, we might either overlap or interrupt the currently
1414                         // playing message.
1415                         //
1416                         // we have to check for num_messages_playing (again), since code for death scream might
1417                         // kill all messages.
1418                         if ( Num_messages_playing ) {
1419                                 if ( message_get_priority(MESSAGE_GET_HIGHEST) < q->priority ) {
1420                                         // lower priority message playing -- kill it.
1421                                         message_kill_all(1);
1422                                         nprintf(("messaging", "Killing all currently playing messages to play high priority builtin\n"));
1423                                 } else if ( message_get_priority(MESSAGE_GET_LOWEST) > q->priority ) {
1424                                         // queued message is a lower priority, so wait it out
1425                                         return;
1426                                 } else {
1427                                         // if we get here, then queued messages is a builtin message with the same priority
1428                                         // as the currently playing messages.  This state will cause messages to overlap.
1429                                         nprintf(("messaging", "playing builtin message (overlap) because priorities match\n"));
1430                                 }
1431                         }
1432                 } else if ( message_playing_unique() && (q->message_num < Num_builtin_messages) ) {
1433                         // code messages can kill any low priority mission specific messages
1434                         if ( Num_messages_playing ) {
1435                                 if ( message_get_priority(MESSAGE_GET_HIGHEST) == MESSAGE_PRIORITY_LOW ) {
1436                                         message_kill_all(1);
1437                                         nprintf(("messaging", "Killing low priority unique messages to play code message\n"));
1438                                 } else {
1439                                         return;                 // do nothing.
1440                                 }
1441                         }
1442                 } else {
1443                         return;
1444                 }
1445         }
1446
1447         // if we are playing the maximum number of voices, then return.  Make the check here since the above
1448         // code might kill of currently playing messages
1449         if ( Num_messages_playing == MAX_PLAYING_MESSAGES )
1450                 return;
1451
1452         Message_shipnum = ship_name_lookup( q->who_from );
1453
1454         // see if we need to check if sending ship is alive
1455         if ( q->flags & MQF_CHECK_ALIVE ) {
1456                 if ( Message_shipnum == -1 ) {
1457                         goto all_done;
1458                 }
1459         }
1460
1461         // if this is a ship, then don't play anything if this ship is already talking
1462         if ( Message_shipnum != -1 ) {
1463                 for ( i = 0; i < Num_messages_playing; i++ ) {
1464                         if ( (Playing_messages[i].shipnum != -1) && (Playing_messages[i].shipnum == Message_shipnum) ){
1465                                 return;
1466                         }
1467                 }
1468         }
1469
1470         // set up module globals for this message
1471         m = &Messages[q->message_num];
1472         Playing_messages[Num_messages_playing].anim = NULL;
1473         Playing_messages[Num_messages_playing].wave  = -1;
1474         Playing_messages[Num_messages_playing].id  = q->message_num;
1475         Playing_messages[Num_messages_playing].priority = q->priority;
1476         Playing_messages[Num_messages_playing].shipnum = Message_shipnum;
1477         Playing_messages[Num_messages_playing].builtin_type = q->builtin_type;
1478
1479         Message_wave_duration = 0;
1480
1481         // translate tokens in message to the real things
1482         message_translate_tokens(buf, m->message);
1483
1484         // AL: added 07/14/97.. only play avi/sound if in gameplay
1485         if ( gameseq_get_state() != GS_STATE_GAME_PLAY )
1486                 goto all_done;
1487
1488         // AL 4-7-98: Can't receive messages if comm is destroyed
1489         if ( hud_communications_state(Player_ship) == COMM_DESTROYED ) {
1490                 goto all_done;
1491         }
1492
1493         //      Don't play death scream unless a small ship.
1494         if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1495                 int t = Ship_info[Ships[Message_shipnum].ship_info_index].flags;
1496                 int t2 = SIF_SMALL_SHIP;
1497                 int t3 = t & t2;
1498                 if (!t3) {
1499                         goto all_done;
1500                 }
1501         }
1502
1503         // play wave first, since need to know duration for picking anim start frame
1504         message_play_wave(q);
1505
1506         // play animation for head
1507         #ifndef DEMO // do we want this for FS2_DEMO
1508                 message_play_anim(q);
1509         #endif
1510         
1511         // distort the message if comms system is damaged
1512         message_maybe_distort_text(buf);
1513
1514 #ifndef NDEBUG
1515         // debug only -- if the message is a builtin message, put in parens whether or not the voice played
1516         if ( Playing_messages[Num_messages_playing].wave == -1 ) {
1517                 strcat( buf, NOX("..(no wavefile for voice)"));
1518                 snd_play(&Snds[SND_CUE_VOICE]);
1519         }
1520 #endif
1521
1522         HUD_sourced_printf( q->source, NOX("%s: %s"), q->who_from, buf );
1523
1524         if ( Message_shipnum >= 0 ) {
1525                 hud_target_last_transmit_add(Message_shipnum);
1526         }
1527
1528 all_done:
1529         Num_messages_playing++;
1530         message_remove_from_queue( q );
1531 }
1532
1533 // queues up a message to display to the player
1534 void message_queue_message( int message_num, int priority, int timing, char *who_from, int source, int group, int delay, int builtin_type )
1535 {
1536         int i, m_persona;
1537
1538         if ( message_num < 0 ) return;
1539
1540         // some messages can get queued quickly.  Try to filter out certain types of messages before
1541         // they get queued if there are other messages of the same type already queued
1542         if ( (builtin_type == MESSAGE_REARM_ON_WAY) || (builtin_type == MESSAGE_OOPS) ) {
1543                 // if it is already playing, then don't play it
1544                 if ( message_playing_specific_builtin(builtin_type) ) 
1545                         return;
1546
1547                 for ( i = 0; i < MessageQ_num; i++ ) {
1548                         // if one of these messages is already queued, the don't play
1549                         if ( (MessageQ[i].message_num == message_num) && (MessageQ[i].builtin_type == builtin_type) )
1550                                 return;
1551
1552                 }
1553         }
1554
1555         // check to be sure that we haven't reached our max limit on these messages yet.
1556         if ( MessageQ_num == MAX_MESSAGE_Q ) {
1557                 Int3();                                                                                 
1558                 return;
1559         }
1560
1561         // if player is a traitor, no messages for him!!!
1562         if ( Player_ship->team == TEAM_TRAITOR ) {
1563                 return;
1564         }
1565
1566         m_persona = Messages[message_num].persona_index;
1567
1568         // put the message into a slot
1569         i = MessageQ_num;
1570         MessageQ[i].time_added = Missiontime;
1571         MessageQ[i].priority = priority;
1572         MessageQ[i].message_num = message_num;
1573         MessageQ[i].source = source;
1574         MessageQ[i].builtin_type = builtin_type;
1575         MessageQ[i].min_delay_stamp = timestamp(delay);
1576         MessageQ[i].group = group;
1577         strcpy(MessageQ[i].who_from, who_from);
1578
1579         // SPECIAL HACK -- if the who_from is terran command, and there is a wingman persona attached
1580         // to this message, then set a bit to tell the wave/anim playing code to play the command version
1581         // of the wave and head
1582         MessageQ[i].flags = 0;
1583         if ( !stricmp(who_from, TERRAN_COMMAND) && (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1584                 MessageQ[i].flags |= MQF_CONVERT_TO_COMMAND;
1585                 MessageQ[i].source = HUD_SOURCE_TERRAN_CMD;
1586         }
1587
1588         if ( (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1589                 if ( !strstr(who_from, ".wav") ) {
1590                         MessageQ[i].flags |= MQF_CHECK_ALIVE;
1591                 }
1592         }
1593
1594         // set the timestamp of when to play this message based on the 'timing' value
1595         if ( timing == MESSAGE_TIME_IMMEDIATE )
1596                 MessageQ[i].window_timestamp = timestamp(MESSAGE_IMMEDIATE_TIMESTAMP);
1597         else if ( timing == MESSAGE_TIME_SOON )
1598                 MessageQ[i].window_timestamp = timestamp(MESSAGE_SOON_TIMESTAMP);
1599         else
1600                 MessageQ[i].window_timestamp = timestamp(MESSAGE_ANYTIME_TIMESTAMP);            // make invalid
1601
1602         MessageQ_num++;
1603         qsort(MessageQ, MAX_MESSAGE_Q, sizeof(message_q), message_queue_priority_compare);
1604
1605         // Try to start it!
1606         // MWA -- called every frame from game loop
1607         //message_queue_process();
1608 }
1609
1610 // function to return the persona index of the given ship.  If it isn't assigned, it will be
1611 // in this function.  persona_type could be a wingman, Terran Command, or other generic ship
1612 // type personas.  ship is the ship we should assign a persona to
1613 int message_get_persona( ship *shipp )
1614 {
1615         int i, ship_type, slist[MAX_PERSONAS], count;
1616
1617         if ( shipp != NULL ) {
1618                 // see if this ship has a persona
1619                 if ( shipp->persona_index != -1 )
1620                         return shipp->persona_index;
1621
1622                 // get the type of ship (i.e. support, fighter/bomber, etc)
1623                 ship_type = Ship_info[shipp->ship_info_index].flags;
1624
1625                 // shorcut for Vasudan personas.  All vasudan fighters/bombers use the same persona.  All Vasudan
1626                 // large ships will use the same persona
1627                 if ( Ship_info[shipp->ship_info_index].species == SPECIES_VASUDAN ) {
1628                         int persona_needed;
1629
1630                         if ( ship_type & (SIF_FIGHTER|SIF_BOMBER) ) {
1631                                 persona_needed = PERSONA_FLAG_WINGMAN;
1632                         } else if ( ship_type & SIF_SUPPORT ) {
1633                                 persona_needed = PERSONA_FLAG_SUPPORT;
1634                         } else {
1635                                 persona_needed = PERSONA_FLAG_LARGE;
1636                         }
1637
1638                         // iternate through the persona list finding the one that we need
1639                         for ( i = 0; i < Num_personas; i++ ) {
1640                                 if ( (Personas[i].flags & persona_needed) && (Personas[i].flags & PERSONA_FLAG_VASUDAN) ) {
1641                                         nprintf(("messaging", "assigning vasudan persona %s to %s\n", Personas[i].name, shipp->ship_name));
1642                                         return i;
1643                                 }
1644                         }
1645
1646                         // make support personas use the terran persona by not returning here when looking for 
1647                         // Vasudan persona
1648                         if ( persona_needed != PERSONA_FLAG_SUPPORT )
1649                                 return -1;                      // shouldn't get here eventually, but return -1 for now to deal with missing persona
1650                 }
1651
1652                 // iterate through the persona list looking for one not used.  Look at the type of persona
1653                 // and try to determine appropriate personas to use.
1654                 for ( i = 0; i < Num_personas; i++ ) {
1655
1656                         // if this is a vasudan persona -- skip it
1657                         if ( Personas[i].flags & PERSONA_FLAG_VASUDAN )
1658                                 continue;
1659
1660                         // check the ship types, and don't try to assign those which don't type match
1661                         if ( (ship_type & SIF_SUPPORT) && !(Personas[i].flags & PERSONA_FLAG_SUPPORT) )
1662                                 continue;
1663                         else if ( (ship_type & (SIF_FIGHTER|SIF_BOMBER)) && !(Personas[i].flags & PERSONA_FLAG_WINGMAN) )
1664                                 continue;
1665                         else if ( !(ship_type & (SIF_FIGHTER|SIF_BOMBER|SIF_SUPPORT)) && !(Personas[i].flags & PERSONA_FLAG_LARGE) )
1666                                 continue;
1667
1668                         if ( !(Personas[i].flags & PERSONA_FLAG_USED) ) {
1669                                 nprintf(("messaging", "assigning persona %s to %s\n", Personas[i].name, shipp->ship_name));
1670                                 Personas[i].flags |= PERSONA_FLAG_USED;
1671                                 return i;
1672                         }
1673                 }
1674
1675                 // grab a random one, and reuse it (staying within type specifications)
1676                 count = 0;
1677                 for ( i = 0; i < Num_personas; i++ ) {
1678
1679                         // see if ship meets our criterea
1680                         if ( (ship_type & SIF_SUPPORT) && !(Personas[i].flags & PERSONA_FLAG_SUPPORT) )
1681                                 continue;
1682                         else if ( (ship_type & (SIF_FIGHTER|SIF_BOMBER)) && !(Personas[i].flags & PERSONA_FLAG_WINGMAN) )
1683                                 continue;
1684                         else if ( !(ship_type & (SIF_FIGHTER|SIF_BOMBER|SIF_SUPPORT)) && !(Personas[i].flags & PERSONA_FLAG_LARGE) )
1685                                 continue;
1686                         else if ( Personas[i].flags & PERSONA_FLAG_VASUDAN )            // don't use any vasudan persona
1687                                 continue;
1688
1689                         slist[count] = i;
1690                         count++;
1691                 }
1692
1693                 // couldn't find appropriate persona type
1694                 if ( count == 0 )
1695                         return -1;
1696
1697                 // now get a random one from the list
1698                 i = (rand() % count);
1699                 i = slist[i];
1700                         
1701                 nprintf(("messaging", "Couldn't find a new persona for ship %s, reusing persona %s\n", shipp->ship_name, Personas[i].name));
1702
1703                 return i;
1704         }
1705
1706         // for now -- we don't support other types of personas (non-wingman personas)
1707         Int3();
1708         return 0;
1709 }
1710
1711 // given a message id#, should it be filtered for me?
1712 int message_filter_multi(int id)
1713 {
1714         // not multiplayer
1715         if(!(Game_mode & GM_MULTIPLAYER)){
1716                 return 0;
1717         }
1718
1719         // bogus
1720         if((id < 0) || (id >= Num_messages)){
1721                 mprintf(("Filtering bogus mission message!\n"));
1722                 return 1;
1723         }
1724
1725         // builtin messages
1726         if(id < Num_builtin_messages){
1727         }
1728         // mission-specific messages
1729         else {
1730                 // not team filtered
1731                 if(Messages[id].multi_team < 0){
1732                         return 0;
1733                 }
1734
1735                 // not TvT
1736                 if(!(Netgame.type_flags & NG_TYPE_TEAM)){
1737                         return 0;
1738                 }
1739
1740                 // is this for my team?
1741                 if((Net_player != NULL) && (Net_player->p_info.team != Messages[id].multi_team)){
1742                         mprintf(("Filtering team-based mission message!\n"));
1743                         return 1;
1744                 }
1745         }               
1746         
1747         return 0;
1748 }
1749
1750 // send_unique_to_player sends a mission unique (specific) message to the player (possibly a multiplayer
1751 // person).  These messages are *not* the builtin messages
1752 void message_send_unique_to_player( char *id, void *data, int m_source, int priority, int group, int delay )
1753 {
1754         int i, source;
1755         char *who_from;
1756
1757         source = 0;
1758         who_from = NULL;
1759         for (i=0; i<Num_messages; i++) {
1760                 // find the message
1761                 if ( !stricmp(id, Messages[i].name) ) {
1762
1763                         // if the ship pointer and special_who are both NULL then this is from generic "Terran Command"
1764                         // if the ship is NULL and special_who is not NULL, then this is from special_who
1765                         // otherwise, message is from ship.
1766                         if ( m_source == MESSAGE_SOURCE_COMMAND ) {
1767                                 who_from = TERRAN_COMMAND;
1768                                 source = HUD_SOURCE_TERRAN_CMD;
1769                         } else if ( m_source == MESSAGE_SOURCE_SPECIAL ) {
1770                                 who_from = (char *)data;
1771                                 source = HUD_SOURCE_TERRAN_CMD;
1772                         } else if ( m_source == MESSAGE_SOURCE_WINGMAN ) {
1773                                 int m_persona, ship_index;
1774
1775                                 // find a wingman with the same persona as this message.  If the message's persona doesn't
1776                                 // exist, we will use Terran command
1777                                 m_persona = Messages[i].persona_index;
1778                                 if ( m_persona == -1 ) {
1779                                         mprintf(("Warning:  Message %d has no persona assigned.\n", i));
1780                                 }
1781
1782                                 // get a ship                                           
1783                                 ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS, 0.0f, m_persona, 1, Messages[i].multi_team);
1784
1785                                 // if the ship_index is -1, then make the message come from Terran command
1786                                 if ( ship_index == -1 ) {
1787                                         who_from = TERRAN_COMMAND;
1788                                         source = HUD_SOURCE_TERRAN_CMD;
1789                                 } else {
1790                                         who_from = Ships[ship_index].ship_name;
1791                                         source = HUD_get_team_source(Ships[ship_index].team);
1792                                 }
1793
1794                         } else if ( m_source == MESSAGE_SOURCE_SHIP ) {
1795                                 ship *shipp;
1796
1797                                 shipp = (ship *)data;
1798                                 who_from = shipp->ship_name;
1799                                 source = HUD_get_team_source(shipp->team);
1800
1801                                 // be sure that this ship can actually send a message!!! (i.e. not-not-flyable -- get it!)
1802                                 Assert( !(Ship_info[shipp->ship_info_index].flags & SIF_NOT_FLYABLE) );         // get allender or alan
1803                         }
1804
1805                         // not multiplayer or this message is for me, then queue it
1806                         // if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
1807
1808                         // maybe filter it out altogether
1809                         if(!message_filter_multi(i)){
1810                                 message_queue_message( i, priority, MESSAGE_TIME_ANYTIME, who_from, source, group, delay );
1811                         }
1812
1813                         // record to the demo if necessary
1814                         if(Game_mode & GM_DEMO_RECORD){
1815                                 demo_POST_unique_message(id, who_from, m_source, priority);
1816                         }
1817                         // }
1818
1819                         // send a message packet to a player if destined for everyone or only a specific person
1820                         if ( MULTIPLAYER_MASTER ){
1821                                 send_mission_message_packet( i, who_from, priority, MESSAGE_TIME_SOON, source, -1, -1, -1);
1822                         }                       
1823
1824                         return;         // all done with displaying             
1825                 }
1826         }
1827         nprintf (("messaging", "Couldn't find message id %s to send to player!\n", id ));
1828 }
1829
1830 // send builtin_to_player sends a message (from messages.tbl) to the player.  These messages are
1831 // the generic infomrational type messages.  The have priorities like misison specific messages,
1832 // and use a timing to tell how long we should wait before playing this message
1833 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 )
1834 {
1835         int i, persona_index;
1836         int source;     
1837
1838         // if we aren't showing builtin msgs, bail
1839         if (The_mission.flags & MISSION_FLAG_NO_BUILTIN_MSGS) {
1840                 return;
1841         }
1842
1843         // see if there is a persona assigned to this ship.  If not, then try to assign one!!!
1844         if ( shipp ) {
1845                 if ( shipp->persona_index == -1 ){
1846                         shipp->persona_index = message_get_persona( shipp );
1847                 }
1848
1849                 persona_index = shipp->persona_index;
1850                 if ( persona_index == -1 ) {
1851                         nprintf(("messaging", "Couldn't find persona for %s\n", shipp->ship_name ));
1852                 }               
1853
1854                 // be sure that this ship can actually send a message!!! (i.e. not-not-flyable -- get it!)
1855                 Assert( !(Ship_info[shipp->ship_info_index].flags & SIF_NOT_FLYABLE) );         // get allender or alan
1856         } else {
1857                 persona_index = Command_persona;                                // use the terran command persona
1858         }
1859
1860         // try to find a builtin message with the given type for the given persona
1861         // make a loop out of this routne since we may try to play a message in the wrong
1862         // persona if we can't find the right message for the given persona
1863         do {
1864                 for ( i = 0; i < Num_builtin_messages; i++ ) {
1865                         char *name, *who_from;
1866
1867                         name = Builtin_message_types[type];
1868
1869                         // see if the have the type of message
1870                         if ( stricmp(Messages[i].name, name) ){
1871                                 continue;
1872                         }
1873
1874                         // must have the correct persona.  persona_index of -1 means find the first
1875                         // message possibly of the correct type
1876                         if ( (persona_index != -1 ) && (Messages[i].persona_index != persona_index) ){
1877                                 continue;
1878                         }
1879
1880                         // get who this message is from -- kind of a hack since we assume Terran Command in the
1881                         // absense of a ship.  This will be fixed later
1882                         if ( shipp ) {
1883                                 source = HUD_get_team_source( shipp->team );
1884                                 who_from = shipp->ship_name;
1885                         } else {
1886                                 source = HUD_SOURCE_TERRAN_CMD;
1887                                 who_from = TERRAN_COMMAND;
1888                         }
1889
1890                         // maybe change the who from here for special rearm cases (always seems like that is the case :-) )
1891                         if ( !stricmp(who_from, TERRAN_COMMAND) && (type == MESSAGE_REARM_ON_WAY) ){
1892                                 who_from = SUPPORT_NAME;
1893                         }
1894
1895                         // determine what we should actually do with this dang message.  In multiplayer, we must
1896                         // deal with the fact that this message might not get played on my machine if I am a server
1897
1898                         // not multiplayer or this message is for me, then queue it
1899                         if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
1900
1901                                 // if this filter matches mine
1902                                 if( (multi_team_filter < 0) || !(Netgame.type_flags & NG_TYPE_TEAM) || ((Net_player != NULL) && (Net_player->p_info.team == multi_team_filter)) ){
1903                                         message_queue_message( i, priority, timing, who_from, source, group, delay, type );
1904
1905                                         // post a builtin message
1906                                         if(Game_mode & GM_DEMO_RECORD){
1907                                                 demo_POST_builtin_message(type, shipp, priority, timing);
1908                                         }
1909                                 }
1910                         }
1911
1912                         // send a message packet to a player if destined for everyone or only a specific person
1913                         if ( MULTIPLAYER_MASTER ) {
1914                                 // only send a message if it is of a particular type
1915                                 if(multi_target == -1){
1916                                         if(multi_message_should_broadcast(type)){                               
1917                                                 send_mission_message_packet( i, who_from, priority, timing, source, type, -1, multi_team_filter );
1918                                         }
1919                                 } else {
1920                                         send_mission_message_packet( i, who_from, priority, timing, source, type, multi_target, multi_team_filter );
1921                                 }
1922                         }
1923
1924                         return;         // all done with displaying
1925                 }
1926
1927                 if ( persona_index >= 0 ) {
1928                         nprintf(("messaging", "Couldn't find builtin message %s for persona %d\n", Builtin_message_types[type], persona_index ));
1929                         nprintf(("messaging", "looking for message for any persona\n"));
1930                         persona_index = -1;
1931                 } else {
1932                         persona_index = -999;           // used here and the next line only -- hard code bad, but I'm lazy
1933                 }
1934         } while ( persona_index != -999 );
1935 }
1936
1937 // message_is_playing()
1938 //
1939 // Return the Message_playing flag.  Message_playing is local to MissionMessage.cpp, but
1940 // this info is needed by code in HUDsquadmsg.cpp
1941 //
1942 int message_is_playing()
1943 {
1944         return Num_messages_playing?1:0;
1945 }
1946
1947 // Functions below pertain only to personas!!!!
1948
1949 // given a character string, try to find the persona index
1950 int message_persona_name_lookup( char *name )
1951 {
1952         int i;
1953
1954         for (i = 0; i < Num_personas; i++ ) {
1955                 if ( !stricmp(Personas[i].name, name) )
1956                         return i;
1957         }
1958
1959         return -1;
1960 }
1961
1962
1963 // Blank out portions of the audio playback for the sound identified by Message_wave
1964 // This works by using the same Distort_pattern[][] that was used to distort the associated text
1965 void message_maybe_distort()
1966 {
1967         int i;
1968         int was_muted;
1969
1970         if ( Num_messages_playing == 0 )
1971                 return;
1972         
1973         for ( i = 0; i < Num_messages_playing; i++ ) {
1974                 if ( !snd_is_playing(Playing_messages[i].wave) )
1975                         return;
1976         }
1977
1978         // distort the number of voices currently playing
1979         for ( i = 0; i < Num_messages_playing; i++ ) {
1980                 Assert(Playing_messages[i].wave >= 0 );
1981
1982                 was_muted = 0;
1983
1984                 // added check to see if EMP effect was active
1985                 // 8/24/98 - DB
1986                 if ( (hud_communications_state(Player_ship) != COMM_OK) || emp_active_local() ) {
1987                         was_muted = Message_wave_muted;
1988                         if ( timestamp_elapsed(Next_mute_time) ) {
1989                                 Next_mute_time = fl2i(Distort_patterns[Distort_num][Distort_next++] * Message_wave_duration);
1990                                 if ( Distort_next >= MAX_DISTORT_LEVELS )
1991                                         Distort_next = 0;
1992
1993                                 Message_wave_muted ^= 1;
1994                         }
1995                 
1996                         if ( Message_wave_muted ) {
1997                                 if ( !was_muted )
1998                                         snd_set_volume(Playing_messages[i].wave, 0.0f);
1999                         } else {
2000                                 if ( was_muted )
2001                                         snd_set_volume(Playing_messages[i].wave, Master_sound_volume);
2002                         }
2003                 }
2004         }
2005 }
2006
2007
2008 // if the player communications systems are heavily damaged, distort incoming messages.
2009 //
2010 // first case: Message_wave_duration == 0 (this occurs when there is no associated voice playback)
2011 //                                      Blank out random runs of characters in the message
2012 //
2013 // second case: Message_wave_duration > 0 (occurs when voice playback accompainies message)
2014 //                                       Blank out portions of the sound based on Distort_num, this this is that same
2015 //                                       data that will be used to blank out portions of the audio playback
2016 //
2017 void message_maybe_distort_text(char *text)
2018 {
2019         int i, j, len, run, curr_offset, voice_duration, next_distort;
2020
2021         if ( (hud_communications_state(Player_ship) == COMM_OK) && !emp_active_local() ) { 
2022                 return;
2023         }
2024
2025         len = strlen(text);
2026         if ( Message_wave_duration == 0 ) {
2027                 next_distort = 5+myrand()%5;
2028                 for ( i = 0; i < len; i++ ) {
2029                         if ( i == next_distort ) {
2030                                 run = 3+myrand()%5;
2031                                 if ( i+run > len )
2032                                         run = len-i;
2033                                 for ( j = 0; j < run; j++) {
2034                                         text[i++] = '-';
2035                                         if ( i >= len )
2036                                                 break;
2037                                 }
2038                                 next_distort = i + (5+myrand()%5);
2039                         }
2040                 }
2041                 return;
2042         }
2043
2044         voice_duration = Message_wave_duration;
2045
2046         // distort text
2047         Distort_num = myrand()%MAX_DISTORT_PATTERNS;
2048         Distort_next = 0;
2049         curr_offset = 0;
2050         while (voice_duration > 0) {
2051                 run = fl2i(Distort_patterns[Distort_num][Distort_next] * len);
2052                 if (Distort_next & 1) {
2053                         for ( i = curr_offset; i < min(len, curr_offset+run); i++ ) {
2054                                 if ( text[i] != ' ' ) 
2055                                         text[i] = '-';
2056                         }
2057                         curr_offset = i;
2058                         if ( i >= len )
2059                                 break;
2060                 } else {
2061                         curr_offset += run;
2062                 }
2063
2064                 voice_duration -= fl2i(Distort_patterns[Distort_num][Distort_next]*Message_wave_duration);
2065                 Distort_next++;
2066                 if ( Distort_next >= MAX_DISTORT_LEVELS )
2067                         Distort_next = 0;
2068         };
2069         
2070         Distort_next = 0;
2071 }
2072
2073 // return 1 if a talking head animation is playing, otherwise return 0
2074 int message_anim_is_playing()
2075 {
2076         int i;
2077
2078         for (i = 0; i < Num_messages_playing; i++ ) {
2079                 if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) )
2080                         return 1;
2081         }
2082
2083         return 0;
2084 }
2085
2086 // Load mission messages (this is called by the level paging code when running with low memory)
2087 void message_pagein_mission_messages()
2088 {
2089         int i;
2090         
2091         mprintf(("Paging in mission messages\n"));
2092
2093         if (Num_messages <= Num_builtin_messages) {
2094                 return;
2095         }
2096
2097         char *sound_filename;
2098
2099         for (i=Num_builtin_messages; i<Num_messages; i++) {
2100                 if (Messages[i].wave_info.index != -1) {
2101                         sound_filename = Message_waves[Messages[i].wave_info.index].name;
2102                         message_load_wave(Messages[i].wave_info.index, sound_filename);
2103                 }
2104         }
2105 }
2106