Assimilation Tutorial #4: MOTD plus Highscores


 

Posted by WarZone (209.223.137.*) on April 28, 1999 at 02:11:21:

This entire tutorial is aviable in TXT format via email if you want it (the line breaks get killed in the html). There are no restrictions on using this particular code, mainly due to the fact that Raven (of the Viking mod) did 99% of the highscores code. And, because there are other motd tutorials already out there. So, it would be silly to restrict its use.

Okay, let's get started! There's a decent ammount of commentary in this tutorial, so hopefully even beginners will be able to follow along. And, if you actually follow the code, rather than cut-n-paste, you should learn a lot by doing this tutorial.


First save the next entire section to highscore.c and add it to your project/makefile.

_______________
<<highscore.c>>
#include "g_local.h"

#define SCORESTOKEEP 15  
     /* # of people to save in the highscore file
        if you changed this number, you will need to delete your old
        highscore files! */
#define SCORE resp.points  
     // variable to use for score tracking
#define MOD_NAME "assim"   
     // the directory you use for your mod
#define MOD_TITLE "-- Assimilation 2.5 --"
     /* this text is appended to the end of the high
        score file */

typedef struct {
        char    netname[16];
        int            score;
        time_t  dt_occured;
} HS_STRUCT;

// room to hold max # of players
HS_STRUCT g_TopScores[SCORESTOKEEP];

// used for the
int MP_Sort(const void *a, const void *b)
{
  return (((HS_STRUCT *)b)->score - ((HS_STRUCT *)a)->score);
}

void highscore (void)
{
  int i,   count=0;
  edict_t  *ent;
  edict_t  *cl_ent;
  FILE     *HS_file;
  char     filename[32], filename2[32];

  i =  sprintf(filename, "./");
  i += sprintf(filename + i, MOD_NAME);
  i += sprintf(filename + i, "/hs/%s_hs.bin", level.mapname);

  i =  sprintf(filename2, "./");
  i += sprintf(filename2 + i, "assim");
  i += sprintf(filename2 + i, "/hs/%s_hs.txt", level.mapname);

  // is the high score file for this map already loaded?

  // no - load it
  if(HS_file = fopen(filename, "rb"))
  {
    fread(g_TopScores, sizeof(g_TopScores[0]) * SCORESTOKEEP, 1, HS_file);
    fclose(HS_file);
  }
  else
  {
    // if it doesnt exist, create it with the top current players in it
    memset(g_TopScores, 0, sizeof(g_TopScores));
    count=0;
    for (i = 0 ; i < maxclients->value; i++)
    {
      cl_ent = g_edicts + 1 + i;
      if (cl_ent->inuse)
      {
        strcpy(g_TopScores[count].netname, game.clients[i].pers.netname);
        g_TopScores[count].score = game.clients[i].SCORE;
        g_TopScores[count].dt_occured = time(NULL);
        count++;
        if (count >= SCORESTOKEEP)
          break;
      }
    }
    // sort it
    qsort(g_TopScores, sizeof(g_TopScores)/sizeof(g_TopScores[0]), sizeof(g_TopScores[0]), MP_Sort);
  }


  for (ent = g_edicts + 1; ent <= g_edicts + (int)maxclients->value; ent++)
  {
    if (!ent->inuse || !ent->client)
      continue;

    // HS_file loaded - see if any entity made the list
    for (i = 0 ; i < maxclients->value ; i++)
    {
      cl_ent = g_edicts + 1 + i;
      if (cl_ent->inuse)
      {
        // if it beat the lowest, keep score
        if (game.clients[i].SCORE > g_TopScores[SCORESTOKEEP-1].score)
        {
          strcpy(g_TopScores[SCORESTOKEEP-1].netname, game.clients[i].pers.netname);
          g_TopScores[SCORESTOKEEP-1].score = game.clients[i].SCORE;
          g_TopScores[SCORESTOKEEP-1].dt_occured = time(NULL);
          // sort it
          qsort(g_TopScores, sizeof(g_TopScores)/sizeof(g_TopScores[0]), sizeof(g_TopScores[0]), MP_Sort);
        }
      }
    }
  } // end main for loop

  // write the high score HS_file
  HS_file = fopen(filename, "wb");
  if (HS_file != NULL)
  {
    fwrite(g_TopScores, sizeof(g_TopScores[0]), SCORESTOKEEP, HS_file);
    fclose(HS_file);
  }

  // print top scores to a readable file
  HS_file = fopen(filename2, "wt");
  if (HS_file != NULL)
  {
    fprintf(HS_file,"Top %d Scores for %s\n", SCORESTOKEEP, level.mapname);
    for (i = 0; i < SCORESTOKEEP; i++)
    fprintf(HS_file, "%2d - %-16.16s %4d\n", i+1, g_TopScores[i].netname, g_TopScores[i].score);
    fprintf(HS_file,"%s\n", MOD_TITLE);
    fclose(HS_file);
  }
}
<<highscore.c>>
---------------


