/*///////////////// Copyright notice and License info //////////////////
  st.c, version 0.0.158 (Space Tyrant), Copyright 2005, Ray Yeargin.
  This software is released under the terms of the GPL Version 2.
  Download a copy of the license from http://librenix.com/st/gpl.txt, or
  see http://www.gnu.org for more information and a copy of the license.
  The documentation is at  http://librenix.com/?inode=6240
  Use 4 space tabs or a very wide window to edit or view this file!
/*////////////// End of Copyright notice and License info //////////////

/*/////// ---- start of compile shell script ---- /////////////////////
gcc -g -O2 -D_REENTRANT -Wall -c -o st.o st.c               # compile #
gcc -g -O2 -D_REENTRANT -Wall -o st st.o -lresolv -lpthread # link it #
rm st.o; strip st                                           # cleanup #
/*/////// ---- end of compile script ---- /////////////////////////////

/*////////
include	//
headers	//
/*////////
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<stdarg.h>
#include	<ctype.h>
#include	<unistd.h>
#include	<pthread.h>
#include	<signal.h>
#include	<errno.h>
#include	<netdb.h>
#include	<syslog.h>
#include	<sys/stat.h>
#include	<sys/socket.h>
#include	<sys/time.h>
#include	<sys/types.h>

/*////////
macros	//
/*////////
#define	min(a,b)	((a) < (b) ? (a) : (b))
#define	max(a,b)	((a) > (b) ? (a) : (b))

/*////////
 the following 3 macros are random number generators that produce
 up to 16, 20, and 24-bit pseudo random numbers.  Use with care lest
 you try to produce a number larger than 65535 with rand16!  The reason
 for the arbitrary limitation on bits is that the higher order bits are
 considerably more random.  Use rand24 when large numbers are needed
 such as when randomly choosing a sector in a large universe.
 example: 	x=rand16(10000); 			// set x to [0 to 9999]
 example: 	x=rand24(GAMESIZE) + 1;		// set x to [1 to GAMESIZE]
 limits: 	rand16(65536)
			rand20(1048576)
			rand24(16777216)
/*////////
#define rand16(num) (((sequc = sequc * 1234582897 + 12739)>>16) % (num))
#define rand20(num) (((sequb = sequb * 1234577777 + 12601)>>12) % (num))
#define rand24(num) (((sequa = sequa * 1234567949 + 12911)>>8 ) % (num))

/*////////////
detached 	//
thread 		//
functions 	//
*/////////////
static void *gameloop(void *);		// executed once: all game logic
static void *userin(void *);		// executed once: reads from network
static void *userout(void *);		// executed once: writes to network
static void *backupdata(void *);  	// executed once: periodically backs up
									// player and map data, plus config info

/*////////////////////////////////////////////
functions used by above threads and main() 	//
/*////////////////////////////////////////////
void 	setsignals(void);		// executed once: ignore unused signals
								//	and setup interrupt handlers
void 	initialization(void);	// executed once: initializes program state
void 	makemap(void);			// executed only when a new game initializes
void 	loadmap(void);			// executed only when a new game initializes
int		filesize(char fname[]); // returns a file size of 'fname'
void 	radioprompt(int th);	// displays radio prompt
void 	echo(int th);			// echos user input to user only
void 	login(int th);			// processes player logins, register new users
void 	updatefuel(int th);		// issues new antimatter
void 	portcalc(int sector);	// updates port inventories
void 	mooncalc(int sector);	// updates planet inventories
void	sig1rtn();				// used by gameloop to kill io threads
void	(* fp1)(); 				// signal 1 handler function pointer
void	sig15rtn();				// used by admin to shutdown entire game
void	(* fp15)(); 			// signal 15 handler function pointer
void	makenews(int th, char *text);// post news message
void	makehistory(int user, int victim, char *text);// create history record
int		show(int th, char *text);// write text to user screen
int		makenewuser(int th); 	// create a new user and initialize struct
int 	santi(int th, int inq);	// subtract antimatter from user on thread th
int 	showsector(int th);		// displays contents of sector
void	dirty(int sector);		// call this to set sector and block as dirty
int		pathdepth(int th, int sector, int target);	// pathcalc mgt
int		pathcalc(int th, int sector, int depth, int mydepth, 
						int target, int ndx);

/*//////////////////////////////////////////////////////////////////////////
functions called by function pointers and commscan(), which selects them ///
/*//////////////////////////////////////////////////////////////////////////
int 	commscan(char c, char cary[]);// returns (ndx) of c in cary[] -or- (0)
int 	nullrtn(int th);	// sends help hint, not much else
int 	autowarp(int th);	// move ship based on autopilot paths
int 	autopilot(int th);	// shortest path algorithm
int 	resetradio(int th);	// resets radionumb to redisplay old messages
int 	resetnews(int th);	// resets newsnumb to redisplay old news
int 	viewsector(int th);	// redisplay sector
int 	deploy(int th);		// deploy fighters to guard sector
int 	jettison(int th);	// dump cargo holds
int 	others(int th);		// other players currently logged on
int 	players(int th);	// player rankings
int 	warprtn(int th);	// accepts 1-6 < , . > - move ship through warp
int 	hangup(int th);		// end session...
int 	haltsys(int th);	// Close all sessions, sync backups, shutdown...
int 	infortn(int th);	// user inventory
int 	command(int th);	// command list, update as needed
int 	portrtn(int th);	// user trades with commodity port
int 	landrtn(int th);	// user gets commodities from planet
int 	radiortn(int th);	// broadcast messages to other players
int 	buyfiters(int th);	// mail order fighters
int 	atakrtn(int th);	// attack deployed fighters, ships
int 	atakship(int th);	// attack other ships
int		showhistory(int th);// show events since last time

int		showdata(int th);	// debugging function 'z'

// commlist is the command list commscan() uses to find *fp[]() index
#define COMM	55			// number of elements in commlist + 1 for null
char
commlist[COMM] =  "abcdefghijklmnopqrstuvwxyz0123456789?#,.<>-[]!~*^@:/= ";

/*/////////////////////////////////////////////////////////////////////
 The following list of functions are loaded into a function pointer
 array, '*fp[COMM]()', and called by the following function call after
 userndx has been derived by commscan() from commlist[], above:

 result=fp[userndx](th); 	// if user inputs an 'a', fp[1]() is called
/*/////////////////////////////////////////////////////////////////////
int (* fp[COMM])()={nullrtn,  // return code 0 -- unused
				  atakrtn, 	// a  attack deployed fighters, ships
                  buyfiters,// b  mailorder fighters
                  nullrtn,	// c
                  deploy, 	// d  deploy fighters in a sector
                  showhistory,// e  show history events
                  nullrtn,	// f
                  nullrtn, 	// g
                  command, 	// h  temp dupe of ? command
                  infortn, 	// i  user inventory and misc. personal info
                  jettison,	// j
                  nullrtn, 	// k
                  landrtn, 	// l  land on planet, take free goods
                  nullrtn, 	// m
                  nullrtn, 	// n
                  others, 	// o  other players logged on now...
                  players, 	// p  player rankings
                  hangup, 	// q  log off (quit): requires 'y' confirmation
                  radiortn, // r  broadcast messages to other players
                  portrtn, 	// s  sell only at port
                  portrtn, 	// t  dock with port, sell & buy goods
                  nullrtn, 	// u
                  viewsector,// v  redisplay current sector
                  nullrtn, 	// w
                  nullrtn,	// x
                  nullrtn, 	// y
                  showdata,	// z  debugging code
                  autopilot,// 0  compute shortest path to [0sector]
                  warprtn, 	// 1  warp to lowest connected sector number
                  warprtn, 	// 2  warp to second sector number
                  warprtn, 	// 3  warp to third sector number
                  warprtn, 	// 4  warp to fourth sector number
                  warprtn, 	// 5  warp to fifth sector number
                  warprtn, 	// 6  warp to highest connected sector number
                  nullrtn, 	// 7
                  nullrtn, 	// 8
                  nullrtn, 	// 9
                  command, 	// ?  list of implemented command letters
                  nullrtn, 	// #
                  warprtn, 	// ,  jump to next-lowest connected sector number
                  warprtn, 	// .  jump to next-highest connected sector number
                  warprtn, 	// <  jump to lowest connected sector number
                  warprtn, 	// >  jump to highest connected sector number
                  warprtn, 	// -  jump to random connected sector number
                  resetradio,// [  reset radionumb to 0
                  resetnews,// ]  reset newsnumb to 0
                  hangup, 	// !  logoff now(!) (no confirmation required)
                  atakship, // ~  attack enemy ships (called by atakrtn)
                  nullrtn, 	// *
                  haltsys,	// ^  user #1 is the only one who can do this
                  nullrtn, 	// @
                  nullrtn, 	// :
                  autowarp,	// /
                  warprtn,	// = jump to random connected sector (same as -)
                  nullrtn};	// ' '  (space)

/*///////////////////////////////////////////////////
UNP functions: from Unix Network Programming, V. 3 //
/*///////////////////////////////////////////////////
static	void err_userin(int, int, const char *, va_list);
int		daemon_init(const char *, int);
void	str_cli(FILE *, int);
int		tcp_listen(const char *, const char *, socklen_t *);
int		Tcp_listen(const char *, const char *, socklen_t *);
void	Listen(int, int);
void	err_sys(const char *, ...);
struct	addrinfo *host_serv(const char *, const char *, int, int);
ssize_t	writen(int, const void *, size_t);

/*////////////////
configuration 	//
constants		//
/*////////////////
// the following ( GAMESIZE/100, 1000, 10000 ) works for games over 60K sectors
//#define GAMESIZE	1000000			// number of sectors in universe
//#define DBLOCK	250				// sectors to a dirty block (GAMESIZE^.4)
//#define GAMESIZE	400000			// number of sectors in universe
//#define DBLOCK	175				// sectors to a dirty block (GAMESIZE^.4)
//#define GAMESIZE	100000			// number of sectors in universe
//#define DBLOCK	100				// sectors to a dirty block (GAMESIZE^.4)
//#define BIGWARP	(GAMESIZE/400)	// range of long warps	(Min: 24)
//#define MEDWARP	(GAMESIZE/2000)	// range of medium warps(Min: 12)
//#define LILWARP	(GAMESIZE/10000)// range of short warps (Min: 6)

// the following ( GAMESIZE/125, 400, 1250 ) work ok for small games: 7500-60K
//#define GAMESIZE	50000			// number of sectors in universe
//#define DBLOCK	75				// sectors to a dirty block (GAMESIZE^.4)
#define GAMESIZE	20000			// number of sectors in universe
#define DBLOCK		52				// sectors to a dirty block (GAMESIZE^.4)
#define BIGWARP		(GAMESIZE/125)	// range of long warps	(Min: 24)
#define MEDWARP		(GAMESIZE/400)	// range of medium warps(Min: 12)
#define LILWARP		(GAMESIZE/1250)	// range of short warps (Min: 6)

// for tiny games (under 7500), hardcode something like B:50 M:20 L:6
//#define GAMESIZE	10000			// number of sectors in universe
//#define DBLOCK	40				// sectors to a dirty block (GAMESIZE^.4)
//#define GAMESIZE	2500			// number of sectors in universe
//#define DBLOCK	15				// sectors to a dirty block (GAMESIZE^.4)
//#define BIGWARP	50				// range of long warps	(Min: 24)
//#define MEDWARP	20				// range of medium warps(Min: 12)
//#define LILWARP	6				// range of short warps (Min: 6)

#define BLOCKSIZE (GAMESIZE/10)		// range of space to show ship loc (o, p)

#define FUELPERDAY	1440000			// fuel issued per day (1000 gm/minute)
#define GAMECYCLE	10				// port goods, fuel accumulate xx days

#define TIMELIMIT	240				// user DAYly timelimit in minutes
#define	TIMEOUT		600				// seconds idle time before time out
#define DAY			86400			// how long is a local day?

#define CENTIDAY	(DAY/100)		// one percent of a day

#define MAXUSERS	1000			// maximum number of total users
									// note: MAXTH minus MAXTHMARGIN limits the
									// 		number of _concurrent_ users!

#define MAXHIST		(MAXUSERS*5 + GAMESIZE/100)	// entries in history array
#define DHBLOCK		100				// dirty history block size

#define MAXTH		128		// concurrent net connection limit (io thread pairs)
#define MAXTHBITS	7		// this is the number of  unsigned bits
							// needed to index into MAXTH  array
							// ( 2^MAXTHBITS must equal MAXTH! )

#define MAXTHMARGIN	28		// session limit = MAXTH - MAXTHMARGIN
							// With MAXTH (which must be a power of 2)
							// set to 256, set this to 56 if you want to
							// limit concurrent users to 200.

#define IDLESLEEP	5000	// microseconds for idle loops to sleep

#define	MAXLINE		159			// max text line length (min: 159)
#define DEFLINE		(MAXLINE+1)	// define buffers: (+1 for terminating 0)
#define RADLINE		160			// size of radio buffers
#define NEWLINE		100			// size of news buffers

#define SCREEN		20			// number of lines to put on a page
#define LINES		(SCREEN - 1)// for convenience

#define BUGS		16		// number of entries in bug history table of func's
#define BUGBITS		4		// unsigned bits to index BUGS length arrays

#define MAXBUF		32	 	// number of entries in output ring buffer (32 min)
#define MAXBUFBITS	5		// # of unsigned bits needed to index ring buffers
#define MAXINBITS	(MAXBUFBITS-2)	// inbufs are 1/4th of outbufs

#define MAXRAD		(MAXBUF*2)		// number of radiorec buffers
#define MAXRADBITS	(MAXBUFBITS+1)	// number bits to index MAXRAD

#define	LISTENQ		1024	// 2nd argument to listen()

#define MAXDEPTH	13		// depth that autopilot will search to (#nodes)		
char
plantname[6][16]={
				"",					// 0 porttype is null -- no port
                "RockMine",			// 1 iron
                "GreenPort", 		// 2 alcohol
                "HardwareDepot",	// 3 hardware
                "TradingPost",		// 4 upgrades (not used)
                "Spaceport",		// 5 railgun rides (not used)
	};

char
sectorname[3][16]={
				"",						// 0 normal sector
                "Nebula",				// 1 nebula
                "Black Hole", 			// 2 black hole
	};

char
goodsname[6][16]={
				"",					// 0 porttype is null -- no port
                "iron",				// 1 iron
                "alcohol", 			// 2 alcohol
                "hardware",			// 3 hardware
                "upgrades",			// 4 not yet used
                "Railgun Rides!",	// 5 not yet used
	};

/*////////////
global		//
arrays and	//
variables	//
/*////////////

int				shutdownnow=0;	// TERM signal sets this to 1
int				shutdowntime=0;	// used by haltsys function

FILE 			*datafp;		// file descriptor for st.xxx.dat
char			filename[64];	// file name to store backup datafile
unsigned int	bu_bkup=0, 		// completed backup count (backupdata())
				bu_pass=0,		// current pass of backup thread
				bu_ucount=0,	// total user blocks backed up
				bu_mcount=0,	// total map blocks backed up
				bu_hcount=0,	// total his tblocks backed up
				bu_dirtybl;		// current number of dirtyblock
char			bu_sleep='I';	// set to S while backup process is sleeping
unsigned int	dirtyuser[MAXUSERS+1];			// dirty user array
unsigned int	dirtysector[GAMESIZE+1];		// dirty sector array
unsigned int	dirtyhistory[MAXHIST];			// dirty history array
unsigned int	dirtyblock[1+(GAMESIZE+1)/DBLOCK];// dirty sector rough table
unsigned int	dirtyhistblock[1+MAXHIST/DBLOCK]; // dirty history rough table

unsigned char
radiorec[MAXRAD][RADLINE];		// radio buffers
int		radionumb[MAXRAD];		// serial number of radio messages
int		radiosender[MAXRAD];	// user id number of sender of this radio msg
int		radionumber=0;			// master copy of radionumber

unsigned char
newsrec[MAXRAD][NEWLINE];		// news buffers
int		newsnumb[MAXRAD];		// serial number of news messages
int		newssender[MAXRAD];		// user id number of originator of this news
int		newsnumber=0;			// master copy of newsnumber

/*///////////////////////////////////////////////////////////////////////////
 The following structs and arrays are close to each other for performance
 reasons.  They are heavily used in the core of gameloop and, if cached in
 the CPU data cache, can greatly speed up looping through idle threads.
   ///// Note that large buffers are not in this section of memory! /////
/*///////////////////////////////////////////////////////////////////////////
struct
radiondxlayout
{
	unsigned int x:MAXRADBITS; // radio master index: 'which radiorec to use'
	unsigned int radbitsx:sizeof(int)*8-MAXRADBITS;	// force word alignment
	unsigned int y:MAXRADBITS; // utility index for redisplays, scans, etc
	unsigned int radbitsy:sizeof(int)*8-MAXRADBITS;	// force word alignment
} radiondx;

