Only in xpilot-4.U.3.svan2: README.rank
diff -ur xpilot-4.U.4a3/src/client/default.c xpilot-4.U.3.svan2/src/client/default.c
--- xpilot-4.U.4a3/src/client/default.c	Fri Jul 23 15:06:43 1999
+++ xpilot-4.U.3.svan2/src/client/default.c	Sat Jul 10 23:21:06 1999
@@ -122,7 +122,8 @@
 const char*	winHelpFile;
 #endif
 
-keys_t buttonDefs[MAX_POINTER_BUTTONS];
+buttondef_t	buttonDefs[MAX_POINTER_BUTTONS];
+
 
 /*
  * Structure to store all the client options.
@@ -2978,16 +2979,24 @@
      * Pointer button bindings
      */
     for (i = 0; i < MAX_POINTER_BUTTONS; i++) {
+	buttonDefs[i].num = 0;
 	sprintf(resValue, "pointerButton%d", i+1);
 	Get_resource(rDB, resValue, resValue, sizeof resValue);
 	ptr = resValue;
-	if (*ptr != '\0') {
+	while (ptr && *ptr != '\0') {
+	    char *next = strchr(ptr, ',');
+
+	    if (next) {
+		*next = '\0';
+		next++;
+	    }
 	    if (!strncasecmp(ptr, "key", 3))
 		ptr += 3;
 	    for (j = 0; j < NELEM(options); j++) {
 		if (options[j].key != KEY_DUMMY) {
 		    if (!strcasecmp(ptr, options[j].name + 3)) {
-			buttonDefs[i] = options[j].key;
+			buttonDefs[i].keys[buttonDefs[i].num] = options[j].key;
+			buttonDefs[i].num++;
 			break;
 		    }
 		}
@@ -2996,6 +3005,8 @@
 		errno = 0;
 		error("Unknown key \"%s\" for pointer button %d", resValue, i);
 	    }
+	    if (buttonDefs[i].num >= MAX_KEYS_PER_BUTTON) break;
+	    ptr = next;
 	}
     }
 
diff -ur xpilot-4.U.4a3/src/client/paintradar.c xpilot-4.U.3.svan2/src/client/paintradar.c
--- xpilot-4.U.4a3/src/client/paintradar.c	Fri Jul 23 15:06:33 1999
+++ xpilot-4.U.3.svan2/src/client/paintradar.c	Sat Jul 10 23:20:12 1999
@@ -147,13 +147,12 @@
 	int s;
 
 	s = radar_ptr[i].size;
-	if (s >= 0x80) {         /* from the same team */
-	    if (maxColors == 4)
-	      XSetForeground(dpy, radarGC, colors[RED].pixel);
-	    /* RED doesn't work with color switching, */
-	    /* default maxColors changed to 8         */
-	    else
-	      XSetForeground(dpy, radarGC, colors[4].pixel);
+	if (s >= 0x80) {   /* from the same team */
+	    if (colorSwitch && !multibuffer && maxColors >= 4) {
+		XSetForeground(dpy, radarGC, colors[4].pixel);
+	    } else {
+		XSetForeground(dpy, radarGC, colors[RED].pixel);
+	    }
  	    s-=0x80;
 	}
 	if (s == 0)
diff -ur xpilot-4.U.4a3/src/client/talk.c xpilot-4.U.3.svan2/src/client/talk.c
--- xpilot-4.U.4a3/src/client/talk.c	Thu Apr 16 19:39:47 1998
+++ xpilot-4.U.3.svan2/src/client/talk.c	Sun May 23 16:36:30 1999
@@ -429,6 +429,76 @@
     return result;	/* keep on talking if true, no more talking if false */
 }
 
+void Talk_paste(void){
+ 
+    int str_len    = strlen(talk_str);  /* current length */
+    int max_len    = MAX_CHARS - 2;     /* absolute max */
+    int new_len;                        /* after pasting */
+    int char_width = XTextWidth(talkFont, " ", 1);
+    int max_width  = (TALK_WINDOW_WIDTH - 2*TALK_INSIDE_BORDER - 5);
+ 
+    int accept_len = (max_width / char_width) - str_len + 1 ;
+                                        /* for still matching the window */
+    char paste_buf[MAX_CHARS - 2];		/* gets the XBuffer */
+    int  n_bytes;			/* returned from XBuffer*/
+    char tmp_str[MAX_CHARS - 2];
+ 
+    bool cursor_visible = talk_cursor.visible;
+    int i;
+ 
+    accept_len = (accept_len+str_len>max_len) ?
+	(max_len - str_len) : accept_len; /* limit it anyway */
+    if (!accept_len){
+	XBell(dpy, 100);
+	return;
+    }
+
+    strncpy(paste_buf, XFetchBytes(dpy, &n_bytes), accept_len);
+    /* XStoreBytes(dpy, "", 0); no spam? */
+    if ( n_bytes > accept_len){
+        n_bytes = accept_len;
+        XBell(dpy, 100);
+    }
+    paste_buf[accept_len] = '\0';
+
+    /* substitute unprintables */
+    for(i = 0; i<n_bytes; i++){
+        if (paste_buf[i] < 33 || (paste_buf[i] > 126 && paste_buf[i] < 161) ){
+            paste_buf[i] = ' ';
+        }
+    }
+ 
+    /* append or insert */
+    strcpy(tmp_str, talk_str);
+    strcpy(&tmp_str[talk_cursor.point], paste_buf);
+    strcpy(&tmp_str[talk_cursor.point + n_bytes],
+           &talk_str[talk_cursor.point]);
+    new_len = str_len + n_bytes;
+ 
+    Talk_cursor(false);
+    /*s. Talk_do_event()*/
+    if (talk_cursor.point < str_len) {
+        XSetForeground(dpy, talkGC, colors[BLACK].pixel);
+        XDrawString(dpy, talk_w, talkGC,
+                    talk_cursor.point * char_width + TALK_INSIDE_BORDER,
+                    talkFont->ascent + TALK_INSIDE_BORDER,
+                    &talk_str[talk_cursor.point],
+                    str_len - talk_cursor.point);
+        XSetForeground(dpy, talkGC, colors[WHITE].pixel);
+    }
+    XDrawString(dpy, talk_w, talkGC,
+                talk_cursor.point * char_width + TALK_INSIDE_BORDER,
+                talkFont->ascent + TALK_INSIDE_BORDER,
+                &tmp_str[talk_cursor.point],
+                new_len - talk_cursor.point);
+    talk_cursor.point += n_bytes;
+    Talk_cursor(cursor_visible);
+ 
+    strcpy(talk_str, tmp_str);
+    return; 
+}
+
+
 void Talk_resize(void)
 {
     if (talk_created) {
diff -ur xpilot-4.U.4a3/src/client/xevent.c xpilot-4.U.3.svan2/src/client/xevent.c
--- xpilot-4.U.4a3/src/client/xevent.c	Fri Jul 23 15:06:43 1999
+++ xpilot-4.U.3.svan2/src/client/xevent.c	Sat Jul 10 21:57:15 1999
@@ -65,7 +65,6 @@
 
 int		initialPointerControl = false;
 bool		pointerControl = false;
-extern keys_t	buttonDefs[MAX_POINTER_BUTTONS];
 extern Cursor	pointerControlCursor;
 
 
@@ -244,6 +243,8 @@
 	    initialPointerControl = true;
 	    Pointer_control_set_state(false);
 	}
+	XSelectInput(dpy, draw, PointerMotionMask | ButtonPressMask
+			| ButtonReleaseMask); /* cut'n'paste */
 	Talk_map_window(true);
     }
     else {
@@ -598,6 +599,7 @@
     n = XEventsQueued(dpy, type);
     for (i = 0; i < n; i++) {
 	XNextEvent(dpy, &event);
+	if (XFilterEvent(&event, None)) continue;
 #endif
 
 	switch (event.type) {
@@ -673,10 +675,18 @@
 		if (pointerControl
 		    && !talk_mapped
 		    && event.xbutton.button <= MAX_POINTER_BUTTONS) {
-		    if (Key_press(buttonDefs[event.xbutton.button-1])) {
-			Net_key_change();
+		    int i;
+		    
+		    for (i = 0;
+			 i < buttonDefs[event.xbutton.button-1].num; i++) {
+			if (Key_press(buttonDefs[event.xbutton.button-1].keys[i])) {
+			    Net_key_change();
+			}
 		    }
 		}
+		if (talk_mapped && event.xbutton.button == 2){
+		    Talk_paste();
+		}
 		break;
 	    }
 	    if (Widget_event(&event) != 0) {
@@ -707,8 +717,13 @@
 		if (pointerControl
 		    && !talk_mapped
 		    && event.xbutton.button <= MAX_POINTER_BUTTONS) {
-		    if (Key_release(buttonDefs[event.xbutton.button-1])) {
-			Net_key_change();
+		    int i;
+		    
+		    for (i = 0;
+			 i < buttonDefs[event.xbutton.button-1].num; i++) {
+			if (Key_release(buttonDefs[event.xbutton.button-1].keys[i])) {
+			    Net_key_change();
+			}
 		    }
 		}
 		break;
diff -ur xpilot-4.U.4a3/src/client/xinit.h xpilot-4.U.3.svan2/src/client/xinit.h
--- xpilot-4.U.4a3/src/client/xinit.h	Fri Oct  2 20:39:22 1998
+++ xpilot-4.U.3.svan2/src/client/xinit.h	Sat Jul 10 21:36:20 1999
@@ -34,8 +34,6 @@
 #define MAX_TOP_HEIGHT	1024
 #define DEF_TOP_HEIGHT	768
 
-#define MAX_POINTER_BUTTONS 5
-
 extern Atom		ProtocolAtom, KillAtom;
 extern int		buttonColor, windowColor, borderColor;
 extern int		ButtonHeight;
@@ -71,6 +69,7 @@
 extern void Talk_resize(void);
 extern void Talk_cursor(bool visible);
 extern void Talk_map_window(bool map);
+extern void Talk_paste(void);
 extern int Talk_do_event(XEvent *event);
 extern void Quit(void);
 extern int FatalError(Display *dpy);
diff -ur xpilot-4.U.4a3/src/common/config.h xpilot-4.U.3.svan2/src/common/config.h
--- xpilot-4.U.4a3/src/common/config.h	Fri Apr 17 13:40:51 1998
+++ xpilot-4.U.3.svan2/src/common/config.h	Sun May 23 16:10:48 1999
@@ -38,7 +38,7 @@
 #	ifdef	_WINDOWS
 #		define DEFAULT_MAP		"default.xp"
 #	else
-#		define DEFAULT_MAP		"globe.xp"
+#		define DEFAULT_MAP		"teamcup.map"
 #	endif
 #endif
 
diff -ur xpilot-4.U.4a3/src/common/keys.h xpilot-4.U.3.svan2/src/common/keys.h
--- xpilot-4.U.4a3/src/common/keys.h	Fri Jul 23 15:06:43 1999
+++ xpilot-4.U.3.svan2/src/common/keys.h	Sat Jul 10 22:05:55 1999
@@ -25,6 +25,9 @@
 #ifndef KEYS_H
 #define KEYS_H
 
+#define MAX_POINTER_BUTTONS	5
+#define MAX_KEYS_PER_BUTTON	10
+
 /*
  * The following enum type defines the possible actions as a result of
  * a keypress or keyrelease.
@@ -161,6 +164,13 @@
 
 extern char* Get_keyHelpString(keys_t key);
 extern const char *Get_keyResourceString(keys_t key);
+
+typedef struct {
+	keys_t	keys[MAX_KEYS_PER_BUTTON];
+	int	num;
+} buttondef_t;
+
+extern buttondef_t	buttonDefs[MAX_POINTER_BUTTONS];
 
 #endif
 
diff -ur xpilot-4.U.4a3/src/server/Imakefile xpilot-4.U.3.svan2/src/server/Imakefile
--- xpilot-4.U.4a3/src/server/Imakefile	Sun Aug 30 14:15:34 1998
+++ xpilot-4.U.3.svan2/src/server/Imakefile	Sun May 23 16:11:05 1999
@@ -40,13 +40,13 @@
 	cannon.c cmdline.c collision.c contact.c event.c frame.c id.c map.c \
 	metaserver.c netserver.c objpos.c option.c play.c player.c \
 	robot.c robotdef.c rules.c saudio.c sched.c server.c \
-	update.c walls.c
+	update.c walls.c rank.c
 
 OBJS = \
 	cannon.o cmdline.o collision.o contact.o event.o frame.o id.o map.o \
 	metaserver.o netserver.o objpos.o option.o play.o player.o \
 	robot.o robotdef.o rules.o saudio.o sched.o server.o \
-	update.o walls.o
+	update.o walls.o rank.o
 
 DEPLIBS = ../common/libxpcommon.a 
 
diff -ur xpilot-4.U.4a3/src/server/collision.c xpilot-4.U.3.svan2/src/server/collision.c
--- xpilot-4.U.4a3/src/server/collision.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/collision.c	Sun May 23 16:29:23 1999
@@ -400,7 +400,7 @@
 {
     player	*pl = Players[ind];
 
-    pl->score += (points);
+    AddScore(pl,points);
 
     if (pl->conn != NOT_CONNECTED)
 	Send_score_object(pl->conn, points, x, y, msg);
@@ -584,7 +584,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 = (int)floor(Rate(Players[i_tank_owner]->score,
 					    Players[j]->score)
@@ -611,7 +611,7 @@
 			Set_message(msg);
 			sound_play_sensors(pl->pos.x, pl->pos.y,
 					   PLAYER_RAN_OVER_PLAYER_SOUND);
-			Players[j]->kills++;
+			AddKill(Players[j]);
 			sc = (int)floor(Rate(Players[j]->score, pl->score)
 				   * runoverKillScoreMult);
 			Score_players(j_tank_owner, sc, pl->name,
@@ -666,6 +666,7 @@
 		    pl->ball = NULL;
 		    sound_play_sensors(pl->pos.x, pl->pos.y,
 				       CONNECT_BALL_SOUND);
+		    pl->grabbedBallFrame = main_loops;
 		}
 	    }
 	} else {
@@ -950,7 +951,7 @@
 			  OBJ_Y_IN_BLOCKS(pl),
 			  Players[killer]->name);
 		} else {
-		    Players[killer]->kills++;
+		    AddBallKill(Players[killer]);
 		    sc = (int)floor(Rate(Players[killer]->score, pl->score)
 			       * ballKillScoreMult);
 		    Score_players(killer, sc, pl->name,
@@ -1220,7 +1221,7 @@
 			      OBJ_Y_IN_BLOCKS(pl),
 			      (killer == -1) ? "[Explosion]" : pl->name);
 		    } else {
-			Players[killer]->kills++;
+			AddKill(Players[killer]);
 			sc = (int)floor(Rate(Players[killer]->score, pl->score)
 				   * explosionKillScoreMult);
 			Score_players(killer, sc, pl->name,
@@ -1362,7 +1363,7 @@
 			      Players[killer]->name);
 		    } else {
 			DFLOAT factor;
-			Players[killer]->kills++;
+			AddKill(Players[killer]);
 			switch (obj->type) {
 			case OBJ_SHOT:
 			    if (BIT(obj->mods.warhead, CLUSTER)) {
@@ -1712,7 +1713,7 @@
 						   PLAYER_ROASTED_SOUND);
 				Set_message(msg);
 				if (pl && pl->id != vic->id) {
-				    pl->kills++;
+				    AddLaserKill(pl);
 				    Robot_war(victims[j].ind, ind);
 				}
 			    }
diff -ur xpilot-4.U.4a3/src/server/contact.c xpilot-4.U.3.svan2/src/server/contact.c
--- xpilot-4.U.4a3/src/server/contact.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/contact.c	Sun Jul 18 01:26:31 1999
@@ -189,7 +189,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;
@@ -197,7 +199,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.",
@@ -215,6 +219,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 -ur xpilot-4.U.4a3/src/server/event.c xpilot-4.U.3.svan2/src/server/event.c
--- xpilot-4.U.4a3/src/server/event.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/event.c	Sat Jul 10 23:03:42 1999
@@ -220,6 +220,7 @@
     int			i;
 
     if (onoff != 0 && !BIT(pl->status, PAUSE)) { /* Turn pause mode on */
+        Detach_ball(ind, -1);
         Swappers[pl->team]=-1;
 	pl->count = 10*FPS;
 	pl->updateVisibility = 1;
@@ -227,6 +228,7 @@
 	SET_BIT(pl->status, PAUSE);
 	pl->mychar = 'P';
 	updateScores = true;
+	strcpy(pl->scorenode->logout, "paused");
 	if (BIT(pl->have, OBJ_BALL))
 	    Detach_ball(ind, -1);
     }
@@ -234,6 +236,7 @@
 	if (pl->count <= 0) {
 	    bool toolate = false;
 
+	    pl->idleCount = 0; /* idle */
 	    CLR_BIT(pl->status, PAUSE);
 	    updateScores = true;
 	    if (BIT(pl->mode, LIMITED_LIVES)) {
@@ -249,6 +252,7 @@
 		    }
 		}
 	    }
+	    strcpy(pl->scorenode->logout, "playing");
 	    if (toolate) {
 		pl->life = 0;
 		pl->mychar = 'W';
diff -ur xpilot-4.U.4a3/src/server/netserver.c xpilot-4.U.3.svan2/src/server/netserver.c
--- xpilot-4.U.4a3/src/server/netserver.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/netserver.c	Sun Jul 18 02:20:40 1999
@@ -159,6 +159,7 @@
 #include "saudio.h"
 #include "checknames.h"
 #include "server.h"
+#include "rank.h"
 
 char netserver_version[] = VERSION;
 
@@ -649,6 +650,66 @@
     return -1;
 }
 
+
+char *banned_reals[] = { "<", NULL };
+char *banned_nicks[] = { "<", NULL };
+char *banned_addrs[] = { NULL };
+char *banned_hosts[] = { "<", NULL };
+
+static void dcase(char *str)
+{
+	while (*str) {
+		*str = tolower(*str);
+		str++;
+	}
+}
+
+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;
+}
+
 /*
  * 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 +1005,10 @@
     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, conntact " LOCALGURU);
+	    return -1;
+    }
 
     return 0;
 }
@@ -954,6 +1019,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)
 {
     connection_t	*connp = &Conn[ind];
@@ -1007,9 +1096,13 @@
 	return -1;
     }
     pl = Players[NumPlayers];
+    strcpy(pl->illegalName, connp->nick);
     strcpy(pl->name, connp->nick);
     strcpy(pl->realname, connp->real);
     strcpy(pl->hostname, connp->host);
+    LegalizeName(pl->name);
+    LegalizeName(pl->realname);
+    LegalizeHost(pl->hostname);
     pl->isowner = (!strcmp(pl->realname, Server.name) &&
 		   !strcmp(connp->addr, "127.0.0.1"));
     if (connp->team != TEAM_NOT_SET) {
@@ -1089,6 +1182,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);
+    }
 
     conn_bit = (1 << ind);
     for (i = 0; i < World.NumCannons; i++) {
@@ -1153,6 +1250,14 @@
 	Set_message(msg);
     }
 
+    for (i = 0;  /* idle */
+	  i < NumPlayers;
+	  i++ )
+	if ( Players[i]->mychar == ' ' ) 
+	    Players[i]->idleCount = 0;
+
+    Get_saved_score(pl); /* ranking */
+
     return 0;
 }
 
@@ -1485,7 +1590,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) {
@@ -1933,6 +2038,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)) {
@@ -2392,6 +2498,22 @@
     return 1;
 }
 
