diff -c -r ../xpilot-4.1.0/Local.config ./Local.config
*** ../xpilot-4.1.0/Local.config	Fri Oct 30 14:35:18 1998
--- ./Local.config	Sun Mar 21 23:21:31 1999
***************
*** 172,182 ****
   * or contains the word "beta" or "alpha".
   */
        VERSION_MAJOR = 4
!       VERSION_MINOR = 1
!  VERSION_PATCHLEVEL = 0
      VERSION_WINDOWS = 13
!      VERSION_STATUS = 
!         RELEASEDATE = Oct 30th, 1998
  
  
  /*
--- 172,182 ----
   * or contains the word "beta" or "alpha".
   */
        VERSION_MAJOR = 4
!       VERSION_MINOR = U
!  VERSION_PATCHLEVEL = 3
      VERSION_WINDOWS = 13
!      VERSION_STATUS =
!         RELEASEDATE = Mar 21th, 1999
  
  
  /*
diff -c -r ../xpilot-4.1.0/src/common/version.h ./src/common/version.h
*** ../xpilot-4.1.0/src/common/version.h	Fri Oct 30 14:36:46 1998
--- ./src/common/version.h	Sun Mar 21 23:22:07 1999
***************
*** 28,42 ****
  #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"
  #endif
  
! #define VERSION			"4.1.0"
  #ifdef	_WINDOWS
! #define	TITLE			"4.1.0-NT13"
  #define	VERSION_WINDOWS	"13"
  #else
! #define TITLE			"XPilot 4.1.0"
  #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"
--- 28,42 ----
  #if defined(__hpux)
  #   pragma COPYRIGHT_DATE	"1991-1998"
  #   pragma COPYRIGHT		"Bjørn Stabell, Ken Ronny Schouten, Bert Gijsbers & Dick Balaska"
! #   pragma VERSIONID		"XPilot 4.U.3"
  #endif
  
! #define VERSION			"4.U.3"
  #ifdef	_WINDOWS
! #define	TITLE			"4.U.3-NT13"
  #define	VERSION_WINDOWS	"13"
  #else
! #define TITLE			"XPilot 4.U.3"
  #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 -c -r ../xpilot-4.1.0/src/server/cannon.c ./src/server/cannon.c
*** ../xpilot-4.1.0/src/server/cannon.c	Sat Sep  5 05:44:51 1998
--- ./src/server/cannon.c	Fri Feb 19 22:54:17 1999
***************
*** 136,142 ****
  		obj->acc.x = 0;
  		obj->acc.y = 0;
  		obj->mass = 10;
! 		obj->life = 1500 + (rand() & 511);
  		obj->count = amount;
  		obj->pl_range = ITEM_SIZE / 2;
  		obj->pl_radius = ITEM_SIZE / 2;
--- 136,142 ----
  		obj->acc.x = 0;
  		obj->acc.y = 0;
  		obj->mass = 10;
! 		obj->life = (1500 + (rand() & 511))*FPSMultiplier;
  		obj->count = amount;
  		obj->pl_range = ITEM_SIZE / 2;
  		obj->pl_radius = ITEM_SIZE / 2;
***************
*** 458,464 ****
  	/* 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);
  	IFSOUND(sound = -1;)
  	break;
      case CW_TRANSPORTER:
--- 458,464 ----
  	/* 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))*FPSMultiplier;
  	IFSOUND(sound = -1;)
  	break;
      case CW_TRANSPORTER:
***************
*** 492,498 ****
  		/* dir */	dir - 4 * (4 - cannonSmartness),
  				dir + 4 * (4 - cannonSmartness),
  		/* speed */	0.1, speed * 4,
! 		/* life */	3, 20);
  	    c->item[ITEM_EMERGENCY_THRUST]--;
  	} else {
  	    Make_debris(
--- 492,498 ----
  		/* dir */	dir - 4 * (4 - cannonSmartness),
  				dir + 4 * (4 - cannonSmartness),
  		/* speed */	0.1, speed * 4,
! 		/* life */	3*FPSMultiplier, 20*FPSMultiplier);
  	    c->item[ITEM_EMERGENCY_THRUST]--;
  	} else {
  	    Make_debris(
***************
*** 509,515 ****
  		/* dir */	dir - 3 * (4 - cannonSmartness),
  				dir + 3 * (4 - cannonSmartness),
  		/* speed */	0.1, speed * 2,
! 		/* life */	3, 20);
  	}
  	c->item[ITEM_FUEL]--;
  	IFSOUND(sound = THRUST_SOUND;)
--- 509,515 ----
  		/* dir */	dir - 3 * (4 - cannonSmartness),
  				dir + 3 * (4 - cannonSmartness),
  		/* speed */	0.1, speed * 2,
! 		/* life */	3*FPSMultiplier, 20*FPSMultiplier);
  	}
  	c->item[ITEM_FUEL]--;
  	IFSOUND(sound = THRUST_SOUND;)
diff -c -r ../xpilot-4.1.0/src/server/cmdline.c ./src/server/cmdline.c
*** ../xpilot-4.1.0/src/server/cmdline.c	Tue Sep  1 21:08:40 1998
--- ./src/server/cmdline.c	Wed Mar 10 00:23:07 1999
***************
*** 232,237 ****
--- 232,244 ----
  
  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 */
+ 
  const char default_map[] = DEFAULT_MAP;
  
  
***************
*** 2272,2277 ****
--- 2279,2338 ----
  	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"
+     }
  };
  
  
***************
*** 2530,2535 ****
--- 2591,2598 ----
      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 -c -r ../xpilot-4.1.0/src/server/collision.c ./src/server/collision.c
*** ../xpilot-4.1.0/src/server/collision.c	Fri Oct 30 12:49:29 1998
--- ./src/server/collision.c	Fri Mar 19 16:07:53 1999
***************
*** 655,660 ****
--- 655,662 ----
  		       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,681 ****
  	     * We want a separate list of balls to avoid searching
  	     * the object list for balls.
  	     */
  	    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) {
  			object *ball = Obj[j];
  			int bteam = -1;
  
--- 673,685 ----
  	     * 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 ((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,700 ****
  			    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
  			 * taking and hiding with the ball... this was
  			 * considered bad gamesmanship.
  			 */
  			if (ball->owner != -1
  			    || (   pl->team != TEAM_NOT_SET
! 				&& pl->team != bteam))
  			    pl->ball = Obj[j];
! 			break;
  		    }
  		}
  	    }
