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