+static char *Addr_by_ind(int ind)
+{
+    int			i;
+    connection_t	*connp;
+
+    for (i = 0; i < max_connections; i++) {
+	    int cind;
+	    connp = &Conn[i];
+	    if (connp->state == CONN_FREE || connp->id == -1) continue;
+	    if (GetInd[connp->id] == ind) return connp->addr;
+    }
+
+    return NULL;
+}
+
+
 static int Ind_by_name(char *name)
 {
   int i,j,len;
@@ -2442,6 +2564,8 @@
     char		*cp,
 			msg[MSG_LEN * 2];
 
+    pl->flooding += FPS/3;
+
     if ((cp = strchr (str, ':')) == NULL
 	|| cp == str
 	|| strchr("-_~)(/\\}{[]", cp[1])	/* smileys are smileys */
@@ -2526,20 +2650,63 @@
     }
 }
 
+
+static void Clear_Swappers(int ind)
+{
+	player   *pl=Players[ind];
+	int i;
+
+	for (i=0; i < MAX_TEAMS; i++) {  /* can't queue to two teams at once */
+		if (Swappers[i] == pl->id) {
+			Swappers[i] = -1;
+		}
+	}
+}
+
+
 static void Swap_team(int ind, char *args)
 {
   int      i,team;
-  player   *pl=Players[ind];
+  player   *pl, *origpl;
   char      msg[MSG_LEN*2];
 
-  for (i=0;i<MAX_TEAMS;i++)   /* can't queue to two teams at once */
-    if (Swappers[i]==pl->id)
-      Swappers[i]=-1;
-
-  if (!args)
-    sprintf(msg,"Not swapping to any team.");
-  else {
-    team=atoi(args);
+  pl = origpl = Players[ind];
+
+  if (!args) {
+	  Clear_Swappers(ind);
+	  sprintf(msg, "Not swapping to any team.");
+  } else {
+    char *rest;
+
+    team = strtol(args, &rest, 0);
+    if (rest != args && *rest != '\0') {
+	    if (!pl->isoperator) {
+		    sprintf(msg, "You need operator status to swap other "
+			    "players. [*Server reply*]");
+		    Set_player_message(pl, msg);
+		    return;
+	    }
+	    while (isspace(*rest) && *rest != '\0') rest++;
+	    ind = Ind_by_name(rest);
+	    switch (ind) {
+	    case -2:
+		    sprintf(msg,
+			    "Can not swap, %s matches more than one player!",
+			    rest);
+		    Set_player_message(pl, msg);
+		    return;
+	    case -1:
+		    sprintf(msg,
+			    "Can not swap, %s does not match any player!",
+			    rest);
+		    Set_player_message(pl, msg);
+		    return;
+	    }
+	    pl = Players[ind];
+    }
+    
+    Clear_Swappers(ind);
+
     if (pl->team >= MAX_TEAMS)
       sprintf(msg,"You do not currently have a team. Swapping doesn't work.");
     else if (team<0 || team>=MAX_TEAMS || World.teams[team].NumBases == 0)
@@ -2656,10 +2823,76 @@
     }
   }
   sprintf(msg+strlen(msg)," [*Server reply*]");
-  Set_player_message(pl,msg);
+  Set_player_message(origpl,msg);
   return;
 }
 