struct
newsndxlayout
{
	unsigned int x:MAXRADBITS; // news master index: 'which newsrec to use'
	unsigned int newsbitsx:sizeof(int)*8-MAXRADBITS;// force word alignment
	unsigned int y:MAXRADBITS; // utility index for redisplays, scans, etc
	unsigned int newsbitsy:sizeof(int)*8-MAXRADBITS;// force word alignment
} newsndx;

unsigned int UserToThread[MAXUSERS+1];//UserToThread[user] holds thread index

unsigned int ThreadToUser[MAXTH+1];	//ThreadToUser[thread] holds user index

int 	inuse[MAXTH + 1];		// elements of threc[] currently in use

char	sectorused[6008];		// for pathcalc
int 	targetfound=0;			// for 	"
int		targetpath[102];

struct 		// this struct is an array of threclayout with one active struct
threclayout	// element for each connected user's input and output threads
{
	pthread_t		itid;					// input thread id (userin)
	pthread_t		otid;					// output thread id (userout)
	time_t 			lastaccess;				// time of last user input activity
	int 			fdconn;					// connected socket file descr.
	int 			myth;					// MY THread index (ndx of threc)

	unsigned int  			user;			// persistent user id #
	unsigned int			loginstatus; 	// 1=login prompt sent (0=new th)
											// 2=newuser prompt sent
											// 3=password prompt sent
											// 4=password prompt resent
											// 5=olduser: password prompt
											// 6=logged in user

	int						control;		// code to call command functions
	unsigned int			status;			// status flag to pass via control
	unsigned int			data;			// add'l data to pass via control
	unsigned int			noshow;			// don't show sector next pass
	int 					radionumb;		// number of last radio message
	int 					showmyrad;		// re-show own radio messages too
	int 					newsnumb;		// number of last radio message
	int 					showmynews;		// re-show own radio messages too
	int 					autowarp;		// pointer to warp letter in input

	unsigned int			noecho;			// don't echo user input (passwds)
	unsigned int			incount;		// count input bytes since CR or
											// 	command completion

	unsigned int			inputctl;		// input[] & incount control flag
											// 2=gameloop, 1=userin, 0=free

	  ////////////////////////////////////////////////////////
	 // wraparound unsigned int bitfields for ring buffers //
	////////////////////////////////////////////////////////
	unsigned int	 	inndx:MAXINBITS;	// for loading inbuf
	unsigned int		inndf:sizeof(int)*8-MAXINBITS;	// force word alignment
	unsigned int	 	inptr:MAXINBITS;	// for reading/clearing inbuf
	unsigned int		inptf:sizeof(int)*8-MAXINBITS;	// force word alignment
	unsigned int	 	intmp:MAXINBITS;	// for temp storage of inptr/ndx
	unsigned int		intmf:sizeof(int)*8-MAXINBITS;	// force word alignment

	unsigned int	 	outndx:MAXBUFBITS;	// for loading outbuf
	unsigned int		outndf:sizeof(int)*8-MAXBUFBITS;// force word alignment
	unsigned int	 	outptr:MAXBUFBITS;	// for reading/clearing outbuf
	unsigned int		outptf:sizeof(int)*8-MAXBUFBITS;// force word alignment
	unsigned int	 	outtmp:MAXBUFBITS;	// for temp storage of outptr/ndx
	unsigned int		outtmf:sizeof(int)*8-MAXBUFBITS;// force word alignment

	unsigned int	 	bugindex:BUGBITS;	// for loading bugbuf	// testcode
	unsigned int		bugfill:sizeof(int)*8-BUGBITS;	// force word alignment
	//////////////////////////////////////////////////////////////////////

	unsigned int		killout;	// if == 1, userout requests reaping
	unsigned int		killin;		// if == 1, userin  requests reaping
	unsigned int		logmeout;	// gameloop function requests thread kills
									// if == 1, count to 10, expire session
	unsigned int		logoutmsg;	// 1 == napping, 
									// 2 == boredom, 
									// 3 == limit
									// 4 == dupe,
	unsigned int		timeout;	// idle keyboard warned flag

	unsigned int		gorite[MAXBUF]; 	// pass control of outbuf[x]'s:
											// set to 1 by gameloop() (if 0)
											// when buffer is loaded.
											// set back to 0 by userout() when
											// output is completed
	unsigned int		gocopy[MAXBUF/4]; 	// pass control of inbuf[x]'s:
											// set to 1 by userin() (if 0)
											// when buffer is loaded.
											// set back to 0 by gameloop() when
											// processing is completed
} 	threc[MAXTH];

struct stat fbuf;

/*///////////////////////////////////////////////////////////////////////////
 The above structs and arrays are close to each other for performance
 reasons.  They are heavily used in the core of gameloop and, if cached in
 the CPU data cache, can greatly speed up looping through idle threads 
   ///// Note that large buffers are not in this section of memory! /////
/*///////////////////////////////////////////////////////////////////////////

int	sessioncount;		// global counter for gameloop to count active sessions

unsigned int sequa=1; 	// rand # generator: used only by gameloop thread
unsigned int sequb=1; 	// rand # generator: used only by gameloop thread
unsigned int sequc=1; 	// rand # generator: used only by gameloop thread

struct 					// These arrays are big or rarely used.  They are 
bufreclayout			// logically an extension of threc, but were moved
{						// here so threc would cache better in CPU cache.
	unsigned char		outbuf[MAXBUF][DEFLINE];	// net output ring buffer
	unsigned char		inbuf[MAXBUF/4][DEFLINE];	// net input ring buffer
	unsigned char		bugbuf[BUGS][DEFLINE];// function names, args: testcode
	unsigned char		input[DEFLINE];		// accumulate multichar input here

	char logname[28];						// identify user on this thread
	char logpword[16];						// identify user on this thread
	char savpword[16];						// identify user on this thread

} bufrec[MAXTH];

unsigned int		scores[MAXUSERS+1];		// cumulative scores of players
unsigned short int	scoresndx[MAXUSERS+1];	// player numbers for scores, above
unsigned int		scoretime=0;			// only update scores when old

/*/////		// everything that need to be backed up and reloaded when the
game //		// game bounces must be in the following struct.  This includes
data //		// user data, map data, and configuration data.  IP number tables,
file //		// the history file, the events file (what happened to you since
/*/////		// last session) should be here also.
struct
datalayout
{
	struct userlayout
	{
		char name[28];				// unique 27-char player name
		char pword[16];				// (to be) one-way hash of 15-char password
		int goods[4];  		 		// cargo holds[0], iro[1], alc[2], hw[3]
		time_t lastcall; 			// time(0) last session
		time_t lastfuel; 			// time(0) last fuel issue
		unsigned int timeused;		// time used today for timelimit
		unsigned int logons;		// logons today for logonlimit
		unsigned int valid;			// 0-254 access level (validated+)
		unsigned int sector;		// sector number of ship's location
		unsigned int lastsector;	// most recent other sector occupied
		signed int eship;  			// last live enemy ship seen by player #
		signed int esect;			// sector # of last live enemy ship seen
		signed int cash;      		// microbots carried with ship
		signed int antitoday; 		// amount of antimatter currently in tanks
		signed int reppoints; 		// reputation points
		signed int fiters;    		// fighters with ship (excludes deployed)
	} user[MAXUSERS+1];

	struct maplayout
	{
		unsigned char sectortype;	// 1=nebula
		unsigned char porttype;		// type of port 0,1,2,3,4   0=none
		unsigned char moontype;		// type of moon 0,1,2,3,4   0=none
		unsigned char fiterscode;	// 253==defensive, 254==spies
		signed int fiters;			// # of fighters in sector
		signed short int portgoods[4];// iron, alc, hardware inv at port
		signed short int portprod[4];// goods productivity
		signed short int moongoods[4];// iron, alc, hardware inv at port
		signed short int moonprod[4];// goods productivity
		unsigned int portcalcdate;	// date inventory last calculated
		unsigned int mooncalcdate;	// date inventory last calculated
		unsigned fitersowner;		// player # who owns fighters
		signed int warp[6];			// connections to other sectors
	} sector[GAMESIZE+1];

	struct histlayout
	{
		unsigned int ts;			// timestamp
		unsigned int victim;	// receiver of this histogram
		unsigned int sender;	// sender of this histogram
		unsigned char text[68];		// content of message
	} histrec[MAXHIST];
} d;


    ///////////////////////////////////////////////////////////////////////
   ///  (function names at line beginning so you can search for ^main) ///
  ///  main() initializes arrays, spawns administrative threads and   ///
 ///  accepts new connections and spawns I/O threads for each user   ///
///////////////////////////////////////////////////////////////////////
int
main(int argc, char **argv)
{
	int		listenfd=0,
			th,					// local subscript for threc (thread array)
			saveth, 			// used to save state of th, above
			consoc,				// descriptor of recently connected socket
			accepted, 			// accept() returned connection successfully
			oresult, 			// result code for creation of output thread
			iresult, 			// result code for creation of input thread
			cleanup;			// error flag: value denotes nature of failure

	pthread_t	itid, 	// (current) input thread ID goes here
				otid, 	// (current) output thread ID goes here
				mtid;	// gameloop thread ID goes here
	pthread_t	btid; 	// backupdata thread id

	socklen_t	addrlen, len;
	struct sockaddr	*cliaddr;
	unsigned char tmp[DEFLINE], serv[32];;

	if (fork()!=0) exit(0);  // let child process take over

	// uncomment OS X psetgrp line to compile on a Mac, comment out Linux line
	setpgrp();   	//reset ps group to disassociate from parent (Linux)
	//setpgrp(0,0);	//reset ps group to disassociate from parent (OSX)

	if (argc==2) strncpy(serv,argv[1],31);
	if (argc==3) strncpy(serv,argv[2],31);

	sprintf(filename,"st.%s.dat",serv);

	initialization(); 	// miscellaneous program initialization, make map

	setsignals(); 		// ignore, handle various signals (needs work)...

	inuse[0]=2; 		// [in|out]buf[0] used by gameloop(); hence, 'in use'

	pthread_create(&mtid, NULL, &gameloop, NULL); // main logic loop

	pthread_create(&btid, NULL, &backupdata, NULL);// backup thread

	if (argc == 2)  	// listen on all available network interfaces
		listenfd = tcp_listen(NULL, argv[1], &addrlen);
	else
		if (argc == 3) 	// listen only on network interface specified
			listenfd = tcp_listen(argv[1], argv[2], &addrlen);
		else
		{
			printf(" usage: st [ <host> ] <service or port>\n");
			exit(1);
		}

	cliaddr = malloc(addrlen);

	for ( ; ; ) 	// loop that listens on port and spawns IO threads
	{				// as users connect
		usleep(50000); 		// allow only 20 connections/second
		len = addrlen;
		consoc = accept(listenfd, cliaddr, &len);
		th=saveth=accepted=cleanup=sessioncount=0; // set th 0 to avoid warnings
		iresult=oresult=1;  // thread create returns 0 on _success_
		if (consoc>0)
		{
			for (th=1;th<MAXTH;th++)  // count thread arrays that are in use
				if (inuse[th])
					sessioncount++;
			if (sessioncount<(MAXTH-MAXTHMARGIN))
			{
				for (th=1;th<MAXTH;th++)		// find an empty slot
				{								// in the thread array
					if (threc[th].fdconn==0)	// got one! fill it in...
						threc[th].fdconn=consoc,// socket filedescriptor
						threc[th].lastaccess=time(NULL), // avoid early timeout
						threc[th].killout=0,	// reset 'kill me' flag
						threc[th].killin=0,		// reset 'kill me' flag
						threc[th].myth=th,		// pass th. index to functions
						accepted=1,				// 'all is well' flag
						saveth=th,				// save 'th' for use below...
						th=MAXTH;				// bail out of for loop
				}
				if (accepted==1)
				{
					inuse[saveth]=1;// so gameloop won't work on it yet
					iresult=pthread_create(&itid, NULL, &userin,
											&threc[saveth].myth);
					oresult=pthread_create(&otid, NULL, &userout,
											&threc[saveth].myth);
					if (iresult || oresult)
						cleanup=4;	// thread error
					else			// two good io threads... we're ready!
						inuse[saveth]=2;// gameloop can work on it now
				}
				else
					cleanup=3; 		// table full
			}
			else 					// max sessions exceeded; set cleanup
			{
				sprintf(tmp,"\r\nSessions: %d: Limit exceeded!\r\n%c"
					,sessioncount,7);
				writen(consoc,tmp,strlen(tmp));
				usleep(10000);
				cleanup=2; 			// too many sessions
			}
		} else
			cleanup=1; 				// accept failed

		if (cleanup) switch (cleanup)// all failures
		{
			case 4:	// bad thread: kill good thread, close socket,
					// clear threc, turn off inuse (all cases are cumulative)
				if (oresult==0)
					pthread_kill(otid, 1);
				if (iresult==0)
					pthread_kill(itid, 1);
					// A thread may have used threc[saveth], so ...
				bzero(&threc[saveth],	sizeof(struct threclayout));
				bzero(&bufrec[saveth],	sizeof(struct bufreclayout));
				inuse[saveth]=0; 	// threc[saveth] no longer in use
			case 2:	// session limit exceeded / socket was open
				close(consoc);
			case 1: // accept failed:  nothing more to do
			case 3: // table full:     nothing more to do
				break;
		}
	}
}

   ///////////////////////////////////////////////////////////////////////
  ///  This thread is executed once at program startup         //////////
 ///  It handles all game logic -- and does no I/O!           //////////