--- 687,704 ----
  			    bteam = World.treasures[ball->treasure].team;
  
  			/*
! 			 * 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->ball = Obj[j];
! 			    mindist=dist;
! 			}
  		    }
  		}
  	    }
***************
*** 1472,1479 ****
  		obj = Obj[objnum];
  	    }
  
! 	    pulse->pos.x += tcos(pulse->dir) * PULSE_SPEED;
! 	    pulse->pos.y += tsin(pulse->dir) * PULSE_SPEED;
  	    if (BIT(World.rules->mode, WRAP_PLAY)) {
  		if (pulse->pos.x < 0) {
  		    pulse->pos.x += World.width;
--- 1476,1483 ----
  		obj = Obj[objnum];
  	    }
  
! 	    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,1594 ****
  	    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;
  		Move_object(objnum);
  		if (obj->life == 0) {
  		    break;
--- 1588,1601 ----
  	    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 - 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
! 		   change 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 -c -r ../xpilot-4.1.0/src/server/event.c ./src/server/event.c
*** ../xpilot-4.1.0/src/server/event.c	Fri Oct  2 21:39:26 1998
--- ./src/server/event.c	Wed Mar 17 19:31:01 1999
***************
*** 192,198 ****
      for (i = 0; i < NumPlayers; i++) {
  	if (i == lock
  	    || (BIT(Players[i]->status, PLAYING|PAUSE|GAME_OVER) != PLAYING)
! 	    || !Player_lock_allowed(ind, i)) {
  	    continue;
  	}
  	l = Wrap_length(Players[i]->pos.x - pl->pos.x,
--- 192,199 ----
      for (i = 0; i < NumPlayers; i++) {
  	if (i == lock
  	    || (BIT(Players[i]->status, PLAYING|PAUSE|GAME_OVER) != PLAYING)
! 	    || !Player_lock_allowed(ind, i)
! 	    || lockOtherTeam && TEAM(ind,i)) {
  	    continue;
  	}
  	l = Wrap_length(Players[i]->pos.x - pl->pos.x,
***************
*** 219,224 ****
--- 220,226 ----
      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);
***************
*** 715,721 ****
  			*l = pl->lock.pl_id;
  		    }
  		} else {
! 		    if (Player_lock_allowed(ind, *l)) {
  			pl->lock.pl_id = *l;
  			SET_BIT(pl->lock.tagged, LOCK_PLAYER);
  		    }
--- 717,723 ----
  			*l = pl->lock.pl_id;
  		    }
  		} else {
! 		    if (Player_lock_allowed(ind, GetInd[*l])) {
  			pl->lock.pl_id = *l;
  			SET_BIT(pl->lock.tagged, LOCK_PLAYER);
  		    }
***************
*** 766,772 ****
  	    case KEY_SELF_DESTRUCT:
  		TOGGLE_BIT(pl->status, SELF_DESTRUCT);
  		if (BIT(pl->status, SELF_DESTRUCT))
! 		    pl->count = 150;
  		break;
  
  	    case KEY_PAUSE:
--- 768,774 ----
  	    case KEY_SELF_DESTRUCT:
  		TOGGLE_BIT(pl->status, SELF_DESTRUCT);
  		if (BIT(pl->status, SELF_DESTRUCT))
! 		    pl->count = 150*FPSMultiplier;
  		break;
  
  	    case KEY_PAUSE:
diff -c -r ../xpilot-4.1.0/src/server/frame.c ./src/server/frame.c
*** ../xpilot-4.1.0/src/server/frame.c	Sat Sep  5 05:44:52 1998
--- ./src/server/frame.c	Tue Jan 26 18:45:29 1999
***************
*** 341,347 ****
  			pl->emergency_shield_left,
  			pl->emergency_shield_max);
      if (BIT(pl->status, SELF_DESTRUCT) && pl->count > 0) {
! 	Send_destruct(conn, pl->count);
      }
      if (BIT(pl->used, OBJ_PHASING_DEVICE))
  	Send_phasingtime(conn,
--- 341,347 ----
  			pl->emergency_shield_left,
  			pl->emergency_shield_max);
      if (BIT(pl->status, SELF_DESTRUCT) && pl->count > 0) {
! 	Send_destruct(conn, pl->count/FPSMultiplier);
      }
      if (BIT(pl->used, OBJ_PHASING_DEVICE))
  	Send_phasingtime(conn,
***************
*** 462,476 ****
  	    if (debris_colors >= 3) {
  		if (debris_colors > 4) {
  		    if (color == BLUE) {
! 			color = (shot->life >> 1);
  		    } else {
! 			color = (shot->life >> 2);
  		    }
  		} else {
  		    if (color == BLUE) {
! 			color = (shot->life >> 2);
  		    } else {
! 			color = (shot->life >> 3);
  		    }
  		}
  		if (color >= debris_colors) {
--- 462,476 ----
  	    if (debris_colors >= 3) {
  		if (debris_colors > 4) {
  		    if (color == BLUE) {
! 			color = (shot->life/FPSMultiplier >> 1);
  		    } else {
! 			color = (shot->life/FPSMultiplier >> 2);
  		    }
  		} else {
  		    if (color == BLUE) {
! 			color = (shot->life/FPSMultiplier >> 2);
  		    } else {
! 			color = (shot->life/FPSMultiplier >> 3);
  		    }
  		}
  		if (color >= debris_colors) {
diff -c -r ../xpilot-4.1.0/src/server/global.h ./src/server/global.h
*** ../xpilot-4.1.0/src/server/global.h	Tue Sep  1 21:08:43 1998
--- ./src/server/global.h	Wed Mar 10 00:18:30 1999
***************
*** 51,56 ****
--- 51,57 ----
   */
  #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,63 ****
--- 59,65 ----
  extern trans_t		*Transporters[];
  extern long		frame_loops;
  extern int		NumPlayers;
+ extern int              NumOperators;
  extern int		NumPseudoPlayers;
  extern int		NumQueuedPlayers;
  extern int		NumObjs;
***************
*** 243,248 ****
--- 245,256 ----
  extern int		maxRoundTime;
  extern int		roundtime;
  
+ extern int              FPSMultiplier;
+ extern bool             useWreckage;
+ extern bool             ignore20MaxFPS;
+ extern int              timerResolution;
+ extern char             *password;
+ extern int              numberOfRounds;
  #endif
  
  #endif /* GLOBAL_H */
diff -c -r ../xpilot-4.1.0/src/server/netserver.c ./src/server/netserver.c
*** ../xpilot-4.1.0/src/server/netserver.c	Sun Aug 30 18:40:50 1998
--- ./src/server/netserver.c	Sun Mar 21 23:23:19 1999
***************
*** 2390,2395 ****
--- 2390,2430 ----
      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,2472 ****
  	}
      }
      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;
