diff -urN xpilot-4.1.0/Local.config xpilot-4.U.4tc99b/Local.config
--- xpilot-4.1.0/Local.config	Fri Oct 30 13:35:18 1998
+++ xpilot-4.U.4tc99b/Local.config	Wed Sep 22 20:44:48 1999
@@ -172,11 +172,11 @@
  * or contains the word "beta" or "alpha".
  */
       VERSION_MAJOR = 4
-      VERSION_MINOR = 1
- VERSION_PATCHLEVEL = 0
+      VERSION_MINOR = U
+ VERSION_PATCHLEVEL = 4
     VERSION_WINDOWS = 13
-     VERSION_STATUS = 
-        RELEASEDATE = Oct 30th, 1998
+     VERSION_STATUS = TC99b
+        RELEASEDATE = Aug 30th, 1999
 
 
 /*
diff -urN xpilot-4.1.0/src/client/client.c xpilot-4.U.4tc99b/src/client/client.c
--- xpilot-4.1.0/src/client/client.c	Wed Sep 16 20:35:40 1998
+++ xpilot-4.U.4tc99b/src/client/client.c	Mon Aug 30 00:58:49 1999
@@ -56,6 +56,8 @@
 
 #define MAX_CHECKPOINT	26
 
+char *fast_msgs[N_MSGS];
+
 int			scoresChanged = 0;
 int			RadarHeight = 0;
 
diff -urN xpilot-4.1.0/src/client/client.h xpilot-4.U.4tc99b/src/client/client.h
--- xpilot-4.1.0/src/client/client.h	Wed Sep 16 20:35:40 1998
+++ xpilot-4.U.4tc99b/src/client/client.h	Mon Aug 30 00:58:50 1999
@@ -252,6 +252,8 @@
 extern int 	maxVolume;		/* maximum volume (in percent) */
 #endif /* SOUND */
 
+extern int	snoopEyesId;		/* client's view of game */
+
 int Fuel_by_pos(int x, int y);
 int Target_alive(int x, int y, int *damage);
 int Target_by_index(int ind, int *xp, int *yp, int *dead_time, int *damage);
diff -urN xpilot-4.1.0/src/client/colors.c xpilot-4.U.4tc99b/src/client/colors.c
--- xpilot-4.1.0/src/client/colors.c	Fri Oct  2 20:39:21 1998
+++ xpilot-4.U.4tc99b/src/client/colors.c	Mon Aug 30 00:58:50 1999
@@ -502,15 +502,18 @@
      * planes at once.
      */
     for (p=0; p<2; p++) {
+	int num=0;
+
 	dpl_1[p] = dpl_2[p] = 0;
 
 	for (i=0; i<32; i++) {
 	    if (!((1<<i)&dbuf_state->masks[p])) {
-		if (dpl_1[p]) {
-		    dpl_2[p] |= 1<<i;
+	        num++;
+		if (num==1 || num==3 ) {
+		    dpl_1[p] |= 1<<i;   /* planes with moving radar objects */
 		}
 		else {
-		    dpl_1[p] |= 1<<i;
+		    dpl_2[p] |= 1<<i;   /* constant map part of radar */
 		}
 	    }
 	}
diff -urN xpilot-4.1.0/src/client/default.c xpilot-4.U.4tc99b/src/client/default.c
--- xpilot-4.1.0/src/client/default.c	Tue Oct  6 16:52:19 1998
+++ xpilot-4.U.4tc99b/src/client/default.c	Mon Aug 30 00:58:50 1999
@@ -71,6 +71,10 @@
 
 char default_version[] = VERSION;
 
+extern char *fast_msgs[];
+char fast_temp_buf[7];   /* can handle up to 999 fast msgs */
+char *fast_temp_buf_big;
+
 #ifndef	lint
 static char sourceid[] =
     "@(#)$Id: default.c,v 4.7 1998/10/06 14:52:19 bert Exp $";
@@ -771,7 +775,7 @@
     {
 	"maxColors",
 	NULL,
-	"4",
+	"8",
 	KEY_DUMMY,
 	"The number of colors to use.  Valid values are 4, 8 and 16.\n"
     },
@@ -979,6 +983,13 @@
 	"Valid values are all even numbers smaller than maxColors.\n"
     },
     {
+        "oldTextColor",
+	NULL,
+	"4",
+	KEY_DUMMY,
+	"Which color number to use for drawing old text strings.\n"
+    },
+    {
 	"outlineDecor",
 	NULL,
 	"No",
@@ -1658,7 +1669,286 @@
 	"Specifies the device name of the frame buffer.\n"
     },
 #endif    
-
+    {
+	"keySendMsg1",
+	NULL,
+	"F1",
+	KEY_MSG_1,
+	"Sends the talkmessage stored in msg1.\n"
+    },
+    {
+	"msg1",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 1.\n"
+    },
+    {
+	"keySendMsg2",
+	NULL,
+	"F2",
+	KEY_MSG_2,
+	"Sends the talkmessage stored in msg2.\n"
+    },
+    {
+	"msg2",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 2.\n"
+    },
+    {
+	"keySendMsg3",
+	NULL,
+	"F3",
+	KEY_MSG_3,
+	"Sends the talkmessage stored in msg3.\n"
+    },
+    {
+	"msg3",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 3.\n"
+    },
+    {
+	"keySendMsg4",
+	NULL,
+	"F4",
+	KEY_MSG_4,
+	"Sends the talkmessage stored in msg4.\n"
+    },
+    {
+	"msg4",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 4.\n"
+    },
+    {
+	"keySendMsg5",
+	NULL,
+	"F5",
+	KEY_MSG_5,
+	"Sends the talkmessage stored in msg5.\n"
+    },
+    {
+	"msg5",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 5.\n"
+    },
+    {
+	"keySendMsg6",
+	NULL,
+	"F6",
+	KEY_MSG_6,
+	"Sends the talkmessage stored in msg6.\n"
+    },
+    {
+	"msg6",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 6.\n"
+    },
+    {
+	"keySendMsg7",
+	NULL,
+	"F7",
+	KEY_MSG_7,
+	"Sends the talkmessage stored in msg7.\n"
+    },
+    {
+	"msg7",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 7.\n"
+    },
+    {
+	"keySendMsg8",
+	NULL,
+	"F8",
+	KEY_MSG_8,
+	"Sends the talkmessage stored in msg8.\n"
+    },
+    {
+	"msg8",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 8.\n"
+    },
+    {
+	"keySendMsg9",
+	NULL,
+	"F9",
+	KEY_MSG_9,
+	"Sends the talkmessage stored in msg9.\n"
+    },
+    {
+	"msg9",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 9.\n"
+    },
+    {
+	"keySendMsg10",
+	NULL,
+	"F10",
+	KEY_MSG_10,
+	"Sends the talkmessage stored in msg10.\n"
+    },
+    {
+	"msg10",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 10.\n"
+    },
+    {
+	"keySendMsg11",
+	NULL,
+	"F11",
+	KEY_MSG_11,
+	"Sends the talkmessage stored in msg11.\n"
+    },
+    {
+	"msg11",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 11.\n"
+    },
+    {
+	"keySendMsg12",
+	NULL,
+	"F12",
+	KEY_MSG_12,
+	"Sends the talkmessage stored in msg12.\n"
+    },
+    {
+	"msg12",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 12.\n"
+    },
+    {
+	"keySendMsg13",
+	NULL,
+	"",
+	KEY_MSG_13,
+	"Sends the talkmessage stored in msg13.\n"
+    },
+    {
+	"msg13",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 13.\n"
+    },
+    {
+	"keySendMsg14",
+	NULL,
+	"",
+	KEY_MSG_14,
+	"Sends the talkmessage stored in msg14.\n"
+    },
+    {
+	"msg14",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 14.\n"
+    },
+    {
+	"keySendMsg15",
+	NULL,
+	"",
+	KEY_MSG_15,
+	"Sends the talkmessage stored in msg15.\n"
+    },
+    {
+	"msg15",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 15.\n"
+    },
+    {
+	"keySendMsg16",
+	NULL,
+	"",
+	KEY_MSG_16,
+	"Sends the talkmessage stored in msg16.\n"
+    },
+    {
+	"msg16",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 16.\n"
+    },
+    {
+	"keySendMsg17",
+	NULL,
+	"",
+	KEY_MSG_17,
+	"Sends the talkmessage stored in msg17.\n"
+    },
+    {
+	"msg17",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 17.\n"
+    },
+    {
+	"keySendMsg18",
+	NULL,
+	"",
+	KEY_MSG_18,
+	"Sends the talkmessage stored in msg18.\n"
+    },
+    {
+	"msg18",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 18.\n"
+    },
+    {
+	"keySendMsg19",
+	NULL,
+	"",
+	KEY_MSG_19,
+	"Sends the talkmessage stored in msg19.\n"
+    },
+    {
+	"msg19",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 19.\n"
+    },
+    {
+	"keySendMsg20",
+	NULL,
+	"",
+	KEY_MSG_20,
+	"Sends the talkmessage stored in msg20.\n"
+    },
+    {
+	"msg20",
+	NULL,
+	"",
+	KEY_DUMMY,
+	"Talkmessage 20.\n"
+    },
 };
 
 
@@ -2271,6 +2561,20 @@
 
 #endif	/* _WINDOWS */
 
+    if ((fast_temp_buf_big = (char *)malloc(FAST_MSG_SIZE)) != NULL)
+    {
+        for (i = 0; i < N_MSGS; ++i)
+        {
+            sprintf (fast_temp_buf, "msg%d", i + 1);
+            Get_resource(rDB, fast_temp_buf, fast_temp_buf_big, FAST_MSG_SIZE);
+            fast_msgs[i] = strdup (fast_temp_buf_big);
+        }
+        free (fast_temp_buf_big);
+    }
+    else
+      for (i = 0; i < N_MSGS; ++i)
+        fast_msgs[i] = NULL;
+
     Get_bool_resource(rDB, "ignoreWindowManager", &ignoreWindowManager);
 
     Get_resource(rDB, "name", nickName, MAX_NAME_LEN);
@@ -2433,6 +2737,7 @@
     Get_int_resource(rDB, "decorColor", &decorColor);
     Get_int_resource(rDB, "decorRadarColor", &decorRadarColor);
     Get_int_resource(rDB, "targetRadarColor", &targetRadarColor);
+    Get_int_resource(rDB, "oldTextColor", &oldTextColor);
     Get_resource(rDB, "sparkColors", sparkColors, MSG_LEN);
 
     instruments = 0;
diff -urN xpilot-4.1.0/src/client/netclient.c xpilot-4.U.4tc99b/src/client/netclient.c
--- xpilot-4.1.0/src/client/netclient.c	Sun Aug 30 18:36:50 1998
+++ xpilot-4.U.4tc99b/src/client/netclient.c	Mon Aug 30 00:58:50 1999
@@ -1486,7 +1486,8 @@
     if (debris_colors > num_spark_colors) {
 	debris_colors = num_spark_colors;
     }
-    if (view_width != draw_width || view_height != draw_height) {
+    if (view_width != (int)(draw_width*scaleFactor) ||
+	view_height != (int)(draw_height*scaleFactor)) {
 	Send_display();
     }
     Game_over_action(stat);
@@ -2408,6 +2409,336 @@
     return 0;
 }
 
+#define TEAM_PLAY               (1<<8)  /* defined in rules.h */
+#define LIMITED_LIVES           (1<<3)  /* defined in rules.h */
+#define MSG_PARSED_FIELD_LEN      20
+
+/* Returns a pointer to the first character after the fields */
+char *Fields_info (char *buf, int *n_fields)
+{
+    int  end_found = 0, level = 0;
+    
+    *n_fields = 0;
+    while (!end_found)
+    {
+	switch (*buf)
+	{
+	case START_DELIMITER:
+	    if (level++ == 0)
+		(*n_fields)++;
+	    break;
+	case MIDDLE_DELIMITER:
+	    if (level == 1)
+		(*n_fields)++;
+	    break;
+	case END_DELIMITER:
+	    level--;
+	    if (level == 0)
+		end_found = 1;
+	    else if (level < 0)
+		return NULL;
+	    break;
+	case '\0':
+	    return NULL;
+	    break;
+	default:
+	    break;
+	}
+	buf++;
+    }
+    return buf;
+}
+
+/* Returns a string pointer to the wanted_field 
+ * This pointer must be freed after using it
+ */
+char *Field_get (char *buf, int wanted_field)
+{
+    int  finished = 0, level = 0, field = 0, len;
+    char *field_ptr, *start_ptr = NULL, *end_ptr = NULL;
+    
+    while (!finished)
+    {
+	switch (*buf)
+	{
+	case START_DELIMITER:
+	    if (level == 0)
+	    {
+		field++;
+		if (field == wanted_field)
+		    start_ptr = buf + 1;
+	    }
+	    level++;
+	    break;
+	case MIDDLE_DELIMITER:
+	    if (level == 1)
+	    {
+		field++;
+		if (field == wanted_field)
+		    start_ptr = buf + 1;
+		else if (field == wanted_field + 1)
+		{
+		    end_ptr = buf;
+		    finished = 1;
+		}
+	    }
+	    break;
+	case END_DELIMITER:
+	    level--;
+	    if (level == 0)
+	    {
+		if (field == wanted_field)
+		    end_ptr = buf;
+		finished = 1;
+	    }
+	    else if (level < 0)
+		return NULL;
+	    break;
+	case '\0':
+	    return NULL;
+	    break;
+	default:
+	    break;
+	}
+	buf++;
+	
+    }
+    len = end_ptr - start_ptr;
+    field_ptr = (char *) malloc (len + 1);
+    strncpy (field_ptr, start_ptr, len);
+    field_ptr[len] = '\0';
+    return field_ptr;
+}
+
+int Net_talk_parsed(char *str)
+{
+    /* Comment: sizeof talk_str === MAX_CHARS */
+    if (str == NULL)
+	return 1;
+    
+    if (Parse (talk_str, str, 0L, MAX_CHARS) > 0)
+    {
+	talk_pending = ++talk_sequence_num;
+	talk_last_send = last_loops - TALK_RETRY;
+    }
+    return 0;
+}
+
+int Parse(char *outbuf, char *inbuf, long pos, long max)
+{
+    FILE *fp;
+    char c;
+    long fsize;
+    int i, n_fields;
+    char *tmpptr, *tmpptr1, *tmpptr2, *tmpptr3, *nextpos, *filename;
+    other_t *player=NULL;
+
+    
+    while ((c = *inbuf++) != '\0')
+    {
+	if (pos >= max - 2)
+	{
+	    if (outbuf == talk_str) /* parsing to the talk buffer */
+	    {
+		outbuf[pos] = '\0';
+		talk_pending = ++talk_sequence_num;
+		if (Packet_printf(&wbuf, "%c%ld%s", PKT_TALK,
+				  talk_pending, outbuf) == -1)
+		    return -1;
+		pos = 0;
+	    }
+	    else
+		goto done;
+	}
+	if (player != NULL)
+	{
+	    switch (c)
+	    {
+	    case 'l':
+		if (BIT(Setup->mode, LIMITED_LIVES))
+		    outbuf[pos++] = player->life + '0';
+		break;
+	    case 'n':
+		tmpptr = player->name;
+		for (i = 0; tmpptr[i] != '\0' && pos < max - 2; ++i)
+		    outbuf[pos++] = tmpptr[i];
+		break;
+	    case 's':
+		if (pos < max - 1 - 6) /* short - "-16535" max no of chars */
+		    pos += sprintf (outbuf+pos, "%d", player->score);
+		break;
+	    case 't':
+		if (BIT(Setup->mode, TEAM_PLAY))
+		    outbuf[pos++] = player->team + '0';
+		break;
+	    default:
+		break;
+	    }
+	    player = NULL;
+	}
+	else
+	{
+	    switch (c)
+	    {
+	    case SPECIAL_TALK_CHAR:
+		if ((c = *inbuf++) == '\0')
+		    goto done;
+		switch (c) {
+		case '=':  /* String comparison */
+		    nextpos = Fields_info (inbuf, &n_fields);
+		    if (n_fields < 3 || n_fields > 4 || nextpos == NULL)
+			break;
+		    /* parse field 1 */
+		    if ((tmpptr = Field_get (inbuf, 1)) == NULL)
+		    {
+			printf ("Field_get (1) error!...\n");
+			break;
+		    }
+		    tmpptr1 = (char *)malloc (MSG_PARSED_FIELD_LEN);
+		    Parse (tmpptr1, tmpptr, 0, MSG_PARSED_FIELD_LEN);
+		    free (tmpptr);
+		    /* parse field 2 */
+		    if ((tmpptr = Field_get (inbuf, 2)) == NULL)
+		    {
+			printf ("Field_get (2) error!...\n");
+			break;
+		    }
+		    tmpptr2 = (char *)malloc (MSG_PARSED_FIELD_LEN);
+		    Parse (tmpptr2, tmpptr, 0, MSG_PARSED_FIELD_LEN);
+		    free (tmpptr);
+		    if (!strcmp(tmpptr1, tmpptr2))
+		    {
+			/* True */
+                        if ((tmpptr3 = Field_get (inbuf, 3)) == NULL)
+			{
+			    printf ("Field_get (3) error!...\n");
+			    free (tmpptr1);
+			    free (tmpptr2);
+			    break;
+			}
+			pos = Parse (outbuf, tmpptr3, pos, max);
+		    }
+		    else if (n_fields == 4)
+		    {
+			/* False */
+			if ((tmpptr3 = Field_get (inbuf, 4)) == NULL)
+			{
+			    printf ("Field_get (4) error!...\n");
+			    free (tmpptr1);
+			    free (tmpptr2);
+			    break;
+			}
+			pos = Parse (outbuf, tmpptr3, pos, max);
+		    }
+		    inbuf = nextpos;
+		    free (tmpptr);
+		    free (tmpptr1);
+		    free (tmpptr2);
+		    free (tmpptr3);
+		    break;
+		case 'f':
+		    nextpos = Fields_info (inbuf, &n_fields);
+		    if (n_fields != 1 || nextpos == NULL)
+			break;
+		    if ((tmpptr = Field_get (inbuf, 1)) == NULL)
+		    {
+			printf ("Field_get error!...\n");
+			break;
+		    }
+		    inbuf = nextpos;
+		    filename = (char *)malloc (MSG_FNLEN);
+		    Parse (filename, tmpptr, 0, MSG_FNLEN);
+		    free (tmpptr);
+		    if ((fp = fopen (filename, "r")) == NULL)
+		    {
+			printf ("Couldn't open file %s\n", tmpptr);
+			free (filename);
+			break;
+		    }
+		    free (filename);
+		    
+		    /* Get filesize */
+		    fseek (fp, 0L, SEEK_END);
+		    fsize = ftell (fp);
+		    rewind (fp);
+		    
+		    if ((tmpptr = (char *)malloc(fsize+1)) == NULL)
+		    {
+			fclose (fp);
+			break;
+		    }
+		    fread (tmpptr, 1, fsize, fp);
+		    tmpptr[fsize] = '\0';
+		    fclose (fp);
+		    pos = Parse (outbuf, tmpptr, pos, max);
+		    free (tmpptr);
+		    break;
+		case 'h':
+		    tmpptr = getenv ("HOME");
+		    while (*tmpptr != '\0' && pos < max - 2)
+		      outbuf[pos++] = *tmpptr++;
+		    break;
+		case 'r':
+		    nextpos = Fields_info (inbuf, &n_fields);
+		    if (n_fields <= 0 || nextpos == NULL)
+		      break;
+		    if ((tmpptr = Field_get (inbuf, rand() % n_fields + 1))
+			== NULL)
+		    {
+			printf ("Field_get error (random) \n");
+			break;
+		    }
+		    inbuf = nextpos;
+		    pos = Parse (outbuf, tmpptr, pos, max);
+		    free (tmpptr); 
+		    break;
+		case 'n':
+		    outbuf[pos] = '\0';
+		    talk_pending = ++talk_sequence_num;
+		    if (Packet_printf(&wbuf, "%c%ld%s", PKT_TALK,
+				      talk_pending, outbuf) == -1)
+		      return -1;
+		    pos = 0;
+		    break;
+		case 'l':
+                    if ( Other_by_id(snoopEyesId) == self 
+			    || Other_by_id(snoopEyesId) == NULL ){
+                        if ((player = Other_by_id(lock_id)) == NULL) {
+                          pos = 0;
+                          goto done;
+                        }
+                    } else {
+                        if ((player = Other_by_id(snoopEyesId)) == NULL) {
+                          pos = 0;
+                          goto done;
+                        }
+		    }
+                    break;
+		case 's':
+		    player = self;
+		    break;
+		case 't':
+		    if (BIT(Setup->mode, TEAM_PLAY))
+		      outbuf[pos++] = self->team + '0';
+		    break;
+			case SPECIAL_TALK_CHAR:
+		    outbuf[pos++] = c;
+		    break;
+			default:
+		    break;
+		}
+	    case '\n':
+		break;
+	    default:
+		outbuf[pos++] = c;
+		break;
+	    }
+	}
+    }
+done:
+    outbuf[pos] = '\0';
+    return pos;
+}
 
 int Send_talk(void)
 {
@@ -2433,8 +2764,6 @@
 		      draw_width, draw_height, num_spark_colors, spark_rand) == -1) {
 #else
 		      (int)(draw_width*scaleFactor), (int)(draw_height*scaleFactor), num_spark_colors, spark_rand) == -1) {
-	draw_width  = (int)(draw_width*scaleFactor);
-	draw_height = (int)(draw_height*scaleFactor);
 #endif
 	return -1;
     }