+
+static void Op_command(int ind, char *args, int issuer)
+{
+    int	      cmd;
+    player   *pl, *pli;
+    char      msg[MSG_LEN*2];
+    char     *origarg = args;
+
+    pl = Players[ind];
+    pli = Players[issuer];
+    
+    if (!args || (*args != '+' && *args != '-')) {
+	sprintf(msg, "Invalid arg to '/op'. [*Server reply*]");
+	Set_player_message(pli, msg);
+	return;
+    } else {
+	int priv = 0;
+    
+	if (ind != issuer && strcasecmp("adamel", pl->name) == 0) {
+		sprintf(msg, "Trying to mess with Adamel huh? [*Server reply*]",
+			origarg, pl->name);
+		Set_player_message(pli, msg);
+		return;
+	}
+
+	cmd = *args;
+	args++;
+	while (*args) {
+	    switch (*args) {
+	    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'. [*Server reply*]",
+			*args);
+		Set_player_message(pli, msg);
+		return;
+	    }
+	    args++;
+	}
+	if (cmd == '+') {
+	    pl->privs |= priv;
+	} else {
+	    pl->privs &= ~priv;
+	}
+	if (ind != issuer) {
+	    sprintf(msg, "%s executed '/op %s' on you.",
+		    pli->name, origarg);
+	    Set_player_message(pl, msg);
+	}
+	sprintf(msg, "Executed '/op %s' on %s [*Server reply*]",
+		origarg, pl->name);
+	Set_player_message(pli, msg);
+    }
+    
+}
+
+
 extern int game_lock;
 
 extern void Reset_all_players(void);
