# /*//////// compile shell script follows: type `sh st.c` to compile st //////// 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 # #gcc -static -g -O2 -D_REENTRANT -Wall -o st st.o -lresolv -lpthread # static # rm st.o; strip st; exit # grepgcc # # /*//////// ----------------- end of compile script ----------------- ///////// /*///////////////// Copyright notice and License info ////////////////// st.c, version 0.352 (Space Tyrant), Copyright 2005-2007, Ray Yeargin. This software is released under the terms of the GPL Version 2. Download a copy of the license from http://spacetyrant.com/gpl.txt, or see http://www.gnu.org for more information and a copy of the license. The documentation is at http://spacetyrant.com/ /*////////////// End of Copyright notice and License info ////////////// /*//////// include // headers // /*//////// #include #include #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)) // getgal() returns the galaxy number for sector 's' #define getgal(s) ((s) > (GAMESIZE/5) ? ((s)-(GAMESIZE/5+1))/8000+1 : 0) /*//////// the following macros are random number generators that produce up to 8, 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) /*//////// // continuous random number generators -- do not reseed // #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)) // 8-bit output randum number generators used by hasher (seeded) #define randpw(num) (((sequp = sequp * 1234567949 + 12601)>>24) % (num)) #define randpx(num) (((sequq = sequq * 1234577777 + 12739)>>24) % (num)) #define randpy(num) (((sequr = sequr * 1234582897 + 12601)>>24) % (num)) #define randpz(num) (((sequs = sequs * 1234567949 + 12739)>>24) % (num)) // 24-bit output, seedable prng, use anywhere in gameloop, seed first #define randz(num) (((sequz = sequz * 1234577777 + 12911)>>8) % (num)) /*//////////// detached // thread // functions // *///////////// static void *gameloop(void *); // executed once: all game logic static void *userin(void *); // one per player: reads from network static void *userout(void *); // one per player: writes to network static void *backupdata(void *); // executed once: periodically backs up // player and map data, plus config info // in addition to the above threads, main() listens for connections // and spawns new pairs of userin/userout for each new session /*//////////////////////////////////////////// functions used by above threads and main() // /*//////////////////////////////////////////// void setsignals(void); // executed once: ignore unused signals // and setup interrupt handlers int checkip(int th); // check for more than 2 conns from same IP int checkgr(int th, int rootcall); // check for group size > GROUPLIMIT int scanip(int th); // keep ip tables updated void initialization(void); // executed once: initializes program state void resetmap(void); // executed only when a new game initializes void resetgame(void); // executed only when a new game initializes void resetplayers(void); // executed only when a new game initializes void resetbeacons(int user); // remove attached beacons of 'user' account void resethist(int user); // executed when a user is created or deleted void resetteam(int user); // executed when a user is created or deleted void makemap(void); // executed only when 1st new game initializes void loadmap(void); // executed at daemon startup with existing game 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, int inqonly); // updates port inventories void mooncalc(int sector); // updates planet inventories void calcproductivity(void); // calculate productivities of GALAXIES void loadgalnames(void); // load and scramble galaxy names 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 getletter(int th); // return trading quality letters for warps int matcher(int user); // find player associations int getshiptype(int ship); // returns index for shiptype[], shiptypeinit[] int growfleets(int segment);// grows fighter fleets in a fraction of universe int sqroot(int product); // return truncated integer square root of product void spiral(int start, int stop); // build spiral 8K galaxy void irregular(int start, int stop); // build random 2-way galaxy void globular(int start, int stop); // build cube 2-way galaxy void globstring(int start, int stop); // build string of small cubes void bar(int start, int stop); // build tubular galaxy int removehb(int th); int sellfiters(int th); int buybeacons(int th); int buycombats(int th); int buytractor(int th); int buyscanner(int th); int buyholds(int th); int chgamecycle(int th); int chprodmult(int th); int chgamelen(int th); int chstartdate(int th); int chdailyanti(int th); int chmaxholds(int th); int chminholds(int th); int chtimelimit(int th); int chempire(int th); int chbeacon(int th); int chgrouplimit(int th); int chtimeout(int th); int chvalid(int th); int gamblertn(int th); int dicertn(int th); int boot(int th); int resetpw(int th); int chpassword(int th); int teamapply(int th); int teamcreate(int th); int teamdisband(int th); int teamexpel(int th); int teamnewcaptain(int th); int teamlist(int th); int teammembers(int th); int teamreview(int th); int teamresign(int th); int teamwithdraw(int th); int teamnameexists(int th); int teaminitexists(int th); char *hasher(char p[], char n[]); // hashes passwords int plotpath(int th, int sector, int target); // Brian's replacement // for pathdepth/pathcalc // (plots autopilot paths) /*////////////////////////////////////////////////////////////////////////// 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 resetstats(int th); // resets scooper statistics int helppage(int th); // display one-page help info int viewsector(int th); // redisplay sector int deploy(int th); // deploy fighters to guard sector int deployhb(int th); // deploy/retrieve beacon in current sector int recall(int th); // recall deployed fighters int jettison(int th); // dump cargo holds int others(int th); // other players currently logged on int forces(int th); // your deployed forces report int makebase(int th); // build/upgrade a starbase int groups(int th); // player associations int players(int th); // player rankings int userteams(int th); // team rankings and userteams stats int findship(int th); // find nearby ships 3+ scanners int nudgeport(int th); // move a port int warpmoon(int th); // move a planet int warprtn(int th); // accepts 1-6 < , . > - = move ship through warp int hangup(int th); // end session... int admin(int th); // shutdown, configure the game, etc... int teamadmin(int th); // team management menu... calls subfunctions... 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 scoop(int th); // automatically trade out paired planet/port int radiortn(int th); // broadcast messages to other players int buyfiters(int th); // mail order fighters int chradch(int th); // change radio channel; eventually, page player int pageplayer(int th); // page another player to random radio channel int computrade(int th); // computerized trading int fightbase(int th); // attack starbase int selfdestruct(int th); // destroy own ship, probably boxed in by forces int tradingpost(int th); // tradingpost menu code int atakrtn(int th); // attack deployed fighters, ships int atakship(int th); // attack other ships int showdebris(int th); // show sector dust int showhistory(int th); // show events since last time int showdata(int th); // debugging function 'z'...shows status of indexes // and buffers as well as state of backup thread // commlist is the command list commscan() uses to find *fp[]() index #define COMM 58 // 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 computrade, // c computer assisted port trading deploy, // d deploy fighters in a sector showhistory, // e show history events findship, // f Find ships command 3+ scanners groups, // g group players by IP address relationships deployhb, // h deploy hb in sector infortn, // i user inventory and misc. personal info jettison, // j dump the cargo to empty holds warprtn, // k bacK up to the previous sector landrtn, // l land on planet, take free goods makebase, // m make/upgrade/downgrade a starbase nudgeport, // n Nudge port command 1+ tractors 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 (no purchases...) portrtn, // t dock with port, sell & buy goods userteams, // u team rankings viewsector, // v redisplay current sector warpmoon, // w Warp planet command 3+ tractors scoop, // x scoop planet/port combination forces, // y Your deployed forces 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 travel down from hotel lobby to next hotel nullrtn, // 8 nullrtn, // 9 travel up from hotel lobby to next hotel command, // ? list of implemented command letters chradch, // # change radio channel, or page player 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) fightbase, // * attack enemy starbase (autocalled) admin, // ^ user #1: admin menu (^^ == game shutdown) pageplayer, // @ page another player to random radio channel tradingpost, // : tradingpost (called by portrtn) autowarp, // / take path plotted by autopilot warprtn, // _ jump to random connected sector (same as -) teamadmin, // & team management menu resetstats, // + reset autoscooper statistics helppage, // = display tiny game overview / help screen 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 // GAMESIZE must be an even multiple of 10000! constants // GAMESIZE must be an even multiple of 10000! /*//////////////// // uncomment the pair you want to set universe size, recomment all others //#define GAMESIZE 2000000 // number of sectors in universe //#define DBLOCK 330 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 1500000 // number of sectors in universe //#define DBLOCK 295 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 1000000 // number of sectors in universe //#define DBLOCK 250 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 500000 // number of sectors in universe //#define DBLOCK 190 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 350000 // number of sectors in universe //#define DBLOCK 165 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 250000 // number of sectors in universe //#define DBLOCK 144 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 150000 // number of sectors in universe //#define DBLOCK 117 // 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 GAMESIZE 70000 // number of sectors in universe //#define DBLOCK 86 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 50000 // number of sectors in universe //#define DBLOCK 75 // sectors to a dirty block (GAMESIZE^.4) //#define GAMESIZE 30000 // number of sectors in universe //#define DBLOCK 61 // 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 GAMESIZE 10000 // number of sectors in universe //#define DBLOCK 40 // sectors to a dirty block (GAMESIZE^.4) #define GALAXIES 201 // # max galaxies in galname array (201=2m sectors) // defaults that initially get loaded into cfg record #define GROUPLIMIT 10 // maximum IP group size (make max+2) #define FUELPERDAY 1440000 // fuel issued per day (1000 gm/minute) #define GAMECYCLE 7 // port goods, fuel accumulate xx days #define GAMELENGTH 70 // game length in days #define PRODMULT 100 // 100==100% (no change) #define TIMELIMIT 240 // user DAYly timelimit in minutes #define TIMEOUT 900 // seconds idle time before time out #define DAY 86400 // how long is a local day? #define MILLIDAY (DAY/1000) // one thousandth of a day (86.4 seconds) #define CENTIDAY (DAY/100) // one hundredth of a day (864 seconds) #define MAXUSERS 1000 // maximum number of total users // note: MAXTH minus MAXTHMARGIN limits the // number of _concurrent_ users! #define MAXIP 16 // length of user IP history... changes // user datalayout, resets game! #define MAXHIST (MAXUSERS*5 + GAMESIZE/100) // entries in history array #define DHBLOCK 100 // dirty history block size #define MAXTH 64 // concurrent connection limit (io thread pairs) #define MAXTHBITS 6 // this is the number of unsigned bits // needed to index into MAXTH array // ( 2^MAXTHBITS must equal MAXTH! ) #define MAXTHMARGIN 3 // session limit = MAXTH - MAXTHMARGIN // With MAXTH (which must be a power of 2) // set to 256, set this to 55 if you want to // limit concurrent users to 200. (0 is reserved) #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 // radio buffer size. 160, no need for longer. #define NEWLINE 100 // news buffer size. 100, no need for longer. #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) // for inbuf: 1/4th as many as outbufs #define MAXRAD (MAXBUF) // number of radiorec buffers #define MAXRADBITS (MAXBUFBITS) // number bits to index MAXRAD #define LISTENQ 1024 // 2nd argument to listen() #define MAXDEPTH (DEFLINE-3) // max warps autowarp, plotpath will use #define AREA 10000 // plotpath()'s area use limit #define RANGE (AREA*8/10) // plotpath()'s limit on move distance #define MAXALLY 80 // initial team size max default (even #!) #define MINHOLDS 50 // initial default value loaded into d.cfg #define MAXHOLDS 100 // initial default value loaded into d.cfg #define FIGPRICE 500 // initial default value loaded into d.cfg #define MINBASE 25 // initial size of minimal starbase in figs #define BASELIMIT 18 // 19 is upper limit at FIGPRICE of 500! #define HOLDPRICE 50000 // initial default value loaded into d.cfg char used[MAXUSERS+1]; // for player associations (groups) char group[MAXUSERS+1]; // for player associations (groups) short grouplist[MAXUSERS+1];// for player associations (groups) short groupuser[MAXUSERS+1];// for player associations (groups) short groupsize[MAXUSERS+1];// for player associations (groups) short groupactv[MAXUSERS+1];// for player associations (groups) int path[AREA], search[AREA];// used by plotpath() unsigned int grouptime=0; // only update grouplist when old char shiptype[10][16]= { "Light Cruiser", // <3 of any device, <5 total devices "Cruiser", // <3 of any device, exactly 5 total "Transport", // 3 extra cargo holds "Freighter", // >3 extra cargo holds "Frigate", // 3 combat systems "Battleship", // >3 combat systems "Light Tug", // 3 tractor devices "Heavy Tug", // >3 tractor devices "Scout", // 3 scanners "Hunter", // >3 scanners }; char shiptypeinit[10][8]={ "L.Cru", // <3 of any device, <5 total devices "Cruis", // <3 of any device, exactly 5 total "Trans", // 3 extra cargo holds "Freig", // >3 extra cargo holds "Friga", // 3 combat systems "Battl", // >3 combat systems "L.Tug", // 3 tractor devices "H.Tug", // >3 tractor devices "Scout", // 3 scanners "Huntr", // >3 scanners }; char nudgeprompt[6][16]={ "", " [Nudge ON] ", " [Nudge ON] ", " [Nudge ON] ", " [Nudge ON] ", " [Nudge ON] ", }; char warpprompt[6][16]={ "", " [Warp ON] ", " [Warp ON] ", " [Warp ON] ", " [Warp ON] ", " [Warp ON] ", }; char plantname[6][16]={ "", // 0 porttype is null -- no port "RockMine", // 1 iron "GreenPort", // 2 alcohol "HardwareDepot", // 3 hardware "TradingPost", // 4 upgrades (1, 2001, 4001, 6001,...) "Spaceport", // 5 links GALAXIES }; char sectorname[BASELIMIT+11][32]={ "", // 0 normal sector "Nebula", // 1 nebula "Crab Nebula", // 2 TP sectors (1,2001,4001,6001,...) "Smoke Ring", // 3 reserved (unused) }; char goodsname[6][16]={ "", // 0 porttype is null -- no port "iron", // 1 iron "alcohol", // 2 alcohol "hardware", // 3 hardware "upgrades", // 4 TradingPost "Wormhole access", // 5 spaceports for TLR worlds }; char galtypename[7][16]={ // new name ___old name||descr___ code "Open Cluster", // 6-exit 1-way 0 "Spiral", // Old Spiral 1 "Giant Globular", // Old Cube 2 "Bar", // 5x5x320 Skyscraper 3 "Globular String", // Cubic Necklace 4 "Irregular", // New Random 2-way 5 "Elliptical" // long 6-exit 1-way 6 (bottom galaxy) }; /*//////////// global // arrays and // variables // /*//////////// int baseprice[BASELIMIT+1]; // prices for starbases int basestrength[BASELIMIT+1]; // fighter equivalent for bases int playercount=0; // gameloop updates this with active session # int idlesystem=0; // backupdata sleeps longer during long idles int shutdownnow=0; // TERM signal sets this to 1 int shutdowntime=0; // used by admin function int nopause=0; // tell others() not to pause for screens int holdrange; // maxholds minus minholds time_t growtime; // time of last growfleets() call int landing=0; // user is scooping; don't display landing int porting=0; // user is scooping; don't display porting int scooping=0; // user is scooping; don't display porting // and landing data. There is only one copy // of this variable since it belongs to the // single thread of gameloop(). 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_tcount=0, // total team blocks backed up bu_mcount=0, // total map blocks backed up bu_hcount=0, // total hist blocks backed up bu_ccount=0, // total cfg blocks 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 dirtyteam[MAXUSERS+1]; // dirty team 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/DHBLOCK]; // dirty history rough table unsigned int dirtycfg; // config data changed char radiorec[MAXRAD][RADLINE]; // radio buffers int radionumb[MAXRAD]; // serial number of radio messages int radiochan[MAXRAD]; // radio message channel int radiosender[MAXRAD]; // user id number of sender of this radio msg int radiopager[MAXRAD]; // user id number of addressee to page int radionumber=0; // master copy of radionumber 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 unsigned int z:MAXRADBITS; // utility index for redisplays, scans, etc unsigned int newsbitsz: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 pthread_mutex_t inmtx[MAXTH+1]; // bufrec.input[] & threc.incount control 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 time_t thiscall; // time of connection unsigned 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 int data; // add'l data to pass via control/status int value; // add'l data to pass via control/status int winnings; // gambling session info int anteused; // gambling session info unsigned int noshow; // don't show sector next pass int fuelused; // for calculating profit margins int earned; // for calculating profit margins int spent; // for calculating profit margins int sentr; // for keeping track of sent radio msgs int radionumb; // number of last radio message int radiochan; // radio channel int pagerchan; // radio channel last received a page int radiorand; // radio channel to page players to 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 int ctrade; // computrade low-water mark letter int deploy; // memory of deployed fighter count int lastdebris; // last sector where debris was seen int lastscan; // last universe scan command (time) int beaconmode; // set to 1 to disable beacons for session int nudgemode; // val: 0-5, number tractors in use (ports) int warpmode; // val: 0-5, number tractors in use (moons) int sourceport; // sector number moving port from (nudgemode) int sourcemoon; // sector number moving moon from (warpmode) unsigned int myip; // IP address of user's box unsigned int noecho; // don't echo user input (passwds) unsigned int incount; // count input bytes since CR or // command completion unsigned int last16[16]; // last 16 sectors visited (lastndx) unsigned int prev16[16]; // path of last 16 moves //////////////////////////////////////////////////////// // wraparound unsigned int bitfields for ring buffers // //////////////////////////////////////////////////////// unsigned int lastndx:4; // index for last16 unsigned int lastndxf:sizeof(int)*8-4;// force word alignment 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, // 6 == boot, 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: gameloop counter of active sessions char portletter[6]; // global: port letters... used by gameloop only! int bestletter; // global: best letter from getletter() int theproduce[4], // used by portcalc, getletter, portrtn thegoods[4], // used by portcalc, getletter, portrtn base[4]={0,6,9,12}; // min sale price to port; max buy from port 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 unsigned int sequp=1; // rand # generator: used only by gameloop's hasher unsigned int sequq=1; // rand # generator: used only by gameloop's hasher unsigned int sequr=1; // rand # generator: used only by gameloop's hasher unsigned int sequs=1; // rand # generator: used only by gameloop's hasher unsigned int sequz=1; // rand # generator: seedable, gameloop only 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. char outbuf[MAXBUF][DEFLINE]; // net output ring buffer char inbuf[MAXBUF/4][DEFLINE]; // net input ring buffer char bugbuf[BUGS][DEFLINE];// function names, args: testcode char input[DEFLINE]; // accumulate multichar input here char insav[DEFLINE]; // saved 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 online[MAXUSERS+1]; // users online for rankings unsigned int scores[MAXUSERS+1]; // cumulative scores of players unsigned int teams[MAXUSERS+1]; // cumulative scores of teams unsigned short int scoresndx[MAXUSERS+1]; // player numbers for scores, above unsigned short int teamsndx[MAXUSERS+1]; // team numbers for scores, above unsigned int scoretime=0; // only update scores when old unsigned int teamtime=0; // only update team scores when old int galprod[GAMESIZE/10000+1]; // store galaxy productivities char *galname[GALAXIES]; // load, randomize galaxy names /*///// // 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 unsigned int beacon; // homing device stuck to hull unsigned char tractor; // number of tractor devices (up to 5) unsigned char combats; // number of combat systems (up to 5) unsigned char scanner; // number of ship scanners (up to 5) unsigned char beacons; // supply of homing beacons (up to 10) 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) short int team; // team player is a member of short int app; // team player has applied to unsigned int ip[MAXIP]; // 16 most recent IP addresses, in order } user[MAXUSERS+1]; struct maplayout { unsigned char sectortype; // 1,2=nebula,3-9=reserved,10+++==starbases 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 dust; // sector debris unsigned short beacon; // owner of deployed homing beacon unsigned short fitersowner;// player # who owns fighters unsigned short int portgoods[4];// iron, alc, hardware inv at port unsigned short int portprod[4]; // goods productivity unsigned short int moongoods[4];// iron, alc, hardware inv at port unsigned short int moonprod[4]; // goods productivity signed int fiters; // # of fighters in sector unsigned int portcalcdate; // date inventory last calculated unsigned int mooncalcdate; // date inventory last calculated signed int warp[6]; // connections to other sectors } sector[GAMESIZE+1]; struct histlayout { unsigned int ts; // timestamp unsigned short victim; // receiver of this histogram unsigned short sender; // sender of this histogram char text[68]; // content of message } histrec[MAXHIST]; struct cfglayout { unsigned int idcode; // unique number 1 to 1000000 unsigned int startdate; // date game began unsigned int gamelength; // game ends on startdate+gamelength unsigned int gamecycle; // game cycle in earth days (inventories) int timeout; // idle keyboard timeout in seconds unsigned int timelimit; // total session minutes per calendar day unsigned int dailyanti; // base grams daily antimatter issue unsigned int minholds; // starting holds in new ship unsigned int maxholds; // maximum holds ship can take unsigned int prodmult; // 100%. 50 for 1/2th, 200 for double unsigned int teamsize; // 0, 2 to (MAXALLY-1) (max team size) unsigned char access[24]; // access levels to ch options, pw, radio unsigned int grouplimit; // hard limit group size before lockout unsigned int spare1; // unused unsigned int spare2; // unused unsigned char sparec1; // unused unsigned char sparec2; // unused unsigned char sparec3; // unused unsigned char empire; // 'empire' setting. PFL! growing fig fleets char galtype[GAMESIZE/10000+1]; } cfg; struct teamlayout { char name[40]; // long name of team char init[4]; // 3-char initials/acronym of team unsigned short int ally[MAXALLY];// members by user #, ally[0]==captain } team[MAXUSERS+1]; // never run out of teams; no checking! } 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 connections, // number of connections from same IP address 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; char tmp[DEFLINE], serv[32]; if (fork()!=0) exit(0); // let child process take over #if defined(__APPLE__) setpgrp(0,0); // format for OSX osx #else setpgrp(); // format for Linux linux #endif if (argc==2) strncpy(serv,argv[1],31); if (argc==3) strncpy(serv,argv[2],31); 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); } 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 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;th2) // same IP testcode { sprintf(tmp,"\r\nConnections: %d: Limit exceeded!\r\n\a" ,connections); writen(consoc,tmp,strlen(tmp)); usleep(5000); cleanup=5; } else { 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" ,sessioncount); 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 ... inuse[saveth]=0; // threc[saveth] no longer in use bzero(&bufrec[saveth], sizeof(struct bufreclayout)); case 5: // user connection limit exceeded / socket open case 2: // session limit exceeded / socket was open bzero(&threc[saveth], sizeof(struct threclayout)); 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; char tmp[DEFLINE]; // local buffer for misc use and messages time_t now; unsigned int resetcounter=0; int count=0, idlecycles=0, // gameloop sleeps longer during long idle periods acount=0, 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... { playercount=acount; acount=count=0;// acount: active ships, count: non-idle threads now=time(NULL); // save time for use later if (now >= (d.cfg.startdate + d.cfg.gamelength * DAY) - 7200) { if (now >= (d.cfg.startdate + d.cfg.gamelength * DAY)) { resetgame(); sprintf(tmp,"\r\n\n*\n*\n* This game is Over! *\n*\n*\a"); makenews(0, tmp); sprintf(tmp,"\r\n\n****************************************"); makenews(0, tmp); sprintf(tmp,"\r\n\n*\n*\n* Welcome to the new game! *\n*\n*"); makenews(0, tmp); } else if ((now - resetcounter)/120) { sprintf(tmp, "\r\n\n*\n*\n* This game resets in %d minutes! *\n*\n*\a", (int)((d.cfg.startdate+(d.cfg.gamelength*DAY))-now)/60); makenews(0, tmp); resetcounter=now; } } fuelnow=now/60; for (th=1;th1) // skip threads not in use { if (threc[th].loginstatus>5) acount++; // an active player; count him if ((threc[th].gocopy[threc[th].inptr]==1) // buffer[inptr] && (threc[th].inptr!=threc[th].inndx)) // has data... { if (pthread_mutex_trylock(&inmtx[th])==0) // try locking // ...to prevent ptr passing ndx { ///-----------------------------------------------/// // this prevents userin() from changing input[] or // incount while gameloop functions are using it. count++; // <==note: work to do ///-----------------------------------------------/// /// Program logic follows below... /// ///-----------------------------------------------/// // normal input in buffer from user is processed here... if (threc[th].autowarp==0) 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... /// ///-----------------------------------------------/// // following 2 lines are Brian's 'backspace bugs' fix if (threc[th].control==0 && threc[th].loginstatus > 5) threc[th].incount=0; pthread_mutex_unlock(&inmtx[th]); // note: if input[] or incount is used outside the range // of gameloop control inmtx mutex lock, inmtx must be // locked, then unlocked 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 if (threc[th].autowarp) // (new 5 lines) { userndx=commscan(threc[th].control, commlist); result=fp[userndx](th); // re-call previous func. } 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); 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 && ((radiochan[radiondx.y] == threc[th].radiochan) || (radiopager[radiondx.y]==ThreadToUser[th]))) { if (radiopager[radiondx.y]==ThreadToUser[th]) { show(th,"\a"); threc[th].pagerchan=radiochan[radiondx.y]; } show(th,radiorec[radiondx.y]); threc[th].radionumb=radionumb[radiondx.y]; sentr++; threc[th].sentr++; } radiondx.y++; } while (radiondx.y != radiondx.x && sentr==0); if (threc[th].showmyrad == 1 && sentr == 0) { show(th,"\r\n\nThere are no messages on this channel"); threc[th].showmyrad=0; threc[th].radionumb=radiondx.x; if (threc[th].control == 'r') radioprompt(th); // redraw radio prompt else showsector(th); } if (radiondx.y==radiondx.x) threc[th].showmyrad=0; // sent radio or news, cleanup display if ((threc[th].sentr && threc[th].showmyrad==0) || (sentn && threc[th].showmynews==0)) { threc[th].sentr=0; if (threc[th].control == 'r' && threc[th].status == 1) radioprompt(th); // redraw radio prompt else { threc[th].noshow=1; showsector(th); } } } // timelimit for incomplete logins... 1/3rd idle timeout if (threc[th].loginstatus < 6) if ((now - threc[th].thiscall) > (d.cfg.timeout/2)) { if ( threc[th].logmeout==0 ) { threc[th].logmeout=1; threc[th].logoutmsg=5; } } // exceed daily timelimit, session limit? if (threc[th].loginstatus > 5) if ((d.user[ThreadToUser[th]].timeused + now - d.user[ThreadToUser[th]].lastcall) > (d.cfg.timelimit*60)) { if ( threc[th].logmeout==0 && d.user[ThreadToUser[th]].lastcall) // registered... { threc[th].logmeout=1; threc[th].logoutmsg=3; } } // timeout? if (((now - threc[th].lastaccess) > d.cfg.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=d.cfg.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; case 5: // 5 == incomplete login idle timeout break; case 6: // 6 == booted by admin sprintf(tmp, "\r\nNews: %s booted by sysadmin", 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)); ThreadToUser[th]=0; 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! { ////////// during idle passes, grow fighter fleets ////////// if (d.cfg.empire) { // non-zero 'empire' setting... if ((now - growtime) > 8) { // every 8.64 seconds, on average growtime = now - (rand24(100) > 63 ? 1 : 0); growfleets(rand24(10000));// grow the fleets in 1/10000th of universe } } if (idlecycles<1200) // idle, but less than 6 seconds idle { usleep(IDLESLEEP); // sleep 1/200th second (@5000 usleep) idlecycles++; } else if (idlecycles<12000) // between 6 and 60 seconds idle { usleep(IDLESLEEP*4); // sleep 1/50th second (@5000 usleep) idlecycles++; } else // more than 60 seconds idle time { usleep(IDLESLEEP*20); // sleep 1/10th second (@5000 usleep) idlesystem++; } } else { idlecycles=0; // reset idlecycles to shorten naps idlesystem=0; } 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=200; // base cost is 200, add equipment, goods to that... char tmp[DEFLINE]; user=ThreadToUser[th]; cost += (d.user[user].goods[0]); // cargo holds: 1 gram each cost += (d.user[user].fiters/10000);// fighters: .0001 gram each cost += (d.user[user].beacons); // beacons in storage: 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 cost += (d.user[user].combats*5); // combat systems; 5 grams each cost += (d.user[user].tractor*5); // tractor devices; 5 grams each cost += (d.user[user].scanner*5); // ship scanners; 5 grams each if (inq == 0) { if (d.user[user].antitoday > cost) { d.user[user].antitoday -= cost; threc[th].fuelused += cost; if (d.user[user].antitoday < 250000) { if ((d.user[user].antitoday/25000) != ((cost+d.user[user].antitoday)/25000)) { sprintf(tmp, "\r\n\n * Antimatter reserves under %d grams! *", (cost+d.user[user].antitoday)/25000 * 25000); if (d.user[user].antitoday < 50000) strcat(tmp,"\a"); show(th,tmp); } } } 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; char tmp[DEFLINE+128]; // local buffer for misc use and messages // temporarily disabled // //return(0); // temporarily disabled // // temporarily disabled // 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\n%dU%d+%dM%d+%dH%d+C%d+%dT%d=%d BU%d%c%d U%dM%dH%dC%dT%d ", MAXUSERS+1, // number of User records (int)sizeof(struct userlayout), // size of User record GAMESIZE+1, // number of Map records (int)sizeof(struct maplayout), // size of Map record MAXHIST, // number of History records (int)sizeof(struct histlayout), // size of History record (int)sizeof(struct cfglayout), // size of Config record MAXUSERS+1, // number of Team records (int)sizeof(struct teamlayout), // size of Team record ( (int)sizeof(struct userlayout) * (MAXUSERS+1) // User data size+ +(int)sizeof(struct maplayout) * (GAMESIZE+1) // Map data size+ +(int)sizeof(struct teamlayout) * (MAXUSERS+1) // Team data size+ +(int)sizeof(struct histlayout) * MAXHIST // Hist data size+ +(int)sizeof(struct cfglayout) ), // Config size == st.dat size bu_bkup, // BackUp number bu_sleep, // Sleeping, Active, or last // bu Unsuccessful in 10 passes bu_pass, // pass number of this backup bu_ucount, // total User records written bu_mcount, // total Map records written bu_hcount, // total History recs written bu_ccount, // total Config recs written bu_tcount // total team records written ); 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); } 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 ThreadToUser[th]=x; UserToThread[x]=th; d.user[x].lastcall=time(NULL)-5; // set back to avoid lastcall > now in gameloop d.user[x].antitoday= d.cfg.dailyanti; // start with 1 day of fuel. d.user[x].lastfuel = d.cfg.startdate/60;// initial LF is SD (in minutes) // so proper fuel will be added // when updatefuel() is called. d.user[x].timeused=0; 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; strncpy(d.user[x].pword, hasher(d.user[x].pword, d.user[x].name), 15); d.user[x].pword[15]=0; d.user[x].reppoints=0; d.user[x].beacon=0; d.user[x].beacons=0; d.user[x].combats=0; d.user[x].tractor=0; d.user[x].scanner=0; d.user[x].valid=10; if (x==1) d.user[x].valid=101; // original admin account d.user[x].logons=1; //d.user[x].sector=rand24(GAMESIZE/5*4)+1+GAMESIZE/5;// be born in outer g's // -- for testing purposes -- d.user[x].sector=rand24(GAMESIZE/5)+1;// be born only in milky way (mw) // correct behavior for real games 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]=d.cfg.minholds; // cargo holds d.user[x].goods[1]=d.cfg.minholds/4; d.user[x].goods[2]=d.cfg.minholds/4; d.user[x].goods[3]=d.cfg.minholds/2; //scan universe, removing fighters/bases belonging to user 'x'... for (y=1;y<(GAMESIZE+1);y++) { if (d.sector[y].beacon==x) { d.sector[y].beacon=0; dirty(y); // notify backupdata() thread that this sector changed } if (d.sector[y].fitersowner==x) { d.sector[y].fiters=0; d.sector[y].fitersowner=0; if (d.sector[y].sectortype>9) d.sector[y].sectortype=0; // get starbases, graffiti, and bunkers too, as needed dirty(y); // notify backupdata() thread that this sector changed } } dirtyuser[x]=1; resethist(x); resetteam(x); resetbeacons(x); return(ThreadToUser[th]); // and return the user index number } char * // pass hasher pointers to password and name, hasher(char p[], char n[]) // it returns a pointer to a hashed password. { register int x; static char h[16]; unsigned int pt[4], nt[4]; struct bits { unsigned int i:4; unsigned int j:4; } b; for (x=0;x<4;x++) pt[x]=nt[x]=0; // initialize ints to 0's for (x=0;x<16;x++) h[x]=0; // initialize hash string to 0's memcpy(&pt[0],p,15); // put password in ints memcpy(&nt[0],n,15); // put name in ints memcpy(h,p,15); // make copy of password string // seed all four rand number generators with pw and name sequp = (pt[0]+pt[1]+pt[2]+pt[3]) ^ (nt[0]+nt[1]+nt[2]+nt[3]); sequq = pt[0] * nt[0]; // word 0 of password and name can't equal zero sequr = pt[0] ^ nt[0]; // if name and pw are same 4 chars, this is 0 ;) sequs = (pt[0]+nt[1]+pt[2]+nt[3]) ^ (nt[0]+pt[1]+nt[2]+pt[3]); b.i=0; b.j=1; // init 4-bit bitfields at 0, 1 for (x=0;x<64;x++) h[b.j++] ^= h[b.i++]; // spread password over 16 bytes for (x=0;x<15;x++) do { h[x] ^= randpw(256); // xor each byte with random # h[x] ^= randpx(256); // xor each byte with random # h[x] ^= randpy(256); // xor each byte with random # h[x] ^= randpz(256); // xor each byte with random # } while (h[x]<32 || h[x]>126); // until all are typeable chars h[15]=0; // terminate hashed string return(h); // return pointer to static h[] } void // create new login accounts for new users and login(int th) // log existing users into the game (gameloop thread) { char tmp[DEFLINE]; // local buffer for misc use and messages char crlf[4]; int namelen, error=0, pwlen, dupe=0, gs, x; crlf[0]=13;crlf[1]=10;crlf[2]=0; if (inuse[th]>1) { switch (threc[th].loginstatus) { case 0: // 0==new connection: issue login prompt now show(th,"\r\nWelcome to Space Tyrant, version 0.352"); if (playercount==1) sprintf(tmp," (There is 1 player online)"); else sprintf(tmp," (There are %d players online)", playercount); show(th,tmp); show(th,"\r\n\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) // Immediate error feedback added by Brian namelen=strlen(bufrec[th].input); // how much in input now? strncpy(&bufrec[th].input[namelen], bufrec[th].inbuf[threc[th].inptr], 29-namelen); bufrec[th].input[29]=0; namelen=strlen(bufrec[th].input); if (namelen > 27 && bufrec[th].input[namelen-1]!=10) error = 1; else if (namelen > 1 && strstr(bufrec[th].input," ")) error = 2; else if (!isalpha(bufrec[th].input[0])) error = 2; else if (bufrec[th].input[namelen-1]==10) { if (namelen > 3) bufrec[th].input[namelen-2]=0; // trim off cr/lf if (namelen < 5) error = 1; else if (bufrec[th].input[namelen-3] == ' ') error = 2; else if (strstr(bufrec[th].input, crlf)) error = 3; else { strncpy(bufrec[th].logname, bufrec[th].input, 27); bufrec[th].logname[27]=0; bufrec[th].input[0]=0; for (x=1;x 15 && bufrec[th].input[pwlen-1]!=10) { 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 { 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 if (strstr(bufrec[th].input, crlf)) { show(th,"\r\nPassword contained an extra [Enter]"); bufrec[th].input[0]=0; // start over... threc[th].incount=0; return; } 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) { if (makenewuser(th)==0) // database full { threc[th].timeout=1; threc[th].logmeout=1; threc[th].lastaccess=time(NULL)-d.cfg.timeout+1; threc[th].loginstatus=0; return; } threc[th].radionumb=radionumber; threc[th].radiorand=rand24(9000000)+1000000; threc[th].newsnumb=newsnumber; threc[th].loginstatus=6; threc[th].noecho=0; scanip(th); if ((gs=checkgr(ThreadToUser[th],1)) > d.cfg.grouplimit) { sprintf(tmp,"\r\n\n\n\n\n\n\n\n\n\n\n\n\n\a * * * Group size: %d Limit: %d\r\n\n * * * Account Limit Exceeded!\r\n\n\n\n\n\n\n\n\n",gs,d.cfg.grouplimit); show(th,tmp); threc[th].logmeout=1; //threc[th].logoutmsg=6; bzero(&d.user[ThreadToUser[th]], sizeof(struct userlayout)); dirtyuser[ThreadToUser[th]]=1; } else { if (ThreadToUser[th]==1) show(th, "\r\n\nWelcome to Space Tyrant, Admin!"); else show(th,"\r\n\nWelcome to Space Tyrant, Captain!"); showhistory(th); helppage(th); threc[th].prev16[0]=d.user[ThreadToUser[th]].sector; 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 (strncmp(d.user[ThreadToUser[th]].pword, hasher(bufrec[th].logpword, d.user[ThreadToUser[th]].name), 15)==0) { // banned account? give no time... if (d.user[ThreadToUser[th]].valid<2) { threc[th].loginstatus=0; threc[th].logmeout=1; return; } if ((gs=checkgr(ThreadToUser[th],1))>1) { sprintf(tmp,"\r\n\n * Group size: %d Account Limit: %d\r\n",gs,d.cfg.grouplimit); show(th,tmp); if (gs == (d.cfg.grouplimit-1)) { sprintf(tmp," * Note: You are near the group size limit\r\n"); show(th,tmp); } if (gs == d.cfg.grouplimit) { sprintf(tmp,"\a * Caution! You are at the group size limit. If you create\r\n another account, existing accounts may become unusable!\r\n"); show(th,tmp); } if (gs > d.cfg.grouplimit) { sprintf(tmp,"\a * Warning! You've exceeded the group size limit.\r\n Random existing accounts may become unusable!"); show(th,tmp); } sprintf(tmp,"\r\n * (Note that account sharing can suddenly increase your group size!)"); show(th,tmp); if (d.user[ThreadToUser[th]].valid < 20 && gs > d.cfg.grouplimit) { threc[th].loginstatus=0; threc[th].logmeout=1; return; } } // 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 || ThreadToUser[th]==1) // the admin { d.user[ThreadToUser[th]].logons=1; d.user[ThreadToUser[th]].timeused=0; } if (d.user[ThreadToUser[th]].timeused >= d.cfg.timelimit*60) { // timelimit exceeded show(th, "\r\n\n * You'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]= d.cfg.minholds; d.user[ThreadToUser[th]].sector= rand24(GAMESIZE/5)+1; sprintf(tmp, "\r\nNews: %s was issued a new Starship", d.user[ThreadToUser[th]].name); makenews(th, tmp); } dirtyuser[ThreadToUser[th]]=1; if (ThreadToUser[th]==1) { sprintf(tmp, "\r\n\nWelcome back to ST game #%d, Admin!", d.cfg.idcode); show(th,tmp); } else show(th,"\r\n\nWelcome back, Captain!"); threc[th].radionumb=radionumber; threc[th].radiorand=rand24(9000000)+1000000; threc[th].newsnumb=newsnumber; threc[th].loginstatus=6; threc[th].noecho=0; sprintf(tmp, "\r\nNews: %s woke up", d.user[ThreadToUser[th]].name); if (d.user[ThreadToUser[th]].sector==0) d.user[ThreadToUser[th]].sector =rand24(GAMESIZE/5)+1; makenews(th, tmp); scanip(th); showhistory(th); threc[th].prev16[0]=d.user[ThreadToUser[th]].sector; 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 a one-page gameplay overview screen helppage(int th) // part of gameloop() thread { //keep each show under 160 bytes, here and elsewhere if possible (MAXLINE) char tmp[DEFLINE]; // local buffer for misc use and messages int x; char menu[16][78]= { "", "The universe consists of a single elliptical galaxy (galaxy #1) and some ", " number of 8000-sector galaxies connected to galaxy #1 via spaceports.", " There is a spaceport randomly placed in each 2000-sector block of gal #1.", " The 'outer' galaxies have varying structures and productivity.", "There is a TradingPost in sector #1. In larger games there are others", " in sectors 2001, 4001, and every 2000 sectors thereafter of galaxy #1.", " You can upgrade your spaceship at TradingPosts by buying more cargo holds", " and other devices. Note that you may want to reserve the last 5 cargo", " hold bays for different devices. Each of the three types of specialized ", " devices gives your ship new abilities. When you buy 3 or more of a", " single device type, your ship gains a significant new ability.", "You can make money by trading at ports. You can make more money by trading", " at planet/port combinations. Use the money to buy fighters and starbases", " for attacking other ships, defending yours, and guarding special sectors.", " Type ? to show the commands available or type = to redisplay this page." }; for (x=0;x<16;x+=2) { sprintf(tmp,"\r\n%s\r\n%s",menu[x], menu[x+1]); show(th,tmp); } sprintf(tmp,"\r\n\nThe first spaceport is in sector %d.", d.sector[GAMESIZE/5+1].portcalcdate); show(th,tmp); return(0); } 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) char tmp[DEFLINE]; // local buffer for misc use and messages int x; char menu[24][78]= { "", "", "", " __move_around_the_universe______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 |", " | (>)largest sector number | show (E)vent log | (J)ettison cargo |", " | (<)smallest sector numb. | (V)iew sector | (X) scoop planet/port |", " | (.)next larger number | (?)show this menu | (+) reboot autoscooper |", " | (,)next smaller number | player (G)roups | (C)omputerized trading |", " | bac(K) up to last sector | (Y)our forces |-------------------------*", " | (C)omputerized trading | (F)ind ships |", " *--. (W)arp planets .--* (=)show help page | _____miscellaneous_____", " | (N)udge ports | (U)nionized rankings | | (Z) debugging info |", " *--------------------*----------------------* | (^) admin commands |", " __radio_&_news_commands__ | (&) team menu |", " | (R)adio transmit mode | ST Command Menu | (Q)uit now(!) |", " | (#)*change channel | *---------------------*", " | ([)*replay radio msgs |________________military_commands_________________", " | (])*replay news msgs | (A)ttack ships/forces (D)eploy/recall fighters |", " | (@)*page a player | (B)uy fighters (H)oming beacon(deploy/recall) |", " | *also work in radio | (M)ake, upgrade, or downgrade a starbase |", " *-----------------------*-------------------------------------------------*" }; for (x=0;x<23;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) { char tmp[DEFLINE]; // local buffer for misc use and messages char daytmp[8]; unsigned int user; // local copies initialized below unsigned int now, avgprofit, galnumb, galend, galstart=1; avgprofit = (threc[th].earned - threc[th].spent) / max((threc[th].fuelused/1000),1); if (inuse[th]>1) { now=time(NULL); sprintf(daytmp,"%d",now%DAY/MILLIDAY+1000); user=ThreadToUser[th]; galnumb=getgal(d.user[user].sector); galend=GAMESIZE/5; if (galnumb) { galstart=GAMESIZE/5+8000*(galnumb-1)+1; galend=galstart+7999; } sprintf(tmp,"\r\n\nGalaxy #%d, %s, %s @ sectors %d-%d, Prod: %d%c", 1+galnumb, galname[galnumb%GALAXIES], galtypename[(int)d.cfg.galtype[galnumb]], galstart, galend, galprod[galnumb], '%'); show(th,tmp); sprintf(tmp,"\r\n\nName: %s, Day %d.%s 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 ,&daytmp[1] ,d.user[user].fiters ,d.user[user].antitoday ,santi(th, 1)); show(th,tmp); sprintf(tmp,"\r\nMicrobots ($): %d\r\nReputation Points: %d\r\n\nCargo Holds:%6d\r\n Iron:%10d\r\n Alcohol:%7d\r\n Hardware:%6d" ,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); sprintf(tmp,"\r\nTractor devices: %d\r\nShip scanners: %d", d.user[user].tractor, d.user[user].scanner); show(th,tmp); sprintf(tmp,"\r\nCombat systems: %d (%s)\r\n\nHoming beacons: %d\r\nSession trading average $/kg: %d", d.user[user].combats, shiptype[getshiptype(user)], d.user[user].beacons, avgprofit); show(th,tmp); sprintf(tmp,"\r\n\nThis game began %d days ago and will end in %d days, %d hours, and %d minutes", (int)((time(NULL) - d.cfg.startdate)/DAY), (int)((d.cfg.startdate+(d.cfg.gamelength*DAY))-now)/DAY, (int)((d.cfg.startdate+(d.cfg.gamelength*DAY))-now)%DAY/3600, (int)((d.cfg.startdate+(d.cfg.gamelength*DAY))-now)%DAY%3600 /60); 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 // team rankings... check timestamp, if over 5 seconds old, userteams(int th) // generate a fresh one, otherwise just reshow the last one. { int x, linecount=1, showcount=0, input; char tmp[DEFLINE]; // local buffer for misc use input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='u') // second, subsequent passes { if (input=='n') { threc[th].control=0; showsector(th); return(0); } else linecount=threc[th].status; } if ((time(NULL) - teamtime) > 5) { for (x=0;x1 && d.user[x].logons) { teams[d.user[x].team] += d.user[x].cash/100; // microbots teams[d.user[x].team] += d.user[x].reppoints/100; // same as cash teams[d.user[x].team] +=(d.user[x].antitoday/20)/100; // 20 gram/$ teams[d.user[x].team] +=(d.user[x].fiters*FIGPRICE)/100; // configurable teams[d.user[x].team] +=(d.user[x].goods[0]*HOLDPRICE)/100;// cargo holds teams[d.user[x].team] +=(d.user[x].beacons*HOLDPRICE)/100; // hom. beacons teams[d.user[x].team] +=(d.user[x].combats*HOLDPRICE)/100; // combat sys teams[d.user[x].team] +=(d.user[x].tractor*HOLDPRICE)/100; // tractors teams[d.user[x].team] +=(d.user[x].scanner*HOLDPRICE)/100; // ship scanners if (d.user[x].beacon) teams[d.user[d.user[x].beacon].team] +=HOLDPRICE/100; // hull beacon teams[d.user[x].team] +=(d.user[x].goods[1]*6)/100; // iron teams[d.user[x].team] +=(d.user[x].goods[2]*9)/100; // alch teams[d.user[x].team] +=(d.user[x].goods[3]*12)/100; // hardware } } for (x=1;x<=GAMESIZE;x++) // add in deployed fighters, bases, beacons { if (d.sector[x].fitersowner && d.sector[x].sectortype > 9 && d.user[d.sector[x].fitersowner].name[0] && d.user[d.sector[x].fitersowner].logons && d.user[d.sector[x].fitersowner].valid>1) { teams[d.user[d.sector[x].fitersowner].team] += (baseprice[d.sector[x].sectortype-9])/100; } if (d.sector[x].fitersowner && d.user[d.sector[x].fitersowner].name[0] && d.user[d.sector[x].fitersowner].logons && d.user[d.sector[x].fitersowner].valid>1) { teams[d.user[d.sector[x].fitersowner].team] += (d.sector[x].fiters*FIGPRICE)/100; } if (d.sector[x].beacon && d.user[d.sector[x].beacon].name[0] && d.user[d.sector[x].beacon].logons && d.user[d.sector[x].beacon].valid>1) teams[d.user[d.sector[x].beacon].team] += HOLDPRICE/100; } } sortint(&teams[0],&teamsndx[0],MAXUSERS,1); sprintf(tmp, "\r\n\nRank _______________Team Name_______________ [INT] ____Score____ TTag"); show(th,tmp); for (x=linecount;x 0) { sprintf(tmp,"\r\n%4d %-39s [%-3s] %11d00 %4d", x, d.team[teamsndx[x-1]].name, d.team[teamsndx[x-1]].init, teams[x-1], teamsndx[x-1]); if (showcount>LINES) break; show(th,tmp); linecount++; showcount++; } } if (x 5) { scoretime=time(NULL); for (x=1;x1) { online[x]=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*FIGPRICE); // configurable scores[x] +=(d.user[x].goods[0]*HOLDPRICE);// cargo holds scores[x] +=(d.user[x].beacons*HOLDPRICE); // homing beacons scores[x] +=(d.user[x].combats*HOLDPRICE); // combat systems scores[x] +=(d.user[x].tractor*HOLDPRICE); // tractor devices scores[x] +=(d.user[x].scanner*HOLDPRICE); // ship scanners scores[d.user[x].beacon]+=HOLDPRICE; // hull beacon 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 if (inuse[UserToThread[x]] && x==ThreadToUser[UserToThread[x]]) online[x]=1; } scoresndx[x]=x; // player number } for (x=1;x<=GAMESIZE;x++) // add in deployed fighters, bases, beacons { if (d.sector[x].sectortype > 9) { if (d.user[d.sector[x].fitersowner].valid>1) scores[d.sector[x].fitersowner] += (baseprice[d.sector[x].sectortype-9]); } if (d.user[d.sector[x].fitersowner].valid>1) { scores[d.sector[x].fitersowner] += (d.sector[x].fiters * FIGPRICE); } scores[d.sector[x].beacon] += HOLDPRICE; } } sortint(&scores[1],&scoresndx[1],MAXUSERS-1,1); sprintf(tmp, "\r\n\nRank ________Player Name________ Team_ Ship_ ___Score___ _____Galaxy_____ _Tag_"); show(th,tmp); for (x=linecount;x 0) { sprintf(tmp,"\r\n%4d%c%-27s [%-3s] %-5s %10d %-16s %5d", x, flag[online[scoresndx[x]]], d.user[scoresndx[x]].name, d.team[d.user[scoresndx[x]].team].init, shiptypeinit[getshiptype(scoresndx[x])], scores[x], d.user[scoresndx[x]].sector==0 ? "~ship destroyed~" : galname[getgal(d.user[scoresndx[x]].sector)%GALAXIES], scoresndx[x]); if (d.user[scoresndx[x]].logons) { if (showcount>LINES) break; show(th,tmp); linecount++; showcount++; } } } if (x 5) // only build grouplist periodically { bzero(used,MAXUSERS+1); bzero(groupuser,(MAXUSERS+1)*2); bzero(grouplist,(MAXUSERS+1)*2); } show(th,"\r\n"); input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='g') // second, subsequent passes { if (input=='n') { threc[th].control=0; showsector(th); return(0); } else { linecount=threc[th].status; } } if ((now - grouptime) > 5) // only build grouplist periodically { grouptime=time(NULL); for (x=1;xLINES) break; if (groupsize[grouplist[x]] > 1) { // don't show groups of 1 sprintf(tmp,"\r\n group%4d: %-27s", grouplist[x], d.user[groupuser[x]].name); if ((now - d.user[groupuser[x]].lastcall) > (d.cfg.gamelength * DAY)) strcat(tmp," [inactive]"); show(th,tmp); linecount++; showcount++; } } } if (x 3) return(3); // 3 "Freighter", >3 extra cargo holds if (c == 3) return(4); // 4 "Frigate", 3 combat systems if (c > 3) return(5); // 5 "Battleship", >3 combat systems if (t == 3) return(6); // 6 "Tug", 3 tractor devices if (t > 3) return(7); // 7 "Heavy Tug", >3 tractor devices if (s == 3) return(8); // 8 "Scout", 3 scanners if (s > 3) return(9); // 9 "Hunter", >3 scanners if (h+c+t+s == 5) return(1); // 1 "Cruiser" <3 of any device, 5 total return(0); // 0 "Light Cruiser" <3 any device, <5 total } int growfleets(segment) { int start, end, x, newfigs, oldfigs, rem, factor; start=GAMESIZE/10000*segment+1; end=start+GAMESIZE/10000; factor=d.cfg.empire/5; for (x=start; x<= end; x++) { if (d.sector[x].fitersowner && d.sector[x].fiters) { oldfigs=d.sector[x].fiters; newfigs=(oldfigs*factor)/20; rem =(oldfigs*factor)%20; if (rand16(20) < rem) newfigs++; if (newfigs > factor) if ((newfigs*newfigs) > (factor*oldfigs)) newfigs=sqroot(factor*oldfigs); d.sector[x].fiters += newfigs; if (newfigs) dirty(x); } } return(0); } int sqroot(product) { register int x; for (x=1;(x*x)<=product;x++); return(x-1); } int matcher(int user) // find all users IP linked to 'user' { int ipu, ipx, x; for (ipu=0;ipu0 && (team==d.user[ship].team)) return(1); return(0); } int // show a user's deployed forces (figs, HB's, bases) forces(int th) { int x, t, input, linecount=1, showcount=0, user; char tmp[DEFLINE]; // local buffer for misc use show(th,"\r\n"); t=time(NULL); if (t - threc[th].lastscan < 10) { show(th, "\r\n * Your deployed forces report isn't ready yet! *"); return(0); } threc[th].lastscan = t; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); user=ThreadToUser[th]; if (threc[th].control=='y') // second, subsequent passes { if (input=='n') { threc[th].control=0; showsector(th); return(0); } else linecount=threc[th].status; } for (x=linecount;x14) if (d.sector[x].fitersowner!=user) if (isally(d.user[user].team, d.sector[x].fitersowner)) { sprintf(tmp,"\r\n A #%d Team Starbase is in sector %d", d.sector[x].sectortype - 9, x); show(th,tmp); linecount++; showcount++; } if (d.sector[x].beacon==user || (d.sector[x].fitersowner==user)) { if (showcount>LINES && nopause==0) break; if (d.sector[x].beacon==user) { sprintf(tmp,"\r\n You have a homing beacon in sector %d",x); show(th,tmp); linecount++; showcount++; } if (d.sector[x].sectortype>9) if (d.sector[x].fitersowner==user) { sprintf(tmp,"\r\n You have a #%d Starbase in sector %d", d.sector[x].sectortype - 9, x); show(th,tmp); linecount++; showcount++; } if (d.sector[x].fitersowner==user && d.sector[x].fiters>0) { if (d.sector[x].fiters==1) sprintf(tmp,"\r\n You have 1 fighter in sector %d", x); else sprintf(tmp,"\r\n You have %d fighters in sector %d", d.sector[x].fiters, x); show(th,tmp); linecount++; showcount++; } } } if (xLINES && nopause==0) break; if (nopause) { sprintf(tmp, "\r\n [tag:%d] %s, active %d.%d minutes [%s]", x, d.user[x].name, (int)((t - d.user[x].lastcall)/60), (int)(((t - d.user[x].lastcall)%60)/6), galname[getgal(d.user[x].sector)%GALAXIES]); } else { sprintf(tmp,"\r\n [tag:%d] %s [%s] (%s), active %d.%d minutes [%s]", x, d.user[x].name, d.team[d.user[x].team].init, shiptype[getshiptype(x)], (int)((t - d.user[x].lastcall)/60), (int)(((t - d.user[x].lastcall)%60)/6), galname[getgal(d.user[x].sector)%GALAXIES]); } show(th,tmp); linecount++; showcount++; } } if (x165) show(th,"\r\n\n(This sector is a virtual junkyard of starship wrecks)"); else if (d.sector[sector].dust>125) show(th,"\r\n\n(There are clumps of twisted and charred metal here)"); else if (d.sector[sector].dust>95) show(th,"\r\n\n(There are massive clouds of starship debris here)"); else if (d.sector[sector].dust>75) show(th,"\r\n\n(Chunks of destroyed starships slowly orbit here)"); else if (d.sector[sector].dust>42) show(th,"\r\n\n(A shattered fuselage of a starship drifts by)"); else if (d.sector[sector].dust>35) show(th,"\r\n\n(There are clumps of fighter parts drifting here)"); else if (d.sector[sector].dust>28) show(th,"\r\n\n(Starlight glints off drifting fighter debris here)"); else if (d.sector[sector].dust>21) show(th,"\r\n\n(Metal dust and bits of fighters drift aimlessly here)"); else if (d.sector[sector].dust>14) show(th,"\r\n\n(There are drifting clouds of cargo dust here)"); else if (d.sector[sector].dust>7) show(th,"\r\n\n(There is a metallic haze in this sector)"); else show(th,"\r\n\n(Faint wisps of metallic fog permeate this sector)"); d.sector[sector].dust--; return(0); } int showsector(int th) // part of gameloop() thread! (displays sector contents) { char tmp[DEFLINE]; // local buffer for misc use char tmp1[DEFLINE]; // local buffer for misc use char showtmp[DEFLINE]; // local buffer for misc use unsigned char state[2][8]= { "asleep", "awake" }; unsigned int x, y, awake; unsigned int sector, count=0, counter=0, user, porttype, moontype; // local copies initialized below bzero(showtmp,DEFLINE); sector=d.user[ThreadToUser[th]].sector; user=ThreadToUser[th]; if (threc[th].noshow==0) { if (threc[th].autowarp == 0) showdebris(th); porttype=d.sector[sector].porttype; moontype=d.sector[sector].moontype; if (inuse[th]>1) // redundant { if (d.user[user].beacon) { if (UserToThread[d.user[user].beacon] && (d.user[user].beacon== ThreadToUser[UserToThread[d.user[user].beacon]]) && (threc[UserToThread[d.user[user].beacon]].beaconmode==0)) { sprintf(tmp,"\r\n(Beacon: %s is in sector %d)\r\n", d.user[user].name, d.user[user].sector); show(UserToThread[d.user[user].beacon],tmp); } } if (d.sector[sector].beacon==user) show(th,"\r\n\n(You have a homing beacon here)"); if (d.sector[sector].sectortype>9 && d.sector[sector].fitersowner!=user) { sprintf(tmp,"\r\n\n(Sector: %d)\r\n\n(There is a #%d Starbase here attached to %s [%s])", sector, d.sector[sector].sectortype-9, d.user[d.sector[sector].fitersowner].name, d.team[d.user[d.sector[sector].fitersowner].team].init); show(th,tmp); if (d.user[user].team==0 || (d.user[user].team && (d.user[user].team != d.user[d.sector[sector].fitersowner].team))) { threc[th].control=0; fightbase(th); return(0); } } 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 // team fighters? if (d.user[user].team && (d.user[user].team == d.user[d.sector[sector].fitersowner].team)) sprintf(tmp,"\r\n\n(%d allied fighters attached to %s)", d.sector[sector].fiters, d.user[d.sector[sector].fitersowner].name); 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); threc[th].control=0; atakrtn(th); return(0); } } else sprintf(tmp,"\r\n\n(%d neutral fighters)", d.sector[sector].fiters); strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); } sprintf(tmp,"\r\n\nSector: %d",sector); strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); if (porttype) { if (d.sector[sector].portprod[0] && ((time(NULL) - d.sector[sector].portcalcdate) < 691200 || d.sector[sector].porttype > 3)) sprintf(tmp,"\r\n%s %s: selling %s", plantname[porttype], d.user[d.sector[sector].portprod[0]].name, goodsname[porttype]); else sprintf(tmp,"\r\n%s (abandoned): selling %s", plantname[porttype], goodsname[porttype]); if (porttype==5) // where does it go...? { sprintf(tmp1," to %s", galname[getgal(d.sector[sector].portcalcdate)%GALAXIES]); strcat(tmp,tmp1); } strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); } if (moontype) { if (d.sector[sector].moonprod[0] && (time(NULL) - d.sector[sector].mooncalcdate) < 691200) sprintf(tmp,"\r\nPlanet %s: producing %s, %s, %s", d.user[d.sector[sector].moonprod[0]].name, goodsname[1], goodsname[2], goodsname[3]); else sprintf(tmp,"\r\nPlanet (abandoned): producing %s, %s, %s", goodsname[1], goodsname[2], goodsname[3]); strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); //show(th,tmp); } show(th,showtmp); bzero(showtmp,DEFLINE); counter = 0; // find other ships... for (x=1;x 9) { sprintf(tmp,"\r\n[%s (%d), owned by %s]", sectorname[d.sector[sector].sectortype], basestrength[d.sector[sector].sectortype-9], d.user[d.sector[sector].fitersowner].name); } else { sprintf(tmp,"\r\n[%s]", sectorname[d.sector[sector].sectortype]); } strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); } getletter(th); bzero(tmp,sizeof(tmp)); sprintf(tmp,"\r\nWarps go to: "); for (y=1;y<7;y++) { if (d.sector[sector].warp[y-1]) { sprintf(tmp1,"(%d)%u%c ", y, d.sector[sector].warp[y-1], portletter[y-1]); strcat(tmp,tmp1); if (portletter[y-1]=='~') { sprintf(tmp1,"%c",7); strcat(tmp,tmp1); } } } show(th,showtmp); bzero(showtmp,DEFLINE); counter = 0; if (threc[th].autowarp == 0) { sprintf(tmp1,"\r\n\n[%d] (mins: %d) %s%s choice: ", sector, d.cfg.timelimit - (int)(1+(d.user[user].timeused + time(NULL) - d.user[user].lastcall)/60), nudgeprompt[threc[th].nudgemode], warpprompt[threc[th].warpmode]); strcat(tmp,tmp1); } strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); //show(th,tmp); } } else { if (threc[th].noshow < 2) { sprintf(tmp,"\r\n\n[%d] (mins: %d) %s %s choice: ", sector, d.cfg.timelimit - (int)(1+(d.user[user].timeused + time(NULL) - d.user[user].lastcall)/60), nudgeprompt[threc[th].nudgemode], warpprompt[threc[th].warpmode]); strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); } if (threc[th].noshow == 5) { sprintf(tmp,"\r\n\n[%d] (mins: %d) admin: ", sector, d.cfg.timelimit - (int)(1+(d.user[user].timeused + time(NULL) - d.user[user].lastcall)/60)); strncat(showtmp,tmp,MAXLINE - counter); counter+=strlen(tmp); } threc[th].noshow=0; } if (counter) show(th,showtmp); bzero(showtmp,DEFLINE); counter = 0; return(0); } int teaminitexists(int th) { int x; for (x=0;x0 // " || d.user[user].app>0) // " return(0); // " input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='c') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='c' && inputlen > 1) //second+ pass: got input yet? { if (((inputlen>40) || (bufrec[th].input[inputlen-2]==13)) // got name. || ((inputlen>4) && (threc[th].data==1))) // got init. { if (threc[th].data==0) { if (inputlen < 5) { show(th,"\r\n\n\a * That name is too short! -- exiting *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } if (bufrec[th].input[inputlen-2]==13) // got name. bufrec[th].input[inputlen-2]=0; // trim cr/lf // filter name for banned strings as well here... if (teamnameexists(th)==1) { show(th,"\r\n\n\a * That name is in use! -- exiting *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } strncpy(bufrec[th].insav,bufrec[th].input,39); bzero(bufrec[th].input,DEFLINE); threc[th].data=1; sprintf(tmp, "\r\n\nEnter a three letter ID for your team: "); show(th,tmp); return(0); } if (threc[th].data==1) { if (inputlen < 3) { show(th,"\r\n\n\a * You need 3 letters here! -- exiting *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } // filter name for banned strings as well here... if (teaminitexists(th)==1) { show(th,"\r\n\n\a * That ID is in use! -- exiting *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } strncpy(teaminit,bufrec[th].input,3); bzero(bufrec[th].input,DEFLINE); threc[th].data=2; for (x=1;x0) continue; else { newteam=x; break; } } bzero(&d.team[newteam], sizeof(struct teamlayout)); d.user[user].team=newteam; dirtyuser[user]=1; d.team[newteam].ally[0]=user; strncpy(d.team[newteam].name,bufrec[th].insav,39); strncpy(d.team[newteam].init,teaminit,3); dirtyteam[newteam]=1; } bufrec[th].input[0]=0; threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { show(th,"\r\n\nPlease select a new team name: "); threc[th].status='c'; threc[th].data=0; } return(0); } int teamdisband(int th) { int team, user, x, members=0; user=ThreadToUser[th]; team=d.user[user].team; if (threc[th].status=='d') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } for (x=1;x0) members++; } if (members>0) { show(th, "\r\n\n * You muse expel all members first! *"); threc[th].status=0; return(0); } if (threc[th].status=='d' && strlen(bufrec[th].input) > 0) { if (tolower(bufrec[th].input[0]=='y')) { bzero(&d.team[team],sizeof(struct teamlayout)); dirtyteam[team]=1; d.user[user].team=0; dirtyuser[user]=1; threc[th].status=0; return(0); } else { threc[th].status=0; return(0); } } if (threc[th].status!='d') { threc[th].status='d'; show(th,"\r\n\nReally disband your team? (Y/N) [N]: "); } return(0); } int teamexpel(int th) { char tmp[DEFLINE]; int team, user, inputlen, mbr=0, val=0; user=ThreadToUser[th]; team=d.user[user].team; if (threc[th].status=='e') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='e') { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val<1 || val > d.cfg.teamsize) { threc[th].status=0; return(0); } mbr=d.team[team].ally[val]; if (mbr > 0 && d.user[mbr].team==team) { d.user[mbr].team=0; d.team[team].ally[val]=0; dirtyuser[mbr]=1; dirtyteam[team]=1; sprintf(tmp,"\r\n%s expelled you from team #%d", d.user[user].name, team); makehistory(user,mbr,tmp); threc[th].status=0; bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } else { threc[th].status=0; return(0); } } } if (threc[th].status!='e') { teammembers(th); threc[th].status='e'; show(th, "\r\n\nEnter the member number to expel ([Enter] to exit): "); } return(0); } int teamnewcaptain(int th) { char tmp[DEFLINE]; int team, user, inputlen, mbr=0, val=0; user=ThreadToUser[th]; team=d.user[user].team; if (threc[th].status=='g') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='g') { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val<1 || val > d.cfg.teamsize) { threc[th].status=0; return(0); } mbr=d.team[team].ally[val]; if (mbr > 0 && d.user[mbr].team==team) { d.team[team].ally[val]=user; d.team[team].ally[0]=mbr; dirtyuser[user]=1; dirtyuser[mbr]=1; dirtyteam[team]=1; sprintf(tmp,"\r\n%s made you captain of team #%d", d.user[user].name, team); makehistory(user,mbr,tmp); } threc[th].status=0; return(0); } } if (threc[th].status!='g') { teammembers(th); threc[th].status='g'; show(th, "\r\n\nEnter member # of new captain ([Enter] to exit): "); } return(0); } int teammembers(int th) { char tmp[DEFLINE]; int x, team; team=d.user[ThreadToUser[th]].team; for (x=0;x0) { sprintf(tmp,"\r\n Member #: %2d Name: %s [%s]", x, d.user[d.team[team].ally[x]].name, d.team[team].init); show(