1 /* playerlist.c
2  *
3  * Fairly substantial re-write to do variable player lists: Sept 93 DRG Major
4  * rewrite for demand driven updates -> huge speedup: Oct. 94 [007] Major
5  * rewrite to fix some bugs and speed things up     : Jan. 95 [Zork]
6  *
7  *
8  * TODO:
9  *
10  * Sort observers separatly: opserver if (pl->p_flags & PFOBSERV) is true.
11  *
12  */
13 
14 #include "config.h"
15 #include "copyright.h"
16 
17 #include <stdio.h>
18 #include "Wlib.h"
19 #include "defs.h"
20 #include "struct.h"
21 #include "data.h"
22 
23 #include "defaults.h"
24 #include "string_util.h"
25 #include "playerlist.h"
26 
27 #define MaxPlistField 18			 /* The width of the longest
28 						  * * * possible plist field */
29 
30 #define IsEmpty(x) (!x)
31 #define IsNotEmpty(x) (x)
32 #define IsZero(x) (!x)
33 #define IsNotZero(x) (x)
34 #define TRUE 1
35 #define FALSE 0
36 
37 /* Current list of flags       */
38 /* ' ' - White Space           */
39 /* 'b' - Armies Bombed         */
40 /* 'd' - Damage Inflicted (DI) */
41 /* 'k' - Max Kills             */
42 /* 'l' - Login Name            */
43 /* 'n' - Ship Number           */
44 /* 'p' - Planets Taken         */
45 /* 'r' - Ratio                 */
46 /* 's' - Speed                 */
47 /* 'v' - Deaths per hour       */
48 /* 'w' - War staus             */
49 /* 'B' - Bombing               */
50 /* 'C' - Curt (short) rank     */
51 /* 'D' - Defense               */
52 /* 'H' - Hours Played          */
53 /* 'K' - Kills                 */
54 /* 'L' - Losses                */
55 /* 'M' - Display, Host Machine */
56 /* 'N' - Name                  */
57 /* 'O' - Offense               */
58 /* 'P' - Planets               */
59 /* 'R' - Rank                  */
60 /* 'S' - Total Rating (stats)  */
61 /* 'T' - Ship Type             */
62 /* 'V' - Kills per hour        */
63 /* 'W' - Wins                  */
64 
65 /*
66  * Global Variables
67  *
68  * partitionPlist    : Separate the goodies from baddies in the sorted list?
69  * plistCustomLayout : The value of `playerlist' in the defaults file.
70  * plistReorder      : True only if the order of the playerlist is out of date.
71  * plistStyle        : The current style number for the player list.
72  * plistUpdated      : True only if the player list is out of date.
73  * sortMyTeamFirst   : Should my team go first or second in the sorted list?
74  * sortPlayers       : Should the player list be sorted?
75  * updatePlayer[plr] : The playerlist entry for "plr" is old.
76  *
77  * plistHasHostile   : True if "Hostile" is a field in the current list.
78  * plistHasSpeed     : True if "Speed" is true in the current playerlist.
79  */
80 
81 int     partitionPlist = FALSE;
82 char   *plistCustomLayout;
83 int     plistReorder = FALSE;
84 int     plistStyle = 0;
85 int     plistUpdated = FALSE;
86 int     sortMyTeamFirst = FALSE;
87 int     sortPlayers = TRUE;
88 char    updatePlayer[MAXPLAYER + 1];
89 
90 #ifdef PLIST2
91 int     plistHasHostile = FALSE;
92 int     plistHasSpeed = FALSE;
93 
94 #endif /* PLIST2 */
95 
96 
97 /*
98  * Local Variables
99  *
100  * plistLayout       : The fields in the current playerlist.
101  * plistPos[plr]     : The player list row assigned to each player.
102  * plistWidth        : The width of the playerlist.
103  * my_classes        : The letters to go with each ship type.
104  */
105 
106 static char *plistLayout = "";
107 static int plistPos[MAXPLAYER];
108 static int plistWidth = 0;
109 static char *my_classes[NUM_TYPES] =
110 {"SC", "DD", "CA", "BB", "AS", "SB", "GA", "??"};
111 
112 
113 /* Local Functions */
114 
115 static int PlistHeader(char *layout, int doWrite);
116 static void PlistLine(struct player *j, int pos);
117 static void WriteSortedPlist(void);
118 static void WriteUnsortedPlist(void);
119 
120 
121 
InitPlayerList()122 void    InitPlayerList()
123 /* Set the global variables associtated with the player list.  This
124  * should be called when the defaults file is loaded or reloaded.
125  */
126 {
127   /* Find the default style number for the player list. */
128 
129   plistCustomLayout = getdefault("playerlist");
130 
131 
132 
133   /* Select a style number base on the information in the defaults * *
134    * (.xtrekrc) file. */
135 
136   plistStyle = intDefault("playerListStyle", -1);
137 
138   if (IsZero(plistStyle) && IsEmpty(plistCustomLayout))
139     {
140       fputs(
141        "Warning: `playerListStyle' set to 0 but `playerlist' is not set.\n",
142 	     stderr);
143       fputs("\tPlease correct your defaults (.xtrekrc) file.\n", stderr);
144       plistStyle = -1;
145     }
146 
147 #ifdef ALWAYS_USE_PLIST_CUSTOM_LAYOUT
148   if (IsNotEmpty(plistCustomLayout))
149     plistStyle = 0;
150   else
151 #endif
152 
153   if (plistStyle > PLISTLASTSTYLE)
154     {
155       plistStyle = PLISTLASTSTYLE;
156     }
157   else if (plistStyle == -1)
158     {
159       /* Backward compatibility */
160 
161       if (IsNotEmpty(plistCustomLayout))
162 	{
163 	  plistStyle = 0;
164 	}
165       else if (booleanDefault("newPlist", FALSE))
166 	{
167 	  plistStyle = 2;
168 	}
169       else
170 	/* use the old playerlist */
171 	{
172 	  plistStyle = 1;
173 	}
174     }
175 
176 
177   /* Read the partitionPlist flag to tell us whether to break up the player *
178    *
179    * * list with blank lines. */
180 
181   partitionPlist = booleanDefault("partitionPlist", partitionPlist);
182 
183 
184   /* Sort the playerlist? */
185 
186   sortPlayers = booleanDefault("sortPlayers", sortPlayers);
187 
188 
189   /* My team goes first (or second)? */
190 
191   sortMyTeamFirst = booleanDefault("sortMyTeamFirst", sortMyTeamFirst);
192 
193 
194   /* plistUpdate[MAXPLAYER] must always be TRUE because thats how we no when
195    * * * to stop looking for a changed player. */
196 
197   updatePlayer[MAXPLAYER] = TRUE;
198   RedrawPlayerList();
199 }
200 
201 
PlistMaxWidth()202 int     PlistMaxWidth()
203 /* Calculate the maximum width of all the defined player lists so that the
204  * width of the player list window can be defined. */
205 {
206   plistCustomLayout = getdefault("playerlist");
207 
208   if (IsNotEmpty(plistCustomLayout))
209     plistWidth = PlistHeader(plistCustomLayout, FALSE);
210   else
211     plistWidth = 0;
212 
213   if (plistWidth < 83)
214     plistWidth = 83;
215 
216   return plistWidth;
217 }
218 
219 
RedrawPlayerList()220 void    RedrawPlayerList()
221 /* Completly redraw the player list, rather than incrementally updating the
222  * list as with UpdatePlayerList().
223  *
224  * This function should be called if the plistStyle changes or if the
225  * window has just been revealed.
226  *
227  * This function is called automatically from InitPlayerList. */
228 {
229   int     i;
230 
231   if (IsEmpty(me) || !W_IsMapped(playerw))
232     return;
233 
234 
235   /* Translate this style number into a player list layout string. */
236 
237   switch (plistStyle)
238     {
239     case 0:					 /* Custom */
240       if (IsEmpty(plistCustomLayout))		 /* this is chacked for in */
241 	{					 /* InitPlayerList */
242 	  plistLayout = "";
243 	  printf("??? empty plistLayout? This can't happen!\n");
244 	}
245       else
246 	plistLayout = plistCustomLayout;
247       break;
248 
249     case 1:					 /* Old Style */
250       plistLayout = "nTRNKWLr O D d ";
251       break;
252 
253     case 2:					 /* COW Style */
254       plistLayout = "nTR N  K lrSd";
255       break;
256 
257     case 3:					 /* Kill Watch Style */
258       plistLayout = "nTK  RNlr Sd";
259       break;
260 
261     case 4:					 /* BRMH Style */
262       plistLayout = "nTR N  K l M";
263       break;
264 
265     default:
266       fprintf(stderr, "Unknown plistStyle in ChangedPlistStyle().\n");
267       break;
268     }
269 
270 
271   /* Redraw the player list. */
272 
273   W_ClearWindow(playerw);
274   (void) PlistHeader(plistLayout, TRUE);
275 
276   plistReorder = TRUE;
277   plistUpdated = TRUE;
278 
279   for (i = 0; i < MAXPLAYER; i++)
280     updatePlayer[i] = TRUE;
281 
282   UpdatePlistFn();
283 }
284 
285 
UpdatePlistFn()286 void    UpdatePlistFn()
287 /* Update the player list.
288  *
289  * This function should usually be called through the UpdatePlayerList() macro
290  * (see playerlist.h).
291  *
292  * This function works incrimentally.  If a dramatic change has taken place
293  * (i.e. if plistStyle changes) then RedrawPlayerList() should be called
294  * instead. */
295 {
296   int     count;
297   char   *update;
298 
299   plistUpdated = FALSE;
300 
301   if (!W_IsMapped(playerw))
302     return;
303 
304   if (!plistReorder)
305     {
306       /* Redraw the lines that have changed. */
307 
308 #if defined(sgi)
309 #pragma set woff 1167
310 #endif
311       update = updatePlayer-1;
312 
313       for (;;)
314 	{
315 	  /* Find the changed player as quickly as possible. */
316 
317 	  do
318 	    ++update;
319 	  while (!(*update));
320 
321 
322 	  /* Is this a valid player?  Remember updatePlayer[MAXPLAYER] is * *
323 	   * always TRUE to make the above loop more efficient.  */
324 
325 	  count = update - updatePlayer;
326 	  if (count == MAXPLAYER)
327 	    break;
328 
329 	  *update = FALSE;
330 
331 
332 	  /* We should not get updates for free players any more, but just *
333 	   * * incase a packet arrives late... */
334 
335 	  if (players[count].p_status != PFREE)
336 	    PlistLine(players + count, plistPos[count]);
337 	}
338     }
339   else
340     {
341       /* Reorder the player list.  Note that this may not require a full * *
342        * rewrite. */
343 
344       plistReorder = FALSE;
345 
346       if (sortPlayers)
347 	WriteSortedPlist();
348       else
349 	WriteUnsortedPlist();
350     }
351 }
352 
353 
WriteSortedPlist()354 static void WriteSortedPlist()
355 /* Update the order of the players in the list and write out any changes. */
356 {
357   int     row, i, last;
358   struct player *current;
359   int     teamPos[NUMTEAM + 1];
360   int    *pos;
361 
362   static int plistLastRow = -1;
363   static int blankLine = -1;
364   static int blankLine2 = -1;
365   static int myTeam = -1;
366 
367 
368   /* If I have changed team, redraw everything (to get the colors right). */
369 
370   if (remap[me->p_team] != myTeam)
371     {
372       myTeam = remap[me->p_team];
373 
374       for (pos = plistPos + MAXPLAYER - 1; pos >= plistPos; --pos)
375 	*pos = -1;
376     }
377 
378 
379   /* If partitionPlist is false, reset the blank line markers */
380 
381   if (!partitionPlist)
382     {
383       blankLine = -1;
384       blankLine2 = -1;
385     }
386 
387 
388   /* Count the number of players in each team. */
389 
390   for (i = NUMTEAM; i >= 0; --i)
391     teamPos[i] = 0;
392 
393   for (current = players + MAXPLAYER - 1; current >= players; --current)
394     {
395       if (current->p_status != PFREE)
396 	++teamPos[remap[current->p_team]];
397     }
398 
399 
400   /* Find the row after the last player in each team. */
401 
402   last = 1;					 /* The title is line zero */
403 
404   if (sortMyTeamFirst)				 /* My team comes at the top */
405     {
406       last += teamPos[myTeam];
407       teamPos[myTeam] = last;
408 
409       if (partitionPlist)
410 	{
411 	  if (blankLine != last)
412 	    {
413 	      blankLine = last;
414 	      W_ClearArea(playerw, 0, last, plistWidth, 1);
415 	    }
416 
417 	  ++last;
418 	}
419     }
420 
421   for (i = 1; i <= NUMTEAM; ++i)
422     {
423       if (i != myTeam)
424 	{
425 	  last += teamPos[i];
426 	  teamPos[i] = last;
427 	}
428     }
429 
430   if (!sortMyTeamFirst)				 /* My team comes below the *
431 						  * others */
432     {
433       if (partitionPlist)
434 	{
435 	  if (blankLine != last)
436 	    {
437 	      blankLine = last;
438 	      W_ClearArea(playerw, 0, last, plistWidth, 1);
439 	    }
440 
441 	  ++last;
442 	}
443 
444       last += teamPos[myTeam];
445       teamPos[myTeam] = last;
446     }
447 
448   if (myTeam != NOBODY)
449     {
450       /* Separate the goodies from the arriving players. */
451 
452       if (partitionPlist)
453 	{
454 	  if (blankLine2 != last)
455 	    {
456 	      blankLine2 = last;
457 	      W_ClearArea(playerw, 0, last, plistWidth, 1);
458 	    }
459 
460 	  ++last;
461 	}
462 
463       last += teamPos[NOBODY];
464       teamPos[NOBODY] = last;
465     }
466 
467 
468   /* Clear some lines if people have left. */
469 
470   for (row = last; row < plistLastRow; ++row)
471     W_ClearArea(playerw, 0, row, plistWidth, 1);
472 
473   plistLastRow = last;
474 
475 
476 
477   /* Write out each player that has either changed position or has * new *
478    * stats. */
479 
480   for (i = MAXPLAYER - 1, current = players + MAXPLAYER - 1;
481        i >= 0;
482        --i, --current)
483     {
484       if (current->p_status == PFREE)
485 	{
486 	  updatePlayer[i] = FALSE;
487 	  continue;
488 	}
489 
490       row = --(teamPos[remap[current->p_team]]);
491 
492       if ((!updatePlayer[i]) && plistPos[i] == row)
493 	continue;
494 
495       plistPos[i] = row;
496       updatePlayer[i] = FALSE;
497 
498       PlistLine(current, row);
499     }
500 
501 }
502 
503 
WriteUnsortedPlist(void)504 static void WriteUnsortedPlist(void)
505 /*
506  *  Update the order of the players in the list and write out any
507  *  changes.
508  */
509 {
510   int     count;
511   int     pos;
512   char   *update;
513   static int myTeam = -1;
514 
515 
516   /*
517    *  If I have changed team, redraw everything (to get the colors
518    *  right).
519    */
520 
521   if (remap[me->p_team] != myTeam)
522     {
523       myTeam = remap[me->p_team];
524 
525       for (update = updatePlayer + MAXPLAYER
526 	   ; update >= updatePlayer
527 	   ; --update)
528 	{
529 	  *update = TRUE;
530 	}
531     }
532 
533 
534 #if defined(sgi)
535 #pragma set woff 1167
536 #endif
537 	update = updatePlayer - 1;
538 
539   for (;;)
540     {
541       /* Find the changed player as quickly as possible. */
542 
543       do
544 	++update;
545       while (!(*update));
546 
547 
548       /* Is this a valid player?  Remember updatePlayer[MAXPLAYER] * is *
549        * always TRUE to make the above loop more efficient.       */
550 
551       count = update - updatePlayer;
552       if (count == MAXPLAYER)
553 	break;
554 
555 
556       /* Update the player. */
557 
558       *update = FALSE;
559       pos = count + 1;
560       plistPos[count] = pos;
561 
562       if (players[count].p_status != PFREE)
563 	PlistLine(players + count, pos);
564       else
565 	W_ClearArea(playerw, 0, pos, plistWidth, 1);
566     }
567 }
568 
569 
570 
PlistHeader(char * layout,int doWrite)571 static int PlistHeader(char *layout, int doWrite)
572 /* Analyse the heading (field names) for a player list, and set the
573  * plistHasSpeed and plistHasHostile flags.
574  *
575  * If doWrite is TRUE, write the heading to the list.
576  *
577  * RETURN the width of the player list. */
578 {
579   char    header[BUFSIZ];
580   int     num = 0;
581 
582 #ifdef PLIST2
583   plistHasSpeed = FALSE;
584   plistHasHostile = FALSE;
585 #endif /* PLIST2 */
586 
587   for (; IsNotZero(*layout); ++layout)
588     {
589       if (num + MaxPlistField >= BUFSIZ)
590 	{
591 	  /* Assume that we have tested the standard layouts so that only * *
592 	   * custom layouts can be too long. *  * If a standard layout is *
593 	   * found to be too long then some compiler's * code will dump core
594 	   * * here because of an attempt to write over a * constant string. */
595 
596 	  fprintf(stderr, "Playerlist truncated to fit buffer.\n");
597 	  layout = '\0';
598 	  break;
599 	}
600 
601       switch (*layout)
602 	{
603 	case 'n':				 /* Ship Number */
604 	  STRNCPY(&header[num], " No", 3);
605 	  num += 3;
606 	  break;
607 	case 'T':				 /* Ship Type */
608 	  STRNCPY(&header[num], " Ty", 3);
609 	  num += 3;
610 	  break;
611 	case 'C':				 /* Curt (short) Rank */
612 	  STRNCPY(&header[num], " Rank", 5);
613 	  num += 5;
614 	  break;
615 	case 'R':				 /* Rank */
616 	  STRNCPY(&header[num], " Rank      ", 11);
617 	  num += 11;
618 	  break;
619 	case 'N':				 /* Name */
620 	  STRNCPY(&header[num], " Name            ", 17);
621 	  num += 17;
622 	  break;
623 	case 'K':				 /* Kills */
624 	  STRNCPY(&header[num], " Kills", 6);
625 	  num += 6;
626 	  break;
627 	case 'l':				 /* Login Name */
628 	  STRNCPY(&header[num], " Login           ", 17);
629 	  num += 17;
630 	  break;
631 	case 'O':				 /* Offense */
632 	  STRNCPY(&header[num], " Offse", 6);
633 	  num += 6;
634 	  break;
635 	case 'W':				 /* Wins */
636 	  STRNCPY(&header[num], "  Wins", 6);
637 	  num += 6;
638 	  break;
639 	case 'D':				 /* Defense */
640 	  STRNCPY(&header[num], " Defse", 6);
641 	  num += 6;
642 	  break;
643 	case 'L':				 /* Losses */
644 	  STRNCPY(&header[num], "  Loss", 6);
645 	  num += 6;
646 	  break;
647 	case 'S':				 /* Total Rating (stats) */
648 	  STRNCPY(&header[num], " Stats", 6);
649 	  num += 6;
650 	  break;
651 	case 'r':				 /* Ratio */
652 	  STRNCPY(&header[num], " Ratio", 6);
653 	  num += 6;
654 	  break;
655 	case 'd':				 /* Damage Inflicted (DI) */
656 	  STRNCPY(&header[num], "      DI", 8);
657 	  num += 8;
658 	  break;
659 	case ' ':				 /* White Space */
660 	  header[num] = ' ';
661 	  num += 1;
662 	  break;
663 
664 #ifdef PLIST1
665 	case 'B':				 /* Bombing */
666 	  STRNCPY(&header[num], " Bmbng", 6);
667 	  num += 6;
668 	  break;
669 	case 'b':				 /* Armies Bombed */
670 	  STRNCPY(&header[num], " Bmbed", 6);
671 	  num += 6;
672 	  break;
673 	case 'P':				 /* Planets */
674 	  STRNCPY(&header[num], " Plnts", 6);
675 	  num += 6;
676 	  break;
677 	case 'p':				 /* Planets Taken */
678 	  STRNCPY(&header[num], " Plnts", 6);
679 	  num += 6;
680 	  break;
681 	case 'M':				 /* Display, Host Machine */
682 	  STRNCPY(&header[num], " Host Machine    ", 17);
683 	  num += 17;
684 	  break;
685 	case 'H':				 /* Hours Played */
686 	  STRNCPY(&header[num], " Hours ", 7);
687 	  num += 7;
688 	  break;
689 	case 'k':				 /* Max Kills */
690 	  STRNCPY(&header[num], " Max K", 6);
691 	  num += 6;
692 	  break;
693 	case 'V':				 /* Kills per hour */
694 	  STRNCPY(&header[num], "   KPH", 6);
695 	  num += 6;
696 	  break;
697 	case 'v':				 /* Deaths per hour */
698 	  STRNCPY(&header[num], "   DPH", 6);
699 	  num += 6;
700 	  break;
701 #endif
702 
703 #ifdef PLIST2
704 	case 'w':				 /* War staus */
705 	  STRNCPY(&header[num], " War Stat", 9);
706 	  num += 9;
707 	  plistHasHostile = TRUE;
708 	  break;
709 	case 's':				 /* Speed */
710 	  STRNCPY(&header[num], " Sp", 3);
711 	  num += 3;
712 	  plistHasSpeed = TRUE;
713 	  break;
714 #endif
715 
716 	default:
717 	  fprintf(stderr,
718 		  "%c is not an option for the playerlist\n", *layout);
719 	  break;
720 	}
721     }
722 
723   header[num] = '\0';
724 
725   if (doWrite)
726     W_WriteText(playerw, 0, 0, textColor, header, num, W_RegularFont);
727 
728   return num;
729 }
730 
731 
PlistLine(struct player * j,int pos)732 static void PlistLine(struct player *j, int pos)
733 /* Write the player list entry for player `j' on line `pos'. */
734 {
735   char    buf[BUFSIZ];
736   char   *ptr;
737   char   *buffPoint;
738   int     kills, losses, my_ticks;
739   float   pRating, oRating, dRating, bRating, Ratings;
740   float   KillsPerHour, LossesPerHour;		 /* Added 12/27/93 ATH */
741   double  ratio, max_kills;
742 
743   if (j->p_ship.s_type == STARBASE)
744     {
745       kills = j->p_stats.st_sbkills;
746       losses = j->p_stats.st_sblosses;
747       max_kills = j->p_stats.st_sbmaxkills;
748 
749       if (SBhours)
750 	my_ticks = j->p_stats.st_sbticks;
751       else
752 	my_ticks = j->p_stats.st_tticks;
753 
754       KillsPerHour = (float) (my_ticks == 0) ? 0.0 :
755 	  (float) kills *36000.0 / (float) my_ticks;
756 
757       LossesPerHour = (float) (my_ticks == 0) ? 0.0 :
758 	  (float) losses *36000.0 / (float) my_ticks;
759     }
760   else
761     {
762       kills = j->p_stats.st_kills + j->p_stats.st_tkills;
763       losses = j->p_stats.st_losses + j->p_stats.st_tlosses;
764       max_kills = j->p_stats.st_maxkills;
765       my_ticks = j->p_stats.st_tticks;
766       KillsPerHour = (float) (my_ticks == 0) ? 0.0 :
767 	  (float) j->p_stats.st_tkills * 36000.0 / (float) my_ticks;
768       LossesPerHour = (float) (my_ticks == 0) ? 0.0 :
769 	  (float) j->p_stats.st_tlosses * 36000.0 / (float) my_ticks;
770     }
771 
772   if (losses == 0)
773     ratio = (double) kills;
774   else
775     ratio = (double) kills / (double) losses;
776 
777   if (!j->p_stats.st_tticks)
778     {
779       oRating = pRating = bRating = Ratings = 0.0;
780       dRating = defenseRating(j);
781     }
782   else
783     {
784       oRating = offenseRating(j);
785       pRating = planetRating(j);
786       bRating = bombingRating(j);
787       Ratings = oRating + pRating + bRating;
788       dRating = defenseRating(j);
789       if ((j->p_ship.s_type == STARBASE) && (SBhours))
790 	{					 /* If SB, show KPH for * *
791 						  * offense etc. */
792 	  oRating = KillsPerHour;
793 	  dRating = LossesPerHour;
794 	}
795     }
796 
797 
798   buffPoint = buf;
799 
800   for (ptr = plistLayout; IsNotZero(*ptr); ++ptr)
801     {
802       *(buffPoint++) = ' ';
803 
804       switch (*ptr)
805 	{
806 	case 'n':				 /* Ship Number */
807 	  if (j->p_status != PALIVE)
808 	    {
809 	      *(buffPoint++) = ' ';
810 	      *(buffPoint++) = shipnos[j->p_no];
811 	    }
812 	  else
813 	    {
814 	      *(buffPoint++) = teamlet[j->p_team];
815 	      *(buffPoint++) = shipnos[j->p_no];
816 	    }
817 
818 	  break;
819 
820 	case 'T':				 /* Ship Type */
821 	  if (j->p_status != PALIVE)
822 	    {
823 	      *(buffPoint++) = ' ';
824 	      *(buffPoint++) = ' ';
825 	    }
826 	  else
827 	    {
828 	      *(buffPoint++) = my_classes[j->p_ship.s_type][0];
829 	      *(buffPoint++) = my_classes[j->p_ship.s_type][1];
830 	    }
831 
832 	  break;
833 
834 	case 'C':				 /* Curt (short) Rank */
835 	  format(buffPoint, (j->p_stats.st_rank < nranks) ?
836 		 ranks[j->p_stats.st_rank].cname : "", 4, 0);
837 	  buffPoint += 4;
838 	  break;
839 
840 	case 'R':				 /* Rank */
841 	  format(buffPoint, (j->p_stats.st_rank < nranks) ?
842 		 ranks[j->p_stats.st_rank].name : "", 10, 0);
843 	  buffPoint += 10;
844 	  break;
845 
846 	case 'N':				 /* Name */
847 	  format(buffPoint, j->p_name, 16, 0);
848 	  buffPoint += 16;
849 	  break;
850 
851 	case 'K':				 /* Kills */
852 	  if (j->p_kills == 0.0) {
853 	    format(buffPoint, "     ", 5, 0);
854 	  } else {
855 	  if (j->p_kills > 100.0)
856 	    ftoa(j->p_kills, buffPoint - 1, 0, 3, 2);
857 	  else
858 	    ftoa(j->p_kills, buffPoint, 0, 2, 2);
859 	  }
860 	  buffPoint += 5;
861 	  break;
862 
863 	case 'l':				 /* Login Name */
864 	  format(buffPoint, j->p_login, 16, 0);
865 	  buffPoint += 16;
866 	  break;
867 
868 	case 'O':				 /* Offense */
869 	  ftoa(oRating, buffPoint, 0, 2, 2);
870 	  buffPoint += 5;
871 	  break;
872 
873 	case 'W':				 /* Wins */
874 	  itoapad(kills, buffPoint, 0, 5);
875 	  buffPoint += 5;
876 	  break;
877 
878 	case 'D':				 /* Defense */
879 	  ftoa(dRating, buffPoint, 0, 2, 2);
880 	  buffPoint += 5;
881 	  break;
882 
883 	case 'L':				 /* Losses */
884 	  itoapad(losses, buffPoint, 0, 5);
885 	  buffPoint += 5;
886 	  break;
887 
888 	case 'S':				 /* Total Rating (stats) */
889 	  ftoa(Ratings, buffPoint, 0, 2, 2);
890 	  buffPoint += 5;
891 	  break;
892 
893 	case 'r':				 /* Ratio */
894 	  ftoa(ratio, buffPoint, 0, 2, 2);
895 	  buffPoint += 5;
896 	  break;
897 
898 	case 'd':				 /* Damage Inflicted (DI) */
899 	  ftoa(Ratings * (j->p_stats.st_tticks / 36000.0),
900 	       buffPoint, 0, 4, 2);
901 	  buffPoint += 7;
902 	  break;
903 
904 	case ' ':				 /* White Space */
905 	  break;
906 
907 #ifdef PLIST1
908 	case 'B':				 /* Bombing */
909 	  ftoa(bRating, buffPoint, 0, 2, 2);
910 	  buffPoint += 5;
911 	  break;
912 
913 	case 'b':				 /* Armies Bombed */
914 	  itoapad(j->p_stats.st_tarmsbomb + j->p_stats.st_armsbomb,
915 		  buffPoint, 0, 5);
916 	  buffPoint += 5;
917 	  break;
918 
919 	case 'P':				 /* Planets */
920 	  ftoa(pRating, buffPoint, 0, 2, 2);
921 	  buffPoint += 5;
922 	  break;
923 
924 	case 'p':				 /* Planets Taken */
925 	  itoapad(j->p_stats.st_tplanets + j->p_stats.st_planets,
926 		  buffPoint, 0, 5);
927 	  buffPoint += 5;
928 	  break;
929 
930 	case 'M':				 /* Display, Host Machine */
931 	  format(buffPoint, j->p_monitor, 16, 0);
932 	  buffPoint += 16;
933 	  break;
934 
935 	case 'H':				 /* Hours Played */
936 	  ftoa(my_ticks / 36000.0, buffPoint, 0, 3, 2);
937 	  buffPoint += 6;
938 	  break;
939 
940 	case 'k':				 /* Max Kills  */
941 	  ftoa(max_kills, buffPoint, 0, 2, 2);
942 	  buffPoint += 5;
943 	  break;
944 
945 	case 'V':				 /* Kills Per Hour  */
946 	  ftoa(KillsPerHour, buffPoint, 0, 3, 1);
947 	  buffPoint += 5;
948 	  break;
949 
950 	case 'v':				 /* Deaths Per Hour  */
951 	  ftoa(LossesPerHour, buffPoint, 0, 3, 1);
952 	  buffPoint += 5;
953 	  break;
954 #endif
955 
956 #ifdef PLIST2
957 	case 'w':				 /* War staus */
958 	  if (j->p_swar & me->p_team)
959 	    STRNCPY(buffPoint, "WAR     ", 8);
960 	  else if (j->p_hostile & me->p_team)
961 	    STRNCPY(buffPoint, "HOSTILE ", 8);
962 	  else
963 	    STRNCPY(buffPoint, "PEACEFUL", 8);
964 
965 	  buffPoint += 8;
966 	  break;
967 
968 	case 's':				 /* Speed */
969 	  itoapad(j->p_speed, buffPoint, 0, 2);
970 	  buffPoint += 2;
971 	  break;
972 #endif
973 
974 	default:
975 	  break;
976 	}
977     }
978 
979   *buffPoint = '\0';
980   W_WriteText(playerw, 0, pos, playerColor(j),
981 	      buf, buffPoint - buf, shipFont(j));
982 }
983