]> icculus.org git repositories - taylor/freespace2.git/blob - src/mission/missionlog.cpp
silence various compiler warnings (clang:rel)
[taylor/freespace2.git] / src / mission / missionlog.cpp
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Mission/MissionLog.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * File to deal with Mission logs
16  *
17  * $Log$
18  * Revision 1.3  2002/06/09 04:41:22  relnev
19  * added copyright header
20  *
21  * Revision 1.2  2002/05/07 03:16:46  theoddone33
22  * The Great Newline Fix
23  *
24  * Revision 1.1.1.1  2002/05/03 03:28:09  root
25  * Initial import.
26  *
27  * 
28  * 10    11/01/99 2:13p Jefff
29  * some changes in how objective events are displayed in german
30  * 
31  * 9     10/13/99 3:22p Jefff
32  * fixed unnumbered XSTRs
33  * 
34  * 8     9/08/99 4:05p Dave
35  * Fixed string ID on self-destruxt message.
36  * 
37  * 7     9/06/99 8:18p Andsager
38  * Make destroyed weapon subsystems display as turrets in event log.
39  * 
40  * 6     8/26/99 8:51p Dave
41  * Gave multiplayer TvT messaging a heavy dose of sanity. Cheat codes.
42  * 
43  * 5     8/22/99 5:53p Dave
44  * Scoring fixes. Added self destruct key. Put callsigns in the logfile
45  * instead of ship designations for multiplayer players.
46  * 
47  * 4     2/26/99 6:01p Andsager
48  * Add sexp has-been-tagged-delay and cap-subsys-cargo-known-delay
49  * 
50  * 3     10/13/98 9:28a Dave
51  * Started neatening up freespace.h. Many variables renamed and
52  * reorganized. Added AlphaColors.[h,cpp]
53  * 
54  * 2     10/07/98 10:53a Dave
55  * Initial checkin.
56  * 
57  * 1     10/07/98 10:49a Dave
58  * 
59  * 57    6/09/98 10:31a Hoffoss
60  * Created index numbers for all xstr() references.  Any new xstr() stuff
61  * added from here on out should be added to the end if the list.  The
62  * current list count can be found in FreeSpace.cpp (search for
63  * XSTR_SIZE).
64  * 
65  * 56    5/07/98 1:11a Ed
66  * AL: fix bogus null pointer test in message_log_add_seg()
67  * 
68  * 55    4/28/98 4:45p Allender
69  * fix mission log problems on clients
70  * 
71  * 54    4/23/98 10:06a Allender
72  * don't use the word "player" in event log for rearm event.  Send
73  * shipname instead (players only)
74  * 
75  * 53    3/28/98 1:56p Allender
76  * clarify the destroyed mission log entry.
77  * 
78  * 52    3/13/98 2:49p Allender
79  * put fix into debug code so that it doesn't trip when no ships have
80  * departed (i.e. wing was waxes before they actulaly left)
81  * 
82  * 51    3/12/98 5:52p Hoffoss
83  * Moved stuff over so icons don't overlap frame.
84  * 
85  * 50    3/09/98 6:22p Hoffoss
86  * Fixed text overrun problems in event log display.
87  * 
88  * 49    2/25/98 4:44p Allender
89  * fix up mission log and wing/ship departed problems (hopefully).  There
90  * was problem where wing was marked departed although all ships were
91  * destroyed (I think).
92  * 
93  * 48    2/25/98 11:07a Allender
94  * added debug code to help trap problems with wing departure and ship
95  * departure weirdness
96  * 
97  * 47    2/23/98 8:55a John
98  * More string externalization
99  * 
100  * 46    2/22/98 4:30p John
101  * More string externalization classification
102  * 
103  * 45    2/10/98 12:47p Allender
104  * clear log memeory before mission start to prevent entries from previous
105  * mission from being used incorrectly
106  * 
107  * 44    2/05/98 10:33a Hoffoss
108  * Changed refernce go "goal" to "objective" instead.
109  * 
110  * 43    1/30/98 11:16a Comet
111  * DOH!  fix bug checking for exited ships
112  * 
113  * 42    1/08/98 1:59p Allender
114  * fixed problem with some subsystems not getting recognized on
115  * is-subsystem-destroyed sexpressions
116  * 
117  * 41    1/07/98 4:41p Allender
118  * minor modification to special messages.  Fixed cargo_revealed problem
119  * for multiplayer and problem with is-cargo-known sexpression
120  * 
121  * 40    11/21/97 10:31a Allender
122  * minor cosmetic changes
123  * 
124  * 39    11/20/97 3:52p Hoffoss
125  * Fixed timestamp color in mission log scrollback.
126  * 
127  * 38    11/19/97 2:30p Allender
128  * hide waypoints done log entries
129  * 
130  * 37    11/13/97 4:25p Allender
131  * hide certain mission log entries
132  * 
133  * 36    11/13/97 4:05p Hoffoss
134  * Added hiding code for mission log entries.
135  * 
136  * 35    11/06/97 5:42p Hoffoss
137  * Added support for fixed size timstamp rendering.
138  * 
139  * 34    11/03/97 10:12p Hoffoss
140  * Finished up work on the hud message/mission log scrollback screen.
141  * 
142  * 33    10/09/97 5:21p Allender
143  * try to check for bogus secondary names on ship destroy.
144  * 
145  * 32    10/07/97 3:09p Allender
146  * fix typo
147  * 
148  * 31    9/26/97 3:47p Allender
149  * add code to keep track of ships which have left mission (departed or
150  * destroyed).  Log who killed who in mission log
151  * 
152  * 30    9/10/97 3:30p Allender
153  * ignore waypoints_done entry when determining teams for log actions
154  * 
155  * 29    9/10/97 8:53a Allender
156  * made the mission log prettier.  Added team info to log entries -- meant
157  * reordering some code
158  * 
159  * 28    8/31/97 6:38p Lawrance
160  * pass in frametime to do_frame loop
161  * 
162  * 27    8/20/97 3:53p Allender
163  * minor fix to message log display
164  * 
165  * 26    8/07/97 11:37a Allender
166  * made mission log cull non-essential entries when log starts getting
167  * full.  More drastic action is taken as the log gets more full.
168  * 
169  * 25    7/24/97 4:13p Mike
170  * Comment out check in mission_log_add_entry that prevented logging
171  * events when player is dead.
172  * 
173  * 24    7/14/97 11:46a Lawrance
174  * allow log entries when in the navmap
175  * 
176  * 23    7/10/97 1:40p Allender
177  * make dock/undock log entries work with either name (docker/dockee)
178  * coming first
179  * 
180  * 22    7/07/97 9:23a Allender
181  * added code for valid/invalid goals.  Invalid goals are eval'ed,
182  * counted, or otherwise used
183  * 
184  * 21    7/01/97 2:52p Allender
185  * added packets for mission log stuff and for pregame chat stuff
186  * 
187  * 20    7/01/97 1:23p Allender
188  * temp checkin
189  * 
190 */
191
192 #include "timer.h"
193 #include "parselo.h"
194 #include "fix.h"
195 #include "gamesequence.h"
196 #include "freespace.h"
197 #include "missionlog.h"
198 #include "key.h"
199 #include "2d.h"
200 #include "font.h"
201 #include "missiongoals.h"
202 #include "multi.h"
203 #include "multimsgs.h"
204 #include "multiutil.h"
205 #include "alphacolors.h"
206 #include "localize.h"
207
208 #define MAX_LOG_ENTRIES         700
209 #define MAX_LOG_LINES           1000
210
211 // used for high water mark for culling out log entries
212 #define LOG_CULL_MARK                           ((int)(MAX_LOG_ENTRIES * 0.95f))
213 #define LOG_CULL_DOORDIE_MARK           ((int)(MAX_LOG_ENTRIES * 0.99f))
214 #define LOG_LAST_DITCH_CULL_NUM ((int)(MAX_LOG_ENTRIES * 0.20f))
215 #define LOG_HALFWAY_REPORT_NUM  ((int)(MAX_LOG_ENTRIES * 0.50f))
216
217 #define ML_FLAG_PRIMARY         1
218 #define ML_FLAG_SECONDARY       2
219
220 #define EMPTY_LOG_NAME          ""
221
222 // defines for X position offsets of different items for mission log
223 #define TIME_X                  10
224 #define OBJECT_X                75
225 #define ACTION_X                250
226
227 #define LOG_COLOR_NORMAL        0
228 #define LOG_COLOR_BRIGHT        1
229 #define LOG_COLOR_FRIENDLY      2
230 #define LOG_COLOR_HOSTILE       3
231 #define LOG_COLOR_OTHER         4
232
233 // defines for log flags
234 #define LOG_FLAG_GOAL_FAILED    (1<<0)
235 #define LOG_FLAG_GOAL_TRUE              (1<<1)
236
237 typedef struct log_text_seg {
238         log_text_seg *next;             // linked list
239         char    *text;                          // the text
240         int     color;                          // color text should be displayed in
241         int     x;                                              // x offset to display text at
242         int     flags;                          // used to possibly print special characters when displaying the log
243 } log_text_seg;
244
245 int Num_log_lines;
246 static int X, P_width;
247
248 // Log_lines is used for scrollback display purposes.
249 static log_text_seg *Log_lines[MAX_LOG_LINES];
250 static int Log_line_timestamps[MAX_LOG_LINES];
251
252 log_entry log_entries[MAX_LOG_ENTRIES]; // static array because John says....
253 int last_entry;
254
255 void mission_log_init()
256 {
257         last_entry = 0;
258
259         // zero out all the memory so we don't get bogus information when playing across missions!
260         memset( log_entries, 0, sizeof(log_entries) );
261 }
262
263 // returns the number of entries in the mission log
264 int mission_log_query_scrollback_size()
265 {
266         return last_entry;
267 }
268
269 // function to clean up the mission log removing obsolete entries.  Entries might get marked obsolete
270 // in several ways -- having to recycle entries, a ship's subsystem destroyed entries when a ship is
271 // fully destroyed, etc.
272 void mission_log_cull_obsolete_entries()
273 {
274         int i, index;
275
276         nprintf(("missionlog", "culling obsolete entries.  starting last entry %d.\n", last_entry));
277         // find the first obsolete entry
278         for (i = 0; i < last_entry; i++ ) 
279                 if ( log_entries[i].flags & MLF_OBSOLETE )
280                         break;
281
282         // nothing to do if next if statement is true
283         if ( i == last_entry )
284                 return;
285
286         // compact the log array, removing the obsolete entries.
287         index = i;                                              // index is the first obsolete entry
288
289         // 'index' should always point to the next element in the list
290         // which is getting compacted.  'i' points to the next array
291         // element to be replaced.
292         do {
293                 // get to the next non-obsolete entry.  The obsolete entry must not be essential either!
294                 while ( (log_entries[index].flags & MLF_OBSOLETE) && !(log_entries[index].flags & MLF_ESSENTIAL) ) {
295                         index++;
296                         last_entry--;
297                 }
298
299                 log_entries[i++] = log_entries[index++];
300         } while ( i < last_entry );
301
302 #ifndef NDEBUG
303         nprintf(("missionlog", "Ending entry: %d.\n", last_entry));
304 #endif
305 }
306
307 // function to mark entries as obsolete.  Passed is the type of entry that is getting added
308 // to the log.  Some entries might get marked obsolete as a result of this type
309 void mission_log_obsolete_entries(int type, const char *pname)
310 {
311         int i;
312         log_entry *entry = NULL;
313
314         // before adding this entry, check to see if the entry type is a ship destroyed entry.
315         // If so, we can remove any subsystem destroyed entries from the log for this ship.  
316         if ( type == LOG_SHIP_DESTROYED ) {
317                 for (i = 0; i < last_entry; i++) {
318                         entry = &log_entries[i];
319
320                         // check to see if the type is a subsystem destroyed entry, and that it belongs to the
321                         // ship passed into this routine.  If it matches, mark as obsolete.  We'll clean up
322                         // the log when it starts to get full
323                         if ( !SDL_strcasecmp( pname, entry->pname ) ) {
324                                 if ( (entry->type == LOG_SHIP_SUBSYS_DESTROYED) || (entry->type == LOG_SHIP_DISARMED) || (entry->type == LOG_SHIP_DISABLED) )
325                                         entry->flags |= MLF_OBSOLETE;
326                         }
327                 }
328         }
329
330         // check to see if we are getting to about 80% of our log capacity.  If so, cull the log.
331         if ( last_entry > LOG_CULL_MARK ) {
332                 mission_log_cull_obsolete_entries();
333
334                 // if we culled the entries, and we are still low on space, we need to take more drastic measures.
335                 // these include removing all non-essential entries from the log.  These entries are entries 
336                 // which has not been asked for by mission_log_get_time
337                 if ( last_entry > LOG_CULL_MARK ) {
338                         nprintf(("missionlog", "marking the first %d non-essential log entries as obsolete\n", LOG_LAST_DITCH_CULL_NUM));
339                         for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ) {
340                                 entry = &log_entries[i];
341                                 if ( !(entry->flags & MLF_ESSENTIAL) ){
342                                         entry->flags |= MLF_OBSOLETE;
343                                 }
344                         }
345
346                         // cull the obsolete entries again
347                         mission_log_cull_obsolete_entries();
348
349                         // if we get to this point, and there are no entries left -- we are in big trouble.  We will simply
350                         // mark the first 20% of the log as obsolete and compress.  Don't do this unless we are *really*
351                         // in trouble
352                         if ( last_entry > LOG_CULL_DOORDIE_MARK ) {
353                                 nprintf(("missionlog", "removing the first %d entries in the mission log!!!!\n", LOG_LAST_DITCH_CULL_NUM));
354                                 for (i = 0; i < LOG_LAST_DITCH_CULL_NUM; i++ ){
355                                         entry->flags |= MLF_OBSOLETE;
356                                 }
357
358                                 mission_log_cull_obsolete_entries();
359                         }
360                 }
361         }
362 }
363
364 // assigns flag values to an entry based on the team value passed in
365 void mission_log_flag_team( log_entry *entry, int which_entry, int team )
366 {
367         if ( which_entry == ML_FLAG_PRIMARY ) {
368                 if ( team == TEAM_FRIENDLY )
369                         entry->flags |= MLF_PRIMARY_FRIENDLY;
370                 else
371                         entry->flags |= MLF_PRIMARY_HOSTILE;
372
373         } else if ( which_entry == ML_FLAG_SECONDARY ) {
374                 if ( team == TEAM_FRIENDLY )
375                         entry->flags |= MLF_SECONDARY_FRIENDLY;
376                 else
377                         entry->flags |= MLF_SECONDARY_HOSTILE;
378
379         } else
380                 Int3();         // get allender -- impossible type
381 }
382
383 // following function adds an entry into the mission log.
384 // pass a type and a string which indicates the object
385 // that this event is for.  Don't add entries with this function for multiplayer
386 void mission_log_add_entry(int type, const char *pname, const char *sname, int info_index)
387 {
388         log_entry *entry;       
389
390         // multiplayer clients don't use this function to add log entries -- they will get
391         // all their info from the host
392         if ( (Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
393                 return;
394         }
395
396 #ifndef NDEBUG
397         int last_entry_save = last_entry;
398 #endif
399
400         // mark any entries as obsolete.  Part of the pruning is done based on the type (and name) passed
401         // for a new entry
402         mission_log_obsolete_entries(type, pname);
403
404         entry = &log_entries[last_entry];
405
406         if ( last_entry == MAX_LOG_ENTRIES ){
407                 return;
408         }
409
410         entry->type = type;
411         if ( pname ) {
412                 SDL_assert (strlen(pname) < NAME_LENGTH);
413                 SDL_strlcpy(entry->pname, pname, SDL_arraysize(entry->pname));
414         } else
415                 SDL_strlcpy( entry->pname, EMPTY_LOG_NAME, SDL_arraysize(entry->pname) );
416
417         if ( sname ) {
418                 SDL_assert (strlen(sname) < NAME_LENGTH);
419                 SDL_strlcpy(entry->sname, sname, SDL_arraysize(entry->sname));
420         } else
421                 SDL_strlcpy( entry->sname, EMPTY_LOG_NAME, SDL_arraysize(entry->sname) );
422
423         entry->index = info_index;
424         entry->flags = 0;
425
426         // determine the contents of the flags member based on the type of entry we added.  We need to store things
427         // like team for the primary and (possibly) secondary object for this entry.
428         switch ( type ) {
429         int index, si;
430
431         case LOG_SHIP_DESTROYED:
432         case LOG_SHIP_ARRIVE:
433         case LOG_SHIP_DEPART:
434         case LOG_SHIP_DOCK:
435         case LOG_SHIP_SUBSYS_DESTROYED:
436         case LOG_SHIP_UNDOCK:
437         case LOG_SHIP_DISABLED:
438         case LOG_SHIP_DISARMED:
439         case LOG_SELF_DESTRUCT:
440                 // multiplayer. callsign is passed in for ship destroyed and self destruct
441                 if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(pname) >= 0)){
442                         int np_index = multi_find_player_by_callsign(pname);
443                         index = multi_get_player_ship( np_index );
444                 } else {
445                         index = ship_name_lookup( pname );
446                 }
447
448                 SDL_assert ( index != -1 );
449                 if(index < 0){
450                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, TEAM_FRIENDLY );         
451                 } else {
452                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, Ships[index].team );             
453                 }
454
455                 // some of the entries have a secondary component.  Figure out what is up with them.
456                 if ( (type == LOG_SHIP_DOCK) || (type == LOG_SHIP_UNDOCK)) {
457                         if ( sname ) {
458                                 index = ship_name_lookup( sname );
459                                 SDL_assert( index != -1 );
460                                 mission_log_flag_team( entry, ML_FLAG_SECONDARY, Ships[index].team );
461                         }
462                 } else if ( type == LOG_SHIP_DESTROYED ) {
463                         if ( sname ) {
464                                 int team;
465
466                                 // multiplayer, player name will possibly be sent in
467                                 if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(sname) >= 0)){
468                                         // get the player's ship
469                                         int np_index = multi_find_player_by_callsign(sname);
470                                         int np_ship = multi_get_player_ship(np_index);
471
472                                         if(np_ship != -1){
473                                                 team = Ships[Objects[Net_players[np_index].player->objnum].instance].team;
474                                         }
475                                         // argh. badness
476                                         else {
477                                                 team = TEAM_FRIENDLY;
478                                         }
479                                 } else {
480                                         index = ship_name_lookup( sname );
481                                         // no ship, then it probably exited -- check the exited 
482                                         if ( index == -1 ) {
483                                                 index = ship_find_exited_ship_by_name( sname );
484                                                 if ( index == -1 ) {
485                                                 //      Int3();         // get allender.  name of object who killed ship appears to be bogus!!!
486                                                         break;
487                                                 }
488                                                 team = Ships_exited[index].team;
489                                         } else {
490                                                 team = Ships[index].team;
491                                         }
492                                 }
493
494                                 mission_log_flag_team( entry, ML_FLAG_SECONDARY, team );
495                         } else {
496                                 nprintf(("missionlog", "No secondary name for ship destroyed log entry!\n"));
497                         }
498                 } else if ( (type == LOG_SHIP_SUBSYS_DESTROYED) && (Ship_info[Ships[index].ship_info_index].flags & SIF_SMALL_SHIP) ) {
499                         // make subsystem destroyed entries for small ships hidden
500                         entry->flags |= MLF_HIDDEN;
501                 } else if ( (type == LOG_SHIP_ARRIVE) && (Ships[index].wingnum != -1 ) ) {
502                         // arrival of ships in wings don't display
503                         entry->flags |= MLF_HIDDEN;
504                 }
505                 break;
506
507         case LOG_WING_DESTROYED:
508         case LOG_WING_DEPART:
509         case LOG_WING_ARRIVE:
510                 index = wing_name_lookup( pname, 1 );
511                 SDL_assert( index != -1 );
512                 SDL_assert( info_index != -1 );                 // this is the team value
513
514                 // get the team value for this wing.  Departed or destroyed wings will pass the team
515                 // value in info_index parameter.  For arriving wings, get the team value from the
516                 // first ship in the list
517                 if ( type == LOG_WING_ARRIVE ) {
518                         si = Wings[index].ship_index[0];
519                         SDL_assert( si != -1 );
520                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, Ships[si].team );
521                 } else {
522                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, info_index );
523                 }
524
525 #ifndef NDEBUG
526                 // MWA 2/25/98.  debug code to try to find any ships in this wing that have departed.
527                 // scan through all log entries and find at least one ship_depart entry for a ship
528                 // that was in this wing.
529                 if ( type == LOG_WING_DEPART ) {
530                         int i;
531
532                         // if all were destroyed, then don't do this debug code.
533                         if ( Wings[index].total_destroyed == Wings[index].total_arrived_count ){
534                                 break;
535                         }
536
537                         for ( i = 0; i < last_entry; i++ ) {
538                                 if ( log_entries[i].type != LOG_SHIP_DEPART ){
539                                         continue;
540                                 }
541                                 if( log_entries[i].index == index ){
542                                         break;
543                                 }
544                         }
545                         if ( i == last_entry ){
546                                 Int3();                                         // get Allender -- cannot find any departed ships from wing that supposedly departed.
547                         }
548                 }
549 #endif
550
551                 break;
552
553                 // don't display waypoint done entries
554         case LOG_WAYPOINTS_DONE:
555                 entry->flags |= MLF_HIDDEN;
556                 break;
557
558         default:
559                 break;
560         }
561
562         entry->timestamp = Missiontime;
563
564         // if in multiplayer and I am the master, send this log entry to everyone
565         if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
566                 send_mission_log_packet( last_entry );
567         }
568
569         last_entry++;
570
571 #ifndef NDEBUG
572         if ( !(last_entry % 10) ) {
573                 if ( (last_entry > LOG_HALFWAY_REPORT_NUM) && (last_entry > last_entry_save) ){
574                         nprintf(("missionlog", "new highwater point reached for mission log (%d entries).\n", last_entry));
575                 }
576         }
577 #endif
578
579 }
580
581 // function, used in multiplayer only, which adds an entry sent by the host of the game, into
582 // the mission log.  The index of the log entry is passed as one of the parameters in addition to
583 // the normal parameters used for adding an entry to the log
584 void mission_log_add_entry_multi( int type, const char *pname, const char *sname, int index, fix timestamp, int flags )
585 {
586         log_entry *entry;
587
588         // we'd better be in multiplayer and not the master of the game
589         SDL_assert ( Game_mode & GM_MULTIPLAYER );
590         SDL_assert ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) );
591
592         // mark any entries as obsolete.  Part of the pruning is done based on the type (and name) passed
593         // for a new entry
594         mission_log_obsolete_entries(type, pname);
595
596         entry = &log_entries[last_entry];
597
598         if ( last_entry == MAX_LOG_ENTRIES ){
599                 return;
600         }
601
602         last_entry++;
603
604         entry->type = type;
605         if ( pname ) {
606                 SDL_assert (strlen(pname) < NAME_LENGTH);
607                 SDL_strlcpy(entry->pname, pname, SDL_arraysize(entry->pname));
608         }
609         if ( sname ) {
610                 SDL_assert (strlen(sname) < NAME_LENGTH);
611                 SDL_strlcpy(entry->sname, sname, SDL_arraysize(entry->sname));
612         }
613         entry->index = index;
614
615         entry->flags = flags;
616         entry->timestamp = timestamp;
617 }
618
619 // function to determine is the given event has taken place count number of times.
620
621 int mission_log_get_time_indexed( int type, const char *pname, const char *sname, int count, fix *time)
622 {
623         int i, found;
624         log_entry *entry;
625
626         entry = &log_entries[0];
627         for (i = 0; i < last_entry; i++) {
628                 found = 0;
629                 if ( entry->type == type ) {
630                         // if we are looking for a dock/undock entry, then we don't care about the order in which the names
631                         // were passed into this function.  Count the entry as found if either name matches both in the other
632                         // set.
633                         if ( (type == LOG_SHIP_DOCK) || (type == LOG_SHIP_UNDOCK) ) {
634                                 SDL_assert ( sname );
635                                 if ( (!SDL_strcasecmp(entry->pname, pname) && !SDL_strcasecmp(entry->sname, sname)) || (!SDL_strcasecmp(entry->pname, sname) && !SDL_strcasecmp(entry->sname, pname)) )
636                                         found = 1;
637                         } else {
638                                 // for non dock/undock goals, then the names are important!
639                                 if ( SDL_strcasecmp(entry->pname, pname) )
640                                         goto next_entry;
641                                 if ( !sname || !SDL_strcasecmp(sname, entry->sname) )
642                                         found = 1;
643                         }
644
645                         if ( found ) {
646                                 count--;
647                                 if ( !count ) {
648                                         entry->flags |= MLF_ESSENTIAL;                          // since the goal code asked for this entry, mark it as essential
649                                         if ( time )
650                                                 *time = entry->timestamp;
651                                         return 1;
652                                 }
653                         }
654                 }
655
656 next_entry:
657                 entry++;
658         }
659
660         return 0;
661 }
662
663 // this function determines if the given type of event on the specified
664 // object has taken place yet.  If not, it returns 0.  If it has, the
665 // timestamp that the event happened is returned in the time parameter
666 int mission_log_get_time( int type, const char *pname, const char *sname, fix *time )
667 {
668         return mission_log_get_time_indexed( type, pname, sname, 1, time );
669 }
670
671 void message_log_add_seg(int n, int x, int color, const char *text, int flags = 0)
672 {
673         log_text_seg *seg, **parent;
674
675         if ((n < 0) || (n >= MAX_LOG_LINES))
676                 return;
677
678         parent = &Log_lines[n];
679         while (*parent)
680                 parent = &((*parent)->next);
681
682         seg = (log_text_seg *) malloc(sizeof(log_text_seg));
683         SDL_assert(seg);
684         seg->text = strdup(text);
685         seg->color = color;
686         seg->x = x;
687         seg->flags = flags;
688         seg->next = NULL;
689         *parent = seg;
690 }
691
692 void message_log_add_segs(const char *text, int color, int flags = 0)
693 {
694         char *log_text = NULL, *log_text_ptr = NULL;
695         char *ptr;
696         int w;
697
698         if (!text) {
699                 mprintf(("Why are you passing a NULL pointer to message_log_add_segs?\n"));
700                 return;
701         }
702
703         log_text = strdup(text);
704         log_text_ptr = log_text;
705
706         if (!log_text) {
707                 return;
708         }
709
710         while (1) {
711                 if (X == ACTION_X) {
712                         while (is_white_space(*log_text_ptr))
713                                 log_text_ptr++;
714                 }
715
716                 if ( !log_text_ptr[0] ) {
717                         break;
718                 }
719
720                 if (P_width - X < 1)
721                         ptr = log_text_ptr;
722                 else
723                         ptr = split_str_once(log_text_ptr, P_width - X);
724
725                 if (ptr != log_text_ptr)
726                         message_log_add_seg(Num_log_lines, X, color, log_text_ptr, flags);
727
728                 if (!ptr) {
729                         gr_get_string_size(&w, NULL, log_text_ptr);
730                         X += w;
731                         break;
732                 }
733
734                 Num_log_lines++;
735                 X = ACTION_X;
736                 log_text_ptr = ptr;
737         }
738
739         free(log_text);
740 }
741
742 void message_log_remove_segs(int n)
743 {
744         log_text_seg *ptr, *ptr2;
745
746         if ((n < 0) || (n >= MAX_LOG_LINES))
747                 return;
748
749         ptr = Log_lines[n];
750         while (ptr) {
751                 ptr2 = ptr->next;
752                 free(ptr);
753                 ptr = ptr2;
754         }
755
756         Log_lines[n] = NULL;
757 }
758
759 // pw = total pixel width
760 void message_log_init_scrollback(int pw)
761 {
762         char text[256];
763         log_entry *entry;
764         int i, c, kill, type;
765
766         P_width = pw;
767         mission_log_cull_obsolete_entries();  // compact array so we don't have gaps
768         
769         // initialize the log lines data
770         Num_log_lines = 0;
771         for (i=0; i<MAX_LOG_LINES; i++) {
772                 Log_lines[i] = NULL;
773                 Log_line_timestamps[i] = 0;
774         }
775
776         for (i=0; i<last_entry; i++) {
777                 entry = &log_entries[i];
778
779                 if (entry->flags & MLF_HIDDEN)
780                         continue;
781
782                 // track time of event (normal timestamp milliseconds format)
783                 Log_line_timestamps[Num_log_lines] = (int) ( f2fl(entry->timestamp) * 1000.0f );
784
785                 // Generate subject ship text for entry
786                 if ( entry->flags & MLF_PRIMARY_FRIENDLY ){
787                         c = LOG_COLOR_FRIENDLY;
788                 } else if ( entry->flags & MLF_PRIMARY_HOSTILE ){
789                         c = LOG_COLOR_HOSTILE;
790                 } else if ( (entry->type == LOG_GOAL_SATISFIED) || (entry->type == LOG_GOAL_FAILED) ){
791                         c = LOG_COLOR_BRIGHT;
792                 } else {
793                         c = LOG_COLOR_OTHER;
794                 }
795
796                 if ( (Lcl_gr) && ((entry->type == LOG_GOAL_FAILED) || (entry->type == LOG_GOAL_SATISFIED)) ) {
797                         // in german goal events, just say "objective" instead of objective name
798                         // this is cuz we cant translate objective names
799                         message_log_add_seg(Num_log_lines, OBJECT_X, c, "Einsatzziel");
800                 } else {
801                         message_log_add_seg(Num_log_lines, OBJECT_X, c, entry->pname);
802                 }
803
804                 // now on to the actual message itself
805                 X = ACTION_X;
806                 kill = 0;
807                 if ( entry->flags & MLF_SECONDARY_FRIENDLY ){
808                         c = LOG_COLOR_FRIENDLY;
809                 } else if ( entry->flags & MLF_SECONDARY_HOSTILE ){
810                         c = LOG_COLOR_HOSTILE;
811                 } else {
812                         c = LOG_COLOR_NORMAL;
813                 }
814
815                 switch (entry->type) {
816                         case LOG_SHIP_DESTROYED:
817                                 message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
818                                 if (strlen(entry->sname)) {
819                                         message_log_add_segs(XSTR( "  Kill: ", 405), LOG_COLOR_NORMAL);
820                                         message_log_add_segs(entry->sname, c);
821                                         if (entry->index >= 0) {
822                                                 SDL_snprintf(text, SDL_arraysize(text), NOX(" (%d%%)"), entry->index);
823                                                 message_log_add_segs(text, LOG_COLOR_BRIGHT);
824                                         }
825                                 }
826                                 break;
827
828                         case LOG_SELF_DESTRUCT:
829                                 message_log_add_segs(XSTR( "Self Destructed", 1476), LOG_COLOR_NORMAL);
830                                 break;
831
832                         case LOG_WING_DESTROYED:
833                                 message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
834                                 break;
835
836                         case LOG_SHIP_ARRIVE:
837                                 message_log_add_segs(XSTR( "Arrived", 406), LOG_COLOR_NORMAL);
838                                 break;
839
840                         case LOG_WING_ARRIVE:
841                                 if (entry->index > 1){
842                                         SDL_snprintf(text, SDL_arraysize(text), XSTR( "Arrived (wave %d)", 407), entry->index);
843                                 } else {
844                                         SDL_strlcpy(text, XSTR( "Arrived", 406), SDL_arraysize(text));
845                                 }
846                                 message_log_add_segs(text, LOG_COLOR_NORMAL);
847                                 break;
848
849                         case LOG_SHIP_DEPART:
850                                 message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
851                                 break;
852
853                         case LOG_WING_DEPART:
854                                 message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
855                                 break;
856
857                         case LOG_SHIP_DOCK:
858                                 message_log_add_segs(XSTR( "docked with ", 409), LOG_COLOR_NORMAL);
859                                 message_log_add_segs(entry->sname, c);
860                                 break;
861
862                         case LOG_SHIP_SUBSYS_DESTROYED: {
863                                 int si_index, model_index;
864
865                                 si_index = (int)((entry->index >> 16) & 0xffff);
866                                 model_index = (int)(entry->index & 0xffff);
867
868                                 message_log_add_segs(XSTR( "Subsystem ", 410), LOG_COLOR_NORMAL);
869                                 //message_log_add_segs(entry->sname, LOG_COLOR_BRIGHT);
870                                 const char *subsys_name = Ship_info[si_index].subsystems[model_index].name;
871                                 if (Ship_info[si_index].subsystems[model_index].type == SUBSYSTEM_TURRET) {
872                                         subsys_name = XSTR("Turret", 1487);
873                                 }
874                                 message_log_add_segs(subsys_name, LOG_COLOR_BRIGHT);
875                                 message_log_add_segs(XSTR( " destroyed", 411), LOG_COLOR_NORMAL);
876                                 break;
877                         }
878
879                         case LOG_SHIP_UNDOCK:
880                                 message_log_add_segs(XSTR( "Undocked with ", 412), LOG_COLOR_NORMAL);
881                                 message_log_add_segs(entry->sname, c);
882                                 break;
883
884                         case LOG_SHIP_DISABLED:
885                                 message_log_add_segs(XSTR( "Disabled", 413), LOG_COLOR_NORMAL);
886                                 break;
887
888                         case LOG_SHIP_DISARMED:
889                                 message_log_add_segs(XSTR( "Disarmed", 414), LOG_COLOR_NORMAL);
890                                 break;
891
892                         case LOG_PLAYER_REARM:
893                                 message_log_add_segs(XSTR( " called for rearm", 415), LOG_COLOR_NORMAL);
894                                 break;
895
896                         case LOG_PLAYER_REARM_ABORT:
897                                 message_log_add_segs(XSTR( " aborted rearm", 416), LOG_COLOR_NORMAL);
898                                 break;
899
900                         case LOG_PLAYER_REINFORCEMENT:
901                                 message_log_add_segs(XSTR( "Called in as reinforcement", 417), LOG_COLOR_NORMAL);
902                                 break;
903
904                         case LOG_CARGO_REVEALED:
905                                 SDL_assert( entry->index != -1 );
906                                 message_log_add_segs(XSTR( "Cargo revealed: ", 418), LOG_COLOR_NORMAL);
907                                 message_log_add_segs( Cargo_names[entry->index], LOG_COLOR_BRIGHT );
908                                 break;
909
910                         case LOG_CAP_SUBSYS_CARGO_REVEALED:
911                                 SDL_assert( entry->index != -1 );
912                                 message_log_add_segs(entry->sname, LOG_COLOR_NORMAL);
913                                 message_log_add_segs(XSTR( " subsystem cargo revealed: ", 1488), LOG_COLOR_NORMAL);
914                                 message_log_add_segs( Cargo_names[entry->index], LOG_COLOR_BRIGHT );
915                                 break;
916
917
918                         case LOG_GOAL_SATISFIED:
919                         case LOG_GOAL_FAILED: {
920                                 type = Mission_goals[entry->index].type & GOAL_TYPE_MASK;
921
922                                 // don't display failed bonus goals
923                                 if ( (type == BONUS_GOAL) && (entry->type == LOG_GOAL_FAILED) ) {
924                                         kill = 1;
925                                         break;  // don't display this line
926                                 }
927
928                                 SDL_snprintf( text, SDL_arraysize(text), XSTR( "%s objective ", 419), Goal_type_text(type) );
929                                 if ( entry->type == LOG_GOAL_SATISFIED )
930                                         SDL_strlcat(text, XSTR( "satisfied.", 420), SDL_arraysize(text));
931                                 else
932                                         SDL_strlcat(text, XSTR( "failed.", 421), SDL_arraysize(text));
933
934                                 message_log_add_segs(text, LOG_COLOR_BRIGHT, (entry->type == LOG_GOAL_SATISFIED?LOG_FLAG_GOAL_TRUE:LOG_FLAG_GOAL_FAILED) );
935                                 break;
936                         }       // matches case statement!
937                 }
938
939                 if (kill) {
940                         message_log_remove_segs(Num_log_lines);
941
942                 } else {
943                         if (Num_log_lines < MAX_LOG_LINES)
944                                 Num_log_lines++;
945                 }
946         }
947 }
948
949 void message_log_shutdown_scrollback()
950 {
951         int i;
952
953         for (i=0; i<MAX_LOG_LINES; i++)
954                 message_log_remove_segs(i);
955
956         Num_log_lines = 0;
957 }
958
959 // message_log_scrollback displays the contents of the mesasge log currently much like the HUD
960 // message scrollback system.  I'm sure this system will be overhauled.         
961 void mission_log_scrollback(int line, int list_x, int list_y, int list_w, int list_h)
962 {
963         char buf[256];
964         int y;
965         int font_h = gr_get_font_height();
966         log_text_seg *seg;
967
968         y = 0;
969         while (y + font_h <= list_h) {
970                 if (line >= Num_log_lines)
971                         break;
972
973                 if (Log_line_timestamps[line]) {
974                         gr_set_color_fast(&Color_text_normal);
975                         gr_print_timestamp(list_x + TIME_X, list_y + y, Log_line_timestamps[line]);
976                 }
977
978                 seg = Log_lines[line];
979                 while (seg) {
980                         switch (seg->color) {
981                                 case LOG_COLOR_BRIGHT:
982                                         gr_set_color_fast(&Color_bright);
983                                         break;
984
985                                 case LOG_COLOR_FRIENDLY:
986                                         gr_set_color_fast(&Color_bright_green);
987                                         break;
988
989                                 case LOG_COLOR_HOSTILE:
990                                         gr_set_color_fast(&Color_bright_red);
991                                         break;
992
993                                 case LOG_COLOR_OTHER:
994                                         gr_set_color_fast(&Color_normal);
995                                         break;
996
997                                 default:
998                                         gr_set_color_fast(&Color_text_normal);
999                                         break;
1000                         }
1001
1002                         SDL_strlcpy(buf, seg->text, SDL_arraysize(buf));
1003                         if (seg->x < ACTION_X)
1004                                 gr_force_fit_string(buf, 256, ACTION_X - OBJECT_X - 8);
1005                         else
1006                                 gr_force_fit_string(buf, 256, list_w - seg->x);
1007
1008                         gr_string(list_x + seg->x, list_y + y, buf);
1009
1010                         // possibly "print" some symbols for interesting log entries
1011                         if ( (seg->flags & LOG_FLAG_GOAL_TRUE) || (seg->flags & LOG_FLAG_GOAL_FAILED) ) {
1012                                 int i;
1013
1014                                 if ( seg->flags & LOG_FLAG_GOAL_FAILED )
1015                                         gr_set_color_fast(&Color_bright_red);
1016                                 else
1017                                         gr_set_color_fast(&Color_bright_green);
1018
1019                                 i = list_y + y + font_h / 2 - 1;
1020                                 gr_circle(list_x + TIME_X - 6, i, 5);
1021
1022                                 gr_set_color_fast(&Color_bright);
1023                                 gr_line(list_x + TIME_X - 10, i, list_x + TIME_X - 8, i);
1024                                 gr_line(list_x + TIME_X - 6, i - 4, list_x + TIME_X - 6, i - 2);
1025                                 gr_line(list_x + TIME_X - 4, i, list_x + TIME_X - 2, i);
1026                                 gr_line(list_x + TIME_X - 6, i + 2, list_x + TIME_X - 6, i + 4);
1027                         }
1028
1029                         seg = seg->next;
1030                 }
1031
1032                 y += font_h;
1033                 line++;
1034         }
1035 }
1036