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