///////////////////////////////////////////////////////////////////////
static void *
gameloop(void *arg)
{
	register int th;
	unsigned char tmp[DEFLINE];		// local buffer for misc use and messages
	time_t now;
	int count,
		sentr,
		sentn,
		fuelnow,
		userndx,
		result;		// result code from command functions

	radiondx.x=0;
	newsndx.x=0;
	pthread_detach(pthread_self());
	for ( ; ; )	// process all user commands in input buffers...
	{
		count=0;// note whether nonidle or idle pass thru buffers
		now=time(NULL); 				// save time for use later
		fuelnow=now/60;
		for (th=1;th<MAXTH;th++)		// scan thread table for activity...
		{
			if (inuse[th]>1)			// skip threads not in use
			{
				if ((threc[th].gocopy[threc[th].inptr]==1)	// buffer[inptr]
							 								// has data...
					&& (threc[th].inputctl==0)// userin() not using input[]?
					&& (threc[th].inptr!=threc[th].inndx)) // testcode...
						// ...to prevent ptr passing ndx
				{
					count++; 							// <==note: work to do
					///-----------------------------------------------///
					// this prevents userin() thread from changing input[] or
					// incount while gameloop functions are using it.
					threc[th].inputctl=2;	// gameloop has control of input[]

					///-----------------------------------------------///
					///  Program logic follows below...               ///
					///-----------------------------------------------///

					// normal input in buffer from user is processed here...

					echo(th); //echo input

					// this thread not completely logged in, call login()
					if (threc[th].loginstatus < 6)
						login(th);
					else
					// previous command requires more input... call it again
					if (threc[th].control > 0)	// contains command letter
					{
						userndx=commscan(threc[th].control, commlist);
						result=fp[userndx](th);	// re-call previous func.
					}
					else
					// process new commands
					{
						if (d.user[ThreadToUser[th]].lastfuel < fuelnow)
							updatefuel(th);
						userndx=commscan(
							tolower(bufrec[th].inbuf[threc[th].inptr][0]),
							commlist);
						result=fp[userndx](th);  // call command function
						if (threc[th].control == 0)	// don't showsector if
							showsector(th);			// fn retains control
					}

					///-----------------------------------------------///
					///  Program logic above...                       ///
					///-----------------------------------------------///

					threc[th].inputctl=0;	// release input[] for userin()
					// note: if input[] or incount is used outside the range
					// of gameloop control where inputctl==2, inputctl must
					// be checked to not be 1, and set to 2, then reset to 0
					// when finished!
					///-----------------------------------------------///

					// after processing command, clear, release input line
					bzero(bufrec[th].inbuf[threc[th].inptr],	DEFLINE);
					threc[th].gocopy[threc[th].inptr++]=0;
				}
				else // empty inbuf, user idle: scan news, radio.
				{
					// scan news
					sentn=0;
					newsndx.y = newsndx.x;
					if ((newsnumber - threc[th].newsnumb) >
						(MAXRAD - 3) && threc[th].showmynews==0)
							threc[th].newsnumb = newsnumber - (MAXRAD - 3);
					do
					{
						if (threc[th].newsnumb < newsnumb[newsndx.y]
							&& (newssender[newsndx.y] != ThreadToUser[th]
								|| 	threc[th].showmynews==1)
							&& (threc[th].control == 0
								|| (threc[th].control == 'r'
									&& threc[th].status == 1))
							&& threc[th].loginstatus > 5)
						{
							show(th,newsrec[newsndx.y]);
							threc[th].newsnumb=newsnumb[newsndx.y];
							sentn++;
						}
					    newsndx.y++;
					} while (newsndx.y != newsndx.x && sentn==0); //send 1
					if (newsndx.y==newsndx.x) threc[th].showmynews=0;

					// scan radio
					sentr=0;
					radiondx.y = radiondx.x;
					if ((radionumber - threc[th].radionumb) >
						(MAXRAD - 3) && threc[th].showmyrad==0)
							threc[th].radionumb = radionumber - (MAXRAD - 3);
					do
					{
						if (threc[th].radionumb < radionumb[radiondx.y]
							&& (radiosender[radiondx.y] != ThreadToUser[th]
								|| 	threc[th].showmyrad==1)
							&& (threc[th].control == 0
								|| (threc[th].control == 'r'
									&& threc[th].status == 1))
							&& threc[th].loginstatus > 5)
						{
							show(th,radiorec[radiondx.y]);
							threc[th].radionumb=radionumb[radiondx.y];
							sentr++;
						}
					    radiondx.y++;
					} while (radiondx.y != radiondx.x && sentr==0);//send 1
					if (radiondx.y==radiondx.x) threc[th].showmyrad=0;

					if (sentr || sentn) // sent radio or news, cleanup display
					if ((sentr && threc[th].showmyrad==0) 	// done resending
						|| (sentn && threc[th].showmynews==0))// news, radio
					{
						if (threc[th].control == 'r' && threc[th].status == 1)
							radioprompt(th); 	// redraw radio prompt
						else
						{
							threc[th].noshow=1;
							showsector(th);
						}
					}
				}

				// need timeout for incomplete logins... allow 4 minutes?

				// exceed daily timelimit, session limit?
				if (threc[th].loginstatus > 5)
					if ((d.user[ThreadToUser[th]].timeused + now -
						d.user[ThreadToUser[th]].lastcall) > (TIMELIMIT*60))
				{
					if ( threc[th].logmeout==0)
					{
						threc[th].logmeout=1;
						threc[th].logoutmsg=3;
					}
				}
				// timeout?
				if (((now - threc[th].lastaccess) > TIMEOUT) // idle keyboard
					&& (threc[th].logmeout==0))
				{
					threc[th].logmeout=1;
					threc[th].logoutmsg=2;
				}
				if (((threc[th].killout>0)			// I/O error in userout() 
					|| (threc[th].killin>0))		// I/O error in userin()
						&& (threc[th].logmeout==0))	// do it only once
				{
					threc[th].logmeout=1;
					if (threc[th].loginstatus > 5)	// logged in valid user
						threc[th].logoutmsg=1;
				}
				if (threc[th].logmeout==1)	// do these only on first pass
					 switch (threc[th].logoutmsg)
				{
					case 1:		// 1 == napping, 
						sprintf(tmp,
							"\r\nNews: %s is napping",
							d.user[ThreadToUser[th]].name);
						makenews(th, tmp);
						break;
					case 2:		// 2 == boredom, 
						sprintf(tmp,
							"\r\nNews: %s passed out from boredom",
							d.user[ThreadToUser[th]].name);
						makenews(th, tmp);
						break;
					case 3:		// 3 == limit
						show(th, 
							"\r\n\n * Time limit exceeded! * \r\n\n\a");
						sprintf(tmp, 
								"\r\nNews: %s: Time limit exceeded",
							d.user[ThreadToUser[th]].name);
						d.user[ThreadToUser[th]].timeused=TIMELIMIT*60;
						dirtyuser[ThreadToUser[th]]=1;
						makenews(th, tmp);
						break;
					case 4:		// 4 == dupe,
						sprintf(tmp,
							"\r\nNews: %s killed duplicate session",
							d.user[ThreadToUser[th]].name);
						makenews(th, tmp);
						break;
				}
				if (threc[th].logmeout > 10)
				{
					pthread_kill(threc[th].otid, 1);
					pthread_kill(threc[th].itid, 1);
					shutdown(threc[th].fdconn,SHUT_RDWR);
					close(threc[th].fdconn);			// close idle socket
					// timeout, killout: reset UtoT if logged in, not dupe kill
					if (threc[th].loginstatus > 5)		// only if logged in
					{
						if (threc[th].logoutmsg != 4)	// not on dupes
						{
							d.user[ThreadToUser[th]].timeused += 
								(now - d.user[ThreadToUser[th]].lastcall);
							UserToThread[ThreadToUser[th]]=0;
						}
						dirtyuser[ThreadToUser[th]]=1;
					}
					bzero(&threc[th],	sizeof(struct threclayout));
					bzero(&bufrec[th],	sizeof(struct bufreclayout));
					inuse[th]=0;
				}
				if (threc[th].logmeout) 	// send messages before logoff...
					threc[th].logmeout++;	// count to 10, log user out
			}
		}
		if (count==0)			// no actual work done... take a long break!
			usleep(IDLESLEEP);	// this deliberately ignores radio, news!
		if (shutdownnow)
		{
			if (shutdownnow==1)
			{
				sprintf(tmp,
					"\r\n\n\n* * * The system is going down now! * * *\a\n");
				makenews(th, tmp);
			}
			if (shutdownnow < 40) shutdownnow++;// let backup thread take over
			if (shutdownnow > 50) exit(1);		// backup thread is finished
		}
	} 							// end of forever loop
}

  /////////////////////////////////////////////////////////////////////////////
 /// Game logic functions (gameloop thread) start here. No IO, no sleeping ///
/////////////////////////////////////////////////////////////////////////////
int 		// scan the commlist array, passed as cary[], for command 'c'
commscan(char c, char cary[])	// and return the 1-relative index position
{
   int x;
   for (x=0;x<(COMM-1);x++)
		if (c==cary[x]) return(int)(x+1);
   return(0);
}

void					// when user uses gameloop commands, like radio, that
accumulateinput(int th)	// require multiple characters of input, they are
{						// accumulated in the thread's 'input' buffer here
	int inputlen;
	inputlen=strlen(bufrec[th].input);
	strncpy(&bufrec[th].input[inputlen],
		bufrec[th].inbuf[threc[th].inptr],
		MAXLINE - inputlen);
	bufrec[th].input[MAXLINE]=0;
	return;
}

int						// Subtract fuel for moving, landing, and porting only
santi(int th, int inq)	// inq: inquiry only... don't subract, only return(cost)
{
	int user, 		// local copy
		cost=100;	// base cost is 100, add equipment, goods to that...

	user=ThreadToUser[th];

	cost += (d.user[user].goods[0]);		// cargo holds: 1 gram each
	cost += (2 * (d.user[user].goods[1] 	// iron: 	2 grams per hold
				+ d.user[user].goods[2] 	// alcohol: 2 grams per hold
				+ d.user[user].goods[3])); 	// hardware:2 grams per hold

	if (inq == 0)
	{
		if (d.user[user].antitoday > cost)
			d.user[user].antitoday -= cost;
		else
			d.user[user].antitoday = 0;
		dirtyuser[user]=1;
	}
	return(cost);
}

void 			// echo the users input (command, radio input, etc) back to him
echo(int th) 	// (part of gameloop() thread!)
{
	// make sure ndx isn't going to catch ptr! // next 3 lines are testcode
	threc[th].outtmp=threc[th].outndx;
	threc[th].outtmp++;
	if (threc[th].outtmp!=threc[th].outptr)
	if (threc[th].gorite[threc[th].outndx]==0)
	{
		if (threc[th].noecho==0)
		{
			strncpy(bufrec[th].outbuf[threc[th].outndx],
					bufrec[th].inbuf[threc[th].inptr],
					MAXLINE);
			bufrec[th].outbuf[threc[th].outndx][MAXLINE]=0;
			if (strlen(bufrec[th].outbuf[threc[th].outndx])>0)
				threc[th].gorite[threc[th].outndx++]=1;
		}
	}
	return;
}

int		// send a line of text to user on thread pair 'th'
show(int th, char *text)	// should be used from gameloop only!
{
	// make sure ndx isn't going to catch ptr! // next 3 lines are testcode
	threc[th].outtmp=threc[th].outndx;
	threc[th].outtmp++;
	if (threc[th].outtmp!=threc[th].outptr)
	if (threc[th].gorite[threc[th].outndx]==0 && strlen(text)>0)
	{
		strncpy(bufrec[th].outbuf[threc[th].outndx], text, MAXLINE);
		bufrec[th].outbuf[threc[th].outndx][MAXLINE]=0;
		threc[th].gorite[threc[th].outndx++]=1;
		return(0);
	}
	return(1);		// ...don't have control of buffer ... or null text...
}

int
showdata(int th)	//testcode
{
	unsigned int x,n,nw,connfd;
	unsigned char tmp[DEFLINE+128];	// local buffer for misc use and messages
	connfd = threc[th].fdconn;
	// set MAXBUF to 32 for complete displays
	sprintf(tmp,"\r\n\n[th #%3d]  ___gorite_output_buffer_flags___  output_   _gocopy_  _input_  bug",th);
	n=strlen(tmp);
	nw=writen(connfd,tmp,n);
	sprintf(tmp,"\r\nth# inuse  0123456789abcdefghijklmnopqrstuv  ndx ptr   01234567  ndx ptr  ndx");
	n=strlen(tmp);
	nw=writen(connfd,tmp,n);
	for (x=1;x<(MAXTH-MAXTHMARGIN);x++) // comment out for only your thread
	{
		if (inuse[x]>0)
		{
			sprintf(tmp,"\r\n%3d   %d    %d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d  %3d %3d   %d%d%d%d%d%d%d%d  %3d %3d  %3d ",
				x,inuse[x],
				threc[x].gorite[0],threc[x].gorite[1],
				threc[x].gorite[2],threc[x].gorite[3],
				threc[x].gorite[4],threc[x].gorite[5],
				threc[x].gorite[6],threc[x].gorite[7],
				threc[x].gorite[8],threc[x].gorite[9],
				threc[x].gorite[10],threc[x].gorite[11],
				threc[x].gorite[12],threc[x].gorite[13],
				threc[x].gorite[14],threc[x].gorite[15],
				threc[x].gorite[16],threc[x].gorite[17],
				threc[x].gorite[18],threc[x].gorite[19],
				threc[x].gorite[20],threc[x].gorite[21],
				threc[x].gorite[22],threc[x].gorite[23],
				threc[x].gorite[24],threc[x].gorite[25],
				threc[x].gorite[26],threc[x].gorite[27],
				threc[x].gorite[28],threc[x].gorite[29],
				threc[x].gorite[30],threc[x].gorite[31],
				threc[x].outndx,threc[x].outptr,
				threc[x].gocopy[0],threc[x].gocopy[1],
				threc[x].gocopy[2],threc[x].gocopy[3],
				threc[x].gocopy[4],threc[x].gocopy[5],
				threc[x].gocopy[6],threc[x].gocopy[7],
				threc[x].inndx,threc[x].inptr,
				threc[x].bugindex);
			n=strlen(tmp);
			nw=writen(connfd,tmp,n);
		}
	}

	sprintf(tmp,
		"\r\nU%d*%d+M%d*%d+H%d*%d=%d BU%d[%c:%d] T:U=%d,M=%d,H=%d ",
		(int)sizeof(struct userlayout), 
		MAXUSERS+1,
		(int)sizeof(struct maplayout), 
		GAMESIZE+1,
		(int)sizeof(struct histlayout), 
		MAXHIST,
		(	 (int)sizeof(struct userlayout)*(MAXUSERS+1)
			+(int)sizeof(struct maplayout) *(GAMESIZE+1)
			+(int)sizeof(struct histlayout) *MAXHIST	),
		bu_bkup,
		bu_sleep,
		bu_pass,
		bu_ucount,
		bu_mcount,
		bu_hcount	
	);
	n=strlen(tmp);
	nw=writen(connfd,tmp,n);

/*
	example entry with info: sprintf(bufrec[th].bugbuf[threc[th].bugindex++], "show(gorite[ndx:%d]==%d)",threc[th].outndx, threc[th].gorite[threc[th].outndx]);	// testcode
	
	example with function name only: sprintf(bufrec[th].bugbuf[threc[th].bugindex++],"makenewuser()");// testcode

	for (x=threc[th].bugindex;x<(threc[th].bugindex+BUGS);x++)
	{
		if (strlen(bufrec[th].bugbuf[x%BUGS])>0)
		{
			sprintf(tmp,"\r\n%d: %s",x%BUGS,bufrec[th].bugbuf[x%BUGS]);
			n=strlen(tmp);
			nw=writen(connfd,tmp,n);
		}
	}
	nw=writen(connfd,tmp,2);	// put a cr/lf after data display
*/

	threc[th].noshow=2;
	return(0);
}

/// ***** alert!  when purging an old user, remove their deployed forces first!
/// this is not yet properly handled!
int
makenewuser(int th)	// initialize a new user's data
{
	unsigned int x,y,	// misc local subscripts
			lowscore,	// most eligible account found so far
			score,		// this account's score
			lowuser=0,	// index of account with lowest score
			start=2,	// start at 2; never purge account #1
			cutoff;		// score above which we won't purge a user
	cutoff=time(NULL)/DAY+8;	// ignore the first logon, initial cash
	lowscore=2000000000;
	if (d.user[0].name[0]==0)
		start=1;	// an empty acount scores 0... stop.
	for (x=start;x<MAXUSERS;x++)
	{
		score =d.user[x].lastcall/DAY;	// initial score is lost login in days
		score+=d.user[x].logons*7;		// each logon adds 7 days
		score+=d.user[x].cash/24;		// each microbot adds 1 hour
		score+=d.user[x].reppoints/24;	// each rep point adds 1 hour
		score+=d.user[x].fiters;		// each fighter adds 1 day
		if (score < lowscore)
		if ((UserToThread[x] == 0) || 				// is user logged off?
			(x != ThreadToUser[UserToThread[x]])) 	// or data inconsistent?
		{
			lowscore=score;
			lowuser=x;
		}
		if (d.user[x].name[0]==0)		// an empty acount scores 0... stop.
		{
			lowscore=0;
			lowuser=x;
			x=MAXUSERS;
		}
	}
	if (lowscore > cutoff)
	{
		show(th, "\r\n\n * Our database is full! * (try tomorrow) \r\n\n\a");
		return(0);
	}
	x=lowuser;			// use this account
	//scan universe, removing fighters/bases belonging to user 'x'...
	for (y=1;y<(GAMESIZE+1);y++)
	{
		if (d.sector[y].fitersowner==x)
		{
			d.sector[y].fitersowner=0;
			d.sector[y].fiters=0;
			// get starbases, graffiti, and bunkers too, as needed
			dirty(y);	// notify backupdata() thread that this sector changed
		}
	}
	UserToThread[x]=th;
	ThreadToUser[th]=x;
	strncpy(d.user[x].pword,
		bufrec[th].logpword,15);
	d.user[x].pword[15]=0;
	strncpy(d.user[x].name,
		bufrec[th].logname,27);
	d.user[x].name[27]=0;
	d.user[x].lastcall=time(NULL);
	d.user[x].lastfuel = (time(NULL)/60);
	d.user[x].timeused=0;
	d.user[x].antitoday=2500000;
	d.user[x].reppoints=0;
	d.user[x].valid=10;
	d.user[x].logons=1;
	d.user[x].sector=rand24(GAMESIZE)+1;
	d.user[x].lastsector=1;
	d.user[x].fiters=0;
	d.user[x].cash=600;		// 600 gives 25 hours credit in purge computation 
	d.user[x].goods[0]=60; 		// cargo holds
	d.user[x].goods[1]=20;
	d.user[x].goods[2]=20;
	d.user[x].goods[3]=20;
	dirtyuser[x]=1;
	return(ThreadToUser[th]);	// and return the user index number
}

