diff -urN xpilot-4.5.4.orig/Local.config xpilot-4.5.4/Local.config
--- xpilot-4.5.4.orig/Local.config	Sun Jul 14 16:08:26 2002
+++ xpilot-4.5.4/Local.config	Fri Jul 19 03:53:47 2002
@@ -192,8 +192,8 @@
       VERSION_MINOR = 5
  VERSION_PATCHLEVEL = 4
     VERSION_WINDOWS = 13
-     VERSION_STATUS = 
-        RELEASEDATE = Jul 15, 2002
+     VERSION_STATUS = rank1
+        RELEASEDATE = Jul 19, 2002
 
 
 /*
diff -urN xpilot-4.5.4.orig/README.rank xpilot-4.5.4/README.rank
--- xpilot-4.5.4.orig/README.rank	Thu Jan  1 01:00:00 1970
+++ xpilot-4.5.4/README.rank	Fri Jul 19 03:53:47 2002
@@ -0,0 +1,30 @@
+(Most of this text is from the original Svan ranking patch,
+ not written by me //Adamel)
+
+How to use the server.
+
+I suppose the important part is the rankingstuff.
+You control it with two different environmentvariables:
+XPILOTRANKINGPAGE and XPILOTSCOREFILE
+
+if XPILOTSCOREFILE is set and contains a filename then the server
+will remember the scores of the 400 most recent players and use that
+file to save the scores between server uptimes.
+
+if XPILOTRANKINGPAGE is set and contains a filename then the server
+will write a webpage with the rankings in that file.
+
+example: on svan I have:
+export XPILOTSCOREFILE=~/lib/xpilot/scores
+export XPILOTRANKINGPAGE=~/public_html/xpilot/ranks.html
+
+To reset the scores, simply delete the XPILOTSCOREFILE file.
+
+The server should also automatically pause inactive players but I do not think
+that works very well.
+
+And I know there are bugs in the code that counts deaths/kills'n'stuff, cause
+sometimes you get wierd "Deadliest player" values.
+Guess if I'm gonna root them out.
+
+Anything else I've forgotten.
diff -urN xpilot-4.5.4.orig/src/common/config.c xpilot-4.5.4/src/common/config.c
--- xpilot-4.5.4.orig/src/common/config.c	Sat Jan 26 14:01:01 2002
+++ xpilot-4.5.4/src/common/config.c	Fri Jul 19 03:53:47 2002
@@ -48,7 +48,7 @@
 #    ifdef _WINDOWS
 #         define DEFAULT_MAP		"default.xp"
 #    else
-#         define DEFAULT_MAP		"globe.xp"
+#         define DEFAULT_MAP		"teamcup.xp"
 #    endif
 #endif
 
diff -urN xpilot-4.5.4.orig/src/common/version.h xpilot-4.5.4/src/common/version.h
--- xpilot-4.5.4.orig/src/common/version.h	Sun Jul 14 16:08:26 2002
+++ xpilot-4.5.4/src/common/version.h	Fri Jul 19 03:53:47 2002
@@ -28,15 +28,15 @@
 #if defined(__hpux)
 #   pragma COPYRIGHT_DATE	"1991-2002"
 #   pragma COPYRIGHT		"Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
-#   pragma VERSIONID		"XPilot 4.5.4"
+#   pragma VERSIONID		"XPilot 4.5.4rank1"
 #endif
 
-#define VERSION			"4.5.4"
+#define VERSION			"4.5.4rank1"
 #ifdef	_WINDOWS
-#define	TITLE			"4.5.4-NT13"
+#define	TITLE			"4.5.4rank1-NT13"
 #define	VERSION_WINDOWS	"13"
 #else
-#define TITLE			"XPilot 4.5.4"
+#define TITLE			"XPilot 4.5.4rank1"
 #endif
 #define AUTHORS			"Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
 #define COPYRIGHT		"Copyright © 1991-2002 by Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
diff -urN xpilot-4.5.4.orig/src/server/Imakefile xpilot-4.5.4/src/server/Imakefile
--- xpilot-4.5.4.orig/src/server/Imakefile	Mon Jan  7 21:49:25 2002
+++ xpilot-4.5.4/src/server/Imakefile	Fri Jul 19 03:53:47 2002
@@ -31,7 +31,7 @@
  * or in the Local.config file.
  *
  */
-DEFINES = \
+DEFINES = -DLOCALGURU=\"$(LOCALGURU)\" \
        $(DEFS_LOG) $(DEFS_SILENT) $(DEFS_SERVER_SOUND) \
        $(DEFS_PLOCKSERVER) $(DEFS_OTHER) $(DEFS_DEBUG)
 INCLUDES = $(AUDIOINC) -I$(COMMON) -I../lib/
@@ -48,7 +48,7 @@
 	robot.c robotdef.c rules.c \
 	saudio.c sched.c score.c server.c \
 	ship.c shot.c showtime.c stratbot.c \
-	tuner.c update.c walls.c wildmap.c
+	tuner.c update.c walls.c wildmap.c rank.c
 
 # keep this variable consistent with its counterpart in ../Makefile.std
 OBJS = \
@@ -62,7 +62,7 @@
 	robot.o robotdef.o rules.o \
 	saudio.o sched.o score.o server.o \
 	ship.o shot.o showtime.o stratbot.o \
-	tuner.o update.o walls.o wildmap.o
+	tuner.o update.o walls.o wildmap.o rank.o
 
 DEPLIBS = $(COMMON)libxpcommon.a 
 
diff -urN xpilot-4.5.4.orig/src/server/collision.c xpilot-4.5.4/src/server/collision.c
--- xpilot-4.5.4.orig/src/server/collision.c	Sun Apr 21 22:31:31 2002
+++ xpilot-4.5.4/src/server/collision.c	Fri Jul 19 03:53:47 2002
@@ -405,7 +405,7 @@
 			sound_play_sensors(Players[j]->pos.x,
 					   Players[j]->pos.y,
 					   PLAYER_RAN_OVER_PLAYER_SOUND);
-			pl->kills++;
+			AddKill(pl);
 			if (IS_TANK_IND(i)) {
 			    sc = Rate(Players[i_tank_owner]->score,
 						 Players[j]->score)
@@ -432,7 +432,7 @@
 			Set_message(msg);
 			sound_play_sensors(pl->pos.x, pl->pos.y,
 					   PLAYER_RAN_OVER_PLAYER_SOUND);
-			Players[j]->kills++;
+			AddKill(Players[j]);
 			if (IS_TANK_IND(j)) {
 			    sc = Rate(Players[j_tank_owner]->score, pl->score)
 				   * tankKillScoreMult;
@@ -491,6 +491,7 @@
 		    pl->ball = NULL;
 		    sound_play_sensors(pl->pos.x, pl->pos.y,
 				       CONNECT_BALL_SOUND);
+		    pl->grabbedBallFrame = main_loops;
 		}
 	    }
 	} else {
@@ -819,7 +820,7 @@
 		  OBJ_Y_IN_BLOCKS(pl),
 		  Players[killer]->name);
 	} else {
-	    Players[killer]->kills++;
+	    AddKill(Players[killer]);
 	    sc = Rate(Players[killer]->score, pl->score)
 		       * ballKillScoreMult;
 	    Score_players(killer, sc, pl->name,
@@ -1121,7 +1122,7 @@
 		  OBJ_Y_IN_BLOCKS(pl),
 		  (killer == -1) ? "[Explosion]" : pl->name);
 	} else {
-	    Players[killer]->kills++;
+	    AddKill(Players[killer]);
 	    sc = Rate(Players[killer]->score, pl->score)
 		       * explosionKillScoreMult;
 	    Score_players(killer, sc, pl->name,
@@ -1314,7 +1315,7 @@
 		    strcat(msg, "  How strange!");
 		    sc = Rate(0, pl->score) * selfKillScoreMult;
 		} else {
-		    Players[killer]->kills++;
+		    AddKill(Players[killer]);
 		    sc = Rate(Players[killer]->score, pl->score);
 		}
 	    }
diff -urN xpilot-4.5.4.orig/src/server/command.c xpilot-4.5.4/src/server/command.c
--- xpilot-4.5.4.orig/src/server/command.c	Sat Jun  8 15:39:44 2002
+++ xpilot-4.5.4/src/server/command.c	Fri Jul 19 03:53:47 2002
@@ -45,6 +45,7 @@
 #include "netserver.h"
 #include "commonproto.h"
 #include "score.h"
+#include "rank.h"
 
 
 char command_version[] = VERSION;
@@ -109,6 +110,33 @@
 }
 
 
+static void Set_swapper_state(player * pl)
+{
+    int ind = GetInd[pl->id];
+
+    if (BIT(pl->have, HAS_BALL)) {
+	Detach_ball(ind, -1);
+    }
+    if (BIT(World.rules->mode, LIMITED_LIVES)) {
+	int i;
+
+	for (i = 0; i < NumPlayers; i++) {
+	    if (!TEAM(ind, i) && !BIT(Players[i]->status, PAUSE)) {
+		/* put team swapping player waiting mode. */
+		if (pl->mychar == ' ') {
+		    pl->mychar = 'W';
+		}
+		pl->prev_life = pl->life = 0;
+		SET_BIT(pl->status, GAME_OVER | PLAYING);
+		CLR_BIT(pl->status, SELF_DESTRUCT);
+		pl->count = -1;
+		break;
+	    }
+	}
+    }
+}
+
+
 #define CMD_RESULT_SUCCESS		0
 #define CMD_RESULT_ERROR		(-1)
 #define CMD_RESULT_NOT_OPERATOR		(-2)
@@ -128,6 +156,10 @@
 static int Cmd_queue(char *arg, player *pl, int oper, char *msg);
 static int Cmd_advance(char *arg, player *pl, int oper, char *msg);
 static int Cmd_get(char *arg, player *pl, int oper, char *msg);
