FS2_Open
Open source remastering of the Freespace 2 engine
missionmessage.cpp
Go to the documentation of this file.
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 the
6  * source.
7  *
8 */
9 
10 
11 
12 
13 #include "anim/animplay.h"
15 #include "gamesnd/gamesnd.h"
16 #include "hud/hud.h"
17 #include "hud/hudconfig.h"
18 #include "hud/hudgauges.h"
19 #include "hud/hudmessage.h"
20 #include "hud/hudtarget.h"
21 #include "iff_defs/iff_defs.h"
22 #include "io/timer.h"
23 #include "localization/localize.h"
24 #include "mission/missionmessage.h"
26 #include "mod_table/mod_table.h"
27 #include "network/multi.h"
28 #include "network/multimsgs.h"
29 #include "network/multiutil.h"
30 #include "parse/parselo.h"
31 #include "parse/scripting.h"
32 #include "parse/sexp.h"
33 #include "ship/ship.h"
34 #include "ship/subsysdamage.h"
35 #include "sound/fsspeech.h"
37 #include "weapon/emp.h"
38 
41 
43 // here is the list of the builtin message names and the settings which control how frequently
44 // they are heard. These names are used to match against names read in for builtin message
45 // radio bits to see what message to play.
46 // These are generic names, meaning that there will be the same message type for a
47 // number of different personas
49 {
50 //XSTR:OFF
51  {"Arrive Enemy", 100, -1, 0},
52  {"Attack Target", 100, -1, 0},
53  {"Beta Arrived", 100, -1, 0},
54  {"Check 6", 100, 2, 6000},
55  {"Engage", 100, -1, 0},
56  {"Gamma Arrived", 100, -1, 0},
57  {"Help", 100, 10, 60000},
58  {"Praise", 100, 10, 60000},
59  {"Backup", 100, -1, 0},
60  {"Ignore Target", 100, -1, 0},
61  {"No", 100, -1, 0},
62  {"Oops 1", 100, -1, 0},
63  {"Permission", 100, -1, 0}, // AL: no code support yet
64  {"Stray", 100, -1, 0}, // DA: no code support
65  {"Depart", 100, -1, 0},
66  {"yes", 100, -1, 0},
67  {"Rearm on Way", 100, -1, 0},
68  {"On way", 100, -1, 0},
69  {"Rearm warping in", 100, -1, 0},
70  {"No Target", 100, -1, 0},
71  {"Docking Start", 100, -1, 0}, // AL: no message seems to exist for this
72  {"Repair Done", 100, -1, 0},
73  {"Repair Aborted", 100, -1, 0},
74  {"Traitor", 100, -1, 0},
75  {"Rearm", 100, -1, 0},
76  {"Disable Target", 100, -1, 0},
77  {"Disarm Target", 100, -1, 0},
78  {"Player Dead", 100, -1, 0},
79  {"Death", 50, 10, 60000},
80  {"Support Killed", 100, -1, 0},
81  {"All Clear", 100, -1, 0}, // DA: no code support
82  {"All Alone", 100, -1, 0},
83  {"Repair", 100, -1, 0},
84  {"Delta Arrived", 100, -1, 0},
85  {"Epsilon Arrived", 100, -1, 0},
86  {"Instructor Hit", 100, -1, 0},
87  {"Instructor Attack", 100, -1, 0},
88  {"Stray Warning", 100, -1, 0},
89  {"Stray Warning Final", 100, -1, 0},
90  {"AWACS at 75", 100, -1, 0},
91  {"AWACS at 25", 100, -1, 0},
92  {"Praise Self", 10, 4, 60000},
93  {"High Praise", 100, -1, 0},
94  {"Rearm Primaries", 100, -1, 0},
95  {"Primaries Low", 100, -1, 0},
96  //XSTR:ON
97 };
98 
100 
103 
105 
108 
109 #define MAX_PLAYING_MESSAGES 2
110 
111 #define MAX_WINGMAN_HEADS 2
112 #define MAX_COMMAND_HEADS 3
113 
114 //XSTR:OFF
115 #define HEAD_PREFIX_STRING "head-"
116 #define COMMAND_HEAD_PREFIX "head-cm1"
117 #define COMMAND_WAVE_PREFIX "TC_"
118 #define SUPPORT_NAME "Support"
119 //XSTR:ON
120 
121 // variables to keep track of messages that are currently playing
122 int Num_messages_playing; // number of is a message currently playing?
123 
124 /*typedef struct pmessage {
125  //anim_instance *anim; // handle of anim currently playing
126  anim *anim_data; // animation data to be used by the talking head HUD gauge handler
127  int start_frame; // the start frame needed to play the animation
128  bool play_anim; // used to tell HUD gauges if they should be playing or not
129  int wave; // handle of wave currently playing
130  int id; // id of message currently playing
131  int priority; // priority of message currently playing
132  int shipnum; // shipnum of ship sending this message, -1 if from Terran command
133  int builtin_type; // if a builtin message, type of the message
134 } pmessage;*/
135 
137 
138 int Message_shipnum; // ship number of who is sending message to player -- used outside this module
139 int Message_expire; // timestamp to extend the duration of message brackets when not using voice files
140 
141 // variables to control message queuing. All new messages to the player are queued. The array
142 // will be ordered by priority, then time submitted.
143 
144 #define MQF_CONVERT_TO_COMMAND (1<<0) // convert this queued message to terran command
145 #define MQF_CHECK_ALIVE (1<<1) // check for the existence of who_from before sending
146 
147 typedef struct message_q {
148  fix time_added; // time at which this entry was added
149  int window_timestamp; // timestamp which will tell us how long we have to play the message
150  int priority; // priority of the message
151  int message_num; // index into the Messages[] array
152  char *special_message; // Goober5000 - message to play if we've replaced stuff (like variables)
153  char who_from[NAME_LENGTH]; // who this message is from
154  int source; // who the source of the message is (HUD_SOURCE_* type)
155  int builtin_type; // type of builtin message (-1 if mission message)
156  int flags; // should this message entry be converted to Terran Command head/wave file
157  int min_delay_stamp; // minimum delay before this message will start playing
158  int group; // message is part of a group, don't time it out
159 } message_q;
160 
161 #define MAX_MESSAGE_Q 30
162 #define MAX_MESSAGE_LIFE F1_0*30 // After being queued for 30 seconds, don't play it
163 #define DEFAULT_MESSAGE_LENGTH 3000 // default number of milliseconds to display message indicator on hud
165 int MessageQ_num; // keeps track of number of entries on the queue.
166 
167 #define MESSAGE_IMMEDIATE_TIMESTAMP 1000 // immediate messages must play within 1 second
168 #define MESSAGE_SOON_TIMESTAMP 5000 // "soon" messages must play within 5 seconds
169 #define MESSAGE_ANYTIME_TIMESTAMP -1 // anytime timestamps are invalid
170 
171 // Persona information
174 
176 {
177 //XSTR:OFF
178  "wingman",
179  "support",
180  "large",
181  "command",
182 //XSTR:ON
183 };
184 
186 
187 // Goober5000
188 // NOTE - these are truncated filenames, i.e. without extensions
190 
192 // used to distort incoming messages when comms are damaged
194 static int Message_wave_muted;
195 static int Message_wave_duration;
196 static int Next_mute_time;
197 
198 #define MAX_DISTORT_PATTERNS 2
199 #define MAX_DISTORT_LEVELS 6
200 static float Distort_patterns[MAX_DISTORT_PATTERNS][MAX_DISTORT_LEVELS] =
201 {
202  {0.20f, 0.20f, 0.20f, 0.20f, 0.20f, 0.20f},
203  {0.10f, 0.20f, 0.25f, 0.25f, 0.05f, 0.15f}
204 };
205 
206 static int Distort_num; // which distort pattern is being used
207 static int Distort_next; // which section of distort pattern is next
208 
210  { // GR_640
211  7, 45
212  },
213  { // GR_1024
214  7, 66
215  }
216 };
217 
218 // forward declarations
219 void message_maybe_distort_text(char *text, int shipnum);
220 int comm_between_player_and_ship(int other_shipnum);
221 
222 // following functions to parse messages.tbl -- code pretty much ripped from weapon/ship table parsing code
223 
224 static void persona_parse_close()
225 {
226  if (Personas != NULL) {
227  vm_free(Personas);
228  Personas = NULL;
229  }
230 }
231 
232 // functions to deal with parsing personas. Personas are just a list of names that give someone
233 // sending a message an identity which spans the life of the mission
235 {
236  int i;
237  char type[NAME_LENGTH];
238 
239  static bool done_at_exit = false;
240  if ( !done_at_exit ) {
241  atexit( persona_parse_close );
242  done_at_exit = true;
243  }
244 
245  // this way should cause the least amount of problems on the various platforms - taylor
246  Personas = (Persona*)vm_realloc( Personas, sizeof(Persona) * (Num_personas + 1) );
247 
248  if (Personas == NULL)
249  Error(LOCATION, "Not enough memory to allocate Personas!" );
250 
251  memset(&Personas[Num_personas], 0, sizeof(Persona));
252 
253  required_string("$Persona:");
254  stuff_string(Personas[Num_personas].name, F_NAME, NAME_LENGTH);
255 
256  // get the type name and set the appropriate flag
257  required_string("$Type:");
258  stuff_string( type, F_NAME, NAME_LENGTH );
259  for ( i = 0; i < MAX_PERSONA_TYPES; i++ ) {
260  if ( !stricmp( type, Persona_type_names[i]) ) {
261 
262  Personas[Num_personas].flags |= (1<<i);
263 
264  // save the Command persona in a global
265  if ( Personas[Num_personas].flags & PERSONA_FLAG_COMMAND ) {
266  // always use the most recent Command persona
267  // found, since that's how retail does it
269  }
270 
271  break;
272  }
273  }
274 
275  if ( i == MAX_PERSONA_TYPES )
276  WarningEx(LOCATION, "Unknown persona type in messages.tbl -- %s\n", type );
277 
278  char cstrtemp[NAME_LENGTH];
279  if ( optional_string("+") )
280  {
281  int j;
282  stuff_string(cstrtemp, F_NAME, NAME_LENGTH);
283 
284  for (j = 0; j < (int)Species_info.size(); j++)
285  {
286  if (!strcmp(cstrtemp, Species_info[j].species_name))
287  {
288  Personas[Num_personas].species = j;
289  break;
290  }
291  }
292 
293  if ( j == (int)Species_info.size() )
294  WarningEx(LOCATION, "Unknown species in messages.tbl -- %s\n", cstrtemp );
295  }
296 
297  if (optional_string("$Allow substitution of missing messages:")) {
298  stuff_boolean(&Personas[Num_personas].substitute_missing_messages);
299  }
300  else
301  {
302  Personas[Num_personas].substitute_missing_messages = true;
303  }
304 
305  Num_personas++;
306 }
307 
308 // two functions to add avi/wave names into a table
309 int add_avi( char *avi_name )
310 {
311  int i;
312  message_extra extra;
313 
314  Assert (strlen(avi_name) < MAX_FILENAME_LEN );
315 
316  // check to see if there is an existing avi being used here
317  for ( i = 0; i < (int)Message_avis.size(); i++ ) {
318  if ( !stricmp(Message_avis[i].name, avi_name) )
319  return i;
320  }
321 
322  // would have returned if a slot existed.
323  generic_anim_init( &extra.anim_data, avi_name );
324  strcpy_s( extra.name, avi_name );
325  extra.num = -1;
326  generic_anim_load(&extra.anim_data); // load only to validate the anim
327  generic_anim_unload(&extra.anim_data); // unload to not waste bmpman slots
328  Message_avis.push_back(extra);
330  return ((int)Message_avis.size() - 1);
331 }
332 
333 int add_wave( const char *wave_name )
334 {
335  int i;
336  message_extra extra;
337 
338  Assert (strlen(wave_name) < MAX_FILENAME_LEN );
339 
340  // check to see if there is an existing wave being used here
341  for ( i = 0; i < (int)Message_waves.size(); i++ ) {
342  if ( !stricmp(Message_waves[i].name, wave_name) )
343  return i;
344  }
345 
346  generic_anim_init( &extra.anim_data );
347  strcpy_s( extra.name, wave_name );
348  extra.num = -1;
349  Message_waves.push_back(extra);
351  return ((int)Message_waves.size() - 1);
352 }
353 
354 // parses an individual message
355 void message_parse(bool importing_from_fsm)
356 {
357  MissionMessage msg;
358  char persona_name[NAME_LENGTH];
359 
360  required_string("$Name:");
362 
363  // team
364  msg.multi_team = -1;
365  if(optional_string("$Team:")){
366  int mt;
367  stuff_int(&mt);
368 
369  // keep it real
370  if((mt < 0) || (mt >= MAX_TVT_TEAMS)){
371  mt = -1;
372  }
373 
374  // only bother with filters in-game if multiplayer and TvT
375  if(Fred_running || (MULTI_TEAM) ){
376  msg.multi_team = mt;
377  }
378  }
379 
380  // backwards compatibility for old fred missions - all new ones should use $MessageNew
381  if(optional_string("$Message:")){
383  } else {
384  required_string("$MessageNew:");
386  }
387 
388  msg.persona_index = -1;
389  if ( optional_string("+Persona:") ) {
390  stuff_string(persona_name, F_NAME, NAME_LENGTH);
391  msg.persona_index = message_persona_name_lookup( persona_name );
392 
393  if ( msg.persona_index == -1 )
394  WarningEx(LOCATION, "Unknown persona in message %s in messages.tbl -- %s\n", msg.name, persona_name );
395  }
396 
397  if ( !Fred_running)
398  msg.avi_info.index = -1;
399  else
400  msg.avi_info.name = NULL;
401 
402  if ( optional_string("+AVI Name:") ) {
403  char avi_name[MAX_FILENAME_LEN];
404 
406 
407  // Goober5000 - for some reason :V: swapped Head-TP1
408  // and Head-TP4 in FS2
409  if (importing_from_fsm && !strnicmp(avi_name, "Head-TP1", 8))
410  avi_name[7] = '4';
411 
412  if ( !Fred_running ) {
413  msg.avi_info.index = add_avi(avi_name);
414  } else {
415  msg.avi_info.name = vm_strdup(avi_name);
416  }
417  }
418 
419  if ( !Fred_running )
420  msg.wave_info.index = -1;
421  else
422  msg.wave_info.name = NULL;
423 
424  if ( optional_string("+Wave Name:") ) {
425  char wave_name[MAX_FILENAME_LEN];
426 
427  stuff_string(wave_name, F_NAME, MAX_FILENAME_LEN);
428  if ( !Fred_running ) {
429  msg.wave_info.index = add_wave(wave_name);
430  } else {
431  msg.wave_info.name = vm_strdup(wave_name);
432  }
433  }
434 
435  if ( optional_string("$Mood:")) {
436  SCP_string buf;
437  bool found = false;
438 
439  stuff_string(buf, F_NAME);
440  for (SCP_vector<SCP_string>::iterator iter = Builtin_moods.begin(); iter != Builtin_moods.end(); ++iter) {
441  if (iter->compare(buf) == 0) {
442  msg.mood = iter - Builtin_moods.begin();
443  found = true;
444  break;
445  }
446  }
447 
448  if (!found) {
449  // found a mood, but it's not in the list of moods at the start of the table
450  Warning(LOCATION, "Message.tbl has an entry for mood type %s, but this mood is not in the #Moods section of the table.", buf.c_str());
451  }
452  }
453  else {
454  msg.mood = 0;
455  }
456 
457  if ( optional_string("$Exclude Mood:")) {
459  bool found = false;
460 
461  stuff_string_list(buff);
462  for (SCP_vector<SCP_string>::iterator parsed_moods = buff.begin(); parsed_moods != buff.end(); ++parsed_moods) {
463  for (SCP_vector<SCP_string>::iterator iter = Builtin_moods.begin(); iter != Builtin_moods.end(); ++iter) {
464  if (!stricmp(iter->c_str(), parsed_moods->c_str())) {
465  msg.excluded_moods.push_back(iter - Builtin_moods.begin());
466  found = true;
467  break;
468  }
469  }
470 
471  if (!found) {
472  // found a mood, but it's not in the list of moods at the start of the table
473  Warning(LOCATION, "Message.tbl has an entry for exclude mood type %s, but this mood is not in the #Moods section of the table.", parsed_moods->c_str());
474  }
475  }
476  }
477 
478  Num_messages++;
479  Messages.push_back(msg);
480 }
481 
483 {
484  char name[32];
485  int i, max_count, min_delay, occurrence_chance;
486  int builtin_type = -1;
487 
488  required_string("$Name:");
490 
491  for (i = 0; i < MAX_BUILTIN_MESSAGE_TYPES; i++) {
492  if (!strcmp(name, Builtin_messages[i].name)) {
493  builtin_type = i;
494  break;
495  }
496  }
497 
498  if (builtin_type == -1) {
499  Warning(LOCATION, "Unknown Builtin Message Type Detected. Type : %s not supported", name);
500  return;
501  }
502 
503  if (optional_string("+Occurrence Chance:")) {
504  stuff_int(&occurrence_chance);
505  if ((occurrence_chance >= 0) && (occurrence_chance <= 100)) {
506  Builtin_messages[builtin_type].occurrence_chance = occurrence_chance;
507  }
508  }
509 
510  if (optional_string("+Maximum Count:")) {
511  stuff_int(&max_count);
512  if (max_count > -2) {
513  Builtin_messages[builtin_type].max_count = max_count;
514  }
515  }
516 
517  if (optional_string("+Minimum Delay:")) {
518  stuff_int(&min_delay);
519  if (min_delay > -1) {
520  Builtin_messages[builtin_type].min_delay = min_delay;
521  }
522  }
523 }
524 
526 {
527 
528  while (required_string_either("#End", "$Mood:")){
529  SCP_string buf;
530 
531  required_string("$Mood:");
532  stuff_string(buf, F_NAME);
533 
534  Builtin_moods.push_back(buf);
535  }
536 
537  required_string("#End");
538 }
539 
541 {
542  int i, j;
543 
544  //speed things up a little by setting the capacities for the message vectors to roughly the FS2 amounts
545  Messages.reserve(500);
546  Message_waves.reserve(300);
547  Message_avis.reserve(30);
548 
549  read_file_text("messages.tbl", CF_TYPE_TABLES);
550  reset_parse();
551  Num_messages = 0;
552  Num_personas = 0;
553 
554  // Goober5000 - ugh, ugly hack to fix the FS2 retail tables
555  char *pVawacs25 = strstr(Mp, "Vawacs25.wav");
556  if (pVawacs25)
557  {
558  char *pAwacs75 = strstr(pVawacs25, "Awacs75.wav");
559  if (pAwacs75)
560  {
561  // move the 'V' from the first filename to the second, and adjust the 'A' case
562  *pVawacs25 = 'A';
563  for (i = 1; i < (pAwacs75 - pVawacs25) - 1; i++)
564  pVawacs25[i] = pVawacs25[i+1];
565  pAwacs75[-1] = 'V';
566  pAwacs75[0] = 'a';
567  }
568  }
569 
570  // now we can start parsing
571  if (optional_string("#Message Frequencies")) {
572  while (!required_string_one_of(3, "$Name:", "#Personas", "#Moods" )) {
574  }
575  }
576 
577  Builtin_moods.push_back("Default");
578  if (optional_string("#Moods")) {
580  }
581 
582 
583  required_string("#Personas");
584  while ( required_string_either("#Messages", "$Persona:")){
585  persona_parse();
586  }
587 
588  required_string("#Messages");
589  while (required_string_either("#End", "$Name:")){
590  message_parse();
591  }
592 
593  required_string("#End");
594 
595  // save the number of builtin message things -- make initing between missions easier
599 
600 
602  // now cycle through the messages to determine which type of builtins we have messages for
603  for (i = 0; i < Num_builtin_messages; i++) {
604  for (j = 0; j < MAX_BUILTIN_MESSAGE_TYPES; j++) {
605  if (!(stricmp(Messages[i].name, Builtin_messages[j].name))) {
607  break;
608  }
609  }
610  }
611 
612 
613  // additional table part!
614  generic_message_filenames.clear();
615  generic_message_filenames.push_back("none");
616  generic_message_filenames.push_back("cuevoice");
617  generic_message_filenames.push_back("emptymsg");
618  generic_message_filenames.push_back("generic");
619  generic_message_filenames.push_back("msgstart");
620 
621  if (optional_string("#Simulated Speech Overrides"))
622  {
624 
625  while (required_string_either("#End", "$File Name:"))
626  {
627  required_string("$File Name:");
629 
630  // get extension
631  char *ptr = strchr(filename, '.');
632  if (ptr == NULL)
633  {
634  Warning(LOCATION, "Simulated speech override file '%s' was provided with no extension!", filename);
635  continue;
636  }
637 
638  // test extension
639  if (stricmp(ptr, ".ogg") && stricmp(ptr, ".wav"))
640  {
641  Warning(LOCATION, "Simulated speech override file '%s' was provided with an extension other than .wav or .ogg!", filename);
642  continue;
643  }
644 
645  // truncate extension
646  *ptr = '\0';
647 
648  // add truncated file name
649  generic_message_filenames.push_back(filename);
650  }
651 
652  required_string("#End");
653  }
654 }
655 
656 // this is called at the start of each level
658 {
659  int i;
660  static int table_read = 0;
661 
662  if ( !table_read ) {
664 
665  try
666  {
667  parse_msgtbl();
668  table_read = 1;
669  }
670  catch (const parse::ParseException& e)
671  {
672  mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", "messages.tbl", e.what()));
673  return;
674  }
675  }
676 
678 
679  // reset the number of messages that we have for this mission
684 
685  // initialize the stuff for the linked lists of messages
686  MessageQ_num = 0;
687  for (i = 0; i < MAX_MESSAGE_Q; i++) {
688  MessageQ[i].priority = -1;
689  MessageQ[i].time_added = -1;
690  MessageQ[i].message_num = -1;
691  MessageQ[i].builtin_type = -1;
692  MessageQ[i].min_delay_stamp = -1;
693  MessageQ[i].group = 0;
694 
695  // Goober5000
696  MessageQ[i].special_message = NULL;
697  }
698 
699  // this forces a reload of the AVI's and waves for builtin messages. Needed because the flic and
700  // sound system also get reset between missions!
701  for (i = 0; i < Num_builtin_avis; i++ ) {
702  generic_anim_unload(&Message_avis[i].anim_data);
703  }
704 
705  for (i = 0; i < Num_builtin_waves; i++ ){
706  Message_waves[i].num = -1;
707  }
708 
709  Message_shipnum = -1;
711  for ( i = 0; i < MAX_PLAYING_MESSAGES; i++ ) {
712  //Playing_messages[i].anim = NULL;
713  Playing_messages[i].anim_data = NULL;
714  Playing_messages[i].start_frame = -1;
715  Playing_messages[i].play_anim = false;
716  Playing_messages[i].wave = -1;
717  Playing_messages[i].id = -1;
718  Playing_messages[i].priority = -1;
719  Playing_messages[i].shipnum = -1;
720  Playing_messages[i].builtin_type = -1;
721  }
722 
723  // reinitialize the personas. mark them all as not used
724  for ( i = 0; i < Num_personas; i++ ){
725  Personas[i].flags &= ~PERSONA_FLAG_USED;
726  }
727 
728  Message_wave_muted = 0;
729  Next_mute_time = 1;
730 
731  //wipe all the non-builtin messages
732  Messages.erase((Messages.begin()+Num_builtin_messages), Messages.end());
733  Message_avis.erase((Message_avis.begin()+Num_builtin_avis), Message_avis.end());
734  Message_waves.erase((Message_waves.begin()+Num_builtin_waves), Message_waves.end());
735 }
736 
737 // free a loaded avi
738 void message_mission_free_avi(int m_index)
739 {
740  // check for bogus index
741  if ( (m_index < 0) || (m_index >= Num_message_avis) )
742  return;
743 
744  // Make sure this code doesn't get run if the talking head guage is off
745  // helps prevent a crash on jump out if this code doesn't work right
747  return;
748 
749  generic_anim_unload(&Message_avis[m_index].anim_data);
750 }
751 
752 // called to do cleanup when leaving a mission
754 {
755  int i;
756 
757  mprintf(("Unloading in mission messages\n"));
758 
760 
761  // kill/stop all playing messages sounds and animations if we need to
762  if (Num_messages_playing) {
763  message_kill_all(1);
764  }
765 
766  // remove the wave sounds from memory
767  for (i = 0; i < Num_message_waves; i++ ) {
768  if ( Message_waves[i].num != -1 ){
769  snd_unload( Message_waves[i].num );
770  }
771  }
772 
773  fsspeech_stop();
774 
775  // free up remaining anim data - taylor
776  for (i=0; i<Num_message_avis; i++) {
778  }
779 
780  // Goober5000 - free up special messages
781  for (i = 0; i < MAX_MESSAGE_Q; i++)
782  {
783  if (MessageQ[i].special_message != NULL)
784  {
785  vm_free(MessageQ[i].special_message);
786  MessageQ[i].special_message = NULL;
787  }
788  }
789 }
790 
791 // call from game_shutdown() ONLY!!!
793 {
794  // free the persona data
795  if (Personas != NULL) {
796  vm_free( Personas );
797  Personas = NULL;
798  }
799 }
800 
801 // functions to deal with queuing messages to the message system.
802 
803 // Compare function for sorting message queue entries based on priority.
804 // Return values set to sort array in _decreasing_ order. If priorities equal, sort based
805 // on time added into queue
806 int message_queue_priority_compare(const void *a, const void *b)
807 {
808  message_q *ma, *mb;
809 
810  ma = (message_q *) a;
811  mb = (message_q *) b;
812 
813  if (ma->priority > mb->priority) {
814  return -1;
815  } else if (ma->priority < mb->priority) {
816  return 1;
817  } else if (ma->time_added < mb->time_added) {
818  return -1;
819  } else if (ma->time_added > mb->time_added) {
820  return 1;
821  } else {
822  return 0;
823  }
824 }
825 
826 // function to kill all currently playing messages. kill_all parameter tells us to
827 // kill only the animations that are playing, or wave files too
828 void message_kill_all( int kill_all )
829 {
830  int i;
831 
833 
834  // kill sounds for all voices currently playing
835  for ( i = 0; i < Num_messages_playing; i++ ) {
836  /*if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) ) {
837  anim_stop_playing( Playing_messages[i].anim );
838  Playing_messages[i].anim=NULL;
839  }*/
840  if ( Playing_messages[i].play_anim) {
841  Playing_messages[i].play_anim = false;
842  }
843 
844  if ( kill_all ) {
845  if ( (Playing_messages[i].wave != -1 ) && snd_is_playing(Playing_messages[i].wave) ){
846  snd_stop( Playing_messages[i].wave );
847  }
848 
849  Playing_messages[i].shipnum = -1;
850  }
851  }
852 
853  if ( kill_all ) {
854  Num_messages_playing = 0;
855  }
856 
857  fsspeech_stop();
858 }
859 
860 // function to kill nth playing message
861 void message_kill_playing( int message_num )
862 {
863  Assert( message_num < Num_messages_playing );
864 
865  /*if ( (Playing_messages[message_num].anim != NULL) && anim_playing(Playing_messages[message_num].anim) ) {
866  anim_stop_playing( Playing_messages[message_num].anim );
867  Playing_messages[message_num].anim=NULL;
868  }*/
869  if ( Playing_messages[message_num].play_anim) {
870  Playing_messages[message_num].play_anim = false;
871  }
872 
873  if ( (Playing_messages[message_num].wave != -1 ) && snd_is_playing(Playing_messages[message_num].wave) )
874  snd_stop( Playing_messages[message_num].wave );
875 
876  Playing_messages[message_num].shipnum = -1;
877 
878  fsspeech_stop();
879 }
880 
881 
882 // returns true if all messages currently playing are builtin messages
884 {
885  int i;
886 
887  for ( i = 0; i < Num_messages_playing; i++ ) {
888  if ( Playing_messages[i].id >= Num_builtin_messages ){
889  break;
890  }
891  }
892 
893  // if we got through the list without breaking, all playing messages are builtin messages
894  if ( i == Num_messages_playing ){
895  return 1;
896  } else {
897  return 0;
898  }
899 }
900 
901 // returns true in any playing message is of the specific builtin type
902 int message_playing_specific_builtin( int builtin_type )
903 {
904  int i;
905 
906  for (i = 0; i < Num_messages_playing; i++ ) {
907  if ( (Playing_messages[i].id < Num_builtin_messages) && (Playing_messages[i].builtin_type == builtin_type) ){
908  return 1;
909  }
910  }
911 
912  return 0;
913 }
914 
915 // returns true if all messages current playing are unique messages
917 {
918  int i;
919 
920  for ( i = 0; i < Num_messages_playing; i++ ) {
921  if ( Playing_messages[i].id < Num_builtin_messages ){
922  break;
923  }
924  }
925 
926  // if we got through the list without breaking, all playing messages are builtin messages
927  if ( i == Num_messages_playing ){
928  return 1;
929  } else {
930  return 0;
931  }
932 }
933 
934 
935 // returns the highest priority of the currently playing messages
936 #define MESSAGE_GET_HIGHEST 1
937 #define MESSAGE_GET_LOWEST 2
938 int message_get_priority(int which)
939 {
940  int i;
941  int priority;
942 
943  if ( which == MESSAGE_GET_HIGHEST ){
944  priority = MESSAGE_PRIORITY_LOW;
945  } else {
946  priority = MESSAGE_PRIORITY_HIGH;
947  }
948 
949  for ( i = 0; i < Num_messages_playing; i++ ) {
950  if ( (which == MESSAGE_GET_HIGHEST) && (Playing_messages[i].priority > priority) ){
951  priority = Playing_messages[i].priority;
952  } else if ( (which == MESSAGE_GET_LOWEST) && (Playing_messages[i].priority < priority) ){
953  priority = Playing_messages[i].priority;
954  }
955  }
956 
957  return priority;
958 }
959 
960 
961 // removes current message from the queue
963 {
964  // quick out if nothing to do.
965  if ( MessageQ_num <= 0 ) {
966  return;
967  }
968 
969  MessageQ_num--;
970  q->priority = -1;
971  q->time_added = -1;
972  q->message_num = -1;
973  q->builtin_type = -1;
974  q->min_delay_stamp = -1;
975  q->group = 0;
976 
977  // Goober5000
978  if (q->special_message != NULL)
979  {
981  q->special_message = NULL;
982  }
983 
984  if ( MessageQ_num > 0 ) {
986  }
987 }
988 
989 // Load in the sound data for a message.
990 //
991 // index - index into the Message_waves[] array
992 //
993 void message_load_wave(int index, const char *filename)
994 {
995  Assertion(index >= 0, "Invalid index passed!");
996 
997  if ( Message_waves[index].num >= 0) {
998  return;
999  }
1000 
1001  if ( !Sound_enabled ) {
1002  Message_waves[index].num = -1;
1003  return;
1004  }
1005 
1006  game_snd tmp_gs;
1007  strcpy_s( tmp_gs.filename, filename );
1008  Message_waves[index].num = snd_load( &tmp_gs, 0 );
1009 
1010  if (Message_waves[index].num == -1)
1011  nprintf(("messaging", "Cannot load message wave: %s. Will not play\n", Message_waves[index].name));
1012 }
1013 
1014 // Goober5000
1016 {
1017  char truncated_filename[MAX_FILENAME_LEN];
1018 
1019  // truncate any file extension
1020  strcpy_s(truncated_filename, filename);
1021  char *ptr = strchr(truncated_filename, '.');
1022 
1023  // extension must be a recognized sound file
1024  if ((ptr == NULL) || (stricmp(ptr, ".ogg") && stricmp(ptr, ".wav")))
1025  return false;
1026 
1027  // truncate it
1028  *ptr = '\0';
1029 
1030  // test against the list
1031  for (unsigned int i = 0; i < generic_message_filenames.size(); i++)
1032  {
1033  if (!stricmp(generic_message_filenames[i].c_str(), truncated_filename))
1034  return true;
1035  }
1036 
1037  return false;
1038 }
1039 
1040 // Play wave file associated with message
1041 // input: m => pointer to message description
1042 //
1043 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1045 {
1046  int index;
1047  MissionMessage *m;
1048  char filename[MAX_FILENAME_LEN];
1049 
1050  m = &Messages[q->message_num];
1051 
1052  if ( m->wave_info.index >= 0 ) {
1053  index = m->wave_info.index;
1054  strcpy_s( filename, Message_waves[index].name );
1055 
1056  // Goober5000 - if we're using simulated speech, it should pre-empt the generic beeps
1058  return false;
1059 
1060  // if we need to bash the wave name because of "conversion" to terran command, do it here
1061  if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1062  char *p, new_filename[MAX_FILENAME_LEN];
1063 
1064  Message_waves[index].num = -1; // forces us to reload the message
1065 
1066  // bash the filename here. Look for "[1-6]_" at the front of the message. If found, then
1067  // convert to TC_*
1068  p = strchr(filename, '_' );
1069  if ( p == NULL ) {
1070  mprintf(("Cannot convert %s to terran command wave -- find Sandeep or Allender\n", Message_waves[index].name));
1071  return false;
1072  }
1073 
1074  // prepend the command name, and then the rest of the filename.
1075  p++;
1076  strcpy_s( new_filename, COMMAND_WAVE_PREFIX );
1077  strcat_s( new_filename, p );
1078  strcpy_s( filename, new_filename );
1079  }
1080 
1081  // load the sound file into memory
1082  message_load_wave(index, filename);
1083  if ( Message_waves[index].num == -1 ) {
1084  m->wave_info.index = -1;
1085  }
1086 
1087  if ( m->wave_info.index >= 0 ) {
1088  // this call relies on the fact that snd_play returns -1 if the sound cannot be played
1089  Message_wave_duration = snd_get_duration(Message_waves[index].num);
1090  Playing_messages[Num_messages_playing].wave = snd_play_raw( Message_waves[index].num, 0.0f );
1091 
1092  return (Playing_messages[Num_messages_playing].wave != -1);
1093  }
1094  }
1095 
1096  return false;
1097 }
1098 
1099 // Determine the starting frame for the animation
1100 // input: time => time of voice clip, in ms
1101 // ani => pointer to anim data
1102 // reverse => flag to indicate that the start should be time ms from the end (used for death screams)
1103 void message_calc_anim_start_frame(int time, generic_anim *ani, int reverse)
1104 {
1105  float wave_time, anim_time;
1106  int start_frame;
1107 
1108  start_frame=0;
1109 
1110  // If no voice clip exists, start from beginning of anim
1111  if ( time <= 0 ) {
1112  return;
1113  }
1114 
1115  // convert time to seconds
1116  wave_time = time/1000.0f;
1117  anim_time = ani->total_time;
1118 
1119  // If voice clip is longer than anim, start from beginning of anim
1120  if ( wave_time >= (anim_time) ) {
1121  return;
1122  }
1123 
1124  float fps = ani->num_frames / ani->total_time;
1125  if ( reverse ) {
1126  start_frame = (ani->num_frames-1) - fl2i(fps * wave_time + 0.5f);
1127  } else {
1128  int num_frames_extra;
1129  num_frames_extra = fl2i(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  ani->current_frame = start_frame;
1141  ani->anim_time = start_frame / fps;
1142 }
1143 
1144 // Play animation associated with message
1145 // input: m => pointer to message description
1146 // q => message queue data
1147 //
1148 // note: changes Messave_wave_duration, Playing_messages[].wave, and Message_waves[].num
1150 {
1151  message_extra *anim_info;
1152  int is_death_scream=0, persona_index=-1, rand_index=0;
1153  char ani_name[MAX_FILENAME_LEN], temp[MAX_FILENAME_LEN], *p;
1154  MissionMessage *m;
1155 
1156  // don't even bother with this stuff if the gauge is disabled - taylor
1158  return;
1159  }
1160 
1161  m = &Messages[q->message_num];
1162 
1163  // check to see if the avi_index is valid -- try and load/play the avi if so.
1164  if ( m->avi_info.index < 0 ) {
1165  return;
1166  }
1167 
1168  anim_info = &Message_avis[m->avi_info.index];
1169 
1170  // get the filename. Strip off the extension since we won't need it anyway
1171  strcpy_s(ani_name, anim_info->name);
1172  p = strchr(ani_name, '.'); // gets us to the extension
1173  if ( p ) {
1174  *p = '\0';
1175  }
1176 
1177  // builtin messages are given a base ani which we should add a suffix on before trying
1178  // to load the animation. See if this message is a builtin message which has a persona
1179  // attached to it. Deal with munging the name
1180 
1181  // support ships use a wingman head.
1182  // terran command uses its own set of heads.
1183  if ( (anim_info->anim_data.first_frame < 0) && // note, first_frame will be >= 0 when ani is an existing file, and will be < 0 when the file does not exist and needs a, b, or c appended
1184  ((q->message_num < Num_builtin_messages) || !(_strnicmp(HEAD_PREFIX_STRING, ani_name, strlen(HEAD_PREFIX_STRING)-1))) ) {
1185  int subhead_selected = FALSE;
1186  persona_index = m->persona_index;
1187 
1188  // if this ani should be converted to a terran command, set the persona to the command persona
1189  // so the correct head plays.
1190  if ( q->flags & MQF_CONVERT_TO_COMMAND ) {
1191  persona_index = The_mission.command_persona;
1192  strcpy_s( ani_name, COMMAND_HEAD_PREFIX );
1193  }
1194 
1195  // Goober5000 - guard against negative array indexing; this way, if no persona was
1196  // assigned, the logic will drop down below like it's supposed to
1197  if (persona_index >= 0)
1198  {
1199  if ( Personas[persona_index].flags & (PERSONA_FLAG_WINGMAN | PERSONA_FLAG_SUPPORT) ) {
1200  // get a random head
1201  if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1202  rand_index = MAX_WINGMAN_HEADS; // [0,MAX) are regular heads; MAX is always death head
1203  is_death_scream = 1;
1204  } else {
1205  rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS);
1206  }
1207  strcpy_s(temp, ani_name);
1208  sprintf(ani_name, "%s%c", temp, 'a'+rand_index);
1209  subhead_selected = TRUE;
1210  } else if ( Personas[persona_index].flags & (PERSONA_FLAG_COMMAND | PERSONA_FLAG_LARGE) ) {
1211  // get a random head
1212  // Goober5000 - *sigh*... if mission designers assign a command persona
1213  // to a wingman head, they risk having the death ani play
1214  if ( !strnicmp(ani_name, "Head-TP", 7) || !strnicmp(ani_name, "Head-VP", 7) ) {
1215  mprintf(("message '%s' incorrectly assigns a command/largeship persona to a wingman animation!\n", m->name));
1216  rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS);
1217  } else {
1218  rand_index = ((int) Missiontime % MAX_COMMAND_HEADS);
1219  }
1220 
1221  strcpy_s(temp, ani_name);
1222  sprintf(ani_name, "%s%c", temp, 'a'+rand_index);
1223  subhead_selected = TRUE;
1224  } else {
1225  mprintf(("message '%s' uses an unrecognized persona type\n", m->name));
1226  }
1227  }
1228 
1229  if (!subhead_selected) {
1230  // choose between a and b
1231  rand_index = ((int) Missiontime % MAX_WINGMAN_HEADS);
1232  strcpy_s(temp, ani_name);
1233  sprintf(ani_name, "%s%c", temp, 'a'+rand_index);
1234  mprintf(("message '%s' with invalid head. Fix by assigning persona to the message.\n", m->name));
1235  }
1236  nprintf(("Messaging", "playing head %s for %s\n", ani_name, q->who_from));
1237  }
1238 
1239  // check to see if the avi has been loaded. If not, then load the AVI. On an error loading
1240  // the avi, set the top level index to -1 to avoid multiple tries at loading the flick.
1241 
1242  // if there is something already here that's not this same file then go ahead a let go of it - taylor
1243  if ( !strstr(anim_info->anim_data.filename, ani_name) ) {
1244  nprintf(("Messaging", "clearing headani data due to name mismatch: (%s) (%s)\n",
1245  anim_info->anim_data.filename, ani_name));
1247  }
1248 
1249  strcpy_s( anim_info->anim_data.filename, ani_name );
1251  anim_info->anim_data.use_hud_color = true;
1252 
1253  if ( generic_anim_stream(&anim_info->anim_data) < 0 ) {
1254  nprintf (("messaging", "Cannot load message avi %s. Will not play.\n", ani_name));
1255  m->avi_info.index = -1; // if cannot load the avi -- set this index to -1 to avoid trying to load multiple times
1256  }
1257 
1258  if ( m->avi_info.index >= 0 ) {
1259  // This call relies on the fact that AVI_play will return -1 if the AVI cannot be played
1260  // if any messages are already playing, kill off any head anims that are currently playing. We will
1261  // only play a head anim of the newest messages being played
1262  if ( Num_messages_playing > 0 ) {
1263  nprintf(("messaging", "killing off any currently playing head animations\n"));
1264  message_kill_all( 0 );
1265  }
1266 
1267  if ( hud_disabled() ) {
1268  return;
1269  }
1270 
1272  Playing_messages[Num_messages_playing].anim_data = &anim_info->anim_data;
1273  message_calc_anim_start_frame(Message_wave_duration, &anim_info->anim_data, is_death_scream);
1274  Playing_messages[Num_messages_playing].play_anim = true;
1275  }
1276 }
1277 
1282 {
1283  char buf[MESSAGE_LENGTH];
1284  char who_from[NAME_LENGTH];
1285  message_q *q;
1286  int i;
1287  MissionMessage *m;
1288  bool builtinMessage = false; // gcc doesn't like var decls crossed by goto's
1289  object* sender = NULL;
1290 
1291  // Don't play messages until first frame has been rendered
1292  if ( Framecount < 2 ) {
1293  return;
1294  }
1295 
1296  // determine if all playing messages (if any) are done playing. If any are done, remove their
1297  // entries collapsing the Playing_messages array if necessary
1298  if ( Num_messages_playing > 0 ) {
1299 
1300  // for each message playing, determine if it is done.
1301  i = 0;
1302  while ( i < Num_messages_playing ) {
1303  int ani_done, wave_done, j;
1304 
1305  ani_done = 1;
1306  if ( Playing_messages[i].play_anim )
1307  ani_done = 0;
1308 
1309  wave_done = 1;
1310 
1311 // if ( (Playing_messages[i].wave != -1) && snd_is_playing(Playing_messages[i].wave) )
1312  if ( (Playing_messages[i].wave != -1) && (snd_time_remaining(Playing_messages[i].wave) > 250) )
1313  wave_done = 0;
1314 
1315  // Goober5000
1316  if (fsspeech_playing())
1317  wave_done = 0;
1318 
1319  // AL 1-20-98: If voice message is done, kill the animation early
1320  if ( (Playing_messages[i].wave != -1) && wave_done ) {
1321  /*if ( !ani_done ) {
1322  anim_stop_playing( Playing_messages[i].anim );
1323  }*/
1324  Playing_messages[i].play_anim = false;
1325  }
1326 
1327  //if player is a traitor remove all messages that aren't traitor related
1328  if ((Playing_messages[i].builtin_type != MESSAGE_OOPS) && (Playing_messages[i].builtin_type != MESSAGE_HAMMER_SWINE)) {
1331  Message_shipnum = -1;
1332  i++;
1333  continue;
1334  }
1335  }
1336 
1337  // see if the ship sending this message is dying. If do, kill wave and anim
1338  if ( Playing_messages[i].shipnum != -1 ) {
1339  if ( (Ships[Playing_messages[i].shipnum].flags & SF_DYING) && (Playing_messages[i].builtin_type != MESSAGE_WINGMAN_SCREAM) ) {
1340  int shipnum;
1341 
1342  shipnum = Playing_messages[i].shipnum;
1343  message_kill_playing( i );
1344  // force this guy to scream
1345  // AL 22-2-98: Ensure don't use -1 to index into ships array. Mark, something is incorrect
1346  // here, since message_kill_playing() seems to always set Playing_messages[i].shipnum to -1
1347  // MWA 3/24/98 -- save shipnum before killing message
1348  //
1349  Assert( shipnum >= 0 );
1350  if ( !(Ships[shipnum].flags & SF_SHIP_HAS_SCREAMED) && !(Ships[shipnum].flags2 & SF2_NO_DEATH_SCREAM) ) {
1351  ship_scream( &Ships[shipnum] );
1352  }
1353  continue; // this should keep us in the while() loop with same value of i.
1354  } // we should enter the next 'if' statement during next pass
1355  }
1356 
1357  // if both ani and wave are done, mark internal variable so we can do next message on queue, and
1358  // global variable to clear voice brackets on hud
1359  if ( wave_done && ani_done && ( timestamp_elapsed(Message_expire) || (Playing_messages[i].wave != -1) || (Playing_messages[i].shipnum == -1) ) ) {
1360  nprintf(("messaging", "Message %d is done playing\n", i));
1361  Message_shipnum = -1;
1363  if ( Num_messages_playing == 0 )
1364  break;
1365 
1366  // there is still another message playing. Collapse the playing_message array
1367  nprintf(("messaging", "Collapsing playing message stack\n"));
1368  for ( j = i+1; j < Num_messages_playing + 1; j++ ) {
1369  Playing_messages[j-1] = Playing_messages[j];
1370  }
1371  } else {
1372  // messages is not done playing -- move to next message
1373  i++;
1374  }
1375  }
1376  }
1377 
1378  // preprocess message queue and remove anything on the queue that is too old. If next message on
1379  // the queue can be played, then break out of the loop. Otherwise, loop until nothing on the queue
1380  while ( MessageQ_num > 0 ) {
1381  q = &MessageQ[0];
1383  // remove message from queue and see if more to remove
1384  nprintf(("messaging", "Message %s didn't play because it didn't fit into time window.\n", Messages[q->message_num].name));
1385  if ( q->message_num < Num_builtin_messages ){ // we should only ever remove builtin messages this way
1387  } else {
1388  break;
1389  }
1390  } else {
1391  break;
1392  }
1393  }
1394 
1395  // no need to process anything if there isn't anything on the queue
1396  if ( MessageQ_num <= 0 ){
1397  return;
1398  }
1399 
1400  // get a pointer to an item on the queue
1401  int found = -1;
1402  int idx = 0;
1403  while((found == -1) && (idx < MessageQ_num)){
1404  // if this guy has no min delay timestamp, or it has expired, select him
1405  if((MessageQ[idx].min_delay_stamp == -1) || timestamp_elapsed(MessageQ[idx].min_delay_stamp)){
1406  found = idx;
1407  break;
1408  }
1409 
1410  // next
1411  idx++;
1412  }
1413 
1414  // if we didn't find anything, bail
1415  if(found == -1){
1416  return;
1417  }
1418  // if this is not the first item on the queue, make it the first item
1419  if(found != 0){
1420  message_q temp;
1421 
1422  // store the entry
1423  memcpy(&temp, &MessageQ[found], sizeof(message_q));
1424 
1425  // move all other entries up
1426  for(idx=found; idx>0; idx--){
1427  memcpy(&MessageQ[idx], &MessageQ[idx-1], sizeof(message_q));
1428  }
1429 
1430  // plop the entry down as being first
1431  memcpy(&MessageQ[0], &temp, sizeof(message_q));
1432  }
1433 
1434  q = &MessageQ[0];
1435  Assert ( q->message_num != -1 );
1436  Assert ( q->priority != -1 );
1437  Assert ( q->time_added != -1 );
1438 
1439  if ( Num_messages_playing ) {
1440  // peek at the first message on the queue to see if it should interrupt, or overlap a currently
1441  // playing message. Mission specific messages will always interrupt builtin messages. They
1442  // will never interrupt other mission specific messages.
1443  //
1444  // Builtin message might interrupt other builtin messages, or overlap them, all depending on
1445  // message priority.
1446 
1447  if ( q->builtin_type == MESSAGE_HAMMER_SWINE ) {
1448  message_kill_all(1);
1450  MessageQ_num = 0;
1451  return;
1453  // builtin is playing and we have a unique message to play. Kill currently playing message
1454  // so unique can play uninterrupted. Only unique messages higher than low priority will interrupt
1455  // other messages.
1456  message_kill_all(1);
1457  nprintf(("messaging", "Killing all currently playing messages to play unique message\n"));
1458  } else if ( message_playing_builtin() && (q->message_num < Num_builtin_messages) ) {
1459  // when a builtin message is queued, we might either overlap or interrupt the currently
1460  // playing message.
1461  //
1462  // we have to check for num_messages_playing (again), since code for death scream might
1463  // kill all messages.
1464  if ( Num_messages_playing ) {
1466  // lower priority message playing -- kill it.
1467  message_kill_all(1);
1468  nprintf(("messaging", "Killing all currently playing messages to play high priority builtin\n"));
1469  } else if ( message_get_priority(MESSAGE_GET_LOWEST) > q->priority ) {
1470  // queued message is a lower priority, so wait it out
1471  return;
1472  } else {
1473  // if we get here, then queued messages is a builtin message with the same priority
1474  // as the currently playing messages. This state will cause messages to overlap.
1475  nprintf(("messaging", "playing builtin message (overlap) because priorities match\n"));
1476  }
1477  }
1478  } else if ( message_playing_unique() && (q->message_num < Num_builtin_messages) ) {
1479  // code messages can kill any low priority mission specific messages
1480  if ( Num_messages_playing ) {
1482  message_kill_all(1);
1483  nprintf(("messaging", "Killing low priority unique messages to play code message\n"));
1484  } else {
1485  return; // do nothing.
1486  }
1487  }
1488  } else {
1489  return;
1490  }
1491  }
1492 
1493  // if we are playing the maximum number of voices, then return. Make the check here since the above
1494  // code might kill off currently playing messages
1496  return;
1497 
1498  // Goober5000 - argh, don't conflate special sources with ships!
1499  // NOTA BENE: don't check for != MESSAGE_SOURCE_COMMAND, because with the new command persona code, Command could be a ship
1500  if ( q->source != MESSAGE_SOURCE_SPECIAL ) {
1502 
1503  // see if we need to check if sending ship is alive
1504  if ( (Message_shipnum < 0) && (q->flags & MQF_CHECK_ALIVE) ) {
1505  goto all_done;
1506  }
1507  }
1508 
1509  // if this is a ship, then don't play anything if this ship is already talking
1510  if ( Message_shipnum != -1 ) {
1511  for ( i = 0; i < Num_messages_playing; i++ ) {
1512  if ( (Playing_messages[i].shipnum != -1) && (Playing_messages[i].shipnum == Message_shipnum) ){
1513  return;
1514  }
1515  }
1516  }
1517 
1518  // set up module globals for this message
1519  m = &Messages[q->message_num];
1520  Playing_messages[Num_messages_playing].anim_data = NULL;
1521  Playing_messages[Num_messages_playing].wave = -1;
1522  Playing_messages[Num_messages_playing].id = q->message_num;
1523  Playing_messages[Num_messages_playing].priority = q->priority;
1524  Playing_messages[Num_messages_playing].shipnum = Message_shipnum;
1525  Playing_messages[Num_messages_playing].builtin_type = q->builtin_type;
1526 
1527  Message_wave_duration = 0;
1528 
1529  // translate tokens in message to the real things
1530  if (q->special_message == NULL)
1532  else
1534 
1535  Message_expire = timestamp(42 * strlen(buf));
1536  // AL: added 07/14/97.. only play avi/sound if in gameplay
1538  goto all_done;
1539 
1540  // AL 4-7-98: Can't receive messages if comm is destroyed
1542  goto all_done;
1543  }
1544 
1545  // Don't play death scream unless a small ship.
1546  if ( q->builtin_type == MESSAGE_WINGMAN_SCREAM ) {
1547  if (Message_shipnum < 0) {
1548  goto all_done;
1549  }
1550  if (!((Ship_info[Ships[Message_shipnum].ship_info_index].flags & SIF_SMALL_SHIP) || (Ships[Message_shipnum].flags2 & SF2_ALWAYS_DEATH_SCREAM)) ) {
1551  goto all_done;
1552  }
1553  }
1554 
1555  // play wave first, since need to know duration for picking anim start frame
1556  if(message_play_wave(q) == false) {
1558  }
1559 
1560  // play animation for head
1561  message_play_anim(q);
1562 
1563  // distort the message if comms system is damaged
1565 
1566 #ifndef NDEBUG
1567  // debug only -- if the message is a builtin message, put in parens whether or not the voice played
1568  if ( Sound_enabled && (Playing_messages[Num_messages_playing].wave == -1) ) {
1569  strcat_s( buf, NOX("..(no wavefile for voice)"));
1571  }
1572 #endif
1573 
1574  strcpy_s (who_from, q->who_from);
1575 
1576  // if this is a ship, do we use name or callsign or ship class?
1577  if ( Message_shipnum >= 0 ) {
1579  if ( shipp->callsign_index >= 0 ) {
1580  hud_stuff_ship_callsign( who_from, shipp );
1581  } else if ( ((Iff_info[shipp->team].flags & IFFF_WING_NAME_HIDDEN) && (shipp->wingnum != -1)) || (shipp->flags2 & SF2_HIDE_SHIP_NAME) ) {
1582  hud_stuff_ship_class( who_from, shipp );
1583  } else {
1585  }
1586  }
1587 
1588  if ( !stricmp(who_from, "<none>") ) {
1589  HUD_sourced_printf( q->source, NOX("%s"), buf );
1590  } else HUD_sourced_printf( q->source, NOX("%s: %s"), who_from, buf );
1591 
1592  if ( Message_shipnum >= 0 ) {
1594  }
1595 
1596  Script_system.SetHookVar("Name", 's', m->name);
1597  Script_system.SetHookVar("Message", 's', buf);
1598  Script_system.SetHookVar("SenderString", 's', who_from);
1599 
1600  builtinMessage = q->builtin_type != -1;
1601  Script_system.SetHookVar("Builtin", 'b', &builtinMessage);
1602 
1603  if (Message_shipnum >= 0) {
1604  sender = &Objects[Ships[Message_shipnum].objnum];
1605  }
1606  Script_system.SetHookObject("Sender", sender);
1607 
1608  Script_system.RunCondition(CHA_MSGRECEIVED, 0, NULL, sender);
1609 
1610  Script_system.RemHookVars(5, "Name", "Message", "SenderString", "Builtin", "Sender");
1611 
1612 all_done:
1615 }
1616 
1617 // queues up a message to display to the player
1618 void message_queue_message( int message_num, int priority, int timing, char *who_from, int source, int group, int delay, int builtin_type )
1619 {
1620  int i, m_persona;
1621  char temp_buf[MESSAGE_LENGTH];
1622 
1623  if ( message_num < 0 ) return;
1624 
1625  // some messages can get queued quickly. Try to filter out certain types of messages before
1626  // they get queued if there are other messages of the same type already queued
1627  if ( (builtin_type == MESSAGE_REARM_ON_WAY) || (builtin_type == MESSAGE_OOPS) ) {
1628  // if it is already playing, then don't play it
1629  if ( message_playing_specific_builtin(builtin_type) )
1630  return;
1631 
1632  for ( i = 0; i < MessageQ_num; i++ ) {
1633  // if one of these messages is already queued, then don't play
1634  if ( (MessageQ[i].message_num == message_num) && (MessageQ[i].builtin_type == builtin_type) )
1635  return;
1636 
1637  }
1638  }
1639 
1640  // check to be sure that we haven't reached our max limit on these messages yet.
1641  if ( MessageQ_num == MAX_MESSAGE_Q ) {
1642  mprintf(("Message queue already full. Message will not be added!\n"));
1643  return;
1644  }
1645 
1646  // if player is a traitor, no messages for him!!!
1647  // unless those messages are traitor related
1648  // Goober5000 - allow messages during multiplayer dogfight (Mantis #1436)
1650  if ((builtin_type != MESSAGE_OOPS) && (builtin_type != MESSAGE_HAMMER_SWINE)) {
1651  return;
1652  }
1653  }
1654 
1655  m_persona = Messages[message_num].persona_index;
1656 
1657  // put the message into a slot
1658  i = MessageQ_num;
1659  MessageQ[i].time_added = Missiontime;
1660  MessageQ[i].priority = priority;
1661  MessageQ[i].message_num = message_num;
1662  MessageQ[i].source = source;
1663  MessageQ[i].builtin_type = builtin_type;
1664  MessageQ[i].min_delay_stamp = timestamp(delay);
1665  MessageQ[i].group = group;
1666  strcpy_s(MessageQ[i].who_from, who_from);
1667 
1668  // Goober5000 - this shouldn't happen, but let's be safe
1669  if (MessageQ[i].special_message != NULL)
1670  {
1671  Int3();
1672  vm_free(MessageQ[i].special_message);
1673  MessageQ[i].special_message = NULL;
1674  }
1675 
1676  // Goober5000 - replace variables if necessary
1677  strcpy_s(temp_buf, Messages[message_num].message);
1679  MessageQ[i].special_message = vm_strdup(temp_buf);
1680 
1681  // SPECIAL HACK -- if the who_from is terran command, and there is a wingman persona attached
1682  // to this message, then set a bit to tell the wave/anim playing code to play the command version
1683  // of the wave and head
1684  MessageQ[i].flags = 0;
1685  if ( !stricmp(who_from, The_mission.command_sender) && (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1686  MessageQ[i].flags |= MQF_CONVERT_TO_COMMAND;
1687  MessageQ[i].source = HUD_SOURCE_TERRAN_CMD;
1688  }
1689 
1690  if ( (m_persona != -1) && (Personas[m_persona].flags & PERSONA_FLAG_WINGMAN) ) {
1691  if ( !strstr(who_from, ".wav") ) {
1692  MessageQ[i].flags |= MQF_CHECK_ALIVE;
1693  }
1694  }
1695 
1696  // set the timestamp of when to play this message based on the 'timing' value
1697  if ( timing == MESSAGE_TIME_IMMEDIATE )
1699  else if ( timing == MESSAGE_TIME_SOON )
1701  else
1702  MessageQ[i].window_timestamp = timestamp(MESSAGE_ANYTIME_TIMESTAMP); // make invalid
1703 
1704  MessageQ_num++;
1706 
1707  // Try to start it!
1708  // MWA -- called every frame from game loop
1709  //message_queue_process();
1710 }
1711 
1712 // function to return the persona index of the given ship. If it isn't assigned, it will be
1713 // in this function. persona_type could be a wingman, Terran Command, or other generic ship
1714 // type personas. ship is the ship we should assign a persona to
1716 {
1717  int i = 0, ship_type, count;
1718  int *slist = new int[Num_personas];
1719  memset( slist, 0, sizeof(int) * Num_personas );
1720 
1721  if ( shipp != NULL ) {
1722  // see if this ship has a persona
1723  if ( shipp->persona_index != -1 ) {
1724  // return shipp->persona_index;
1725  i = shipp->persona_index;
1726  goto I_Done;
1727  }
1728 
1729  // get the type of ship (i.e. support, fighter/bomber, etc)
1730  ship_type = Ship_info[shipp->ship_info_index].flags;
1731 
1732  int persona_needed;
1733  count = 0;
1734 
1735  if ( ship_type & (SIF_FIGHTER|SIF_BOMBER) )
1736  {
1737  persona_needed = PERSONA_FLAG_WINGMAN;
1738  } else if ( ship_type & SIF_SUPPORT )
1739  {
1740  persona_needed = PERSONA_FLAG_SUPPORT;
1741  }
1742  else
1743  {
1744  persona_needed = PERSONA_FLAG_LARGE;
1745  }
1746 
1747  // first try to go for an unused persona
1748  for (i = 0; i < Num_personas; i++)
1749  {
1750  // this Persona is not our species - skip it
1751  if (Personas[i].species != Ship_info[shipp->ship_info_index].species)
1752  continue;
1753 
1754  // check the ship types, and don't try to assign those which don't type match
1755  if ( Personas[i].flags & persona_needed)
1756  {
1757  if (!(Personas[i].flags & PERSONA_FLAG_USED))
1758  {
1759  // if it hasn't been used - USE IT!
1760  Personas[i].flags |= PERSONA_FLAG_USED;
1761  // return i;
1762  goto I_Done;
1763  }
1764  else
1765  {
1766  // otherwise add it to our list of valid options to randomly select from
1767  slist[count] = i;
1768  count++;
1769  }
1770  }
1771  }
1772 
1773  // we didn't find an unused one - so we randomly select one
1774  if(count != 0)
1775  {
1776  i = (rand() % count);
1777  i = slist[i];
1778  }
1779  // RT Protect against count being zero
1780  else
1781  i = slist[0];
1782 
1783  //return i;
1784  goto I_Done;
1785  }
1786 
1787  // for now -- we don't support other types of personas (non-wingman personas)
1788  Int3();
1789 // return 0;
1790 
1791 I_Done:
1792  delete[] slist;
1793 
1794  return i;
1795 }
1796 
1797 // given a message id#, should it be filtered for me?
1799 {
1800  // not multiplayer
1801  if(!(Game_mode & GM_MULTIPLAYER)){
1802  return 0;
1803  }
1804 
1805  // bogus
1806  if((id < 0) || (id >= Num_messages)){
1807  mprintf(("Filtering bogus mission message!\n"));
1808  return 1;
1809  }
1810 
1811  // builtin messages
1812  if(id < Num_builtin_messages){
1813  }
1814  // mission-specific messages
1815  else {
1816  // not team filtered
1817  if(Messages[id].multi_team < 0){
1818  return 0;
1819  }
1820 
1821  // not TvT
1822  if(!(Netgame.type_flags & NG_TYPE_TEAM)){
1823  return 0;
1824  }
1825 
1826  // is this for my team?
1827  if((Net_player != NULL) && (Net_player->p_info.team != Messages[id].multi_team)){
1828  mprintf(("Filtering team-based mission message!\n"));
1829  return 1;
1830  }
1831  }
1832 
1833  return 0;
1834 }
1835 
1836 // send_unique_to_player sends a mission unique (specific) message to the player (possibly a multiplayer
1837 // person). These messages are *not* the builtin messages
1838 void message_send_unique_to_player( char *id, void *data, int m_source, int priority, int group, int delay )
1839 {
1840  int i, source;
1841  char *who_from;
1842 
1843  source = 0;
1844  who_from = NULL;
1845  for (i=0; i<Num_messages; i++) {
1846  // find the message
1847  if ( !stricmp(id, Messages[i].name) ) {
1848 
1849  // if the ship pointer and special_who are both NULL then this is from generic "Terran Command"
1850  // if the ship is NULL and special_who is not NULL, then this is from special_who
1851  // otherwise, message is from ship.
1852  if ( m_source == MESSAGE_SOURCE_COMMAND ) {
1853  who_from = The_mission.command_sender;
1854  source = HUD_SOURCE_TERRAN_CMD;
1855  } else if ( m_source == MESSAGE_SOURCE_SPECIAL ) {
1856  who_from = (char *)data;
1857  source = HUD_SOURCE_TERRAN_CMD;
1858  } else if ( m_source == MESSAGE_SOURCE_WINGMAN ) {
1859  int m_persona, ship_index;
1860 
1861  // find a wingman with the same persona as this message. If the message's persona doesn't
1862  // exist, we will use Terran command
1863  m_persona = Messages[i].persona_index;
1864  if ( m_persona == -1 ) {
1865  mprintf(("Warning: Message %s has no persona assigned.\n", Messages[i].name));
1866  }
1867 
1868  // get a ship. we allow silenced ships since this is a unique messange and therefore the mission designer
1869  // should have taken into account that the ship may have been silenced.
1870  ship_index = ship_get_random_player_wing_ship( SHIP_GET_NO_PLAYERS, 0.0f, m_persona, 1, Messages[i].multi_team);
1871 
1872  // if the ship_index is -1, then make the message come from Terran command
1873  if ( ship_index == -1 ) {
1874  who_from = The_mission.command_sender;
1875  source = HUD_SOURCE_TERRAN_CMD;
1876  } else {
1877  who_from = Ships[ship_index].ship_name;
1878  source = HUD_team_get_source(Ships[ship_index].team);
1879  }
1880 
1881  } else if ( m_source == MESSAGE_SOURCE_SHIP ) {
1882  ship *shipp;
1883 
1884  shipp = (ship *)data;
1885  who_from = shipp->ship_name;
1886  source = HUD_team_get_source(shipp->team);
1887  } else if ( m_source == MESSAGE_SOURCE_NONE ) {
1888  who_from = "<none>";
1889  }
1890 
1891  // not multiplayer or this message is for me, then queue it
1892  // if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
1893 
1894  // maybe filter it out altogether
1895  if (!message_filter_multi(i)) {
1896  message_queue_message( i, priority, MESSAGE_TIME_ANYTIME, who_from, source, group, delay );
1897  }
1898 
1899  // send a message packet to a player if destined for everyone or only a specific person
1900  if ( MULTIPLAYER_MASTER ){
1901  send_mission_message_packet( i, who_from, priority, MESSAGE_TIME_SOON, source, -1, -1, -1, delay);
1902  }
1903 
1904  return; // all done with displaying
1905  }
1906  }
1907  nprintf (("messaging", "Couldn't find message id %s to send to player!\n", id ));
1908 }
1909 
1910 #define BUILTIN_MATCHES_TYPE 0
1911 #define BUILTIN_MATCHES_SPECIES 1
1912 #define BUILTIN_MATCHES_PERSONA_CHECK_MOOD 2
1913 #define BUILTIN_MATCHES_PERSONA_EXCLUDED 3
1914 #define BUILTIN_MATCHES_PERSONA 4
1915 #define BUILTIN_MATCHES_PERSONA_MOOD 5
1916 
1917 typedef struct matching_builtin {
1921 
1922 // send builtin_to_player sends a message (from messages.tbl) to the player. These messages are
1923 // the generic informational type messages. The have priorities like misison specific messages,
1924 // and use a timing to tell how long we should wait before playing this message
1925 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 )
1926 {
1927  int i, persona_index = -1, persona_species = -1, message_index = -1, random_selection = -1;
1928  int source;
1929  int num_matching_builtins = 0;
1930  char *who_from;
1931  int best_match = -1;
1932 
1933  matching_builtin current_builtin;
1934  SCP_vector <matching_builtin> matching_builtins;
1935 
1936 
1937  // if we aren't showing builtin msgs, bail
1939  return;
1940 
1941  // Karajorma - If we aren't showing builtin msgs from command and this is not a ship, bail
1942  if ( (shipp == NULL) && (The_mission.flags & MISSION_FLAG_NO_BUILTIN_COMMAND) )
1943  return;
1944 
1945  // builtin type isn't supported by this version of the table
1946  if (!Valid_builtin_message_types[type]) {
1947  // downgrade certain message types to more generic ones more likely to be supported
1948  if (type == MESSAGE_HIGH_PRAISE ) {
1949  type = MESSAGE_PRAISE;
1950  }
1951  else if ( type == MESSAGE_REARM_PRIMARIES ) {
1952  type = MESSAGE_REARM_REQUEST;
1953  }
1954  else {
1955  return;
1956  }
1957 
1958  // check if the downgraded type is also invalid
1959  if (!Valid_builtin_message_types[type]) {
1960  return;
1961  }
1962  }
1963 
1964  // see if there is a persona assigned to this ship. If not, then try to assign one!!!
1965  if ( shipp ) {
1966  // Karajorma - the game should assert if a silenced ship gets this far
1967  Assert( !(shipp->flags2 & SF2_NO_BUILTIN_MESSAGES) );
1968 
1969  if ( shipp->persona_index == -1 )
1970  shipp->persona_index = message_get_persona( shipp );
1971 
1972  persona_index = shipp->persona_index;
1973 
1974  if ( persona_index == -1 )
1975  nprintf(("messaging", "Couldn't find persona for %s\n", shipp->ship_name ));
1976 
1977  // be sure that this ship can actually send a message!!! (i.e. not-not-flyable -- get it!)
1978  Assert( !(Ship_info[shipp->ship_info_index].flags & SIF_NOT_FLYABLE) ); // get allender or alan
1979  } else {
1980  persona_index = The_mission.command_persona; // use the terran command persona
1981  }
1982 
1983  char *name = Builtin_messages[type].name;
1984 
1985  if (persona_index >= 0) {
1986  persona_species = Personas[persona_index].species;
1987  }
1988 
1989  // try to find a builtin message with the given type for the given persona
1990  // we may try to play a message in the wrong persona if we can't find the right message for the given persona
1991  for ( i = 0; i < Num_builtin_messages; i++ ) {
1992  // check the type of message
1993  if ( !stricmp(Messages[i].name, name) ) {
1994  // condition 1: we have a type match
1995  current_builtin.message_index = i;
1996  current_builtin.type_of_match = BUILTIN_MATCHES_TYPE;
1997 
1998  // check the species of this persona (if required)
1999  if ( (persona_species >= 0) && (Personas[Messages[i].persona_index].species == persona_species) ) {
2000  // condition 2: we have a type + species match
2001  current_builtin.type_of_match = BUILTIN_MATCHES_SPECIES;
2002  }
2003 
2004  // check the exact persona (if required)
2005  // NOTE: doesn't need to be nested under the species condition above
2006  if ( (persona_index >= 0) && (Messages[i].persona_index == persona_index) ) {
2007  // condition 3: type + species + persona index match
2009  }
2010 
2011  // check if the personas mood suits this particular message, first check if it is excluded
2012  if (!Messages[i].excluded_moods.empty() && (current_builtin.type_of_match == BUILTIN_MATCHES_PERSONA_CHECK_MOOD)) {
2013  for (SCP_vector<int>::iterator iter = Messages[i].excluded_moods.begin(); iter != Messages[i].excluded_moods.end(); ++iter) {
2014  if (*iter == Current_mission_mood) {
2016  break;
2017  }
2018  }
2019  }
2020 
2021  if (current_builtin.type_of_match == BUILTIN_MATCHES_PERSONA_CHECK_MOOD) {
2022  if (Current_mission_mood == Messages[i].mood) {
2023  current_builtin.type_of_match = BUILTIN_MATCHES_PERSONA_MOOD;
2024  }
2025  else {
2026  current_builtin.type_of_match = BUILTIN_MATCHES_PERSONA;
2027  }
2028  }
2029 
2030  if (current_builtin.type_of_match == best_match) {
2031  num_matching_builtins++;
2032  }
2033  // otherwise check to see if the this is the best kind of match we've found so far
2034  else if (current_builtin.type_of_match > best_match) {
2035  best_match = current_builtin.type_of_match;
2036  num_matching_builtins = 1;
2037  }
2038 
2039  // add the match to our list
2040  matching_builtins.push_back(current_builtin);
2041  }
2042  }
2043 
2044  switch (best_match) {
2046  nprintf(("MESSAGING", "Couldn't find builtin message %s for persona %d with a none excluded mood\n", Builtin_messages[type].name, persona_index));
2047  if (!Personas[persona_index].substitute_missing_messages) {
2048  nprintf(("MESSAGING", "Persona does not allow substitution, skipping message."));
2049  return;
2050  }
2051  else
2052  nprintf(("MESSAGING", "using an excluded message for this persona\n"));
2053  break;
2055  nprintf(("MESSAGING", "Couldn't find builtin message %s for persona %d\n", Builtin_messages[type].name, persona_index));
2056  if (!Personas[persona_index].substitute_missing_messages) {
2057  nprintf(("MESSAGING", "Persona does not allow substitution, skipping message."));
2058  return;
2059  }
2060  else
2061  nprintf(("MESSAGING", "using a message for any persona of that species\n"));
2062  break;
2063  case BUILTIN_MATCHES_TYPE:
2064  nprintf(("MESSAGING", "Couldn't find builtin message %s for persona %d\n", Builtin_messages[type].name, persona_index));
2065  if (!Personas[persona_index].substitute_missing_messages) {
2066  nprintf(("MESSAGING", "Persona does not allow substitution, skipping message."));
2067  return;
2068  }
2069  else
2070  nprintf(("MESSAGING", "looking for message for any persona of any species\n"));
2071  break;
2072  case -1:
2073  Error(LOCATION, "Couldn't find any builtin message of type %d\n", type);
2074  return;
2075  }
2076 
2077 
2078  // since we may have multiple builtins we need to pick one at random
2079  random_selection = (int)(rand32() % num_matching_builtins) + 1;
2080 
2081  // loop through the vector until we have found enough elements of the correct matching type
2082  for (i = 0; i < (int)matching_builtins.size(); i++) {
2083  if (matching_builtins[i].type_of_match == best_match) {
2084  random_selection--;
2085  if (random_selection == 0) {
2086  message_index = matching_builtins[i].message_index;
2087  break;
2088  }
2089  }
2090  }
2091 
2092  Assertion (random_selection == 0, "unable to randomly select built in message correctly, still have %d selections left", random_selection);
2093 
2094  // get who this message is from -- kind of a hack since we assume Terran Command in the
2095  // absence of a ship. This will be fixed later
2096  if ( shipp ) {
2097  who_from = shipp->ship_name;
2098  source = HUD_team_get_source( shipp->team );
2099  } else {
2100  who_from = The_mission.command_sender;
2101 
2102  // Goober5000 - if Command is a ship that is present, change the source accordingly
2103  int shipnum = ship_name_lookup(who_from);
2104  if (shipnum >= 0)
2105  source = HUD_team_get_source( Ships[shipnum].team );
2106  else
2107  source = HUD_SOURCE_TERRAN_CMD;
2108  }
2109 
2110  // maybe change the who from here for special rearm cases (always seems like that is the case :-) )
2111  if ( !stricmp(who_from, The_mission.command_sender) && (type == MESSAGE_REARM_ON_WAY) ){
2112  who_from = SUPPORT_NAME;
2113  }
2114 
2115  // determine what we should actually do with this dang message. In multiplayer, we must
2116  // deal with the fact that this message might not get played on my machine if I am a server
2117 
2118  // not multiplayer or this message is for me, then queue it
2119  if ( !(Game_mode & GM_MULTIPLAYER) || ((multi_target == -1) || (multi_target == MY_NET_PLAYER_NUM)) ){
2120 
2121  // if this filter matches mine
2122  if( (multi_team_filter < 0) || !(Netgame.type_flags & NG_TYPE_TEAM) || ((Net_player != NULL) && (Net_player->p_info.team == multi_team_filter)) ){
2123  message_queue_message( message_index, priority, timing, who_from, source, group, delay, type );
2124  }
2125  }
2126 
2127  // send a message packet to a player if destined for everyone or only a specific person
2128  if ( MULTIPLAYER_MASTER ) {
2129  // only send a message if it is of a particular type
2130  if(multi_target == -1){
2131  if(multi_message_should_broadcast(type)){
2132  send_mission_message_packet( message_index, who_from, priority, timing, source, type, -1, multi_team_filter );
2133  }
2134  } else {
2135  send_mission_message_packet( message_index, who_from, priority, timing, source, type, multi_target, multi_team_filter );
2136  }
2137  }
2138 }
2139 
2140 // message_is_playing()
2141 //
2142 // Return the Message_playing flag. Message_playing is local to MissionMessage.cpp, but
2143 // this info is needed by code in HUDsquadmsg.cpp
2144 //
2146 {
2147  return Num_messages_playing?1:0;
2148 }
2149 
2150 // Functions below pertain only to personas!!!!
2151 
2152 // given a character string, try to find the persona index
2154 {
2155  int i;
2156 
2157  for (i = 0; i < Num_personas; i++ ) {
2158  if ( !stricmp(Personas[i].name, name) )
2159  return i;
2160  }
2161 
2162  return -1;
2163 }
2164 
2165 // Blank out portions of the audio playback for the sound identified by Message_wave
2166 // This works by using the same Distort_pattern[][] that was used to distort the associated text
2168 {
2169  int i;
2170  int was_muted;
2171 
2172  if ( Num_messages_playing == 0 )
2173  return;
2174 
2175  for ( i = 0; i < Num_messages_playing; i++ ) {
2176  if ( !snd_is_playing(Playing_messages[i].wave) )
2177  return;
2178  }
2179 
2180  // distort the number of voices currently playing
2181  for ( i = 0; i < Num_messages_playing; i++ ) {
2182  Assert(Playing_messages[i].wave >= 0 );
2183 
2184  was_muted = 0;
2185 
2186  if ( comm_between_player_and_ship(Playing_messages[i].shipnum) != COMM_OK) {
2187  was_muted = Message_wave_muted;
2188  if ( timestamp_elapsed(Next_mute_time) ) {
2189  Next_mute_time = fl2i(Distort_patterns[Distort_num][Distort_next++] * Message_wave_duration);
2190  if ( Distort_next >= MAX_DISTORT_LEVELS )
2191  Distort_next = 0;
2192 
2193  Message_wave_muted ^= 1;
2194  }
2195 
2196  if ( Message_wave_muted ) {
2197  if ( !was_muted )
2198  snd_set_volume(Playing_messages[i].wave, 0.0f);
2199  } else {
2200  if ( was_muted )
2201  snd_set_volume(Playing_messages[i].wave, (Master_sound_volume * aav_voice_volume));
2202  }
2203  }
2204  }
2205 }
2206 
2207 
2208 // if the player communications systems are heavily damaged, distort incoming messages.
2209 //
2210 // first case: Message_wave_duration == 0 (this occurs when there is no associated voice playback)
2211 // Blank out random runs of characters in the message
2212 //
2213 // second case: Message_wave_duration > 0 (occurs when voice playback accompainies message)
2214 // Blank out portions of the sound based on Distort_num, this this is that same
2215 // data that will be used to blank out portions of the audio playback
2216 //
2217 void message_maybe_distort_text(char *text, int shipnum)
2218 {
2219  int i, j, len, run, curr_offset, voice_duration, next_distort;
2220 
2221  if ( comm_between_player_and_ship(shipnum) == COMM_OK ) {
2222  return;
2223  }
2224 
2225  len = strlen(text);
2226  if ( Message_wave_duration == 0 ) {
2227  next_distort = 5+myrand()%5;
2228  for ( i = 0; i < len; i++ ) {
2229  if ( i == next_distort ) {
2230  run = 3+myrand()%5;
2231  if ( i+run > len )
2232  run = len-i;
2233  for ( j = 0; j < run; j++) {
2234  text[i++] = '-';
2235  if ( i >= len )
2236  break;
2237  }
2238  next_distort = i + (5+myrand()%5);
2239  }
2240  }
2241  return;
2242  }
2243 
2244  voice_duration = Message_wave_duration;
2245 
2246  // distort text
2247  Distort_num = myrand()%MAX_DISTORT_PATTERNS;
2248  Distort_next = 0;
2249  curr_offset = 0;
2250  while (voice_duration > 0) {
2251  run = fl2i(Distort_patterns[Distort_num][Distort_next] * len);
2252  if (Distort_next & 1) {
2253  for ( i = curr_offset; i < MIN(len, curr_offset+run); i++ ) {
2254  if ( text[i] != ' ' )
2255  text[i] = '-';
2256  }
2257  curr_offset = i;
2258  if ( i >= len )
2259  break;
2260  } else {
2261  curr_offset += run;
2262  }
2263 
2264  voice_duration -= fl2i(Distort_patterns[Distort_num][Distort_next]*Message_wave_duration);
2265  Distort_next++;
2266  if ( Distort_next >= MAX_DISTORT_LEVELS )
2267  Distort_next = 0;
2268  };
2269 
2270  Distort_next = 0;
2271 }
2272 
2273 // return 1 if a talking head animation is playing, otherwise return 0
2275 {
2276  int i;
2277 
2278  for (i = 0; i < Num_messages_playing; i++ ) {
2279  //if ( (Playing_messages[i].anim != NULL) && anim_playing(Playing_messages[i].anim) )
2280  if(Playing_messages[i].play_anim)
2281  return 1;
2282  }
2283 
2284  return 0;
2285 }
2286 
2287 // Load mission messages (this is called by the level paging code when running with low memory)
2289 {
2290  int i;
2291 
2292  mprintf(("Paging in mission messages\n"));
2293 
2295  return;
2296  }
2297 
2298  char *sound_filename;
2299 
2300  for (i=Num_builtin_messages; i<Num_messages; i++) {
2301  if (Messages[i].wave_info.index != -1) {
2302  sound_filename = Message_waves[Messages[i].wave_info.index].name;
2303  message_load_wave(Messages[i].wave_info.index, sound_filename);
2304  }
2305  }
2306 }
2307 
2308 // ---------------------------------------------------
2309 // Add and remove messages - used by autopilot code now, but useful elswhere
2310 
2311 bool add_message(char *name, char *message, int persona_index, int multi_team)
2312 {
2313  MissionMessage msg;
2314  strcpy_s(msg.name, name);
2315  strcpy_s(msg.message, message);
2316  msg.persona_index = persona_index;
2317  msg.multi_team = multi_team;
2318  msg.avi_info.index = -1;
2319  msg.wave_info.index = -1;
2320  Messages.push_back(msg);
2321  Num_messages++;
2322 
2323  return true;
2324 }
2325 
2326 bool change_message(char *name, char *message, int persona_index, int multi_team)
2327 {
2328  for (int i = Num_builtin_messages; i < Num_messages; i++)
2329  {
2330  if (!strcmp(Messages[i].name, name))
2331  {
2332  strcpy_s(Messages[i].message, message);
2333  Messages[i].persona_index = persona_index;
2334  Messages[i].multi_team = multi_team;
2335 
2336  Messages[i].avi_info.index = -1;
2337  Messages[i].wave_info.index = -1;
2338  return true;
2339  }
2340  }
2341 
2342  // not found.. fall through
2343  return add_message(name, message, persona_index, multi_team);
2344 }
2345 
2352 int comm_between_player_and_ship(int other_shipnum)
2353 {
2354  int player_comm_state = hud_communications_state(Player_ship);
2355 
2356  if (other_shipnum < 0)
2357  return player_comm_state;
2358 
2359  int other_comm_state = hud_communications_state(&Ships[other_shipnum]);
2360 
2361  /* here is where you would check the flag
2362  if (hypothetical_ai_profiles_flag)
2363  {
2364  return MIN(player_comm_state, other_comm_state);
2365  }
2366  else
2367  */
2368  {
2369  if (player_comm_state == COMM_OK && other_comm_state == COMM_OK)
2370  return COMM_OK;
2371  else if (player_comm_state == COMM_SCRAMBLED || other_comm_state == COMM_SCRAMBLED)
2372  return COMM_SCRAMBLED;
2373  else
2374  return player_comm_state;
2375  }
2376 }
#define SUPPORT_NAME
void message_queue_message(int message_num, int priority, int timing, char *who_from, int source, int group, int delay, int builtin_type)
#define MAX_MESSAGE_Q
#define MAX_FILENAME_LEN
Definition: pstypes.h:324
#define MESSAGE_TIME_IMMEDIATE
int timestamp(int delta_ms)
Definition: timer.cpp:226
int flags
Definition: iff_defs.h:45
#define MY_NET_PLAYER_NUM
Definition: multi.h:127
int Framecount
Definition: systemvars.cpp:22
int i
Definition: multi_pxo.cpp:466
fix Missiontime
Definition: systemvars.cpp:19
#define MESSAGE_REARM_ON_WAY
#define vm_free(ptr)
Definition: pstypes.h:548
SCP_vector< message_extra > Message_avis
void message_mission_close()
#define MISSION_FLAG_NO_BUILTIN_MSGS
Definition: missionparse.h:71
int stuff_string_list(SCP_vector< SCP_string > &slp)
Definition: parselo.cpp:2689
#define MIN(a, b)
Definition: pstypes.h:296
void fsspeech_stop()
Definition: fsspeech.h:45
#define MESSAGE_SOURCE_NONE
int team
Definition: ship.h:606
#define HUD_TALKING_HEAD
Definition: hudgauges.h:43
#define BUILTIN_MATCHES_PERSONA_MOOD
int objnum
Definition: ship.h:537
int Game_mode
Definition: systemvars.cpp:24
int message_anim_is_playing()
message_q MessageQ[MAX_MESSAGE_Q]
#define COMM_SCRAMBLED
Definition: subsysdamage.h:51
int message_playing_builtin()
int current_frame
Definition: generic.h:23
net_player * Net_player
Definition: multi.cpp:94
SCP_vector< game_snd > Snds
Definition: gamesnd.cpp:19
void message_maybe_distort()
int snd_load(game_snd *gs, int allow_hardware_load)
Definition: sound.cpp:281
#define SHIP_GET_NO_PLAYERS
Definition: ship.h:1787
bool Full_color_head_anis
Definition: mod_table.cpp:29
int required_string_one_of(int arg_count,...)
Checks for one of any of the given required strings.
Definition: parselo.cpp:708
GLuint index
Definition: Glext.h:5608
void training_mission_shutdown()
int Fred_running
Definition: fred.cpp:44
void message_parse(bool importing_from_fsm)
#define MESSAGE_SOURCE_WINGMAN
int gameseq_get_state(void)
Definition: fredstubs.cpp:60
void _cdecl void void _cdecl void _cdecl Warning(char *filename, int line, SCP_FORMAT_STRING const char *format,...) SCP_FORMAT_STRING_ARGS(3
int generic_anim_load(generic_anim *ga)
Definition: generic.cpp:138
Assert(pm!=NULL)
char message[MESSAGE_LENGTH]
void _cdecl void void _cdecl void _cdecl void _cdecl WarningEx(char *filename, int line, SCP_FORMAT_STRING const char *format,...) SCP_FORMAT_STRING_ARGS(3
#define GR_NUM_RESOLUTIONS
Definition: 2d.h:651
#define mprintf(args)
Definition: pstypes.h:238
#define PERSONA_FLAG_LARGE
#define MESSAGE_ANYTIME_TIMESTAMP
float total_time
Definition: generic.h:27
int hud_gauge_active(int gauge_index)
Determine if the specified HUD gauge should be displayed.
Definition: hud.cpp:3006
CButton * team
int message_filter_multi(int id)
#define PERSONA_FLAG_COMMAND
GLboolean GLuint group
Definition: Glext.h:10591
#define BUILTIN_MATCHES_PERSONA_EXCLUDED
GLclampf f
Definition: Glext.h:7097
#define TRUE
Definition: pstypes.h:399
int snd_is_playing(int sig)
Definition: sound.cpp:1047
#define Assertion(expr, msg,...)
Definition: clang.h:41
int message_persona_name_lookup(char *name)
char who_from[NAME_LENGTH]
#define PERSONA_FLAG_WINGMAN
int comm_between_player_and_ship(int other_shipnum)
#define MESSAGE_HIGH_PRAISE
void message_calc_anim_start_frame(int time, generic_anim *ani, int reverse)
int Message_shipnum
char name[NAME_LENGTH]
struct message_q message_q
GLenum GLuint GLenum GLsizei const GLchar * message
Definition: Glext.h:5156
std::basic_string< char, std::char_traits< char >, std::allocator< char > > SCP_string
Definition: vmallocator.h:21
#define MAX_COMMAND_HEADS
int Num_message_avis
#define Int3()
Definition: pstypes.h:292
#define MESSAGE_HAMMER_SWINE
void message_frequency_parse()
void HUD_sourced_printf(int source, const char *format,...)
Definition: hudmessage.cpp:571
int rand32()
Definition: systemvars.cpp:112
int required_string_either(char *str1, char *str2)
Checks for one of two required strings.
Definition: parselo.cpp:673
ship * shipp
Definition: lua.cpp:9162
void send_mission_message_packet(int id, char *who_from, int priority, int timing, int source, int builtin_type, int multi_target, int multi_team_filter, int delay)
Definition: multimsgs.cpp:3400
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition: Glext.h:7308
SCP_vector< message_extra > Message_waves
script_state Script_system("FS2_Open Scripting")
GLenum type
Definition: Gl.h:1492
void generic_anim_unload(generic_anim *ga)
Definition: generic.cpp:291
char filename[MAX_FILENAME_LEN]
Definition: sound.h:76
int Num_builtin_waves
iff_info Iff_info[MAX_IFFS]
Definition: iff_defs.cpp:20
int MessageQ_num
#define COMMAND_WAVE_PREFIX
int Num_messages_playing
#define MAX_DISTORT_LEVELS
SCP_vector< MMessage > Messages
#define SIF_BOMBER
Definition: ship.h:886
int num_frames
Definition: generic.h:20
typedef int(SCP_EXT_CALLCONV *SCPDLL_PFVERSION)(SCPDLL_Version *)
SCP_vector< SCP_string > generic_message_filenames
net_player_info p_info
Definition: multi.h:473
#define MAX_BUILTIN_MESSAGE_TYPES
int type_flags
Definition: multi.h:493
#define MESSAGE_GET_LOWEST
#define MAX_PERSONA_TYPES
#define nprintf(args)
Definition: pstypes.h:239
#define GM_MULTIPLAYER
Definition: systemvars.h:18
float Master_sound_volume
Definition: sound.cpp:53
int first_frame
Definition: generic.h:19
void messages_init()
GLboolean GLboolean GLboolean GLboolean a
Definition: Glext.h:5781
#define SF2_NO_DEATH_SCREAM
Definition: ship.h:495
void message_kill_all(int kill_all)
#define BUILTIN_MATCHES_PERSONA_CHECK_MOOD
#define SIF_SMALL_SHIP
Definition: ship.h:943
#define PERSONA_FLAG_SUPPORT
char * filename
#define MAX_WINGMAN_HEADS
int wingnum
Definition: ship.h:623
#define strnicmp(s1, s2, n)
Definition: config.h:272
netgame_info Netgame
Definition: multi.cpp:97
bool use_hud_color
Definition: generic.h:47
Persona * Personas
#define SF2_HIDE_SHIP_NAME
Definition: ship.h:498
int message_get_priority(int which)
void stuff_string(char *outstr, int type, int len, char *terminators)
Definition: parselo.cpp:1189
#define MESSAGE_REARM_REQUEST
#define MESSAGE_PRAISE
int persona_index
Definition: ship.h:696
int ship_get_random_player_wing_ship(int flags, float max_dist, int persona_index, int get_first, int multi_team)
Definition: ship.cpp:14508
void message_translate_tokens(char *buf, char *text)
#define MESSAGE_SOURCE_SHIP
#define CF_TYPE_TABLES
Definition: cfile.h:50
sprintf(buf,"(%f,%f,%f)", v3->xyz.x, v3->xyz.y, v3->xyz.z)
#define NG_TYPE_TEAM
Definition: multi.h:650
int Current_mission_mood
int Message_debug_index
#define HEAD_PREFIX_STRING
#define vm_strdup(ptr)
Definition: pstypes.h:549
union MissionMessage::@254 avi_info
int required_string(const char *pstr)
Definition: parselo.cpp:468
int snd_play(game_snd *gs, float pan, float vol_scale, int priority, bool is_voice_msg)
Definition: sound.cpp:517
void message_mission_shutdown()
int optional_string(const char *pstr)
Definition: parselo.cpp:539
#define MESSAGE_GET_HIGHEST
bool message_play_wave(message_q *q)
#define PERSONA_FLAG_USED
int callsign_index
Definition: ship.h:557
void insertion_sort(void *array_base, size_t array_size, size_t element_size, int(*fncompare)(const void *, const void *))
Definition: systemvars.cpp:599
Definition: ship.h:534
#define COMM_DESTROYED
Definition: subsysdamage.h:49
void read_file_text(const char *filename, int mode, char *processed_text, char *raw_text)
Definition: parselo.cpp:1995
void SetHookObject(char *name, object *objp)
Definition: scripting.cpp:551
void fsspeech_play(int type, const char *text)
Definition: fsspeech.h:44
#define MQF_CHECK_ALIVE
int Valid_builtin_message_types[MAX_BUILTIN_MESSAGE_TYPES]
int idx
Definition: multiui.cpp:761
#define MESSAGE_PRIORITY_LOW
builtin_message Builtin_messages[]
int Sound_enabled
Definition: sound.cpp:51
void parse_msgtbl()
void message_maybe_distort_text(char *text, int shipnum)
#define COMMAND_HEAD_PREFIX
char name[MAX_FILENAME_LEN]
int snd_get_duration(int snd_id)
Definition: sound.cpp:1082
object Objects[MAX_OBJECTS]
Definition: object.cpp:62
#define MESSAGE_LENGTH
Definition: globals.h:23
long fix
Definition: pstypes.h:54
int message_playing_unique()
int hud_disabled()
Checks if HUD disabled.
Definition: hud.cpp:1306
int message_is_playing()
#define MULTI_TEAM
Definition: multi.h:655
ship * Player_ship
Definition: ship.cpp:124
generic_anim anim_data
bool change_message(char *name, char *message, int persona_index, int multi_team)
void stuff_boolean(int *i, bool a_to_eol)
Definition: parselo.cpp:2519
#define F_MESSAGE
Definition: parselo.h:42
#define MAX_DISTORT_PATTERNS
void message_remove_from_queue(message_q *q)
#define NOX(s)
Definition: pstypes.h:473
bool end_string_at_first_hash_symbol(char *src)
Definition: parselo.cpp:3833
#define MAX_PLAYING_MESSAGES
#define BUILTIN_MATCHES_SPECIES
int hud_communications_state(ship *sp)
Definition: hudtarget.cpp:4607
void _cdecl void void _cdecl Error(const char *filename, int line, SCP_FORMAT_STRING const char *format,...) SCP_FORMAT_STRING_ARGS(3
int Num_builtin_avis
GLbitfield flags
Definition: Glext.h:6722
generic_anim * anim_data
#define SF2_NO_BUILTIN_MESSAGES
Definition: ship.h:491
void reset_parse(char *text)
Definition: parselo.cpp:3305
GLuint const GLchar * name
Definition: Glext.h:5608
void hud_target_last_transmit_add(int ship_num)
Definition: hudtarget.cpp:5197
union MissionMessage::@255 wave_info
int RunCondition(int condition, char format='\0', void *data=NULL, class object *objp=NULL, int more_data=0)
Definition: scripting.cpp:924
int Num_builtin_messages
#define _strnicmp(s1, s2, n)
Definition: config.h:273
pmessage Playing_messages[MAX_PLAYING_MESSAGES]
int Num_personas
#define vm_realloc(ptr, size)
Definition: pstypes.h:551
char command_sender[NAME_LENGTH]
Definition: missionparse.h:159
int multi_message_should_broadcast(int type)
Definition: multiutil.cpp:1605
#define SIF_SUPPORT
Definition: ship.h:878
bool sexp_replace_variable_names_with_values(char *text, int max_len)
Definition: sexp.cpp:29395
GLboolean GLboolean GLboolean b
Definition: Glext.h:5781
void stuff_int(int *i)
Definition: parselo.cpp:2372
ship Ships[MAX_SHIPS]
Definition: ship.cpp:122
void message_kill_playing(int message_num)
#define MESSAGE_PRIORITY_HIGH
#define GENERIC_ANIM_DIRECTION_NOLOOP
Definition: generic.h:13
bool add_message(char *name, char *message, int persona_index, int multi_team)
bool fsspeech_play_from(int type)
Definition: fsspeech.h:52
int HUD_team_get_source(int team)
Definition: hudmessage.cpp:515
GLdouble GLdouble GLdouble GLdouble q
Definition: Glext.h:5345
GLuint GLuint num
Definition: Glext.h:9089
int add_avi(char *avi_name)
#define MESSAGE_SOON_TIMESTAMP
#define MQF_CONVERT_TO_COMMAND
int message_queue_priority_compare(const void *a, const void *b)
#define strcat_s(...)
Definition: safe_strings.h:68
#define NAME_LENGTH
Definition: globals.h:15
struct matching_builtin matching_builtin
int Message_expire
int snd_time_remaining(int handle)
Definition: sound.cpp:1291
#define fl2i(fl)
Definition: floating.h:33
void ship_scream(ship *sp)
Definition: ship.cpp:15757
#define HUD_SOURCE_TERRAN_CMD
Definition: hudmessage.h:27
#define MESSAGE_REARM_PRIMARIES
#define MESSAGE_TIME_ANYTIME
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)
bool play_anim
bool message_filename_is_generic(char *filename)
float anim_time
Definition: generic.h:28
void generic_anim_init(generic_anim *ga)
Definition: generic.cpp:80
bool substitute_missing_messages
int Num_messages
#define CHA_MSGRECEIVED
Definition: scripting.h:77
int message_get_persona(ship *shipp)
#define COMM_OK
Definition: subsysdamage.h:52
void RemHookVars(unsigned int num,...)
Definition: scripting.cpp:754
int ship_info_index
Definition: ship.h:539
GLfloat GLfloat p
Definition: Glext.h:8373
#define MULTIPLAYER_MASTER
Definition: multi.h:130
void message_send_unique_to_player(char *id, void *data, int m_source, int priority, int group, int delay)
#define F_NAME
Definition: parselo.h:34
void snd_set_volume(int sig, float volume)
Definition: sound.cpp:920
unsigned char direction
Definition: generic.h:25
SCP_vector< ship_info > Ship_info
Definition: ship.cpp:164
int builtin_type
#define LOCATION
Definition: pstypes.h:245
#define SIF_FIGHTER
Definition: ship.h:885
GLenum GLsizei GLenum GLenum const GLvoid * data
Definition: Gl.h:1509
#define MESSAGE_WINGMAN_SCREAM
#define timestamp_elapsed(stamp)
Definition: timer.h:102
SCP_vector< species_info > Species_info
void SetHookVar(char *name, char format, void *data=NULL)
Definition: scripting.cpp:650
#define MISSION_FLAG_NO_BUILTIN_COMMAND
Definition: missionparse.h:86
const GLfloat * m
Definition: Glext.h:10319
#define SF2_ALWAYS_DEATH_SCREAM
Definition: ship.h:496
int snd_unload(int n)
Definition: sound.cpp:415
int Iff_traitor
Definition: iff_defs.cpp:22
#define MAX_TVT_TEAMS
Definition: globals.h:57
#define BUILTIN_MATCHES_TYPE
void hud_stuff_ship_class(char *ship_class_text, ship *shipp)
Definition: hudtarget.cpp:5325
#define SIF_NOT_FLYABLE
Definition: ship.h:946
GLint GLsizei count
Definition: Gl.h:1491
GLsizei GLsizei GLchar * source
Definition: Glext.h:5625
GLenum GLsizei len
Definition: Glext.h:6283
void message_queue_process()
int Default_command_persona
int Num_message_waves
uint flags2
Definition: ship.h:645
float aav_voice_volume
Definition: sound.cpp:78
#define SF_DYING
Definition: ship.h:447
int temp
Definition: lua.cpp:4996
#define IFFF_WING_NAME_HIDDEN
Definition: iff_defs.h:29
int command_persona
Definition: missionparse.h:158
SCP_vector< SCP_string > Builtin_moods
char filename[MAX_FILENAME_LEN]
Definition: generic.h:18
void message_play_anim(message_q *q)
int ship_name_lookup(const char *name, int inc_players)
Definition: ship.cpp:12900
SCP_vector< int > excluded_moods
#define SF_SHIP_HAS_SCREAMED
Definition: ship.h:468
void snd_stop(int sig)
Definition: sound.cpp:875
void hud_stuff_ship_callsign(char *ship_callsign_text, ship *shipp)
Definition: hudtarget.cpp:5290
bool fsspeech_playing()
Definition: fsspeech.h:53
mission The_mission
#define MESSAGE_SOURCE_SPECIAL
#define BUILTIN_MATCHES_PERSONA
#define MESSAGE_SOURCE_COMMAND
char * Persona_type_names[MAX_PERSONA_TYPES]
#define FALSE
Definition: pstypes.h:400
int message_playing_specific_builtin(int builtin_type)
int generic_anim_stream(generic_anim *ga)
Definition: generic.cpp:159
int Head_coords[GR_NUM_RESOLUTIONS][2]
#define F_MULTITEXT
Definition: parselo.h:43
#define stricmp(s1, s2)
Definition: config.h:271
char * Mp
Definition: parselo.cpp:48
sound to indicate voice is about to start
Definition: gamesnd.h:132
#define timestamp_valid(stamp)
Definition: timer.h:104
void message_load_wave(int index, const char *filename)
char * special_message
char ship_name[NAME_LENGTH]
Definition: ship.h:604
void message_mission_free_avi(int m_index)
int myrand()
Definition: systemvars.cpp:102
int add_wave(const char *wave_name)
#define MESSAGE_OOPS
#define NG_TYPE_DOGFIGHT
Definition: multi.h:651
#define MESSAGE_TIME_SOON
void message_pagein_mission_messages()
void message_moods_parse()
#define strcpy_s(...)
Definition: safe_strings.h:67
Definition: sound.h:72
int snd_play_raw(int soundnum, float pan, float vol_scale, int priority)
Definition: sound.cpp:477
void persona_parse()
#define MESSAGE_IMMEDIATE_TIMESTAMP