2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Mission/MissionTraining.cpp $
15 * Special code for training missions. Stuff like displaying training messages in
16 * the special training window, listing the training objectives, etc.
19 * Revision 1.3 2002/06/09 04:41:22 relnev
20 * added copyright header
22 * Revision 1.2 2002/05/07 03:16:46 theoddone33
23 * The Great Newline Fix
25 * Revision 1.1.1.1 2002/05/03 03:28:10 root
29 * 10 8/27/99 2:20p Andsager
30 * Sort directives by born on date
32 * 9 8/22/99 7:57p Alanl
33 * fix bug that was scaling down mission training voice twice
35 * 8 8/02/99 7:29p Jefff
36 * moved instructor text in hi res
38 * 7 8/02/99 3:49p Jefff
39 * moved directives window down in 1024
41 * 6 7/10/99 1:44p Andsager
42 * Modified directives listing so that current and recently
43 * satisfied/failed directives stay on screen.
45 * 5 6/10/99 3:43p Dave
46 * Do a better job of syncing text colors to HUD gauges.
48 * 4 2/17/99 2:10p Dave
49 * First full run of squad war. All freespace and tracker side stuff
52 * 3 10/13/98 9:28a Dave
53 * Started neatening up freespace.h. Many variables renamed and
54 * reorganized. Added AlphaColors.[h,cpp]
56 * 2 10/07/98 10:53a Dave
59 * 1 10/07/98 10:49a Dave
61 * 58 8/25/98 1:48p Dave
62 * First rev of EMP effect. Player side stuff basically done. Next comes
65 * 57 6/13/98 5:19p Hoffoss
66 * externalized control config texts.
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
74 * 55 6/01/98 11:43a John
75 * JAS & MK: Classified all strings for localization.
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.
81 * 53 5/16/98 9:13p Allender
82 * don't display the directive display in multiplayer
84 * 52 5/07/98 11:59p Mike
85 * Don't show training popup for unbound key unless in training mission.
88 * 51 4/30/98 12:01a Lawrance
89 * using nprintf((Warning,...)) when audiostream can't be opened
91 * 50 4/27/98 11:20a Hoffoss
92 * Doubled TRAINING_MSG_QUE_MAX.
94 * 49 4/22/98 3:21p Hoffoss
95 * Fixed slight training message problems with training_failure state.
97 * 48 4/16/98 4:33p Hoffoss
98 * Added support for detecting instructor terminating training due to
99 * player shooting at him.
101 * 47 4/15/98 10:16p Mike
102 * Training mission #5.
103 * Fix application of subsystem damage.
105 * 46 4/15/98 5:25p Lawrance
106 * only show damage gauge when training msg not visible
108 * 45 4/03/98 2:52p Allender
109 * use Missiontime insteadof timestamp() for objective display
111 * 44 3/31/98 5:23p Hoffoss
112 * Fixed bug with training message token handling.
114 * 43 3/18/98 6:22p John
115 * Made directives display wiggle when afterburner is on
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.
121 * 41 2/23/98 8:31a John
122 * More string externalization
124 * 40 2/22/98 4:30p John
125 * More string externalization classification
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
131 * 38 1/30/98 4:24p Hoffoss
132 * Added a 3 second delay for directives before they get displayed.
134 * 37 1/26/98 6:27p Lawrance
135 * Change popups to use '&' meta-char for underlined hotkey.
137 * 36 1/22/98 11:46p Hoffoss
138 * Fixed linking problem with Fred.
140 * 35 1/22/98 11:00p Mike
141 * Fix Fred link error by commenting out line, email Hoffoss with info.
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
147 * 33 1/20/98 2:26p Hoffoss
148 * Removed references to timestamp_ticker, used timestamp() instead.
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.
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
159 * 30 1/09/98 10:47a Lawrance
160 * Only clear auto-target, linking etc. when actually starting a training
163 * 29 1/08/98 4:07p Hoffoss
164 * Made objective key text appear in Green.
166 * 28 1/06/98 2:07p Lawrance
167 * strip leading whitespace on training msg lines
169 * 27 1/05/98 11:05p Lawrance
170 * Clear primary/secondary linking if playing a training mission.
172 * 26 1/05/98 10:03p Lawrance
173 * Fix HUD gauge flag that didn't get changed.
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.
179 * 24 1/05/98 11:16a Mike
180 * Clear auto-targeting and auto-speed-matching in a training mission.
182 * 23 1/02/98 9:10p Lawrance
183 * Big changes to how colors get set on the HUD.
185 * 22 12/22/97 6:07p Hoffoss
186 * Made directives flash when completed, fixed but with is-destroyed
189 * 21 12/19/97 12:43p Hoffoss
190 * Changed code to allow counts in directives.
192 * 20 12/18/97 11:53a Lawrance
193 * fix bug with displaying hud directives
195 * 19 12/16/97 9:13p Lawrance
196 * Integrate new gauges into HUD config.
198 * 18 12/15/97 5:26p Allender
199 * temporary code to display for 5 second completion status of objectives
201 * 17 11/20/97 1:10a Lawrance
202 * add support for voice volumes
204 * 16 11/17/97 6:38p Lawrance
205 * move directives window down
207 * 15 11/14/97 1:24p Lawrance
208 * draw 'directives' as the title
210 * 14 11/14/97 1:21p Lawrance
211 * split directives lines if they get too long
213 * 13 11/12/97 6:00p Hoffoss
214 * Added training messages to hud scrollback log.
216 * 12 11/11/97 10:27p Lawrance
217 * implement new HUD directives gauge
219 * 11 11/11/97 12:59a Lawrance
220 * move objective window up slightly
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.
226 * 9 10/21/97 3:35p Dan
227 * ALAN: if wave file doesn't exist, don't play it
229 * 8 10/17/97 6:39p Hoffoss
230 * Added delayability to key-pressed operator and training-msg operator.
232 * 7 10/17/97 10:23a Hoffoss
233 * Added more comments.
235 * 6 10/14/97 11:35p Lawrance
236 * change snd_load parameters
238 * 5 10/13/97 4:33p Hoffoss
239 * Made training messages go away after time.
241 * 4 10/13/97 3:24p Hoffoss
242 * Made it so training message text can be arbitrarily bolded.
244 * 3 10/10/97 6:15p Hoffoss
245 * Implemented a training objective list display.
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.
251 * 1 10/09/97 2:41p Hoffoss
260 #include "audiostr.h"
261 #include "missionmessage.h"
262 #include "missiongoals.h"
263 #include "missionparse.h"
265 #include "controlsconfig.h"
268 #include "hudtarget.h"
269 #include "freespace.h"
272 #include "hudconfig.h"
275 #include "gamesequence.h"
277 #include "alphacolors.h"
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
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)
302 #define TRAINING_OBJ_LINES_MASK(n) (Training_obj_lines[n] & 0xffff)
304 #define TMMOD_NORMAL 0
309 int mode; // what function to perform at given position (TMMOD_*)
310 } training_msg_mods; // training message modifiers
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];
335 // coordinates for training messages
336 int Training_msg_window_coords[GR_NUM_RESOLUTIONS][2] = {
341 // coordinates for "directives" window on hud
342 int Training_obj_window_coords[GR_NUM_RESOLUTIONS][2] = {
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];
353 // local module prototypes
354 void training_process_msg(char *msg);
355 void message_translate_tokens(char *buf, const int max_buflen, char *text);
358 #define NUM_DIRECTIVE_GAUGES 3
359 static hud_frames Directive_gauge[NUM_DIRECTIVE_GAUGES];
360 static int Directive_frames_loaded = 0;
362 static const char *Directive_fnames[3] =
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] =
393 // displays (renders) the training objectives list
394 void training_obj_display()
396 char buf[256], *second_line;
397 int i, t, x, y, z, height, end, offset, bx, by, y_count;
400 if (!Training_obj_num_lines){
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) ) {
411 // don't ever display directives display in multiplayer missions
412 // if ( Game_mode & GM_MULTIPLAYER ){
416 height = gr_get_font_height();
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;
425 // draw top of objective display
426 // hud_set_default_color();
427 hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
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));
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));
437 bx = DIRECTIVE_X+fl2i(HUD_offset_x);
438 by = Directive_coords[gr_screen.res][DIRECTIVE_COORDS_MIDDLE][1]+fl2i(HUD_offset_y);
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);
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;
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);
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)){
465 switch (mission_get_event_status(z)) {
467 // gr_set_color_fast(&Color_bright_white);
468 c = &Color_bright_white;
472 // gr_set_color_fast(&Color_bright_red);
473 c = &Color_bright_red;
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;
483 c = &Color_bright_white;
486 c = &Color_bright_blue;
492 // maybe split the directives line
493 second_line = split_str_once(buf, 167);
495 // blit the background frames
496 // hud_set_default_color();
497 hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
499 GR_AABITMAP(Directive_gauge[1].first_frame, bx, by);
500 // gr_set_bitmap(Directive_gauge[1].first_frame);
501 // gr_aabitmap(bx, by);
506 GR_AABITMAP(Directive_gauge[1].first_frame, bx, by);
507 // gr_set_bitmap(Directive_gauge[1].first_frame);
508 // gr_aabitmap(bx, by);
514 gr_set_color_fast(c);
516 emp_hud_string(x, y, EG_OBJ1 + i, buf);
517 // gr_printf(x, y, buf);
522 y = Training_obj_window_coords[gr_screen.res][1] + fl2i(HUD_offset_y) + y_count * height + height / 2 + 1;
524 emp_hud_string(x+12, y, EG_OBJ1 + i + 1, second_line);
525 // gr_printf(x+12, y, second_line);
531 // draw the bottom of objective display
532 // hud_set_default_color();
533 hud_set_gauge_color(HUD_DIRECTIVES_VIEW);
535 GR_AABITMAP(Directive_gauge[2].first_frame, bx, by);
536 // gr_set_bitmap(Directive_gauge[2].first_frame);
537 // gr_aabitmap(bx, by);
540 // mission initializations (called once before a new mission is started)
541 void training_mission_init()
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;
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]);
560 Directive_frames_loaded = 1;
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 );
569 void training_mission_page_in()
572 for ( i = 0; i < NUM_DIRECTIVE_GAUGES; i++ ) {
573 bm_page_in_aabitmap( Directive_gauge[i].first_frame, Directive_gauge[i].num_frames );
578 int comp_training_lines_by_born_on_date(const void *m1, const void *m2)
584 SDL_assert(Mission_events[*e1 & 0xffff].born_on_date != 0);
585 SDL_assert(Mission_events[*e2 & 0xffff].born_on_date != 0);
587 return (Mission_events[*e1 & 0xffff].born_on_date - Mission_events[*e2 & 0xffff].born_on_date);
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()
597 int i, event_status, offset;
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);
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;
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));
615 if (event_status == EVENT_CURRENT) {
616 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
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;
623 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
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;
630 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
635 // if there are no directives which should be moved, we're done
636 if (num_offset_events == 0) {
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));
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;
650 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
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;
656 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
662 int slot_idx, unkn_vis;
663 // go through list and bump as needed
664 for (i=0; i<num_offset_events; i++) {
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) {
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 ) {
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];
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;
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;
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()
700 int i, event_idx, event_status;
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)) {
708 // check for the actual objective
709 for (i=0; i<Training_obj_num_lines; i++) {
710 if (Training_obj_lines[i] == event_idx) {
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) {
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];
723 Training_obj_num_lines--;
725 // add the new directive
726 Training_obj_lines[Training_obj_num_lines++] = event_idx;
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)) {
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) {
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) {
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];
748 Training_obj_num_lines--;
750 // mark objective as having key text
751 Training_obj_lines[Training_obj_num_lines++] = event_idx | TRAINING_OBJ_LINES_KEY;
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];
760 Training_obj_num_lines--;
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();
773 // called to do cleanup when leaving a mission
774 void training_mission_shutdown()
776 if (Training_voice >= 0) {
777 if (Training_voice_type) {
778 audiostream_close_file(Training_voice_handle, 0);
781 snd_stop(Training_voice_handle);
786 Training_num_lines = Training_obj_num_lines = 0;
790 // translates special tokens. Handles one token only.
791 char *translate_msg_token(char *str, const int max_len)
793 if (!SDL_strcasecmp(str, NOX("wp"))) {
794 SDL_snprintf(str, max_len, "%d", Training_context_goal_waypoint + 1);
801 // translates all special tokens in a message, producing the new finalized message to be displayed
802 void message_translate_tokens(char *buf, const int max_buflen, char *text)
804 char temp[40], *toke1, *toke2, *ptr;
809 toke1 = SDL_strchr(text, '$');
810 toke2 = SDL_strchr(text, '#');
811 while (toke1 || toke2) { // is either token types present?
812 if (!toke2 || (toke1 && (toke1 < toke2))) { // found $ before #
813 len = SDL_min(toke1 - text + 1, max_buflen);
814 SDL_strlcpy(buf, text, len); // copy text up to token
815 buf += toke1 - text + 1;
816 text = toke1 + 1; // advance pointers past processed data
818 toke2 = SDL_strchr(text, '$');
819 if (!toke2) // No second one?
822 len = SDL_min(toke2 - text + 1, max_buflen);
823 SDL_strlcpy(temp, text, len); // isolate token into seperate buffer
824 ptr = (char *)translate_key(temp); // try and translate key
825 if (ptr) { // was key translated properly?
826 if (!SDL_strcasecmp(ptr, NOX("none")) && (Training_bind_warning != Missiontime)) {
827 if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
828 r = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, XSTR( "&Bind Control", 424), XSTR( "&Abort mission", 425),
829 XSTR( "Warning\nYou have no control bound to the action \"%s\". You must do so before you can continue with your training.", 426),
830 XSTR(Control_config[Failed_key_index].text, CONTROL_CONFIG_XSTR + Failed_key_index));
832 if (r) { // do they want to abort the mission?
833 gameseq_post_event(GS_EVENT_END_GAME);
837 gameseq_post_event(GS_EVENT_CONTROL_CONFIG); // goto control config screen to bind the control
841 buf--; // erase the $
842 SDL_strlcpy(buf, ptr, max_buflen); // put translated key in place of token
848 len = SDL_min(toke2 - text + 1, max_buflen);
849 SDL_strlcpy(buf, text, len); // copy text up to token
850 buf += toke2 - text + 1;
851 text = toke2 + 1; // advance pointers past processed data
853 toke1 = SDL_strchr(text, '#');
854 if (!toke1) // No second one?
857 len = SDL_min(toke1 - text + 1, max_buflen);
858 SDL_strlcpy(temp, text, len); // isolate token into seperate buffer
859 ptr = translate_msg_token(temp, SDL_arraysize(temp)); // try and translate key
860 if (ptr) { // was key translated properly?
861 buf--; // erase the #
862 SDL_strlcpy(buf, ptr, max_buflen); // put translated key in place of token
868 toke1 = SDL_strchr(text, '$');
869 toke2 = SDL_strchr(text, '#');
872 SDL_strlcpy(buf, text, max_buflen);
876 // plays the voice file associated with a training message. Automatically streams the file
877 // from disk if it's over 100k, otherwise plays it as a normal file in memory. Returns -1
878 // if it didn't play, otherwise index of voice
879 int message_play_training_voice(int index)
885 if (Training_voice >= 0) {
886 if (Training_voice_type) {
887 audiostream_close_file(Training_voice_handle, 0);
890 snd_stop(Training_voice_handle);
898 if (Message_waves[index].num < 0) {
899 fp = cfopen(Message_waves[index].name, "rb");
903 len = cfilelength(fp);
906 if ((Training_voice < 0) || !Training_voice_type || (Training_voice != index)) {
907 if (Training_voice >= 0) {
908 if (Training_voice_type) {
909 if (Training_voice == index)
910 audiostream_stop(Training_voice_handle);
912 audiostream_close_file(Training_voice_handle, 0);
915 snd_stop(Training_voice_handle);
919 if (SDL_strcasecmp(Message_waves[index].name, NOX("none.wav"))) {
920 Training_voice_handle = audiostream_open(Message_waves[index].name, ASF_VOICE);
921 if (Training_voice_handle < 0) {
922 nprintf(("Warning", "Unable to load voice file %s\n", Message_waves[index].name));
923 // Warning(LOCATION, "Unable to load voice file %s\n", Message_waves[index].name);
926 } // Training_voice should be valid and loaded now
928 Training_voice_type = 1;
929 if (Training_voice_handle >= 0)
930 audiostream_play(Training_voice_handle, Master_voice_volume, 0);
932 Training_voice = index;
933 return Training_voice;
937 memset(&tmp_gs, 0, sizeof(game_snd));
938 SDL_strlcpy(tmp_gs.filename, Message_waves[index].name, SDL_arraysize(tmp_gs.filename));
939 Message_waves[index].num = snd_load(&tmp_gs);
940 if (Message_waves[index].num < 0) {
941 nprintf(("Warning", "Cannot load message wave: %s. Will not play\n", Message_waves[index].name));
947 if (Training_voice >= 0) {
948 if (Training_voice_type) {
949 audiostream_close_file(Training_voice_handle, 0);
952 snd_stop(Training_voice_handle);
956 Training_voice = index;
957 if (Message_waves[index].num >= 0)
958 Training_voice_handle = snd_play_raw(Message_waves[index].num, 0.0f);
960 Training_voice_handle = -1;
962 Training_voice_type = 0;
963 return Training_voice;
966 // one time initializations done when we want to display a new training mission. This does
967 // all the processing and setup required to actually display it, including starting the
968 // voice file playing
969 void message_training_setup(int m, int length)
971 if ((m < 0) || !Messages[m].message[0]) { // remove current message from the screen
972 Training_num_lines = 0;
976 message_translate_tokens(Training_buf, SDL_arraysize(Training_buf), Messages[m].message);
977 HUD_add_to_scrollback(Training_buf, HUD_SOURCE_TRAINING);
978 SDL_strlcpy(Training_text, Messages[m].message, SDL_arraysize(Training_text));
980 if (message_play_training_voice(Messages[m].wave_info.index) < 0) {
982 Training_msg_timestamp = timestamp(length * 1000);
984 Training_msg_timestamp = timestamp(TRAINING_TIMING_BASE + strlen(Messages[m].message) * TRAINING_TIMING); // no voice file playing
987 Training_msg_timestamp = 0;
990 // adds simple text to the directives display
991 /*id message_training_add_simple( char *text )
995 training_process_msg(text);
996 HUD_add_to_scrollback(Training_buf, HUD_SOURCE_TRAINING);
997 Training_num_lines = split_str(Training_buf, TRAINING_LINE_WIDTH, Training_line_sizes, Training_lines, MAX_TRAINING_MSG_LINES);
998 SDL_assert(Training_num_lines > 0);
999 for (i=0; i<Training_num_lines; i++)
1000 Training_lines[i][Training_line_sizes[i]] = 0;
1002 Training_msg_timestamp = timestamp(5000);
1005 // add a message to the que to be sent later.
1006 void message_training_que(char *text, int timestamp, int length)
1010 SDL_assert(Training_msg_que_count < TRAINING_MSG_QUE_MAX);
1011 if (Training_msg_que_count < TRAINING_MSG_QUE_MAX) {
1012 if (!SDL_strcasecmp(text, NOX("none")))
1016 for (m=0; m<Num_messages; m++)
1017 if (!SDL_strcasecmp(text, Messages[m].name))
1020 SDL_assert(m < Num_messages);
1021 if (m >= Num_messages)
1025 Training_msg_que[Training_msg_que_count].num = m;
1026 Training_msg_que[Training_msg_que_count].timestamp = timestamp;
1027 Training_msg_que[Training_msg_que_count].length = length;
1028 Training_msg_que_count++;
1032 // check the training message que to see if we should play a new message yet or not.
1033 void message_training_que_check()
1035 int i, j, iship_num;
1037 // get the instructor's ship.
1038 iship_num = ship_name_lookup(NOX("instructor"));
1039 if ( iship_num == -1 )
1042 // if the instructor is dying or departing, do nothing
1043 if (Ships[iship_num].flags & (SF_DYING | SF_DEPARTING))
1046 if (Training_failure)
1049 for (i=0; i<Training_msg_que_count; i++) {
1050 if (timestamp_elapsed(Training_msg_que[i].timestamp)) {
1051 message_training_setup(Training_msg_que[i].num, Training_msg_que[i].length);
1052 // remove this message from the que now.
1053 for (j=i+1; j<Training_msg_que_count; j++)
1054 Training_msg_que[j - 1] = Training_msg_que[j];
1057 Training_msg_que_count--;
1062 // displays (renders) the training message to the screen
1063 void message_training_display()
1065 char *str, buf[256];
1066 int i, z, x, y, height, mode, count;
1068 Training_msg_visible = 0;
1069 message_training_que_check();
1070 training_obj_display();
1072 if (Training_failure){
1076 if (timestamp_elapsed(Training_msg_timestamp) || !strlen(Training_text)){
1080 message_translate_tokens(Training_buf, SDL_arraysize(Training_buf), Training_text);
1081 training_process_msg(Training_text);
1082 Training_num_lines = split_str(Training_buf, TRAINING_LINE_WIDTH, Training_line_sizes, Training_lines, MAX_TRAINING_MSG_LINES);
1083 SDL_assert(Training_num_lines > 0);
1084 for (i=0; i<Training_num_lines; i++) {
1085 Training_lines[i][Training_line_sizes[i]] = 0;
1086 drop_leading_white_space(Training_lines[i]);
1089 if (Training_num_lines <= 0){
1093 height = gr_get_font_height();
1094 gr_set_shader(&Training_msg_glass);
1095 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);
1097 gr_set_color_fast(&Color_bright_blue);
1099 Training_msg_visible = 1;
1100 for (i=0; i<Training_num_lines; i++) { // loop through all lines of message
1101 str = Training_lines[i];
1103 x = Training_msg_window_coords[gr_screen.res][0] + (TRAINING_MSG_WINDOW_WIDTH - TRAINING_LINE_WIDTH) / 2;
1104 y = Training_msg_window_coords[gr_screen.res][1] + i * height + height / 2 + 1;
1106 while (*str) { // loop through each character of each line
1107 if ((count < MAX_TRAINING_MSG_MODS) && (str == Training_msg_mods[count].pos)) {
1109 gr_printf(x, y, buf);
1110 gr_get_string_size(&z, NULL, buf);
1114 mode = Training_msg_mods[count++].mode;
1117 gr_set_color_fast(&Color_bright_blue);
1121 gr_set_color_fast(&Color_white);
1131 gr_printf(x, y, "%s", buf);
1135 Training_msg_method = 0;
1136 // if (Training_msg_method) {
1137 // char *msg = "Press a key to continue";
1139 // gr_get_string_size(&i, NULL, msg);
1140 // gr_printf(TRAINING_MSG_WINDOW_X + TRAINING_MSG_WINDOW_WIDTH / 2 - i / 2, TRAINING_MSG_WINDOW_Y + (Training_num_lines + 2) * height, msg);
1143 if ((Training_voice >= 0) && (Training_num_lines > 0) && !(Training_msg_timestamp)) {
1144 if (Training_voice_type)
1145 z = audiostream_is_playing(Training_voice_handle);
1147 z = snd_is_playing(Training_voice_handle);
1150 Training_msg_timestamp = timestamp(2000); // 2 second delay
1154 // processes a new training message to get hilighting information and store it in internal structures.
1155 void training_process_msg(char *msg)
1158 char *src, *dest, buf[8192];
1160 message_translate_tokens(buf, SDL_arraysize(buf), msg);
1163 dest = Training_buf;
1165 if (!SDL_strncasecmp(src, NOX("<b>"), 3)) {
1166 SDL_assert(count < MAX_TRAINING_MSG_MODS);
1168 Training_msg_mods[count].pos = dest;
1169 Training_msg_mods[count].mode = TMMOD_BOLD;
1173 if (!SDL_strncasecmp(src, NOX("</b>"), 4)) {
1174 SDL_assert(count < MAX_TRAINING_MSG_MODS);
1176 Training_msg_mods[count].pos = dest;
1177 Training_msg_mods[count].mode = TMMOD_NORMAL;
1185 if (count < MAX_TRAINING_MSG_MODS)
1186 Training_msg_mods[count].pos = NULL;
1189 void training_fail()
1191 Training_failure = 1;
1192 // JasonH: Add code here to suspend training and display a directive to warp out.
1193 // Suspend training.
1194 // Give directive to warp out.
1195 // Also ensure that a special failure debriefing is given. Must mention firing at instructor.
1196 // Ask Sandeep to write it (or you can).