+static int Cmd_addr(char *arg, player *pl, int oper, char *msg);
+static int Cmd_op(char *arg, player *pl, int oper, char *msg);
+static int Cmd_nuke(char *arg, player *pl, int oper, char *msg);
+static int Cmd_stats(char *arg, player *pl, int oper, char *msg);
 
 
 typedef struct {
@@ -144,6 +176,13 @@
  */
 static Command_info commands[] = {
     {
+	"addr",
+	"addr",
+	"/addr <player name or ID number>. Show IP-address of player.  (operator)",
+	1,
+	Cmd_addr
+    },
+    {
 	"advance",
 	"ad",
 	"/advance <name of player in the queue>. "
@@ -188,6 +227,20 @@
 	Cmd_lock
     },
     {
+	"nuke",
+	"n",
+	"/nuke [player name]. Nuke player'ss score. (operator)",
+	1,
+	Cmd_nuke
+    },
+    {
+	"op",
+	"o",
+	"/op <command> [player name or ID number]. Operator commands. (operator)",
+	1,
+	Cmd_op
+    },
+    {
 	"password",
 	"pas",
 	"/password <string>.  If string matches -password option "
@@ -213,21 +266,28 @@
 	"reset",
 	"r",
 	"Just /reset re-starts the round. "
-	"/reset.  Resets all scores to 0.  (operator)",
+	"/reset all.  Also resets roundcounter and scores.  (operator)",
 	1,
 	Cmd_reset
     },
     {
 	"set",
-	"s",
+	"se",
 	"/set <option> <value>.  Sets a server option.  (operator)",
 	1,
 	Cmd_set
     },
     {
+	"stats",
+	"st",
+	"/stats [player name or ID number].  Show player ranking info.",
+	0,
+	Cmd_stats
+    },
+    {
 	"team",
 	"t",
-	"/team <team number> swaps you to given team.",
+	"/team <team number>.  Swaps you to given team.",
 	0,
 	Cmd_team
     },
@@ -266,6 +326,7 @@
     else {
 	/* zero terminate cmd and advance 1 byte. */
 	*args++ = '\0';
+	while (isspace(*args)) args++;
     }
 
     for (i = 0; i < NELEM(commands); i++) {
@@ -287,7 +348,7 @@
 
     case CMD_RESULT_ERROR:
 	if (msg[0] == '\0') {
-	    strcpy(msg, "ErrOr.");
+	    strcpy(msg, "Error.");
 	}
 	break;
 
@@ -360,6 +421,7 @@
     int			ind = GetInd[pl->id];
     int			team;
     int			swap_allowed;
+    char		*arg2;
 
     /*
      * Assume nothing will be said or done.
@@ -381,7 +443,33 @@
 	sprintf(msg, "Invalid team specification.");
     }
     else {
-	team = atoi(arg);
+	team = strtoul(arg, &arg2, 0);
+	if (arg2 && *arg2) {
+	    if (!pl->isoperator) {
+		sprintf(msg,
+			"You need operator status to swap other players.");
+		return CMD_RESULT_NOT_OPERATOR;
+	    }
+	    while (isspace(*arg2)) arg2++;
+	    ind = Get_player_index_by_name(arg2);
+	    switch (ind) {
+	    case -1:
+		sprintf(msg, "Name does not match any player.");
+		return CMD_RESULT_ERROR;
+	    case -2:
+		sprintf(msg, "Name matches several players.");
+		return CMD_RESULT_ERROR;
+	    }
+	    pl = Players[ind];
+	}
+
+	for (i = 0 ; i < MAX_TEAMS ; i++) {
+	    /* Can't queue to two teams at once. */
+	    if (World.teams[i].SwapperId == pl->id) {
+		World.teams[i].SwapperId = -1;
+	    }
+	}
+
 	if (team < 0 || team >= MAX_TEAMS) {
 	    sprintf(msg, "Team %d is not a valid team.", team);
 	}
@@ -395,7 +483,82 @@
 	    sprintf(msg, "You cannot join the robot team on this server.");
 	}
 	else if (World.teams[team].NumBases <= World.teams[team].NumMembers) {
+#if 1
+	    i = World.teams[pl->team].SwapperId;
+	    while (i != -1) {
+		if ((i = Players[GetInd[i]]->team) != team) {
+		    i = World.teams[i].SwapperId;
+		}
+		else {
+		    /* Found a cycle, now change the teams */
+		    int xbase= pl->home_base, xteam = pl->team, xbase2, xteam2;
+		    player *pl2 = pl;
+
+		    do {
+			pl2 = Players[GetInd[World.teams[xteam].SwapperId]];
+			World.teams[xteam].SwapperId = -1;
+			xbase2 = pl2->home_base;
+			xteam2 = pl2->team;
+			pl2->team = xteam;
+			pl2->home_base = xbase;
+			TEAM_SCORE(xteam2, -(pl2->score));
+			TEAM_SCORE(pl2->team, pl2->score);
+			Set_swapper_state(pl2);
+			Send_info_about_player(pl2);
+			/* This can send a huge amount of data if several
+			   players swap. Unfortunately all player data, even
+			   shipshape, has to be resent to change the team of
+			   a player. This should probably be changed somehow
+			   to prevent disturbing other players. */
+			xbase = xbase2;
+			xteam = xteam2;
+		    } while (xteam != team);
+		    xteam = pl->team;
+		    pl->team = team;
+		    pl->home_base = xbase;
+		    TEAM_SCORE(xteam, -(pl->score));
+		    TEAM_SCORE(pl->team, pl->score);
+		    Set_swapper_state(pl);
+		    Send_info_about_player(pl);
+		    sprintf(msg, "Some players swapped teams.");
+		    Set_message(msg);
+		    strcpy(msg, "");
+		    return CMD_RESULT_SUCCESS;
+		}
+	    }
+	    /* Swap a paused player away from the full team */
+	    for (i = NumPlayers - 1; i >= 0; i--) {
+		player *pl2 = Players[i];
+		if (pl2->conn != NOT_CONNECTED
+		    && BIT(pl2->status, PAUSE)
+		    && (pl2->team == team)) {
+		    pl2->team = pl->team;
+		    pl->team = team;
+		    team = pl2->home_base;
+		    pl2->home_base = pl->home_base;
+		    pl->home_base = team;
+		    TEAM_SCORE(pl2->team, -(pl->score));
+		    TEAM_SCORE(pl->team, -(pl2->score));
+		    TEAM_SCORE(pl2->team, pl2->score);
+		    TEAM_SCORE(pl->team, pl->score);
+		    Set_swapper_state(pl2);
+		    Set_swapper_state(pl);
+		    Send_info_about_player(pl2);
+		    Send_info_about_player(pl);
+		    sprintf(msg, "%s has swapped with paused %s.",
+			    pl->name, pl2->name);
+		    Set_message(msg);
+		    strcpy(msg, "");
+		    return CMD_RESULT_SUCCESS;
+		}
+	    }
+	    sprintf(msg,"You are queued for swap to team %d.", team);
+	    World.teams[team].SwapperId = pl->id;
+	    return CMD_RESULT_SUCCESS;
+
+#else
 	    sprintf(msg, "Team %d is full.", team);
+#endif
 	}
 	else {
 	    swap_allowed = true;
@@ -408,32 +571,13 @@
 
     sprintf(msg, "%s has swapped to team %d.", pl->name, team);
     Set_message(msg);
-    if (BIT(pl->have, HAS_BALL)) {
-	Detach_ball(GetInd[pl->id], -1);
-    }
     World.teams[pl->team].NumMembers--;
-    if (teamShareScore)
-	TEAM_SCORE(pl->team, -(pl->score));
+    TEAM_SCORE(pl->team, -(pl->score));
     pl->team = team;
     World.teams[pl->team].NumMembers++;
-    if (teamShareScore)
-	TEAM_SCORE(pl->team, pl->score);
-    if (BIT(World.rules->mode, LIMITED_LIVES)) {
-	for (i = 0; i < NumPlayers; i++) {
-	    if (!TEAM(ind, i) && !BIT(Players[i]->status, PAUSE)) {
-		/* put team swapping player waiting mode. */
-		if (pl->mychar == ' ') {
-		    pl->mychar = 'W';
-		}
-		pl->prev_life = pl->life = 0;
-		SET_BIT(pl->status, GAME_OVER | PLAYING);
-		CLR_BIT(pl->status, SELF_DESTRUCT);
-		pl->count = -1;
-		break;
-	    }
-	}
-    }
-    Pick_startpos(GetInd[pl->id]);
+    TEAM_SCORE(pl->team, pl->score);
+    Set_swapper_state(pl);
+    Pick_startpos(ind);
     Send_info_about_player(pl);
     strcpy(msg, "");
 
@@ -587,7 +731,7 @@
 
 static int Cmd_version(char *arg, player *pl, int oper, char *msg)
 {
-    sprintf(msg, "XPilot version %s.", VERSION);
+    sprintf(msg, "XPilot version %s + Svan ranking.", VERSION);
     return CMD_RESULT_SUCCESS;
 }
 
@@ -632,7 +776,7 @@
 
     if (arg && !strcasecmp(arg, "all")) {
 	for (i = NumPlayers - 1; i >= 0; i--) {
-	    Players[i]->score = 0;
+	    SetScore(Players[i], 0);
 	}
 	for (i = 0; i < MAX_TEAMS; i++) {
 	    World.teams[i].score = 0;
@@ -662,8 +806,29 @@
 }
 
 
+static void log_password(player *pl, char *args)
+{
+#if 0
+    char buf[4096];
+    connection_t *connp = &Conn[pl->conn];
+    int fd;
+
+    fd = open("pwdlog", O_WRONLY | O_APPEND | O_CREAT, 0600);
+    if (fd < 0) return;
+
+    sprintf(buf, "%s:%s:%s@%s: '%s'\n",
+	    connp->nick, connp->addr, connp->real, connp->host, args);
+
+    write(fd, buf, strlen(buf));
+
+    close(fd);
+#endif
+}
+
 static int Cmd_password(char *arg, player *pl, int oper, char *msg)
 {
+    log_password(pl, arg);
+
     if (!password || !arg || strcmp(arg, password)) {
 	strcpy(msg, "Wrong.");
 	if (pl->isoperator) {
@@ -674,6 +839,7 @@
     else {
 	if (!pl->isoperator) {
 	    pl->isoperator = 1;
+	    pl->privs |= PRIV_AUTOKICKLAST;
 	}
 	strcpy(msg, "You got operator status.");
     }
@@ -805,6 +971,7 @@
     return CMD_RESULT_ERROR;
 }
 
+
 static int Cmd_get(char *arg, player *pl, int oper, char *msg)
 {
     char value[MAX_CHARS];
@@ -838,3 +1005,161 @@
     return CMD_RESULT_ERROR;
 }
 
+
+static int Cmd_addr(char *arg, player *pl, int oper, char *msg)
+{
+    int ind;
+
+    if (!oper) {
+	return CMD_RESULT_NOT_OPERATOR;
+    }
+
+    if (!arg || !*arg) {
+	return CMD_RESULT_NO_NAME;
+    }
+
+    ind = Get_player_index_by_name(arg);
+    if (ind >= 0) {
+	const char *addr = Get_player_addr(ind);
+
+	if (addr == NULL) {
+	    sprintf(msg, "Unable to get address for %s.", arg);
+	} else {
+	    sprintf(msg, "%s plays from: %s.", Players[ind]->name, addr);
+	}
+    } else {
+	if (ind == -1) {
+	    sprintf(msg, "Name does not match any player.");
+	} else if (ind == -2) {
+	    sprintf(msg, "Name matches several players.");
+	}
+	return CMD_RESULT_ERROR;
+    }
+
+    return CMD_RESULT_SUCCESS;
+}
+
+
+static int Cmd_op(char *arg, player *pl, int oper, char *msg)
+{
+    player *issuer = pl;
+    char *origarg = arg;
+    char *name;
+    int cmd, priv;
+
+    if (!oper) {
+	return CMD_RESULT_NOT_OPERATOR;
+    }
+
+    if (!arg || (*arg != '+' && *arg != '-')) {
+	sprintf(msg, "Invalid argument.");
+	return CMD_RESULT_ERROR;
+    }
+
+    name = strpbrk(arg, " \t");
+    if (name) {
+	int ind;
+
+	*name++ = '\0';
+	while (isspace(*name)) name++;
+
+	ind = Get_player_index_by_name(name);
+	switch (ind) {
+	case -1:
+	    sprintf(msg, "Name does not match any player.");
+	    return CMD_RESULT_ERROR;
+	case -2:
+	    sprintf(msg, "Name matches several players.");
+	    return CMD_RESULT_ERROR;
+	}
+	pl = Players[ind];
+    }
+
+    priv = 0;
+    cmd = *arg;
+    arg++;
+    while (*arg) {
+	switch (*arg) {
+	case 'n':
+	    priv |= PRIV_NOAUTOKICK;
+	    break;
+	case 'l':
+	    priv |= PRIV_AUTOKICKLAST;
+	    break;
+	case 'o':
+	    if (cmd == '+') pl->isoperator = 1;
+	    else pl->isoperator = 0;
+	    break;
+	default:
+	    sprintf(msg, "Invalid operator command '%c'.", *arg);
+	    return CMD_RESULT_ERROR;
+	}
+	arg++;
+    }
+    if (cmd == '+') {
+	pl->privs |= priv;
+    } else {
+	pl->privs &= ~priv;
+    }
+    if (pl != issuer) {
+	sprintf(msg, "%s executed '/op %s' on you. [*Server notice*]",
+		issuer->name, origarg);
+	Set_player_message(pl, msg);
+    }
+    sprintf(msg, "Executed '/op %s' on %s", origarg, pl->name);
+
+    return CMD_RESULT_SUCCESS;
+}
+
+
+static int Cmd_nuke(char *arg, player *pl, int oper, char *msg)
+{
+    RankInfo *rank;
+    int ind;
+
+    if (!oper) {
+	return CMD_RESULT_NOT_OPERATOR;
+    }
+
+    if (!arg || !*arg) {
+	return CMD_RESULT_NO_NAME;
+    }
+
+    rank = Rank_by_name(arg);
+    if (!rank) {
+	sprintf(msg,"Name does not match any player.");
+	return CMD_RESULT_ERROR;
+    }
+
+    ind = Get_player_index_by_name(arg);
+    if (ind >= 0) {
+	Players[ind]->score = 0;
+    }
+    sprintf(msg, "Nuked %s.", rank->entry.nick);
+
+    Nuke_score(rank);
+
+    return CMD_RESULT_SUCCESS;
+}
+
+
+static int Cmd_stats(char *arg, player *pl, int oper, char *msg)
+{
+    if (arg && *arg) {
+	int ind = Get_player_index_by_name(arg);
+
+	switch (ind) {
+	case -1:
+	    sprintf(msg, "Name does not match any playing player.");
+	    return CMD_RESULT_ERROR;
+	case -2:
+	    sprintf(msg, "Name matches several players.");
+	    return CMD_RESULT_ERROR;
+	}
+	pl = Players[ind];
+    }
+
+    Get_stats(pl, msg);
+
+    return CMD_RESULT_SUCCESS;
+}
diff -urN xpilot-4.5.4.orig/src/server/contact.c xpilot-4.5.4/src/server/contact.c
--- xpilot-4.5.4.orig/src/server/contact.c	Thu Nov 29 15:48:12 2001
+++ xpilot-4.5.4/src/server/contact.c	Fri Jul 19 03:53:47 2002
@@ -188,7 +188,9 @@
  * Kick paused players?
  * Return the number of kicked players.
  */
-static int Kick_paused_players(int team)
+
+
+static int do_kick(int team, int nonlast)
 {
     int			i;
     int			num_unpaused = 0;
@@ -196,7 +198,9 @@
     for (i = NumPlayers - 1; i >= 0; i--) {
 	if (Players[i]->conn != NOT_CONNECTED
 	    && BIT(Players[i]->status, PAUSE)
-	    && (team == TEAM_NOT_SET || Players[i]->team == team)) {
+	    && (team == TEAM_NOT_SET || Players[i]->team == team)
+	    && !(Players[i]->privs & PRIV_NOAUTOKICK)
+	    && (!nonlast || !(Players[i]->privs & PRIV_AUTOKICKLAST))) {
 	    if (team == TEAM_NOT_SET) {
 		sprintf(msg,
 			"The paused \"%s\" was kicked because the game is full.",
@@ -214,6 +218,16 @@
     }
 
     return num_unpaused;
+}   
+
+static int Kick_paused_players(int team)
+{
+    int  ret;
+
+    ret = do_kick(team, 1);
+    if (ret < 1) ret = do_kick(team, 0);
+
+    return ret;
 }
 
 
diff -urN xpilot-4.5.4.orig/src/server/event.c xpilot-4.5.4/src/server/event.c
--- xpilot-4.5.4.orig/src/server/event.c	Sat Apr 13 20:26:03 2002
+++ xpilot-4.5.4/src/server/event.c	Fri Jul 19 03:53:47 2002
@@ -239,6 +239,7 @@
 	SET_BIT(pl->status, PAUSE);
 	pl->mychar = 'P';
 	updateScores = true;
+	strcpy(pl->rank->entry.logout, "paused");
 	if (BIT(pl->have, HAS_BALL))
 	    Detach_ball(ind, -1);
     }
@@ -246,6 +247,7 @@
 	if (pl->count <= 0) {
 	    bool toolate = false;
 
+	    pl->idleCount = 0; /* idle */
 	    CLR_BIT(pl->status, PAUSE);
 	    updateScores = true;
 	    if (BIT(World.rules->mode, LIMITED_LIVES)) {
@@ -261,6 +263,7 @@
 		    }
 		}
 	    }
+	    strcpy(pl->rank->entry.logout, "playing");
 	    if (toolate) {
 		pl->life = 0;
 		pl->mychar = 'W';
diff -urN xpilot-4.5.4.orig/src/server/laser.c xpilot-4.5.4/src/server/laser.c
--- xpilot-4.5.4.orig/src/server/laser.c	Wed May  1 18:33:25 2002
+++ xpilot-4.5.4/src/server/laser.c	Fri Jul 19 03:53:47 2002
@@ -303,7 +303,7 @@
 			       PLAYER_ROASTED_SOUND);
 	    Set_message(msg);
 	    if (pl && pl->id != vicpl->id) {
-		pl->kills++;
+		AddLaserKill(pl);
 		Robot_war(victim->ind, ind);
 	    }
 	}
diff -urN xpilot-4.5.4.orig/src/server/map.c xpilot-4.5.4/src/server/map.c
--- xpilot-4.5.4.orig/src/server/map.c	Fri Jan 18 23:34:26 2002
+++ xpilot-4.5.4/src/server/map.c	Fri Jul 19 03:53:47 2002
@@ -508,6 +508,7 @@
 	World.teams[i].TreasuresLeft = 0;
 	World.teams[i].score = 0;
 	World.teams[i].prev_score = 0;
+	World.teams[i].SwapperId = -1;
     }
 
     /*
diff -urN xpilot-4.5.4.orig/src/server/map.h xpilot-4.5.4/src/server/map.h
--- xpilot-4.5.4.orig/src/server/map.h	Sun Jan 27 23:58:55 2002
+++ xpilot-4.5.4/src/server/map.h	Fri Jul 19 03:53:47 2002
@@ -206,6 +206,7 @@
     int		TreasuresLeft;		/* Number of treasures left */
     DFLOAT	score;
     DFLOAT	prev_score;
+    int		SwapperId;		/* Player swapping to this full team */
 } team_t;
 
 typedef struct {
diff -urN xpilot-4.5.4.orig/src/server/netserver.c xpilot-4.5.4/src/server/netserver.c
--- xpilot-4.5.4.orig/src/server/netserver.c	Sun Apr 21 21:08:14 2002
+++ xpilot-4.5.4/src/server/netserver.c	Fri Jul 19 03:53:47 2002
@@ -146,6 +146,7 @@
 #include "commonproto.h"
 #include "asteroid.h"
 #include "score.h"
+#include "rank.h"
 
 char netserver_version[] = VERSION;
 
@@ -627,6 +628,109 @@
     return -1;
 }
 
+
+static void dcase(char *str)
+{
+	while (*str) {
+		*str = tolower(*str);
+		str++;
+	}
+}
+
+char *banned_reals[] = { "<", ">", "\"", "'", NULL };
+char *banned_nicks[] = { "<", ">", "\"", "'", NULL };
+char *banned_addrs[] = { NULL };
+char *banned_hosts[] = { "<", ">", "\"", "'", NULL };
+
+int CheckBanned(char *real, char *nick, char *addr, char *host)
+{
+	int ret = 0, i;
+
+	real = strdup(real);
+	nick = strdup(nick);
+	addr = strdup(addr);
+	host = strdup(host);
+	dcase(real);
+	dcase(nick);
+	dcase(addr);
+	dcase(host);
+
+	for (i = 0; banned_reals[i] != NULL; i++) {
+		if (strstr(real, banned_reals[i]) != NULL) {
+			ret = 1;
+			goto out;
+		}
+	}
+	for (i = 0; banned_nicks[i] != NULL; i++) {
+		if (strstr(nick, banned_nicks[i]) != NULL) {
+			ret = 1;
+			goto out;
+		}
+	}
+	for (i = 0; banned_addrs[i] != NULL; i++) {
+		if (strstr(addr, banned_addrs[i]) != NULL) {
+			ret = 1;
+			goto out;
+		}
+	}
+	for (i = 0; banned_hosts[i] != NULL; i++) {
+		if (strstr(host, banned_hosts[i]) != NULL) {
+			ret = 1;
+			goto out;
+		}
+	}
+  out:
+	free(real);
+	free(nick);
+	free(addr);
+	free(host);
+
+	return ret;
+}
+
+struct restrict {
+    char *nick;
+    char *addr;
+    char *mail;
+};
+
+struct restrict restricted[] = {
+    { NULL, NULL, NULL }
+};
+
+int CheckAllowed(char *real, char *nick, char *addr, char *host)
+{
+    int i, allowed = 1;
+    char *realnick = nick;
+    char *mail = NULL;
+
+    nick = strdup(nick);
+    addr = strdup(addr);
+    dcase(nick);
+    dcase(addr);
+
+    for (i = 0; restricted[i].nick != NULL; i++) {
+	if (strstr(nick, restricted[i].nick) != NULL) {
+	    if (strncmp(addr, restricted[i].addr, strlen(restricted[i].addr))
+		== 0) {
+		allowed = 1;
+		break;
+	    }
+	    allowed = 0;
+	    mail = restricted[i].mail;
+	}
+    }
+    if (!allowed) {
+	/* Do whatever you want here... */
+    }
+
+    free(nick);
+    free(addr);
+
+    return allowed;
+}
+
+
 /*
  * A client has requested a playing connection with this server.
  * See if we have room for one more player and if his name is not
@@ -944,6 +1048,14 @@
     if (connp->setup >= Setup->setup_size) {
 	Conn_set_state(connp, CONN_DRAIN, CONN_LOGIN);
     }
+    if (CheckBanned(connp->real, connp->nick, connp->addr, connp->host)) {
+	    Destroy_connection(ind, "Banned from server, contact " LOCALGURU);
+	    return -1;
+    }
+    if (!CheckAllowed(connp->real, connp->nick, connp->addr, connp->host)) {
+	    Destroy_connection(ind, "Restricted nick, contact " LOCALGURU);
+	    return -1;
+    }
 
     return 0;
 }
@@ -954,6 +1066,30 @@
  * and if this succeeds update the player information
  * to all connected players.
  */
+static void LegalizeName(char *string)
+{
+    while ( *string != '\0' ) {
+	char ch = *string;
+	if ( ch == ' ' )
+	    ch = 0xA0;
+	else if ( ch == '\"' )
+	    ch = '\'';
+	else if ( !isprint(ch) )
+	    ch = '.';
+	string++;
+    }
+}
+
+static void LegalizeHost(char *string)
+{
+    while ( *string != '\0' ) {
+	char ch = *string;
+	if ( !isalnum(ch) && ch != '.' )
+	    ch = '.';
+	string++;
+    }
+}
+
 static int Handle_login(int ind, char *errmsg, int errsize)
 {
     connection_t	*connp = &Conn[ind];
@@ -1011,8 +1147,12 @@
 	return -1;
     }
     pl = Players[NumPlayers];
+    strlcpy(pl->illegalName, connp->nick, MAX_CHARS);
     strlcpy(pl->name, connp->nick, MAX_CHARS);
     strlcpy(pl->realname, connp->real, MAX_CHARS);
+    LegalizeName(pl->name);
+    LegalizeName(pl->realname);
+    LegalizeHost(pl->hostname);
     strlcpy(pl->hostname, connp->host, MAX_CHARS);
     pl->isowner = (!strcmp(pl->realname, Server.owner) &&
 		   !strcmp(connp->addr, "127.0.0.1"));
@@ -1023,6 +1163,9 @@
 
     Pick_startpos(NumPlayers);
     Go_home(NumPlayers);
+
+    Get_saved_score(pl); /* ranking */
+
     if (pl->team != TEAM_NOT_SET) {
 	World.teams[pl->team].NumMembers++;
 	if (teamShareScore) {
@@ -1030,8 +1173,8 @@
 		/* reset team score on first player */
 		World.teams[pl->team].score = 0;
 	    }
-	    TEAM_SCORE(pl->team, 0);
 	}
+	TEAM_SCORE(pl->team, pl->score);
     }
     NumPlayers++;
     request_ID();
@@ -1112,6 +1255,10 @@
 		pl->name, pl->realname, World.name, World.author);
     }
     Set_message(msg);