diff -urN xpilot-4.1.0/src/client/netclient.h xpilot-4.U.4tc99b/src/client/netclient.h
--- xpilot-4.1.0/src/client/netclient.h	Thu Apr 16 19:39:29 1998
+++ xpilot-4.U.4tc99b/src/client/netclient.h	Mon Aug 30 00:58:50 1999
@@ -113,5 +113,7 @@
 int Send_audio_request(int onoff);
 int Send_fps_request(int fps);
 int Receive_loseitem(void);
+int Net_talk_parsed(char *str);
+int Parse(char *outbuf, char *inbuf, long pos, long max);
 
 #endif
diff -urN xpilot-4.1.0/src/client/paint.h xpilot-4.U.4tc99b/src/client/paint.h
--- xpilot-4.1.0/src/client/paint.h	Thu Apr 16 19:39:31 1998
+++ xpilot-4.U.4tc99b/src/client/paint.h	Mon Aug 30 00:58:50 1999
@@ -182,6 +182,7 @@
 extern int	targetRadarColor;	/* Color index for targets on radar */
 extern int	decorColor;		/* Color index for decoration drawing */
 extern int	decorRadarColor;	/* Color index for decorations on radar */
+extern int      oldTextColor;           /* Color index for old text */
 extern bool	gotFocus;
 extern bool	talk_mapped;
 extern short	view_width, view_height;	/* Visible area from server */
diff -urN xpilot-4.1.0/src/client/painthud.c xpilot-4.U.4tc99b/src/client/painthud.c
--- xpilot-4.1.0/src/client/painthud.c	Wed Sep 16 20:35:41 1998
+++ xpilot-4.U.4tc99b/src/client/painthud.c	Mon Aug 30 00:58:50 1999
@@ -72,6 +72,7 @@
 
 int	hudColor;		/* Color index for HUD drawing */
 int	hudLockColor;		/* Color index for lock on HUD drawing */
+int     oldTextColor;           /* Color index for old text */
 DFLOAT	charsPerTick = 0.0;	/* Output speed of messages */
 
 message_t	*TalkMsg[MAX_MSGS], *GameMsg[MAX_MSGS];
@@ -680,7 +681,7 @@
 	if (msg->life > MSG_FLASH)
 	    XSetForeground(dpy, messageGC, colors[RED].pixel);
 	else
-	    XSetForeground(dpy, messageGC, colors[WHITE].pixel);
+	    XSetForeground(dpy, messageGC, colors[oldTextColor].pixel);
 	len = (int)(charsPerTick * (MSG_DURATION - msg->life));
 	len = MIN(msg->len, len);
 	rd.drawString(dpy, p_draw, messageGC,
diff -urN xpilot-4.1.0/src/client/paintobjects.c xpilot-4.U.4tc99b/src/client/paintobjects.c
--- xpilot-4.1.0/src/client/paintobjects.c	Wed Sep 16 20:35:41 1998
+++ xpilot-4.U.4tc99b/src/client/paintobjects.c	Mon Aug 30 00:58:50 1999
@@ -651,6 +651,7 @@
     }
 
     if (num_ship > 0) {
+	snoopEyesId = -1;
 	for (i = 0; i < num_ship; i++) {
 	    x = ship_ptr[i].x;
 	    y = ship_ptr[i].y;
@@ -665,6 +666,14 @@
 		points[cnt].y = WINSCALE(Y(y + ship->pts[cnt][dir].y));
 	    }
 	    points[cnt++] = points[0];
+
+            /*
+             * ship in the center? (svenska-hack)
+             */
+	    if ( abs(X(x)-view_width/2) <=1 && abs(Y(y)-view_height/2) <=1
+	&& Other_by_id(ship_ptr[i].id) != NULL ){
+		  snoopEyesId = ship_ptr[i].id;
+	    }
 
 	    /*
 	     * Determine if the name of the player should be drawn below
diff -urN xpilot-4.1.0/src/client/paintradar.c xpilot-4.U.4tc99b/src/client/paintradar.c
--- xpilot-4.1.0/src/client/paintradar.c	Wed Sep 23 21:42:43 1998
+++ xpilot-4.U.4tc99b/src/client/paintradar.c	Mon Aug 30 00:58:50 1999
@@ -145,8 +145,19 @@
     }
     for (i = 0; i<num_radar; i++) {
 	int s;
-	if ((s = radar_ptr[i].size) <= 0)
-	    s = 1;
+
+	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);
+ 	    s-=0x80;
+	}
+	if (s == 0)
+	  s = 1;
 	x = (int)(radar_ptr[i].x * xf + 0.5) - s / 2;
 	y = RadarHeight - (int)(radar_ptr[i].y * yf + 0.5) - 1 - s / 2;
 	(*radarPlayerRectFN)(dpy, p_radar, radarGC, x, y, s, s);
@@ -168,6 +179,7 @@
 		}
 	    }
 	}
+	XSetForeground(dpy, radarGC, colors[WHITE].pixel);
     }
     if (num_radar) {
 	RELEASE(radar_ptr, num_radar, max_radar);
diff -urN xpilot-4.1.0/src/client/xevent.c xpilot-4.U.4tc99b/src/client/xevent.c
--- xpilot-4.1.0/src/client/xevent.c	Tue Oct  6 16:52:19 1998
+++ xpilot-4.U.4tc99b/src/client/xevent.c	Mon Aug 30 00:58:50 1999
@@ -57,6 +57,8 @@
 
 char xevent_version[] = VERSION;
 
+extern char *fast_msgs[];
+
 extern setup_t		*Setup;
 
 static BITV_DECL(keyv, NUM_KEYS);
@@ -283,6 +285,9 @@
 
 static bool Key_press(keys_t key)
 {
+   if (key >= KEY_MSG_1 && key < KEY_MSG_1 + N_MSGS)
+	Net_talk_parsed (fast_msgs[key - KEY_MSG_1]);
+
     switch (key) {
     case KEY_ID_MODE:
 	showRealName = showRealName ? false : true;
diff -urN xpilot-4.1.0/src/client/xinit.c xpilot-4.U.4tc99b/src/client/xinit.c
--- xpilot-4.1.0/src/client/xinit.c	Fri Oct  2 20:39:22 1998
+++ xpilot-4.U.4tc99b/src/client/xinit.c	Mon Aug 30 00:58:50 1999
@@ -469,6 +469,8 @@
 	|| ((targetRadarColor & 1) && colorSwitch)) {
 	targetRadarColor = BLUE;
     }
+    if (oldTextColor >= maxColors || oldTextColor < 0)
+        oldTextColor = WHITE;
     if (decorColor >= maxColors || decorColor <= 0) {
 	decorColor = RED;
     }
diff -urN xpilot-4.1.0/src/client/xpilot.c xpilot-4.U.4tc99b/src/client/xpilot.c
--- xpilot-4.1.0/src/client/xpilot.c	Thu Apr 16 19:39:56 1998
+++ xpilot-4.U.4tc99b/src/client/xpilot.c	Mon Aug 30 00:58:50 1999
@@ -96,6 +96,8 @@
 char			**Argv;
 int			Argc;
 
+int			snoopEyesId = -1;
+
 static void Check_client_versions(void);
 
 
diff -urN xpilot-4.1.0/src/common/config.h xpilot-4.U.4tc99b/src/common/config.h
--- xpilot-4.1.0/src/common/config.h	Fri Apr 17 13:40:51 1998
+++ xpilot-4.U.4tc99b/src/common/config.h	Mon Aug 30 01:03:12 1999
@@ -36,9 +36,9 @@
 
 #ifndef	DEFAULT_MAP
 #	ifdef	_WINDOWS
-#		define DEFAULT_MAP		"default.xp"
+#		define DEFAULT_MAP		"tc99.xp"
 #	else
-#		define DEFAULT_MAP		"globe.xp"
+#		define DEFAULT_MAP		"tc99.xp"
 #	endif
 #endif
 
diff -urN xpilot-4.1.0/src/common/const.h xpilot-4.U.4tc99b/src/common/const.h
--- xpilot-4.1.0/src/common/const.h	Sat Aug 29 21:49:53 1998
+++ xpilot-4.U.4tc99b/src/common/const.h	Sat Sep 18 20:31:15 1999
@@ -293,7 +293,7 @@
 
 #define GRAVS_POWER		2.7
 
-#define SHIP_SZ		        14  /* Size (pixels) of radius for legal HIT! */
+#define SHIP_SZ		        16  /* Size (pixels) of radius for legal HIT! */
 #define VISIBILITY_DISTANCE	1000.0
 #define WARNING_DISTANCE	(VISIBILITY_DISTANCE*0.8)
 
@@ -404,5 +404,13 @@
 /* STDRUP_OBJ should be uncomented in Makefile also */
 extern char* strdup(const char*);
 #endif
+
+#define N_MSGS 20
+#define FAST_MSG_SIZE 400
+#define MSG_FNLEN         100
+#define START_DELIMITER   '['
+#define END_DELIMITER     ']'
+#define MIDDLE_DELIMITER  '|'
+#define SPECIAL_TALK_CHAR '#'
 
 #endif
diff -urN xpilot-4.1.0/src/common/keys.h xpilot-4.U.4tc99b/src/common/keys.h
--- xpilot-4.1.0/src/common/keys.h	Thu Apr 16 19:40:42 1998
+++ xpilot-4.U.4tc99b/src/common/keys.h	Mon Aug 30 00:58:51 1999
@@ -116,6 +116,27 @@
      * retaining compatibility.  Change this at the next major cleanup.
      */
     ,
+    KEY_MSG_1,
+    KEY_MSG_2,
+    KEY_MSG_3,
+    KEY_MSG_4,
+    KEY_MSG_5,
+    KEY_MSG_6,
+    KEY_MSG_7,
+    KEY_MSG_8,
+    KEY_MSG_9,
+    KEY_MSG_10,
+    KEY_MSG_11,
+    KEY_MSG_12,
+    KEY_MSG_13,
+    KEY_MSG_14,
+    KEY_MSG_15,
+    KEY_MSG_16,
+    KEY_MSG_17,
+    KEY_MSG_18,
+    KEY_MSG_19,
+    KEY_MSG_20,
+
     KEY_ID_MODE,
     KEY_TOGGLE_OWNED_ITEMS,
     KEY_TOGGLE_MESSAGES,
diff -urN xpilot-4.1.0/src/common/pack.h xpilot-4.U.4tc99b/src/common/pack.h
--- xpilot-4.1.0/src/common/pack.h	Sun Aug 30 17:40:54 1998
+++ xpilot-4.U.4tc99b/src/common/pack.h	Mon Aug 30 00:58:51 1999
@@ -90,7 +90,7 @@
  * 3.8.0.0: new items (deflector, hyperjump, phasing), keyboardsize and rounddelay.
  * 4.1.0.0: new item (mirror).
  */
-#define	MAGIC		0x4100F4ED
+#define	MAGIC		0x4101F4ED
 
 #define MAGIC2VERSION(M)	(((M) >> 16) & 0xFFFF)
 #define VERSION2MAGIC(V)	((((V) & 0xFFFF) << 16) | (MAGIC & 0xFFFF))
diff -urN xpilot-4.1.0/src/common/version.h xpilot-4.U.4tc99b/src/common/version.h
--- xpilot-4.1.0/src/common/version.h	Fri Oct 30 13:36:46 1998
+++ xpilot-4.U.4tc99b/src/common/version.h	Wed Sep 22 20:44:48 1999
@@ -28,15 +28,15 @@
 #if defined(__hpux)
 #   pragma COPYRIGHT_DATE	"1991-1998"
 #   pragma COPYRIGHT		"Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
-#   pragma VERSIONID		"XPilot 4.1.0"
+#   pragma VERSIONID		"XPilot 4.U.4TC99b"
 #endif
 
-#define VERSION			"4.1.0"
+#define VERSION			"4.U.4TC99b"
 #ifdef	_WINDOWS
-#define	TITLE			"4.1.0-NT13"
+#define	TITLE			"4.U.4TC99b-NT13"
 #define	VERSION_WINDOWS	"13"
 #else
-#define TITLE			"XPilot 4.1.0"
+#define TITLE			"XPilot 4.U.4TC99b"
 #endif
 #define AUTHORS			"Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
 #define COPYRIGHT		"Copyright © 1991-1998 by Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
diff -urN xpilot-4.1.0/src/replay/xp-replay.c xpilot-4.U.4tc99b/src/replay/xp-replay.c
--- xpilot-4.1.0/src/replay/xp-replay.c	Sat Apr 18 14:53:08 1998
+++ xpilot-4.U.4tc99b/src/replay/xp-replay.c	Mon Aug 30 00:58:51 1999
@@ -88,6 +88,7 @@
 #include "items/itemTractorBeam.xbm"
 #include "items/itemAutopilot.xbm"
 #include "items/itemEmergencyShield.xbm"
+#include "items/itemMirror.xbm"
 
 #include "tools/eject.xbm"
 #include "tools/fastf.xbm"
@@ -1386,7 +1387,8 @@
     itemEmergencyShield_bits,
     itemDeflector_bits,
     itemHyperJump_bits,
-    itemPhasingDevice_bits
+    itemPhasingDevice_bits,
+    itemMirror_bits
 };
 
 static XFontStruct *loadQueryFont(const char *fontName, GC gc)