@@ -2686,11 +2919,12 @@
 enum Command {
   KICK_CMD, VERSION_CMD, HELP_CMD, RESET_CMD, TEAM_CMD,
   PASSWORD_CMD, LOCK_CMD, SET_CMD, PAUSE_CMD, SHOW_CMD, 
-  ADVANCE_CMD, NO_CMD
+  ADVANCE_CMD, ADDR_CMD, OP_CMD, NO_CMD
 };
 
 typedef struct {
   const char *name;
+  const char *match;
   const char *help;
   int operOnly;
   enum Command number;
@@ -2699,12 +2933,14 @@
 static commandInfo commands[] = {
   {
     "help",
+    "h",
     "Without arguments, prints command list. /help <command> gives more info.",
     0,
     HELP_CMD
   },
   {
     "team",
+    "t",
     "/team <team number> swaps you to given team. "
                  "Can be used with full teams too.",
     0,
@@ -2712,18 +2948,21 @@
   },
   {
     "version",
+    "v",
     "Print server version.",
     0,
     VERSION_CMD
   },
   {
     "lock",
+    "l",
     "Just /lock tells lock status. /lock 1 locks, /lock 0 unlocks. (operator)",
     0,      /* checked in the function */
     LOCK_CMD
   },
   {
     "password",
+    "pas",
     "/password <string>. If string matches -password option, "
                                       "gives operator status.",
     0,
@@ -2731,12 +2970,14 @@
   },
   {
     "pause",
+    "pau",
     "/pause <player name or ID number>. Pauses player. (operator)",
     1,
     PAUSE_CMD
   },
   {
     "reset",
+    "r",
     "Just /reset starts a new round."
         "/reset all  also sets scores to 0. (operator)",
     1,
@@ -2744,28 +2985,47 @@
   },
   {
     "set",
+    "set",
     "/set <option> <value> sets a server option. (operator)",
     1,
     SET_CMD
   },
   {
     "kick",
+    "k",
     "/kick <player name or ID number>. Remove a player from game. (operator)",
     1,
     KICK_CMD
   },
   {
     "show",
-    "/show queue. Show the names of players waiting to enter.",
+    "sh",
+    "/show queue. Show the names of players waiting to enter.\n"
+    "/show stats [player name or ID number]. Show player ranking info.",
     0,
     SHOW_CMD
   },
   {
     "advance",
+    "adv",
     "/advance <name of player in the queue>. "
          "Move the player to the front of the queue. (operator)",
     1,
     ADVANCE_CMD
+  },
+  {
+    "addr",
+    "addr",
+    "/addr <player name or ID number>. Show IP-address of player. (operator)",
+    1,
+    ADDR_CMD
+  },
+  {
+    "op",
+    "o",
+    "/op <command> [player name or ID number]. Operator commands. (operator)",
+    1,
+    OP_CMD
   }
 };
 
@@ -2775,13 +3035,18 @@
     int                 plind  = GetInd[connp->id];
     player		*pl = Players[plind];
     int			i;
-    char		*args, msg[MSG_LEN * 2];
+    char		*args, *args2, msg[MSG_LEN * 2];
 
-    if (args=strchr(cmd,' '))
-      *args++=0;               /* separate arguments from command */
+    if (args=strchr(cmd,' ')) {
+	    /* separate arguments from command */
+	    *args = '\0';
+	    args++;
+	    args = strtok(args," ");
+	    args2 = strtok(NULL," ");
+    }
 
     for (i=0; i<NELEM(commands); i++)
-      if (!strcasecmp(cmd,commands[i].name))
+      if (!strncasecmp(cmd,commands[i].match,strlen(commands[i].match)))
 	break;
     if (i==NELEM(commands)) {
       i=NO_CMD;
@@ -2833,7 +3098,7 @@
     case SHOW_CMD:
       if (!args)
 	sprintf(msg,"Show what?");
-      else if (!strcasecmp(args,"queue")) {
+      else if (!strncasecmp(args,"q",1)) { /* queue */
 	int len=0, i, count;
 	struct queued_player *p=qp_list;
 
@@ -2850,8 +3115,26 @@
 	  p=p->next;
 	} while (p && len < MSG_LEN - 25);
 	*(msg+len-2)=0;                  /* -2 to strip spaces */
-      } else
-	sprintf(msg,"Unrecognized argument to /show.");
+      } else if (!strncasecmp(args,"s", 1)) { /* stats */
+	      if (args2) {
+		      i = Ind_by_name(args2);
+		      if (i < 0) {
+			      if (i == -1)
+				      sprintf(msg,"Name does not match any player.");
+			      else if (i == -2)
+				      sprintf(msg,"Name matches several players.");
+			      break;
+		      }
+		      Get_stats(Players[i], msg);
+	      } else {
+		      Get_stats(pl, msg);
+	      }
+      } else {
+	      if (!args2) {
+		      sprintf(msg,"Unrecognized argument to /show.");
+		      break;
+	      }
+      }
       break;
 
     case TEAM_CMD:
@@ -2876,18 +3159,54 @@
       else
 	sprintf(msg,"Error.");
       break;
+      
+    case ADDR_CMD:
+	if ((i = Ind_by_name(args)) >= 0) {
+	    char *addr;
+
+	    addr = Addr_by_ind(i);
+	    if (addr == NULL) {
+		sprintf(msg, "Unable to get address for %s", args);
+	    } else {
+		sprintf(msg, "%s plays from: %s",
+			Players[i]->name, addr);
+	    }
+	} else if (i==-1)
+	    sprintf(msg,"Name does not match any player.");
+	else if (i==-2)
+	    sprintf(msg,"Name matches several players.");
+	else
+	    sprintf(msg,"Error.");
+	break;
 
+    case OP_CMD:
+	if (!args2) {
+	    Op_command(plind, args, plind);
+	    return;
+	} else {
+	    if ((i = Ind_by_name(args2)) >= 0) {
+		Op_command(i, args, plind);
+		return;
+	    } else if (i==-1)
+		sprintf(msg,"Name does not match any player.");
+	    else if (i==-2)
+		sprintf(msg,"Name matches several players.");
+	    else
+		sprintf(msg,"Error.");
+	}
+	break;
+		    
     case VERSION_CMD:
-      sprintf(msg,"Xpilot 4.1.0 + patch 4.U.4alpha2");
+      sprintf(msg,"Xpilot 4.1.0 + patch 4.U.4alpha3 + Svan ranking");
       break;
-
+      
     case HELP_CMD:
       if (!args)
 	sprintf(msg,"Commands: help team version lock password pause "\
-		"reset set kick show");
+		"reset set kick show advance addr op");
       else {
 	for (i=0;i<NELEM(commands);i++)
-	  if (!strcasecmp(args,commands[i].name))
+	  if (!strncasecmp(args,commands[i].match,strlen(commands[i].match)))
 	    break;
 	if (i==NELEM(commands))
 	  sprintf(msg,"No help for nonexistent command '%s'.",args);
@@ -2918,16 +3237,31 @@
 	  numberOfRounds=0;
 	return;
       }