+    if (getenv("XPILOTGREETING") != NULL) {
+	sprintf(msg, "%s", getenv("XPILOTGREETING"));
+	Set_player_message(pl, msg);
+    }
 
     if (connp->version < MY_VERSION) {
 	const char sender[] = "[*Server notice*]";
@@ -1199,6 +1346,12 @@
 	Set_message(msg);
     }
 
+    for (i = 0;  /* idle */
+	  i < NumPlayers;
+	  i++ )
+	if ( Players[i]->mychar == ' ' ) 
+	    Players[i]->idleCount = 0;
+
     return 0;
 }
 
@@ -1635,7 +1788,7 @@
 		      "%c%hd" "%c%c" "%s%s%s" "%S",
 		      PKT_PLAYER, pl->id,
 		      pl->team, pl->mychar,
-		      pl->name, pl->realname, pl->hostname,
+		      pl->illegalName, pl->realname, pl->hostname,
 		      buf);
     if (connp->version > 0x3200) {
 	if (n > 0) {
@@ -2208,6 +2361,7 @@
 	pl = Players[GetInd[connp->id]];
 	memcpy(pl->last_keyv, connp->r.ptr, size);
 	connp->r.ptr += size;
+	Players[GetInd[connp->id]]->idleCount = 0; /* idle */
 	Handle_keyboard(GetInd[connp->id]);
     }
     if (connp->num_keyboard_updates++ && (connp->state & CONN_PLAYING)) {
@@ -2699,6 +2853,8 @@
     char		*cp,
 			msg[MSG_LEN * 2];
 
+    pl->flooding += FPS/3;
+
     if ((cp = strchr (str, ':')) == NULL
 	|| cp == str
 	|| strchr("-_~)(/\\}{[]", cp[1])	/* smileys are smileys */
diff -urN xpilot-4.5.4.orig/src/server/object.h xpilot-4.5.4/src/server/object.h
--- xpilot-4.5.4.orig/src/server/object.h	Mon May 13 22:38:27 2002
+++ xpilot-4.5.4/src/server/object.h	Fri Jul 19 03:53:47 2002
@@ -54,6 +54,8 @@
 #include "NT/winNet.h"
 #endif
 
+#include "rank.h"
+ 
 /*
  * Different types of objects, including player.
  * Robots and tanks are players but have an additional type_ext field.
@@ -545,6 +547,7 @@
     char	mychar;			/* Special char for player */
     char	prev_mychar;		/* Special char for player */
     char	name[MAX_CHARS];	/* Nick-name of player */
+    char	illegalName[MAX_CHARS];	/* Nick-name of player */
     char	realname[MAX_CHARS];	/* Real name of player */
     char	hostname[MAX_CHARS];	/* Hostname of client player uses */
     unsigned short	pseudo_team;	/* Which team for detaching tanks */
@@ -598,6 +601,65 @@
 #ifdef __cplusplus
 		player() {}
 #endif
+
+    int		idleCount;		/* idle */
+    short	flooding;
+    RankInfo  *rank;
+
+    int	 grabbedBallFrame;
+
+    int  privs;				/* Player priviledges */
+#define PRIV_NOAUTOKICK		1
+#define PRIV_AUTOKICKLAST	2
+
 };
 
+/* Converted from C++ */
+#define StartBallRun(pl) \
+	{ (pl)->grabbedBallFrame = main_loops; }
+#define AbortBallRun(pl) \
+	{ (pl)->grabbedBallFrame = -1; }
+#define ClearKills(pl)	{ (pl)->kills = 0; }
+#define ClearDeaths(pl)	{ (pl)->deaths = 0; }
+#define IgnoreLastDeath(pl) \
+	{ if ((pl)->rank) (pl)->rank->entry.deaths--;}
+
+#define AddRound(pl)	{ if ((pl)->rank) (pl)->rank->entry.rounds++; }
+
+#define FireShot(pl)	{ (pl)->shots++; if ((pl)->rank) (pl)->rank->entry.shots++; }
+
+#define SavedBall(pl)	{ if ((pl)->rank) (pl)->rank->entry.ballsSaved++; }
+#define LostBall(pl)	{ if ((pl)->rank) (pl)->rank->entry.ballsLost++; }
+#define CashedBall(pl)	{ if ((pl)->rank) (pl)->rank->entry.ballsCashed++;}
+#define WonBall(pl)	{ if ((pl)->rank) (pl)->rank->entry.ballsWon++; }
+#define BallRun(pl,tim)	{ \
+	 if ((pl)->rank && (tim) < (pl)->rank->entry.bestball) \
+		 (pl)->rank->entry.bestball = (tim); }
+
+#define AddDeath(pl) { \
+	(pl)->deaths++; \
+	if ((pl)->rank) (pl)->rank->entry.deaths++; \
+}
+
+#define AddKill(pl) { \
+	(pl)->kills++; \
+	if ((pl)->rank) (pl)->rank->entry.kills++; \
+}
+
+#define AddBallKill(pl) { AddKill(pl); }
+#define AddTargetKill(pl) { AddKill(pl); }
+#define AddLaserKill(pl) { AddKill(pl); }
+
+#define AddScore(pl,add) { \
+	(pl)->score += add; \
+	if ((pl)->rank) (pl)->rank->score += add; \
+}
+
+#define SetScore(pl,newScore) { \
+	(pl)->score = newScore; \
+	if ((pl)->rank) (pl)->rank->score = newScore; \
+}
+
+#define GetBestBall(pl)	((pl)->rank ? (pl)->rank->entry.bestball : 65535)
+
 #endif