diff -urN xpilot-4.1.0/src/server/cannon.c xpilot-4.U.4tc99b/src/server/cannon.c
--- xpilot-4.1.0/src/server/cannon.c	Sat Sep  5 04:44:51 1998
+++ xpilot-4.U.4tc99b/src/server/cannon.c	Mon Aug 30 00:58:51 1999
@@ -136,7 +136,7 @@
 		obj->acc.x = 0;
 		obj->acc.y = 0;
 		obj->mass = 10;
-		obj->life = 1500 + (rand() & 511);
+		obj->life = (1500 + (rand() & 511))*FPSMultiplier;
 		obj->count = amount;
 		obj->pl_range = ITEM_SIZE / 2;
 		obj->pl_radius = ITEM_SIZE / 2;
@@ -458,7 +458,7 @@
 	/* smarter cannons use tractors more often and also push/pull longer */
 	c->tractor_is_pressor = (rand() % (cannonSmartness + 1) == 0);
 	c->tractor_target = pl->id;
-	c->tractor_count = 11 + rand() % ((3 * cannonSmartness) + 1);
+	c->tractor_count = (11 + rand() % ((3 * cannonSmartness) + 1))*FPSMultiplier;
 	IFSOUND(sound = -1;)
 	break;
     case CW_TRANSPORTER:
@@ -492,7 +492,7 @@
 		/* dir */	dir - 4 * (4 - cannonSmartness),
 				dir + 4 * (4 - cannonSmartness),
 		/* speed */	0.1, speed * 4,
-		/* life */	3, 20);
+		/* life */	3*FPSMultiplier, 20*FPSMultiplier);
 	    c->item[ITEM_EMERGENCY_THRUST]--;
 	} else {
 	    Make_debris(
@@ -509,7 +509,7 @@
 		/* dir */	dir - 3 * (4 - cannonSmartness),
 				dir + 3 * (4 - cannonSmartness),
 		/* speed */	0.1, speed * 2,
-		/* life */	3, 20);
+		/* life */	3*FPSMultiplier, 20*FPSMultiplier);
 	}
 	c->item[ITEM_FUEL]--;
 	IFSOUND(sound = THRUST_SOUND;)
diff -urN xpilot-4.1.0/src/server/cmdline.c xpilot-4.U.4tc99b/src/server/cmdline.c
--- xpilot-4.1.0/src/server/cmdline.c	Tue Sep  1 20:08:40 1998
+++ xpilot-4.U.4tc99b/src/server/cmdline.c	Wed Sep 22 20:45:18 1999
@@ -40,6 +40,7 @@
 #include "defaults.h"
 #include "error.h"
 #include "portability.h"
+#include "status.h"
 
 char cmdline_version[] = VERSION;
 
@@ -232,6 +233,21 @@
 
 bool		pLockServer;		/* Is server swappable out of memory?  */
 
+int             FPSMultiplier;          /* slow everything by this factor */
+bool            useWreckage;            /* create wreckage or not? */
+bool            ignore20MaxFPS;         /* ignore client maxFPS request if it is 20 */
+int             timerResolution;        /* OS timer resolution (times/s) */
+char            *password;              /* password for operator status */
+int             numberOfRounds;         /* how many rounds to play */
+int             playerLimit;            /* allow less players than bases */
+/* BEGIN - TEAMCUP */
+bool            teamcup;                /* Is this a teamcup match? */
+char		*statServer;		/* Status server to report to */
+int		statPort;		/* Port to use on the status server */
+int		matchNumber;		/* The number of the match */
+/* END - TEAMCUP */
+
+
 const char default_map[] = DEFAULT_MAP;
 
 
@@ -2272,6 +2288,104 @@
 	tuner_plock,
 	"Whether the server is prevented from being swapped out of memory.\n"
     },
+    {
+        "FPSMultiplier",
+	"FPSMultiplier",
+	"1",
+	&FPSMultiplier,
+	valInt,
+	tuner_none,
+	"Everything is slowed by this factor. Allows using higher FPS\n"               "without making the game too fast.\n"
+    },
+    {
+        "wreckage",
+	"wreckage",
+	"true",
+	&useWreckage,
+	valBool,
+	tuner_dummy,
+	"Do destroyed ships leave wreckage?\n"
+    },
+    {   "ignore20MaxFPS",
+	"ignore20MaxFPS",
+	"true",
+	&ignore20MaxFPS,
+	valBool,
+	tuner_dummy,
+	"Ignore client maxFPS request if it is 20 (the default setting).\n"
+    },
+    {
+        "timerResolution",
+	"timerResolution",
+	"0",
+	&timerResolution,
+	valInt,
+	tuner_none,
+	"If set to nonzero, xpilots will requests signals from the OS at\n"
+	"1/timerResolution second intervals. Server will then compute a new\n"
+	"frame FPS times out of every timerResolution signals.\n"
+    },
+    {
+        "password",
+	"password",
+	NULL,
+	&password,
+	valString,
+	tuner_dummy,
+	"The password needed to get operator privileges.\n"
+    },
+    {   "numberOfRounds",
+	"numRounds",
+	"0",
+	&numberOfRounds,
+	valInt,
+	tuner_dummy,
+	"The number of rounds to play. If 0, unlimited.\n"
+    },
+    {
+        "playerLimit",
+	"playerLimit",
+	"0",
+	&playerLimit,
+	valInt,
+	tuner_dummy,
+	"Allow only (number of bases)-playerLimit players to enter.\n"
+    }
+    /* BEGIN - TEAMCUP */
+    ,
+    {   "teamcup",
+	"teamcup",
+	false,
+	&teamcup,
+	valBool,
+	tuner_dummy,
+	"Is this a teamcup match?.\n"
+    },
+    {   "statServer",
+	"statServer",
+	"rubin.e.kth.se",
+	&statServer,
+	valString,
+	tuner_dummy,
+	"Status server to report to.\n"
+    },
+    {   "statPort",
+	"statPort",
+	STATUS_PORTSTR,
+	&statPort,
+	valInt,
+	tuner_dummy,
+	"Port of the status server.\n"
+    },
+    {   "match",
+	"match",
+	"-1",
+	&matchNumber,
+	valInt,
+	tuner_dummy,
+	"The number of the match.\n"
+    }
+    /* END - TEAMCUP */
 };
 
 
@@ -2440,7 +2554,7 @@
 
 void Parser(int argc, char **argv)
 {
-    int			i, j;
+    int			i, j, gotmatch = 0;
     char		*fname;
 
 
@@ -2470,6 +2584,9 @@
 	    puts(TITLE);
 	    IFNWINDOWS( exit(0); )
 	}
+	if (strcmp("-match", argv[i]) == 0 && i + 1 != argc) {
+		gotmatch = 1;
+	}
 
 	if (argv[i][0] == '-' || argv[i][0] == '+') {
 	    for (j = 0; j < NELEM(options); j++) {
@@ -2501,6 +2618,12 @@
 	errno = 0;
 	error("Unknown option '%s'", argv[i]);
     }
+    if (!gotmatch) {
+	    xpprintf("\n\n"
+	"!!! You must use the '-match' option to start this server.\n"
+	"!!! To practise you may use '-match 0'.\n\n\n");
+	    End_game();
+    }
 
     /*
      * Read map file if map data not found yet.
@@ -2530,6 +2653,8 @@
     for (j = 0; j < NELEM(options); j++)
 	addOption(options[j].name, options[j].defaultValue, 0, &options[j]);
     parseOptions();
+    fireRepeatRate *= FPSMultiplier; /* might not want to do this */
+    ShotsLife *= FPSMultiplier;
     Grok_map();
 }
 
diff -urN xpilot-4.1.0/src/server/collision.c xpilot-4.U.4tc99b/src/server/collision.c
--- xpilot-4.1.0/src/server/collision.c	Fri Oct 30 11:49:29 1998
+++ xpilot-4.U.4tc99b/src/server/collision.c	Mon Aug 30 00:58:51 1999
@@ -655,6 +655,8 @@
 		       not the team the ball belongs to. the latter is
 		       found through the ball's treasure */
 		    ball->team = pl->team;
+		    if (ball->owner == -1)
+		      ball->life=LONG_MAX;  /* for frame counter */
 		    ball->owner = pl->id;
 		    ball->length = distance;
 		    SET_BIT(ball->status, GRAVITY);
@@ -671,11 +673,13 @@
 	     * We want a separate list of balls to avoid searching
 	     * the object list for balls.
 	     */
+
+	    int mindist=BALL_STRING_LENGTH,dist;
 	    for (j = 0; j < NumObjs; j++) {
 		if (BIT(Obj[j]->type, OBJ_BALL) && Obj[j]->id == -1) {
-		    if (Wrap_length(pl->pos.x - Obj[j]->pos.x,
-				    pl->pos.y - Obj[j]->pos.y)
-			  < BALL_STRING_LENGTH) {
+		    if ((dist=Wrap_length(pl->pos.x - Obj[j]->pos.x,
+				    pl->pos.y - Obj[j]->pos.y))
+			  < mindist) {
 			object *ball = Obj[j];
 			int bteam = -1;
 
@@ -683,18 +687,18 @@
 			    bteam = World.treasures[ball->treasure].team;
 
 			/*
-			 * If the treasure's team cannot connect before
-			 * other non-team members wait until somebody has
-			 * else has owned the ball before allowing a
-			 * connection.  This was done to stop team members
+			 * The treasure's team cannot connect before
+			 * somebody else has owned the ball.
+			 * This was done to stop team members
 			 * taking and hiding with the ball... this was
 			 * considered bad gamesmanship.
 			 */
 			if (ball->owner != -1
 			    || (   pl->team != TEAM_NOT_SET
-				&& pl->team != bteam))
+				   && pl->team != bteam)) {
 			    pl->ball = Obj[j];
-			break;
+			    mindist=dist;
+			}
 		    }
 		}
 	    }
@@ -1472,8 +1476,8 @@
 		obj = Obj[objnum];
 	    }
 
