/*///////////////// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*//////// 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 [ ] \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;th1) // 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 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 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;x0) && (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>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>offset)==1)&&(y>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 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 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;xLINES) break; } } if (x1) // 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'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 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] (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;xLINES) break; } } if (x 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;x0) { 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;x1) 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) 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 //////////////