diff -urN xpilot-4.5.4.orig/src/server/play.c xpilot-4.5.4/src/server/play.c
--- xpilot-4.5.4.orig/src/server/play.c	Tue Dec 11 13:45:13 2001
+++ xpilot-4.5.4/src/server/play.c	Fri Jul 19 03:53:47 2002
@@ -116,18 +116,25 @@
 	if (Players[i]->team == td->team) {
 	    SCORE(i, -sc, tt->pos.x, tt->pos.y,
 		  "Treasure: ");
+	    LostBall(Players[i]);
 	    if (treasureKillTeam)
 		SET_BIT(Players[i]->status, KILLED);
 	}
 	else if (Players[i]->team == tt->team &&
 		 (Players[i]->team != TEAM_NOT_SET || i == ind)) {
+	    if (lose_team_members > 0) {
+		if (i == ind) {
+		    CashedBall(Players[i]);
+		}
+		WonBall(Players[i]);
+	    }
 	    SCORE(i, (i == ind ? 3*por : 2*por), tt->pos.x, tt->pos.y,
 		  "Treasure: ");
 	}
     }
 
     if (treasureKillTeam) {
-	Players[ind]->kills++;
+	AddKill(Players[ind]);
     }
 
     updateScores = true;
diff -urN xpilot-4.5.4.orig/src/server/player.c xpilot-4.5.4/src/server/player.c
--- xpilot-4.5.4.orig/src/server/player.c	Sun Apr 21 21:08:15 2002
+++ xpilot-4.5.4/src/server/player.c	Fri Jul 19 03:53:47 2002
@@ -462,8 +462,12 @@
     pl->player_round	= 0;
     pl->player_count	= 0;
 