-	    pulse->pos.x += tcos(pulse->dir) * PULSE_SPEED;
-	    pulse->pos.y += tsin(pulse->dir) * PULSE_SPEED;
+	    pulse->pos.x += tcos(pulse->dir) * PULSE_SPEED/FPSMultiplier;
+	    pulse->pos.y += tsin(pulse->dir) * PULSE_SPEED/FPSMultiplier;
 	    if (BIT(World.rules->mode, WRAP_PLAY)) {
 		if (pulse->pos.x < 0) {
 		    pulse->pos.x += World.width;
@@ -1584,11 +1588,14 @@
 	    Object_position_init_pixels(obj, x1, y1);
 
 	    for (i = hits = 0; i <= max; i += PULSE_SAMPLE_DISTANCE) {
-
 		x = x1 + (i * dx) / max;
 		y = y1 + (i * dy) / max;
-		obj->vel.x = x - obj->pos.x;
-		obj->vel.y = y - obj->pos.y;
+		obj->vel.x = (x - CLICK_TO_FLOAT(obj->pos.cx))*FPSMultiplier;
+		obj->vel.y = (y - CLICK_TO_FLOAT(obj->pos.cy))*FPSMultiplier;
+		/* changed from = x - obj->pos.x to make lasers disappear
+		   less frequently when wrapping. There's still a small
+		   chance of it happening though. Didn't bother to really
+		   fix the code to completely prevent that. */
 		Move_object(objnum);
 		if (obj->life == 0) {
 		    break;
diff -urN xpilot-4.1.0/src/server/contact.c xpilot-4.U.4tc99b/src/server/contact.c
--- xpilot-4.1.0/src/server/contact.c	Sat Aug 29 21:49:54 1998
+++ xpilot-4.U.4tc99b/src/server/contact.c	Mon Aug 30 00:58:51 1999
@@ -736,7 +736,7 @@
     /*
      * Is the game full?
      */
-    if (NumPlayers - NumPseudoPlayers + login_in_progress + NumQueuedPlayers >= World.NumBases) {
+    if (NumPlayers - NumPseudoPlayers + login_in_progress + NumQueuedPlayers >= World.NumBases - playerLimit) {
 	if (NumQueuedPlayers > 0) {
 	    return E_GAME_FULL;
 	}
@@ -745,7 +745,7 @@
 		return E_GAME_FULL;
 	    }
 	}
-	if (NumPlayers - NumPseudoPlayers + login_in_progress + NumQueuedPlayers >= World.NumBases) {
+	if (NumPlayers - NumPseudoPlayers + login_in_progress + NumQueuedPlayers >= World.NumBases - playerLimit) {
 	    return E_GAME_FULL;
 	}
     }
@@ -825,7 +825,7 @@
     long			last_ack_recv;
 };
 
-static struct queued_player	*qp_list;
+struct queued_player	*qp_list;
 
 static void Queue_remove(struct queued_player *qp, struct queued_player *prev)
 {
@@ -902,11 +902,11 @@
 	if (last_unqueued_loops + 2 + (FPS >> 2) < main_loops) {
 
 	    /* is there a homebase available? */
-	    if (NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases
+	    if (NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases - playerLimit
 		|| (Kick_robot_players(TEAM_NOT_SET)
-		    && NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases)
+		    && NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases - playerLimit)
 		|| (Kick_paused_players(TEAM_NOT_SET)
-		    && NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases)) {
+		    && NumPlayers - NumPseudoPlayers + login_in_progress < World.NumBases - playerLimit)) {
 
 		/* find a team for this fellow. */
 		if (BIT(World.rules->mode, TEAM_PLAY)) {
diff -urN xpilot-4.1.0/src/server/event.c xpilot-4.U.4tc99b/src/server/event.c
--- xpilot-4.1.0/src/server/event.c	Fri Oct  2 20:39:26 1998
+++ xpilot-4.U.4tc99b/src/server/event.c	Sat Sep 18 20:31:16 1999
@@ -192,7 +192,8 @@
     for (i = 0; i < NumPlayers; i++) {
 	if (i == lock
 	    || (BIT(Players[i]->status, PLAYING|PAUSE|GAME_OVER) != PLAYING)
-	    || !Player_lock_allowed(ind, i)) {
+	    || !Player_lock_allowed(ind, i)
+	    || TEAM(ind,i)) {
 	    continue;
 	}
 	l = Wrap_length(Players[i]->pos.x - pl->pos.x,
@@ -219,6 +220,7 @@
     int			i;
 
     if (onoff != 0 && !BIT(pl->status, PAUSE)) { /* Turn pause mode on */
+        Swappers[pl->team]=-1;
 	pl->count = 10*FPS;
 	pl->updateVisibility = 1;
 	CLR_BIT(pl->status, SELF_DESTRUCT|PLAYING);
@@ -295,7 +297,8 @@
 	}
 	pressed = BITV_ISSET(pl->last_keyv, key) != 0;
 	BITV_TOGGLE(pl->prev_keyv, key);
-	pl->frame_last_busy = frame_loops;
+	if (key != KEY_SHIELD)               /* Client might automatically */
+	  pl->frame_last_busy = frame_loops; /* keep shields up */
 
 	/*
 	 * Allow these functions before a round has started.
@@ -478,7 +481,7 @@
 		    if (i == j)
 			break;
 		} while (i == ind
-			 || BIT(Players[i]->status, GAME_OVER)
+			 || BIT(Players[i]->status, GAME_OVER|PAUSE)
 			 || !Player_lock_allowed(ind, i));
 		if (i == ind) {
 		    CLR_BIT(pl->lock.tagged, LOCK_PLAYER);
@@ -715,7 +718,8 @@
 			*l = pl->lock.pl_id;
 		    }
 		} else {
-		    if (Player_lock_allowed(ind, *l)) {
+		    if ((*l!=NOT_CONNECTED)
+			&& Player_lock_allowed(ind, GetInd[*l])) {
 			pl->lock.pl_id = *l;
 			SET_BIT(pl->lock.tagged, LOCK_PLAYER);
 		    }
@@ -766,7 +770,7 @@
 	    case KEY_SELF_DESTRUCT:
 		TOGGLE_BIT(pl->status, SELF_DESTRUCT);
 		if (BIT(pl->status, SELF_DESTRUCT))
-		    pl->count = 150;
+		    pl->count = 150*FPSMultiplier;
 		break;
 
 	    case KEY_PAUSE:
diff -urN xpilot-4.1.0/src/server/frame.c xpilot-4.U.4tc99b/src/server/frame.c
--- xpilot-4.1.0/src/server/frame.c	Sat Sep  5 04:44:52 1998
+++ xpilot-4.U.4tc99b/src/server/frame.c	Mon Aug 30 00:59:46 1999
@@ -341,7 +341,7 @@
 			pl->emergency_shield_left,
 			pl->emergency_shield_max);
     if (BIT(pl->status, SELF_DESTRUCT) && pl->count > 0) {
-	Send_destruct(conn, pl->count);
+	Send_destruct(conn, pl->count/FPSMultiplier);
     }
     if (BIT(pl->used, OBJ_PHASING_DEVICE))
 	Send_phasingtime(conn,
@@ -462,15 +462,15 @@
 	    if (debris_colors >= 3) {
 		if (debris_colors > 4) {
 		    if (color == BLUE) {
-			color = (shot->life >> 1);
+			color = (shot->life/FPSMultiplier >> 1);
 		    } else {
-			color = (shot->life >> 2);
+			color = (shot->life/FPSMultiplier >> 2);
 		    }
 		} else {
 		    if (color == BLUE) {
-			color = (shot->life >> 2);
+			color = (shot->life/FPSMultiplier >> 2);
 		    } else {
-			color = (shot->life >> 3);
+			color = (shot->life/FPSMultiplier >> 3);
 		    }
 		}
 		if (color >= debris_colors) {
@@ -819,7 +819,7 @@
 		&& frame_loops % 5 >= 3) {
 		continue;
 	    }
-	    Send_radar(conn, (int)x, (int)y, 3);
+	    Send_radar(conn, (int)x, (int)y, TEAM(i,ind) ? 3+0x80:3);
 	}
     }
 }
@@ -994,6 +994,9 @@
     } else {
 	msg = message;
     }
+    /* BEGIN - TEAMCUP */
+    teamcup_log("    %s\n", message);
+    /* END - TEAMCUP */
     for (i = 0; i < NumPlayers; i++) {
 	pl = Players[i];
 	if (pl->conn != NOT_CONNECTED) {
diff -urN xpilot-4.1.0/src/server/global.h xpilot-4.U.4tc99b/src/server/global.h
--- xpilot-4.1.0/src/server/global.h	Tue Sep  1 20:08:43 1998
+++ xpilot-4.U.4tc99b/src/server/global.h	Wed Sep 22 20:45:18 1999
@@ -51,6 +51,7 @@
  */
 #ifdef SERVER
 #define FPS			framesPerSecond
+extern int              Swappers[MAX_TEAMS];   /* person swapping to a team */
 extern player		**Players;
 extern object		*Obj[];
 extern pulse_t		*Pulses[];
@@ -58,6 +59,7 @@
 extern trans_t		*Transporters[];
 extern long		frame_loops;
 extern int		NumPlayers;
+extern int              NumOperators;
 extern int		NumPseudoPlayers;
 extern int		NumQueuedPlayers;
 extern int		NumObjs;
@@ -243,6 +245,40 @@
 extern int		maxRoundTime;
 extern int		roundtime;
 
+extern int              FPSMultiplier;
+extern bool             useWreckage;
+extern bool             ignore20MaxFPS;
+extern int              timerResolution;
+extern char             *password;
+extern int              numberOfRounds;
+extern int              playerLimit;
+extern char		*statServer;
+extern int		statPort;
+extern int		matchNumber;
+
+/* BEGIN - TEAMCUP */
+extern FILE		*teamcup_score_file;
+extern bool		teamcup;
+#if !defined(STDVA)
+#   if defined(__STDC__) && !defined(__sun__) || defined(__cplusplus)
+#	define STDVA	1		/* has ANSI stdarg stuff */
+#   else
+#	define STDVA	0		/* nope, still the K&R way */
+#   endif
+#endif
+#if STDVA
+void teamcup_log(const char *fmt, ...);
+#else
+void teamcup_log();
+#endif
+void teamcup_round_start();
+void teamcup_round_end(int winning_team);
+void teamcup_open_score_file();
+void teamcup_close_score_file();
+
+extern int			teamcup_match_inited;
+extern int			teamcup_status_fd;
+/* END - TEAMCUP */
 #endif
 
 #endif /* GLOBAL_H */
diff -urN xpilot-4.1.0/src/server/netserver.c xpilot-4.U.4tc99b/src/server/netserver.c
--- xpilot-4.1.0/src/server/netserver.c	Sun Aug 30 17:40:50 1998
+++ xpilot-4.U.4tc99b/src/server/netserver.c	Wed Sep 22 20:44:48 1999
@@ -1776,7 +1776,9 @@
 {
     connection_t	*connp = &Conn[ind];
 
-    return Packet_printf(&connp->w, "%c%hd%hd%c", PKT_RADAR, x, y, size);
+    if (connp->version < 0x4101 && size >= 0x80)
+      size-=0x80;      /* friends and enemies look the same to old clients */
+      return Packet_printf(&connp->w, "%c%hd%hd%c", PKT_RADAR, x, y, size);
 }
 
 int Send_damaged(int ind, int damaged)
@@ -2022,9 +2024,11 @@
 	pl->turnspeed_s = power;
 	break;
     case PKT_TURNRESISTANCE:
+	if (!BIT(pl->used, OBJ_AUTOPILOT) && !BIT(pl->status, HOVERPAUSE))
 	pl->turnresistance = power;
 	break;
     case PKT_TURNRESISTANCE_S:
+	if (!BIT(pl->used, OBJ_AUTOPILOT) && !BIT(pl->status, HOVERPAUSE))
 	pl->turnresistance_s = power;
 	break;
     default:
@@ -2390,6 +2394,41 @@
     return 1;
 }
 
+static int Ind_by_name(char *name)
+{
+  int i,j,len;
+
+  if (!name)
+    return -1;
+
+  if (isdigit(*name)) {     /* Id given directly */
+    i=atoi(name);
+    if (i>0 && i<=NUM_IDS && (j=GetInd[i]) >= 0 && j<NumPlayers
+	&& Players[j]->id==i)
+      return j;
+    else
+      return -1;
+  }
+
+  /* first look for an exact match on player nickname. */
+  for (i = 0; i < NumPlayers; i++) {
+    if (strcasecmp(Players[i]->name, name) == 0) {
+      return i;
+    }
+  }
+
+
+  /* now look for a partial match on both nick and realname. */
+  len=strlen(name);
+  for (j = -1, i = 0; i < NumPlayers; i++) {
+    if (strncasecmp(Players[i]->name, name, len) == 0
+	|| strncasecmp(Players[i]->realname, name, len) == 0)
+      j = (j == -1) ? i : -2;
+  }
+  
+  return j;
+}
+
 /*
  * If a message contains a colon then everything before that colon is
  * either a unique player name prefix, or a team number with players.
@@ -2451,22 +2490,7 @@
 	}
     }
     else {						/* Player message */
-	sent = -1;
-	/* first look for an exact match on player nickname. */
-	for (i = 0; i < NumPlayers; i++) {
-	    if (strcasecmp(Players[i]->name, str) == 0) {
-		sent = i;
-		break;
-	    }
-	}
-	if (sent == -1) {
-	    /* now look for a partial match on both nick and realname. */
-	    for (sent = -1, i = 0; i < NumPlayers; i++) {
-		if (strncasecmp(Players[i]->name, str, len) == 0
-		    || strncasecmp(Players[i]->realname, str, len) == 0)
-		    sent = (sent == -1) ? i : -2;
-	    }
-	}
+        sent=Ind_by_name(str);
 	switch (sent) {
 	case -2:
 	    sprintf(msg, "Message not sent, %s matches more than one player!",
@@ -2489,6 +2513,490 @@
     }
 }
 
+int Swappers[MAX_TEAMS]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; /* fix this */
+
+static void Send_all_info(player *pl)
+{
+  int i;
+
+  for (i=0;i<NumPlayers;i++)
+    if (Players[i]->conn != NOT_CONNECTED) {
+      Send_player(Players[i]->conn,pl->id);
+      Send_score(Players[i]->conn,pl->id,pl->score,pl->life,
+		 pl->mychar);
+      Send_base(Players[i]->conn,pl->id,pl->home_base);
+    }
+}
+
+static void Swap_team(int ind, char *args)
+{
+  int      i,team;
+  player   *pl=Players[ind];
+  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);
+    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)
+      sprintf(msg,"There are no bases for team %d on this map.",team);
+    else if (reserveRobotTeam && team==robotTeam)
+      sprintf(msg,"You cannot join the robot team on this server.");
+    else if (team ==  pl->team)
+      sprintf(msg,"You already are on team %d.",team);
+    else if (World.teams[team].NumBases - World.teams[team].NumMembers > 0) {
+      sprintf(msg,"%s has swapped to team %d.",pl->name,team);
+      Set_message(msg);
+      if (BIT(pl->have, OBJ_BALL))
+	Detach_ball(ind, -1);
+      World.teams[pl->team].NumMembers--;
+      pl->team=team;
+      World.teams[pl->team].NumMembers++;
+      if (BIT(pl->mode, LIMITED_LIVES))
+	for (i = 0; i < NumPlayers; i++)
+	  if (!TEAM(ind, i) && !BIT(Players[i]->status,PAUSE)) {
+	    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]);
+      Send_all_info(pl);
+      return;
+    } else {
+      i=Swappers[pl->team];
+      while (i != -1)
+	if ( (i=Players[GetInd[i]]->team) != team)
+	  i=Swappers[i];
+	else {
+	  int xbase=pl->home_base, xteam=pl->team,xbase2,xteam2;
+	  player *pl2=pl;
+
+	  do {
+	    pl2=Players[GetInd[Swappers[xteam]]];
+	    Swappers[xteam]=-1;
+	    xbase2=pl2->home_base;
+	    xteam2=pl2->team;
+	    if (BIT(pl2->have, OBJ_BALL))
+	      Detach_ball(GetInd[pl2->id], -1);
+	    pl2->team=xteam;
+	    pl2->home_base=xbase;
+	    if (pl2->mychar == ' ')
+	      pl2->mychar	= 'W';
+	    pl2->prev_life = pl2->life = 0;
+	    SET_BIT(pl2->status, GAME_OVER|PLAYING);
+	    CLR_BIT(pl2->status, SELF_DESTRUCT);
+	    pl2->count=-1;
+	    Go_home(GetInd[pl2->id]);
+	    Send_all_info(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);
+	  if (BIT(pl->have, OBJ_BALL))
+	    Detach_ball(ind, -1);
+	  pl->team=team;
+	  pl->home_base=xbase;
+	  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;
+	  Go_home(ind);
+	  Send_all_info(pl);
+	  sprintf(msg,"Some players swapped teams.");
+	  Set_message(msg);
+	  return;
+	}
+      
+      for (i = NumPlayers - 1; i >= 0; i--)
+	if (Players[i]->conn != NOT_CONNECTED 
+	    && BIT(Players[i]->status, PAUSE)
+	    && (Players[i]->team == team)) {
+	  sprintf(msg,"%s has swapped with paused %s.",pl->name,
+		  Players[i]->name);
+	  Set_message(msg);
+	  if (BIT(pl->have, OBJ_BALL))
+	    Detach_ball(GetInd[pl->id], -1);
+	  Players[i]->team=pl->team;
+	  pl->team=team;
+	  team=Players[i]->home_base;
+	  Players[i]->home_base=pl->home_base;
+	  Go_home(i);
+	  pl->home_base=team;
+	  Send_all_info(Players[i]);
+	  if (BIT(pl->mode, LIMITED_LIVES))
+	    for (i = 0; i < NumPlayers; i++)
+	      if (!TEAM(ind, i) && !BIT(Players[i]->status,PAUSE)) {
+		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;
+		Go_home(ind);
+		break;
+	      }
+	  Send_all_info(pl);
+	  return;
+	}
+      sprintf(msg,"You are queued for swap to team %d.",team);
+      Swappers[team]=pl->id;
+    }
+  }
+  sprintf(msg+strlen(msg)," [*Server reply*]");
+  Set_player_message(pl,msg);
+  return;
+}
+
+extern int game_lock;
+
+extern void Reset_all_players(void);
+
+extern int roundCounter;
+
+struct queued_player {
+    struct queued_player	*next;
+    char			real_name[MAX_CHARS];
+    char			nick_name[MAX_CHARS];
+    char			disp_name[MAX_CHARS];
+    char			host_name[MAX_CHARS];
+    char			host_addr[24];
+    int				port;
+    int				team;
+    unsigned			version;
+    int				login_port;
+    long			last_ack_sent;
+    long			last_ack_recv;
+};
+
+extern struct queued_player *qp_list;
+
+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
+};
+
+typedef struct {
+  const char *name;
+  const char *help;
+  int operOnly;
+  enum Command number;
+} commandInfo;
+
+static commandInfo commands[] = {
+  {
+    "help",
+    "Without arguments, prints command list. /help <command> gives more info.",
+    0,
+    HELP_CMD
+  },
+  {
+    "team",
+    "/team <team number> swaps you to given team. "
+                 "Can be used with full teams too.",
+    0,
+    TEAM_CMD
+  },
+  {
+    "version",
+    "Print server version.",
+    0,
+    VERSION_CMD
+  },
+  {
+    "lock",
+    "Just /lock tells lock status. /lock 1 locks, /lock 0 unlocks. (operator)",
+    0,      /* checked in the function */
+    LOCK_CMD
+  },
+  {
+    "password",
+    "/password <string>. If string matches -password option, "
+                                      "gives operator status.",
+    0,
+    PASSWORD_CMD
+  },
+  {
+    "pause",
+    "/pause <player name or ID number>. Pauses player. (operator)",
+    1,
+    PAUSE_CMD
+  },
+  {
+    "reset",
+    "Just /reset starts a new round."
+        "/reset all  also sets scores to 0. (operator)",
+    1,
+    RESET_CMD
+  },
+  {
+    "set",
+    "/set <option> <value> sets a server option. (operator)",
+    1,
+    SET_CMD
+  },
+  {
+    "kick",
+    "/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.",
+    0,
+    SHOW_CMD
+  },
+  {
+    "advance",
+    "/advance <name of player in the queue>. "
+         "Move the player to the front of the queue. (operator)",
+    1,
+    ADVANCE_CMD
+  }
+};
+
+static void Handle_command(int ind, char *cmd)   /* no leading / */
+{
+    connection_t	*connp = &Conn[ind];
+    int                 plind  = GetInd[connp->id];
+    player		*pl = Players[plind];
+    int			i;
+    char		*args, msg[MSG_LEN * 2];
+
+    if (args=strchr(cmd,' '))
+      *args++=0;               /* separate arguments from command */
+
+    for (i=0; i<NELEM(commands); i++)
+      if (!strcasecmp(cmd,commands[i].name))
+	break;
+    if (i==NELEM(commands)) {
+      i=NO_CMD;
+      sprintf(msg,"Unknown command %s",cmd);
+    }
+    else if (!pl->isoperator && commands[i].operOnly) {
+      i=NO_CMD;
+      sprintf(msg,"You need operator status to use this command.");
+    }
+    else
+    i=commands[i].number;
+
+    switch(i) {
+    case NO_CMD:
+      break;
+
+    case ADVANCE_CMD:
+      if (!args)
+	sprintf(msg, "You must give a player name as an argument.");
+      else {
+	struct queued_player *last=qp_list, *p;
+
+	if (!last || !(p=last->next)) {
+	  sprintf(msg, "There are less than 2 players in the queue.");
+	  break;
+	}
+	if (!strcasecmp(last->nick_name, args)) {
+	  sprintf(msg, "Already first.");
+	  break;
+	}
+	while (1)
+	  if (strcasecmp(p->nick_name, args)) {
+	    last=p;
+	    p=p->next;
+	    if (!p) {
+	      sprintf(msg, "No player named %s in the queue.",args);
+	      break;
+	    }
+	  } else {
+	    last->next=p->next;
+	    p->next=qp_list;
+	    qp_list=p;
+	    sprintf(msg, "Done.");
+	    break;
+	  }
+      }
+      break;
+	
+    case SHOW_CMD:
+      if (!args)
+	sprintf(msg,"Show what?");
+      else if (!strcasecmp(args,"queue")) {
+	int len=0, i, count;
+	struct queued_player *p=qp_list;
+
+	if (!p) {
+	  sprintf(msg, "The queue is empty.");
+	  break;
+	}
+	sprintf(msg, "Queue: ");
+	len=strlen(msg);
+	count=1;
+	do {
+	  sprintf(msg+len, "%d. %s  ", count++, p->nick_name);
+	  len+=strlen(msg+len);
+	  p=p->next;
+	} while (p && len < MSG_LEN - 25);
+	*(msg+len-2)=0;                  /* -2 to strip spaces */
+      } else
+	sprintf(msg,"Unrecognized argument to /show.");
+      break;
+
+    case TEAM_CMD:
+      Swap_team(plind,args);
+      return;
+
+    case KICK_CMD:
+      if ( (i=Ind_by_name(args)) >= 0) {
+	sprintf(msg,"%s kicked %s out! [*Server notice*]",
+		pl->name, Players[i]->name);
+	Set_message(msg);
+	if (Players[i]->conn == NOT_CONNECTED)
+	  Delete_player(i);
+	else
+	  Destroy_connection(Players[i]->conn, "kicked out");
+	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.U.4TC99b");
+      break;
+
+    case HELP_CMD:
+      if (!args)
+	sprintf(msg,"Commands: help team version lock password pause "\
+		"reset set kick show");
+      else {
+	for (i=0;i<NELEM(commands);i++)
+	  if (!strcasecmp(args,commands[i].name))
+	    break;
+	if (i==NELEM(commands))
+	  sprintf(msg,"No help for nonexistent command '%s'.",args);
+	else 
+	  sprintf(msg,"%s",commands[i].help);
+      }
+      break;
+
+    case RESET_CMD:
+      if (args && !strcasecmp(args,"all")) {
+	for (i=NumPlayers-1;i>=0;i--)
+	  Players[i]->score=0;
+	Reset_all_players();
+	roundCounter = 1;
+	if (gameDuration == -1)
+	  gameDuration = 0;
+	sprintf(msg," < Total reset by %s! >",pl->name);
+	Set_message(msg);
+	/* BEGIN - TEAMCUP */
+	teamcup_close_score_file();
+	teamcup_open_score_file();
+	teamcup_round_start();
+	/* END - TEAMCUP */
+	return;
+      }
+      else {
+	Reset_all_players();
+	sprintf(msg," < Round reset by %s! >",pl->name);
+	Set_message(msg);
+	if (gameDuration == -1)
+	  gameDuration = 0;
+	if (roundCounter == numberOfRounds+1)
+	  numberOfRounds=0;
+	return;
+      }
+
+    case PASSWORD_CMD:
+      if (!password || !args || strcmp(args,password))
+	sprintf(msg,"Wrong.");
+      else {
+	if (!pl->isoperator)
+	  NumOperators++;
+	pl->isoperator=1;
+	sprintf(msg,"You got operator status.");
+      }
+      break;
+
+    case LOCK_CMD:
+      if (!args)
+	sprintf(msg,"The game is currently %s.",game_lock?"locked":"unlocked");
+      else if (!pl->isoperator)
+	sprintf(msg,"You need operator status for this.");
+      else if (atoi(args)) {
+	sprintf(msg," < The game has been locked by %s! >",pl->name);
+	Set_message(msg);
+	game_lock=1;
+	return;
+      }
+      else {
+	sprintf(msg," < The game has been unlocked by %s! >",pl->name);
+	Set_message(msg);
+	game_lock=0;
+	return;
+      }
+      break;
+
+    case SET_CMD:
+      if (!args || !(args=strtok(args," ")) || !(cmd=strtok(NULL," ")) )
+	sprintf(msg,"Usage: /set option value.");
+      else if ((i=Tune_option(args,cmd)) == 1) {
+	if (!strcasecmp(args,"password"))
+	  sprintf(msg,"Operation successful.");
+	else {
+	  sprintf(msg," < Option %s set to %s by %s. >",
+		  args,cmd,pl->name);
+	  Set_message(msg);
+	  return;
+	}
+      }
+      else if (i==0)
+	sprintf(msg,"Invalid value.");
+      else if (i==-1)
+	sprintf(msg,"This option cannot be changed at runtime.");
+      else if (i==-2)
+	sprintf(msg,"No option named \"%s\".",args);
+      else
+	sprintf(msg,"Error.");
+      break;
+
+    case PAUSE_CMD:
+      if ((i=Ind_by_name(args))>=0 && Players[i]->conn != NOT_CONNECTED) {
+	if (BIT(Players[i]->status, PLAYING|PAUSE|GAME_OVER|KILLED) == PLAYING)
+	  Kill_player(i);
+	Pause_player(i,1);
+	sprintf(msg,"%s was paused by %s.",Players[i]->name,pl->name);
+	Set_message(msg);
+	return;
+      }
+      sprintf(msg,"Invalid player id.");
+      break;
+    }
+
+    sprintf(msg+strlen(msg)," [*Server reply*]");
+    Set_player_message(pl,msg);
+    return;
+}
+
 static int Receive_talk(int ind)
 {
     connection_t	*connp = &Conn[ind];
@@ -2511,7 +3019,10 @@
 	    return n;
 	}
 	connp->talk_sequence_num = seq;
-	Handle_talk (ind, str);
+	if (*str=='/')
+	  Handle_command(ind,str+1);
+	else
+	  Handle_talk (ind, str);
     }
     return 1;
 }
@@ -2845,8 +3356,8 @@
 	return n;
     }
     pl = Players[GetInd[connp->id]];
-    if (BIT(pl->used, OBJ_AUTOPILOT))
-	Autopilot(ind, 0);
+    if (BIT(pl->used, OBJ_AUTOPILOT) && !BIT(pl->status, HOVERPAUSE))
+	Autopilot(GetInd[connp->id], 0);
     turnspeed = movement * pl->turnspeed / MAX_PLAYER_TURNSPEED;
     if (turnspeed < 0) {
 	turndir = -1.0;
@@ -2855,7 +3366,20 @@
     else {
 	turndir = 1.0;
     }
-    LIMIT(turnspeed, MIN_PLAYER_TURNSPEED, MAX_PLAYER_TURNSPEED);
+
+    if (pl->turnresistance)
+      LIMIT(turnspeed, MIN_PLAYER_TURNSPEED, MAX_PLAYER_TURNSPEED);
+      /* This is just stupid - minimum amount of turning if you want to
+	 turn at all??? And the only effect of that maximum is making
+	 finding the correct settings harder for new mouse players,
+	 because the limit is checked BEFORE multiplying by turnres!
+	 Kept here to avoid changing the feeling for old players who
+	 are already used to this odd behavior. New players should set
+	 turnresistance to 0.
+      */
+    else
+      LIMIT(turnspeed,0,5*RES);
+
     pl->turnvel -= turndir * turnspeed;
 
     return 1;
@@ -2879,8 +3403,9 @@
 	pl = Players[GetInd[connp->id]];
 	pl->player_fps = fps;
 	if (fps > FPS) pl->player_fps = FPS;
-	if (fps < (FPS / 2)) pl->player_fps = FPS / 2;
+	if (fps < (FPS / 2)) pl->player_fps = (FPS+1) / 2;
 	if (fps == 0) pl->player_fps = FPS;
+	if ((fps == 20) && ignore20MaxFPS) pl->player_fps = FPS;
 	n = FPS - pl->player_fps;
 	if (n <= 0) {
 	    pl->player_count = 0;
diff -urN xpilot-4.1.0/src/server/object.h xpilot-4.U.4tc99b/src/server/object.h
--- xpilot-4.1.0/src/server/object.h	Sat Aug 29 21:49:55 1998
+++ xpilot-4.U.4tc99b/src/server/object.h	Mon Aug 30 00:58:51 1999
@@ -420,6 +420,8 @@
 
     int		isowner;		/* If player started this server. */
 
+    int         isoperator;             /* Player has operator privileges */
+
 #ifdef __cplusplus
 		player() {}
 #endif
diff -urN xpilot-4.1.0/src/server/play.c xpilot-4.U.4tc99b/src/server/play.c
--- xpilot-4.1.0/src/server/play.c	Fri Oct  2 20:39:27 1998
+++ xpilot-4.U.4tc99b/src/server/play.c	Mon Aug 30 00:58:52 1999
@@ -94,8 +94,9 @@
 		    ? MAX_AFTERBURNER
 		    : pl->item[ITEM_AFTERBURNER]);
     alt_sparks = afterburners
-		    ? AFTER_BURN_SPARKS(tot_sparks-1, afterburners) + 1
+		    ? AFTER_BURN_SPARKS(tot_sparks-1, afterburners)/FPSMultiplier + 1
 		    : 0;
+    tot_sparks/=FPSMultiplier;
 
     Make_debris(
 	/* pos.x, pos.y   */ x, y,
@@ -110,7 +111,7 @@
 	/* min,max debris */ tot_sparks-alt_sparks, tot_sparks-alt_sparks,
 	/* min,max dir    */ min_dir, max_dir,
 	/* min,max speed  */ 1.0, max_speed,
-	/* min,max life   */ 3, max_life
+	/* min,max life   */ 3*FPSMultiplier, max_life*FPSMultiplier
 	);
 
     Make_debris(
@@ -126,7 +127,7 @@
 	/* min,max debris */ alt_sparks, alt_sparks,
 	/* min,max dir    */ min_dir, max_dir,
 	/* min,max speed  */ 1.0, max_speed,
-	/* min,max life   */ 3, max_life
+	/* min,max life   */ 3*FPSMultiplier, max_life*FPSMultiplier
 	);
 }
 
@@ -537,7 +538,7 @@
     obj->acc.x =
     obj->acc.y = 0.0;
     obj->mass = 10.0;
-    obj->life = 1500 + (rand()&511);
+    obj->life = (1500 + (rand()&511))*FPSMultiplier;
     obj->count = num_per_pack;
     obj->pl_range = ITEM_SIZE/2;
     obj->pl_radius = ITEM_SIZE/2;
@@ -668,7 +669,7 @@
 	return;
     }
     percent = TRACTOR_PERCENT(pl->lock.distance, maxdist);
-    cost = (long)TRACTOR_COST(percent);
+    cost = (long)TRACTOR_COST(percent)/FPSMultiplier;
     if (pl->fuel.sum < -cost) {
 	CLR_BIT(pl->used, OBJ_TRACTOR_BEAM);
 	return;
@@ -693,7 +694,7 @@
     if (dist > maxdist)
 	return;
     percent = TRACTOR_PERCENT(dist, maxdist);
-    cost = (long)TRACTOR_COST(percent);
+    cost = (long)TRACTOR_COST(percent)/FPSMultiplier;
     force = TRACTOR_FORCE(pressor, percent, maxforce);
     
     sound_play_sensors(x, y,
@@ -705,15 +706,14 @@
     theta = (int)Wrap_findDir(x - victim->pos.x, y - victim->pos.y);
 
     if (pl) {
-	pl->vel.x += tcos(theta) * (force / pl->mass);
-	pl->vel.y += tsin(theta) * (force / pl->mass);
+	pl->vel.x += tcos(theta) * (force / pl->mass)/FPSMultiplier;
+	pl->vel.y += tsin(theta) * (force / pl->mass)/FPSMultiplier;
 	Record_shove(pl, victim, frame_loops);
 	Record_shove(victim, pl, frame_loops);
     }
-    victim->vel.x -= tcos(theta) * (force / victim->mass);
-    victim->vel.y -= tsin(theta) * (force / victim->mass);
+    victim->vel.x -= tcos(theta) * (force / victim->mass)/FPSMultiplier;
+    victim->vel.y -= tsin(theta) * (force / victim->mass)/FPSMultiplier;
 }
-
 void Place_mine(int ind)
 {
     player *pl = Players[ind];
@@ -879,7 +879,7 @@
 	     * This causes the added initial velocity to reduce to
 	     * zero over the MINI_MINE_SPREAD_TIME.
 	     */
-	    mine->spread_left = MINI_MINE_SPREAD_TIME;
+	    mine->spread_left = MINI_MINE_SPREAD_TIME*FPSMultiplier;
 	    mine->acc.x = -mv.x / (MINI_MINE_SPREAD_TIME+1);
 	    mine->acc.y = -mv.y / (MINI_MINE_SPREAD_TIME+1);
 	} else {
@@ -1019,17 +1019,18 @@
 	    }
 	}
     }
-    if (!somebody_flag) {
-	SCORE(ind, Rate(pl->score, CANNON_SCORE)/2,
-	      tt->pos.x, tt->pos.y, "Treasure:");
-	return 0;
-    }
 
     sound_play_all(DESTROY_BALL_SOUND);
     sprintf(msg, " < %s's (%d) team has destroyed team %d treasure >",
 	    pl->name, pl->team, td->team);
     Set_message(msg);
 
+    if (!somebody_flag) {
+	SCORE(ind, Rate(pl->score, CANNON_SCORE)/2,
+	      tt->pos.x, tt->pos.y, "Treasure:");
+	return 0;
+    }
+
     td->destroyed++;
     World.teams[td->team].TreasuresLeft--;
     World.teams[tt->team].TreasuresDestroyed++;
@@ -1886,13 +1887,13 @@
 		    /* min,max debris */ 75, 150,
 		    /* min,max dir    */ 0, RES-1,
 		    /* min,max speed  */ 20, 95,
-		    /* min,max life   */ 10, 2*(FPS+15)
+		    /* min,max life   */ 10*FPSMultiplier, 2*(FPS+15)
 		    );
 	    } else {
 		Make_debris(
 		    shot->prevpos.x, shot->prevpos.y, shot->vel.x, shot->vel.y,
 		    shot->id, shot->team, OBJ_DEBRIS, DEBRIS_MASS, GRAVITY,
-		    RED, 8, 10, 20, 0, RES-1, 10, 50, 10, 2*(FPS+15));
+		    RED, 8, 10, 20, 0, RES-1, 10, 50, 10*FPSMultiplier, 2*(FPS+15));
 	    }
 
 	}
