]> icculus.org git repositories - taylor/freespace2.git/blob - src/mission/missionlog.cpp
clean up and simplify effects and updating
[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 = &log_entries[i];
356                                         entry->flags |= MLF_OBSOLETE;
357                                 }
358
359                                 mission_log_cull_obsolete_entries();
360                         }
361                 }
362         }
363 }
364
365 // assigns flag values to an entry based on the team value passed in
366 void mission_log_flag_team( log_entry *entry, int which_entry, int team )
367 {
368         if ( which_entry == ML_FLAG_PRIMARY ) {
369                 if ( team == TEAM_FRIENDLY )
370                         entry->flags |= MLF_PRIMARY_FRIENDLY;
371                 else
372                         entry->flags |= MLF_PRIMARY_HOSTILE;
373
374         } else if ( which_entry == ML_FLAG_SECONDARY ) {
375                 if ( team == TEAM_FRIENDLY )
376                         entry->flags |= MLF_SECONDARY_FRIENDLY;
377                 else
378                         entry->flags |= MLF_SECONDARY_HOSTILE;
379
380         } else
381                 Int3();         // get allender -- impossible type
382 }
383
384 // following function adds an entry into the mission log.
385 // pass a type and a string which indicates the object
386 // that this event is for.  Don't add entries with this function for multiplayer
387 void mission_log_add_entry(int type, const char *pname, const char *sname, int info_index)
388 {
389         log_entry *entry;       
390
391         // multiplayer clients don't use this function to add log entries -- they will get
392         // all their info from the host
393         if ( (Game_mode & GM_MULTIPLAYER) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
394                 return;
395         }
396
397 #ifndef NDEBUG
398         int last_entry_save = last_entry;
399 #endif
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         entry->type = type;
412         if ( pname ) {
413                 SDL_assert (strlen(pname) < NAME_LENGTH);
414                 SDL_strlcpy(entry->pname, pname, SDL_arraysize(entry->pname));
415         } else
416                 SDL_strlcpy( entry->pname, EMPTY_LOG_NAME, SDL_arraysize(entry->pname) );
417
418         if ( sname ) {
419                 SDL_assert (strlen(sname) < NAME_LENGTH);
420                 SDL_strlcpy(entry->sname, sname, SDL_arraysize(entry->sname));
421         } else
422                 SDL_strlcpy( entry->sname, EMPTY_LOG_NAME, SDL_arraysize(entry->sname) );
423
424         entry->index = info_index;
425         entry->flags = 0;
426
427         // determine the contents of the flags member based on the type of entry we added.  We need to store things
428         // like team for the primary and (possibly) secondary object for this entry.
429         switch ( type ) {
430         int index, si;
431
432         case LOG_SHIP_DESTROYED:
433         case LOG_SHIP_ARRIVE:
434         case LOG_SHIP_DEPART:
435         case LOG_SHIP_DOCK:
436         case LOG_SHIP_SUBSYS_DESTROYED:
437         case LOG_SHIP_UNDOCK:
438         case LOG_SHIP_DISABLED:
439         case LOG_SHIP_DISARMED:
440         case LOG_SELF_DESTRUCT:
441                 // multiplayer. callsign is passed in for ship destroyed and self destruct
442                 if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(pname) >= 0)){
443                         int np_index = multi_find_player_by_callsign(pname);
444                         index = multi_get_player_ship( np_index );
445                 } else {
446                         index = ship_name_lookup( pname );
447                 }
448
449                 SDL_assert ( index != -1 );
450                 if(index < 0){
451                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, TEAM_FRIENDLY );         
452                 } else {
453                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, Ships[index].team );             
454                 }
455
456                 // some of the entries have a secondary component.  Figure out what is up with them.
457                 if ( (type == LOG_SHIP_DOCK) || (type == LOG_SHIP_UNDOCK)) {
458                         if ( sname ) {
459                                 index = ship_name_lookup( sname );
460                                 SDL_assert( index != -1 );
461                                 mission_log_flag_team( entry, ML_FLAG_SECONDARY, Ships[index].team );
462                         }
463                 } else if ( type == LOG_SHIP_DESTROYED ) {
464                         if ( sname ) {
465                                 int team;
466
467                                 // multiplayer, player name will possibly be sent in
468                                 if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_callsign(sname) >= 0)){
469                                         // get the player's ship
470                                         int np_index = multi_find_player_by_callsign(sname);
471                                         int np_ship = multi_get_player_ship(np_index);
472
473                                         if(np_ship != -1){
474                                                 team = Ships[Objects[Net_players[np_index].player->objnum].instance].team;
475                                         }
476                                         // argh. badness
477                                         else {
478                                                 team = TEAM_FRIENDLY;
479                                         }
480                                 } else {
481                                         index = ship_name_lookup( sname );
482                                         // no ship, then it probably exited -- check the exited 
483                                         if ( index == -1 ) {
484                                                 index = ship_find_exited_ship_by_name( sname );
485                                                 if ( index == -1 ) {
486                                                 //      Int3();         // get allender.  name of object who killed ship appears to be bogus!!!
487                                                         break;
488                                                 }
489                                                 team = Ships_exited[index].team;
490                                         } else {
491                                                 team = Ships[index].team;
492                                         }
493                                 }
494
495                                 mission_log_flag_team( entry, ML_FLAG_SECONDARY, team );
496                         } else {
497                                 nprintf(("missionlog", "No secondary name for ship destroyed log entry!\n"));
498                         }
499                 } else if ( (type == LOG_SHIP_SUBSYS_DESTROYED) && (Ship_info[Ships[index].ship_info_index].flags & SIF_SMALL_SHIP) ) {
500                         // make subsystem destroyed entries for small ships hidden
501                         entry->flags |= MLF_HIDDEN;
502                 } else if ( (type == LOG_SHIP_ARRIVE) && (Ships[index].wingnum != -1 ) ) {
503                         // arrival of ships in wings don't display
504                         entry->flags |= MLF_HIDDEN;
505                 }
506                 break;
507
508         case LOG_WING_DESTROYED:
509         case LOG_WING_DEPART:
510         case LOG_WING_ARRIVE:
511                 index = wing_name_lookup( pname, 1 );
512                 SDL_assert( index != -1 );
513                 SDL_assert( info_index != -1 );                 // this is the team value
514
515                 // get the team value for this wing.  Departed or destroyed wings will pass the team
516                 // value in info_index parameter.  For arriving wings, get the team value from the
517                 // first ship in the list
518                 if ( type == LOG_WING_ARRIVE ) {
519                         si = Wings[index].ship_index[0];
520                         SDL_assert( si != -1 );
521                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, Ships[si].team );
522                 } else {
523                         mission_log_flag_team( entry, ML_FLAG_PRIMARY, info_index );
524                 }
525
526 #ifndef NDEBUG
527                 // MWA 2/25/98.  debug code to try to find any ships in this wing that have departed.
528                 // scan through all log entries and find at least one ship_depart entry for a ship
529                 // that was in this wing.
530                 if ( type == LOG_WING_DEPART ) {
531                         int i;
532
533                         // if all were destroyed, then don't do this debug code.
534                         if ( Wings[index].total_destroyed == Wings[index].total_arrived_count ){
535                                 break;
536                         }
537
538                         for ( i = 0; i < last_entry; i++ ) {
539                                 if ( log_entries[i].type != LOG_SHIP_DEPART ){
540                                         continue;
541                                 }
542                                 if( log_entries[i].index == index ){
543                                         break;
544                                 }
545                         }
546                         if ( i == last_entry ){
547                                 Int3();                                         // get Allender -- cannot find any departed ships from wing that supposedly departed.
548                         }
549                 }
550 #endif
551
552                 break;
553
554                 // don't display waypoint done entries
555         case LOG_WAYPOINTS_DONE:
556                 entry->flags |= MLF_HIDDEN;
557                 break;
558
559         default:
560                 break;
561         }
562
563         entry->timestamp = Missiontime;
564
565         // if in multiplayer and I am the master, send this log entry to everyone
566         if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
567                 send_mission_log_packet( last_entry );
568         }
569
570         last_entry++;
571
572 #ifndef NDEBUG
573         if ( !(last_entry % 10) ) {
574                 if ( (last_entry > LOG_HALFWAY_REPORT_NUM) && (last_entry > last_entry_save) ){
575                         nprintf(("missionlog", "new highwater point reached for mission log (%d entries).\n", last_entry));
576                 }
577         }
578 #endif
579
580 }
581
582 // function, used in multiplayer only, which adds an entry sent by the host of the game, into
583 // the mission log.  The index of the log entry is passed as one of the parameters in addition to
584 // the normal parameters used for adding an entry to the log
585 void mission_log_add_entry_multi( int type, const char *pname, const char *sname, int index, fix timestamp, int flags )
586 {
587         log_entry *entry;
588
589         // we'd better be in multiplayer and not the master of the game
590         SDL_assert ( Game_mode & GM_MULTIPLAYER );
591         SDL_assert ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) );
592
593         // mark any entries as obsolete.  Part of the pruning is done based on the type (and name) passed
594         // for a new entry
595         mission_log_obsolete_entries(type, pname);
596
597         entry = &log_entries[last_entry];
598
599         if ( last_entry == MAX_LOG_ENTRIES ){
600                 return;
601         }
602
603         last_entry++;
604
605         entry->type = type;
606         if ( pname ) {
607                 SDL_assert (strlen(pname) < NAME_LENGTH);
608                 SDL_strlcpy(entry->pname, pname, SDL_arraysize(entry->pname));
609         }
610         if ( sname ) {
611                 SDL_assert (strlen(sname) < NAME_LENGTH);
612                 SDL_strlcpy(entry->sname, sname, SDL_arraysize(entry->sname));
613         }
614         entry->index = index;
615
616         entry->flags = flags;
617         entry->timestamp = timestamp;
618 }
619
620 // function to determine is the given event has taken place count number of times.
621
622 int mission_log_get_time_indexed( int type, const char *pname, const char *sname, int count, fix *time)
623 {
624         int i, found;
625         log_entry *entry;
626
627         entry = &log_entries[0];
628         for (i = 0; i < last_entry; i++) {
629                 found = 0;
630                 if ( entry->type == type ) {
631                         // if we are looking for a dock/undock entry, then we don't care about the order in which the names
632                         // were passed into this function.  Count the entry as found if either name matches both in the other
633                         // set.
634                         if ( (type == LOG_SHIP_DOCK) || (type == LOG_SHIP_UNDOCK) ) {
635                                 SDL_assert ( sname );
636                                 if ( (!SDL_strcasecmp(entry->pname, pname) && !SDL_strcasecmp(entry->sname, sname)) || (!SDL_strcasecmp(entry->pname, sname) && !SDL_strcasecmp(entry->sname, pname)) )
637                                         found = 1;
638                         } else {
639                                 // for non dock/undock goals, then the names are important!
640                                 if ( SDL_strcasecmp(entry->pname, pname) )
641                                         goto next_entry;
642                                 if ( !sname || !SDL_strcasecmp(sname, entry->sname) )
643                                         found = 1;
644                         }
645
646                         if ( found ) {
647                                 count--;
648                                 if ( !count ) {
649                                         entry->flags |= MLF_ESSENTIAL;                          // since the goal code asked for this entry, mark it as essential
650                                         if ( time )
651                                                 *time = entry->timestamp;
652                                         return 1;
653                                 }
654                         }
655                 }
656
657 next_entry:
658                 entry++;
659         }
660
661         return 0;
662 }
663
664 // this function determines if the given type of event on the specified
665 // object has taken place yet.  If not, it returns 0.  If it has, the
666 // timestamp that the event happened is returned in the time parameter
667 int mission_log_get_time( int type, const char *pname, const char *sname, fix *time )
668 {
669         return mission_log_get_time_indexed( type, pname, sname, 1, time );
670 }
671
672 void message_log_add_seg(int n, int x, int color, const char *text, int flags = 0)
673 {
674         log_text_seg *seg, **parent;
675
676         if ((n < 0) || (n >= MAX_LOG_LINES))
677                 return;
678
679         parent = &Log_lines[n];
680         while (*parent)
681                 parent = &((*parent)->next);
682
683         seg = (log_text_seg *) malloc(sizeof(log_text_seg));
684         SDL_assert(seg);
685         seg->text = strdup(text);
686         seg->color = color;
687         seg->x = x;
688         seg->flags = flags;
689         seg->next = NULL;
690         *parent = seg;
691 }
692
693 void message_log_add_segs(const char *text, int color, int flags = 0)
694 {
695         char *log_text = NULL, *log_text_ptr = NULL;
696         char *ptr;
697         int w;
698
699         if (!text) {
700                 mprintf(("Why are you passing a NULL pointer to message_log_add_segs?\n"));
701                 return;
702         }
703
704         log_text = strdup(text);
705         log_text_ptr = log_text;
706
707         if (!log_text) {
708                 return;
709         }
710
711         while (1) {
712                 if (X == ACTION_X) {
713                         while (is_white_space(*log_text_ptr))
714                                 log_text_ptr++;
715                 }
716
717                 if ( !log_text_ptr[0] ) {
718                         break;
719                 }
720
721                 if (P_width - X < 1)
722                         ptr = log_text_ptr;
723                 else
724                         ptr = split_str_once(log_text_ptr, P_width - X);
725
726                 if (ptr != log_text_ptr)
727                         message_log_add_seg(Num_log_lines, X, color, log_text_ptr, flags);
728
729                 if (!ptr) {
730                         gr_get_string_size(&w, NULL, log_text_ptr);
731                         X += w;
732                         break;
733                 }
734
735                 Num_log_lines++;
736                 X = ACTION_X;
737                 log_text_ptr = ptr;
738         }
739
740         free(log_text);
741 }
742
743 void message_log_remove_segs(int n)
744 {
745         log_text_seg *ptr, *ptr2;
746
747         if ((n < 0) || (n >= MAX_LOG_LINES))
748                 return;
749
750         ptr = Log_lines[n];
751         while (ptr) {
752                 ptr2 = ptr->next;
753                 free(ptr);
754                 ptr = ptr2;
755         }
756
757         Log_lines[n] = NULL;
758 }
759
760 // pw = total pixel width
761 void message_log_init_scrollback(int pw)
762 {
763         char text[256];
764         log_entry *entry;
765         int i, c, kill, type;
766
767         P_width = pw;
768         mission_log_cull_obsolete_entries();  // compact array so we don't have gaps
769         
770         // initialize the log lines data
771         Num_log_lines = 0;
772         for (i=0; i<MAX_LOG_LINES; i++) {
773                 Log_lines[i] = NULL;
774                 Log_line_timestamps[i] = 0;
775         }
776
777         for (i=0; i<last_entry; i++) {
778                 entry = &log_entries[i];
779
780                 if (entry->flags & MLF_HIDDEN)
781                         continue;
782
783                 // track time of event (normal timestamp milliseconds format)
784                 Log_line_timestamps[Num_log_lines] = (int) ( f2fl(entry->timestamp) * 1000.0f );
785
786                 // Generate subject ship text for entry
787                 if ( entry->flags & MLF_PRIMARY_FRIENDLY ){
788                         c = LOG_COLOR_FRIENDLY;
789                 } else if ( entry->flags & MLF_PRIMARY_HOSTILE ){
790                         c = LOG_COLOR_HOSTILE;
791                 } else if ( (entry->type == LOG_GOAL_SATISFIED) || (entry->type == LOG_GOAL_FAILED) ){
792                         c = LOG_COLOR_BRIGHT;
793                 } else {
794                         c = LOG_COLOR_OTHER;
795                 }
796
797                 if ( (Lcl_gr) && ((entry->type == LOG_GOAL_FAILED) || (entry->type == LOG_GOAL_SATISFIED)) ) {
798                         // in german goal events, just say "objective" instead of objective name
799                         // this is cuz we cant translate objective names
800                         message_log_add_seg(Num_log_lines, OBJECT_X, c, "Einsatzziel");
801                 } else {
802                         message_log_add_seg(Num_log_lines, OBJECT_X, c, entry->pname);
803                 }
804
805                 // now on to the actual message itself
806                 X = ACTION_X;
807                 kill = 0;
808                 if ( entry->flags & MLF_SECONDARY_FRIENDLY ){
809                         c = LOG_COLOR_FRIENDLY;
810                 } else if ( entry->flags & MLF_SECONDARY_HOSTILE ){
811                         c = LOG_COLOR_HOSTILE;
812                 } else {
813                         c = LOG_COLOR_NORMAL;
814                 }
815
816                 switch (entry->type) {
817                         case LOG_SHIP_DESTROYED:
818                                 message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
819                                 if (strlen(entry->sname)) {
820                                         message_log_add_segs(XSTR( "  Kill: ", 405), LOG_COLOR_NORMAL);
821                                         message_log_add_segs(entry->sname, c);
822                                         if (entry->index >= 0) {
823                                                 SDL_snprintf(text, SDL_arraysize(text), NOX(" (%d%%)"), entry->index);
824                                                 message_log_add_segs(text, LOG_COLOR_BRIGHT);
825                                         }
826                                 }
827                                 break;
828
829                         case LOG_SELF_DESTRUCT:
830                                 message_log_add_segs(XSTR( "Self Destructed", 1476), LOG_COLOR_NORMAL);
831                                 break;
832
833                         case LOG_WING_DESTROYED:
834                                 message_log_add_segs(XSTR( "Destroyed", 404), LOG_COLOR_NORMAL);
835                                 break;
836
837                         case LOG_SHIP_ARRIVE:
838                                 message_log_add_segs(XSTR( "Arrived", 406), LOG_COLOR_NORMAL);
839                                 break;
840
841                         case LOG_WING_ARRIVE:
842                                 if (entry->index > 1){
843                                         SDL_snprintf(text, SDL_arraysize(text), XSTR( "Arrived (wave %d)", 407), entry->index);
844                                 } else {
845                                         SDL_strlcpy(text, XSTR( "Arrived", 406), SDL_arraysize(text));
846                                 }
847                                 message_log_add_segs(text, LOG_COLOR_NORMAL);
848                                 break;
849
850                         case LOG_SHIP_DEPART:
851                                 message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
852                                 break;
853
854                         case LOG_WING_DEPART:
855                                 message_log_add_segs(XSTR( "Departed", 408), LOG_COLOR_NORMAL);
856                                 break;
857
858                         case LOG_SHIP_DOCK:
859                                 message_log_add_segs(XSTR( "docked with ", 409), LOG_COLOR_NORMAL);
860                                 message_log_add_segs(entry->sname, c);
861                                 break;
862
863                         case LOG_SHIP_SUBSYS_DESTROYED: {
864                                 int si_index, model_index;
865
866                                 si_index = (int)((entry->index >> 16) & 0xffff);
867                                 model_index = (int)(entry->index & 0xffff);
868
869                                 message_log_add_segs(XSTR( "Subsystem ", 410), LOG_COLOR_NORMAL);
870                                 //message_log_add_segs(entry->sname, LOG_COLOR_BRIGHT);
871                                 const char *subsys_name = Ship_info[si_index].subsystems[model_index].name;
872                                 if (Ship_info[si_index].subsystems[model_index].type == SUBSYSTEM_TURRET) {
873                                         subsys_name = XSTR("Turret", 1487);
874                                 }
875                                 message_log_add_segs(subsys_name, LOG_COLOR_BRIGHT);
876                                 message_log_add_segs(XSTR( " destroyed", 411), LOG_COLOR_NORMAL);
877                                 break;
878                         }
879
880                         case LOG_SHIP_UNDOCK:
881                                 message_log_add_segs(XSTR( "Undocked with ", 412), LOG_COLOR_NORMAL);
882                                 message_log_add_segs(entry->sname, c);
883                                 break;
884
885                         case LOG_SHIP_DISABLED:
886                                 message_log_add_segs(XSTR( "Disabled", 413), LOG_COLOR_NORMAL);
887                                 break;
888
889                         case LOG_SHIP_DISARMED:
890                                 message_log_add_segs(XSTR( "Disarmed", 414), LOG_COLOR_NORMAL);
891                                 break;
892
893                         case LOG_PLAYER_REARM:
894                                 message_log_add_segs(XSTR( " called for rearm", 415), LOG_COLOR_NORMAL);
895                                 break;
896
897                         case LOG_PLAYER_REARM_ABORT:
898                                 message_log_add_segs(XSTR( " aborted rearm", 416), LOG_COLOR_NORMAL);
899                                 break;
900
901                         case LOG_PLAYER_REINFORCEMENT:
902                                 message_log_add_segs(XSTR( "Called in as reinforcement", 417), LOG_COLOR_NORMAL);
903                                 break;
904
905                         case LOG_CARGO_REVEALED:
906                                 SDL_assert( entry->index != -1 );
907                                 message_log_add_segs(XSTR( "Cargo revealed: ", 418), LOG_COLOR_NORMAL);
908                                 message_log_add_segs( Cargo_names[entry->index], LOG_COLOR_BRIGHT );
909                                 break;
910
911                         case LOG_CAP_SUBSYS_CARGO_REVEALED:
912                                 SDL_assert( entry->index != -1 );
913                                 message_log_add_segs(entry->sname, LOG_COLOR_NORMAL);
914                                 message_log_add_segs(XSTR( " subsystem cargo revealed: ", 1488), LOG_COLOR_NORMAL);
915                                 message_log_add_segs( Cargo_names[entry->index], LOG_COLOR_BRIGHT );
916                                 break;
917
918
919                         case LOG_GOAL_SATISFIED:
920                         case LOG_GOAL_FAILED: {
921                                 type = Mission_goals[entry->index].type & GOAL_TYPE_MASK;
922
923                                 // don't display failed bonus goals
924                                 if ( (type == BONUS_GOAL) && (entry->type == LOG_GOAL_FAILED) ) {
925                                         kill = 1;
926                                         break;  // don't display this line
927                                 }
928
929                                 SDL_snprintf( text, SDL_arraysize(text), XSTR( "%s objective ", 419), Goal_type_text(type) );
930                                 if ( entry->type == LOG_GOAL_SATISFIED )
931                                         SDL_strlcat(text, XSTR( "satisfied.", 420), SDL_arraysize(text));
932                                 else
933                                         SDL_strlcat(text, XSTR( "failed.", 421), SDL_arraysize(text));
934
935                                 message_log_add_segs(text, LOG_COLOR_BRIGHT, (entry->type == LOG_GOAL_SATISFIED?LOG_FLAG_GOAL_TRUE:LOG_FLAG_GOAL_FAILED) );
936                                 break;
937                         }       // matches case statement!
938                 }
939
940                 if (kill) {
941                         message_log_remove_segs(Num_log_lines);
942
943                 } else {
944                         if (Num_log_lines < MAX_LOG_LINES)
945                                 Num_log_lines++;
946                 }
947         }
948 }
949
950 void message_log_shutdown_scrollback()
951 {
952         int i;
953
954         for (i=0; i<MAX_LOG_LINES; i++)
955                 message_log_remove_segs(i);
956
957         Num_log_lines = 0;
958 }
959
960 // message_log_scrollback displays the contents of the mesasge log currently much like the HUD
961 // message scrollback system.  I'm sure this system will be overhauled.         
962 void mission_log_scrollback(int line, int list_x, int list_y, int list_w, int list_h)
963 {
964         char buf[256];
965         int y;
966         int font_h = gr_get_font_height();
967         log_text_seg *seg;
968
969         y = 0;
970         while (y + font_h <= list_h) {
971                 if (line >= Num_log_lines)
972                         break;
973
974                 if (Log_line_timestamps[line]) {
975                         gr_set_color_fast(&Color_text_normal);
976                         gr_print_timestamp(list_x + TIME_X, list_y + y, Log_line_timestamps[line]);
977                 }
978
979                 seg = Log_lines[line];
980                 while (seg) {
981                         switch (seg->color) {
982                                 case LOG_COLOR_BRIGHT:
983                                         gr_set_color_fast(&Color_bright);
984                                         break;
985
986                                 case LOG_COLOR_FRIENDLY:
987                                         gr_set_color_fast(&Color_bright_green);
988                                         break;
989
990                                 case LOG_COLOR_HOSTILE:
991                                         gr_set_color_fast(&Color_bright_red);
992                                         break;
993
994                                 case LOG_COLOR_OTHER:
995                                         gr_set_color_fast(&Color_normal);
996                                         break;
997
998                                 default:
999                                         gr_set_color_fast(&Color_text_normal);
1000                                         break;
1001                         }
1002
1003                         SDL_strlcpy(buf, seg->text, SDL_arraysize(buf));
1004                         if (seg->x < ACTION_X)
1005                                 gr_force_fit_string(buf, 256, ACTION_X - OBJECT_X - 8);
1006                         else
1007                                 gr_force_fit_string(buf, 256, list_w - seg->x);
1008
1009                         gr_string(list_x + seg->x, list_y + y, buf);
1010
1011                         // possibly "print" some symbols for interesting log entries
1012                         if ( (seg->flags & LOG_FLAG_GOAL_TRUE) || (seg->flags & LOG_FLAG_GOAL_FAILED) ) {
1013                                 int i;
1014
1015                                 if ( seg->flags & LOG_FLAG_GOAL_FAILED )
1016                                         gr_set_color_fast(&Color_bright_red);
1017                                 else
1018                                         gr_set_color_fast(&Color_bright_green);
1019
1020                                 i = list_y + y + font_h / 2 - 1;
1021                                 gr_circle(list_x + TIME_X - 6, i, 5);
1022
1023                                 gr_set_color_fast(&Color_bright);
1024                                 gr_line(list_x + TIME_X - 10, i, list_x + TIME_X - 8, i);
1025                                 gr_line(list_x + TIME_X - 6, i - 4, list_x + TIME_X - 6, i - 2);
1026                                 gr_line(list_x + TIME_X - 4, i, list_x + TIME_X - 2, i);
1027                                 gr_line(list_x + TIME_X - 6, i + 2, list_x + TIME_X - 6, i + 4);
1028                         }
1029
1030                         seg = seg->next;
1031                 }
1032
1033                 y += font_h;
1034                 line++;
1035         }
1036 }
1037