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