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