! 	    }
! 	}
  	switch (sent) {
  	case -2:
  	    sprintf(msg, "Message not sent, %s matches more than one player!",
--- 2486,2492 ----
  	}
      }
      else {						/* Player message */
!         sent=Ind_by_name(str);
  	switch (sent) {
  	case -2:
  	    sprintf(msg, "Message not sent, %s matches more than one player!",
***************
*** 2489,2494 ****
--- 2509,2906 ----
      }
  }
  
+ 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;
+ 
+ enum Command {
+   KICK_CMD, VERSION_CMD, HELP_CMD, RESET_CMD, TEAM_CMD,
+   PASSWORD_CMD, LOCK_CMD, SET_CMD, PAUSE_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",
+     "Prints 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>. Removes player from game. (operator)",
+     1,
+     KICK_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 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.1.0 + patch 4.U.3");
+       break;
+       
+     case HELP_CMD:
+       if (!args)
+ 	sprintf(msg,"Commands: help team version lock password pause "\
+ 		"reset set kick");
+       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);
+ 	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,2517 ****
  	    return n;
  	}
  	connp->talk_sequence_num = seq;
! 	Handle_talk (ind, str);
      }
      return 1;
  }
--- 2923,2932 ----
  	    return n;
  	}
  	connp->talk_sequence_num = seq;
! 	if (*str=='/')
! 	  Handle_command(ind,str+1);
! 	else
! 	  Handle_talk (ind, str);
      }
      return 1;
  }
***************
*** 2855,2861 ****
      else {
  	turndir = 1.0;
      }
!     LIMIT(turnspeed, MIN_PLAYER_TURNSPEED, MAX_PLAYER_TURNSPEED);
      pl->turnvel -= turndir * turnspeed;
  
      return 1;
--- 3270,3289 ----
      else {
  	turndir = 1.0;
      }
! 
!     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;
***************
*** 2881,2886 ****
--- 3309,3315 ----
  	if (fps > FPS) pl->player_fps = FPS;
  	if (fps < (FPS / 2)) pl->player_fps = FPS / 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 -c -r ../xpilot-4.1.0/src/server/object.h ./src/server/object.h
*** ../xpilot-4.1.0/src/server/object.h	Sat Aug 29 22:49:55 1998
--- ./src/server/object.h	Mon Feb 15 17:41:00 1999
***************
*** 420,425 ****
--- 420,427 ----
  
      int		isowner;		/* If player started this server. */
  
+     int         isoperator;             /* Player has operator privileges */
+ 
  #ifdef __cplusplus
  		player() {}
  #endif
diff -c -r ../xpilot-4.1.0/src/server/play.c ./src/server/play.c
*** ../xpilot-4.1.0/src/server/play.c	Fri Oct  2 21:39:27 1998
--- ./src/server/play.c	Fri Mar 19 16:10:15 1999
***************
*** 94,101 ****
  		    ? MAX_AFTERBURNER
  		    : pl->item[ITEM_AFTERBURNER]);
      alt_sparks = afterburners
! 		    ? AFTER_BURN_SPARKS(tot_sparks-1, afterburners) + 1
  		    : 0;
  
      Make_debris(
  	/* pos.x, pos.y   */ x, y,
--- 94,102 ----
  		    ? MAX_AFTERBURNER
  		    : pl->item[ITEM_AFTERBURNER]);
      alt_sparks = afterburners
! 		    ? AFTER_BURN_SPARKS(tot_sparks-1, afterburners)/FPSMultiplier + 1
  		    : 0;
+     tot_sparks/=FPSMultiplier;
  
      Make_debris(
  	/* pos.x, pos.y   */ x, y,
***************
*** 110,116 ****
  	/* 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
  	);
  
      Make_debris(
--- 111,117 ----
  	/* 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*FPSMultiplier, max_life*FPSMultiplier
  	);
  
      Make_debris(
***************
*** 126,132 ****
  	/* 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
  	);
  }
  
--- 127,133 ----
  	/* 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*FPSMultiplier, max_life*FPSMultiplier
  	);
  }
  
***************
*** 537,543 ****
      obj->acc.x =
      obj->acc.y = 0.0;
      obj->mass = 10.0;
!     obj->life = 1500 + (rand()&511);
      obj->count = num_per_pack;
      obj->pl_range = ITEM_SIZE/2;
      obj->pl_radius = ITEM_SIZE/2;
--- 538,544 ----
      obj->acc.x =
      obj->acc.y = 0.0;
      obj->mass = 10.0;
!     obj->life = (1500 + (rand()&511))*FPSMultiplier;
      obj->count = num_per_pack;
      obj->pl_range = ITEM_SIZE/2;
      obj->pl_radius = ITEM_SIZE/2;
***************
*** 668,674 ****
  	return;
      }
      percent = TRACTOR_PERCENT(pl->lock.distance, maxdist);
!     cost = (long)TRACTOR_COST(percent);
      if (pl->fuel.sum < -cost) {
  	CLR_BIT(pl->used, OBJ_TRACTOR_BEAM);
  	return;
--- 669,675 ----
  	return;
      }
      percent = TRACTOR_PERCENT(pl->lock.distance, maxdist);
!     cost = (long)TRACTOR_COST(percent)/FPSMultiplier;
      if (pl->fuel.sum < -cost) {
  	CLR_BIT(pl->used, OBJ_TRACTOR_BEAM);
  	return;
***************
*** 693,699 ****
      if (dist > maxdist)
  	return;
      percent = TRACTOR_PERCENT(dist, maxdist);
!     cost = (long)TRACTOR_COST(percent);
      force = TRACTOR_FORCE(pressor, percent, maxforce);
      
      sound_play_sensors(x, y,
--- 694,700 ----
      if (dist > maxdist)
  	return;
      percent = TRACTOR_PERCENT(dist, maxdist);
!     cost = (long)TRACTOR_COST(percent)/FPSMultiplier;
      force = TRACTOR_FORCE(pressor, percent, maxforce);
      
      sound_play_sensors(x, y,
***************
*** 705,719 ****
      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);
  	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);
  }
- 
  void Place_mine(int ind)
  {
      player *pl = Players[ind];
--- 706,719 ----
      theta = (int)Wrap_findDir(x - victim->pos.x, y - victim->pos.y);
  
      if (pl) {
! 	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)/FPSMultiplier;
!     victim->vel.y -= tsin(theta) * (force / victim->mass)/FPSMultiplier;
  }
  void Place_mine(int ind)
  {
      player *pl = Players[ind];
***************
*** 879,885 ****
  	     * This causes the added initial velocity to reduce to
  	     * zero over the MINI_MINE_SPREAD_TIME.
  	     */
! 	    mine->spread_left = MINI_MINE_SPREAD_TIME;
  	    mine->acc.x = -mv.x / (MINI_MINE_SPREAD_TIME+1);
  	    mine->acc.y = -mv.y / (MINI_MINE_SPREAD_TIME+1);
  	} else {
--- 879,885 ----
  	     * This causes the added initial velocity to reduce to
  	     * zero over the 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,1035 ****
  	    }
  	}
      }
-     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);
  
      td->destroyed++;
      World.teams[td->team].TreasuresLeft--;
      World.teams[tt->team].TreasuresDestroyed++;