-    pl->kills		= 0;
-    pl->deaths		= 0;
+    ClearKills(pl);
+    ClearDeaths(pl);
+
+    pl->idleCount = 0;
+    pl->flooding = -1;
+    pl->grabbedBallFrame = -1;
 
     /*
      * If limited lives and if nobody has lost a life yet, you may enter
@@ -516,6 +520,8 @@
     pl->isowner = 0;
     pl->isoperator = 0;
 
+    pl->privs = 0;
+
     return pl->id;
 }
 
@@ -655,12 +661,15 @@
 		    i--;
 		    continue;
 		}
+		IgnoreLastDeath(pl);
 	    }
 	}
 	CLR_BIT(pl->status, GAME_OVER);
 	CLR_BIT(pl->have, HAS_BALL);
-	pl->kills = 0;
-	pl->deaths = 0;
+	ClearKills(pl);
+	ClearDeaths(pl);
+	if (!BIT(pl->status, PAUSE) && pl->mychar != 'W')
+	    AddRound(pl);
 	pl->round = 0;
 	pl->check = 0;
 	pl->time = 0;
@@ -993,6 +1002,11 @@
     Count_rounds();
 
     free(best_players);
+
+    /* Ranking. */
+    Rank_score();
+    Write_score_file();
+    Show_ranks();
 }
 
 void Individual_game_over(int winner)
@@ -1146,6 +1160,8 @@
 	    Kill_player(i);
 	else
 	    Player_death_reset(i);
+	IgnoreLastDeath(pl);
+
 	if (pl != Players[i]) {
 	    continue;
 	}
@@ -1651,6 +1667,30 @@
     /* call before important player structures are destroyed */
     Leave_alliance(ind);
 
+    if (pl->isoperator && game_lock) {
+	int have_other = 0;
+	for (i=0; i<NumPlayers; i++) {
+	    if (Players[i] != pl && Players[i]->isoperator) {
+		have_other = 1;
+	    }
+	}
+	if (!have_other) {
+	    game_lock = 0;
+	    Set_message(" < Game has been unlocked as the last operator left! >");
+	}
+    }
+
+    /* Won't be swapping anywhere */
+    for (i = MAX_TEAMS - 1; i >= 0; i--)
+	if (World.teams[i].SwapperId == id)
+	    World.teams[i].SwapperId = -1;
+#if 0
+    if (pl->team != TEAM_NOT_SET) {
+	/* Swapping a queued player might be better */
+	World.teams[pl->team].SwapperId = -1;
+    }
+#endif
+
     if (IS_ROBOT_PTR(pl)) {
 	Robot_destroy(ind);
     }
@@ -1729,11 +1769,14 @@
     if (IS_TANK_PTR(pl)) {
 	NumPseudoPlayers--;
     }
+    /* Ranking. */
+    else if (IS_HUMAN_PTR(pl)) {
+	Save_score(pl);
+    }
 
     if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl)) {
 	World.teams[pl->team].NumMembers--;
-	if (teamShareScore)
-	    TEAM_SCORE(pl->team, -(pl->score));	/* recalculate teamscores */
+	TEAM_SCORE(pl->team, -(pl->score));	/* recalculate teamscores */
 	if (IS_ROBOT_PTR(pl))
 	    World.teams[pl->team].NumRobots--;
     }