void 					// create new login accounts for new users and
login(int th) 			// log existing users into the game (gameloop thread)
{
	unsigned char tmp[DEFLINE];		// local buffer for misc use and messages
	int namelen,
	    pwlen,
	    dupe=0,
	    buflen,
		breakflag=0,// used to break from outer for
		x;			// general purpose local subscript
	if (inuse[th]>1)
	{
		switch (threc[th].loginstatus)
		{
			case 0: // 0==new connection: issue login prompt now
				show(th,"\r\n(Space Tyrant, version 0.0.158)");
				show(th,"\r\nPlease enter your name or pseudonym: ");
				threc[th].loginstatus=1;	// 1==we've issued login prompt.
				bufrec[th].input[0]=0;
				threc[th].incount=0;
				break;
			case 1: // 1=login prompt sent (get login chars until cr, process)
				namelen=strlen(bufrec[th].input); // how much in input now?
				buflen=strlen(bufrec[th].inbuf[threc[th].inptr]); // + this...

				if ((namelen + buflen) > 27)
				{
					show(th, "\r\n(Names are limited to 27 characters)");
					show(th, "\r\nPlease enter a name: ");
					bufrec[th].input[0]=0;	// start over...
					threc[th].incount=0;
					break;
				} else
				{
					strncpy(&bufrec[th].input[namelen],
						bufrec[th].inbuf[threc[th].inptr],
						27-namelen);
					bufrec[th].input[27]=0;
					namelen=strlen(bufrec[th].input);
					if (namelen > 1)
					if (bufrec[th].input[namelen-1]==10)
					// search database: if user isn't in database...
					{
						if (namelen<5)
						{
							show(th,"\r\nPlease use a longer name! (3+): ");
							bufrec[th].input[0]=0;	// start over...
							threc[th].incount=0;
							return;
						} else
						if ((bufrec[th].input[namelen-3]==' ')
								|| 	(bufrec[th].input[0]==' ')
								|| 	(strstr(bufrec[th].input,"  ")))
						{
							show(th, "\r\n(Names can't contain leading, trailing, or consecutive spaces)");
							show(th, "\r\nPlease enter a name: ");
							bufrec[th].input[0]=0;	// start over...
							threc[th].incount=0;
							break;
						}
						bufrec[th].input[namelen-2]=0; // trim off cr/lf
						strncpy(bufrec[th].logname,
							bufrec[th].input,
							27);
						bufrec[th].logname[27]=0;
						bufrec[th].input[0]=0;
						for (x=1;x<MAXUSERS;x++)
						{
							if (strncasecmp(bufrec[th].logname,
											d.user[x].name,27)==0)
							{
								ThreadToUser[th]=x;
								show(th, "\r\nPlease enter your password: ");
								threc[th].loginstatus=5;
								threc[th].noecho=1;
								x=MAXUSERS;
								breakflag=1;
								break;
							}
						}
						if (breakflag==1)
							break;
						threc[th].loginstatus=2;
						show(th, "\r\nAre you a new user? (Y/N): ");
					}
					break;
				}
			case 2: // 2=newuser prompt sent (is it y or Y? else back to 1)
				threc[th].incount=0;
				if (tolower(bufrec[th].inbuf[threc[th].inptr][0])=='y')
				{
					show(th, "\r\nPlease enter a password: ");
					threc[th].loginstatus=3;
					threc[th].noecho=1;
					break;
				}

				show(th, "\r\nPlease enter your name or pseudonym: ");
				threc[th].loginstatus=1;
				bufrec[th].input[0]=0;	// start over...
				threc[th].incount=0;
				break;
			case 3: // 3=password prompt sent (get pw chars, echo *, until cr)
			case 4: // 4=password prompt resent (get pw chars, echo *, until cr)
			case 5: // 5=password prompt sent, user # in ThreadToUser[th]
					// turn on '*' echo mode here...
				pwlen=strlen(bufrec[th].input); // how much in password now?
				buflen=strlen(bufrec[th].inbuf[threc[th].inptr]); // + this...
				if ((pwlen + buflen) > 15)
				{
					show(th, "\r\n(Passwords are limited to 15 characters)");
					show(th, "\r\nPlease enter a password: ");
					bufrec[th].input[0]=0;	// start over...
					threc[th].incount=0;
					break;
				} else
				{
					strncpy(&bufrec[th].input[pwlen],
						bufrec[th].inbuf[threc[th].inptr],
						15-pwlen);
					bufrec[th].input[15]=0;
					pwlen=strlen(bufrec[th].input);
					if (pwlen > 1)  					// password not null &&
					if (bufrec[th].input[pwlen-1]==10)	// [Enter] key is hit
					{
						if (pwlen<5)
						{
							show(th,"\r\nUse a longer password! (3+): ");
							bufrec[th].input[0]=0;		// start over...
							threc[th].incount=0;
							return;
						}
						bufrec[th].input[pwlen-2]=0;		// trim cr/lf
						strncpy(bufrec[th].logpword,
							bufrec[th].input,
							15);
						bufrec[th].logpword[15]=0;
						bufrec[th].input[0]=0;			// for verification
						if (threc[th].loginstatus==3) 	// first password copy
						{
							strncpy(bufrec[th].savpword,bufrec[th].logpword,15),
							bufrec[th].savpword[15]=0;
							bufrec[th].logpword[0]=0,	// for verification
							show(th, "\r\nPlease enter it again: ");
							threc[th].loginstatus=4,
							threc[th].noecho=1;
						}
						else				// verification password copy
						if (threc[th].loginstatus==4)
						{
							if (strncasecmp(bufrec[th].logpword,
								bufrec[th].savpword,15)==0)
							{
								// scan users here for dupe names!
								for (x=1;x<MAXUSERS;x++)
								{
									if (strncasecmp(bufrec[th].logname,
											d.user[x].name,27)==0)
									{
										show(th, "\r\n\nThat name is taken!\r\n\nPlease choose another name: ");
										bufrec[th].input[0]=0;	// start over...
										threc[th].incount=0;
										threc[th].loginstatus=1;
										threc[th].noecho=0;
										return;
									}
								}
								if (makenewuser(th)==0) // database full
								{
									threc[th].timeout=1;
									threc[th].logmeout=1;
									threc[th].lastaccess=time(NULL)-TIMEOUT+1;
									threc[th].loginstatus=0;
									return;
								}
								if (ThreadToUser[th]==1)
									show(th,"\r\n\nWelcome Admin!");
								else
									show(th,"\r\n\nWelcome Captain!");
								threc[th].radionumb=radionumber;
								threc[th].newsnumb=newsnumber;
								threc[th].loginstatus=6;
								threc[th].noecho=0;
								showsector(th);
								sprintf(tmp,
									"\r\nNews: %s launched a Starship",
									d.user[ThreadToUser[th]].name);
								makenews(th, tmp);
							}
							else
							{
								bufrec[th].logpword[0]=0;
								bufrec[th].savpword[0]=0;
								show(th, "\r\nPasswords do not match! ");
								show(th, "\r\nPlease enter a password: ");
								threc[th].loginstatus=3;
								threc[th].incount=0;
							}
						}
						else // existing user in database (loginstatus==5)
						{
							if (strncasecmp(bufrec[th].logpword,
								d.user[ThreadToUser[th]].pword,15)==0)
							{
								// kill old session, if it exists...
								if ((UserToThread[ThreadToUser[th]]>0)
							    	&& (UserToThread[ThreadToUser[th]] != th))
								{
									show(th,"\r\n\n*\n*\n*\n*\n*\n* Your last active session was killed! *\n*\n*\n*\n*\n*\a");
									threc[UserToThread[ // kill old thread
										ThreadToUser[th]]].logmeout=1;
									threc[UserToThread[ // kill old thread
										ThreadToUser[th]]].logoutmsg=4;
									dupe=1;
								}
								UserToThread[ThreadToUser[th]]=th;
								if (d.user[ThreadToUser[th]].lastcall/DAY
									!=time(NULL)/DAY)	// a new day
								{
									d.user[ThreadToUser[th]].logons=0;
									d.user[ThreadToUser[th]].timeused=0;
								}
	
								if (d.user[ThreadToUser[th]].timeused
									>= TIMELIMIT*60)
								{	// timelimit exceeded
									show(th,
										"\r\n\nYou're out of time today\r\n\n");
									threc[th].loginstatus=0;
									threc[th].logmeout=1;
									return;
								}
								updatefuel(th);
								if (dupe==0)// dupe not charged for logon,
								{			// inherits old session times
									d.user[ThreadToUser[th]].logons++;
									d.user[ThreadToUser[th]].timeused+=60;
									d.user[ThreadToUser[th]].lastcall
										=time(NULL);
								}

								if (d.user[ThreadToUser[th]].goods[0]==0)
								{
									d.user[ThreadToUser[th]].goods[0]=60;
									sprintf(tmp, "\r\nNews: %s was issued a replacement Starship",
										d.user[ThreadToUser[th]].name);
									makenews(th, tmp);
								}
								dirtyuser[ThreadToUser[th]]=1;
								if (ThreadToUser[th]==1)
									show(th,"\r\n\nWelcome back, Admin!");
								else
									show(th,"\r\n\nWelcome back, Captain!");
								threc[th].radionumb=radionumber;
								threc[th].newsnumb=newsnumber;
								threc[th].loginstatus=6;
								threc[th].noecho=0;
								sprintf(tmp,
									"\r\nNews: %s woke up",
									d.user[ThreadToUser[th]].name);
								makenews(th, tmp);
								if (showhistory(th)==0)
									showsector(th);
								break;
							}
							else
							{
								show(th,"\r\nIncorrect password!\r\n");
								show(th,"\r\nPlease enter your password: ");
								threc[th].loginstatus=5;
								threc[th].incount=0;
							}
						}
					}
					break;
				}
		}
	}
	return;
}

int 					// show the commands available to a logged in user
command(int th) 		// part of gameloop() thread
{
	//keep each show under 160 bytes, here and elsewhere if possible (MAXLINE)
	unsigned char tmp[DEFLINE];		// local buffer for misc use and messages
 	int x;
 	char menu[20][78]= {
 "",
 ".__move_around_the_galaxy__.____information____._________trading_________.",
 "| (1-6) move to new sector | (I)nventory       | (T)rade with port       |",
 "| (0) move via autopilot   | others (O)nline   | (S)ell goods to port    |",
 "| (-) random movement      | (P)layer listings | (L)and or Locate planet |",
 "| (=) same as (-), above   | show (E)vent log  | (J)ettison cargo        |",
 "| (>)largest sector number | (V)iew sector     |_________________________|",
 "| (<)smallest sector numb. |___________________|",
 "| (.)next larger number    |                     .____miscellaneous____.",
 "| (,)next smaller number   |    o----------o     | (Z) debugging info  |",
 "|______.               .___|    | choice:  |     | (^) admin commands  |",
 "       |____log_off____|        | commands |     | (?) show this menu  |",
 "       | (Q)uit now(!) |        |__________|     |_____________________|",
 "       |_______________|",
 "",
 "._radio_&_news_commands_.________________military_commands________________.",
 "| (R)adio transmit mode | (A)ttack ships/forces  (D)eploy/recall fighters |",
 "| ([) replay radio msgs | (B)uy fighters  ._______________________________|",
 "| (]) replay news msgs  |_________________|",
 "|_______________________|"};

	for (x=0;x<19;x+=2)
	{
		sprintf(tmp,"\r\n%s\r\n%s",menu[x], menu[x+1]);
		show(th,tmp);
	}
	threc[th].noshow=1;
	return(0);
}

int
infortn(int th) 		// part of gameloop() thread! (displays user inventory)
{
	unsigned char tmp[DEFLINE];		// local buffer for misc use and messages
	unsigned int user;				// local copies initialized below
	unsigned int now;
	if (inuse[th]>1)
	{
		now=time(NULL);
		user=ThreadToUser[th];

		sprintf(tmp,"\r\n\nName: %s,  Day %d.%d of the Epoch\r\nFighters: %d\r\nAntimatter: %d\r\n grams to move, port, land: %d"
				,d.user[user].name
				,now/DAY-12861	// ST Epoch began on day 12861 of Unix Epoch
				,now%DAY/CENTIDAY	
				,d.user[user].fiters
				,d.user[user].antitoday
				,santi(th, 1));
		show(th,tmp);

		sprintf(tmp,"\r\nMicrobots ($): %d\r\nReputation Points: %d\r\nCargo Holds: %d\r\n Iron: %d\r\n Alcohol: %d\r\n Hardware: %d"
				,d.user[user].cash
				,d.user[user].reppoints
				,d.user[user].goods[0]
				,d.user[user].goods[1]
				,d.user[user].goods[2]
				,d.user[user].goods[3]);
		show(th,tmp);
	}
	threc[th].noshow=1;
	return(0);
}

// radix-inspired, recursive, bit-plane sort that requires no data memory. 
// written for Starship Traders in 1998 because qsort was a memory hog. ;)
int
sortint(unsigned int array[],unsigned short int array2[],int numb,int bit)
{
	register int x; 
	unsigned int mask=1,zeros[2],count,y=0,z=0,hold,offset;
	zeros[0]=zeros[1]=0;offset=(32-bit);
	mask = (mask << offset);
	for (x=0;x<numb;x++) zeros[(array[x]&mask)>>offset]++; // count 0's & 1's
	if ((zeros[0]>0) && (zeros[1]>0))
	{
		z=numb-1;
		count=zeros[0];if (count>zeros[1]) count=zeros[1];	// use short list 
		for (x=0;x<count;x++) 								// reorder 
		{
			while ((((array[y]&mask)>>offset)==1)&&(y<z)) {y++;}// find next 0
			while ((((array[z]&mask)>>offset)==0)&&(z>0)) {z--;}// find prev 1
			if (z>y)
			{
				hold=array[y];  
				array[y]= array[z];  
				array[z]= hold; 
				hold=array2[y]; 
				array2[y]=array2[z]; 
				array2[z]=hold; 
			} else x=count; 			// exit early 
		}
	}

	// sort next bit plane in top block 
	if ((zeros[1]>1)&&(bit<32)) 
		sortint(array,array2,zeros[1],bit+1); 

	// sort next bit plane in bottom block 
	if ((zeros[0]>1)&&(bit<32)) 
		sortint(&array[zeros[1]], &array2[zeros[1]], zeros[0], bit+1); 

	return(0);
}

int				// player rankings... check timestamp, if over 5 seconds old,
players(int th)	// generate a fresh one, otherwise just reshow the last one.
{
	int x, linecount=1, showcount=0, input;
	unsigned char tmp[DEFLINE];			// local buffer for misc use
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='p')			// second, subsequent passes
	{
		if (input=='n')
		{
			threc[th].control=0;
			showsector(th);
			return(0);
		}
		else
			linecount=threc[th].status;
	}
	if ((time(NULL) - scoretime) > 5)
	{
		scoretime=time(NULL);
		for (x=1;x<MAXUSERS;x++) 
		{
			scores[x]=0;
			if (d.user[x].name[0])
			{
				scores[x]	 = d.user[x].cash;				// microbots
				scores[x]	+= d.user[x].reppoints;			// same as cash
				scores[x]	+=(d.user[x].antitoday/20);		// fuel: 20 gram / $
				scores[x]	+=(d.user[x].fiters*100);		// fighters cost 100
				scores[x]	+=(d.user[x].goods[0]*1000);	// cargo holds
				scores[x]	+=(d.user[x].goods[1]*6);		// iron
				scores[x]	+=(d.user[x].goods[2]*9);		// alch
				scores[x]	+=(d.user[x].goods[3]*12);		// hardware
			}
			scoresndx[x]=x;									// player number
		}
		for (x=1;x<=GAMESIZE;x++)		// add in deployed fighters
		{
			scores[d.sector[x].fitersowner] += (d.sector[x].fiters * 100);
		}
	}
	sortint(&scores[1],&scoresndx[1],MAXUSERS-1,1);
	sprintf(tmp,
		"\r\n\nRank  _______Player Name________  ___Score___  __Region__");
 	show(th,tmp);	
	for (x=linecount;x<MAXUSERS;x++)
	{
		if (scores[x] > 0)
		{
			sprintf(tmp,"\r\n%4d  %-27s  %10d  %8d's", x,
				d.user[scoresndx[x]].name, 
				scores[x],
				(((d.user[scoresndx[x]].sector-1)/BLOCKSIZE)*BLOCKSIZE)+1);
			show(th,tmp);
			linecount++;
			showcount++;
			if (showcount>LINES)
				break;
		}
	}
	if (x<MAXUSERS)
	{
		if (scores[x+1] > 0)
		{
    		show(th,"\r\n\nContinue listing? (Y/N) [Y]: ");
    		threc[th].control='p';
    		threc[th].status=x+1;
			return(0);
		}
	}
	threc[th].noshow=1;
	if (threc[th].control=='p')
	{
		threc[th].control=0;
		showsector(th);
	}
	return(0);
}

int			// show a user who else is currently logged in to the game
others(int th)
{
	int x, t, input, linecount=1, showcount=0;
	unsigned char tmp[DEFLINE];			// local buffer for misc use
	show(th,"\r\n");
	t=time(NULL);
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='o')			// second, subsequent passes
	{
		if (input=='n')
		{
			threc[th].control=0;
			showsector(th);
			return(0);
		}
		else
			linecount=threc[th].status;
	}
	for (x=linecount;x<MAXUSERS;x++)
	{
		if (inuse[UserToThread[x]] && (x==ThreadToUser[UserToThread[x]]))
		{
			sprintf(tmp,"\r\n %s, online for %d.%d minutes [%d's]",
				d.user[x].name, 
				(int)((t - d.user[x].lastcall)/60),
				(int)(((t - d.user[x].lastcall)%60)/6),
				(((d.user[x].sector-1)/BLOCKSIZE)*BLOCKSIZE)+1
				);
			show(th,tmp);
			linecount++;
			showcount++;
			if (showcount>LINES)
				break;
		}
	}
	if (x<MAXUSERS)
	{
   		show(th,"\r\n\nContinue listing? (Y/N) [Y]: ");
   		threc[th].control='o';
   		threc[th].status=x+1;
		return(0);
	}
	threc[th].noshow=1;
	if (threc[th].control=='o')
	{
		threc[th].control=0;
		showsector(th);
	}
	return(0);
}