--- 1019,1036 ----
  	    }
  	}
      }
  
      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,1898 ****
  		    /* min,max debris */ 75, 150,
  		    /* min,max dir    */ 0, RES-1,
  		    /* min,max speed  */ 20, 95,
! 		    /* min,max life   */ 10, 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));
  	    }
  
  	}
--- 1887,1899 ----
  		    /* min,max debris */ 75, 150,
  		    /* min,max dir    */ 0, RES-1,
  		    /* min,max speed  */ 20, 95,
! 		    /* 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*FPSMultiplier, 2*(FPS+15));
  	    }
  
  	}
***************
*** 2005,2012 ****
  	    /* min,max speed  */ 20 * speed_modv,
  				 (intensity >> 2) * speed_modv,
  #endif
! 	    /* min,max life   */ (int)(8 * life_modv),
! 				 (int)((intensity >> 1) * life_modv)
  	    );
  	break;
  
--- 2006,2013 ----
  	    /* min,max speed  */ 20 * speed_modv,
  				 (intensity >> 2) * speed_modv,
  #endif
! 	    /* min,max life   */ (int)(8 * life_modv)*FPSMultiplier,
! 				 (int)((intensity >> 1) * life_modv)*FPSMultiplier
  	    );
  	break;
  
***************
*** 2102,2109 ****
  	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 = WRAP_XPIXEL(x);
  	    y = WRAP_YPIXEL(y);
  	    if (x >= 0 && x < World.width && y >= 0 && y < World.height) {
--- 2103,2110 ----
  	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/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,2143 ****
      pulse->team = team;
      pulse->dir = dir;
      pulse->len = PULSE_LENGTH;
!     pulse->life = life;
      pulse->mods = mods;
      pulse->refl = false;
!     pulse->pos.x = x - PULSE_SPEED * tcos(dir);
!     pulse->pos.y = y - PULSE_SPEED * tsin(dir);
      NumPulses++;
      if (pl)
  	pl->num_pulses++;
--- 2134,2144 ----
      pulse->team = team;
      pulse->dir = dir;
      pulse->len = PULSE_LENGTH;
!     pulse->life = (life-1)*FPSMultiplier+1; 
      pulse->mods = mods;
      pulse->refl = false;
!     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,2162 ****
      int		i;
      long	dist, dx, dy;
  
!     if (pl->fuel.sum < -ED_DEFLECTOR) {
  	CLR_BIT(pl->used, OBJ_DEFLECTOR);
  	return;
      }
!     Add_fuel(&(pl->fuel), (long)ED_DEFLECTOR);
  
      for (i = 0; i < NumObjs; i++) {
  	obj = Obj[i];
--- 2153,2163 ----
      int		i;
      long	dist, dx, dy;
  
!     if (pl->fuel.sum < -ED_DEFLECTOR/FPSMultiplier) {
  	CLR_BIT(pl->used, OBJ_DEFLECTOR);
  	return;
      }
!     Add_fuel(&(pl->fuel), (long)ED_DEFLECTOR/FPSMultiplier);
  
      for (i = 0; i < NumObjs; i++) {
  	obj = Obj[i];
***************
*** 2207,2214 ****
  				/ (RES * 0.25);
  		DFLOAT dv = force / ABS(obj->mass);
  
! 		obj->vel.x += tcos(dir) * dv;
! 		obj->vel.y += tsin(dir) * dv;
  	    }
  	}
      }
--- 2208,2215 ----
  				/ (RES * 0.25);
  		DFLOAT dv = force / ABS(obj->mass);
  
! 		obj->vel.x += tcos(dir) * dv/FPSMultiplier;
! 		obj->vel.y += tsin(dir) * dv/FPSMultiplier;
  	    }
  	}
      }
***************
*** 2601,2607 ****
  	     */
  	    SET_BIT(shot->status, CONFUSED);
  	    shot->ecm_range = range;
! 	    shot->count = CONFUSED_TIME;
  	    if (pl
  		&& BIT(pl->lock.tagged, LOCK_PLAYER)
  		&& (pl->lock.distance <= pl->sensor_range
--- 2602,2608 ----
  	     */
  	    SET_BIT(shot->status, CONFUSED);
  	    shot->ecm_range = range;
! 	    shot->count = CONFUSED_TIME*FPSMultiplier;
  	    if (pl
  		&& BIT(pl->lock.tagged, LOCK_PLAYER)
  		&& (pl->lock.distance <= pl->sensor_range
***************
*** 2752,2758 ****
  
  	    if (!IS_ROBOT_PTR(p) || !ecmsReprogramRobots || !pl) {
  		/* player is blinded by light flashes. */
! 		long duration = (int)(damage * pow(0.75, p->item[ITEM_SENSOR]));
  		p->damaged += duration;
  		if (pl)
  		    Record_shove(p, pl, frame_loops + duration);
--- 2753,2759 ----
  
  	    if (!IS_ROBOT_PTR(p) || !ecmsReprogramRobots || !pl) {
  		/* player is blinded by light flashes. */
! 		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,2934 ****
  
      /* 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;
  
      /* 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;
  
  #endif	/* ORIGINAL_BALL */
  }
--- 2923,2935 ----
  
      /* compute accelleration for player, assume t = 1 */
      accell = (force + pl_damping + ball_damping) / pl->mass;
!     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/FPSMultiplier;
!     ball->vel.y += -D.y * accell/FPSMultiplier;
  
  #endif	/* ORIGINAL_BALL */
  }
***************
*** 2956,2963 ****
  	    shot->acc.x = 0;
  	    shot->acc.y = 0;
  	}
! 	shot->vel.x += acc * tcos(shot->dir);
! 	shot->vel.y += acc * tsin(shot->dir);
  	return;
      }
  
--- 2957,2964 ----
  	    shot->acc.x = 0;
  	    shot->acc.y = 0;
  	}
! 	shot->vel.x += acc * tcos(shot->dir)/FPSMultiplier;
! 	shot->vel.y += acc * tsin(shot->dir)/FPSMultiplier;
  	return;
      }
  