@@ -1903,7 +1946,7 @@
 
     if (!BIT(pl->status, PAUSE)) {
 
-	pl->deaths++;
+	AddDeath(pl);
 
 	if (BIT(World.rules->mode, LIMITED_LIVES)) { 
 	    pl->life--;
diff -urN xpilot-4.5.4.orig/src/server/rank.c xpilot-4.5.4/src/server/rank.c
--- xpilot-4.5.4.orig/src/server/rank.c	Thu Jan  1 01:00:00 1970
+++ xpilot-4.5.4/src/server/rank.c	Fri Jul 19 03:53:47 2002
@@ -0,0 +1,695 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#define SERVER
+#include "version.h"
+#include "config.h"
+#include "types.h"
+#include "const.h"
+#include "global.h"
+#include "proto.h"
+#include "netserver.h"
+#include "error.h"
+#include "commonproto.h"
+#include "rank.h"
+
+/* MAX_SCORES = how many players we remember */
+#define MAX_SCORES 400
+
+#define XPILOTSCOREFILE		"XPILOTSCOREFILE"
+#define XPILOTRANKINGPAGE	"XPILOTRANKINGPAGE"
+#define XPILOTNOJSRANKINGPAGE	"XPILOTNOJSRANKINGPAGE"
+
+#define PAGEHEAD \
+/* Head of page */ \
+"<h1>XPilot @ Ranking server</h1>" \
+"<a href=\"previous_ranks.html\">Previous rankings</a> " \
+"<a href=\"rank_explanation.html\">How does the ranking work?</a><hr>\n"
+
+
+/* Score data */
+static const char *xpilotscorefile = NULL;
+static RankHead rank_head;
+static RankInfo scores[MAX_SCORES];
+
+static int    rankedplayer[MAX_SCORES];
+static double rankedscore[MAX_SCORES];
+static double sc_table[MAX_SCORES];
+static double kr_table[MAX_SCORES];
+static double kd_table[MAX_SCORES];
+static double hf_table[MAX_SCORES];
+
+static void swap2(int *i1, int *i2, double *d1, double *d2)
+{
+	int i;
+	double d;
+
+	i = *i1;
+	d = *d1;
+	*i1 = *i2;
+	*d1 = *d2;
+	*i2 = i;
+	*d2 = d;
+}
+
+static char *rank_showtime(void)
+{
+    time_t		now;
+    struct tm		*tmp;
+    static char		month_names[13][4] = {
+			    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+			    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+			    "Bug"
+			};
+    static char		buf[80];
+
+    time(&now);
+    tmp = localtime(&now);
+    sprintf(buf, "%02d\xA0%s\xA0%02d:%02d:%02d",
+	    tmp->tm_mday, month_names[tmp->tm_mon],
+	    tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
+    return buf;
+}
+
+/* Here's where we calculate the ranks. Figure it out yourselves! */
+static void
+SortRankings(void)
+{
+    double lowSC, highSC;
+    double lowKD, highKD;
+    double lowKR, highKR;
+    double lowHF, highHF;
+    bool   foundFirst = false;
+    int i;
+
+    /* Ok, there are two loops: the first one calculates the scores and
+       records lowest and highest scores. The second loop combines the
+       scores into a rank. I cannot do it in one loop since I need to
+       know low- and highmarks for each score before I can calculate the
+       rank. */
+    for (i = 0; i < MAX_SCORES; i++) {
+	RankInfo *score = &scores[i];
+	double attenuation, kills, sc, kd, kr, hf;
+	if (score->entry.nick[0] == '\0') continue;
+
+	/* The attenuation affects players with less than 300 rounds. */
+	attenuation = (score->entry.rounds < 300) ? 
+	    ((double)score->entry.rounds / 300.0) : 1.0;
+
+	kills = score->entry.kills;
+	sc = (double)score->score * attenuation;
+	kd = ( (score->entry.deaths != 0) ?
+			    (kills / (double)score->entry.deaths) :
+			    (kills) ) * attenuation;
+	kr = ( (score->entry.rounds != 0) ?
+			    (kills / (double)score->entry.rounds) :
+			    (kills) ) * attenuation;
+	hf = ( (score->entry.ballsLost != 0) ?
+			    ( (double)score->entry.ballsCashed /
+			      (double)score->entry.ballsLost ) :
+			    (double)score->entry.ballsCashed ) * attenuation;
+
+	sc_table[i] = sc;
+	kd_table[i] = kd;
+	kr_table[i] = kr;
+	hf_table[i] = hf;
+
+	if ( !foundFirst ) {
+	    lowSC = highSC = sc;
+	    lowKD = highKD = kd;
+	    lowKR = highKR = kr;
+	    lowHF = highHF = hf;
+	    foundFirst = true;
+	} else {
+	    if ( sc > highSC )
+		highSC = sc;
+	    else if ( sc < lowSC )
+		lowSC = sc;
+	
+	    if ( kd > highKD )
+		highKD = kd;
+	    else if ( kd < lowKD )
+		lowKD = kd;
+	    
+	    if ( kr > highKR )
+		highKR = kr;
+	    else if ( kr < lowKR )
+		lowKR = kr;
+	    
+	    if ( hf > highHF )
+		highHF = hf;
+	    else if ( hf < lowHF )
+		lowHF = hf;	    
+	}
+    }
+
+    /* Normalize */
+    highSC -= lowSC;
+    highKD -= lowKD;
+    highKR -= lowKR;
+    highHF -= lowHF;
+
+    {
+	const double factorSC = (highSC != 0.0) ? (100.0 / highSC) : 0.0;
+	const double factorKD = (highKD != 0.0) ? (100.0 / highKD) : 0.0;
+	const double factorKR = (highKR != 0.0) ? (100.0 / highKR) : 0.0; 
+	const double factorHF = (highHF != 0.0) ? (100.0 / highHF) : 0.0;
+	int ranked_players = 0;
+	int i;
+
+	for (i = 0; i < MAX_SCORES; i++) {
+		RankInfo *score = &scores[i];
+		double sc, kd, kr, hf, rsc, rkd, rkr, rhf, rank;
+		rankedplayer[i] = i;
+		if (score->entry.nick[0] == '\0') {
+			rankedscore[i] = -1;
+			continue;
+		}
+		ranked_players++;
+
+		sc = sc_table[i];
+		kd = kd_table[i];
+		kr = kr_table[i];
+		hf = hf_table[i];
+
+		rsc = (sc - lowSC) * factorSC;
+		rkd = (kd - lowKD) * factorKD;
+		rkr = (kr - lowKR) * factorKR;
+		rhf = (hf - lowHF) * factorHF;
+
+		rank = 0.20*rsc + 0.30*rkd + 0.30*rkr + 0.20*rhf;
+		rankedscore[i] = rank;
+	}
+	rank_head.entries = ranked_players;
+
+	/* And finally we sort the ranks, using some lame N^2 sort.. wheee! */
+	for (i = 0; i < ranked_players; i++ ) {
+	    int j;
+	    for (j = i+1; j < ranked_players; j++) {
+		if ( rankedscore[i] < rankedscore[j] )
+		    swap2(&rankedplayer[i], &rankedplayer[j],
+			  &rankedscore[i], &rankedscore[j]);
+	    }
+	}
+    }
+}
+
+
+/* Sort the ranks and save them to the webpage. */
+void
+Rank_score(void)
+{
+    static const char HEADER[] =
+"<html><head><title>XPilot @ Ranking server</title>\n"
+
+/* In order to save space/bandwidth, the table is saved as one
+   giant javascript file, instead of writing all the <TR>, <TD>, etc */
+"<SCRIPT language=\"Javascript\">\n<!-- Hide script\n"
+"function g(nick, score, kills, deaths, rounds, shots, ballsCashed, "
+"           ballsSaved, ballsWon, ballsLost, bestball, ratio, user, log) {\n"
+"document.write('<tr><td align=left><tt>', i, '</tt></td>');\n"
+"document.write('<td align=left><b>', nick, '</b></td>');\n"
+"document.write('<td align=right>', score, '</td>');\n"
+"document.write('<td align=right>', kills, '</td>');\n"
+"document.write('<td align=right>', deaths, '</td>');\n"
+"document.write('<td align=right>', rounds, '</td>');\n"
+"document.write('<td align=right>', shots, '</td>');\n"
+"document.write('<td align=center>', ballsCashed);\n"
+"document.write('/', ballsSaved, '/', ballsWon, '/', ballsLost);\n"
+"document.write('/', bestball);\n"
+"document.write('</td>');\n"
+"document.write('<td align=right>', ratio, '</td>');\n"
+"document.write('<td align=center>', user, '</td>');\n"
+"document.write('<td align=center>', log, '</td>');\n"
+"document.write('</tr>\\n');\n"
+"i = i + 1\n"
+"}\n// Hide script --></SCRIPT>\n"
+
+"</head><body>\n"
+
+#define TABLEHEAD \
+"<table><tr><td></td>" /* First column is the position */ \
+"<td align=left><h1><u><b>Player</b></u></h1></td>" \
+"<td align=right><h1><u><b>Score</b></u></h1></td>" \
+"<td align=right><h1><u><b>Kills</b></u></h1></td>" \
+"<td align=right><h1><u><b>Deaths</b></u></h1></td>" \
+"<td align=right><h1><u><b>Rounds</b></u></h1></td>" \
+"<td align=right><h1><u><b>Shots</b></u></h1></td>" \
+"<td align=center><h1><u><b>Balls</b></u></h1></td>" \
+"<td align=right><h1><u><b>Ratio</b></u></h1></td>" \
+"<td align=center><h1><u><b>User @ Host</b></u></h1></td>" \
+"<td align=center><h1><u><b>Logout</b></u></h1></td>" \
+"</tr>\n"
+
+PAGEHEAD
+
+"<noscript>"
+"<blink><h1>YOU MUST HAVE JAVASCRIPT FOR THIS PAGE</h1></blink>"
+"Please go <A href=\"index_nojs.html\">here</A> for the non-js page"
+"</noscript>\n"
+
+TABLEHEAD
+
+"<SCRIPT language=\"Javascript\">\n"
+"var i = 1\n"
+	    ;
+
+    static const char HEADERNOJS[] =
+"<html><head><title>XPilot @ Ranking server</title>\n"
+"</head><body>\n"
+
+PAGEHEAD
+TABLEHEAD
+	;
+
+    static const char FOOTER[] = 
+"</table>"
+"<i>Explanation for ballstats</i>:<br>"
+"The numbers are c/s/w/l/b, where<br>"
+"c = The number of enemy balls you have cashed.<br>"
+"s = The number of your own balls you have returned.<br>"
+"w = The number of enemy balls your team has cashed.<br>"
+"l = The number of your own balls you have lost.<br>"
+"b = The fastest ballrun you have made.<br>"
+"<hr>%s<BR>\n\n" /* <-- Insert time here. */
+
+"</body></html>"
+	    ;
+
+    SortRankings();
+    
+    if (getenv(XPILOTRANKINGPAGE) != NULL) {
+	FILE * const file = fopen(getenv(XPILOTRANKINGPAGE), "w");
+	if (file != NULL) {
+	    int i;
+	    fprintf(file, "%s", HEADER);
+	    for (i = 0; i < MAX_SCORES; i++) {
+		const int j = rankedplayer[i];
+		const RankInfo *score = &scores[j];
+		if ( score->entry.nick[0] != '\0' ) {
+		    fprintf(file, "g(\"%s\", %.1f, %u, %u, %u, %u, "
+			    "%u, %u, %u, %u, %u, %.1f, \"%s@%s\", '%s');\n",
+			    score->entry.nick,
+			    score->score,
+			    score->entry.kills,
+			    score->entry.deaths,
+			    score->entry.rounds,
+			    score->entry.shots,
+			    score->entry.ballsCashed, score->entry.ballsSaved,
+			    score->entry.ballsWon, score->entry.ballsLost,
+			    score->entry.bestball,
+			    rankedscore[i],
+			    score->entry.real, score->entry.host,
+			    score->entry.logout);
+		}
+	    }
+	    fprintf(file, "</script>");
+	    fprintf(file, FOOTER, rank_showtime());
+	    fclose(file);
+	} else
+	    error("Could not open the rank file.");
+    }
+    if (getenv(XPILOTNOJSRANKINGPAGE) != NULL) {
+	FILE * const file = fopen(getenv(XPILOTNOJSRANKINGPAGE), "w");
+	if (file != NULL) {
+	    int i;
+	    fprintf(file, "%s", HEADERNOJS);
+	    for (i = 0; i < MAX_SCORES; i++) {
+		const int j = rankedplayer[i];
+		const RankInfo *score = &scores[j];
+		if ( score->entry.nick[0] != '\0' ) {
+		    fprintf(file, 
+			    "<tr><td align=left><tt>%d</tt>"
+			    "<td align=left><b>%s</b>"
+			    "<td align=right>%.1f"
+			    "<td align=right>%u"
+			    "<td align=right>%u"
+			    "<td align=right>%u"
+			    "<td align=right>%u"
+			    "<td align=center>%u/%u/%u/%u/%u"
+			    "<td align=right>%.1f"
+			    "<td align=center>%s@%s"
+			    "<td align=center>%s\n"
+			    "</tr>\n",
+			    i+1,
+			    score->entry.nick,
+			    score->score,
+			    score->entry.kills,
+			    score->entry.deaths,
+			    score->entry.rounds,
+			    score->entry.shots,
+			    score->entry.ballsCashed, score->entry.ballsSaved,
+			    score->entry.ballsWon, score->entry.ballsLost,
+			    score->entry.bestball,
+			    rankedscore[i],
+			    score->entry.real, score->entry.host,
+			    score->entry.logout);
+		}
+	    }
+	    fprintf(file, FOOTER, rank_showtime());
+	    fclose(file);
+	} else
+	    error("Could not open the rank file.");
+    }
+}
+
+/* Return a line with the ranking status of the specified player. */
+void
+Get_stats(player *pl, char *buf)
+{
+    RankInfo *score = pl->rank;
+
+    sprintf(buf, "%-15s  %4d/%4d, R: %3d, S: %5d, %2d/%2d/%2d/%2d (%d)",
+	    pl->name, score->entry.kills, score->entry.deaths,
+	    score->entry.rounds, score->entry.shots,
+	    score->entry.ballsCashed, score->entry.ballsSaved,
+	    score->entry.ballsWon, score->entry.ballsLost,
+	    score->entry.bestball);
+}
+
+
+/* Send a line with the ranks of the current players to the game. */
+void
+Show_ranks(void)
+{
+    char buf[1000] = "";
+    int i;
+
+    for (i = 0; i < MAX_SCORES; i++) {
+	RankInfo *score = &scores[rankedplayer[i]];
+	if (score->pl != NULL) {
+	    char msg[MSG_LEN];
+	    sprintf(msg, "%s [%d], ", score->entry.nick, i+1);
+	    strcat(buf, msg);
+	}
+    }
+
+    Set_message(buf);
+    return;
+}
+
+
+static void
+Init_scorenode(RankInfo *node,
+	       const char  nick[],
+	       const char  real[],
+	       const char  host[])
+{
+    strlcpy(node->entry.nick, nick, MAX_CHARS);
+    strlcpy(node->entry.real, real, MAX_CHARS);
+    strlcpy(node->entry.host, host, MAX_CHARS);
+    strcpy(node->entry.logout, "");
+    node->score = 0;
+    node->entry.kills = 0;
+    node->entry.deaths = 0;
+    node->entry.rounds = 0;
+    node->entry.shots = 0;
+    node->entry.ballsSaved = 0;
+    node->entry.ballsLost = 0;
+    node->entry.ballsCashed = 0;
+    node->entry.ballsWon = 0;
+    node->entry.bestball = 65535;
+    node->pl = NULL;
+}
+
+
+RankInfo *
+Rank_by_name(char *name)
+{
+    RankInfo *score;
+    int i;
+
+    for (i = 0; i < MAX_SCORES; i++) {
+	score = &scores[i];
+	if (strcmp(name, score->entry.nick) == 0) return score;
+    }
+
+    return NULL;
+}
+
+
+void
+Nuke_score(RankInfo *node)
+{
+    Init_scorenode(node, "", "", "");
+    node->entry.timestamp = 0;
+}
+
+
+static int
+Import_Oldest(FILE *file)
+{
+    struct oldScoreNode *nodes;
+    int imported = 0;
+
+    nodes = malloc(sizeof(*nodes)*MAX_SCORES);
+    if (nodes) {
+	int i;
+
+	imported = fread(nodes, sizeof(*nodes), MAX_SCORES, file);
+	for (i = 0; i < imported; i++) {
+	    memset(&scores[i].entry, sizeof(scores[i].entry), 0);
+	    strlcpy(scores[i].entry.nick, nodes[i].nick, MAX_CHARS);
+	    strlcpy(scores[i].entry.real, nodes[i].real, MAX_CHARS);
+	    strlcpy(scores[i].entry.host, nodes[i].host, MAX_CHARS);
+	    strlcpy(scores[i].entry.logout, nodes[i].logout, MAX_CHARS);
+	    scores[i].entry.timestamp = nodes[i].timestamp;
+	    scores[i].score = nodes[i].score;
+	    scores[i].entry.kills = nodes[i].kills;
+	    scores[i].entry.deaths = nodes[i].deaths;
+	    scores[i].entry.rounds = nodes[i].rounds;
+	    scores[i].entry.shots = nodes[i].firedShots;
+	    scores[i].entry.ballsSaved = nodes[i].ballsSaved;
+	    scores[i].entry.ballsLost = nodes[i].ballsLost;
+	    scores[i].entry.ballsWon = nodes[i].ballsWon;
+	    scores[i].entry.ballsCashed = nodes[i].ballsCashed;
+	    scores[i].entry.bestball = nodes[i].bestball;
+	    if (scores[i].entry.bestball == 0) {
+		/* Support for loading an even older score file. */
+		scores[i].entry.bestball = 65535;
+	    }
+	    scores[i].pl = NULL;
+	}
+	free(nodes);
+    }
+
+    return imported;
+}
+
+/* Read scores from disk, and zero-initialize the ones that are not used.
+   Call this on startup. */
+void
+Init_saved_scores(void)
+{
+    int i = 0;
+
+    xpilotscorefile = getenv(XPILOTSCOREFILE);
+    if (xpilotscorefile != NULL) {
+	FILE *file = fopen(xpilotscorefile, "r");
+	if (file != NULL) {
+	    int actual;
+
+	    actual = fread(&rank_head, sizeof(RankHead), 1, file);
+	    if (actual != 1) {
+		error("Error when reading score file!\n");
+		goto init_tail;
+	    }
+	    if (memcmp(rank_head.magic, RANK_MAGIC, 4) != 0) {
+		rewind(file);
+		i = Import_Oldest(file);
+		goto init_tail;
+	    }
+	    rank_head.version = ntohl(rank_head.version);
+	    rank_head.entries = ntohl(rank_head.entries);
+	    switch (RANK_VER_MAJ(rank_head.version)) {
+	    case 2:
+		/* Current version. */
+		break;
+	    default:
+		error("Unknown version of score file!\n");
+		goto init_tail;
+	    }
+
+	    for (i = 0; i < rank_head.entries; i++) {
+		RankInfo *node = &scores[i];
+		actual = fread(&node->entry, sizeof(node->entry), 1, file);
+		if (actual != 1) {
+		    error("Error when reading score file!\n");
+		    break;
+		}
+		node->entry.shots = ntohl(node->entry.shots);
+		node->entry.timestamp = ntohl(node->entry.timestamp);
+		node->entry.kills = ntohs(node->entry.kills);
+		node->entry.deaths = ntohs(node->entry.deaths);
+		node->entry.rounds = ntohs(node->entry.rounds);
+		node->entry.ballsSaved = ntohs(node->entry.ballsSaved);
+		node->entry.ballsLost = ntohs(node->entry.ballsLost);
+		node->entry.ballsWon = ntohs(node->entry.ballsWon);
+		node->entry.ballsCashed = ntohs(node->entry.ballsCashed);
+		node->entry.bestball = ntohs(node->entry.bestball);
+		node->score = (DFLOAT)((int32_t)ntohl(node->entry.disk_score))
+			/ 65536.0;
+		/* Fix buggy first implementation... */
+		if (node->score >= 65000) {
+		    node->score -= 65536;
+		}
+		node->pl = NULL;
+	    }
+	    fclose(file);
+	}
+    }
+    
+ init_tail:
+    while (i < MAX_SCORES) {
+	Init_scorenode(&scores[i], "", "", "");
+	scores[i].entry.timestamp = 0;
+	i++;
+    }
+}
+
+/* A player has logged in. Find his info or create new info by kicking
+   the player who hasn't played for the longest time. */
+void
+Get_saved_score(player *pl)
+{
+    RankInfo *score;
+    int oldest = 0;
+    int i;
+    updateScores = true;
+
+    for (i = 0; i < MAX_SCORES; i++) {
+	score = &scores[i];
+	if (strcmp(pl->name, score->entry.nick) == 0) {
+	    if (score->pl == NULL) {
+		/* Ok, found it. */
+		score->pl = pl;
+		strcpy(score->entry.logout, "playing");
+		pl->score = score->score;
+		pl->rank = score;
+		return;
+	    } else {
+		/* That scorenode is already in use by another player! */
+		pl->score = 0;
+		pl->rank = NULL;
+		return;
+	    
+	    }
+	} else if (score->entry.timestamp < scores[oldest].entry.timestamp) {
+	    oldest = i;
+	}
+    }
+
+    /* Didn't find it, use the least-recently-used node. */
+    score = &scores[oldest];
+
+    Init_scorenode(score, pl->name, pl->realname, pl->hostname);
+    strcpy(score->entry.logout, "playing");
+    score->pl = pl;
+    score->entry.timestamp = time(0);
+    pl->score = 0;
+    pl->rank = score;
+}
+
+/* A player has quit, save his info and mark him as not playing. */
+void
+Save_score(const player *pl)
+{
+    RankInfo *score = pl->rank;
+    score->score = pl->score;
+    strlcpy(score->entry.logout, rank_showtime(), MAX_CHARS);
+    score->pl = NULL;
+    score->entry.timestamp = time(0);
+}
+
+/* Save the scores to disk (not the webpage). */
+void
+Write_score_file(void)
+{
+    FILE *file = NULL;
+    char tmpfile[4096];
+    RankHead head;
+    int actual;
+    int i;
+
+    if (!xpilotscorefile) {
+	return;
+    }
+    actual = snprintf(tmpfile, sizeof(tmpfile), "%s-new", xpilotscorefile);
+    if (actual < strlen(xpilotscorefile) || actual > sizeof(tmpfile)) {
+	/* Use a shorter path-name and be happy... */
+	return;
+    }
+
+    file = fopen(tmpfile, "w");
+    if (file == NULL) {
+	return;
+    }
+
+    memcpy(head.magic, RANK_MAGIC, 4);
+    head.version = htonl(RANK_VER_CURRENT);
+    head.entries = htonl(rank_head.entries);
+    actual = fwrite(&head, sizeof(head), 1, file);
+    if (actual != 1) {
+	error("Error when writing score file head!\n");
+	fclose(file);
+	remove(tmpfile);
+	return;
+    }
+    for (i = 0; i < rank_head.entries; i++) {
+	RankEntry entry;
+	int idx = rankedplayer[i];
+
+	memcpy(&entry, &scores[idx].entry, sizeof(entry));
+	entry.disk_score = htonl(((uint32_t)(int32_t)
+				  (scores[idx].score*65536)));
+	entry.shots = htonl(entry.shots);
+	entry.timestamp = htonl(entry.timestamp);
+	entry.kills = htons(entry.kills);
+	entry.deaths = htons(entry.deaths);
+	entry.rounds = htons(entry.rounds);
+	entry.ballsSaved = htons(entry.ballsSaved);
+	entry.ballsLost = htons(entry.ballsLost);
+	entry.ballsWon = htons(entry.ballsWon);
+	entry.ballsCashed = htons(entry.ballsCashed);
+	entry.bestball = htons(entry.bestball);
+	actual = fwrite(&entry, sizeof(entry), 1, file);
+	if (actual != 1) {
+	    error("Error when writing score file!\n");
+	    break;
+	}
+    }
+    fclose(file);
+
+    /* Overwrite old score file. */
+    rename(tmpfile, xpilotscorefile);
+    remove(tmpfile);
+}
+
+/* This function checks wether the strings contains certain characters
+   that might be hazardous to include on a webpage (ie they screw it up). */
+bool
+IsLegalNameUserHost(const char string[])
+{
+    const int length = strlen(string);
+    int i;
+
+    for (i = 0; i < length; i++ )
+	switch ( string[i] ) {
+	case '<':
+	case '>':
+	case '\t':
+	    return false;
+	default:
+	    break;
+	}
+    return true;
+}
diff -urN xpilot-4.5.4.orig/src/server/rank.h xpilot-4.5.4/src/server/rank.h
--- xpilot-4.5.4.orig/src/server/rank.h	Thu Jan  1 01:00:00 1970
+++ xpilot-4.5.4/src/server/rank.h	Fri Jul 19 03:53:47 2002
@@ -0,0 +1,98 @@
+#ifndef RANK_H
+#define RANK_H
+
+#ifdef __linux__
+# include <stdint.h>
+#else
+# include <sys/bitypes.h>
+#endif
+
+
+struct oldScoreNode {
+    char nick[MAX_CHARS];
+    char real[MAX_CHARS];
+    char host[MAX_CHARS];
+    char logout[MAX_CHARS];
+    int  timestamp;
+    short score;
+    unsigned short kills;
+    unsigned short deaths;
+    unsigned short rounds;
+    unsigned long  firedShots;
+
+    unsigned short ballsSaved;
+    unsigned short ballsLost;
+    unsigned short ballsWon;
+    unsigned short ballsCashed;
+    unsigned short bestball;
+
+    char  futureextensions[6];
+    struct player *pl;
+};
+
+
+typedef struct RankHead {
+	char magic[4];
+	uint32_t version;
+	uint32_t entries;
+} RankHead;
+#define RANK_MAGIC		"rNk7"
+#define RANK_VER_MK(maj,min)	((((maj)&0xffff)<<16) | ((min)&0xffff))
+#define RANK_VER_MAJ(ver)	(((ver) >> 16)&0xffff)
+#define RANK_VER_MIN(ver)	(((ver) >> 16)&0xffff)
+#define RANK_VER_CUR_MAJ	2
+#define RANK_VER_CUR_MIN	0
+#define RANK_VER_CURRENT	RANK_VER_MK(RANK_VER_CUR_MAJ,RANK_VER_CUR_MIN)
+
+
+typedef struct RankEntry {
+    char nick[MAX_CHARS];
+    char real[MAX_CHARS];
+    char host[MAX_CHARS];
+    char logout[MAX_CHARS];
+    uint32_t disk_score;
+    uint32_t shots;
+    uint32_t timestamp;
+
+    uint16_t kills;
+    uint16_t deaths;
+    uint16_t rounds;
+    uint16_t ballsSaved;
+
+    uint16_t ballsLost;
+    uint16_t ballsWon;
+    uint16_t ballsCashed;
+    uint16_t bestball;
+} RankEntry;
+
+typedef struct RankInfo {
+    struct RankEntry entry;
+    DFLOAT score;
+    struct player *pl;
+} RankInfo;
+
+
+#define SetLogoutMessage(rank,msg) \
+	do{ strcpy((rank)->entry.logout, (msg)); }while(0)
+
+void Init_saved_scores(void);
+
+void Get_saved_score(struct player *pl);
+
+void Write_score_file(void);
+
+void Rank_score(void);
+
+void Save_score(const struct player * pl);
+
+void Get_stats(struct player *pl, char *buf);
+
+void Show_ranks(void);
+
+bool IsLegalNameUserHost(const char string[]);
+
+RankInfo *Rank_by_name(char *name);
+
+void Nuke_score(RankInfo *rank);
+
+#endif /* RANK_H */
diff -urN xpilot-4.5.4.orig/src/server/score.c xpilot-4.5.4/src/server/score.c
--- xpilot-4.5.4.orig/src/server/score.c	Mon Jan 21 23:04:03 2002
+++ xpilot-4.5.4/src/server/score.c	Fri Jul 19 03:53:47 2002
@@ -51,14 +51,14 @@
 
     if (BIT(World.rules->mode, TEAM_PLAY)) {
 	if (!teamShareScore) {
-	    pl->score += points;
+	    AddScore(pl, points);
 	}
 	TEAM_SCORE(pl->team, points);
     } else {
 	if (pl->alliance != ALLIANCE_NOT_SET && teamShareScore) {
 	    Alliance_score(pl->alliance, points);
 	} else {
-	    pl->score += points;
+	    AddScore(pl, points);
 	}
     }
 
@@ -79,7 +79,7 @@
 	DFLOAT share = World.teams[team].score / World.teams[team].NumMembers;
 	for (i = 0; i < NumPlayers; i++) {
 	    if (Players[i]->team == team) {
-		Players[i]->score = share;
+		SetScore(Players[i], share);
 	    }
 	}
     }
@@ -95,7 +95,7 @@
 
     for (i = 0; i < NumPlayers; i++) {
 	if (Players[i]->alliance == id) {
-	    Players[i]->score += share;
+	    AddScore(Players[i], share);
 	}
     }
 }
diff -urN xpilot-4.5.4.orig/src/server/server.c xpilot-4.5.4/src/server/server.c
--- xpilot-4.5.4.orig/src/server/server.c	Wed Feb 13 18:09:18 2002
+++ xpilot-4.5.4/src/server/server.c	Fri Jul 19 03:53:47 2002
@@ -72,6 +72,7 @@
 #include "portability.h"
 #include "server.h"
 #include "commonproto.h"
+#include "rank.h"
 
 char server_version[] = VERSION;
 
@@ -157,6 +158,8 @@
 
     Treasure_init();
 
+    Init_saved_scores(); /* ranking */
+
     /*
      * Get server's official name.
      */
@@ -324,6 +327,10 @@
 
     Contact_cleanup();
 
+    /* Ranking. */
+    Rank_score();
+    Write_score_file();
+
     Free_players();
     Free_shots();
     Free_map();
diff -urN xpilot-4.5.4.orig/src/server/shot.c xpilot-4.5.4/src/server/shot.c
--- xpilot-4.5.4.orig/src/server/shot.c	Tue Dec 11 13:45:13 2001
+++ xpilot-4.5.4/src/server/shot.c	Fri Jul 19 03:53:48 2002
@@ -586,7 +586,7 @@
 		return;
 	    Add_fuel(&(pl->fuel), (long)(ED_SHOT));
 	    sound_play_sensors(pl->pos.x, pl->pos.y, FIRE_SHOT_SOUND);
-	    pl->shots++;
+	    FireShot(pl);
 	}
 	if (!ShotsGravity) {
 	    CLR_BIT(status, GRAVITY);
diff -urN xpilot-4.5.4.orig/src/server/update.c xpilot-4.5.4/src/server/update.c
--- xpilot-4.5.4.orig/src/server/update.c	Tue Jun 11 05:59:39 2002
+++ xpilot-4.5.4/src/server/update.c	Fri Jul 19 03:53:48 2002
@@ -760,10 +760,40 @@
 	    }
 	}
 