int
viewsector(int th)			// do nothing, successfully... after each commlist
{							// indexed function, the sector is redisplayed.
	return(0);				// any invalid function (nullrtn) also will display
}							// the sector, but this one doesn't assume an error

int
showsector(int th) 		// part of gameloop() thread! (displays sector contents)
{
	unsigned char tmp[DEFLINE];		// local buffer for misc use
	unsigned char state[2][8]= { "asleep", "awake" };
	unsigned int x, awake;
	unsigned int sector,
				count=0,
				user,
				porttype,
				moontype;			// local copies initialized below
	sector=d.user[ThreadToUser[th]].sector;
	user=ThreadToUser[th];
	if (threc[th].noshow==0)
	{
		porttype=d.sector[sector].porttype;
		moontype=d.sector[sector].moontype;
		if (inuse[th]>1) 	// redundant
		{
			if (d.sector[sector].fiters)
			{
				if (d.sector[sector].fitersowner)
				{
					if (d.sector[sector].fitersowner==user)
						sprintf(tmp,"\r\n\n(You have %d fighters here)",
							d.sector[sector].fiters);
					else 	// enemy fighters!
					{
						sprintf(tmp,"\r\n\n(Sector: %d)\r\n\n(%d fighters attached to %s)",
							sector,
							d.sector[sector].fiters,
							d.user[d.sector[sector].fitersowner].name);
						show(th,tmp);
						atakrtn(th);
						return(0);
					}
				}
				else
					sprintf(tmp,"\r\n\n(%d neutral fighters)",
						d.sector[sector].fiters);
				show(th,tmp);	
			}
			sprintf(tmp,"\r\n\nSector: %d",sector);
			show(th,tmp);	
			if (porttype)
			{
				sprintf(tmp,"\r\n%s: selling %s",
					plantname[porttype],
					goodsname[porttype]);
				show(th,tmp);	
			}
			if (moontype)
			{
				sprintf(tmp,"\r\nPlanet: producing %s, %s, %s",
								goodsname[1],
								goodsname[2],
								goodsname[3]);
				show(th,tmp);	
			}
			// find other ships...
			for (x=1;x<MAXUSERS;x++)
			{
				if (d.user[x].sector==sector && ThreadToUser[th]!=x)
				{
					if (count==0)
					{
						sprintf(tmp,"\r\nOther ships:");
						show(th,tmp);
						count++;
					}
					awake=0;
					if (UserToThread[x] && (x==ThreadToUser[UserToThread[x]]))
						awake=1;
					sprintf(tmp,"\r\n  Captain %s with %d fighters [%s]",
							d.user[x].name, d.user[x].fiters, state[awake]);
					show(th,tmp);
					if (awake==1 &&
						(ThreadToUser[th]!=d.user[x].eship
						||
						d.user[ThreadToUser[th]].sector!=d.user[x].esect))
					{
						sprintf(tmp,
							"\r\n\n\aNotice: %s has your ship on-screen... ",
								d.user[ThreadToUser[th]].name);
						show(UserToThread[x],tmp);
						d.user[ThreadToUser[th]].eship=x;
						d.user[ThreadToUser[th]].esect=
										d.user[ThreadToUser[th]].sector;
						d.user[x].eship=ThreadToUser[th];
						d.user[x].esect=d.user[ThreadToUser[th]].sector;
						dirtyuser[ThreadToUser[th]]=1;
						dirtyuser[x]=1;
					}
				}
			}
			if (d.sector[sector].sectortype)
			{
				sprintf(tmp,"\r\n[%s]",
					sectorname[d.sector[sector].sectortype]); 
				show(th,tmp);
			}
			sprintf(tmp,"\r\nWarps go to: %u[1] %u[2] %u[3] %u[4] %u[5] %u[6]\r\n\n[%d] (mins: %d)  choice: ",
				d.sector[sector].warp[0],
				d.sector[sector].warp[1],
				d.sector[sector].warp[2],
				d.sector[sector].warp[3],
				d.sector[sector].warp[4],
				d.sector[sector].warp[5],
				sector,
				TIMELIMIT - (int)(1+(d.user[user].timeused 
					+ time(NULL) 
					- d.user[user].lastcall)/60));
			show(th,tmp);
		}
	}
	else
	{
		if (threc[th].noshow < 2)
		{
			sprintf(tmp,"\r\n\n[%d] (mins: %d)  choice: ", 
				sector,
				TIMELIMIT - (int)(1+(d.user[user].timeused 
					+ time(NULL) 
					- d.user[user].lastcall)/60));
			show(th,tmp);
		}
		threc[th].noshow=0;
	}
	return(0);
}

int
haltsys(int th)
{
	char tmp[DEFLINE];
	sprintf(tmp,"\r\n\nYou are player #%d",ThreadToUser[th]);
	show(th,tmp);
	if (ThreadToUser[th]==1)
	{
		if (time(NULL)-shutdowntime < 5)
			shutdownnow=1;
		else
		{
			show(th,"\r\n\nPress ^ again within 4 seconds to halt the game");
			shutdowntime = time(NULL);
		}
	}
	return(0);
}

int 							// process logoff requests
hangup(int th)					// kill io threads for th, exit.
{								// coordinate flush of backup thread...someday
	char input;					// local copy, initialized below
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='q')
	{
		if (threc[th].status==1)// q pressed... needs 'y'
		{
			if (input=='y' || input=='!')
			{
				threc[th].logmeout=1;
				threc[th].logoutmsg=1;
			}
			else
				showsector(th);
		}
		threc[th].control=0;
		threc[th].status=1;
		return(0);
	}
	if (input=='q')
	{
		show(th,"\r\n\nDo you really want to log off? (Y/N) [N]: ");
		threc[th].control='q';
		threc[th].status=1;
	}
	else						// hangup now!
	{
		show(th,"\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\r\n\n\n\n\n\r\n\n\n\n\n\r\n\n\n\n\n\n\n\n");
		threc[th].logmeout=1;
		threc[th].logoutmsg=1;
		threc[th].noshow=2;
	}
	return(0);
}

int
autowarp(int th)
{
	threc[th].autowarp=2;
	while(threc[th].autowarp)
	{
		if (bufrec[th].input[threc[th].autowarp]>'0' 
				&& bufrec[th].input[threc[th].autowarp]<'7' 
				&& threc[th].autowarp<=MAXDEPTH+1)
		{
			warprtn(th);
			threc[th].autowarp++;
		}
		else
		{
			threc[th].autowarp=0;
			break;
		}
	}
	bzero(bufrec[th].input,DEFLINE);
	bzero(&targetpath[0], sizeof(int)*100);
	return(0);
}

int 	// process user request to move to a new sector via '1,2,3,4,5,6'
warprtn(int th)					// and move ship via  < , . > - =  commands
{
	unsigned int sector, input;	// local copies, initialized below
	unsigned int count=0;		// number of warps in sector
	unsigned int x;				// local subscript
	if (d.user[ThreadToUser[th]].antitoday < 1)
	{
		show(th,"\r\n\n\aYou're out of antimatter!");
		threc[th].autowarp=0;
		return(1);
	}
	sector=d.user[ThreadToUser[th]].sector;
	for (x=0;x<6;x++) 			// count warps out of this sector
		if (d.sector[sector].warp[x])
			count++;
	if (count) count--;			// turn count into a 0-relative index
	if (threc[th].autowarp)
		input=(int)bufrec[th].input[threc[th].autowarp];
	else
		input=(int)bufrec[th].inbuf[threc[th].inptr][0]; // unchanged input
	if (input > 48 && input < 55)
		input=input - 48;// returns 0-9
	switch (input)
	{
		case '=':									// same as '-'
		case '-': d.user[ThreadToUser[th]].sector 	// random
					= d.sector[sector].warp[rand16(count+1)];
				break;
		case ',': if (count)						// next smaller
					d.user[ThreadToUser[th]].sector
						= d.sector[sector].warp[2];
				break;
		case '.': if (count)						// next larger
					d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[count - 2];
				break;
		case '>': d.user[ThreadToUser[th]].sector 	// largest
					= d.sector[sector].warp[count];
				break;

		case '<': d.user[ThreadToUser[th]].sector 	// smallest
					= d.sector[sector].warp[0];
				break;

		// cases 1-6 are to move through warps 0-5
		case 1: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[0];
				break;
		case 2: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[1];
				break;
		case 3: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[2];
				break;
		case 4: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[3];
				break;
		case 5: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[4];
				break;
		case 6: d.user[ThreadToUser[th]].sector
					= d.sector[sector].warp[5];
				break;
	}
	if (d.user[ThreadToUser[th]].sector != sector)
	{
	    d.user[ThreadToUser[th]].lastsector = sector;
		santi(th, 0);
	    dirtyuser[ThreadToUser[th]]=1;
		if (threc[th].autowarp)
		{
			if (bufrec[th].input[threc[th].autowarp+1]==0)
				threc[th].noshow=2;
			showsector(th);
		}
	}
	return(0);
}

int			// a user attacks another ship.  (called from atakrtn function)
atakship(int th)
{
	int input, user, fleet, limit, sector;// local copies, initialized below
	int inputlen, x=0, victim=0, start=0, killed=0;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);

	// deployed fighters guarding this sector... can't attack ships!
	if (d.sector[sector].fiters && d.sector[sector].fitersowner != user)
	{
		show(th,"\r\n\nYou must destroy the deployed fighters here first!");
		return(0);
	}

	// initial arrival here... initialize status, data, fall through
	if (input=='~' || input=='a')
	{
		threc[th].data=0;
		threc[th].status=0;
	}

	// data is already initialized: we've found our victim, fall through
	if (threc[th].status>0)
		start=threc[th].status;

	// we have victim, need to know how many to attack with, fall through
	if (threc[th].data>0)
		accumulateinput(th);	// accumulate digits and fall through
	else
	{							// else, initialize input array, input counter
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}

	// user said 'yes' to attacking 'status': load userid into 'data'
	if (threc[th].status > 0 && input=='y')
	{
		victim=threc[th].data=threc[th].status;
		limit=(int)(d.user[user].fiters);
		sprintf(tmp,
			"\r\n\nAttack %s with how many fighters? (0-%d) [0]: ",
				d.user[victim].name,limit);
		show(th,tmp);
		if (UserToThread[victim] && // victim is awake!
			(victim==ThreadToUser[UserToThread[victim]]))
			sprintf(tmp,
				"\r\n\nWarning! %s has a Laser target lock on your ship...",
						d.user[user].name);
			show(UserToThread[victim],tmp);
		return(0);
	}

	// check for ships
	if (threc[th].data==0 && input!='y')
	{
		start=threc[th].status;
		if (start==0)
			start=1;
		else
			start++;
		for (x=start;x<MAXUSERS;x++)
		{
			if (d.user[x].sector==sector && user!=x)
			{
				if (UserToThread[x] && // victim is awake!
					(x==ThreadToUser[UserToThread[x]]))
				{
					sprintf(tmp,"\r\n\nAlert: %s is scanning your ship... ",
						d.user[user].name);
					show(UserToThread[x],tmp);
				}
				sprintf(tmp,"\r\n\nAttack %s? (Y/N) [N]: ",
						d.user[x].name);
				show(th,tmp);
				threc[th].control='~';
				threc[th].status=x;
				x=MAXUSERS;
				return(0);
			}
		}
	}

	// no ships, or no _more_ ships found to attack...
	if (threc[th].status < start)
	{
		show(th,"\r\n\nThere is nothing else to attack here!");
		if (threc[th].status) showsector(th);
		bufrec[th].input[0]=0;
		threc[th].control=0;
		threc[th].status=0;
		return(0);
	}

	// attack the poor victim -- but notify victim if he's logged in
	limit=(int)(d.user[user].fiters);
	inputlen=strlen(bufrec[th].input);
	victim=threc[th].data;
	if (threc[th].control=='~' && inputlen > 1 && victim>0)
	{
		if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13))
		{
			if (UserToThread[victim] && // victim is awake!
				(victim==ThreadToUser[UserToThread[victim]]))
				sprintf(tmp, "\r\n\nDanger! Incoming %s Fighters... ",
					d.user[user].name);
				show(UserToThread[victim],tmp);
			fleet=atoi(bufrec[th].input);
			sprintf(tmp,
				"\r\n\nattacking %s with %d fighters... ",
					d.user[victim].name,fleet);
			show(th,tmp);
			if (fleet > limit)
			{
				sprintf(tmp,
					"\r\n\nToo few fighters: sending %d fighters... ",
						limit);
				show(th,tmp);
			}
			if (d.user[victim].sector != sector)
			{
				show(th,"\r\n\nThe enemy ship dodged your attack!");
				threc[th].status=0;
				threc[th].control=0;
				bufrec[th].input[0]=0;
				showsector(th);
				return(0);
			}
			fleet=min(limit,atoi(bufrec[th].input));
			if (fleet > 0)
			{
				do
				{
					if (rand16(2)==1)				// we won a dogfight
					{
						d.user[victim].fiters--;	// victim lost 1 fighter
						killed++;
						d.user[user].reppoints+=100;// add to our reputation
						d.user[victim].reppoints+=50;// compensate vic rep some
					}
					else							// victim won that fight
					{
						d.user[user].fiters--;		// We lost one fighter
						fleet--;					// deduct it from fleet
					}
				} while (d.user[victim].fiters>(-1) // attack until ship's dead
						&& d.user[user].fiters>0	// or we still have figs
						&& fleet > 0);				// in our attack fleet
	    		dirtyuser[user]=1;
			}
			if (killed)
			{
				sprintf(tmp,
					"\r\n\nLog: You destroyed %d enemy fighters... ",
						killed);
				show(th,tmp);
	    		dirtyuser[victim]=1;
			}
			if (UserToThread[victim] && // if victim is awake...
				(victim==ThreadToUser[UserToThread[victim]]))
			{
				if (killed)				// victim lost some fighters
				{
					sprintf(tmp,
						"\r\n\nLog: %s destroyed %d of your fighters... ",
							d.user[user].name, killed);
				}
				else					// victim lost no fighters -- inform
					sprintf(tmp,
						"\r\n\nLog: No damage taken in the attack... ");
				show(UserToThread[victim],tmp);
			}
			else
			{
				if (killed)
				{
					sprintf(tmp,"\r\n%s killed %d of your fighters",
						d.user[user].name,
						killed);
					makehistory(user,victim,tmp);
	    			dirtyuser[victim]=1;
				}
			}
			if (d.user[victim].fiters<0)		// victim ship was destroyed
			{									// init. new ship, location...
				d.user[victim].reppoints = d.user[victim].reppoints/2;
				d.user[victim].goods[0]=0;
				d.user[victim].sector=rand24(GAMESIZE)+1;
				d.user[victim].lastsector=1;
				d.user[victim].fiters=0;
				d.user[victim].fiters=0;
				d.user[victim].esect=0;
				d.user[victim].eship=0;
				d.user[victim].cash=600;
				d.user[victim].goods[0]=60; 		// cargo holds
				d.user[victim].goods[1]=20;
				d.user[victim].goods[2]=20;
				d.user[victim].goods[3]=20;
	    		dirtyuser[victim]=1;
				show(th,"\r\n\nLog: You destroyed the enemy ship... ");

				// broadcast news out over the news system for all to see
				sprintf(tmp,
						"\r\nNews: %s destroyed %s's ship",
						d.user[ThreadToUser[th]].name,
						d.user[victim].name);
				makenews(th, tmp);

				if (UserToThread[victim] && // victim is awake!
					(victim==ThreadToUser[UserToThread[victim]]))
				{
					sprintf(tmp,"\r\n\nLog: %s destroyed your ship... ",
						d.user[user].name);
					show(UserToThread[victim],tmp);
					sprintf(tmp,
						"\r\nNews: %s was issued a replacement Starship",
						d.user[victim].name);
					makenews(th, tmp);
				}
				else
				{
					d.user[victim].goods[0]=0; 		// cargo holds
					sprintf(tmp,"\r\n%s destroyed your flagship",
						d.user[user].name);
					makehistory(user,victim,tmp);
	    			dirtyuser[victim]=1;
				}
			}
			threc[th].status=0;	// all done here.  cleanup and display sector
			threc[th].control=0;
			bufrec[th].input[0]=0;
			showsector(th);
		}
	}
	return(0);
}