***************
*** 2975,2981 ****
  	} else {
  	    /* No player. Number of moves so that new target is searched */
  	    pl = 0;
! 	    shot->count = HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR;
  	}
  	if (pl && BIT(pl->status, THRUSTING)) {
  	    /*
--- 2976,2982 ----
  	} else {
  	    /* No player. Number of moves so that new target is searched */
  	    pl = 0;
! 	    shot->count = (HEAT_WIDE_TIMEOUT + HEAT_WIDE_ERROR)*FPSMultiplier;
  	}
  	if (pl && BIT(pl->status, THRUSTING)) {
  	    /*
***************
*** 2983,3006 ****
  	     * set number to moves to correct error value
  	     */
  	    if (range < HEAT_CLOSE_RANGE) {
! 		shot->count = HEAT_CLOSE_ERROR;
  	    } else if (range < HEAT_MID_RANGE) {
! 		shot->count = HEAT_MID_ERROR;
  	    } else {
! 		shot->count = HEAT_WIDE_ERROR;
  	    }
  	} else {
  	    shot->count++;
  	    /* Look for new target */
  	    if ((range < HEAT_CLOSE_RANGE
! 		 && shot->count > 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) {
  		DFLOAT l;
  		int i;
  
! 		range = HEAT_RANGE * (shot->count/HEAT_CLOSE_TIMEOUT);
  		for (i=0; i<NumPlayers; i++) {
  		    player *p = Players[i];
  
--- 2984,3007 ----
  	     * set number to moves to correct error value
  	     */
  	    if (range < HEAT_CLOSE_RANGE) {
! 		shot->count = HEAT_CLOSE_ERROR*FPSMultiplier;
  	    } else if (range < HEAT_MID_RANGE) {
! 		shot->count = HEAT_MID_ERROR*FPSMultiplier;
  	    } else {
! 		shot->count = HEAT_WIDE_ERROR*FPSMultiplier;
  	    }
  	} else {
  	    shot->count++;
  	    /* Look for new target */
  	    if ((range < HEAT_CLOSE_RANGE
! 		 && shot->count/FPSMultiplier > HEAT_CLOSE_TIMEOUT + HEAT_CLOSE_ERROR)
  		|| (range < HEAT_MID_RANGE
! 		    && 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/FPSMultiplier/HEAT_CLOSE_TIMEOUT);
  		for (i=0; i<NumPlayers; i++) {
  		    player *p = Players[i];
  
***************
*** 3026,3031 ****
--- 3027,3033 ----
  			    l < HEAT_CLOSE_RANGE ?
  				HEAT_CLOSE_ERROR : l < HEAT_MID_RANGE ?
  				    HEAT_MID_ERROR : HEAT_WIDE_ERROR;
+ 			shot->count *= FPSMultiplier;
  			pl = p;
  		    }
  		}
***************
*** 3037,3050 ****
  	 * 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;
  
      } else {
  
  	if (BIT(shot->status, CONFUSED)
! 	    && (!(frame_loops % CONFUSED_UPDATE_GRANULARITY)
! 		|| shot->count == CONFUSED_TIME)) {
  
  	    if (shot->count) {
  		shot->info = Players[rand() % NumPlayers]->id;
--- 3039,3052 ----
  	 * 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/FPSMultiplier;
! 	y_dif = (rand()&3) * shot->count/FPSMultiplier;
  
      } else {
  
  	if (BIT(shot->status, CONFUSED)
! 	    && (!(frame_loops % (CONFUSED_UPDATE_GRANULARITY*FPSMultiplier))
! 		|| shot->count == CONFUSED_TIME*FPSMultiplier)) {
  
  	    if (shot->count) {
  		shot->info = Players[rand() % NumPlayers]->id;
***************
*** 3121,3127 ****
  	    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);
  		}
  		foundw = 1;
  	    }
--- 3123,3129 ----
  	    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)/FPSMultiplier;
  		}
  		foundw = 1;
  	    }
***************
*** 3180,3186 ****
  	    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;
  	    }
  #endif
  	}
--- 3182,3188 ----
  	    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)/FPSMultiplier;
  	    }
  #endif
  	}
***************
*** 3196,3209 ****
      angle = angle - shot->dir - RES/2;
  
      if (angle < 0)
! 	shot->dir += (u_byte)(((-angle < shot->turnspeed) ? -angle : shot->turnspeed));
      else
! 	shot->dir -= (u_byte)(((angle < shot->turnspeed) ? angle : shot->turnspeed));
  
      shot->dir = MOD2(shot->dir, RES); /* NOTE!!!! */
  
      if (shot_speed < shot->max_speed)
! 	shot_speed += acc;
  
      /*  shot->velocity = MIN(shot->velocity, shot->max_speed);  */
  
--- 3198,3211 ----
      angle = angle - shot->dir - RES/2;
  
      if (angle < 0)
! 	shot->dir += (u_byte)(((-angle < shot->turnspeed/FPSMultiplier) ? -angle : shot->turnspeed/FPSMultiplier));
      else
! 	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/FPSMultiplier;
  
      /*  shot->velocity = MIN(shot->velocity, shot->max_speed);  */
  
***************
*** 3286,3299 ****
  	    if (!fuel) {
  		if (t
  		    && t != ft->current
! 		    && *f >= low_level + REFUEL_RATE
! 		    && *(f-1) <= TANK_CAP(t-1) - REFUEL_RATE) {
  
! 		    *f -= REFUEL_RATE;
! 		    fuel = REFUEL_RATE;
  		} else if (t && *f < low_level) {
! 		    *f += REFUEL_RATE;
! 		    fuel = -REFUEL_RATE;
  		}
  	    }
  	    if (fuel && t == 0) {
--- 3288,3301 ----
  	    if (!fuel) {
  		if (t
  		    && t != ft->current
! 		    && *f >= low_level + REFUEL_RATE/FPSMultiplier
! 		    && *(f-1) <= TANK_CAP(t-1) - REFUEL_RATE/FPSMultiplier) {
  
! 		    *f -= REFUEL_RATE/FPSMultiplier;
! 		    fuel = REFUEL_RATE/FPSMultiplier;
  		} else if (t && *f < low_level) {
! 		    *f += REFUEL_RATE/FPSMultiplier;
! 		    fuel = -REFUEL_RATE/FPSMultiplier;
  		}
  	    }
  	    if (fuel && t == 0) {
***************
*** 3520,3529 ****
  	    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 (max_speed < min_speed)
  	max_speed = min_speed;
  
--- 3522,3531 ----
  	    max_life = ShotsLife;
  	}
      }
!     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,3567 ****
  	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);
  	}
  	debris->life = life;
  	debris->fuselife = life;
--- 3562,3569 ----
  	debris->mass = mass;
  	debris->type = type;
  	life = (int)(min_life + rfrac() * (max_life - min_life) + 1);
! 	if (life * speed / FPSMultiplier > World.hypotenuse) {
! 	    life = (long)(World.hypotenuse * FPSMultiplier / speed);
  	}
  	debris->life = life;
  	debris->fuselife = life;
***************
*** 3593,3598 ****
--- 3595,3602 ----
      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,3720 ****
  	/* 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))
  	);
  
      if ( !BIT(pl->status, KILLED) )
--- 3718,3724 ----
  	/* 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*FPSMultiplier, (int)(5 + (pl->mass * 1.5))*FPSMultiplier
  	);
  
      if ( !BIT(pl->status, KILLED) )
***************
*** 3731,3737 ****
  	/* 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))
  	);
  
  }
--- 3735,3741 ----
  	/* max wreckage     */ 10,
  	/* min,max dir      */ 0, RES-1,
  	/* min,max speed    */ 10.0, 10 + (((int)(pl->mass))>>1),
