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