// this function is used to attack deployed fighters.  If there are no fighters
// in the sector, it passes control over to the atakship() function
int
atakrtn(int th)
{
	int input, user, fleet, limit, sector;// local copies, initialized below
	int inputlen, dfiters=0, killed=0;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='a' && threc[th].status==1)
		accumulateinput(th);
	else
	{
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}
	if (d.sector[sector].fiters && d.sector[sector].fitersowner != user)
		dfiters=d.sector[sector].fiters;	// attack deployed fighters
	else	// no deployed fighters here... pass control to atakship()
	{
		threc[th].control='~';
		atakship(th);
		return(0);
	}

	limit=(int)(d.user[user].fiters);
	inputlen=strlen(bufrec[th].input);

	// attack deployed fighters...
	//if (dfiters>0 && input=='a')
	if (dfiters>0 && threc[th].control==0)
	{
		threc[th].control='a';
		threc[th].status=1;
		threc[th].autowarp=0;
		sprintf(tmp,
			"\r\n\n(Retreat or attack deployed fighters)\r\n\nAttack with how many fighters? (0-%d, R to Retreat) [0]: ",
						limit);
		show(th,tmp);
		bufrec[th].input[0]=0;
		threc[th].control='a';
	} else
	if (threc[th].control=='a' && inputlen == 1)
	{
		if (tolower(bufrec[th].input[0])=='r')
		{
			d.user[user].sector=d.user[user].lastsector;
			d.user[user].lastsector=sector;
	    	dirtyuser[user]=1;
			threc[th].control=0;		// all done here.  cleanup, show sector
			bufrec[th].input[0]=0;
			threc[th].status=0;
			showsector(th);
			return(0);
		}
	}
	else
	if (threc[th].control=='a' && inputlen > 1)
	{
		if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13))
		{
			fleet=atoi(bufrec[th].input);
			sprintf(tmp,
				"\r\n\nattacking deployed fighters with %d fighters...",
					fleet);
			show(th,tmp);
			if (fleet > limit)
			{
				sprintf(tmp,
					"\r\n\nToo few fighters: sending %d fighters...", limit);
				show(th,tmp);
			}
			fleet=min(limit,atoi(bufrec[th].input));
			if (fleet > 0)
			{
				do
				{
					if (rand16(2)==1)				// attacker won a dogfight!
					{
						killed++;					// count the dead enemies
						d.sector[sector].fiters--;	// decrement sector fleet
						d.user[user].reppoints+=100;// user earned some rep.
					}
					else
					{
						d.user[user].fiters--;		// attacker lost a fighter
						fleet--;					// decrement attack fleet
					}
				} while (d.sector[sector].fiters>0	// attack until no sec figs
						&& d.user[user].fiters>0	// or we're outa fighters
						&& fleet > 0);				// or til attack fleet gone
	    		dirtyuser[user]=1;
				dirty(sector);
			}
			if (killed)
			{
				sprintf(tmp,
					"\r\n\nLog: You destroyed %d deployed fighters... ",
						killed);
				show(th,tmp);
				sprintf(tmp,"\r\n%s killed %d fighters at %d",
					d.user[user].name,
					killed, 
					sector);
				makehistory(user,d.sector[sector].fitersowner,tmp);
			}
			threc[th].control=0;		// all done here.  cleanup, show sector
			bufrec[th].input[0]=0;
			threc[th].status=0;
			showsector(th);
		}
	}
	return(0);
}

int
autopilot(int th)
{
	int sector, input, user, target;// local variables, initialized below
	int inputlen, upperlimit, lowerlimit;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	upperlimit=min(sector+1000,GAMESIZE);
	lowerlimit=max(1,sector-1000);
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='0')
		accumulateinput(th);
	else
	{
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}
	inputlen=strlen(bufrec[th].input);
	if (threc[th].control=='0' && inputlen > 1)	//second+ pass: got number yet?
	{
		if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number.
		{
			target=atoi(bufrec[th].input);
			sprintf(tmp, "\r\n\ncomputing path to sector %d...", target);
			show(th,tmp);
			if (target > upperlimit || target < lowerlimit)
			{
				sprintf(tmp,
					"\r\n\n * Autopilot: Internal limit exceeded! *");
				show(th,tmp);
				threc[th].control=0;
				showsector(th);
				return(0);
			}
			pathdepth(th, sector, target);
			threc[th].control=0;
			bufrec[th].input[0]=0;
			showsector(th);
		}
	}
	else if (input=='0')	// first pass through function... prompt for number
	{
		sprintf(tmp,
			"\r\n\nAutopilot: Where do you want to go today? (%d-%d): ",
				lowerlimit,upperlimit);
		show(th,tmp);
		bufrec[th].input[0]=0;
		threc[th].control='0';
	}
	return(0);
}

// let user buy fighters with microbots.  This should be replaced someday
// with a 'build fighters' routine that uses microbots and consumes iron
int
buyfiters(int th)
{
	int input, user, buy, limit;	// local variables, initialized below
	int inputlen;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='b')
		accumulateinput(th);
	else
	{
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}
	inputlen=strlen(bufrec[th].input);
	limit=(int)(d.user[user].cash/100);
	if (threc[th].control=='b' && inputlen > 1)	//second+ pass: got number yet?
	{
		if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number.
		{
			buy=atoi(bufrec[th].input);
			sprintf(tmp, "\r\n\nBuying %d fighters...", buy);
			show(th,tmp);
			if (buy > limit)	// number exceeded cash-limited max purchase
			{
				sprintf(tmp,
					"\r\n\nNot enough money... buying %d fighters...", limit);
				show(th,tmp);
			}
			buy=min(limit,atoi(bufrec[th].input));	// buy smaller of number
			if (buy > 0)							// requested or cash-limied
			{										// maximum
				d.user[user].fiters += buy;
				d.user[user].cash -= (buy*100);
	    		dirtyuser[user]=1;
			}
			threc[th].control=0;
			bufrec[th].input[0]=0;
			showsector(th);
		}
	}
	else if (input=='b')	// first pass through function... prompt for number
	{
		sprintf(tmp,
			"\r\n\nHow many fighters will you buy @ 100/each? (0-%d) [0]: ",
						limit);
		show(th,tmp);
		bufrec[th].input[0]=0;
		threc[th].control='b';
	}
	return(0);
}

// dump cargo (to empty holds), probably to scoop some more useful stuff from
// planet, but maybe just to increase fuel economy for a long journey
int
jettison(int th)
{
	char input;					// local copy, initialized below
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if ((d.user[ThreadToUser[th]].goods[1]+
			d.user[ThreadToUser[th]].goods[2]+
			d.user[ThreadToUser[th]].goods[3]) == 0)
	{
		show(th,"\r\n\nYou have no cargo to jettison!");
		return(0);
	}
	if (threc[th].control=='j')
	{
		if (input=='y')
		{
			d.user[ThreadToUser[th]].goods[1]=0;
			d.user[ThreadToUser[th]].goods[2]=0;
			d.user[ThreadToUser[th]].goods[3]=0;
			dirtyuser[ThreadToUser[th]]=1;
			show(th,"\r\n\nCargo Jettisoned!");
		}
		threc[th].control=0;
		showsector(th);
	}
	else
	if (input=='j')
	{
		show(th,"\r\n\nDo you really want to jettison cargo? (Y/N) [N]: ");
		threc[th].control='j';
	}
	return(0);
}

int
resetnews(int th)			// reset newsnumb to redisplay old news
{							// each idle pass of gameloop's main loop scans
	threc[th].showmynews=1;	// news and mail for new entries. This makes all
	threc[th].newsnumb=0;	// the old news new again
	return(0);
}

int
resetradio(int th)			// reset radionumb to redisplay old messages
{
	threc[th].showmyrad=1;
	threc[th].radionumb=0;
	return(0);
}

int
deploy(int th)				// deploy fighters to guard sector (gameloop)
{
	int input, buy, user, limit, sector;
	int inputlen;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (d.sector[sector].sectortype)
	{
		show(th,"\r\n\nYou can't deploy fighters in a nebula!");
		return(0);
	}	
	if (d.sector[sector].fiters && d.sector[sector].fitersowner != user)
	{
		show(th,"\r\n\nYou must destroy the fighters here first!");
		return(0);
	}	
	if (threc[th].control=='d')
		accumulateinput(th);
	else
	{
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}
	inputlen=strlen(bufrec[th].input);
	limit=(int)(d.user[user].fiters + d.sector[sector].fiters);
	if (threc[th].control=='d' && inputlen > 1)	//second+ pass: got number yet?
	{
		if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number.
		{
			buy=atoi(bufrec[th].input);
			if (buy>=0)
				sprintf(tmp, "\r\n\nDeploying %d fighters...", buy);
			else
				sprintf(tmp, "\r\n\nThat's impossible!");
			show(th,tmp);
			if (buy > limit)	// number exceeded cash-limited max purchase
			{
				sprintf(tmp,
					"\r\n\nToo few fighters... deploying %d...", limit);
				show(th,tmp);
			}
			buy=min(limit,atoi(bufrec[th].input));	// deploy smaller of number
			if (buy >= 0)							// requested or maximum
			{
				d.user[user].fiters = limit - buy;
				d.sector[sector].fiters = buy;
				d.sector[sector].fitersowner = user;
				d.sector[sector].fiterscode = 253;
				if (buy==0)
				{
					d.sector[sector].fitersowner = 0;
					d.sector[sector].fiterscode = 0;
				}
				dirtyuser[user]=1;
				dirty(sector);
			}
			threc[th].control=0;
			bufrec[th].input[0]=0;
			showsector(th);
		}
	}
	else if (input=='d')	// first pass through function... prompt for number
	{
		sprintf(tmp,
			"\r\n\nHow many fighters will you deploy here? (0-%d) [0]: ",
						limit);
		show(th,tmp);
		bufrec[th].input[0]=0;
		threc[th].control='d';
	}
	return(0);
}

int 				// process invalid/unimplemented command requests
nullrtn(int th)		// ( show help hint )
{
	show(th,"\r\n\n(Type ? for command list)");
	return(0);
}

int // call pathcalc with increasing depth until destination is found
pathdepth(int th, int sector, int target)
{
	int depth=0, len=0, x;
	char tmp[200];
	bzero(tmp,200);
	bzero(bufrec[th].input,DEFLINE);
	bzero(&targetpath[0], sizeof(int)*100);
	targetfound=0;
	while (targetfound==0 && depth < MAXDEPTH)
	{
		bzero(sectorused,6008);
		pathcalc(th, sector, depth, 0, target, 0);
		depth++;
	}
	if (targetfound)
	{
		for (x=2;x<=depth;x++) // 1 is starting sector, 0 is unused
		{
			len=strlen(tmp);
			sprintf(&tmp[len],"%d;",targetpath[x]);
		}
		len=strlen(tmp);
		sprintf(&tmp[len],"\r\n\n(Type / to autowarp there)");
		show(th,tmp);
		//show(th,&bufrec[th].input[2]);
		return(0);
	}
	else
		show(th,"\r\n\n * There is no path to that sector! *");
	return(1);
}

int 
pathcalc(int th, int sector, int depth, int mydepth, int target, int ndx)
{
	register int x;
	if (target==0 && d.sector[sector].moontype)
	{
		targetpath[mydepth+1]=sector;
		bufrec[th].input[mydepth+1]=ndx;
		targetfound=1;
	}
	else
	if (sector==target) 
	{
		targetpath[mydepth+1]=sector;
		bufrec[th].input[mydepth+1]=ndx;
		targetfound=1;
	}
	else
	if (mydepth==depth)
		return(0);
	else					// try the next level of connections
	{
		mydepth++;
		sectorused[sector%6000]=MAXDEPTH-mydepth;
		for (x=0;x<6;x++)
		{
			if (target==0)
			{
				pathcalc(th, d.sector[sector].warp[x], depth, mydepth, 
											target, x+49);
			}
			else
			{
				if ((d.sector[sector].warp[x]) 
					&& (sectorused[d.sector[sector].warp[x]%6000]
							<MAXDEPTH-mydepth)
					&& (d.sector[sector].warp[x] < (target + 2990))
					&& (d.sector[sector].warp[x] > (target - 2990)))
					pathcalc(th, d.sector[sector].warp[x], 
							depth, mydepth, target, x+49);
			}
			if (targetfound)
				break;
		}
	}
	if (targetfound)
	{
		targetpath[mydepth]=sector;
		bufrec[th].input[mydepth]=ndx;
	}
	return(0);
}

void	// calculate planet inventories and update planet calculation date
mooncalc(int sector)
{
	int goodprod, hours, limit, max, x;
	time_t now;
	if (d.sector[sector].moontype != 1) // no normal planet here
		return;
	now=time(NULL);
	limit=GAMECYCLE * 24;
	hours = (now - d.sector[sector].mooncalcdate)/3600;
	hours = min(hours,limit);
	if (hours)
	{
		for (x=1;x<4;x++)
		{
			max = GAMECYCLE * d.sector[sector].moonprod[x];
			goodprod = (d.sector[sector].moonprod[x] * hours) / 24;
			d.sector[sector].moongoods[x] =
				min(goodprod + d.sector[sector].moongoods[x], max);
		}
		d.sector[sector].mooncalcdate=now;
		dirty(sector);
	}
	return;
}

void	// calculate port inventories and update port calculation date
portcalc(int sector)
{
	int goodprod, hours, limit, max, x;
	time_t now;
	if ((d.sector[sector].porttype<1) || (d.sector[sector].porttype>3))
		return; 					// no commodity port here
	now=time(NULL);
	limit=GAMECYCLE * 24;
	hours = (now - d.sector[sector].portcalcdate)/3600;
	hours = min(hours,limit);
	if (hours)
	{
		for (x=1;x<4;x++)
		{
			max = GAMECYCLE * d.sector[sector].portprod[x];
			goodprod = (d.sector[sector].portprod[x] * hours) / 24;
			d.sector[sector].portgoods[x] =
				min(goodprod + d.sector[sector].portgoods[x], max);
		}
		d.sector[sector].portcalcdate=now;
		dirty(sector);
	}
	return;
}

int		// process user request to land on a planet and take free goods
landrtn(int th)
{
	int sector, trade, empties, porttype, user, max, x;
	char tmp[DEFLINE];
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	porttype = d.sector[sector].porttype;
	if (d.sector[sector].moontype != 1)
	{
		show(th,"\r\n\nThere is no planet in this sector! ");
		pathdepth(th, sector, 0);
		return(0);
	}
	empties = d.user[user].goods[0] - (d.user[user].goods[1]
										+ d.user[user].goods[2]
										+ d.user[user].goods[3]);
	if (empties==0)
	{
		show(th,"\r\n\nYou have no empty cargo holds!");
		return(0);
	}
	santi(th, 0);	// deduct antimatter for porting...
	sprintf(tmp,"\r\n\nLanding on Planet . . .\r\n");
	show(th,tmp);
	mooncalc(sector);

	sprintf(tmp,"\r\n(You have: %d %s, %d %s, %d %s)\r\n",
					d.user[user].goods[1],goodsname[1],
					d.user[user].goods[2],goodsname[2],
					d.user[user].goods[3],goodsname[3]);
	show(th,tmp);

	// show inventories...
	for (x=3;x>0;x--)
	{
		sprintf(tmp,"\r\n  There are %5d holds of %8s",
						d.sector[sector].moongoods[x],
						goodsname[x]);
		show(th,tmp);
	}
	for (x=3;x>0;x--)
	{
		if (porttype != x)
		{
			max=min(d.sector[sector].moongoods[x],	// lesser of: planet stock
					d.sector[sector].portgoods[x]);	//			or port needs
			trade = min(empties, max);
			d.user[user].goods[x] += trade;			// trade:	add
			dirtyuser[user]=1;
			d.sector[sector].moongoods[x] -= trade;	// 			subtract
			dirty(sector);
			if (trade)
			{
				sprintf(tmp,"\r\n\n(You took %d holds of %s)",
							trade,goodsname[x]);
				empties = d.user[user].goods[0] - (d.user[user].goods[1]
												+ d.user[user].goods[2]
												+ d.user[user].goods[3]);
				show(th,tmp);
			}
		}
	}
	if (empties && porttype)	// got non-porttype goods, still not full
	{
		max=d.sector[sector].moongoods[porttype];
		trade = min(empties, max);
		d.user[user].goods[porttype] 		+= trade;	// trade:	add
		dirtyuser[user]=1;
		d.sector[sector].moongoods[porttype] -= trade;	// 			subtract
		dirty(sector);
		if (trade)
		{
			sprintf(tmp,"\r\n\n(You took %d holds of %s)",
						trade,goodsname[porttype]);
			show(th,tmp);
		}
	}
	return(0);
}