@@ -2005,8 +2006,8 @@
 	    /* min,max speed  */ 20 * speed_modv,
 				 (intensity >> 2) * speed_modv,
 #endif
-	    /* min,max life   */ (int)(8 * life_modv),
-				 (int)((intensity >> 1) * life_modv)
+	    /* min,max life   */ (int)(8 * life_modv)*FPSMultiplier,
+				 (int)((intensity >> 1) * life_modv)*FPSMultiplier
 	    );
 	break;
 
@@ -2102,8 +2103,8 @@
 	if (pl->fuel.sum <= -ED_LASER) {
 	    CLR_BIT(pl->used, OBJ_LASER);
 	} else {
-	    x = pl->pos.x + pl->ship->m_gun[pl->dir].x + pl->vel.x;
-	    y = pl->pos.y + pl->ship->m_gun[pl->dir].y + pl->vel.y;
+	    x = pl->pos.x + pl->ship->m_gun[pl->dir].x + pl->vel.x/FPSMultiplier;
+	    y = pl->pos.y + pl->ship->m_gun[pl->dir].y + pl->vel.y/FPSMultiplier;
 	    x = WRAP_XPIXEL(x);
 	    y = WRAP_YPIXEL(y);
 	    if (x >= 0 && x < World.width && y >= 0 && y < World.height) {
@@ -2133,11 +2134,11 @@
     pulse->team = team;
     pulse->dir = dir;
     pulse->len = PULSE_LENGTH;
-    pulse->life = life;
+    pulse->life = (life-1)*FPSMultiplier+1; 
     pulse->mods = mods;
     pulse->refl = false;
-    pulse->pos.x = x - PULSE_SPEED * tcos(dir);
-    pulse->pos.y = y - PULSE_SPEED * tsin(dir);
+    pulse->pos.x = x - PULSE_SPEED * tcos(dir)/FPSMultiplier;
+    pulse->pos.y = y - PULSE_SPEED * tsin(dir)/FPSMultiplier;
     NumPulses++;
     if (pl)
 	pl->num_pulses++;
@@ -2152,11 +2153,11 @@
     int		i;
     long	dist, dx, dy;
 
-    if (pl->fuel.sum < -ED_DEFLECTOR) {
+    if (pl->fuel.sum < -ED_DEFLECTOR/FPSMultiplier) {
 	CLR_BIT(pl->used, OBJ_DEFLECTOR);
 	return;
     }
-    Add_fuel(&(pl->fuel), (long)ED_DEFLECTOR);
+    Add_fuel(&(pl->fuel), (long)ED_DEFLECTOR/FPSMultiplier);
 
     for (i = 0; i < NumObjs; i++) {
 	obj = Obj[i];
@@ -2207,8 +2208,8 @@
 				/ (RES * 0.25);
 		DFLOAT dv = force / ABS(obj->mass);
 
-		obj->vel.x += tcos(dir) * dv;
-		obj->vel.y += tsin(dir) * dv;
+		obj->vel.x += tcos(dir) * dv/FPSMultiplier;
+		obj->vel.y += tsin(dir) * dv/FPSMultiplier;
 	    }
 	}
     }
@@ -2601,7 +2602,7 @@
 	     */
 	    SET_BIT(shot->status, CONFUSED);
 	    shot->ecm_range = range;
-	    shot->count = CONFUSED_TIME;
+	    shot->count = CONFUSED_TIME*FPSMultiplier;
 	    if (pl
 		&& BIT(pl->lock.tagged, LOCK_PLAYER)
 		&& (pl->lock.distance <= pl->sensor_range
@@ -2752,7 +2753,7 @@
 
 	    if (!IS_ROBOT_PTR(p) || !ecmsReprogramRobots || !pl) {
 		/* player is blinded by light flashes. */
-		long duration = (int)(damage * pow(0.75, p->item[ITEM_SENSOR]));
+		long duration = (int)(damage * pow(0.75, p->item[ITEM_SENSOR]))*FPSMultiplier;
 		p->damaged += duration;
 		if (pl)
 		    Record_shove(p, pl, frame_loops + duration);
@@ -2922,13 +2923,13 @@
 
     /* compute accelleration for player, assume t = 1 */
     accell = (force + pl_damping + ball_damping) / pl->mass;
-    pl->vel.x += D.x * accell;
-    pl->vel.y += D.y * accell;
+    pl->vel.x += D.x * accell/FPSMultiplier;
+    pl->vel.y += D.y * accell/FPSMultiplier;
 
     /* compute accelleration for ball, assume t = 1 */
     accell = (force + ball_damping + pl_damping) / ball->mass;
-    ball->vel.x += -D.x * accell;
-    ball->vel.y += -D.y * accell;
+    ball->vel.x += -D.x * accell/FPSMultiplier;
+    ball->vel.y += -D.y * accell/FPSMultiplier;
 
 #endif	/* ORIGINAL_BALL */
 }
@@ -2956,8 +2957,8 @@
 	    shot->acc.x = 0;
 	    shot->acc.y = 0;
 	}
-	shot->vel.x += acc * tcos(shot->dir);
-	shot->vel.y += acc * tsin(shot->dir);
+	shot->vel.x += acc * tcos(shot->dir)/FPSMultiplier;
+	shot->vel.y += acc * tsin(shot->dir)/FPSMultiplier;
 	return;
     }
 
@@ -2975,7 +2976,7 @@
 	} else {
 	    /* No player. Number of moves so that new target is searched */
 	    pl = 0;
-	    shot->count = HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR;
+	    shot->count = (HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR)*FPSMultiplier;
 	}
 	if (pl && BIT(pl->status, THRUSTING)) {
 	    /*
@@ -2983,24 +2984,24 @@
 	     * set number to moves to correct error value
 	     */
 	    if (range < HEAT_CLOSE_RANGE) {
-		shot->count = HEAT_CLOSE_ERROR;
+		shot->count = HEAT_CLOSE_ERROR*FPSMultiplier;
 	    } else if (range < HEAT_MID_RANGE) {
-		shot->count = HEAT_MID_ERROR;
+		shot->count = HEAT_MID_ERROR*FPSMultiplier;
 	    } else {
-		shot->count = HEAT_WIDE_ERROR;
+		shot->count = HEAT_WIDE_ERROR*FPSMultiplier;
 	    }
 	} else {
 	    shot->count++;
 	    /* Look for new target */
 	    if ((range < HEAT_CLOSE_RANGE
-		 && shot->count > HEAT_CLOSE_TIMEOUT + HEAT_CLOSE_ERROR)
+		 && shot->count/FPSMultiplier > HEAT_CLOSE_TIMEOUT + HEAT_CLOSE_ERROR)
 		|| (range < HEAT_MID_RANGE
-		    && shot->count > HEAT_MID_TIMEOUT + HEAT_MID_ERROR)
-		|| shot->count > HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR) {
+		    && shot->count/FPSMultiplier > HEAT_MID_TIMEOUT + HEAT_MID_ERROR)
+		|| shot->count/FPSMultiplier > HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR) {
 		DFLOAT l;
 		int i;
 
-		range = HEAT_RANGE * (shot->count/HEAT_CLOSE_TIMEOUT);
+		range = HEAT_RANGE * (shot->count/FPSMultiplier/HEAT_CLOSE_TIMEOUT);
 		for (i=0; i<NumPlayers; i++) {
 		    player *p = Players[i];
 
@@ -3026,6 +3027,7 @@
 			    l < HEAT_CLOSE_RANGE ?
 				HEAT_CLOSE_ERROR : l < HEAT_MID_RANGE ?
 				    HEAT_MID_ERROR : HEAT_WIDE_ERROR;
+			shot->count *= FPSMultiplier;
 			pl = p;
 		    }
 		}