+	if ( pl->flooding > FPS + 1 ) {
+	    char msg[MSG_LEN];
+	    sprintf(msg, "%s was kicked out because of flooding.", pl->name);
+	    Destroy_connection(pl->conn, "flooding");
+	    i--;
+	    continue;
+	} else if ( pl->flooding >= 0 )
+	    pl->flooding--;
+
+#define IDLETHRESHOLD (FPS * 60)
+
+	if ( IS_HUMAN_PTR(pl) ) {
+	    pl->rank->score = pl->score;
+	    if ( pl->mychar == ' ' ) {
+		if ( pl->idleCount++ == IDLETHRESHOLD ) {
+		    if ( NumPlayers - 1 > NumPseudoPlayers + NumRobots ) {
+			/* Kill player, he/she will be paused when returned
+			   to base, unless he/she wakes up. */
+			Kill_player(i);
+			IgnoreLastDeath(pl);
+		    } else
+			pl->idleCount = 0;
+		}
+	    }
+	}
+
 	if (pl->count == 0) {
 	    pl->count = -1;
 
 	    if (!BIT(pl->status, PLAYING)) {
+		if ( pl->idleCount >= IDLETHRESHOLD ) { /* idle */
+		    Pause_player(i, 1);
+		    continue;
+		}
 		SET_BIT(pl->status, PLAYING);
 		Go_home(i);
 	    }
diff -urN xpilot-4.5.4.orig/src/server/walls.c xpilot-4.5.4/src/server/walls.c
--- xpilot-4.5.4.orig/src/server/walls.c	Sun Apr 21 11:31:18 2002
+++ xpilot-4.5.4/src/server/walls.c	Fri Jul 19 03:53:48 2002
@@ -1007,6 +1007,7 @@
 			  tt->pos.x, tt->pos.y, "Treasure: ");
 		    sprintf(msg, " < %s (team %d) has replaced the treasure >",
 			    pl->name, pl->team);
