# /*//////// compile shell script follows: type `sh sti.c` to compile st //////// gcc -g -O2 -D_REENTRANT -Wall -c -o st.o sti.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.354 (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 in unix time unsigned int gamelength; // game ends on startdate+(gamelength*DAY) 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 older versions of OSX osx //#else setpgrp(); // format for Linux linux, IntelLeopard, PPCLeopard?, Tiger? //#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) - 3600) { 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)/300) { 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(tolower(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(tolower(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.354"); 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(th,tmp); } } return(0); } int teamreview(int th) { char tmp[DEFLINE]; int team, user, x, y, input=0; user=ThreadToUser[th]; team=d.user[user].team; if (threc[th].status=='r') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } if (threc[th].status=='r' && strlen(bufrec[th].input) > 0) { input=tolower(bufrec[th].input[0]); if (input=='y') { // admit user currently in 'data' for (y=1;y0) { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val<1 || val > MAXUSERS) { threc[th].status=0; return(0); } for (x=1;x0) { for (y=0;y0) count++; } if (count && count < d.cfg.teamsize) { sprintf(tmp,"\r\n Team #:%4d [%s] Vac:%3d Name: %s", x, d.team[x].init, d.cfg.teamsize - count, d.team[x].name); show(th,tmp); } } return(0); } int teammenu(int th) { int user, team; char tmp[DEFLINE]; #define TMENULEN 12 char menu[TMENULEN][36]= { ".___________________________.", "| (A) Apply to a team |", "| (C) Create a new team |", "| (D) Disband your team |", "| (E) Expel a member |", "| (G) Give up captain role |", "| (L) List team members |", "| (R) Review applications |", "| (T) Turn in resignation |", "| (W) Withdraw application |", "| (Q) Quit from team menu |", "|___________________________|" }; user=ThreadToUser[th]; team=d.user[user].team; if (d.user[user].app>0) { sprintf(tmp,"\r\n\n * Your application to team #%d is pending *", d.user[user].app); show(th,tmp); } sprintf(tmp,"\r\n%s",menu[0]); show(th,tmp); if (d.user[user].team==0 && d.user[user].app==0) { sprintf(tmp,"\r\n%s",menu[1]); show(th,tmp); } if (d.user[user].fiters>999 && d.user[user].team==0 && d.user[user].app==0) { sprintf(tmp,"\r\n%s",menu[2]); show(th,tmp); } if (d.user[user].team>0 && d.team[team].ally[0]==user) { sprintf(tmp,"\r\n%s",menu[3]); show(th,tmp); } if (d.user[user].team>0 && d.team[team].ally[0]==user) { sprintf(tmp,"\r\n%s",menu[4]); show(th,tmp); } if (d.user[user].team>0 && d.team[team].ally[0]==user) { sprintf(tmp,"\r\n%s",menu[5]); show(th,tmp); } if (d.user[user].team>0) { sprintf(tmp,"\r\n%s",menu[6]); show(th,tmp); } if (d.user[user].team>0 && d.team[team].ally[0]==user) { sprintf(tmp,"\r\n%s",menu[7]); show(th,tmp); } if (d.user[user].team>0 && d.team[team].ally[0]!=user) { sprintf(tmp,"\r\n%s",menu[8]); show(th,tmp); } if (d.user[user].app>0) { sprintf(tmp,"\r\n%s",menu[9]); show(th,tmp); } sprintf(tmp,"\r\n%s",menu[10]); show(th,tmp); sprintf(tmp,"\r\n%s",menu[TMENULEN-1]); show(th,tmp); threc[th].noshow=5; threc[th].incount=0; showsector(th); return(0); } int teamadmin(int th) { int input, user, team; user=ThreadToUser[th]; team=d.user[user].team; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status) // admin picked a function in earlier pass... input=threc[th].status; // ...load it into input if (threc[th].control=='&') { threc[th].noshow=5; switch(input) { // fix the if statements to qualify user for command // ***** here and in menu display! case 'a': if (d.user[user].team==0 && d.user[user].app==0) teamapply(th); break; case 'c': if (d.user[user].fiters > 999 && d.user[user].team==0) teamcreate(th); break; case 'd': if (d.user[user].team>0 && d.team[team].ally[0]==user) teamdisband(th); break; case 'e': if (d.user[user].team>0 && d.team[team].ally[0]==user) teamexpel(th); break; case 'g': if (d.user[user].team>0 && d.team[team].ally[0]==user) teamnewcaptain(th); break; case 'l': if (d.user[user].team>0) teammembers(th); break; case 'r': if (d.user[user].team>0 && d.team[team].ally[0]==user) teamreview(th); break; case 't': if (d.user[user].team>0 && d.team[team].ally[0]!=user) teamresign(th); break; case 'w': if (d.user[user].app>0) teamwithdraw(th); break; case 13: // cr hit... exit case '!': // ... exit case 'q': threc[th].control=0; threc[th].noshow=0; showsector(th); return(0); default: show(th,"\r\n\n * Invalid selection *"); } if (threc[th].status==0) // no function active; show menu again teammenu(th); } else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; threc[th].status=0; threc[th].data=0; threc[th].control='&'; teammenu(th); input = 0; accumulateinput(th); return(0); } return(0); } int admin(int th) { int x, input, user; char tmp[DEFLINE]; #define MENULEN 19 char menu[MENULEN][36]= { ".___________________________.", "| (G) change Gamecycle |", "| (P) Productivity mult. |", "| (L) change game Length |", "| (S) change Start date |", "| (F) Fuel:daily antimatter |", "| (X) maXimum cargo holds |", "| (N) miNimum cargo holds |", "| (T) daily Time limit |", "| (I) Idle keyboard timeout |", "| (V) user Validation level |", "| (W) change passWord |", "| (Q) Quit from this menu |", "| (Z) max group siZe |", "| (B) Boot a player |", "| (R) Reset player password |", "| (E) grow deployed fleets! |", "| (M) toggle beacon mode |", "|___________________________|" }; d.cfg.access[17]=1; // patched until edit access levels is implemented user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status) // admin picked a function in earlier pass... input=threc[th].status; // ...load it into input if (threc[th].control=='^') { threc[th].noshow=5; switch(input) { case 'g': if (d.user[user].valid >= d.cfg.access[1]) chgamecycle(th); break; case 'p': if (d.user[user].valid >= d.cfg.access[2]) chprodmult(th); break; case 'l': if (d.user[user].valid >= d.cfg.access[3]) chgamelen(th); break; case 's': if (d.user[user].valid >= d.cfg.access[4]) chstartdate(th); break; case 'f': if (d.user[user].valid >= d.cfg.access[5]) chdailyanti(th); break; case 'x': if (d.user[user].valid >= d.cfg.access[6]) chmaxholds(th); break; case 'n': if (d.user[user].valid >= d.cfg.access[7]) chminholds(th); break; case 't': if (d.user[user].valid >= d.cfg.access[8]) chtimelimit(th); break; case 'i': if (d.user[user].valid >= d.cfg.access[9]) chtimeout(th); break; case 'v': if (d.user[user].valid >= d.cfg.access[10]) chvalid(th); break; case 'b': if (d.user[user].valid >= d.cfg.access[14]) boot(th); break; case 'r': if (d.user[user].valid >= d.cfg.access[14]) resetpw(th); break; case 'e': if (d.user[user].valid >= d.cfg.access[15]) chempire(th); break; case 'm': if (d.user[user].valid >= d.cfg.access[17]) chbeacon(th); break; case 'w': if (d.user[user].valid >= d.cfg.access[11]) chpassword(th); break; case 'z': if (d.user[user].valid >= d.cfg.access[13]) chgrouplimit(th); break; case 13: // cr hit... exit case '!': // ... exit case 'q': threc[th].control=0; threc[th].noshow=0; showsector(th); return(0); default: show(th,"\r\n\n * Invalid selection *"); } if (threc[th].status==0) // no function active; show menu again { sprintf(tmp,"\r\n%s",menu[0]); show(th,tmp); for (x=1;x= d.cfg.access[x]) { sprintf(tmp,"\r\n%s",menu[x]); show(th,tmp); } } sprintf(tmp,"\r\n%s",menu[MENULEN-1]); show(th,tmp); threc[th].noshow=5; threc[th].incount=0; showsector(th); } } else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; threc[th].status=0; threc[th].data=0; sprintf(tmp,"\r\n\n * You are player #%d * ", user); show(th,tmp); if (d.user[user].valid >= 100) { show(th," (The system admin)\r\n\n(Press ^ within 4 seconds to halt the game)\r\n"); shutdowntime = time(NULL); } { sprintf(tmp,"\r\n%s",menu[0]); show(th,tmp); for (x=1;x= d.cfg.access[x]) { sprintf(tmp,"\r\n%s",menu[x]); show(th,tmp); } } sprintf(tmp,"\r\n%s",menu[MENULEN-1]); show(th,tmp); threc[th].noshow=5; threc[th].control='^'; showsector(th); return(0); } } if (d.user[user].valid >=100 && (time(NULL) - shutdowntime) < 5 && input == '^') { threc[th].noshow=0; threc[th].control=0; shutdownnow=1; } return(0); } int tpmenu(int th) { int user, devices, limit; char tmp[DEFLINE]; #define TPMENULEN 12 char menu[TPMENULEN][80]= { ".__________________________.____________________________________________.", "| (H) *buy cargo Holds | Bays are shared with *other devices. |", "| *(C) buy Combat systems | * You can buy a total of 5 combat systems, |", "| *(T) buy Tractor devices | tractor devices, and ship scanners only |", "| *(S) buy Ship scanners | if you leave 5 cargo hold bays unused! |", "| (B) buy homing Beacons | Drop these in space to attach to ships. |", "| (R) Remove homing beacon | There is a homing beacon on you hull! |", "| (F) sell Fighters | Get a full refund for used fighters. |", "| (N) gamble at 'Numbers' | Bet, guess the number, win big money! |", "| (D) gamble at 'Dice' | Bet, guess the dice total, win cash! |", "| (Q) Quit from this menu |____________________________________________|", "|__________________________|" }; user=ThreadToUser[th]; sprintf(tmp,"\r\n%s",menu[0]); show(th,tmp); limit=(int)(d.user[user].cash/HOLDPRICE); if ( (limit > 0) && d.user[user].goods[0] // holds + d.user[user].combats + d.user[user].tractor + d.user[user].scanner < d.cfg.maxholds) { sprintf(tmp,"\r\n%s",menu[1]); show(th,tmp); } limit=(int)(d.user[user].cash/HOLDPRICE); devices=d.user[user].combats + d.user[user].tractor + d.user[user].scanner; limit=min(limit,d.cfg.maxholds - devices - d.user[user].goods[0]); limit=min(5-devices,limit); if (limit>0) { sprintf(tmp,"\r\n%s",menu[2]); show(th,tmp); } if (limit>0) { sprintf(tmp,"\r\n%s",menu[3]); show(th,tmp); } if (limit>0) { sprintf(tmp,"\r\n%s",menu[4]); show(th,tmp); } limit=(int)(d.user[user].cash/HOLDPRICE); if (d.user[user].beacons<10 && limit>0) { sprintf(tmp,"\r\n%s",menu[5]); show(th,tmp); } if (d.user[user].beacon>0) { sprintf(tmp,"\r\n%s",menu[6]); show(th,tmp); } if (d.user[user].fiters>0) { sprintf(tmp,"\r\n%s",menu[7]); show(th,tmp); } if (d.user[user].cash>24999 && d.user[user].antitoday>24999) { sprintf(tmp,"\r\n%s",menu[8]); show(th,tmp); } if (d.user[user].cash>24999 && d.user[user].antitoday>24999) { sprintf(tmp,"\r\n%s",menu[9]); show(th,tmp); } sprintf(tmp,"\r\n%s",menu[TPMENULEN-2]); show(th,tmp); sprintf(tmp,"\r\n%s",menu[TPMENULEN-1]); show(th,tmp); return(0); } int // implement 36-number 'dice' game dicertn(int th) { char tmp[DEFLINE]; int prizelist[13]= {0,0,1080,540,360,270,216,180,216,270,360,540,1080}; int user, sector, lucky, number=0, inputlen, bet, prize, max, a, b; int pfac=200, pdiv=300; // 200-401 == .66 - 1.33 x bet pfac = pfac + (d.cfg.prodmult-25) / 7; // 0-67 pfac = pfac + ((d.cfg.maxholds-99)* 8) / 9; // 0-134 if (pfac < 200) pfac=200; // in case maxholds/prodmult code is changed user=ThreadToUser[th]; sector=d.user[user].sector; sequz=sector+d.cfg.idcode+373465; max=randz(18000)+2000; if (d.user[user].cash<25000) { show(th,"\r\n\n\a * You don't have enough microbots! *"); threc[th].status=0; threc[th].value=0; return(0); } if (d.user[user].antitoday<25000) { show(th,"\r\n\n\a * You don't have enough antimatter! *"); threc[th].status=0; threc[th].value=0; return(0); } if (threc[th].status=='d') // get input on all but initial prompting pass accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (inputlen>1) //...all values initialized. This is a normal gambling pass if (threc[th].status=='d' && (threc[th].data>0||threc[th].data==-3) && (inputlen > 4 || bufrec[th].input[inputlen-2]==13)) { bet=threc[th].value; if (bufrec[th].input[0]==13) number = threc[th].data > 0 ? threc[th].data : 7; else { number=atoi(bufrec[th].input); threc[th].data=number; } if (number > 12) number = 12; if (number) if (number < 2) number = 2; threc[th].data=number; a=rand16(6)+1; b=rand16(6)+1; lucky=a+b; if (number!=0) { // play ...gambling code goes here... prize=0; if (number==lucky) prize=bet*prizelist[number]/10*pfac/pdiv; d.user[user].antitoday-=bet; d.user[user].cash-=bet; threc[th].anteused+=bet; threc[th].winnings-=bet; if (prize>0) { sprintf(tmp,"\r\n\n **** You won a prize of %d! ****\n", prize); show(th,tmp); d.user[user].cash+=(prize); threc[th].winnings+=(prize); } sprintf(tmp,"\r\n Your cash: %d, your fuel: %d\r\n Your winnings: %d, your Profit/Kg fuel used: %d", d.user[user].cash, d.user[user].antitoday, threc[th].winnings, threc[th].winnings/(1+(threc[th].anteused/1000))); show(th,tmp); sprintf(tmp,"\r\n Dice show: %d, %d, your total: %d, bet: %d ", a, b, number, bet); show(th,tmp); bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; inputlen=0; dirtyuser[user]=1; } } if (threc[th].value==-1) { //...second+ pass, until value full, load bet... if (inputlen>1) if (inputlen > 5 || bufrec[th].input[inputlen-2]==13) { // ...got number... if (bufrec[th].input[0]==13) bet=max/3; else { bet=atoi(bufrec[th].input); if (bet<500) bet=500; if (bet>max) bet=max; } threc[th].value=bet; threc[th].data=-2; // ...accept number next... bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; inputlen=0; } } if (number>0 || threc[th].data==-2) { //...second+ pass, bet loaded, prompt # sprintf(tmp,"\r\n\n Pick a 2-dice total from 2 to 12 (0 to quit) [%d]: ", threc[th].data>0 ? threc[th].data : 7); if (threc[th].data==-2) threc[th].data=-3; // ...accept number next... show(th,tmp); } if (threc[th].status!='d') { //...first pass. Initialize value, data, etc... threc[th].data=-1; threc[th].value=-1; threc[th].status='d'; threc[th].winnings=0; threc[th].anteused=0; sprintf(tmp,"\r\n\n Place a bet from 500 to %d microbots [%d]: ", max, max/3); show(th,tmp); } if (threc[th].data==0) { // ....data (user's lucky number) is 0, exit... threc[th].status=0; threc[th].value=0; } return(0); } int // implement 1000-number 'numbers' game gamblertn(int th) { char tmp[DEFLINE]; int prizelist[26]= {250,200,150,130,120,110,100,90,80,70,60,50,40, 35, 30, 25, 20, 15, 12, 10, 8, 6, 5, 4, 3, 2}; int user, sector, lucky, number=0, inputlen, bet, offset, prize, max; int pfac=200, pdiv=300; // 200-401 == .66 - 1.33 x bet pfac = pfac + (d.cfg.prodmult-25) / 7; // 0-67 pfac = pfac + ((d.cfg.maxholds-99)* 8) / 9; // 0-134 if (pfac < 200) pfac=200; // in case maxholds/prodmult code is changed user=ThreadToUser[th]; sector=d.user[user].sector; sequz=sector+d.cfg.idcode+748461; max=randz(9000)+1000; if (d.user[user].cash<25000) { show(th,"\r\n\n\a * You don't have enough microbots! *"); threc[th].status=0; threc[th].value=0; return(0); } if (d.user[user].antitoday<25000) { show(th,"\r\n\n\a * You don't have enough antimatter! *"); threc[th].status=0; threc[th].value=0; return(0); } if (threc[th].status=='n') // get input on all but initial prompting pass accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (inputlen>1) //...all values initialized. This is a normal gambling pass if (threc[th].status=='n' && (threc[th].data>0||threc[th].data==-3) && (inputlen > 4 || bufrec[th].input[inputlen-2]==13)) { bet=threc[th].value; if (bufrec[th].input[0]==13) number = threc[th].data > 0 ? threc[th].data : 42; else { number=atoi(bufrec[th].input); threc[th].data=number; } if (number>1000) number=1000; threc[th].data=number; lucky=rand16(1000)+1; if (number!=0) { // play ...gambling code goes here... if (number500) offset=1000-offset; prize=0; if (offset<26) prize=prizelist[offset]*pfac/pdiv; d.user[user].antitoday-=bet; d.user[user].cash-=bet; threc[th].anteused+=bet; threc[th].winnings-=bet; if (prize>0) { sprintf(tmp,"\r\n\n **** You won a prize of %d! ****\n", prize*bet); show(th,tmp); d.user[user].cash+=(bet*prize); threc[th].winnings+=(bet*prize); } if (lucky == number) { sprintf(tmp,"\r\nNews: %s won a $%d jackpot!", d.user[user].name, bet*prize); makenews(th,tmp); sprintf(tmp,"\r\n\a%s\r\n%s\r\n%s\n", " ******************************", " ****** Grand Prize! ******", " ******************************"); show(th,tmp); } sprintf(tmp,"\r\n Your cash: %d, your fuel: %d\r\n Your winnings: %d, your Profit/Kg fuel used: %d", d.user[user].cash, d.user[user].antitoday, threc[th].winnings, threc[th].winnings/(1+(threc[th].anteused/1000))); show(th,tmp); sprintf(tmp,"\r\n Lucky number: %d, your number: %d, bet: %d ", lucky, number, bet); show(th,tmp); bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; inputlen=0; dirtyuser[user]=1; } } if (threc[th].value==-1) { //...second+ pass, until value full, load bet... if (inputlen>1) if (inputlen > 5 || bufrec[th].input[inputlen-2]==13) { // ...got number... if (bufrec[th].input[0]==13) bet=max/3; else { bet=atoi(bufrec[th].input); if (bet<200) bet=200; if (bet>max) bet=max; } threc[th].value=bet; threc[th].data=-2; // ...accept number next... bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; inputlen=0; } } if (number>0 || threc[th].data==-2) { //...second+ pass, bet loaded, prompt # sprintf(tmp,"\r\n\n Pick a number from 1 to 1000 (0 to quit) [%d]: ", threc[th].data>0 ? threc[th].data : 42); if (threc[th].data==-2) threc[th].data=-3; // ...accept number next... show(th,tmp); } if (threc[th].status!='n') { //...first pass. Initialize value, data, etc... threc[th].data=-1; threc[th].value=-1; threc[th].status='n'; threc[th].winnings=0; threc[th].anteused=0; sprintf(tmp,"\r\n\n Place a bet from 200 to %d microbots [%d]: ", max, max/3); show(th,tmp); } if (threc[th].data==0) { // ....data (user's lucky number) is 0, exit... threc[th].status=0; threc[th].value=0; } return(0); } int tradingpost(int th) // kiosk { int input, user; user=ThreadToUser[th]; if (d.sector[d.user[user].sector].porttype!=4) { show(th,"\r\n\n * There is no Tradingpost in this sector! *"); return(0); } input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status) // player picked a function in earlier pass... input=threc[th].status; // ...load it into input if (threc[th].control==':') { threc[th].noshow=1; switch(input) { case 'h': buyholds(th); break; case 'b': buybeacons(th); break; case 'c': buycombats(th); break; case 't': buytractor(th); break; case 's': buyscanner(th); break; case 'r': removehb(th); break; case 'n': gamblertn(th); break; case 'd': dicertn(th); break; case 'f': sellfiters(th); break; case 13: // cr hit... exit case '!': // exit case 'q': threc[th].control=0; threc[th].noshow=0; showsector(th); return(0); default: show(th,"\r\n\n * Invalid selection *"); } if (threc[th].status==0) // no function active; show menu again { tpmenu(th); threc[th].incount=0; threc[th].noshow=1; showsector(th); if (d.sector[d.user[user].sector].portprod[0]!=user) { d.sector[d.user[user].sector].portprod[0]=user; dirty(d.user[user].sector); } } } else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; threc[th].status=0; threc[th].data=0; tpmenu(th); threc[th].noshow=1; threc[th].control=':'; showsector(th); } return(0); } int buycombats(int th) { char tmp[DEFLINE]; int input, inputlen, user, val, limit, devices; user=ThreadToUser[th]; 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; } limit=(int)(d.user[user].cash/HOLDPRICE); devices=d.user[user].combats + d.user[user].tractor + d.user[user].scanner; limit=min(limit,d.cfg.maxholds - devices - d.user[user].goods[0]); limit=min(5-devices,limit); inputlen=strlen(bufrec[th].input); if (threc[th].status=='c' && inputlen>0) { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > limit) { sprintf(tmp, "\r\n\nYou can't buy that many! Buying %d combat systemss...", limit); show(th,tmp); } val=min(limit,val); val=max(0,val); if (val > 0) { d.user[user].combats+=val; d.user[user].cash-=(HOLDPRICE*val); dirtyuser[user]=1; sprintf(tmp, "\r\nNews: %s bought Combat Systems", d.user[user].name); makenews(th, tmp); } threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\n[Buy 1 for a .1 defensive fighter boost, \r\n 2 for .2, \r\n 3 for .3 def. and .3 offensive boost, \r\n 4 for .4, \r\n 5 for .5]"); show(th,tmp); sprintf(tmp, "\r\n\nHow many combat systems will you buy @ %d/each? (0-%d) [0]: ", HOLDPRICE, limit); show(th,tmp); threc[th].status='c'; } return(0); } int buytractor(int th) { char tmp[DEFLINE]; int input, inputlen, user, val, limit, devices; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='t') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } limit=(int)(d.user[user].cash/HOLDPRICE); devices=d.user[user].combats + d.user[user].tractor + d.user[user].scanner; limit=min(limit,d.cfg.maxholds - devices - d.user[user].goods[0]); limit=min(5-devices,limit); inputlen=strlen(bufrec[th].input); if (threc[th].status=='t' && inputlen>0) { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > limit) { sprintf(tmp, "\r\n\nYou can't buy that many! Buying %d tractor devices...", limit); show(th,tmp); } val=min(limit,val); val=max(0,val); if (val > 0) { d.user[user].tractor+=val; d.user[user].cash-=(HOLDPRICE*val); dirtyuser[user]=1; sprintf(tmp, "\r\nNews: %s bought Tractor devices", d.user[user].name); makenews(th, tmp); } threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\n[Buy 1 to move ports,\r\n 2 to move ports cheaper, use autopilot,\r\n 3 to move planets,\r\n 4 to move into nebulas,\r\n 5 to move through nebulas]"); show(th,tmp); sprintf(tmp, "\r\n\nHow many tractor devices will you buy @ %d/each? (0-%d) [0]: ", HOLDPRICE, limit); show(th,tmp); threc[th].status='t'; } return(0); } int buyscanner(int th) { char tmp[DEFLINE]; int input, inputlen, user, val, limit, devices; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='s') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } limit=(int)(d.user[user].cash/HOLDPRICE); devices=d.user[user].combats + d.user[user].tractor + d.user[user].scanner; limit=min(limit,d.cfg.maxholds - devices - d.user[user].goods[0]); limit=min(5-devices,limit); inputlen=strlen(bufrec[th].input); if (threc[th].status=='s' && inputlen>0) { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > limit) { sprintf(tmp, "\r\n\nYou can't buy that many! Buying %d ship scanners...", limit); show(th,tmp); } val=min(limit,val); val=max(0,val); if (val > 0) { d.user[user].scanner+=val; d.user[user].cash-=(HOLDPRICE*val); dirtyuser[user]=1; sprintf(tmp, "\r\nNews: %s bought Ship Scanners", d.user[user].name); makenews(th, tmp); } threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\n[1 shows adjacent ships as '?',\r\n 2+ improves chances to see guarded ships,\r\n 3 adds [F]ind ships command,\r\n 4+ searches farther and cheaper]"); show(th,tmp); sprintf(tmp, "\r\n\nHow many ship scanners will you buy @ %d/each? (0-%d) [0]: ", HOLDPRICE, limit); show(th,tmp); threc[th].status='s'; } return(0); } int buyholds(int th) { char tmp[DEFLINE]; int inputlen, user, val, limit; user=ThreadToUser[th]; if (threc[th].status=='h') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } limit=(int)(d.user[user].cash/HOLDPRICE); limit=min(limit,d.cfg.maxholds - d.user[user].combats - d.user[user].tractor - d.user[user].scanner - d.user[user].goods[0]); inputlen=strlen(bufrec[th].input); if (threc[th].status=='h' && inputlen>0) { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > limit) { sprintf(tmp, "\r\n\nYou can't buy that many! Buying %d holds...", limit); show(th,tmp); } val=min(limit,val); val=max(0,val); if (val > 0) { d.user[user].goods[0]+=val; d.user[user].cash-=(HOLDPRICE*val); dirtyuser[user]=1; sprintf(tmp, "\r\nNews: %s bought Cargo Holds", d.user[user].name); makenews(th, tmp); } threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\n[You can use up to 5 bays for Combat systems, Scanners, & Tractor devices]\r\n\nHow many cargo holds will you buy @ %d/each? (0-%d) [0]: ", HOLDPRICE, limit); show(th,tmp); threc[th].status='h'; } return(0); } int buybeacons(int th) { char tmp[DEFLINE]; int input, inputlen, user, val, limit; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='b') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } limit=(int)(d.user[user].cash/HOLDPRICE); limit=min(limit, d.cfg.maxholds/10); limit=min(limit, d.cfg.maxholds/10 - d.user[user].beacons); inputlen=strlen(bufrec[th].input); if (threc[th].status=='b' && inputlen>0) { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > limit) { sprintf(tmp, "\r\n\nYou can't buy that many! Buying %d beacons...", limit); show(th,tmp); } val=min(limit,val); val=max(0,val); if (val > 0) { d.user[user].beacons+=val; d.user[user].cash-=(HOLDPRICE*val); dirtyuser[user]=1; sprintf(tmp, "\r\nNews: %s bought Homing Beacons", d.user[user].name); makenews(th, tmp); } threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\nHow many beacons will you buy @ %d/each? (0-%d) [0]: ", HOLDPRICE, limit); show(th,tmp); threc[th].status='b'; } return(0); } int sellfiters(int th) { char tmp[DEFLINE]; int input, inputlen, user, val; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='f') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='f' && inputlen>0) { if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val > d.user[user].fiters) { sprintf(tmp, "\r\n\nYou don't have that many! Selling %d fighters...", d.user[user].fiters); show(th,tmp); } val=min(d.user[user].fiters,val); val=max(0,val); d.user[user].fiters-=val; d.user[user].cash+=(FIGPRICE*val); dirtyuser[user]=1; threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nHow many fighters do you want to sell? (0-%d) [0]: ", d.user[user].fiters); show(th,tmp); threc[th].status='f'; } return(0); } int removehb(int th) { char tmp[DEFLINE]; int input, inputlen, user; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (d.user[user].beacon==0) { show(th, "\r\n\n * You don't have a homing beacon on your ship! *"); return(0); } if (threc[th].status=='r') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='r' && inputlen>0) { if (input=='y' || input == 13) { sprintf(tmp,"\r\n%s removed your homing beacon", d.user[user].name); makehistory(user,d.user[user].beacon,tmp); d.user[user].beacon=0; dirtyuser[user]=1; } threc[th].incount=0; threc[th].status=0; return(0); } if (threc[th].status==0) { sprintf(tmp,"\r\n\nRemove %s's homing beacon? (Y/N) [Y]: ", d.user[d.user[user].beacon].name); show(th,tmp); threc[th].status='r'; } return(0); } int chgamecycle(int th) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); 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' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>2) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.gamecycle; val=min(15,val); val=max(3,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.gamecycle=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter a goods accumulation period (3-15) [%d]: ", d.cfg.gamecycle); show(th,tmp); threc[th].status='g'; } return(0); } int // limit to 500 (5.0*15*400==30000) to allow for tlrworlds up to double chprodmult(int th) // 5.0(500%) 15(max gamecycle) 400(max normal productivity) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='p') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='p' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.prodmult; val=min(500,val); val=max(25,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.prodmult=val; calcproductivity(); dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter a productivity multiplier %c (25-500) [%d]: ", '%', d.cfg.prodmult); show(th,tmp); threc[th].status='p'; } return(0); } int chgamelen(int th) { char tmp[DEFLINE]; int input, inputlen, val; time_t now; now=time(NULL); input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='l') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='l' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>5) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.gamelength; if (now >= d.cfg.startdate + val * DAY) val=d.cfg.gamelength; val=min(366,val); val=max(1,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.gamelength=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nHow long should the game run in days? [%d]: ", d.cfg.gamelength); show(th,tmp); threc[th].status='l'; } return(0); } int chstartdate(int th) { char tmp[DEFLINE]; int input, inputlen, val; time_t now; now=time(NULL); input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='s') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='s' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>6) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; if (now >= ((d.cfg.startdate - val * 60) + d.cfg.gamelength*DAY)) val=0; d.cfg.startdate-=(val*60); d.cfg.startdate=min(time(NULL),d.cfg.startdate); dirtycfg=1; /* // set back planet/port inventories for (x=1;x0 && d.sector[x].porttype<4) { d.sector[x].portcalcdate-=(val*60); dirty(x); } if (d.sector[x].moontype==1) { d.sector[x].mooncalcdate-=(val*60); dirty(x); } } */ // set back planet/port inventories threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp, "\r\n\nAdd/subtract minutes to game age (%d minutes/%d hours/%d days): ", (int)((time(NULL) - d.cfg.startdate)/60), (int)((time(NULL) - d.cfg.startdate)/3600), (int)((time(NULL) - d.cfg.startdate)/DAY)); show(th,tmp); threc[th].status='s'; } return(0); } int chdailyanti(int th) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='f') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='f' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>7) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.dailyanti; val=min(9999999,val); val=max(99999,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.dailyanti=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter new daily fuel amount (99999-9999999) [%d]: ", d.cfg.dailyanti); show(th,tmp); threc[th].status='f'; } return(0); } int chmaxholds(int th) { char tmp[DEFLINE]; int input, inputlen, val, x; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='x') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='x' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.maxholds; val=min(250,val); val=max(99,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.maxholds=val; holdrange=d.cfg.maxholds - d.cfg.minholds; dirtycfg=1; for (x=1;xd.cfg.maxholds) { d.user[x].goods[0]=d.cfg.maxholds; dirtyuser[x]=1; } } threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter a new maximum for cargo holds (99-250) [%d]: ", d.cfg.maxholds); show(th,tmp); threc[th].status='x'; } return(0); } int chminholds(int th) { char tmp[DEFLINE]; int input, inputlen, val, x; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='n') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='n' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>2) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.minholds; val=min(90,val); val=max(25,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.minholds=val; holdrange=d.cfg.maxholds - d.cfg.minholds; dirtycfg=1; for (x=1;x 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.grouplimit; val=min(1000,val); val=max(1,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.grouplimit=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter new IP group size limit (1-1000) [%d]: ", d.cfg.grouplimit); show(th,tmp); threc[th].status='z'; } return(0); } int chbeacon(int th) // toggle display of beacon reports on or off { threc[th].beaconmode = 1 - threc[th].beaconmode; if (threc[th].beaconmode == 1) show(th,"\r\n\n * Beacon reports are now disabled *"); else show(th,"\r\n\n * Beacon reports are now enabled *"); return(0); } int chempire(int th) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); 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' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) { val=d.cfg.empire; if (bufrec[th].input[0]=='0') val=0; } val=(val/5)*5; val=min(25,val); val=max(0,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.empire=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter fighter fleet growth percent (0, 5, 10-25) [%d]: ", d.cfg.empire); show(th,tmp); threc[th].status='e'; } return(0); } int chtimelimit(int th) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='t') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='t' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.timelimit; val=min(480,val); val=max(30,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.timelimit=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter daily timelimit in minutes (30-480) [%d]: ", d.cfg.timelimit); show(th,tmp); threc[th].status='t'; } return(0); } int // reset a user password resetpw(int th) { char holdpw[16]; char tmp[DEFLINE]; char abc[36]="abcdefghijklmnopqrstuvwxyz23456789"; int input, inputlen, val, x; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='r') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='r' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (threc[th].data==0) { if (val<=MAXUSERS && val>0) { if ((d.user[ThreadToUser[th]].valid<100) || (d.user[ThreadToUser[th]].valid<=d.user[val].valid)) { show(th,"\r\n\n\a * You can't reset that player's password! *"); } else { for (x=0;x<15;x++) holdpw[x]=abc[rand16(34)]; holdpw[x]=0; sprintf(tmp,"\r\nChanged %s password to: %s\r\n", d.user[val].name, holdpw); show(th,tmp); strncpy(d.user[val].pword, hasher(holdpw, d.user[val].name), 16); dirtyuser[val]=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\nEnter the user tag # to reset password: "); threc[th].status='r'; threc[th].data=0; } return(0); } int // boot a user boot(int th) { int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='b') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='b' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (threc[th].data==0) { if (val<=MAXUSERS && val>0) { if ((d.user[ThreadToUser[th]].valid<100) || (d.user[ThreadToUser[th]].valid<=d.user[val].valid)) { show(th,"\r\n\n\a * You can't boot that player! *"); } else { if (inuse[UserToThread[val]] && val==ThreadToUser[UserToThread[val]]) { threc[UserToThread[val]].logmeout=1; threc[UserToThread[val]].logoutmsg=6; dirtyuser[val]=1; } else show(th,"\r\n\n\a * That user isn't online! *"); } } } bufrec[th].input[0]=0; threc[th].incount=0; threc[th].status=0; return(0); } } if (threc[th].status==0) { show(th,"\r\n\nEnter the user tag # to boot: "); threc[th].status='b'; threc[th].data=0; } return(0); } int // change access level (1: user lockout, 10+: use radio) chvalid(int th) { int input, inputlen, val, x; char tmp[DEFLINE]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='v') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='v' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>3) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (threc[th].data==0) { if (val>MAXUSERS || val<1) { threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } if ((d.user[ThreadToUser[th]].valid<100) || (d.user[ThreadToUser[th]].valid<=d.user[val].valid)) { show(th,"\r\n\n\a * You can't edit that record! *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; return(0); } threc[th].data=val; bzero(bufrec[th].input,DEFLINE); sprintf(tmp, "\r\n\nEnter a new access level for %s (0-%d: 1=lock, 0=delete) [%d]: ", d.user[threc[th].data].name, d.user[ThreadToUser[th]].valid-1, d.user[threc[th].data].valid); show(th,tmp); return(0); } if (val || bufrec[th].input[0]=='0') { if (val==0 && bufrec[th].input[0]=='0' && threc[th].data) { // if user online, boot before user delete... if (inuse[UserToThread[threc[th].data]] && threc[th].data==ThreadToUser[UserToThread[threc[th].data]]) { threc[UserToThread[threc[th].data]].logmeout=1; threc[UserToThread[threc[th].data]].logoutmsg=6; } for (x=1;x9) d.sector[x].sectortype=0; dirty(x); } if (d.sector[x].beacon==threc[th].data) { d.sector[x].beacon=0; dirty(x); } } resethist(threc[th].data); // remove user history log resetteam(threc[th].data); // remove user from team resetbeacons(threc[th].data);// remove beacons from ships bzero(&d.user[threc[th].data], sizeof(struct userlayout)); dirtyuser[threc[th].data]=1; } else d.user[threc[th].data].valid = min(d.user[ThreadToUser[th]].valid-1,val); dirtyuser[threc[th].data]=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\nEnter the user tag # to modify: "); threc[th].status='v'; threc[th].data=0; } return(0); } int chpassword(int th) { int input, inputlen; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='w') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='w' && inputlen > 1) //second+ pass: got pw yet? { if ((inputlen>15) || (bufrec[th].input[inputlen-2]==13)) // got passwd { if (inputlen<5) { show(th,"\r\n\n\a * Password too short! (Not changed) *"); threc[th].status=0; bufrec[th].input[0]=0; threc[th].incount=0; threc[th].noecho=0; return(0); } if (bufrec[th].input[inputlen-1]==10) // [Enter] key is hit bufrec[th].input[inputlen-2]=0; // trim off cr/lf if (bufrec[th].logpword[0]==0) { strncpy(bufrec[th].logpword,bufrec[th].input,16); bzero(bufrec[th].input,DEFLINE); show(th,"\r\n\nPlease enter it again: "); threc[th].noecho=1; return(0); } if (strncmp(bufrec[th].logpword, bufrec[th].input,16)==0) { strncpy(d.user[ThreadToUser[th]].pword, hasher( bufrec[th].logpword, d.user[ThreadToUser[th]].name), 16); show(th,"\r\n\nPassword successfully changed"); dirtyuser[ThreadToUser[th]]=1; } else show(th,"\r\n\n\a * Password mismatch! (Not changed) *"); bufrec[th].input[0]=0; threc[th].incount=0; threc[th].status=0; threc[th].noecho=0; return(0); } } if (threc[th].status==0) { show(th,"\r\n\nPlease enter a new password (3-15 characters): "); bufrec[th].logpword[0]=0; threc[th].status='w'; threc[th].noecho=1; } return(0); } int chtimeout(int th) { char tmp[DEFLINE]; int input, inputlen, val; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].status=='i') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].status=='i' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { val=atoi(bufrec[th].input); if (val==0) val=d.cfg.timeout; val=min(1800,val); val=max(120,val); bufrec[th].input[0]=0; threc[th].incount=0; d.cfg.timeout=val; dirtycfg=1; threc[th].status=0; return(0); } } if (threc[th].status==0) { sprintf(tmp,"\r\n\nEnter idle keyboard timeout in seconds (120-1800) [%d]: ", d.cfg.timeout); show(th,tmp); threc[th].status='i'; } return(0); } int // process logoff requests hangup(int th) // kill io threads for th, exit. { // coordinate flush of backup thread...someday char input; // local copy, initialized below input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='q') { if (threc[th].status==1)// q pressed... needs 'y' { if (input=='y' || input=='!') { threc[th].logmeout=1; threc[th].logoutmsg=1; } else showsector(th); } threc[th].control=0; threc[th].status=1; return(0); } if (input=='q') { show(th,"\r\n\nDo you really want to log off? (Y/N) [N]: "); threc[th].control='q'; threc[th].status=1; } else // clear screen and log off now! { show(th,"\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); threc[th].logmeout=1; threc[th].logoutmsg=1; threc[th].noshow=2; } return(0); } int autowarp(int th) { int oldsector; oldsector=d.user[ThreadToUser[th]].sector; if (threc[th].autowarp<2) threc[th].autowarp=2; while(threc[th].autowarp) { if (bufrec[th].input[threc[th].autowarp]>'0' && bufrec[th].input[threc[th].autowarp]<'7' && threc[th].autowarp - = commands { unsigned int sector, input, user; // local copies, initialized below unsigned int x, t, moontype, porttype, sectortype, osectortype; unsigned int beacon, savelast, nexthi, nextlo, hi, lo, nsector, cost; char tmp[DEFLINE]; // following fields are used for warpmoon and nudgeport planet/port movment int ptype, mtype; unsigned short int pgoods[8]; // store portgoods and prod fields unsigned short int mgoods[8]; // store moongoods and prod fields unsigned int pcalcdate, mcalcdate, sourceport, sourcemoon; user=ThreadToUser[th]; if (d.user[user].antitoday < 1) { show(th,"\r\n\n\a * You're out of antimatter! *"); threc[th].autowarp=0; return(1); } sector=d.user[user].sector; savelast=d.user[user].lastsector; if (threc[th].autowarp) input=(int)bufrec[th].input[threc[th].autowarp]; else input=(int)bufrec[th].inbuf[threc[th].inptr][0]; // unchanged input if (input > 48 && input < 55) input=input - 48;// returns 0-9 switch (input) { case '_': // same as '-' case '/': // same as '-' case '-': do // try to avoid the last 16 visited sectors... { do // definitely avoid warps to sector 0... { t=d.sector[sector].warp[rand16(6)]; } while (t==0); // if target sector is in visited list, break with x < 16 for (x=0;x<16;x++) if (threc[th].last16[x]==t) break; } while (x<16 && rand16(100)); // 1% chance of a visited sector d.user[user].sector=t; break; case 'k': case 'K': if (threc[th].prev16[1]) { d.user[user].sector=threc[th].prev16[1]; for (x=1;x<16;x++) threc[th].prev16[x-1]=threc[th].prev16[x]; threc[th].prev16[15]=0; } break; case ',': nextlo=0; for (x=0;x<6;x++) if (d.sector[sector].warp[x]>nextlo && d.sector[sector].warp[x]sector && d.sector[sector].warp[x]': hi=sector; for (x=0;x<6;x++) if (d.sector[sector].warp[x]>hi) hi=d.sector[sector].warp[x]; d.user[user].sector = hi; break; case '<': lo=sector; for (x=0;x<6;x++) if (d.sector[sector].warp[x]) if (d.sector[sector].warp[x] 0) { d.user[user].lastsector = sector; if (threc[th].warpmode || threc[th].nudgemode) // nudge, warp code block { nsector=d.user[user].sector; sectortype=d.sector[nsector].sectortype; osectortype=d.sector[threc[th].sourceport].sectortype; if (sectortype==2) sectortype=1; if (osectortype==2) osectortype=1; if (threc[th].nudgemode)// determine elibibility, move port { // turn off nudgmode if autopilot & < 2 tractors ptype=d.sector[nsector].porttype; porttype=d.sector[threc[th].sourceport].porttype; if ((sectortype==0 || (sectortype==1 && d.user[user].tractor>3)) && (osectortype==0 || (osectortype==1 && d.user[user].tractor>4)) && porttype<4 && ptype<4) { sourceport=threc[th].sourceport; memcpy(&pgoods[0], &d.sector[nsector].portgoods[0], 16); pcalcdate=d.sector[nsector].portcalcdate; memcpy(&d.sector[nsector].portgoods[0], &d.sector[sourceport].portgoods[0], 16); d.sector[nsector].portcalcdate = d.sector[sourceport].portcalcdate; d.sector[nsector].porttype = d.sector[sourceport].porttype; memcpy(&d.sector[sourceport].portgoods[0], &pgoods[0], 16); d.sector[sourceport].portcalcdate = pcalcdate; d.sector[sourceport].porttype = ptype; dirty(nsector); dirty(sourceport); cost=d.sector[nsector].portprod[1]+ 2*d.sector[nsector].portprod[2]+ 3*d.sector[nsector].portprod[3]; cost /= d.user[user].tractor; if (d.user[user].antitoday > cost) d.user[user].antitoday -= cost; else d.user[user].antitoday = 0; sprintf(tmp,"\r\n\n(You used %d grams of antimatter moving the %s)", cost, plantname[porttype]); show(th,tmp); if (threc[th].autowarp && d.user[user].tractor==1) threc[th].nudgemode = threc[th].sourceport = 0; else threc[th].sourceport=nsector; } else // wrong dest. sectortype/porttype; turn off nudgemode. if (d.user[user].tractor<5) threc[th].nudgemode = threc[th].sourceport = 0; } if (threc[th].warpmode) // determine eligibility(def.sec?), move moon, { // turn off nudgemode if autopilot, <2 tractors osectortype=d.sector[threc[th].sourcemoon].sectortype; if (osectortype==2) osectortype=1; if ((sectortype==0 || (sectortype==1 && d.user[user].tractor>3)) && (osectortype==0 || (osectortype==1 && d.user[user].tractor>4))) { sourcemoon=threc[th].sourcemoon; moontype=d.sector[nsector].moontype; memcpy(&mgoods[0], &d.sector[nsector].moongoods[0], 16); mcalcdate=d.sector[nsector].mooncalcdate; mtype=moontype; memcpy(&d.sector[nsector].moongoods[0], &d.sector[sourcemoon].moongoods[0], 16); d.sector[nsector].mooncalcdate = d.sector[sourcemoon].mooncalcdate; d.sector[nsector].moontype = d.sector[sourcemoon].moontype; memcpy(&d.sector[sourcemoon].moongoods[0], &mgoods[0], 16); d.sector[sourcemoon].mooncalcdate = mcalcdate; d.sector[sourcemoon].moontype = mtype; dirty(nsector); dirty(sourcemoon); cost=2*d.sector[nsector].moonprod[1]+ 4*d.sector[nsector].moonprod[2]+ 6*d.sector[nsector].moonprod[3]; cost /= d.user[user].tractor; if (d.user[user].antitoday > cost) d.user[user].antitoday -= cost; else d.user[user].antitoday = 0; sprintf(tmp,"\r\n\n(You used %d grams of antimatter moving the %s)", cost, "Planet"); show(th,tmp); if (threc[th].autowarp && d.user[user].tractor==1) threc[th].warpmode = threc[th].sourcemoon = 0; else threc[th].sourcemoon=nsector; } else // wrong dest. sectortype/porttype; turn off nudgemode. if (d.user[user].tractor<5) threc[th].warpmode = threc[th].sourcemoon = 0; } } // end nudge, warp code block if (input!='k'&&input!='K') { for (x=15;x>0;x--) threc[th].prev16[x]=threc[th].prev16[x-1]; threc[th].prev16[0]=d.user[user].sector; } for (x=0;x<16;x++) if (threc[th].last16[x]==sector) break; if (x==16) threc[th].last16[threc[th].lastndx++]=sector; } else { d.user[user].sector = sector; return(0); } santi(th, 0); if (d.sector[sector].beacon>0 // homing beacon here && d.sector[sector].beacon!=user) { if (d.user[user].beacon==0 || rand16(10)==1) { beacon=d.user[user].beacon; // save user beacon d.user[user].beacon=d.sector[sector].beacon; d.sector[sector].beacon=beacon; // swap them, if needed dirtyuser[user]=1; dirty(sector); sprintf(tmp,"\r\n%s picked up your beacon at %d", d.user[user].name, sector); makehistory(user,d.user[user].beacon,tmp); } } dirtyuser[user]=1; if (threc[th].autowarp) { if (bufrec[th].input[threc[th].autowarp+1]==0) threc[th].noshow=2; showsector(th); } } return(0); } int // a user attacks another ship. (called from atakrtn function) atakship(int th) { int input, user, fleet, limit, sector;// local copies, initialized below int inputlen, x=0, victim=0, start=0, killed=0, lost=0, team; char tmp[DEFLINE]; user=ThreadToUser[th]; team=d.user[user].team; sector=d.user[user].sector; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); // deployed fighters guarding this sector... can't attack ships! if (d.sector[sector].fiters && d.sector[sector].fitersowner != user) { if (team && (d.user[d.sector[sector].fitersowner].team == team)) ; else { show(th,"\r\n\nYou must destroy the deployed fighters here first!"); threc[th].control=0; return(0); } } // initial arrival here... initialize status, data, fall through if (input=='~' || input=='a') { threc[th].data=0; threc[th].status=0; } // data is already initialized: we've found our victim, fall through if (threc[th].status>0) start=threc[th].status; // we have victim, need to know how many to attack with, fall through if (threc[th].data>0) accumulateinput(th); // accumulate digits and fall through else { // else, initialize input array, input counter bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } // user said 'yes' to attacking 'status': load userid into 'data' if (threc[th].status > 0 && input=='y') { victim=threc[th].data=threc[th].status; limit=(int)(d.user[user].fiters); sprintf(tmp, "\r\n\nAttack %s with how many fighters? (0-%d) [0]: ", d.user[victim].name,limit); show(th,tmp); if (UserToThread[victim] && // victim is awake! (victim==ThreadToUser[UserToThread[victim]])) sprintf(tmp, "\r\n\nWarning! %s has a Laser target lock on your ship...", d.user[user].name); show(UserToThread[victim],tmp); return(0); } // check for ships if (threc[th].data==0 && input!='y') { start=threc[th].status; if (start==0) start=1; else start++; for (x=start;x 1 && victim>0) { if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) { if (UserToThread[victim] && // victim is awake! (victim==ThreadToUser[UserToThread[victim]])) sprintf(tmp, "\r\n\nDanger! Incoming %s Fighters... ", d.user[user].name); show(UserToThread[victim],tmp); fleet=atoi(bufrec[th].input); sprintf(tmp, "\r\n\nattacking %s with %d fighters... ", d.user[victim].name,fleet); show(th,tmp); if (fleet > limit) { sprintf(tmp, "\r\n\nToo few fighters: sending %d fighters... ", limit); show(th,tmp); } if (d.user[victim].sector != sector) { show(th,"\r\n\nThe enemy ship dodged your attack!"); threc[th].status=0; threc[th].control=0; bufrec[th].input[0]=0; showsector(th); return(0); } fleet=min(limit,atoi(bufrec[th].input)); if (fleet > 0) { do { if (rand16(1000+ (d.user[user].combats < 3 ? 0 : d.user[user].combats) * 50) > (500+d.user[victim].combats*25)) //kill! { d.user[victim].fiters--; // victim lost 1 fighter killed++; d.user[victim].reppoints+=FIGPRICE/2;// comp vic w/rep } else // victim won that fight { d.user[user].fiters--; // We lost one fighter d.user[user].reppoints+=FIGPRICE;// add to our rep fleet--; // deduct it from fleet lost++; } } while (d.user[victim].fiters>(-1) // attack until ship's dead && d.user[user].fiters>0 // or we still have figs && fleet > 0); // in our attack fleet dirtyuser[user]=1; } if (lost) { sprintf(tmp, "\r\n\nLog: You lost %d fighters...", lost); show(th,tmp); } if (killed) { sprintf(tmp, "\r\n\nLog: You destroyed %d enemy fighters... ", killed); show(th,tmp); dirtyuser[victim]=1; if (d.sector[sector].dust < 42) { d.sector[sector].dust = min(42,killed/100+d.sector[sector].dust+3); dirty(sector); threc[th].lastdebris=0; } } if (UserToThread[victim] && // if victim is awake... (victim==ThreadToUser[UserToThread[victim]])) { if (killed) // victim lost some fighters { sprintf(tmp, "\r\n\nLog: %s destroyed %d of your fighters... ", d.user[user].name, killed); } else // victim lost no fighters -- inform sprintf(tmp, "\r\n\nLog: No damage taken in the attack... "); show(UserToThread[victim],tmp); } else { if (killed) { sprintf(tmp,"\r\n%s killed %d of your fighters", d.user[user].name, killed); makehistory(user,victim,tmp); dirtyuser[victim]=1; } } if (d.user[victim].fiters<0) // victim ship was destroyed { // init. new ship, location... d.user[victim].reppoints = d.user[victim].reppoints/2; d.user[victim].goods[0]=0; d.user[victim].sector=0; d.user[victim].lastsector=1; d.user[victim].fiters=0; d.user[victim].combats=0; d.user[victim].beacon=0; d.user[victim].beacons=0; d.user[victim].tractor=0; d.user[victim].scanner=0; d.user[victim].esect=0; d.user[victim].eship=0; d.user[victim].cash=600; d.user[victim].goods[0]=0; // cargo holds d.user[victim].goods[1]=d.cfg.minholds/4; d.user[victim].goods[2]=d.cfg.minholds/4; d.user[victim].goods[3]=d.cfg.minholds/2; dirtyuser[victim]=1; show(th,"\r\n\nLog: You destroyed the enemy ship... "); // broadcast news out over the news system for all to see sprintf(tmp, "\r\nNews: %s destroyed %s's ship", d.user[ThreadToUser[th]].name, d.user[victim].name); makenews(th, tmp); if (d.sector[sector].dust < 250) { d.sector[sector].dust = min(250,75+d.sector[sector].dust); dirty(sector); threc[th].lastdebris=0; } if (UserToThread[victim] && // victim is awake! (victim==ThreadToUser[UserToThread[victim]])) { d.user[victim].goods[0] = d.cfg.minholds; d.user[victim].sector = rand24(GAMESIZE/5)+1; sprintf(tmp,"\r\n\nLog: %s destroyed your ship... ", d.user[user].name); show(UserToThread[victim],tmp); sprintf(tmp, "\r\nNews: %s was issued a new Starship", d.user[victim].name); makenews(th, tmp); show(UserToThread[victim], "\r\n\n*\n*\n* Your ship was destroyed! *\n*\n*\n\a"); } else { d.user[victim].goods[0]=0; // cargo holds sprintf(tmp,"\r\n%s destroyed your flagship", d.user[user].name); makehistory(user,victim,tmp); dirtyuser[victim]=1; } } threc[th].status=0; // all done here. cleanup and display sector threc[th].control=0; bufrec[th].input[0]=0; showsector(th); } } return(0); } // this function is used to attack enemy starbases int fightbase(int th) { int input, user, fleet, limit, sector;// local copies, initialized below int inputlen, dfiters=0, killed=0, lost=0, basetype=0, owner=0; char tmp[DEFLINE]; user=ThreadToUser[th]; sector=d.user[user].sector; basetype=d.sector[sector].sectortype-9; owner=d.sector[sector].fitersowner; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='*' && threc[th].status==13) accumulateinput(th); else if (threc[th].control=='*' && threc[th].status==1) accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } if (d.sector[sector].sectortype> 9 && d.sector[sector].fitersowner != user) dfiters=basestrength[d.sector[sector].sectortype-9]; else // no base here, return return(0); limit=(int)(d.user[user].fiters); inputlen=strlen(bufrec[th].input); // fight the base... if (dfiters>0 && threc[th].control==0) { threc[th].control='*'; threc[th].status=1; threc[th].autowarp=0; sprintf(tmp, "\r\n\n(attack %d fighter starbase, Retreat, Self-destruct)\r\n\nAttack with how many fighters? (0-%d, R to Retreat, S to Self-destruct) [0]: ", dfiters, limit); show(th,tmp); bufrec[th].input[0]=0; } else if (threc[th].control=='*' && inputlen == 1) { if (tolower(bufrec[th].input[0])=='s') { show(th, "\r\n\n\a * Really destroy your own ship? (Y/N) [N]: "); threc[th].status=13; return(0); } else if (tolower(bufrec[th].input[0])=='r') { d.user[user].sector=d.user[user].lastsector; d.user[user].lastsector=sector; dirtyuser[user]=1; threc[th].control=0; // all done here. cleanup, show sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); return(0); } } else if (threc[th].control=='*' && inputlen > 1) { if (threc[th].status==13 && tolower(input)=='y') { selfdestruct(th); threc[th].control=0; // all done here. cleanup, show (new) sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); return(0); } if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) { fleet=atoi(bufrec[th].input); sprintf(tmp, "\r\n\nattacking starbase with %d fighters...", fleet); show(th,tmp); if (fleet > limit) { sprintf(tmp, "\r\n\nToo few fighters: sending %d fighters...", limit); show(th,tmp); } fleet=min(limit,atoi(bufrec[th].input)); if (fleet >= dfiters ) { d.sector[sector].sectortype=0; d.user[user].reppoints+=dfiters*FIGPRICE;// user earned rep. d.user[owner].reppoints+=dfiters*FIGPRICE/2;// victim rep. d.user[user].fiters-=dfiters; // subtract fighters fleet-=dfiters; // decrement attack fleet lost=dfiters; killed=dfiters; dirtyuser[user]=1; dirty(sector); sprintf(tmp, "\r\nNews: %s destroyed a #%d Starbase", d.user[user].name,basetype); makenews(th, tmp); } else if (fleet >= dfiters/2 && d.sector[sector].sectortype>10) { d.sector[sector].sectortype-=1; d.user[user].reppoints+=dfiters/2*FIGPRICE;// user earned rep. d.user[owner].reppoints+=dfiters/2*FIGPRICE/2; d.user[user].fiters-=dfiters/2; // subtract fighters fleet-=dfiters/2; // decrement attack fleet lost=dfiters/2; killed=dfiters/2; dirtyuser[user]=1; dirty(sector); sprintf(tmp, "\r\nNews: %s damaged a #%d Starbase", d.user[user].name,basetype); makenews(th, tmp); } if (lost) { sprintf(tmp, "\r\n\nLog: You lost %d fighters...", lost); show(th,tmp); } if (killed) { if (killed==dfiters) sprintf(tmp, "\r\n\nLog: You destroyed the enemy Starbase"); else sprintf(tmp, "\r\n\nLog: You damaged the enemy Starbase"); show(th,tmp); if (d.sector[sector].dust < 100) { d.sector[sector].dust = min(100,killed/100+d.sector[sector].dust+3); dirty(sector); threc[th].lastdebris=0; } if (killed==dfiters) sprintf(tmp,"\r\n%s destroyed a #%d Starbase at %d", d.user[user].name, basetype, sector); else sprintf(tmp,"\r\n%s damaged a #%d Starbase at %d", d.user[user].name, basetype, sector); makehistory(user,owner,tmp); } threc[th].control=0; // all done here. cleanup, show sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); } } return(0); } int selfdestruct(int th) { int user; char tmp[DEFLINE]; user=ThreadToUser[th]; d.user[user].reppoints = d.user[user].reppoints/2; d.user[user].goods[0]=0; d.user[user].sector=rand24(GAMESIZE/5)+1; d.user[user].lastsector=1; d.user[user].fiters=0; d.user[user].combats=0; d.user[user].beacon=0; d.user[user].beacons=0; d.user[user].tractor=0; d.user[user].scanner=0; d.user[user].esect=0; d.user[user].eship=0; d.user[user].cash=600; d.user[user].goods[0]=d.cfg.minholds; d.user[user].goods[1]=d.cfg.minholds/4; d.user[user].goods[2]=d.cfg.minholds/4; d.user[user].goods[3]=d.cfg.minholds/2; if (d.user[user].antitoday > 250000) d.user[user].antitoday = d.user[user].antitoday / 10 * 9; else if (d.user[user].antitoday > 25000) d.user[user].antitoday -= 25000; else d.user[user].antitoday = 0; dirtyuser[user]=1; show(th,"\r\n\nLog: You destroyed your ship... "); sprintf(tmp, "\r\nNews: %s self destructed", d.user[user].name); makenews(th, tmp); sprintf(tmp, "\r\nNews: %s was issued a new Starship", d.user[ThreadToUser[th]].name); makenews(th, tmp); return(0); } // this function is used to attack deployed fighters. If there are no fighters // in the sector, it passes control over to the atakship() function int atakrtn(int th) { int input, user, fleet, limit, sector;// local copies, initialized below int inputlen, dfiters=0, killed=0, lost=0, team; char tmp[DEFLINE]; user=ThreadToUser[th]; team=d.user[user].team; sector=d.user[user].sector; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='a' && threc[th].status==13) accumulateinput(th); else if (threc[th].control=='a' && threc[th].status==1) accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } if ((d.sector[sector].fiters && d.sector[sector].fitersowner != user) && (d.user[d.sector[sector].fitersowner].team != team || d.sector[sector].fitersowner == 0 || d.user[d.sector[sector].fitersowner].team == 0)) dfiters=d.sector[sector].fiters; // attack fighters else // no deployed fighters here... pass control to atakship() { threc[th].control='~'; atakship(th); return(0); } limit=(int)(d.user[user].fiters); inputlen=strlen(bufrec[th].input); // attack deployed fighters... //if (dfiters>0 && input=='a') if (dfiters>0 && threc[th].control==0) { threc[th].control='a'; threc[th].status=1; threc[th].autowarp=0; sprintf(tmp, "\r\n\n(attack, Retreat, or Self-destruct)\r\n\nAttack with how many fighters? (0-%d, R to Retreat, S to Self-destruct) [0]: ", limit); show(th,tmp); bufrec[th].input[0]=0; } else if (threc[th].control=='a' && inputlen == 1) { if (tolower(bufrec[th].input[0])=='s') { show(th, "\r\n\n\a * Really destroy your own ship? (Y/N) [N]: "); threc[th].status=13; return(0); } else if (tolower(bufrec[th].input[0])=='r') { d.user[user].sector=d.user[user].lastsector; d.user[user].lastsector=sector; dirtyuser[user]=1; threc[th].control=0; // all done here. cleanup, show sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); return(0); } } else if (threc[th].control=='a' && inputlen > 1) { if (threc[th].status==13 && tolower(input)=='y') { selfdestruct(th); threc[th].control=0; // all done here. cleanup, show (new) sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); return(0); } if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) { fleet=atoi(bufrec[th].input); sprintf(tmp, "\r\n\nattacking deployed fighters with %d fighters...", fleet); show(th,tmp); if (fleet > limit) { sprintf(tmp, "\r\n\nToo few fighters: sending %d fighters...", limit); show(th,tmp); } fleet=min(limit,atoi(bufrec[th].input)); if (fleet > 0) { do { //if (rand16(100+d.user[user].combats*5) > 49) //kill! if (rand16(100+ (d.user[user].combats < 3 ? 0 : d.user[user].combats) *5) > 49) //kill! { killed++; // count the dead enemies d.sector[sector].fiters--; // decrement sector fleet if (d.sector[sector].fitersowner) // non-neutrals... d.user[d.sector[sector].fitersowner].reppoints+= FIGPRICE/2; } else { d.user[user].reppoints+=FIGPRICE;// user earned rep. d.user[user].fiters--; // attacker lost a fighter fleet--; // decrement attack fleet lost++; } } while (d.sector[sector].fiters>0 // attack until no sec figs && d.user[user].fiters>0 // or we're outa fighters && fleet > 0); // or til attack fleet gone dirtyuser[user]=1; dirty(sector); } if (lost) { sprintf(tmp, "\r\n\nLog: You lost %d fighters...", lost); show(th,tmp); } if (killed) { sprintf(tmp, "\r\n\nLog: You destroyed %d deployed fighters...", killed); show(th,tmp); if (d.sector[sector].dust < 42) { d.sector[sector].dust = min(42,killed/100+d.sector[sector].dust+3); dirty(sector); threc[th].lastdebris=0; } sprintf(tmp,"\r\n%s killed %d fighters at %d", d.user[user].name, killed, sector); makehistory(user,d.sector[sector].fitersowner,tmp); } threc[th].control=0; // all done here. cleanup, show sector bufrec[th].input[0]=0; threc[th].status=0; showsector(th); } } return(0); } int autopilot(int th) { int sector, input, user, target;// local variables, initialized below int inputlen, upperlimit, lowerlimit, galnumb; char tmp[DEFLINE]; user=ThreadToUser[th]; sector=d.user[user].sector; galnumb=getgal(d.user[user].sector); if (galnumb) { lowerlimit=GAMESIZE/5+8000*(galnumb-1)+1; upperlimit=lowerlimit+7999; } else { upperlimit=min(sector+RANGE, GAMESIZE/5); lowerlimit=max(sector-RANGE, 1); } input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='0') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].control=='0' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number. { target=atoi(bufrec[th].input); sprintf(tmp, "\r\n\ncomputing path to sector %d...", target); show(th,tmp); if (target > upperlimit || target < lowerlimit) { sprintf(tmp, "\r\n\n * Autopilot: Internal limit exceeded! *"); show(th,tmp); threc[th].control=0; showsector(th); return(0); } plotpath(th, sector, target); threc[th].control=0; bufrec[th].input[0]=0; showsector(th); } } else if (input=='0') // first pass through function... prompt for number { sprintf(tmp, "\r\n\nAutopilot: Please enter a destination sector (%d-%d): ", lowerlimit,upperlimit); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='0'; } return(0); } int pageplayer(int th) { int input, user, value; // local variables, initialized below int inputlen; char tmp[DEFLINE]; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='@') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].control=='@' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>9) || (bufrec[th].input[inputlen-2]==13)) // got number. { value=atoi(bufrec[th].input); value=min(value,MAXUSERS); value=max(0,value); threc[th].control=0; bufrec[th].input[0]=0; if (value) { sprintf(tmp, "\r\n\nPaging %s on channel %d...", d.user[value].name, threc[th].radiorand); show(th,tmp); threc[th].radiochan=threc[th].radiorand; radionumber++; sprintf(tmp, "\r\n * Incoming signal from %s to %s on ch. %d%s", d.user[user].name, d.user[value].name, threc[th].radiochan, "\r\n * Secret conversation request (to chat type #)"); radiochan[radiondx.x]=threc[th].radiochan; radiosender[radiondx.x]=ThreadToUser[th]; radiopager[radiondx.x]=value; sprintf(radiorec[radiondx.x], "%s",tmp); radionumb[radiondx.x]=radionumber; // 'Time'stamp it radiondx.x++; if (threc[th].data=='r') { threc[th].control='r'; radioprompt(th); // redraw radio prompt return(0); } else showsector(th); } else showsector(th); } } else if (input=='@') // first pass through function... prompt for number { nopause=1; others(th); nopause=0; sprintf(tmp, "\r\n\nEnter Tag # of player you wish to page. [0]: "); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='@'; } return(0); } int chradch(int th) { int input, user, value; // local variables, initialized below int inputlen; char tmp[DEFLINE]; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='#') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].control=='#' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>9) || (bufrec[th].input[inputlen-2]==13) || (tolower(bufrec[th].input[0])=='t')) { value=atoi(bufrec[th].input); value=min(99999999,value); // pick smaller of numbers value=max(value,0); // eliminate negatives if (tolower(bufrec[th].input[0])=='t') value=d.user[user].team + 100000000; // unaligned get ch# 100M if (value || bufrec[th].input[0]=='0') { threc[th].radiochan=value; sprintf(tmp, "\r\n\nchanging channel to %d...", value); } else { threc[th].radiochan=threc[th].pagerchan; sprintf(tmp, "\r\n\nchanging channel to %d...", threc[th].pagerchan); } show(th,tmp); threc[th].control=0; bufrec[th].input[0]=0; if (threc[th].data=='r') { threc[th].control='r'; radioprompt(th); // redraw radio prompt return(0); } else showsector(th); } } else if (input=='#') // first pass through function... prompt for number { sprintf(tmp, "\r\n\nTune radio to channel 0-99999999? (or T for Team) [%d]: ", threc[th].pagerchan); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='#'; threc[th].data=0; } return(0); } // let user buy fighters with microbots. This should be replaced someday // with a 'build fighters' routine that uses microbots and consumes iron int buyfiters(int th) { int input, user, buy, limit; // local variables, initialized below int inputlen; char tmp[DEFLINE]; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='b') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); limit=(int)(d.user[user].cash/FIGPRICE); if (threc[th].control=='b' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number. { buy=atoi(bufrec[th].input); sprintf(tmp, "\r\n\nBuying %d fighters...", buy); show(th,tmp); if (buy > limit) // number exceeded cash-limited max purchase { sprintf(tmp, "\r\n\nNot enough money... buying %d fighters...", limit); show(th,tmp); } buy=min(limit,buy); // buy smaller of number if (buy > 0) // requested or cash-limied { // maximum d.user[user].fiters += buy; d.user[user].cash -= (buy*FIGPRICE); dirtyuser[user]=1; } threc[th].control=0; bufrec[th].input[0]=0; showsector(th); } } else if (input=='b') // first pass through function... prompt for number { sprintf(tmp, "\r\n\nHow many fighters will you buy @ %d/each? (0-%d) [0]: ", FIGPRICE, limit); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='b'; } return(0); } // locate nearby ships: 3scanners: search 60 sectors, 4:120 sec., 5:180 sec. // random sectors within a range : of 600 sectors, 1200 sec., 1800 sec. // @20gm/scansector @17gm/sec., @14gm/sec. // 1200 gms 2040 gms 2520 gms int findship(int th) { int sector, galstart=1, galend, galnumb, target, start, end, mode, x, user, fcount; char tmp[DEFLINE]; int range[4]= { 0, 600, 1200, 1800 }; int cost[4]= { 0, 1200, 2040, 2520 }; int scanpower[6] = { 0, 1600, 6400, 25600, 102400, 409600 }; user = ThreadToUser[th]; if (d.user[user].scanner<3) { show(th,"\r\n\n\a * You need 3+ scanners to search for ships! *"); return(1); } mode = d.user[user].scanner - 2; if (d.user[user].antitoday < cost[mode]) { show(th,"\r\n\n\a * You don't have enough antimatter! *"); return(0); } sector = d.user[user].sector; galnumb=getgal(sector); galend=GAMESIZE/5; if (galnumb) { galstart=GAMESIZE/5+8000*(galnumb-1)+1; galend=galstart+7999; } start = sector - range[mode]/2; end = sector + range[mode]/2; if (start < galstart) { start=galstart; end=galstart+range[mode]; } if (end > galend) { start=galend - range[mode]; end=galend; } d.user[user].antitoday -= cost[mode]; dirtyuser[user]=1; sprintf(tmp, "\r\n\n(%d fuel used scanning %d sectors)", cost[mode], range[mode]); show(th, tmp); for (x=1;x= start)) { fcount=0; if (d.sector[target].sectortype<3) fcount=d.sector[target].sectortype*20000; else if (d.sector[target].sectortype>9) fcount=basestrength[d.sector[target].sectortype-9]; fcount+=d.sector[target].fiters; sequz=user+target+d.cfg.idcode; if (randz(scanpower[d.user[user].scanner]) > fcount) { sprintf(tmp, "\r\n*** %s [%s] is in sector %d", d.user[x].name, d.team[d.user[x].team].init, d.user[x].sector); show(th, tmp); } } } } return(0); } // this function _only_ turns on and off threc[th].nudgemode & sets sourceport int nudgeport(int th) { if (threc[th].nudgemode) { threc[th].nudgemode=0; show(th,"\r\n\n * Port released *"); } else if (d.user[ThreadToUser[th]].tractor==0) show(th,"\r\n\n\a * You need a tractor device to move ports! *"); else if (d.sector[d.user[ThreadToUser[th]].sector].porttype==0) show(th,"\r\n\n\a * There is no port in this sector! *"); else if (d.sector[d.user[ThreadToUser[th]].sector].porttype>3) show(th,"\r\n\n\a * You can't attach to this port! *"); else if (d.sector[d.user[ThreadToUser[th]].sector].sectortype==1 && d.user[ThreadToUser[th]].tractor<5) show(th,"\r\n\n\a * There is a nebula here -- you'll need 5 tractors! *"); else if (threc[th].nudgemode) threc[th].nudgemode=0; else { threc[th].nudgemode=d.user[ThreadToUser[th]].tractor; threc[th].sourceport=d.user[ThreadToUser[th]].sector; show(th,"\r\n\n * Tractor device attached to port *"); } return(0); } // this function _only_ turns on and off threc[th].warpmode & sets sourcemoon int warpmoon(int th) { if (threc[th].warpmode) { threc[th].warpmode=0; show(th,"\r\n\n * Planet released *"); } else if (d.user[ThreadToUser[th]].tractor<3) show(th,"\r\n\n\a * You need 3+ tractor devices to move planets! *"); else if (d.sector[d.user[ThreadToUser[th]].sector].moontype==0) show(th,"\r\n\n\a * There is no planet in this sector! *"); else if (d.sector[d.user[ThreadToUser[th]].sector].sectortype==1 && d.user[ThreadToUser[th]].tractor<5) show(th,"\r\n\n\a * There is a nebula here -- you'll need 5 tractors! *"); else if (threc[th].warpmode) threc[th].nudgemode=0; else { threc[th].warpmode=d.user[ThreadToUser[th]].tractor; threc[th].sourcemoon=d.user[ThreadToUser[th]].sector; show(th,"\r\n\n * Tractor device attached to planet *"); } return(0); } // dump cargo (to empty holds), probably to scoop some more useful stuff from // planet, but maybe just to increase fuel economy for a long journey int jettison(int th) { char input; // local copy, initialized below input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if ((d.user[ThreadToUser[th]].goods[1]+ d.user[ThreadToUser[th]].goods[2]+ d.user[ThreadToUser[th]].goods[3]) == 0) { show(th,"\r\n\nYou have no cargo to jettison!"); return(0); } if (threc[th].control=='j') { if (input=='y') { d.user[ThreadToUser[th]].goods[1]=0; d.user[ThreadToUser[th]].goods[2]=0; d.user[ThreadToUser[th]].goods[3]=0; dirtyuser[ThreadToUser[th]]=1; show(th,"\r\n\nCargo Jettisoned!"); threc[th].lastdebris=0; if (d.sector[d.user[ThreadToUser[th]].sector].dust < 10) d.sector[d.user[ThreadToUser[th]].sector].dust += 5; } threc[th].control=0; showsector(th); } else if (input=='j') { show(th,"\r\n\nDo you really want to jettison cargo? (Y/N) [N]: "); threc[th].control='j'; } return(0); } int resetstats(int th) // reset scooper statistics memory { threc[th].fuelused=0; threc[th].earned=0; threc[th].spent=0; show(th,"\r\n\n * Autoscooper statistics reset! *"); return(0); } int resetnews(int th) // reset newsnumb to redisplay old news { // each idle pass of gameloop's main loop scans threc[th].showmynews=1; // news and mail for new entries. This makes all threc[th].newsnumb=0; // the old news new again return(0); } int resetradio(int th) // reset radionumb to redisplay old messages { threc[th].showmyrad=1; threc[th].radionumb=0; return(0); } int recall(int th) { register int y; int user, figs=0, fleets=0, fuel=0; char tmp[DEFLINE]; user=ThreadToUser[th]; if (time(NULL) - threc[th].lastscan < 10) { show(th, "\r\n\n * The Recall command isn't ready yet! *"); return(0); } threc[th].lastscan = time(NULL); for (y=1;y<(GAMESIZE+1);y++) { if (d.sector[y].fitersowner==user && d.sector[y].fiters) { if (d.user[user].antitoday < 1) { show(th,"\r\n\nYou have no more antimatter; some fighters may remain deployed!"); break; } fleets++; figs += d.sector[y].fiters; fuel += (1+d.sector[y].fiters); d.user[user].antitoday -= (1+d.sector[y].fiters); d.user[user].fiters+=d.sector[y].fiters; if (d.sector[y].sectortype<10) d.sector[y].fitersowner=0; d.sector[y].fiters=0; // get starbases, graffiti, and bunkers too, as needed dirty(y); dirtyuser[user]=1; if (d.user[user].antitoday < 0) { d.user[user].antitoday = 0; dirtyuser[user]=1; break; } } } sprintf(tmp, "\r\n\n%d grams of antimatter used recalling %d fighters from %d sector(s).", fuel, figs, fleets); show(th,tmp); threc[th].lastscan = time(NULL); return(0); } int deployhb(int th) { int sector,user; user=ThreadToUser[th]; sector=d.user[user].sector; if (d.user[user].beacons<1 && d.sector[sector].beacon!=user) { show(th,"\r\n\n * You have no homing beacons! *"); return(0); } if (d.user[user].beacons>=d.cfg.maxholds/10 && d.sector[sector].beacon==user) { show(th,"\r\n\n * You can't carry any more homing beacons! *"); return(0); } if (d.sector[sector].beacon==user) // retrieve beacon { d.user[user].beacons++; d.sector[sector].beacon=0; // retrieve beacon } else if (d.user[user].beacons>0) { d.user[user].beacons--; d.sector[sector].beacon=user; // deploy beacon santi(th, 0); } dirtyuser[user]=1; dirty(sector); return(0); } int makebase(int th) // make/upgrade/downgrade a starbase { int input, post, user, limit=0, sector, x; int inputlen, basetype=0, cost=0; char tmp[DEFLINE]; user=ThreadToUser[th]; sector=d.user[user].sector; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (d.sector[sector].sectortype>0 && d.sector[sector].sectortype<10) { show(th,"\r\n\nThis sector is too crowded for Starbase construction!"); return(0); } if ((d.sector[sector].sectortype || d.sector[sector].fiters) && d.sector[sector].fitersowner != user) { show(th,"\r\n\nYou must destroy the forces here first!"); return(0); } if (d.sector[sector].sectortype>9) basetype=d.sector[sector].sectortype-9; for (x=1;x= baseprice[x]) limit=x; } limit=min(limit,BASELIMIT); // biggest base is currently a #BASELIMIT limit=max(limit,0); // 0-limit only please... if (threc[th].control=='m') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } inputlen=strlen(bufrec[th].input); if (threc[th].control=='m' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>4) || (bufrec[th].input[inputlen-2]==13)) // got number. { if (bufrec[th].input[0]==13) // user pressed [enter] ... post=basetype; // default to old base size else post=atoi(bufrec[th].input); post=max(post,0); // 0-limit only please... if (post > limit) // number exceeded cash-limited max purchase { sprintf(tmp,"\r\n\nYou can only afford a #%d Starbase!",limit); show(th,tmp); } post=min(limit,post); // deploy smaller of number cost = baseprice[post] - baseprice[basetype]; // down is negative! sprintf(tmp,"\r\n\nBuilding a #%d Starbase [$%d]...",post,cost); show(th,tmp); if (post >= 0) // requested or maximum { d.user[user].cash = d.user[user].cash - cost; d.sector[sector].sectortype = 9 + post; if (post==0) d.sector[sector].sectortype = 0; d.sector[sector].fitersowner = user; if (post==0 && d.sector[sector].fiters==0) d.sector[sector].fitersowner = 0; dirtyuser[user]=1; dirty(sector); } threc[th].control=0; bufrec[th].input[0]=0; if ((basetype > 4 || post > 4) && (basetype != post)) { if (basetype < post) { sprintf(tmp, "\r\nNews: %s made a #%d Starbase", d.user[user].name,post); makenews(th, tmp); } else { sprintf(tmp, "\r\nNews: %s downgraded a #%d Starbase" ,d.user[user].name,basetype); makenews(th, tmp); } } showsector(th); } } else if (input=='m') // first pass through function... prompt for number { sprintf(tmp, "\r\n\nWhat size Starbase do you want here?\r\n\nUse 0 to remove a Starbase: (0,1...%d : $%d,$%d...$%d) [%d]: ", limit, baseprice[0]-baseprice[basetype], baseprice[1]-baseprice[basetype], baseprice[limit]-baseprice[basetype], basetype); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='m'; } return(0); } int deploy(int th) // deploy fighters to guard sector (gameloop) { int input, post, user, limit, sector; int inputlen; char tmp[DEFLINE]; user=ThreadToUser[th]; sector=d.user[user].sector; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (d.sector[sector].sectortype) { if (d.sector[sector].sectortype<10) { show(th,"\r\n\nYou can't deploy fighters in a nebula!"); return(0); } else if (d.sector[sector].fitersowner!=user) { show(th,"\r\n\nYou must destroy the Starbase first!"); return(0); } } if (d.sector[sector].fiters && d.sector[sector].fitersowner != user) { show(th,"\r\n\nYou must destroy the fighters here first!"); return(0); } if (threc[th].control=='d') accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } if (tolower(input)=='r') { recall(th); threc[th].control=0; bufrec[th].input[0]=0; showsector(th); return(0); } inputlen=strlen(bufrec[th].input); limit=(int)(d.user[user].fiters + d.sector[sector].fiters); if (threc[th].control=='d' && inputlen > 1) //second+ pass: got number yet? { if ((inputlen>8) || (bufrec[th].input[inputlen-2]==13)) // got number. { if (bufrec[th].input[0]==13) post=threc[th].deploy; else post=atoi(bufrec[th].input); if (post>0) { sprintf(tmp, "\r\n\nDeploying %d fighters...", post); show(th,tmp); } else if (post<0) { sprintf(tmp, "\r\n\nThat's impossible!"); show(th,tmp); } if (post > limit) // number exceeded cash-limited max purchase { sprintf(tmp, "\r\n\nToo few fighters... deploying %d...", limit); show(th,tmp); } post=min(limit,post); // build smaller of two numbers if (post >= 0) { d.user[user].fiters = limit - post; d.sector[sector].fiters = post; d.sector[sector].fitersowner = user; if (post==0 && d.sector[sector].sectortype < 10) d.sector[sector].fitersowner = 0; dirtyuser[user]=1; dirty(sector); threc[th].deploy=post; } threc[th].control=0; bufrec[th].input[0]=0; showsector(th); } } else if (input=='d') // first pass through function... prompt for number { if (threc[th].deploy > limit) threc[th].deploy = limit; sprintf(tmp, "\r\n\nHow many fighters will you deploy here? \r\n\nOr, type 'r' to recall all deployed fighters. (r, 0-%d) [%d]: ", limit, threc[th].deploy); show(th,tmp); bufrec[th].input[0]=0; threc[th].control='d'; } return(0); } int // process invalid/unimplemented command requests nullrtn(int th) // ( show help hint ) { show(th,"\r\n\n(Type ? for command list)"); return(0); } // plotpath() by Brian Estabrooks is a looping function that replaces the // original recursive pathcalc/pathdepth functions. It's far more efficient // and maybe a bit easier to understand too ;) int plotpath(int th, int sector, int target) { int x, myexit, mysector, galnumb, bot, top, next=0, index=0, len=0, found=0; char tmp[MAXDEPTH*8+80]; // max length of path + prompt, below bzero(tmp,MAXDEPTH*8+80); bzero(bufrec[th].input,DEFLINE); bzero(search,sizeof(int)*AREA); bzero(path,sizeof(int)*AREA); mysector = sector; if (target == mysector) { show(th,"\r\n\n * You're already there! *"); return(0); } galnumb=getgal(sector); bot=min(sector,target); top=max(sector,target); bot-=((AREA-abs(target-sector))/2); top+=((AREA-abs(target-sector))/2); while (index < AREA) { for(x=0;x<6;x++) { myexit = d.sector[mysector].warp[x]; if(!myexit) continue; if(path[myexit % AREA]) continue; if (target && galnumb==0) { if(myexit > top) continue; if(myexit < bot) continue; } path[myexit % AREA] = mysector; if(myexit==target || (!target && d.sector[myexit].moontype==1)) { found = 1; break; } search[index++] = myexit; } if(found || index == next) break; mysector = search[next++]; } if (found) // now build the search path for autowarp() { index = AREA - 1; search[index] = myexit; while((search[index-1] = path[search[index]%AREA]) != sector) index--; next = 2; mysector = sector; while(index < AREA) { for(x=0;x<6;x++) { if(d.sector[mysector].warp[x] == search[index]) break; } if (next+1 > MAXDEPTH) { show(th,"\r\n\n* Hop limit reached / Partial path used *\r\n"); break; } bufrec[th].input[next++]= x + 49; len=strlen(tmp); sprintf(&tmp[len],"%s%d;",((next+7)%10)==0?"\r\n":"",search[index]); mysector = search[index++]; } len=strlen(tmp); sprintf(&tmp[len],"\r\n\n(Type / to autowarp there)"); len=strlen(tmp); for (index=0;index3)) return; // no commodity port here now=time(NULL); limit=d.cfg.gamecycle * 96; qhours = (now - d.sector[sector].portcalcdate)/900; qhours = min(qhours,limit); gxy=getgal(sector); for (x=1;x<4;x++) { max=d.cfg.gamecycle * d.sector[sector].portprod[x]*galprod[gxy] /100; goodprod=(d.sector[sector].portprod[x]*galprod[gxy] /100*qhours)/96; if (!inqonly) d.sector[sector].portgoods[x] = min(goodprod + d.sector[sector].portgoods[x], max); thegoods[x]=min(goodprod + d.sector[sector].portgoods[x], max); theproduce[x]=d.sector[sector].portprod[x]*galprod[gxy] /100; } if (!inqonly) d.sector[sector].portcalcdate=now; return; } int // process user request to land on a planet and take free goods landrtn(int th) { int sector, trade, empties, porttype, user, max, x; char tmp[DEFLINE]; user=ThreadToUser[th]; if (d.user[user].antitoday<1) { show(th,"\r\n\n\a * You're out of antimatter! *"); return(0); } sector=d.user[user].sector; porttype = d.sector[sector].porttype; if (d.sector[sector].moontype != 1) { show(th,"\r\n\nThere is no planet here! Path to closest planet... "); plotpath(th, sector, 0); return(0); } empties = d.user[user].goods[0] - (d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3]); if (empties==0) { show(th,"\r\n\nYou have no empty cargo holds!"); return(0); } santi(th, 0); // deduct antimatter for porting... if (scooping == 0 || landing == 1 ) { sprintf(tmp,"\r\n\nLanding on Planet . . ."); show(th,tmp); if (d.sector[sector].moongoods[0] && ((d.sector[sector].moongoods[0] != user) || (time(NULL) - d.sector[sector].mooncalcdate > 7200))) { if (time(NULL) - d.sector[sector].mooncalcdate < 7201) sprintf(tmp, "\r\n(The last trader to land here was %s %d.%d minutes ago)", d.user[d.sector[sector].moongoods[0]].name, (int)(time(NULL) - d.sector[sector].mooncalcdate) / 60, (int)(time(NULL) - d.sector[sector].mooncalcdate)%60 / 6); else sprintf(tmp, "\r\n(The last trader to land here was %s %d hours ago)", d.user[d.sector[sector].moongoods[0]].name, (int)(time(NULL) - d.sector[sector].mooncalcdate) / 3600); show(th,tmp); } if (d.sector[sector].moonprod[0]!=user) if ((d.sector[sector].moonprod[0]==0) || ((time(NULL) - d.sector[sector].mooncalcdate)>rand20(691200))) { sprintf(tmp,"\r\n\n(This planet now bears your name)"); show(th,tmp); d.sector[sector].moonprod[0]=user; } d.sector[sector].moongoods[0]=user; // remember last user here } if (scooping == 0 || landing == 1 ) { mooncalc(sector); if (porttype && porttype < 4) portcalc(sector,1); } if (scooping==0) { sprintf(tmp,"\r\n\n(You have: %d %s, %d %s, %d %s)\r\n", d.user[user].goods[1],goodsname[1], d.user[user].goods[2],goodsname[2], d.user[user].goods[3],goodsname[3]); show(th,tmp); } // show inventories... if (scooping==0) { for (x=3;x>0;x--) { sprintf(tmp,"\r\n There are %5d holds of %8s", d.sector[sector].moongoods[x], goodsname[x]); show(th,tmp); } } for (x=3;x>0;x--) { if (porttype != x) { max=min(d.sector[sector].moongoods[x], // lesser of: planet stock thegoods[x]); // or port needs trade = min(empties, max); d.user[user].goods[x] += trade; // trade: add dirtyuser[user]=1; d.sector[sector].moongoods[x] -= trade; // subtract dirty(sector); if (trade) { empties = d.user[user].goods[0] - (d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3]); if (scooping==0) { sprintf(tmp,"\r\n\n(You took %d holds of %s)", trade,goodsname[x]); show(th,tmp); } } } } if (empties && porttype) // got non-porttype goods, still not full { max=d.sector[sector].moongoods[porttype]; trade = min(empties, max); d.user[user].goods[porttype] += trade; // trade: add d.sector[sector].moongoods[porttype] -= trade; // subtract if (scooping==0) { dirtyuser[user]=1; dirty(sector); if (trade) { sprintf(tmp,"\r\n\n(You took %d holds of %s)", trade,goodsname[porttype]); show(th,tmp); } } } return(0); } int scoop(int th) // autotrade out planet/port combo (gameloop thread) { int sector, porttype, user, startfuel, startcash, tradecount=0, lastcash, lastprofit, lastfuel, lastfueluse, avgprofit; char tmp[DEFLINE]; user=ThreadToUser[th]; if (d.user[user].antitoday<25000) { show(th,"\r\n\n * Insufficient Antimatter! *"); return(0); } sector=d.user[user].sector; porttype = d.sector[sector].porttype; if (d.sector[sector].moontype != 1) { show(th,"\r\n\nThere is no planet here! Path to closest planet... "); plotpath(th, sector, 0); return(0); } if (d.sector[sector].porttype < 1 || d.sector[sector].porttype > 3) { show(th,"\r\n\n * There is no commodity port in this sector! *"); return(0); } startcash = d.user[user].cash; startfuel = d.user[user].antitoday; avgprofit = (threc[th].earned - threc[th].spent) / max((threc[th].fuelused/1000),1); d.user[user].goods[porttype]=0;// dump wrong goods type scooping=porting=landing=0; do { tradecount++; scooping++; lastcash = d.user[user].cash; lastfuel = d.user[user].antitoday; if ((d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3]) < d.user[user].goods[0]) { landing++; landrtn(th);// it should always land unless full holds (pass 1) } if ((d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3] - d.user[user].goods[porttype]) == d.user[user].goods[0]) { porting++; portrtn(th); // automatic sellonly if scooping>0 } else scooping = 0; lastprofit = d.user[user].cash - lastcash; lastfueluse = lastfuel - d.user[user].antitoday; if ((d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3] ) > 0) { d.user[user].goods[1]=d.user[user].goods[2]=d.user[user].goods[3]=0; scooping = 0; } if (lastprofit*1000/max(lastfueluse,1) < avgprofit) scooping = 0; if (d.user[user].antitoday<25000) scooping=0; if (scooping==0) d.user[user].goods[1]=d.user[user].goods[2]=d.user[user].goods[3]=0; } while (scooping); dirtyuser[user]=1; dirty(sector); lastprofit = d.user[user].cash - startcash; lastfueluse = startfuel - d.user[user].antitoday; sprintf(tmp,"\r\n\nAutoscoop %d trade summary:\r\n\n (Session average $/kg: %d)\r\n Antimatter used: %d\r\n Total Profit: %d\r\n Profit/Kg fuel used: %d", tradecount, avgprofit, lastfueluse, lastprofit, (lastprofit*1000)/(max(lastfueluse,1)) ); // avoid divide by 0 show(th,tmp); return(0); } int getletter(th) // get trading-quality letter from adjacent sectors { register int x; int y, z, target,workvalue, max=0, user, sector, fcount, scalemax, porttype, stuffedholds[4], goodsprice[4]; int scanpower[6] = { 0, 1600, 6400, 25600, 102400, 409600 }; user=ThreadToUser[th]; sector=d.user[user].sector; scalemax=d.user[user].goods[0]*18/25; bestletter=255; for (y=0;y<6;y++) { target=d.sector[sector].warp[y]; portletter[y]=' '; workvalue=0; if (target>0) { porttype=d.sector[target].porttype; if ( (d.sector[target].porttype) && (d.sector[target].sectortype < 10 || d.sector[target].fitersowner == user) && ( (d.sector[target].fiters == 0) || (d.sector[target].fitersowner == 0) || (d.sector[target].fitersowner == user) || (rand16(11)>d.sector[target].fiters) ) ) { if (porttype < 4) { portcalc(target,1); stuffedholds[0]=0; // total stuffed cntainers for (x=1;x<4;x++) { stuffedholds[x]=0; // unsellable stuff if ((d.user[user].goods[x] > thegoods[x]) && (x != porttype)) { stuffedholds[0] += (d.user[user].goods[x] - thegoods[x]); stuffedholds[x] = (d.user[user].goods[x] - thegoods[x]); } } for (z=1;z<4;z++) { max=d.user[user].goods[z] - stuffedholds[z];// sale max if (porttype==z) max = d.user[user].goods[0] - d.user[user].goods[z] - stuffedholds[0]; else // port diff type than commod we're considering if (thegoods[z]9)) portletter[y]=(char)(25-workvalue/scalemax+65);//ucase else portletter[y]=(char)(25-workvalue/scalemax+97);//lcase if (portletter[y]>='a') if (toupper(bestletter)>toupper(portletter[y])) bestletter=tolower(portletter[y]); } else if (porttype==4) portletter[y]='*'; // tradingposts else if (porttype==5) portletter[y]='~'; // spaceports } else if (d.sector[target].sectortype > 9 && (d.user[user].scanner>0 || user==d.sector[target].fitersowner)) portletter[y]='!'; // no port but there is a starbase there else if (d.sector[target].fitersowner==user && d.sector[target].fiters) portletter[y]='-'; // no port but user has fighters if (portletter[y]==' ') { if (d.sector[target].fitersowner==0 && d.sector[target].fiters) portletter[y]='^'; else if (d.sector[target].fitersowner!=0 && d.sector[target].fiters && d.user[user].scanner>0) portletter[y]='='; } // the more ships in a guarded sector, the greater the chance to be seen if (porttype<4 && d.user[user].scanner > 0) // TP's, SP's override ships { for (x=1;x9) fcount=basestrength[d.sector[target].sectortype-9]; fcount+=d.sector[target].fiters; sequz=user+target+d.cfg.idcode; if (randz(scanpower[d.user[user].scanner]) > fcount) portletter[y]='?'; } } } } } return(0); } int computrade(int th) { int t=0, user, sector, x,y, oldbestletter; int start=0, inc=1, stop=6; char tmp[DEFLINE]; if (rand16(2)==0) { start=5; inc=-1; stop=-1; } user=ThreadToUser[th]; sector=d.user[user].sector; if (threc[th].ctrade == 0) threc[th].ctrade = 'z'; threc[th].autowarp=2; getletter(th); oldbestletter=bestletter; if (bestletter <= threc[th].ctrade) { for (x=start;x!=stop;x+=inc) { if (tolower(portletter[x])==bestletter) { t=d.sector[sector].warp[x]; bufrec[th].input[2]= x + 49; break; } } } else { do // try to avoid the last 16 visited sectors... { do // definitely avoid warps to sector 0... { x=rand16(6); t=d.sector[sector].warp[x]; } while (t==0); // if target sector is in visited list, break with y < 16 for (y=0;y<16;y++) if (threc[th].last16[y]==t) break; } while (y<16 && rand16(50)); // 2% chance of a visited sector bufrec[th].input[2]= x + 49; } threc[th].noshow=2; sprintf(tmp,"\r\n\nEntering sector %d . . .",t); show(th,tmp); warprtn(th); // bestletter gets replaced here... threc[th].autowarp=0; if (d.user[user].antitoday<1) // last warprtn may have exhausted fuel... { threc[th].noshow=0; return(0); } if (oldbestletter <= threc[th].ctrade) { portrtn(th); if (threc[th].ctrade > 'k' && oldbestletter < threc[th].ctrade) threc[th].ctrade--; } else { if (threc[th].ctrade < 'z') threc[th].ctrade++; } return(0); } int usespaceport(int th) { int user, sector, fueluse; char tmp[DEFLINE]; user=ThreadToUser[th]; sector=d.user[user].sector; fueluse = 20*santi(th, 1); // calculate antimatter for transport... d.user[user].antitoday=max(0, d.user[user].antitoday - fueluse); d.user[user].sector=d.sector[sector].portcalcdate; d.user[user].lastsector=sector; threc[th].nudgemode = threc[th].warpmode = 0; dirtyuser[user]=1; d.sector[sector].portprod[0]=user; dirty(sector); sector=d.user[user].sector; sprintf(tmp, "\r\nNews: %s traveled to %s", d.user[ThreadToUser[th]].name, galname[getgal(sector)%GALAXIES]); makenews(th, tmp); sprintf(tmp,"\r\n\nWelcome to Galaxy %s!",galname[getgal(sector)%GALAXIES]); show(th,tmp); return(0); } int // process user request to dock with a port (to trade goods) portrtn(int th) { int sector, trade, empties, porttype, user, max, sellonly=0, gxy=0; int x, goodsprice[4]; char buysell[2][8] = {"buying", "selling"}; char tmp[DEFLINE]; sellonly=scooping; if (tolower(bufrec[th].inbuf[threc[th].inptr][0])=='s') sellonly=1; user=ThreadToUser[th]; if (d.user[user].antitoday<1) { show(th,"\r\n\n\a * You're out of antimatter! *"); return(0); } sector=d.user[user].sector; gxy=getgal(sector); porttype = d.sector[sector].porttype; if (porttype == 0) { show(th,"\r\n\nThere is no port in this sector!\r\n"); return(1); } if (porttype == 5) usespaceport(th); if (porttype == 4) tradingpost(th); if (porttype > 3) return(0); santi(th, 0); // deduct antimatter for porting... if (scooping == 0 || porting == 1 ) { sprintf(tmp,"\r\n\nDocking with %s . . .", plantname[porttype]); show(th,tmp); if (d.sector[sector].portgoods[0] && ((d.sector[sector].portgoods[0] != user) || (time(NULL) - d.sector[sector].portcalcdate > 7200))) { if (time(NULL) - d.sector[sector].portcalcdate < 7201) sprintf(tmp, "\r\n(The last trader to dock here was %s %d.%d minutes ago)", d.user[d.sector[sector].portgoods[0]].name, (int)(time(NULL) - d.sector[sector].portcalcdate) / 60, (int)(time(NULL) - d.sector[sector].portcalcdate)%60 / 6); else sprintf(tmp, "\r\n(The last trader to dock here was %s %d hours ago)", d.user[d.sector[sector].portgoods[0]].name, (int)(time(NULL) - d.sector[sector].portcalcdate) / 3600); show(th,tmp); } if (d.sector[sector].portprod[0]!=user) if ((d.sector[sector].portprod[0]==0) || ((time(NULL) - d.sector[sector].portcalcdate)>rand20(691200))) { sprintf(tmp,"\r\n\n(This %s now bears your name)", plantname[d.sector[sector].porttype]); show(th,tmp); d.sector[sector].portprod[0]=user; } d.sector[sector].portgoods[0]=user; // remember last user here } if (scooping == 0 || porting == 1 ) portcalc(sector,0); if (scooping==0) { sprintf(tmp,"\r\n\n(You have: %d %s, %d %s, %d %s)\r\n", d.user[user].goods[1],goodsname[1], d.user[user].goods[2],goodsname[2], d.user[user].goods[3],goodsname[3]); show(th,tmp); } // show inventories for (x=1;x<4;x++) { if (d.sector[sector].porttype==x) goodsprice[x]=base[x] - (base[x]*2/3 * d.sector[sector].portgoods[x]) / (d.cfg.gamecycle * d.sector[sector].portprod[x] * galprod[gxy] / 100); else goodsprice[x]=base[x] + (base[x]*d.sector[sector].portgoods[x]) / (d.cfg.gamecycle * d.sector[sector].portprod[x] * galprod[gxy] / 100); if (scooping==0) { sprintf(tmp,"\r\n %7s up to %5d holds of %8s for $%2d/hold", buysell[x==porttype], d.sector[sector].portgoods[x], goodsname[x], goodsprice[x]); show(th,tmp); } } // now sell some goods for (x=1;x<4;x++) { if (porttype!=x) { trade=min(d.sector[sector].portgoods[x], d.user[user].goods[x]); if (trade > 0) { d.user[user].goods[x] -= trade; d.sector[sector].portgoods[x] -= trade; d.user[user].cash += (trade * goodsprice[x]); threc[th].earned += (trade * goodsprice[x]); if (scooping==0) { dirty(sector); dirtyuser[user]=1; sprintf(tmp,"\r\n\n(You sold %d holds of %s for $%d/hold)", trade, goodsname[x], goodsprice[x]); show(th,tmp); } } } } // now buy some goods... if (sellonly==0) { empties = d.user[user].goods[0] - (d.user[user].goods[1] + d.user[user].goods[2] + d.user[user].goods[3]); max = d.user[user].cash / goodsprice[porttype]; // cash limited trade = min(empties, max); trade = min(trade,d.sector[sector].portgoods[porttype]); if (trade > 0) { d.user[user].goods[porttype] += trade; // do lesser trade... d.sector[sector].portgoods[porttype] -= trade; d.user[user].cash -= (trade * goodsprice[porttype]); threc[th].spent += (trade * goodsprice[porttype]); if (scooping==0) { dirty(sector); dirtyuser[user]=1; sprintf(tmp,"\r\n\n(You bought %d holds of %s for $%d/hold)", trade, goodsname[porttype], goodsprice[porttype]); show(th,tmp); } } } return(0); } void // update a users fuel inventory as new fuel is issued each minute updatefuel(int th) // if this hurts backup of busy games, up to 10 mins { int fuelminute, minutes, daylimit; time_t now; now=time(NULL); minutes = (now/60 - d.user[ThreadToUser[th]].lastfuel); if (minutes>0) { daylimit=min(184,d.cfg.gamelength/2);// up to 1/2 gamelength capacity daylimit=max(7,daylimit); // but not > 184 or < 7 days. fuelminute=d.cfg.dailyanti/1440; // fuel issue in grams-per-minute if (d.user[ThreadToUser[th]].antitoday < 0) d.user[ThreadToUser[th]].antitoday = 0; d.user[ThreadToUser[th]].antitoday += (fuelminute * minutes); d.user[ThreadToUser[th]].lastfuel = (now/60); // fueldate is in minutes d.user[ThreadToUser[th]].antitoday = min(d.cfg.dailyanti*daylimit, d.user[ThreadToUser[th]].antitoday); dirtyuser[ThreadToUser[th]]=1; } return; } // keep radio displays+header under RADLINE size to fit in radiorec[] int // send radio message to other logged in players radiortn(int th) // part of gameloop() thread! msg from any user { int input, inputlen, sent=0, crsent; char tmp[DEFLINE]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (d.user[ThreadToUser[th]].valid < 10) return(0); if (threc[th].control=='r' && threc[th].status>0) accumulateinput(th); else { bzero(bufrec[th].input,DEFLINE); threc[th].incount=0; } if (bufrec[th].input[0]=='#') // pass control to chradch() { bufrec[th].input[0]=0; sprintf(tmp, "\r\n\nTune radio to channel 0 - 99999999? [%d]: ", threc[th].pagerchan); show(th,tmp); threc[th].data='r'; threc[th].control='#'; return(0); } if (bufrec[th].input[0]=='@') // pass control to pageplayer() { nopause=1; others(th); nopause=0; sprintf(tmp, "\r\n\nEnter Tag # of player you wish to page. [0]: "); show(th,tmp); bufrec[th].input[0]=0; threc[th].data='r'; threc[th].control='@'; return(0); } if (bufrec[th].input[0]=='[') // redisplay past radio messages { threc[th].showmyrad=1; threc[th].radionumb=0; bufrec[th].input[0]=0; threc[th].incount=0; threc[th].control='r'; threc[th].status=1; } if (bufrec[th].input[0]==']') // redisplay past news messages { threc[th].showmynews=1; threc[th].newsnumb=0; bufrec[th].input[0]=0; threc[th].incount=0; threc[th].control='r'; threc[th].status=1; } if (bufrec[th].input[0]=='!') // exit { threc[th].control=0; // finished.... cleanup bufrec[th].input[0]=0; threc[th].incount=0; threc[th].status=0; showsector(th); // get out return(0); } inputlen=strlen(bufrec[th].input); if (inputlen) threc[th].status=2; if (threc[th].control=='r' && inputlen > 1) { if ((inputlen > 77) || (bufrec[th].input[inputlen-2]==13)) { bufrec[th].input[78]=0; crsent=0; if (bufrec[th].input[inputlen-2]==13) { bufrec[th].input[inputlen-2]=0; crsent=1; } inputlen=strlen(bufrec[th].input); if (inputlen>0) { radionumber++; sprintf(tmp,"\r\n(radio message #%d transmitted on channel %d)" , radionumber, threc[th].radiochan); show(th,tmp); sprintf(tmp, "\r\nIncoming radio message %d from %s on ch. %d:\r\n%s", radionumber, d.user[ThreadToUser[th]].name, threc[th].radiochan, bufrec[th].input); radiochan[radiondx.x]=threc[th].radiochan; radiosender[radiondx.x]=ThreadToUser[th]; radiopager[radiondx.x]=0; sprintf(radiorec[radiondx.x], "%s",tmp); radionumb[radiondx.x]=radionumber; // 'Time'stamp it radiondx.x++; if (crsent) show(th,"\r"); else show(th,"\r\n"); sent++; } else { threc[th].control=0; // finished.... cleanup bufrec[th].input[0]=0; threc[th].incount=0; threc[th].status=0; showsector(th); // get out return(0); } input='r'; // after xmit, prepare for next one bufrec[th].input[0]=0; threc[th].incount=0; threc[th].control=0; // trigger radio prompt, below } } if (threc[th].control==0 && input=='r') { threc[th].control='r'; threc[th].status=1; radioprompt(th); } return(0); } void // show the radio input prompt to a user sending a radio message radioprompt(int th) { show(th,"\r\n________Type your message below, Hit 'Enter' to exit, '[' to redisplay________\r\n"); } // keep news displays+header under NEWLINE size to fit in newsrec[] void makenews(int th, char *text) { newsnumber++; newssender[newsndx.x]=ThreadToUser[th]; sprintf(newsrec[newsndx.x], "%s",text); newsnumb[newsndx.x]=newsnumber; // 'Time'stamp it newsndx.x++; return; } int showhistory(int th) { int x, found=0, user, showcount=0, linecount=1, input; user=ThreadToUser[th]; input=tolower(bufrec[th].inbuf[threc[th].inptr][0]); if (threc[th].control=='e') // second, subsequent passes { if (input=='n') { threc[th].control=0; showsector(th); return(1); } else { linecount=threc[th].status; found=1; } } for (x=linecount;xLINES) break; show(th, d.histrec[x].text); bzero(&d.histrec[x],sizeof(struct histlayout)); dirtyhistory[x]=1; dirtyhistblock[x/DHBLOCK]=1; linecount++; showcount++; } } if (x 0) // don't send history to 0 (neutrals, etc) { // put events into history in time order start=time(NULL)/900; // use 15 minute increments as startpoints start=start%(MAXHIST/4*3); // divmod by .75 history to not overrun eof for (x=start;x0) { nw=writen(connfd,bufrec[th].outbuf[threc[th].outptr],n); if (nw!=n) { // make user reconnect! threc[th].killout=1; sleep(60); // allow 1 minute for reaping } } bzero(bufrec[th].outbuf[threc[th].outptr],DEFLINE); threc[th].gorite[threc[th].outptr++]=0; idlecount=0; // user did something; reset idlecount usleep(IDLESLEEP/10); // [* not idle, keep user honest: 1/2000th sec *] } // [* if this is longer than sleep in showsector ships, it overruns *] else { usleep(5*IDLESLEEP); // idle busy loop; sleep 1/40th second if (idlecount++ > 600) // 15s idle; sleep an extra 40th sec... { usleep(5*IDLESLEEP); if (idlecount > 2100) // 90s idle; sleep an extra 40th sec... usleep(5*IDLESLEEP);// for total of 1/13th second sleep } } if (threc[th].timeout==1) // check if user's still idle { // user transmitted recently; reset idle warning flag. if ((time(NULL) - threc[th].lastaccess) < (d.cfg.timeout*4/5)) threc[th].timeout=0; } else if ((time(NULL) - threc[th].lastaccess) > (d.cfg.timeout*4/5)) // idle? if (threc[th].logmeout==0 && threc[th].timeout==0) { sprintf(tmp, "\r\n *** Idle keyboard: timeout in %d seconds! ***\r\n%c" ,d.cfg.timeout/5,7), nw=writen(connfd,tmp,strlen(tmp)); threc[th].timeout=1; } if (shutdownnow>10) threc[th].killout=1; } sleep(60); // something turned off inuse[th] flag; die pthread_exit(NULL); } /////////////////////////////////////////////////////////////////////// /// This thread is executed once for each connection, reads input /// /////////////////////////////////////////////////////////////////////// static void * // all user network input comes in via this thread userin(void *arg) { register int th=0; // index for use with thread arrays int iac=0, n=0, connfd, // connected socket file descriptor x,y; // general purpose subscripts char tmp[DEFLINE]; unsigned char work[DEFLINE]; pthread_detach(pthread_self()); th = *((int *) arg); // who am I? connfd = threc[th].fdconn; threc[th].itid=pthread_self(); threc[th].lastaccess=time(NULL)-5; // BE fix for DoS // send charmode telnet negotiation request // sprintf(bufrec[th].inbuf[threc[th].inndx] // load first buffer... ,"%c%c%c%c%c%c%c%c%c" // ...as if user input... ,255,251,1 // (iac will echo) ,255,251,3 // (iac will suppress-ga) ,255,253,3 // (iac do suppress-ga) ); threc[th].gocopy[threc[th].inndx++]=1; // ...and set flag while (inuse[th]) { // while, below, waits in case gameloop has our next buffer in use while (threc[th].gocopy[threc[th].inndx]==1) usleep(25000); // make sure ndx isn't going to catch ptr! // next 8 lines are testcode threc[th].intmp=threc[th].inndx; threc[th].intmp++; while (threc[th].intmp==threc[th].inptr) { usleep(25000); threc[th].intmp=threc[th].inndx; threc[th].intmp++; } bzero(work,DEFLINE); if ( (n = read(connfd, work, MAXLINE)) > 0) { // following loops through telnet negotiation responses for (x=y=0;x0) { if (threc[th].noecho==0) { // should backspaces be handled in inbuf?? sprintf(tmp,"%c%c%c",8,32,8); writen(threc[th].fdconn,tmp,strlen(tmp)); } pthread_mutex_lock(&inmtx[th]); threc[th].incount--; bufrec[th].input[threc[th].incount]=0; pthread_mutex_unlock(&inmtx[th]); } } else // normal byte (strip non-text) if (work[x]>31 && work[x]<127) { bufrec[th].inbuf[threc[th].inndx][y++] = work[x]; pthread_mutex_lock(&inmtx[th]); threc[th].incount++; pthread_mutex_unlock(&inmtx[th]); } } threc[th].lastaccess=time(NULL)-5; // BE fix for DoS // the following if is Brian's fix for 'backspace bugs' if (strlen(bufrec[th].inbuf[threc[th].inndx])>0) threc[th].gocopy[threc[th].inndx++]=1; // done; inc. ndx } if (n<1) if (errno != EINTR) threc[th].killin=1, // request gameloop to reap me sleep(60); // ...1 minute to be reaped... usleep(66666); // limit input speed; sleep 1/15th second } sleep(60); // something turned off inuse[th] flag; die pthread_exit(NULL); } /////////////////////////////////////////////////////////////////////// /// Misc program initialization code -- runs before gameloop! /////// /////////////////////////////////////////////////////////////////////// void // initialize structs, setup interrupt service routines, etc initialization() { // and any other startup initialization // init random number generators, etc int datasize, x, increment=0, basevalue=0; char tmp[32]; for (x=0;x= 0 ; y--) d.user[user].ip[y+1]=d.user[user].ip[y]; d.user[user].ip[0]=myip; dirtyuser[user]=1; } return(0); } int filesize(char fname[]) { if (stat(fname,&fbuf)==(-1)) return(0); else return(fbuf.st_size); } void loadmap() // load existing map in order, user data from 'filename' { int user, sector, hist; bzero(&d,sizeof(struct datalayout)); if ((datafp=fopen(filename,"r"))==NULL) return; for (user=0;user < (MAXUSERS+1);user++) fread(&d.user[user],sizeof(struct userlayout),1,datafp); for (sector=0;sector < (GAMESIZE+1);sector++) fread(&d.sector[sector],sizeof(struct maplayout),1,datafp); for (hist=0;hist < MAXHIST;hist++) fread(&d.histrec[hist],sizeof(struct histlayout),1,datafp); fread(&d.cfg,sizeof(struct cfglayout),1,datafp); for (user=0;user < (MAXUSERS+1);user++) // ...same # of teams as users... fread(&d.team[user],sizeof(struct teamlayout),1,datafp); fclose(datafp); } void resetplayers() // this function is called _after_ game reset { unsigned int x, now; now=time(NULL); for (x=1;x0) { //d.user[x].lastcall=now; //d.user[x].antitoday= d.cfg.dailyanti; d.user[x].antitoday=0; d.user[x].lastfuel = now/60 - 1440; d.user[x].timeused=0; d.user[x].reppoints=0; d.user[x].beacon=0; d.user[x].beacons=0; d.user[x].scanner=0; d.user[x].tractor=0; d.user[x].combats=0; d.user[x].lastsector=1; d.user[x].fiters=0; d.user[x].team=0; d.user[x].app=0; d.user[x].cash=600; // 600 gives 25 hours credit for purge purposes if (UserToThread[x] && (x==ThreadToUser[UserToThread[x]])) { d.user[x].sector=rand24(GAMESIZE/5)+1; // awake! be reborn! //d.user[x].goods[0]=d.cfg.minholds; // cargo holds d.user[x].goods[0]=0; // cargo holds d.user[x].logons=1; } else { d.user[x].sector=0; d.user[x].goods[0]=0; // cargo holds d.user[x].logons=0; } 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; dirtyuser[x]=1; } } } void resetgame() { unsigned int now, x; char tmp[DEFLINE]; now=time(NULL); d.cfg.startdate=now; // date new game began d.cfg.idcode=rand20(1000000)+1; // unique gameid: number 1 to 1000000 dirtycfg=1; bzero(&d.sector[0],sizeof(struct maplayout) * (GAMESIZE+1)); calcproductivity(); loadgalnames(); // loads and scrambles galaxy names resetmap(); for (x=1;x0) { if (d.team[team].ally[0] == user) // deleted user is captain { d.team[team].ally[0]=0; // delete capt & team (temporarily) for (x=1;x -1 && countt > 1 && countx > 2 && countz > 4 && countltx) { d.sector[d.sector[x].warp[t]].warp[v]=0; d.sector[x].warp[t]=0; } } return; } void globular(int start, int stop) { register int y; int w[6], size, x; start--; size=stop-start; for (x=1;x<=size;x++) { w[0]= (x-400) + start; if (w[0] <= start) w[0]=0; w[1]= (x-20) + start; if (w[1]/400*400==w[1] || (w[1]-1)/400 != (x+start-1)/400) w[1]=0; w[2]= (x-1) + start; if (w[2]/20*20==w[2] || (w[2]-1)/20 != (x+start-1)/20) w[2]=0; w[3]= (x+1) + start; if ((w[3]-1)/20 != (x+start-1)/20) w[3]=0; w[4]= (x+20) + start; if ((w[4]-1)/400 != (x+start-1)/400) w[4]=0; w[5]= (x+400) + start; if (w[5] > start+size) w[5]=0; for (y=0;y<6;y++) d.sector[x+start].warp[y] = w[y]; } return; } void globstring(int start, int stop) { register int y; int w[6], size, x, cubesize=125, cubestart; start--; size=stop-start; for (cubestart=start;cubestart cubestart+cubesize) w[5]=0; for (y=0;y<6;y++) d.sector[x+cubestart].warp[y] = w[y]; } } // link the cubies... d.sector[start+1].warp[0]=stop; d.sector[stop].warp[5]=start+1; for (cubestart=start+126;cubestart start+size) w[5]=0; for (y=0;y<6;y++) d.sector[x+start].warp[y] = w[y]; } return; } void spiral(int start, int stop) { register int y; int hold, w[6], size, x, z; start--; size=stop-start; for (x=1;x<=size;x++) { w[0]= (x+size-400) % size + start; w[1]= (x+size-20) % size + start; w[2]= (x+size-1) % size + start; w[3]= (x+1) % size + start; w[4]= (x+20) % size + start; w[5]= (x+400) % size + start; for (y=0;y<6;y++) w[y] = w[y]==start ? stop : w[y]; // sort: always fill w[0] first, order maps with small numbers first! for (z=0;z<6;z++) // bubble party... little warps up front for (y=z;y<6;y++) if (w[z] > w[y]) hold=w[z], w[z]=w[y], w[y]=hold; for (y=0;y<6;y++) d.sector[x+start].warp[y] = w[y]; } return; } void resetmap() { register int x; int g, gg, h, hh, y, z, s, t, u; // general purpose local subscripts int galcount, bigwarp, medwarp, lilwarp, bubcount; int galsize[GAMESIZE/10000+1]; int galstart[GAMESIZE/10000+1]; int w[6]; // temp array to build warps for each sector... int hold; // used in the warp sort as temporary storage galsize[0]=GAMESIZE/5; // size of Milky Way, the first (big) galaxy galstart[0]=1; // first sector of Milky Way galcount=GAMESIZE/10000; // total number of external GALAXIES for (x=1;x<=galcount;x++) // load sizes, start sector of ext. GALAXIES { galsize[x]=8000; galstart[x]=galsize[0]+8000*(x-1)+1; } ////////////////////////////////////////////////////////////////// // the following loop loads ports, planets, nebulas, and neutral // fighters into the entire universe of GAMESIZE size. It // also builds the interconnections (warps) between the many // locations (sectors) in the universe as one-way cluster // galaxy types. Later, some of the outer GALAXIES are rebuilt // as other galaxy types. ////////////////////////////////////////////////////////////////// bigwarp=max(96, GAMESIZE/1500); medwarp=max(24, GAMESIZE/6000); lilwarp=max(6, GAMESIZE/24000); for (g=0;g<=galcount;g++) { d.cfg.galtype[g]=0; if (g>0) // for 8000 sector outer GALAXIES, flatten { bigwarp=999; medwarp=666; lilwarp=444; } for (x=galstart[g];x= (galstart[g]+galsize[g])) w[3] = (galstart[g]+galsize[g]) - rand24(lilwarp); } while (w[3] >= (galstart[g]+galsize[g]) || w[3] == x); do { w[4] = x + rand24(medwarp) + (lilwarp + 1); if (w[4] >= (galstart[g]+galsize[g])) w[4] = (galstart[g]+galsize[g]) - rand24(medwarp / 2); } while (w[4] >= (galstart[g]+galsize[g]) || w[4] == x); do { w[5] = x + rand24(bigwarp) + (medwarp + lilwarp +1); if (w[5] >= (galstart[g]+galsize[g])) w[5] = (galstart[g]+galsize[g]) - rand24(bigwarp / 3); } while (w[5] >= (galstart[g]+galsize[g]) || w[5] == x); // sort: always fill w[0] first, order maps with small numbers first! for (z=0;z<6;z++) // bubble party... little warps up front for (y=z;y<6;y++) if (w[z] > w[y]) hold=w[z], w[z]=w[y], w[y]=hold; // for random 6-exit galaxy types, eliminate duplicate warps... for (y=0;y<6;y++) // load map warps from temp array d.sector[x].warp[y]=w[y]; for (y=0;y<5;y++) { if (w[y]==w[y+1]) // ooops! found a duplicate warp... { x--; // from the top, redo this sector's warps. break; // exit now so we only decrement x once } } } } for (y=1;y<7;y++) // manually set up the warps from sector 1 to 2-7 // and make those sectors contain nebulas { d.sector[y+1].sectortype=1; // make sectors 2-7 nebulas d.sector[y+1].fiters= // put neutrals in sectors 2-7 rand20 (d.cfg.gamelength // random portion * (d.cfg.dailyanti/99999 + 10) * d.cfg.prodmult/500 ) + d.cfg.gamelength*d.cfg.gamecycle/3; // minimum portion d.sector[y+1].warp[0]=1; // link 2-7 to sector 1 d.sector[1].warp[y-1]=y+1; // make sector 1 link to 2,3,4,5,6,7 } for (x=1;x w[y]) hold=w[z], w[z]=w[y], w[y]=hold; for (y=0;y<6;y++) // load 't' warps back to map d.sector[d.sector[s].warp[t]].warp[y]=w[y]; d.sector[s].dust=0; // clean it } } } while (bubcount); // a pass with bubcount==0; no more bubbles! } // description... new name.... Type // 6-exit 1-way "Open Cluster" 0 // Traditional "Spiral" 1 // Old Cube "Giant Globular" 2 // 5x5x320 "Bar" 3 // Cubic Necklace "Globular String" 4 // New Random 2-way "Irregular" 5 // long 6-exit 1-way "Elliptical" 6 (elongated 6-exit one-way -- bottom) d.cfg.galtype[0]=6; // big bottom galaxy we start in for (g=1;g<=galcount;g++) // for each _external_ galaxy... { x=rand24(1990)+5+2000*(g-1); // select spaceport sector for MW d.sector[x].porttype=5; // put spaceport in sector x of MW d.sector[x].portcalcdate=galstart[g]; // point to first sector of gal[g] d.sector[galstart[g]].porttype=5; // put spaceport in gal[g] d.sector[galstart[g]].portcalcdate=x; // point back to spaceport in MW d.cfg.galtype[g]=0; if (rand16(100) < 16) // change galaxy type... { d.cfg.galtype[g]=5; irregular(galstart[g], galstart[g]+7999); } else if (rand16(100) < 20) { d.cfg.galtype[g]=4; globstring(galstart[g], galstart[g]+7999); } else if (rand16(100) < 25) { d.cfg.galtype[g]=1; spiral(galstart[g], galstart[g]+7999); } else if (rand16(100) < 33) { d.cfg.galtype[g]=2; globular(galstart[g], galstart[g]+7999); } else if (rand16(100) < 50) { d.cfg.galtype[g]=3; bar(galstart[g], galstart[g]+7999); } // ...else default to an Open Cluster, already built, above... } // interlink outer galaxies of same galtype below for (g=2;g<=galcount;g++) { // for each _external_ galaxy for (h=1;h<=galcount;h++) { // for each _external_ galaxy if (g<=h) continue; // link only once but not to itself ... if (d.cfg.galtype[g] != d.cfg.galtype[h]) continue; // ... or a diff galtype // (got two different galaxies of same type pointed to by g and h indices) // select empty sector, gg, in gal g while (d.sector[gg=(galstart[g]+rand24(7980+10))].porttype>0); // select empty sector, hh, in gal h while (d.sector[hh=(galstart[h]+rand24(7980+10))].porttype>0); // put in matching spaceports d.sector[gg].porttype=5; // put spaceport in gal g d.sector[hh].porttype=5; // put spaceport in gal h d.sector[gg].portcalcdate=hh; // point gal g spaceport to sector hh d.sector[hh].portcalcdate=gg; // point gal h spaceport to sector gg } } return; } void makemap() // simple universe (map file) generation, performed at game startup { // check if map/user data (d.) exists... load it else build new ones // This routine will only be used to initialize a new game once the // backup thread is running and load-from-data-file function exists int user, sector, hist, x; bzero(&d,sizeof(struct datalayout)); // set initial game configuration codes and values d.cfg.idcode=rand20(1000000)+1; // unique gameid: number 1 to 1000000 d.cfg.startdate=time(NULL); // date game began d.cfg.gamelength=GAMELENGTH; // game ends on startdate+gamelength d.cfg.gamecycle=GAMECYCLE; // game cycle in earth days (inventories) d.cfg.timeout=TIMEOUT; // idle keyboard timelimit in seconds d.cfg.timelimit=TIMELIMIT; // Total session minutes per calendar day d.cfg.grouplimit=GROUPLIMIT; // maximum group size before autobanning d.cfg.dailyanti=FUELPERDAY; // grams daily antimatter issue d.cfg.minholds=MINHOLDS; // starting holds in new ship d.cfg.maxholds=MAXHOLDS; // maximum holds ship can take d.cfg.prodmult=PRODMULT; // 10% - 500%. 75% == 3/4th, 400% == 4x d.cfg.teamsize=MAXALLY-1; // limit of team members // default access limits; for exemption to grouplimit(13), access must be // >= 20. Don't confuse this with the ability to _change_ grouplimit. for (x=0;x<24;x++) // 0==use radio, 10=update users' access d.cfg.access[x]=100; // 1-9 to change above 9 parms d.cfg.access[0]=10; // use radio d.cfg.access[10]=50; // change users valid. level (if < yours) d.cfg.access[11]=5; // change pw d.cfg.access[12]=1; // quit d.cfg.access[17]=1; // toggle display of beacon reports resetmap(); // write out new data file to disk here // if ((datafp=fopen(filename,"w+"))==NULL) return; for (user=0;user < (MAXUSERS+1);user++) fwrite(&d.user[user],sizeof(struct userlayout),1,datafp); for (sector=0;sector < (GAMESIZE+1);sector++) fwrite(&d.sector[sector],sizeof(struct maplayout),1,datafp); for (hist=0;hist < MAXHIST;hist++) fwrite(&d.histrec[hist],sizeof(struct histlayout),1,datafp); fwrite(&d.cfg,sizeof(struct cfglayout),1,datafp); for (user=0;user < (MAXUSERS+1);user++) // same # of teams as users fwrite(&d.team[user],sizeof(struct teamlayout),1,datafp); fclose(datafp); return; } void dirty(int sector) // call this to set sector and block as dirty { dirtysector[sector]=1; dirtyblock[sector/DBLOCK]=1; return; } /////////////////////////////////////////////////////////////////////// /// This sets up signal handlers -- add backup flush to hangup() /// /////////////////////////////////////////////////////////////////////// // When the backup thread is running, a shutdown signal should be // sent to force write out the state of the data before exiting void setsignals() { fp1=sig1rtn; // used by gameloop to kill idle & logged off threads signal(1,fp1); signal(2,SIG_IGN); signal(3,SIG_IGN); signal(4,SIG_IGN); signal(5,SIG_IGN); signal(7,SIG_IGN); signal(8,SIG_IGN); signal(10,SIG_IGN); signal(11,SIG_IGN); signal(12,SIG_IGN); signal(13,SIG_IGN); signal(14,SIG_IGN); fp15=sig15rtn; // used by admin to shutdown entire game gracefully signal(15,fp15); signal(16,SIG_IGN); signal(17,SIG_IGN); signal(18,SIG_IGN); signal(19,SIG_IGN); signal(20,SIG_IGN); signal(21,SIG_IGN); signal(22,SIG_IGN); return; } /////////////////////////////////////////////////////////////////////// /// various wrapper and miscellaneous functions (unp3, mostly) ///// /////////////////////////////////////////////////////////////////////// void Listen(int fd, int backlog) { char *ptr; if ( (ptr = getenv("LISTENQ")) != NULL) backlog = atoi(ptr); if (listen(fd, backlog) < 0) err_sys("listen error"); } int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) { int listenfd, n; const int on = 1; struct addrinfo hints, *res, *ressave; bzero(&hints, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) { printf("tcp_listen error for %s, %s: %s", host, serv, gai_strerror(n)); exit(1); } ressave = res; do { listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (listenfd < 0) continue; /* error, try next one */ if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))<0) err_sys("setsockopt error"); if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0) break; /* success */ close(listenfd); /* bind error, close and try next one */ } while ( (res = res->ai_next) != NULL); if (res == NULL) /* errno from final socket() or bind() */ err_sys("tcp_listen error for %s, %s", host, serv); Listen(listenfd, LISTENQ); if (addrlenp) *addrlenp = res->ai_addrlen;/* return size of protocol address */ freeaddrinfo(ressave); return(listenfd); } int daemon_proc; /* set nonzero by daemon_init() */ void err_sys(const char *fmt, ...) { va_list ap; va_start(ap, fmt); err_userin(1, LOG_ERR, fmt, ap); va_end(ap); exit(1); } static void err_userin(int errnoflag, int level, const char *fmt, va_list ap) { int errno_save, n; char buf[DEFLINE]; errno_save = errno; /* value caller might want printed */ vsnprintf(buf, MAXLINE, fmt, ap); n = strlen(buf); if (errnoflag) snprintf(buf + n, MAXLINE - n, ": %s", strerror(errno_save)); strcat(buf, "\n"); if (daemon_proc) { syslog(level, buf); } else { fflush(stdout); /* in case stdout and stderr are the same */ fputs(buf, stderr); fflush(stderr); } return; } ssize_t // Write "n" bytes writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; // and call write() again else return(-1); // error } nleft -= nwritten; ptr += nwritten; } return(n); } //////// end of UNP3 (Unix Network Programming) functions //////////// void sig1rtn() { // used by gameloop to kill idle or logged off threads pthread_exit(NULL); } void sig15rtn() { // used by admin to shutdown entire game gracefully shutdownnow=1; } ///////////////////////////////////////////////////////////// /// Backup thread: Scans datalayout structs and backs up /// /// all records with dirty == 1, then sets dirty to 0. /// /// It continues making passes, with shorter delay /// /// intervals until 10 passes -or- no more dirty blocks. /// ///////////////////////////////////////////////////////////// static void * backupdata(void *arg) { int shutdownflag=0; int sector, user, hist, cfgrec, userrec, usersize, maprec, mapsize, histrec, histsize, teamrec, teamsize, x, y, start, stop, delay, loopcount; pthread_detach(pthread_self()); userrec=sizeof(struct userlayout); teamrec=sizeof(struct teamlayout); histrec=sizeof(struct histlayout); maprec=sizeof(struct maplayout); cfgrec=sizeof(struct cfglayout); usersize=userrec * (MAXUSERS+1); teamsize=teamrec * (MAXUSERS+1); mapsize=maprec * (GAMESIZE+1); histsize=histrec * MAXHIST; for ( ; ; ) // continuously do backups { if (shutdownflag) // backup completed, shutdown in progress { while(shutdownnow<40) usleep(IDLESLEEP);// wait for gameloop... shutdownnow=55; pthread_exit(NULL); } if (bu_dirtybl) bu_sleep='U'; // last backup Unsuccessful after 10 passes else bu_sleep='S'; // tell showdata() backup is Sleeping peacefully // idlesystem==1: system's been idle 60 seconds // idlesystem==1200:system's been idle 3 minutes (60s + 1200*.1) loopcount = idlesystem; loopcount = loopcount < 300 ? 300 : loopcount; // limit min to 300 loopcount = loopcount > 48000 ? 48000 : loopcount;// limit max to 48000 if (bu_dirtybl) loopcount = 300;// last backup incomplete, shorten... for (x=0;x 300) if (idlesystem==0) break; // system no longer idle usleep(20000); // sleep 6-960 secs (20000*(300 to 48000)) } if (shutdownnow) delay=0; // system going down... turn off delay else delay=9999; // initial periodic usleep when writing if (shutdownnow>20) shutdownflag=1; // shutdown after this backup bu_sleep='A'; // let showdata() know backup is Active bu_pass=0; // starting a new backup, initialize pass counter bu_bkup++; // enumerate this backup for showdata() do { bu_pass++; // how many passes does it take for consistent backup? if ((datafp=fopen(filename,"r+"))==NULL) exit(1); bu_dirtybl=0; // count dirty blocks found each pass, loop til 0 for (user=0;user < (MAXUSERS+1);user++) { if (dirtyuser[user]==1) { dirtyuser[user]=0; bu_ucount++; // count dirty user blocks written bu_dirtybl++; fseek(datafp,user*userrec,SEEK_SET); fwrite(&d.user[user],userrec,1,datafp); } if (delay) { if (shutdownnow) delay=0; // system going down. no delay if (user%333==166) usleep(delay); } } for (y=0;y<(1+(GAMESIZE+1)/DBLOCK);y++) { if (dirtyblock[y]) // look at big blocks first, only if dirty { // do we scan the individual records in it dirtyblock[y]=0; start=y*DBLOCK; // first record in this block stop=start+DBLOCK; // last record in this block if (stop>(GAMESIZE+1)) // last block is probably shorter stop=(GAMESIZE+1); if (start > stop) break; for (sector=start;sector < stop;sector++) { if (dirtysector[sector]==1) { dirtysector[sector]=0; bu_mcount++; // count dirty map blocks written bu_dirtybl++; fseek(datafp,(sector*maprec)+usersize,SEEK_SET); fwrite(&d.sector[sector],maprec,1,datafp); } } } if (delay) { if (shutdownnow) delay=0; // system going down. no delay if (y%10==3) usleep(delay); } } for (y=0;y<(1+MAXHIST/DHBLOCK);y++) { if (dirtyhistblock[y]) // if big blocks are dirty... { // we scan the individual records dirtyhistblock[y]=0; start=y*DHBLOCK; // first record in this block stop=start+DHBLOCK; // last record in this block if (stop>MAXHIST) // last block is probably shorter stop=MAXHIST; if (start > stop) break; for (hist=start;hist < stop;hist++) { if (dirtyhistory[hist]==1) { dirtyhistory[hist]=0; bu_hcount++; // count dirty hist blocks written bu_dirtybl++; fseek(datafp,(hist*histrec)+mapsize+usersize, SEEK_SET); fwrite(&d.histrec[hist],histrec,1,datafp); } } } if (delay) { if (shutdownnow) delay=0; // system going down. no delay if (y%10==3) usleep(delay); } } if (dirtycfg) { dirtycfg=0; bu_ccount++; // count dirty cfg records written bu_dirtybl++; fseek(datafp,histsize+mapsize+usersize, SEEK_SET); fwrite(&d.cfg,cfgrec,1,datafp); } for (user=0;user < (MAXUSERS+1);user++) // same # of teams as users { if (dirtyteam[user]==1) { dirtyteam[user]=0; bu_tcount++; // count dirty team blocks written bu_dirtybl++; fseek(datafp,usersize+mapsize+histsize+cfgrec +user*teamrec,SEEK_SET); fwrite(&d.team[user],teamrec,1,datafp); } if (delay) { if (shutdownnow) delay=0; // system going down. no delay if (user%333==166) usleep(delay); } } fclose(datafp); delay/=10; } while (bu_dirtybl && (bu_pass < 10)); // 5 successively faster passes // then 5 full speed passes. if (shutdownnow==0) sync(); // call disk sync } } char *galname[GALAXIES]; char *galnamei[GALAXIES] = { "Cosmos", "Vega", "Sirius", "Betelgeuse", "Sands of Time", "Barnard's Star", "Far Star", "Albirion", "Shady Rest", "Wetumpka", "Lyra", "Capella", "M31", "Polaris", "Alkaid", "Nair al Zaurak", "Regulus", "Spica", "Kochab", "Victim", "Dheneb Kaitos", "Deneb", "Gamma Centauri", "Acamar", "Bellatrix", "Althena", "Castor", "Alpheratz", "Nunki", "Pollux", "Theta Centauri", "Mu Velorum", "Alnilam", "Altair", "M42", "Cor Caroli", "Orion", "Maia", "Taygete", "Merope", "Munson Slough", "Iota Centauri", "Lunar", "Asterope", "Alcyone", "New Venus", "Newton's Base", "Cassiopeia", "Electra", "Celaeno", "Hyades", "Hercules", "Pegasus", "Paradox Point", "Murzim", "Challenger", "Markab", "Leo Minor", "LaGrange 5", "Cygnus", "Crab Nebula", "Horsehead Nebula", "Denebola", "Menkar", "Lacerta", "Algol", "Gienah", "Kiffa Borealis", "Mira", "Scutum", "Andromeda", "Einstein Station", "Kiffa Australis", "Crater", "Equuleus", "Scorpius", "Wezen", "Adhara", "Camelopardalis", "Pictor", "Eridanus", "Fornax", "Sculptor", "Vulpecula", "Sagitta", "Corona Borealis", "Alphecca", "Alioth", "Corvus", "Sextans", "The Master", "Mirfak", "Canopus", "Perseus", "Almach", "Triangulum", "Scheat", "Aquarius", "Sadr", "Microscopium", "Grus", "Telescopium", "Sargas Alpha", "Sabik", "Libra", "Alderamin", "Lupus", "Indus", "Aquila", "New Tokyo", "Agena", "Delta Velorum", "Achernar", "Tucana", "Pavo", "Askone", "Ruins of Empire", "Thunder Gulch", "Mountain", "Horvendile", "Arrakis", "Centaurus A", "Sputnik", "Convergence", "Sphinx", "Kalgan", "New Arctic", "No Paradise", "Omega", "Entropy", "Empire", "Pioneer", "Koschei", "Anacreon", "Voyager", "Terminus", "Gaia", "Down", "Diego Garcia", "Explorer", "Galileo", "Wunderland", "Jinx", "Quinn's Mill", "Relic of Empire", "Plateau", "Arcturus", "Gemini", "The Rock", "Viking", "Bajaja", "Columbia", "Cepheid", "Still Life", "Atari", "Slidewalk Villa", "Tequila", "Home Sweet Home", "Southern Cross", "Prime Radiant", "The Tide", "Tazenda", "Todos Santos", "Deep Space", "Absalom", "Discovery", "Asgard", "Woden's Base", "Kzin", "Frontier", "Coalsack", "Beanstalk", "America", "Land's End", "Oort", "Esteban", "We Made It", "Apollo", "Luna City", "Mariner", "Zenith", "Java", "Bali", "Sumatra", "Borneo", "Seychelles", "Celebes", "Tonga", "Easter", "Bikini", "Pitcairn", "Bora Bora", "Falkland", "Tasmania", "Azores", "Barbados", "Tobago", "Bermuda", "Samoa", "Maldive", "Winery", }; void // won't compile under osx with long list of names. To expand loadgalnames() // from [GALAXIES], add names, increase galname array(above) { int x, s; char *hold; for (x=0;x