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/missionlog.h"
14 #include "playerman/player.h"
15 #include "graphics/font.h"
16 #include "mission/missiongoals.h"
17 #include "globalincs/alphacolors.h"
18 #include "localization/localize.h"
19 #include "mission/missionparse.h"
20 #include "parse/parselo.h"
21 #include "ship/ship.h"
22 #include "iff_defs/iff_defs.h"
23 #include "network/multi.h"
24 #include "network/multimsgs.h"
25 #include "network/multiutil.h"
26 
27 
28 
29 #define MAX_LOG_ENTRIES		700
30 #define MAX_LOG_LINES		1000
31 
32 // used for high water mark for culling out log entries
33 #define LOG_CULL_MARK				((int)(MAX_LOG_ENTRIES * 0.95f))
34 #define LOG_CULL_DOORDIE_MARK		((int)(MAX_LOG_ENTRIES * 0.99f))
35 #define LOG_LAST_DITCH_CULL_NUM	((int)(MAX_LOG_ENTRIES * 0.20f))
36 #define LOG_HALFWAY_REPORT_NUM	((int)(MAX_LOG_ENTRIES * 0.50f))
37 
38 #define EMPTY_LOG_NAME		""
39 
40 // defines for X position offsets of different items for mission log
41 #define TIME_X			10
42 #define OBJECT_X		75
43 #define ACTION_X		250
44 
45 #define LOG_COLOR_NORMAL	0
46 #define LOG_COLOR_BRIGHT	1
47 #define LOG_COLOR_OTHER		2
48 #define NUM_LOG_COLORS		3
49 
50 // defines for log flags
51 #define LOG_FLAG_GOAL_FAILED	(1<<0)
52 #define LOG_FLAG_GOAL_TRUE		(1<<1)
53 
54 typedef struct log_text_seg {
55 	log_text_seg *next;		// linked list
56 	char	*text;				// the text
57 	int	color;				// color text should be displayed in
58 	int	x;						// x offset to display text at
59 	int	flags;				// used to possibly print special characters when displaying the log
60 } log_text_seg;
61 
62 int Num_log_lines;
63 static int X, P_width;
64 
65 // Log_lines is used for scrollback display purposes.
66 static log_text_seg *Log_lines[MAX_LOG_LINES];
67 static fix Log_line_timestamps[MAX_LOG_LINES];
68 
69 log_entry log_entries[MAX_LOG_ENTRIES];	// static array because John says....
70 int last_entry;
71 
mission_log_init()72 void mission_log_init()
73 {
74 	last_entry = 0;
75 
76 	// zero out all the memory so we don't get bogus information when playing across missions!
77 	memset( log_entries, 0, sizeof(log_entries) );
78 }
79 
80 // returns the number of entries in the mission log
mission_log_query_scrollback_size()81 int mission_log_query_scrollback_size()
82 {
83 	return last_entry;
84 }
85 
86 // function to clean up the mission log removing obsolete entries.  Entries might get marked obsolete
87 // in several ways -- having to recycle entries, a ship's subsystem destroyed entries when a ship is
88 // fully destroyed, etc.
mission_log_cull_obsolete_entries()89 void mission_log_cull_obsolete_entries()
90 {
91 	int i, index;
92 
93 	nprintf(("missionlog", "culling obsolete entries.  starting last entry %d.\n", last_entry));
94 	// find the first obsolete entry
95 	for (i = 0; i < last_entry; i++ )
96 		if ( log_entries[i].flags & MLF_OBSOLETE )
97 			break;
98 
99 	// nothing to do if next if statement is true
100 	if ( i == last_entry )
101 		return;
102 
103 	// compact the log array, removing the obsolete entries.
104 	index = i;						// index is the first obsolete entry
105 
106 	// 'index' should always point to the next element in the list
107 	// which is getting compacted.  'i' points to the next array
108 	// element to be replaced.
109 	do {
110 		// get to the next non-obsolete entry.  The obsolete entry must not be essential either!
111 		while ( (log_entries[index].flags & MLF_OBSOLETE) && !(log_entries[index].flags & MLF_ESSENTIAL) ) {
112 			index++;
113 			last_entry--;
114 		}
115 
116 		log_entries[i++] = log_entries[index++];
117 	} while ( i < last_entry );
118 
119 #ifndef NDEBUG
120 	nprintf(("missionlog", "Ending entry: %d.\n", last_entry));
121 #endif
122 }
123 
124 // function to mark entries as obsolete.  Passed is the type of entry that is getting added
125 // to the log.  Some entries might get marked obsolete as a result of this type
mission_log_obsolete_entries(int type,char * pname)126 void mission_log_obsolete_entries(int type, char *pname)
127 {
128 	int i;
129 	log_entry *entry = NULL;
130 
131 	// before adding this entry, check to see if the entry type is a ship destroyed or destructed entry.
132 	// If so, we can remove any subsystem destroyed entries from the log for this ship.
133 	if ( type == LOG_SHIP_DESTROYED || type == LOG_SELF_DESTRUCTED ) {
134 		for (i = 0; i < last_entry; i++) {
135 			entry = &log_entries[i];
136 
137 			// check to see if the type is a subsystem destroyed entry, and that it belongs to the
138 			// ship passed into this routine.  If it matches, mark as obsolete.  We'll clean up
139 			// the log when it starts to get full
140 			if ( !stricmp( pname, entry->pname ) ) {
141 				if ( (entry->type == LOG_SHIP_SUBSYS_DESTROYED) || (entry->type == LOG_SHIP_DISARMED) || (entry->type == LOG_SHIP_DISABLED) )
142 					entry->flags |= MLF_OBSOLETE;
143 			}
144 		}
145 	}
146 
147 	// check to see if we are getting to about 80% of our log capacity.  If so, cull the log.
148 	if ( last_entry > LOG_CULL_MARK ) {
149 		mission_log_cull_obsolete_entries();
150 
151 		// if we culled the entries, and we are still low on space, we need to take more drastic measures.
152 		// these include removing all non-essential entries from the log.  These entries are entries
153 		// which has not been asked for by mission_log_get_time
154 		if ( last_entry > LOG_CULL_MARK ) {
155 			nprintf(("missionlog", "marking the first %d non-essential log entries as obsolete\n", LOG_LAST_DITCH_CULL_NUM));
156 			for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ) {
157 				entry = &log_entries[i];
158 				if ( !(entry->flags & MLF_ESSENTIAL) ){
159 					entry->flags |= MLF_OBSOLETE;
160 				}
161 			}
162 
163 			// cull the obsolete entries again
164 			mission_log_cull_obsolete_entries();
165 
166 			// if we get to this point, and there are no entries left -- we are in big trouble.  We will simply
167 			// mark the first 20% of the log as obsolete and compress.  Don't do this unless we are *really*
168 			// in trouble
169 			if ( last_entry > LOG_CULL_DOORDIE_MARK ) {
170 				nprintf(("missionlog", "removing the first %d entries in the mission log!!!!\n", LOG_LAST_DITCH_CULL_NUM));
171 				for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ){
172 					entry->flags |= MLF_OBSOLETE;
173 				}
174 
175 				mission_log_cull_obsolete_entries();
176 			}
177 		}
178 	}
179 }
180 
181 // following function adds an entry into the mission log.
182 // pass a type and a string which indicates the object
183 // that this event is for.  Don't add entries with this function for multiplayer
mission_log_add_entry(int type,char * pname,char * sname,int info_index)184 void mission_log_add_entry(int type, char *pname, char *sname, int info_index)
185 {
186 	int last_entry_save;
187 	log_entry *entry;
188 
189 	// multiplayer clients don't use this function to add log entries -- they will get
190 	// all their info from the host
191 	if ( MULTIPLAYER_CLIENT ){
192 		return;
193 	}
194 
195 	last_entry_save = last_entry;
196 
197 	// mark any entries as obsolete.  Part of the pruning is done based on the type (and name) passed
198 	// for a new entry
199 	mission_log_obsolete_entries(type, pname);
200 
201 	entry = &log_entries[last_entry];
202 
203 	if ( last_entry == MAX_LOG_ENTRIES ){
204 		return;
205 	}
206 
207 	entry->type = type;
208 	if ( pname ) {
209 		Assert (strlen(pname) < NAME_LENGTH);
210 		strcpy_s(entry->pname, pname);
211 	} else
212 		strcpy_s( entry->pname, EMPTY_LOG_NAME );
213 
214 	if ( sname ) {
215 		Assert (strlen(sname) < NAME_LENGTH);
216 		strcpy_s(entry->sname, sname);
217 	} else
218 		strcpy_s( entry->sname, EMPTY_LOG_NAME );
219 
220 	entry->index = info_index;
221 	entry->flags = 0;
222 	entry->primary_team = -1;
223 	entry->secondary_team = -1;
224 
225 	// determine the contents of the flags member based on the type of entry we added.  We need to store things
226 	// like team for the primary and (possibly) secondary object for this entry.
227 	switch ( type ) {
228 	int index;
229 
230 	case LOG_SHIP_DESTROYED:
231 	case LOG_SHIP_ARRIVED:
232 	case LOG_SHIP_DEPARTED:
233 	case LOG_SHIP_DOCKED:
234 	case LOG_SHIP_SUBSYS_DESTROYED:
235 	case LOG_SHIP_UNDOCKED:
236 	case LOG_SHIP_DISABLED:
237 	case LOG_SHIP_DISARMED:
238 	case LOG_SELF_DESTRUCTED:
239 		// multiplayer. callsign is passed in for ship destroyed and self destruct
240 		if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(pname) >= 0)){
241 			int np_index = multi_find_player_by_callsign(pname);
242 			index = multi_get_player_ship( np_index );
243 		} else {
244 			index = ship_name_lookup( pname );
245 		}
246 
247 		Assert (index >= 0);
248 		entry->primary_team = Ships[index].team;
249 
250 		// some of the entries have a secondary component.  Figure out what is up with them.
251 		if ( (type == LOG_SHIP_DOCKED) || (type == LOG_SHIP_UNDOCKED)) {
252 			if ( sname ) {
253 				index = ship_name_lookup( sname );
254 				Assert (index >= 0);
255 				entry->secondary_team = Ships[index].team;
256 			}
257 		} else if ( type == LOG_SHIP_DESTROYED ) {
258 			if ( sname ) {
259 				int team;
260 
261 				// multiplayer, player name will possibly be sent in
262 				if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(sname) >= 0)) {
263 					// get the player's ship
264 					int np_index = multi_find_player_by_callsign(sname);
265 					int np_ship = multi_get_player_ship(np_index);
266 
267 					if(np_ship < 0)
268 					{
269 						// argh. badness
270 						Int3();
271 						team = Player_ship->team;
272 					}
273 					else
274 					{
275 						team = Ships[Objects[Net_players[np_index].m_player->objnum].instance].team;
276 					}
277 				}
278 				else
279 				{
280 					index = ship_name_lookup( sname );
281 					// no ship, then it probably exited -- check the exited
282 					if ( index == -1 ) {
283 						index = ship_find_exited_ship_by_name( sname );
284 						if ( index == -1 ) {
285 							break;
286 						}
287 						team = Ships_exited[index].team;
288 					} else {
289 						team = Ships[index].team;
290 					}
291 				}
292 
293 				entry->secondary_team = team;
294  			} else {
295 				nprintf(("missionlog", "No secondary name for ship destroyed log entry!\n"));
296 			}
297 		} else if ( (type == LOG_SHIP_SUBSYS_DESTROYED) && (Ship_info[Ships[index].ship_info_index].flags & SIF_SMALL_SHIP) ) {
298 			// make subsystem destroyed entries for small ships hidden
299 			entry->flags |= MLF_HIDDEN;
300 		} else if ( (type == LOG_SHIP_ARRIVED) && (Ships[index].wingnum != -1 ) ) {
301 			// arrival of ships in wings don't display
302 			entry->flags |= MLF_HIDDEN;
303 		}
304 		break;
305 
306 	case LOG_WING_DESTROYED:
307 	case LOG_WING_DEPARTED:
308 	case LOG_WING_ARRIVED:
309 		index = wing_name_lookup(pname, 1);
310 		Assert(index != -1);
311 		Assert(info_index != -1);			// this is the team value
312 
313 		// get the team value for this wing.  Departed or destroyed wings will pass the team
314 		// value in info_index parameter.  For arriving wings, get the team value from the
315 		// first ship in the list because the info_index contains the wave count
316 		if ( type == LOG_WING_ARRIVED ) {
317 			int i, si = -1;
318 
319 			// Goober5000 - get the team value from any ship in the list, because
320 			// ships that arrive initially docked could be created in random order
321 			for (i = 0; i < MAX_SHIPS_PER_WING; ++i) {
322 				// get first valid ship
323 				si = Wings[index].ship_index[i];
324 				if (si >= 0) {
325 					break;
326 				}
327 			}
328 			Assert( si != -1 );
329 			entry->primary_team = Ships[si].team;
330 		} else {
331 			entry->primary_team = info_index;
332 		}
333 
334 #ifndef NDEBUG
335 		// MWA 2/25/98.  debug code to try to find any ships in this wing that have departed.
336 		// scan through all log entries and find at least one ship_depart entry for a ship
337 		// that was in this wing.
338 		if ( type == LOG_WING_DEPARTED ) {
339 			int i;
340 
341 			// if all were destroyed, then don't do this debug code.
342 			if ( (Wings[index].total_destroyed + Wings[index].total_vanished) == Wings[index].total_arrived_count ){
343 				break;
344 			}
345 
346 			for ( i = 0; i < last_entry; i++ ) {
347 				if ( log_entries[i].type != LOG_SHIP_DEPARTED ){
348 					continue;
349 				}
350 				if( log_entries[i].index == index ){
351 					break;
352 				}
353 			}
354 			if ( i == last_entry ){
355 				Int3();						// get Allender -- cannot find any departed ships from wing that supposedly departed.
356 			}
357 		}
358 #endif
359 
360 		break;
361 
362 		// don't display waypoint done entries
363 	case LOG_WAYPOINTS_DONE:
364 		entry->flags |= MLF_HIDDEN;
365 		break;
366 
367 	default:
368 		break;
369 	}
370 
371 	entry->timestamp = Missiontime;
372 
373 	// if in multiplayer and I am the master, send this log entry to everyone
374 	if ( MULTIPLAYER_MASTER ){
375 		send_mission_log_packet( last_entry );
376 	}
377 
378 	last_entry++;
379 
380 #ifndef NDEBUG
381 	if ( !(last_entry % 10) ) {
382 		if ( (last_entry > LOG_HALFWAY_REPORT_NUM) && (last_entry > last_entry_save) ){
383 			nprintf(("missionlog", "new highwater point reached for mission log (%d entries).\n", last_entry));
384 		}
385 	}
386 #endif
387 
388 }
389 
390 // function, used in multiplayer only, which adds an entry sent by the host of the game, into
391 // the mission log.  The index of the log entry is passed as one of the parameters in addition to
392 // the normal parameters used for adding an entry to the log
mission_log_add_entry_multi(int type,char * pname,char * sname,int index,fix timestamp,int flags)393 void mission_log_add_entry_multi( int type, char *pname, char *sname, int index, fix timestamp, int flags )
394 {
395 	log_entry *entry;
396 
397 	// we'd better be in multiplayer and not the master of the game
398 	Assert ( Game_mode & GM_MULTIPLAYER );
399 	Assert ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) );
400 
401 	// mark any entries as obsolete.  Part of the pruning is done based on the type (and name) passed
402 	// for a new entry
403 	mission_log_obsolete_entries(type, pname);
404 
405 	entry = &log_entries[last_entry];
406 
407 	if ( last_entry == MAX_LOG_ENTRIES ){
408 		return;
409 	}
410 
411 	last_entry++;
412 
413 	entry->type = type;
414 	if ( pname ) {
415 		Assert (strlen(pname) < NAME_LENGTH);
416 		strcpy_s(entry->pname, pname);
417 	}
418 	if ( sname ) {
419 		Assert (strlen(sname) < NAME_LENGTH);
420 		strcpy_s(entry->sname, sname);
421 	}
422 	entry->index = index;
423 
424 	entry->flags = flags;
425 	entry->timestamp = timestamp;
426 }
427 
428 // function to determine is the given event has taken place count number of times.
429 
mission_log_get_time_indexed(int type,char * pname,char * sname,int count,fix * time)430 int mission_log_get_time_indexed( int type, char *pname, char *sname, int count, fix *time)
431 {
432 	int i, found;
433 	log_entry *entry;
434 
435 	entry = &log_entries[0];
436 
437 	for (i = 0; i < last_entry; i++, entry++) {
438 		found = 0;
439 
440 		if ( entry->type == type ) {
441 			// if we are looking for a dock/undock entry, then we don't care about the order in which the names
442 			// were passed into this function.  Count the entry as found if either name matches both in the other
443 			// set.
444 			if ( (type == LOG_SHIP_DOCKED) || (type == LOG_SHIP_UNDOCKED) ) {
445 				if (sname == NULL) {
446 					Int3();
447 					return 0;
448 				}
449 
450 				if ( (!stricmp(entry->pname, pname) && !stricmp(entry->sname, sname)) || (!stricmp(entry->pname, sname) && !stricmp(entry->sname, pname)) ) {
451 					found = 1;
452 				}
453 			} else {
454 				// for non dock/undock goals, then the names are important!
455 				if (pname == NULL) {
456 					Int3();
457 					return 0;
458 				}
459 
460 				if ( stricmp(entry->pname, pname) ) {
461 					continue;
462 				}
463 
464 				// if we are looking for a subsystem entry, the subsystem names must be compared
465 				if ((type == LOG_SHIP_SUBSYS_DESTROYED || type == LOG_CAP_SUBSYS_CARGO_REVEALED)) {
466 					if ( (sname == NULL) || !subsystem_stricmp(sname, entry->sname) ) {
467 						found = 1;
468 					}
469 				} else {
470 					if ( (sname == NULL) || !stricmp(sname, entry->sname) ) {
471 						found = 1;
472 					}
473 				}
474 			}
475 
476 			if ( found ) {
477 				count--;
478 
479 				if ( !count ) {
480 					entry->flags |= MLF_ESSENTIAL;				// since the goal code asked for this entry, mark it as essential
481 
482 					if (time) {
483 						*time = entry->timestamp;
484 					}
485 
486 					return 1;
487 				}
488 			}
489 		}
490 	}
491 
492 	return 0;
493 }
494 
495 // this function determines if the given type of event on the specified
496 // object has taken place yet.  If not, it returns 0.  If it has, the
497 // timestamp that the event happened is returned in the time parameter
mission_log_get_time(int type,char * pname,char * sname,fix * time)498 int mission_log_get_time( int type, char *pname, char *sname, fix *time )
499 {
500 	return mission_log_get_time_indexed( type, pname, sname, 1, time );
501 }
502 
503 // determines the number of times the given type of event takes place
504 
mission_log_get_count(int type,char * pname,char * sname)505 int mission_log_get_count( int type, char *pname, char *sname )
506 {
507 	int i;
508 	log_entry *entry;
509 	int count = 0;
510 
511 	entry = &log_entries[0];
512 
513 	for (i = 0; i < last_entry; i++, entry++) {
514 
515 		if ( entry->type == type ) {
516 			// if we are looking for a dock/undock entry, then we don't care about the order in which the names
517 			// were passed into this function.  Count the entry as found if either name matches both in the other
518 			// set.
519 			if ( (type == LOG_SHIP_DOCKED) || (type == LOG_SHIP_UNDOCKED) ) {
520 				if (sname == NULL) {
521 					Int3();
522 					return 0;
523 				}
524 
525 				if ( (!stricmp(entry->pname, pname) && !stricmp(entry->sname, sname)) || (!stricmp(entry->pname, sname) && !stricmp(entry->sname, pname)) ) {
526 					count++;
527 				}
528 			} else {
529 				// for non dock/undock goals, then the names are important!
530 				if (pname == NULL) {
531 					Int3();
532 					return 0;
533 				}
534 
535 				if ( stricmp(entry->pname, pname) ) {
536 					continue;
537 				}
538 
539 				if ( (sname == NULL) || !stricmp(sname, entry->sname) ) {
540 					count++;
541 				}
542 			}
543 		}
544 	}
545 
546 	return count;
547 }
548 
549 
message_log_add_seg(int n,int x,int msg_color,const char * text,int flags=0)550 void message_log_add_seg(int n, int x, int msg_color, const char *text, int flags = 0)
551 {
552 	log_text_seg *seg, **parent;
553 
554 	if ((n < 0) || (n >= MAX_LOG_LINES))
555 		return;
556 
557 	parent = &Log_lines[n];
558 	while (*parent)
559 		parent = &((*parent)->next);
560 
561 	seg = (log_text_seg *) vm_malloc(sizeof(log_text_seg));
562 	Assert(seg);
563 	seg->text = vm_strdup(text);
564 	seg->color = msg_color;
565 	seg->x = x;
566 	seg->flags = flags;
567 	seg->next = NULL;
568 	*parent = seg;
569 }
570 
message_log_add_segs(const char * source_string,int msg_color,int flags=0)571 void message_log_add_segs(const char *source_string, int msg_color, int flags = 0)
572 {
573 	if (!source_string) {
574 		mprintf(("Why are you passing a NULL pointer to message_log_add_segs?\n"));
575 		return;
576 	}
577 	if (!*source_string) {
578 		return;
579 	}
580 
581 	int w;
582 
583 	// duplicate the string so that we can split it without modifying the source
584 	char *dup_string = vm_strdup(source_string);
585 	char *str = dup_string;
586 	char *split = NULL;
587 
588 	while (true) {
589 		if (X == ACTION_X) {
590 			while (is_white_space(*str))
591 				str++;
592 		}
593 
594 		if (P_width - X < 1)
595 			split = str;
596 		else
597 			split = split_str_once(str, P_width - X);
598 
599 		if (split != str)
600 			message_log_add_seg(Num_log_lines, X, msg_color, str, flags);
601 
602 		if (!split) {
603 			gr_get_string_size(&w, NULL, str);
604 			X += w;
605 			break;
606 		}
607 
608 		Num_log_lines++;
609 		X = ACTION_X;
610 		str = split;
611 	}
612 
613 	// free the buffer
614 	vm_free(dup_string);
615 }
616 
message_log_remove_segs(int n)617 void message_log_remove_segs(int n)
618 {
619 	log_text_seg *ptr, *ptr2;
620 
621 	if ((n < 0) || (n >= MAX_LOG_LINES))
622 		return;
623 
624 	ptr = Log_lines[n];
625 	while (ptr) {
626 		ptr2 = ptr->next;
627 		vm_free(ptr);
628 		ptr = ptr2;
629 	}
630 
631 	Log_lines[n] = NULL;
632 }
633 
message_log_color_get_team(int msg_color)634 int message_log_color_get_team(int msg_color)
635 {
636 	return msg_color - NUM_LOG_COLORS;
637 }
638 
message_log_team_get_color(int team)639 int message_log_team_get_color(int team)
640 {
641 	return NUM_LOG_COLORS + team;
642 }
643 
644 // pw = total pixel width
message_log_init_scrollback(int pw)645 void message_log_init_scrollback(int pw)
646 {
647 	char text[256];
648 	log_entry *entry;
649 	int i, c, kill, type;
650 
651 	P_width = pw;
652 	mission_log_cull_obsolete_entries();  // compact array so we don't have gaps
653 
654 	// initialize the log lines data
655 	Num_log_lines = 0;
656 	for (i=0; i<MAX_LOG_LINES; i++) {
657 		Log_lines[i] = NULL;
658 		Log_line_timestamps[i] = 0;
659 	}
660 
661 	for (i=0; i<last_entry; i++) {
662 		entry = &log_entries[i];
663 
664 		if (entry->flags & MLF_HIDDEN)
665 			continue;
666 
667 		// track time of event (normal timestamp milliseconds format)
668 		Log_line_timestamps[Num_log_lines] = entry->timestamp;
669 
670 		// Goober5000
671 		if ((entry->type == LOG_GOAL_SATISFIED) || (entry->type == LOG_GOAL_FAILED))
672 			c = LOG_COLOR_BRIGHT;
673 		else if (entry->primary_team >= 0)
674 			c = message_log_team_get_color(entry->primary_team);
675 		else
676 			c = LOG_COLOR_OTHER;
677 
678 		if ( (Lcl_gr) && ((entry->type == LOG_GOAL_FAILED) || (entry->type == LOG_GOAL_SATISFIED)) ) {
679 			// in german goal events, just say "objective" instead of objective name
680 			// this is cuz we can't translate objective names
681 			message_log_add_seg(Num_log_lines, OBJECT_X, c, "Einsatzziel");
682 		} else {
683 			message_log_add_seg(Num_log_lines, OBJECT_X, c, entry->pname);
684 		}
685 
686 		// now on to the actual message itself
687 		X = ACTION_X;
688 		kill = 0;
689 
690 		// Goober5000
691 		if (entry->secondary_team >= 0)
692 			c = message_log_team_get_color(entry->secondary_team);
693 		else
694 			c = LOG_COLOR_NORMAL;
695 
696 		switch (entry->type) {
697 			case LOG_SHIP_DESTROYED:
698 				message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
699 				if (strlen(entry->sname)) {
700 					message_log_add_segs(XSTR( "  Kill: ", 405), LOG_COLOR_NORMAL);
701 					message_log_add_segs(entry->sname, c);
702 					if (entry->index >= 0) {
703 						sprintf(text, NOX(" (%d%%)"), entry->index);
704 						message_log_add_segs(text, LOG_COLOR_BRIGHT);
705 					}
706 				}
707 				break;
708 
709 			case LOG_SELF_DESTRUCTED:
710 				message_log_add_segs(XSTR( "Self Destructed", 1476), LOG_COLOR_NORMAL);
711 				break;
712 
713 			case LOG_WING_DESTROYED:
714 				message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
715 				break;
716 
717 			case LOG_SHIP_ARRIVED:
718 				message_log_add_segs(XSTR( "Arrived", 406), LOG_COLOR_NORMAL);
719 				break;
720 
721 			case LOG_WING_ARRIVED:
722 				if (entry->index > 1){
723 					sprintf(text, XSTR( "Arrived (wave %d)", 407), entry->index);
724 				} else {
725 					strcpy_s(text, XSTR( "Arrived", 406));
726 				}
727 				message_log_add_segs(text, LOG_COLOR_NORMAL);
728 				break;
729 
730 			case LOG_SHIP_DEPARTED:
731 				message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
732 				break;
733 
734 			case LOG_WING_DEPARTED:
735 				message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
736 				break;
737 
738 			case LOG_SHIP_DOCKED:
739 				message_log_add_segs(XSTR( "Docked with ", 409), LOG_COLOR_NORMAL);
740 				message_log_add_segs(entry->sname, c);
741 				break;
742 
743 			case LOG_SHIP_SUBSYS_DESTROYED: {
744 				int si_index, model_index;
745 
746 				si_index = (int)((entry->index >> 16) & 0xffff);
747 				model_index = (int)(entry->index & 0xffff);
748 
749 				message_log_add_segs(XSTR( "Subsystem ", 410), LOG_COLOR_NORMAL);
750 				//message_log_add_segs(entry->sname, LOG_COLOR_BRIGHT);
751 				const char *subsys_name = Ship_info[si_index].subsystems[model_index].subobj_name;
752 				if (Ship_info[si_index].subsystems[model_index].type == SUBSYSTEM_TURRET) {
753 					subsys_name = XSTR("Turret", 1487);
754 				}
755 				message_log_add_segs(subsys_name, LOG_COLOR_BRIGHT);
756 				message_log_add_segs(XSTR( " destroyed", 411), LOG_COLOR_NORMAL);
757 				break;
758 			}
759 
760 			case LOG_SHIP_UNDOCKED:
761 				message_log_add_segs(XSTR( "Undocked with ", 412), LOG_COLOR_NORMAL);
762 				message_log_add_segs(entry->sname, c);
763 				break;
764 
765 			case LOG_SHIP_DISABLED:
766 				message_log_add_segs(XSTR( "Disabled", 413), LOG_COLOR_NORMAL);
767 				break;
768 
769 			case LOG_SHIP_DISARMED:
770 				message_log_add_segs(XSTR( "Disarmed", 414), LOG_COLOR_NORMAL);
771 				break;
772 
773 			case LOG_PLAYER_CALLED_FOR_REARM:
774 				message_log_add_segs(XSTR( " called for rearm", 415), LOG_COLOR_NORMAL);
775 				break;
776 
777 			case LOG_PLAYER_ABORTED_REARM:
778 				message_log_add_segs(XSTR( " aborted rearm", 416), LOG_COLOR_NORMAL);
779 				break;
780 
781 			case LOG_PLAYER_CALLED_FOR_REINFORCEMENT:
782 				message_log_add_segs(XSTR( "Called in as reinforcement", 417), LOG_COLOR_NORMAL);
783 				break;
784 
785 			case LOG_CARGO_REVEALED:
786 				Assert( entry->index >= 0 );
787 				Assert(!(entry->index & CARGO_NO_DEPLETE));
788 
789 				message_log_add_segs(XSTR( "Cargo revealed: ", 418), LOG_COLOR_NORMAL);
790 				strncpy(text, Cargo_names[entry->index], sizeof(text) - 1);
791 				message_log_add_segs( text, LOG_COLOR_BRIGHT );
792 				break;
793 
794 			case LOG_CAP_SUBSYS_CARGO_REVEALED:
795 				Assert( entry->index >= 0 );
796 				Assert(!(entry->index & CARGO_NO_DEPLETE));
797 
798 				message_log_add_segs(entry->sname, LOG_COLOR_NORMAL);
799 				message_log_add_segs(XSTR( " subsystem cargo revealed: ", 1488), LOG_COLOR_NORMAL);
800 				strncpy(text, Cargo_names[entry->index], sizeof(text) - 1);
801 				message_log_add_segs( text, LOG_COLOR_BRIGHT );
802 				break;
803 
804 
805 			case LOG_GOAL_SATISFIED:
806 			case LOG_GOAL_FAILED: {
807 				type = Mission_goals[entry->index].type & GOAL_TYPE_MASK;
808 
809 				// don't display failed bonus goals
810 				if ( (type == BONUS_GOAL) && (entry->type == LOG_GOAL_FAILED) ) {
811 					kill = 1;
812 					break;  // don't display this line
813 				}
814 
815 				sprintf( text, XSTR( "%s objective ", 419), Goal_type_text(type) );
816 				if ( entry->type == LOG_GOAL_SATISFIED )
817 					strcat_s(text, XSTR( "satisfied.", 420));
818 				else
819 					strcat_s(text, XSTR( "failed.", 421));
820 
821 				message_log_add_segs(text, LOG_COLOR_BRIGHT, (entry->type == LOG_GOAL_SATISFIED?LOG_FLAG_GOAL_TRUE:LOG_FLAG_GOAL_FAILED) );
822 				break;
823 			}	// matches case statement!
824 		}
825 
826 		if (kill) {
827 			message_log_remove_segs(Num_log_lines);
828 
829 		} else {
830 			if (Num_log_lines < MAX_LOG_LINES-1)
831 				Num_log_lines++;
832 		}
833 	}
834 }
835 
message_log_shutdown_scrollback()836 void message_log_shutdown_scrollback()
837 {
838 	int i;
839 
840 	for (i=0; i<MAX_LOG_LINES; i++)
841 		message_log_remove_segs(i);
842 
843 	Num_log_lines = 0;
844 }
845 
846 // message_log_scrollback displays the contents of the mesasge log currently much like the HUD
847 // message scrollback system.  I'm sure this system will be overhauled.
mission_log_scrollback(int line,int list_x,int list_y,int list_w,int list_h)848 void mission_log_scrollback(int line, int list_x, int list_y, int list_w, int list_h)
849 {
850 	char buf[256];
851 	int y;
852 	int font_h = gr_get_font_height();
853 	log_text_seg *seg;
854 
855 	y = 0;
856 	while (y + font_h <= list_h) {
857 		if (line >= Num_log_lines)
858 			break;
859 
860 		if (Log_line_timestamps[line]) {
861 			gr_set_color_fast(&Color_text_normal);
862 			gr_print_timestamp(list_x + TIME_X, list_y + y, Log_line_timestamps[line], GR_RESIZE_MENU);
863 		}
864 
865 		seg = Log_lines[line];
866 		while (seg) {
867 			switch (seg->color) {
868 				case LOG_COLOR_BRIGHT:
869 					gr_set_color_fast(&Color_bright);
870 					break;
871 
872 				case LOG_COLOR_OTHER:
873 					gr_set_color_fast(&Color_normal);
874 					break;
875 
876 				default:
877 				{
878 					int team = message_log_color_get_team(seg->color);
879 					if (team < 0)
880 						gr_set_color_fast(&Color_text_normal);
881 					else
882 						gr_set_color_fast(iff_get_color_by_team(team, -1, 1));
883 
884 					break;
885 				}
886 			}
887 
888 			strcpy_s(buf, seg->text);
889 			if (seg->x < ACTION_X)
890 				gr_force_fit_string(buf, 256, ACTION_X - OBJECT_X - 8);
891 			else
892 				gr_force_fit_string(buf, 256, list_w - seg->x);
893 
894 			end_string_at_first_hash_symbol(buf);
895 			gr_string(list_x + seg->x, list_y + y, buf, GR_RESIZE_MENU);
896 
897 			// possibly "print" some symbols for interesting log entries
898 			if ( (seg->flags & LOG_FLAG_GOAL_TRUE) || (seg->flags & LOG_FLAG_GOAL_FAILED) ) {
899 				int i;
900 
901 				if ( seg->flags & LOG_FLAG_GOAL_FAILED )
902 					gr_set_color_fast(&Color_bright_red);
903 				else
904 					gr_set_color_fast(&Color_bright_green);
905 
906 				i = list_y + y + font_h / 2 - 1;
907 				gr_circle(list_x + TIME_X - 6, i, 5, GR_RESIZE_MENU);
908 
909 				gr_set_color_fast(&Color_bright);
910 				gr_line(list_x + TIME_X - 10, i, list_x + TIME_X - 8, i, GR_RESIZE_MENU);
911 				gr_line(list_x + TIME_X - 6, i - 4, list_x + TIME_X - 6, i - 2, GR_RESIZE_MENU);
912 				gr_line(list_x + TIME_X - 4, i, list_x + TIME_X - 2, i, GR_RESIZE_MENU);
913 				gr_line(list_x + TIME_X - 6, i + 2, list_x + TIME_X - 6, i + 4, GR_RESIZE_MENU);
914 			}
915 
916 			seg = seg->next;
917 		}
918 
919 		y += font_h;
920 		line++;
921 	}
922 }
923