-
-    case PASSWORD_CMD:
-      if (!password || !args || strcmp(args,password))
+      
+    case PASSWORD_CMD: {
+      char salt[3];
+      
+      if (!password || !args)
 	sprintf(msg,"Wrong.");
       else {
-	if (!pl->isoperator)
-	  NumOperators++;
-	pl->isoperator=1;
-	sprintf(msg,"You got operator status.");
+        if (strlen(password) < 2) {
+		sprintf(msg,"Bogus password in server.");
+		break;
+	}
+	salt[0] = password[0];
+	salt[1] = password[1];
+	salt[2] = '\0';
+	if (strcmp(crypt(args,salt),password)) {
+		sprintf(msg,"Wrong.");
+	} else {
+	  if (!pl->isoperator)
+	     NumOperators++;
+	  pl->isoperator=1;
+	  pl->privs |= PRIV_AUTOKICKLAST;
+	  sprintf(msg,"You got operator status.");
+	}
       }
+    }
       break;
 
     case LOCK_CMD:
@@ -2950,14 +3284,14 @@
       break;
 
     case SET_CMD:
-      if (!args || !(args=strtok(args," ")) || !(cmd=strtok(NULL," ")) )
+      if (!args || !args2)
 	sprintf(msg,"Usage: /set option value.");
