1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12
13 #include "mission/missiontraining.h"
14 #include "parse/parselo.h"
15 #include "sound/sound.h"
16 #include "sound/audiostr.h"
17 #include "mission/missionmessage.h"
18 #include "mission/missiongoals.h"
19 #include "mission/missionparse.h"
20 #include "io/timer.h"
21 #include "hud/hudmessage.h"
22 #include "hud/hud.h"
23 #include "cfile/cfile.h"
24 #include "playerman/player.h"
25 #include "popup/popup.h"
26 #include "gamesequence/gamesequence.h"
27 #include "weapon/emp.h"
28 #include "globalincs/alphacolors.h"
29 #include "ship/ship.h"
30 #include "parse/sexp.h"
31 #include "network/multi.h"
32 #include "mod_table/mod_table.h"
33
34
35
36 #define MAX_TRAINING_MESSAGE_LINES 10
37 #define TRAINING_MESSAGE_WINDOW_WIDTH 266
38 #define TRAINING_LINE_WIDTH 250 // width in pixels of actual text
39 #define TRAINING_TIMING 150 // milliseconds per character to display messages
40 #define TRAINING_TIMING_BASE 1000 // Minimum milliseconds to display any message
41 #define TRAINING_OBJ_WND_WIDTH 170 // number of pixels wide window is.
42 #define TRAINING_OBJ_LINE_WIDTH 150 // number of pixels wide text can be
43 #define TRAINING_OBJ_LINES 50 // number of lines to track in objective list
44 #define TRAINING_OBJ_DISPLAY_LINES 5 // only display this many lines on screen max
45 #define MAX_TRAINING_MESSAGE_MODS 20
46 #define TRAINING_MESSAGE_QUEUE_MAX 40
47
48 #define TRAINING_OBJ_STATUS_UNKNOWN (1 << 28) // directive status is unknown
49 #define TRAINING_OBJ_STATUS_KNOWN (1 << 29) // directive status is known (satisfied or failed)
50 #define TRAINING_OBJ_LINES_KEY (1 << 30) // flag indicating line describes the key associated with objective
51 #define TRAINING_OBJ_LINES_EVENT_STATUS_MASK (TRAINING_OBJ_STATUS_KNOWN | TRAINING_OBJ_STATUS_UNKNOWN)
52
53 #define TRAINING_OBJ_LINES_MASK(n) (Training_obj_lines[n] & 0xffff)
54
55 #define TMMOD_NORMAL 0
56 #define TMMOD_BOLD 1
57
58 typedef struct {
59 char *pos;
60 int mode; // what function to perform at given position (TMMOD_*)
61 } training_message_mods; // training message modifiers
62
63 typedef struct {
64 int num;
65 int timestamp;
66 int length;
67 char *special_message;
68 } training_message_queue;
69
70 char Training_buf[TRAINING_MESSAGE_LENGTH];
71 const char *Training_lines[MAX_TRAINING_MESSAGE_LINES]; // Training message split into lines
72 int Training_line_lengths[MAX_TRAINING_MESSAGE_LINES];
73
74 char Training_voice_filename[NAME_LENGTH];
75 int Max_directives = TRAINING_OBJ_DISPLAY_LINES;
76 int Training_message_timestamp;
77 int Training_message_method = 1;
78 int Training_num_lines = 0;
79 int Training_voice = -1;
80 int Training_voice_type;
81 int Training_voice_handle;
82 int Training_flag = 0;
83 int Training_failure = 0;
84 int Training_message_queue_count = 0;
85 int Training_bind_warning = -1; // Missiontime at which we last gave warning
86 int Training_message_visible;
87 training_message_queue Training_message_queue[TRAINING_MESSAGE_QUEUE_MAX];
88
89 // coordinates for training messages
90 int Training_message_window_coords[GR_NUM_RESOLUTIONS][2] = {
91 { 174, 40 },
92 { 379, 125 }
93 };
94
95 // coordinates for "directives" window on hud
96 int Training_obj_window_coords[GR_NUM_RESOLUTIONS][2] = {
97 { 0, 187 },
98 { 0, 287 }
99 };
100
101
102 // training objectives global vars.
103 int Training_obj_num_lines;
104 int Training_obj_lines[TRAINING_OBJ_LINES];
105 training_message_mods Training_message_mods[MAX_TRAINING_MESSAGE_MODS];
106
107 // local module prototypes
108 void training_process_message(char *message);
109 void message_translate_tokens(char *buf, char *text);
110
111
112 #define NUM_DIRECTIVE_GAUGES 3
113 static hud_frames Directive_gauge[NUM_DIRECTIVE_GAUGES];
114 static int Directive_frames_loaded = 0;
115
116 #define DIRECTIVE_H 9
117 #define DIRECTIVE_X 5
118 #define NUM_DIRECTIVE_COORDS 3
119 #define DIRECTIVE_COORDS_TOP 0
120 #define DIRECTIVE_COORDS_MIDDLE 1
121 #define DIRECTIVE_COORDS_TITLE 2
122
HudGaugeDirectives()123 HudGaugeDirectives::HudGaugeDirectives():
124 HudGauge(HUD_OBJECT_DIRECTIVES, HUD_DIRECTIVES_VIEW, false, true, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255)
125 {
126 }
127
initHeaderOffsets(int x,int y)128 void HudGaugeDirectives::initHeaderOffsets(int x, int y)
129 {
130 header_offsets[0] = x;
131 header_offsets[1] = y;
132 }
133
initMiddleFrameOffsetY(int y)134 void HudGaugeDirectives::initMiddleFrameOffsetY(int y)
135 {
136 middle_frame_offset_y = y;
137 }
138
initTextStartOffsets(int x,int y)139 void HudGaugeDirectives::initTextStartOffsets(int x, int y)
140 {
141 text_start_offsets[0] = x;
142 text_start_offsets[1] = y;
143 }
144
initTextHeight(int h)145 void HudGaugeDirectives::initTextHeight(int h)
146 {
147 text_h = h;
148 }
149
initMaxLineWidth(int w)150 void HudGaugeDirectives::initMaxLineWidth(int w)
151 {
152 max_line_width = w;
153 }
154
initBottomBgOffset(int offset)155 void HudGaugeDirectives::initBottomBgOffset(int offset)
156 {
157 bottom_bg_offset = offset;
158 }
159
initBitmaps(char * fname_top,char * fname_middle,char * fname_bottom)160 void HudGaugeDirectives::initBitmaps(char *fname_top, char *fname_middle, char *fname_bottom)
161 {
162 directives_top.first_frame = bm_load_animation(fname_top, &directives_top.num_frames);
163 if ( directives_top.first_frame < 0 ) {
164 Warning(LOCATION,"Cannot load hud ani: %s\n", fname_top);
165 }
166
167 directives_middle.first_frame = bm_load_animation(fname_middle, &directives_middle.num_frames);
168 if ( directives_middle.first_frame < 0 ) {
169 Warning(LOCATION,"Cannot load hud ani: %s\n", fname_middle);
170 }
171
172 directives_bottom.first_frame = bm_load_animation(fname_bottom, &directives_bottom.num_frames);
173 if ( directives_bottom.first_frame < 0 ) {
174 Warning(LOCATION,"Cannot load hud ani: %s\n", fname_bottom);
175 }
176 }
177
canRender()178 bool HudGaugeDirectives::canRender()
179 {
180 if (sexp_override) {
181 return false;
182 }
183
184 if(hud_disabled_except_messages()) {
185 return false;
186 }
187
188 if (hud_disabled() && !hud_disabled_except_messages()) {
189 return false;
190 }
191
192 // force the directives list to display in training missions even if this gauge isn't active.
193 if(!active && !(The_mission.game_type & MISSION_TYPE_TRAINING))
194 return false;
195
196 if ( !(Game_detail_flags & DETAIL_FLAG_HUD) ) {
197 return false;
198 }
199
200 if ((Viewer_mode & disabled_views)) {
201 return false;
202 }
203
204 if(pop_up) {
205 if(!popUpActive()) {
206 return false;
207 }
208 }
209
210 if (gauge_config == HUD_ETS_GAUGE) {
211 if (Ships[Player_obj->instance].flags2 & SF2_NO_ETS) {
212 return false;
213 }
214 }
215
216 return true;
217 }
218
pageIn()219 void HudGaugeDirectives::pageIn()
220 {
221 bm_page_in_aabitmap(directives_top.first_frame, directives_top.num_frames);
222 bm_page_in_aabitmap(directives_middle.first_frame, directives_middle.num_frames);
223 bm_page_in_aabitmap(directives_bottom.first_frame, directives_bottom.num_frames);
224 }
225
render(float frametime)226 void HudGaugeDirectives::render(float frametime)
227 {
228 char buf[256], *second_line;
229 int i, t, x, y, z, end, offset, bx, by, y_count;
230 color *c;
231
232 if (!Training_obj_num_lines){
233 return;
234 }
235
236 offset = 0;
237 end = Training_obj_num_lines;
238 if (end > Max_directives) {
239 end = Max_directives;
240 offset = Training_obj_num_lines - end;
241 }
242
243 // draw top of objective display
244 setGaugeColor();
245
246 renderBitmap(directives_top.first_frame, position[0], position[1]);
247
248 // print out title
249 renderPrintf(position[0] + header_offsets[0], position[1] + header_offsets[1], EG_OBJ_TITLE, XSTR( "directives", 422));
250
251 bx = position[0];
252 by = position[1] + middle_frame_offset_y;
253
254 y_count = 0;
255 for (i=0; i<end; i++) {
256 x = position[0] + text_start_offsets[0];
257 y = position[1] + text_start_offsets[1] + y_count * text_h;
258 z = TRAINING_OBJ_LINES_MASK(i + offset);
259
260 c = &Color_normal;
261 if (Training_obj_lines[i + offset] & TRAINING_OBJ_LINES_KEY) {
262 message_translate_tokens(buf, Mission_events[z].objective_key_text); // remap keys
263 c = &Color_bright_green;
264 } else {
265 strcpy_s(buf, Mission_events[z].objective_text);
266 if (Mission_events[z].count){
267 sprintf(buf + strlen(buf), NOX(" [%d]"), Mission_events[z].count);
268 }
269
270 // if this is a multiplayer tvt game, and this is event is not for my team, don't display it
271 if((MULTI_TEAM) && (Net_player != NULL)){
272 if((Mission_events[z].team != -1) && (Net_player->p_info.team != Mission_events[z].team)){
273 continue;
274 }
275 }
276
277 switch (mission_get_event_status(z)) {
278 case EVENT_CURRENT:
279 c = &Color_bright_white;
280 break;
281
282 case EVENT_FAILED:
283 c = &Color_bright_red;
284 break;
285
286 case EVENT_SATISFIED:
287 t = Mission_events[z].satisfied_time;
288 if (t + i2f(2) > Missiontime) {
289 if (Missiontime % fl2f(.4f) < fl2f(.2f)){
290 c = &Color_bright_blue;
291 } else {
292 c = &Color_bright_white;
293 }
294 } else {
295 c = &Color_bright_blue;
296 }
297 break;
298 }
299 }
300
301 // maybe split the directives line
302 second_line = split_str_once(buf, max_line_width);
303 Assert( second_line != buf );
304
305 // blit the background frames
306 setGaugeColor();
307
308 renderBitmap(directives_middle.first_frame, bx, by);
309
310 by += text_h;
311
312 if ( second_line ) {
313 renderBitmap(directives_middle.first_frame, bx, by);
314
315 by += text_h;
316 }
317
318 // blit the text
319 gr_set_color_fast(c);
320
321 renderString(x, y, EG_OBJ1 + i, buf);
322
323 y_count++;
324
325 if ( second_line ) {
326 y = position[1] + text_start_offsets[1] + y_count * text_h;
327
328 renderString(x+12, y, EG_OBJ1 + i + 1, second_line);
329
330 y_count++;
331 }
332 }
333
334 // draw the bottom of objective display
335 setGaugeColor();
336
337 renderBitmap(directives_bottom.first_frame, bx, by + bottom_bg_offset);
338 }
339
340 /**
341 * Mission initializations (called once before a new mission is started)
342 */
training_mission_init()343 void training_mission_init()
344 {
345 int i;
346
347 Assert(!Training_num_lines);
348 Training_obj_num_lines = 0;
349 Training_message_queue_count = 0;
350 Training_failure = 0;
351 if (Max_directives > TRAINING_OBJ_LINES) {
352 Max_directives = TRAINING_OBJ_LINES;
353 }
354 for (i=0; i<TRAINING_OBJ_LINES; i++)
355 Training_obj_lines[i] = -1;
356
357 // Goober5000
358 for (i = 0; i < TRAINING_MESSAGE_QUEUE_MAX; i++)
359 Training_message_queue[i].special_message = NULL;
360
361 // The E: This is now handled by the new HUD code. No need to check here.
362 Directive_frames_loaded = 1;
363
364 // only clear player flags if this is actually a training mission
365 if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
366 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 );
367 }
368 }
369
training_mission_page_in()370 void training_mission_page_in()
371 {
372 int i;
373 for ( i = 0; i < NUM_DIRECTIVE_GAUGES; i++ ) {
374 bm_page_in_aabitmap( Directive_gauge[i].first_frame, Directive_gauge[i].num_frames );
375 }
376 }
377
378
comp_training_lines_by_born_on_date(const void * m1,const void * m2)379 int comp_training_lines_by_born_on_date(const void *m1, const void *m2)
380 {
381 int *e1, *e2;
382 e1 = (int*) m1;
383 e2 = (int*) m2;
384
385 Assert(Mission_events[*e1 & 0xffff].born_on_date != 0);
386 Assert(Mission_events[*e2 & 0xffff].born_on_date != 0);
387
388 return (Mission_events[*e1 & 0xffff].born_on_date - Mission_events[*e2 & 0xffff].born_on_date);
389 }
390
391
392 /**
393 * Sort list of training events
394 *
395 * Sort on EVENT_CURRENT and born on date, for other events (EVENT_SATISFIED, EVENT_FAILED) sort on born on date
396 */
397 #define MIN_SATISFIED_TIME 5
398 #define MIN_FAILED_TIME 7
sort_training_objectives()399 void sort_training_objectives()
400 {
401 int i, event_status, offset;
402
403 // start by sorting on born on date
404 insertion_sort(Training_obj_lines, Training_obj_num_lines, sizeof(int), comp_training_lines_by_born_on_date);
405
406 // get the index of the first directive that will be displayed
407 // if less than 0, display all lines
408 offset = Training_obj_num_lines - Max_directives;
409
410 if (offset <= 0) {
411 return;
412 }
413
414 // go through lines 0 to offset-1 and check if there are any CURRENT or RECENTLY_KNOWN events that should be shown
415 int num_offset_events = 0;
416 for (i=0; i<offset; i++) {
417 event_status = mission_get_event_status(TRAINING_OBJ_LINES_MASK(i));
418
419 // if this is a multiplayer tvt game, and this is event is for another team, don't touch it
420 if(MULTI_TEAM && (Net_player != NULL)){
421 if((Mission_events[TRAINING_OBJ_LINES_MASK(i)].team != -1) && (Net_player->p_info.team != Mission_events[TRAINING_OBJ_LINES_MASK(i)].team)){
422 continue ;
423 }
424 }
425
426 if (event_status == EVENT_CURRENT) {
427 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
428 num_offset_events++;
429 } else if (event_status == EVENT_SATISFIED) {
430 if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_SATISFIED_TIME) {
431 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
432 num_offset_events++;
433 } else {
434 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
435 }
436 } else if (event_status == EVENT_FAILED) {
437 if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_FAILED_TIME) {
438 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
439 num_offset_events++;
440 } else {
441 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
442 }
443 }
444 }
445
446 // if there are no directives which should be moved, we're done
447 if (num_offset_events == 0) {
448 return;
449 }
450
451 // go through lines offset to Training_obj_num_lines to check which should be shown, since some will need to be bumped
452 for (i=offset; i<Training_obj_num_lines; i++) {
453 event_status = mission_get_event_status(TRAINING_OBJ_LINES_MASK(i));
454
455 // if this is a multiplayer tvt game, and this is event is for another team, it can be bumped
456 if(MULTI_TEAM && (Net_player != NULL)){
457 if((Mission_events[TRAINING_OBJ_LINES_MASK(i)].team != -1) && (Net_player->p_info.team != Mission_events[TRAINING_OBJ_LINES_MASK(i)].team)){
458 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
459 continue ;
460 }
461 }
462
463 if (event_status == EVENT_CURRENT) {
464 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
465 } else if (event_status == EVENT_SATISFIED) {
466 if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_SATISFIED_TIME) {
467 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
468 } else {
469 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
470 }
471 } else if (event_status == EVENT_FAILED) {
472 if (f2i(Missiontime - Mission_events[TRAINING_OBJ_LINES_MASK(i)].satisfied_time) < MIN_FAILED_TIME) {
473 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_UNKNOWN;
474 } else {
475 Training_obj_lines[i] |= TRAINING_OBJ_STATUS_KNOWN;
476 }
477 }
478 }
479
480
481 int slot_idx, unkn_vis, last_directive;
482 // go through list and bump as needed
483 for (i=0; i<num_offset_events; i++) {
484
485 // find most recent directive that would not be shown
486 for (unkn_vis=offset-1; unkn_vis>=0; unkn_vis--) {
487 if (Training_obj_lines[unkn_vis] & TRAINING_OBJ_STATUS_UNKNOWN) {
488 break;
489 }
490 }
491
492 // find first slot that can be bumped
493 // look at the last (N-4 to N) positions
494 for (slot_idx=0; slot_idx<Max_directives; slot_idx++) {
495 if ( Training_obj_lines[i+offset] & TRAINING_OBJ_STATUS_KNOWN ) {
496 break;
497 }
498 }
499
500 // shift and replace (mark old one as STATUS_KNOWN)
501 // store the directive that won't be shown
502 last_directive = Training_obj_lines[Training_obj_num_lines-1];
503
504 for (int j=slot_idx; j>0; j--) {
505 Training_obj_lines[j+offset-1] = Training_obj_lines[j+offset-2];
506 }
507 Training_obj_lines[offset] = Training_obj_lines[unkn_vis];
508 Training_obj_lines[unkn_vis] = last_directive;
509 Training_obj_lines[unkn_vis] &= ~TRAINING_OBJ_LINES_EVENT_STATUS_MASK;
510 Training_obj_lines[unkn_vis] |= TRAINING_OBJ_STATUS_KNOWN;
511 }
512
513 // remove event status
514 for (i=0; i<Training_obj_num_lines; i++) {
515 Training_obj_lines[i] &= ~TRAINING_OBJ_LINES_EVENT_STATUS_MASK;
516 }
517 }
518
519 /**
520 * Maintains the objectives listing, adding, removing and updating items
521 *
522 * Called at same rate as goals/events are evaluated.
523 */
training_check_objectives()524 void training_check_objectives()
525 {
526 int i, event_idx, event_status;
527
528 Training_obj_num_lines = 0;
529 for (event_idx=0; event_idx<Num_mission_events; event_idx++) {
530 event_status = mission_get_event_status(event_idx);
531 if ( (event_status != EVENT_UNBORN) && Mission_events[event_idx].objective_text && (timestamp() > Mission_events[event_idx].born_on_date + Directive_wait_time) ) {
532 if (!Training_failure || !strnicmp(Mission_events[event_idx].name, XSTR( "Training failed", 423), 15)) {
533
534 // check for the actual objective
535 for (i=0; i<Training_obj_num_lines; i++) {
536 if (Training_obj_lines[i] == event_idx) {
537 break;
538 }
539 }
540
541 // not in objective list, need to add it
542 if (i == Training_obj_num_lines) {
543 if (Training_obj_num_lines == TRAINING_OBJ_LINES) {
544
545 // objective list full, remove topmost objective
546 for (i=1; i<TRAINING_OBJ_LINES; i++) {
547 Training_obj_lines[i - 1] = Training_obj_lines[i];
548 }
549 Training_obj_num_lines--;
550 }
551 // add the new directive
552 Training_obj_lines[Training_obj_num_lines++] = event_idx;
553 }
554
555 // now check for the associated keypress text
556 for (i=0; i<Training_obj_num_lines; i++) {
557 if (Training_obj_lines[i] == (event_idx | TRAINING_OBJ_LINES_KEY)) {
558 break;
559 }
560 }
561
562 // if there is a keypress message with directive, process that too.
563 if (Mission_events[event_idx].objective_key_text) {
564 if (event_status == EVENT_CURRENT) {
565
566 // not in objective list, need to add it
567 if (i == Training_obj_num_lines) {
568 if (Training_obj_num_lines == TRAINING_OBJ_LINES) {
569
570 // objective list full, remove topmost objective
571 for (i=1; i<Training_obj_num_lines; i++) {
572 Training_obj_lines[i - 1] = Training_obj_lines[i];
573 }
574 Training_obj_num_lines--;
575 }
576 // mark objective as having key text
577 Training_obj_lines[Training_obj_num_lines++] = event_idx | TRAINING_OBJ_LINES_KEY;
578 }
579
580 } else {
581 // message with key press text is no longer valid, so remove it
582 if (i < Training_obj_num_lines) {
583 for (; i<Training_obj_num_lines - 1; i++) {
584 Training_obj_lines[i] = Training_obj_lines[i + 1];
585 }
586 Training_obj_num_lines--;
587 }
588 }
589 }
590 }
591 }
592 }
593
594 // now sort list of events
595 // sort on EVENT_CURRENT and born on date, for other events (EVENT_SATISFIED, EVENT_FAILED) sort on born on date
596 sort_training_objectives();
597 }
598
599 /**
600 * Do cleanup when leaving a mission
601 */
training_mission_shutdown()602 void training_mission_shutdown()
603 {
604 int i;
605
606 if (Training_voice >= 0) {
607 if (Training_voice_type) {
608 audiostream_close_file(Training_voice_handle, 0);
609
610 } else {
611 snd_stop(Training_voice_handle);
612 }
613 }
614
615 // Goober5000
616 for (i = 0; i < TRAINING_MESSAGE_QUEUE_MAX; i++)
617 {
618 if (Training_message_queue[i].special_message != NULL)
619 {
620 vm_free(Training_message_queue[i].special_message);
621 Training_message_queue[i].special_message = NULL;
622 }
623 }
624
625 Training_voice = -1;
626 Training_num_lines = Training_obj_num_lines = 0;
627
628 *Training_buf = 0;
629 }
630
631 /**
632 * Translates special tokens. Handles one token only.
633 */
translate_message_token(char * str)634 char *translate_message_token(char *str)
635 {
636 if (!stricmp(str, NOX("wp"))) {
637 sprintf(str, "%d", Training_context_goal_waypoint + 1);
638 return str;
639 }
640
641 return NULL;
642 }
643
644 /**
645 * Translates all special tokens in a message, producing the new finalized message to be displayed
646 */
message_translate_tokens(char * buf,char * text)647 void message_translate_tokens(char *buf, char *text)
648 {
649 char temp[40], *toke1, *toke2, *ptr;
650 int r;
651
652 *buf = 0;
653 toke1 = strchr(text, '$');
654 toke2 = strchr(text, '#');
655 while (toke1 || toke2) { // is either token types present?
656 if (!toke2 || (toke1 && (toke1 < toke2))) { // found $ before #
657 strncpy(buf, text, toke1 - text + 1); // copy text up to token
658 buf += toke1 - text + 1;
659 text = toke1 + 1; // advance pointers past processed data
660
661 toke2 = strchr(text, '$');
662 if (!toke2 || ((toke2 - text) == 0)) // No second one, or possibly a double?
663 break;
664
665 // make sure we aren't going to have any type of out-of-bounds issues
666 if ( ((toke2 - text) < 0) || ((toke2 - text) >= (ptr_s)sizeof(temp)) ) {
667 Int3();
668 } else {
669 strncpy(temp, text, toke2 - text); // isolate token into seperate buffer
670 temp[toke2 - text] = 0; // null terminate string
671 ptr = translate_key(temp); // try and translate key
672 if (ptr) { // was key translated properly?
673 if (!stricmp(ptr, NOX("none")) && (Training_bind_warning != Missiontime)) {
674 if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
675 r = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, XSTR( "&Bind Control", 424), XSTR( "&Abort mission", 425),
676 XSTR( "Warning\nYou have no control bound to the action \"%s\". You must do so before you can continue with your training.", 426),
677 XSTR(Control_config[Failed_key_index].text, CONTROL_CONFIG_XSTR + Failed_key_index));
678
679 if (r) { // do they want to abort the mission?
680 gameseq_post_event(GS_EVENT_END_GAME);
681 return;
682 }
683
684 gameseq_post_event(GS_EVENT_CONTROL_CONFIG); // goto control config screen to bind the control
685 }
686 }
687
688 buf--; // erase the $
689 strcpy(buf, ptr); // put translated key in place of token
690 buf += strlen(buf);
691 text = toke2 + 1;
692 }
693 }
694 } else {
695 strncpy(buf, text, toke2 - text + 1); // copy text up to token
696 buf += toke2 - text + 1;
697 text = toke2 + 1; // advance pointers past processed data
698
699 toke1 = strchr(text, '#');
700 if (!toke1 || ((toke1 - text) == 0)) // No second one, or possibly a double?
701 break;
702
703 // make sure we aren't going to have any type of out-of-bounds issues
704 if ( ((toke1 - text) < 0) || ((toke1 - text) >= (ptr_s)sizeof(temp)) ) {
705 Int3();
706 } else {
707 strncpy(temp, text, toke1 - text); // isolate token into seperate buffer
708 temp[toke1 - text] = 0; // null terminate string
709 ptr = translate_message_token(temp); // try and translate key
710 if (ptr) { // was key translated properly?
711 buf--; // erase the #
712 strcpy(buf, ptr); // put translated key in place of token
713 buf += strlen(buf);
714 text = toke1 + 1;
715 }
716 }
717 }
718
719 toke1 = strchr(text, '$');
720 toke2 = strchr(text, '#');
721 }
722
723 strcpy(buf, text);
724 return;
725 }
726
727 /**
728 * Plays the voice file associated with a training message.
729 *
730 * Automatically streams the file from disk if it's over 100k, otherwise plays it as
731 * a normal file in memory. Returns -1 if it didn't play, otherwise index of voice
732 */
message_play_training_voice(int index)733 int message_play_training_voice(int index)
734 {
735 int len;
736 CFILE *fp;
737
738 if (index < 0) {
739 if (Training_voice >= 0) {
740 if (Training_voice_type) {
741 audiostream_close_file(Training_voice_handle, 0);
742
743 } else {
744 snd_stop(Training_voice_handle);
745 }
746 }
747
748 Training_voice = -1;
749 return -1;
750 }
751
752 if (Message_waves[index].num < 0) {
753 fp = cfopen(Message_waves[index].name, "rb");
754 if (!fp)
755 return -1;
756
757 len = cfilelength(fp);
758 cfclose(fp);
759 if (len > 100000) {
760 if ((Training_voice < 0) || !Training_voice_type || (Training_voice != index)) {
761 if (Training_voice >= 0) {
762 if (Training_voice_type) {
763 if (Training_voice == index)
764 audiostream_stop(Training_voice_handle, 1, 0);
765 else
766 audiostream_close_file(Training_voice_handle, 0);
767
768 } else {
769 snd_stop(Training_voice_handle);
770 }
771 }
772
773 if (strnicmp(Message_waves[index].name, NOX("none.wav"), 4)) {
774 Training_voice_handle = audiostream_open(Message_waves[index].name, ASF_VOICE);
775 if (Training_voice_handle < 0) {
776 nprintf(("Warning", "Unable to load voice file %s\n", Message_waves[index].name));
777 }
778 }
779 } // Training_voice should be valid and loaded now
780
781 Training_voice_type = 1;
782 if (Training_voice_handle >= 0)
783 audiostream_play(Training_voice_handle, (Master_voice_volume * aav_voice_volume), 0);
784
785 Training_voice = index;
786 return Training_voice;
787
788 } else {
789 game_snd tmp_gs;
790 strcpy_s(tmp_gs.filename, Message_waves[index].name);
791 Message_waves[index].num = snd_load(&tmp_gs, 0);
792 if (Message_waves[index].num < 0) {
793 nprintf(("Warning", "Cannot load message wave: %s. Will not play\n", Message_waves[index].name));
794 return -1;
795 }
796 }
797 }
798
799 if (Training_voice >= 0) {
800 if (Training_voice_type) {
801 audiostream_close_file(Training_voice_handle, 0);
802
803 } else {
804 snd_stop(Training_voice_handle);
805 }
806 }
807
808 Training_voice = index;
809 if (Message_waves[index].num >= 0)
810 Training_voice_handle = snd_play_raw(Message_waves[index].num, 0.0f);
811 else
812 Training_voice_handle = -1;
813
814 Training_voice_type = 0;
815 return Training_voice;
816 }
817
818 /**
819 * One time initializations done when we want to display a new training mission.
820 *
821 * This does all the processing and setup required to actually display it, including
822 * starting the voice file playing
823 */
message_training_setup(int m,int length,char * special_message)824 void message_training_setup(int m, int length, char *special_message)
825 {
826 if ((m < 0) || !Messages[m].message[0]) { // remove current message from the screen
827 Training_num_lines = 0;
828 return;
829 }
830
831 // translate tokens in message to the real things
832 if (special_message == NULL)
833 message_translate_tokens(Training_buf, Messages[m].message);
834 else
835 message_translate_tokens(Training_buf, special_message);
836
837 HUD_add_to_scrollback(Training_buf, HUD_SOURCE_TRAINING);
838
839 // moved from message_training_display() because we got rid of an extra buffer and we have to determine
840 // the number of lines earlier to avoid inadvertant modification of Training_buf. - taylor
841 training_process_message(Training_buf);
842 Training_num_lines = split_str(Training_buf, TRAINING_LINE_WIDTH, Training_line_lengths, Training_lines, MAX_TRAINING_MESSAGE_LINES);
843
844 Assert( Training_num_lines >= 0 );
845
846 if (message_play_training_voice(Messages[m].wave_info.index) < 0) {
847 if (length > 0)
848 Training_message_timestamp = timestamp(length * 1000);
849 else
850 Training_message_timestamp = timestamp(TRAINING_TIMING_BASE + strlen(Messages[m].message) * TRAINING_TIMING); // no voice file playing
851
852 } else
853 Training_message_timestamp = 0;
854 }
855
856 /**
857 * Add a message to the queue to be sent later
858 */
message_training_queue(char * text,int timestamp,int length)859 void message_training_queue(char *text, int timestamp, int length)
860 {
861 int m;
862 char temp_buf[TRAINING_MESSAGE_LENGTH];
863
864 Assert(Training_message_queue_count < TRAINING_MESSAGE_QUEUE_MAX);
865 if (Training_message_queue_count < TRAINING_MESSAGE_QUEUE_MAX) {
866 if (!stricmp(text, NOX("none"))) {
867 m = -1;
868 } else {
869 for (m=0; m<Num_messages; m++)
870 if (!stricmp(text, Messages[m].name))
871 break;
872
873 Assert(m < Num_messages);
874 if (m >= Num_messages)
875 return;
876 }
877
878 Training_message_queue[Training_message_queue_count].num = m;
879 Training_message_queue[Training_message_queue_count].timestamp = timestamp;
880 Training_message_queue[Training_message_queue_count].length = length;
881
882 // Goober5000 - this shouldn't happen, but let's be safe
883 if (Training_message_queue[Training_message_queue_count].special_message != NULL)
884 {
885 Int3();
886 vm_free(Training_message_queue[Training_message_queue_count].special_message);
887 Training_message_queue[Training_message_queue_count].special_message = NULL;
888 }
889
890 // Goober5000 - replace variables if necessary
891 strcpy_s(temp_buf, Messages[m].message);
892 if (sexp_replace_variable_names_with_values(temp_buf, MESSAGE_LENGTH))
893 Training_message_queue[Training_message_queue_count].special_message = vm_strdup(temp_buf);
894
895 Training_message_queue_count++;
896 }
897 }
898
899 /**
900 * Removes current message from the queue
901 */
message_training_remove_from_queue(int idx)902 void message_training_remove_from_queue(int idx)
903 {
904 // we're overwriting all messages with the next message, but to
905 // avoid memory leaks, we should free the special message entry
906 if (Training_message_queue[idx].special_message != NULL)
907 {
908 vm_free(Training_message_queue[idx].special_message);
909 Training_message_queue[idx].special_message = NULL;
910 }
911
912 // replace current message with the one above it, etc.
913 for (int j=idx+1; j<Training_message_queue_count; j++)
914 Training_message_queue[j - 1] = Training_message_queue[j];
915
916 // delete the topmost message
917 Training_message_queue_count--;
918 Training_message_queue[Training_message_queue_count].length = -1;
919 Training_message_queue[Training_message_queue_count].num = -1;
920 Training_message_queue[Training_message_queue_count].timestamp = -1;
921 Training_message_queue[Training_message_queue_count].special_message = NULL; // not a memory leak because we copied the pointer
922 }
923
924 /**
925 * Check the training message queue to see if we should play a new message yet or not.
926 */
message_training_queue_check()927 void message_training_queue_check()
928 {
929 int i, iship_num;
930
931 // get the instructor's ship.
932 iship_num = ship_name_lookup(NOX("instructor"));
933
934 // if the instructor is dying or departing, do nothing
935 if ( iship_num != -1 ) // added by Goober5000
936 if (Ships[iship_num].flags & (SF_DYING | SF_DEPARTING))
937 return;
938
939 if (Training_failure)
940 return;
941
942 for (i=0; i<Training_message_queue_count; i++) {
943 if (timestamp_elapsed(Training_message_queue[i].timestamp)) {
944 message_training_setup(Training_message_queue[i].num, Training_message_queue[i].length, Training_message_queue[i].special_message);
945
946 // remove this message from the queue now.
947 message_training_remove_from_queue(i);
948 i--;
949 }
950 }
951 }
952
message_training_update_frame()953 void message_training_update_frame()
954 {
955 int z;
956
957 if ((Viewer_mode & (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY ))) {
958 return;
959 }
960
961 if ( hud_disabled() && !hud_disabled_except_messages()) {
962 return;
963 }
964
965 Training_message_visible = 0;
966 message_training_queue_check();
967
968 if (Training_failure){
969 return;
970 }
971
972 if (timestamp_elapsed(Training_message_timestamp) || !strlen(Training_buf)){
973 return;
974 }
975
976 // the code that preps the training message and counts the number of lines
977 // has been moved to message_training_setup()
978
979 if (Training_num_lines <= 0){
980 return;
981 }
982
983 Training_message_visible = 1;
984
985 if ((Training_voice >= 0) && (Training_num_lines > 0) && !(Training_message_timestamp)) {
986 if (Training_voice_type)
987 z = audiostream_is_playing(Training_voice_handle);
988 else
989 z = snd_is_playing(Training_voice_handle);
990
991 if (!z)
992 Training_message_timestamp = timestamp(2000); // 2 second delay
993 }
994
995 Training_message_method = 0;
996 }
997
HudGaugeTrainingMessages()998 HudGaugeTrainingMessages::HudGaugeTrainingMessages():
999 HudGauge(HUD_OBJECT_TRAINING_MESSAGES, HUD_DIRECTIVES_VIEW, false, true, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255)
1000 {
1001 }
1002
canRender()1003 bool HudGaugeTrainingMessages::canRender()
1004 {
1005 if (hud_disabled() && !hud_disabled_except_messages()) {
1006 return false;
1007 }
1008
1009 if ( !(Game_detail_flags & DETAIL_FLAG_HUD) ) {
1010 return false;
1011 }
1012
1013 if ((Viewer_mode & disabled_views)) {
1014 return false;
1015 }
1016
1017 if(pop_up) {
1018 if(!popUpActive()) {
1019 return false;
1020 }
1021 }
1022
1023 return true;
1024 }
1025
pageIn()1026 void HudGaugeTrainingMessages::pageIn()
1027 {
1028 }
1029
1030 /**
1031 * Displays (renders) the training message to the screen
1032 */
render(float frametime)1033 void HudGaugeTrainingMessages::render(float frametime)
1034 {
1035 const char *str;
1036 char buf[256];
1037 int i, z, x, y, height, mode, count;
1038
1039 if (Training_failure){
1040 return;
1041 }
1042
1043 if (timestamp_elapsed(Training_message_timestamp) || !strlen(Training_buf)){
1044 return;
1045 }
1046
1047 if (Training_num_lines <= 0){
1048 return;
1049 }
1050
1051 gr_set_screen_scale(base_w, base_h);
1052 height = gr_get_font_height();
1053 gr_set_shader(&Training_msg_glass);
1054 gr_shade(position[0], position[1], TRAINING_MESSAGE_WINDOW_WIDTH, Training_num_lines * height + height);
1055 gr_reset_screen_scale();
1056
1057 gr_set_color_fast(&Color_bright_blue);
1058 mode = count = 0;
1059 for (i=0; i<Training_num_lines; i++) { // loop through all lines of message
1060 str = Training_lines[i];
1061 z = 0;
1062 x = position[0] + (TRAINING_MESSAGE_WINDOW_WIDTH - TRAINING_LINE_WIDTH) / 2;
1063 y = position[1] + i * height + height / 2 + 1;
1064
1065 while ((str - Training_lines[i]) < Training_line_lengths[i]) { // loop through each character of each line
1066 if ((count < MAX_TRAINING_MESSAGE_MODS) && (str == Training_message_mods[count].pos)) {
1067 buf[z] = 0;
1068 renderPrintf(x, y, buf);
1069 gr_get_string_size(&z, NULL, buf);
1070 x += z;
1071 z = 0;
1072
1073 mode = Training_message_mods[count++].mode;
1074 switch (mode) {
1075 case TMMOD_NORMAL:
1076 gr_set_color_fast(&Color_bright_blue);
1077 break;
1078
1079 case TMMOD_BOLD:
1080 gr_set_color_fast(&Color_white);
1081 break;
1082 }
1083 }
1084
1085 buf[z++] = *str++;
1086 }
1087
1088 if (z) {
1089 buf[z] = 0;
1090 renderPrintf(x, y, "%s", buf);
1091 }
1092 }
1093 }
1094
1095 /**
1096 * Processes a new training message to get hilighting information and store it in internal structures.
1097 */
training_process_message(char * message)1098 void training_process_message(char *message)
1099 {
1100 int count;
1101 char *src, *dest, buf[TRAINING_MESSAGE_LENGTH];
1102
1103 message_translate_tokens(buf, message);
1104 count = 0;
1105 src = buf;
1106 dest = Training_buf;
1107 while (*src) {
1108 if (!strnicmp(src, NOX("<b>"), 3)) {
1109 Assert(count < MAX_TRAINING_MESSAGE_MODS);
1110 src += 3;
1111 Training_message_mods[count].pos = dest;
1112 Training_message_mods[count].mode = TMMOD_BOLD;
1113 count++;
1114 }
1115
1116 if (!strnicmp(src, NOX("</b>"), 4)) {
1117 Assert(count < MAX_TRAINING_MESSAGE_MODS);
1118 src += 4;
1119 Training_message_mods[count].pos = dest;
1120 Training_message_mods[count].mode = TMMOD_NORMAL;
1121 count++;
1122 }
1123
1124 *dest++ = *src++;
1125 }
1126
1127 *dest = 0;
1128 if (count < MAX_TRAINING_MESSAGE_MODS)
1129 Training_message_mods[count].pos = NULL;
1130 }
1131
training_fail()1132 void training_fail()
1133 {
1134 Training_failure = 1;
1135 // JasonH: Add code here to suspend training and display a directive to warp out.
1136 // Suspend training.
1137 // Give directive to warp out.
1138 // Also ensure that a special failure debriefing is given. Must mention firing at instructor.
1139 // Ask Sandeep to write it (or you can).
1140 }
1141