! 	/* min,max life     */ 5*FPSMultiplier, (int)(5 + (pl->mass * 1.5))*FPSMultiplier
  	);
  
  }
diff -c -r ../xpilot-4.1.0/src/server/player.c ./src/server/player.c
*** ../xpilot-4.1.0/src/server/player.c	Wed Sep 23 22:42:49 1998
--- ./src/server/player.c	Wed Mar 10 00:37:22 1999
***************
*** 490,495 ****
--- 490,497 ----
  
      pl->isowner = 0;
  
+     pl->isoperator = 0;
+ 
      return pl->id;
  }
  
***************
*** 583,589 ****
  }
  
  
! static void Reset_all_players(void)
  {
      player		*pl;
      int			i, j;
--- 585,591 ----
  }
  
  
! void Reset_all_players(void)
  {
      player		*pl;
      int			i, j;
***************
*** 874,879 ****
--- 876,898 ----
  	  "[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++;
+   return;
+ }
+ 
  void Team_game_over(int winning_team, const char *reason)
  {
      int			i, j;
***************
*** 939,944 ****
--- 958,965 ----
  
      Reset_all_players();
  
+     Count_rounds();
+ 
      free(best_players);
  }
  
***************
*** 1010,1015 ****
--- 1031,1038 ----
  
      Reset_all_players();
  
+     Count_rounds();
+ 
      free(best_players);
  }
  
***************
*** 1135,1140 ****
--- 1158,1165 ----
  		    "ashamed of yourselves.");
      }
  
+     Count_rounds();
+ 
      Reset_all_players();
  }
  
***************
*** 1577,1582 ****
--- 1602,1609 ----
      }
  }
  
+ extern int game_lock;
+ 
  void Delete_player(int ind)
  {
      player		*pl = Players[ind];
***************
*** 1587,1592 ****
--- 1614,1631 ----
      if (IS_ROBOT_PTR(pl)) {
  	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--) {
diff -c -r ../xpilot-4.1.0/src/server/sched.c ./src/server/sched.c
*** ../xpilot-4.1.0/src/server/sched.c	Wed Apr 22 16:56:43 1998
--- ./src/server/sched.c	Tue Jan 26 18:45:29 1999
***************
*** 126,131 ****
--- 126,133 ----
   */
  static void catch_timer(int signum)
  {
+     static unsigned int count;
+ 
  #ifdef OS2DEBUG
      static int counter = 0;
  
***************
*** 137,143 ****
  	fflush( stdout );
      }
  #endif
!     timer_ticks++;
  }
  
  #ifdef _OS2_
--- 139,150 ----
  	fflush( stdout );
      }
  #endif
! 
!     count+=FPS;
!     if (count>=timerResolution) {
!       count-=timerResolution;
!       timer_ticks++;
!     }
  }
  
  #ifdef _OS2_
diff -c -r ../xpilot-4.1.0/src/server/server.c ./src/server/server.c
*** ../xpilot-4.1.0/src/server/server.c	Sat Aug 29 22:49:57 1998
--- ./src/server/server.c	Wed Mar 10 00:27:07 1999
***************
*** 82,87 ****
--- 82,88 ----
   * Global variables
   */
  int			NumPlayers = 0;
+ int                     NumOperators = 0;
  int			NumObjs = 0;
  int			NumPulses = 0;
  int			NumEcms = 0;
***************
*** 97,102 ****
--- 98,104 ----
  char			ShutdownReason[MAX_CHARS];
  int 			framesPerSecond = 18;
  long			main_loops = 0;		/* needed in events.c */
+ int                     roundCounter = 1;
  
  static int		serverSocket;
  #ifdef LOG
***************
*** 205,213 ****
  
  #ifdef	_WINDOWS
      /* Windows returns here, we let the worker thread call sched() */
!     install_timer_tick(ServerThreadTimerProc, FPS);
  #else
!     install_timer_tick(Main_loop, FPS);
  
      sched();
      xpprintf("sched returned!?");
--- 207,215 ----
  
  #ifdef	_WINDOWS
      /* Windows returns here, we let the worker thread call sched() */
!     install_timer_tick(ServerThreadTimerProc, timerResolution?timerResolution:FPS);
  #else
!     install_timer_tick(Main_loop, timerResolution?timerResolution:FPS);
  
      sched();
      xpprintf("sched returned!?");
***************
*** 619,625 ****
  void Game_Over(void)
  {
      long		maxsc, minsc;
!     int			i, win, loose;
      char		msg[128];
  
      Set_message("Game over...");
--- 621,627 ----
  void Game_Over(void)
  {
      long		maxsc, minsc;
!     int			i, win, lose;
      char		msg[128];
  
      Set_message("Game over...");
***************
*** 633,639 ****
  	int teamscore[MAX_TEAMS];
  	maxsc = -32767;
  	minsc = 32767;
! 	win = loose = -1;
  
  	for (i=0; i < MAX_TEAMS; i++) {
  	    teamscore[i] = 1234567; /* These teams are not used... */
--- 635,641 ----
  	int teamscore[MAX_TEAMS];
  	maxsc = -32767;
  	minsc = 32767;
! 	win = lose = -1;
  
  	for (i=0; i < MAX_TEAMS; i++) {
  	    teamscore[i] = 1234567; /* These teams are not used... */
***************
*** 657,663 ****
  		}
  		if (teamscore[i] < minsc) {
  		    minsc = teamscore[i];
! 		    loose = i;
  		}
  	    }
  	}
--- 659,665 ----
  		}
  		if (teamscore[i] < minsc) {
  		    minsc = teamscore[i];
! 		    lose = i;
  		}
  	    }
  	}
***************
*** 668,675 ****
  	    xpprintf("%s\n", msg);
  	}
  
! 	if (loose != -1 && loose != win) {
! 	    sprintf(msg,"Worst team (%ld Pts): Team %d", minsc, loose);
  	    Set_message(msg);
  	    xpprintf("%s\n", msg);
  	}
--- 670,677 ----
  	    xpprintf("%s\n", msg);
  	}
  
! 	if (lose != -1 && lose != win) {
! 	    sprintf(msg,"Worst team (%ld Pts): Team %d", minsc, lose);
  	    Set_message(msg);
  	    xpprintf("%s\n", msg);
  	}
***************
*** 677,683 ****
  
      maxsc = -32767;
      minsc = 32767;
!     win = loose = -1;
  
      for (i = 0; i < NumPlayers; i++) {
  	SET_BIT(Players[i]->status, GAME_OVER);
--- 679,685 ----
  
      maxsc = -32767;
      minsc = 32767;
!     win = lose = -1;
  
      for (i = 0; i < NumPlayers; i++) {
  	SET_BIT(Players[i]->status, GAME_OVER);
***************
*** 688,694 ****
  	    }
  	    if (Players[i]->score < minsc) {
  		minsc = Players[i]->score;
! 		loose = i;
  	    }
  	}
      }