-      else if ((i=Tune_option(args,cmd)) == 1) {
+      else if ((i=Tune_option(args,args2)) == 1) {
 	if (!strcasecmp(args,"password"))
 	  sprintf(msg,"Operation successful.");
 	else {
 	  sprintf(msg," < Option %s set to %s by %s. >",
-		  args,cmd,pl->name);
+		  args,args2,pl->name);
 	  Set_message(msg);
 	  return;
 	}
@@ -3237,7 +3571,11 @@
 
 	motd_loops = main_loops;
 
-	if ((fd = open(SERVERMOTDFILE, O_RDONLY)) == -1) {
+#define XPILOTSERVERMOTD	"XPILOTSERVERMOTD"
+	if ((fd = open(getenv(XPILOTSERVERMOTD) ?
+		       getenv(XPILOTSERVERMOTD)
+		       : SERVERMOTDFILE,
+		       O_RDONLY)) == -1) {
 	    motd_size = 0;
 	    return -1;
 	}
diff -ur xpilot-4.U.4a3/src/server/object.h xpilot-4.U.3.svan2/src/server/object.h
--- xpilot-4.U.4a3/src/server/object.h	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/object.h	Sun Jul 18 00:40:45 1999
@@ -50,7 +50,10 @@
 #include "NT/winNet.h"
 #endif
 
-/*
+#include "rank.h"
+#include "rules.h"
+ 
+ /*
  * Different types of objects, including player.
  * Robots and tanks are players but have an additional bit.
  * Smart missile, heatseeker and torpedoe can be merged into missile.
@@ -378,6 +381,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 */
     u_short	pseudo_team;		/* Which team is used for my tanks */
@@ -425,6 +429,67 @@
 #ifdef __cplusplus
 		player() {}
 #endif
+
+    int		idleCount;		/* idle */
+    short	flooding;
+    ScoreNode * scorenode;
+
+    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)	\
+	{ if ((pl)->scorenode) (pl)->deaths = 0; }
+#define IgnoreLastDeath(pl) \
+	{ if ((pl)->scorenode) (pl)->scorenode->deaths--;}
+
+#define AddRound(pl)	{ if ((pl)->scorenode) (pl)->scorenode->rounds++; }
+
+#define FireShot(pl)	{ if ((pl)->scorenode) (pl)->scorenode->firedShots++; }
+
+#define SavedBall(pl)	{ if ((pl)->scorenode) (pl)->scorenode->ballsSaved++; }
+#define LostBall(pl)	{ if ((pl)->scorenode) (pl)->scorenode->ballsLost++; }
+#define CashedBall(pl)	{ if ((pl)->scorenode) (pl)->scorenode->ballsCashed++;}
+#define WonBall(pl)	{ if ((pl)->scorenode) (pl)->scorenode->ballsWon++; }
+#define BallRun(pl,tim)	{ \
+	 if ((pl)->scorenode && (tim) < (pl)->scorenode->bestball) \
+		 (pl)->scorenode->bestball = (tim); }
+
+#define AddDeath(pl) { \
+	(pl)->deaths++; \
+	if ((pl)->scorenode) (pl)->scorenode->deaths++; \
+}
+
+#define AddKill(pl) { \
+	(pl)->kills++; \
+	if ((pl)->scorenode) (pl)->scorenode->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)->scorenode) (pl)->scorenode->score += add; \
+}
+
+#define SetScore(pl,newScore) { \
+	(pl)->score = newScore; \
+	if ((pl)->scorenode) (pl)->scorenode->score = newScore; \
+}
+
+#define GetBestBall(pl)	((pl)->scorenode ? (pl)->scorenode->bestball : 65535)
 
 #endif