int		// process user request to dock with a port (to trade goods)
portrtn(int th)
{
	int sector, trade, empties, porttype, user, max, sellonly=0;
	int x, goodsbaseprice[4], goodsprice[4];
	char buysell[2][8] = {"buying", "selling"};
	char tmp[DEFLINE];
	if (tolower(bufrec[th].inbuf[threc[th].inptr][0])=='s')
		sellonly=1;
	user=ThreadToUser[th];
	sector=d.user[user].sector;
	porttype = d.sector[sector].porttype;
	if (porttype == 0)
	{
		show(th,"\r\n\nThere is no port in this sector!\r\n");
		return(0);
	}
	if (porttype > 3)
		return(0);	// s/b TPTrade()...
	santi(th, 0);	// deduct antimatter for porting...

	sprintf(tmp,"\r\n\nDocking with %s . . .\r\n",
				plantname[porttype]);
	show(th,tmp);
	portcalc(sector);

	sprintf(tmp,"\r\n(You have: %d %s, %d %s, %d %s)\r\n",
					d.user[user].goods[1],goodsname[1],
					d.user[user].goods[2],goodsname[2],
					d.user[user].goods[3],goodsname[3]);
	show(th,tmp);

	// show inventories
	for (x=1;x<4;x++)
	{
		goodsbaseprice[x] = 3 + (x * 3);
		if (d.sector[sector].porttype==x)
            goodsprice[x]=goodsbaseprice[x]
				- ((goodsbaseprice[x]*2/3) * d.sector[sector].portgoods[x])
				/ (GAMECYCLE * d.sector[sector].portprod[x]);
		else
            goodsprice[x]=goodsbaseprice[x]
				+ (goodsbaseprice[x]*d.sector[sector].portgoods[x])
				/ (GAMECYCLE * d.sector[sector].portprod[x]);
		sprintf(tmp,"\r\n  %7s up to %5d holds of %8s for $%2d/hold",
						buysell[x==porttype],
						d.sector[sector].portgoods[x],
						goodsname[x],
						goodsprice[x]);
		show(th,tmp);
	}

	// now sell some goods
	for (x=1;x<4;x++)
	{
		if (porttype!=x)
		{
			trade=min(d.sector[sector].portgoods[x], d.user[user].goods[x]);
			if (trade > 0)
			{
				d.user[user].goods[x] -= trade;
				d.sector[sector].portgoods[x] -= trade;
				dirty(sector);
				d.user[user].cash += (trade * goodsprice[x]);
				dirtyuser[user]=1;
				sprintf(tmp,"\r\n\n(You sold %d holds of %s for $%d/hold)",
					trade, goodsname[x], goodsprice[x]);
				show(th,tmp);
			}
		}
	}

	// now buy some goods...
	if (sellonly==0)
	{
		empties = d.user[user].goods[0] - (d.user[user].goods[1]
											+ d.user[user].goods[2]
											+ d.user[user].goods[3]);
		max = d.user[user].cash / goodsprice[porttype];	// cash limited
		trade = min(empties, max);
		trade = min(trade,d.sector[sector].portgoods[porttype]);
		if (trade > 0)
		{
			d.user[user].goods[porttype] += trade;		// do lesser trade...
			d.sector[sector].portgoods[porttype] -= trade;
			dirty(sector);
			d.user[user].cash -= (trade * goodsprice[porttype]);
			dirtyuser[user]=1;
			sprintf(tmp,"\r\n\n(You bought %d holds of %s for $%d/hold)",
					trade, goodsname[porttype], goodsprice[porttype]);
			show(th,tmp);
		}
	}
	return(0);
}

void // update a users fuel inventory as new fuel is issued each minute
updatefuel(int th)	// if this hurts backup of busy games, up to 10 mins
{
	int fuelminute, minutes;
	time_t now;
	now=time(NULL);
	fuelminute=FUELPERDAY/1440;
	minutes = (now/60 - d.user[ThreadToUser[th]].lastfuel);
	if (minutes>0)
	{
		d.user[ThreadToUser[th]].antitoday += (fuelminute * minutes);
		d.user[ThreadToUser[th]].lastfuel = (now/60);
		dirtyuser[ThreadToUser[th]]=1;
	}
	return;
}

// keep radio displays+header under RADLINE size to fit in radiorec[]
int 				// send radio message to other logged in players
radiortn(int th)	// part of gameloop() thread! msg from any user
{
	int input, inputlen, sent=0, crsent;
	char tmp[DEFLINE];
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='r' && threc[th].status>0)
		accumulateinput(th);
	else
	{
		bzero(bufrec[th].input,DEFLINE);
		threc[th].incount=0;
	}
	if (bufrec[th].input[0]=='[')	// redisplay past radio messages
	{
		threc[th].showmyrad=1;
		threc[th].radionumb=0;
		bufrec[th].input[0]=0;
		threc[th].incount=0;
		threc[th].control='r';
		threc[th].status=1;
	}
	if (bufrec[th].input[0]==']')	// redisplay past news messages
	{
		threc[th].showmynews=1;
		threc[th].newsnumb=0;
		bufrec[th].input[0]=0;
		threc[th].incount=0;
		threc[th].control='r';
		threc[th].status=1;
	}
	inputlen=strlen(bufrec[th].input);
	if (inputlen) threc[th].status=2;
	if (threc[th].control=='r' && inputlen > 1)
	{
		if ((inputlen > 77) || (bufrec[th].input[inputlen-2]==13))
		{
			bufrec[th].input[78]=0;
			crsent=0;
		    if (bufrec[th].input[inputlen-2]==13)
			{
				bufrec[th].input[inputlen-2]=0;
				crsent=1;
			}
			inputlen=strlen(bufrec[th].input);
			if (inputlen>0)
			{
				radionumber++;
				sprintf(tmp,"\r\n(radio message #%d transmitted)",radionumber);
				show(th,tmp);
				sprintf(tmp,
					"\r\nIncoming radio message %d from %s:\r\n%s",
					radionumber,
					d.user[ThreadToUser[th]].name,
					bufrec[th].input);

				radiosender[radiondx.x]=ThreadToUser[th];
               	sprintf(radiorec[radiondx.x], "%s",tmp);
               	radionumb[radiondx.x]=radionumber;		 // 'Time'stamp it
				radiondx.x++;

				if (crsent)
					show(th,"\r");
				else
					show(th,"\r\n");
				sent++;
			}
			else
			{
				threc[th].control=0;	// finished.... cleanup
				bufrec[th].input[0]=0;
				threc[th].incount=0;
				threc[th].status=0;

				showsector(th);			// get out
				return(0);
			}

			input='r';				// after xmit, prepare for next one
			bufrec[th].input[0]=0;
			threc[th].incount=0;
			threc[th].control=0;	// trigger radio prompt, below
		}
	}
	if (threc[th].control==0 && input=='r')
	{
		threc[th].control='r';
		threc[th].status=1;
		radioprompt(th);
	}
	return(0);
}

void		// show the radio input prompt to a user sending a radio message
radioprompt(int th)
{
	show(th,"\r\n________Type your message below, Hit 'Enter' to exit, '[' to redisplay________\r\n");
}

// keep news displays+header under NEWLINE size to fit in newsrec[]
void
makenews(int th, char *text)
{
	newsnumber++;
	newssender[newsndx.x]=ThreadToUser[th];
           	sprintf(newsrec[newsndx.x], "%s",text);
           	newsnumb[newsndx.x]=newsnumber;		 // 'Time'stamp it
	newsndx.x++;
	return;
}

int
showhistory(int th)
{
	int x, found=0, user, showcount=0, linecount=1, input;
	user=ThreadToUser[th];
	input=tolower(bufrec[th].inbuf[threc[th].inptr][0]);
	if (threc[th].control=='e')			// second, subsequent passes
	{
		if (input=='n')
		{
			threc[th].control=0;
			showsector(th);
			return(1);
		}
		else
		{
			linecount=threc[th].status;
			found=1;
		}
	}
	for (x=linecount;x<MAXHIST;x++)
	{
		if (d.histrec[x].victim==user)
		{
			if (found==0)
			{
				show(th,"\r\n\n******** Incoming events log! ********\a");
				found=1;
			}
			show(th, d.histrec[x].text);
			bzero(&d.histrec[x],sizeof(struct histlayout));
			dirtyhistory[x]=1;
			dirtyhistblock[x/DHBLOCK]=1;
			linecount++;
			showcount++;
			if (showcount>LINES)
				break;
		}
	}
	if (x<MAXHIST) 
	{
    	show(th,"\r\n\nContinue listing? (Y/N) [Y]: ");
    	threc[th].control='e';
    	threc[th].status=x+1;
		return(found);
	}
	else if (found==1)
		show(th,"\r\n************* End of log *************");
	threc[th].control=0;
	if (found==1)
	{
		threc[th].noshow=1;
		showsector(th);
		threc[th].noshow=2;
	}
	return(found);
}

void
makehistory(int user, int victim, char *text)
{
	int x, start;
	unsigned char tmp[DEFLINE];		// buffer for local use
	if (victim > 0)					// don't send history to 0 (neutrals, etc)
	{ 								// put events into history in time order
		start=time(NULL)/900; 		// use 15 minute increments as startpoints
		start=start%(MAXHIST/4*3);	// divmod by .75 history to not overrun eof
		for (x=start;x<MAXHIST;x++)	// find an empty record
			if (d.histrec[x].ts==0) break;
		if (x==MAXHIST) 			// no empty records from start to MAXHIST..
			return;					// ..bail
		sprintf(tmp,"%s",text);		// otherwise, load history record
		strncpy(d.histrec[x].text,tmp,67);
		d.histrec[x].text[67]=0;
		d.histrec[x].ts=time(NULL);
		d.histrec[x].victim=victim;
		d.histrec[x].sender=user;
		dirtyhistory[x]=1;			// make sure it gets backed up
		dirtyhistblock[x/DHBLOCK]=1;
	}
	return;
}

///  Game logic functions (gameloop thread) end here, no IO, no sleeping! ///

  ////////////////////////////////////////////////////////////////////////
 /// This thread is executed once for each connection (writes to net) ///
////////////////////////////////////////////////////////////////////////
static void * 		// all user network out put goes through this thread
userout(void *arg)
{
	register int th=0;		// index for use with thread arrays

	int
		n=0,				// number of bytes to write
		nw=0,				// number of bytes actually written, or error
		connfd; 			// connected socket file descriptor

	unsigned char tmp[DEFLINE];	// local buffer for misc use and local messages

	pthread_detach(pthread_self());

	th = *((int *) arg);	// who am I?
	connfd = threc[th].fdconn;
	threc[th].otid=pthread_self();

	threc[th].timeout=0;
	while (inuse[th])
	{
		if ((threc[th].gorite[threc[th].outptr]==1)
			&& (threc[th].outptr!=threc[th].outndx)) // testcode...
						// ...to prevent ptr passing ndx
		{
			if ((n=strlen(bufrec[th].outbuf[threc[th].outptr]))>0)
			{
				nw=writen(connfd,bufrec[th].outbuf[threc[th].outptr],n);
				if (nw!=n)
				{					// make user reconnect!
					threc[th].killout=1;
					sleep(60); 		// allow 1 minute for reaping
				}
			}
			bzero(bufrec[th].outbuf[threc[th].outptr],DEFLINE);
			threc[th].gorite[threc[th].outptr++]=0;
		}
		else
			usleep(IDLESLEEP);	// sleep a spell (idle busy loop)

		if (threc[th].timeout==1)	// check if user's still idle
		{			// user transmitted recently; reset idle warning flag.
			if ((time(NULL) - threc[th].lastaccess) < (TIMEOUT*4/5))
				threc[th].timeout=0;
		}
		else
		if ((time(NULL) - threc[th].lastaccess) > (TIMEOUT*4/5)) // idle?
		if (threc[th].logmeout==0 && threc[th].timeout==0)
		{
		
			sprintf(tmp,
				"\r\n *** Idle keyboard: timeout in %d seconds! ***\r\n%c"
				,TIMEOUT/5,7),
			nw=writen(connfd,tmp,strlen(tmp));
			threc[th].timeout=1;
		}
		if (shutdownnow>10)
			threc[th].killout=1;
	}
	sleep(60); 				// something turned off inuse[th] flag; die
	pthread_exit(NULL);
}

  ///////////////////////////////////////////////////////////////////////
 ///  This thread is executed once for each connection, reads input  ///
///////////////////////////////////////////////////////////////////////
static void * 	// all user network input comes in via this thread
userin(void *arg)
{
	register int th=0;		// index for use with thread arrays
	int	iac=0,
		n=0,
		connfd,				// connected socket file descriptor
		x,y;				// general purpose subscripts

	unsigned char tmp[DEFLINE];
	unsigned char work[DEFLINE];

	pthread_detach(pthread_self());

	th = *((int *) arg);	// who am I?
	connfd = threc[th].fdconn;
	threc[th].itid=pthread_self();
	threc[th].lastaccess=time(NULL);

	// send charmode telnet negotiation request //
	sprintf(bufrec[th].inbuf[threc[th].inndx] 	//	load first buffer...
		,"%c%c%c%c%c%c%c%c%c"					//	...as if user input...
			,255,251,1							//	(iac will echo)
			,255,251,3							//	(iac will suppress-ga)
			,255,253,3							//	(iac do suppress-ga)
	);
	threc[th].gocopy[threc[th].inndx++]=1;		//	...and set flag

	while (inuse[th])
	{
		// while, below, waits in case gameloop has our next buffer in use
		while (threc[th].gocopy[threc[th].inndx]==1) usleep(25000);

		// make sure ndx isn't going to catch ptr! // next 8 lines are testcode
		threc[th].intmp=threc[th].inndx;
		threc[th].intmp++;
		while (threc[th].intmp==threc[th].inptr)
		{
			usleep(25000);
			threc[th].intmp=threc[th].inndx;
			threc[th].intmp++;
		}

		bzero(work,DEFLINE);
		if ( (n = read(connfd,  work, MAXLINE)) > 0)
		{ 			// following loops through telnet negotiation responses
			for (x=y=0;x<n;x++)
			{
				if (iac)	// skip telnet responses, don't put in buffer
				{
					iac--;
				}
				else
				if (work[x] == 255)	// telnet response... skip 3 bytes
				{
					iac=2;			// 255 is first byte; skip next 2 bytes
					while (threc[th].inputctl>1) usleep(1000);
					threc[th].inputctl=1;
					if (threc[th].loginstatus < 2)
						bufrec[th].input[0]=0;
					threc[th].incount=0;
					threc[th].inputctl=0;
				}
				else
				if (work[x] == 13)	// process CR into CRLF
				{
					bufrec[th].inbuf[threc[th].inndx][y++] = 13;
					bufrec[th].inbuf[threc[th].inndx][y++] = 10;
					while (threc[th].inputctl>1) usleep(1000);
					threc[th].inputctl=1;
					threc[th].incount=0;
					threc[th].inputctl=0;
				}
				else 				// translate BS's & DEL's into BS-SPACE-BS
				if (work[x] == 8 || work[x] == 127)
				{
					if (threc[th].incount>0)
					{
						if (threc[th].noecho==0)
						{ // should backspaces be handled in inbuf??
							sprintf(tmp,"%c%c%c",8,32,8);
							writen(threc[th].fdconn,tmp,strlen(tmp));
						}
						while (threc[th].inputctl>1) usleep(1000);
						threc[th].inputctl=1;
						threc[th].incount--;
						bufrec[th].input[threc[th].incount]=0;
						threc[th].inputctl=0;
					}
				}
				else 				// normal byte (strip non-text)
				if (work[x]>31 && work[x]<127)
				{
					bufrec[th].inbuf[threc[th].inndx][y++] = work[x];
					while (threc[th].inputctl>1) usleep(1000);
					threc[th].inputctl=1;
					threc[th].incount++;
					threc[th].inputctl=0;
				}
			}

			threc[th].lastaccess=time(NULL);
			threc[th].gocopy[threc[th].inndx++]=1;		// done; inc. ndx
		}

		if (n<1)
			if (errno != EINTR)
				threc[th].killin=1,			// request gameloop to reap me
				sleep(60);  				// ...1 minute to be reaped...

		usleep(50000); 	// limit input speed; sleep 1/20th second
	}
	sleep(60);			// something turned off inuse[th] flag; die
	pthread_exit(NULL);
}

  ///////////////////////////////////////////////////////////////////////
 ///  Misc program initialization code -- runs before gameloop!  ///////