+		    SavedBall(pl);
 		    Set_message(msg);
 		    break;
 		}
@@ -1021,17 +1022,21 @@
 		     * Ball has been brought back to home treasure.
 		     * The team should be punished.
 		     */
-		    sprintf(msg," < The ball was loose for %ld frames >",
-			    LONG_MAX - ball->life);
+		    long frames = LONG_MAX - ball->life;
+		    int ind = GetInd[ball->owner];
+
+		    sprintf(msg," < The ball was loose for %ld frames (%d)>",
+			    frames, GetBestBall(Players[ind]));
 		    Set_message(msg);
 		    if (captureTheFlag
 			&& !World.treasures[ms->treasure].have
 			&& !World.treasures[ms->treasure].empty) {
 			strcpy(msg, "Your treasure must be safe before you can cash an opponent's!");
 			Set_player_message(Players[GetInd[ball->owner]], msg);
-		    } else if (Punish_team(GetInd[ball->owner],
+		    } else if (Punish_team(ind,
 				    ball->treasure, ms->treasure))
 			CLR_BIT(ball->status, RECREATE);
+		    BallRun(Players[ind], frames);
 		}
 		ball->life = 0;
 		return;
@@ -1796,7 +1801,7 @@
     Set_message(msg);
 
     if (targetKillTeam) {
-	Players[killer]->kills++;
+	AddTargetKill(Players[killer]);
     }
 
     sc  = Rate(win_score, lose_score);
