]> icculus.org git repositories - taylor/freespace2.git/blob - src/mission/missiontraining.cpp
The Great Newline Fix
[taylor/freespace2.git] / src / mission / missiontraining.cpp
1 /*
2  * $Logfile: /Freespace2/code/Mission/MissionTraining.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Special code for training missions.  Stuff like displaying training messages in
8  * the special training window, listing the training objectives, etc.
9  *
10  * $Log$
11  * Revision 1.2  2002/05/07 03:16:46  theoddone33
12  * The Great Newline Fix
13  *
14  * Revision 1.1.1.1  2002/05/03 03:28:10  root
15  * Initial import.
16  *
17  * 
18  * 10    8/27/99 2:20p Andsager
19  * Sort directives by born on date
20  * 
21  * 9     8/22/99 7:57p Alanl
22  * fix bug that was scaling down mission training voice twice
23  * 
24  * 8     8/02/99 7:29p Jefff
25  * moved instructor text in hi res
26  * 
27  * 7     8/02/99 3:49p Jefff
28  * moved directives window down in 1024
29  * 
30  * 6     7/10/99 1:44p Andsager
31  * Modified directives listing so that current and recently
32  * satisfied/failed directives stay on screen.
33  * 
34  * 5     6/10/99 3:43p Dave
35  * Do a better job of syncing text colors to HUD gauges.
36  * 
37  * 4     2/17/99 2:10p Dave
38  * First full run of squad war. All freespace and tracker side stuff
39  * works.
40  * 
41  * 3     10/13/98 9:28a Dave
42  * Started neatening up freespace.h. Many variables renamed and
43  * reorganized. Added AlphaColors.[h,cpp]
44  * 
45  * 2     10/07/98 10:53a Dave
46  * Initial checkin.
47  * 
48  * 1     10/07/98 10:49a Dave
49  * 
50  * 58    8/25/98 1:48p Dave
51  * First rev of EMP effect. Player side stuff basically done. Next comes
52  * AI code.
53  * 
54  * 57    6/13/98 5:19p Hoffoss
55  * externalized control config texts.
56  * 
57  * 56    6/09/98 10:31a Hoffoss
58  * Created index numbers for all xstr() references.  Any new xstr() stuff
59  * added from here on out should be added to the end if the list.  The
60  * current list count can be found in FreeSpace.cpp (search for
61  * XSTR_SIZE).
62  * 
63  * 55    6/01/98 11:43a John
64  * JAS & MK:  Classified all strings for localization.
65  * 
66  * 54    5/23/98 4:14p John
67  * Added code to preload textures to video card for AGP.   Added in code
68  * to page in some bitmaps that weren't getting paged in at level start.
69  * 
70  * 53    5/16/98 9:13p Allender
71  * don't display the directive display in multiplayer
72  * 
73  * 52    5/07/98 11:59p Mike
74  * Don't show training popup for unbound key unless in training mission.
75  * (Happens in demo01)
76  * 
77  * 51    4/30/98 12:01a Lawrance
78  * using nprintf((Warning,...)) when audiostream can't be opened
79  * 
80  * 50    4/27/98 11:20a Hoffoss
81  * Doubled TRAINING_MSG_QUE_MAX.
82  * 
83  * 49    4/22/98 3:21p Hoffoss
84  * Fixed slight training message problems with training_failure state.
85  * 
86  * 48    4/16/98 4:33p Hoffoss
87  * Added support for detecting instructor terminating training due to
88  * player shooting at him.
89  * 
90  * 47    4/15/98 10:16p Mike
91  * Training mission #5.
92  * Fix application of subsystem damage.
93  * 
94  * 46    4/15/98 5:25p Lawrance
95  * only show damage gauge when training msg not visible
96  * 
97  * 45    4/03/98 2:52p Allender
98  * use Missiontime insteadof timestamp() for objective display
99  * 
100  * 44    3/31/98 5:23p Hoffoss
101  * Fixed bug with training message token handling.
102  * 
103  * 43    3/18/98 6:22p John
104  * Made directives display wiggle when afterburner is on
105  * 
106  * 42    3/11/98 2:46p Hoffoss
107  * Shortened the width of the training message window so it doesn't
108  * overlap other hud objects.
109  * 
110  * 41    2/23/98 8:31a John
111  * More string externalization
112  * 
113  * 40    2/22/98 4:30p John
114  * More string externalization classification
115  * 
116  * 39    2/04/98 4:32p Allender
117  * support for multiple briefings and debriefings.  Changes to mission
118  * type (now a bitfield).  Bitfield defs for multiplayer modes
119  * 
120  * 38    1/30/98 4:24p Hoffoss
121  * Added a 3 second delay for directives before they get displayed.
122  * 
123  * 37    1/26/98 6:27p Lawrance
124  * Change popups to use '&' meta-char for underlined hotkey.
125  * 
126  * 36    1/22/98 11:46p Hoffoss
127  * Fixed linking problem with Fred.
128  * 
129  * 35    1/22/98 11:00p Mike
130  * Fix Fred link error by commenting out line, email Hoffoss with info.
131  * 
132  * 34    1/22/98 4:15p Hoffoss
133  * Added code to allow popup to tell player he needs to bind a key for the
134  * training mission.
135  * 
136  * 33    1/20/98 2:26p Hoffoss
137  * Removed references to timestamp_ticker, used timestamp() instead.
138  * 
139  * 32    1/19/98 9:37p Allender
140  * Great Compiler Warning Purge of Jan, 1998.  Used pragma's in a couple
141  * of places since I was unsure of what to do with code.
142  * 
143  * 31    1/15/98 6:00p Hoffoss
144  * Added option to quit menu (in game) to restart the mission.  Doesn't
145  * seem to quite work, though.  Checking code in so someone else can look
146  * into it.
147  * 
148  * 30    1/09/98 10:47a Lawrance
149  * Only clear auto-target, linking etc. when actually starting a training
150  * mission.
151  * 
152  * 29    1/08/98 4:07p Hoffoss
153  * Made objective key text appear in Green.
154  * 
155  * 28    1/06/98 2:07p Lawrance
156  * strip leading whitespace on training msg lines
157  * 
158  * 27    1/05/98 11:05p Lawrance
159  * Clear primary/secondary linking if playing a training mission.
160  * 
161  * 26    1/05/98 10:03p Lawrance
162  * Fix HUD gauge flag that didn't get changed.
163  * 
164  * 25    1/05/98 4:04p Hoffoss
165  * Changed training-msg sexp operator to allow it to control the length of
166  * time a message is displayed for.
167  * 
168  * 24    1/05/98 11:16a Mike
169  * Clear auto-targeting and auto-speed-matching in a training mission.
170  * 
171  * 23    1/02/98 9:10p Lawrance
172  * Big changes to how colors get set on the HUD.
173  * 
174  * 22    12/22/97 6:07p Hoffoss
175  * Made directives flash when completed, fixed but with is-destroyed
176  * operator.
177  * 
178  * 21    12/19/97 12:43p Hoffoss
179  * Changed code to allow counts in directives.
180  * 
181  * 20    12/18/97 11:53a Lawrance
182  * fix bug with displaying hud directives
183  * 
184  * 19    12/16/97 9:13p Lawrance
185  * Integrate new gauges into HUD config.
186  * 
187  * 18    12/15/97 5:26p Allender
188  * temporary code to display for 5 second completion status of objectives
189  * 
190  * 17    11/20/97 1:10a Lawrance
191  * add support for voice volumes
192  * 
193  * 16    11/17/97 6:38p Lawrance
194  * move directives window down
195  * 
196  * 15    11/14/97 1:24p Lawrance
197  * draw 'directives' as the title
198  * 
199  * 14    11/14/97 1:21p Lawrance
200  * split directives lines if they get too long
201  * 
202  * 13    11/12/97 6:00p Hoffoss
203  * Added training messages to hud scrollback log.
204  * 
205  * 12    11/11/97 10:27p Lawrance
206  * implement new HUD directives gauge
207  * 
208  * 11    11/11/97 12:59a Lawrance
209  * move objective window up slightly
210  * 
211  * 10    10/21/97 7:18p Hoffoss
212  * Overhauled the key/joystick control structure and usage throughout the
213  * entire FreeSpace code.  The whole system is very different now.
214  * 
215  * 9     10/21/97 3:35p Dan
216  * ALAN: if wave file doesn't exist, don't play it
217  * 
218  * 8     10/17/97 6:39p Hoffoss
219  * Added delayability to key-pressed operator and training-msg operator.
220  * 
221  * 7     10/17/97 10:23a Hoffoss
222  * Added more comments.
223  * 
224  * 6     10/14/97 11:35p Lawrance
225  * change snd_load parameters
226  * 
227  * 5     10/13/97 4:33p Hoffoss
228  * Made training messages go away after time.
229  * 
230  * 4     10/13/97 3:24p Hoffoss
231  * Made it so training message text can be arbitrarily bolded.
232  * 
233  * 3     10/10/97 6:15p Hoffoss
234  * Implemented a training objective list display.
235  * 
236  * 2     10/09/97 4:44p Hoffoss
237  * Dimmed training window glass and made it less transparent, added flags
238  * to events, set he stage for detecting current events.
239  * 
240  * 1     10/09/97 2:41p Hoffoss
241  *
242  * $NoKeywords: $
243  */
244
245 #include "pstypes.h"
246 #include "parselo.h"
247 #include "cfile.h"
248 #include "sound.h"
249 #include "audiostr.h"
250 #include "missionmessage.h"
251 #include "missiongoals.h"
252 #include "missionparse.h"
253 #include "timer.h"
254 #include "controlsconfig.h"
255 #include "sexp.h"
256 #include "2d.h"
257 #include "hudtarget.h"
258 #include "freespace.h"
259 #include "hud.h"
260 #include "bmpman.h"
261 #include "hudconfig.h"
262 #include        "player.h"
263 #include "popup.h"
264 #include "gamesequence.h"
265 #include "emp.h"
266 #include "alphacolors.h"
267 #include "multi.h"
268
269 #define MAX_TRAINING_MSG_LINES          10
270 //#define TRAINING_MSG_WINDOW_X                 174
271 //#define TRAINING_MSG_WINDOW_Y                 40
272 #define TRAINING_MSG_WINDOW_WIDTH       266
273 #define TRAINING_LINE_WIDTH                     250  // width in pixels of actual text
274 #define TRAINING_TIMING                                 150  // milliseconds per character to display messages
275 #define TRAINING_TIMING_BASE                    1000  // Minimum milliseconds to display any message
276 //#define TRAINING_OBJ_WND_X                            0               // offset of left edge of window
277 //#define TRAINING_OBJ_WND_Y                            180     // offset of top edge of window
278 //#define TRAINING_OBJ_WND_Y                            187     // offset of top edge of window
279 #define TRAINING_OBJ_WND_WIDTH          170     // number of pixels wide window is.
280 #define TRAINING_OBJ_LINE_WIDTH         150     // number of pixels wide text can be
281 #define TRAINING_OBJ_LINES                              50              // number of lines to track in objective list
282 #define TRAINING_OBJ_DISPLAY_LINES      5               // only display this many lines on screen max
283 #define MAX_TRAINING_MSG_MODS                   20
284 #define TRAINING_MSG_QUE_MAX                    40
285
286 #define TRAINING_OBJ_STATUS_UNKNOWN             (1 << 28)       // directive status is unknown
287 #define TRAINING_OBJ_STATUS_KNOWN               (1 << 29)       // directive status is known (satisfied or failed)
288 #define TRAINING_OBJ_LINES_KEY                  (1 << 30)       // flag indicating line describes the key associated with objective
289 #define TRAINING_OBJ_LINES_EVENT_STATUS_MASK (TRAINING_OBJ_STATUS_KNOWN | TRAINING_OBJ_STATUS_UNKNOWN)
290
291 #define TRAINING_OBJ_LINES_MASK(n)      (Training_obj_lines[n] & 0xffff)
292
293 #define TMMOD_NORMAL    0
294 #define TMMOD_BOLD      1
295
296 typedef struct {
297         char *pos;
298         int mode;  // what function to perform at given position (TMMOD_*)
299 } training_msg_mods;  // training message modifiers
300
301 typedef struct {
302         int num;
303         int timestamp;
304         int length;
305 } training_msg_que;
306
307 char Training_buf[8192], Training_text[8192];
308 char *Training_lines[MAX_TRAINING_MSG_LINES];  // Training message split into lines
309 char Training_voice_filename[NAME_LENGTH];
310 int Training_msg_timestamp;
311 int Training_line_sizes[MAX_TRAINING_MSG_LINES];
312 int Training_msg_method = 1;
313 int Training_num_lines = 0;
314 int Training_voice = -1;
315 int Training_voice_type;
316 int Training_voice_handle;
317 int Training_flag = 0;
318 int Training_failure = 0;
319 int Training_msg_que_count = 0;
320 int Training_bind_warning = -1;  // Missiontime at which we last gave warning
321 int Training_msg_visible;
322 training_msg_que Training_msg_que[TRAINING_MSG_QUE_MAX];
323
324 // coordinates for training messages
325 int Training_msg_window_coords[GR_NUM_RESOLUTIONS][2] = {
326         { 174, 40 },
327         { 379, 125 }
328 };
329
330 // coordinates for "directives" window on hud
331 int Training_obj_window_coords[GR_NUM_RESOLUTIONS][2] = {
332         { 0, 187 },
333         { 0, 287 }
334 };
335
336
337 // training objectives global vars.
338 int Training_obj_num_lines;
339 int Training_obj_lines[TRAINING_OBJ_LINES];
340 training_msg_mods Training_msg_mods[MAX_TRAINING_MSG_MODS];
341
342 // local module prototypes
343 void training_process_msg(char *msg);
344 void message_translate_tokens(char *buf, char *text);
345
346
347 #define NUM_DIRECTIVE_GAUGES                    3
348 static hud_frames Directive_gauge[NUM_DIRECTIVE_GAUGES];
349 static int Directive_frames_loaded = 0;
350
351 static char *Directive_fnames[3] = 
352 {
353 //XSTR:OFF
354         "directives1",
355         "directives2",
356         "directives3"
357 //XSTR:ON
358 };
359
360 #define DIRECTIVE_H                                             9
361 #define DIRECTIVE_X                                             5
362 #define NUM_DIRECTIVE_COORDS                    3
363 #define DIRECTIVE_COORDS_TOP                    0
364 #define DIRECTIVE_COORDS_MIDDLE         1
365 #define DIRECTIVE_COORDS_TITLE          2
366 static int Directive_coords[GR_NUM_RESOLUTIONS][NUM_DIRECTIVE_COORDS][2] =
367 {
368         {
369                 // GR_640
370                 {5,178},
371                 {5,190},
372                 {7,180}
373         },
374         {
375                 // GR_1024
376                 {5,278},
377                 {5,290},
378                 {7,280}
379         }
380 };
381
382 // displays (renders) the training objectives list
383 void training_obj_display()
384 {
385         char buf[256], *second_line;
386         int i, t, x, y, z, height, end, offset, bx, by, y_count;
387         color *c;
388
389         if (!Training_obj_num_lines){
390                 return;
391         }
392
393         if ( !hud_gauge_active(HUD_DIRECTIVES_VIEW) ) {
394                 // Always draw the directives display if this is a training mission
395                 if ( !(The_mission.game_type & MISSION_TYPE_TRAINING) ) {
396                         return;
397                 }
398         }
399
400         // don't ever display directives display in multiplayer missions
401         // if ( Game_mode & GM_MULTIPLAYER ){
402         //      return;
403         // }
404
405         height = gr_get_font_height();
406
407         offset = 0;
408         end = Training_obj_num_lines;
409         if (end > TRAINING_OBJ_DISPLAY_LINES) {
410                 end = TRAINING_OBJ_DISPLAY_LINES;
411                 offset = Training_obj_num_lines - end;
412         }
413
414         // draw top of objective display
415         // hud_set_default_color();
416         hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
417
418         GR_AABITMAP(Directive_gauge[0].first_frame, Directive_coords[gr_screen.res][DIRECTIVE_COORDS_TOP][0]+fl2i(HUD_offset_x), Directive_coords[gr_screen.res][DIRECTIVE_COORDS_TOP][1]+fl2i(HUD_offset_y));
419         // gr_set_bitmap(Directive_gauge[0].first_frame);
420         // gr_aabitmap(Directive_coords[DIRECTIVE_COORDS_TOP][0]+fl2i(HUD_offset_x), Directive_coords[DIRECTIVE_COORDS_TOP][1]+fl2i(HUD_offset_y));
421
422         // print out title
423         emp_hud_printf(Directive_coords[gr_screen.res][DIRECTIVE_COORDS_TITLE][0]+fl2i(HUD_offset_x), Directive_coords[gr_screen.res][DIRECTIVE_COORDS_TITLE][1]+fl2i(HUD_offset_y), EG_OBJ_TITLE, XSTR( "directives", 422));
424         // gr_printf(Directive_coords[DIRECTIVE_COORDS_TITLE][0]+fl2i(HUD_offset_x), Directive_coords[DIRECTIVE_COORDS_TITLE][1]+fl2i(HUD_offset_y), XSTR( "directives", 422));
425         
426         bx = DIRECTIVE_X+fl2i(HUD_offset_x);
427         by = Directive_coords[gr_screen.res][DIRECTIVE_COORDS_MIDDLE][1]+fl2i(HUD_offset_y);
428
429         y_count = 0;
430         for (i=0; i<end; i++) {
431                 x = DIRECTIVE_X + 3 + fl2i(HUD_offset_x);
432                 y = Training_obj_window_coords[gr_screen.res][1] + fl2i(HUD_offset_y) + y_count * height + height / 2 + 1;
433                 z = TRAINING_OBJ_LINES_MASK(i + offset);
434
435                 c = &Color_normal;
436                 if (Training_obj_lines[i + offset] & TRAINING_OBJ_LINES_KEY) {
437                         message_translate_tokens(buf, Mission_events[z].objective_key_text);  // remap keys
438 //                      gr_set_color_fast(&Color_normal);
439                         c = &Color_bright_green;
440                 } else {
441                         strcpy(buf, Mission_events[z].objective_text);
442                         if (Mission_events[z].count){
443                                 sprintf(buf + strlen(buf), NOX(" [%d]"), Mission_events[z].count);
444                         }
445
446                         // if this is a multiplayer tvt game, and this is event is not for my team, don't display it
447                         if((Game_mode & GM_MULTIPLAYER) && (Netgame.type_flags & NG_TYPE_TEAM) && (Net_player != NULL)){
448                                 if((Mission_events[z].team != -1) && (Net_player->p_info.team != Mission_events[z].team)){
449                                         continue;
450                                 }
451                         }
452
453                         switch (mission_get_event_status(z)) {
454                         case EVENT_CURRENT:
455 //                              gr_set_color_fast(&Color_bright_white);
456                                 c = &Color_bright_white;
457                                 break;
458
459                         case EVENT_FAILED:
460 //                              gr_set_color_fast(&Color_bright_red);
461                                 c = &Color_bright_red;
462                                 break;
463
464                         case EVENT_SATISFIED:
465 //                              gr_set_color_fast(&Color_bright_blue);
466                                 t = Mission_events[z].satisfied_time;
467                                 if (t + i2f(2) > Missiontime) {
468                                         if (Missiontime % fl2f(.4f) < fl2f(.2f)){
469                                                 c = &Color_bright_blue;
470                                         } else {
471                                                 c = &Color_bright_white;
472                                         }
473                                 } else {
474                                         c = &Color_bright_blue;
475                                 }
476                                 break;
477                         }
478                 }
479
480                 // maybe split the directives line
481                 second_line = split_str_once(buf, 167);
482
483                 // blit the background frames
484                 // hud_set_default_color();
485                 hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
486
487                 GR_AABITMAP(Directive_gauge[1].first_frame, bx, by);
488                 // gr_set_bitmap(Directive_gauge[1].first_frame);
489                 // gr_aabitmap(bx, by);
490                 
491                 by += DIRECTIVE_H;
492
493                 if ( second_line ) {
494                         GR_AABITMAP(Directive_gauge[1].first_frame, bx, by);
495                         // gr_set_bitmap(Directive_gauge[1].first_frame);
496                         // gr_aabitmap(bx, by);
497                         
498                         by += DIRECTIVE_H;
499                 }
500
501                 // blit the text
502                 gr_set_color_fast(c);
503                 
504                 emp_hud_string(x, y, EG_OBJ1 + i, buf);
505                 // gr_printf(x, y, buf);
506                 
507                 y_count++;
508
509                 if ( second_line ) {
510                         y = Training_obj_window_coords[gr_screen.res][1] + fl2i(HUD_offset_y) + y_count * height + height / 2 + 1;
511                         
512                         emp_hud_string(x+12, y, EG_OBJ1 + i + 1, second_line);
513                         // gr_printf(x+12, y, second_line);
514                         
515                         y_count++;
516                 }
517         }
518
519         // draw the bottom of objective display
520         // hud_set_default_color();
521         hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
522
523         GR_AABITMAP(Directive_gauge[2].first_frame, bx, by);
524         // gr_set_bitmap(Directive_gauge[2].first_frame);
525         // gr_aabitmap(bx, by);
526 }
527
528 // mission initializations (called once before a new mission is started)
529 void training_mission_init()
530 {
531         int i;
532
533         Assert(!Training_num_lines);
534         Training_obj_num_lines = 0;
535         Training_msg_que_count = 0;
536         Training_failure = 0;
537         for (i=0; i<TRAINING_OBJ_LINES; i++)
538                 Training_obj_lines[i] = -1;
539
540         if ( !Directive_frames_loaded ) {
541                 for ( i = 0; i < NUM_DIRECTIVE_GAUGES; i++ ) {
542                         Directive_gauge[i].first_frame = bm_load_animation(Directive_fnames[i], &Directive_gauge[i].num_frames);
543                         if ( Directive_gauge[i].first_frame < 0 ) {
544                                 Warning(LOCATION,"Cannot load hud ani: %s\n", Directive_fnames[i]);
545                         }
546                 }
547
548                 Directive_frames_loaded = 1;
549         }
550
551         // only clear player flags if this is actually a training mission
552         if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
553                 Player->flags &= ~(PLAYER_FLAGS_MATCH_TARGET | PLAYER_FLAGS_MSG_MODE | PLAYER_FLAGS_AUTO_TARGETING | PLAYER_FLAGS_AUTO_MATCH_SPEED | PLAYER_FLAGS_LINK_PRIMARY | PLAYER_FLAGS_LINK_SECONDARY );
554         }
555 }
556
557 void training_mission_page_in()
558 {
559         int i;
560         for ( i = 0; i < NUM_DIRECTIVE_GAUGES; i++ ) {
561                 bm_page_in_aabitmap( Directive_gauge[i].first_frame, Directive_gauge[i].num_frames );
562         }
563 }
564
565
566 int comp_training_lines_by_born_on_date(const void *m1, const void *m2)
567 {
568         int *e1, *e2;
569         e1 = (int*) m1;
570         e2 = (int*) m2;
571         
572         Assert(Mission_events[*e1 & 0xffff].born_on_date != 0);
573         Assert(Mission_events[*e2 & 0xffff].born_on_date != 0);
574
575         return (Mission_events[*e1 & 0xffff].born_on_date - Mission_events[*e2 & 0xffff].born_on_date);
576 }
577
578
579 // now sort list of events
580 // sort on EVENT_CURRENT and born on date, for other events (EVENT_SATISFIED, EVENT_FAILED) sort on born on date
581 #define MIN_SATISFIED_TIME              5
582 #define MIN_FAILED_TIME                 7
583 void sort_training_objectives()
584 {
585         int i, event_status, offset;
586
587         // start by sorting on born on date
588         qsort(Training_obj_lines, Training_obj_num_lines, sizeof(int), comp_training_lines_by_born_on_date);
589
590         // get the index of the first directive that will be displayed
591         // if less than 0, display all lines
592         offset = Training_obj_num_lines - TRAINING_OBJ_DISPLAY_LINES;
593
594         if (offset <= 0) {
595                 return;
596         }
597
598         // go through lines 0 to offset-1 and check if there are any CURRENT or RECENTLY_KNOWN events that should be shown
599         int num_offset_events = 0;
600         for (i=0; i<offset; i++) {
601                 event_status = mission_get_event_status(TRAINING_OBJ_LINES_MASK(i));
602
603                 if (event_status == EVENT_CURRENT)  {
604                         Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
605                         num_offset_events++;
606                 } else if (event_status ==      EVENT_SATISFIED) {
607                         if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_SATISFIED_TIME) {
608                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
609                                 num_offset_events++;
610                         } else {
611                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
612                         }
613                 } else if (event_status ==      EVENT_FAILED) {
614                         if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_FAILED_TIME) {
615                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
616                                 num_offset_events++;
617                         } else {
618                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
619                         }
620                 }
621         }
622
623         // if there are no directives which should be moved, we're done
624         if (num_offset_events == 0) {
625                 return;
626         }
627
628         // go through lines offset to Training_obj_num_lines to check which should be shown, since some will need to be bumped
629         for (i=offset; i<Training_obj_num_lines; i++) {
630                 event_status = mission_get_event_status(TRAINING_OBJ_LINES_MASK(i));
631
632                 if (event_status == EVENT_CURRENT)  {
633                         Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
634                 } else if (event_status ==      EVENT_SATISFIED) {
635                         if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_SATISFIED_TIME) {
636                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
637                         } else {
638                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
639                         }
640                 } else if (event_status ==      EVENT_FAILED) {
641                         if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_FAILED_TIME) {
642                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
643                         } else {
644                                 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
645                         }
646                 }
647         }
648
649
650         int slot_idx, unkn_vis;
651         // go through list and bump as needed
652         for (i=0; i<num_offset_events; i++) {
653
654                 // find most recent directive that would not be shown
655                 for (unkn_vis=offset-1; unkn_vis>=0; unkn_vis--) {
656                         if (Training_obj_lines[unkn_vis] & TRAINING_OBJ_STATUS_UNKNOWN) {
657                                 break;
658                         }
659                 }
660
661                 // find first slot that can be bumped
662                 // look at the last (N-4 to N) positions
663                 for (slot_idx=0; slot_idx<TRAINING_OBJ_DISPLAY_LINES; slot_idx++) {
664                         if ( Training_obj_lines[i+offset] & TRAINING_OBJ_STATUS_KNOWN ) {
665                                 break;
666                         }
667                 }
668
669                 // shift and replace (mark old one as STATUS_KNOWN)
670                 for (int j=slot_idx; j>0; j--) {
671                         Training_obj_lines[j+offset-1] = Training_obj_lines[j+offset-2];
672                 }
673                 Training_obj_lines[offset] = Training_obj_lines[unkn_vis];
674                 Training_obj_lines[unkn_vis] &= ~TRAINING_OBJ_LINES_EVENT_STATUS_MASK;
675                 Training_obj_lines[unkn_vis] |= TRAINING_OBJ_STATUS_KNOWN;
676         }
677
678         // remove event status
679         for (i=0; i<Training_obj_num_lines; i++) {
680                 Training_obj_lines[i] &= ~TRAINING_OBJ_LINES_EVENT_STATUS_MASK;
681         }
682 }
683
684 // called at same rate as goals/events are evaluated.  Maintains the objectives listing, adding,
685 // removing and updating items
686 void training_check_objectives()
687 {
688         int i, event_idx, event_status;
689
690         Training_obj_num_lines = 0;
691         for (event_idx=0; event_idx<Num_mission_events; event_idx++) {
692                 event_status = mission_get_event_status(event_idx);
693                 if ( (event_status != EVENT_UNBORN) && Mission_events[event_idx].objective_text && (timestamp() > Mission_events[event_idx].born_on_date + 3000) ) {
694                         if (!Training_failure || !strnicmp(Mission_events[event_idx].name, XSTR( "Training failed", 423), 15)) {
695
696                                 // check for the actual objective
697                                 for (i=0; i<Training_obj_num_lines; i++) {
698                                         if (Training_obj_lines[i] == event_idx) {
699                                                 break;
700                                         }
701                                 }
702
703                                 // not in objective list, need to add it
704                                 if (i == Training_obj_num_lines) {
705                                         if (Training_obj_num_lines == TRAINING_OBJ_LINES) {
706
707                                                 // objective list full, remove topmost objective
708                                                 for (i=1; i<TRAINING_OBJ_LINES; i++) {
709                                                         Training_obj_lines[i - 1] = Training_obj_lines[i];
710                                                 }
711                                                 Training_obj_num_lines--;
712                                         }
713                                         // add the new directive
714                                         Training_obj_lines[Training_obj_num_lines++] = event_idx;
715                                 }
716
717                                 // now check for the associated keypress text
718                                 for (i=0; i<Training_obj_num_lines; i++) {
719                                         if (Training_obj_lines[i] == (event_idx | TRAINING_OBJ_LINES_KEY)) {
720                                                 break;
721                                         }
722                                 }
723
724                                 // if there is a keypress message with directive, process that too.
725                                 if (Mission_events[event_idx].objective_key_text) {
726                                         if (event_status == EVENT_CURRENT) {
727
728                                                 // not in objective list, need to add it
729                                                 if (i == Training_obj_num_lines) {
730                                                         if (Training_obj_num_lines == TRAINING_OBJ_LINES) {
731
732                                                                 // objective list full, remove topmost objective
733                                                                 for (i=1; i<Training_obj_num_lines; i++) {
734                                                                         Training_obj_lines[i - 1] = Training_obj_lines[i];
735                                                                 }
736                                                                 Training_obj_num_lines--;
737                                                         }
738                                                         // mark objective as having key text
739                                                         Training_obj_lines[Training_obj_num_lines++] = event_idx | TRAINING_OBJ_LINES_KEY;
740                                                 }
741
742                                         } else {
743                                                 // message with key press text is no longer valid, so remove it
744                                                 if (i < Training_obj_num_lines) {
745                                                         for (; i<Training_obj_num_lines - 1; i++) {
746                                                                 Training_obj_lines[i] = Training_obj_lines[i + 1];
747                                                         }
748                                                         Training_obj_num_lines--;
749                                                 }
750                                         }
751                                 }
752                         }
753                 }
754         }
755
756         // now sort list of events
757         // sort on EVENT_CURRENT and born on date, for other events (EVENT_SATISFIED, EVENT_FAILED) sort on born on date
758         sort_training_objectives();
759 }
760
761 // called to do cleanup when leaving a mission
762 void training_mission_shutdown()
763 {
764         if (Training_voice >= 0) {
765                 if (Training_voice_type) {
766                         audiostream_close_file(Training_voice_handle, 0);
767
768                 } else {
769                         snd_stop(Training_voice_handle);
770                 }
771         }
772
773         Training_voice = -1;
774         Training_num_lines = Training_obj_num_lines = 0;
775         *Training_text = 0;
776 }
777
778 // translates special tokens.  Handles one token only.
779 char *translate_msg_token(char *str)
780 {
781         if (!stricmp(str, NOX("wp"))) {
782                 sprintf(str, "%d", Training_context_goal_waypoint + 1);
783                 return str;
784         }
785
786         return NULL;
787 }
788
789 // translates all special tokens in a message, producing the new finalized message to be displayed
790 void message_translate_tokens(char *buf, char *text)
791 {
792         char temp[40], *toke1, *toke2, *ptr, *orig_buf;
793         int r;
794
795         orig_buf = buf;
796         *buf = 0;
797         toke1 = strchr(text, '$');
798         toke2 = strchr(text, '#');
799         while (toke1 || toke2) {  // is either token types present?
800                 if (!toke2 || (toke1 && (toke1 < toke2))) {  // found $ before #
801                         strncpy(buf, text, toke1 - text + 1);  // copy text up to token
802                         buf += toke1 - text + 1;
803                         text = toke1 + 1;  // advance pointers past processed data
804
805                         toke2 = strchr(text, '$');
806                         if (!toke2)  // No second one?
807                                 break;
808
809                         strncpy(temp, text, toke2 - text);  // isolate token into seperate buffer
810                         temp[toke2 - text] = 0;  // null terminate string
811                         ptr = translate_key(temp);  // try and translate key
812                         if (ptr) {  // was key translated properly?
813                                 if (!stricmp(ptr, NOX("none")) && (Training_bind_warning != Missiontime)) {
814                                         if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
815                                                 r = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, XSTR( "&Bind Control", 424), XSTR( "&Abort mission", 425),
816                                                         XSTR( "Warning\nYou have no control bound to the action \"%s\".  You must do so before you can continue with your training.", 426),
817                                                         XSTR(Control_config[Failed_key_index].text, CONTROL_CONFIG_XSTR + Failed_key_index));
818
819                                                 if (r) {  // do they want to abort the mission?
820                                                         gameseq_post_event(GS_EVENT_END_GAME);
821                                                         return;
822                                                 }
823
824                                                 gameseq_post_event(GS_EVENT_CONTROL_CONFIG);  // goto control config screen to bind the control
825                                         }
826                                 }
827
828                                 buf--;  // erase the $
829                                 strcpy(buf, ptr);  // put translated key in place of token
830                                 buf += strlen(buf);
831                                 text = toke2 + 1;
832                         }
833
834                 } else {
835                         strncpy(buf, text, toke2 - text + 1);  // copy text up to token
836                         buf += toke2 - text + 1;
837                         text = toke2 + 1;  // advance pointers past processed data
838
839                         toke1 = strchr(text, '#');
840                         if (toke1)  // No second one?
841                                 break;
842
843                         strncpy(temp, text, toke1 - text);  // isolate token into seperate buffer
844                         temp[toke1 - text] = 0;  // null terminate string
845                         ptr = translate_msg_token(temp);  // try and translate key
846                         if (ptr) {  // was key translated properly?
847                                 buf--;  // erase the #
848                                 strcpy(buf, ptr);  // put translated key in place of token
849                                 buf += strlen(buf);
850                                 text = toke1 + 1;
851                         }
852                 }
853
854                 toke1 = strchr(text, '$');
855                 toke2 = strchr(text, '#');
856         }
857
858         strcpy(buf, text);
859         return;
860 }
861
862 // plays the voice file associated with a training message.  Automatically streams the file
863 // from disk if it's over 100k, otherwise plays it as a normal file in memory.  Returns -1
864 // if it didn't play, otherwise index of voice
865 int message_play_training_voice(int index)
866 {
867         int len;
868         CFILE *fp;
869
870         if (index < 0) {
871                 if (Training_voice >= 0) {
872                         if (Training_voice_type) {
873                                 audiostream_close_file(Training_voice_handle, 0);
874
875                         } else {
876                                 snd_stop(Training_voice_handle);
877                         }
878                 }
879
880                 Training_voice = -1;
881                 return -1;
882         }
883
884         if (Message_waves[index].num < 0) {
885                 fp = cfopen(Message_waves[index].name, "rb");
886                 if (!fp)
887                         return -1;
888
889                 len = cfilelength(fp);
890                 cfclose(fp);
891                 if (len > 100000) {
892                         if ((Training_voice < 0) || !Training_voice_type || (Training_voice != index)) {
893                                 if (Training_voice >= 0) {
894                                         if (Training_voice_type) {
895                                                 if (Training_voice == index)
896                                                         audiostream_stop(Training_voice_handle);
897                                                 else
898                                                         audiostream_close_file(Training_voice_handle, 0);
899
900                                         } else {
901                                                 snd_stop(Training_voice_handle);
902                                         }
903                                 }
904
905                                 if (stricmp(Message_waves[index].name, NOX("none.wav"))) {
906                                         Training_voice_handle = audiostream_open(Message_waves[index].name, ASF_VOICE);
907                                         if (Training_voice_handle < 0) {
908                                                 nprintf(("Warning", "Unable to load voice file %s\n", Message_waves[index].name));
909                                         //      Warning(LOCATION, "Unable to load voice file %s\n", Message_waves[index].name);
910                                         }
911                                 }
912                         }  // Training_voice should be valid and loaded now
913
914                         Training_voice_type = 1;
915                         if (Training_voice_handle >= 0)
916                                 audiostream_play(Training_voice_handle, Master_voice_volume, 0);
917
918                         Training_voice = index;
919                         return Training_voice;
920
921                 } else {
922                         game_snd tmp_gs;
923                         memset(&tmp_gs, 0, sizeof(game_snd));
924                         strcpy(tmp_gs.filename, Message_waves[index].name);
925                         Message_waves[index].num = snd_load(&tmp_gs);
926                         if (Message_waves[index].num < 0) {
927                                 nprintf(("Warning", "Cannot load message wave: %s.  Will not play\n", Message_waves[index].name));
928                                 return -1;
929                         }
930                 }
931         }
932
933         if (Training_voice >= 0) {
934                 if (Training_voice_type) {
935                         audiostream_close_file(Training_voice_handle, 0);
936
937                 } else {
938                         snd_stop(Training_voice_handle);
939                 }
940         }
941
942         Training_voice = index;
943         if (Message_waves[index].num >= 0)
944                 Training_voice_handle = snd_play_raw(Message_waves[index].num, 0.0f);
945         else
946                 Training_voice_handle = -1;
947
948         Training_voice_type = 0;
949         return Training_voice;
950 }
951
952 // one time initializations done when we want to display a new training mission.  This does
953 // all the processing and setup required to actually display it, including starting the
954 // voice file playing
955 void message_training_setup(int m, int length)
956 {
957         if ((m < 0) || !Messages[m].message[0]) {  // remove current message from the screen
958                 Training_num_lines = 0;
959                 return;
960         }
961
962         message_translate_tokens(Training_buf, Messages[m].message);
963         HUD_add_to_scrollback(Training_buf, HUD_SOURCE_TRAINING);
964         strcpy(Training_text, Messages[m].message);
965
966         if (message_play_training_voice(Messages[m].wave_info.index) < 0) {
967                 if (length > 0)
968                         Training_msg_timestamp = timestamp(length * 1000);
969                 else
970                         Training_msg_timestamp = timestamp(TRAINING_TIMING_BASE + strlen(Messages[m].message) * TRAINING_TIMING);  // no voice file playing
971
972         } else
973                 Training_msg_timestamp = 0;
974 }
975
976 // adds simple text to the directives display
977 /*id message_training_add_simple( char *text )
978 {
979         int i;
980
981         training_process_msg(text);
982         HUD_add_to_scrollback(Training_buf, HUD_SOURCE_TRAINING);
983         Training_num_lines = split_str(Training_buf, TRAINING_LINE_WIDTH, Training_line_sizes, Training_lines, MAX_TRAINING_MSG_LINES);
984         Assert(Training_num_lines > 0);
985         for (i=0; i<Training_num_lines; i++)
986                 Training_lines[i][Training_line_sizes[i]] = 0;
987
988         Training_msg_timestamp = timestamp(5000);
989 } */
990
991 // add a message to the que to be sent later.
992 void message_training_que(char *text, int timestamp, int length)
993 {
994         int m;
995
996         Assert(Training_msg_que_count < TRAINING_MSG_QUE_MAX);
997         if (Training_msg_que_count < TRAINING_MSG_QUE_MAX) {
998                 if (!stricmp(text, NOX("none")))
999                         m = -1;
1000
1001                 else {
1002                         for (m=0; m<Num_messages; m++)
1003                                 if (!stricmp(text, Messages[m].name))
1004                                         break;
1005
1006                         Assert(m < Num_messages);
1007                         if (m >= Num_messages)
1008                                 return;
1009                 }
1010
1011                 Training_msg_que[Training_msg_que_count].num = m;
1012                 Training_msg_que[Training_msg_que_count].timestamp = timestamp;
1013                 Training_msg_que[Training_msg_que_count].length = length;
1014                 Training_msg_que_count++;
1015         }
1016 }
1017
1018 // check the training message que to see if we should play a new message yet or not.
1019 void message_training_que_check()
1020 {
1021         int i, j, iship_num;
1022
1023         // get the instructor's ship.
1024         iship_num = ship_name_lookup(NOX("instructor"));
1025         if ( iship_num == -1 )
1026                 return;
1027
1028         // if the instructor is dying or departing, do nothing
1029         if (Ships[iship_num].flags & (SF_DYING | SF_DEPARTING))
1030                 return;
1031
1032         if (Training_failure)
1033                 return;
1034
1035         for (i=0; i<Training_msg_que_count; i++) {
1036                 if (timestamp_elapsed(Training_msg_que[i].timestamp)) {
1037                         message_training_setup(Training_msg_que[i].num, Training_msg_que[i].length);
1038                         // remove this message from the que now.
1039                         for (j=i+1; j<Training_msg_que_count; j++)
1040                                 Training_msg_que[j - 1] = Training_msg_que[j];
1041
1042                         i--;
1043                         Training_msg_que_count--;
1044                 }
1045         }
1046 }
1047
1048 // displays (renders) the training message to the screen
1049 void message_training_display()
1050 {
1051         char *str, buf[256];
1052         int i, z, x, y, height, mode, count;
1053
1054         Training_msg_visible = 0;
1055         message_training_que_check();
1056         training_obj_display();
1057
1058         if (Training_failure){
1059                 return;
1060         }
1061
1062         if (timestamp_elapsed(Training_msg_timestamp) || !strlen(Training_text)){
1063                 return;
1064         }
1065
1066         message_translate_tokens(Training_buf, Training_text);
1067         training_process_msg(Training_text);
1068         Training_num_lines = split_str(Training_buf, TRAINING_LINE_WIDTH, Training_line_sizes, Training_lines, MAX_TRAINING_MSG_LINES);
1069         Assert(Training_num_lines > 0);
1070         for (i=0; i<Training_num_lines; i++) {
1071                 Training_lines[i][Training_line_sizes[i]] = 0;
1072                 drop_leading_white_space(Training_lines[i]);
1073         }
1074
1075         if (Training_num_lines <= 0){
1076                 return;
1077         }
1078
1079         height = gr_get_font_height();
1080         gr_set_shader(&Training_msg_glass);
1081         gr_shade(Training_msg_window_coords[gr_screen.res][0], Training_msg_window_coords[gr_screen.res][1], TRAINING_MSG_WINDOW_WIDTH, Training_num_lines * height + height);
1082
1083         gr_set_color_fast(&Color_bright_blue);
1084         mode = count = 0;
1085         Training_msg_visible = 1;
1086         for (i=0; i<Training_num_lines; i++) {  // loop through all lines of message
1087                 str = Training_lines[i];
1088                 z = 0;
1089                 x = Training_msg_window_coords[gr_screen.res][0] + (TRAINING_MSG_WINDOW_WIDTH - TRAINING_LINE_WIDTH) / 2;
1090                 y = Training_msg_window_coords[gr_screen.res][1] + i * height + height / 2 + 1;
1091
1092                 while (*str) {  // loop through each character of each line
1093                         if ((count < MAX_TRAINING_MSG_MODS) && (str == Training_msg_mods[count].pos)) {
1094                                 buf[z] = 0;
1095                                 gr_printf(x, y, buf);
1096                                 gr_get_string_size(&z, NULL, buf);
1097                                 x += z;
1098                                 z = 0;
1099
1100                                 mode = Training_msg_mods[count++].mode;
1101                                 switch (mode) {
1102                                         case TMMOD_NORMAL:
1103                                                 gr_set_color_fast(&Color_bright_blue);
1104                                                 break;
1105
1106                                         case TMMOD_BOLD:
1107                                                 gr_set_color_fast(&Color_white);
1108                                                 break;
1109                                 }
1110                         }
1111
1112                         buf[z++] = *str++;
1113                 }
1114
1115                 if (z) {
1116                         buf[z] = 0;
1117                         gr_printf(x, y, "%s", buf);
1118                 }
1119         }
1120
1121         Training_msg_method = 0;
1122 //      if (Training_msg_method) {
1123 //              char *msg = "Press a key to continue";
1124
1125 //              gr_get_string_size(&i, NULL, msg);
1126 //              gr_printf(TRAINING_MSG_WINDOW_X + TRAINING_MSG_WINDOW_WIDTH / 2 - i / 2, TRAINING_MSG_WINDOW_Y + (Training_num_lines + 2) * height, msg);
1127 //      }
1128
1129         if ((Training_voice >= 0) && (Training_num_lines > 0) && !(Training_msg_timestamp)) {
1130                 if (Training_voice_type)
1131                         z = audiostream_is_playing(Training_voice_handle);
1132                 else
1133                         z = snd_is_playing(Training_voice_handle);
1134
1135                 if (!z)
1136                         Training_msg_timestamp = timestamp(2000);  // 2 second delay
1137         }
1138 }
1139
1140 // processes a new training message to get hilighting information and store it in internal structures.
1141 void training_process_msg(char *msg)
1142 {
1143         int count;
1144         char *src, *dest, buf[8192];
1145
1146         message_translate_tokens(buf, msg);
1147         count = 0;
1148         src = buf;
1149         dest = Training_buf;
1150         while (*src) {
1151                 if (!strnicmp(src, NOX("<b>"), 3)) {
1152                         Assert(count < MAX_TRAINING_MSG_MODS);
1153                         src += 3;
1154                         Training_msg_mods[count].pos = dest;
1155                         Training_msg_mods[count].mode = TMMOD_BOLD;
1156                         count++;
1157                 }
1158
1159                 if (!strnicmp(src, NOX("</b>"), 4)) {
1160                         Assert(count < MAX_TRAINING_MSG_MODS);
1161                         src += 4;
1162                         Training_msg_mods[count].pos = dest;
1163                         Training_msg_mods[count].mode = TMMOD_NORMAL;
1164                         count++;
1165                 }
1166
1167                 *dest++ = *src++;
1168         }
1169
1170         *dest = 0;
1171         if (count < MAX_TRAINING_MSG_MODS)
1172                 Training_msg_mods[count].pos = NULL;
1173 }
1174
1175 void training_fail()
1176 {
1177         Training_failure = 1;
1178         //      JasonH: Add code here to suspend training and display a directive to warp out.
1179         //      Suspend training.
1180         //      Give directive to warp out.
1181         //      Also ensure that a special failure debriefing is given.  Must mention firing at instructor.
1182         //      Ask Sandeep to write it (or you can).
1183 }
1184