///////////////////////////////////////////////////////////////////////
void		// initialize structs, setup interrupt service routines, etc
initialization()
{			// and any other startup initialization
			// init random number generators, etc
	int datasize;

	commlist[COMM] = 0;
	bzero(&threc[0],	sizeof(struct threclayout) * MAXTH);
	bzero(&bufrec[0],	sizeof(struct bufreclayout) * MAXTH);
	bzero(&inuse[0],	sizeof(int) * (MAXTH + 1));
	bzero(&radiorec[0][0],	RADLINE * MAXRAD);
	bzero(&newsrec[0][0],	NEWLINE * MAXRAD);
	bzero(&UserToThread[0], sizeof(int) * (MAXUSERS + 1));
	bzero(&ThreadToUser[0], sizeof(int) * (MAXTH + 1));
	bzero(&radionumb[0],	sizeof(time_t) * MAXRAD);

	bzero(&dirtyuser[0], sizeof(int) * (MAXUSERS + 1));
	bzero(&dirtysector[0], sizeof(int) * (GAMESIZE + 1));
	bzero(&dirtyblock[0], sizeof(int) * (1+(GAMESIZE + 1)/DBLOCK));
	bzero(&dirtyhistblock[0], sizeof(int) * (1+MAXHIST/DBLOCK));

	sequa=time(NULL);
	sequb=time(NULL);
	sequc=time(NULL);

	datasize=(int)sizeof(struct userlayout)*(MAXUSERS+1)
			+(int)sizeof(struct maplayout) *(GAMESIZE+1)
			+(int)sizeof(struct histlayout) *MAXHIST;
	if (filesize(filename)!=datasize) 
    	makemap();
	else
    	loadmap();

	return;
}

int
filesize(char fname[])
{
   if (stat(fname,&fbuf)==(-1))
      return(0);
   else
      return(fbuf.st_size);
}

void
loadmap()	// load existing map, user data from 'filename'
{
	int user, sector, hist;
	bzero(&d,sizeof(struct datalayout));
	if ((datafp=fopen(filename,"r"))==NULL) 
		return;
	for (user=0;user < (MAXUSERS+1);user++)
		fread(&d.user[user],sizeof(struct userlayout),1,datafp);
	for (sector=0;sector < (GAMESIZE+1);sector++)
		fread(&d.sector[sector],sizeof(struct maplayout),1,datafp);
	for (hist=0;hist < MAXHIST;hist++)
		fread(&d.histrec[hist],sizeof(struct histlayout),1,datafp);
	fclose(datafp);
}

void
makemap()	// simple universe (map file) generation, performed at game startup
{
	// check if map/user data (d.) exists... load it else build new ones
	// This routine will only be used to initialize a new game once the
	// backup thread is running and load-from-data-file function exists
	int user, sector, hist;
	int x, y, z;// general purpose local subscripts
	int hold;	// used in the warp sort as temporary storage
	int w[6];	// temp array to build warps for each sector...

	bzero(&d,sizeof(struct datalayout));

	//////////////////////////////////////////////////////////////////
	// the following loop loads ports, planets, nebulas, and neutral
	// fighters into a single-galaxy universe of GAMESIZE size.  It
	// also builds the interconnections (warps) between the many
	// locations (sectors) in the universe
	//////////////////////////////////////////////////////////////////
	for (x=1;x<GAMESIZE+1;x++)
	{
		if (rand16(10)<4) 		// put a port in 40% of sectors
		{
			if (rand16(10)<3)	// put nebula in 30% of port sectors
				d.sector[x].sectortype=1;
			d.sector[x].porttype=rand16(3)+1; // select port type 1,2, or 3
			if (rand16(10)<2)	// put planet in 20% of port sectors
			{
				d.sector[x].moontype=1;	// 1==normal planet
				for (y=1;y<4;y++)
				{
					if (rand16(10)<5)	// force 50% of 2,3 porttypes to 1
						d.sector[x].porttype=1; // when in planet sectors.
					d.sector[x].moonprod[y]=rand16(400)+50; // set productivity
					d.sector[x].moongoods[y]=	// and initial inventory
						d.sector[x].moonprod[y]+// for the new planet
						rand16(GAMECYCLE) * d.sector[x].moonprod[y];
				}
				d.sector[x].mooncalcdate=time(NULL)-86400;// add a day...
			}
			for (y=1;y<4;y++)
			{
				d.sector[x].portprod[y]=rand16(200)+10;// set productivity
				d.sector[x].portgoods[y]=		// and initial inventory
					d.sector[x].portprod[y]+	// for the new port
					rand16(GAMECYCLE) * d.sector[x].portprod[y];
			}
			d.sector[x].portcalcdate=time(NULL)-86400;	// add a day...
		}
		if (rand16(100)<4) 	// 4%: owner of 0, code of 0 is neutral figs
			d.sector[x].fiters=rand16(BIGWARP*2);	// put some neutrals here

		// 6-exit one-way bar with reflective ends (not wrap-around ends)
		// load temp array with valid sector numbers for sector 'x'
		do {
			w[0] = x - rand24(BIGWARP) - (MEDWARP + LILWARP + 1);
			if (w[0] < 1) w[0] = rand24(BIGWARP / 3);
		} while (w[0]<1 || w[0] == x);

		do {
			w[1] = x - rand24(MEDWARP) - (LILWARP + 1);
			if (w[1] < 1) w[1] = rand24(MEDWARP / 2);
		} while (w[1]<1 || w[1] == x);

		do {
			w[2] = x - rand24(LILWARP) - 1;
			if (w[2] < 1) w[2] = rand24(LILWARP);
		} while (w[2]<1 || w[2] == x);

		do {
			w[3] = x + rand24(LILWARP) + 1;
			if (w[3] > GAMESIZE) w[3] = GAMESIZE - rand24(LILWARP);
		} while (w[3]>GAMESIZE || w[3] == x);

		do {
			w[4] = x + rand24(MEDWARP) + (LILWARP + 1);
			if (w[4] > GAMESIZE) w[4] = GAMESIZE - rand24(MEDWARP / 2);
		} while (w[4]>GAMESIZE || w[4] == x);

		do {
			w[5] = x + rand24(BIGWARP) + (MEDWARP + LILWARP +1);
			if (w[5] > GAMESIZE) w[5] = GAMESIZE - rand24(BIGWARP / 3);
		} while (w[5]>GAMESIZE || w[5] == x);

		// sort: always fill w[0] first, order maps with small numbers first!
		for (z=0;z<6;z++) 		// bubble party... little warps up front
			for (y=z;y<6;y++)
				if (w[y])		// not all galaxy types will have 6 exits
					if (w[z] > w[y])
						hold=w[z],
						w[z]=w[y],
						w[y]=hold;

		// for random 6-exit galaxy types, eliminate duplicate warps...

		for (y=0;y<6;y++) 		// load map warps from temp array
			d.sector[x].warp[y]=w[y];

		for (y=0;y<5;y++)
		{
			if (w[y]==w[y+1])	// ooops!  found a duplicate warp...
			{
				x--;			// from the top, redo this sector's warps.
				break;			// exit now so we only decrement x once
			}
		}
	}
	for (y=1;y<7;y++)		// manually set up the warps from sector 1 to 2-7
							// and make those sectors contain nebulas
	{
		d.sector[y].sectortype=1;		// make sectors 1-6 nebulas
		d.sector[1].warp[y-1]=y+1;		// make sector 1 go to 2,3,4,5,6,7
		d.sector[y].fiters=				// put neutrals in sectors 1-6
				rand20(BIGWARP*5)+BIGWARP;
	}

	// write out new data file to disk here //
	if ((datafp=fopen(filename,"w+"))==NULL) 
		return;
	for (user=0;user < (MAXUSERS+1);user++)
		fwrite(&d.user[user],sizeof(struct userlayout),1,datafp);
	for (sector=0;sector < (GAMESIZE+1);sector++)
		fwrite(&d.sector[sector],sizeof(struct maplayout),1,datafp);
	for (hist=0;hist < MAXHIST;hist++)
		fwrite(&d.histrec[hist],sizeof(struct histlayout),1,datafp);
	fclose(datafp);
	return;
}

void	
dirty(int sector)	// call this to set sector and block as dirty
{
	dirtyblock[sector/DBLOCK]=1;
	dirtysector[sector]=1;
	return;
}

  ///////////////////////////////////////////////////////////////////////
 ///  This sets up signal handlers -- add backup flush to hangup()   ///
///////////////////////////////////////////////////////////////////////
// When the backup thread is running, a shutdown signal should be
// implemented to write out the state of the data before exiting
void
setsignals()
{
	fp1=sig1rtn; 		// used by gameloop to kill idle & logged off threads
	signal(1,fp1);
	signal(2,SIG_IGN);
	signal(3,SIG_IGN);
	signal(4,SIG_IGN);
	signal(5,SIG_IGN);
	signal(7,SIG_IGN);
	signal(8,SIG_IGN);
	signal(10,SIG_IGN);
	signal(11,SIG_IGN);
	signal(12,SIG_IGN);
	signal(13,SIG_IGN);
	signal(14,SIG_IGN);
	fp15=sig15rtn; 		// used by admin to shutdown entire game gracefully
	signal(15,fp15);
	signal(16,SIG_IGN);
	signal(17,SIG_IGN);
	signal(18,SIG_IGN);
	signal(19,SIG_IGN);
	signal(20,SIG_IGN);
	signal(21,SIG_IGN);
	signal(22,SIG_IGN);
	return;
}

  ///////////////////////////////////////////////////////////////////////
 ///  various wrapper and miscellaneous functions  (unp3, mostly)  /////
///////////////////////////////////////////////////////////////////////
void
Listen(int fd, int backlog)
{
	char	*ptr;

	if ( (ptr = getenv("LISTENQ")) != NULL)
		backlog = atoi(ptr);

	if (listen(fd, backlog) < 0)
		err_sys("listen error");
}

int
tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
	int				listenfd, n;
	const int		on = 1;
	struct addrinfo	hints, *res, *ressave;

	bzero(&hints,	sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
	{
		printf("tcp_listen error for %s, %s: %s",
				 host, serv, gai_strerror(n));
	 	exit(1);
	}
	ressave = res;

	do {
		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (listenfd < 0)
			continue;		/* error, try next one */

		if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on,
				sizeof(on))<0)
		    err_sys("setsockopt error");
		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
			break;			/* success */

		close(listenfd);	/* bind error, close and try next one */
	} while ( (res = res->ai_next) != NULL);

	if (res == NULL)	/* errno from final socket() or bind() */
		err_sys("tcp_listen error for %s, %s", host, serv);

	Listen(listenfd, LISTENQ);

	if (addrlenp)
	   *addrlenp = res->ai_addrlen;/* return size of protocol address */

	freeaddrinfo(ressave);

	return(listenfd);
}

int		daemon_proc;		/* set nonzero by daemon_init() */

void
err_sys(const char *fmt, ...)
{
	va_list		ap;

	va_start(ap, fmt);
	err_userin(1, LOG_ERR, fmt, ap);
	va_end(ap);
	exit(1);
}

static void
err_userin(int errnoflag, int level, const char *fmt, va_list ap)
{
	int		errno_save, n;
	char	buf[DEFLINE];

	errno_save = errno;		/* value caller might want printed */
	vsnprintf(buf, MAXLINE, fmt, ap);
	n = strlen(buf);
	if (errnoflag)
		snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save));
	strcat(buf, "\n");

	if (daemon_proc) {
		syslog(level, buf);
	} else {
		fflush(stdout);	/* in case stdout and stderr are the same */
		fputs(buf, stderr);
		fflush(stderr);
	}
	return;
}

ssize_t						// Write "n" bytes
writen(int fd, const void *vptr, size_t n)
{
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;	// and call write() again
			else
				return(-1);	// error
		}
		nleft -= nwritten;
		ptr   += nwritten;
	}
	return(n);
}
//////// end of UNP3 (Unix Network Programming) functions ////////////

void sig1rtn()
{ 	// used by admin to shutdown entire game gracefully
	pthread_exit(NULL);
}

void sig15rtn()
{ 	// used by gameloop to kill idle or logged off threads
	shutdownnow=1;
}

     /////////////////////////////////////////////////////////////
    /// Backup thread: Scans datalayout and backs up records  ///
   /// all records with dirty == 1, then sets them to 0.     ///
  /// It continues making passes, with shorter and shorter  ///
 /// delay intervals until it finds no dirty blocks.       ///
/////////////////////////////////////////////////////////////
static void *
backupdata(void *arg)
{
	int shutdownflag=0;
	int sector, user, hist, 
		userrec, usersize, 
		maprec, mapsize, 
		histrec, histsize,
		x, y, start, stop, delay;
	pthread_detach(pthread_self());
	userrec=sizeof(struct userlayout);
	histrec=sizeof(struct histlayout);
	maprec=sizeof(struct maplayout);
	usersize=userrec * (MAXUSERS+1);
	mapsize=maprec * (GAMESIZE+1);
	histsize=histrec * MAXHIST;
	for ( ; ; ) // continuosly do backups
	{
		if (shutdownflag)	// backup completed, shutdown in progress
		{
			while(shutdownnow<40) usleep(10000);// wait for gameloop...
			shutdownnow=55;	
			pthread_exit(NULL);
		}
		if (bu_dirtybl)
			bu_sleep='U';	// last backup Unsuccessful after 10 passes
		else
			bu_sleep='S';	// tell showdata() backup is Sleeping peacefully

		for (x=0;x<300;x++)
		{
			if (shutdownnow) break;		// system going down... don't sleep
			usleep(GAMESIZE/20);		// usleep in proportion to game size
			usleep(20000);				// sleep 6 seconds
		}

		if (shutdownnow)
			delay=0;	// system going down... turn off delay
		else
			delay=9999;	// initial time for periodic usleep when writing

		if (shutdownnow>20) 
			shutdownflag=1;				// shutdown after this backup

		bu_sleep='A';	// let showdata() know backup is Active
		bu_pass=0;		// starting a new backup, initialize pass counter
		bu_bkup++;		// enumerate this backup for showdata()
		do
		{
			bu_pass++;	// how many passes does it take for consistent backup?
			if ((datafp=fopen(filename,"r+"))==NULL)
				exit(1);
			bu_dirtybl=0;	// count dirty blocks found each pass, loop til 0
			for (user=0;user < (MAXUSERS+1);user++)
			{
				if (dirtyuser[user]==1) 
				{
					dirtyuser[user]=0;
					bu_ucount++;		// count dirty user blocks written
					bu_dirtybl++;
					fseek(datafp,user*userrec,SEEK_SET);
					fwrite(&d.user[user],userrec,1,datafp);
				}	
				if (delay) if (user%333==166) usleep(delay);
			}
			for (y=0;y<(1+(GAMESIZE+1)/DBLOCK);y++)
			{	
				if (dirtyblock[y])	// look at big blocks first, only if dirty
				{					// do we scan the individual records in it
					dirtyblock[y]=0;
					start=y*DBLOCK;		// first record in this block
					stop=start+DBLOCK;	// last  record in this block
					if (stop>(GAMESIZE+1))	// last block is probably shorter
						stop=(GAMESIZE+1);
					if (start > stop)
						break;
					for (sector=start;sector < stop;sector++)
					{
						if (dirtysector[sector]==1) 
						{
							dirtysector[sector]=0;
							bu_mcount++;	// count dirty map blocks written
							bu_dirtybl++;
							fseek(datafp,(sector*maprec)+usersize,SEEK_SET);
							fwrite(&d.sector[sector],maprec,1,datafp);
						}	
					}
				}
				if (delay) if (y%10==3)	usleep(delay);
			}
			for (y=0;y<(1+MAXHIST/DHBLOCK);y++)
			{	
				if (dirtyhistblock[y])	// if big blocks are dirty...
				{						// we scan the individual records
					dirtyhistblock[y]=0;
					start=y*DHBLOCK;	// first record in this block
					stop=start+DHBLOCK;	// last  record in this block
					if (stop>MAXHIST)	// last block is probably shorter
						stop=MAXHIST;
					if (start > stop)
						break;
					for (hist=start;hist < stop;hist++)
					{
						if (dirtyhistory[hist]==1) 
						{
							dirtyhistory[hist]=0;
							bu_hcount++;	// count dirty hist blocks written
							bu_dirtybl++;
							fseek(datafp,(hist*histrec)+mapsize+usersize,
																SEEK_SET);
							fwrite(&d.histrec[hist],histrec,1,datafp);
						}	
					}
				}
				if (delay) if (y%10==3)	usleep(delay);
			}
			fclose(datafp);
			delay/=10;
		} while (bu_dirtybl && (bu_pass < 10)); // 5 successively faster passes
												// then 5 full speed passes.
		sync();		
	}
}

/*///////////////// Copyright notice and License info //////////////////
  st.c, version 0.0.158 (Space Tyrant), Copyright 2005, Ray Yeargin.
  The documentation is at  http://librenix.com/?inode=6240
  Use 4 space tabs or a very wide window to edit or view this file!
  This software is released under the terms of the GPL Version 2.
  Download a copy of the license from http://librenix.com/st/gpl.txt, or
  see http://www.gnu.org for more information and a copy of the license.
/*////////////// End of Copyright notice and License info //////////////

