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