@@ -3037,14 +3039,14 @@
 	 * Heat seekers cannot fly exactly, if target is far away or thrust
 	 * isn't active.  So simulate the error:
 	 */
-	x_dif = (rand()&3) * shot->count;
-	y_dif = (rand()&3) * shot->count;
+	x_dif = (rand()&3) * shot->count/FPSMultiplier;
+	y_dif = (rand()&3) * shot->count/FPSMultiplier;
 
     } else {
 
 	if (BIT(shot->status, CONFUSED)
-	    && (!(frame_loops % CONFUSED_UPDATE_GRANULARITY)
-		|| shot->count == CONFUSED_TIME)) {
+	    && (!(frame_loops % (CONFUSED_UPDATE_GRANULARITY*FPSMultiplier))
+		|| shot->count == CONFUSED_TIME*FPSMultiplier)) {
 
 	    if (shot->count) {
 		shot->info = Players[rand() % NumPlayers]->id;
@@ -3121,7 +3123,7 @@
 	    case CANNON:
 		if (range > (SMART_SHOT_LOOK_AH-i)*(BLOCK_SZ/BLOCK_PARTS)) {
 		    if (shot_speed > SMART_SHOT_MIN_SPEED)
-			shot_speed -= acc * (SMART_SHOT_DECFACT+1);
+			shot_speed -= acc * (SMART_SHOT_DECFACT+1)/FPSMultiplier;
 		}
 		foundw = 1;
 	    }
@@ -3180,7 +3182,7 @@
 	    if (!foundw && range > (SHOT_LOOK_AH-i) * BLOCK_SZ) {
 		if (shot_speed
 		    > (SMART_SHOT_MIN_SPEED + SMART_SHOT_MAX_SPEED)/2)
-		    shot_speed -= SMART_SHOT_DECC+SMART_SHOT_ACC;
+		    shot_speed -= (SMART_SHOT_DECC+SMART_SHOT_ACC)/FPSMultiplier;
 	    }
 #endif
 	}
@@ -3196,14 +3198,14 @@
     angle = angle - shot->dir - RES/2;
 
     if (angle < 0)
-	shot->dir += (u_byte)(((-angle < shot->turnspeed) ? -angle : shot->turnspeed));
+	shot->dir += (u_byte)(((-angle < shot->turnspeed/FPSMultiplier) ? -angle : shot->turnspeed/FPSMultiplier));
     else
-	shot->dir -= (u_byte)(((angle < shot->turnspeed) ? angle : shot->turnspeed));
+	shot->dir -= (u_byte)(((angle < shot->turnspeed/FPSMultiplier) ? angle : shot->turnspeed/FPSMultiplier));
 
     shot->dir = MOD2(shot->dir, RES); /* NOTE!!!! */
 
     if (shot_speed < shot->max_speed)
-	shot_speed += acc;
+	shot_speed += acc/FPSMultiplier;
 
     /*  shot->velocity = MIN(shot->velocity, shot->max_speed);  */
 
@@ -3286,14 +3288,14 @@
 	    if (!fuel) {
 		if (t
 		    && t != ft->current
-		    && *f >= low_level + REFUEL_RATE
-		    && *(f-1) <= TANK_CAP(t-1) - REFUEL_RATE) {
+		    && *f >= low_level + REFUEL_RATE/FPSMultiplier
+		    && *(f-1) <= TANK_CAP(t-1) - REFUEL_RATE/FPSMultiplier) {
 
-		    *f -= REFUEL_RATE;
-		    fuel = REFUEL_RATE;
+		    *f -= REFUEL_RATE/FPSMultiplier;
+		    fuel = REFUEL_RATE/FPSMultiplier;
 		} else if (t && *f < low_level) {
-		    *f += REFUEL_RATE;
-		    fuel = -REFUEL_RATE;
+		    *f += REFUEL_RATE/FPSMultiplier;
+		    fuel = -REFUEL_RATE/FPSMultiplier;
 		}
 	    }
 	    if (fuel && t == 0) {
@@ -3520,10 +3522,10 @@
 	    max_life = ShotsLife;
 	}
     }
-    if (min_speed * max_life > World.hypotenuse)
-	min_speed = World.hypotenuse / max_life;
-    if (max_speed * min_life > World.hypotenuse)
-	max_speed = World.hypotenuse / min_life;
+    if (min_speed * max_life / FPSMultiplier > World.hypotenuse)
+	min_speed = World.hypotenuse * FPSMultiplier / max_life;
+    if (max_speed * min_life / FPSMultiplier > World.hypotenuse)
+	max_speed = World.hypotenuse *FPSMultiplier / min_life;
     if (max_speed < min_speed)
 	max_speed = min_speed;
 
@@ -3560,8 +3562,8 @@
 	debris->mass = mass;
 	debris->type = type;
 	life = (int)(min_life + rfrac() * (max_life - min_life) + 1);
-	if (life * speed > World.hypotenuse) {
-	    life = (long)(World.hypotenuse / speed);
+	if (life * speed / FPSMultiplier > World.hypotenuse) {
+	    life = (long)(World.hypotenuse * FPSMultiplier / speed);
 	}
 	debris->life = life;
 	debris->fuselife = life;
@@ -3593,6 +3595,8 @@
     modifiers		mods;
     DFLOAT		mass, sum_mass = 0.0;
 
+    if (!useWreckage)
+      return;
     if (BIT(World.rules->mode, WRAP_PLAY)) {
 	if (x < 0) x += World.width;
 	else if (x >= World.width) x -= World.width;
@@ -3714,7 +3718,7 @@
 	/* min,max debris */ min_debris, max_debris,
 	/* min,max dir    */ 0, RES-1,
 	/* min,max speed  */ 20.0, 20 + (((int)(pl->mass))>>1),
-	/* min,max life   */ 5, (int)(5 + (pl->mass * 1.5))
+	/* min,max life   */ 5*FPSMultiplier, (int)(5 + (pl->mass * 1.5))*FPSMultiplier
 	);
 
     if ( !BIT(pl->status, KILLED) )
@@ -3731,7 +3735,7 @@
 	/* max wreckage     */ 10,
 	/* min,max dir      */ 0, RES-1,
 	/* min,max speed    */ 10.0, 10 + (((int)(pl->mass))>>1),
-	/* min,max life     */ 5, (int)(5 + (pl->mass * 1.5))
+	/* min,max life     */ 5*FPSMultiplier, (int)(5 + (pl->mass * 1.5))*FPSMultiplier
 	);
 
 }
diff -urN xpilot-4.1.0/src/server/player.c xpilot-4.U.4tc99b/src/server/player.c
--- xpilot-4.1.0/src/server/player.c	Wed Sep 23 21:42:49 1998
+++ xpilot-4.U.4tc99b/src/server/player.c	Wed Sep 22 20:45:18 1999
@@ -44,6 +44,7 @@
 #include "saudio.h"
 #include "error.h"
 #include "objpos.h"
+#include "status.h"
 
 char player_version[] = VERSION;
 
@@ -490,6 +491,8 @@
 
     pl->isowner = 0;
 
+    pl->isoperator = 0;
+
     return pl->id;
 }
 
@@ -583,7 +586,7 @@
 }
 
 
-static void Reset_all_players(void)
+void Reset_all_players(void)
 {
     player		*pl;
     int			i, j;
@@ -874,6 +877,28 @@
 	  "[Winner]");
 }
 
+extern int roundCounter;
+
+static void Count_rounds(void)
+{
+  char msg[100];
+
+  if (!numberOfRounds)
+    return;
+
+  sprintf(msg," < Round %d out of %d completed. >",roundCounter,numberOfRounds);
+  Set_message(msg);
+  if (roundCounter==numberOfRounds)
+    Game_Over();
+  roundCounter++;
+
+  /* BEGIN - TEAMCUP */
+  teamcup_round_start();
+  /* END - TEAMCUP */
+
+  return;
+}
+
 void Team_game_over(int winning_team, const char *reason)
 {
     int			i, j;
@@ -937,8 +962,14 @@
 	}
     }
 
+    /* BEGIN - TEAMCUP */
+    teamcup_round_end(winning_team);
+    /* END - TEAMCUP */
+
     Reset_all_players();
 
+    Count_rounds();
+
     free(best_players);
 }
 
@@ -1135,6 +1166,8 @@
 		    "ashamed of yourselves.");
     }
 
+    Count_rounds();
+
     Reset_all_players();
 }
 
@@ -1577,6 +1610,8 @@
     }
 }
 
+extern int game_lock;
+
 void Delete_player(int ind)
 {
     player		*pl = Players[ind];
@@ -1588,6 +1623,18 @@
 	Robot_destroy(ind);
     }
 