diff -ur xpilot-4.U.4a3/src/server/play.c xpilot-4.U.3.svan2/src/server/play.c
--- xpilot-4.U.4a3/src/server/play.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/play.c	Sun May 23 16:11:06 1999
@@ -1002,8 +1002,7 @@
 		|| (BIT(Players[i]->status, PAUSE)
 		    && Players[i]->count <= 0)
 		|| (BIT(Players[i]->status, GAME_OVER)
-		    && Players[i]->mychar == 'W'
-		    && Players[i]->score == 0)) {
+		    && Players[i]->mychar == 'W')) {
 		continue;
 	    }
 	    if (Players[i]->team == td->team) {
@@ -1051,18 +1050,23 @@
 	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 (i == ind && lose_team_members > 0) {
+		    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;
@@ -1286,6 +1290,7 @@
 	    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 -ur xpilot-4.U.4a3/src/server/player.c xpilot-4.U.3.svan2/src/server/player.c
--- xpilot-4.U.4a3/src/server/player.c	Fri Jul 23 15:06:34 1999
+++ xpilot-4.U.3.svan2/src/server/player.c	Sun Jul 18 00:28:04 1999
@@ -22,6 +22,8 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+#define HAVE_GETPRIORITY
+
 #ifdef	_WINDOWS
 #include "NT/winServer.h"
 #include <math.h>
@@ -29,6 +31,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#ifdef HAVE_GETPRIORITY
+#include <sys/resource.h>
+#endif
 #endif
 
 #define SERVER
@@ -44,6 +49,7 @@
 #include "saudio.h"
 #include "error.h"
 #include "objpos.h"
+#include "rank.h"
 
 char player_version[] = VERSION;
 
@@ -441,8 +447,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
@@ -492,6 +502,8 @@
 
     pl->isoperator = 0;
 
+    pl->privs = 0;
+
     return pl->id;
 }
 
@@ -603,12 +615,15 @@
 		    i--;
 		    continue;
 		}