Now, open up g_local.h and Search for "showscores". The line should look like this:

 

  g_local.h ->  qboolean    showscores;

 

Change it to this:

 

  g_local.h ->  int         showscores;

 

Now, scroll down to the very end of that file and add these two new variables:

 

char  motd[1400]; //master motd variable to store the motd file contents
char  highscores[1400]; //master variable to store the highscore printout

 

Close, and save. Now open p_hud.c and Search for the "Cmd_Score_f" function. Replace the old function with this one:

 

void Cmd_Score_f (edict_t *ent)
{
  ent->client->showinventory = false;
  ent->client->showhelp = false;

  if (!deathmatch->value && !coop->value)
    return;

  switch (ent->client->showscores)
  {
    case 0: //nothing showing..
      ent->client->showscores = 1; //show the standard DM scoreboard
      break;
    case 1: //score board showing..
      if (level.intermissiontime)
        ent->client->showscores = 3; //show the highscores
      else
        ent->client->showscores = 0; //show nothing
      break;
    case 2: //motd showing
      ent->client->showscores = 0; //show nothing
      break;
    case 3: //highscores showing
      if (level.intermissiontime)
        ent->client->showscores = 1; //show the standard DM scoreboard
      break;
    default: //???
      ent->client->showscores = 0; //show nothing
      break;
  }

  switch (ent->client->showscores)
  {
    case 1:
      DeathmatchScoreboard (ent);
      break;
    case 2:
      ShowMOTD (ent);
      break;
    case 3:
      ShowHighScores (ent);
      gi.unicast (ent, true); //update the score display (is this necessary?)
      break;
    default:
      break;
  }
}

 

Now there's a bunch of changes that need to be made to the original source to accomidate the new definition of "showscores". First open up p_client.c and Search for the "player_die" function and add the indicated line into the function:

 

  player_die()
  {
  ...
      if (deathmatch->value)
      {
+       self->client->showscores = 0; //clear the hud
        Cmd_Help_f (self);    // show score board
      }
  ...
  }

Now Search for the "ClientBeginDeathmatch" function and add the indicated line:

  ClientBeginDeathMatch()
  {
  ...
+   ent->client->showscores = 4; //make the MOTD show up

    // send effect
    gi.WriteByte (svc_muzzleflash);
    gi.WriteShort (ent-g_edicts);
    gi.WriteByte (MZ_LOGIN);
    gi.multicast (ent->s.origin, MULTICAST_PVS);
  ...
  }


Now save and close. Now open up p_view.c and search for the "ClientEndServerFrame" function. Then replace the following lines:

 

  // if the scoreboard is up, update it
  if (ent->client->showscores && !(level.framenum & 31) )
  {
    DeathmatchScoreboardMessage (ent, ent->enemy);
    gi.unicast (ent, false);
  }

 

With this code:

 

  // if the scoreboard is up, update it
  if (ent->client->showscores > 0 && !(level.framenum & 31))
  {                         
    switch (ent->client->showscores)
    {
      case 1:
        DeathmatchScoreboardMessage (ent, ent->enemy);
        break;
      case 2:
        ShowMOTD (ent);
        break;
      default: //highscores, etc. don't need to be updated
        break;
    }
    gi.unicast (ent, false);
  }

Now the only thing left to do is add the MOTD functions into your source. Open up p_hud.c and add the following functions to the end of the file:

 