--- 690,696 ----
  	    }
  	    if (Players[i]->score < minsc) {
  		minsc = Players[i]->score;
! 		lose = i;
  	    }
  	}
      }
***************
*** 697,704 ****
  	Set_message(msg);
  	xpprintf("%s\n", msg);
      }
!     if (loose != -1 && loose != win) {
! 	sprintf(msg,"Worst human player: %s", Players[loose]->name);
  	Set_message(msg);
  	xpprintf("%s\n", msg);
      }
--- 699,706 ----
  	Set_message(msg);
  	xpprintf("%s\n", msg);
      }
!     if (lose != -1 && lose != win) {
! 	sprintf(msg,"Worst human player: %s", Players[lose]->name);
  	Set_message(msg);
  	xpprintf("%s\n", msg);
      }
diff -c -r ../xpilot-4.1.0/src/server/update.c ./src/server/update.c
*** ../xpilot-4.1.0/src/server/update.c	Sat Aug 29 22:49:57 1998
--- ./src/server/update.c	Fri Feb 19 22:55:40 1999
***************
*** 56,68 ****
  
  #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;	\
      } else {								\
! 	(o_)->vel.x += (o_)->acc.x;					\
! 	(o_)->vel.y += (o_)->acc.y;					\
      }
  
  int	rdelay = 0;		/* delay until start of next round */
--- 56,68 ----
  
  #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)/FPSMultiplier;\
! 	(o_)->vel.y += ((o_)->acc.y					\
! 	   + World.gravity[(o_)->pos.bx][(o_)->pos.by].y)/FPSMultiplier;\
      } else {								\
! 	(o_)->vel.x += (o_)->acc.x/FPSMultiplier;			\
!         (o_)->vel.y += (o_)->acc.y/FPSMultiplier;	        	\
      }
  
  int	rdelay = 0;		/* delay until start of next round */
***************
*** 441,448 ****
       * 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);
  	for (i=0; i<World.NumFuels; i++) {
  	    if (World.fuel[i].fuel == MAX_STATION_FUEL) {
  		continue;
--- 441,448 ----
       * Let the fuel stations regenerate some fuel.
       */
      if (NumPlayers > 0) {
! 	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,476 ****
      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);
  
--- 468,473 ----
***************
*** 487,492 ****
--- 484,491 ----
  		(obj->rotation + (int) (obj->turnspeed * RES)) % RES;
  	}
  
+ 	update_object_speed(obj);
+ 	Move_object(i);
      }
  
      /*
***************
*** 536,542 ****
  	    weapon = Cannon_select_weapon(i);
  	    Cannon_aim(i, weapon, &target, &dir);
  	    if (target != -1) {
! 		if (rand() % 16 == 0)
  		    Cannon_fire(i, weapon, target, dir);
  	    } else if (cannonsUseItems
  		       && itemProbMult > 0
--- 535,541 ----
  	    weapon = Cannon_select_weapon(i);
  	    Cannon_aim(i, weapon, &target, &dir);
  	    if (target != -1) {
! 		if (rand() % (16*FPSMultiplier) == 0)
  		    Cannon_fire(i, weapon, target, dir);
  	    } else if (cannonsUseItems
  		       && itemProbMult > 0
***************
*** 762,768 ****
  	 * Compute turn
  	 */
  	pl->turnvel	+= pl->turnacc;
! 	pl->turnvel	*= pl->turnresistance;
  
  #ifdef TURN_FUEL
  	tf = pl->oldturnvel - pl->turnvel;
--- 761,768 ----
  	 * Compute turn
  	 */
  	pl->turnvel	+= pl->turnacc;
! 	if (pl->turnresistance)     /* use old mouse control */
! 	    pl->turnvel	*= pl->turnresistance;
  
  #ifdef TURN_FUEL
  	tf = pl->oldturnvel - pl->turnvel;
***************
*** 780,790 ****
  
  	pl->float_dir	+= pl->turnvel;
  
! 	if (pl->float_dir < 0)
  	    pl->float_dir += RES;
! 	if (pl->float_dir >= RES)
  	    pl->float_dir -= RES;
  
  	Turn_player(i);
  
  
--- 780,794 ----
  
  	pl->float_dir	+= pl->turnvel;
  
! 	
! 	while (pl->float_dir < 0)
  	    pl->float_dir += RES;
! 	while (pl->float_dir >= RES)
  	    pl->float_dir -= RES;
  
+ 	if (!pl->turnresistance)
+ 	    pl->turnvel=0;
+ 
  	Turn_player(i);
  
  
***************
*** 792,801 ****
  	 * Compute energy drainage
  	 */
  	if (BIT(pl->used, OBJ_SHIELD))
! 	    Add_fuel(&(pl->fuel), (long)ED_SHIELD);
  
  	if (BIT(pl->used, OBJ_CLOAKING_DEVICE))
! 	    Add_fuel(&(pl->fuel), (long)ED_CLOAKING_DEVICE);
  
  #define UPDATE_RATE 100
  
--- 796,805 ----
  	 * Compute energy drainage
  	 */
  	if (BIT(pl->used, OBJ_SHIELD))
! 	    Add_fuel(&(pl->fuel), (long)ED_SHIELD/FPSMultiplier);
  
  	if (BIT(pl->used, OBJ_CLOAKING_DEVICE))
! 	    Add_fuel(&(pl->fuel), (long)ED_CLOAKING_DEVICE/FPSMultiplier);
  
  #define UPDATE_RATE 100
  