+    if (pl->isoperator)
+      if (!--NumOperators && game_lock) {
+	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 (Swappers[i]==id)
+	Swappers[i]=-1;
+    Swappers[pl->team]=-1;  /* change this behavior later (pause too) */
+
     /* Delete remaining shots */
     for (i = NumObjs - 1; i >= 0; i--) {
 	obj = Obj[i];
@@ -1837,3 +1884,119 @@
     pl->used	&= ~(USED_KILL);
     pl->used	&= pl->have;
 }
+
+/* BEGIN - TEAMCUP */
+void teamcup_round_start()
+{
+  if (!teamcup)
+    return;
+
+  teamcup_log("\nRound %d\n", roundCounter);
+}
+
+void teamcup_round_end(int winning_team)
+{
+  int i;
+  int j;
+  int *list;
+  int team_score[MAX_TEAMS];
+  int team_players[MAX_TEAMS];
+  int best_score;
+  int best;
+  player *pl;
+
+  if (!teamcup)
+    return;
+
+  list = malloc(NumPlayers * sizeof(int));
+  if (list == NULL) {
+    error("Can't allocate memory for list");
+    End_game();
+  }
+
+  for (i = 0; i < NumPlayers; i++)
+    list[i] = i;
+
+  for (i = 0; i < MAX_TEAMS; i++)
+    team_score[i] = INT_MAX;
+
+  for (i = 0; i < MAX_TEAMS; i++)
+    team_players[i] = 0;
+
+  for (i = 0; i < NumPlayers; i++) {
+    best = NumPlayers;
+    for (j = 0; j < NumPlayers; j++) {
+      if (list[j] == NumPlayers)
+	continue;
+      pl = Players[j];
+      if (best == NumPlayers || pl->score > best_score) {
+	best_score = pl->score;
+	best = j;
+      }
+    }
+
+    list[best] = NumPlayers;
+    pl = Players[best];
+    teamcup_log("%d\t%d\t%2d/%d\t%s\n", pl->team, pl->score, pl->kills,
+	     pl->deaths, pl->name);
+
+    if (team_score[pl->team] == INT_MAX)
+      team_score[pl->team] = 0;
+    team_score[pl->team] += pl->score;
+    team_players[pl->team]++;
+  }
+
+  for (i = 0; i < MAX_TEAMS; i++)
+    if (team_score[i] != INT_MAX)
+      teamcup_log("Team %d\t%d\n", i, team_score[i]);
+  if (teamcup_score_file != NULL)
+    fflush(teamcup_score_file);
+
+  if (teamcup_status_fd) {
+      struct round_end rend;
+
+      if (!teamcup_match_inited) {
+	  struct match_init minit;
+
+	  minit.magic = htons(INIT_MATCH_MAGIC);
+	  minit.matchno = htons(matchNumber);
+	  minit.t2players = team_players[2];
+	  minit.t4players = team_players[4];
+	  write(teamcup_status_fd, &minit, sizeof(minit));
+
+	  for (i = 0; i < NumPlayers; i++) {
+	      struct player_init pinit;
+
+	      pl = Players[i];
+	      pinit.magic = htons(INIT_PLAYER_MAGIC);
+	      pinit.id = htons(i);
+	      pinit.team = pl->team;
+	      pinit.namelen = strlen(pl->name);
+	      write(teamcup_status_fd, &pinit, sizeof(pinit));
+	      write(teamcup_status_fd, &pl->name, pinit.namelen);
+	  }
+
+	  teamcup_match_inited = 1;
+      }
+
+      for (i = 0; i < NumPlayers; i++) {
+	  struct player_status pstat;
+
+	  pl = Players[i];
+	  pstat.magic = htons(PLAYER_STATUS_MAGIC);
+	  pstat.id = htons(i);
+	  pstat.score = htons(pl->score);
+	  pstat.kills = pl->kills;
+	  pstat.deaths = pl->deaths;
+	  write(teamcup_status_fd, &pstat, sizeof(pstat));
+      }
+
+      rend.magic = htons(ROUND_END_MAGIC);
+      rend.roundno = roundCounter;
+      rend.winner = winning_team;
+      write(teamcup_status_fd, &rend, sizeof(rend));
+  }
+
+  free(list);
+}
+/* END - TEAMCUP */
diff -urN xpilot-4.1.0/src/server/sched.c xpilot-4.U.4tc99b/src/server/sched.c
--- xpilot-4.1.0/src/server/sched.c	Wed Apr 22 15:56:43 1998
+++ xpilot-4.U.4tc99b/src/server/sched.c	Mon Aug 30 00:58:52 1999
@@ -126,6 +126,8 @@
  */
 static void catch_timer(int signum)
 {
+    static unsigned int count;
+
 #ifdef OS2DEBUG
     static int counter = 0;
 
@@ -137,7 +139,12 @@
 	fflush( stdout );
     }
 #endif
-    timer_ticks++;
+
+    count+=FPS;
+    if (count>=timerResolution) {
+      count-=timerResolution;
+      timer_ticks++;
+    }
 }
 
 #ifdef _OS2_
diff -urN xpilot-4.1.0/src/server/server.c xpilot-4.U.4tc99b/src/server/server.c
--- xpilot-4.1.0/src/server/server.c	Sat Aug 29 21:49:57 1998
+++ xpilot-4.U.4tc99b/src/server/server.c	Wed Sep 22 20:45:18 1999
@@ -70,6 +70,8 @@
 #include "portability.h"
 #include "server.h"
 
+#include "status.h"
+
 char server_version[] = VERSION;
 
 #ifndef	lint
@@ -82,6 +84,7 @@
  * Global variables
  */
 int			NumPlayers = 0;
+int                     NumOperators = 0;
 int			NumObjs = 0;
 int			NumPulses = 0;
 int			NumEcms = 0;
@@ -97,6 +100,7 @@
 char			ShutdownReason[MAX_CHARS];
 int 			framesPerSecond = 18;
 long			main_loops = 0;		/* needed in events.c */
+int                     roundCounter = 1;
 
 static int		serverSocket;
 #ifdef LOG
@@ -114,6 +118,189 @@
 extern void Main_loop(void);
 static void Handle_signal(int sig_no);
 
+/* BEGIN - TEAMCUP */
+FILE			*teamcup_score_file = NULL;
+char			*teamcup_score_file_name = NULL;
+int			teamcup_match_inited = 0;
+int			teamcup_have_fork = 0;
+int			teamcup_status_fd = -1;
+int			teamcup_child_pid = 1;	/* Set to 1 so we don't send
+						   any dangerous pid to kill */
+
+#define STATUSBUFS	8
+#define STATBUFSIZE		512
+static void
+teamcup_status_thread(int readfd)
+{
+    char buf[STATUSBUFS][STATBUFSIZE], *bufptr[STATUSBUFS];
+    int len[STATUSBUFS];
+    int sockfd;
+    int wrbuf, rdbuf, maxfd, i;
+
+    for (i = 0; i < 64; i++) {
+	signal(i, _exit);
+    }
+    sockfd = CreateClientSocket(statServer, statPort);
+    if (sockfd < 0) {
+	xpprintf("\n!!! Unable to connect to master server\n\n");
+	_exit(1);
+    }
+
+    wrbuf = 0;
+    rdbuf = 0;
+    for (i = 0; i < STATUSBUFS; i++) {
+	bufptr[i] = NULL;
+	len[i] = 0;
+    }
+
+    /* Read first block of data */
+    do {
+	len[rdbuf] = read(readfd, buf[rdbuf], STATBUFSIZE);
+    } while (len[rdbuf] <= 0);
+    bufptr[rdbuf] = buf[rdbuf];
+    rdbuf++;
+
+    if (readfd > sockfd) maxfd = readfd + 1;
+    else maxfd = sockfd + 1;
+
+    while (1) {
+	fd_set rfds, wfds;
+	int dowr = 0;
+
+	FD_ZERO(&rfds);
+	FD_SET(readfd, &rfds);
+	if (bufptr[wrbuf]) {
+	    FD_ZERO(&wfds);
+	    FD_SET(sockfd, &wfds);
+	    dowr = 1;
+	}
+	do {
+	    FD_ZERO(&rfds);
+	    FD_SET(readfd, &rfds);
+	    if (bufptr[wrbuf]) {
+		FD_ZERO(&wfds);
+		FD_SET(sockfd, &wfds);
+		dowr = 1;
+	    }
+	} while (select(maxfd, &rfds, (dowr ? &wfds : NULL), NULL, NULL) <= 0);
+	if (dowr && FD_ISSET(sockfd, &wfds)) {
+	    int ret;
+
+	    ret = write(sockfd, bufptr[wrbuf], len[wrbuf]);
+	    if (ret < 0) ret = 0;
+	    len[wrbuf] -= ret;
+	    bufptr[wrbuf] += ret;
+	    if (len[wrbuf] <= 0) {
+		bufptr[wrbuf] = NULL;
+		wrbuf++;
+		if (wrbuf >= STATUSBUFS) wrbuf = 0;
+	    }
+	}
+	if (FD_ISSET(readfd, &rfds)) {
+	    int ret;
+
+	    if (bufptr[rdbuf] != NULL) {
+		char dummybuf[STATBUFSIZE];
+
+		read(readfd, dummybuf, STATBUFSIZE);
+		continue;
+	    }
+	    ret = read(readfd, buf[rdbuf], STATBUFSIZE);
+	    if (ret <= 0) continue;
+	    len[rdbuf] = ret;
+	    bufptr[rdbuf] = buf[rdbuf];
+	    rdbuf++;
+	    if (rdbuf >= STATUSBUFS) rdbuf = 0;
+	}
+    }
+}
+
+
+void teamcup_open_score_file()
+{
+  if (!teamcup)
+    return;
+
+  if (teamcup_score_file != NULL) {
+    error("teamcup_score_file != NULL");
+    End_game();
+  }
+  if (teamcup_score_file_name != NULL) {
+    error("teamcup_score_file_name != NULL");
+    End_game();
+  }
+
+  teamcup_score_file_name = tempnam(NULL, "teamcup-");
+  if (teamcup_score_file_name == NULL) {
+    error("tempnam() failed, could not create score file name");
+    End_game();
+  }
+  teamcup_score_file = fopen(teamcup_score_file_name, "w");
+  if (teamcup_score_file == NULL) {
+    error("fopen() failed, could not create score file");
+    End_game();
+  }
+  xpprintf("score file is \"%s\".\n", teamcup_score_file_name);
+
+  teamcup_log("%s\n",
+	   "1) Fill the names of the teams below.\n"
+	   "2) Fill the team number of total winner only if this was the second match.\n"
+	   "3) Send this file to <mackan@stacken.kth.se> with subject TC99-RESULT\n"
+	   "4) Copy this file in a safe place.  Do not delete it after sending.\n"
+	   "\nTeam 2 name: \n"
+	   "Team 4 name: \n"
+	   "Total winner (team number): \n"
+	   "\nDO NOT CHANGE ANYTHING AFTER THIS LINE\n"
+    );
+
+  if (matchNumber && !teamcup_have_fork) {
+      int mypipes[2];
+
+      if (pipe(mypipes) != 0) {
+	  xpprintf("Unable to create pipes!\n");
+	  End_game();
+      }
+
+      teamcup_status_fd = mypipes[1];
+      teamcup_child_pid = fork();
+      switch (teamcup_child_pid) {
+      case -1:
+	  teamcup_child_pid = 1;
+	  xpprintf("Unable to fork!\n");
+	  End_game();
+	  break;
+      case 0:
+	  teamcup_child_pid = 1;
+	  teamcup_status_thread(mypipes[0]);
+	  break;
+      default:
+	  break;
+      }
+      teamcup_have_fork = 1;
+  }
+  teamcup_match_inited = 0;
+}
+
+void teamcup_close_score_file()
+{
+  char msg[MSG_LEN];
+
+  if (!teamcup || teamcup_score_file == NULL)
+    return;
+
+  fclose(teamcup_score_file);
+
+  sprintf(msg,"score file \"%s\" closed", teamcup_score_file_name);
+  Set_message(msg);
+  xpprintf("%s\n", msg);
+
+  free(teamcup_score_file_name);
+
+  teamcup_score_file_name = NULL;
+  teamcup_score_file = NULL;
+}
+/* END - TEAMCUP */
+
 int main(int argc, char **argv)
 {
 
@@ -202,12 +389,15 @@
 #ifndef SILENT
     xpprintf("%s Server runs at %d frames per second\n", showtime(), framesPerSecond);
 #endif
-
+    /* BEGIN - TEAMCUP */
+    teamcup_open_score_file();
+    teamcup_round_start();
+    /* END - TEAMCUP */
 #ifdef	_WINDOWS
     /* Windows returns here, we let the worker thread call sched() */
-    install_timer_tick(ServerThreadTimerProc, FPS);
+    install_timer_tick(ServerThreadTimerProc, timerResolution?timerResolution:FPS);
 #else
-    install_timer_tick(Main_loop, FPS);
+    install_timer_tick(Main_loop, timerResolution?timerResolution:FPS);
 
     sched();
     xpprintf("sched returned!?");
@@ -295,6 +485,10 @@
 	sprintf(msg, "server exiting");
     }
 
+    /* BEGIN - TEAMCUP */
+    teamcup_close_score_file();
+    /* END - TEAMCUP */
+
     while (NumPlayers > 0) {	/* Kick out all remaining players */
 	pl = Players[NumPlayers - 1];
 	if (pl->conn == NOT_CONNECTED) {
@@ -315,6 +509,10 @@
     Free_cells();
     Log_game("END");			    /* Log end */
 
+    if (teamcup_child_pid != 0) {
+	kill(teamcup_child_pid, SIGINT);
+    }
+
 #ifndef	_WINDOWS
     exit (0);
 #endif
@@ -619,11 +817,23 @@
 void Game_Over(void)
 {
     long		maxsc, minsc;
-    int			i, win, loose;
+    int			i, win, lose;
     char		msg[128];
 
     Set_message("Game over...");
 
+    /* BEGIN - TEAMCUP */
+    if (teamcup_status_fd) {
+	struct match_end mend;
+
+	mend.magic = htons(END_MATCH_MAGIC);
+	mend.matchno = htons(matchNumber);
+	write(teamcup_status_fd, &mend, sizeof(mend));
+    }
+
+    teamcup_close_score_file();
+    /* END - TEAMCUP */
+
     /*
      * Hack to prevent Compute_Game_Status from starting over again...
      */
@@ -633,7 +843,7 @@
 	int teamscore[MAX_TEAMS];
 	maxsc = -32767;
 	minsc = 32767;
-	win = loose = -1;
+	win = lose = -1;
 
 	for (i=0; i < MAX_TEAMS; i++) {
 	    teamscore[i] = 1234567; /* These teams are not used... */
@@ -657,7 +867,7 @@
 		}
 		if (teamscore[i] < minsc) {
 		    minsc = teamscore[i];
-		    loose = i;
+		    lose = i;
 		}
 	    }
 	}
@@ -668,8 +878,8 @@
 	    xpprintf("%s\n", msg);
 	}
 
-	if (loose != -1 && loose != win) {
-	    sprintf(msg,"Worst team (%ld Pts): Team %d", minsc, loose);
+	if (lose != -1 && lose != win) {
+	    sprintf(msg,"Worst team (%ld Pts): Team %d", minsc, lose);
 	    Set_message(msg);
 	    xpprintf("%s\n", msg);
 	}
@@ -677,7 +887,7 @@
 
     maxsc = -32767;
     minsc = 32767;
-    win = loose = -1;
+    win = lose = -1;
 
     for (i = 0; i < NumPlayers; i++) {
 	SET_BIT(Players[i]->status, GAME_OVER);
@@ -688,7 +898,7 @@
 	    }
 	    if (Players[i]->score < minsc) {
 		minsc = Players[i]->score;
-		loose = i;
+		lose = i;
 	    }
 	}
     }
@@ -697,8 +907,8 @@
 	Set_message(msg);
 	xpprintf("%s\n", msg);
     }