+		IgnoreLastDeath(pl);
 	    }
 	}
 	CLR_BIT(pl->status, GAME_OVER);
 	CLR_BIT(pl->have, OBJ_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;
@@ -893,6 +908,20 @@
   return;
 }
 
+static void
+new_process(void)
+{
+	switch (fork()) {
+	case -1:
+		return;
+	case 0:
+		break;
+	default:
+		raise(SIGKILL);
+		break;
+	}
+}
+
 void Team_game_over(int winning_team, const char *reason)
 {
     int			i, j;
@@ -956,11 +985,18 @@
 	}
     }
 
+#if 0
+    /* Fork and kill parent */
+    new_process();
+#endif
+	    
     Reset_all_players();
 
     Count_rounds();
 
     free(best_players);
+
+    Rank_score(); Show_ranks();  /* ranking */
 }
 
 void Individual_game_over(int winner)
@@ -1116,6 +1152,8 @@
 	    Kill_player(i);
 	else
 	    Player_death_reset(i);
+	IgnoreLastDeath(pl);
+
 	if (pl != Players[i]) {
 	    continue;
 	}
@@ -1683,6 +1721,20 @@
 	NumPseudoPlayers--;
     }
 
+    else if ( IS_HUMAN_PTR(pl) ) { /* ranking */
+	Save_score(pl);
+	if ( NumPlayers == NumRobots + NumPseudoPlayers ) {
+	    Rank_score();
+	    Print_saved_scores();
+#ifdef HAVE_GETPRIORITY
+	    if (getpriority(PRIO_PROCESS, 0) > 0) {
+		    End_game();
+	    }
+#endif
+
+	}
+    }
+
     if (pl->team != TEAM_NOT_SET && !IS_TANK_PTR(pl)) {
 	World.teams[pl->team].NumMembers--;
 	if (IS_ROBOT_PTR(pl))
@@ -1869,7 +1921,7 @@
 	pl->life++;
     }
 
-    pl->deaths++;
+    AddDeath(pl);
 
     pl->have	= DEF_HAVE;
     pl->used	|= DEF_USED;
Only in xpilot-4.U.3.svan2/src/server: rank.c
Only in xpilot-4.U.3.svan2/src/server: rank.h
diff -ur xpilot-4.U.4a3/src/server/server.c xpilot-4.U.3.svan2/src/server/server.c
--- xpilot-4.U.4a3/src/server/server.c	Fri Jul 23 15:06:35 1999
+++ xpilot-4.U.3.svan2/src/server/server.c	Sun May 23 16:11:06 1999
@@ -69,6 +69,7 @@
 #include "error.h"
 #include "portability.h"
 #include "server.h"
+#include "rank.h"
 
 char server_version[] = VERSION;
 
@@ -157,6 +158,8 @@
 
     Robot_init();
 
+    Init_saved_scores(); /* ranking */
+
     if (BIT(World.rules->mode, TEAM_PLAY)) {
 	int i;
 	for (i=0; i < World.NumTreasures; i++)
@@ -310,6 +313,9 @@
     Meta_gone();
 
     Contact_cleanup();
+
+    Rank_score();  /* ranking */
+    Print_saved_scores();
 
     Free_players();
     Free_shots();
diff -ur xpilot-4.U.4a3/src/server/update.c xpilot-4.U.3.svan2/src/server/update.c
--- xpilot-4.U.4a3/src/server/update.c	Fri Jul 23 15:06:35 1999
+++ xpilot-4.U.3.svan2/src/server/update.c	Sat Jul 10 23:04:22 1999
@@ -652,10 +652,39 @@
 	    }
 	}
 
+	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->scorenode->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 -ur xpilot-4.U.4a3/src/server/walls.c xpilot-4.U.3.svan2/src/server/walls.c
--- xpilot-4.U.4a3/src/server/walls.c	Fri Jul 23 15:06:35 1999
+++ xpilot-4.U.3.svan2/src/server/walls.c	Fri Jul 16 01:19:34 1999
@@ -987,6 +987,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;
 		}
@@ -1000,12 +1001,16 @@
 		     * Ball has been brought back to home treasure.
 		     * The team should be punished.
 		     */
-		    sprintf(msg," < The ball was loose for %d frames >",
-			    LONG_MAX - mi->obj->life);
+		    int frames = LONG_MAX - mi->obj->life;
+		    int ind = GetInd[mi->obj->owner];
+
+		    sprintf(msg," < The ball was loose for %d frames (%d)>",
+			    frames, GetBestBall(Players[ind]));
 		    Set_message(msg);
-		    if (Punish_team(GetInd[mi->obj->owner],
-				    mi->obj->treasure, ms->treasure))
+		    if (Punish_team(ind, mi->obj->treasure, ms->treasure)) {
 			CLR_BIT(mi->obj->status, RECREATE);
+		    }
+		    BallRun(Players[ind], frames);
 		}
 		mi->obj->life = 0;
 		return;
@@ -1763,7 +1768,7 @@
     Set_message(msg);
 
     if (targetKillTeam) {
-	Players[killer]->kills++;
+	AddTargetKill(Players[killer]);
     }
 
     sc  = Rate(win_score, lose_score);