***************
*** 807,813 ****
  		pl->visibility[j].canSee = 1;
  	    else if (pl->updateVisibility
  		     || Players[j]->updateVisibility
! 		     || rand() % UPDATE_RATE
  		     < ABS(frame_loops - pl->visibility[j].lastChange)) {
  
  		pl->visibility[j].lastChange = frame_loops;
--- 811,817 ----
  		pl->visibility[j].canSee = 1;
  	    else if (pl->updateVisibility
  		     || Players[j]->updateVisibility
! 		     || rand() % (UPDATE_RATE*FPSMultiplier)
  		     < ABS(frame_loops - pl->visibility[j].lastChange)) {
  
  		pl->visibility[j].lastChange = frame_loops;
***************
*** 833,843 ****
  		int ct = pl->fuel.current;
  
  		do {
! 		    if (World.fuel[pl->fs].fuel > REFUEL_RATE) {
! 			World.fuel[pl->fs].fuel -= REFUEL_RATE;
  			World.fuel[pl->fs].conn_mask = 0;
  			World.fuel[pl->fs].last_change = frame_loops;
! 			Add_fuel(&(pl->fuel), REFUEL_RATE);
  		    } else {
  			Add_fuel(&(pl->fuel), World.fuel[pl->fs].fuel);
  			World.fuel[pl->fs].fuel = 0;
--- 837,847 ----
  		int ct = pl->fuel.current;
  
  		do {
! 		    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/FPSMultiplier);
  		    } else {
  			Add_fuel(&(pl->fuel), World.fuel[pl->fs].fuel);
  			World.fuel[pl->fs].fuel = 0;
***************
*** 870,880 ****
  		int ct = pl->fuel.current;
  
  		do {
! 		    if (pl->fuel.tank[pl->fuel.current] > REFUEL_RATE) {
! 			targ->damage += TARGET_FUEL_REPAIR_PER_FRAME;
  			targ->conn_mask = 0;
  			targ->last_change = frame_loops;
! 			Add_fuel(&(pl->fuel), -REFUEL_RATE);
  			if (targ->damage > TARGET_DAMAGE) {
  			    targ->damage = TARGET_DAMAGE;
  			    break;
--- 874,884 ----
  		int ct = pl->fuel.current;
  
  		do {
! 		    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/FPSMultiplier);
  			if (targ->damage > TARGET_DAMAGE) {
  			    targ->damage = TARGET_DAMAGE;
  			    break;
***************
*** 915,921 ****
  	    }
  	    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 */
  	} else {
  	    pl->acc.x = pl->acc.y = 0.0;
  	}
--- 919,925 ----
  	    }
  	    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)/FPSMultiplier); /* Decrement fuel */
  	} else {
  	    pl->acc.x = pl->acc.y = 0.0;
  	}
diff -c -r ../xpilot-4.1.0/src/server/walls.c ./src/server/walls.c
*** ../xpilot-4.1.0/src/server/walls.c	Sat Sep  5 05:44:52 1998
--- ./src/server/walls.c	Sat Mar 20 18:29:02 1999
***************
*** 990,997 ****
  		    Set_message(msg);
  		    break;
  		}
- 		mi->obj->life = 0;
  		if (mi->obj->owner == -1) {
  		    return;
  		}
  		if (World.treasures[ms->treasure].team ==
--- 990,997 ----
  		    Set_message(msg);
  		    break;
  		}
  		if (mi->obj->owner == -1) {
+ 		    mi->obj->life = 0;
  		    return;
  		}
  		if (World.treasures[ms->treasure].team ==
***************
*** 1000,1009 ****
--- 1000,1013 ----
  		     * Ball has been brought back to home treasure.
  		     * The team should be punished.
  		     */
+ 		    sprintf(msg," < The ball was loose %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,1534 ****
      int			killer = -1;
      player		*pl = NULL;
  
!     cannon->dead_time = CANNON_DEAD_TIME;
      cannon->conn_mask = 0;
      World.block[cannon->blk_pos.x][cannon->blk_pos.y] = SPACE;
      Cannon_throw_items(ms->cannon);
--- 1532,1538 ----
      int			killer = -1;
      player		*pl = NULL;
  
!     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,1553 ****
  	/* 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
  	);
      Make_wreckage(
  	/* pos.x, pos.y   */ x, y,
--- 1551,1557 ----
  	/* 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*FPSMultiplier, 68*FPSMultiplier
  	);
      Make_wreckage(
  	/* pos.x, pos.y   */ x, y,
***************
*** 1561,1567 ****
  	/* 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
  	);
  
      if (!ms->mip->pl) {
--- 1565,1571 ----
  	/* 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*FPSMultiplier, 68*FPSMultiplier
  	);
  
      if (!ms->mip->pl) {
***************
*** 1690,1696 ****
  	/* min,max debris */ 75, 150,
  	/* min,max dir    */ 0, RES-1,
  	/* min,max speed  */ 20, 70,
! 	/* min,max life   */ 10, 100
  	);
  
      if (BIT(World.rules->mode, TEAM_PLAY)) {
--- 1694,1700 ----
  	/* min,max debris */ 75, 150,
  	/* min,max dir    */ 0, RES-1,
  	/* min,max speed  */ 20, 70,
! 	/* min,max life   */ 10*FPSMultiplier, 100*FPSMultiplier
  	);
  
      if (BIT(World.rules->mode, TEAM_PLAY)) {
***************
*** 1852,1860 ****
      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);
  	    x = WRAP_XCLICK(x);
  	    y = WRAP_YCLICK(y);
  	    Object_position_set_clicks(obj, (int)(x), (int)(y));
--- 1856,1864 ----
      dist = walldist[obj->pos.bx][obj->pos.by];
      if (dist > 2) {
  	int max = ((dist - 2) * BLOCK_SZ) >> 1;
! 	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,1887 ****
      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.dir = obj->dir;
      ms.mip = &mi;
  
--- 1884,1891 ----
      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/FPSMultiplier);
!     ms.todo.y = FLOAT_TO_CLICK(ms.vel.y/FPSMultiplier);
      ms.dir = obj->dir;
      ms.mip = &mi;
  
***************
*** 1893,1901 ****
  		break;
  	    }
  	    if (ms.bounce && ms.bounce != BounceEdge) {
! 		obj->life = (long)(obj->life * objectWallBounceLifeFactor);
  		if (obj->life <= 0) {
! 		    break;
  		}
  		/*
  		 * Any bouncing sparks are no longer owner immune to give
--- 1897,1906 ----
  		break;
  	    }
  	    if (ms.bounce && ms.bounce != BounceEdge) {
! 	        if (obj->type != OBJ_BALL)
! 		  obj->life = (long)(obj->life * objectWallBounceLifeFactor);
  		if (obj->life <= 0) {
! 		  break;
  		}
  		/*
  		 * Any bouncing sparks are no longer owner immune to give
***************
*** 2173,2181 ****
      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);
  	    pos.x = WRAP_XCLICK(pos.x);
  	    pos.y = WRAP_YCLICK(pos.y);
  	    Player_position_set_clicks(pl, pos.x, pos.y);
--- 2178,2186 ----
      dist = walldist[pl->pos.bx][pl->pos.by];
      if (dist > 3) {
  	int max = ((dist - 3) * BLOCK_SZ) >> 1;
! 	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,2203 ****
      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);
      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;
--- 2201,2208 ----
      mi.phased = BIT(pl->used, OBJ_PHASING_DEVICE);
  
      vel = pl->vel;
!     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,2415 ****
  			/* 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)
  			);
  		    sound_play_sensors(pl->pos.x, pl->pos.y,
  				       PLAYER_BOUNCED_SOUND);
--- 2414,2420 ----
  			/* 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*FPSMultiplier, (10 + (intensity>>1))*FPSMultiplier
  			);
  		    sound_play_sensors(pl->pos.x, pl->pos.y,
  				       PLAYER_BOUNCED_SOUND);