-    if (loose != -1 && loose != win) {
-	sprintf(msg,"Worst human player: %s", Players[loose]->name);
+    if (lose != -1 && lose != win) {
+	sprintf(msg,"Worst human player: %s", Players[lose]->name);
 	Set_message(msg);
 	xpprintf("%s\n", msg);
     }
@@ -842,3 +1052,29 @@
 {
     pLockServer = (plock_server(pLockServer) == 1) ? true : false;
 }
+
+/* BEGIN - TEAMCUP */
+#if STDVA
+void teamcup_log(const char *fmt, ...)
+#else
+void teamcup_log(va_alist)
+    va_dcl
+#endif
+{
+  va_list ap;
+  
+#if STDVA
+    va_start(ap, fmt);
+#else
+    char		*fmt;
+
+    va_start(ap);
+    fmt = va_arg(ap, char *);
+#endif
+  if (!teamcup || teamcup_score_file == NULL)
+    return;
+
+  vfprintf(teamcup_score_file, fmt, ap);
+  va_end(ap);
+}
+/* END - TEAMCUP */
diff -urN xpilot-4.1.0/src/server/status.h xpilot-4.U.4tc99b/src/server/status.h
--- xpilot-4.1.0/src/server/status.h	Thu Jan  1 01:00:00 1970
+++ xpilot-4.U.4tc99b/src/server/status.h	Sun Sep 19 23:25:25 1999
@@ -0,0 +1,105 @@
+#ifndef _STATUS_STATUS_H
+#define _STATUS_STATUS_H
+
+#include "const.h"
+
+typedef unsigned int	ux32;
+typedef unsigned short	ux16;
+typedef unsigned char	ux8;
+
+typedef signed int	sx32;
+typedef signed short	sx16;
+typedef signed char	sx8;
+
+
+#define MAX_MATCHES	256
+#define STATUS_PORTNO	3749
+#define STATUS_PORTSTR	"3749"
+
+
+struct player_info {
+	int	matchno;
+	int	id;
+	ux8	team;
+	ux8	namelen;
+	ux8	name[MAX_CHARS];
+	sx16	score;
+	ux16	last_kills;
+	ux16	last_deaths;
+	ux16	total_kills;
+	ux16	total_deaths;
+};
+
+
+struct match_info {
+	ux32	inet_addr;
+	char	addr_str[16];
+	int	matchno;
+	int	finished;
+	int	inited;
+	int	lastround;
+	int	lastwinner;
+	int	fd;
+	char	t2name[MAX_CHARS];
+	char	t4name[MAX_CHARS];
+	ux8	t2players;
+	ux8	t4players;
+	sx16	t2score;
+	sx16	t4score;
+	struct player_info *team2;
+	struct player_info *team4;
+};
+
+
+#define INIT_MATCH_MAGIC	0xada0
+#define END_MATCH_MAGIC		0x6daf
+#define ROUND_END_MAGIC		0x0f41
+#define INIT_PLAYER_MAGIC	0xfeed
+#define PLAYER_STATUS_MAGIC	0xa537
+
+struct match_init {
+	ux16	magic;
+	ux16	matchno;
+	ux8	t2players;
+	ux8	t4players;
+	ux8	pad[2];
+};
+
+
+struct match_end {
+	ux16	magic;
+	ux16	matchno;
+};
+
+
+struct player_init {
+	ux16	magic;
+	ux16	id;
+	ux8	team;
+	ux8	namelen;
+	ux8	pad[2];
+	/* Name here */
+};
+
+
+struct round_end {
+	ux16	magic;
+	ux8	roundno;
+	ux8	winner;
+	ux8	by;
+#define	BY_DRAW		0
+#define BY_BALL		1
+#define BY_ALIVE	2
+	ux8	pad[3];
+};
+
+
+struct player_status {
+	ux16	magic;
+	ux16	id;
+	sx16	score;
+	ux8	kills;
+	ux8	deaths;
+};
+
+#endif /* _STATUS_STATUS_H */
diff -urN xpilot-4.1.0/src/server/update.c xpilot-4.U.4tc99b/src/server/update.c
--- xpilot-4.1.0/src/server/update.c	Sat Aug 29 21:49:57 1998
+++ xpilot-4.U.4tc99b/src/server/update.c	Mon Aug 30 00:58:52 1999
@@ -56,13 +56,13 @@
 
 #define update_object_speed(o_)						\
     if (BIT((o_)->status, GRAVITY)) {					\
-	(o_)->vel.x += (o_)->acc.x					\
-		    + World.gravity[(o_)->pos.bx][(o_)->pos.by].x;	\
-	(o_)->vel.y += (o_)->acc.y					\
-		    + World.gravity[(o_)->pos.bx][(o_)->pos.by].y;	\
+	(o_)->vel.x += ((o_)->acc.x					\
+	   + World.gravity[(o_)->pos.bx][(o_)->pos.by].x)/FPSMultiplier;\
+	(o_)->vel.y += ((o_)->acc.y					\
+	   + World.gravity[(o_)->pos.bx][(o_)->pos.by].y)/FPSMultiplier;\
     } else {								\
-	(o_)->vel.x += (o_)->acc.x;					\
-	(o_)->vel.y += (o_)->acc.y;					\
+	(o_)->vel.x += (o_)->acc.x/FPSMultiplier;			\
+        (o_)->vel.y += (o_)->acc.y/FPSMultiplier;	        	\
     }
 
 int	rdelay = 0;		/* delay until start of next round */
@@ -441,8 +441,8 @@
      * Let the fuel stations regenerate some fuel.
      */
     if (NumPlayers > 0) {
-	int fuel = (int)(NumPlayers * STATION_REGENERATION);
-	int frames_per_update = MAX_STATION_FUEL / (fuel * BLOCK_SZ);
+	int fuel = (int)(NumPlayers * STATION_REGENERATION)/FPSMultiplier;
+	int frames_per_update = MAX_STATION_FUEL * FPSMultiplier / (fuel * BLOCK_SZ);
 	for (i=0; i<World.NumFuels; i++) {
 	    if (World.fuel[i].fuel == MAX_STATION_FUEL) {
 		continue;
@@ -468,9 +468,6 @@
     for (i=0; i<NumObjs; i++) {
 	obj = Obj[i];
 
-	update_object_speed(obj);
-	Move_object(i);
-
 	if (BIT(obj->type, OBJ_MINE))
 	    Move_mine(i);
 
@@ -487,6 +484,8 @@
 		(obj->rotation + (int) (obj->turnspeed * RES)) % RES;
 	}
 
+	update_object_speed(obj);
+	Move_object(i);
     }
 
     /*
@@ -536,7 +535,7 @@
 	    weapon = Cannon_select_weapon(i);
 	    Cannon_aim(i, weapon, &target, &dir);
 	    if (target != -1) {
-		if (rand() % 16 == 0)
+		if (rand() % (16*FPSMultiplier) == 0)
 		    Cannon_fire(i, weapon, target, dir);
 	    } else if (cannonsUseItems
 		       && itemProbMult > 0
@@ -762,7 +761,8 @@
 	 * Compute turn
 	 */
 	pl->turnvel	+= pl->turnacc;
-	pl->turnvel	*= pl->turnresistance;
+	if (pl->turnresistance)     /* use old mouse control */
+	    pl->turnvel	*= pl->turnresistance;
 
 #ifdef TURN_FUEL
 	tf = pl->oldturnvel - pl->turnvel;
@@ -780,11 +780,15 @@
 
 	pl->float_dir	+= pl->turnvel;
 
-	if (pl->float_dir < 0)
+	
+	while (pl->float_dir < 0)
 	    pl->float_dir += RES;
-	if (pl->float_dir >= RES)
+	while (pl->float_dir >= RES)
 	    pl->float_dir -= RES;
 
+	if (!pl->turnresistance)
+	    pl->turnvel=0;
+
 	Turn_player(i);
 
 
@@ -792,10 +796,10 @@
 	 * Compute energy drainage
 	 */
 	if (BIT(pl->used, OBJ_SHIELD))
-	    Add_fuel(&(pl->fuel), (long)ED_SHIELD);
+	    Add_fuel(&(pl->fuel), (long)ED_SHIELD/FPSMultiplier);
 
 	if (BIT(pl->used, OBJ_CLOAKING_DEVICE))
-	    Add_fuel(&(pl->fuel), (long)ED_CLOAKING_DEVICE);
+	    Add_fuel(&(pl->fuel), (long)ED_CLOAKING_DEVICE/FPSMultiplier);
 
 #define UPDATE_RATE 100
 
@@ -807,7 +811,7 @@
 		pl->visibility[j].canSee = 1;
 	    else if (pl->updateVisibility
 		     || Players[j]->updateVisibility
-		     || rand() % UPDATE_RATE
+		     || rand() % (UPDATE_RATE*FPSMultiplier)
 		     < ABS(frame_loops - pl->visibility[j].lastChange)) {
 
 		pl->visibility[j].lastChange = frame_loops;
@@ -833,11 +837,11 @@
 		int ct = pl->fuel.current;
 
 		do {
-		    if (World.fuel[pl->fs].fuel > REFUEL_RATE) {
-			World.fuel[pl->fs].fuel -= REFUEL_RATE;
+		    if (World.fuel[pl->fs].fuel > REFUEL_RATE/FPSMultiplier) {
+			World.fuel[pl->fs].fuel -= REFUEL_RATE/FPSMultiplier;
 			World.fuel[pl->fs].conn_mask = 0;
 			World.fuel[pl->fs].last_change = frame_loops;
-			Add_fuel(&(pl->fuel), REFUEL_RATE);
+			Add_fuel(&(pl->fuel), REFUEL_RATE/FPSMultiplier);
 		    } else {
 			Add_fuel(&(pl->fuel), World.fuel[pl->fs].fuel);
 			World.fuel[pl->fs].fuel = 0;
@@ -870,11 +874,11 @@
 		int ct = pl->fuel.current;
 
 		do {
-		    if (pl->fuel.tank[pl->fuel.current] > REFUEL_RATE) {
-			targ->damage += TARGET_FUEL_REPAIR_PER_FRAME;
+		    if (pl->fuel.tank[pl->fuel.current] > REFUEL_RATE/FPSMultiplier) {
+			targ->damage += TARGET_FUEL_REPAIR_PER_FRAME/FPSMultiplier;
 			targ->conn_mask = 0;
 			targ->last_change = frame_loops;
-			Add_fuel(&(pl->fuel), -REFUEL_RATE);
+			Add_fuel(&(pl->fuel), -REFUEL_RATE/FPSMultiplier);
 			if (targ->damage > TARGET_DAMAGE) {
 			    targ->damage = TARGET_DAMAGE;
 			    break;
@@ -915,7 +919,7 @@
 	    }
 	    pl->acc.x = power * tcos(pl->dir) / inert;
 	    pl->acc.y = power * tsin(pl->dir) / inert;
-	    Add_fuel(&(pl->fuel), (long)(-f * FUEL_SCALE_FACT)); /* Decrement fuel */
+	    Add_fuel(&(pl->fuel), (long)(-f * FUEL_SCALE_FACT)/FPSMultiplier); /* Decrement fuel */
 	} else {
 	    pl->acc.x = pl->acc.y = 0.0;
 	}
diff -urN xpilot-4.1.0/src/server/walls.c xpilot-4.U.4tc99b/src/server/walls.c
--- xpilot-4.1.0/src/server/walls.c	Sat Sep  5 04:44:52 1998
+++ xpilot-4.U.4tc99b/src/server/walls.c	Mon Aug 30 00:58:52 1999
@@ -990,8 +990,8 @@
 		    Set_message(msg);
 		    break;
 		}
-		mi->obj->life = 0;
 		if (mi->obj->owner == -1) {
+		    mi->obj->life = 0;
 		    return;
 		}
 		if (World.treasures[ms->treasure].team ==
@@ -1000,10 +1000,14 @@
 		     * 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);
+		    Set_message(msg);
 		    if (Punish_team(GetInd[mi->obj->owner],
 				    mi->obj->treasure, ms->treasure))
 			CLR_BIT(mi->obj->status, RECREATE);
 		}
+		mi->obj->life = 0;
 		return;
 	    }
 	}
@@ -1528,7 +1532,7 @@
     int			killer = -1;
     player		*pl = NULL;
 
-    cannon->dead_time = CANNON_DEAD_TIME;
+    cannon->dead_time = CANNON_DEAD_TIME*FPSMultiplier;
     cannon->conn_mask = 0;
     World.block[cannon->blk_pos.x][cannon->blk_pos.y] = SPACE;
     Cannon_throw_items(ms->cannon);
@@ -1547,7 +1551,7 @@
 	/* min,max debris */ 20, 40,
 	/* min,max dir    */ (int)(cannon->dir - (RES * 0.2)), (int)(cannon->dir + (RES * 0.2)),
 	/* min,max speed  */ 20, 50,
-	/* min,max life   */ 8, 68
+	/* min,max life   */ 8*FPSMultiplier, 68*FPSMultiplier
 	);
     Make_wreckage(
 	/* pos.x, pos.y   */ x, y,
@@ -1561,7 +1565,7 @@
 	/* max wreckage   */ 10,
 	/* min,max dir    */ (int)(cannon->dir - (RES * 0.2)), (int)(cannon->dir + (RES * 0.2)),
 	/* min,max speed  */ 10, 25,
-	/* min,max life   */ 8, 68
+	/* min,max life   */ 8*FPSMultiplier, 68*FPSMultiplier
 	);
 
     if (!ms->mip->pl) {
@@ -1690,7 +1694,7 @@
 	/* min,max debris */ 75, 150,
 	/* min,max dir    */ 0, RES-1,
 	/* min,max speed  */ 20, 70,
-	/* min,max life   */ 10, 100
+	/* min,max life   */ 10*FPSMultiplier, 100*FPSMultiplier
 	);
 
     if (BIT(World.rules->mode, TEAM_PLAY)) {
@@ -1852,9 +1856,9 @@
     dist = walldist[obj->pos.bx][obj->pos.by];
     if (dist > 2) {
 	int max = ((dist - 2) * BLOCK_SZ) >> 1;
-	if (sqr(max) >= sqr(obj->vel.x) + sqr(obj->vel.y)) {
-	    DFLOAT x = obj->pos.cx + FLOAT_TO_CLICK(obj->vel.x);
-	    DFLOAT y = obj->pos.cy + FLOAT_TO_CLICK(obj->vel.y);
+	if (sqr(max*FPSMultiplier) >= sqr(obj->vel.x) + sqr(obj->vel.y)) {
+	    DFLOAT x = obj->pos.cx + FLOAT_TO_CLICK(obj->vel.x/FPSMultiplier);
+	    DFLOAT y = obj->pos.cy + FLOAT_TO_CLICK(obj->vel.y/FPSMultiplier);
 	    x = WRAP_XCLICK(x);
 	    y = WRAP_YCLICK(y);
 	    Object_position_set_clicks(obj, (int)(x), (int)(y));
@@ -1880,8 +1884,8 @@
     ms.pos.x = obj->pos.cx;
     ms.pos.y = obj->pos.cy;
     ms.vel = obj->vel;
-    ms.todo.x = FLOAT_TO_CLICK(ms.vel.x);
-    ms.todo.y = FLOAT_TO_CLICK(ms.vel.y);
+    ms.todo.x = FLOAT_TO_CLICK(ms.vel.x/FPSMultiplier);
+    ms.todo.y = FLOAT_TO_CLICK(ms.vel.y/FPSMultiplier);
     ms.dir = obj->dir;
     ms.mip = &mi;
 
@@ -1893,9 +1897,10 @@
 		break;
 	    }
 	    if (ms.bounce && ms.bounce != BounceEdge) {
-		obj->life = (long)(obj->life * objectWallBounceLifeFactor);
+	        if (obj->type != OBJ_BALL)
+		  obj->life = (long)(obj->life * objectWallBounceLifeFactor);
 		if (obj->life <= 0) {
-		    break;
+		  break;
 		}
 		/*
 		 * Any bouncing sparks are no longer owner immune to give
@@ -2173,9 +2178,9 @@
     dist = walldist[pl->pos.bx][pl->pos.by];
     if (dist > 3) {
 	int max = ((dist - 3) * BLOCK_SZ) >> 1;
-	if (max >= pl->velocity) {
-	    pos.x = pl->pos.cx + FLOAT_TO_CLICK(pl->vel.x);
-	    pos.y = pl->pos.cy + FLOAT_TO_CLICK(pl->vel.y);
+	if (max*FPSMultiplier >= pl->velocity) {
+	    pos.x = pl->pos.cx + FLOAT_TO_CLICK(pl->vel.x/FPSMultiplier);
+	    pos.y = pl->pos.cy + FLOAT_TO_CLICK(pl->vel.y/FPSMultiplier);
 	    pos.x = WRAP_XCLICK(pos.x);
 	    pos.y = WRAP_YCLICK(pos.y);
 	    Player_position_set_clicks(pl, pos.x, pos.y);
@@ -2196,8 +2201,8 @@
     mi.phased = BIT(pl->used, OBJ_PHASING_DEVICE);
 
     vel = pl->vel;
-    todo.x = FLOAT_TO_CLICK(vel.x);
-    todo.y = FLOAT_TO_CLICK(vel.y);
+    todo.x = FLOAT_TO_CLICK(vel.x/FPSMultiplier);
+    todo.y = FLOAT_TO_CLICK(vel.y/FPSMultiplier);
     for (i = 0; i < pl->ship->num_points; i++) {
 	DFLOAT x = pl->ship->pts[i][pl->dir].x;
 	DFLOAT y = pl->ship->pts[i][pl->dir].y;
@@ -2409,7 +2414,7 @@
 			/* min,max debris */ intensity>>1, intensity,
 			/* min,max dir    */ wall_dir - (RES/4), wall_dir + (RES/4),
 			/* min,max speed  */ 20, 20 + (intensity>>2),
-			/* min,max life   */ 10, 10 + (intensity>>1)
+			/* min,max life   */ 10*FPSMultiplier, (10 + (intensity>>1))*FPSMultiplier
 			);
 		    sound_play_sensors(pl->pos.x, pl->pos.y,
 				       PLAYER_BOUNCED_SOUND);