//---begin new code
void LoadMOTD (void)
{
        char    entry[1024];
        char    string[1400];
        int            stringlength;
  int   i, j;

  FILE *motd_file;
  char line[80];

  if (!(motd_file = fopen("assim/motd.txt", "r")))
    return;

  string[0] = 0;
        stringlength = strlen(string);

  Com_sprintf (entry, sizeof(entry),
    "xv 12 yv 8 string2 \"Welcome to <Mod Name> Version <version>\" "); // change this line!!

  j = strlen(entry);
  if (stringlength + j < 1024)
  {
    strcpy (string + stringlength, entry);
    stringlength += j;
  }

  i = 0;
  while ( fgets(line, 80, motd_file) )
  {
    Com_sprintf (entry, sizeof(entry),
      "xv 2 yv %i string \"%s\" ",
      i*8 + 24, line);
    j = strlen(entry);
    if (stringlength + j > 1024)
      break;
    strcpy (string + stringlength, entry);
    stringlength += j;
    i++;
  }
  // be good now ! ... close the file
  fclose(motd_file);

  j = strlen(entry);
  if (stringlength + j < 1024)
  {
    strcpy (string + stringlength, entry);
    stringlength += j;
  }

  Com_sprintf (motd, sizeof(motd), string);
}

void LoadHighScores (void)
{
        char    entry[1024];
        char    string[1400];
        int            stringlength;
  int   i, j;

  FILE    *motd_file;
  char    filename[32];
  char    line[80];

  i =  sprintf(filename, "./");
  i += sprintf(filename + i, "assim");
  i += sprintf(filename + i, "/hs/%s_hs.txt", level.mapname);

  if (!(motd_file = fopen(filename, "r")))
    return;

  string[0] = 0;
        stringlength = strlen(string);

  i = 0;
  while ( fgets(line, 80, motd_file) )
  {
    Com_sprintf (entry, sizeof(entry),
      "xv 2 yv %i string \"%s\" ",
      i*8 + 24, line);
    j = strlen(entry);
    if (stringlength + j > 1024)
      break;
    strcpy (string + stringlength, entry);
    stringlength += j;
    i++;
  }
  // be good now ! ... close the file
  fclose(motd_file);

  j = strlen(entry);
  if (stringlength + j < 1024)
  {
    strcpy (string + stringlength, entry);
    stringlength += j;
  }

  Com_sprintf (highscores, sizeof(highscores), string);
}

void ShowMOTD (edict_t *ent)
{
  gi.WriteByte (svc_layout);
  gi.WriteString (motd); // send the MOTD to the client
}

void ShowHighScores (edict_t *ent)
{
  gi.WriteByte (svc_layout);
  gi.WriteString (highscores); // send the high scores to the client
}
//---end new code

 

You could actually cut-n-paste the LoadHighScores() function into highscore.c if you want, but it really doesn't matter where you put it.

 

Now that the features of the High Score list and MOTD are in place, its time to actually add the function calls that will make it all happen. First open g_save.c and add these two lines to the very end of the "InitGame" function.

 

  InitGame()
  {
  ...
    LoadMOTD();
    LoadHighScores();
  }

 

Save and close. Now open p_hud.c again and find the function "BeginIntermission" and add the indicated lines:

 

  BeginIntermission()
  {
  ...
+   highscore(); // first add players to the highscores list
+   LoadHighScores(); // then load the new list.

    level.intermissiontime = level.time;
    level.changemap = targ->map;
 
    if (strstr(level.changemap, "*"))
  ...
  }

 

Now find the "MoveClientToIntermission" function (still in p_hud.c) and add the indicated lines to the end of the function:

 

  MoveClientToIntermission()
  {
  ...
+   if (deathmatch->value || coop->value)
+   {
+     ent->client->showscores = 5;
+     ShowHighScores (ent);
+     gi.unicast (ent, true);
+   }
  }

 

There! Now save and close every thing! All you need to do now is create a directory called "hs" (stands for highscores) in your mod's directory. And finally create the motd.txt file (also in your mod's directory). Here is a sample motd.txt file:

 

  Welcome to <Mod Name> -- Version <version>

  Be sure to stop by:
  <mod's homepage>

  For the latest news, and updates.
  Press F1 to remove this message

Enjoy!

-- WarZone