From db9bd67be925f4e33821fbaf50be2f7ae8885d12 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 10 Feb 2016 15:50:38 +0100 Subject: [PATCH 01/45] protocol bump no longer compatible with 1.2.0.2 also: nade effect quantisation fixed --- source/src/clients2c.cpp | 6 +++--- source/src/protocol.h | 2 +- source/src/tools.cpp | 2 +- source/src/weapon.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 821abc54..fcdb6e21 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -726,9 +726,9 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) case SV_THROWNADE: { - vec from, to; + vec from, vel; loopk(3) from[k] = getint(p)/DMF; - loopk(3) to[k] = getint(p)/DMF; + loopk(3) vel[k] = getint(p)/DNF; int nademillis = getint(p); if(!d) break; d->lastaction = lastmillis; @@ -736,7 +736,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) d->lastattackweapon = d->weapons[GUN_GRENADE]; if(d->weapons[GUN_GRENADE]) { - d->weapons[GUN_GRENADE]->attackfx(from, to, nademillis); + d->weapons[GUN_GRENADE]->attackfx(from, vel, nademillis); d->weapons[GUN_GRENADE]->reloading = 0; } if(d!=player1) d->pstatshots[GUN_GRENADE]++; //NEW diff --git a/source/src/protocol.h b/source/src/protocol.h index fd30d6a7..ab07823b 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -5,7 +5,7 @@ #define CUBE_SERVINFO_PORT_LAN 28762 #define CUBE_SERVINFO_PORT(serverport) (serverport+1) #define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1) -#define PROTOCOL_VERSION 1201 // bump when protocol changes (use negative numbers for mods!) +#define PROTOCOL_VERSION 1203 // bump when protocol changes (use negative numbers for mods!) #define DEMO_VERSION 2 // bump when demo format changes #define DEMO_MAGIC "ASSAULTCUBE_DEMO" #define DEMO_MINTIME 10000 // don't keep demo recordings with less than 10 seconds diff --git a/source/src/tools.cpp b/source/src/tools.cpp index a8732def..928a4afc 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -15,7 +15,7 @@ char *strtok_r(char *s, const char *delim, char **b) } #endif -string _timestringbuffer; +string _timestringbuffer = ""; const char *timestring(time_t t, bool local, const char *fmt, char *buf) { diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index 633d675c..f65bace0 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -1175,7 +1175,7 @@ void grenadeent::_throw(const vec &from, const vec &vel) inwater = waterlevel > o.z; if(local) { - addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DMF), int(vel.y*DMF), int(vel.z*DMF), lastmillis-millis); + addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF), lastmillis-millis); audiomgr.playsound(S_GRENADETHROW, SP_HIGH); } else audiomgr.playsound(S_GRENADETHROW, owner); From 1ca4ce54ebb8ff183cd25645d17dd8f00cfbd96c Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 25 Aug 2016 19:12:56 +0200 Subject: [PATCH 02/45] some server changes tbc --- source/src/clientgame.cpp | 19 +- source/src/clients2c.cpp | 64 ---- source/src/cube.h | 1 - source/src/protocol.h | 1 + source/src/protos.h | 5 +- source/src/server.cpp | 612 ++++++++++++++++++++----------------- source/src/server.h | 20 +- source/src/serveractions.h | 43 ++- source/src/serverchecks.h | 108 ++----- source/src/serverevents.h | 18 +- source/src/serverfiles.h | 303 ++++-------------- source/src/serverms.cpp | 11 +- source/src/tools.cpp | 187 +----------- source/src/tools.h | 1 - source/src/worldio.cpp | 9 +- 15 files changed, 472 insertions(+), 930 deletions(-) diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 5882c963..010172ae 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -809,15 +809,6 @@ void respawnself() } } -extern int checkarea(int maplayout_factor, char *maplayout); -extern int MA; -extern float Mh; - -bool bad_map() // this function makes a pair with good_map from clients2c -{ - return (gamemode != GMODE_COOPEDIT && ( Mh >= MAXMHEIGHT || MA >= MAXMAREA )); -} - inline const char * spawn_message() { if (spawnpermission == SP_WRONGMAP) @@ -839,11 +830,7 @@ int waiting_permission = 0; bool tryrespawn() { - if(m_mp(gamemode) && multiplayer(NULL) && bad_map()) - { - hudoutf("This map is not supported in multiplayer. Read the docs about map quality/dimensions."); - } - else if(spawnpermission > SP_OK_NUM) + if(spawnpermission > SP_OK_NUM) { hudeditf(HUDMSG_TIMER, "\f%s", spawn_message()); } @@ -1176,7 +1163,7 @@ void resetmap(bool mrproper) } int suicided = -1; -extern bool good_map(); + extern bool item_fail; extern int MA, F2F, Ma, Hhits; extern float Mh; @@ -1215,8 +1202,6 @@ void startmap(const char *name, bool reset, bool norespawn) // called just aft player1->followplayercn = -1; if(m_valid(gamemode) && !m_mp(gamemode)) respawnself(); else findplayerstart(player1); - if(good_map()==MAP_IS_BAD) conoutf("You cannot play in this map due to quality requisites. Please, report this incident."); - if (mapstats_hud) showmapstats(); if(!reset) return; diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index fcdb6e21..565bce87 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -236,7 +236,6 @@ void parsepositions(ucharbuf &p) } } -extern int checkarea(int maplayout_factor, char *maplayout); char *mlayout = NULL; int Mv = 0, Ma = 0, F2F = 1000 * MINFF; // moved up:, MA = 0; float Mh = 0; @@ -245,68 +244,6 @@ extern bool noflags; bool item_fail = false; int map_quality = MAP_IS_EDITABLE; -/// TODO: many functions and variables are redundant between client and server... someone should redo the entire server code and unify client and server. -bool good_map() // call this function only at startmap -{ - return true; - if (mlayout) MA = checkarea(sfactor, mlayout); - - F2F = 1000 * MINFF; - if(m_flags) - { -// flaginfo &f0 = flaginfos[0]; - // flaginfo &f1 = flaginfos[1]; -#define DIST(x) (f0.pos.x - f1.pos.x) - F2F = 1000;//(!numflagspawn[0] || !numflagspawn[1]) ? 1000 * MINFF : DIST(x)*DIST(x)+DIST(y)*DIST(y); -#undef DIST - } - - item_fail = false; - loopv(ents) - { - entity &e1 = ents[i]; - if (e1.type < I_CLIPS || e1.type > I_AKIMBO) continue; - float density = 0, hdensity = 0; - loopvj(ents) - { - entity &e2 = ents[j]; - if (e2.type < I_CLIPS || e2.type > I_AKIMBO || i == j) continue; - // only I_CLIPS, I_AMMO, I_GRENADE, I_HEALTH, I_HELMET, I_ARMOUR, I_AKIMBO -#define DIST(x) (e1.x - e2.x) -#define DIST_ATT ((e1.z + float(e1.attr1) / entscale[e1.type][0]) - (e2.z + float(e2.attr1) / entscale[e2.type][0])) - float r2 = DIST(x)*DIST(x) + DIST(y)*DIST(y) + DIST_ATT*DIST_ATT; -#undef DIST_ATT -#undef DIST - if ( r2 == 0.0f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[e1.type], entnames[e2.type],e1.x,e1.y); item_fail = true; break; } - r2 = 1/r2; - if (r2 < 0.0025f) continue; - if (e1.type != e2.type) - { - hdensity += r2; - continue; - } - density += r2; - } - if ( hdensity > 0.5f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],hdensity,e1.x,e1.y); item_fail = true; break; } - switch(e1.type) - { -#define LOGTHISSWITCH(X) if( density > X ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],density,e1.x,e1.y); item_fail = true; break; } - case I_CLIPS: - case I_HEALTH: LOGTHISSWITCH(0.24f); break; - case I_AMMO: LOGTHISSWITCH(0.04f); break; - case I_HELMET: LOGTHISSWITCH(0.02f); break; - case I_ARMOUR: - case I_GRENADE: - case I_AKIMBO: LOGTHISSWITCH(0.005f); break; - default: break; -#undef LOGTHISSWITCH - } - } - - map_quality = (!item_fail && F2F > MINFF && MA < MAXMAREA && Mh < MAXMHEIGHT && Hhits < MAXHHITS) ? MAP_IS_GOOD : MAP_IS_BAD; - if ( (!connected || gamemode == GMODE_COOPEDIT) && map_quality == MAP_IS_BAD ) map_quality = MAP_IS_EDITABLE; - return map_quality > 0; -} VARP(hudextras, 0, 0, 3); @@ -1532,7 +1469,6 @@ void receivefile(uchar *data, int len) int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); - /* int revision = */ getint(p); int size = mapsize + cfgsizegz; if(MAXMAPSENDSIZE < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE) { // sam's suggestion conoutf("map %s is too large to receive", text); diff --git a/source/src/cube.h b/source/src/cube.h index 8f59d162..14988da6 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -41,7 +41,6 @@ extern vector ents; // map entities extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world extern int lastmillis, totalmillis, skipmillis; // last time extern int curtime; // current frame time -extern int interm; extern int gamemode, nextmode; extern int gamespeed; extern int xtraverts; diff --git a/source/src/protocol.h b/source/src/protocol.h index ab07823b..725fb4c4 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -143,6 +143,7 @@ enum GMMASK_BOTTEAMONESHOTONKILL = 1 << GMODE_BOTTEAMONESHOTONKILL, GMMASK__ALL = (1 << GMODE_NUM) - 1, GMMASK__FLAGS = GMMASK_CTF | GMMASK_HUNTTHEFLAG | GMMASK_KEEPTHEFLAG | GMMASK_TEAMKEEPTHEFLAG, + GMMASK__FLAGENTS = GMMASK_CTF | GMMASK_KEEPTHEFLAG | GMMASK_TEAMKEEPTHEFLAG, GMMASK__BOT = GMMASK_BOTDEATHMATCH | GMMASK_BOTLSS | GMMASK_BOTONESHOTONEKILL | GMMASK_BOTPISTOLFRENZY | GMMASK_BOTTEAMDEATHMATCH | GMMASK_BOTTEAMONESHOTONKILL | GMMASK_BOTTEAMSURVIVOR, GMMASK__MP = GMMASK_TEAMDEATHMATCH | GMMASK_COOPEDIT | GMMASK_DEATHMATCH | GMMASK_SURVIVOR | GMMASK_TEAMSURVIVOR | GMMASK_CTF | GMMASK_PISTOLFRENZY | GMMASK_LASTSWISSSTANDING | GMMASK_ONESHOTONEKILL | GMMASK_TEAMONESHOTONEKILL | GMMASK_HUNTTHEFLAG | GMMASK_TEAMKEEPTHEFLAG | GMMASK_KEEPTHEFLAG | GMMASK_TEAMPF | GMMASK_TEAMLSS, diff --git a/source/src/protos.h b/source/src/protos.h index f5336758..a90ac798 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -843,7 +843,7 @@ extern const char *particletypenames[MAXPARTYPES + 1]; // worldio enum { LWW_ENTATTROVERFLOW = 0x1, LWW_DECODEERR = 0x10, LWW_WORLDERROR = 0x100, LWW_MISSINGMEDIA = 0x1000, LWW_CONFIGERR = 0x10000, LWW_MODELERR = 0x100000, LWW_SCRIPTERR = 0x1000000 }; -extern const char *setnames(const char *name); +extern const char **setnames(const char *name); extern void save_world(char *mname, bool skipoptimise = false, bool addcomfort = false); extern int _ignoreillegalpaths; extern int load_world(char *mname); @@ -1063,7 +1063,7 @@ extern void trimtrailingwhitespace(char *s); extern string mastername; extern int masterport; extern ENetSocket connectmaster(); -extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version); +extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm); extern int msgsizelookup(int msg); extern const char *genpwdhash(const char *name, const char *pwd, int salt); extern void servermsinit(const char *master, const char *ip, int serverport, bool listen); @@ -1076,6 +1076,7 @@ extern char *votestring(int type, char *arg1, char *arg2, char *arg3); extern int wizardmain(int argc, char **argv); // some tools +extern header *peekmapheader(uchar *data, int len); extern servsqr *createservworld(const sqr *s, int _cubicsize); extern int calcmapdims(mapdim_s &md, const servsqr *s, int _ssize); extern int calcmapareastats(mapareastats_s &ms, servsqr *s, int _ssize, const mapdim_s &md); diff --git a/source/src/server.cpp b/source/src/server.cpp index 841b1f5e..59128ab5 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -13,9 +13,65 @@ VARP(serverdebug, 0, 0, 1); #include "server.h" #include "servercontroller.h" #include "serverfiles.h" -// 2011feb05:ft: quitproc + #include "signal.h" +struct servergame +{ + int mastermode; + bool autoteam; + int matchteamsize; + bool forceintermission; + string servdesc_current; + ENetAddress servdesc_caller; + bool custom_servdesc; + + // current game + string smapname, nextmapname; + int smode, nextgamemode; + int interm; + int minremain, gamemillis, gamelimit, nextsendscore; + int arenaround, arenaroundstartmillis; + int lastfillup; + vector sents; + sflaginfo sflaginfos[2]; + + servermap *curmap; + char *layout; // NULL: edit mode or singleplayer without properly loaded map + uchar *coop_mapdata; + int coop_cgzlen, coop_cfglen, coop_cfglengz; + + void init() + { + mastermode = MM_OPEN; + autoteam = true; + matchteamsize = 0; + forceintermission = false; + custom_servdesc = false; + smode = 0; + interm = 0; + minremain = 0; + gamemillis = 0; + gamelimit = 0; + nextsendscore = 0; + arenaround = 0; + arenaroundstartmillis = 0; + lastfillup = 0; + sents.setsize(0); + loopi(2) sflaginfos[i].actor_cn = -1; + curmap = NULL; + layout = NULL; + coop_mapdata = NULL; + coop_cgzlen = coop_cfglen = coop_cfglengz = 0; + } + + void free() + { + DELETEA(layout); + DELETEA(coop_mapdata); + } +} cursgame, *sg = &cursgame; + // config vector servermaps; // all available maps kept in memory @@ -32,7 +88,7 @@ serverinfofile infofiles; bool isdedicated = false; ENetHost *serverhost = NULL; -int laststatus = 0, servmillis = 0, lastfillup = 0; +int laststatus = 0, servmillis = 0; vector clients; vector worldstates; @@ -40,29 +96,8 @@ vector savedscores; vector bans; vector demofiles; -int mastermode = MM_OPEN; -static bool autoteam = true; -int matchteamsize = 0; - long int incoming_size = 0; -static bool forceintermission = false; - -string servdesc_current; -ENetAddress servdesc_caller; -bool custom_servdesc = false; - -// current game -string smapname, nextmapname; -int smode = 0, nextgamemode; -int interm = 0; -static int minremain = 0, gamemillis = 0, gamelimit = 0, /*lmsitemtype = 0,*/ nextsendscore = 0; -mapstats smapstats; -vector sents; -char *maplayout = NULL, *testlayout = NULL; -int maplayout_factor, testlayout_factor, maplayoutssize; -servermapbuffer mapbuffer; - // cmod char *global_name; int totalclients = 0; @@ -153,6 +188,11 @@ void poll_serverthreads() // called once per mainloop-timeslice } } +servermap *getservermap(const char *mapname) // check, if the server knows a map (by filename) +{ + loopv(servermaps) if(!strcmp(servermaps[i]->fname, mapname)) return servermaps[i]; + return NULL; +} bool valid_client(int cn) @@ -318,7 +358,7 @@ int *numteamclients(int exclude = -1) int sendservermode(bool send = true) { - int sm = (autoteam ? AT_ENABLED : AT_DISABLED) | ((mastermode & MM_MASK) << 2) | (matchteamsize << 4); + int sm = (sg->autoteam ? AT_ENABLED : AT_DISABLED) | ((sg->mastermode & MM_MASK) << 2) | (sg->matchteamsize << 4); if(send) sendf(-1, 1, "ri2", SV_SERVERMODE, sm); return sm; } @@ -326,19 +366,19 @@ int sendservermode(bool send = true) void changematchteamsize(int newteamsize) { if(newteamsize < 0) return; - if(matchteamsize != newteamsize) + if(sg->matchteamsize != newteamsize) { - matchteamsize = newteamsize; + sg->matchteamsize = newteamsize; sendservermode(); } - if(mastermode == MM_MATCH && matchteamsize && m_teammode) + if(sg->mastermode == MM_MATCH && sg->matchteamsize && m_teammode) { int size[2] = { 0 }; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap) { if(team_isactive(clients[i]->team)) { - if(++size[clients[i]->team] > matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE); + if(++size[clients[i]->team] > sg->matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE); } } } @@ -346,18 +386,18 @@ void changematchteamsize(int newteamsize) void changemastermode(int newmode) { - if(mastermode != newmode) + if(sg->mastermode != newmode) { - mastermode = newmode; + sg->mastermode = newmode; senddisconnectedscores(-1); - if(mastermode != MM_MATCH) + if(sg->mastermode != MM_MATCH) { loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) { if(clients[i]->team == TEAM_CLA_SPECT || clients[i]->team == TEAM_RVSF_SPECT) updateclientteam(i, TEAM_SPECT, FTR_SILENTFORCE); } } - else if(matchteamsize) changematchteamsize(matchteamsize); + else if(sg->matchteamsize) changematchteamsize(sg->matchteamsize); sendservermode(); } } @@ -375,7 +415,7 @@ int findcnbyaddress(ENetAddress *address) savedscore *findscore(client &c, bool insert) { if(c.type!=ST_TCPIP) return NULL; - enet_uint32 mask = ENET_HOST_TO_NET_32(mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed + enet_uint32 mask = ENET_HOST_TO_NET_32(sg->mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed if(!insert) { loopv(clients) @@ -450,12 +490,12 @@ void sendf(int cn, int chan, const char *format, ...) void sendextras() { - if ( gamemillis < nextsendscore ) return; + if (sg->gamemillis < sg->nextsendscore ) return; int count = 0, list[MAXCLIENTS]; loopv(clients) { client &c = *clients[i]; - if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < gamemillis) ) continue; + if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < sg->gamemillis) ) continue; if ( c.md.combosend ) { sendf(c.clientnum, 1, "ri2", SV_HUDEXTRAS, min(c.md.combo,c.md.combofrags)-1 + HE_COMBO); @@ -467,7 +507,7 @@ void sendextras() count++; } } - nextsendscore = gamemillis + 160; // about 4 cicles + sg->nextsendscore = sg->gamemillis + 160; // about 4 cicles if ( !count ) return; packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); @@ -494,13 +534,13 @@ void sendspawn(client *c) if(team_isspect(c->team)) return; clientstate &gs = c->state; gs.respawn(); - gs.spawnstate(smode); + gs.spawnstate(sg->smode); gs.lifesequence++; sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence, gs.health, gs.armour, gs.primary, gs.gunselect, m_arena ? c->spawnindex : -1, NUMGUNS, gs.ammo, NUMGUNS, gs.mag); - gs.lastspawn = gamemillis; + gs.lastspawn = sg->gamemillis; } // demo @@ -511,7 +551,7 @@ int nextplayback = 0; void writedemo(int chan, void *data, int len) { if(!demorecord) return; - int stamp[3] = { gamemillis, chan, len }; + int stamp[3] = { sg->gamemillis, chan, len }; lilswap(stamp, 3); demorecord->write(stamp, sizeof(stamp)); demorecord->write(data, len); @@ -650,7 +690,7 @@ void enddemorecord() if(!demotmp) return; - if(gamemillis < DEMO_MINTIME) + if(sg->gamemillis < DEMO_MINTIME) { delete demotmp; demotmp = NULL; @@ -665,25 +705,20 @@ void enddemorecord() delete[] demofiles[0].data; demofiles.remove(0); } - int mr = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; + int mr = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1)/60000; demofile &d = demofiles.add(); - //2010oct10:ft: suggests : formatstring(d.info)("%s, %s, %.2f%s", modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); // the datetime bit is pretty useless in the servmesg, no?! - formatstring(d.info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); + formatstring(d.info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), sg->smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); } defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); sendservmsg(msg); logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); - // 2011feb05:ft: previously these two static formatstrings were used .. - //formatstring(d.file)("%s_%s_%s", timestring(), behindpath(smapname), modestr(gamemode, true)); // 20100522_10.08.48_ac_mines_DM.dmo - //formatstring(d.file)("%s_%s_%s", modestr(gamemode, true), behindpath(smapname), timestring( true, "%Y.%m.%d_%H%M")); // DM_ac_mines.2010.05.22_1008.dmo - // .. now we use client-side parseable fileattribs - int mPLAY = gamemillis >= gamelimit ? gamelimit/1000 : gamemillis/1000; - int mDROP = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis)/1000; + int mPLAY = sg->gamemillis >= sg->gamelimit ? sg->gamelimit/1000 : sg->gamemillis/1000; + int mDROP = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis)/1000; int iTIME = time(NULL); const char *mTIME = numtime(); - const char *sMAPN = behindpath(smapname); + const char *sMAPN = behindpath(sg->smapname); string iMAPN; copystring(iMAPN, sMAPN); formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN); @@ -740,9 +775,9 @@ void setupdemorecord() lilswap(&hdr.version, 1); lilswap(&hdr.protocol, 1); memset(hdr.desc, 0, DHDR_DESCCHARS); - defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(smapname), asctimestr(), servdesc_current); + defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); if(strlen(desc) > DHDR_DESCCHARS) - formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctimestr(), servdesc_current); + formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); desc[DHDR_DESCCHARS - 1] = '\0'; strcpy(hdr.desc, desc); memset(hdr.plist, 0, DHDR_PLISTCHARS); @@ -793,7 +828,7 @@ void senddemo(int cn, int num) { client *cl = cn>=0 ? clients[cn] : NULL; bool is_admin = (cl && cl->role == CR_ADMIN); - if(scl.demo_interm && (!interm || totalclients > 2) && !is_admin) + if(scl.demo_interm && (!sg->interm || totalclients > 2) && !is_admin) { sendservmsg("\f3sorry, but this server only sends demos at intermission.\n wait for the end of this game, please", cn); return; @@ -819,7 +854,7 @@ void senddemo(int cn, int num) ci.ip = cl->peer->address.host; ci.clientnum = cl->clientnum; - if (interm) sending_demo = true; + if (sg->interm) sending_demo = true; packetbuf p(MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE); putint(p, SV_SENDDEMO); sendstring(d.file, p); @@ -850,7 +885,7 @@ void setupdemoplayback() demoheader hdr; string msg; msg[0] = '\0'; - defformatstring(file)("demos/%s.dmo", smapname); + defformatstring(file)("demos/%s.dmo", sg->smapname); path(file); demoplayback = opengzfile(file, "rb"); if(!demoplayback) formatstring(msg)("could not read demo \"%s\"", file); @@ -874,7 +909,7 @@ void setupdemoplayback() formatstring(msg)("playing demo \"%s\"", file); sendservmsg(msg); - sendf(-1, 1, "risi", SV_DEMOPLAYBACK, smapname, -1); + sendf(-1, 1, "risi", SV_DEMOPLAYBACK, sg->smapname, -1); watchingdemo = true; if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) @@ -888,7 +923,7 @@ void setupdemoplayback() void readdemo() { if(!demoplayback) return; - while(gamemillis>=nextplayback) + while(sg->gamemillis >= nextplayback) { int chan, len; if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) || @@ -917,21 +952,9 @@ void readdemo() } } -struct sflaginfo -{ - int state; - int actor_cn; - float pos[3]; - int lastupdate; - int stolentime; - short x, y; // flag entity location - - sflaginfo() { actor_cn = -1; } -} sflaginfos[2]; - void putflaginfo(packetbuf &p, int flag) { - sflaginfo &f = sflaginfos[flag]; + sflaginfo &f = sg->sflaginfos[flag]; putint(p, SV_FLAGINFO); putint(p, flag); putint(p, f.state); @@ -949,7 +972,7 @@ void putflaginfo(packetbuf &p, int flag) inline void send_item_list(packetbuf &p) { putint(p, SV_ITEMLIST); - loopv(sents) if(sents[i].spawned) putint(p, i); + loopv(sg->sents) if(sg->sents[i].spawned) putint(p, i); putint(p, -1); if(m_flags) loopi(2) putflaginfo(p, i); } @@ -978,7 +1001,7 @@ bool flagdistance(sflaginfo &f, int cn) { c.farpickups++; logline(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)", - c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sflaginfos + 1), dist, c.farpickups); + c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sg->sflaginfos + 1), dist, c.farpickups); if (pdist==2) return false; } return lagging ? false : true; // today I found a lag hacker :: Brahma, 19-oct-2010... lets test it a bit @@ -995,7 +1018,7 @@ void sendflaginfo(int flag = -1, int cn = -1) void flagmessage(int flag, int message, int actor, int cn = -1) { if(message == FM_KTFSCORE) - sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (gamemillis - sflaginfos[flag].stolentime) / 1000); + sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (sg->gamemillis - sg->sflaginfos[flag].stolentime) / 1000); else sendf(cn, 1, "riiii", SV_FLAGMSG, flag, message, actor); } @@ -1003,8 +1026,8 @@ void flagmessage(int flag, int message, int actor, int cn = -1) void flagaction(int flag, int action, int actor) { if(!valid_flag(flag)) return; - sflaginfo &f = sflaginfos[flag]; - sflaginfo &of = sflaginfos[team_opposite(flag)]; + sflaginfo &f = sg->sflaginfos[flag]; + sflaginfo &of = sg->sflaginfos[team_opposite(flag)]; bool deadactor = valid_client(actor) ? clients[actor]->state.state != CS_ALIVE || team_isspect(clients[actor]->team): true; int abort = 0; int score = 0; @@ -1070,7 +1093,7 @@ void flagaction(int flag, int action, int actor) if(deadactor || f.state != CTFF_INBASE || !flagdistance(f, actor)) { abort = 20; break; } f.state = CTFF_STOLEN; f.actor_cn = actor; - f.stolentime = gamemillis; + f.stolentime = sg->gamemillis; message = FM_PICKUP; break; case FA_SCORE: // f = carried by actor flag @@ -1133,7 +1156,7 @@ void flagaction(int flag, int action, int actor) logline(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); break; case FM_KTFSCORE: - logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (gamemillis - f.stolentime) / 1000, c.state.flagscore); + logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (sg->gamemillis - f.stolentime) / 1000, c.state.flagscore); break; case FM_SCOREFAIL: logline(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name); @@ -1157,7 +1180,7 @@ void flagaction(int flag, int action, int actor) } } - f.lastupdate = gamemillis; + f.lastupdate = sg->gamemillis; sendflaginfo(flag); if(message >= 0) flagmessage(flag, message, valid_client(actor) ? actor : -1); @@ -1167,7 +1190,7 @@ int clienthasflag(int cn) { if(m_flags && valid_client(cn)) { - loopi(2) { if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==cn) return i; } + loopi(2) { if(sg->sflaginfos[i].state==CTFF_STOLEN && sg->sflaginfos[i].actor_cn==cn) return i; } } return -1; } @@ -1177,9 +1200,9 @@ void ctfreset() int idleflag = m_ktf ? rnd(2) : -1; loopi(2) { - sflaginfos[i].actor_cn = -1; - sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE; - sflaginfos[i].lastupdate = -1; + sg->sflaginfos[i].actor_cn = -1; + sg->sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE; + sg->sflaginfos[i].lastupdate = -1; } } @@ -1197,7 +1220,7 @@ void resetflag(int cn) void htf_forceflag(int flag) { - sflaginfo &f = sflaginfos[flag]; + sflaginfo &f = sg->sflaginfos[flag]; int besthealth = 0; vector clientnumbers; loopv(clients) if(clients[i]->type!=ST_EMPTY) @@ -1228,18 +1251,17 @@ void htf_forceflag(int flag) flagmessage(flag, FM_PICKUP, cl->clientnum); logline(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name); } - f.lastupdate = gamemillis; + f.lastupdate = sg->gamemillis; } -int arenaround = 0, arenaroundstartmillis = 0; - int cmpscore(const int *a, const int *b) { return clients[*a]->at3_score - clients[*b]->at3_score; } vector tdistrib; vector sdistrib; void distributeteam(int team) { - int numsp = team == 100 ? smapstats.spawns[2] : smapstats.spawns[team]; + int numsp = 0; + if(sg->curmap) numsp = team == 100 ? sg->curmap->entstats.spawns[2] : sg->curmap->entstats.spawns[team & 1]; if(!numsp) numsp = 30; // no spawns: try to distribute anyway twoint ti; tdistrib.shrink(0); @@ -1285,24 +1307,16 @@ void distributespawns() } } -bool items_blocked = false; -bool free_items(int n) -{ - client *c = clients[n]; - int waitspawn = min(c->ping,200) + c->state.spawn; // flowtron to Brahma: can't this be removed now? (re: rev. 5270) - return !items_blocked && (waitspawn < gamemillis); -} - void checkitemspawns(int); void arenacheck() { - if(!m_arena || interm || gamemillisinterm || sg->gamemillis < sg->arenaround || !numactiveclients()) return; - if(arenaround) + if(sg->arenaround) { // start new arena round - arenaround = 0; - arenaroundstartmillis = gamemillis; + sg->arenaround = 0; + sg->arenaroundstartmillis = sg->gamemillis; distributespawns(); checkitemspawns(60*1000); // the server will respawn all items now loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) @@ -1310,7 +1324,6 @@ void arenacheck() if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) sendspawn(clients[i]); } - items_blocked = false; return; } @@ -1332,7 +1345,7 @@ void arenacheck() if(enemies && (!alive_enemies || player1->state == CS_DEAD)) { sendf(-1, 1, "ri2", SV_ARENAWIN, m_teammode ? (alive ? alive->clientnum : -1) : (alive && alive->type == ENT_BOT ? -2 : player1->state == CS_ALIVE ? player1->clientnum : -1)); - arenaround = gamemillis+5000; + sg->arenaround = sg->gamemillis + 5000; } return; } @@ -1356,16 +1369,15 @@ void arenacheck() } } - if(autoteam && m_teammode && mastermode != MM_MATCH) + if(sg->autoteam && m_teammode && sg->mastermode != MM_MATCH) { int *ntc = numteamclients(); if((!ntc[0] || !ntc[1]) && (ntc[0] > 1 || ntc[1] > 1)) refillteams(true, FTR_AUTOTEAM); } - if(!dead || gamemillis < lastdeath + 500) return; - items_blocked = true; + if(!dead || sg->gamemillis < lastdeath + 500) return; sendf(-1, 1, "ri2", SV_ARENAWIN, alive ? alive->clientnum : -1); - arenaround = gamemillis+5000; - if(autoteam && m_teammode) refillteams(true); + sg->arenaround = sg->gamemillis + 5000; + if(sg->autoteam && m_teammode) refillteams(true); } #define SPAMREPEATINTERVAL 20 // detect doubled lines only if interval < 20 seconds @@ -1498,12 +1510,12 @@ int spawntime(int type) bool serverpickup(int i, int sender) // server side item pickup, acknowledge first client that gets it { const char *hn = sender >= 0 && clients[sender]->type == ST_TCPIP ? clients[sender]->hostname : NULL; - if(!sents.inrange(i)) + if(!sg->sents.inrange(i)) { if(hn && !m_coop) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i); return false; } - server_entity &e = sents[i]; + server_entity &e = sg->sents[i]; if(!e.spawned) { if(!e.legalpickup && hn && !m_demo) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]); @@ -1514,7 +1526,7 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl client *cl = clients[sender]; if(cl->type==ST_TCPIP) { - if( cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type) || ( m_arena && !free_items(sender) ) ) return false; + if(cl->state.state != CS_ALIVE || !cl->state.canpickup(e.type)) return false; vec v(e.x, e.y, cl->state.o.z); float dist = cl->state.o.dist(v); int pdist = check_pdist(cl,dist); @@ -1527,8 +1539,8 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl } } sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); - cl->state.pickup(sents[i].type); - if (m_lss && sents[i].type == I_GRENADE) cl->state.pickup(sents[i].type); // get two nades at lss + cl->state.pickup(sg->sents[i].type); + if (m_lss && sg->sents[i].type == I_GRENADE) cl->state.pickup(sg->sents[i].type); // get two nades at lss } e.spawned = false; if(!m_lms) e.spawntime = spawntime(e.type); @@ -1538,13 +1550,13 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl void checkitemspawns(int diff) { if(!diff) return; - loopv(sents) if(sents[i].spawntime) + loopv(sg->sents) if(sg->sents[i].spawntime) { - sents[i].spawntime -= diff; - if(sents[i].spawntime<=0) + sg->sents[i].spawntime -= diff; + if(sg->sents[i].spawntime<=0) { - sents[i].spawntime = 0; - sents[i].spawned = true; + sg->sents[i].spawntime = 0; + sg->sents[i].spawned = true; sendf(-1, 1, "ri2", SV_ITEMSPAWN, i); } } @@ -1553,7 +1565,7 @@ void checkitemspawns(int diff) void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0)) { if (!m_demo && !m_coop && !validdamage(target, actor, damage, gun, gib)) return; - if ( m_arena && gun == GUN_GRENADE && arenaroundstartmillis + 2000 > gamemillis && target != actor ) return; + if ( m_arena && gun == GUN_GRENADE && sg->arenaroundstartmillis + 2000 > sg->gamemillis && target != actor ) return; clientstate &ts = target->state; ts.dodamage(damage, gun); if(damage < INT_MAX) @@ -1602,7 +1614,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, } target->position.setsize(0); ts.state = CS_DEAD; - ts.lastdeath = gamemillis; + ts.lastdeath = sg->gamemillis; if(!suic) logline(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, valid_weapon(gun) ? killmessages[gib ? 1 : 0][gun] : "smurfed", tk ? " their teammate" : "", target->name); if(m_flags && targethasflag >= 0) { @@ -1621,7 +1633,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, if( actor->state.frags < scl.banthreshold || /** teamkilling more than 6 (defaults), more than 2 per minute and less than 4 frags per tk */ ( actor->state.teamkills >= -scl.banthreshold && - actor->state.teamkills * 30 * 1000 > gamemillis && + actor->state.teamkills * 30 * 1000 > sg->gamemillis && actor->state.frags < 4 * actor->state.teamkills ) ) { addban(actor, DISC_AUTOBAN); @@ -1629,7 +1641,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, else if( actor->state.frags < scl.kickthreshold || /** teamkilling more than 5 (defaults), more than 1 tk per minute and less than 4 frags per tk */ ( actor->state.teamkills >= -scl.kickthreshold && - actor->state.teamkills * 60 * 1000 > gamemillis && + actor->state.teamkills * 60 * 1000 > sg->gamemillis && actor->state.frags < 4 * actor->state.teamkills ) ) disconnect_client(actor->clientnum, DISC_AUTOKICK); } } else if ( target!=actor && isteam(target->team, actor->team) ) check_ffire (target, actor, damage); // friendly fire counter @@ -1643,30 +1655,30 @@ void updatesdesc(const char *newdesc, ENetAddress *caller = NULL) { if(!newdesc || !newdesc[0] || !updatedescallowed()) { - copystring(servdesc_current, scl.servdesc_full); - custom_servdesc = false; + copystring(sg->servdesc_current, scl.servdesc_full); + sg->custom_servdesc = false; } else { - formatstring(servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf); - custom_servdesc = true; - if(caller) servdesc_caller = *caller; + formatstring(sg->servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf); + sg->custom_servdesc = true; + if(caller) sg->servdesc_caller = *caller; } } int canspawn(client *c) // beware: canspawn() doesn't check m_arena! { if(!c || c->type == ST_EMPTY || !c->isauthed || !team_isvalid(c->team) || - (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) || + (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? sg->gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) || (c->type == ST_TCPIP && (servmillis - c->connectmillis < 1000 + c->state.reconnections * 2000 && - gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team)))) return SP_OK_NUM; // equivalent to SP_DENY + sg->gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team)))) return SP_OK_NUM; // equivalent to SP_DENY if(!c->isonrightmap) return SP_WRONGMAP; - if(mastermode == MM_MATCH && matchteamsize) + if(sg->mastermode == MM_MATCH && sg->matchteamsize) { if(c->team == TEAM_SPECT || (team_isspect(c->team) && !m_teammode)) return SP_SPECT; if(c->team == TEAM_CLA_SPECT || c->team == TEAM_RVSF_SPECT) { - if(numteamclients()[team_base(c->team)] >= matchteamsize) return SP_SPECT; + if(numteamclients()[team_base(c->team)] >= sg->matchteamsize) return SP_SPECT; else return SP_REFILLMATCH; } } @@ -1675,7 +1687,7 @@ int canspawn(client *c) // beware: canspawn() doesn't check m_arena! void autospawncheck() { - if(mastermode != MM_MATCH || !m_autospawn || interm) return; + if(sg->mastermode != MM_MATCH || !m_autospawn || sg->interm) return; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && team_isactive(clients[i]->team)) { @@ -1696,17 +1708,17 @@ bool updateclientteam(int cln, int newteam, int ftr) client &cl = *clients[cln]; if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change int *teamsizes = numteamclients(cln); - if( mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH && + if( sg->mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH && newteam < TEAM_SPECT && team_base(cl.team) != team_base(newteam) ) return false; // no free will changes to forced people if(newteam == TEAM_ANYACTIVE) // when spawning from spect { - if(mastermode == MM_MATCH && cl.team < TEAM_SPECT) + if(sg->mastermode == MM_MATCH && cl.team < TEAM_SPECT) { newteam = team_base(cl.team); } else { - if(autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF; + if(sg->autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF; else { // join weaker team int teamscore[2] = {0, 0}, sum = calcscores(); @@ -1720,23 +1732,23 @@ bool updateclientteam(int cln, int newteam, int ftr) } if(ftr == FTR_PLAYERWISH) { - if(mastermode == MM_MATCH && matchteamsize && m_teammode) + if(sg->mastermode == MM_MATCH && sg->matchteamsize && m_teammode) { if(newteam != TEAM_SPECT && (team_base(newteam) != team_base(cl.team) || !m_teammode)) return false; // no switching sides in match mode when teamsize is set } if(team_isactive(newteam)) { if(!m_teammode && cl.state.state == CS_ALIVE) return false; // no comments - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { - if(m_teammode && matchteamsize && teamsizes[newteam] >= matchteamsize) return false; // ensure maximum team size + if(m_teammode && sg->matchteamsize && teamsizes[newteam] >= sg->matchteamsize) return false; // ensure maximum team size } else { - if(m_teammode && autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team + if(m_teammode && sg->autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team } } - else if(mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team + else if(sg->mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team } if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change if(cl.team != newteam) sdropflag(cl.clientnum); @@ -1745,7 +1757,7 @@ bool updateclientteam(int cln, int newteam, int ftr) if(ftr != FTR_INFO && !team_isspect(newteam) && team_isspect(cl.team)) sendspawn(&cl); if (team_isspect(newteam)) { cl.state.state = CS_SPECTATE; - cl.state.lastdeath = gamemillis; + cl.state.lastdeath = sg->gamemillis; } cl.team = newteam; return true; @@ -1768,7 +1780,7 @@ void shuffleteams(int ftr = FTR_AUTOTEAM) { int numplayers = numclients(); int team, sums = calcscores(); - if(gamemillis < 2 * 60 *1000) + if(sg->gamemillis < 2 * 60 *1000) { // random int teamsize[2] = {0, 0}; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) // only shuffle active players @@ -1808,7 +1820,7 @@ void shuffleteams(int ftr = FTR_AUTOTEAM) bool balanceteams(int ftr) // pro vs noobs never more { - if(mastermode != MM_OPEN || totalclients < 3 ) return true; + if(sg->mastermode != MM_OPEN || totalclients < 3 ) return true; int tsize[2] = {0, 0}, tscore[2] = {0, 0}; int totalscore = 0, nplayers = 0; int flagmult = (m_ctf ? 50 : (m_htf ? 25 : 12)); @@ -1819,7 +1831,7 @@ bool balanceteams(int ftr) // pro vs noobs never more if(c->isauthed && team_isactive(c->team)) { int time = servmillis - c->connectmillis + 5000; - if ( time > gamemillis ) time = gamemillis + 5000; + if(time > sg->gamemillis ) time = sg->gamemillis + 5000; tsize[c->team]++; // effective score per minute, thanks to wtfthisgame for the nice idea // in a normal game, normal players will do 500 points in 10 minutes @@ -1863,7 +1875,7 @@ bool balanceteams(int ftr) // pro vs noobs never more if ( hid >= 0 ) { updateclientteam(hid, l, ftr); - clients[hid]->at3_lastforce = gamemillis; + clients[hid]->at3_lastforce = sg->gamemillis; clients[hid]->state.forced = true; return true; } @@ -1893,7 +1905,7 @@ bool balanceteams(int ftr) // pro vs noobs never more { updateclientteam(bestpair[h], l, ftr); updateclientteam(bestpair[l], h, ftr); - clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = gamemillis; + clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = sg->gamemillis; clients[bestpair[h]]->state.forced = clients[bestpair[l]]->state.forced = true; return true; } @@ -1905,7 +1917,7 @@ int lastbalance = 0, waitbalance = 2 * 60 * 1000; bool refillteams(bool now, int ftr) // force only minimal amounts of players { - if(mastermode == MM_MATCH) return false; + if(sg->mastermode == MM_MATCH) return false; static int lasttime_eventeams = 0; int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0}; bool switched = false; @@ -1933,10 +1945,10 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players int allplayers = teamsize[0] + teamsize[1]; int diffnum = teamsize[bigteam] - teamsize[!bigteam]; int diffscore = teamscore[bigteam] - teamscore[!bigteam]; - if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0; + if(lasttime_eventeams > sg->gamemillis) lasttime_eventeams = 0; if(diffnum > 1) { - if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) + if(now || sg->gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) { // time to even out teams loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->team != bigteam) clients[i]->at3_dontmove = true; // dont move small team players @@ -1950,7 +1962,7 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players { int fit = targetscore - clients[i]->at3_score; if(fit < 0 ) fit = -(fit * 15) / 10; // avoid too good players - int forcedelay = clients[i]->at3_lastforce ? (1000 - (gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0; + int forcedelay = clients[i]->at3_lastforce ? (1000 - (sg->gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0; if(forcedelay > 0) fit += (fit * forcedelay) / 600; // avoid lately forced players if(fit < bestfit + fit * rnd(100) / 400) // search 'almost' best fit { @@ -1966,7 +1978,7 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players { diffnum -= 2; diffscore -= 2 * clients[pick]->at3_score; - clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes + clients[pick]->at3_lastforce = sg->gamemillis; // try not to force this player again for the next 5 minutes switched = true; } } @@ -1974,22 +1986,22 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players } if(diffnum < 2) { - if ( ( gamemillis - lastbalance ) > waitbalance && ( gamelimit - gamemillis ) > 4*60*1000 ) + if ( ( sg->gamemillis - lastbalance ) > waitbalance && ( sg->gamelimit - sg->gamemillis ) > 4*60*1000 ) { if ( balanceteams (ftr) ) { - waitbalance = 2 * 60 * 1000 + gamemillis / 3; + waitbalance = 2 * 60 * 1000 + sg->gamemillis / 3; switched = true; } else waitbalance = 20 * 1000; - lastbalance = gamemillis; + lastbalance = sg->gamemillis; } - else if ( lastbalance > gamemillis ) + else if ( lastbalance > sg->gamemillis ) { lastbalance = 0; waitbalance = 2 * 60 * 1000; } - lasttime_eventeams = gamemillis; + lasttime_eventeams = sg->gamemillis; } return switched; } @@ -1999,27 +2011,29 @@ void resetserver(const char *newname, int newmode, int newtime) if(m_demo) enddemoplayback(); else enddemorecord(); - smode = newmode; - copystring(smapname, newname); + sg->free(); // for now: clean out old servergame states + sg->curmap = NULL; - minremain = newtime > 0 ? newtime : defaultgamelimit(newmode); - gamemillis = 0; - gamelimit = minremain*60000; - arenaround = arenaroundstartmillis = 0; - memset(&smapstats, 0, sizeof(smapstats)); + sg->smode = newmode; + copystring(sg->smapname, newname); - interm = nextsendscore = 0; - lastfillup = servmillis; - sents.shrink(0); - if(mastermode == MM_PRIVATE) + sg->minremain = newtime > 0 ? newtime : defaultgamelimit(newmode); + sg->gamemillis = 0; + sg->gamelimit = sg->minremain * 60000; + sg->arenaround = sg->arenaroundstartmillis = 0; + + sg->interm = sg->nextsendscore = 0; + sg->lastfillup = servmillis; + sg->sents.shrink(0); + if(sg->mastermode == MM_PRIVATE) { loopv(savedscores) savedscores[i].valid = false; } else savedscores.shrink(0); ctfreset(); - nextmapname[0] = '\0'; - forceintermission = false; + sg->nextmapname[0] = '\0'; + sg->forceintermission = false; } void startdemoplayback(const char *newname) @@ -2041,68 +2055,76 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) bool lastteammode = m_teammode; resetserver(newname, newmode, newtime); // beware: may clear *newname - if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0) + if(sg->custom_servdesc && findcnbyaddress(&sg->servdesc_caller) < 0) { updatesdesc(NULL); if(notify) { sendservmsg("server description reset to default"); - logline(ACLOG_INFO, "server description reset to '%s'", servdesc_current); + logline(ACLOG_INFO, "server description reset to '%s'", sg->servdesc_current); } } - int maploc = MAP_VOID; - mapstats *ms = getservermapstats(smapname, isdedicated, &maploc); - mapbuffer.clear(); - if(isdedicated && distributablemap(maploc)) mapbuffer.load(); - if(ms) + servermap *sm = NULL; + if(isdedicated) + { // dedicated server: only use pre-loaded maps + sm = getservermap(sg->smapname); + } +#ifndef STANDALONE + else + { // local server: load map temporarily + static servermap *localmap = NULL; + DELETEP(localmap); + const char *lpath = checklocalmap(sg->smapname); + if(lpath) localmap = new servermap(sg->smapname, lpath); + if(localmap) + { + localmap->load(); + if(localmap->isok) sm = localmap; + else conoutf("\f3local server failed to load map \"%s%s\", error: %s", lpath, sg->smapname, localmap->err); + } + } +#endif + sg->curmap = sm; + if(sm) { - smapstats = *ms; + sg->layout = sm->getlayout(); // get floorplan + loopi(2) { - sflaginfo &f = sflaginfos[i]; - if(smapstats.flags[i] == 1) // don't check flag positions, if there is more than one flag per team + sflaginfo &f = sg->sflaginfos[i]; + if(sg->curmap->entstats.hasflags) // don't check flag positions, if there is more than one flag per team { - short *fe = smapstats.entposs + smapstats.flagents[i] * 3; - f.x = *fe; - fe++; - f.y = *fe; + f.x = sg->curmap->entpos_x[sg->curmap->entstats.flagents[i]]; + f.y = sg->curmap->entpos_y[sg->curmap->entstats.flagents[i]]; } else f.x = f.y = -1; - } - if (smapstats.flags[0] == 1 && smapstats.flags[1] == 1) - { - sflaginfo &f0 = sflaginfos[0], &f1 = sflaginfos[1]; - FlagFlag = pow2(f0.x - f1.x) + pow2(f0.y - f1.y); - coverdist = FlagFlag > 6 * COVERDIST ? COVERDIST : FlagFlag / 6; + f.actor_cn = -1; } entity e; - loopi(smapstats.hdr.numents) + loopi(sg->curmap->numents) { - e.type = smapstats.enttypes[i]; - e.transformtype(smode); - server_entity se = { e.type, false, false, false, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]}; - sents.add(se); - if(e.fitsmode(smode)) sents[i].spawned = sents[i].legalpickup = true; + e.type = sg->curmap->enttypes[i]; + e.transformtype(sg->smode); + server_entity se = { e.type, false, false, false, 0, sg->curmap->entpos_x[i], sg->curmap->entpos_y[i] }; + sg->sents.add(se); + if(e.fitsmode(sg->smode)) sg->sents[i].spawned = sg->sents[i].legalpickup = true; } - mapbuffer.setrevision(); - logline(ACLOG_INFO, "Map height density information for %s: H = %.2f V = %d, A = %d and MA = %d", smapname, Mheight, Mvolume, Marea, Mopen); - items_blocked = false; } - else if(isdedicated) sendservmsg("\f3server error: map not found - please start another map or send this map to the server"); + else if(isdedicated && sg->smode != GMODE_COOPEDIT) fatal("servermap not found"); // FIXME: coop should also start with a map if(notify) { // change map - sendf(-1, 1, "risiii", SV_MAPCHANGE, smapname, smode, mapbuffer.available(), mapbuffer.revision); - if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri3", SV_TIMEUP, gamemillis, gamelimit); + sendf(-1, 1, "risiii", SV_MAPCHANGE, sg->smapname, sg->smode, sm && sm->isdistributable() ? sm->cgzlen : 0, sm && sm->isdistributable() ? sm->maprevision : 0); + if(sg->smode > 1 || (sg->smode == 0 && numnonlocalclients() > 0)) sendf(-1, 1, "ri3", SV_TIMEUP, sg->gamemillis, sg->gamelimit); } packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); send_item_list(q); // always send the item list when a game starts sendpacket(-1, 1, q.finalize()); - defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(smode), smapname, numclients(), minremain, mastermode); - if(mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", matchteamsize); - if(ms) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", smapstats.hdr.maprevision, smapstats.cgzsize, maplocstr[maploc], mapbuffer.available() ? "" : "not "); - else concatformatstring(gsmsg, "error: failed to preload map (map: %s)", maplocstr[maploc]); + defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(sg->smode), sg->smapname, numclients(), sg->minremain, sg->mastermode); + if(sg->mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", sg->matchteamsize); + if(sm) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", sm->maprevision, sm->cgzlen, sm->getpathdesc(), sm->isdistributable() ? "" : "not "); + else concatformatstring(gsmsg, "error: failed to preload map"); logline(ACLOG_INFO, "\n%s", gsmsg); if(m_arena) distributespawns(); if(notify) @@ -2112,7 +2134,7 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) { if(!lastteammode) shuffleteams(FTR_INFO); - else if(autoteam) + else if(sg->autoteam) refillteams(true, FTR_INFO); } // prepare spawns; players will spawn, once they've loaded the correct map @@ -2430,7 +2452,7 @@ void senddisconnectedscores(int cn) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, SV_DISCSCORES); - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { loopv(savedscores) { @@ -2481,7 +2503,7 @@ void disconnect_client(int n, int reason) clients[n]->zap(); sendf(-1, 1, "rii", SV_CDIS, n); if(curvote) curvote->evaluate(); - if(*scoresaved && mastermode == MM_MATCH) senddisconnectedscores(-1); + if(*scoresaved && sg->mastermode == MM_MATCH) senddisconnectedscores(-1); } // for AUTH: WIP @@ -2636,26 +2658,26 @@ void welcomeinitclient(packetbuf &p, int exclude = -1) void welcomepacket(packetbuf &p, int n) { - if(!smapname[0]) maprot.next(false); + if(!sg->smapname[0]) maprot.next(false); client *c = valid_client(n) ? clients[n] : NULL; int numcl = numclients(); putint(p, SV_WELCOME); - putint(p, smapname[0] && !m_demo ? numcl : -1); - if(smapname[0] && !m_demo) + putint(p, sg->smapname[0] && !m_demo ? numcl : -1); + if(sg->smapname[0] && !m_demo) { putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, smode); - putint(p, mapbuffer.available()); - putint(p, mapbuffer.revision); - if(smode>1 || (smode==0 && numnonlocalclients()>0)) + sendstring(sg->smapname, p); + putint(p, sg->smode); + putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->cgzlen : 0); + putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->maprevision : 0); + if(sg->smode>1 || (sg->smode==0 && numnonlocalclients()>0)) { putint(p, SV_TIMEUP); - putint(p, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis); - putint(p, gamelimit); - //putint(p, minremain*60); + putint(p, (sg->gamemillis>=sg->gamelimit || sg->forceintermission) ? sg->gamelimit : sg->gamemillis); + putint(p, sg->gamelimit); + //putint(p, sg->minremain*60); } send_item_list(p); // this includes the flags } @@ -2663,7 +2685,7 @@ void welcomepacket(packetbuf &p, int n) if(c) { if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); - c->team = mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT; + c->team = sg->mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT; putint(p, SV_SETTEAM); putint(p, n); putint(p, c->team | (FTR_INFO << 4)); @@ -2743,7 +2765,7 @@ int checktype(int type, client *cl) if(cl) { loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; - loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return smode==GMODE_COOPEDIT ? type : -1; + loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return sg->smode==GMODE_COOPEDIT ? type : -1; if(++cl->overflow >= 200) return -2; } return type; @@ -2785,8 +2807,8 @@ void process(ENetPacket *packet, int sender, int chan) int bantype = getbantype(sender); bool banned = bantype > BAN_NONE; bool srvfull = numnonlocalclients() > scl.maxclients; - bool srvprivate = mastermode == MM_PRIVATE || mastermode == MM_MATCH; - bool matchreconnect = mastermode == MM_MATCH && findscore(*cl, false); + bool srvprivate = sg->mastermode == MM_PRIVATE || sg->mastermode == MM_MATCH; + bool matchreconnect = sg->mastermode == MM_MATCH && findscore(*cl, false); int bl = 0, wl = nickblacklist.checkwhitelist(*cl); if(wl == NWL_PASS) concatstring(tags, ", nickname whitelist match"); if(wl == NWL_UNLISTED) bl = nickblacklist.checkblacklist(cl->name); @@ -2901,8 +2923,7 @@ void process(ENetPacket *packet, int sender, int chan) if(type!=SV_POS && type!=SV_POSC && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) { DEBUGVAR(cl->name); - ASSERT(type>=0 && type= 0) { DEBUGVAR(messagenames[type]); } protocoldebug(DEBUGCOND); } else protocoldebug(false); @@ -2954,7 +2975,7 @@ void process(ENetPacket *packet, int sender, int chan) bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) { - if(mastermode != MM_MATCH || !matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat + if(sg->mastermode != MM_MATCH || !sg->matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat { logline(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); @@ -3000,7 +3021,7 @@ void process(ENetPacket *packet, int sender, int chan) bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) { - bool allowed = !(mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); + bool allowed = !(sg->mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); logline(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text); } @@ -3046,7 +3067,7 @@ void process(ENetPacket *packet, int sender, int chan) { int gzs = getint(p); int rev = getint(p); - if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev)) + if(!isdedicated || (sg->curmap && sg->curmap->cgzlen == gzs && sg->curmap->maprevision == rev)) { // here any game really starts for a client: spawn, if it's a new game - don't spawn if the game was already running cl->isonrightmap = true; int sp = canspawn(cl); @@ -3056,7 +3077,7 @@ void process(ENetPacket *packet, int sender, int chan) bool spawn = false; if(team_isspect(cl->team)) { - if(numclients() < 2 && !m_demo && mastermode != MM_MATCH) // spawn on empty servers + if(numclients() < 2 && !m_demo && sg->mastermode != MM_MATCH) // spawn on empty servers { spawn = updateclientteam(cl->clientnum, TEAM_ANYACTIVE, FTR_INFO); } @@ -3083,7 +3104,7 @@ void process(ENetPacket *packet, int sender, int chan) case SV_ITEMPICKUP: { int n = getint(p); - if(!arenaround || arenaround - gamemillis > 2000) + if(!sg->arenaround || sg->arenaround - sg->gamemillis > 2000) { gameevent &pickup = cl->addevent(); pickup.type = GE_PICKUP; @@ -3186,7 +3207,7 @@ void process(ENetPacket *packet, int sender, int chan) updateclientteam(sender, TEAM_ANYACTIVE, FTR_PLAYERWISH); sp = canspawn(cl); } - if( !m_arena && sp < SP_OK_NUM && gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl); + if( !m_arena && sp < SP_OK_NUM && sg->gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl); break; } @@ -3196,7 +3217,7 @@ void process(ENetPacket *packet, int sender, int chan) if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD && cl->state.state!=CS_SPECTATE) || ls!=cl->state.lifesequence || cl->state.lastspawn<0 || !valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; cl->state.lastspawn = -1; - cl->state.spawn = gamemillis; + cl->state.spawn = sg->gamemillis; cl->autospawn = false; cl->upspawnp = false; cl->state.state = CS_ALIVE; @@ -3228,11 +3249,11 @@ void process(ENetPacket *packet, int sender, int chan) #define seteventmillis(event) \ { \ event.id = getint(p); \ - if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(gamemillis))) \ + if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(sg->gamemillis))) \ { \ cl->timesync = true; \ - cl->gameoffset = gamemillis - event.id; \ - event.millis = gamemillis; \ + cl->gameoffset = sg->gamemillis - event.id; \ + event.millis = sg->gamemillis; \ } \ else event.millis = cl->gameoffset + event.id; \ } @@ -3393,8 +3414,9 @@ void process(ENetPacket *packet, int sender, int chan) int g2 = q.getbits(1); // shooting cl->g = (g1<<4) | (g2<<5); + if(!sg->curmap) break; // no compact POS packets without loaded servermap if(!cl->isonrightmap || p.remaining() || p.overread()) { p.flags = 0; break; } - if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (smapstats.hdr.sfactor < 7 ? 7 : smapstats.hdr.sfactor)) break; + if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (sg->curmap->sfactor < 7 ? 7 : sg->curmap->sfactor)) break; cl->state.o[0] = xt / DMF; cl->state.o[1] = yt / DMF; cl->state.o[2] = zt / DMF; @@ -3421,42 +3443,56 @@ void process(ENetPacket *packet, int sender, int chan) p.forceoverread(); break; } - int mp = findmappath(sentmap); - if(readonlymap(mp)) + servermap *sm = getservermap(sentmap); + header *h = peekmapheader(&p.buf[p.len], mapsize); + int actualrevision = h ? h->maprevision : 0; + if(sm && sm->isro()) { reject = "map is ro"; defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap); sendservmsg(msg, sender); } - else if( scl.incoming_limit && ( scl.incoming_limit << 20 ) < incoming_size + mapsize + cfgsizegz ) + else if(scl.incoming_limit && (scl.incoming_limit << 20) - incoming_size < mapsize + cfgsizegz) { reject = "server incoming reached its limits"; sendservmsg("\f3server does not support more incomings: limit reached", sender); } - else if(mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN) + else if(!sm && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN) { reject = "no permission for initial upload"; sendservmsg("\f3initial map upload rejected: you need to be admin", sender); } - else if(mp == MAP_TEMP && revision >= mapbuffer.revision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps + else if(sm && !sm->isro() && revision >= sm->maprevision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps { reject = "no permission to update"; sendservmsg("\f3map update rejected: you need to be admin", sender); } - else if(mp == MAP_TEMP && revision < mapbuffer.revision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions + else if(revision != actualrevision) + { + reject = "fake revision number"; + } + else if(sm && !sm->isro() && revision < sm->maprevision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions { reject = "no permission to revert revision"; sendservmsg("\f3map revert to older revision rejected: you need to be admin to upload an older map", sender); } else - { - if(mapbuffer.sendmap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) + { // map accepted + if(serverwritemap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) { - incoming_size += mapsize + cfgsizegz; + if(sg->smode == GMODE_COOPEDIT && !strcmp(sentmap, behindpath(sg->smapname))) + { // update buffer only in coopedit mode (and on same map) + sg->coop_cgzlen = mapsize; + sg->coop_cfglen = cfgsize; + sg->coop_cfglengz = cfgsizegz; + DELETEA(sg->coop_mapdata); + sg->coop_mapdata = new uchar[mapsize + cfgsizegz]; + memcpy(sg->coop_mapdata, &p.buf[p.len], mapsize + cfgsizegz); + } + incoming_size += mapsize + cfgsize; logline(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written", clients[sender]->hostname, clients[sender]->name, sentmap, revision, mapsize, cfgsize, cfgsizegz); - defformatstring(msg)("%s (%d) up%sed map %s, rev %d%s", clients[sender]->name, sender, mp == MAP_NOTFOUND ? "load": "dat", sentmap, revision, - /*strcmp(sentmap, behindpath(smapname)) || smode == GMODE_COOPEDIT ? "" :*/ "\f3 (restart game to use new map version)"); + defformatstring(msg)("%s (%d) up%sed map %s, rev %d", clients[sender]->name, sender, !sm ? "load": "dat", sentmap, revision); sendservmsg(msg); } else @@ -3476,16 +3512,22 @@ void process(ENetPacket *packet, int sender, int chan) case SV_RECVMAP: { - if(mapbuffer.available()) + if(sg->smode == GMODE_COOPEDIT && sg->coop_mapdata) // "getmap" only in coopedit { resetflag(cl->clientnum); // drop ctf flag savedscore *sc = findscore(*cl, true); // save score if(sc) sc->save(cl->state, cl->team); - mapbuffer.sendmap(cl, 2); + packetbuf p(MAXTRANS + sg->coop_cgzlen + sg->coop_cfglengz, ENET_PACKET_FLAG_RELIABLE); + putint(p, SV_RECVMAP); + sendstring(sg->smapname, p); + putint(p, sg->coop_cgzlen); + putint(p, sg->coop_cfglen); + putint(p, sg->coop_cfglengz); + p.put(sg->coop_mapdata, sg->coop_cgzlen + sg->coop_cfglengz); + sendpacket(cl->clientnum, 2, p.finalize()); cl->mapchange(true); sendwelcome(cl, 2); // resend state properly } - else sendservmsg("no map to get", cl->clientnum); break; } @@ -3495,11 +3537,11 @@ void process(ENetPacket *packet, int sender, int chan) filtertext(text, text, FTXT__MAPNAME); string filename; const char *rmmap = behindpath(text), *reject = NULL; - int mp = findmappath(rmmap); + servermap *sm = getservermap(rmmap); int reqrole = strchr(scl.mapperm, 'D') ? CR_ADMIN : (strchr(scl.mapperm, 'd') ? CR_DEFAULT : CR_ADMIN + 100); if(cl->role < reqrole) reject = "no permission"; - else if(readonlymap(mp)) reject = "map is readonly"; - else if(mp == MAP_NOTFOUND) reject = "map not found"; + else if(sm && sm->isro()) reject = "map is readonly"; + else if(!sm) reject = "map not found"; else { formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", rmmap); @@ -3705,10 +3747,10 @@ void process(ENetPacket *packet, int sender, int chan) // intermediate solution to set the teamsize (will be voteable) getstring(text, p, n); - if(valid_client(sender) && clients[sender]->role==CR_ADMIN && mastermode == MM_MATCH) + if(valid_client(sender) && clients[sender]->role==CR_ADMIN && sg->mastermode == MM_MATCH) { changematchteamsize(atoi(text)); - defformatstring(msg)("match team size set to %d", matchteamsize); + defformatstring(msg)("match team size set to %d", sg->matchteamsize); sendservmsg(msg, -1); } } @@ -3776,23 +3818,23 @@ client &addclient() void checkintermission() { - if(minremain>0) + if(sg->minremain>0) { - minremain = (gamemillis>=gamelimit || forceintermission) ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; - sendf(-1, 1, "ri3", SV_TIMEUP, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis, gamelimit); + sg->minremain = (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1) / 60000; + sendf(-1, 1, "ri3", SV_TIMEUP, (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? sg->gamelimit : sg->gamemillis, sg->gamelimit); } - if(!interm && minremain<=0) interm = gamemillis+10000; - forceintermission = false; + if(!sg->interm && sg->minremain <= 0) sg->interm = sg->gamemillis + 10000; + sg->forceintermission = false; } void resetserverifempty() { loopv(clients) if(clients[i]->type!=ST_EMPTY) return; resetserver("", 0, 10); - matchteamsize = 0; - autoteam = true; + sg->matchteamsize = 0; + sg->autoteam = true; changemastermode(MM_OPEN); - nextmapname[0] = '\0'; + sg->nextmapname[0] = '\0'; } void sendworldstate() @@ -3820,10 +3862,10 @@ void loggamestatus(const char *reason) { int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}; string text; - formatstring(text)("%d minutes remaining", minremain); + formatstring(text)("%d minutes remaining", sg->minremain); logline(ACLOG_INFO, ""); logline(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s", - modestr(gamemode), smapname, reason ? reason : text, mmfullname(mastermode), totalclients, custom_servdesc ? ',' : '\0', servdesc_current); + modestr(gamemode), sg->smapname, reason ? reason : text, mmfullname(sg->mastermode), totalclients, sg->custom_servdesc ? ',' : '\0', sg->servdesc_current); if(!scl.loggamestatus) return; logline(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : ""); loopv(clients) @@ -3845,7 +3887,7 @@ void loggamestatus(const char *reason) pnum[t] += 1; } } - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { loopv(savedscores) { @@ -3945,7 +3987,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis; #endif int diff = nextmillis - servmillis; - gamemillis += diff; + sg->gamemillis += diff; servmillis = nextmillis; servertime = ((diff + 3 * servertime)>>2); if (servertime > 40) serverlagged = servmillis; @@ -3955,24 +3997,24 @@ void serverslice(uint timeout) // main server update, called from cube main lo { readdemo(); extern void silenttimeupdate(int milliscur, int millismax); - silenttimeupdate(gamemillis, gametimemaximum); + silenttimeupdate(sg->gamemillis, gametimemaximum); } #endif - if(minremain>0) + if(sg->minremain>0) { processevents(); checkitemspawns(diff); bool ktfflagingame = false; if(m_flags) loopi(2) { - sflaginfo &f = sflaginfos[i]; - if(f.state == CTFF_DROPPED && gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1); - if(m_htf && f.state == CTFF_INBASE && gamemillis-f.lastupdate > (smapstats.hasflags ? 10000 : 1000)) + sflaginfo &f = sg->sflaginfos[i]; + if(f.state == CTFF_DROPPED && sg->gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1); + if(m_htf && f.state == CTFF_INBASE && sg->gamemillis-f.lastupdate > (sg->curmap && sg->curmap->entstats.hasflags ? 10000 : 1000)) { htf_forceflag(i); } - if(m_ktf && f.state == CTFF_STOLEN && gamemillis-f.lastupdate > 15000) + if(m_ktf && f.state == CTFF_STOLEN && sg->gamemillis-f.lastupdate > 15000) { flagaction(i, FA_SCORE, -1); } @@ -3983,7 +4025,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo else if(m_autospawn) autospawncheck(); // if(m_lms) lmscheck(); sendextras(); - if ( scl.afk_limit && mastermode == MM_OPEN && next_afk_check < servmillis && gamemillis > 20 * 1000 ) check_afk(); + if ( scl.afk_limit && sg->mastermode == MM_OPEN && next_afk_check < servmillis && sg->gamemillis > 20 * 1000 ) check_afk(); } if(curvote) @@ -3994,20 +4036,20 @@ void serverslice(uint timeout) // main server update, called from cube main lo int nonlocalclients = numnonlocalclients(); - if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000)) + if(sg->forceintermission || ((sg->smode>1 || (gamemode==0 && nonlocalclients)) && sg->gamemillis-diff>0 && sg->gamemillis/60000!=(sg->gamemillis-diff)/60000)) checkintermission(); if(m_demo && !demoplayback) maprot.restart(); - else if(interm && ( (scl.demo_interm && sending_demo) ? gamemillis>(interm<<1) : gamemillis>interm ) ) + else if(sg->interm && ( (scl.demo_interm && sending_demo) ? sg->gamemillis > (sg->interm<<1) : sg->gamemillis > sg->interm ) ) { sending_demo = false; loggamestatus("game finished"); if(demorecord) enddemorecord(); - interm = nextsendscore = 0; + sg->interm = sg->nextsendscore = 0; //start next game - if(nextmapname[0]) startgame(nextmapname, nextgamemode); + if(sg->nextmapname[0]) startgame(sg->nextmapname, sg->nextgamemode); else maprot.next(); - nextmapname[0] = '\0'; + sg->nextmapname[0] = '\0'; map_queued = false; } @@ -4017,9 +4059,9 @@ void serverslice(uint timeout) // main server update, called from cube main lo poll_serverthreads(); - serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION); + serverms(sg->smode, numclients(), sg->minremain, sg->smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION, sg->servdesc_current, sg->interm); - if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis; + if(sg->autoteam && m_teammode && !m_arena && !sg->interm && servmillis - sg->lastfillup > 5000 && refillteams()) sg->lastfillup = servmillis; static unsigned int lastThrottleEpoch = 0; if(serverhost->bandwidthThrottleEpoch != lastThrottleEpoch) @@ -4109,7 +4151,7 @@ void cleanupserver() int getpongflags(enet_uint32 ip) { - int flags = mastermode << PONGFLAG_MASTERMODE; + int flags = sg->mastermode << PONGFLAG_MASTERMODE; flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0; loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; } flags |= ipblacklist.check(ip) ? 1 << PONGFLAG_BLACKLIST : 0; @@ -4183,7 +4225,7 @@ void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENet if(clients[i]->type != ST_TCPIP) continue; if(pid>-1 && clients[i]->clientnum!=pid) continue; - bool ismatch = mastermode == MM_MATCH; + bool ismatch = sg->mastermode == MM_MATCH; putint(p,EXT_PLAYERSTATS_RESP_STATS); // send player stats following putint(p,clients[i]->clientnum); //add player id putint(p,clients[i]->ping); //Ping @@ -4216,7 +4258,7 @@ void extinfo_teamscorebuf(ucharbuf &p) { putint(p, m_teammode ? EXT_ERROR_NONE : EXT_ERROR); putint(p, gamemode); - putint(p, minremain); // possible TODO: use gamemillis, gamelimit here too? + putint(p, sg->minremain); if(!m_teammode) return; int teamsizes[TEAM_NUM] = { 0 }, fragscores[TEAM_NUM] = { 0 }, flagscores[TEAM_NUM] = { 0 }; @@ -4310,7 +4352,7 @@ void initserver(bool dedicated, int argc, char **argv) if ( strlen(scl.servdesc_full) ) global_name = scl.servdesc_full; else global_name = server_name; - smapname[0] = '\0'; + sg->smapname[0] = '\0'; string identity; if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); @@ -4320,7 +4362,7 @@ void initserver(bool dedicated, int argc, char **argv) printf("WARNING: logging not started!\n"); logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); - copystring(servdesc_current, scl.servdesc_full); + copystring(sg->servdesc_current, scl.servdesc_full); servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated); if((isdedicated = dedicated)) @@ -4332,7 +4374,7 @@ void initserver(bool dedicated, int argc, char **argv) loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; maprot.init(scl.maprot); - maprot.next(false, true); // ensure minimum maprot length of '1' +// maprot.next(false, true); // ensure minimum maprot length of '1' passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); nickblacklist.init(scl.nbfile); diff --git a/source/src/server.h b/source/src/server.h index eb599be5..25024b75 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -1,6 +1,6 @@ // server.h -#define gamemode smode // allows the gamemode macros to work with the server mode +#define gamemode sg->smode // allows the gamemode macros to work with the server mode #define SERVER_PROTOCOL_VERSION (PROTOCOL_VERSION) // server without any gameplay modification //#define SERVER_PROTOCOL_VERSION (-PROTOCOL_VERSION) // server with gameplay modification but compatible to vanilla client (using /modconnect) @@ -11,7 +11,7 @@ enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SUICIDE, GE_PICKUP }; enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; -extern int smode, servmillis; +extern int servmillis; struct shotevent { @@ -292,7 +292,7 @@ struct client // server side version of "dynent" type events.setsize(0); overflow = 0; timesync = false; - isonrightmap = m_coop; + isonrightmap = false; spawnperm = SP_WRONGMAP; spawnpermsent = servmillis; autospawn = false; @@ -376,6 +376,16 @@ struct server_entity // server side version of "entity" type short x, y; }; +struct sflaginfo +{ + int state; + int actor_cn; + float pos[3]; + int lastupdate; + int stolentime; + short x, y; // flag entity location +}; + struct clientidentity { uint ip; @@ -397,7 +407,6 @@ void sendiplist(int receiver, int cn = -1); int clienthasflag(int cn); bool refillteams(bool now = false, int ftr = FTR_AUTOTEAM); void changeclientrole(int client, int role, char *pwd = NULL, bool force=false); -mapstats *getservermapstats(const char *mapname, bool getlayout = false, int *maploc = NULL); int findmappath(const char *mapname, char *filename = NULL); int calcscores(); void recordpacket(int chan, void *data, int len); @@ -412,9 +421,6 @@ void forcedeath(client *cl); void sendf(int cn, int chan, const char *format, ...); extern bool isdedicated; -extern string smapname; -extern mapstats smapstats; -extern char *maplayout; const char *messagenames[SV_NUM] = { diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 0f04a1a6..e9f6c5e6 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -42,14 +42,14 @@ struct mapaction : serveraction { if(queue) { - nextgamemode = mode; - copystring(nextmapname, map); + sg->nextgamemode = mode; + copystring(sg->nextmapname, map); } - else if(isdedicated && numclients() > 2 && smode >= 0 && smode != 1 && ( gamemillis > gamelimit/4 || scl.demo_interm )) + else if(isdedicated && numclients() > 2 && sg->smode >= 0 && sg->smode != 1 && ( sg->gamemillis > sg->gamelimit/4 || scl.demo_interm )) { - forceintermission = true; - nextgamemode = mode; - copystring(nextmapname, map); + sg->forceintermission = true; + sg->nextgamemode = mode; + copystring(sg->nextmapname, map); } else { @@ -63,32 +63,24 @@ struct mapaction : serveraction if(isdedicated) { bool notify = valid_client(caller); - int maploc = MAP_NOTFOUND; - mapstats *ms = map[0] ? getservermapstats(map, false, &maploc) : NULL; + servermap *sm = getservermap(map); bool validname = validmapname(map); - mapok = (ms != NULL) && validname && ( (mode != GMODE_COOPEDIT && mapisok(ms)) || (mode == GMODE_COOPEDIT && !readonlymap(maploc)) ); + mapok = sm && validname; if(!mapok) { if(notify) { - if(!validname) - sendservmsg("invalid map name", caller); - else - { - sendservmsg(ms ? - ( mode == GMODE_COOPEDIT ? "this map cannot be coopedited in this server" : "sorry, but this map does not satisfy some quality requisites to be played in MultiPlayer Mode" ) : - "the server does not have this map", - caller); - } + if(!validname) sendservmsg("invalid map name", caller); + else sendservmsg("the server does not have this map", caller); } } else { // check, if map supports mode if(mode == GMODE_COOPEDIT && !strchr(scl.voteperm, 'e')) role = CR_ADMIN; - bool romap = mode == GMODE_COOPEDIT && readonlymap(maploc); - int smode = mode; // 'borrow' the mode macros by replacing a global by a local var - bool spawns = mode == GMODE_COOPEDIT || (m_teammode && !m_ktf ? ms->hasteamspawns : ms->hasffaspawns); - bool flags = mode != GMODE_COOPEDIT && m_flags && !m_htf ? ms->hasflags : true; + bool romap = mode == GMODE_COOPEDIT && sm->isro(); + int gmmask = 1 << mode; + bool spawns = mode == GMODE_COOPEDIT || ((gmmask & GMMASK__TEAMSPAWN) ? sm->entstats.hasteamspawns : sm->entstats.hasffaspawns); + bool flags = (gmmask & GMMASK__FLAGENTS) ? sm->entstats.hasflags : true; if(!spawns || !flags || romap) { // unsupported mode if(strchr(scl.voteperm, 'P')) role = CR_ADMIN; @@ -130,10 +122,9 @@ struct mapaction : serveraction } } else - { - int mp = findmappath(map); - mapok = mp == MAP_LOCAL || mp == MAP_OFFICIAL; // clients can only load from packages/maps and packages/maps/official + { // local server #ifndef STANDALONE + mapok = checklocalmap(map) != NULL; // clients can only load from packages/maps and packages/maps/official if(!mapok) conoutf("\f3map '%s' not found", map); #endif } @@ -264,7 +255,7 @@ struct autoteamaction : enableaction { void perform() { - autoteam = enable; + sg->autoteam = enable; sendservermode(); if(m_teammode && enable) refillteams(true); } diff --git a/source/src/serverchecks.h b/source/src/serverchecks.h index 9a13a0a8..d263f717 100644 --- a/source/src/serverchecks.h +++ b/source/src/serverchecks.h @@ -5,15 +5,15 @@ inline bool is_lagging(client *cl) inline bool outside_border(vec &po) { - return (po.x < 0 || po.y < 0 || po.x >= maplayoutssize || po.y >= maplayoutssize); + return sg->curmap && (po.x < sg->curmap->x1 || po.y < sg->curmap->y1 || po.x > sg->curmap->x2 || po.y > sg->curmap->y2 || po.z < sg->curmap->zmin || po.z > sg->curmap->zmax); } inline void checkclientpos(client *cl) { vec &po = cl->state.o; - if( outside_border(po) || maplayout[((int) po.x) + (((int) po.y) << maplayout_factor)] > po.z + 3) + if(outside_border(po) || (sg->curmap && sg->layout && sg->layout[((int) po.x) + (((int) po.y) << sg->curmap->sfactor)] > po.z + 3)) { - if(gamemillis > 10000 && (servmillis - cl->connectmillis) > 10000) cl->mapcollisions++; + if(sg->gamemillis > 10000 && (servmillis - cl->connectmillis) > 10000) cl->mapcollisions++; if(cl->mapcollisions && !(cl->mapcollisions % 25)) { logline(ACLOG_INFO, "[%s] %s is colliding with the map", cl->hostname, cl->name); @@ -31,75 +31,6 @@ extern inline void addban(client *cl, int reason, int type = BAN_AUTO); #define MINELINE 50 -//FIXME -/* There are smarter ways to implement this function, but most probably they will be very complex */ -int getmaxarea(int inversed_x, int inversed_y, int transposed, int ml_factor, char *ml) -{ - int ls = (1 << ml_factor); - int xi = 0, oxi = 0, xf = 0, oxf = 0, fx = 0, fy = 0; - int area = 0, maxarea = 0; - bool sav_x = false, sav_y = false; - - if (transposed) fx = ml_factor; - else fy = ml_factor; - - // walk on x for each y - for ( int y = (inversed_y ? ls-1 : 0); (inversed_y ? y >= 0 : y < ls); (inversed_y ? y-- : y++) ) { - - /* Analyzing each cube of the line */ - for ( int x = (inversed_x ? ls-1 : 0); (inversed_x ? x >= 0 : x < ls); (inversed_x ? x-- : x++) ) { - if ( ml[ ( x << fx ) + ( y << fy ) ] != 127 ) { // if it is not solid - if ( sav_x ) { // if the last cube was saved - xf = x; // new end for this line - } - else { - xi = x; // new begin of the line - sav_x = true; // accumulating cubes from now - } - } else { // solid - if ( xf - xi > MINELINE ) break; // if the empty line is greater than a minimum, get out - sav_x = false; // stop the accumulation of cubes - } - } - - /* Analyzing this line with the previous one */ - if ( xf - xi > MINELINE ) { // if the line has the minimun threshold of emptiness - if ( sav_y ) { // if the last line was saved - if ( 2*oxi + MINELINE < 2*xf && - 2*xi + MINELINE < 2*oxf ) { // if the last line intersect this one - area += xf - xi; - } else { - oxi = xi; // new area vertices - oxf = xf; - } - } - else { - oxi = xi; - oxf = xf; - sav_y = true; // accumulating lines from now - } - } else { - sav_y = false; // stop the accumulation of lines - if (area > maxarea) maxarea = area; // new max area - area=0; - } - - sav_x = false; // reset x - xi = xf = 0; - } - return maxarea; -} - -int checkarea(int maplayout_factor, char *maplayout) -{ - int area = 0, maxarea = 0; - for (int i=0; i < 8; i++) { - area = getmaxarea((i & 1),(i & 2),(i & 4), maplayout_factor, maplayout); - if ( area > maxarea ) maxarea = area; - } - return maxarea; -} - /** This part is related to medals system. WIP */ @@ -116,7 +47,7 @@ inline void addpt(client *c, int points, int n = -1) { c->state.points += points; c->md.dpt += points; c->md.updated = true; - c->md.upmillis = gamemillis + 240; // about 2 AR shots + c->md.upmillis = sg->gamemillis + 240; // about 2 AR shots print_medal_messages(c,n); } @@ -278,9 +209,9 @@ int checkteamrequests(int sender) loopv(clients) { client *prot = clients[i]; if ( i!=sender && prot->type != ST_EMPTY && prot->team == ant->team && - prot->state.state == CS_ALIVE && prot->md.askmillis > gamemillis && prot->md.ask >= 0 ) { + prot->state.state == CS_ALIVE && prot->md.askmillis > sg->gamemillis && prot->md.ask >= 0 ) { float dist = POW2XY(ant->state.o,prot->state.o); - if ( dist < REPLYDIST && (dtime=prot->md.askmillis-gamemillis) > besttime) { + if ( dist < REPLYDIST && (dtime=prot->md.askmillis - sg->gamemillis) > besttime) { bestid = i; besttime = dtime; } @@ -298,14 +229,14 @@ void checkteamplay(int s, int sender) if ( actor->state.state != CS_ALIVE ) return; switch(s){ case S_IMONDEFENSE: // informs - actor->md.linkmillis = gamemillis + 20000; + actor->md.linkmillis = sg->gamemillis + 20000; actor->md.linkreason = s; break; case S_COVERME: // demands case S_STAYTOGETHER: case S_STAYHERE: actor->md.ask = s; - actor->md.askmillis = gamemillis + 5000; + actor->md.askmillis = sg->gamemillis + 5000; break; case S_AFFIRMATIVE: // replies case S_ALLRIGHTSIR: @@ -315,8 +246,8 @@ void checkteamplay(int s, int sender) if ( id >= 0 ) { client *sgt = clients[id]; actor->md.linked = id; - if ( actor->md.linkmillis < gamemillis ) addpt(actor,REPLYPT); - actor->md.linkmillis = gamemillis + 30000; + if ( actor->md.linkmillis < sg->gamemillis ) addpt(actor,REPLYPT); + actor->md.linkmillis = sg->gamemillis + 30000; actor->md.linkreason = sgt->md.ask; sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, HE_NUM+id); switch( actor->md.linkreason ) { // check demands @@ -336,7 +267,7 @@ void computeteamwork(int team, int exclude) // testing loopv(clients) { client *actor = clients[i]; - if ( i == exclude || actor->type == ST_EMPTY || actor->team != team || actor->state.state != CS_ALIVE || actor->md.linkmillis < gamemillis ) continue; + if ( i == exclude || actor->type == ST_EMPTY || actor->team != team || actor->state.state != CS_ALIVE || actor->md.linkmillis < sg->gamemillis ) continue; vec position; bool teamworkdone = false; switch( actor->md.linkreason ) @@ -387,7 +318,7 @@ inline bool testcover(int msg, int factor, client *actor) bool validlink(client *actor, int cn) { - return actor->md.linked >= 0 && actor->md.linked == cn && gamemillis < actor->md.linkmillis && valid_client(actor->md.linked); + return actor->md.linked >= 0 && actor->md.linked == cn && sg->gamemillis < actor->md.linkmillis && valid_client(actor->md.linked); } /** WIP */ @@ -402,8 +333,8 @@ void checkcover(client *target, client *actor) int cnumber = totalclients < 13 ? totalclients : 12; if ( m_flags ) { - sflaginfo &f = sflaginfos[team]; - sflaginfo &of = sflaginfos[oteam]; + sflaginfo &f = sg->sflaginfos[team]; + sflaginfo &of = sg->sflaginfos[oteam]; if ( m_ctf ) { @@ -511,7 +442,7 @@ void check_afk() client &c = *clients[i]; if ( c.type != ST_TCPIP || c.connectmillis + 60 * 1000 > servmillis || c.inputmillis + scl.afk_limit > servmillis || clienthasflag(c.clientnum) != -1 ) continue; - if ( ( c.state.state == CS_DEAD && !m_arena && c.state.lastdeath + 45 * 1000 < gamemillis) || + if ( ( c.state.state == CS_DEAD && !m_arena && c.state.lastdeath + 45 * 1000 < sg->gamemillis) || ( c.state.state == CS_ALIVE && c.upspawnp ) /*|| ( c.state.state == CS_SPECTATE && totalclients >= scl.maxclients ) // only kick spectator if server is full - 2011oct16:flowtron: mmh, that seems reasonable enough .. still, kicking spectators for inactivity seems harsh! disabled ATM, kick them manually if you must. */ @@ -530,9 +461,9 @@ void check_afk() In normal games, the players go over 6 tks only in the worst cases */ void check_ffire(client *target, client *actor, int damage) { - if ( mastermode != MM_OPEN ) return; + if ( sg->mastermode != MM_OPEN ) return; actor->ffire += damage; - if ( actor->ffire > 300 && actor->ffire * 600 > gamemillis) { + if ( actor->ffire > 300 && actor->ffire * 600 > sg->gamemillis) { logline(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); defformatstring(msg)("%s %s", actor->name, "kicked for excessive friendly fire"); sendservmsg(msg); @@ -573,8 +504,8 @@ If you know nothing about these detections, please, just ignore it. inline void checkmove(client *cl) { - cl->ldt = gamemillis - cl->lmillis; - cl->lmillis = gamemillis; + cl->ldt = sg->gamemillis - cl->lmillis; + cl->lmillis = sg->gamemillis; if ( cl->ldt < 40 ) cl->ldt = 40; cl->t += cl->ldt; cl->spj = (( 7 * cl->spj + cl->ldt ) >> 3); @@ -585,7 +516,6 @@ inline void checkmove(client *cl) cl->inputmillis = servmillis; } - if(maplayout) checkclientpos(cl); #ifdef ACAC m_engine(cl); diff --git a/source/src/serverevents.h b/source/src/serverevents.h index 248abbc6..19a06318 100644 --- a/source/src/serverevents.h +++ b/source/src/serverevents.h @@ -37,18 +37,18 @@ void processevent(client *c, shotevent &e) { clientstate &gs = c->state; int wait = e.millis - gs.lastshot; - if(!gs.isalive(gamemillis) || + if(!gs.isalive(sg->gamemillis) || !valid_weapon(e.gun) || waitgamemillis) return; if(e.gun!=GUN_KNIFE) gs.mag[e.gun]--; loopi(NUMGUNS) if(gs.gunwait[i]) gs.gunwait[i] = max(gs.gunwait[i] - (e.millis-gs.lastshot), 0); gs.lastshot = e.millis; gs.gunwait[e.gun] = attackdelay(e.gun); - if(e.gun==GUN_PISTOL && gs.akimbomillis>gamemillis) gs.gunwait[e.gun] /= 2; + if(e.gun==GUN_PISTOL && gs.akimbomillis>sg->gamemillis) gs.gunwait[e.gun] /= 2; sendf(-1, 1, "ri6x", SV_SHOTFX, c->clientnum, e.gun, // int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF), int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF), @@ -115,20 +115,20 @@ void processevent(client *c, suicideevent &e) void processevent(client *c, pickupevent &e) { clientstate &gs = c->state; - if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; + if(m_mp(gamemode) && !gs.isalive(sg->gamemillis)) return; serverpickup(e.ent, c->clientnum); } void processevent(client *c, reloadevent &e) { clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || + if(!gs.isalive(sg->gamemillis) || !valid_weapon(e.gun) || !reloadable_gun(e.gun) || gs.ammo[e.gun]<=0) return; - if(e.gun == GUN_AKIMBO && gs.akimbomillis < gamemillis) return; + if(e.gun == GUN_AKIMBO && gs.akimbomillis < sg->gamemillis) return; bool akimbo = e.gun==GUN_PISTOL && gs.akimbomillis>e.millis; int mag = (akimbo ? 2 : 1) * magsize(e.gun), numbullets = min(gs.ammo[e.gun], mag - gs.mag[e.gun]); if(numbullets<=0) return; @@ -150,7 +150,7 @@ void processevent(client *c, reloadevent &e) void processevent(client *c, akimboevent &e) { clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || gs.akimbomillis) return; + if(!gs.isalive(sg->gamemillis) || gs.akimbomillis) return; gs.akimbomillis = e.millis+30000; } @@ -167,13 +167,13 @@ void processevents() { client *c = clients[i]; if(c->type==ST_EMPTY) continue; - if(c->state.akimbomillis && c->state.akimbomillis < gamemillis) { c->state.akimbomillis = 0; c->state.akimbo = false; } + if(c->state.akimbomillis && c->state.akimbomillis < sg->gamemillis) { c->state.akimbomillis = 0; c->state.akimbo = false; } while(c->events.length()) { gameevent &e = c->events[0]; if(e.typegamemillis) break; + if(e.shot.millis>sg->gamemillis) break; if(e.shot.millislastevent) { clearevent(c); continue; } c->lastevent = e.shot.millis; } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 294c25e1..db30fd69 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -5,10 +5,12 @@ #define SERVERMAXMAPFACTOR 10 // 10 is huge... if you're running low on RAM, consider 9 as maximum (don't go higher than 10, or players will name their ugly dog after you) #define SERVERMAP_PATH_BUILTIN "packages" PATHDIVS "maps" PATHDIVS "official" PATHDIVS +#define SERVERMAP_PATH_LOCAL "packages" PATHDIVS "maps" PATHDIVS #define SERVERMAP_PATH "packages" PATHDIVS "maps" PATHDIVS "servermaps" PATHDIVS #define SERVERMAP_PATH_INCOMING "packages" PATHDIVS "maps" PATHDIVS "servermaps" PATHDIVS "incoming" PATHDIVS const char *servermappath_off = SERVERMAP_PATH_BUILTIN; +const char *servermappath_local = SERVERMAP_PATH_LOCAL; const char *servermappath_serv = SERVERMAP_PATH; const char *servermappath_incom = SERVERMAP_PATH_INCOMING; @@ -38,6 +40,7 @@ struct servermap // in-memory version of a map file on a server uchar *enttypes; // table of entity types short *entpos_x, *entpos_y; + const char *err; bool isok; // definitive flag! #ifdef _DEBUG char maptitle[129]; @@ -48,15 +51,36 @@ struct servermap // in-memory version of a map file on a server bool isro() { return fpath == servermappath_off || fpath == servermappath_serv; } bool isofficial() { return fpath == servermappath_off; } + bool isdistributable() { return fpath == servermappath_serv || fpath == servermappath_incom; } int getmemusage() { return sizeof(struct servermap) + cgzlen + cfggzlen + layoutgzlen + numents * (sizeof(uchar) + sizeof(short) * 3); } + char *getlayout() // uncompress prefabricated floorplan + { + if(isok && layoutgz && layoutgzlen > 0 && layoutgzlen < layoutlen) + { + char *lo = new char[layoutlen]; + uLongf rawsize = layoutlen; + if(lo && uncompress((Bytef*)lo, &rawsize, layoutgz, layoutgzlen) == Z_OK && rawsize - layoutlen == 0) return lo; + delete[] lo; + } + return NULL; + } + + const char *getpathdesc() + { + if(fpath == servermappath_off) return "official"; + if(fpath == servermappath_local) return "local"; + if(fpath == servermappath_serv) return "custom"; + if(fpath == servermappath_incom) return "temporary"; + ASSERT(0); return "unknown"; + } + void load(void) // load map into memory and extract everything important about it (assumes struct to be zeroed: can only be called once) { static uchar *staticbuffer = NULL; if(!staticbuffer) staticbuffer = new uchar[FLOORPLANBUFSIZE]; // this buffer gets reused for every map load several times (also: because of this, load() is not thread safe) - const char *err = NULL; stream *f = NULL; int restofhead; @@ -292,6 +316,7 @@ struct servermap // in-memory version of a map file on a server if(err) goto loadfailed; // run mipmapper (to get corners right) + // TODO :) // calculate area statistics from type & vdelta values (destroys vdelta!) @@ -504,189 +529,45 @@ int readmapsthread(void *logfileprefix) return 0; } - - - - - - - -struct servermapbuffer // sending of maps between clients +#ifndef STANDALONE +const char *checklocalmap(const char *mapname) // check, if a map file exists in packages/maps/official or packages/maps - return proper servermap-path, if found { - string mapname; - int cgzsize, cfgsize, cfgsizegz, revision, datasize; - uchar *data, *gzbuf; + const char **fn = setnames(behindpath(mapname)), *locs[2] = { servermappath_off, servermappath_local }; + int l = 0; + if(!isdedicated && (getfilesize(fn[l]) > 10 || getfilesize(fn[++l]) > 10)) return locs[l]; + return NULL; +} +#endif - servermapbuffer() : data(NULL) { gzbuf = new uchar[GZBUFSIZE]; } - ~servermapbuffer() { delete[] gzbuf; } - - void clear() { DELETEA(data); revision = 0; } - - int available() +bool serverwritemap(const char *mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data) +{ + FILE *fp; + bool written = false; + if(!mapname[0] || mapsize <= 0 || cfgsizegz < 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client + defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", mapname); + path(name); + fp = fopen(name, "wb"); + if(fp) { - if(data && !strcmp(mapname, behindpath(smapname)) && cgzsize == smapstats.cgzsize) - { - if( !revision || (revision == smapstats.hdr.maprevision)) return cgzsize; - } - return 0; - } - - void setrevision() - { - if(available() && !revision) revision = smapstats.hdr.maprevision; - } - - void load(void) // load currently played map into the buffer (if distributable), clear buffer otherwise - { - string cgzname, cfgname; - const char *name = behindpath(smapname); // no paths allowed here - - clear(); - formatstring(cgzname)(SERVERMAP_PATH "%s.cgz", name); - path(cgzname); - if(fileexists(cgzname, "r")) - { - formatstring(cfgname)(SERVERMAP_PATH "%s.cfg", name); - } - else - { - formatstring(cgzname)(SERVERMAP_PATH_INCOMING "%s.cgz", name); - path(cgzname); - formatstring(cfgname)(SERVERMAP_PATH_INCOMING "%s.cfg", name); - } - path(cfgname); - uchar *cgzdata = (uchar *)loadfile(cgzname, &cgzsize); - uchar *cfgdata = (uchar *)loadfile(cfgname, &cfgsize); - if(cgzdata && (!cfgdata || cfgsize < MAXCFGFILESIZE)) - { - uLongf gzbufsize = GZBUFSIZE; - if(!cfgdata || compress2(gzbuf, &gzbufsize, cfgdata, cfgsize, 9) != Z_OK) - { - cfgsize = 0; - gzbufsize = 0; - } - cfgsizegz = (int) gzbufsize; - if(cgzsize + cfgsizegz < MAXMAPSENDSIZE) - { // map is ok, fill buffer - copystring(mapname, name); - datasize = cgzsize + cfgsizegz; - data = new uchar[datasize]; - memcpy(data, cgzdata, cgzsize); - memcpy(data + cgzsize, gzbuf, cfgsizegz); - logline(ACLOG_INFO,"loaded map %s, %d + %d(%d) bytes.", cgzname, cgzsize, cfgsize, cfgsizegz); - } - } - DELETEA(cgzdata); - DELETEA(cfgdata); - } - - bool sendmap(const char *nmapname, int nmapsize, int ncfgsize, int ncfgsizegz, uchar *ndata) - { - FILE *fp; - bool written = false; - - if(!nmapname[0] || nmapsize <= 0 || ncfgsizegz < 0 || nmapsize + ncfgsizegz > MAXMAPSENDSIZE || ncfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client - int cfgsize = ncfgsize; - if(smode == GMODE_COOPEDIT && !strcmp(nmapname, behindpath(smapname))) - { // update mapbuffer only in coopedit mode (and on same map) - copystring(mapname, nmapname); - datasize = nmapsize + ncfgsizegz; - revision = 0; - DELETEA(data); - data = new uchar[datasize]; - memcpy(data, ndata, datasize); - } - - defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", nmapname); + fwrite(data, 1, mapsize, fp); + fclose(fp); + formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", mapname); path(name); fp = fopen(name, "wb"); if(fp) { - fwrite(ndata, 1, nmapsize, fp); + uLongf rawsize = cfgsize; + uchar *gzbuf = new uchar[cfgsize]; + if(uncompress(gzbuf, &rawsize, data + mapsize, cfgsizegz) == Z_OK && rawsize - cfgsize == 0) + fwrite(gzbuf, 1, cfgsize, fp); fclose(fp); - formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", nmapname); - path(name); - fp = fopen(name, "wb"); - if(fp) - { - uLongf rawsize = ncfgsize; - if(uncompress(gzbuf, &rawsize, ndata + nmapsize, ncfgsizegz) == Z_OK && rawsize - ncfgsize == 0) - fwrite(gzbuf, 1, cfgsize, fp); - fclose(fp); - written = true; - } + delete[] gzbuf; + written = true; } - return written; } - - void sendmap(client *cl, int chan) - { - if(!available()) return; - packetbuf p(MAXTRANS + datasize, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_RECVMAP); - sendstring(mapname, p); - putint(p, cgzsize); - putint(p, cfgsize); - putint(p, cfgsizegz); - putint(p, revision); - p.put(data, datasize); - sendpacket(cl->clientnum, chan, p.finalize()); - } -}; - - -// provide maps by the server - -enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_LOCAL, MAP_OFFICIAL, MAP_VOID }; -static const char * const maplocstr[] = { "not found", "temporary", "custom", "local", "official", "void" }; -#define readonlymap(x) ((x) >= MAP_CUSTOM) -#define distributablemap(x) ((x) == MAP_TEMP || (x) == MAP_CUSTOM) - -int findmappath(const char *mapname, char *filename) -{ - if(!mapname[0]) return MAP_NOTFOUND; - string tempname; - if(!filename) filename = tempname; - const char *name = behindpath(mapname); - formatstring(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name); - path(filename); - int loc = MAP_NOTFOUND; - if(getfilesize(filename) > 10) loc = MAP_OFFICIAL; - else - { -#ifndef STANDALONE - copystring(filename, setnames(name)); - if(!isdedicated && getfilesize(filename) > 10) loc = MAP_LOCAL; - else - { -#endif - formatstring(filename)(SERVERMAP_PATH "%s.cgz", name); - path(filename); - if(isdedicated && getfilesize(filename) > 10) loc = MAP_CUSTOM; - else - { - formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name); - path(filename); - if(isdedicated && getfilesize(filename) > 10) loc = MAP_TEMP; - } -#ifndef STANDALONE - } -#endif - } - return loc; + return written; } -mapstats *getservermapstats(const char *mapname, bool getlayout, int *maploc) -{ - string filename; - int ml; - if(!maploc) maploc = &ml; - *maploc = findmappath(mapname, filename); - if(getlayout) DELETEA(maplayout); - return *maploc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout); -} - - // server config files void serverconfigfile::init(const char *name) @@ -734,76 +615,7 @@ struct configset }; }; -int FlagFlag = MINFF * 1000; -int Mvolume, Marea, SHhits, Mopen = 0; -float Mheight = 0; - -bool mapisok(mapstats *ms) -{ - if ( Mheight > MAXMHEIGHT ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The overall ceil height is too high (%.1f cubes)", Mheight); return false; } - if ( Mopen > MAXMAREA ) { logline(ACLOG_INFO, "MAP CHECK FAIL: There is a big open area in this (hint: use more solid walls)"); return false; } - if ( SHhits > MAXHHITS ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Too high height in some parts of the map (%d hits)", SHhits); return false; } - - if ( ms->hasflags ) // Check if flags are ok - { - struct { short x, y; } fl[2]; - loopi(2) - { - if(ms->flags[i] == 1) - { - short *fe = ms->entposs + ms->flagents[i] * 3; - fl[i].x = *fe; fe++; fl[i].y = *fe; - } - else fl[i].x = fl[i].y = 0; // the map has no valid flags - } - FlagFlag = pow2(fl[0].x - fl[1].x) + pow2(fl[0].y - fl[1].y); - } - else FlagFlag = MINFF * 1000; // the map has no flags - - if ( FlagFlag < MINFF ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The flags are too close to each other"); return false; } - - for (int i = 0; i < ms->hdr.numents; i++) - { - int v = ms->enttypes[i]; - if (v < I_CLIPS || v > I_AKIMBO) continue; - short *p = &ms->entposs[i*3]; - float density = 0, hdensity = 0; - for(int j = 0; j < ms->hdr.numents; j++) - { - int w = ms->enttypes[j]; - if (w < I_CLIPS || w > I_AKIMBO || i == j) continue; - short *q = &ms->entposs[j*3]; - float r2 = 0; - loopk(3){ r2 += (p[k]-q[k])*(p[k]-q[k]); } - if ( r2 == 0.0f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[v], entnames[w],p[0],p[1]); return false; } - r2 = 1/r2; - if (r2 < 0.0025f) continue; - if (w != v) - { - hdensity += r2; - continue; - } - density += r2; - } -/* if (hdensity > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK H %s %f", entnames[v], hdensity); } - if (density > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK D %s %f", entnames[v], density); }*/ - if ( hdensity > 0.5f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],hdensity,p[0],p[1]); return false; } - switch(v) - { -#define LOGTHISSWITCH(X) if( density > X ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],density,p[0],p[1]); return false; } - case I_CLIPS: - case I_HEALTH: LOGTHISSWITCH(0.24f); break; - case I_AMMO: LOGTHISSWITCH(0.04f); break; - case I_HELMET: LOGTHISSWITCH(0.02f); break; - case I_ARMOUR: - case I_GRENADE: - case I_AKIMBO: LOGTHISSWITCH(0.005f); break; - default: break; -#undef LOGTHISSWITCH - } - } - return true; -} +servermap *getservermap(const char *mapname); struct servermaprot : serverconfigfile { @@ -873,9 +685,8 @@ struct servermaprot : serverconfigfile c = &configsets[ccs]; if((n >= c->minplayer || i >= csl) && (!c->maxplayer || n <= c->maxplayer || i >= 2 * csl)) { - mapstats *ms = NULL; - if((ms = getservermapstats(c->mapname)) && mapisok(ms)) break; - else logline(ACLOG_INFO, "maprot error: map '%s' %s", c->mapname, (ms ? "does not satisfy some basic requirements" : "not found")); + if(getservermap(c->mapname)) break; + else logline(ACLOG_INFO, "maprot error: map '%s' not found", c->mapname); } if(i >= 3 * csl) fatal("maprot unusable"); // not a single map in rotation can be found... } @@ -896,7 +707,7 @@ struct servermaprot : serverconfigfile return; } #endif - startgame(smapname, smode, -1, notify); + startgame("ac_desert", 0, -1, notify); } configset *current() { return configsets.inrange(curcfgset) ? &configsets[curcfgset] : NULL; } diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 3db4c09a..5d335097 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -165,9 +165,9 @@ extern int totalclients; // send alive signal to masterserver after 40 minutes of uptime and if currently in intermission (so theoretically <= 1 hour) // TODO?: implement a thread to drop this "only in intermission" business, we'll need it once AUTH gets active! -static inline void updatemasterserver(int millis, int port) +static inline void updatemasterserver(int millis, int port, int _interm) { - if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (interm || !totalclients))) + if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { char servername[30]; memset(servername,'\0',30); filtertext(servername, global_name, FTXT__GLOBALNAME, 20); if(mastername[0]) requestmasterf("regserv %d %s %d\n", port, servername[0] ? servername : "noname", AC_VERSION); @@ -178,10 +178,10 @@ static inline void updatemasterserver(int millis, int port) ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL; extern int getpongflags(enet_uint32 ip); -void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version) +void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm) { flushmasteroutput(); - updatemasterserver(millis, localaddr.port); + updatemasterserver(millis, localaddr.port, _interm); static ENetSocketSet sockset; ENET_SOCKETSET_EMPTY(sockset); @@ -221,14 +221,13 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(getint(pi) != 0) // std pong { extern struct servercommandline scl; - extern string servdesc_current; (*mnum)++; *mrec += len; std = true; putint(po, protocol_version); putint(po, mode); putint(po, numplayers); putint(po, minremain); sendstring(smapname, po); - sendstring(servdesc_current, po); + sendstring(servdesccur, po); putint(po, scl.maxclients); putint(po, getpongflags(addr.host)); if(pi.remaining()) diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 928a4afc..ef7943a9 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -79,6 +79,20 @@ int fixmapheadersize(int version, int headersize) // we can't trust hdr.header return headersize; } +header *peekmapheader(uchar *data, int len) // extract the header from an in-memory mapfile (doesn't process all values: only used to get the map revision and timestamp) +{ + static header h; + uLongf rawsize = (int)sizeof(header); + if(uncompress((Bytef*)&h, &rawsize, data, len) == Z_BUF_ERROR && (!strncmp(h.head, "CUBE", 4) || !strncmp(h.head, "ACMP",4))) + { + lilswap(&h.version, 4); // version, headersize, sfactor, numents + if(h.version < 4) memset(&h.waterlevel, 0, sizeof(int) * 16); + else lilswap(&h.maprevision, 4); // maprevision, ambient, flags, timestamp + return &h; + } + return NULL; +} + // map geometry statistics servsqr *createservworld(const sqr *s, int _cubicsize) // create a server-style floorplan on the client, to use same statistics functions on client and server @@ -316,8 +330,6 @@ const char *rateentitystats(entitystats_s &es) } #endif - - int cmpintasc(const int *a, const int *b) { return *a - *b; } // leads to ascending sort order int cmpintdesc(const int *a, const int *b) { return *b - *a; } // leads to descending sort order int stringsort(const char **a, const char **b) { return strcmp(*a, *b); } @@ -325,177 +337,6 @@ int stringsortrev(const char **a, const char **b) { return strcmp(*b, *a); } int stringsortignorecase(const char **a, const char **b) { return strcasecmp(*a, *b); } int stringsortignorecaserev(const char **a, const char **b) { return strcasecmp(*b, *a); } -extern char *maplayout, *testlayout; -extern int maplayout_factor, testlayout_factor, Mvolume, Marea, Mopen, SHhits; -extern float Mheight; -extern int checkarea(int, char *); - -mapstats *loadmapstats(const char *filename, bool getlayout) -{ - const int sizeof_header = sizeof(header), sizeof_baseheader = sizeof_header - sizeof(int) * 16; - static mapstats s; - static uchar *enttypes = NULL; - static short *entposs = NULL; - - DELETEA(enttypes); - loopi(MAXENTTYPES) s.entcnt[i] = 0; - loopi(3) s.spawns[i] = 0; - loopi(2) s.flags[i] = 0; - - stream *f = opengzfile(filename, "rb"); - if(!f) return NULL; - memset(&s.hdr, 0, sizeof_header); - if(f->read(&s.hdr, sizeof_baseheader) != sizeof_baseheader || (strncmp(s.hdr.head, "CUBE", 4) && strncmp(s.hdr.head, "ACMP",4))) { delete f; return NULL; } - lilswap(&s.hdr.version, 4); - s.hdr.headersize = fixmapheadersize(s.hdr.version, s.hdr.headersize); - int restofhead = min(s.hdr.headersize, sizeof_header) - sizeof_baseheader; - if(s.hdr.version > MAPVERSION || s.hdr.numents > MAXENTITIES || - f->read(&s.hdr.waterlevel, restofhead) != restofhead || - !f->seek(clamp(s.hdr.headersize - sizeof_header, 0, MAXHEADEREXTRA), SEEK_CUR)) { delete f; return NULL; } - if(s.hdr.version>=4) - { - lilswap(&s.hdr.waterlevel, 1); - lilswap(&s.hdr.maprevision, 2); - } - else s.hdr.waterlevel = -100000; - entity e; - enttypes = new uchar[s.hdr.numents]; - entposs = new short[s.hdr.numents * 3]; - loopi(s.hdr.numents) - { - f->read(&e, s.hdr.version < 10 ? 12 : sizeof(persistent_entity)); - lilswap((short *)&e, 4); - transformoldentitytypes(s.hdr.version, e.type); - if(e.type == PLAYERSTART && (e.attr2 == 0 || e.attr2 == 1 || e.attr2 == 100)) s.spawns[e.attr2 == 100 ? 2 : e.attr2]++; - if(e.type == CTF_FLAG && (e.attr2 == 0 || e.attr2 == 1)) { s.flags[e.attr2]++; s.flagents[e.attr2] = i; } - s.entcnt[e.type]++; - enttypes[i] = e.type; - entposs[i * 3] = e.x; entposs[i * 3 + 1] = e.y; entposs[i * 3 + 2] = e.z + e.attr1; - } - DELETEA(testlayout); - int minfloor = 0; - int maxceil = 0; - if(s.hdr.sfactor <= LARGEST_FACTOR && s.hdr.sfactor >= SMALLEST_FACTOR) - { - testlayout_factor = s.hdr.sfactor; - int layoutsize = 1 << (testlayout_factor * 2); - bool fail = false; - testlayout = new char[layoutsize + 256]; - memset(testlayout, 0, layoutsize * sizeof(char)); - char *t = NULL; - char floor = 0, ceil; - int diff = 0; - Mvolume = Marea = SHhits = 0; - loopk(layoutsize) - { - char *c = testlayout + k; - int type = f->getchar(); - int n = 1; - switch(type) - { - case 255: - { - if(!t || (n = f->getchar()) < 0) { fail = true; break; } - memset(c, *t, n); - k += n - 1; - break; - } - case 254: // only in MAPVERSION<=2 - if(!t) { fail = true; break; } - *c = *t; - f->getchar(); f->getchar(); - break; - default: - if(type<0 || type>=MAXTYPE) { fail = true; break; } - floor = f->getchar(); - ceil = f->getchar(); - if(floor >= ceil && ceil > -128) floor = ceil - 1; // for pre 12_13 - diff = ceil - floor; - if(type == FHF) floor = -128; - if(floor!=-128 && floormaxceil) maxceil = ceil; - f->getchar(); f->getchar(); - if(s.hdr.version>=2) f->getchar(); - if(s.hdr.version>=5) f->getchar(); - - case SOLID: - *c = type == SOLID ? 127 : floor; - f->getchar(); f->getchar(); - if(s.hdr.version<=2) { f->getchar(); f->getchar(); } - break; - } - if ( type != SOLID && diff > 6 ) - { - // Lucas (10mar2013): Removed "pow2" because it was too strict - if (diff > MAXMHEIGHT) SHhits += /*pow2*/(diff-MAXMHEIGHT)*n; - Marea += n; - Mvolume += diff * n; - } - if(fail) break; - t = c; - } - if(fail) { DELETEA(testlayout); } - else - { - Mheight = Marea ? (float)Mvolume/Marea : 0; - Mopen = checkarea(testlayout_factor, testlayout); - } - } - if(getlayout) - { - DELETEA(maplayout); - if (testlayout) - { - maplayout_factor = testlayout_factor; - extern int maplayoutssize; - maplayoutssize = 1 << testlayout_factor; - int layoutsize = 1 << (testlayout_factor * 2); - maplayout = new char[layoutsize + 256]; - memcpy(maplayout, testlayout, layoutsize * sizeof(char)); - -/* memset(&mapdims, 0, sizeof(struct mapdim_s)); - mapdims.x1 = mapdims.y1 = maplayoutssize; - loopk(layoutsize) if (testlayout[k] != 127) - { - int cwx = k%maplayoutssize, - cwy = k/maplayoutssize; - if(cwx < mapdims.x1) mapdims.x1 = cwx; - if(cwy < mapdims.y1) mapdims.y1 = cwy; - if(cwx > mapdims.x2) mapdims.x2 = cwx; - if(cwy > mapdims.y2) mapdims.y2 = cwy; - } - mapdims.xspan = mapdims.x2 - mapdims.x1; - mapdims.yspan = mapdims.y2 - mapdims.y1; - mapdims.minfloor = minfloor; - mapdims.maxceil = maxceil; -*/ } - } - delete f; - s.hasffaspawns = s.spawns[2] > 0; - s.hasteamspawns = s.spawns[0] > 0 && s.spawns[1] > 0; - s.hasflags = s.flags[0] > 0 && s.flags[1] > 0; - s.enttypes = enttypes; - s.entposs = entposs; - s.cgzsize = getfilesize(filename); - return &s; -} - - - - - - - - - - - - - - - - - ///////////////////////// debugging /////////////////////// #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) diff --git a/source/src/tools.h b/source/src/tools.h index fd688c81..fffc93b2 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -968,7 +968,6 @@ extern void *zipmanualopen(stream *f, vector &files); extern stream *zipmanualstream(void *a, int n); extern int zipmanualread(void *a, int n, stream *f, int maxlen = INT_MAX); extern void zipmanualclose(void *a); -extern struct mapstats *loadmapstats(const char *filename, bool getlayout); extern bool cmpb(void *b, int n, enet_uint32 c); extern bool cmpf(char *fn, enet_uint32 c); extern enet_uint32 adler(unsigned char *data, size_t len); diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 47cf343b..450c4288 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -7,8 +7,9 @@ VARP(worldiodebug, 0, 0, 1); static string cgzname, ocgzname, bakname, cbakname, mcfname, omcfname; -const char *setnames(const char *name) +const char **setnames(const char *name) { + static const char *paths[2] = { ocgzname, cgzname }; const char *mapname = behindpath(name), *nt = numtime(); formatstring(cgzname) ("packages" PATHDIVS "maps" PATHDIVS "%s.cgz", mapname); formatstring(mcfname) ("packages" PATHDIVS "maps" PATHDIVS "%s.cfg", mapname); @@ -16,7 +17,7 @@ const char *setnames(const char *name) formatstring(omcfname)("packages" PATHDIVS "maps" PATHDIVS "official" PATHDIVS "%s.cfg", mapname); formatstring(bakname) ("packages" PATHDIVS "maps" PATHDIVS "%s_%s.BAK", mapname, nt); formatstring(cbakname)("packages" PATHDIVS "maps" PATHDIVS "%s.cfg_%s.BAK", mapname, nt); - return cgzname; + return paths; } // the optimize routines below are here to reduce the detrimental effects of messy mapping by @@ -55,8 +56,8 @@ uchar *readmap(char *name, int *size, int *revision) setnames(name); uchar *data = (uchar *)loadfile(cgzname, size); if(!data) { conoutf("\f3could not read map %s", cgzname); return NULL; } - mapstats *ms = loadmapstats(cgzname, false); - if(revision) *revision = ms->hdr.maprevision; + header *h = peekmapheader(data, *size); + if(revision) *revision = h ? h->maprevision : 0; return data; } From 25deee5287ac32aec76898f99aa93f53562e2ba9 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 20 Feb 2016 20:15:06 +0100 Subject: [PATCH 03/45] more versatile disconnect reasons --- source/src/client.cpp | 4 +--- source/src/protocol.cpp | 37 +++++++++++++++++++++++++++++++++++++ source/src/protocol.h | 6 +++++- source/src/protos.h | 3 ++- source/src/server.cpp | 22 ++++++++-------------- source/src/serveractions.h | 6 +++--- source/src/serverchecks.h | 2 +- source/src/tools.h | 1 + 8 files changed, 58 insertions(+), 23 deletions(-) diff --git a/source/src/client.cpp b/source/src/client.cpp index fffa8290..64faedcd 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -164,7 +164,7 @@ void disconnect(int onlyclean, int async) { if(!discmillis) { - enet_peer_disconnect(curpeer, DISC_NONE); + enet_peer_disconnect(curpeer, DISC_BECAUSE); enet_host_flush(clienthost); discmillis = totalmillis; } @@ -629,8 +629,6 @@ void gets2c() // get updates from the server case ENET_EVENT_TYPE_DISCONNECT: { - extern const char *disc_reason(int reason); - if(event.data>=DISC_NUM) event.data = DISC_NONE; if(event.peer==connpeer) { conoutf("\f3could not connect to server"); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 002bac13..977c86d8 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -438,3 +438,40 @@ int msgsizelookup(int msg) return msg >= 0 && msg < SV_NUM ? sizetable[msg] : -1; } +const char *disc_reason(int reason) +{ + static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync" }, + *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"" }, + *removed[] = { "removed from server", "failed admin login", "inappropriate nickname", "annoyance limit exceeded", "SPAM", "DOS", "ludicrous speed", "fishslapped", "afk" }, + *kick[] = { "kicked", "banned", "kicked by vote", "banned by vote", "kicked by server operator", "banned by server operator", + "auto kicked - teamkilling", "auto banned - teamkilling", "auto kicked - friendly fire", "auto banned - friendly fire", "auto kick", "auto ban" }; + static string res; + const int prot_n = constarraysize(prot), ref_n = constarraysize(refused), rem_n = constarraysize(removed), kick_n = constarraysize(kick); + if(reason >= DISC_PROTOCOL && reason < DISC_REFUSED) + { // protocol related errors + if(reason < DISC_PROTOCOL + prot_n) return prot[reason]; + else formatstring(res)("network protocol error '%d'", reason); + } + else if(reason >= DISC_REFUSED && reason < DISC_REMOVED) + { // connection refused + if(reason < DISC_REFUSED + ref_n) return refused[reason - DISC_REFUSED]; + else formatstring(res)("connection refused '%d'", reason); + } + else if(reason >= DISC_REMOVED && reason < DISC_KICK) + { // player removed from server + if(reason < DISC_REMOVED + rem_n) return removed[reason - DISC_REMOVED]; + else formatstring(res)("removed from server '%d'", reason); + } + else if(reason >= DISC_KICK && reason < DISC_UNKNOWN) + { // kicks & bans + reason -= DISC_KICK; + if(reason < kick_n) return kick[reason]; + else formatstring(res)("%s '%d'", reason & 1 ? "banned" : "kicked", reason + DISC_KICK); + } + else + { // unknown reasons + formatstring(res)("unknown '%d'", reason); + } + return res; +} + diff --git a/source/src/protocol.h b/source/src/protocol.h index 725fb4c4..cee35f76 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -73,7 +73,11 @@ enum { EDITXY_HEIGHT = 0, EDITXY_TEX, EDITXY_TYPE, EDITXY_VDELTA, EDITXY_EQUALIS #define DNF 100.0f #define DVELF 4.0f -enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_MKICK, DISC_MBAN, DISC_TAGT, DISC_BANREFUSE, DISC_WRONGPW, DISC_SOPLOGINFAIL, DISC_MAXCLIENTS, DISC_MASTERMODE, DISC_AUTOKICK, DISC_AUTOBAN, DISC_DUP, DISC_BADNICK, DISC_OVERFLOW, DISC_ABUSE, DISC_AFK, DISC_FFIRE, DISC_CHEAT, DISC_NUM }; +enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, // div. protocol errors + DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, // connection refused + DISC_REMOVED = 32, DISC_SOPLOGINFAIL, DISC_BADNICK, DISC_ANNOY, DISC_SPAM, DISC_DOS, DISC_SPEEDING, DISC_FISHSLAP, DISC_AFK, // disconnected + DISC_KICK = 48, DISC_BAN, DISC_VOTEKICK, DISC_VOTEBAN, DISC_OPKICK, DISC_OPBAN, DISC_TKKICK, DISC_TKBAN, DISC_FFKICK, DISC_FFBAN, DISC_AUTOKICK, DISC_AUTOBAN, + DISC_UNKNOWN = 99, DISC_BECAUSE }; enum { BAN_NONE = 0, BAN_VOTE, BAN_AUTO, BAN_BLACKLIST, BAN_MASTER }; #define EXT_ACK -1 diff --git a/source/src/protos.h b/source/src/protos.h index a90ac798..85b318b9 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1060,11 +1060,12 @@ extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof( extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); extern void filterlang(char *d, const char *s); extern void trimtrailingwhitespace(char *s); +extern int msgsizelookup(int msg); +extern const char *disc_reason(int reason); extern string mastername; extern int masterport; extern ENetSocket connectmaster(); extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm); -extern int msgsizelookup(int msg); extern const char *genpwdhash(const char *name, const char *pwd, int salt); extern void servermsinit(const char *master, const char *ip, int serverport, bool listen); extern bool serverpickup(int i, int sender); diff --git a/source/src/server.cpp b/source/src/server.cpp index 59128ab5..9ce01588 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -2191,7 +2191,7 @@ void addgban(const char *name) { client &c = *clients[i]; if(c.type!=ST_TCPIP) continue; - if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANREFUSE); + if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANNED); } } @@ -2472,12 +2472,6 @@ void senddisconnectedscores(int cn) sendpacket(cn, 1, p.finalize()); } -const char *disc_reason(int reason) -{ - static const char *disc_reasons[] = { "normal", "error - end of packet", "error - client num", "vote-kicked from the server", "vote-banned from the server", "error - tag type", "connection refused - you have been banned from this server", "incorrect password", "unsuccessful administrator login", "the server is FULL - try again later", "servers mastermode is \"private\" - wait until the servers mastermode is \"open\"", "auto-kick - your score dropped below the servers threshold", "auto-ban - your score dropped below the servers threshold", "duplicate connection", "inappropriate nickname", "error - packet flood", "auto-kick - excess spam detected", "auto-kick - inactivity detected", "auto-kick - team killing detected", "auto-kick - abnormal client behavior detected" }; - return reason >= 0 && (size_t)reason < sizeof(disc_reasons)/sizeof(disc_reasons[0]) ? disc_reasons[reason] : "unknown"; -} - void disconnect_client(int n, int reason) { if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; @@ -2854,9 +2848,9 @@ void process(ENetPacket *packet, int sender, int chan) } else disconnect_client(sender, DISC_WRONGPW); } - else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE); + else if(srvprivate) disconnect_client(sender, DISC_PRIVATE); else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS); - else if(banned) disconnect_client(sender, DISC_BANREFUSE); + else if(banned) disconnect_client(sender, DISC_BANNED); else { cl->isauthed = true; @@ -2952,7 +2946,7 @@ void process(ENetPacket *packet, int sender, int chan) if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -2994,7 +2988,7 @@ void process(ENetPacket *packet, int sender, int chan) if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -3031,7 +3025,7 @@ void process(ENetPacket *packet, int sender, int chan) if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -3148,7 +3142,7 @@ void process(ENetPacket *packet, int sender, int chan) { ++cl->fastprofileupdates; if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam", sender); - if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_ABUSE); break; } + if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_SPAM); break; } } else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0; cl->lastprofileupdate = servmillis; @@ -3192,7 +3186,7 @@ void process(ENetPacket *packet, int sender, int chan) { ++cl->fastprofileupdates; if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam", sender); - if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_ABUSE); + if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_SPAM); } else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0; cl->lastprofileupdate = servmillis; diff --git a/source/src/serveractions.h b/source/src/serveractions.h index e9f6c5e6..3d6e904a 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -30,7 +30,7 @@ void kick_abuser(int cn, int &cmillis, int &count, int limit) if ( count <= 0 ) count = 1; } cmillis = servmillis; - if( count >= limit ) disconnect_client(cn, DISC_ABUSE); + if( count >= limit ) disconnect_client(cn, DISC_SPAM); } struct mapaction : serveraction @@ -188,7 +188,7 @@ struct giveadminaction : playeraction struct kickaction : playeraction { bool wasvalid; - void perform() { disconnect(DISC_MKICK); } + void perform() { disconnect(DISC_VOTEKICK); } virtual bool isvalid() { return wasvalid || playeraction::isvalid(); } kickaction(int cn, char *reason) : playeraction(cn) { @@ -208,7 +208,7 @@ struct banaction : playeraction void perform() { int i = findcnbyaddress(&address); - if(i >= 0) addban(clients[i], DISC_MBAN, BAN_VOTE); + if(i >= 0) addban(clients[i], DISC_VOTEBAN, BAN_VOTE); } virtual bool isvalid() { return wasvalid || playeraction::isvalid(); } banaction(int cn, char *reason) : playeraction(cn) diff --git a/source/src/serverchecks.h b/source/src/serverchecks.h index d263f717..15d3c9e8 100644 --- a/source/src/serverchecks.h +++ b/source/src/serverchecks.h @@ -467,7 +467,7 @@ void check_ffire(client *target, client *actor, int damage) logline(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); defformatstring(msg)("%s %s", actor->name, "kicked for excessive friendly fire"); sendservmsg(msg); - disconnect_client(actor->clientnum, DISC_FFIRE); + disconnect_client(actor->clientnum, DISC_FFKICK); } } diff --git a/source/src/tools.h b/source/src/tools.h index fffc93b2..cd900560 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -66,6 +66,7 @@ inline int iabs(int n) { return labs(n); } #define rnd(x) ((int)(randomMT()&0xFFFFFF)%(x)) #define rndscale(x) (float((randomMT()&0xFFFFFF)*double(x)/double(0xFFFFFF))) #define detrnd(s, x) ((int)(((((uint)(s))*1103515245+12345)>>16)%(x))) +#define constarraysize(x) ((int)(sizeof(x)/sizeof(*x))) #define loop(v,m) for(int v = 0; v Date: Sat, 27 Feb 2016 22:51:25 +0100 Subject: [PATCH 04/45] fix parsing of commandline switches for servers also: * -Y now sets the private key for the server * -m without argument uses default masterserver URL * servers run LAN-only by default (set ms URL and private key to register with the master) * use generic locale "POSIX" --- config/servercmdline.txt | 3 +- source/src/clientgame.cpp | 3 -- source/src/main.cpp | 9 ++--- source/src/platform.h | 1 + source/src/protos.h | 27 +++++++++++--- source/src/server.cpp | 68 +++++++++++++---------------------- source/src/serverbrowser.cpp | 5 ++- source/src/servercontroller.h | 21 +++-------- source/src/serverms.cpp | 28 ++++++++------- 9 files changed, 75 insertions(+), 90 deletions(-) diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 436bb4a4..26f5bbab 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -22,6 +22,8 @@ // -f // port, 1..65534, default 28763 // -c // max client number, 1..16, default 6 // -D // number of demos to keep in RAM, default 5 +// -Y // 32-byte private server key (you should maybe include that from another config file and remove it from the server after startup) +// -m // masterserver URL (no parameter uses default URL), if not set, the server will be LAN-only // the switches o, n, n1 and n2 support coloring and character escaping; 'o' also supports escaped newlines // the available colors are: 0: green, 1: blue, 2: yellow, 3: red, 4: gray, 5: white, 6: dark brown, 7: dark red @@ -95,5 +97,4 @@ // -u // uprate // -i // ip, only for machines with multiple network interfaces -// -m // masterserver URL (exception: use "-m localhost", if you don't want AC to register at a masterserver at all) diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 010172ae..3b30f97d 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -45,15 +45,12 @@ void setskin(playerent *pl, int skin, int team) pl->setskin(team, skin); } -extern char *global_name; - bool duplicatename(playerent *d, char *name = NULL) { if(!name) name = d->name; if(d!=player1 && !strcmp(name, player1->name) && !watchingdemo) return true; if(!strcmp(name, "you")) return true; loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true; - global_name = player1->name; // this certainly is not the best place to put this return false; } diff --git a/source/src/main.cpp b/source/src/main.cpp index 6331e59d..46526c25 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -1012,6 +1012,7 @@ int main(int argc, char **argv) char *initscript = NULL; char *initdemo = NULL; + setlocale(LC_ALL, "POSIX"); if(bootclientlog) cvecprintf(*bootclientlog, "######## start logging: %s\n", timestring(true)); const char *initmap = rndmapname(); @@ -1134,8 +1135,8 @@ int main(int argc, char **argv) if(enet_initialize()<0) fatal("Unable to initialise network module"); if (!dedicated) initclient(); - //FIXME the server executed in this way does not catch the SIGTERM or ^C - initserver(dedicated,argc,argv); // never returns if dedicated + + initserver(dedicated); // never returns if dedicated initlog("world (" STRINGIFY(AC_VERSION) ")"); empty_world(7, true); @@ -1158,10 +1159,6 @@ int main(int argc, char **argv) int usedcolorbits = 0, useddepthbits = 0, usedfsaa = 0; setupscreen(usedcolorbits, useddepthbits, usedfsaa); - // no more TTF ATM. - //initlog("font"); - //initfont(); - initlog("video: misc"); SDL_WM_SetCaption("AssaultCube", NULL); diff --git a/source/src/platform.h b/source/src/platform.h index 78817729..44c43e64 100644 --- a/source/src/platform.h +++ b/source/src/platform.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #ifdef __GNUC__ #include diff --git a/source/src/protos.h b/source/src/protos.h index 85b318b9..bae58dfc 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -958,6 +958,9 @@ extern void tigerhash(uchar *hash, const uchar *msg, int len); extern void *tigerhash_init(uchar *hash); extern void tigerhash_add(uchar *hash, const void *msg, int len, void *state); extern void tigerhash_finish(uchar *hash, void *state); +extern const char *bin2hex(char *d, const uchar *s, int len); +extern int hex2bin(uchar *d, const char *s, int maxlen); +extern void ed25519_pubkey_from_private(uchar *pubkey, const uchar *privkey); extern void loadcertdir(); // load all certs in "config/certs" #if 0 // crypto // for AUTH @@ -1027,7 +1030,7 @@ extern const char *modestr(int n, bool acronyms = false); extern const char *voteerrorstr(int n); extern const char *mmfullname(int n); extern void fatal(const char *s, ...) PRINTFARGS(1, 2); -extern void initserver(bool dedicated, int argc = 0, char **argv = NULL); +extern void initserver(bool dedicated); extern void cleanupserver(); extern void localconnect(); extern void localdisconnect(); @@ -1067,7 +1070,7 @@ extern int masterport; extern ENetSocket connectmaster(); extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm); extern const char *genpwdhash(const char *name, const char *pwd, int salt); -extern void servermsinit(const char *master, const char *ip, int serverport, bool listen); +extern void servermsinit(bool listen); extern bool serverpickup(int i, int sender); extern bool valid_client(int cn); extern void extinfo_cnbuf(ucharbuf &p, int cn); @@ -1127,7 +1130,8 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service; + uchar *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -1137,7 +1141,8 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), + ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { @@ -1154,7 +1159,18 @@ struct servercommandline int ai = atoi(a); // client: dtwhzbsave switch(arg[1]) - { // todo: gjlqEGHJQUYZ + { // todo: gjlqEGHJQU + case 'Y': // private server key + { + uchar temp[64]; + if(hex2bin(temp, a, 32) == 32) + { + if(!ssk) ssk = new uchar[64]; + memcpy(ssk, temp, 32); + ed25519_pubkey_from_private(ssk + 32, ssk); + } + break; + } case '-': if(!strncmp(arg, "--demofilenameformat=", 21)) { @@ -1244,6 +1260,7 @@ struct servercommandline case 'P': concatstring(voteperm, a); break; case 'M': concatstring(mapperm, a); break; case 'Z': if(ai >= 0) incoming_limit = ai; break; + case 'S': service = a; break; case 'V': verbose++; break; case 'C': if(*a && clfilenesting < 3) { diff --git a/source/src/server.cpp b/source/src/server.cpp index 9ce01588..a4085cf8 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -99,7 +99,6 @@ vector demofiles; long int incoming_size = 0; // cmod -char *global_name; int totalclients = 0; int cn2boot; int servertime = 0, serverlagged = 0; @@ -4304,60 +4303,25 @@ void processmasterinput(const char *cmd, int cmdlen, const char *args) else if(sscanf(cmd, "addgban %s", val) == 1) addgban(val); } -string server_name = "unarmed server"; - void quitproc(int param) { // this triggers any "atexit"-calls: exit(param == 2 ? EXIT_SUCCESS : EXIT_FAILURE); // 3 is the only reply on Win32 apparently, SIGINT == 2 == Ctrl-C } -void initserver(bool dedicated, int argc, char **argv) +void initserver(bool dedicated) { - const char *service = NULL; - - for(int i = 1; iargc = argc; svcctrl->argv = argv; - svcctrl->start(); - } - } - - if ( strlen(scl.servdesc_full) ) global_name = scl.servdesc_full; - else global_name = server_name; - sg->smapname[0] = '\0'; + // start logging string identity; if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport); int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO); - if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp)) - printf("WARNING: logging not started!\n"); + if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp)) printf("WARNING: logging not started!\n"); logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); - copystring(sg->servdesc_current, scl.servdesc_full); - servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated); + servermsinit(dedicated); if((isdedicated = dedicated)) { @@ -4367,6 +4331,8 @@ void initserver(bool dedicated, int argc, char **argv) if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; + string pubkey; + if(scl.ssk) logline(ACLOG_VERBOSE,"server public key: %s", bin2hex(pubkey, scl.ssk + 32, 32)); maprot.init(scl.maprot); // maprot.next(false, true); // ensure minimum maprot length of '1' passwords.init(scl.pwdfile, scl.adminpasswd); @@ -4446,13 +4412,27 @@ int main(int argc, char **argv) #endif #endif - for(int i = 1; istart(); + } + + if(enet_initialize() < 0) fatal("Unable to initialise network module"); + initserver(true); return EXIT_SUCCESS; #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 5ce3d614..0f07cb46 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1306,7 +1306,6 @@ void clearservers() #define RETRIEVELIMIT 5000 -extern char *global_name; bool cllock = false, clfail = false; int progress_callback_retrieveservers(void *data, float progress) @@ -1330,7 +1329,7 @@ void retrieveservers(vector &data) if(h.set_host(mastername)) { formatstring(progresstext)("retrieving servers from %s:%d... (esc to abort)", mastername, masterport); - defformatstring(url)("/retrieve.do?action=list&name=%s&version=%d&build=%d", urlencode(global_name, true), AC_VERSION, getbuildtype()|(1<<16)); + defformatstring(url)("/retrieve.do?action=list&name=%s&version=%d&build=%d", urlencode(player1->name, true), AC_VERSION, getbuildtype()|(1<<16)); h.outvec = (vector *) &data; // ouch... show_out_of_renderloop_progress(0, progresstext); int got = h.get(url, RETRIEVELIMIT, RETRIEVELIMIT); @@ -1358,7 +1357,7 @@ void retrieveservers(vector &data) defformatstring(text)("retrieving servers from %s:%d... (esc to abort)", mastername, masterport); show_out_of_renderloop_progress(0, text); int starttime = SDL_GetTicks(), timeout = 0; - defformatstring(request)("list %s %d %d\n", global_name, AC_VERSION, getbuildtype()); + defformatstring(request)("list %s %d %d\n", player1->name, AC_VERSION, getbuildtype()); const char *req = request; int reqlen = strlen(req); ENetBuffer buf; diff --git a/source/src/servercontroller.h b/source/src/servercontroller.h index 911e1655..44b729bd 100644 --- a/source/src/servercontroller.h +++ b/source/src/servercontroller.h @@ -19,11 +19,13 @@ struct winservice : servercontroller HANDLE stopevent; const char *name; - winservice(const char *name) : name(name) + winservice(const char *name, int _argc, char **_argv) : name(name) { - callbacks::svc = this; + callbacks::svc = this; statushandle = 0; - }; + argc = _argc; + argv = _argv; + } ~winservice() { @@ -109,25 +111,12 @@ struct winservice : servercontroller return main(argc, argv); } - struct callbacks { static winservice *svc; static int WINAPI main() { return svc->svcmain(); }; static void WINAPI requesthandler(DWORD request) { svc->requesthandler(request); }; }; - - /*void log(const char *msg, bool error) - { - HANDLE eventsrc = RegisterEventSource(NULL, "AC Server"); - if(eventsrc) - { - int eventid = ((error ? 0x11 : 0x1) << 10) & (0x1 << 9) & (FACILITY_NULL << 6) & 0x1; // TODO: create event definitions - LPCTSTR msgs[1] = { msg }; - int r = ReportEvent(eventsrc, error ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE, 0, 4, NULL, 1, 0, msgs, NULL); - DeregisterEventSource(eventsrc); - } - }*/ }; winservice *winservice::callbacks::svc = (winservice *)NULL; diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 5d335097..ea94fd76 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -16,6 +16,8 @@ int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remot } #endif +extern struct servercommandline scl; +bool usemaster = false; ENetSocket mastersock = ENET_SOCKET_NULL; ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY }; string mastername = AC_MASTER_URI; @@ -43,9 +45,8 @@ void disconnectmaster() ENetSocket connectmaster() { - if(!mastername[0]) return ENET_SOCKET_NULL; - extern servercommandline scl; - if(scl.maxclients>MAXCL) { logline(ACLOG_WARNING, "maxclient exceeded: cannot register"); return ENET_SOCKET_NULL; } + if(!mastername[0] || !usemaster) return ENET_SOCKET_NULL; + if(scl.maxclients > MAXCL) { logline(ACLOG_WARNING, "maxclient exceeded: cannot register"); return ENET_SOCKET_NULL; } if(masteraddress.host == ENET_HOST_ANY) { @@ -160,7 +161,6 @@ void flushmasterinput() else disconnectmaster(); } -extern char *global_name; extern int totalclients; // send alive signal to masterserver after 40 minutes of uptime and if currently in intermission (so theoretically <= 1 hour) @@ -169,8 +169,9 @@ static inline void updatemasterserver(int millis, int port, int _interm) { if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { - char servername[30]; memset(servername,'\0',30); filtertext(servername, global_name, FTXT__GLOBALNAME, 20); - if(mastername[0]) requestmasterf("regserv %d %s %d\n", port, servername[0] ? servername : "noname", AC_VERSION); + string servdesc, pubkey; + filtertext(servdesc, scl.servdesc_full[0] ? scl.servdesc_full : "noname", FTXT__GLOBALNAME, 20); + if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? bin2hex(pubkey, scl.ssk + 32, 32) : "nokey", AC_VERSION); lastupdatemaster = millis + 1; } } @@ -325,20 +326,21 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput(); } -// this function should be made better, because it is used just ONCE (no need of so much parameters) -void servermsinit(const char *master, const char *ip, int infoport, bool listen) +void servermsinit(bool listen) { - copystring(mastername, master); + if(scl.master && *scl.master) copystring(mastername, scl.master); + usemaster = !listen || (listen && scl.master && scl.ssk); // true: connect to master, false: LAN-only server disconnectmaster(); if(listen) { - ENetAddress address = { ENET_HOST_ANY, (enet_uint16)infoport }; - if(*ip) + ENetAddress address = { ENET_HOST_ANY, (enet_uint16) CUBE_SERVINFO_PORT(scl.serverport) }; + if(*scl.ip) { - if(enet_address_set_host(&address, ip)<0) logline(ACLOG_WARNING, "server ip not resolved"); + if(enet_address_set_host(&address, scl.ip) < 0) logline(ACLOG_WARNING, "server ip \"%s\" not resolved", scl.ip); else serveraddress.host = address.host; } + pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) { @@ -347,6 +349,7 @@ void servermsinit(const char *master, const char *ip, int infoport, bool listen) } if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket"); else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); + address.port = CUBE_SERVINFO_PORT_LAN; lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) @@ -357,4 +360,5 @@ void servermsinit(const char *master, const char *ip, int infoport, bool listen) if(lansock == ENET_SOCKET_NULL) logline(ACLOG_WARNING, "could not create LAN server info socket"); else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); } + if(!usemaster) logline(ACLOG_INFO, "server is LAN-only - without connection to masterserver"); } From ac5229dfba9d7f2d4e67b84fbb207dcc94c373c2 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Aug 2016 16:23:54 +0200 Subject: [PATCH 05/45] server cleanup * restrict reading of config files to times when the server is empty * collect entropy * remove hint how to convert pre-1.0 blacklists --- config/serverblacklist.cfg | 1 - source/src/autodownload.cpp | 2 +- source/src/clientgame.cpp | 7 +++---- source/src/server.cpp | 15 +++++++-------- source/src/serverms.cpp | 1 + 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/config/serverblacklist.cfg b/config/serverblacklist.cfg index d4fef5cc..4c5f61c5 100644 --- a/config/serverblacklist.cfg +++ b/config/serverblacklist.cfg @@ -8,6 +8,5 @@ // 1.2.3.4/24 = Ban an IP block (1.2.3.0 - 1.2.3.255) // 1.2.3.4 - 2.3.4.5 = Ban an IP range. -// Hint: sed 's!#!//!g' old_blacklist.txt > new_blacklist.cfg diff --git a/source/src/autodownload.cpp b/source/src/autodownload.cpp index d2354050..43146e0f 100644 --- a/source/src/autodownload.cpp +++ b/source/src/autodownload.cpp @@ -76,7 +76,7 @@ int pingpckserver(void *data) // fetch updates.txt from a media server, measure u.set(s->host); s->resolved = h.set_host(u.domain) ? 1 : 0; SDL_mutexP(pckpinglog_lock); - cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ntohl(h.ip.host))); + cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ENET_NET_TO_HOST_32(h.ip.host))); SDL_mutexV(pckpinglog_lock); if(s->resolved) { diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 3b30f97d..c26b753a 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -953,10 +953,9 @@ void dokill(playerent *pl, playerent *act, bool gib, int gun) exechook(HOOK_SP, "onKill", "%d %d %d %d", act->clientnum, pl->clientnum, gun, gib ? 1 : 0); - string pname, aname, death; - copystring(pname, pl==player1 ? "you" : colorname(pl)); - copystring(aname, act==player1 ? "you" : colorname(act)); - copystring(death, killmessage(gun, gib)); + const char *pname = pl == player1 ? "you" : colorname(pl); + const char *aname = act == player1 ? "you" : colorname(act); + const char *death = killmessage(gun, gib); void (*outf)(const char *s, ...) = (pl == player1 || act == player1) ? hudoutf : conoutf; if(pl==act) diff --git a/source/src/server.cpp b/source/src/server.cpp index a4085cf8..0872599a 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -101,7 +101,6 @@ long int incoming_size = 0; // cmod int totalclients = 0; int cn2boot; -int servertime = 0, serverlagged = 0; // synchronising the worker threads... @@ -3982,8 +3981,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo int diff = nextmillis - servmillis; sg->gamemillis += diff; servmillis = nextmillis; - servertime = ((diff + 3 * servertime)>>2); - if (servertime > 40) serverlagged = servmillis; + entropy_add_byte(diff); #ifndef STANDALONE if(m_demo) @@ -3994,7 +3992,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo } #endif - if(sg->minremain>0) + if(sg->minremain > 0) { processevents(); checkitemspawns(diff); @@ -4016,9 +4014,8 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog if(m_arena) arenacheck(); else if(m_autospawn) autospawncheck(); -// if(m_lms) lmscheck(); sendextras(); - if ( scl.afk_limit && sg->mastermode == MM_OPEN && next_afk_check < servmillis && sg->gamemillis > 20 * 1000 ) check_afk(); + if(scl.afk_limit && sg->mastermode == MM_OPEN && next_afk_check < servmillis && sg->gamemillis > 20 * 1000) check_afk(); } if(curvote) @@ -4029,7 +4026,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo int nonlocalclients = numnonlocalclients(); - if(sg->forceintermission || ((sg->smode>1 || (gamemode==0 && nonlocalclients)) && sg->gamemillis-diff>0 && sg->gamemillis/60000!=(sg->gamemillis-diff)/60000)) + if(sg->forceintermission || ((sg->smode > 1 || (gamemode == 0 && nonlocalclients)) && sg->gamemillis-diff > 0 && sg->gamemillis / 60000 != (sg->gamemillis - diff) / 60000)) checkintermission(); if(m_demo && !demoplayback) maprot.restart(); else if(sg->interm && ( (scl.demo_interm && sending_demo) ? sg->gamemillis > (sg->interm<<1) : sg->gamemillis > sg->interm ) ) @@ -4066,7 +4063,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(servmillis - laststatus > 60 * 1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; - rereadcfgs(); + if(!nonlocalclients) rereadcfgs(); // configs are read from actual files - only attempt to do that, when the server is empty if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); @@ -4089,6 +4086,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(enet_host_service(serverhost, &event, timeout) <= 0) break; serviced = true; } + entropy_add_byte(event.type); switch(event.type) { case ENET_EVENT_TYPE_CONNECT: @@ -4413,6 +4411,7 @@ int main(int argc, char **argv) #endif setlocale(LC_ALL, "POSIX"); + entropy_init(time(NULL) + (uint)(size_t)&scl + (uint)(size_t)"" + (uint)(size_t)entropy_init + (int)enet_time_get()); for(int i = 1; i < argc; i++) { diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index ea94fd76..b52f77eb 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -318,6 +318,7 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli } buf.dataLength = len + po.length(); + entropy_add_byte(addr.host ^ (addr.host >> 24)); enet_socket_send(pongsock, &addr, &buf, 1); if(std) *msend += (int)buf.dataLength; else *csend += (int)buf.dataLength; From 63668e0ddaff02b9d9310c062e52106629559c47 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 25 Feb 2016 22:01:13 +0100 Subject: [PATCH 06/45] add support for a geolocation table including two converter scripts to use Geolite2 and Webnet77 tables --- config/geoip.cfg | 19 +++++ config/servercmdline.txt | 1 + source/src/protos.h | 7 +- source/src/server.cpp | 7 ++ source/src/server.h | 2 + source/src/serverfiles.h | 88 ++++++++++++++++++++ source/src/tools.cpp | 7 +- source/src/tools.h | 1 + source/tools/conv_maxmind-geolite2_to_ac.awk | 61 ++++++++++++++ source/tools/conv_software77-geoip_to_ac.awk | 30 +++++++ 10 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 config/geoip.cfg create mode 100644 source/tools/conv_maxmind-geolite2_to_ac.awk create mode 100644 source/tools/conv_software77-geoip_to_ac.awk diff --git a/config/geoip.cfg b/config/geoip.cfg new file mode 100644 index 00000000..50d2b27f --- /dev/null +++ b/config/geoip.cfg @@ -0,0 +1,19 @@ +// geoip table +// =========== +// WARNING: You can NOT use CubeScript in here!! + +// The following schemes are accepted... + +// 1.2.3.4 CC +// 1.2.3.4/24 CC +// 1.2.3.4 - 2.3.4.5 CC +// 38273024 CC +// 38273024/24 CC +// 38273024-38797311 CC +// (with CC being ISO 3166-1 alpha-2 country codes) +// +// check source/tools/ for scripts to convert geoip lists from various free sources +// + + +192.168.0.0/16 XH // home diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 26f5bbab..160fd56f 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -40,6 +40,7 @@ // -r // server maprot file, default config/maprot.cfg // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg +// -G // server geoip file, default config/geoip.cfg // -K // server nickname blacklist file, default config/nicknameblacklist.cfg // -g // server forbidden words file, default config/forbidden.cfg // -P // voting permission string (read docs/commandline.html) diff --git a/source/src/protos.h b/source/src/protos.h index bae58dfc..83c0535c 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1130,7 +1130,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service; uchar *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; @@ -1140,7 +1140,7 @@ struct servercommandline servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), - maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), nbfile("config/nicknameblacklist.cfg"), + maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), @@ -1159,7 +1159,7 @@ struct servercommandline int ai = atoi(a); // client: dtwhzbsave switch(arg[1]) - { // todo: gjlqEGHJQU + { // todo: gjlqEHJQU case 'Y': // private server key { uchar temp[64]; @@ -1240,6 +1240,7 @@ struct servercommandline case 'r': maprot = a; break; case 'X': pwdfile = a; break; case 'B': blfile = a; break; + case 'G': geoipfile = a; break; case 'K': nbfile = a; break; case 'I': infopath = a; break; case 'o': filterrichtext(motd, a); break; diff --git a/source/src/server.cpp b/source/src/server.cpp index 0872599a..35e63c73 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -79,6 +79,7 @@ servercontroller *svcctrl = NULL; servercommandline scl; servermaprot maprot; serveripblacklist ipblacklist; +serveripcclist geoiplist; servernickblacklist nickblacklist; serverforbiddenlist forbiddenlist; serverpasswords passwords; @@ -3845,6 +3846,7 @@ void rereadcfgs(void) { maprot.read(); ipblacklist.read(); + geoiplist.read(); nickblacklist.read(); forbiddenlist.read(); passwords.read(); @@ -4100,6 +4102,10 @@ void serverslice(uint timeout) // main server update, called from cube main lo c.salt = rnd(0x1000000)*((servmillis%1000)+1); char hn[1024]; copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); + c.ip = ENET_NET_TO_HOST_32(c.peer->address.host); + const char *gi = geoiplist.check(c.ip); + copystring(c.country, gi ? gi : "--", 3); + entropy_add_byte(c.ip); logline(ACLOG_INFO,"[%s] client connected", c.hostname); sendservinfo(c); totalclients++; @@ -4335,6 +4341,7 @@ void initserver(bool dedicated) // maprot.next(false, true); // ensure minimum maprot length of '1' passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); + geoiplist.init(scl.geoipfile); nickblacklist.init(scl.nbfile); forbiddenlist.init(scl.forbidden); infofiles.init(scl.infopath, scl.motdpath); diff --git a/source/src/server.h b/source/src/server.h index 25024b75..45dd1498 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -229,6 +229,8 @@ struct client // server side version of "dynent" type int clientnum; ENetPeer *peer; string hostname; + enet_uint32 ip; + char country[4]; string name; int team; char lang[3]; diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index db30fd69..2f342bf0 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -714,6 +714,94 @@ struct servermaprot : serverconfigfile configset *get(int ccs) { return configsets.inrange(ccs) ? &configsets[ccs] : NULL; } }; +// generic commented IP list + +struct serveripcclist : serverconfigfile +{ + vector ipranges; + + bool parsecomment(iprangecc &ipr, const char *l) + { + memset(ipr.cc, 0, 4); + l += strspn(l, " "); + loopi(3) if(isprint(l[i])) ipr.cc[i] = l[i]; else break; + return ipr.cc[0] && ipr.cc[1]; + } + + const char *printcomment(iprangecc &ipr) + { + return ipr.cc[3] == '\0' ? ipr.cc : "ERR"; + } + + void read() + { + if(getfilesize(filename) == filelen) return; + ipranges.shrink(0); + if(!load()) return; + + iprangecc ir; + int line = 0, errors = 0; + char *l, *r, *p = buf; + logline(ACLOG_VERBOSE,"reading ip list '%s'", filename); + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; line++; + if((r = (char *) atoipr(l, &ir)) && parsecomment(ir, r)) + ipranges.add(ir); + else + logline(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; + } + DELETEA(buf); + ipranges.sort(cmpiprange); + int orglength = ipranges.length(); + loopv(ipranges) + { + if(!i) continue; + if(ipranges[i].ur <= ipranges[i - 1].ur) + { + if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) + logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i])); + else + logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + ipranges.remove(i--); continue; + } + if(ipranges[i].lr <= ipranges[i - 1].ur) + { + if(ipranges[i].cu == ipranges[i - 1].cu) // same comment + { + logline(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + ipranges[i - 1].ur = ipranges[i].ur; + ipranges.remove(i--); continue; + } + else + { + logline(ACLOG_VERBOSE," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1]), + iprtoa(ipranges[i]), printcomment(ipranges[i]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + errors++; + ipranges.remove(i--); continue; + } + } + if(ipranges[i].lr - 1 == ipranges[i - 1].ur && ipranges[i].cu == ipranges[i - 1].cu) + { + logline(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + ipranges[i - 1].ur = ipranges[i].ur; + ipranges.remove(i--); continue; + } + } + loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i]), printcomment(ipranges[i])); + logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); + } + + const char *check(enet_uint32 ip) // ip: host byte order + { + iprangecc t, *res; + t.lr = ip; + t.ur = 0; + res = ipranges.search(&t, cmpipmatch); + return res ? printcomment(*res) : NULL; + } +}; + // serverblacklist.cfg struct serveripblacklist : serverconfigfile diff --git a/source/src/tools.cpp b/source/src/tools.cpp index ef7943a9..747f3305 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -466,7 +466,12 @@ const char *atoip(const char *s, enet_uint32 *ip) { unsigned int d[4]; int n; - if(!s || sscanf(s, "%u.%u.%u.%u%n", d, d + 1, d + 2, d + 3, &n) != 4) return NULL; + if(!s) return NULL; + if(sscanf(s, "%u.%u.%u.%u%n", d, d + 1, d + 2, d + 3, &n) != 4) + { + *ip = strtoul(s, (char **)&s, 0); // try single-integer IPs + return *ip > 0xffffff ? s : NULL; // require first octet to be non-zero to avoid misinterpreting faulty dotted IPs + } *ip = 0; loopi(4) { diff --git a/source/src/tools.h b/source/src/tools.h index cd900560..446c66f6 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -985,6 +985,7 @@ extern void entropy_add_block(const uchar *s, int len); extern void entropy_get(uchar *buf, int len); struct iprange { enet_uint32 lr, ur; }; +struct iprangecc : iprange { union { int ci; char cc[4]; enet_uint32 cu; }; }; extern const char *atoip(const char *s, enet_uint32 *ip); extern const char *atoipr(const char *s, iprange *ir); extern const char *iptoa(const enet_uint32 ip); diff --git a/source/tools/conv_maxmind-geolite2_to_ac.awk b/source/tools/conv_maxmind-geolite2_to_ac.awk new file mode 100644 index 00000000..035327d9 --- /dev/null +++ b/source/tools/conv_maxmind-geolite2_to_ac.awk @@ -0,0 +1,61 @@ +# convert Geolite2 lists for Assaultcube servers +# +# expects to process GeoLite2-Country-Locations-en.csv and GeoLite2-Country-Blocks-IPv4.csv (in that order) +# +# gawk -f conv_maxmind-geolite2_to_ac.awk GeoLite2-Country-Locations-en.csv GeoLite2-Country-Blocks-IPv4.csv > geoip.cfg +# +# note: the ISO 3166-1 alpha-2 list reserves XA..XZ for user-assigned code elements - we use xs, xp and xz here, see below +# +# download lists from http://dev.maxmind.com/geoip/geoip2/geolite2/ (please don't download too often: once a month should be enough) +# +# thanks to MaxMind: +# This product includes GeoLite2 data created by MaxMind, available from http://www.maxmind.com +# + +BEGIN { +FS="," # expect csv +getcc=0 +} + +# geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name +$1=="geoname_id" && $5=="country_iso_code" { + getcc=1 # use first line to switch mode + next +} + +# network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider +$1=="network" && $2=="geoname_id" { + getcc=0 # use first line to switch mode + next +} + +getcc==1 { + if ( length($5)==2 ) + cctab[$1]=$5 # has country_iso_code + else if ( length($3)==2 ) + cctab[$1]=tolower($3) # has continent_code (convert to lowercase to distinguish from country codes) + else + print "ERROR: no usable country code in line " $0 # should not occur + next +} + +getcc==0 { + if ( $2=="" ) { # pick geoname_id over represented_country_geoname_id over registered_country_geoname_id + if ( $4 == "" ) + cc=$3 + else + cc=$4 + } else { + cc=$2 + } + if ( cc=="" && $6!=0 ) + print $1 " xs" # use XS to mark satellite providers with no other country affiliation + else if ( cc=="" && $5!=0 ) + print $1 " xp" # use XP to mark anonymous proxies with no other country affiliation + else if ( cc in cctab ) + print $1 " " cctab[cc] + else + print $1 " xz // " $0 " not found" # use XZ to mark table conversion errors + next +} + diff --git a/source/tools/conv_software77-geoip_to_ac.awk b/source/tools/conv_software77-geoip_to_ac.awk new file mode 100644 index 00000000..5f641047 --- /dev/null +++ b/source/tools/conv_software77-geoip_to_ac.awk @@ -0,0 +1,30 @@ +# convert geo-ip lists from software77 for Assaultcube servers +# +# expects to process IPV4-CSV lists from Webnet77 (please consider donations, if you use their lists) +# +# gawk -f conv_software77-geoip_to_ac.awk IpToCountry.csv > geoip.cfg +# +# download lists from http://software77.net/geo-ip/ (please don't download too often: once a month should be enough) +# +# thanks to Webnet77: http://webnet77.com +# + +BEGIN { +FS="," # expect csv +getcc=0 +} + +function unquote(q) { + gsub("\"", "", q) + return q +} + +substr($1,1,1)=="#" { + next # comment +} + +# expect lines like "38273024","38797311","ripencc","1275523200","KZ","KAZ","Kazakhstan" +{ + print unquote($1) "-" unquote($2) " " unquote($5) " // " unquote($7) + #print unquote($1) "-" unquote($2) " " unquote($5) # use this to drop the comments +} From 7c54645785cac2e603253274db9893f07c82956c Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Feb 2016 10:14:47 +0100 Subject: [PATCH 07/45] concatenate consecutive identical geoip ranges silently slightly faster and wastes less RAM also: better detection of entry overlapping --- source/src/serverfiles.h | 43 +++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 2f342bf0..aa3f47a8 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -733,6 +733,11 @@ struct serveripcclist : serverconfigfile return ipr.cc[3] == '\0' ? ipr.cc : "ERR"; } + int cmpcomment(iprangecc &ipr1, iprangecc &ipr2) + { + return ipr1.ci - ipr2.ci; + } + void read() { if(getfilesize(filename) == filelen) return; @@ -740,14 +745,23 @@ struct serveripcclist : serverconfigfile if(!load()) return; iprangecc ir; - int line = 0, errors = 0; + int line = 0, errors = 0, concatd = 0; char *l, *r, *p = buf; logline(ACLOG_VERBOSE,"reading ip list '%s'", filename); while(p < buf + filelen) { - l = p; p += strlen(p) + 1; line++; - if((r = (char *) atoipr(l, &ir)) && parsecomment(ir, r)) - ipranges.add(ir); + l = p + strspn(p, " "); p += strlen(p) + 1; line++; + if(!l[0]) continue; + if((r = (char *) atoipr(l, &ir)) && parsecomment(ir, r)) + { + if(ipranges.length() && ipranges.last().ur == ir.lr - 1 && !cmpcomment(ipranges.last(), ir)) + { + ipranges.last().ur = ir.ur; // direct concatenation (for pre-sorted lists) + concatd++; + } + else + ipranges.add(ir); + } else logline(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; } @@ -755,19 +769,24 @@ struct serveripcclist : serverconfigfile ipranges.sort(cmpiprange); int orglength = ipranges.length(); loopv(ipranges) - { + { // make sure, ranges don't overlap - otherwise bsearch gets unpredictable if(!i) continue; if(ipranges[i].ur <= ipranges[i - 1].ur) { - if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i])); + if(cmpcomment(ipranges[i], ipranges[i - 1])) + logline(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i]), printcomment(ipranges[i]), iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1])); else - logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + { + if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) + logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i])); + else + logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + } ipranges.remove(i--); continue; } if(ipranges[i].lr <= ipranges[i - 1].ur) { - if(ipranges[i].cu == ipranges[i - 1].cu) // same comment + if(!cmpcomment(ipranges[i], ipranges[i - 1])) // same comment { logline(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; @@ -775,13 +794,13 @@ struct serveripcclist : serverconfigfile } else { - logline(ACLOG_VERBOSE," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1]), + logline(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i]), iprtoa(ipranges[i]), printcomment(ipranges[i])); errors++; ipranges.remove(i--); continue; } } - if(ipranges[i].lr - 1 == ipranges[i - 1].ur && ipranges[i].cu == ipranges[i - 1].cu) + if(ipranges[i].lr - 1 == ipranges[i - 1].ur && !cmpcomment(ipranges[i], ipranges[i - 1])) { logline(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; @@ -789,7 +808,7 @@ struct serveripcclist : serverconfigfile } } loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i]), printcomment(ipranges[i])); - logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); + logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } const char *check(enet_uint32 ip) // ip: host byte order From 1d1230544bc63c8187f0e1bca834c00e149c4887 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Feb 2016 11:11:58 +0100 Subject: [PATCH 08/45] speed up logging don't bother processing a line, if the level is too high for any queue --- source/src/log.cpp | 8 ++++++- source/src/protos.h | 1 + source/src/serverfiles.h | 45 +++++++++++++++++++++------------------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/source/src/log.cpp b/source/src/log.cpp index 47b2fc9b..8fb8dc4c 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -76,13 +76,19 @@ void exitlogging() enabled = false; } +bool logcheck(int level) +{ + return enabled && level >= 0 && level < ACLOG_NUM && (consolethreshold <= level || (fp && filethreshold <= level) || syslogthreshold <= level); +} + bool logline(int level, const char *msg, ...) { if(!enabled) return false; if(level < 0 || level >= ACLOG_NUM) return false; + bool logtocon = consolethreshold <= level, logtofile = fp && filethreshold <= level, logtosyslog = syslogthreshold <= level; + if(!(logtocon || logtofile || logtosyslog)) return false; defvformatstring(sf, msg, msg); filtertext(sf, sf, FTXT__LOG); - bool logtocon = consolethreshold <= level, logtofile = fp && filethreshold <= level, logtosyslog = syslogthreshold <= level; const char *ts = timestamp ? timestring(true, "%b %d %H:%M:%S ") : "", *ld = levelprefix[level]; char *p, *l = sf; do diff --git a/source/src/protos.h b/source/src/protos.h index 83c0535c..3bbf5854 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1109,6 +1109,7 @@ enum { ACLOG_DEBUG = 0, ACLOG_VERBOSE, ACLOG_INFO, ACLOG_WARNING, ACLOG_ERROR, A extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp); extern void exitlogging(); +extern bool logcheck(int level); extern bool logline(int level, const char *msg, ...) PRINTFARGS(2, 3); // server config diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index aa3f47a8..8140eda2 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -807,7 +807,7 @@ struct serveripcclist : serverconfigfile ipranges.remove(i--); continue; } } - loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i]), printcomment(ipranges[i])); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i]), printcomment(ipranges[i])); logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } @@ -873,7 +873,7 @@ struct serveripblacklist : serverconfigfile ipranges.remove(i--); continue; } } - loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i])); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i])); logline(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); } @@ -978,29 +978,32 @@ struct servernickblacklist : serverconfigfile line++; } DELETEA(buf); - logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); - string text; - enumeratekt(whitelist, const char *, key, int, idx, + if(logcheck(ACLOG_VERBOSE)) { - text[0] = '\0'; - for(int i = idx; i >= 0; i = whitelistranges[i].next) + logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); + string text; + enumeratekt(whitelist, const char *, key, int, idx, { - iprchain &ic = whitelistranges[i]; - if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); - else concatformatstring(text, " %s", iprtoa(ic.ipr)); - } - logline(ACLOG_VERBOSE, " accept %s%s", key, text); - }); - logline(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); - loopv(blacklines) - { - text[0] = '\0'; - loopj(MAXNICKFRAGMENTS) + text[0] = '\0'; + for(int i = idx; i >= 0; i = whitelistranges[i].next) + { + iprchain &ic = whitelistranges[i]; + if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); + else concatformatstring(text, " %s", iprtoa(ic.ipr)); + } + logline(ACLOG_VERBOSE, " accept %s%s", key, text); + }); + logline(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); + loopv(blacklines) { - int k = blacklines[i].frag[j]; - if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); } + text[0] = '\0'; + loopj(MAXNICKFRAGMENTS) + { + int k = blacklines[i].frag[j]; + if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); } + } + logline(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } - logline(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } logline(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors); } From 1c109bfa62681f289a983c5da407f1a90df3e44f Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Feb 2016 18:01:48 +0100 Subject: [PATCH 09/45] new commandline parameter to specify path for server logfiles use -H to set path default is logs/ --- .gitignore | 1 + config/servercmdline.txt | 1 + source/src/log.cpp | 5 +++-- source/src/protos.h | 9 +++++---- source/src/server.cpp | 5 +++-- source/src/serverfiles.h | 9 ++------- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 349c36e6..bcd9169f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ clientlog*.txt dumpmapconfig.txt packetlog.txt profile/* +logs/* diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 160fd56f..3bce2d98 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -37,6 +37,7 @@ // -kA // kickthreshold (in seconds) for being AFK, > 30, default 45 // -kB // time in minutes a banned player will stay banned, default 20 // -y // banthreshold, -1..-100, default -6 +// -H // server logfile path, default logs/ // -r // server maprot file, default config/maprot.cfg // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg diff --git a/source/src/log.cpp b/source/src/log.cpp index 8fb8dc4c..5337466e 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -32,7 +32,7 @@ static int facility = -1, consolethreshold = ACLOG_INFO; static bool timestamp = false, enabled = false; -bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp) +bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath) { facility = facility_; timestamp = logtimestamp; @@ -49,7 +49,8 @@ bool initlogging(const char *identity, int facility_, int consolethres, int file if((logsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM)) == ENET_SOCKET_NULL || enet_address_set_host(&logdest, "localhost") < 0) syslogthreshold = ACLOG_NUM; #endif } - formatstring(filepath)("serverlog_%s_%s.txt", timestring(true), identity); + formatstring(filepath)("%sserverlog_%s_%s.txt", logfilepath, timestring(true), identity); + path(filepath); if(fp) { fclose(fp); fp = NULL; } if(filethreshold < ACLOG_NUM) { diff --git a/source/src/protos.h b/source/src/protos.h index 3bbf5854..2023b18d 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1107,7 +1107,7 @@ extern void enddemoplayback(); enum { ACLOG_DEBUG = 0, ACLOG_VERBOSE, ACLOG_INFO, ACLOG_WARNING, ACLOG_ERROR, ACLOG_NUM }; -extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp); +extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath); extern void exitlogging(); extern bool logcheck(int level); extern bool logline(int level, const char *msg, ...) PRINTFARGS(2, 3); @@ -1131,7 +1131,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath; uchar *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; @@ -1142,7 +1142,7 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) @@ -1160,7 +1160,7 @@ struct servercommandline int ai = atoi(a); // client: dtwhzbsave switch(arg[1]) - { // todo: gjlqEHJQU + { // todo: gjlqEJQU case 'Y': // private server key { uchar temp[64]; @@ -1208,6 +1208,7 @@ struct servercommandline case 'S': syslogthres = atoi(a + 1); break; } break; + case 'H': logfilepath = a; break; case 'A': if(*a) adminonlymaps.add(a); break; case 'c': if(ai > 0) maxclients = min(ai, MAXCLIENTS); break; case 'k': diff --git a/source/src/server.cpp b/source/src/server.cpp index 35e63c73..bbecd61c 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -4322,7 +4322,7 @@ void initserver(bool dedicated) if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport); int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO); - if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp)) printf("WARNING: logging not started!\n"); + if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp, scl.logfilepath)) printf("WARNING: logging not started!\n"); logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); copystring(sg->servdesc_current, scl.servdesc_full); servermsinit(dedicated); @@ -4387,7 +4387,8 @@ void initserver(bool dedicated) // start file-IO threads readmapsthread_sem = new sl_semaphore(0, NULL); - sl_createthread(readmapsthread, (void *)"xxxx"); + defformatstring(readmapslogfilename)("%sreadmaps_log.txt", scl.logfilepath); + sl_createthread(readmapsthread, (void *)path(readmapslogfilename)); for(;;) serverslice(5); } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 8140eda2..b3b011b9 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -473,7 +473,7 @@ void tagmapfile(const char *fpath, const char *fname, int epoch) // tag list en if(mapfileindex >= 0) mapfilenames[mapfileindex].epoch = epoch; } -int readmapsthread(void *logfileprefix) +int readmapsthread(void *logfilename) { static int readmaps_epoch = 0; @@ -481,12 +481,7 @@ int readmapsthread(void *logfileprefix) { while(!startnewservermapsepoch) readmapsthread_sem->wait(); // wait without using the cpu - if(logfileprefix) - { - defformatstring(logfilename)("%sreadmaps_log.txt", (const char *)logfileprefix); - path(logfilename); - readmaplog = openfile(logfilename, "a"); - } + if(logfilename) readmaplog = openfile((const char *)logfilename, "a"); vector maps_off, maps_serv, maps_incom; From 5ec04b82e16ae2df47bab9d11c9ffc8099c68406 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 27 Feb 2016 15:45:55 +0100 Subject: [PATCH 10/45] delegate checking and loading server config files to own thread "readserverconfigsthread" gets triggered by poll_serverthreads() once a minute to check the filesizes of all server config files. Altered files get loaded into memory. poll_serverthreads() in the main thread then processes the buffered files one at a time. During processing, a semaphore is locked, so that other threads can use the data structures derived from config files - if they use the semaphore to lock the ressource. Using the data from main thread can be done without using the semaphore, since _all_ changes are done in the main thread. If the main thread wants to update data that is locked by a thread, the main thread will defer the changes for a minute. The main thread will never lock. --- source/src/protos.h | 11 ++-- source/src/server.cpp | 42 +++++++-------- source/src/serverfiles.h | 107 +++++++++++++++++++++++++-------------- source/src/tools.h | 2 +- 4 files changed, 98 insertions(+), 64 deletions(-) diff --git a/source/src/protos.h b/source/src/protos.h index 2023b18d..7fe06307 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1119,12 +1119,17 @@ struct serverconfigfile string filename; int filelen; char *buf; - serverconfigfile() : filelen(0), buf(NULL) { filename[0] = '\0'; } + sl_semaphore busy; + serverconfigfile() : filelen(0), buf(NULL), busy(1, NULL) { filename[0] = '\0'; } virtual ~serverconfigfile() { DELETEA(buf); } + virtual void clear() {} virtual void read() {} - void init(const char *name); + void init(const char *name, bool tracking = true); bool load(); + bool isbusy(); + void trypreload(); + void process(); }; // server commandline parsing @@ -1268,7 +1273,7 @@ struct servercommandline case 'C': if(*a && clfilenesting < 3) { serverconfigfile cfg; - cfg.init(a); + cfg.init(a, false); cfg.load(); int line = 1; clfilenesting++; diff --git a/source/src/server.cpp b/source/src/server.cpp index bbecd61c..9167b02b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -108,7 +108,7 @@ int cn2boot; void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; - static int stage = 0, lastworkerthreadstart = 0; + static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; switch(stage) { @@ -158,24 +158,28 @@ void poll_serverthreads() // called once per mainloop-timeslice } break; } - - case 2: // wake readconfigsthread -#if 0 + case 2: // wake readserverconfigsthread { - readallconfigfiles = true; - readconfigsthread_sem.post(); + recheckallserverconfigs = true; + nextserverconfig = 0; + readserverconfigsthread_sem->post(); stage++; break; } - - case 3: // readconfigsthread + case 3: // readserverconfigsthread { - stage++; + if(!recheckallserverconfigs) + { + if(serverconfigs.inrange(nextserverconfig)) + { + serverconfigs[nextserverconfig]->process(); // processing of config files is done in mainloop, one at a time + nextserverconfig++; + } + else stage++; + } break; } - case 4: // pause worker threads for a while (restart once a minute) -#endif { if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) @@ -3842,16 +3846,6 @@ void sendworldstate() if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent } -void rereadcfgs(void) -{ - maprot.read(); - ipblacklist.read(); - geoiplist.read(); - nickblacklist.read(); - forbiddenlist.read(); - passwords.read(); -} - void loggamestatus(const char *reason) { int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}; @@ -4049,7 +4043,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(!isdedicated) return; // below is network only - poll_serverthreads(); + poll_serverthreads(); // read config and map files in the background, process the results in the main thread serverms(sg->smode, numclients(), sg->minremain, sg->smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION, sg->servdesc_current, sg->interm); @@ -4065,7 +4059,6 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(servmillis - laststatus > 60 * 1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; - if(!nonlocalclients) rereadcfgs(); // configs are read from actual files - only attempt to do that, when the server is empty if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); @@ -4390,6 +4383,9 @@ void initserver(bool dedicated) defformatstring(readmapslogfilename)("%sreadmaps_log.txt", scl.logfilepath); sl_createthread(readmapsthread, (void *)path(readmapslogfilename)); + readserverconfigsthread_sem = new sl_semaphore(0, NULL); + sl_createthread(readserverconfigsthread, NULL); + for(;;) serverslice(5); } } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index b3b011b9..72b7278b 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -565,11 +565,16 @@ bool serverwritemap(const char *mapname, int mapsize, int cfgsize, int cfgsizegz // server config files -void serverconfigfile::init(const char *name) +vector serverconfigs; // list of server config files, automatically polled in the background +volatile bool recheckallserverconfigs = false; // signal readserverconfigsthread to poll all files +sl_semaphore *readserverconfigsthread_sem = NULL; // sync readmapsthread with main thread + +void serverconfigfile::init(const char *name, bool tracking) { copystring(filename, name); path(filename); - read(); + clear(); + if(tracking) serverconfigs.add(this); } bool serverconfigfile::load() @@ -581,6 +586,7 @@ bool serverconfigfile::load() logline(ACLOG_INFO,"could not read config file '%s'", filename); return false; } + entropy_add_block((const uchar *)buf, filelen); char *p; if('\r' != '\n') // this is not a joke! { @@ -596,6 +602,41 @@ bool serverconfigfile::load() return true; } +bool serverconfigfile::isbusy() +{ + return busy.getvalue() < 1; +} + +void serverconfigfile::trypreload() // in readserverconfigsthread: load changed files into RAM +{ + if(!buf && !isbusy() && getfilesize(filename) != filelen && !load()) buf = newstring(""); +} + +void serverconfigfile::process() // in main thread: refill data structures with preloaded file content +{ + if(buf && !isbusy() && !busy.trywait()) + { + clear(); + if(strlen(buf)) read(); + DELETEA(buf); + busy.post(); + } +} + +int readserverconfigsthread(void *data) +{ + while(1) + { + while(!recheckallserverconfigs) readserverconfigsthread_sem->wait(); // wait without using the cpu + + loopv(serverconfigs) serverconfigs[i]->trypreload(); + + entropy_save(); + recheckallserverconfigs = false; + } + return 0; +} + // maprot.cfg #define CONFIG_MAXPAR 6 @@ -619,12 +660,13 @@ struct servermaprot : serverconfigfile servermaprot() : curcfgset(-1) {} + void clear() + { + configsets.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - configsets.shrink(0); - if(!load()) return; - const char *sep = ": "; configset c; int i, line = 0; @@ -651,9 +693,7 @@ struct servermaprot : serverconfigfile else logline(ACLOG_INFO," error in line %d, file %s", line, filename); } } - DELETEA(buf); logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); - return; } int next(bool notify = true, bool nochange = false) // load next maprotation set @@ -733,12 +773,13 @@ struct serveripcclist : serverconfigfile return ipr1.ci - ipr2.ci; } + void clear() + { + ipranges.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - ipranges.shrink(0); - if(!load()) return; - iprangecc ir; int line = 0, errors = 0, concatd = 0; char *l, *r, *p = buf; @@ -760,7 +801,6 @@ struct serveripcclist : serverconfigfile else logline(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; } - DELETEA(buf); ipranges.sort(cmpiprange); int orglength = ipranges.length(); loopv(ipranges) @@ -806,13 +846,16 @@ struct serveripcclist : serverconfigfile logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } - const char *check(enet_uint32 ip) // ip: host byte order + const char *check(enet_uint32 ip, bool lock = false) // ip: host byte order { + if(lock) busy.wait(); iprangecc t, *res; t.lr = ip; t.ur = 0; res = ipranges.search(&t, cmpipmatch); - return res ? printcomment(*res) : NULL; + const char *cc = res ? printcomment(*res) : NULL; + if(lock) busy.post(); + return cc; } }; @@ -822,12 +865,13 @@ struct serveripblacklist : serverconfigfile { vector ipranges; + void clear() + { + ipranges.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - ipranges.shrink(0); - if(!load()) return; - iprange ir; int line = 0, errors = 0; char *l, *r, *p = buf; @@ -847,7 +891,6 @@ struct serveripblacklist : serverconfigfile errors++; } } - DELETEA(buf); ipranges.sort(cmpiprange); int orglength = ipranges.length(); loopv(ipranges) @@ -895,7 +938,7 @@ struct servernickblacklist : serverconfigfile vector blacklines; vector blfraglist; - void destroylists() + void clear() { whitelistranges.setsize(0); enumeratek(whitelist, const char *, key, delete key); @@ -906,10 +949,6 @@ struct servernickblacklist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - destroylists(); - if(!load()) return; - const char *sep = " "; int line = 1, errors = 0; iprchain iprc; @@ -972,7 +1011,6 @@ struct servernickblacklist : serverconfigfile } line++; } - DELETEA(buf); if(logcheck(ACLOG_VERBOSE)) { logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); @@ -1105,7 +1143,7 @@ struct serverforbiddenlist : serverconfigfile int num; char entries[100][2][FORBIDDENSIZE+1]; // 100 entries and 2 words per entry is more than enough - void initlist() + void clear() { num = 0; memset(entries,'\0',2*100*(FORBIDDENSIZE+1)); @@ -1129,10 +1167,6 @@ struct serverforbiddenlist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - initlist(); - if(!load()) return; - char *l, *p = buf; logline(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); while(p < buf + filelen) @@ -1140,7 +1174,6 @@ struct serverforbiddenlist : serverconfigfile l = p; p += strlen(p) + 1; addentry(l); } - DELETEA(buf); } bool canspeech(char *s) @@ -1187,12 +1220,13 @@ struct serverpasswords : serverconfigfile serverconfigfile::init(name); } + void clear() + { + adminpwds.shrink(staticpasses); + } + void read() { - if(getfilesize(filename) == filelen) return; - adminpwds.shrink(staticpasses); - if(!load()) return; - pwddetail c; const char *sep = " "; int i, line = 1, par[ADMINPWD_MAXPAR]; @@ -1221,7 +1255,6 @@ struct serverpasswords : serverconfigfile } line++; } - DELETEA(buf); logline(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); } diff --git a/source/src/tools.h b/source/src/tools.h index 446c66f6..b882d5d4 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -1018,7 +1018,7 @@ struct sl_semaphore sl_semaphore(int init, int *errorcount); // init: initial semaphore value; errorcount: pointer to error counter for semaphore-related errors or NULL ~sl_semaphore(); void wait(); // blocks, until semaphore gets available - int trywait(); // returns 0, if semaphore was locked (like wait(), but returns !=0 instead of blocking) + int trywait(); // returns 0, if semaphore was successfully locked (like wait(), but returns !=0 instead of blocking) int timedwait(int howlongmillis); // like trywait(), but waits for a litte before returning failure int getvalue(); // returns current semaphore value void post(); // increments (unlocks) semaphore From e147000ba20396e1a9d910f8bcc5a0e25664cf94 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 28 Feb 2016 17:37:13 +0100 Subject: [PATCH 11/45] replace some more strtok also make sure, randomMT survives concurring use from different threads --- source/src/crypto.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index 53cef444..70e8bb15 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -369,13 +369,10 @@ void seedMT(uint seed) uint randomMT() { - int cur = mt_next; - if(++mt_next >= MT_N) - { - if(mt_next > MT_N) { seedMT(5489U + time(NULL)); cur = mt_next++; } - else mt_next = 0; - } - uint y = (mt_state[cur] & 0x80000000U) | (mt_state[mt_next] & 0x7FFFFFFFU); + int nxt = (mt_next + 1) % MT_N, cur = (nxt + MT_N -1) % MT_N; + if(mt_next >= MT_N) seedMT(5489U + time(NULL)); + mt_next = nxt; + uint y = (mt_state[cur] & 0x80000000U) | (mt_state[nxt] & 0x7FFFFFFFU); mt_state[cur] = y = mt_state[cur < MT_N - MT_M ? cur + MT_M : cur + MT_M - MT_N] ^ (y >> 1) ^ (-int(y & 1U) & MT_K); y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; @@ -669,12 +666,12 @@ void ed25519test(char *vectorfilename) // check ed25519 functions with test vec path(vectorfilename); stopwatch watch; int vectorfilesize = 0; - char *vectorfile = loadfile(vectorfilename, &vectorfilesize); + char *vectorfile = loadfile(vectorfilename, &vectorfilesize), *b; if(!vectorfile) { conoutf("could not read test vector file %s", vectorfilename); return; } conoutf("loaded %d bytes from %s", vectorfilesize, vectorfilename); watch.start(); int lines = 0, inputerrors = 0, pubfail = 0, signfail = 0, verifyfail = 0, verifyfail2 = 0; - for(char *l = strtok(vectorfile, "\n"); l; l = strtok(NULL, "\n")) + for(char *l = strtok_r(vectorfile, "\n", &b); l; l = strtok_r(NULL, "\n", &b)) { // a line consists of 32 bytes private key, 32 byte public key, ":", 32 byte public key (again), ":", message, ":", signed message, ":" char *vprivpub = l, *vpub = strchr(l, ':'), *vmsg, *vsmsg, hextemp[65]; int linelen = strlen(l), msglen = 0; @@ -885,14 +882,14 @@ void authsetup(char **args, int numargs) // set up private and public keys xor_block(prepriv, keyhash, preprivlen); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPREPRIVATECFGFILE; - char *oldfile = loadfile(fn, NULL); + char *oldfile = loadfile(fn, NULL), *b; stream *f = openfile(fn, "wb"); if(f) { f->printf("\n" "// remove this file from your computer immediately!\n" "// either print it out and delete it or move it to a thumbdrive.\n" "// YOU DO NOT NEED THIS FILE TO PLAY AC!\n" "\n"); - if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + if(oldfile) for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); f->printf("authsetup pre %s", bin2hex(hextemp, prepriv, preprivlen)); if(preprivpwdcfg) f->printf(" %s %u", bin2hex(hextemp, psalt, 16), preprivpwdcfg & ~1); f->printf("\n\n"); @@ -916,11 +913,11 @@ void authsetup(char **args, int numargs) // set up private and public keys xor_block(priv, keyhash, 32); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPRIVATECFGFILE; - char *oldfile = loadfile(fn, NULL); + char *oldfile = loadfile(fn, NULL), *b; stream *f = openfile(fn, "wb"); if(f) { - if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + if(oldfile) for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); f->printf("\nauthsetup priv %s", bin2hex(hextemp, priv, 32)); if(privpwdcfg) f->printf(" %s %u", bin2hex(hextemp, salt, 16), privpwdcfg); f->printf("\nauthsetup pub %s\n\n", bin2hex(hextemp, keyhash + 32, 32)); @@ -1008,7 +1005,7 @@ void authkey_(char **args, int numargs) // set up misc keys stream *f = openfile(AUTHKEYSCFGFILE, "wb"); if(f) { // don't really delete the old keys, just comment them out - for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + for(char *b, *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); delete f; } delete[] oldfile; @@ -1130,7 +1127,7 @@ bool cert::parse() // parse orgmsg[] and check the signature if(hex2bin(signature, workmsg + certheaderlen, 64) == 64) { // parse all lines certline line; - for(char *l = strtok(workmsg + certheaderlenfull, "\n\r"); l; l = strtok(NULL, "\n\r")) + for(char *b, *l = strtok_r(workmsg + certheaderlenfull, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) { // get comment line.comment = strstr(l, "//"); From b82a38b2e204cbb5a695748e8fe304b819ccc0de Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Aug 2016 16:25:20 +0200 Subject: [PATCH 12/45] read-ahead for server info files * poll and read motd and serverinfo along the other server config files * "en" language setting is now immutable --- config/motd_en.txt | 5 +- source/src/protocol.h | 3 +- source/src/server.cpp | 30 +++++++----- source/src/serverfiles.h | 101 ++++++++++++++++----------------------- 4 files changed, 62 insertions(+), 77 deletions(-) diff --git a/config/motd_en.txt b/config/motd_en.txt index f6396309..e6f02396 100644 --- a/config/motd_en.txt +++ b/config/motd_en.txt @@ -2,7 +2,6 @@ // ======================== // WARNING: You can NOT use CubeScript in here!! // -// This text file is read, whenever a the MOTD is sent to a client. // Comments are removed, also leading and trailing blanks and empty lines. // // Don't use more than 80 characters per line. You can use colors and TABs. @@ -14,7 +13,7 @@ // Y: light gray, Z: dark gray // // Example: -// BobsInstagibServer \f3play nicely!\n -// Coloring a motd is fun: \f0Green\f5 and another \fLgreen\f5 from the set.\n +// BobsInstagibServer \f3play nicely! +// Coloring a motd is fun: \f0Green\f5 and another \fLgreen\f5 from the set. // contact me at a@b.c diff --git a/source/src/protocol.h b/source/src/protocol.h index cee35f76..0c6f3490 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -213,7 +213,8 @@ enum #define FTXT__MDLATTR (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) #define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME) #define FTXT__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR) -#define FTXT__SERVERINFO (FTXT_ALLOWTAB) +#define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE) +#define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL) #define FTXT__MOTD (FTXT_TABTOBLANK) #define FTXT__CHAT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) #define FTXT__LOG (FTXT_NOCOLOR | FTXT_TABTOBLANK) diff --git a/source/src/server.cpp b/source/src/server.cpp index 9167b02b..ccee245b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -83,7 +83,8 @@ serveripcclist geoiplist; servernickblacklist nickblacklist; serverforbiddenlist forbiddenlist; serverpasswords passwords; -serverinfofile infofiles; +serverinfofile serverinfoinfo; +serverinfofile serverinfomotd; // server state bool isdedicated = false; @@ -2718,8 +2719,8 @@ void welcomepacket(packetbuf &p, int n) } putint(p, SV_SERVERMODE); putint(p, sendservermode(false)); - const char *motd = scl.motd[0] ? scl.motd : infofiles.getmotd(c ? c->lang : ""); - if(motd) + const char *motd = scl.motd[0] ? scl.motd : serverinfomotd.getmsg(); + if(*motd) { putint(p, SV_TEXT); sendstring(motd, p); @@ -4159,16 +4160,19 @@ void extping_namelist(ucharbuf &p) void extping_serverinfo(ucharbuf &pi, ucharbuf &po) { - char lang[3]; - lang[0] = tolower(getint(pi)); lang[1] = tolower(getint(pi)); lang[2] = '\0'; - const char *reslang = lang, *buf = infofiles.getinfo(lang); // try client language - if(!buf) buf = infofiles.getinfo(reslang = "en"); // try english - sendstring(buf ? reslang : "", po); - if(buf) + getint(pi); getint(pi); // dummy read language code + if(strlen(serverinfoinfo.getmsg())) { - for(const char *c = buf; *c && po.remaining() > MAXINFOLINELEN + 10; c += strlen(c) + 1) sendstring(c, po); - sendstring("", po); + sendstring("en", po); + int pos = 0; + string buf; + while(po.remaining() > MAXINFOLINELEN + 10) + { + sendstring(serverinfoinfo.getmsgline(buf, &pos), po); + if(!*buf) break; + } } + else sendstring("", po); } void extping_maprot(ucharbuf &po) @@ -4337,8 +4341,8 @@ void initserver(bool dedicated) geoiplist.init(scl.geoipfile); nickblacklist.init(scl.nbfile); forbiddenlist.init(scl.forbidden); - infofiles.init(scl.infopath, scl.motdpath); - infofiles.getinfo("en"); // cache 'en' serverinfo + serverinfoinfo.init(scl.infopath, MAXTRANS / 2); + serverinfomotd.init(scl.motdpath, MAXSTRLEN - 5); logline(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 72b7278b..f960672c 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1298,78 +1298,59 @@ struct serverpasswords : serverconfigfile #define MAXINFOLINELEN 100 // including color codes -struct serverinfofile +struct serverinfofile : serverconfigfile // plaintext info file, used for serverinfo and motd { - struct serverinfotext { const char *type; char lang[3]; char *info; int lastcheck; }; - vector serverinfotexts; - const char *infobase, *motdbase; + vector msg; + int maxlen; - void init(const char *info, const char *motd) { infobase = info; motdbase = motd; } - - char *readinfofile(const char *fnbase, const char *lang) + void init(const char *fnbase, int ml) { - defformatstring(fname)("%s_%s.txt", fnbase, lang); - path(fname); - int len, n; - char *c, *b, *s, *t, *buf = loadfile(fname, &len); - if(!buf) return NULL; - char *nbuf = new char[len + 2]; - for(t = nbuf, s = strtok_r(buf, "\n\r", &b); s; s = strtok_r(NULL, "\n\r", &b)) - { - c = strstr(s, "//"); - if(c) *c = '\0'; // strip comments - for(n = (int)strlen(s) - 1; n >= 0 && s[n] == ' '; n--) s[n] = '\0'; // strip trailing blanks - filterrichtext(t, s + strspn(s, " "), MAXINFOLINELEN); // skip leading blanks - n = (int)strlen(t); - if(n) t += n + 1; - } - *t = '\0'; - DELETEA(buf); - if(!*nbuf) DELETEA(nbuf); - logline(ACLOG_DEBUG,"read file \"%s\"", fname); - return nbuf; + maxlen = ml; + defformatstring(fname)("%s_en.txt", fnbase); + serverconfigfile::init(fname); } - const char *getinfocache(const char *fnbase, const char *lang) + void clear() { - serverinfotext sn = { fnbase, { 0, 0, 0 }, NULL, 0} , *s = &sn; - filterlang(sn.lang, lang); - if(!sn.lang[0]) return NULL; - loopv(serverinfotexts) - { - serverinfotext &si = serverinfotexts[i]; - if(si.type == s->type && !strcmp(si.lang, s->lang)) - { - if(servmillis - si.lastcheck > (si.info ? 15 : 45) * 60 * 1000) - { // re-read existing files after 15 minutes; search missing again after 45 minutes - DELETEA(si.info); - s = &si; - } - else return si.info; - } - } - s->info = readinfofile(fnbase, lang); - s->lastcheck = servmillis; - if(s == &sn) serverinfotexts.add(*s); - if(fnbase == motdbase && s->info) - { - char *c = s->info; - while(*c) { c += strlen(c); if(c[1]) *c++ = '\n'; } - if(strlen(s->info) > MAXSTRLEN) s->info[MAXSTRLEN] = '\0'; // keep MOTD at sane lengths - } - return s->info; + msg.shrink(0); } - const char *getinfo(const char *lang) + void read() { - return getinfocache(infobase, lang); + string s; + char *l, *p = buf; + logline(ACLOG_VERBOSE,"reading info text file '%s'", filename); + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; + ASSERT(MAXINFOLINELEN < MAXSTRLEN); + filtertext(s, l, FTXT__SERVERINFOLINE, MAXINFOLINELEN); + if(*s) cvecprintf(msg, "%s\n", s); + } + if(msg.length() > maxlen) + { + logline(ACLOG_VERBOSE,"info text file '%s' truncated: %d -> %d", filename, msg.length(), maxlen); + msg.shrink(maxlen); + } } - const char *getmotd(const char *lang) + const char *getmsg() { - const char *motd; - if(*lang && (motd = getinfocache(motdbase, lang))) return motd; - return getinfocache(motdbase, "en"); + if(!msg.length() || msg.last()) msg.add('\0'); // make sure, msg is terminated + return msg.getbuf(); + } + + char *getmsgline(string s, int *pos) + { + s[0] = '\0'; + if(msg.inrange(*pos)) + { + const char *p = getmsg() + *pos; + int len = strcspn(p, "\n") + 1; + copystring(s, p, len); + *pos += len; + } + return s; } }; From 7dc72aebb19ce367c02a0fe3178b8649cfba9e55 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 4 Mar 2016 12:31:56 +0100 Subject: [PATCH 13/45] add basic functions for game mode bitmasks --- source/src/protocol.cpp | 28 ++++++++++++++++++++++++++++ source/src/protos.h | 3 +++ source/src/server.cpp | 6 +++++- source/src/tools.cpp | 1 + 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 977c86d8..0b83f22f 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -399,6 +399,34 @@ const char *mmfullname(int n) { return (n>=0 && n < MM_NUM) ? mmfullnames[n] : " int defaultgamelimit(int gamemode) { return m_teammode ? 15 : 10; } +int gmode_possible(bool hasffaspawns, bool hasteamspawns, bool hasflags) // return bitmask of playable modes, according to existing spawn and flag entities +{ + return ((hasffaspawns ? GMMASK__FFASPAWN : 0) | (hasteamspawns ? GMMASK__TEAMSPAWN : 0)) & ~(hasflags ? 0 : GMMASK__FLAGENTS); +} + +int gmode_parse(const char *list) // convert a list of mode acronyms to a bitmask +{ + char *buf = newstring(list), *b; + int res = 0; + for(char *p = strtok_r(buf, "|", &b); p; p = strtok_r(NULL, "|", &b)) + { + loopi(GMODE_NUM) if(!strcasecmp(p, modeacronymnames[i + 1])) res |= 1 << i; + } + delete buf; + return res; +} + +char *gmode_enum(int gm, char *buf) // convert mode bitmask to string with sorted list of mode acronyms +{ + vector mas; + loopi(GMODE_NUM) if(gm & (1 << i)) mas.add(modeacronymnames[i + 1]); + mas.sort(stringsortignorecase); + buf[0] = '\0'; + loopv(mas) concatformatstring(buf, "%s%s", i ? "|" : "", mas[i]); + filtertext(buf, buf, FTXT_TOLOWER); + return buf; +} + static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { SV_SERVINFO, 5, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, diff --git a/source/src/protos.h b/source/src/protos.h index 7fe06307..c6a5a8ca 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -951,6 +951,9 @@ extern const char *docgetdesc(const char *name); extern const char *acronymmodestr(int n); extern const char *fullmodestr(int n); extern int defaultgamelimit(int gamemode); +extern int gmode_possible(bool hasffaspawns, bool hasteamspawns, bool hasflags); +extern int gmode_parse(const char *list); +extern char *gmode_enum(int gm, char *buf); // crypto #define TIGERHASHSIZE 24 diff --git a/source/src/server.cpp b/source/src/server.cpp index ccee245b..4b26721b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -2084,7 +2084,11 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) if(localmap) { localmap->load(); - if(localmap->isok) sm = localmap; + if(localmap->isok) + { + sm = localmap; + if(!(sm->entstats.modes_possible & 1 << sg->smode)) conoutf("\f3map %s does not support game mode %s", sm->fname, fullmodestr(sg->smode)); + } else conoutf("\f3local server failed to load map \"%s%s\", error: %s", lpath, sg->smapname, localmap->err); } } diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 747f3305..99362ea8 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -320,6 +320,7 @@ void calcentitystats(entitystats_s &es, const persistent_entity *pents, int pent es.pickupdistance[r]++; } es.pickups = picks.length(); + es.modes_possible = gmode_possible(es.hasffaspawns, es.hasteamspawns, es.hasflags); } #if 0 const char *rateentitystats(entitystats_s &es) From 110a209d4e35af04b5e4eb7185dc7014d519c687 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 6 Mar 2016 17:42:53 +0100 Subject: [PATCH 14/45] move validmapname add map name checks to servermap::load() --- source/src/protocol.cpp | 37 +++++++++++++++++++++++-- source/src/protocol.h | 1 + source/src/protos.h | 1 + source/src/serverfiles.h | 58 ++++++++++++++++++++++------------------ source/src/tools.h | 29 -------------------- 5 files changed, 69 insertions(+), 57 deletions(-) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 0b83f22f..61d1c905 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -207,12 +207,45 @@ void freegzbuf(ucharbuf *p) // free a ucharbuf created by getgzbuf() } } +//#define FILENAMESALLLOWERCASE + +bool validmapname(const char *s) // checks for length, allowed chars and special DOS filenames +{ + int len = strlen(s); + if(len > MAXMAPNAMELEN) return false; + if(len == 3 || len == 4) + { + char uc[4]; + loopi(3) uc[i] = toupper(s[i]); + uc[3] = '\0'; + const char *resd = "COMLPTCONPRNAUXNUL", *fnd = strstr(resd, uc); + if(fnd) + { + int pos = (int) (fnd - resd); + if(pos == 0 || pos == 3) + { + if(isdigit(s[3])) return false; // COMx, LPTx + } + else if(pos % 3 == 0) return false; // CON, PRN, AUX, NUL + } + } + while(*s != '\0') + { +#ifdef FILENAMESALLLOWERCASE + if(!islower(*s) && !isdigit(*s) && *s != '_' && *s != '-' && *s != '.') return false; +#else + if(!isalnum(*s) && *s != '_' && *s != '-' && *s != '.') return false; +#endif + ++s; + } + return true; +} + + // filter text according to rules // dst can be identical to src; dst needs to be of size "min(len, strlen(s)) + 1" // returns dst -//#define FILENAMESALLLOWERCASE - char *filtertext(char *dst, const char *src, int flags, int len) { char *res = dst; diff --git a/source/src/protocol.h b/source/src/protocol.h index 0c6f3490..58d10090 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -9,6 +9,7 @@ #define DEMO_VERSION 2 // bump when demo format changes #define DEMO_MAGIC "ASSAULTCUBE_DEMO" #define DEMO_MINTIME 10000 // don't keep demo recordings with less than 10 seconds +#define MAXMAPNAMELEN 31 #define MAXMAPSENDSIZE 65536 #define MAXCFGFILESIZE 65536 #define MAXGZMSGSIZE 65536 diff --git a/source/src/protos.h b/source/src/protos.h index c6a5a8ca..480b53a1 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1062,6 +1062,7 @@ extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS); extern void putgzbuf(vector &d, vector &s); // zips a vector into a stream, stored in another vector extern ucharbuf *getgzbuf(ucharbuf &p); // fetch a gzipped buffer; needs to be freed properly later extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() +extern bool validmapname(const char *s); // checks for length, allowed chars and special DOS filenames extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); extern void filterlang(char *d, const char *s); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index f960672c..b13f6a0d 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -83,37 +83,43 @@ struct servermap // in-memory version of a map file on a server stream *f = NULL; int restofhead; + string filename; + + if(!validmapname(fname)) err = "illegal filename"; + if(err) goto loadfailed; // load map files, prepare sendmap buffer - defformatstring(filename)("%s%s.cfg", fpath, fname); - path(filename); - uchar *cfgraw = (uchar *)loadfile(filename, &cfglen); - if(cfgraw) { - tigerhash(cfghash, cfgraw, cfglen); - loopk(cfglen) if(cfgraw[k] > 0x7f || (cfgraw[k] < 0x20 && !isspace(cfgraw[k]))) err = "illegal chars in cfg file"; - } - formatstring(filename)("%s%s.cgz", fpath, fname); - path(filename); - cgzraw = (uchar *)loadfile(filename, &cgzlen); - if(cgzraw) tigerhash(cgzhash, cgzraw, cgzlen); - if(!cgzraw) err = "loading cgz failed"; - else if(cfglen > MAXCFGFILESIZE) err = "cfg file too big"; - else if(cgzlen >= MAXMAPSENDSIZE) err = "cgz file too big"; - else if(cfgraw) - { - uLongf gzbufsize = GZBUFSIZE; - ASSERT(GZBUFSIZE < FLOORPLANBUFSIZE); - if(compress2(staticbuffer, &gzbufsize, cfgraw, cfglen, 9) != Z_OK) gzbufsize = 0; - cfggzlen = (int) gzbufsize; - if(cgzlen + cfggzlen < MAXMAPSENDSIZE) - { // map is small enough to be sent - cfgrawgz = new uchar[cfggzlen]; - memcpy(cfgrawgz, staticbuffer, cfggzlen); + formatstring(filename)("%s%s.cfg", fpath, fname); + path(filename); + uchar *cfgraw = (uchar *)loadfile(filename, &cfglen); + if(cfgraw) + { + tigerhash(cfghash, cfgraw, cfglen); + loopk(cfglen) if(cfgraw[k] > 0x7f || (cfgraw[k] < 0x20 && !isspace(cfgraw[k]))) err = "illegal chars in cfg file"; } - else err = "cgz + cfg.gz too big to send"; + formatstring(filename)("%s%s.cgz", fpath, fname); + path(filename); + cgzraw = (uchar *)loadfile(filename, &cgzlen); + if(cgzraw) tigerhash(cgzhash, cgzraw, cgzlen); + if(!cgzraw) err = "loading cgz failed"; + else if(cfglen > MAXCFGFILESIZE) err = "cfg file too big"; + else if(cgzlen >= MAXMAPSENDSIZE) err = "cgz file too big"; + else if(cfgraw) + { + uLongf gzbufsize = GZBUFSIZE; + ASSERT(GZBUFSIZE < FLOORPLANBUFSIZE); + if(compress2(staticbuffer, &gzbufsize, cfgraw, cfglen, 9) != Z_OK) gzbufsize = 0; + cfggzlen = (int) gzbufsize; + if(cgzlen + cfggzlen < MAXMAPSENDSIZE) + { // map is small enough to be sent + cfgrawgz = new uchar[cfggzlen]; + memcpy(cfgrawgz, staticbuffer, cfggzlen); + } + else err = "cgz + cfg.gz too big to send"; + } + DELETEA(cfgraw); } - DELETEA(cfgraw); if(err) goto loadfailed; // extract entity data and header info; compile map statistics; create floorplan diff --git a/source/src/tools.h b/source/src/tools.h index b882d5d4..f8f5b2a8 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -139,35 +139,6 @@ struct stringformatter #define defformatstring(d) string d; formatstring(d) #define defvformatstring(d,last,fmt) string d; { va_list ap; va_start(ap, last); vformatstring(d, fmt, ap); va_end(ap); } -#define MAXMAPNAMELEN 64 -inline bool validmapname(const char *s) -{ - int len = strlen(s); - if(len > MAXMAPNAMELEN) return false; - if(len == 3 || len == 4) - { - char uc[4]; - loopi(3) uc[i] = toupper(s[i]); - uc[3] = '\0'; - const char *resd = "COMLPTCONPRNAUXNUL", *fnd = strstr(resd, uc); - if(fnd) - { - int pos = (int) (fnd - resd); - if(pos == 0 || pos == 3) - { - if(isdigit(s[3])) return false; // COMx, LPTx - } - else if(pos % 3 == 0) return false; // CON, PRN, AUX, NUL - } - } - while(*s != '\0') - { - if(!isalnum(*s) && *s != '_' && *s != '-' && *s != '.') return false; - ++s; - } - return true; -} - #define loopv(v) for(int i = 0; i<(v).length(); i++) #define loopvj(v) for(int j = 0; j<(v).length(); j++) #define loopvk(v) for(int k = 0; k<(v).length(); k++) From 3376c5e299a439dd66b983c427c99659c0fa154d Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 8 Mar 2016 15:38:33 +0100 Subject: [PATCH 15/45] detect server config file changes by hashing Checking only file sizes obviously misses content changes that don't change the size. --- source/src/crypto.cpp | 16 ---------------- source/src/protos.h | 4 +++- source/src/serverfiles.h | 30 ++++++++++++++++++++++++++---- source/src/tools.h | 16 +++++++++++++++- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index 70e8bb15..1d39c514 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -327,22 +327,6 @@ int __hashtest() // crash, if hash functions don't work properly - no one ever t static int __ht = __hashtest(); #endif -///////////////////////////////////////////////////////// FNV-1a /////////////////////////////////////////////////////////////////// -// Fowler–Noll–Vo is a non-cryptographic hash function created by Glenn Fowler, Landon Curt Noll, and Phong Vo. It is public domain. -// We use the 32-bit-version here. - -inline void fnv1a_init(uint32_t &hash) -{ - hash = 2166136261UL; -} - -inline void fnv1a_add(uint32_t &hash, uchar byte) -{ - hash ^= byte; - hash *= 16777619; -} - - ////////////////////////// crypto rand (Mersenne twister) //////////////////////////////////////// #define MT_N (624) diff --git a/source/src/protos.h b/source/src/protos.h index 480b53a1..490dedad 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1123,8 +1123,10 @@ struct serverconfigfile string filename; int filelen; char *buf; + uint32_t filehash; sl_semaphore busy; - serverconfigfile() : filelen(0), buf(NULL), busy(1, NULL) { filename[0] = '\0'; } + bool readfailed, updated; + serverconfigfile() : filelen(0), buf(NULL), busy(1, NULL) { filename[0] = '\0'; fnv1a_init(filehash); } virtual ~serverconfigfile() { DELETEA(buf); } virtual void clear() {} diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index b13f6a0d..661b2847 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -587,7 +587,7 @@ bool serverconfigfile::load() { DELETEA(buf); buf = loadfile(filename, &filelen); - if(!buf) + if((readfailed = !buf)) { logline(ACLOG_INFO,"could not read config file '%s'", filename); return false; @@ -615,17 +615,39 @@ bool serverconfigfile::isbusy() void serverconfigfile::trypreload() // in readserverconfigsthread: load changed files into RAM { - if(!buf && !isbusy() && getfilesize(filename) != filelen && !load()) buf = newstring(""); + if(!buf && !isbusy()) + { + int newfilelen; + bool contentchanged = false; + char *newbuf = loadfile(filename, &newfilelen); + readfailed = !newbuf; + if(newbuf) + { + uint32_t newhash; + uchar *p = (uchar*)newbuf; + fnv1a_init(newhash); + loopirev(newfilelen) fnv1a_add(newhash, *p++); + if(newhash != filehash) + { + contentchanged = true; + filehash = newhash; + } + delete newbuf; + } + if(contentchanged) load(); + } } void serverconfigfile::process() // in main thread: refill data structures with preloaded file content { - if(buf && !isbusy() && !busy.trywait()) + updated = false; + if((readfailed || buf) && !isbusy() && !busy.trywait()) { clear(); - if(strlen(buf)) read(); + if(buf && strlen(buf)) read(); DELETEA(buf); busy.post(); + updated = true; } } diff --git a/source/src/tools.h b/source/src/tools.h index f8f5b2a8..ca8dc5fa 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -1002,5 +1002,19 @@ extern void sl_detachthread(void *ti); extern void sl_sleep(int duration); extern bool ismainthread(); -#endif +///////////////////////////////////////////////////////// FNV-1a /////////////////////////////////////////////////////////////////// +// Fowler–Noll–Vo is a non-cryptographic hash function created by Glenn Fowler, Landon Curt Noll, and Phong Vo. It is public domain. +// We use the 32-bit-version here. +inline void fnv1a_init(uint32_t &hash) +{ + hash = 2166136261UL; +} + +inline void fnv1a_add(uint32_t &hash, uchar byte) +{ + hash ^= byte; + hash *= 16777619; +} + +#endif From 7486317497872839d1d10c3ee3241f83fee239f0 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Aug 2016 16:27:00 +0200 Subject: [PATCH 16/45] add changeable server parameters A list of parameters is read from config/serverparameters.cfg once per minute and the values written to corresponding server data structures. If the binary is compiled in debug mode, starting a dedicated server will write a blank config file with all possible parameters and their descriptions, sorted and grouped into chapters. The name of the config file can be set with the commandline parameter -A. So far, syntax errors are not reported. Instead, it's possible to dump all current values to a file every time something changes. --- config/servercmdline.txt | 2 +- config/serverparameters.cfg | 19 +++ source/src/protos.h | 10 +- source/src/server.cpp | 28 ++++- source/src/serverfiles.h | 233 ++++++++++++++++++++++++++++++++++++ source/src/tools.cpp | 5 +- 6 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 config/serverparameters.cfg diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 3bce2d98..9af8915a 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -39,6 +39,7 @@ // -y // banthreshold, -1..-100, default -6 // -H // server logfile path, default logs/ // -r // server maprot file, default config/maprot.cfg +// -A // server parameter file, default config/serverparameters.cfg // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg // -G // server geoip file, default config/geoip.cfg @@ -88,7 +89,6 @@ // 3 WARNING: log only messages of level WARNING and above // 4 ERROR: log only messages of level ERROR // 5 do not write to the log -// -A // Restricts voting for a map/mode to admins. This switch can be used several times. // these switches control the naming of demos (see -W) // --demofilenameformat="%Mmin_%G_%w_%H_%n" // default: "%w_%h_%n_%Mmin_%G" diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg new file mode 100644 index 00000000..4e11f635 --- /dev/null +++ b/config/serverparameters.cfg @@ -0,0 +1,19 @@ +// serverparameters.cfg +// +// parameters from this file are read once per minute by the server +// and are effective immediately + +// each setting consists of a keyword and a value, written like this: keyword:value +// there are no spaces allowed between keyword and value +// using two colons (keyword::value) activates rich-text filtering for the value + + + +// **** Debug switches **** + +// dumpparameters dump server parameters when updated +// integer [0..1], default 0, 0:disabled, 1:enabled +dumpparameters:0 + + + diff --git a/source/src/protos.h b/source/src/protos.h index 490dedad..ae343830 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1132,7 +1132,7 @@ struct serverconfigfile virtual void clear() {} virtual void read() {} void init(const char *name, bool tracking = true); - bool load(); + virtual bool load(); bool isbusy(); void trypreload(); void process(); @@ -1142,18 +1142,18 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath; uchar *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; - vector adminonlymaps; + vector adminonlymaps; // FIXME: remove this servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) @@ -1220,7 +1220,7 @@ struct servercommandline } break; case 'H': logfilepath = a; break; - case 'A': if(*a) adminonlymaps.add(a); break; + case 'A': parfilepath = a; break; case 'c': if(ai > 0) maxclients = min(ai, MAXCLIENTS); break; case 'k': { diff --git a/source/src/server.cpp b/source/src/server.cpp index 4b26721b..162abaa3 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -76,6 +76,7 @@ struct servergame vector servermaps; // all available maps kept in memory servercontroller *svcctrl = NULL; +serverparameter serverparameters; servercommandline scl; servermaprot maprot; serveripblacklist ipblacklist; @@ -105,6 +106,8 @@ int totalclients = 0; int cn2boot; // synchronising the worker threads... +const char *endis[] = { "disabled", "enabled", "" }; +SERVPARLIST(dumpparameters, 0, 0, 0, endis, "ddump server parameters when updated"); void poll_serverthreads() // called once per mainloop-timeslice { @@ -180,7 +183,23 @@ void poll_serverthreads() // called once per mainloop-timeslice } break; } - case 4: // pause worker threads for a while (restart once a minute) + case 4: // check for fresh config files + { + if(serverparameters.updated) + { + serverparameters.updated = false; + if(dumpparameters) + { + defformatstring(fname)("%sdebug/serverparameter_dump_%s.txt", scl.logfilepath, numtime()); + stream *f = openfile(path(fname), "w"); + f->printf("// server parameter dump, %s\n\n", asctimestr()); + serverparameters.dump(f); + delete f; + } + } + else stage++; + } + case 5: // pause worker threads for a while (restart once a minute) { if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) @@ -4330,6 +4349,8 @@ void initserver(bool dedicated) if((isdedicated = dedicated)) { + serverparameters.init(scl.parfilepath); + if(serverparameters.load()) serverparameters.read(); // do this early, so the parameters are properly set during initialisation ENetAddress address = { ENET_HOST_ANY, (enet_uint16)scl.serverport }; if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logline(ACLOG_WARNING, "server ip not resolved!"); serverhost = enet_host_create(&address, scl.maxclients+1, 3, 0, scl.uprate); @@ -4433,7 +4454,10 @@ int main(int argc, char **argv) else printf("WARNING: unknown commandline argument \"%s\"\n", argv[i]); } } - +#ifdef _DEBUG + defformatstring(spdoc)("%s/docs/serverparameters.cfg", scl.logfilepath); + serverparameters_dumpdocu(spdoc); +#endif if(scl.service && !svcctrl) { #ifdef WIN32 diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 661b2847..202d8555 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1,5 +1,18 @@ // serverfiles.h +// abuse globals to register server parameters +#ifdef _DEBUG +#define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, desc) +#define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, desc) +#define SERVPARF(name, min, cur, max, body, desc) extern int last_##name, name; void var_##name() { body; } int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, var_##name, false, true, desc) +#define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, desc) +#else +#define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, NULL) +#define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, NULL) +#define SERVPARF(name, min, cur, max, body, desc) extern int last_##name, name; void var_##name() { body; } int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, var_##name, false, true, NULL) +#define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, NULL) +#endif + // map management #define SERVERMAXMAPFACTOR 10 // 10 is huge... if you're running low on RAM, consider 9 as maximum (don't go higher than 10, or players will name their ugly dog after you) @@ -1382,3 +1395,223 @@ struct serverinfofile : serverconfigfile // plaintext info file, used for serve } }; +// realtime server parameters + +enum { SID_INT, SID_STR }; + +struct servpar +{ + int type; // one of SPAR_* above + const char *name; + union + { + int minval; // SID_INT + int minlen; // SID_STR + }; + union + { + int maxval; // SID_INT + int maxlen; // SID_STR + }; + union + { + int *i; // SID_INT + char *s; // SID_STR + }; + union + { + int *shadow_i; // SID_INT + char *shadow_s; // SID_STR + }; + void (*fun)(); + union + { + const char **list; // SID_INT + const char *defaultstr; // SID_STR + }; + union + { + int defaultint; // SID_INT + int filter; // SID_STR + }; +#ifdef _DEBUG + const char *desc; + int chapter; +#endif + bool log; // log value changes (careful!) + bool fromfile; // true: value can be set by config file, false: value is set by server + + servpar() {} + + // SID_INT + servpar(int type, const char *name, int minval, int maxval, const char *list[], int *i, int *shadow_i, int defval, void (*fun)(), bool log, bool fromfile, const char *_desc) + : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), log(log), fromfile(fromfile) + { DEBUGCODE(desc = _desc); } + + // SID_STR + servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool log, int fromfile, const char *_desc) + : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), log(log), fromfile(fromfile) + { DEBUGCODE(desc = _desc;) } +}; + +hashtable *servpars = NULL; + +int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool log, bool fromfile, const char *desc) +{ + if(!servpars) servpars = new hashtable; + if(list && minval == maxval) while(list[maxval - minval + 1][0]) maxval++; // get list length + ASSERT(cur >= minval && cur <= maxval); + servpar v(SID_INT, name, minval, maxval, list, storage, shadowstorage, cur, fun, log, fromfile, desc); + servpars->access(name, v); + *shadowstorage = cur; + return cur; +} + +bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool log, bool fromfile, const char *desc) +{ + if(!servpars) servpars = new hashtable; + ASSERT(maxlen > minlen && minlen >= 0 && int(strlen(cur)) >= minlen && int(strlen(cur)) <= maxlen); + servpar v(SID_STR, name, minlen, maxlen, filt, storage, shadowstorage, cur, fun, log, fromfile, desc); + servpars->access(name, v); + strncpy(storage, cur, maxlen + 1); + strncpy(shadowstorage, cur, maxlen + 1); + storage[maxlen] = shadowstorage[maxlen] = '\0'; + return true; +} + +struct serverparameter : serverconfigfile +{ + bool load() + { + if(serverconfigfile::load()) + { + int line = 0, idx; + char *b, *l, *p = buf, *kb, *k, *v; + servpar *id; + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; line++; + l = strtok_r(l, " ", &b); + if(l && (k = strtok_r(l, ":", &kb)) && (v = strtok_r(NULL, ":", &kb)) && (id = servpars->access(k)) && id->fromfile) switch(id->type) + { + case SID_INT: + { + int i = ATOI(v); + if(id->list && (idx = getlistindex(v, id->list, false, -1)) >= 0) i = idx + id->minval; // multiple choice + if(i >= id->minval && i <= id->maxval) *(id->shadow_i) = i; + break; + } + case SID_STR: + { + if((v - k) - strlen(k) > 1) filterrichtext(v, v); + filtertext(v, v, id->filter); + int i = (int)strlen(v); + if(i >= id->minlen && i <= id->maxlen) strncpy(id->shadow_s, v, id->maxlen + 1); + break; + } + } + } + return true; + } + return false; + } + + void read() + { // in mainloop: update values from shadow values + enumerate(*servpars, servpar, id, + switch(id.type) + { + case SID_INT: + { + if(*id.i != *id.shadow_i) + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + *id.i = *id.shadow_i; + } + break; + } + case SID_STR: + { + if(strcmp(id.s, id.shadow_s)) + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + strncpy(id.s, id.shadow_s, id.maxlen + 1); + } + break; + } + } + ); + } + + void dump(stream *f) + { + enumerate(*servpars, servpar, id, + switch(id.type) + { + case SID_INT: f->printf("%s:%d // default %d\n", id.name, *id.i, id.defaultint); break; + case SID_STR: f->printf("%s:%s // default %s\n", id.name, id.s, id.defaultstr); break; + } + ); + } +}; + +#ifdef _DEBUG +int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } + +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "" }; +const char *siddocchaptersorting = "vmd"; + +void serverparameters_dumpdocu(char *fname) +{ + vector sp; + enumerate(*servpars, servpar, id, if(id.fromfile && id.desc && id.desc[0]) sp.add(&id)); + loopv(sp) + { + const char *e = strchr(siddocchaptersorting, sp[i]->desc[0]); + sp[i]->chapter = e ? (int) (e - siddocchaptersorting) : sp[i]->desc[0]; + } + sp.sort(siddocsort); + stream *f = openfile(path(fname), "w"); + int lastchap = 0; + if(f) + { + f->printf("// serverparameters.cfg\n//\n// parameters from this file are read once per minute by the server\n// and are effective immediately\n\n" + "// each setting consists of a keyword and a value, written like this: keyword:value\n// there are no spaces allowed between keyword and value\n" + "// using two colons (keyword::value) activates rich-text filtering for the value\n\n\n"); + loopv(sp) + { + servpar &id = *sp[i]; + if(lastchap != id.desc[0]) + { + int ch = -1; + for(int k = 0; siddocchapters[k][0]; k++) if(siddocchapters[k][0] == id.desc[0]) ch = k; + lastchap = id.desc[0]; + if(ch < 0) f->printf("\n// **** untitled chapter %c ****\n\n", lastchap); + else f->printf("\n// **** %s ****\n\n", siddocchapters[ch] + 1); + } + switch(id.type) + { + case SID_INT: + { + f->printf("// %s %s\n// integer [%d..%d], default %d", id.name, id.desc + 1, id.minval, id.maxval, id.defaultint); + if(id.list) + { + for(int k = 0; id.list[k] && id.list[k][0]; k++) f->printf(", %d:%s", id.minval + k, id.list[k]); + } + f->printf("\n%s:%d\n\n", id.name, id.defaultint); + break; + } + case SID_STR: + { + f->printf("// %s %s\n// string [%d..%d chars], default \"%s\"\n%s:%s\n\n", id.name, id.desc + 1, id.minlen, id.maxlen, id.defaultstr, id.name, id.defaultstr); + break; + } + } + } + f->printf("\n\n"); + delete f; + } +} +#endif + + diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 99362ea8..f78c8a15 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -561,7 +561,7 @@ const char *hiddenpwd(const char *pwd, int showchars) int getlistindex(const char *key, const char *list[], bool acceptnumeric, int deflt) { int max = 0; - while(list[max][0]) if(!strcasecmp(key, list[max])) return max; else max++; + while(list[max] && list[max][0]) if(!strcasecmp(key, list[max])) return max; else max++; if(acceptnumeric && isdigit(key[0])) { int i = (int)strtol(key, NULL, 0); @@ -569,7 +569,8 @@ int getlistindex(const char *key, const char *list[], bool acceptnumeric, int de } #if !defined(STANDALONE) && defined(_DEBUG) char *opts = conc(list, -1, true); - if(*key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); + extern bool isdedicated; + if(!isdedicated && *key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); delstring(opts); #endif return deflt; From 5fee02c77b7f7e55ac29101c57466b14e324b203 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 13 Mar 2016 16:09:58 +0100 Subject: [PATCH 17/45] new maprot Instead of a list of specific map+mode combinations the maprot now only contains general rules for maps and game modes. The server then calculates the list by itself when needed. The server accounts played maps and modes (and map+mode combinations) and calculates the list specifically not to overplay maps or modes. May need further testing. --- config/maprot.cfg | 195 +++++-------------------- config/serverparameters.cfg | 23 +++ source/src/entity.h | 2 +- source/src/protocol.h | 1 + source/src/protos.h | 6 +- source/src/server.cpp | 122 +++++++++++++--- source/src/server.h | 2 + source/src/serveractions.h | 2 +- source/src/serverfiles.h | 280 +++++++++++++++++++++++++++--------- 9 files changed, 387 insertions(+), 246 deletions(-) diff --git a/config/maprot.cfg b/config/maprot.cfg index 0476c2ce..d27ce1a4 100644 --- a/config/maprot.cfg +++ b/config/maprot.cfg @@ -1,168 +1,49 @@ // Server Side Maprotations... // WARNING: You can NOT use CubeScript in here!! -// Use the following scheme... -// map:mode:time:allowVote[:minplayer[:maxplayer[:skiplines]]] -// map will be skipped, if minplayer/maxplayer requirements are failed +// This file specifies a set of rules for the maps to be played on the server. The server automatically uses all available +// maps, so this list only needs to specify general settings and exceptions. It is not necessary, to mention every map here. +// The file is read from top to bottom, so if settings conflict, values from further down overwrite earlier ones (like all +// settings in this file overwrite builtin general defaults). Essentially, every combination of map and mode has a set of +// values for all keywords. -// map: map filename without extension -// do only use standard maps and maps from your packages/maps/servermaps folder -// for the flag modes (5, 14 & 15), be sure, the map contains flag bases +// per line: map_name list_of_modes keyword:value [keyword:value] [keyword:value] ... -// mode: Team Deathmatch (0), Deathmatch (2), Pistol Frenzy (6), Team Pistol Frenzy (16), -// Capture the Flag (5), Hunt the Flag (13), Keep the Flag (15), Team Keep the Flag (14), -// Team Survivor (4), Survivor (3), Last Swiss Standing (9), Team Last Swiss Standing (17), -// One Shot One Kill (10), Team One Shot One Kill (11) +// map_name: +// - a corresponding map file needs to be in packages/maps/official, packages/maps/servermaps or packages/maps/servermaps/incoming +// - the file ending "cgz" will be added automatically +// - map name can end with an asterisk to match multiple maps, as single "*" will match all maps +// - map names can only contain: lowercase chars, digits, '_', '-' and '.' -// time: game time in minutes, usually 15 minutes for flag modes, 10 minutes for all other modes +// list_of_modes: (no spaces) +// - can contain any number and combination of these: ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv -// vote: (1) allow players to vote for other maps -// (0) only admin can change the map +// keywords: (no spaces between keyword and value allowed) +// - weight: values range from -100 to +100 and influence how often a map+mode will be played (higher number: more often) +// - repeat: values range from -100 to +100 and influence how fast a map+mode can be played again (higher number: sooner) +// - time: default game time in minutes +// - mintime: minimum voteable game time in minutes +// - maxtime: maximum voteable game time in minutes +// - minplayers: minimum number of players for the map+mode to be suggested +// - maxplayers: maximum number of players for the map+mode to be suggested +// - maxteamsize: maximum number of active players per team (total for ffa modes) +// - teamthreshold: number of players that mark the threshold between "better play team" and "better play ffa" +// - manual: 1:map+mode will not be suggested by the server (users need to vote for this map+mode themselves) +// - restrict: 0:player, 1:master, 2:admin, 3:owner 9:elvis (default is 0) +// (weight and repeat values can accumulate within their boundaries) -// minplayer: if set, the server will skip the map, if fewer than required players are connected +// default settings for all maps: +* ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv mintime:4 maxtime:20 teamthreshold:4 // all modes +* ctf|htf|tdm|tktf|tlss|tosok|tpf|tsurv time:15 maxteamsize:6 minplayers:4 // team modes +* dm|ktf|lss|osok|pf|surv time:10 maxteamsize:10 maxplayers:7 // ffa modes -// maxplayer: if set, the server will skip the map, if more than allowed players are connected +// default tweaks +* lss|osok|surv|tlss|tosok|tsurv weight:-10 // be careful with suggesting arena modes automatically +* pf|surv|tlss|tpf|tsurv manual:1 // players need to really want these +* ctf weight:10 // some players like this mode -// skiplines: lines to skip in maprot.cfg, after this map got actually played -// this can be used to select one of several lines, instead of playing all lines - -// minplayer and maxplayer numbers are only checked when a new game is picked from the map rotation. -// If the player number changes during gameplay, no action will be taken. - -ac_scaffold : 2: 8: 1: 0: 3: 1 // dm, 8 minutes, 0..3 players -ac_scaffold : 0:10: 1: 4:11: 0 // tdm, 10 minutes, 4..11 players - -ac_industrial :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_industrial : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_industrial :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_power : 5:12: 1: 4:12: 1 // ctf, 12 minutes, 4..12 players -ac_power :13:12: 1:13: 0: 0 // htf, 12 minutes, 13.. players - -ac_desert : 2: 8: 1: 0: 4: 1 // dm, 8 minutes, 0..4 players -ac_desert :11:10: 1: 5:11: 0 // tosok, 10 minutes, 5..11 players - -ac_keller : 5:12: 1: 4:12: 1 // ctf, 12 minutes, 4..12 players -ac_keller :13:12: 1:13: 0: 0 // htf, 12 minutes, 13.. players - -ac_arctic : 0:10: 1: 4: 7: 1 // tdm, 10 minutes, 4..7 players -ac_arctic :11:10: 1: 8: 0: 0 // tosok, 10 minutes, 8.. players - -ac_shine : 5:12: 1: 4:13: 1 // ctf, 12 minutes, 4..13 players -ac_shine :13:12: 1:14: 0: 0 // htf, 12 minutes, 14.. players - -ac_elevation :15: 8: 1: 2: 5: 2 // ktf, 8 minutes, 2..5 players -ac_elevation : 5:12: 1: 6:10: 1 // ctf, 12 minutes, 6..10 players -ac_elevation :14:10: 1:11:14: 0 // tktf, 10 minutes, 11..14 players - -ac_desert : 9: 8: 1: 0: 5: 0 // lss, 8 minutes, 0..5 players - -ac_toxic : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_toxic : 0:10: 1: 6: 0: 0 // tdm, 10 minutes, 6.. players - -ac_mines :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_mines : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_mines :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_scaffold :11: 8: 1: 6: 9: 0 // tosok, 8 minutes, 6..9 players - -ac_urban : 0:10: 1: 5: 0: 0 // tdm, 10 minutes, 5.. players - -ac_desert3 : 5:12: 1: 4:10: 1 // ctf, 12 minutes, 4..10 players -ac_desert3 :14:10: 1:11: 0: 0 // tktf, 10 minutes, 11.. players - -ac_depot : 5:12: 1: 4:12: 0 // ctf, 12 minutes, 4..12 players - -ac_desert2 : 0:10: 1: 3: 7: 2 // tdm, 10 minutes, 3..7 players -ac_desert2 :11: 8: 1: 8:10: 1 // tosok, 8 minutes, 8..10 players -ac_desert2 :13:10: 1:11: 0: 0 // htf, 10 minutes, 11.. players - -// one short game on ac_snow, either osok or deathmatch (pseudorandom) -ac_snow :10: 6: 1: 0: 1: 7 // osok, 6 minutes, 0..1 players -ac_snow : 2: 6: 1: 2: 2: 6 // dm, 6 minutes, 2 players -ac_snow :10: 6: 1: 3: 3: 5 // osok, 6 minutes, 3 players -ac_snow : 2: 6: 1: 4: 4: 4 // dm, 6 minutes, 4 players -ac_snow :10: 6: 1: 5: 5: 3 // osok, 6 minutes, 5 players -ac_snow : 2: 6: 1: 6: 6: 2 // dm, 6 minutes, 6 players -ac_snow :10: 6: 1: 7: 7: 1 // osok, 6 minutes, 7 players -ac_snow : 0: 6: 1: 8:11: 0 // tdm, 6 minutes, 8..11 players - -ac_sunset : 5:12: 1: 4:12: 0 // ctf, 12 minutes, 4..12 players - -ac_complex : 0:10: 1: 3:13: 0 // tdm, 10 minutes, 3..13 players - -ac_aqueous :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_aqueous : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_aqueous :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_arid : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_arid :17:10: 1: 6: 0: 0 // tlss, 10 minutes, 6.. players - -ac_edifice :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_edifice : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_edifice :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_iceroad : 5:10: 1: 5: 0: 0 // ctf, 10 minutes, 5.. players - -ac_douze :10: 6: 1: 5: 5: 1 // osok, 6 minutes, 5 players -ac_douze : 2: 6: 1: 6: 6: 0 // dm, 6 minutes, 6 players - -ac_gothic :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_gothic : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_gothic :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_rattrap : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_rattrap :16:10: 1: 6: 0: 0 // tpf, 10 minutes, 6.. players - -ac_arabian :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_arabian : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_arabian :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_werk :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_werk : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_werk :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_stellar :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_stellar : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_stellar :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_ingress :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_ingress : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_ingress :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_avenue :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_avenue : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_avenue :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_edifice :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_edifice : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_edifice :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_wasteland : 9: 8: 1: 3: 6: 0 // lss, 8 minutes, 3..6 players - -ac_swamp : 6: 6: 1: 2: 3: 1 // pf, 6 minutes, 2..3 players -ac_swamp :13:12: 1: 4:10: 0 // htf, 12 minutes, 4..10 players - -ac_venison : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_venison :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_venison : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_terros : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_terros :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_terros : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_lainio : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_lainio :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_lainio : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_cavern :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_cavern :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_cavern : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_wasteland : 2: 8: 1: 0: 4: 1 // dm, 8 minutes, 0..4 players -ac_wasteland :11:10: 1: 5:11: 0 // tosok, 10 minutes, 5..11 players - - -// Change this file to make the map rotation different and add more.. -// do only use standard maps and maps from your packages/maps/servermaps folder! +// map-specific setting for official maps: (some examples) +//ac_douze ctf manual:1 restrict:9 // disable ctf douze, if it were a possible game mode +//ac_iceroad ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv manual:1 restrict:9 // completely disable ac_iceroad +//ac_shine ctf repeat:-50 // not too often diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 4e11f635..f69f8033 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -9,8 +9,31 @@ +// **** Maprot settings **** + +// gamepenalty_cutoff Number of minutes to remember that a map+mode combination has been played +// integer [30..120], default 60 +gamepenalty_cutoff:60 + +// gamepenalty_random Amount of random weight to add to each map+mode combination +// integer [1..60], default 1 +gamepenalty_random:1 + +// mappenalty_cutoff Number of minutes to remember that a map has been played +// integer [45..240], default 60 +mappenalty_cutoff:60 + +// mappenalty_weight Influence of play history of a map (-100: no influence, 0: normal, 100: high influence) +// integer [-100..100], default 0 +mappenalty_weight:0 + + // **** Debug switches **** +// dumpmaprot dump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt) +// integer [0..1], default 1, 0:disabled, 1:enabled +dumpmaprot:1 + // dumpparameters dump server parameters when updated // integer [0..1], default 0, 0:disabled, 1:enabled dumpparameters:0 diff --git a/source/src/entity.h b/source/src/entity.h index 45438574..0614be06 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -113,7 +113,7 @@ static inline const char *team_string(int t, bool abbr = false) { const char **n enum { ENT_PLAYER = 0, ENT_BOT, ENT_CAMERA, ENT_BOUNCE }; enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATE }; -enum { CR_DEFAULT = 0, CR_ADMIN }; +enum { CR_DEFAULT = 0, CR_MASTER, CR_ADMIN, CR_OWNER, CR_NUM }; enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_OVERVIEW, SM_NUM }; class worldobject diff --git a/source/src/protocol.h b/source/src/protocol.h index 58d10090..57afd1a9 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -1,4 +1,5 @@ #define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed +#define MAXPLAYERS 32 // in a multiplayer game, can be arbitrarily changed #define DEFAULTCLIENTS 6 #define MAXTRANS 5000 // max amount of data to swallow in 1 go #define CUBE_DEFAULT_SERVER_PORT 28763 diff --git a/source/src/protos.h b/source/src/protos.h index ae343830..974ede37 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1142,7 +1142,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath; uchar *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; @@ -1152,7 +1152,7 @@ struct servercommandline servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), - maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), + maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), @@ -1250,7 +1250,7 @@ struct servercommandline break; } case 'W': demopath = a; break; - case 'r': maprot = a; break; + case 'r': maprotfile = a; break; case 'X': pwdfile = a; break; case 'B': blfile = a; break; case 'G': geoipfile = a; break; diff --git a/source/src/server.cpp b/source/src/server.cpp index 162abaa3..33849ebf 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -73,7 +73,7 @@ struct servergame } cursgame, *sg = &cursgame; // config -vector servermaps; // all available maps kept in memory +vector servermaps, gamesuggestions; // all available maps kept in memory servercontroller *svcctrl = NULL; serverparameter serverparameters; @@ -107,12 +107,14 @@ int cn2boot; // synchronising the worker threads... const char *endis[] = { "disabled", "enabled", "" }; +SERVPARLIST(dumpmaprot, 0, 1, 0, endis, "ddump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt)"); SERVPARLIST(dumpparameters, 0, 0, 0, endis, "ddump server parameters when updated"); void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; + static bool debugmaprot = true; switch(stage) { @@ -148,7 +150,10 @@ void poll_serverthreads() // called once per mainloop-timeslice { servermaps.add(fresh); logline(ACLOG_INFO,"added servermap %s%s", fresh->fpath, fresh->fname); + maprot.initmap(fresh, NULL); + servermaps.sort(servermapsortname); // keep list sorted at all times } + else delete fresh; servermapdropbox = NULL; } readmapsthread_sem->post(); @@ -185,7 +190,16 @@ void poll_serverthreads() // called once per mainloop-timeslice } case 4: // check for fresh config files { - if(serverparameters.updated) + if(maprot.updated) + { + maprot.updated = false; + defformatstring(fname)("%sdebug/maprot_debug_verbose.txt", scl.logfilepath); + stream *f = debugmaprot && dumpmaprot ? openfile(path(fname), "w") : NULL; + loopv(servermaps) maprot.initmap(servermaps[i], f); // may stall a few msec if there are very many maps + DELETEP(f); + debugmaprot = false; // only once during server start + } + else if(serverparameters.updated) { serverparameters.updated = false; if(dumpparameters) @@ -204,19 +218,95 @@ void poll_serverthreads() // called once per mainloop-timeslice if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) { // empty server and nothing to do: - loopvrev(servermapstodelete) delete servermapstodelete.remove(i); // delete outdated servermaps + loopvrev(servermapstodelete) + { + gamesuggestions.removeobj(servermapstodelete[i]); + delete servermapstodelete.remove(i); // delete outdated servermaps + } } break; } } } -servermap *getservermap(const char *mapname) // check, if the server knows a map (by filename) +SERVPAR(gamepenalty_cutoff, 30, 60, 120, "gNumber of minutes to remember that a map+mode combination has been played"); +SERVPAR(gamepenalty_random, 1, 1, 60, "gAmount of random weight to add to each map+mode combination"); +SERVPAR(mappenalty_cutoff, 45, 60, 240, "gNumber of minutes to remember that a map has been played"); +SERVPAR(mappenalty_weight, -100, 0, 100, "gInfluence of play history of a map (-100: no influence, 0: normal, 100: high influence)"); + +void servermaprot::calcmappenalties() // sum up, how long ago each map+mode was played -> get individual penalties for every map+mode combination { - loopv(servermaps) if(!strcmp(servermaps[i]->fname, mapname)) return servermaps[i]; - return NULL; + int modecutoff = gamepenalty_cutoff * 60, mapcutoff = mappenalty_cutoff * 60; + loopv(servermaps) + { + servermap &s = *servermaps[i]; + s.mappenalty = 0; + loopi(GMODE_NUM) s.penalty[i] = 0; + loopi(GAMEHISTLEN) if(s.lastplayed[i] && ((1 << s.lastmodes[i]) & s.modes_allowed)) + { + int d = (servmillis - s.lastplayed[i]) / 1000, m = s.lastmodes[i]; + if(d < modecutoff) + { + s.penalty[m] += ((modecutoff - d + 119) * PENALTYUNIT) / modecutoff; // last minute -> 1 PENALTYUNIT, "cutoff" minutes ago -> 0 PENALTYUNIT, linear inbetween + } + if(d < mapcutoff) + { + s.mappenalty += ((mapcutoff - d + 59) * PENALTYUNIT) / mapcutoff; // last minute -> 1 PENALTYUNIT, "cutoff" minutes ago -> 0 PENALTYUNIT, linear inbetween + } + } + loopi(GMODE_NUM) if((1 << i) & s.modes_allowed) + { + s.penalty[i] = (s.penalty[i] * (100 - s.modes[i].repeat)) / 100; + s.penalty[i] -= s.modes[i].weight * PENALTYUNIT; + s.penalty[i] += rnd(gamepenalty_random * PENALTYUNIT); + } + } } +servermap *randommap() +{ + if(servermaps.length() < 1) fatal("no maps available"); + servermap *s = servermaps[rnd(servermaps.length())]; + s->bestmode = 0; // tdm + return s; +} + +servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of playable games (map+mode) +{ + calcmodepenalties(); + calcmappenalties(); + gamesuggestions.setsize(0); + if(numpl >= MAXPLAYERS) numpl = MAXPLAYERS - 1; + loopv(servermaps) + { + servermap &s = *servermaps[i]; + s.bestmode = -1; + s.weight = INT_MIN; + int bestpen = INT_MAX; + int modemask = (numpl < 0 ? s.modes_allowed : s.modes_pn[numpl]) & s.modes_auto; + loopi(GMODE_NUM) if(modemask & (1 << i)) + { + if((s.penalty[i] + modepenalty[i]) < bestpen) + { + bestpen = s.penalty[i] + modepenalty[i]; + s.bestmode = i; + } + } + if(s.bestmode >= 0) + { + s.weight = -bestpen - ((mappenalty_weight + 101) * s.mappenalty) / 100; + gamesuggestions.add(&s); + } + } + gamesuggestions.sort(servermapsortweight); + return gamesuggestions.length() ? gamesuggestions[0] : randommap(); +} + +servermap *getservermap(const char *mapname) // check, if the server knows a map (by filename) +{ + servermap *k = (servermap *)&mapname, **res = servermaps.search(&k, servermapsortname); + return res ? *res : NULL; +} bool valid_client(int cn) { @@ -3618,15 +3708,14 @@ void process(ENetPacket *packet, int sender, int chan) else filtertext(text, behindpath(text), FTXT__MAPNAME); if(time <= 0) time = -1; time = min(time, 60); - if (vi->gonext) + if(vi->gonext) { - int ccs = rnd(maprot.configsets.length()); - configset *c = maprot.get(ccs); + servermap *c = maprot.next(false, true); if(c) { - strcpy(vi->text,c->mapname); - mode = vi->num1 = c->mode; - time = vi->num2 = c->time; + strcpy(vi->text,c->fname); + vi->num1 = c->bestmode; + vi->num2 = c->modes[c->bestmode].time; } else fatal("unable to get next map in maprot"); } @@ -4083,6 +4172,8 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(servmillis - laststatus > 60 * 1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; + maprot.oneminutepassed(nonlocalclients ? sg->smode : -1); + if(sg->curmap && nonlocalclients) sg->curmap->oneminuteplayed(servmillis, sg->smode); if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); @@ -4201,7 +4292,7 @@ void extping_serverinfo(ucharbuf &pi, ucharbuf &po) void extping_maprot(ucharbuf &po) { putint(po, CONFIG_MAXPAR); - string text; +/* string text; bool abort = false; loopv(maprot.configsets) { @@ -4212,7 +4303,7 @@ void extping_maprot(ucharbuf &po) sendstring(abort ? "-- list truncated --" : text, po); loopi(CONFIG_MAXPAR) putint(po, c.par[i]); if(abort) break; - } + }*/ sendstring("", po); } @@ -4359,8 +4450,7 @@ void initserver(bool dedicated) string pubkey; if(scl.ssk) logline(ACLOG_VERBOSE,"server public key: %s", bin2hex(pubkey, scl.ssk + 32, 32)); - maprot.init(scl.maprot); -// maprot.next(false, true); // ensure minimum maprot length of '1' + maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); geoiplist.init(scl.geoipfile); diff --git a/source/src/server.h b/source/src/server.h index 45dd1498..55ded46e 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -552,6 +552,8 @@ const char *gunnames[NUMGUNS + 1]; const char *teamnames[] = {"CLA", "RVSF", "CLA-SPECT", "RVSF-SPECT", "SPECTATOR", "", "void"}; const char *teamnames_s[] = {"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "", "void"}; +const char *rolenames[CR_NUM + 1] = { "unarmed", "master", "admin", "owner", "" }; + const char *killmessages[2][NUMGUNS] = { { "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "busted", "", "busted" }, diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 3d6e904a..dd5637d2 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -57,7 +57,7 @@ struct mapaction : serveraction } } bool isvalid() { return serveraction::isvalid() && mode != GMODE_DEMO && map[0] && mapok && !(isdedicated && !m_mp(mode)); } - bool isdisabled() { return maprot.current() && !maprot.current()->vote; } + bool isdisabled() { return false /*maprot.current() && !maprot.current()->vote*/; } mapaction(char *map, int mode, int time, int caller, bool q) : map(map), mode(mode), time(time), queue(q) { if(isdedicated) diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 202d8555..7df17421 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -13,6 +13,26 @@ #define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, NULL) #endif +#define CONFIG_MAXPAR 11 +const char *maprotkeywords[CONFIG_MAXPAR] = { "weight", "repeat", "time", "mintime", "maxtime", "minplayers", "maxplayers", "maxteamsize", "teamthreshold", "manual", "restrict" }; + +struct configsetvalues +{ + union + { + struct { char weight, repeat, time, mintime, maxtime, minplayers, maxplayers, maxteamsize, teamthreshold, manual, restrict; }; + char par[CONFIG_MAXPAR]; + }; + + void empty() { weight = repeat = 0; time = mintime = maxtime = minplayers = maxplayers = maxteamsize = teamthreshold = manual = restrict = -1; } + void add(configsetvalues u) + { + weight = clamp(weight + u.weight, -100, 100); + repeat = clamp(repeat + u.repeat, -100, 100); + for(int i = 2; i < CONFIG_MAXPAR; i++) if(u.par[i] >= 0) par[i] = u.par[i]; + } +}; + // map management #define SERVERMAXMAPFACTOR 10 // 10 is huge... if you're running low on RAM, consider 9 as maximum (don't go higher than 10, or players will name their ugly dog after you) @@ -30,6 +50,9 @@ const char *servermappath_incom = SERVERMAP_PATH_INCOMING; #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) #define FLOORPLANBUFSIZE (sizeof(struct servsqr) << ((SERVERMAXMAPFACTOR) * 2)) // that's 4MB for size 10 maps (or 1 MB for size 9 maps) +#define GAMEHISTLEN 32 // account the last half hour that the map was played +#define PENALTYUNIT 100 + stream *readmaplog = NULL; // the readmaps thread always logs directly to file struct servermap // in-memory version of a map file on a server @@ -53,6 +76,17 @@ struct servermap // in-memory version of a map file on a server uchar *enttypes; // table of entity types short *entpos_x, *entpos_y; + int modes_allowed; // modes_possible reduced to MP modes (if dedicated server) + configsetvalues modes[GMODE_NUM]; // maprot parameters for this map + int modes_auto; // modes to be suggested automatically + int modes_cr[CR_NUM]; // modes available for manual voting, depending on client role + int modes_pn[MAXPLAYERS]; // modes suited for a certain number of players + char lastmodes[GAMEHISTLEN]; // last played game modes on this map (one entry per minute) + int lastplayed[GAMEHISTLEN]; // time the map was played last (one entry per minute) + int penalty[GMODE_NUM]; // prevent overplaying a mode on this map + int mappenalty; // prevent overplaying of map + int bestmode, weight; // mode with least penalty, weight of that mode on this map + const char *err; bool isok; // definitive flag! #ifdef _DEBUG @@ -414,8 +448,19 @@ struct servermap // in-memory version of a map file on a server isok = true; } } + + void oneminuteplayed(int tm, char mode) + { + memmove(lastmodes + 1, lastmodes, GAMEHISTLEN - 1); + memmove(lastplayed + 1, lastplayed, GAMEHISTLEN - 1); + lastmodes[0] = mode; + lastplayed[0] = tm; + } }; +int servermapsortname(servermap **a, servermap **b) { return strcmp((*a)->fname, (*b)->fname); } // sort by name (ascending) +int servermapsortweight(servermap **a, servermap **b) { return (*b)->weight - (*a)->weight; } // sort by weight (descending) + // data structures to sync data flow between main thread and readmapsthread volatile servermap *servermapdropbox = NULL; // changed servermap entry back to the main thread volatile bool startnewservermapsepoch = false; // signal readmapsthread to start an new full search @@ -680,64 +725,182 @@ int readserverconfigsthread(void *data) // maprot.cfg -#define CONFIG_MAXPAR 6 +#define MODEHISTLEN 64 // remember the game modes of the last hour (roughly) -struct configset +struct configset : configsetvalues { - string mapname; - union - { - struct { int mode, time, vote, minplayer, maxplayer, skiplines; }; - int par[CONFIG_MAXPAR]; - }; + char mapname[MAXMAPNAMELEN + 1]; + int mmask; + char asterisk; }; -servermap *getservermap(const char *mapname); - struct servermaprot : serverconfigfile { - vector configsets; - int curcfgset; + vector configsets, preloaded; + configsetvalues base[GMODE_NUM], prebase[GMODE_NUM]; - servermaprot() : curcfgset(-1) {} + int lastplayedmodepointer; + char lastplayedmodes[MODEHISTLEN]; + int modepenalty[GMODE_NUM]; + servermap *current; + + servermaprot() + { + lastplayedmodepointer = 0; + loopi(MODEHISTLEN) lastplayedmodes[i] = -1; + current = NULL; + } void clear() { configsets.shrink(0); + loopi(GMODE_NUM) base[i].empty(); + } + + bool load() + { + preloaded.shrink(0); + loopi(GMODE_NUM) prebase[i].empty(); + if(serverconfigfile::load()) + { + configset c, cempty; + memset(&cempty, 0, sizeof(cempty)); + cempty.empty(); + int line = 0; + char *b, *l, *p = buf; + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; line++; + l = strtok_r(l, " ", &b); + if(l) + { + c = cempty; + char *e = strchr(l, '*'); + c.asterisk = e ? clamp(int(e - l), 0, MAXMAPNAMELEN) : -1; + filtertext(c.mapname, behindpath(l), FTXT__MAPNAME, MAXMAPNAMELEN); + bool haskeywords = false; + l = strtok_r(NULL, " ", &b); + if(l) c.mmask = gmode_parse(l); + while((l = strtok_r(NULL, " ", &b))) + { + char *kb, *k = strtok_r(l, ":", &kb); + if(k) + { + loopi(CONFIG_MAXPAR) if(!strcmp(k, maprotkeywords[i])) + { + if((k = strtok_r(NULL, ":", &kb))) + { + c.par[i] = clamp(atoi(k), -100, 100); + haskeywords = true; + break; + } + } + } + } + if(haskeywords) + { + if(c.asterisk == 0) + { + loopj(GMODE_NUM) if((1 << j) & c.mmask) prebase[j].add(c); + } + else preloaded.add(c); + } + } + } + // prepare compiled maprot message for clients + return true; + } + return false; } void read() { - const char *sep = ": "; - configset c; - int i, line = 0; - char *b, *l, *p = buf; - logline(ACLOG_VERBOSE,"reading map rotation '%s'", filename); - while(p < buf + filelen) - { - l = p; p += strlen(p) + 1; line++; - l = strtok_r(l, sep, &b); - if(l) - { - copystring(c.mapname, behindpath(l)); - for(i = 3; i < CONFIG_MAXPAR; i++) c.par[i] = 0; // default values - for(i = 0; i < CONFIG_MAXPAR; i++) - { - if((l = strtok_r(NULL, sep, &b)) != NULL) c.par[i] = atoi(l); - else break; - } - if(i > 2) - { - configsets.add(c); - logline(ACLOG_VERBOSE," %s, %s, %d minutes, vote:%d, minplayer:%d, maxplayer:%d, skiplines:%d", c.mapname, modestr(c.mode, false), c.time, c.vote, c.minplayer, c.maxplayer, c.skiplines); - } - else logline(ACLOG_INFO," error in line %d, file %s", line, filename); - } - } + loopi(GMODE_NUM) base[i] = prebase[i]; + loopv(preloaded) configsets.add(preloaded[i]); logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); } - int next(bool notify = true, bool nochange = false) // load next maprotation set + void initmap(servermap *m, stream *f) // set maprot parameters of a servermap + { + memcpy(m->modes, base, sizeof(m->modes)); + loopv(configsets) + { + configset &c = configsets[i]; + if(c.asterisk < 0 ? !strcmp(c.mapname, m->fname) : !strncmp(c.mapname, m->fname, c.asterisk)) + { + loopj(GMODE_NUM) if((1 << j) & c.mmask) m->modes[j].add(c); + } + } + m->modes_allowed = m->entstats.modes_possible & (isdedicated ? GMMASK__MPNOCOOP : GMMASK__ALL); + int rm[CR_NUM] = { 0 }, man = 0; + loopj(GMODE_NUM) + { + if(m->modes[j].manual > 0) man |= 1 << j; + for(int i = max(0, (int)m->modes[j].restrict); i < CR_NUM; i++) rm[i] |= 1 << j; + } + m->modes_auto = m->modes_allowed & ~man; + loopi(CR_NUM) m->modes_cr[i] = m->modes_allowed & rm[i]; + loopi(MAXPLAYERS) m->modes_pn[i] = 0; + loopj(GMODE_NUM) + { + if((1 << j) & m->modes_allowed) + { + int lb = m->modes[j].minplayers < 1 ? 0 : m->modes[j].minplayers - 1, ub = m->modes[j].maxplayers < 1 || m->modes[j].maxplayers > MAXPLAYERS ? MAXPLAYERS : m->modes[j].maxplayers; + while(lb < ub) m->modes_pn[lb++] |= 1 << j; + } + } + if(f) + { + string s; + f->printf("maprot parameters for: %s%s\n", m->fpath, m->fname); + configsetvalues e; + e.empty(); + loopj(GMODE_NUM) if((1 << j) & GMMASK__MPNOCOOP) + { + f->printf(" mode %s:", gmode_enum(1 << j, s)); + loopk(CONFIG_MAXPAR) if(e.par[k] != m->modes[j].par[k]) f->printf(" %s:%d", maprotkeywords[k], m->modes[j].par[k]); + f->printf("\n"); + } + f->printf(" modes_allowed %s\n", gmode_enum(m->modes_allowed, s)); + f->printf(" modes_auto %s\n", gmode_enum(m->modes_auto, s)); + f->printf(" modes man %s\n", gmode_enum(man, s)); + loopi(CR_NUM) f->printf(" modes_cr[%d] %s\n", i, gmode_enum(m->modes_cr[i], s)); + loopi(MAXPLAYERS) f->printf(" modes_pn[%02d] %s\n", i + 1, gmode_enum(m->modes_pn[i], s)); + f->printf("\n"); + } + } + + void oneminutepassed(int mode) + { + lastplayedmodes[lastplayedmodepointer++] = mode; + lastplayedmodepointer %= MODEHISTLEN; + } + + void calcmodepenalties() // sum up, how many minutes each game mode was played during the last hour (including server-empty times) + { + loopi(GMODE_NUM) modepenalty[i] = 0; + loopi(MODEHISTLEN) + { + int n = lastplayedmodes[i]; + if(n >= 0 && (GMMASK__MPNOCOOP & (1 << n))) modepenalty[n] += PENALTYUNIT; + } + } + + void calcmappenalties(); // sum up, how long ago each map+mode was played + servermap *recalcgamesuggestions(int numpl); // regenerate list of playable games (map+mode) + + servermap *imfeelinglucky(int numpl = -1) + { + return recalcgamesuggestions(numpl); + } + + servermap *getcurrent() + { + if(!current) current = imfeelinglucky(); + return current; + } + + servermap *next(bool notify = true, bool nochange = false) // load next maprotation set { #ifndef STANDALONE if(!isdedicated) @@ -745,33 +908,16 @@ struct servermaprot : serverconfigfile defformatstring(nextmapalias)("nextmap_%s", getclientmap()); const char *map = getalias(nextmapalias); // look up map in the cycle startgame(map && notify ? map : getclientmap(), getclientmode(), -1, notify); - return -1; + return NULL; } #endif - if(configsets.empty()) fatal("maprot unavailable"); - int n = numclients(); - int csl = configsets.length(); - int ccs = curcfgset; - if(ccs >= 0 && ccs < csl) ccs += configsets[ccs].skiplines; - configset *c = NULL; - loopi(3 * csl + 1) - { - ccs++; - if(ccs >= csl || ccs < 0) ccs = 0; - c = &configsets[ccs]; - if((n >= c->minplayer || i >= csl) && (!c->maxplayer || n <= c->maxplayer || i >= 2 * csl)) - { - if(getservermap(c->mapname)) break; - else logline(ACLOG_INFO, "maprot error: map '%s' not found", c->mapname); - } - if(i >= 3 * csl) fatal("maprot unusable"); // not a single map in rotation can be found... - } + servermap *s = imfeelinglucky(numclients()); if(!nochange) { - curcfgset = ccs; - startgame(c->mapname, c->mode, c->time, notify); + current = s; + startgame(s->fname, s->bestmode, s->modes[s->bestmode].time, notify); } - return ccs; + return s; } void restart(bool notify = true) // restart current map @@ -783,11 +929,9 @@ struct servermaprot : serverconfigfile return; } #endif - startgame("ac_desert", 0, -1, notify); + servermap *s = getcurrent(); + startgame(s->fname, s->bestmode, s->modes[s->bestmode].time, notify); } - - configset *current() { return configsets.inrange(curcfgset) ? &configsets[curcfgset] : NULL; } - configset *get(int ccs) { return configsets.inrange(ccs) ? &configsets[ccs] : NULL; } }; // generic commented IP list @@ -1558,8 +1702,8 @@ struct serverparameter : serverconfigfile #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "" }; -const char *siddocchaptersorting = "vmd"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "" }; +const char *siddocchaptersorting = "gvmd"; void serverparameters_dumpdocu(char *fname) { From 2147d60934b2597709976a4884f96246a1a3f256 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 15 Mar 2016 15:41:51 +0100 Subject: [PATCH 18/45] option to dump maprot suggestions to file --- config/serverparameters.cfg | 4 ++++ source/src/server.cpp | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index f69f8033..75e5f639 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -38,5 +38,9 @@ dumpmaprot:1 // integer [0..1], default 0, 0:disabled, 1:enabled dumpparameters:0 +// dumpsuggestions dump maprot suggestions whenever recalculated +// integer [0..1], default 1, 0:disabled, 1:enabled +dumpsuggestions:1 + diff --git a/source/src/server.cpp b/source/src/server.cpp index 33849ebf..3b798af8 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -271,6 +271,8 @@ servermap *randommap() return s; } +SERVPARLIST(dumpsuggestions, 0, 1, 0, endis, "ddump maprot suggestions whenever recalculated"); + servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of playable games (map+mode) { calcmodepenalties(); @@ -299,6 +301,23 @@ servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of } } gamesuggestions.sort(servermapsortweight); + if(dumpsuggestions) + { + defformatstring(fname)("%sdebug/maprotsuggestions_dump_%s.txt", scl.logfilepath, numtime()); + stream *f = openfile(path(fname), "w"); + f->printf("// maprot suggestions dump, %s\n\nmode penalties:", asctimestr()); + string b; + loopj(GMODE_NUM) if((1 << j) & GMMASK__MPNOCOOP) f->printf(" %s:%d", gmode_enum(1 << j, b), modepenalty[j]); + f->printf("\n\n"); + loopv(gamesuggestions) + { + servermap &s = *gamesuggestions[i]; + f->printf("map: \"%s%s\", weight: %d, bestmode: %d (%s), mappenalty: %d\n mode penalties:", s.fpath, s.fname, s.weight, s.bestmode, gmode_enum(1 << s.bestmode, b), s.mappenalty); + loopj(GMODE_NUM) if(s.penalty[j]) f->printf(" %s:%d", gmode_enum(1 << j, b), s.penalty[j]); + f->printf("\n\n"); + } + delete f; + } return gamesuggestions.length() ? gamesuggestions[0] : randommap(); } From f58150696029ef625d31864ee2d5b4ce34be194d Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 16 Mar 2016 00:33:06 +0100 Subject: [PATCH 19/45] no more static buffers for iptoa and iprtoa --- source/src/autodownload.cpp | 3 ++- source/src/serverfiles.h | 30 ++++++++++++++++-------------- source/src/tools.cpp | 20 +++++++------------- source/src/tools.h | 4 ++-- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/source/src/autodownload.cpp b/source/src/autodownload.cpp index 43146e0f..20a91152 100644 --- a/source/src/autodownload.cpp +++ b/source/src/autodownload.cpp @@ -76,7 +76,8 @@ int pingpckserver(void *data) // fetch updates.txt from a media server, measure u.set(s->host); s->resolved = h.set_host(u.domain) ? 1 : 0; SDL_mutexP(pckpinglog_lock); - cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ENET_NET_TO_HOST_32(h.ip.host))); + string b; + cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ENET_NET_TO_HOST_32(h.ip.host), b)); SDL_mutexV(pckpinglog_lock); if(s->resolved) { diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 7df17421..14420188 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -988,19 +988,20 @@ struct serveripcclist : serverconfigfile } ipranges.sort(cmpiprange); int orglength = ipranges.length(); + string b1, b2; loopv(ipranges) { // make sure, ranges don't overlap - otherwise bsearch gets unpredictable if(!i) continue; if(ipranges[i].ur <= ipranges[i - 1].ur) { if(cmpcomment(ipranges[i], ipranges[i - 1])) - logline(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i]), printcomment(ipranges[i]), iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1])); + logline(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i], b1), printcomment(ipranges[i]), iprtoa(ipranges[i - 1], b2), printcomment(ipranges[i - 1])); else { if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i])); + logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); else - logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); } ipranges.remove(i--); continue; } @@ -1008,26 +1009,26 @@ struct serveripcclist : serverconfigfile { if(!cmpcomment(ipranges[i], ipranges[i - 1])) // same comment { - logline(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + logline(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } else { - logline(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1]), printcomment(ipranges[i - 1]), - iprtoa(ipranges[i]), printcomment(ipranges[i]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + logline(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1], b1), printcomment(ipranges[i - 1]), + iprtoa(ipranges[i], b2), printcomment(ipranges[i]), b2, printcomment(ipranges[i])); errors++; ipranges.remove(i--); continue; } } if(ipranges[i].lr - 1 == ipranges[i - 1].ur && !cmpcomment(ipranges[i], ipranges[i - 1])) { - logline(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i]), printcomment(ipranges[i])); + logline(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } } - if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i]), printcomment(ipranges[i])); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i], b1), printcomment(ipranges[i])); logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } @@ -1078,25 +1079,26 @@ struct serveripblacklist : serverconfigfile } ipranges.sort(cmpiprange); int orglength = ipranges.length(); + string b1, b2; loopv(ipranges) { if(!i) continue; if(ipranges[i].ur <= ipranges[i - 1].ur) { if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i])); + logline(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); else - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + logline(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); ipranges.remove(i--); continue; } if(ipranges[i].lr <= ipranges[i - 1].ur) { - logline(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i])); + logline(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2)); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } } - if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i])); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i], b1)); logline(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); } @@ -1199,7 +1201,7 @@ struct servernickblacklist : serverconfigfile if(logcheck(ACLOG_VERBOSE)) { logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); - string text; + string text, b; enumeratekt(whitelist, const char *, key, int, idx, { text[0] = '\0'; @@ -1207,7 +1209,7 @@ struct servernickblacklist : serverconfigfile { iprchain &ic = whitelistranges[i]; if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); - else concatformatstring(text, " %s", iprtoa(ic.ipr)); + else concatformatstring(text, " %s", iprtoa(ic.ipr, b)); } logline(ACLOG_VERBOSE, " accept %s%s", key, text); }); diff --git a/source/src/tools.cpp b/source/src/tools.cpp index f78c8a15..68ca9bc3 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -503,23 +503,17 @@ const char *atoipr(const char *s, iprange *ir) return s; } -const char *iptoa(const enet_uint32 ip) +const char *iptoa(const enet_uint32 ip, char *b) { - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - formatstring(s[buf])("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); - return s[buf]; + formatstring(b)("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); + return b; } -const char *iprtoa(const struct iprange &ipr) +const char *iprtoa(const struct iprange &ipr, char *b) { - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - if(ipr.lr == ipr.ur) copystring(s[buf], iptoa(ipr.lr)); - else formatstring(s[buf])("%s-%s", iptoa(ipr.lr), iptoa(ipr.ur)); - return s[buf]; + if(ipr.lr == ipr.ur) return iptoa(ipr.lr, b); + else formatstring(b)("%d.%d.%d.%d-%d.%d.%d.%d", (ipr.lr >> 24) & 255, (ipr.lr >> 16) & 255, (ipr.lr >> 8) & 255, ipr.lr & 255, (ipr.ur >> 24) & 255, (ipr.ur >> 16) & 255, (ipr.ur >> 8) & 255, ipr.ur & 255); + return b; } int cmpiprange(const struct iprange *a, const struct iprange *b) diff --git a/source/src/tools.h b/source/src/tools.h index ca8dc5fa..56fdba08 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -959,8 +959,8 @@ struct iprange { enet_uint32 lr, ur; }; struct iprangecc : iprange { union { int ci; char cc[4]; enet_uint32 cu; }; }; extern const char *atoip(const char *s, enet_uint32 *ip); extern const char *atoipr(const char *s, iprange *ir); -extern const char *iptoa(const enet_uint32 ip); -extern const char *iprtoa(const struct iprange &ipr); +extern const char *iptoa(const enet_uint32 ip, char *b); +extern const char *iprtoa(const struct iprange &ipr, char *b); extern int cmpiprange(const struct iprange *a, const struct iprange *b); extern int cmpipmatch(const struct iprange *a, const struct iprange *b); extern int cvecprintf(vector &v, const char *s, ...) PRINTFARGS(2, 3); From 0148c5b9a97f60174149d4a4274ed28bdbe49434 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sun, 20 Mar 2016 14:45:50 +0100 Subject: [PATCH 20/45] add new protocol data type for IP addresses (to avoid casting IPs to signed integer) also mute single number debug messages for compound data structures like string and key: no longer print a debug message for every char of a string --- source/src/protocol.cpp | 27 +++++++++++++++++++++++++-- source/src/protos.h | 4 ++++ source/src/server.cpp | 29 +++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 61d1c905..bc0f4352 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -3,9 +3,9 @@ #include "cube.h" #ifdef _DEBUG -bool protocoldbg = false; +bool protocoldbg = false, debugmute = false; void protocoldebug(bool enable) { protocoldbg = enable; } -#define DEBUGCOND (protocoldbg) +#define DEBUGCOND (protocoldbg && !debugmute) #endif // all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small). @@ -123,9 +123,11 @@ float getfloat(ucharbuf &p) template static inline void sendstring_(const char *text, T &p) { + DEBUGCODE(debugmute = true); const char *t = text; if(t) { while(*t) putint(p, *t++); } putint(p, 0); + DEBUGCODE(debugmute = false); DEBUGVAR(text); } void sendstring(const char *t, ucharbuf &p) { sendstring_(t, p); } @@ -134,6 +136,7 @@ void sendstring(const char *t, vector &p) { sendstring_(t, p); } void getstring(char *text, ucharbuf &p, int len) { + DEBUGCODE(debugmute = true); char *t = text; do { @@ -142,9 +145,29 @@ void getstring(char *text, ucharbuf &p, int len) *t = getint(p); } while(*t++); + DEBUGCODE(debugmute = false); DEBUGVAR(text); } +template +static inline void putip4_(T &p, enet_uint32 ip) +{ + DEBUGCODE(string IP; iptoa(ip, IP)); + DEBUGVAR(IP); + p.put(ip); p.put(ip>>8); p.put(ip>>16); p.put(ip>>24); +} +void putip4(ucharbuf &p, enet_uint32 ip) { putip4_(p, ip); } +void putip4(packetbuf &p, enet_uint32 ip) { putip4_(p, ip); } +void putip4(vector &p, enet_uint32 ip) { putip4_(p, ip); } + +enet_uint32 getip4(ucharbuf &p) +{ + enet_uint32 ip = p.get() | (p.get()<<8) | (p.get()<<16) | (p.get()<<24); + DEBUGCODE(string IP; iptoa(ip, IP)); + DEBUGVAR(IP); + return ip; +} + #define GZMSGBUFSIZE ((MAXGZMSGSIZE * 11) / 10) static uchar *gzbuf = new uchar[GZMSGBUFSIZE]; // not thread-safe, but nesting is no problem diff --git a/source/src/protos.h b/source/src/protos.h index 974ede37..c8ac7f15 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1059,6 +1059,10 @@ extern void sendstring(const char *t, ucharbuf &p); extern void sendstring(const char *t, packetbuf &p); extern void sendstring(const char *t, vector &p); extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS); +extern void putip4(ucharbuf &p, enet_uint32 ip); +extern void putip4(packetbuf &p, enet_uint32 ip); +extern void putip4(vector &p, enet_uint32 ip); +extern enet_uint32 getip4(ucharbuf &p); extern void putgzbuf(vector &d, vector &s); // zips a vector into a stream, stored in another vector extern ucharbuf *getgzbuf(ucharbuf &p); // fetch a gzipped buffer; needs to be freed properly later extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() diff --git a/source/src/server.cpp b/source/src/server.cpp index 3b798af8..b403635e 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -584,11 +584,11 @@ void sendf(int cn, int chan, const char *format, ...) va_start(args, format); while(*format) switch(*format++) { - case 'x': + case 'x': // exclude client from getting this message exclude = va_arg(args, int); break; - case 'v': + case 'v': // integer array of variable length { int n = va_arg(args, int); int *v = va_arg(args, int *); @@ -596,20 +596,37 @@ void sendf(int cn, int chan, const char *format, ...) break; } - case 'i': + case 'k': // 32 bytes (uchar) + p.put(va_arg(args, uchar *), 32); + break; + + case 'i': // one to nine integer arguments { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); break; } - case 'a': + + case 'a': // one to nine mostly positive integer arguments { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putaint(p, va_arg(args, int)); break; } - case 's': sendstring(va_arg(args, const char *), p); break; - case 'm': + + case 'u': // unsigned integer argument + putuint(p, va_arg(args, int)); + break; + + case 'I': // IPv4 address argument + putip4(p, va_arg(args, enet_uint32)); + break; + + case 's': // string + sendstring(va_arg(args, const char *), p); + break; + + case 'm': // raw message block (unused) { int n = va_arg(args, int); p.put(va_arg(args, uchar *), n); From d6af5be3da38538e74298c33748c049d1816fb67 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 21 Mar 2016 13:58:27 +0100 Subject: [PATCH 21/45] add auth to client server connect protocol --- config/serverparameters.cfg | 11 +++ source/src/bot/bot.cpp | 1 - source/src/clientgame.cpp | 1 - source/src/clients2c.cpp | 89 +++++++++++++++++++- source/src/crypto.cpp | 2 +- source/src/protocol.cpp | 18 +++- source/src/protocol.h | 7 +- source/src/protos.h | 24 ++---- source/src/server.cpp | 156 ++++++++++++++++++++++++++++++++--- source/src/server.h | 14 +++- source/src/serverbrowser.cpp | 1 - source/src/serverfiles.h | 4 +- source/src/serverms.cpp | 4 +- 13 files changed, 287 insertions(+), 45 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 75e5f639..456fdd4c 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -9,6 +9,17 @@ +// **** Server setup settings **** + +// auth_verify_ip Verify server IP reported by the client during auth +// integer [0..1], default 0, 0:disabled, 1:enabled +auth_verify_ip:0 + +// mandatory_auth Enforce IDs for all clients even on unlisted server/LAN game +// integer [0..1], default 1, 0:disabled, 1:enabled +mandatory_auth:1 + + // **** Maprot settings **** // gamepenalty_cutoff Number of minutes to remember that a map+mode combination has been played diff --git a/source/src/bot/bot.cpp b/source/src/bot/bot.cpp index 1fa0c83d..06ff761b 100644 --- a/source/src/bot/bot.cpp +++ b/source/src/bot/bot.cpp @@ -17,7 +17,6 @@ vector bots; extern int triggertime; extern itemstat itemstats[]; -extern ENetHost *clienthost; const vec g_vecZero(0, 0, 0); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index c26b753a..816c5d7f 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -370,7 +370,6 @@ const char *currentserver(int i) // [client version] // using the curpeer directly we can get the info of our currently connected server string r; r[0] = '\0'; - extern ENetPeer *curpeer; if(curpeer) { switch(i) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 565bce87..8a955400 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -320,6 +320,9 @@ void onChangeVote(int mod, int id) VARP(voicecomsounds, 0, 1, 2); bool medals_arrived=0; medalsst a_medals[END_MDS]; + +struct session_s { enet_uint32 serverip, clientip, clientipcensored, curpeerip; int curpeerport, serverclock, clientclock, cn, clientsalt; uchar serverpubkey[32], clientsignature[64]; } session; + void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { static char text[MAXTRANS]; @@ -356,7 +359,12 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { case SV_SERVINFO: // welcome message from the server { - int mycn = getint(p), prot = getint(p); + string tmp1, tmp2; + session_s *s = &session; + memset(s, 0, sizeof(session_s)); + + s->cn = getint(p); + int prot = getint(p); if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION)) { conoutf("\f3incompatible game protocol (local protocol: %d :: server protocol: %d)", CUR_PROTOCOL_VERSION, prot); @@ -365,9 +373,84 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) else disconnect(); return; } - sessionid = getint(p); - player1->clientnum = mycn; + int req_auth = getint(p); + if(!sk && req_auth) + { + conoutf("\f3server requires client ID to connect"); + conoutf("\f3please unlock your ID (by entering the corect password) or generate a new one"); + disconnect(); + return; + } if(getint(p) > 0) conoutf("INFO: this server is password protected"); + sessionid = getint(p); // salt + s->serverip = getip4(p); + s->clientip = getip4(p); + s->clientipcensored = getip4(p); + s->serverclock = getint(p); + char cc[3] = { (char)getint(p), (char)getint(p), 0 }; + filtercountrycode(cc, cc); + p.get(s->serverpubkey, 32); + entropy_get((uchar *)&(s->clientsalt), sizeof(int)); + s->clientclock = (int) (time(NULL) / (time_t) 60); + int clockoffset = s->serverclock - s->clientclock; + if(curpeer && iabs(clockoffset) > 60 * 24) conoutf("\f3warning: server and client clock are offset more than one day (%d minutes)", clockoffset); + + clientlogf("own IP: %s, censored own IP: %s, %s, clock offset %d hours %d minutes", iptoa(s->clientip, tmp1), iptoa(s->clientipcensored, tmp2), cc, clockoffset / 60, clockoffset % 60); + if(curpeer && s->serverip) + { + s->curpeerip = ENET_NET_TO_HOST_32(curpeer->address.host); + s->curpeerport = curpeer->address.port; + } + if(s->curpeerip && s->curpeerip != s->serverip) conoutf("\f3warning: server IP mismatch (true: %s, reported: %s)", iptoa(s->curpeerip, tmp1), iptoa(s->serverip, tmp2)); + + defformatstring(challenge)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", sessionid, s->cn, iptoa(s->clientipcensored, tmp1), cc, iptoa(s->serverip, tmp2), s->curpeerport); + concatformatstring(challenge, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, s->serverpubkey, 32), s->serverclock, s->clientclock, s->clientsalt); + clientlogf("auth challenge: %s", challenge); + int chlen = (int)strlen(challenge); + if(chlen >= MAXSTRLEN - 65) + { + conoutf("\f3ERROR: oversized auth challenge (%d), can't connect", chlen); + disconnect(); + return; + } + if(sk) ed25519_sign((uchar*)challenge, NULL, (uchar*)challenge, chlen, sk); + + packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(pr, SV_SERVINFO_RESPONSE); + putint(pr, s->clientsalt); + putint(pr, s->clientclock); + putint(pr, s->curpeerport); + putip4(pr, s->curpeerip); + int wantauth = sk && curpeer; + putint(pr, wantauth); + if(wantauth) + { + memcpy(s->clientsignature, challenge, 64); + pr.put(sk + 32, 32); + pr.put(s->clientsignature, 64); + } + sendpackettoserv(1, pr.finalize()); + break; + } + + case SV_SERVINFO_CONTD: // 2nd welcome message from the server + { + session_s *s = &session; + string tmp, servauth; + int auth = getint(p); + if(auth) + { + formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); + memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); + p.get((uchar*)servauth, 64); + } + if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); + if(sk && auth) + { + if(!ed25519_sign_check((uchar*)servauth, auth + 64, s->serverpubkey)) conoutf("\f3server authentication failed"); + else clientlogf("server authenticated as %s", bin2hex(tmp, s->serverpubkey, 32)); + } + player1->clientnum = s->cn; sendintro(); break; } diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index 1d39c514..ef1c4df9 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -726,7 +726,7 @@ VARP(authmemusage, 2, 24, (1<<10) - 1); // megabytes of RAM to use for passw VARP(authrounds, 0, 0, INT_MAX - 1); // create new password hashes with a fixed number of rounds (if authrounds > 0) VARP(authmaxtime, 1<<9, 1<<12, 1<<16); // create new password hashes with a fixed amount of time (in ms) -static uchar *sk = NULL; // game key +uchar *sk = NULL; // game key static struct { uchar *salt, *priv; uint pwdcfg; } passdargs; static int passdeferred(void *pass) // decrypt the private key in the background diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index bc0f4352..0887c75a 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -422,6 +422,17 @@ void filterlang(char *d, const char *s) *d = '\0'; } +void filtercountrycode(char *d, const char *s) // returns exactly two uppercase chars or "--" +{ + d[0] = d[1] = '-'; + d[2] = '\0'; + if(isalpha(s[0]) && isalpha(s[1]) && !s[2]) + { + d[0] = toupper(s[0]); + d[1] = toupper(s[1]); + } +} + void trimtrailingwhitespace(char *s) { for(int n = (int)strlen(s) - 1; n >= 0 && isspace(s[n]); n--) @@ -485,7 +496,8 @@ char *gmode_enum(int gm, char *buf) // convert mode bitmask to string with sorte static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { - SV_SERVINFO, 5, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, + SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, + SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_AUTHT, 0, SV_AUTHREQ, 0, SV_AUTHTRY, 0, SV_AUTHANS, 0, SV_AUTHCHAL, 0, SV_GIBDIED, 5, SV_DIED, 5, SV_GIBDAMAGE, 7, SV_DAMAGE, 7, SV_HITPUSH, 6, SV_SHOTFX, 6, SV_THROWNADE, 8, @@ -524,8 +536,8 @@ int msgsizelookup(int msg) const char *disc_reason(int reason) { - static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync" }, - *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"" }, + static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync", "auth failed" }, + *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"", "server overloaded" }, *removed[] = { "removed from server", "failed admin login", "inappropriate nickname", "annoyance limit exceeded", "SPAM", "DOS", "ludicrous speed", "fishslapped", "afk" }, *kick[] = { "kicked", "banned", "kicked by vote", "banned by vote", "kicked by server operator", "banned by server operator", "auto kicked - teamkilling", "auto banned - teamkilling", "auto kicked - friendly fire", "auto banned - friendly fire", "auto kick", "auto ban" }; diff --git a/source/src/protocol.h b/source/src/protocol.h index 57afd1a9..225c5c19 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -6,6 +6,7 @@ #define CUBE_SERVINFO_PORT_LAN 28762 #define CUBE_SERVINFO_PORT(serverport) (serverport+1) #define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1) +#define CLIENTIPCENSOR 12 // blank out the last 12 bits of client's IPs before telling them to others (compromise between "16" from AC 0.9x and "8" from AC 1.1) #define PROTOCOL_VERSION 1203 // bump when protocol changes (use negative numbers for mods!) #define DEMO_VERSION 2 // bump when demo format changes #define DEMO_MAGIC "ASSAULTCUBE_DEMO" @@ -24,7 +25,7 @@ extern bool modprotocol; // network messages codes, c2s, c2c, s2c enum { - SV_SERVINFO = 0, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, + SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_AUTHT, SV_AUTHREQ, SV_AUTHTRY, SV_AUTHANS, SV_AUTHCHAL, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, @@ -75,8 +76,8 @@ enum { EDITXY_HEIGHT = 0, EDITXY_TEX, EDITXY_TYPE, EDITXY_VDELTA, EDITXY_EQUALIS #define DNF 100.0f #define DVELF 4.0f -enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, // div. protocol errors - DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, // connection refused +enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, DISC_AUTH, // div. protocol errors + DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, DISC_OVERLOAD, // connection refused DISC_REMOVED = 32, DISC_SOPLOGINFAIL, DISC_BADNICK, DISC_ANNOY, DISC_SPAM, DISC_DOS, DISC_SPEEDING, DISC_FISHSLAP, DISC_AFK, // disconnected DISC_KICK = 48, DISC_BAN, DISC_VOTEKICK, DISC_VOTEBAN, DISC_OPKICK, DISC_OPBAN, DISC_TKKICK, DISC_TKBAN, DISC_FFKICK, DISC_FFBAN, DISC_AUTOKICK, DISC_AUTOBAN, DISC_UNKNOWN = 99, DISC_BECAUSE }; diff --git a/source/src/protos.h b/source/src/protos.h index c8ac7f15..3dfd3706 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -521,6 +521,7 @@ extern void gets2c(); extern void c2sinfo(playerent *d); extern void c2skeepalive(); extern void neterr(const char *s); +extern ENetPeer *curpeer; extern int getclientnum(); extern void getmap(char *name = NULL, char *callback = NULL); extern void newteam(char *name); @@ -964,6 +965,9 @@ extern void tigerhash_finish(uchar *hash, void *state); extern const char *bin2hex(char *d, const uchar *s, int len); extern int hex2bin(uchar *d, const char *s, int maxlen); extern void ed25519_pubkey_from_private(uchar *pubkey, const uchar *privkey); +extern void ed25519_sign(uchar *sm, int *smlen, const uchar *m, int mlen, const uchar *sk); +extern uchar *ed25519_sign_check(uchar *sm, int smlen, const uchar *pk); +extern uchar *sk; // client game key extern void loadcertdir(); // load all certs in "config/certs" #if 0 // crypto // for AUTH @@ -1070,11 +1074,13 @@ extern bool validmapname(const char *s); // checks for length, allowed chars and extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); extern void filterlang(char *d, const char *s); +extern void filtercountrycode(char *d, const char *s); // returns exactly two uppercase chars or "--" extern void trimtrailingwhitespace(char *s); extern int msgsizelookup(int msg); extern const char *disc_reason(int reason); extern string mastername; extern int masterport; +extern bool usemaster; extern ENetSocket connectmaster(); extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm); extern const char *genpwdhash(const char *name, const char *pwd, int salt); @@ -1146,8 +1152,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath; - uchar *ssk; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -1157,8 +1162,7 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), - ssk(NULL), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { @@ -1176,17 +1180,7 @@ struct servercommandline // client: dtwhzbsave switch(arg[1]) { // todo: gjlqEJQU - case 'Y': // private server key - { - uchar temp[64]; - if(hex2bin(temp, a, 32) == 32) - { - if(!ssk) ssk = new uchar[64]; - memcpy(ssk, temp, 32); - ed25519_pubkey_from_private(ssk + 32, ssk); - } - break; - } + case 'Y': ssk = a; break; // private server key case '-': if(!strncmp(arg, "--demofilenameformat=", 21)) { diff --git a/source/src/server.cpp b/source/src/server.cpp index b403635e..e120c929 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -74,6 +74,9 @@ struct servergame // config vector servermaps, gamesuggestions; // all available maps kept in memory +string servpubkey; +uchar ssk[64] = { 0 }, *spk = ssk + 32; +enet_uint32 servownip; // IP address of this server, as seen by the master or other clients/servers servercontroller *svcctrl = NULL; serverparameter serverparameters; @@ -91,7 +94,7 @@ serverinfofile serverinfomotd; bool isdedicated = false; ENetHost *serverhost = NULL; -int laststatus = 0, servmillis = 0; +int laststatus = 0, servmillis = 0, servclock = 0; vector clients; vector worldstates; @@ -116,7 +119,7 @@ void poll_serverthreads() // called once per mainloop-timeslice static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; static bool debugmaprot = true; - switch(stage) + switch(stage) // cycle through some tasks once per minute (mostly file polling) { default: // start first thread { @@ -125,6 +128,7 @@ void poll_serverthreads() // called once per mainloop-timeslice // wake readmapsthread logline(ACLOG_INFO,"waking readmapsthread"); startnewservermapsepoch = true; + servclock = (int) (time(NULL) / (time_t) 60); readmapsthread_sem->post(); stage = 1; lastworkerthreadstart = servmillis; @@ -626,7 +630,7 @@ void sendf(int cn, int chan, const char *format, ...) sendstring(va_arg(args, const char *), p); break; - case 'm': // raw message block (unused) + case 'm': // raw message block { int n = va_arg(args, int); p.put(va_arg(args, uchar *), n); @@ -2770,7 +2774,8 @@ bool restorescore(client &c) void sendservinfo(client &c) { - sendf(c.clientnum, 1, "ri5", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0); + sendf(c.clientnum, 1, "ri6IIIi3k", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.needsauth ? 1 : 0, scl.serverpassword[0] ? 1 : 0, c.salt, + servownip, c.ip, c.ip_censored, (c.connectclock = servclock), c.country[0], c.country[1], spk); } void putinitclient(client &c, packetbuf &p) @@ -2918,9 +2923,93 @@ int checktype(int type, client *cl) return type; } + +// process auth calculations in separate thread, since it may take a while + +struct servinfoauth +{ + string clientmsg, servermsg; // client message to verify, server message to sign + uchar clientpubkey[32]; + int type, wantauth, cn, clientsequence, clientmsglen, servermsglen, success; +}; + +ringbuf servinfoauths; +volatile servinfoauth *servinfoauth_dropbox = NULL; +volatile bool servinfoauth_done = false; +sl_semaphore servinfoauth_deferred(0, NULL); + +int deferredprocessingthread(void *nop) +{ + for(;;) + { + servinfoauth_deferred.wait(); + if(!servinfoauth_done && servinfoauths.length()) + { + servinfoauth *sa = servinfoauths.remove(); + servinfoauth_dropbox = sa; + sa->success = 0; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(!sa->wantauth) sa->success = 1; + else if(sa->clientmsglen && ed25519_sign_check((uchar*)sa->clientmsg, sa->clientmsglen + 64, sa->clientpubkey)) + { // client message checks out, now sign the answer + ed25519_sign((uchar*)sa->servermsg, NULL, (uchar*)sa->servermsg, sa->servermsglen, ssk); + sa->success = 1; + } + break; + } + } + servinfoauth_done = true; + } + } + return 0; +} + +void polldeferredprocessing() +{ + static void *deferredprocessingthread_ti = NULL; + if(!deferredprocessingthread_ti) deferredprocessingthread_ti = sl_createthread(deferredprocessingthread, NULL); + + if(servinfoauth_done) + { + servinfoauth *sa = (servinfoauth *)servinfoauth_dropbox; + servinfoauth_done = false; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(clients[sa->cn]->clientsequence == sa->clientsequence) // make sure, it's still the same client using that cn + { + if(!sa->success) disconnect_client(sa->cn, DISC_AUTH); + else + { // continue connecting + client *cl = clients[sa->cn]; + if(sa->wantauth) + { + memcpy(cl->pubkey, sa->clientpubkey, 32); + memcpy(cl->servinforesponse, sa->clientmsg, sa->clientmsglen + 64); + bin2hex(cl->pubkeyhex, cl->pubkey, 32); + cl->needsauth = true; // may have been voluntary, but it's done + sendf(sa->cn, 1, "riim", SV_SERVINFO_CONTD, 1, 64, sa->servermsg); + } + else sendf(sa->cn, 1, "rii", SV_SERVINFO_CONTD, 0); + } + } + break; + } + } + delete sa; + } + if(!servinfoauths.empty() && servinfoauth_deferred.getvalue() < 1) servinfoauth_deferred.post(); +} + // server side processing of updates: does very little and most state is tracked client only // could be extended to move more gameplay to server (at expense of lag) +SERVPARLIST(auth_verify_ip, 0, 0, 0, endis, "sVerify server IP reported by the client during auth"); + void process(ENetPacket *packet, int sender, int chan) { ucharbuf p(packet->data, packet->dataLength); @@ -2934,9 +3023,44 @@ void process(ENetPacket *packet, int sender, int chan) int clientrole = CR_DEFAULT; if(chan==0) return; - else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT); - else + else if(chan!=1 || ((type = getint(p)) != SV_SERVINFO_RESPONSE && type != SV_CONNECT)) disconnect_client(sender, DISC_TAGT); + else if(type == SV_SERVINFO_RESPONSE) { + if(servinfoauths.full()) disconnect_client(sender, DISC_OVERLOAD); + else + { + string tmp1, tmp2; + servinfoauth *sa = servinfoauths.stage(new servinfoauth); + memset(sa, 0, sizeof(servinfoauth)); + int clientsalt = getint(p), clientclock = getint(p), curpeerport = getint(p); + enet_uint32 curpeerip = getip4(p); + if((sa->wantauth = getint(p))) + { + p.get(sa->clientpubkey, 32); + p.get((uchar*)sa->clientmsg, 64); + } + bool ipfail = servownip && servownip != curpeerip; + if(ipfail) logline(ACLOG_INFO, "[%s] client reported different server IP %s:%d (known own IP is %s)", cl->hostname, iptoa(curpeerip, tmp1), curpeerport, iptoa(servownip, tmp2)); + formatstring(text)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", cl->salt, sender, iptoa(cl->ip_censored, tmp1), cl->country, iptoa(servownip, tmp2), curpeerport); + concatformatstring(text, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, spk, 32), cl->connectclock, clientclock, clientsalt); + sa->clientmsglen = (int)strlen(text); + if(sa->clientmsglen < MAXSTRLEN - 65) memcpy(sa->clientmsg + 64, text, sa->clientmsglen); + else sa->clientmsglen = 0; + formatstring(sa->servermsg)("SERVINFOSIGNED<%s>", bin2hex(tmp1, (uchar*)sa->clientmsg, 64)); + sa->servermsglen = (int)strlen(sa->servermsg); + if((!sa->wantauth && cl->needsauth) || (ipfail && auth_verify_ip)) { delete sa; disconnect_client(sender, DISC_AUTH); } + else + { + sa->type = type; + sa->cn = sender; + sa->clientsequence = cl->clientsequence; + servinfoauths.commit(); + } + } + return; + } + else + { // SV_CONNECT cl->acversion = getint(p); cl->acbuildtype = getint(p); defformatstring(tags)(", AC: %d|%x", cl->acversion, cl->acbuildtype); @@ -3950,6 +4074,7 @@ void localclienttoserver(int chan, ENetPacket *packet) client &addclient() { + static int clientsequencecounter = 0; client *c = NULL; loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; } if(!c) @@ -3959,6 +4084,7 @@ client &addclient() clients.add(c); } c->reset(); + c->clientsequence = ++clientsequencecounter; return *c; } @@ -4114,6 +4240,8 @@ void linequalitystats(int elapsed) } } +SERVPARLIST(mandatory_auth, 0, 1, 0, endis, "sEnforce IDs for all clients even on unlisted server/LAN game"); + void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop { static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0; @@ -4189,6 +4317,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo } resetserverifempty(); + polldeferredprocessing(); if(!isdedicated) return; // below is network only @@ -4239,18 +4368,21 @@ void serverslice(uint timeout) // main server update, called from cube main lo { client &c = addclient(); c.type = ST_TCPIP; + c.needsauth = usemaster || mandatory_auth; c.peer = event.peer; c.peer->data = (void *)(size_t)c.clientnum; c.connectmillis = servmillis; c.state.state = CS_SPECTATE; - c.salt = rnd(0x1000000)*((servmillis%1000)+1); + c.salt = (rnd(0x1000000)*((servmillis%1000)+1)) ^ rnd(0x1000000); char hn[1024]; copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); c.ip = ENET_NET_TO_HOST_32(c.peer->address.host); + c.ip_censored = c.ip & ~((1 << CLIENTIPCENSOR) - 1); const char *gi = geoiplist.check(c.ip); copystring(c.country, gi ? gi : "--", 3); + filtercountrycode(c.country, c.country); entropy_add_byte(c.ip); - logline(ACLOG_INFO,"[%s] client connected", c.hostname); + logline(ACLOG_INFO,"[%s] client connected (%s)", c.hostname, c.country); sendservinfo(c); totalclients++; break; @@ -4437,6 +4569,7 @@ void localconnect() c.type = ST_LOCAL; c.role = CR_ADMIN; c.salt = 0; + c.needsauth = false; copystring(c.hostname, "local"); sendservinfo(c); } @@ -4462,6 +4595,10 @@ void quitproc(int param) void initserver(bool dedicated) { + copystring(servpubkey, ""); + if(scl.ssk && hex2bin(ssk, scl.ssk, 32) != 32) scl.ssk = NULL; + ed25519_pubkey_from_private(spk, ssk); + bin2hex(servpubkey, spk, 32); sg->smapname[0] = '\0'; // start logging @@ -4484,8 +4621,7 @@ void initserver(bool dedicated) if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; - string pubkey; - if(scl.ssk) logline(ACLOG_VERBOSE,"server public key: %s", bin2hex(pubkey, scl.ssk + 32, 32)); + if(scl.ssk) logline(ACLOG_INFO,"server public key: %s", servpubkey); maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); diff --git a/source/src/server.h b/source/src/server.h index 55ded46e..e6093c98 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -226,11 +226,15 @@ struct medals struct client // server side version of "dynent" type { int type; - int clientnum; + int clientnum, clientsequence; ENetPeer *peer; string hostname; - enet_uint32 ip; + enet_uint32 ip, ip_censored; + int connectclock; char country[4]; + uchar pubkey[32]; + char pubkeyhex[68]; + string servinforesponse; string name; int team; char lang[3]; @@ -242,6 +246,7 @@ struct client // server side version of "dynent" type int mute, spam, lastvc; // server side voice comm spam control int acversion, acbuildtype; bool isauthed; // for passworded servers + bool needsauth; bool haswelcome; bool isonrightmap, loggedwrongmap, freshgame; bool timesync; @@ -327,6 +332,8 @@ struct client // server side version of "dynent" type void reset() { name[0] = pwd[0] = demoflags = 0; + ip = ip_censored = 0; + pubkeyhex[0] = '\0'; bottomRTT = ping = 9999; team = TEAM_SPECT; state.state = CS_SPECTATE; @@ -426,7 +433,8 @@ extern bool isdedicated; const char *messagenames[SV_NUM] = { - "SV_SERVINFO", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", + "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", + "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL", "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE", diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 0f07cb46..8e6ca75b 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -293,7 +293,6 @@ serverinfo *findserverinfo(ENetAddress address) serverinfo *getconnectedserverinfo() { - extern ENetPeer *curpeer; if(!curpeer) return NULL; return findserverinfo(curpeer->address); } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 14420188..578681f9 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1704,8 +1704,8 @@ struct serverparameter : serverconfigfile #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "" }; -const char *siddocchaptersorting = "gvmd"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "" }; +const char *siddocchaptersorting = "sgvmd"; void serverparameters_dumpdocu(char *fname) { diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index b52f77eb..2487ea3f 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -169,9 +169,9 @@ static inline void updatemasterserver(int millis, int port, int _interm) { if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { - string servdesc, pubkey; + string servdesc; extern string servpubkey; filtertext(servdesc, scl.servdesc_full[0] ? scl.servdesc_full : "noname", FTXT__GLOBALNAME, 20); - if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? bin2hex(pubkey, scl.ssk + 32, 32) : "nokey", AC_VERSION); + if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? servpubkey : "nokey", AC_VERSION); lastupdatemaster = millis + 1; } } From dd295bb54ad2fec1a17c9730f8364e640cc9ebdd Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 21 Mar 2016 17:53:47 +0100 Subject: [PATCH 22/45] initialise server parameters from commandline overwrite defaults with commandline parameters before first use (log level settings used as an example) --- config/serverparameters.cfg | 15 +++++++ source/src/log.cpp | 2 +- source/src/server.cpp | 34 +++++++++++++--- source/src/serverfiles.h | 81 ++++++++++++++++++++++++++++--------- 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 456fdd4c..402e5f6b 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -54,4 +54,19 @@ dumpparameters:0 dumpsuggestions:1 +// **** Commandline switch overrides **** + +// logthreshold_console Console log level +// integer [0..5], default 2, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_console:2 + +// logthreshold_file Logfile log level +// integer [0..5], default 2, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_file:2 + +// logthreshold_syslog Syslog log level +// integer [0..5], default 5, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_syslog:5 + + diff --git a/source/src/log.cpp b/source/src/log.cpp index 5337466e..9fb5bb73 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -71,7 +71,7 @@ void exitlogging() { if(fp) { fclose(fp); fp = NULL; } #ifdef AC_USE_SYSLOG - if(syslogthreshold < ACLOG_NUM) closelog(); + if(enabled && syslogthreshold < ACLOG_NUM) closelog(); #endif syslogthreshold = ACLOG_NUM; enabled = false; diff --git a/source/src/server.cpp b/source/src/server.cpp index e120c929..6c47d33b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -4593,6 +4593,24 @@ void quitproc(int param) exit(param == 2 ? EXIT_SUCCESS : EXIT_FAILURE); // 3 is the only reply on Win32 apparently, SIGINT == 2 == Ctrl-C } +const char *loglevelnames[] = { "debug", "verbose", "info", "warning", "error", "disabled", "" }; + +SERVPARLISTF(logthreshold_console, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CConsole log level"); +SERVPARLISTF(logthreshold_syslog, 0, ACLOG_NUM, 0, loglevelnames, restartlogging, "CSyslog log level"); +SERVPARLISTF(logthreshold_file, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CLogfile log level"); + +void restartlogging() +{ + if(isdedicated) + { + exitlogging(); + string identity; + if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); + else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport); + initlogging(identity, scl.syslogfacility, logthreshold_console, logthreshold_file, logthreshold_syslog, scl.logtimestamp, scl.logfilepath); + } +} + void initserver(bool dedicated) { copystring(servpubkey, ""); @@ -4602,16 +4620,20 @@ void initserver(bool dedicated) sg->smapname[0] = '\0'; // start logging - string identity; - if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); - else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport); - int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO); - if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp, scl.logfilepath)) printf("WARNING: logging not started!\n"); + initserverparameter("logthreshold_console", scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO)); + initserverparameter("logthreshold_syslog", scl.syslogthres); + initserverparameter("logthreshold_file", scl.filethres); + if((isdedicated = dedicated)) + { + restartlogging(); + if(!logcheck(ACLOG_ERROR)) printf("WARNING: logging not started!\n"); + } logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); + copystring(sg->servdesc_current, scl.servdesc_full); servermsinit(dedicated); - if((isdedicated = dedicated)) + if(isdedicated) { serverparameters.init(scl.parfilepath); if(serverparameters.load()) serverparameters.read(); // do this early, so the parameters are properly set during initialisation diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 578681f9..71b7f4af 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1,16 +1,22 @@ // serverfiles.h // abuse globals to register server parameters +extern int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc); +extern bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc); #ifdef _DEBUG #define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, desc) #define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, desc) -#define SERVPARF(name, min, cur, max, body, desc) extern int last_##name, name; void var_##name() { body; } int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, var_##name, false, true, desc) +#define SERVPARF(name, min, cur, max, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, fun, false, true, desc) +#define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, desc) #define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, desc) +#define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, desc) #else #define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, NULL) #define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, NULL) -#define SERVPARF(name, min, cur, max, body, desc) extern int last_##name, name; void var_##name() { body; } int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, var_##name, false, true, NULL) +#define SERVPARF(name, min, cur, max, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, fun, false, true, NULL) +#define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, NULL) #define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, NULL) +#define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, NULL) #endif #define CONFIG_MAXPAR 11 @@ -1584,40 +1590,40 @@ struct servpar const char *desc; int chapter; #endif - bool log; // log value changes (careful!) + bool logchanges; // log value changes (careful!) bool fromfile; // true: value can be set by config file, false: value is set by server servpar() {} // SID_INT - servpar(int type, const char *name, int minval, int maxval, const char *list[], int *i, int *shadow_i, int defval, void (*fun)(), bool log, bool fromfile, const char *_desc) - : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), log(log), fromfile(fromfile) + servpar(int type, const char *name, int minval, int maxval, const char *list[], int *i, int *shadow_i, int defval, void (*fun)(), bool logchanges, bool fromfile, const char *_desc) + : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), logchanges(logchanges), fromfile(fromfile) { DEBUGCODE(desc = _desc); } // SID_STR - servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool log, int fromfile, const char *_desc) - : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), log(log), fromfile(fromfile) + servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, int fromfile, const char *_desc) + : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile) { DEBUGCODE(desc = _desc;) } }; hashtable *servpars = NULL; -int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool log, bool fromfile, const char *desc) +int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc) { if(!servpars) servpars = new hashtable; if(list && minval == maxval) while(list[maxval - minval + 1][0]) maxval++; // get list length ASSERT(cur >= minval && cur <= maxval); - servpar v(SID_INT, name, minval, maxval, list, storage, shadowstorage, cur, fun, log, fromfile, desc); + servpar v(SID_INT, name, minval, maxval, list, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); servpars->access(name, v); *shadowstorage = cur; return cur; } -bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool log, bool fromfile, const char *desc) +bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc) { if(!servpars) servpars = new hashtable; ASSERT(maxlen > minlen && minlen >= 0 && int(strlen(cur)) >= minlen && int(strlen(cur)) <= maxlen); - servpar v(SID_STR, name, minlen, maxlen, filt, storage, shadowstorage, cur, fun, log, fromfile, desc); + servpar v(SID_STR, name, minlen, maxlen, filt, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); servpars->access(name, v); strncpy(storage, cur, maxlen + 1); strncpy(shadowstorage, cur, maxlen + 1); @@ -1671,8 +1677,11 @@ struct serverparameter : serverconfigfile { if(*id.i != *id.shadow_i) { - if(id.fun) ((void (__cdecl *)())id.fun)(); + int tmp = *id.i; *id.i = *id.shadow_i; + *id.shadow_i = tmp; // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + if(id.fun) ((void (__cdecl *)())id.fun)(); + *id.shadow_i = *id.i; } break; } @@ -1680,8 +1689,16 @@ struct serverparameter : serverconfigfile { if(strcmp(id.s, id.shadow_s)) { - if(id.fun) ((void (__cdecl *)())id.fun)(); - strncpy(id.s, id.shadow_s, id.maxlen + 1); + if(id.fun) + { + char tmp[id.maxlen + 1]; + strncpy(tmp, id.s, id.maxlen + 1); + strncpy(id.s, id.shadow_s, id.maxlen + 1); + strncpy(id.shadow_s, tmp, id.maxlen + 1); // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + ((void (__cdecl *)())id.fun)(); + strncpy(id.shadow_s, id.s, id.maxlen + 1); + } + else strncpy(id.s, id.shadow_s, id.maxlen + 1); } break; } @@ -1701,11 +1718,38 @@ struct serverparameter : serverconfigfile } }; +// optionally overwrite builtin default values with commandline parameters - before updating from file starts + +void initserverparameter(const char *name, int value) +{ + ASSERT(servpars); + servpar *id = servpars->access(name); + ASSERT(id && id->type == SID_INT); + if(value >= id->minval && value <= id->maxval) *id->i = *id->shadow_i = value; +} + +void initserverparameter(const char *name, const char *value) +{ + ASSERT(servpars); + servpar *id = servpars->access(name); + ASSERT(id && id->type == SID_STR); + string tmp; + filtertext(tmp, value, id->filter); + int len = (int)strlen(tmp); + if(len >= id->minlen && len <= id->maxlen) + { + strncpy(id->s, value, id->maxlen + 1); + strncpy(id->shadow_s, value, id->maxlen + 1); + } +} + +// write a nice template for serverparameters.cfg + #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "" }; -const char *siddocchaptersorting = "sgvmd"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "CCommandline switch overrides", "" }; +const char *siddocchaptersorting = "sgvmdC", *siddocchaptercommentedout = "C"; void serverparameters_dumpdocu(char *fname) { @@ -1735,6 +1779,7 @@ void serverparameters_dumpdocu(char *fname) if(ch < 0) f->printf("\n// **** untitled chapter %c ****\n\n", lastchap); else f->printf("\n// **** %s ****\n\n", siddocchapters[ch] + 1); } + const char *commentout = strchr(siddocchaptercommentedout, id.desc[0]) != NULL ? "//" : ""; switch(id.type) { case SID_INT: @@ -1744,12 +1789,12 @@ void serverparameters_dumpdocu(char *fname) { for(int k = 0; id.list[k] && id.list[k][0]; k++) f->printf(", %d:%s", id.minval + k, id.list[k]); } - f->printf("\n%s:%d\n\n", id.name, id.defaultint); + f->printf("\n%s%s:%d\n\n", commentout, id.name, id.defaultint); break; } case SID_STR: { - f->printf("// %s %s\n// string [%d..%d chars], default \"%s\"\n%s:%s\n\n", id.name, id.desc + 1, id.minlen, id.maxlen, id.defaultstr, id.name, id.defaultstr); + f->printf("// %s %s\n// string [%d..%d chars], default \"%s\"\n%s%s:%s\n\n", id.name, id.desc + 1, id.minlen, id.maxlen, id.defaultstr, commentout, id.name, id.defaultstr); break; } } From 035d7caf2f9c74414cd3d6baeb20abd6fb4903aa Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 21 Mar 2016 20:52:13 +0100 Subject: [PATCH 23/45] thread-safe and fully buffered logging Uses own thread to actually write to logs, so the main thread can't be blocked by a slow logfile. Log entries are buffered in two ringbuffers, one for the main thread and one for all other threads. The main thread will copy the second buffer to the main buffer periodically. The main thread buffer is read from the log worker thread. Since two buffers are kept, there are now several functions to add a log entry: mlog() for main thread functions only: will never block tlog() other threads only: may block, until main thread copied some entries xlog() automatically determines, which thread it's been called from and then writes to the proper buffer (will block accordingly) To avoid improper usage, mlog and tlog verify, if they are used correctly. (Only for debug builds. An assert triggers, if necessary.) --- source/src/cube.h | 2 +- source/src/log.cpp | 156 +++++++++++++++++++++------ source/src/master.cpp | 7 +- source/src/protos.h | 5 +- source/src/server.cpp | 198 ++++++++++++++++++----------------- source/src/serveractions.h | 2 +- source/src/serverbrowser.cpp | 2 +- source/src/serverchecks.h | 8 +- source/src/serverfiles.h | 67 ++++++------ source/src/serverms.cpp | 18 ++-- source/src/tools.h | 10 +- 11 files changed, 285 insertions(+), 190 deletions(-) diff --git a/source/src/cube.h b/source/src/cube.h index 14988da6..ebfdf991 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -58,7 +58,7 @@ extern float waterlevel; #define AC_VERSION 1202 #define AC_MASTER_URI "ms.cubers.net" #define AC_MASTER_PORT 28760 -#define MAXCL 16 +#define MAXCLIENTSONMASTER 16 // FIXME #define CONFIGROTATEMAX 5 // keep 5 old versions of saved.cfg and init.cfg around #define DEFAULT_FOG 180 diff --git a/source/src/log.cpp b/source/src/log.cpp index 9fb5bb73..a399d964 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -32,8 +32,15 @@ static int facility = -1, consolethreshold = ACLOG_INFO; static bool timestamp = false, enabled = false; +enum { LOGTARGET_CONSOLE = 1, LOGTARGET_FILE = 2, LOGTARGET_SYSLOG = 4 }; + +static char loglevelenabled[ACLOG_NUM]; + bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath) { + static void *logworkerthread_id = NULL; + extern int logworkerthread(void *nop); + if(!logworkerthread_id) logworkerthread_id = sl_createthread(logworkerthread, NULL); // always start, if dedicated server facility = facility_; timestamp = logtimestamp; if(consolethres >= 0) consolethreshold = min(consolethres, (int)ACLOG_NUM); @@ -63,12 +70,14 @@ bool initlogging(const char *identity, int facility_, int consolethres, int file if(syslogthreshold < ACLOG_NUM) concatformatstring(msg, ", \"%s\", local%d", ident, facility); concatformatstring(msg, "), timestamp(%s)", timestamp ? "ENABLED" : "DISABLED"); enabled = consolethreshold < ACLOG_NUM || fp || syslogthreshold < ACLOG_NUM; + loopi(ACLOG_NUM) loglevelenabled[i] = (consolethreshold <= i ? LOGTARGET_CONSOLE : 0) | (fp && filethreshold <= i ? LOGTARGET_FILE : 0) | (syslogthreshold <= i ? LOGTARGET_SYSLOG : 0); if(enabled) printf("%s\n", msg); return enabled; } void exitlogging() { + loopi(ACLOG_NUM) loglevelenabled[i] = 0; if(fp) { fclose(fp); fp = NULL; } #ifdef AC_USE_SYSLOG if(enabled && syslogthreshold < ACLOG_NUM) closelog(); @@ -79,42 +88,129 @@ void exitlogging() bool logcheck(int level) { - return enabled && level >= 0 && level < ACLOG_NUM && (consolethreshold <= level || (fp && filethreshold <= level) || syslogthreshold <= level); + ASSERT(level >= 0 && level < ACLOG_NUM); + return loglevelenabled[level] != 0; } -bool logline(int level, const char *msg, ...) +struct logline_s { int level; time_t t; string msg; }; + +static ringbuf mainlog; // increase this number, if log overflows occur +static ringbuf threadlog; +static int mainlogoverflow = 0; +static sl_semaphore threadlogsem(1, NULL), threadlogfullsem(0, NULL), mainlogsem(0, NULL); + +int logworkerthread(void *nop) { - if(!enabled) return false; - if(level < 0 || level >= ACLOG_NUM) return false; - bool logtocon = consolethreshold <= level, logtofile = fp && filethreshold <= level, logtosyslog = syslogthreshold <= level; - if(!(logtocon || logtofile || logtosyslog)) return false; - defvformatstring(sf, msg, msg); - filtertext(sf, sf, FTXT__LOG); - const char *ts = timestamp ? timestring(true, "%b %d %H:%M:%S ") : "", *ld = levelprefix[level]; - char *p, *l = sf; - do - { // break into single lines first - if((p = strchr(l, '\n'))) *p = '\0'; - if(logtocon) printf("%s%s%s\n", ts, ld, l); - if(logtofile) fprintf(fp, "%s%s%s\n", ts, ld, l); - if(logtosyslog) + string tmp; + for(;;) + { + while(mainlog.empty()) mainlogsem.wait(); + logline_s *ll = mainlog.remove(); + int targets = loglevelenabled[ll->level]; + bool logtocon = (targets & LOGTARGET_CONSOLE) != 0, logtofile = fp && (targets & LOGTARGET_FILE) != 0, logtosyslog = (targets & LOGTARGET_SYSLOG) != 0; + const char *ts = timestamp ? timestring(ll->t, true, "%b %d %H:%M:%S ", tmp) : "", *ld = levelprefix[ll->level]; + char *p, *l = ll->msg; + do + { // break into single lines first + if((p = strchr(l, '\n'))) *p = '\0'; + if(logtocon) printf("%s%s%s\n", ts, ld, l); + if(logtofile) fprintf(fp, "%s%s%s\n", ts, ld, l); + if(logtosyslog) #ifdef AC_USE_SYSLOG - syslog(levels[level], "%s", l); + syslog(levels[ll->level], "%s", l); #else - { - defformatstring(text)("<%d>%s: %s", (16 + facility) * 8 + levels[level], ident, l); // no TIMESTAMP, no hostname: syslog will add this - ENetBuffer buf; - buf.data = text; - buf.dataLength = strlen(text); - enet_socket_send(logsock, &logdest, &buf, 1); + { + defformatstring(text)("<%d>%s: %s", (16 + facility) * 8 + levels[ll->level], ident, l); // no TIMESTAMP, no hostname: syslog will add this + ENetBuffer buf; + buf.data = text; + buf.dataLength = strlen(text); + enet_socket_send(logsock, &logdest, &buf, 1); + } +#endif + l = p + 1; } -#endif - l = p + 1; - } - while(p); + while(p); #ifdef _DEBUG - if(logtocon) fflush(stdout); - if(logtofile) fflush(fp); + if(logtocon) fflush(stdout); + if(logtofile) fflush(fp); #endif - return logtocon; + delete ll; + } + return 0; +} + +void mlog(int level, const char *msg, ...) // log line from main thread (never blocks) +{ + ASSERT(level >= 0 && level < ACLOG_NUM && ismainthread()); + if(!loglevelenabled[level]) return; + if(mainlog.full()) { mainlogoverflow++; return; } // do. not. block. + logline_s *ll = mainlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + mainlog.commit(); + if(mainlogsem.getvalue() < 1) mainlogsem.post(); +} + +void tlog(int level, const char *msg, ...) // log line from other-than-main thread (may block) +{ + ASSERT(level >= 0 && level < ACLOG_NUM && !ismainthread()); + if(!loglevelenabled[level]) return; + threadlogsem.wait(); // properly lock, so different threads can use this + while(threadlog.full()) threadlogfullsem.wait(); // wait for buffer to clear + logline_s *ll = threadlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + threadlog.commit(); + threadlogsem.post(); +} + +void xlog(int level, const char *msg, ...) // log line from any thread (more expensive: only use for functions, that are called from main and other threads) +{ + ASSERT(level >= 0 && level < ACLOG_NUM); + if(!loglevelenabled[level]) return; + bool ismain = ismainthread(); + if(ismain) + { + if(mainlog.full()) { mainlogoverflow++; return; } // do. not. block. + } + else + { + threadlogsem.wait(); // properly lock, so different threads can use this + while(threadlog.full()) threadlogfullsem.wait(); // wait for buffer to clear + } + logline_s *ll = ismain ? mainlog.stage(new logline_s) : threadlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + if(ismain) + { + mainlog.commit(); + if(mainlogsem.getvalue() < 1) mainlogsem.post(); + } + else + { + threadlog.commit(); + threadlogsem.post(); + } +} + +void poll_logbuffers() // just copy threadlog to mainlog and restart blocked threads, if necessary +{ + while(!mainlog.full() && !threadlog.empty()) + { + mainlog.stage(threadlog.remove()); + mainlog.commit(); + } + if(mainlogoverflow && !mainlog.full()) + { + mlog(ACLOG_ERROR, "main log ringbuffer overflow, %d entries lost", mainlogoverflow); + mainlogoverflow = 0; + } + if(!threadlog.full() && threadlogfullsem.getvalue() < 1) threadlogfullsem.post(); + if(!mainlog.empty() && mainlogsem.getvalue() < 1) mainlogsem.post(); } diff --git a/source/src/master.cpp b/source/src/master.cpp index cc35ceb6..5ed629c5 100644 --- a/source/src/master.cpp +++ b/source/src/master.cpp @@ -214,12 +214,7 @@ void output(client &c, const char *msg, int len = 0) void outputf(client &c, const char *fmt, ...) { - string msg; - va_list args; - va_start(args, fmt); - vformatstring(msg, fmt, args); - va_end(args); - + defvformatstring(msg, fmt, fmt); output(c, msg); } diff --git a/source/src/protos.h b/source/src/protos.h index 3dfd3706..0a20c48a 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1124,7 +1124,10 @@ enum { ACLOG_DEBUG = 0, ACLOG_VERBOSE, ACLOG_INFO, ACLOG_WARNING, ACLOG_ERROR, A extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath); extern void exitlogging(); extern bool logcheck(int level); -extern bool logline(int level, const char *msg, ...) PRINTFARGS(2, 3); +extern void mlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from main thread (never blocks) +extern void tlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from other-than-main thread (may block) +extern void xlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from any thread (more expensive: only use for functions, that are called from main and other threads) +extern void poll_logbuffers(); // server config diff --git a/source/src/server.cpp b/source/src/server.cpp index 6c47d33b..45734ea8 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -126,7 +126,7 @@ void poll_serverthreads() // called once per mainloop-timeslice if(startnewservermapsepoch || readmapsthread_sem->getvalue()) fatal("thread management mishap"); // zero tolerance... // wake readmapsthread - logline(ACLOG_INFO,"waking readmapsthread"); + mlog(ACLOG_INFO,"waking readmapsthread"); startnewservermapsepoch = true; servclock = (int) (time(NULL) / (time_t) 60); readmapsthread_sem->post(); @@ -146,14 +146,14 @@ void poll_serverthreads() // called once per mainloop-timeslice { if(!strcmp(servermaps[i]->fname, fresh->fname)) // we don't check paths here - map filenames have to be unique { // found map of same name - logline(ACLOG_INFO,"marked servermap %s%s for deletion", servermaps[i]->fpath, servermaps[i]->fname); + mlog(ACLOG_INFO,"marked servermap %s%s for deletion", servermaps[i]->fpath, servermaps[i]->fname); servermapstodelete.add(servermaps.remove(i)); // mark old version for deletion } } if(fresh->isok) { servermaps.add(fresh); - logline(ACLOG_INFO,"added servermap %s%s", fresh->fpath, fresh->fname); + mlog(ACLOG_INFO,"added servermap %s%s", fresh->fpath, fresh->fname); maprot.initmap(fresh, NULL); servermaps.sort(servermapsortname); // keep list sorted at all times } @@ -231,6 +231,11 @@ void poll_serverthreads() // called once per mainloop-timeslice break; } } + + // update thread-safe data structures (vectors and hashtables can be read concurrently - but to be updated, they need to be locked) + poll_logbuffers(); + + } SERVPAR(gamepenalty_cutoff, 30, 60, 120, "gNumber of minutes to remember that a map+mode combination has been played"); @@ -808,13 +813,13 @@ const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *s strftime(cfspp, sizeof(string) - 1, DEMOTSFORMAT, timeinfo); break; } - default: logline(ACLOG_INFO, "bad formatstring: demonameformat @ %d", cc); cc-=1; break; // don't drop the bad char + default: mlog(ACLOG_INFO, "bad formatstring: demonameformat @ %d", cc); cc-=1; break; // don't drop the bad char } concatstring(dmofn, cfspp); } else { - logline(ACLOG_INFO, "trailing %%-sign in demonameformat"); + mlog(ACLOG_INFO, "trailing %%-sign in demonameformat"); } cc+=1; break; @@ -847,7 +852,7 @@ void enddemorecord() { delete demotmp; demotmp = NULL; - logline(ACLOG_INFO, "Demo discarded."); + mlog(ACLOG_INFO, "Demo discarded."); return; } @@ -865,7 +870,7 @@ void enddemorecord() if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); } defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); sendservmsg(msg); - logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); + mlog(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); int mPLAY = sg->gamemillis >= sg->gamelimit ? sg->gamelimit/1000 : sg->gamemillis/1000; int mDROP = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis)/1000; @@ -890,11 +895,11 @@ void enddemorecord() { int wlen = (int) demo->write(d.data, d.len); delete demo; - logline(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen); + mlog(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen); } else { - logline(ACLOG_INFO, "failed to write demo to file \"%s\"", msg); + mlog(ACLOG_INFO, "failed to write demo to file \"%s\"", msg); } } } @@ -915,8 +920,8 @@ void setupdemorecord() return; } - sendservmsg("recording demo"); - logline(ACLOG_INFO, "Demo recording started."); + sendservmsg("recording demo"); // FIXME + mlog(ACLOG_INFO, "Demo recording started."); demorecord = f; recordpackets = false; @@ -1153,7 +1158,7 @@ bool flagdistance(sflaginfo &f, int cn) if(pdist) { c.farpickups++; - logline(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)", + mlog(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)", c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sg->sflaginfos + 1), dist, c.farpickups); if (pdist==2) return false; } @@ -1276,7 +1281,7 @@ void flagaction(int flag, int action, int actor) } if(abort) { - logline(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort); // FIXME: remove this logline after some time - it will only show a few bad ping effects + mlog(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort); sendflaginfo(); return; } @@ -1293,29 +1298,29 @@ void flagaction(int flag, int action, int actor) switch(message) { case FM_PICKUP: - logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up"); + mlog(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up"); break; case FM_DROP: case FM_LOST: - logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped"); + mlog(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped"); break; case FM_RETURN: - logline(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name); + mlog(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name); break; case FM_SCORE: if(m_htf) - logline(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); else - logline(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); break; case FM_KTFSCORE: - logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (sg->gamemillis - f.stolentime) / 1000, c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (sg->gamemillis - f.stolentime) / 1000, c.state.flagscore); break; case FM_SCOREFAIL: - logline(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name); + mlog(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name); break; default: - logline(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); + mlog(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); break; } flagpoints (&c, message); @@ -1325,10 +1330,10 @@ void flagaction(int flag, int action, int actor) switch(message) { case FM_RESET: - logline(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag)); + mlog(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag)); break; default: - logline(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message); + mlog(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message); break; } } @@ -1402,7 +1407,7 @@ void htf_forceflag(int flag) f.actor_cn = cl->clientnum; sendflaginfo(flag); flagmessage(flag, FM_PICKUP, cl->clientnum); - logline(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name); + mlog(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name); } f.lastupdate = sg->gamemillis; } @@ -1665,13 +1670,13 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl const char *hn = sender >= 0 && clients[sender]->type == ST_TCPIP ? clients[sender]->hostname : NULL; if(!sg->sents.inrange(i)) { - if(hn && !m_coop) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i); + if(hn && !m_coop) mlog(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i); return false; } server_entity &e = sg->sents[i]; if(!e.spawned) { - if(!e.legalpickup && hn && !m_demo) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]); + if(!e.legalpickup && hn && !m_demo) mlog(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]); return false; } if(sender>=0) @@ -1686,7 +1691,7 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl if (pdist) { cl->farpickups++; - if (!m_demo) logline(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)", + if (!m_demo) mlog(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)", cl->hostname, cl->name, (pdist==2?"tried to pick":"picked"), i, entnames[e.type], dist, cl->farpickups); if (pdist==2) return false; } @@ -1757,7 +1762,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, { // suicide actor->state.frags--; suic = true; - logline(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name); + mlog(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name); } sendf(-1, 1, "ri5", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags, gun); if((suic || tk) && (m_htf || m_ktf) && targethasflag >= 0) @@ -1768,7 +1773,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, target->position.setsize(0); ts.state = CS_DEAD; ts.lastdeath = sg->gamemillis; - if(!suic) logline(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, valid_weapon(gun) ? killmessages[gib ? 1 : 0][gun] : "smurfed", tk ? " their teammate" : "", target->name); + if(!suic) mlog(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, valid_weapon(gun) ? killmessages[gib ? 1 : 0][gun] : "smurfed", tk ? " their teammate" : "", target->name); if(m_flags && targethasflag >= 0) { if(m_ctf) @@ -2214,7 +2219,7 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) if(notify) { sendservmsg("server description reset to default"); - logline(ACLOG_INFO, "server description reset to '%s'", sg->servdesc_current); + mlog(ACLOG_INFO, "server description reset to '%s'", sg->servdesc_current); } } @@ -2282,7 +2287,7 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) if(sg->mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", sg->matchteamsize); if(sm) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", sm->maprevision, sm->cgzlen, sm->getpathdesc(), sm->isdistributable() ? "" : "not "); else concatformatstring(gsmsg, "error: failed to preload map"); - logline(ACLOG_INFO, "\n%s", gsmsg); + mlog(ACLOG_INFO, "\n%s", gsmsg); if(m_arena) distributespawns(); if(notify) { @@ -2462,7 +2467,7 @@ bool svote(int sender, int vote, ENetPacket *msg) // true if the vote was placed sendpacket(-1, 1, msg, sender); clients[sender]->vote = vote; - logline(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes"); + mlog(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes"); curvote->evaluate(); return true; } @@ -2476,14 +2481,14 @@ void scallvotesuc(voteinfo *v) clients[v->owner]->lastvotecall = servmillis; clients[v->owner]->nvotes--; // successful votes do not count as abuse sendf(v->owner, 1, "ri", SV_CALLVOTESUC); - logline(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]"); + mlog(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]"); } void scallvoteerr(voteinfo *v, int error) { if(!valid_client(v->owner)) return; sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, error); - logline(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error)); + mlog(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error)); } bool map_queued = false; @@ -2597,8 +2602,8 @@ void changeclientrole(int client, int role, char *pwd, bool force) clients[client]->role = role; sendserveropinfo(-1); if(pd.line > -1) - logline(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line); - logline(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin) + mlog(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line); + mlog(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin) if(role > CR_DEFAULT) sendiplist(client); } else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force @@ -2631,6 +2636,7 @@ void senddisconnectedscores(int cn) void disconnect_client(int n, int reason) { + ASSERT(ismainthread()); if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; sdropflag(n); client &c = *clients[n]; @@ -2646,8 +2652,8 @@ void disconnect_client(int n, int reason) } } int sp = (servmillis - c.connectmillis) / 1000; - if(reason>=0) logline(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved); - else logline(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved); + if(reason>=0) mlog(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved); + else mlog(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved); totalclients--; c.peer->data = (void *)-1; if(reason>=0) enet_peer_disconnect(c.peer, reason); @@ -2677,7 +2683,7 @@ void authsucceeded(uint id) client *cl = findauth(id); if(!cl) return; cl->authreq = 0; - logline(ACLOG_INFO, "player authenticated: %s", cl->name); + mlog(ACLOG_INFO, "player authenticated: %s", cl->name); defformatstring(auth4u)("player authenticated: %s", cl->name); sendf(-1, 1, "ris", SV_SERVMSG, auth4u); //setmaster(cl, true, "", ci->authname);//TODO? compare to sauerbraten @@ -3040,7 +3046,7 @@ void process(ENetPacket *packet, int sender, int chan) p.get((uchar*)sa->clientmsg, 64); } bool ipfail = servownip && servownip != curpeerip; - if(ipfail) logline(ACLOG_INFO, "[%s] client reported different server IP %s:%d (known own IP is %s)", cl->hostname, iptoa(curpeerip, tmp1), curpeerport, iptoa(servownip, tmp2)); + if(ipfail) mlog(ACLOG_INFO, "[%s] client reported different server IP %s:%d (known own IP is %s)", cl->hostname, iptoa(curpeerip, tmp1), curpeerport, iptoa(servownip, tmp2)); formatstring(text)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", cl->salt, sender, iptoa(cl->ip_censored, tmp1), cl->country, iptoa(servownip, tmp2), curpeerport); concatformatstring(text, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, spk, 32), cl->connectclock, clientclock, clientsalt); sa->clientmsglen = (int)strlen(text); @@ -3086,16 +3092,16 @@ void process(ENetPacket *packet, int sender, int chan) if(matchreconnect && !banned) { // former player reconnecting to a server in match mode cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags); } else if(wl == NWL_IPFAIL || wl == NWL_PWDFAIL) { // nickname matches whitelist, but IP is not in the required range or PWD doesn't match - logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags); disconnect_client(sender, DISC_BADNICK); } else if(bl > 0) { // nickname matches blacklist - logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags); disconnect_client(sender, DISC_BADNICK); } else if(passwords.check(cl->name, cl->pwd, cl->salt, &pd, (cl->type==ST_TCPIP ? cl->peer->address.host : 0)) && (!pd.denyadmin || (banned && !srvfull && !srvprivate)) && bantype != BAN_MASTER) // pass admins always through @@ -3114,14 +3120,14 @@ void process(ENetPacket *packet, int sender, int chan) break; } } - logline(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags); + mlog(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags); } else if(scl.serverpassword[0] && !(srvprivate || srvfull || banned)) { // server password required if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), cl->pwd)) { cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags); } else disconnect_client(sender, DISC_WRONGPW); } @@ -3131,7 +3137,7 @@ void process(ENetPacket *packet, int sender, int chan) else { cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags); } } if(!cl->isauthed) return; @@ -3161,7 +3167,7 @@ void process(ENetPacket *packet, int sender, int chan) } } - if(!cl) { logline(ACLOG_ERROR, " client in process()"); return; } // should never happen anyway + if(!cl) { mlog(ACLOG_ERROR, " client in process()"); return; } // should never happen anyway if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; @@ -3213,12 +3219,12 @@ void process(ENetPacket *packet, int sender, int chan) bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) // team chat { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); sendteamtext(text, sender, type); } else { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { @@ -3248,19 +3254,19 @@ void process(ENetPacket *packet, int sender, int chan) { if(sg->mastermode != MM_MATCH || !sg->matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat { - logline(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); + mlog(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); QUEUE_STR(text); } else // spect chat { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); sendteamtext(text, sender, type == SV_TEXTME ? SV_TEAMTEXTME : SV_TEAMTEXT); } } else { - logline(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "", + mlog(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { @@ -3293,12 +3299,12 @@ void process(ENetPacket *packet, int sender, int chan) if(!spamdetect(cl, text) && canspeech) { bool allowed = !(sg->mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); - logline(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); + mlog(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text); } else { - logline(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); + mlog(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); @@ -3344,7 +3350,7 @@ void process(ENetPacket *packet, int sender, int chan) int sp = canspawn(cl); sendf(sender, 1, "rii", SV_SPAWNDENY, sp); cl->spawnperm = sp; - if(cl->loggedwrongmap) logline(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + if(cl->loggedwrongmap) mlog(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs); bool spawn = false; if(team_isspect(cl->team)) { @@ -3364,7 +3370,7 @@ void process(ENetPacket *packet, int sender, int chan) else { forcedeath(cl); - logline(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + mlog(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs); cl->loggedwrongmap = true; sendf(sender, 1, "rii", SV_SPAWNDENY, SP_WRONGMAP); } @@ -3410,7 +3416,7 @@ void process(ENetPacket *packet, int sender, int chan) if(!text[0]) copystring(text, "unarmed"); QUEUE_STR(text); bool namechanged = strcmp(cl->name, text) != 0; - if(namechanged) logline(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text); + if(namechanged) mlog(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text); copystring(cl->name, text, MAXNAMELEN+1); if(namechanged) { @@ -3428,7 +3434,7 @@ void process(ENetPacket *packet, int sender, int chan) { case NWL_PWDFAIL: case NWL_IPFAIL: - logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name); disconnect_client(sender, DISC_BADNICK); break; @@ -3437,7 +3443,7 @@ void process(ENetPacket *packet, int sender, int chan) int l = nickblacklist.checkblacklist(cl->name); if(l >= 0) { - logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l); disconnect_client(sender, DISC_BADNICK); } break; @@ -3761,7 +3767,7 @@ void process(ENetPacket *packet, int sender, int chan) memcpy(sg->coop_mapdata, &p.buf[p.len], mapsize + cfgsizegz); } incoming_size += mapsize + cfgsize; - logline(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written", + mlog(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written", clients[sender]->hostname, clients[sender]->name, sentmap, revision, mapsize, cfgsize, cfgsizegz); defformatstring(msg)("%s (%d) up%sed map %s, rev %d", clients[sender]->name, sender, !sm ? "load": "dat", sentmap, revision); sendservmsg(msg); @@ -3774,7 +3780,7 @@ void process(ENetPacket *packet, int sender, int chan) } if (reject) { - logline(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s", + mlog(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s", clients[sender]->hostname, clients[sender]->name, sentmap, revision, reject); } p.len += mapsize + cfgsizegz; @@ -3821,11 +3827,11 @@ void process(ENetPacket *packet, int sender, int chan) remove(filename); defformatstring(msg)("map '%s' deleted", rmmap); sendservmsg(msg, sender); - logline(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap); + mlog(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap); } if (reject) { - logline(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject); + mlog(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject); defformatstring(msg)("\f3can't delete map '%s', %s", rmmap, reject); sendservmsg(msg, sender); } @@ -4008,7 +4014,7 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p, n); if(valid_client(sender) && clients[sender]->role==CR_ADMIN) { - logline(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text); + mlog(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text); sendservmsg("your message has been logged", sender); } } @@ -4126,11 +4132,11 @@ void loggamestatus(const char *reason) int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}; string text; formatstring(text)("%d minutes remaining", sg->minremain); - logline(ACLOG_INFO, ""); - logline(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s", + mlog(ACLOG_INFO, ""); + mlog(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s", modestr(gamemode), sg->smapname, reason ? reason : text, mmfullname(sg->mastermode), totalclients, sg->custom_servdesc ? ',' : '\0', sg->servdesc_current); if(!scl.loggamestatus) return; - logline(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : ""); + mlog(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : ""); loopv(clients) { client &c = *clients[i]; @@ -4141,7 +4147,7 @@ void loggamestatus(const char *reason) concatformatstring(text, "%6d ", c.state.points); // score concatformatstring(text, "%4d %5d", c.state.frags, c.state.deaths); // frag death if(m_teammode) concatformatstring(text, " %2d", c.state.teamkills); // tk - logline(ACLOG_INFO, "%s%5d %s %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname); + mlog(ACLOG_INFO, "%s%5d %s %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname); if(c.team != TEAM_SPECT) { int t = team_base(c.team); @@ -4160,7 +4166,7 @@ void loggamestatus(const char *reason) if(m_teammode) formatstring(text)("%-4s ", team_string(sc.team, true)); else text[0] = '\0'; if(m_flags) concatformatstring(text, "%4d ", sc.flagscore); - logline(ACLOG_INFO, " %-16s %s%4d %5d%s - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? " -" : ""); + mlog(ACLOG_INFO, " %-16s %s%4d %5d%s - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? " -" : ""); if(sc.team != TEAM_SPECT) { int t = team_base(sc.team); @@ -4173,9 +4179,9 @@ void loggamestatus(const char *reason) } if(m_teammode) { - loopi(2) logline(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]); + loopi(2) mlog(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]); } - logline(ACLOG_INFO, ""); + mlog(ACLOG_INFO, ""); } static unsigned char chokelog[MAXCLIENTS + 1] = { 0 }; @@ -4236,7 +4242,7 @@ void linequalitystats(int elapsed) chokelog[i] = (s << 4) + c; concatformatstring(msg, "%02X ", chokelog[i]); } - logline(ACLOG_DEBUG, "%s] +%d", msg, ncs); + mlog(ACLOG_DEBUG, "%s] +%d", msg, ncs); } } @@ -4342,7 +4348,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); - logline(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);" + mlog(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);" " Ping: #%d|%d|%d; CSL: #%d|%d|%d (bytes)", timestring(true, "%d-%m-%Y %H:%M:%S"), nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024, mnum, msend, mrec, cnum, csend, crec); @@ -4382,7 +4388,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo copystring(c.country, gi ? gi : "--", 3); filtercountrycode(c.country, c.country); entropy_add_byte(c.ip); - logline(ACLOG_INFO,"[%s] client connected (%s)", c.hostname, c.country); + mlog(ACLOG_INFO,"[%s] client connected (%s)", c.hostname, c.country); sendservinfo(c); totalclients++; break; @@ -4628,7 +4634,7 @@ void initserver(bool dedicated) restartlogging(); if(!logcheck(ACLOG_ERROR)) printf("WARNING: logging not started!\n"); } - logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); + mlog(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); copystring(sg->servdesc_current, scl.servdesc_full); servermsinit(dedicated); @@ -4638,12 +4644,12 @@ void initserver(bool dedicated) serverparameters.init(scl.parfilepath); if(serverparameters.load()) serverparameters.read(); // do this early, so the parameters are properly set during initialisation ENetAddress address = { ENET_HOST_ANY, (enet_uint16)scl.serverport }; - if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logline(ACLOG_WARNING, "server ip not resolved!"); + if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) mlog(ACLOG_WARNING, "server ip not resolved!"); serverhost = enet_host_create(&address, scl.maxclients+1, 3, 0, scl.uprate); if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; - if(scl.ssk) logline(ACLOG_INFO,"server public key: %s", servpubkey); + if(scl.ssk) mlog(ACLOG_INFO,"server public key: %s", servpubkey); maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); @@ -4652,19 +4658,19 @@ void initserver(bool dedicated) forbiddenlist.init(scl.forbidden); serverinfoinfo.init(scl.infopath, MAXTRANS / 2); serverinfomotd.init(scl.motdpath, MAXSTRLEN - 5); - logline(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); - if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); - if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); - if(scl.mapperm[0]) logline(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); - logline(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); - if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logline(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); - logline(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold); - if(scl.master) logline(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master); - if(scl.serverpassword[0]) logline(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword)); + mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); + if(scl.demopath[0]) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); + if(scl.voteperm[0]) mlog(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); + if(scl.mapperm[0]) mlog(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); + mlog(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); + if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) mlog(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); + mlog(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold); + if(scl.master) mlog(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master); + if(scl.serverpassword[0]) mlog(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword)); #ifdef ACAC - logline(ACLOG_INFO, "anticheat: enabled"); + mlog(ACLOG_INFO, "anticheat: enabled"); #else - logline(ACLOG_INFO, "anticheat: disabled"); + mlog(ACLOG_INFO, "anticheat: disabled"); #endif } @@ -4676,17 +4682,15 @@ void initserver(bool dedicated) SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); #endif // kill -2 / Ctrl-C - see http://msdn.microsoft.com/en-us/library/xdkz3x12%28v=VS.100%29.aspx (or VS-2008?) for caveat (seems not to pertain to AC - 2011feb05:ft) - if (signal(SIGINT, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGINT!"); + if (signal(SIGINT, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGINT!"); // kill -15 / probably process-manager on Win32 *shrug* - if (signal(SIGTERM, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGTERM!"); + if (signal(SIGTERM, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGTERM!"); #ifndef WIN32 // kill -1 - if (signal(SIGHUP, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGHUP!"); - // kill -9 is uncatchable - http://en.wikipedia.org/wiki/SIGKILL - //if (signal(SIGKILL, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGKILL!"); + if (signal(SIGHUP, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGHUP!"); #endif - logline(ACLOG_INFO, "dedicated server started, waiting for clients..."); - logline(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft: + mlog(ACLOG_INFO, "dedicated server started, waiting for clients..."); + mlog(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft: atexit(enet_deinitialize); atexit(cleanupserver); enet_time_set(0); @@ -4710,8 +4714,8 @@ void fatal(const char *s, ...) { defvformatstring(msg,s,s); defformatstring(out)("AssaultCube fatal error: %s", msg); - if (logline(ACLOG_ERROR, "%s", out)); - else puts(out); + xlog(ACLOG_ERROR, "%s", out); + puts(out); cleanupserver(); exit(EXIT_FAILURE); } diff --git a/source/src/serveractions.h b/source/src/serveractions.h index dd5637d2..c8367b3b 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -95,7 +95,7 @@ struct mapaction : serveraction concatstring(msg, " missing"); } if(notify) sendservmsg(msg, caller); - logline(ACLOG_INFO, "%s", msg); + xlog(ACLOG_INFO, "%s", msg); } } loopv(scl.adminonlymaps) diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 8e6ca75b..0562bee8 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1042,7 +1042,7 @@ void refreshservers(void *menu, bool init) { serverinfo &si = *servers[i]; si.menuline_to = si.menuline_from = ((gmenu *)menu)->items.length(); - if( (!showallservers && si.lastpingmillis <= servermenumillis) || (si.maxclients>MAXCL && searchlan<2) ) continue; // no pong yet or forbidden + if((!showallservers && si.lastpingmillis <= servermenumillis) || (si.maxclients > MAXCLIENTSONMASTER && searchlan<2) ) continue; // no pong yet or forbidden int banned = ((si.pongflags >> PONGFLAG_BANNED) & 1) | ((si.pongflags >> (PONGFLAG_BLACKLIST - 1)) & 2); bool showthisone = !(banned && showonlygoodservers) && !(showonlyfavourites > 0 && si.favcat != showonlyfavourites - 1); bool serverfull = si.numplayers >= si.maxclients; diff --git a/source/src/serverchecks.h b/source/src/serverchecks.h index 15d3c9e8..2fcd2414 100644 --- a/source/src/serverchecks.h +++ b/source/src/serverchecks.h @@ -16,7 +16,7 @@ inline void checkclientpos(client *cl) if(sg->gamemillis > 10000 && (servmillis - cl->connectmillis) > 10000) cl->mapcollisions++; if(cl->mapcollisions && !(cl->mapcollisions % 25)) { - logline(ACLOG_INFO, "[%s] %s is colliding with the map", cl->hostname, cl->name); + xlog(ACLOG_INFO, "[%s] %s is colliding with the map", cl->hostname, cl->name); } } } @@ -40,7 +40,7 @@ enum { CTFLDEF, CTFLCOV, HTFLDEF, HTFLCOV, COVER, MEDALMESSAGENUM }; inline void print_medal_messages(client *c, int n) { if (n<0 || n>=MEDALMESSAGENUM) return; - logline(ACLOG_VERBOSE, "[%s] %s %s", c->hostname, c->name, medal_messages[n]); + xlog(ACLOG_VERBOSE, "[%s] %s %s", c->hostname, c->name, medal_messages[n]); } inline void addpt(client *c, int points, int n = -1) { @@ -448,7 +448,7 @@ void check_afk() */ ) { - logline(ACLOG_INFO, "[%s] %s %s", c.hostname, c.name, "is afk"); + xlog(ACLOG_INFO, "[%s] %s %s", c.hostname, c.name, "is afk"); defformatstring(msg)("%s is afk", c.name); sendservmsg(msg); disconnect_client(c.clientnum, DISC_AFK); @@ -464,7 +464,7 @@ void check_ffire(client *target, client *actor, int damage) if ( sg->mastermode != MM_OPEN ) return; actor->ffire += damage; if ( actor->ffire > 300 && actor->ffire * 600 > sg->gamemillis) { - logline(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); + xlog(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); defformatstring(msg)("%s %s", actor->name, "kicked for excessive friendly fire"); sendservmsg(msg); disconnect_client(actor->clientnum, DISC_FFKICK); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 71b7f4af..e77a64d6 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -653,7 +653,7 @@ bool serverconfigfile::load() buf = loadfile(filename, &filelen); if((readfailed = !buf)) { - logline(ACLOG_INFO,"could not read config file '%s'", filename); + xlog(ACLOG_INFO,"could not read config file '%s'", filename); return false; } entropy_add_block((const uchar *)buf, filelen); @@ -823,7 +823,7 @@ struct servermaprot : serverconfigfile { loopi(GMODE_NUM) base[i] = prebase[i]; loopv(preloaded) configsets.add(preloaded[i]); - logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); + mlog(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); } void initmap(servermap *m, stream *f) // set maprot parameters of a servermap @@ -974,7 +974,7 @@ struct serveripcclist : serverconfigfile iprangecc ir; int line = 0, errors = 0, concatd = 0; char *l, *r, *p = buf; - logline(ACLOG_VERBOSE,"reading ip list '%s'", filename); + mlog(ACLOG_VERBOSE,"reading ip list '%s'", filename); while(p < buf + filelen) { l = p + strspn(p, " "); p += strlen(p) + 1; line++; @@ -989,8 +989,7 @@ struct serveripcclist : serverconfigfile else ipranges.add(ir); } - else - logline(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; + else mlog(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; } ipranges.sort(cmpiprange); int orglength = ipranges.length(); @@ -1001,13 +1000,13 @@ struct serveripcclist : serverconfigfile if(ipranges[i].ur <= ipranges[i - 1].ur) { if(cmpcomment(ipranges[i], ipranges[i - 1])) - logline(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i], b1), printcomment(ipranges[i]), iprtoa(ipranges[i - 1], b2), printcomment(ipranges[i - 1])); + mlog(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i], b1), printcomment(ipranges[i]), iprtoa(ipranges[i - 1], b2), printcomment(ipranges[i - 1])); else { if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); + mlog(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); else - logline(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); + mlog(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); } ipranges.remove(i--); continue; } @@ -1015,13 +1014,13 @@ struct serveripcclist : serverconfigfile { if(!cmpcomment(ipranges[i], ipranges[i - 1])) // same comment { - logline(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); + mlog(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } else { - logline(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1], b1), printcomment(ipranges[i - 1]), + mlog(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1], b1), printcomment(ipranges[i - 1]), iprtoa(ipranges[i], b2), printcomment(ipranges[i]), b2, printcomment(ipranges[i])); errors++; ipranges.remove(i--); continue; @@ -1029,13 +1028,13 @@ struct serveripcclist : serverconfigfile } if(ipranges[i].lr - 1 == ipranges[i - 1].ur && !cmpcomment(ipranges[i], ipranges[i - 1])) { - logline(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); + mlog(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } } - if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i], b1), printcomment(ipranges[i])); - logline(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) mlog(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i], b1), printcomment(ipranges[i])); + mlog(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); } const char *check(enet_uint32 ip, bool lock = false) // ip: host byte order @@ -1067,7 +1066,7 @@ struct serveripblacklist : serverconfigfile iprange ir; int line = 0, errors = 0; char *l, *r, *p = buf; - logline(ACLOG_VERBOSE,"reading ip blacklist '%s'", filename); + mlog(ACLOG_VERBOSE,"reading ip blacklist '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; line++; @@ -1079,7 +1078,7 @@ struct serveripblacklist : serverconfigfile if(l[strspn(l, " ")]) { for(int i = (int)strlen(l) - 1; i > 0 && l[i] == ' '; i--) l[i] = '\0'; - logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, l); + mlog(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, l); errors++; } } @@ -1092,20 +1091,20 @@ struct serveripblacklist : serverconfigfile if(ipranges[i].ur <= ipranges[i - 1].ur) { if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); + mlog(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); else - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); + mlog(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); ipranges.remove(i--); continue; } if(ipranges[i].lr <= ipranges[i - 1].ur) { - logline(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2)); + mlog(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2)); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } } - if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i], b1)); - logline(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) mlog(ACLOG_VERBOSE," %s", iprtoa(ipranges[i], b1)); + mlog(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); } bool check(enet_uint32 ip) // ip: network byte order @@ -1147,7 +1146,7 @@ struct servernickblacklist : serverconfigfile iprchain iprc; blackline bl; char *b, *l, *s, *r, *p = buf; - logline(ACLOG_VERBOSE,"reading nickname blacklist '%s'", filename); + mlog(ACLOG_VERBOSE,"reading nickname blacklist '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1199,14 +1198,14 @@ struct servernickblacklist : serverconfigfile bl.line = line; blacklines.add(bl); } - else { logline(ACLOG_INFO," error in line %d, file %s: unknown keyword '%s'", line, filename, l); errors++; } - if(s && s[strspn(s, " ")]) { logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, s); errors++; } + else { mlog(ACLOG_INFO," error in line %d, file %s: unknown keyword '%s'", line, filename, l); errors++; } + if(s && s[strspn(s, " ")]) { mlog(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, s); errors++; } } line++; } if(logcheck(ACLOG_VERBOSE)) { - logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); + mlog(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); string text, b; enumeratekt(whitelist, const char *, key, int, idx, { @@ -1217,9 +1216,9 @@ struct servernickblacklist : serverconfigfile if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); else concatformatstring(text, " %s", iprtoa(ic.ipr, b)); } - logline(ACLOG_VERBOSE, " accept %s%s", key, text); + mlog(ACLOG_VERBOSE, " accept %s%s", key, text); }); - logline(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); + mlog(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); loopv(blacklines) { text[0] = '\0'; @@ -1228,10 +1227,10 @@ struct servernickblacklist : serverconfigfile int k = blacklines[i].frag[j]; if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); } } - logline(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); + mlog(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } } - logline(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors); + mlog(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors); } int checkwhitelist(const client &c) @@ -1361,7 +1360,7 @@ struct serverforbiddenlist : serverconfigfile void read() { char *l, *p = buf; - logline(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); + mlog(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1424,7 +1423,7 @@ struct serverpasswords : serverconfigfile const char *sep = " "; int i, line = 1, par[ADMINPWD_MAXPAR]; char *b, *l, *p = buf; - logline(ACLOG_VERBOSE,"reading admin passwords '%s'", filename); + mlog(ACLOG_VERBOSE,"reading admin passwords '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1443,12 +1442,12 @@ struct serverpasswords : serverconfigfile c.line = line; c.denyadmin = par[0] > 0; adminpwds.add(c); - logline(ACLOG_VERBOSE,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0); + mlog(ACLOG_VERBOSE,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0); } } line++; } - logline(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); + mlog(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); } bool check(const char *name, const char *pwd, int salt, pwddetail *detail = NULL, enet_uint32 address = 0) @@ -1512,7 +1511,7 @@ struct serverinfofile : serverconfigfile // plaintext info file, used for serve { string s; char *l, *p = buf; - logline(ACLOG_VERBOSE,"reading info text file '%s'", filename); + mlog(ACLOG_VERBOSE,"reading info text file '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1522,7 +1521,7 @@ struct serverinfofile : serverconfigfile // plaintext info file, used for serve } if(msg.length() > maxlen) { - logline(ACLOG_VERBOSE,"info text file '%s' truncated: %d -> %d", filename, msg.length(), maxlen); + mlog(ACLOG_VERBOSE,"info text file '%s' truncated: %d -> %d", filename, msg.length(), maxlen); msg.shrink(maxlen); } } diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 2487ea3f..203310de 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -46,11 +46,10 @@ void disconnectmaster() ENetSocket connectmaster() { if(!mastername[0] || !usemaster) return ENET_SOCKET_NULL; - if(scl.maxclients > MAXCL) { logline(ACLOG_WARNING, "maxclient exceeded: cannot register"); return ENET_SOCKET_NULL; } if(masteraddress.host == ENET_HOST_ANY) { - logline(ACLOG_INFO, "looking up %s:%d...", mastername, masterport); + xlog(ACLOG_INFO, "looking up %s:%d...", mastername, masterport); masteraddress.port = masterport; if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; } @@ -62,7 +61,7 @@ ENetSocket connectmaster() } if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0) { - logline(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); + xlog(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); return ENET_SOCKET_NULL; } @@ -105,10 +104,10 @@ void processmasterinput() while(args < end && isspace(*args)) args++; if(!strncmp(input, "failreg", cmdlen)) - logline(ACLOG_WARNING, "master server registration failed: %s", args); + xlog(ACLOG_WARNING, "master server registration failed: %s", args); else if(!strncmp(input, "succreg", cmdlen)) { - logline(ACLOG_INFO, "master server registration succeeded"); + xlog(ACLOG_INFO, "master server registration succeeded"); } else processmasterinput(input, cmdlen, args); @@ -330,7 +329,8 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli void servermsinit(bool listen) { if(scl.master && *scl.master) copystring(mastername, scl.master); - usemaster = !listen || (listen && scl.master && scl.ssk); // true: connect to master, false: LAN-only server + usemaster = !listen || (listen && scl.master && scl.ssk && scl.maxclients <= MAXCLIENTSONMASTER); // true: connect to master, false: LAN-only server + if(scl.maxclients > MAXCLIENTSONMASTER) mlog(ACLOG_WARNING, "maxclient exceeded: cannot register"); disconnectmaster(); if(listen) @@ -338,7 +338,7 @@ void servermsinit(bool listen) ENetAddress address = { ENET_HOST_ANY, (enet_uint16) CUBE_SERVINFO_PORT(scl.serverport) }; if(*scl.ip) { - if(enet_address_set_host(&address, scl.ip) < 0) logline(ACLOG_WARNING, "server ip \"%s\" not resolved", scl.ip); + if(enet_address_set_host(&address, scl.ip) < 0) mlog(ACLOG_WARNING, "server ip \"%s\" not resolved", scl.ip); else serveraddress.host = address.host; } @@ -358,8 +358,8 @@ void servermsinit(bool listen) enet_socket_destroy(lansock); lansock = ENET_SOCKET_NULL; } - if(lansock == ENET_SOCKET_NULL) logline(ACLOG_WARNING, "could not create LAN server info socket"); + if(lansock == ENET_SOCKET_NULL) mlog(ACLOG_WARNING, "could not create LAN server info socket"); else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); } - if(!usemaster) logline(ACLOG_INFO, "server is LAN-only - without connection to masterserver"); + if(!usemaster) mlog(ACLOG_INFO, "server is LAN-only - without connection to masterserver"); } diff --git a/source/src/tools.h b/source/src/tools.h index 56fdba08..f984a88f 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -117,7 +117,8 @@ inline int iabs(int n) { return labs(n); } #define MAXSTRLEN 260 typedef char string[MAXSTRLEN]; -inline void vformatstring(char *d, const char *fmt, va_list v, int len = MAXSTRLEN) { _vsnprintf(d, len, fmt, v); d[len-1] = 0; } +#define vformatstring(d,last,fmt) { va_list ap; va_start(ap, last); vformatstring_(d, fmt, ap); va_end(ap); } +inline void vformatstring_(char *d, const char *fmt, va_list v, int len = MAXSTRLEN) { _vsnprintf(d, len, fmt, v); d[len-1] = 0; } inline char *copystring(char *d, const char *s, size_t len = MAXSTRLEN) { strncpy(d, s, len); d[len-1] = 0; return d; } inline char *concatstring(char *d, const char *s, size_t len = MAXSTRLEN) { size_t used = strlen(d); return used < len ? copystring(d+used, s, len-used) : d; } extern char *concatformatstring(char *d, const char *s, ...) PRINTFARGS(2, 3); @@ -128,16 +129,13 @@ struct stringformatter stringformatter(char *buf): buf((char *)buf) {} void operator()(const char *fmt, ...) PRINTFARGS(2, 3) { - va_list v; - va_start(v, fmt); - vformatstring(buf, fmt, v); - va_end(v); + vformatstring(buf, fmt, fmt); } }; #define formatstring(d) stringformatter((char *)d) #define defformatstring(d) string d; formatstring(d) -#define defvformatstring(d,last,fmt) string d; { va_list ap; va_start(ap, last); vformatstring(d, fmt, ap); va_end(ap); } +#define defvformatstring(d,last,fmt) string d; vformatstring(d,last,fmt) #define loopv(v) for(int i = 0; i<(v).length(); i++) #define loopvj(v) for(int j = 0; j<(v).length(); j++) From 0b656d2380dd7de9d43c8280d18bc4dfdc2a9e50 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 26 Aug 2016 16:28:08 +0200 Subject: [PATCH 24/45] add server-side player vita database The server keeps track of every player ID that ever connected successfully. This database stores the last names and IPs used by the player and the last countries the connections came from. Some stats are counted, like "time connected" and "total tks". Most importantly, the database stores rights (like admin) and restrictions (like ban). All rights and restrictions are kept as dates (or timecodes), so, for example, if your ban date is in the future, you are banned - and if it's in the past, you can play. The collected stats are meant to help server owners at managing players - especially, if they want to give someone admin rights. The database is periodically saved to a file and read from that file on startup. The default file is config/servervita.cfg and one backup is kept in config/servervita_bak.cfg. During server start, if the regular file can't be loaded, the backup is tried instead. While the server is running, it checks periodically for a file named config/servervita_update.cfg, and if the file exists, it is renamed to a backup filename including a timecode and read and merged into the database. The automatic rename makes sure, that a file is only read once. Using such updates, a server owner can easily give several people admin rights or for example, add clan tags for a whole list of IDs. (Yes, the database also stores a clan tag for every ID...) The database also stores two short comments for every ID. One can only be read by server admins, but the other one is public. The public reason, for example, can state a reason for a ban. One database record takes up 264 bytes of RAM. During autosave, accounts, that have not been used for a specified time (default 12 months), will not be written. After a server restart, those old records will be gone. Currently, there is no process in place, to also clean up old records from RAM. Maybe, that'll be added later... --- .gitignore | 1 + config/servercmdline.txt | 1 + config/serverparameters.cfg | 8 ++ source/src/clients2c.cpp | 20 ++++- source/src/command.cpp | 26 ------ source/src/crypto.cpp | 4 +- source/src/protocol.cpp | 32 +++++++ source/src/protocol.h | 2 + source/src/protos.h | 10 ++- source/src/server.cpp | 71 +++++++++++++-- source/src/server.h | 25 +++++- source/src/serverfiles.h | 175 ++++++++++++++++++++++++++++++++++++ source/src/tools.h | 14 +-- 13 files changed, 344 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index bcd9169f..aca7538a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ config/servers.cfg config/configtemplates.zip config/mapmodelattributes.cfg config/entropy.dat +config/servervita* packages/maps/*.cfg !packages/maps/new_content.cfg packages/maps/*.cgz diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 9af8915a..6e1b57ec 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -40,6 +40,7 @@ // -H // server logfile path, default logs/ // -r // server maprot file, default config/maprot.cfg // -A // server parameter file, default config/serverparameters.cfg +// -U // vitapath, path & filename prefix to store the server vita database at, default: config/servervita // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg // -G // server geoip file, default config/geoip.cfg diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 402e5f6b..c15305a9 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -19,6 +19,14 @@ auth_verify_ip:0 // integer [0..1], default 1, 0:disabled, 1:enabled mandatory_auth:1 +// vitaautosave Vita file autosave interval in minutes (0: only when server is empty) +// integer [0..1440], default 10 +vitaautosave:10 + +// vitamaxage Omit vitas from autosave, if the last login has been more than the specified number of months ago +// integer [3..120], default 12 +vitamaxage:12 + // **** Maprot settings **** diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 8a955400..3c7781f2 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -321,7 +321,13 @@ VARP(voicecomsounds, 0, 1, 2); bool medals_arrived=0; medalsst a_medals[END_MDS]; -struct session_s { enet_uint32 serverip, clientip, clientipcensored, curpeerip; int curpeerport, serverclock, clientclock, cn, clientsalt; uchar serverpubkey[32], clientsignature[64]; } session; +struct session_s +{ + enet_uint32 serverip, clientip, clientipcensored, curpeerip; + int curpeerport, serverclock, clientclock, cn, clientsalt, datecodes; + uchar serverpubkey[32], clientsignature[64]; + string clan, publiccomment; +} session; void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { @@ -443,6 +449,18 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); p.get((uchar*)servauth, 64); + s->datecodes = getint(p); + getstring(text, p); + filtertext(s->clan, text, FTXT__VITACLAN); + getstring(text, p); + filtertext(s->publiccomment, text, FTXT__VITACOMMENT); +#ifdef _DEBUG + formatstring(text)("server-side%s", s->datecodes ? " timed flags:" : ""); + loopi(VS_NUMCOUNTERS) if(s->datecodes & (1 << i)) concatformatstring(text, " %s", vskeywords[i]); + if(s->clan[0]) concatformatstring(text, " clan tag: \"%s\"", s->clan); + if(s->publiccomment[0]) concatformatstring(text, " comment: \"%s\"", s->publiccomment); + if(strlen(text) > 12) conoutf("%s", text); +#endif } if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); if(sk && auth) diff --git a/source/src/command.cpp b/source/src/command.cpp index fffb24e6..a79ef267 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -1492,32 +1492,6 @@ COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); #ifndef STANDALONE -const char *escapestring(const char *s, bool force, bool noquotes) -{ - static vector strbuf[3]; - static int stridx = 0; - if(noquotes) force = false; - if(!s) return force ? "\"\"" : ""; - if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\n\r$"))) return s; - stridx = (stridx + 1) % 3; - vector &buf = strbuf[stridx]; - buf.setsize(0); - if(!noquotes) buf.add('"'); - for(; *s; s++) switch(*s) - { - case '\n': buf.put("\\n", 2); break; - case '\r': buf.put("\\n", 2); break; - case '\t': buf.put("\\t", 2); break; - case '\a': buf.put("\\a", 2); break; - case '\f': buf.put("\\f", 2); break; - case '"': buf.put("\\\"", 2); break; - case '\\': buf.put("\\\\", 2); break; - default: buf.add(*s); break; - } - if(!noquotes) buf.add('"'); - buf.add(0); - return buf.getbuf(); -} COMMANDF(escape, "s", (const char *s) { result(escapestring(s));}); int sortident(ident **a, ident **b) { return strcasecmp((*a)->name, (*b)->name); } diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index ef1c4df9..58587c51 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -1092,7 +1092,7 @@ static int certtypemaxrank[] = { 0, 1, 2, 3, 3, 3, 0 }; // max allowed rank of static int certtypeexpiration[] = { 0, 0, 0, 2 * CERTTIMEMONTH, CERTTIMEMONTH, 3 * CERTTIMEMONTH, CERTTIMEMONTH }; // time after which certs are usually re-signed, after 3x the time they expire vector certs; -hashtable certblacklist; +hashtable certblacklist; static inline int aktcerttime() { return time(NULL) / (time_t) 60; } // use minutes instead of seconds as timebase, so int will be more than enough @@ -1345,7 +1345,7 @@ void rebuildcerttree() // determines the rank of all certs; ages them; rem if(c->days2renew < 0) c->needsrenewal = true; if(c->days2expire < 0) { c->expired = true; c->isvalid = false; } } - if(c->pubkey && certblacklist.access(c->pubkey)) + if(c->pubkey && certblacklist.access(*((uchar32*)c->pubkey))) { c->blacklisted = true; c->isvalid = false; diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 0887c75a..49e71ffa 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -391,6 +391,7 @@ void filterrichtext(char *dst, const char *src, int len) case 'a': c = '\a'; break; case 't': c = '\t'; break; case 'n': c = '\n'; break; + case '_': c = ' '; break; case 'x': b = 16; c = *++src; @@ -410,6 +411,37 @@ void filterrichtext(char *dst, const char *src, int len) *dst = '\0'; } +// counterpart of filterrichtext + +const char *escapestring(const char *s, bool force, bool noquotes, vector *buf) +{ + static vector strbuf[3]; + static int stridx = 0; + extern bool isdedicated; + if(noquotes) force = false; + if(!s) return force ? "\"\"" : ""; + if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\a\n\r$"))) return s; + if(!buf) buf = &strbuf[stridx++]; + stridx %= 3; + buf->setsize(0); + if(!noquotes) buf->add('"'); + for(; *s; s++) switch(*s) + { + case '\n': buf->put("\\n", 2); break; + case '\r': buf->put("\\n", 2); break; + case '\t': buf->put("\\t", 2); break; + case '\a': buf->put("\\a", 2); break; + case '\f': buf->put("\\f", 2); break; + case '"': buf->put("\\\"", 2); break; + case '\\': buf->put("\\\\", 2); break; + case ' ': if(isdedicated) { buf->put("\\_", 2); break; } + default: buf->add(*s); break; + } + if(!noquotes) buf->add('"'); + buf->add(0); + return buf->getbuf(); +} + // ensures, that d is either two lowercase chars or "" void filterlang(char *d, const char *s) diff --git a/source/src/protocol.h b/source/src/protocol.h index 225c5c19..43e29ddc 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -232,3 +232,5 @@ enum #define FTXT__ZIPDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) #define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_TOLOWER) #define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK) +#define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) +#define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) diff --git a/source/src/protos.h b/source/src/protos.h index 0a20c48a..5e86b3af 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1019,7 +1019,6 @@ extern void writecfg(); extern void deletecfg(); extern void identnames(vector &names, bool builtinonly); extern void explodelist(const char *s, vector &elems); -extern const char *escapestring(const char *s, bool force = true, bool noquotes = false); extern int execcontext; extern void pushscontext(int newcontext); extern int popscontext(); @@ -1030,6 +1029,9 @@ extern void dumpexecutionstack(stream *f); extern const char *currentserver(int i); // server +enum { /* timecodes */ VS_FIRSTLOGIN = 0, VS_LASTLOGIN, VS_MUTE, VS_NOVOTE, VS_BAN, VS_ADMIN, VS_OWNER, VS_MAPUPLOAD, + /* stats/counters */ VS_MINUTESCONNECTED, VS_MINUTESACTIVE, VS_FLAGS, VS_FRAGS, VS_DEATHS, VS_TKS, VS_DAMAGE, VS_FF, VS_NUM, VS_NUMCOUNTERS = VS_MINUTESCONNECTED }; +extern const char *vskeywords[VS_NUM + 1], *vsnames[VS_NUM + 1]; extern int modeacronyms; extern void servertoclient(int chan, uchar *buf, int len, bool demo = false); extern void localservertoclient(int chan, uchar *buf, int len, bool demo = false); @@ -1073,6 +1075,7 @@ extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() extern bool validmapname(const char *s); // checks for length, allowed chars and special DOS filenames extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); +extern const char *escapestring(const char *s, bool force = true, bool noquotes = false, vector *buf = NULL); extern void filterlang(char *d, const char *s); extern void filtercountrycode(char *d, const char *s); // returns exactly two uppercase chars or "--" extern void trimtrailingwhitespace(char *s); @@ -1155,7 +1158,7 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; @@ -1165,7 +1168,7 @@ struct servercommandline maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { @@ -1222,6 +1225,7 @@ struct servercommandline break; case 'H': logfilepath = a; break; case 'A': parfilepath = a; break; + case 'U': vitabasename = a; break; case 'c': if(ai > 0) maxclients = min(ai, MAXCLIENTS); break; case 'k': { diff --git a/source/src/server.cpp b/source/src/server.cpp index 45734ea8..8d87490c 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -112,12 +112,14 @@ int cn2boot; const char *endis[] = { "disabled", "enabled", "" }; SERVPARLIST(dumpmaprot, 0, 1, 0, endis, "ddump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt)"); SERVPARLIST(dumpparameters, 0, 0, 0, endis, "ddump server parameters when updated"); +SERVPAR(vitaautosave, 0, 10, 24 * 60, "sVita file autosave interval in minutes (0: only when server is empty)"); +SERVPAR(vitamaxage, 3, 12, 120, "sOmit vitas from autosave, if the last login has been more than the specified number of months ago"); void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; - static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0; - static bool debugmaprot = true; + static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0, lastvitasave = 0; + static bool debugmaprot = true, serverwasnotempty = false; switch(stage) // cycle through some tasks once per minute (mostly file polling) { @@ -188,7 +190,15 @@ void poll_serverthreads() // called once per mainloop-timeslice serverconfigs[nextserverconfig]->process(); // processing of config files is done in mainloop, one at a time nextserverconfig++; } - else stage++; + else + { + if(vitaupdatebuf) + { + mlog(ACLOG_INFO, "read/updated %d vitas from %s", parsevitas(vitaupdatebuf, vitaupdatebuflen), vitafilename_update_backup);; + DELETEA(vitaupdatebuf); + } + stage++; + } } break; } @@ -215,6 +225,25 @@ void poll_serverthreads() // called once per mainloop-timeslice delete f; } } + else if(!vitastosave && serverwasnotempty && (numclients() == 0 || (vitaautosave && (servmillis - lastvitasave) > 1000 * 60 * vitaautosave))) + { + serverwasnotempty = false; + lastvitasave = servmillis; + vitastosave = new vector; + vitakey_s vk; + int too_old = 0; + enumeratekt(vitas, uchar32, k, vita_s, v, + { + if((servclock - v.vs[VS_LASTLOGIN]) / (60 * 24 * 31) <= vitamaxage) + { + vk.k = &k; + vk.v = &v; + vitastosave->add(vk); + } + else too_old++; + }); + if(too_old) mlog(ACLOG_VERBOSE, "omitted %d vitas from autosave (exceeding maxage of %d months)", too_old, vitamaxage); + } else stage++; } case 5: // pause worker threads for a while (restart once a minute) @@ -228,6 +257,7 @@ void poll_serverthreads() // called once per mainloop-timeslice delete servermapstodelete.remove(i); // delete outdated servermaps } } + else serverwasnotempty = true; break; } } @@ -2994,11 +3024,24 @@ void polldeferredprocessing() client *cl = clients[sa->cn]; if(sa->wantauth) { - memcpy(cl->pubkey, sa->clientpubkey, 32); + memcpy(cl->pubkey.u, sa->clientpubkey, 32); memcpy(cl->servinforesponse, sa->clientmsg, sa->clientmsglen + 64); - bin2hex(cl->pubkeyhex, cl->pubkey, 32); + bin2hex(cl->pubkeyhex, cl->pubkey.u, 32); + cl->vita = vitas.access(cl->pubkey); + if(!cl->vita) + { + vita_s v; + memset(&v, 0, sizeof(v)); + cl->vita = &vitas.access(cl->pubkey, v); + cl->vita->vs[VS_FIRSTLOGIN] = servclock; + } + if(cl->country[0] != '-') cl->vita->addcc(cl->country); + cl->vita->addip(cl->ip); + cl->vita->vs[VS_LASTLOGIN] = servclock; cl->needsauth = true; // may have been voluntary, but it's done - sendf(sa->cn, 1, "riim", SV_SERVINFO_CONTD, 1, 64, sa->servermsg); + int datecodes = 0, *vs = cl->vita->vs; + loopi(VS_NUMCOUNTERS) datecodes |= vs[i] == 1 || (servclock - vs[i]) < 0 ? (1 << i) : 0; + sendf(sa->cn, 1, "riimiss", SV_SERVINFO_CONTD, 1, 64, sa->servermsg, datecodes, cl->vita->clan, cl->vita->publiccomment); } else sendf(sa->cn, 1, "rii", SV_SERVINFO_CONTD, 0); } @@ -3152,6 +3195,7 @@ void process(ENetPacket *packet, int sender, int chan) } } + if(cl->vita) cl->vita->addname(cl->name); sendwelcome(cl); if(restorescore(*cl)) { sendresume(*cl, true); senddisconnectedscores(-1); } else if(cl->type==ST_TCPIP) senddisconnectedscores(sender); @@ -3449,6 +3493,7 @@ void process(ENetPacket *packet, int sender, int chan) break; } } + if(cl->vita) cl->vita->addname(cl->name); } break; } @@ -4356,6 +4401,11 @@ void serverslice(uint timeout) // main server update, called from cube main lo linequalitystats(0); } serverhost->totalSentData = serverhost->totalReceivedData = 0; + loopv(clients) if(clients[i]->type == ST_TCPIP && clients[i]->isauthed && clients[i]->vita) + { + clients[i]->vita->vs[VS_MINUTESCONNECTED]++; + if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) clients[i]->vita->vs[VS_MINUTESACTIVE]++; + } } ENetEvent event; @@ -4650,6 +4700,15 @@ void initserver(bool dedicated) loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; if(scl.ssk) mlog(ACLOG_INFO,"server public key: %s", servpubkey); + formatstring(vitafilename)("%s.cfg", scl.vitabasename); + formatstring(vitafilename_backup)("%s_bak.cfg", scl.vitabasename); + formatstring(vitafilename_update)("%s_update.cfg", scl.vitabasename); + formatstring(vitafilename_update_backup_base)("%s_update_", scl.vitabasename); + path(vitafilename); path(vitafilename_backup); path(vitafilename_update); path(vitafilename_update_backup_base); + char *vn; + int gotvitas = readvitas((vn = vitafilename)); + if(gotvitas < 0) gotvitas = readvitas((vn = vitafilename_backup)); + if(gotvitas >= 0) mlog(ACLOG_INFO, "read %d player vitas from %s", gotvitas, vn); maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); diff --git a/source/src/server.h b/source/src/server.h index e6093c98..f3b9b59d 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -223,6 +223,27 @@ struct medals } }; +#define VITANAMEHISTLEN 4 +#define VITAIPHISTLEN 4 +#define VITACCHISTLEN 4 +#define VITACOMMENTLEN 31 +#define VITACLANLEN 7 + +struct vita_s +{ + char namehist[VITANAMEHISTLEN][MAXNAMELEN + 1]; + enet_uint32 iphist[VITAIPHISTLEN]; + char privatecomment[VITACOMMENTLEN + 1], publiccomment[VITACOMMENTLEN + 1]; // private comments can be seen by admins and owners only, public comments can be seen by everyone + char clan[VITACLANLEN + 1]; + char cchist[VITACCHISTLEN * 2]; + int vs[VS_NUM]; + void addname(const char *name); + void addip(enet_uint32 ip); + void addcc(const char *cc); +}; + +struct vitakey_s { const vita_s *v; const uchar32 *k; }; + struct client // server side version of "dynent" type { int type; @@ -232,8 +253,9 @@ struct client // server side version of "dynent" type enet_uint32 ip, ip_censored; int connectclock; char country[4]; - uchar pubkey[32]; + uchar32 pubkey; char pubkeyhex[68]; + vita_s *vita; string servinforesponse; string name; int team; @@ -334,6 +356,7 @@ struct client // server side version of "dynent" type name[0] = pwd[0] = demoflags = 0; ip = ip_censored = 0; pubkeyhex[0] = '\0'; + vita = NULL; bottomRTT = ping = 9999; team = TEAM_SPECT; state.state = CS_SPECTATE; diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index e77a64d6..e2324fd9 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -715,6 +715,11 @@ void serverconfigfile::process() // in main thread: refill data structures with } } +string vitafilename, vitafilename_backup, vitafilename_update, vitafilename_update_backup_base, vitafilename_update_backup; +char *vitaupdatebuf = NULL; +int vitaupdatebuflen = 0; +vector *vitastosave = NULL; + int readserverconfigsthread(void *data) { while(1) @@ -723,6 +728,27 @@ int readserverconfigsthread(void *data) loopv(serverconfigs) serverconfigs[i]->trypreload(); + // special treatment for vita updates: read once and move out of the way + if(!vitaupdatebuf && getfilesize(vitafilename_update) > 0) + { + formatstring(vitafilename_update_backup)("%s%d.cfg", vitafilename_update_backup_base, (int)(time(NULL) / 60)); + backup(vitafilename_update, vitafilename_update_backup); + serverconfigfile vsf; + vsf.init(vitafilename_update_backup, false); + vsf.load(); + vitaupdatebuf = vsf.buf; + vitaupdatebuflen = vsf.filelen; + vsf.buf = NULL; + } + if(vitastosave) + { // mainloop prepared a selection of vitas to save + backup(vitafilename, vitafilename_backup); + stream *f = openfile(vitafilename, "w"); + extern void writevitas(stream *f, vector &vs); + if(f) writevitas(f, *vitastosave); + DELETEP(f); DELETEP(vitastosave); + } + entropy_save(); recheckallserverconfigs = false; } @@ -1804,4 +1830,153 @@ void serverparameters_dumpdocu(char *fname) } #endif +// player ID bookkeeping + +const char *vskeywords[VS_NUM + 1] = { "first", "last", "mute", "novote", "ban", "admin", "owner", "mapupload", "minconn", "minact", "flags", "frags", "deaths", "tks", "damage", "ff", "" }; +const char *vsnames[VS_NUM + 1] = { "first login", "last login", "muted until", "novote until", "banned until", "admin until", "owner until", "mapupload until", + "minutes connected", "minutes active", "flags", "frags", "deaths", "teamkills", "damage dealt", "team damage dealt", "" }; +hashtable vitas; + +void vita_s::addname(const char *name) +{ + int rem = 0; + for(; rem < VITANAMEHISTLEN - 1; rem++) if(!strcasecmp(name, namehist[rem])) break; + if(rem) memmove(namehist[1], namehist[0], rem * (MAXNAMELEN + 1)); + copystring(namehist[0], name, MAXNAMELEN + 1); +} + +void vita_s::addip(enet_uint32 ip) +{ + int rem = 0; + for(; rem < VITAIPHISTLEN - 1; rem++) if(iphist[rem] == ip) break; + if(rem) memmove(iphist + 1, iphist, rem * sizeof(ip)); + iphist[0] = ip; +} + +void vita_s::addcc(const char *cc) +{ + int rem = 0; + for(; rem < VITACCHISTLEN - 1; rem++) if(cchist[rem * 2] == cc[0] && cchist[rem * 2 + 1] == cc[1]) break; + if(rem) memmove(cchist + 2, iphist, rem * 2); + loopi(2) cchist[i] = cc[i]; +} + +int parsevitas(char *buf, int filelen) +{ + vita_s v, *vp = &v; + uchar32 pubkey; + string tmp; + enet_uint32 ip; + int res = 0; + char *l, *r, *p = buf, *pe = p + filelen, *b, *o; + while(p < pe) + { + l = p; p += strlen(p) + 1; + l = strtok_r(l, " \t", &b); + while(l && (r = strtok_r(NULL, " \t", &b))) + { + filterrichtext(r, r); + if(!strcmp(l, "PUBKEY")) + { + if(hex2bin(pubkey.u, r, 32) == 32) + { + if(!(vp = vitas.access(pubkey))) + { + memset(&v, 0, sizeof(v)); + vp = &vitas.access(pubkey, v); + } + res++; + } + else vp = &v; // bad pubkey: point to dummy + break; + } + else if(!strcmp(l, "NAME")) + { + filtertext(tmp, r, FTXT__PLAYERNAME, MAXNAMELEN); + if(*tmp) vp->addname(tmp); + } + else if(!strcmp(l, "PUBCOM") || !strcmp(l, "PRIVCOM")) + { + filtertext(tmp, r, FTXT__VITACOMMENT, VITACOMMENTLEN); + copystring(l[1] == 'U' ? vp->publiccomment : vp->privatecomment, tmp, VITACOMMENTLEN + 1); + break; + } + else if(!strcmp(l, "IP")) + { + if(atoip(r, &ip) && ip) vp->addip(ip); + } + else if(!strcmp(l, "CC")) + { + filtercountrycode(r, r); + if(*r != '-') vp->addcc(r); + } + else if(!strcmp(l, "CLAN")) + { + filtertext(tmp, r, FTXT__VITACLAN, VITACLANLEN); + copystring(vp->clan, tmp, VITACLANLEN + 1); + break; + } + else if(!strcmp(l, "STAT")) + { + if((o = strchr(r, ':'))) // key:value + { + *o++ = '\0'; // r is key, o is value + loopk(VS_NUM) if(!strcmp(vskeywords[k], r)) + { + vp->vs[k] = atoi(o); + break; + } + } + } + } + } + return res; +} + +bool readvitas(const char *fname) +{ + serverconfigfile vsf; + vsf.init(fname, false); + vsf.load(); + return vsf.buf ? parsevitas(vsf.buf, vsf.filelen) : -1; +} + +void writevitas(stream *f, vector &vs) +{ + string tmp; + vector ctmp; + f->printf("// AC server vita database\n// autosaved %s\n// (don't edit this file, while the server is running)\n\n", timestring(true, "%c", tmp)); + loopvj(vs) + { + const vita_s &v = *vs[j].v; + f->printf("PUBKEY %s", bin2hex(tmp, vs[j].k->u, 32)); + if(v.namehist[0][0]) + { + f->printf("\n\tNAME"); + loopirev(VITANAMEHISTLEN) if(v.namehist[i][0]) f->printf(" %s", escapestring(v.namehist[i], true, true, &ctmp)); + } + if(v.iphist[0]) + { + f->printf("\n\tIP"); + loopirev(VITAIPHISTLEN) if(v.iphist[i]) f->printf(" %s", iptoa(v.iphist[i], tmp)); + } + if(v.privatecomment[0]) f->printf("\n\tPRIVCOM %s", escapestring(v.privatecomment, true, true, &ctmp)); + if(v.publiccomment[0]) f->printf("\n\tPUBCOM %s", escapestring(v.publiccomment, true, true, &ctmp)); + if(v.clan[0]) f->printf("\n\tCLAN %s", escapestring(v.clan, true, true, &ctmp)); + if(v.cchist[0]) + { + f->printf("\n\tCC"); + loopirev(VITACCHISTLEN) if(v.cchist[i * 2]) f->printf(" %c%c", v.cchist[i * 2], v.cchist[i * 2 + 1]); + } + f->printf("\n\tSTAT"); + loopi(VS_NUM) if(v.vs[i]) f->printf(" %s:%d", vskeywords[i], v.vs[i]); + f->printf("\n\n"); + } +} + + + + + + diff --git a/source/src/tools.h b/source/src/tools.h index f984a88f..3b6be9d2 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -506,14 +506,16 @@ static inline bool htcmp(const char *x, const char *y) return !strcmp(x, y); } -static inline uint hthash(const uchar *key) +struct uchar32 { uchar u[32]; }; + +static inline uint hthash(const uchar32 &key) { - return *((uint *)key); + return *((uint *)&key); } -static inline bool htcmp(const uchar *x, const uchar *y) // assume "uchar *" points to 32byte public keys +static inline bool htcmp(const uchar32 &x, const uchar32 &y) { - return !memcmp(x, y, 32); + return !memcmp(&x, &y, 32); } static inline uint hthash(int key) @@ -590,10 +592,10 @@ template struct hashtable } chain *c = unused; unused = unused->next; - c->key = key; c->next = table[h]; - table[h] = c; + c->key = key; numelems++; + table[h] = c; return c; } From cefe93ce182c8fcd1629447551482df9b66718bf Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 23 Mar 2016 21:24:51 +0100 Subject: [PATCH 25/45] don't autoplay maps from servermaps/incoming --- source/src/serverfiles.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index e2324fd9..c48c3d79 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -105,6 +105,7 @@ struct servermap // in-memory version of a map file on a server bool isro() { return fpath == servermappath_off || fpath == servermappath_serv; } bool isofficial() { return fpath == servermappath_off; } bool isdistributable() { return fpath == servermappath_serv || fpath == servermappath_incom; } + bool isautoplay() { return isro(); } int getmemusage() { return sizeof(struct servermap) + cgzlen + cfggzlen + layoutgzlen + numents * (sizeof(uchar) + sizeof(short) * 3); } @@ -870,7 +871,7 @@ struct servermaprot : serverconfigfile if(m->modes[j].manual > 0) man |= 1 << j; for(int i = max(0, (int)m->modes[j].restrict); i < CR_NUM; i++) rm[i] |= 1 << j; } - m->modes_auto = m->modes_allowed & ~man; + m->modes_auto = m->isautoplay() ? m->modes_allowed & ~man : 0; loopi(CR_NUM) m->modes_cr[i] = m->modes_allowed & rm[i]; loopi(MAXPLAYERS) m->modes_pn[i] = 0; loopj(GMODE_NUM) From 49232e05e02a1a9313cfd4e2bb6c1fa22c44f199 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 25 Aug 2016 19:18:10 +0200 Subject: [PATCH 26/45] new commands to add modeinfo and artist info to maps mapartist print|set|get|clear print: prints the map artist player ID set: sets the map artist player ID to the currently logged in player get: returns the current map artist player ID clear: erases an existing map artist record in the current map modeinfo list|get|clear| list: lists current modeinfo entries get: returns list of current modeinfo entries clear: erases current list of modeinfo entries : specifies flags for a list of modes DOCREF modeinfo mapartist --- config/scripts.cfg | 2 + config/serverparameters.cfg | 4 ++ source/src/entities.cpp | 1 + source/src/protocol.h | 2 + source/src/protos.h | 1 + source/src/server.cpp | 2 + source/src/server.h | 2 + source/src/serverfiles.h | 116 ++++++++++++++++++++++++++++-------- source/src/worldio.cpp | 110 +++++++++++++++++++++++++++++++++- 9 files changed, 214 insertions(+), 26 deletions(-) diff --git a/config/scripts.cfg b/config/scripts.cfg index e7dfbfdb..dc24f9e6 100644 --- a/config/scripts.cfg +++ b/config/scripts.cfg @@ -93,6 +93,8 @@ looplist [ listcomplete edittagclip "none plclip clip" listcomplete getvarrange "min max default" listcomplete mapareacheck "vdelta steepest total pprest pp" +listcomplete modeinfo "list clear get all ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv" +listcomplete mapartist "print get set clear" listcomplete authsetup "pre priv pub ppass pass passd needpass genpre genpriv genpub newppass newpass unarmed" listcomplete authkey "clear list delete new add selfcert" looplist [ addzipmod zipmodremove zipmodgetdesc ] cmd [complete $cmd "mods" zip] diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index c15305a9..717b580a 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -46,6 +46,10 @@ mappenalty_cutoff:60 // integer [-100..100], default 0 mappenalty_weight:0 +// use_hx_modeinfo List of maprot keywords to copy from map headers +// string [0..159 chars], default "weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold" +use_hx_modeinfo:weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold + // **** Debug switches **** diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 225a866f..43121f80 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -623,6 +623,7 @@ void entstats_(void) } if(es.entcnt[CTF_FLAG]) conoutf(" flag distance: %d", es.flagentdistance); conoutf(" map capabilities: has ffa spawns %d, has team spawns %d, has flags %d", es.hasffaspawns ? 1 : 0, es.hasteamspawns ? 1 : 0, es.hasflags ? 1 : 0); + if(es.modes_possible & GMMASK__MPNOCOOP) conoutf(" possible multiplayer modes: %s", gmode_enum(es.modes_possible & GMMASK__MPNOCOOP, txt)); conoutf("total entities: %d", ents.length()); intret(xmodels + xsounds); } diff --git a/source/src/protocol.h b/source/src/protocol.h index 43e29ddc..f3732c42 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -234,3 +234,5 @@ enum #define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK) #define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) #define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) +#define FTXT__MAPROT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER) + diff --git a/source/src/protos.h b/source/src/protos.h index 5e86b3af..fef3287c 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1038,6 +1038,7 @@ extern void localservertoclient(int chan, uchar *buf, int len, bool demo = false extern const char *modestr(int n, bool acronyms = false); extern const char *voteerrorstr(int n); extern const char *mmfullname(int n); +extern void filterconfigset(char *list, void *cs = NULL); extern void fatal(const char *s, ...) PRINTFARGS(1, 2); extern void initserver(bool dedicated); extern void cleanupserver(); diff --git a/source/src/server.cpp b/source/src/server.cpp index 8d87490c..31b937e3 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -4758,6 +4758,8 @@ void initserver(bool dedicated) readmapsthread_sem = new sl_semaphore(0, NULL); defformatstring(readmapslogfilename)("%sreadmaps_log.txt", scl.logfilepath); sl_createthread(readmapsthread, (void *)path(readmapslogfilename)); + defformatstring(_modeinfologfilename)("%smisc/modeinfo.txt", scl.logfilepath); + modeinfologfilename = path(_modeinfologfilename); readserverconfigsthread_sem = new sl_semaphore(0, NULL); sl_createthread(readserverconfigsthread, NULL); diff --git a/source/src/server.h b/source/src/server.h index f3b9b59d..28f76ea7 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -454,6 +454,8 @@ void sendf(int cn, int chan, const char *format, ...); extern bool isdedicated; +const char *modeinfologfilename = NULL; + const char *messagenames[SV_NUM] = { "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index c48c3d79..b2910275 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -31,12 +31,8 @@ struct configsetvalues }; void empty() { weight = repeat = 0; time = mintime = maxtime = minplayers = maxplayers = maxteamsize = teamthreshold = manual = restrict = -1; } - void add(configsetvalues u) - { - weight = clamp(weight + u.weight, -100, 100); - repeat = clamp(repeat + u.repeat, -100, 100); - for(int i = 2; i < CONFIG_MAXPAR; i++) if(u.par[i] >= 0) par[i] = u.par[i]; - } + void add(configsetvalues u); + bool parse(char *keyval); }; // map management @@ -82,6 +78,9 @@ struct servermap // in-memory version of a map file on a server uchar *enttypes; // table of entity types short *entpos_x, *entpos_y; + configsetvalues *hx_modeinfo; // maprot parameters from map author + uchar *hx_mapartist; // pubkey of map artist + int modes_allowed; // modes_possible reduced to MP modes (if dedicated server) configsetvalues modes[GMODE_NUM]; // maprot parameters for this map int modes_auto; // modes to be suggested automatically @@ -100,7 +99,7 @@ struct servermap // in-memory version of a map file on a server #endif servermap(const char *mname, const char *mpath) { memset(&fname, 0, sizeof(struct servermap)); fname = newstring(mname); fpath = mpath; } - ~servermap() { delstring(fname); DELETEA(cgzraw); DELETEA(cfgrawgz); DELETEA(enttypes); DELETEA(entpos_x); DELETEA(entpos_y); DELETEA(layoutgz); } + ~servermap() { delstring(fname); DELETEA(cgzraw); DELETEA(cfgrawgz); DELETEA(enttypes); DELETEA(entpos_x); DELETEA(entpos_y); DELETEA(layoutgz); DELETEA(hx_modeinfo); DELETEA(hx_mapartist); } bool isro() { return fpath == servermappath_off || fpath == servermappath_serv; } bool isofficial() { return fpath == servermappath_off; } @@ -137,7 +136,7 @@ struct servermap // in-memory version of a map file on a server stream *f = NULL; int restofhead; - string filename; + string filename, tmp; if(!validmapname(fname)) err = "illegal filename"; if(err) goto loadfailed; @@ -225,9 +224,27 @@ struct servermap // in-memory version of a map file on a server case HX_CONFIG: break; - case HX_MAPINFO: - case HX_MODEINFO: // for new maprot... + case HX_MODEINFO: + { + hx_modeinfo = new configsetvalues[GMODE_NUM]; + char done[GMODE_NUM] = { 0 }; + loopi(GMODE_NUM) hx_modeinfo[i].empty(); + string line; + for(int mode = getuint(q); !q.overread() && mode > 0; mode = getuint(q)) + { + getstring(line, q, MAXSTRLEN); + loopi(GMODE_NUM) if((mode & (1 << i)) && !done[i]++) filterconfigset(line, hx_modeinfo + i); + stream *mr = modeinfologfilename ? openfile(modeinfologfilename, "a") : NULL; + if(mr) { mr->printf("%s %s %s\n", fname, gmode_enum(mode, tmp), line); delete mr; } // dump modeinfo lines to file, may be used as maprot + } + break; + } case HX_ARTIST: + if(len == 32) q.get((hx_mapartist = new uchar[32]), 32); + else err = "invalid HX_ARTIST record"; + break; + + case HX_MAPINFO: default: break; } @@ -288,6 +305,8 @@ struct servermap // in-memory version of a map file on a server entpos_x[i] = e.x; entpos_y[i] = e.y; } + // respect map author wishes about disallowed modes + if(hx_modeinfo) loopi(GMODE_NUM) if(hx_modeinfo[i].restrict == 81) entstats.modes_possible &= ~(1 << i); // disallowed by elvis^2 } if(err) goto loadfailed; @@ -760,6 +779,60 @@ int readserverconfigsthread(void *data) #define MODEHISTLEN 64 // remember the game modes of the last hour (roughly) +void configsetvalues::add(configsetvalues u) +{ + weight = clamp(weight + u.weight, -100, 100); + repeat = clamp(repeat + u.repeat, -100, 100); + for(int i = 2; i < CONFIG_MAXPAR; i++) if(u.par[i] >= 0) par[i] = u.par[i]; +} + +bool configsetvalues::parse(char *keyval) // parse one key:value pair +{ + char *kb, *k = strtok_r(keyval, ":", &kb); + if(k) + { + loopi(CONFIG_MAXPAR) if(!strcmp(k, maprotkeywords[i])) + { + if((k = strtok_r(NULL, ":", &kb))) + { + par[i] = clamp(atoi(k), -100, 100); + return true; + } + } + } + return false; +} + +void filterconfigset(char *list, void *cs) +{ + configsetvalues e, u, *c = cs ? (configsetvalues *) cs : &u; + e.empty(), c->empty(); + char *b, *kv = strtok_r(list, " ", &b); + while(kv) + { + c->parse(kv); + kv = strtok_r(NULL, " ", &b); + } + list[0] = '\0'; + loopi(CONFIG_MAXPAR) if(c->par[i] != e.par[i]) concatformatstring(list, "%s%s:%d", list[0] ? " " : "", maprotkeywords[i], c->par[i]); +} + +SERVSTRF(use_hx_modeinfo, "weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold", 0, 159, FTXT__MAPROT, filter_use_hx_modeinfo, "gList of maprot keywords to copy from map headers"); + +int use_hx_modeinfo_mask = 0; + +void filter_use_hx_modeinfo() +{ + use_hx_modeinfo_mask = 0; + char *tmp = newstring(use_hx_modeinfo), *b, *kv = strtok_r(tmp, "|", &b); + while(kv) + { + loopi(CONFIG_MAXPAR) if(!strcmp(kv, maprotkeywords[i])) use_hx_modeinfo_mask |= 1 << i; + kv = strtok_r(NULL, "|", &b); + } + delstring(tmp); +} + struct configset : configsetvalues { char mapname[MAXMAPNAMELEN + 1]; @@ -816,19 +889,7 @@ struct servermaprot : serverconfigfile if(l) c.mmask = gmode_parse(l); while((l = strtok_r(NULL, " ", &b))) { - char *kb, *k = strtok_r(l, ":", &kb); - if(k) - { - loopi(CONFIG_MAXPAR) if(!strcmp(k, maprotkeywords[i])) - { - if((k = strtok_r(NULL, ":", &kb))) - { - c.par[i] = clamp(atoi(k), -100, 100); - haskeywords = true; - break; - } - } - } + if(c.parse(l)) haskeywords = true; } if(haskeywords) { @@ -855,7 +916,15 @@ struct servermaprot : serverconfigfile void initmap(servermap *m, stream *f) // set maprot parameters of a servermap { + configsetvalues e; memcpy(m->modes, base, sizeof(m->modes)); + if(m->hx_modeinfo && use_hx_modeinfo_mask) loopj(GMODE_NUM) + { + e.empty(); + loopk(CONFIG_MAXPAR) if(use_hx_modeinfo_mask & (1 << k)) e.par[k] = m->hx_modeinfo[j].par[k]; + m->modes[j].add(e); + } + loopv(configsets) { configset &c = configsets[i]; @@ -886,7 +955,6 @@ struct servermaprot : serverconfigfile { string s; f->printf("maprot parameters for: %s%s\n", m->fpath, m->fname); - configsetvalues e; e.empty(); loopj(GMODE_NUM) if((1 << j) & GMMASK__MPNOCOOP) { diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 450c4288..3a92fc26 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -283,7 +283,7 @@ void unpackheaderextra(uchar *buf, int len) // break the extra data from the ma while(1) { int len = getuint(p), flags = getuint(p); - if(p.overread() || len > p.remaining()) break; + if(p.overread() || len > p.remaining() || len < 0 || flags < 0) break; DEBUGCODE(clientlogf(" found headerextra \"%s\", %d bytes%s", hx_name(flags & HX_TYPEMASK), len, flags & HX_FLAG_PERSIST ? ", persistent" : "")); // debug info headerextras.add(new headerextra(len, flags, p.subbuf(len).buf)); } @@ -306,7 +306,8 @@ void parseheaderextra(bool clearnonpersist = true, int ignoretypes = 0) // pars case HX_CONFIG: setcontext("map", "embedded"); - execute((const char *)q.buf); // needs to have '\0' at the end + if(headerextras[i]->len > 0 && headerextras[i]->data[headerextras[i]->len - 1] == '\0') execute((const char *)q.buf); // needs to have '\0' at the end, better check... + else conoutf("malformed emb config"); resetcontext(); break; @@ -541,6 +542,111 @@ bool gotovantagepoint() } COMMANDF(gotovantagepoint, "", () { intret((editmode || (!multiplayer(NULL) && player1->isspectating())) && gotovantagepoint() ? 1 : 0); }); +void modeinfo(char *mode, char *info) +{ + char *modeinfolines[GMODE_NUM] = { NULL }; + string line, tmp; + + // find and unpack HX_MODEINFO + int he = findheaderextra(HX_MODEINFO); + if(he >= 0) + { + ucharbuf q(headerextras[he]->data, headerextras[he]->len); + for(int mode = getuint(q); !q.overread() && mode > 0; mode = getuint(q)) + { + getstring(line, q, MAXSTRLEN); + filterconfigset(line); + loopi(GMODE_NUM) if((mode & (1 << i)) && !modeinfolines[i]) modeinfolines[i] = newstring(line); + } + } + + // parse command arguments and apply changes or generate output + vector res; + copystring(line, info); + filterconfigset(line); + int modes = gmode_parse(mode), n = 0; + if(!strcmp(mode, "*") || !strcmp(mode, "all")) modes = GMMASK__MPNOCOOP; + else if(!strcmp(mode, "list")) + { + loopi(GMODE_NUM) if(modeinfolines[i] && ++n) conoutf("%s: %s", gmode_enum(1 << i, tmp), escapestring(modeinfolines[i])); + if(!n) conoutf("no modeinfo lines set"); + } + else if(!strcmp(mode, "get")) + { + loopi(GMODE_NUM) if(modeinfolines[i]) cvecprintf(res, "%s %s\n", gmode_enum(1 << i, tmp), escapestring(modeinfolines[i])); + } + else if(!strcmp(mode, "clear")) + { + loopi(GMODE_NUM) if(modeinfolines[i]) { n++; DELSTRING(modeinfolines[i]); } + conoutf("deleted %d modeinfo lines", n); + } + if(modes) loopi(GMODE_NUM) + { + if(modes & (1 << i)) + { + DELSTRING(modeinfolines[i]); + modeinfolines[i] = newstring(line); + } + } + if(res.length()) res.last() = '\0'; + else res.add('\0'); + result(res.getbuf()); + + // recreate HX_MODEINFO + for(int n; (n = findheaderextra(HX_MODEINFO)) >= 0; ) deleteheaderextra(n); + vector p; + entitystats_s es; + calcentitystats(es, NULL, 0); + int todo = es.modes_possible; + bool have_modeinfo = false; + loopk(GMODE_NUM) if(modeinfolines[k] && modeinfolines[k][0] && (todo & (1 << k))) + { + int modes = 0; + for(int i = k; i < GMODE_NUM; i++) + { + if(modeinfolines[i] && !strcmp(modeinfolines[k], modeinfolines[i])) modes |= (1 << i); + } + putuint(p, modes); + sendstring(modeinfolines[k], p); + todo &= ~modes; + have_modeinfo = true; + } + putuint(p, 0); + if(have_modeinfo) headerextras.add(new headerextra(p.length(), HX_MODEINFO|HX_FLAG_PERSIST, p.getbuf())); +} +COMMAND(modeinfo, "ss"); + +void mapartist(char *what) +{ + uchar32 buf; + bool update = false, remove = false;; + + // find and unpack HX_ARTIST + int he = findheaderextra(HX_ARTIST); + if(he >= 0 && headerextras[he]->len == 32) memcpy(buf.u, headerextras[he]->data, 32); + + // parse command arguments and apply changes or generate output + string tmp; + if(!strcmp(what, "print")) conoutf("map artist key: %s", bin2hex(tmp, buf.u, 32)); + else if(!strcmp(what, "set")) + { + if(*sk) + { + memcpy(buf.u, sk + 32, 32); + update = true; + } + else conoutf("no player account in use"); + } + else if(!strcmp(what, "clear")) remove = true; + bin2hex(tmp, buf.u, 32); + result(he >= 0 || update ? tmp : ""); + + // recreate HX_ARTIST + if(update || remove) for(int n; (n = findheaderextra(HX_ARTIST)) >= 0; ) deleteheaderextra(n); + if(update) headerextras.add(new headerextra(32, HX_ARTIST|HX_FLAG_PERSIST, buf.u)); +} +COMMAND(mapartist, "s"); + void setmapinfo(const char *newlicense, const char *newcomment) { string s; From daf4b9d24a0caa2ef7eed14c6bad81f98c0a4bdb Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 28 Mar 2016 19:54:00 +0200 Subject: [PATCH 27/45] add server parameters for statistic and diagnostic purposes Logged server parameters are checked for changes once a minute. If a change is detected, it is logged. --- config/serverparameters.cfg | 7 ++ source/src/log.cpp | 8 ++ source/src/server.cpp | 11 +- source/src/serverfiles.h | 198 +++++++++++++++++++++++++----------- 4 files changed, 163 insertions(+), 61 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 717b580a..f7cd3147 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -81,4 +81,11 @@ dumpsuggestions:1 //logthreshold_syslog:5 +// **** Server load statistics **** + +// stat_mainlog_peaklevel Main log peak fill level (0..100%) + +// stat_theadlog_peaklevel Threadlog peak fill level (0..100%) + + diff --git a/source/src/log.cpp b/source/src/log.cpp index a399d964..2351508c 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -99,12 +99,16 @@ static ringbuf threadlog; static int mainlogoverflow = 0; static sl_semaphore threadlogsem(1, NULL), threadlogfullsem(0, NULL), mainlogsem(0, NULL); +extern int stat_mainlog_peaklevel; + int logworkerthread(void *nop) { string tmp; for(;;) { while(mainlog.empty()) mainlogsem.wait(); + int mainloglevel = (100 * mainlog.length()) / mainlog.maxsize(); + if(mainloglevel > stat_mainlog_peaklevel) stat_mainlog_peaklevel = mainloglevel; logline_s *ll = mainlog.remove(); int targets = loglevelenabled[ll->level]; bool logtocon = (targets & LOGTARGET_CONSOLE) != 0, logtofile = fp && (targets & LOGTARGET_FILE) != 0, logtosyslog = (targets & LOGTARGET_SYSLOG) != 0; @@ -199,8 +203,12 @@ void xlog(int level, const char *msg, ...) // log line from any thread (more exp } } +extern int stat_theadlog_peaklevel; + void poll_logbuffers() // just copy threadlog to mainlog and restart blocked threads, if necessary { + int threadloglevel = (100 * threadlog.length()) / threadlog.maxsize(); + if(threadloglevel > stat_theadlog_peaklevel) stat_theadlog_peaklevel = threadloglevel; while(!mainlog.full() && !threadlog.empty()) { mainlog.stage(threadlog.remove()); diff --git a/source/src/server.cpp b/source/src/server.cpp index 31b937e3..939dbf44 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -245,8 +245,14 @@ void poll_serverthreads() // called once per mainloop-timeslice if(too_old) mlog(ACLOG_VERBOSE, "omitted %d vitas from autosave (exceeding maxage of %d months)", too_old, vitamaxage); } else stage++; + break; } - case 5: // pause worker threads for a while (restart once a minute) + case 5: // log changed server diagnostics parameters + serverparameters.log(); + stage++; + break; + + case 6: // pause worker threads for a while (restart once a minute) { if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) @@ -4654,6 +4660,8 @@ const char *loglevelnames[] = { "debug", "verbose", "info", "warning", "error", SERVPARLISTF(logthreshold_console, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CConsole log level"); SERVPARLISTF(logthreshold_syslog, 0, ACLOG_NUM, 0, loglevelnames, restartlogging, "CSyslog log level"); SERVPARLISTF(logthreshold_file, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CLogfile log level"); +SERVSTAT(stat_mainlog_peaklevel, 0, "lMain log peak fill level (0..100%)"); +SERVSTAT(stat_theadlog_peaklevel, 0, "lThreadlog peak fill level (0..100%)"); void restartlogging() { @@ -4674,6 +4682,7 @@ void initserver(bool dedicated) ed25519_pubkey_from_private(spk, ssk); bin2hex(servpubkey, spk, 32); sg->smapname[0] = '\0'; + servparallfun(); // start logging initserverparameter("logthreshold_console", scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO)); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index b2910275..65022b7e 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -10,6 +10,8 @@ extern bool addservparstr(const char *name, int minlen, int maxlen, int filt, co #define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, desc) #define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, desc) #define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, desc) +#define SERVSTAT(name, cur, desc) int last_##name, name = addservparint(#name, 0, cur, 0, NULL, &name, &last_##name, NULL, false, false, desc) +#define SERVSTATLOG(name, cur, level, desc) int last_##name, name = addservparint(#name, level, cur, 0, NULL, &name, &last_##name, NULL, true, false, desc) #else #define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, NULL) #define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, NULL) @@ -17,6 +19,8 @@ extern bool addservparstr(const char *name, int minlen, int maxlen, int filt, co #define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, NULL) #define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, NULL) #define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, NULL) +#define SERVSTAT(name, cur, desc) int last_##name, name = addservparint(#name, 0, cur, 0, NULL, &name, &last_##name, NULL, false, false, desc) +#define SERVSTATLOG(name, cur, level, desc) int last_##name, name = addservparint(#name, level, cur, 0, NULL, &name, &last_##name, NULL, true, false, desc) #endif #define CONFIG_MAXPAR 11 @@ -1698,6 +1702,65 @@ struct servpar servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, int fromfile, const char *_desc) : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile) { DEBUGCODE(desc = _desc;) } + + void update() + { + if(fromfile) switch(type) + { + case SID_INT: + { + if(*i != *shadow_i) + { + int tmp = *i; + *i = *shadow_i; + *shadow_i = tmp; // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + if(fun) ((void (__cdecl *)())fun)(); + *shadow_i = *i; + } + break; + } + case SID_STR: + { + if(strcmp(s, shadow_s)) + { + if(fun) + { + char tmp[maxlen + 1]; + s[maxlen] = shadow_s[maxlen] = '\0'; + strncpy(tmp, s, maxlen + 1); + strncpy(s, shadow_s, maxlen + 1); + strncpy(shadow_s, tmp, maxlen + 1); // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + ((void (__cdecl *)())fun)(); + strncpy(shadow_s, s, maxlen + 1); + } + else strncpy(s, shadow_s, maxlen + 1); + } + break; + } + } + } + + bool nextvalue(char *nv) + { + switch(type) + { + case SID_INT: + { + int ni = ATOI(nv), idx; + if(list && (idx = getlistindex(nv, list, false, -1)) >= 0) ni = idx + minval; // multiple choice + if(ni >= minval && ni <= maxval) { *shadow_i = ni; return true; } + break; + } + case SID_STR: + { + filtertext(nv, nv, filter); + int ni = (int)strlen(nv); + if(ni >= minlen && ni <= maxlen) { strncpy(shadow_s, nv, maxlen + 1); return true; } + break; + } + } + return false; + } }; hashtable *servpars = NULL; @@ -1706,7 +1769,7 @@ int addservparint(const char *name, int minval, int cur, int maxval, const char { if(!servpars) servpars = new hashtable; if(list && minval == maxval) while(list[maxval - minval + 1][0]) maxval++; // get list length - ASSERT(cur >= minval && cur <= maxval); + ASSERT((fromfile && cur >= minval && cur <= maxval) || (!fromfile && minval < ACLOG_NUM)); servpar v(SID_INT, name, minval, maxval, list, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); servpars->access(name, v); *shadowstorage = cur; @@ -1716,7 +1779,7 @@ int addservparint(const char *name, int minval, int cur, int maxval, const char bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc) { if(!servpars) servpars = new hashtable; - ASSERT(maxlen > minlen && minlen >= 0 && int(strlen(cur)) >= minlen && int(strlen(cur)) <= maxlen); + ASSERT(((fromfile && maxlen > minlen && minlen >= 0 && int(strlen(cur)) >= minlen) || (!fromfile && minlen < ACLOG_NUM)) && int(strlen(cur)) <= maxlen); servpar v(SID_STR, name, minlen, maxlen, filt, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); servpars->access(name, v); strncpy(storage, cur, maxlen + 1); @@ -1731,30 +1794,17 @@ struct serverparameter : serverconfigfile { if(serverconfigfile::load()) { - int line = 0, idx; + int line = 0; char *b, *l, *p = buf, *kb, *k, *v; servpar *id; while(p < buf + filelen) { l = p; p += strlen(p) + 1; line++; l = strtok_r(l, " ", &b); - if(l && (k = strtok_r(l, ":", &kb)) && (v = strtok_r(NULL, ":", &kb)) && (id = servpars->access(k)) && id->fromfile) switch(id->type) + if(l && (k = strtok_r(l, ":", &kb)) && (v = strtok_r(NULL, ":", &kb)) && (id = servpars->access(k)) && id->fromfile) { - case SID_INT: - { - int i = ATOI(v); - if(id->list && (idx = getlistindex(v, id->list, false, -1)) >= 0) i = idx + id->minval; // multiple choice - if(i >= id->minval && i <= id->maxval) *(id->shadow_i) = i; - break; - } - case SID_STR: - { - if((v - k) - strlen(k) > 1) filterrichtext(v, v); - filtertext(v, v, id->filter); - int i = (int)strlen(v); - if(i >= id->minlen && i <= id->maxlen) strncpy(id->shadow_s, v, id->maxlen + 1); - break; - } + if(id->type == SID_STR && (v - k) - strlen(k) > 1) filterrichtext(v, v); + id->nextvalue(v); } } return true; @@ -1764,40 +1814,7 @@ struct serverparameter : serverconfigfile void read() { // in mainloop: update values from shadow values - enumerate(*servpars, servpar, id, - switch(id.type) - { - case SID_INT: - { - if(*id.i != *id.shadow_i) - { - int tmp = *id.i; - *id.i = *id.shadow_i; - *id.shadow_i = tmp; // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) - if(id.fun) ((void (__cdecl *)())id.fun)(); - *id.shadow_i = *id.i; - } - break; - } - case SID_STR: - { - if(strcmp(id.s, id.shadow_s)) - { - if(id.fun) - { - char tmp[id.maxlen + 1]; - strncpy(tmp, id.s, id.maxlen + 1); - strncpy(id.s, id.shadow_s, id.maxlen + 1); - strncpy(id.shadow_s, tmp, id.maxlen + 1); // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) - ((void (__cdecl *)())id.fun)(); - strncpy(id.shadow_s, id.s, id.maxlen + 1); - } - else strncpy(id.s, id.shadow_s, id.maxlen + 1); - } - break; - } - } - ); + enumerate(*servpars, servpar, id, id.update()); } void dump(stream *f) @@ -1810,6 +1827,41 @@ struct serverparameter : serverconfigfile } ); } + + void log() + { // in mainloop: check for changed values and log if necessary + enumerate(*servpars, servpar, id, + { + if(id.logchanges) switch(id.type) + { + case SID_INT: + { + if(*id.i != *id.shadow_i) + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + mlog(id.minval, "server statistics parameter %s changed from %d to %d", id.name, *id.shadow_i, *id.i); + *id.shadow_i = *id.i; + } + break; + } + case SID_STR: + { + if(strcmp(id.s, id.shadow_s)) + { + id.s[id.maxlen] = id.shadow_s[id.maxlen] = '\0'; + if(id.fun) ((void (__cdecl *)())id.fun)(); + string tmp1; string tmp2; + filtertext(tmp1, id.shadow_s, id.filter); + filtertext(tmp2, id.s, id.filter); + mlog(id.minval, "server statistics parameter %s changed from \"%s\" to \"%s\"", id.name, tmp1, tmp2); + strncpy(id.shadow_s, id.s, id.maxlen + 1); + id.shadow_s[id.maxlen] = '\0'; + } + break; + } + } + }); + } }; // optionally overwrite builtin default values with commandline parameters - before updating from file starts @@ -1819,7 +1871,11 @@ void initserverparameter(const char *name, int value) ASSERT(servpars); servpar *id = servpars->access(name); ASSERT(id && id->type == SID_INT); - if(value >= id->minval && value <= id->maxval) *id->i = *id->shadow_i = value; + if(value >= id->minval && value <= id->maxval) + { + *id->shadow_i = value; + id->update(); + } } void initserverparameter(const char *name, const char *value) @@ -1832,27 +1888,38 @@ void initserverparameter(const char *name, const char *value) int len = (int)strlen(tmp); if(len >= id->minlen && len <= id->maxlen) { - strncpy(id->s, value, id->maxlen + 1); strncpy(id->shadow_s, value, id->maxlen + 1); + id->update(); } } +void servparallfun() // trigger all functions once, to make sure, unchanged defaults are translated into filtered values +{ + enumerate(*servpars, servpar, id, + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + }); +} + // write a nice template for serverparameters.cfg #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "CCommandline switch overrides", "" }; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "CCommandline switch overrides", "lServer load statistics", "" }; const char *siddocchaptersorting = "sgvmdC", *siddocchaptercommentedout = "C"; void serverparameters_dumpdocu(char *fname) { vector sp; - enumerate(*servpars, servpar, id, if(id.fromfile && id.desc && id.desc[0]) sp.add(&id)); + enumerate(*servpars, servpar, id, if(id.desc && id.desc[0]) sp.add(&id)); loopv(sp) { - const char *e = strchr(siddocchaptersorting, sp[i]->desc[0]); - sp[i]->chapter = e ? (int) (e - siddocchaptersorting) : sp[i]->desc[0]; + servpar &id = *sp[i]; + const char *e = strchr(siddocchaptersorting, id.desc[0]); + id.chapter = e ? (int) (e - siddocchaptersorting) : id.desc[0]; + if(!id.fromfile) id.chapter |= 1 << 20; + if(!id.logchanges) id.chapter |= 1 << 19; } sp.sort(siddocsort); stream *f = openfile(path(fname), "w"); @@ -1874,7 +1941,7 @@ void serverparameters_dumpdocu(char *fname) else f->printf("\n// **** %s ****\n\n", siddocchapters[ch] + 1); } const char *commentout = strchr(siddocchaptercommentedout, id.desc[0]) != NULL ? "//" : ""; - switch(id.type) + if(id.fromfile) switch(id.type) { case SID_INT: { @@ -1892,6 +1959,17 @@ void serverparameters_dumpdocu(char *fname) break; } } + else switch(id.type) // stat parameter + { + case SID_INT: + case SID_STR: + { + f->printf("// %s %s", id.name, id.desc + 1); + if(id.logchanges) f->printf(" (changes logged at level %d)", id.type == SID_INT ? id.minval : id.minlen); + f->printf("\n\n"); + break; + } + } } f->printf("\n\n"); delete f; From 00c05ef3534cfd707142561517fbd9e72f34e8e3 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Mon, 28 Mar 2016 20:24:37 +0200 Subject: [PATCH 28/45] add debug access to server parameters Server owners can overwrite values on a server, which then don't get updated from file anymore. With a reset command, all parameters get read from file again. Implemented as "serverextensions". serverextension servpar::get parname print parname and value serverextension servpar::set parname:value set parname to value also disables reading of this parameter from file (otherwise, the new value wouldn't last very long) serverextension servpar::reset set all parameters to be read from file again serverextension servpar::dump prints unsorted list of all parameters, values, limits and descriptions --- source/src/protocol.h | 1 + source/src/server.cpp | 71 ++++++++++++++++++++++++++++++++++++++++ source/src/server.h | 6 ++++ source/src/serverfiles.h | 7 ++-- 4 files changed, 82 insertions(+), 3 deletions(-) diff --git a/source/src/protocol.h b/source/src/protocol.h index f3732c42..a1e7f344 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -235,4 +235,5 @@ enum #define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) #define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__MAPROT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER) +#define FTXT__SERVPARNAME (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_SAFECS) diff --git a/source/src/server.cpp b/source/src/server.cpp index 939dbf44..26589731 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -4069,6 +4069,77 @@ void process(ENetPacket *packet, int sender, int chan) sendservmsg("your message has been logged", sender); } } + else if(!strcmp(ext, "servpar::get")) + { + getstring(text, p, n); + if(valid_client(sender)) + { + filtertext(text, text, FTXT__SERVPARNAME); + servpar *id = servpars->access(text); + string msg; + if(id && id->type == SID_INT) formatstring(msg)("%s = %d", id->name, *id->i); + else if(id && id->type == SID_STR) formatstring(msg)("%s = \"%s\"", id->name, id->s); + else formatstring(msg)("servpar %s not found", text); + sendservmsg(msg, sender); + } + + } + else if(!strcmp(ext, "servpar::set")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + char *b, *k = strtok_r(text, " :", &b), *v = strtok_r(NULL, " :", &b); + if(k && v) + { + filtertext(k, k, FTXT__SERVPARNAME); + servpar *id = servpars->access(k); + string msg; + if(!id) formatstring(msg)("servpar %s not found", k); + else if(id->nextvalue(v)) + { + serverparameters.busy.wait(); // lock parameter access + id->fromfile = false; + id->update(); + serverparameters.busy.post(); + formatstring(msg)("%s set to %s", id->name, v); + } + else formatstring(msg)("%s cannot be set to \"%s\"", id->name, v); + sendservmsg(msg, sender); + } + } + } + else if(!strcmp(ext, "servpar::reset")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + enumerate(*servpars, servpar, id, id.fromfile = id.fromfile_org); + serverparameters.filehash = 0; // make sure, the parameter file is read soon + sendservmsg("reset all server parameters to be read from file", sender); + } + + } +#ifdef _DEBUG + else if(!strcmp(ext, "servpar::dump")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + string msg; + enumerate(*servpars, servpar, id, + { + switch(id.type) + { + case SID_INT: formatstring(msg)("%s = %d, int %d..%d, def %d, %s", id.name, *id.i, id.minval, id.maxval, id.defaultint, id.desc && *id.desc ? id.desc + 1 : ""); break; + case SID_STR: formatstring(msg)("%s = \"%s\", def %s, %s", id.name, id.s, id.defaultstr, id.desc && *id.desc ? id.desc + 1 : ""); break; + default: msg[0] = '\0'; + } + if(*msg) sendservmsg(msg, sender); + }); + } + } +#endif else if(!strcmp(ext, "set::teamsize")) { // intermediate solution to set the teamsize (will be voteable) diff --git a/source/src/server.h b/source/src/server.h index 28f76ea7..7c35012d 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -386,6 +386,12 @@ struct client // server side version of "dynent" type role = CR_DEFAULT; isauthed = haswelcome = false; } + + bool checkvitadate(int n) + { + extern int servclock; + return vita && (vita->vs[n] == 1 || (servclock - vita->vs[n]) < 0); + } }; struct ban diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 65022b7e..2c11deeb 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1690,22 +1690,23 @@ struct servpar #endif bool logchanges; // log value changes (careful!) bool fromfile; // true: value can be set by config file, false: value is set by server + bool fromfile_org; // "fromfile" as initially set (a backup to undo manual changes) servpar() {} // SID_INT servpar(int type, const char *name, int minval, int maxval, const char *list[], int *i, int *shadow_i, int defval, void (*fun)(), bool logchanges, bool fromfile, const char *_desc) - : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), logchanges(logchanges), fromfile(fromfile) + : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), logchanges(logchanges), fromfile(fromfile), fromfile_org(fromfile) { DEBUGCODE(desc = _desc); } // SID_STR servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, int fromfile, const char *_desc) - : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile) + : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile), fromfile_org(fromfile) { DEBUGCODE(desc = _desc;) } void update() { - if(fromfile) switch(type) + if(fromfile_org) switch(type) { case SID_INT: { From df6719439b6b02eb9dc918a14811f2302c7f88e5 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 29 Mar 2016 01:10:50 +0200 Subject: [PATCH 29/45] optionally reverse influence of previously played games modes a negative modepenalty_weight value makes the server more likely to suggest games with the same mode as was recently played default is a positive value to more likely mix it up --- config/serverparameters.cfg | 10 +++++++--- source/src/server.cpp | 7 ++++--- source/src/serverfiles.h | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index f7cd3147..ba437d71 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -42,9 +42,13 @@ gamepenalty_random:1 // integer [45..240], default 60 mappenalty_cutoff:60 -// mappenalty_weight Influence of play history of a map (-100: no influence, 0: normal, 100: high influence) -// integer [-100..100], default 0 -mappenalty_weight:0 +// mappenalty_weight Influence of play history of a map (0: no influence, 100: normal, 200: high influence) +// integer [0..200], default 100 +mappenalty_weight:100 + +// modepenalty_weight Influence of play history of a game mode (-100: stay with played mode, 0: ignore played mode, 100: suggest other modes) +// integer [-100..100], default 50 +modepenalty_weight:50 // use_hx_modeinfo List of maprot keywords to copy from map headers // string [0..159 chars], default "weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold" diff --git a/source/src/server.cpp b/source/src/server.cpp index 26589731..d8807ae0 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -277,7 +277,8 @@ void poll_serverthreads() // called once per mainloop-timeslice SERVPAR(gamepenalty_cutoff, 30, 60, 120, "gNumber of minutes to remember that a map+mode combination has been played"); SERVPAR(gamepenalty_random, 1, 1, 60, "gAmount of random weight to add to each map+mode combination"); SERVPAR(mappenalty_cutoff, 45, 60, 240, "gNumber of minutes to remember that a map has been played"); -SERVPAR(mappenalty_weight, -100, 0, 100, "gInfluence of play history of a map (-100: no influence, 0: normal, 100: high influence)"); +SERVPAR(mappenalty_weight, 0, 100, 200, "gInfluence of play history of a map (0: no influence, 100: normal, 200: high influence)"); +SERVPAR(modepenalty_weight, -100, 50, 100, "gInfluence of play history of a game mode (-100: stay with played mode, 0: ignore played mode, 100: suggest other modes)"); void servermaprot::calcmappenalties() // sum up, how long ago each map+mode was played -> get individual penalties for every map+mode combination { @@ -320,7 +321,7 @@ SERVPARLIST(dumpsuggestions, 0, 1, 0, endis, "ddump maprot suggestions whenever servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of playable games (map+mode) { - calcmodepenalties(); + calcmodepenalties(modepenalty_weight); calcmappenalties(); gamesuggestions.setsize(0); if(numpl >= MAXPLAYERS) numpl = MAXPLAYERS - 1; @@ -341,7 +342,7 @@ servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of } if(s.bestmode >= 0) { - s.weight = -bestpen - ((mappenalty_weight + 101) * s.mappenalty) / 100; + s.weight = -bestpen - ((mappenalty_weight + 1) * s.mappenalty) / 100; gamesuggestions.add(&s); } } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 2c11deeb..0ec35555 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -981,13 +981,14 @@ struct servermaprot : serverconfigfile lastplayedmodepointer %= MODEHISTLEN; } - void calcmodepenalties() // sum up, how many minutes each game mode was played during the last hour (including server-empty times) + void calcmodepenalties(int weight) // sum up, how many minutes each game mode was played during the last hour (including server-empty times) { + weight = (weight * PENALTYUNIT) / 100; loopi(GMODE_NUM) modepenalty[i] = 0; loopi(MODEHISTLEN) { int n = lastplayedmodes[i]; - if(n >= 0 && (GMMASK__MPNOCOOP & (1 << n))) modepenalty[n] += PENALTYUNIT; + if(n >= 0 && (GMMASK__MPNOCOOP & (1 << n))) modepenalty[n] += weight; } } From 7d698c340c879e82e226b7bdf77a07533866d590 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 31 Mar 2016 21:22:44 +0200 Subject: [PATCH 30/45] clean up ip byte mangling for now, censored IPs just show a '0' at the end... --- source/src/clientgame.cpp | 13 ++++--------- source/src/server.cpp | 6 ++---- source/src/tools.h | 9 --------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 816c5d7f..37862593 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -292,11 +292,8 @@ void playerinfo(int *cn, const char *attr) ATTR_INT(skin_rvsf, p->skin(TEAM_RVSF)); ATTR_INT(skin, p->skin(player1->team)); - string addrstr = ""; - uint2ip(p->address, addr); - if(addr[3] != 0 || player1->clientrole==CR_ADMIN) - formatstring(addrstr)("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); // full IP - else formatstring(addrstr)("%d.%d.%d.x", addr[0], addr[1], addr[2]); // censored IP + string addrstr; + iptoa(p->address, addrstr); ATTR_STR(ip, addrstr); conoutf("invalid attribute: %s", attr); @@ -1626,13 +1623,11 @@ void cleanplayervotes(playerent *p) void whois(int *cn) { + string tmp; loopv(players) if(players[i] && players[i]->type == ENT_PLAYER && (*cn == -1 || players[i]->clientnum == *cn)) { playerent *p = players[i]; - uint2ip(p->address, ip); - if(ip[3] != 0 || player1->clientrole==CR_ADMIN) - conoutf("WHOIS client %d:\tname %s , IP %d.%d.%d.%d , %d teamkill%s", p->clientnum, colorname(p), ip[0], ip[1], ip[2], ip[3], p->tks, p->tks == 1 ? "" : "s"); // full IP - else conoutf("WHOIS client %d:\tname %s , IP %d.%d.%d.x , %d teamkill%s", p->clientnum, colorname(p), ip[0], ip[1], ip[2], p->tks, p->tks == 1 ? "" : "s"); // censored IP + conoutf("WHOIS client %d:\tname %s , IP %s , %d teamkill%s", p->clientnum, colorname(p), iptoa(p->address, tmp), p->tks, p->tks == 1 ? "" : "s"); // full IP } } COMMAND(whois, "i"); diff --git a/source/src/server.cpp b/source/src/server.cpp index d8807ae0..29f482e0 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -2774,7 +2774,7 @@ void sendiplist(int receiver, int cn) && (clients[i]->clientnum == cn || cn == -1)) { putint(p, i); - putint(p, isbigendian() ? endianswap(clients[i]->peer->address.host) : clients[i]->peer->address.host); + putint(p, clients[i]->ip); } putint(p, -1); sendpacket(receiver, 1, p.finalize()); @@ -2829,9 +2829,7 @@ void putinitclient(client &c, packetbuf &p) putint(p, c.skin[TEAM_CLA]); putint(p, c.skin[TEAM_RVSF]); putint(p, c.team); - enet_uint32 ip = 0; - if(c.type == ST_TCPIP) ip = c.peer->address.host & 0xFFFFFF; - putint(p, isbigendian() ? endianswap(ip) : ip); + putint(p, c.ip_censored); } void sendinitclient(client &c) diff --git a/source/src/tools.h b/source/src/tools.h index 3b6be9d2..83e6e2bd 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -845,14 +845,6 @@ template inline void endiansame(T *buf, int len) {} template inline void bigswap(T *buf, int len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); } #endif -#define uint2ip(address, ip) uchar ip[4]; \ -if(isbigendian())\ -{ \ - enet_uint32 big = endianswap(address);\ - memcpy(&ip, &big, 4);\ -}\ -else memcpy(&ip, &address, 4);\ - /* workaround for some C platforms that have these two functions as macros - not used anywhere */ #ifdef getchar #undef getchar @@ -943,7 +935,6 @@ extern void zipmanualclose(void *a); extern bool cmpb(void *b, int n, enet_uint32 c); extern bool cmpf(char *fn, enet_uint32 c); extern enet_uint32 adler(unsigned char *data, size_t len); -extern void endianswap(void *, int, int); extern bool isbigendian(); extern void strtoupper(char *t, const char *s = NULL); extern void seedMT(uint seed); From 2b10bf1e6872d668e52ce83ebed85ba13e5ad7d9 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Thu, 31 Mar 2016 23:45:35 +0200 Subject: [PATCH 31/45] misc VS warning fixes --- source/src/serverfiles.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 0ec35555..923ce184 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1701,9 +1701,9 @@ struct servpar { DEBUGCODE(desc = _desc); } // SID_STR - servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, int fromfile, const char *_desc) + servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, bool fromfile, const char *_desc) : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile), fromfile_org(fromfile) - { DEBUGCODE(desc = _desc;) } + { DEBUGCODE(desc = _desc); } void update() { @@ -1727,13 +1727,14 @@ struct servpar { if(fun) { - char tmp[maxlen + 1]; + char *tmp = new char[maxlen + 1]; s[maxlen] = shadow_s[maxlen] = '\0'; strncpy(tmp, s, maxlen + 1); strncpy(s, shadow_s, maxlen + 1); strncpy(shadow_s, tmp, maxlen + 1); // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) ((void (__cdecl *)())fun)(); strncpy(shadow_s, s, maxlen + 1); + delete[] tmp; } else strncpy(s, shadow_s, maxlen + 1); } @@ -2082,7 +2083,7 @@ int parsevitas(char *buf, int filelen) return res; } -bool readvitas(const char *fname) +int readvitas(const char *fname) { serverconfigfile vsf; vsf.init(fname, false); From 7d1edd0fa1def1e98f7c4f309b48fb2661816dcb Mon Sep 17 00:00:00 2001 From: ac-stef Date: Fri, 1 Apr 2016 19:48:21 +0200 Subject: [PATCH 32/45] cleaned up demo filename nonsense If a filename does not contain at least mode, map and timestamp (due to messed up format strings), the default format is used to ensure unique filenames. server side: added server parameters demo_max_number number of demo files to hold in RAM demo_save enable/disable writing of demos to file demo_path demo file path and filename prefix demo_filenameformat format string to assemble demo information to a filename %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) %m : minutes remaining %M : minutes played %s : seconds remaining %S : seconds played %h : IP of server %H : hostname of server (client only) %n : mapName %w : timestamp (formatted by demo_timestampformat) demo_timestampformat timestamp format string (see strftime() for format options) if the string starts with 'U', UTC is used - otherwise local time client side: changed demonameformat and demotimeformat to SVARs demonameformat like demo_filenameformat demotimeformat like demo_timestampformat removed demotimelocal getdemonameformat getdemotimeformat getdemotimelocal DOCREF demonameformat demotimeformat DOCREF demotimelocal getdemonameformat getdemotimeformat getdemotimelocal --- config/servercmdline.txt | 5 -- config/serverparameters.cfg | 23 +++++++ source/src/clients2c.cpp | 35 ++-------- source/src/protocol.cpp | 4 +- source/src/protocol.h | 7 +- source/src/protos.h | 25 ++----- source/src/server.cpp | 132 +++++------------------------------- source/src/serverfiles.h | 5 +- source/src/tools.cpp | 65 +++++++++++++++++- source/src/tools.h | 2 + 10 files changed, 123 insertions(+), 180 deletions(-) diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 6e1b57ec..461fd467 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -91,11 +91,6 @@ // 4 ERROR: log only messages of level ERROR // 5 do not write to the log -// these switches control the naming of demos (see -W) -// --demofilenameformat="%Mmin_%G_%w_%H_%n" // default: "%w_%h_%n_%Mmin_%G" -// --demotimestampformat="%H%M_%Y%m%d" // default: "%Y%m%d_%H%M" -// --demotimelocal=1 // default: 0 - // don't use these switches, unless you really know what you're doing: // -u // uprate diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index ba437d71..8dd9f41c 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -55,6 +55,29 @@ modepenalty_weight:50 use_hx_modeinfo:weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold +// **** Demo recording settings **** + +// demo_filenameformat Demo file format string +// string [0..63 chars], default "%w_%h_%n_%Mmin_%G" +//demo_filenameformat:%w_%h_%n_%Mmin_%G + +// demo_max_number Maximum number of demo files in RAM +// integer [3..16], default 5 +//demo_max_number:5 + +// demo_path Demo path (and filename) prefix +// string [0..63 chars], default "" +//demo_path: + +// demo_save Write demos to file +// integer [0..1], default 1, 0:disabled, 1:enabled +//demo_save:1 + +// demo_timestampformat Demo timestamp format string +// string [0..23 chars], default "%Y%m%d_%H%M" +//demo_timestampformat:%Y%m%d_%H%M + + // **** Debug switches **** // dumpmaprot dump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 3c7781f2..fb1673ae 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1466,34 +1466,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) #endif } -void setDemoFilenameFormat(char *fmt) -{ - extern string demofilenameformat; - if(fmt && fmt[0]!='\0') - { - copystring(demofilenameformat, fmt); - } else copystring(demofilenameformat, DEFDEMOFILEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demonameformat, setDemoFilenameFormat, "s"); -void setDemoTimestampFormat(char *fmt) -{ - extern string demotimestampformat; - if(fmt && fmt[0]!='\0') - { - copystring(demotimestampformat, fmt); - } else copystring(demotimestampformat, DEFDEMOTIMEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demotimeformat, setDemoTimestampFormat, "s"); -void setDemoTimeLocal(int *truth) -{ - extern int demotimelocal; - demotimelocal = *truth == 0 ? 0 : 1; -} -COMMANDN(demotimelocal, setDemoTimeLocal, "i"); -void getdemonameformat() { extern string demofilenameformat; result(demofilenameformat); } COMMAND(getdemonameformat, ""); -void getdemotimeformat() { extern string demotimestampformat; result(demotimestampformat); } COMMAND(getdemotimeformat, ""); -void getdemotimelocal() { extern int demotimelocal; intret(demotimelocal); } COMMAND(getdemotimelocal, ""); - +SVARP(demonameformat, "%w_%h_%n_%Mmin_%G"); +SVARP(demotimestampformat, "%Y%m%d_%H%M"); const char *parseDemoFilename(char *srvfinfo) { @@ -1523,8 +1497,9 @@ const char *parseDemoFilename(char *srvfinfo) } copystring(srvmap, pch); } - extern const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap); - return getDemoFilename(gmode, mplay, mdrop, stamp, srvmap); + static string buf; + enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; + return formatdemofilename(demonameformat, demotimestampformat, srvmap, gmode, stamp / 60, mplay, mdrop, ip, buf); } void receivefile(uchar *data, int len) diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 49e71ffa..9434627e 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -284,7 +284,7 @@ char *filtertext(char *dst, const char *src, int flags, int len) toupp = (flags & FTXT_TOUPPER) != 0, // translates to all-uppercase tolow = (flags & FTXT_TOLOWER) != 0, // translates to all-lowercase filename = (flags & FTXT_FILENAME) != 0, // strict a-z, 0-9 and "-_.()" (also translates "[]" and "{}" to "()"), removes everything between '<' and '>' - allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME + allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME or FTXT_MAPNAME mapname = (flags & FTXT_MAPNAME) != 0, // only allows lowercase chars, digits, '_', '-' and '.'; probably should be used in combination with TOLOWER cropwhite = (flags & FTXT_CROPWHITE) != 0, // removes leading and trailing whitespace pass = false; @@ -348,7 +348,7 @@ char *filtertext(char *dst, const char *src, int flags, int len) if(src[1]) ++src; continue; } - if(mapname && !isalnum(c) && !strchr("_-./\\", c)) continue; + if(mapname && !isalnum(c) && !strchr("_-.", c) && !(allowslash && strchr("/\\", c))) continue; if(isspace(c)) { if(leadingwhite) continue; diff --git a/source/src/protocol.h b/source/src/protocol.h index a1e7f344..752fa27c 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -212,9 +212,9 @@ enum #define FTXT__MAPMSG (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) #define FTXT__MAPINFO_LICENSE (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER | FTXT_MAPNAME) #define FTXT__MAPINFO_COMMENT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK) -#define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_TOLOWER) +#define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER) #define FTXT__MDLATTR (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) -#define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME) +#define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH) #define FTXT__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE) #define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL) @@ -230,10 +230,11 @@ enum #define FTXT__MEDIAFILENAME (FTXT_FILENAME) #define FTXT__MEDIAFILEPATH (FTXT_FILENAME | FTXT_ALLOWSLASH) #define FTXT__ZIPDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) -#define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_TOLOWER) +#define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER) #define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK) #define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK) #define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__MAPROT (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_TOLOWER) #define FTXT__SERVPARNAME (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_SAFECS) +#define FTXT__FORMATSTRING (FTXT_NOWHITE | FTXT_NOCOLOR) diff --git a/source/src/protos.h b/source/src/protos.h index fef3287c..2d261500 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1114,8 +1114,6 @@ struct demoheader char desc[DHDR_DESCCHARS]; char plist[DHDR_PLISTCHARS]; }; -#define DEFDEMOFILEFMT "%w_%h_%n_%Mmin_%G" -#define DEFDEMOTIMEFMT "%Y%m%d_%H%M" extern bool watchingdemo; extern int demoprotocol; @@ -1159,23 +1157,21 @@ struct serverconfigfile struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; vector adminonlymaps; // FIXME: remove this - servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), + servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(-1), maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), - ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), + ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(NULL), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = mapperm[0] = '\0'; - demofilenameformat = DEFDEMOFILEFMT; - demotimestampformat = DEFDEMOTIMEFMT; } bool checkarg(const char *arg) @@ -1189,20 +1185,7 @@ struct servercommandline { // todo: gjlqEJQU case 'Y': ssk = a; break; // private server key case '-': - if(!strncmp(arg, "--demofilenameformat=", 21)) - { - demofilenameformat = arg+21; - } - else if(!strncmp(arg, "--demotimestampformat=", 22)) - { - demotimestampformat = arg+22; - } - else if(!strncmp(arg, "--demotimelocal=", 16)) - { - int ai = atoi(arg+16); - demotimelocal = ai == 0 ? 0 : 1; - } - else if(!strncmp(arg, "--masterport=", 13)) + if(!strncmp(arg, "--masterport=", 13)) { int ai = atoi(arg+13); masterport = ai == 0 ? AC_MASTER_PORT : ai; diff --git a/source/src/server.cpp b/source/src/server.cpp index 29f482e0..4f579423 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -741,6 +741,7 @@ void sendspawn(client *c) // demo stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; bool recordpackets = false; +#define MAXDEMOS 16 int nextplayback = 0; void writedemo(int chan, void *data, int len) @@ -762,119 +763,14 @@ void recordpacket(int chan, ENetPacket *packet) if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); } -#ifdef STANDALONE -const char *currentserver(int i) -{ - static string curSRVinfo; - string r; - r[0] = '\0'; - switch(i) - { - case 1: { copystring(r, scl.ip[0] ? scl.ip : "local"); break; } // IP - case 2: { copystring(r, scl.logident[0] ? scl.logident : "local"); break; } // HOST - case 3: { formatstring(r)("%d", scl.serverport); break; } // PORT - // the following are used by a client, a server will simply return empty strings for them - case 4: - case 5: - case 6: - case 7: - case 8: - { - break; - } - default: - { - formatstring(r)("%s %d", scl.ip[0] ? scl.ip : "local", scl.serverport); - break; - } - } - copystring(curSRVinfo, r); - return curSRVinfo; -} -#endif +SERVPAR(demo_max_number, 3, 5, MAXDEMOS, "DMaximum number of demo files in RAM"); +SERVPARLIST(demo_save, 0, 1, 0, endis, "DWrite demos to file"); +SERVSTR(demo_path, "", 0, 63, FTXT__DEMONAME, "DDemo path (and filename) prefix"); +SERVSTR(demo_filenameformat, "%w_%h_%n_%Mmin_%G", 0, 63, FTXT__FORMATSTRING, "DDemo file format string"); +SERVSTR(demo_timestampformat, "%Y%m%d_%H%M", 0, 23, FTXT__FORMATSTRING, "DDemo timestamp format string"); -// these are actually the values used by the client, the server ones are in "scl". -string demofilenameformat = DEFDEMOFILEFMT; -string demotimestampformat = DEFDEMOTIMEFMT; int demotimelocal = 0; -#ifdef STANDALONE -#define DEMOFORMAT scl.demofilenameformat -#define DEMOTSFORMAT scl.demotimestampformat -#else -#define DEMOFORMAT demofilenameformat -#define DEMOTSFORMAT demotimestampformat -#endif - -const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap) -{ - // we use the following internal mapping of formatchars: - // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) - // %m : minutes remaining %M : minutes played - // %s : seconds remaining %S : seconds played - // %h : IP of server %H : hostname of server - // %n : mapName - // %w : timestamp "when" - static string dmofn; - copystring(dmofn, ""); - - int cc = 0; - int mc = strlen(DEMOFORMAT); - - while(ccsize(); demotmp->seek(0, SEEK_SET); - if(demofiles.length() >= scl.maxdemos) + if(demofiles.length() >= demo_max_number) { delete[] demofiles[0].data; demofiles.remove(0); @@ -914,7 +810,7 @@ void enddemorecord() int iTIME = time(NULL); const char *mTIME = numtime(); const char *sMAPN = behindpath(sg->smapname); - string iMAPN; + string iMAPN, buf; copystring(iMAPN, sMAPN); formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN); @@ -923,9 +819,9 @@ void enddemorecord() demotmp->read(d.data, len); delete demotmp; demotmp = NULL; - if(scl.demopath[0]) + if(demo_save) { - formatstring(msg)("%s%s.dmo", scl.demopath, getDemoFilename(gamemode, mPLAY, mDROP, iTIME, iMAPN)); //d.file); + formatstring(msg)("%s%s.dmo", demo_path, formatdemofilename(demo_filenameformat, demo_timestampformat, iMAPN, gamemode, iTIME / 60, mPLAY, mDROP, servownip, buf)); path(msg); stream *demo = openfile(msg, "wb"); if(demo) @@ -4754,6 +4650,10 @@ void initserver(bool dedicated) sg->smapname[0] = '\0'; servparallfun(); + // init server parameters from commandline + if(scl.demopath) initserverparameter("demo_path", scl.demopath); + if(scl.maxdemos > 0) initserverparameter("demo_max_number", scl.maxdemos); + // start logging initserverparameter("logthreshold_console", scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO)); initserverparameter("logthreshold_syslog", scl.syslogthres); @@ -4796,8 +4696,8 @@ void initserver(bool dedicated) forbiddenlist.init(scl.forbidden); serverinfoinfo.init(scl.infopath, MAXTRANS / 2); serverinfomotd.init(scl.motdpath, MAXSTRLEN - 5); - mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); - if(scl.demopath[0]) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); + mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", demo_max_number); + if(demo_save) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", demo_path); if(scl.voteperm[0]) mlog(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); if(scl.mapperm[0]) mlog(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); mlog(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 923ce184..fe1731b6 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1909,8 +1909,9 @@ void servparallfun() // trigger all functions once, to make sure, unchanged defa #ifdef _DEBUG int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } -const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", "CCommandline switch overrides", "lServer load statistics", "" }; -const char *siddocchaptersorting = "sgvmdC", *siddocchaptercommentedout = "C"; +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", + "CCommandline switch overrides", "DDemo recording settings", "lServer load statistics", "" }; +const char *siddocchaptersorting = "sgDvmdC", *siddocchaptercommentedout = "CD"; void serverparameters_dumpdocu(char *fname) { diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 68ca9bc3..b9ab9fc3 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -2,6 +2,8 @@ #include "cube.h" +extern bool isdedicated; + #ifdef NO_POSIX_R char *strtok_r(char *s, const char *delim, char **b) { @@ -516,6 +518,61 @@ const char *iprtoa(const struct iprange &ipr, char *b) return b; } +char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf) +{ + vector d; + // we use the following internal mapping of formatchars: + // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) + // %m : minutes remaining %M : minutes played + // %s : seconds remaining %S : seconds played + // %h : IP of server %H : hostname of server (client only) + // %n : mapName + // %w : timestamp "when" + bool did_map = false, did_mode = false, did_ts = false; + for(const char *s = demoformat; *s; s++) + { + if(*s == '%') + { + switch(*++s) + { + case 'F': cvecconcat(d, fullmodestr(mode)); did_mode = true; break; + case 'g': cvecprintf(d, "%d", mode); did_mode = true; break; + case 'G': cvecconcat(d, acronymmodestr(mode)); did_mode = true; break; + case 'H': + if(!isdedicated) + { +#ifndef STANDALONE + ENetAddress a = { htonl(ip), 0 }; + cvecconcat(d, !enet_address_get_host(&a, buf, MAXSTRLEN) ? buf : "unknown"); + break; +#endif + } // fallthrough to "IP" on dedicated servers + case 'h': cvecconcat(d, ip ? iptoa(ip, buf) : "local"); break; + case 'm': cvecprintf(d, "%d", secondsremaining / 60); break; + case 'M': cvecprintf(d, "%d", secondsplayed / 60); break; + case 'n': cvecconcat(d, map); did_map = true; break; + case 's': cvecprintf(d, "%d", secondsremaining); break; + case 'S': cvecprintf(d, "%d", secondsplayed); break; + case 'w': + { + time_t t = ((time_t) srvclock) * 60; + bool utc = *timestampformat == 'U'; + cvecconcat(d, timestring(t, !utc, timestampformat + int(utc), buf)); + did_ts = true; + break; + } + default: + if(*s) d.add(*s); + break; + } + } + else d.add(*s); + } + d.add('\0'); + filtertext(buf, d.getbuf(), FTXT__DEMONAME); + return did_map && did_mode && did_ts ? buf : formatdemofilename("%w_%h_%n_%G", "%Y%m%d_%H%M", map, mode, srvclock, secondsplayed, secondsremaining, ip, buf); +} + int cmpiprange(const struct iprange *a, const struct iprange *b) { if(a->lr < b->lr) return -1; @@ -542,6 +599,13 @@ int cvecprintf(vector &v, const char *s, ...) return len; } +int cvecconcat(vector &v, const char *s) +{ + int len = strlen(s); + if(len) v.put(s, len); + return len; +} + const char *hiddenpwd(const char *pwd, int showchars) { static int sc = 3; @@ -563,7 +627,6 @@ int getlistindex(const char *key, const char *list[], bool acceptnumeric, int de } #if !defined(STANDALONE) && defined(_DEBUG) char *opts = conc(list, -1, true); - extern bool isdedicated; if(!isdedicated && *key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); delstring(opts); #endif diff --git a/source/src/tools.h b/source/src/tools.h index 83e6e2bd..376633a8 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -952,9 +952,11 @@ extern const char *atoip(const char *s, enet_uint32 *ip); extern const char *atoipr(const char *s, iprange *ir); extern const char *iptoa(const enet_uint32 ip, char *b); extern const char *iprtoa(const struct iprange &ipr, char *b); +extern char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf); extern int cmpiprange(const struct iprange *a, const struct iprange *b); extern int cmpipmatch(const struct iprange *a, const struct iprange *b); extern int cvecprintf(vector &v, const char *s, ...) PRINTFARGS(2, 3); +extern int cvecconcat(vector &v, const char *s); extern const char *hiddenpwd(const char *pwd, int showchars = 0); extern int getlistindex(const char *key, const char *list[], bool acceptnumeric = true, int deflt = -1); extern void parseupdatelist(hashtable &ht, char *buf, const char *prefix = NULL, const char *suffix = NULL); From 2f80c5bae58fd1c292dd4d561c6776ac345e85ce Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 10 Sep 2016 15:26:32 +0200 Subject: [PATCH 33/45] add channel for less important server messages (so that the client can choose to not display them) --- source/src/clients2c.cpp | 1 + source/src/protocol.cpp | 2 +- source/src/protocol.h | 2 +- source/src/server.cpp | 7 ++++++- source/src/server.h | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index fb1673ae..d15d8d77 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1090,6 +1090,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_SERVMSGVERB: // FIXME case SV_SERVMSG: getstring(text, p); conoutf("%s", text); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 9434627e..afea8e33 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -539,7 +539,7 @@ static const int msgsizes[] = // size inclusive message token, 0 f SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, SV_EDITMODE, 2, SV_EDITXY, 8, SV_EDITARCH, 56, SV_EDITBLOCK, 0, SV_EDITD, 6, SV_EDITE, 6, SV_NEWMAP, 2, SV_SENDMAP, 0, SV_RECVMAP, 1, SV_REMOVEMAP, 0, - SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, + SV_SERVMSG, 0, SV_SERVMSGVERB, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, SV_FLAGACTION, 3, SV_FLAGINFO, 0, SV_FLAGMSG, 0, SV_FLAGCNT, 3, SV_ARENAWIN, 2, SV_SETADMIN, 0, SV_SERVOPINFO, 3, diff --git a/source/src/protocol.h b/source/src/protocol.h index 752fa27c..d0de6afc 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -35,7 +35,7 @@ enum SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, SV_EDITMODE, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP, SV_SENDMAP, SV_RECVMAP, SV_REMOVEMAP, - SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, + SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, SV_FLAGACTION, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, SV_SETADMIN, SV_SERVOPINFO, diff --git a/source/src/server.cpp b/source/src/server.cpp index 4f579423..9585909c 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -724,6 +724,11 @@ void sendservmsg(const char *msg, int cn = -1) sendf(cn, 1, "ris", SV_SERVMSG, msg); } +void sendservmsgverbose(const char *msg, int cn = -1) // channel server spam here... +{ + sendf(cn, 1, "ris", SV_SERVMSGVERB, msg); +} + void sendspawn(client *c) { if(team_isspect(c->team)) return; @@ -2843,7 +2848,7 @@ int checktype(int type, client *cl) SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_AUTHREQ, SV_AUTHCHAL, SV_SPAWNSTATE, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME, SV_DISCSCORES, SV_TIMEUP, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG, - SV_SERVMSG, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, + SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, SV_SERVOPINFO, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST, diff --git a/source/src/server.h b/source/src/server.h index 7c35012d..c9d681d1 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -475,7 +475,7 @@ const char *messagenames[SV_NUM] = "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_GAMEMODE", "SV_EDITMODE", "SV_EDITXY", "SV_EDITARCH", "SV_EDITBLOCK", "SV_EDITD", "SV_EDITE", "SV_NEWMAP", "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP", - "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", + "SV_SERVMSG", "SV_SERVMSGVERB", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", "SV_FLAGACTION", "SV_FLAGINFO", "SV_FLAGMSG", "SV_FLAGCNT", "SV_ARENAWIN", "SV_SETADMIN", "SV_SERVOPINFO", From 7fe7e473179a4a409d07a671926fe093a6df0674 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 13 Sep 2016 12:01:33 +0200 Subject: [PATCH 34/45] improved demo recording The demo data is collected, hashed and compressed fully in memory, mostly in a worker thread. While in memory, each demo is identified by an unique sequence number, which is, for example, used to request a download by the client. If so configured, the demo is written to file by the worker thread. If F10 is pressed later than two minutes into the next game, instead of downloading the last game, the current game is scheduled to be automatically downloaded when it ends. At the end of a game, all clients and the server sign the demo file. --- source/src/clients2c.cpp | 139 +++++------ source/src/protocol.cpp | 2 +- source/src/protocol.h | 3 +- source/src/protos.h | 12 +- source/src/server.cpp | 517 ++++++++++++++++++++++++++++----------- source/src/server.h | 11 +- source/src/worldio.cpp | 8 +- 7 files changed, 456 insertions(+), 236 deletions(-) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index d15d8d77..6d83fd26 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -329,6 +329,8 @@ struct session_s string clan, publiccomment; } session; +int srvgamesalt; + void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { static char text[MAXTRANS]; @@ -573,6 +575,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) int mode = getint(p); int downloadable = getint(p); int revision = getint(p); + srvgamesalt = getint(p); localwrongmap = !changemapserv(text, mode, downloadable, revision); if(m_arena && joining > 1 && !watchingdemo) deathstate(player1); break; @@ -1409,12 +1412,18 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) demo_mlines.reserve(demos); loopi(demos) { - getstring(text, p); - conoutf("%d. %s", i+1, text); + int seq = getint(p); + getstring(text, p); // info + conoutf("%d. %s", seq, text); mline &m = demo_mlines.add(); - formatstring(m.name)("%d. %s", i+1, text); - formatstring(m.cmd)("getdemo %d", i+1); + formatstring(m.name)("%d. %s", seq, text); + formatstring(m.cmd)("getdemo %d", seq); menuitemmanual(downloaddemomenu, m.name, m.cmd); + getstring(text, p); // mapname + getint(p); // gmode + getint(p); // timeplayed + getint(p); // timeremain + getint(p); // timestamp } } break; @@ -1456,6 +1465,33 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_DEMOCHECKSUM: // raw data checksum of demo file + { + uchar dtiger[TIGERHASHSIZE]; + if(p.get(dtiger, TIGERHASHSIZE) == TIGERHASHSIZE) + { + bin2hex(text, dtiger, TIGERHASHSIZE); + defformatstring(msg)("CLIENTDEMOEND<%s %s %d>", msg, iptoa(session.curpeerip, text + 100), session.curpeerport); + int msglen = (int)strlen(msg); + ASSERT(msglen + 64 < MAXSTRLEN); + if(sk) ed25519_sign((uchar*)msg, NULL, (uchar*)msg, msglen, sk); + else memset(msg, 0, 64); + + packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(pr, SV_DEMOSIGNATURE); + putint(pr, session.curpeerport); + putip4(pr, session.curpeerip); + pr.put((uchar*)msg, 64); + sendpackettoserv(1, pr.finalize()); + } + break; + } + case SV_DEMOSIGNATURE: // client acknowledging a demo checksum + getint(p); + getip4(p); + p.get((uchar*)text, 64); + break; + default: neterr("type"); return; @@ -1470,99 +1506,58 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) SVARP(demonameformat, "%w_%h_%n_%Mmin_%G"); SVARP(demotimestampformat, "%Y%m%d_%H%M"); -const char *parseDemoFilename(char *srvfinfo) -{ - int gmode = 0; //-314; - int mplay = 0; - int mdrop = 0; - int stamp = 0; - string srvmap; - if(srvfinfo && srvfinfo[0]) - { - int fip = 0; - char sep[] = ":"; - char *pch, *b; - pch = strtok_r(srvfinfo, sep, &b); - while (pch != NULL && fip < 4) - { - fip++; - switch(fip) - { - case 1: gmode = atoi(pch); break; - case 2: mplay = atoi(pch); break; - case 3: mdrop = atoi(pch); break; - case 4: stamp = atoi(pch); break; - default: break; - } - pch = strtok_r(NULL, sep, &b); - } - copystring(srvmap, pch); - } - static string buf; - enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; - return formatdemofilename(demonameformat, demotimestampformat, srvmap, gmode, stamp / 60, mplay, mdrop, ip, buf); -} - void receivefile(uchar *data, int len) { static char text[MAXTRANS]; ucharbuf p(data, len); int type = getint(p); - data += p.length(); - len -= p.length(); switch(type) { case SV_SENDDEMO: { + /* int sequence = */ getint(p); getstring(text, p); - extern string demosubpath; - defformatstring(demofn)("%s", parseDemoFilename(text)); - defformatstring(fname)("demos/%s%s.dmo", demosubpath, demofn); - copystring(demosubpath, ""); - data += strlen(text); + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); + int gmode = getint(p); + int timeplayed = getint(p); + int timeremain = getint(p); + int timestamp = getint(p); int demosize = getint(p); - if(p.remaining() < demosize) + if(demosize <= MAXDEMOSENDSIZE && demosize <= p.remaining()) { - p.forceoverread(); - break; + extern string demosubpath; + string buf; + enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; + defformatstring(fname)("demos/%s%s.dmo", demosubpath, formatdemofilename(demonameformat, demotimestampformat, text, gmode, timestamp, timeplayed, timeremain, ip, buf)); + copystring(demosubpath, ""); + + path(fname); + stream *demo = openfile(fname, "wb"); + if(demo) + { + conoutf("received demo \"%s\"", fname); + demo->write(&p.buf[p.len], demosize); + delete demo; + } + else conoutf("failed writing to \"%s\"", fname); } - path(fname); - stream *demo = openfile(fname, "wb"); - if(!demo) - { - conoutf("failed writing to \"%s\"", fname); - return; - } - conoutf("received demo \"%s\"", fname); - demo->write(&p.buf[p.len], demosize); - delete demo; break; } case SV_RECVMAP: { getstring(text, p); + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); conoutf("received map \"%s\" from server, reloading..", text); int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); int size = mapsize + cfgsizegz; - if(MAXMAPSENDSIZE < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE) { // sam's suggestion - conoutf("map %s is too large to receive", text); - } else { - if(p.remaining() < size) - { - p.forceoverread(); - break; - } - if(securemapcheck(text)) - { - p.len += size; - break; - } - writemap(path(text), mapsize, &p.buf[p.len]); + if(mapsize <= MAXMAPSENDSIZE && cfgsizegz <= MAXMAPSENDSIZE && size <= MAXMAPSENDSIZE && cfgsize <= MAXCFGFILESIZE && size <= p.remaining() && !securemapcheck(behindpath(text))) + { + writemap(behindpath(text), mapsize, &p.buf[p.len]); p.len += mapsize; - writecfggz(path(text), cfgsize, cfgsizegz, &p.buf[p.len]); + writecfggz(behindpath(text), cfgsize, cfgsizegz, &p.buf[p.len]); p.len += cfgsizegz; } break; diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index afea8e33..12fd77c5 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -551,7 +551,7 @@ static const int msgsizes[] = // size inclusive message token, 0 f SV_SWITCHNAME, 0, SV_SWITCHSKIN, 0, SV_SWITCHTEAM, 0, SV_CLIENT, 0, SV_EXTENSION, 0, - SV_MAPIDENT, 3, SV_HUDEXTRAS, 2, SV_POINTS, 0, + SV_MAPIDENT, 3, SV_HUDEXTRAS, 2, SV_POINTS, 0, SV_DEMOCHECKSUM, 0, SV_DEMOSIGNATURE, 0, -1 }; diff --git a/source/src/protocol.h b/source/src/protocol.h index d0de6afc..bf66834d 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -14,6 +14,7 @@ #define MAXMAPNAMELEN 31 #define MAXMAPSENDSIZE 65536 #define MAXCFGFILESIZE 65536 +#define MAXDEMOSENDSIZE 10240001 #define MAXGZMSGSIZE 65536 #define MAXMEDIADOWNLOADFILESIZE 1024000 // hard cap on filesizes (raw and unzipped) - to limit the effect of zip bombs - no nice error messages: just cap #define MAXMODDOWNLOADSIZE 1024000 // hard cap on the filesize of downloaded mod packages - to keep stuff reasonable @@ -47,7 +48,7 @@ enum SV_SWITCHNAME, SV_SWITCHSKIN, SV_SWITCHTEAM, SV_CLIENT, SV_EXTENSION, - SV_MAPIDENT, SV_HUDEXTRAS, SV_POINTS, + SV_MAPIDENT, SV_HUDEXTRAS, SV_POINTS, SV_DEMOCHECKSUM, SV_DEMOSIGNATURE, SV_NUM }; diff --git a/source/src/protos.h b/source/src/protos.h index 2d261500..ec6ddc52 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -849,10 +849,10 @@ extern void save_world(char *mname, bool skipoptimise = false, bool addcomfort = extern int _ignoreillegalpaths; extern int load_world(char *mname); extern char *getfiledesc(const char *dir, const char *name, const char *ext); -extern void writemap(char *name, int size, uchar *data); -extern void writecfggz(char *name, int size, int sizegz, uchar *data); -extern uchar *readmap(char *name, int *size, int *revision); -extern uchar *readmcfggz(char *name, int *size, int *sizegz); +extern void writemap(const char *name, int size, uchar *data); +extern void writecfggz(const char *name, int size, int sizegz, uchar *data); +extern uchar *readmap(const char *name, int *size, int *revision); +extern uchar *readmcfggz(const char *name, int *size, int *sizegz); extern void rlencodecubes(vector &f, sqr *s, int len, bool preservesolids); extern bool rldecodecubes(ucharbuf &f, sqr *s, int len, int version, bool silent); extern void clearheaderextras(); @@ -1156,7 +1156,7 @@ struct serverconfigfile // server commandline parsing struct servercommandline { - int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; + int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time; const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; @@ -1164,7 +1164,7 @@ struct servercommandline vector adminonlymaps; // FIXME: remove this servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(-1), - maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), + maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(NULL), maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"), diff --git a/source/src/server.cpp b/source/src/server.cpp index 9585909c..85b34cc9 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -28,7 +28,7 @@ struct servergame // current game string smapname, nextmapname; - int smode, nextgamemode; + int smode, nextgamemode, srvgamesalt; int interm; int minremain, gamemillis, gamelimit, nextsendscore; int arenaround, arenaroundstartmillis; @@ -36,6 +36,9 @@ struct servergame vector sents; sflaginfo sflaginfos[2]; + bool recordpackets; + int demosequence; // # of demo to fetch with F10 (usually curent game, until one minute after it finished) + servermap *curmap; char *layout; // NULL: edit mode or singleplayer without properly loaded map uchar *coop_mapdata; @@ -59,6 +62,8 @@ struct servergame lastfillup = 0; sents.setsize(0); loopi(2) sflaginfos[i].actor_cn = -1; + recordpackets = false; + demosequence = 0; curmap = NULL; layout = NULL; coop_mapdata = NULL; @@ -100,7 +105,6 @@ vector clients; vector worldstates; vector savedscores; vector bans; -vector demofiles; long int incoming_size = 0; @@ -128,7 +132,7 @@ void poll_serverthreads() // called once per mainloop-timeslice if(startnewservermapsepoch || readmapsthread_sem->getvalue()) fatal("thread management mishap"); // zero tolerance... // wake readmapsthread - mlog(ACLOG_INFO,"waking readmapsthread"); + mlog(ACLOG_DEBUG,"waking readmapsthread"); startnewservermapsepoch = true; servclock = (int) (time(NULL) / (time_t) 60); readmapsthread_sem->post(); @@ -501,6 +505,12 @@ bool buildworldstate() } } +client *getclientbysequence(int seq) +{ + loopv(clients) if(clients[i]->type != ST_EMPTY && clients[i]->clientsequence == seq) return clients[i]; + return NULL; +} + int countclients(int type, bool exclude = false) { int num = 0; @@ -743,140 +753,294 @@ void sendspawn(client *c) gs.lastspawn = sg->gamemillis; } -// demo -stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; -bool recordpackets = false; +// simultaneous demo recording, fully buffered + +#define DEMORINGBUFSIZE (1<<18) // 256KB #define MAXDEMOS 16 -int nextplayback = 0; -void writedemo(int chan, void *data, int len) -{ - if(!demorecord) return; - int stamp[3] = { sg->gamemillis, chan, len }; - lilswap(stamp, 3); - demorecord->write(stamp, sizeof(stamp)); - demorecord->write(data, len); -} - -void recordpacket(int chan, void *data, int len) -{ - if(recordpackets) writedemo(chan, data, len); -} - -void recordpacket(int chan, ENetPacket *packet) -{ - if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); -} - -SERVPAR(demo_max_number, 3, 5, MAXDEMOS, "DMaximum number of demo files in RAM"); +SERVPAR(demo_max_number, 3, 5, MAXDEMOS - 1, "DMaximum number of demo files in RAM"); SERVPARLIST(demo_save, 0, 1, 0, endis, "DWrite demos to file"); SERVSTR(demo_path, "", 0, 63, FTXT__DEMONAME, "DDemo path (and filename) prefix"); SERVSTR(demo_filenameformat, "%w_%h_%n_%Mmin_%G", 0, 63, FTXT__FORMATSTRING, "DDemo file format string"); SERVSTR(demo_timestampformat, "%Y%m%d_%H%M", 0, 23, FTXT__FORMATSTRING, "DDemo timestamp format string"); -int demotimelocal = 0; +typedef ringbuf demoringbuf; +typedef vector demobuf; +ringbuf demoringbuf_store; +ringbuf demobuf_store; + +struct demo_s +{ + volatile int sequence; + string info, mapname, filename; + vector clients_waiting, clients_served; + demoringbuf *rb; // uncomressed ringbuffer + stream *gz, *dbs; // compression handle + demobuf *db; // temp compressed file + uchar *data; // finished compressed file + int len; // bytes in data + int gmode, timeplayed, timeremain, timestamp; + uchar tiger[TIGERHASHSIZE]; // raw data checksum + void *tigerstate; + + // flags only valid for sequence > 0 + bool finish; // game ended, finish up buffer and copy to 'data' + bool error; // buffering overflow, demo lost -> double DEMORINGBUFSIZE and recompile + bool remove; // cleanup slot + bool done; // demo ready to be downloaded + bool save; // write demo to file + + demo_s() : sequence(0), rb(NULL), gz(NULL), db(NULL), data(NULL), tigerstate(NULL) {} +}; + +demo_s demos[MAXDEMOS]; + +demo_s *initdemoslot() // fetch empty demo slot, clean up surplus slots +{ + static int demosequencecounter = 0, lastdemoslot = 0; + const int demosequencewraparound = max(126, MAXDEMOS * 4); + ASSERT(ismainthread() && MAXDEMOS < 126); + for(;;) + { + int used = 0; + loopi(MAXDEMOS) if(demos[i].sequence) used++; + if(used >= demo_max_number) + { // drop old demos (oldest sequence counters first) + int oldestage = -1, oldestdemo; + loopi(MAXDEMOS) if(demos[i].sequence) + { + int age = (demosequencecounter - demos[i].sequence + demosequencewraparound) % demosequencewraparound; + if(age > oldestage) + { + age = oldestage; + oldestdemo = i; + } + } + if(oldestage >= 0) + { + demo_s &c = demos[oldestdemo]; + mlog(ACLOG_VERBOSE, "dropping demo #%d \"%s\", age %d", c.sequence, c.info, oldestage); + if(!c.done) mlog(ACLOG_ERROR, "clearing unfinished demo slot #%d, probably leaked some memory...", c.sequence); + else DELETEA(c.data); + c.rb = NULL; + c.db = NULL; + c.data = NULL; + c.tigerstate = NULL; + c.gz = c.dbs = NULL; + c.sequence = 0; + } + } + else break; + } + demo_s *d = NULL; + int n; + loopi(MAXDEMOS) if(!demos[(n = (i + lastdemoslot) % MAXDEMOS)].sequence) { d = demos + n; lastdemoslot = n + 1; break; } + ASSERT(d != NULL); + d->clients_waiting.setsize(0); + d->clients_served.setsize(0); + d->rb = demoringbuf_store.length() ? demoringbuf_store.remove() : new demoringbuf; + d->db = demobuf_store.length() ? demobuf_store.remove() : new demobuf; + DELETEA(d->data); + d->rb->clear(); + d->db->setsize(0); + d->len = 0; + d->tigerstate = tigerhash_init(d->tiger); + d->dbs = openvecfile(d->db, false); // no autodelete: we'll keep db allocated + d->gz = opengzfile(NULL, "wb", d->dbs); + d->remove = d->finish = d->error = d->done = false; + for(bool tryagain = true; tryagain; ) + { // get unique sequence number (brute force) + demosequencecounter = (d->sequence = (demosequencecounter + 1)) % demosequencewraparound; + tryagain = false; + loopi(MAXDEMOS) if(demos[i].sequence == d->sequence && demos + i != d) tryagain = true; + } + return d; +} + +sl_semaphore sem_demoworkerthread(1, NULL); // trigger demoworkerthread + +int demoworkerthread(void *logfileprefix) // demo worker thread: compress demo data and write demo files +{ + int len; + while(1) + { + loopi(MAXDEMOS) if(demos[i].sequence) + { + demo_s &s = demos[i]; + while(s.gz && s.rb && (len = s.rb->length())) + { // compress until rb is empty + uchar *rbp = s.rb->peek(&len); + if(len != s.gz->write(rbp, len)) s.error = true; + s.rb->remove(&len); + } + if(s.finish && s.gz && s.rb && !s.rb->length()) + { // close and copy zip file + tigerhash_finish(s.tiger, s.tigerstate); + s.tigerstate = NULL; + string msg; + bin2hex(msg, s.tiger, TIGERHASHSIZE); + defformatstring(text)("SERVDEMOEND<%s>", msg); + ed25519_sign((uchar*)msg, NULL, (uchar*)text, strlen(text), ssk); + int stamp[3] = { -1, -1, TIGERHASHSIZE + 64 }; + lilswap(stamp, 3); + s.gz->write(stamp, sizeof(stamp)); + s.gz->write(s.tiger, TIGERHASHSIZE); + s.gz->write(msg, 64); + DELETEP(s.gz); + s.len = s.dbs->size(); + s.data = new uchar[s.len]; + s.dbs->seek(0, SEEK_SET); + s.dbs->read(s.data, s.len); + if(s.save) + { + stream *demo = openfile(s.filename, "wb"); + if(demo) + { + int wlen = (int) demo->write(s.data, s.len); + delete demo; + tlog(ACLOG_INFO, "demo #%d written to file \"%s\" (%d bytes)", s.sequence, s.filename, wlen); + } + else + { + tlog(ACLOG_INFO, "failed to write demo #%d to file \"%s\"", s.sequence, s.filename); + } + } + else tlog(ACLOG_INFO, "recording demo #%d \"%s\" (%d bytes) finished, not saved to file", s.sequence, s.filename, s.len); + s.done = true; + } + if(s.finish || s.error || s.remove) + { // cleanup demo slot + DELETEP(s.gz); + DELETEP(s.dbs); + if(s.db) + { // keep the demo buffer for the next recording + if(!demobuf_store.full()) demobuf_store.add(s.db); + else delete s.db; + s.db = NULL; + } + if(s.rb) + { // keep the demo ringbuffer for the next recording + if(!demoringbuf_store.full()) demoringbuf_store.add(s.rb); + else delete s.rb; + s.rb = NULL; + } + if(!s.done) s.sequence = 0; + } + } + sem_demoworkerthread.timedwait(500); // wait for mainloop to add some work + } + return 0; +} + +demo_s *demorecord = NULL; + +void writedemo(int chan, void *data, int len) +{ + if(demorecord && !demorecord->sequence) demorecord = NULL; + if(!demorecord || !demorecord->rb) return; + int stamp[3] = { sg->gamemillis, chan, len }; + if(demorecord->rb->maxsize() - demorecord->rb->length() > int(sizeof(stamp)) + len) + { + lilswap(stamp, 3); + demorecord->rb->add((uchar *)stamp, sizeof(stamp)); + tigerhash_add(demorecord->tiger, (uchar *)stamp, sizeof(stamp), demorecord->tigerstate); + demorecord->rb->add((uchar *)data, len); + tigerhash_add(demorecord->tiger, (uchar *)data, len, demorecord->tigerstate); + sem_demoworkerthread.post(); + } + else + { + if(!demorecord->error) mlog(ACLOG_ERROR, "demorecord ringbuffer overflow -> discarding demo #%d", demorecord->sequence); + demorecord->error = true; // ringbuffer-overflow: discard demo + } +} + +void recordpacket(int chan, void *data, int len) +{ + if(sg->recordpackets) writedemo(chan, data, len); +} + +void recordpacket(int chan, ENetPacket *packet) +{ + if(sg->recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); +} + +void demorecord_beginintermission() +{ // send hash to clients to sign and restart demo hash + if(!demorecord) return; + + uchar interm_tiger[TIGERHASHSIZE]; + tigerhash_finish(demorecord->tiger, demorecord->tigerstate); + memcpy(interm_tiger, demorecord->tiger, TIGERHASHSIZE); + demorecord->tigerstate = tigerhash_init(demorecord->tiger); // start again to hash the rest of the demo + + sendf(-1, 1, "rim", SV_DEMOCHECKSUM, TIGERHASHSIZE, interm_tiger); +} void enddemorecord() { if(!demorecord) return; - - delete demorecord; - recordpackets = false; - demorecord = NULL; - - if(!demotmp) return; + sg->recordpackets = false; if(sg->gamemillis < DEMO_MINTIME) { - delete demotmp; - demotmp = NULL; - mlog(ACLOG_INFO, "Demo discarded."); - return; + mlog(ACLOG_INFO, "Demo #%d discarded.", demorecord->sequence); + demorecord->remove = true; } + else + { // finish recording, assemble game data, + int len = demorecord->dbs ? demorecord->dbs->size() : 0; // (still a few bytes missing, sue me) + int mr = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1)/60000; + formatstring(demorecord->info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), sg->smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); + if(mr) concatformatstring(demorecord->info, ", %d mr", mr); + defformatstring(msg)("Demo #%d \"%s\" recorded\nPress F10 to download it from the server..", demorecord->sequence, demorecord->info); + sendservmsgverbose(msg); + mlog(ACLOG_INFO, "Demo \"%s\" recorded.", demorecord->info); - int len = demotmp->size(); - demotmp->seek(0, SEEK_SET); - if(demofiles.length() >= demo_max_number) - { - delete[] demofiles[0].data; - demofiles.remove(0); - } - int mr = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1)/60000; - demofile &d = demofiles.add(); + copystring(demorecord->mapname, behindpath(sg->smapname)); + demorecord->gmode = gamemode; + demorecord->timeplayed = sg->gamemillis >= sg->gamelimit ? sg->gamelimit/1000 : sg->gamemillis/1000; + demorecord->timeremain = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis)/1000; + demorecord->timestamp = int(time(NULL) / 60); + demorecord->save = demo_save != 0; + string buf; + formatstring(demorecord->filename)("%s%s.dmo", demo_path, formatdemofilename(demo_filenameformat, demo_timestampformat, demorecord->mapname, gamemode, demorecord->timestamp, demorecord->timeplayed, demorecord->timeremain, servownip, buf)); + path(demorecord->filename); - formatstring(d.info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), sg->smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); - if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); } - defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); - sendservmsg(msg); - mlog(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); - - int mPLAY = sg->gamemillis >= sg->gamelimit ? sg->gamelimit/1000 : sg->gamemillis/1000; - int mDROP = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis)/1000; - int iTIME = time(NULL); - const char *mTIME = numtime(); - const char *sMAPN = behindpath(sg->smapname); - string iMAPN, buf; - copystring(iMAPN, sMAPN); - formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN); - - d.data = new uchar[len]; - d.len = len; - demotmp->read(d.data, len); - delete demotmp; - demotmp = NULL; - if(demo_save) - { - formatstring(msg)("%s%s.dmo", demo_path, formatdemofilename(demo_filenameformat, demo_timestampformat, iMAPN, gamemode, iTIME / 60, mPLAY, mDROP, servownip, buf)); - path(msg); - stream *demo = openfile(msg, "wb"); - if(demo) - { - int wlen = (int) demo->write(d.data, d.len); - delete demo; - mlog(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen); - } - else - { - mlog(ACLOG_INFO, "failed to write demo to file \"%s\"", msg); - } + sg->demosequence = demorecord->sequence; + demorecord->finish = true; } + demorecord = NULL; + sem_demoworkerthread.post(); } void setupdemorecord() { if(numlocalclients() || !m_mp(gamemode) || gamemode == GMODE_COOPEDIT) return; - defformatstring(demotmppath)("demos/demorecord_%s_%d", scl.ip[0] ? scl.ip : "local", scl.serverport); - demotmp = opentempfile(demotmppath, "w+b"); - if(!demotmp) return; + ASSERT(demorecord == NULL); - stream *f = opengzfile(NULL, "wb", demotmp); - if(!f) - { - delete demotmp; - demotmp = NULL; - return; - } + demorecord = initdemoslot(); + if(!sg->demosequence) sg->demosequence = demorecord->sequence; + formatstring(demorecord->info)("%s %s", modestr(gamemode), sg->smapname); - sendservmsg("recording demo"); // FIXME - mlog(ACLOG_INFO, "Demo recording started."); + sendservmsgverbose("recording demo"); + mlog(ACLOG_INFO, "Demo recording started (#%d).", demorecord->sequence); - demorecord = f; - recordpackets = false; + sg->recordpackets = false; demoheader hdr; + memset(&hdr, 0, sizeof(demoheader)); memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)); hdr.version = DEMO_VERSION; hdr.protocol = SERVER_PROTOCOL_VERSION; lilswap(&hdr.version, 1); lilswap(&hdr.protocol, 1); - memset(hdr.desc, 0, DHDR_DESCCHARS); defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); if(strlen(desc) > DHDR_DESCCHARS) formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); desc[DHDR_DESCCHARS - 1] = '\0'; strcpy(hdr.desc, desc); - memset(hdr.plist, 0, DHDR_PLISTCHARS); const char *bl = ""; loopv(clients) { @@ -885,19 +1049,35 @@ void setupdemorecord() if(strlen(hdr.plist) + strlen(ci->name) < DHDR_PLISTCHARS - 2) { strcat(hdr.plist, bl); strcat(hdr.plist, ci->name); } bl = " "; } - demorecord->write(&hdr, sizeof(demoheader)); + tigerhash_add(demorecord->tiger, (uchar *)&hdr, sizeof(demoheader), demorecord->tigerstate); + demorecord->rb->add((uchar *)&hdr, sizeof(demoheader)); packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); welcomepacket(p, -1); writedemo(1, p.buf, p.len); } +int donedemosort(demo_s **a, demo_s **b) { return (*a)->timestamp - (*b)->timestamp; } + void listdemos(int cn) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, SV_SENDDEMOLIST); - putint(p, demofiles.length()); - loopv(demofiles) sendstring(demofiles[i].info, p); + vector donedemos; + loopi(MAXDEMOS) if(demos[i].sequence && demos[i].done) donedemos.add(demos + i); + putint(p, donedemos.length()); + donedemos.sort(donedemosort); + loopv(donedemos) + { + demo_s &d = *donedemos[i]; + putint(p, d.sequence); + sendstring(d.info, p); + sendstring(d.mapname, p); + putint(p, d.gmode); + putint(p, d.timeplayed); + putint(p, d.timeremain); + putint(p, d.timestamp); + } sendpacket(cn, 1, p.finalize()); } @@ -905,62 +1085,99 @@ static void cleardemos(int n) { if(!n) { - loopv(demofiles) delete[] demofiles[i].data; - demofiles.shrink(0); + loopi(MAXDEMOS) if(demos[i].done) demos[i].remove = true; sendservmsg("cleared all demos"); } - else if(demofiles.inrange(n-1)) + else loopi(MAXDEMOS) if(demos[i].sequence == n && demos[i].done) { - delete[] demofiles[n-1].data; - demofiles.remove(n-1); - defformatstring(msg)("cleared demo %d", n); + demos[i].remove = true; + defformatstring(msg)("cleared demo #%d", n); sendservmsg(msg); } } -bool sending_demo = false; - void senddemo(int cn, int num) { client *cl = cn>=0 ? clients[cn] : NULL; - bool is_admin = (cl && cl->role == CR_ADMIN); - if(scl.demo_interm && (!sg->interm || totalclients > 2) && !is_admin) + if(num < 1) num = sg->demosequence; + demo_s *d = NULL; + loopi(MAXDEMOS) if(demos[i].sequence == num) d = demos + i; + if(d) { - sendservmsg("\f3sorry, but this server only sends demos at intermission.\n wait for the end of this game, please", cn); - return; - } - if(!num) num = demofiles.length(); - if(!demofiles.inrange(num-1)) - { - if(demofiles.empty()) sendservmsg("no demos available", cn); - else + bool alreadywaiting = false, alreadyserved = false; + loopv(d->clients_waiting) if(d->clients_waiting[i] == cl->clientsequence) alreadywaiting = true; + loopv(d->clients_served) if(d->clients_served[i] == cl->clientsequence) alreadyserved = true; + if(alreadyserved) { - defformatstring(msg)("no demo %d available", num); - sendservmsg(msg, cn); + sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum); + } + else if(!alreadywaiting) + { + d->clients_waiting.add(cl->clientsequence); + if(!d->done) + { + defformatstring(msg)("scheduled download of demo #%d \"%s\"\n...download will begin, once the demo is finished recording", num, d->info); + sendservmsg(msg, cn); + } } - return; } - demofile &d = demofiles[num-1]; - loopv(d.clientssent) if(d.clientssent[i].ip == cl->peer->address.host && d.clientssent[i].clientnum == cl->clientnum) + else { - sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum); - return; + defformatstring(msg)("demo #%d not available", num); + sendservmsg(msg, cn); } - clientidentity &ci = d.clientssent.add(); - ci.ip = cl->peer->address.host; - ci.clientnum = cl->clientnum; +} - if (sg->interm) sending_demo = true; - packetbuf p(MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_SENDDEMO); - sendstring(d.file, p); - putint(p, d.len); - p.put(d.data, d.len); - sendpacket(cn, 2, p.finalize()); +void checkdemotransmissions() +{ + static int dpointer = 0, curcl = -1, curclseq = 0, curdemoseq = 0;//, curdemosent = 0; + if(curcl >= 0) + { // demo transmission in progress + client *cl = clients[curcl]; + demo_s *d = NULL; + loopi(MAXDEMOS) if(demos[i].sequence == curdemoseq && demos[i].done) d = demos + i; + if(d && cl->clientsequence == curclseq) + { // demo and client still exist: continue transmission + + // FIXME.... + packetbuf p(MAXTRANS + d->len, ENET_PACKET_FLAG_RELIABLE); + putint(p, SV_SENDDEMO); + putint(p, d->sequence); + sendstring(d->mapname, p); + putint(p, d->gmode); + putint(p, d->timeplayed); + putint(p, d->timeremain); + putint(p, d->timestamp); + putint(p, d->len); + p.put(d->data, d->len); + sendpacket(cl->clientnum, 2, p.finalize()); + + } + curcl = -1; // done + } + else + { + demo_s *d = demos + dpointer; + if(d->sequence && d->done && d->clients_waiting.length()) + { + client *cl = getclientbysequence(d->clients_waiting[0]); + if(cl) + { + d->clients_served.add(d->clients_waiting.remove(0)); + curcl = cl->clientnum; + curclseq = cl->clientsequence; + curdemoseq = d->sequence; + //curdemosent = 0; + } + } + else dpointer = (dpointer + 1) % MAXDEMOS; + } } int demoprotocol; bool watchingdemo = false; +stream *demoplayback = NULL; +int nextplayback = 0; void enddemoplayback() { @@ -2112,6 +2329,7 @@ void resetserver(const char *newname, int newmode, int newtime) sg->smode = newmode; copystring(sg->smapname, newname); + sg->srvgamesalt = (rnd(0x1000000)*((servmillis%4200)+1)) ^ rnd(0x1000000); sg->minremain = newtime > 0 ? newtime : defaultgamelimit(newmode); sg->gamemillis = 0; @@ -2215,7 +2433,7 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) if(notify) { // change map - sendf(-1, 1, "risiii", SV_MAPCHANGE, sg->smapname, sg->smode, sm && sm->isdistributable() ? sm->cgzlen : 0, sm && sm->isdistributable() ? sm->maprevision : 0); + sendf(-1, 1, "risiiii", SV_MAPCHANGE, sg->smapname, sg->smode, sm && sm->isdistributable() ? sm->cgzlen : 0, sm && sm->isdistributable() ? sm->maprevision : 0, sg->srvgamesalt); if(sg->smode > 1 || (sg->smode == 0 && numnonlocalclients() > 0)) sendf(-1, 1, "ri3", SV_TIMEUP, sg->gamemillis, sg->gamelimit); } packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); @@ -2766,6 +2984,7 @@ void welcomepacket(packetbuf &p, int n) putint(p, sg->smode); putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->cgzlen : 0); putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->maprevision : 0); + putint(p, sg->srvgamesalt); if(sg->smode>1 || (sg->smode==0 && numnonlocalclients()>0)) { putint(p, SV_TIMEUP); @@ -2853,7 +3072,7 @@ int checktype(int type, client *cl) SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, - SV_CLIENT, SV_HUDEXTRAS, SV_POINTS }; + SV_CLIENT, SV_HUDEXTRAS, SV_POINTS, SV_DEMOCHECKSUM }; // only allow edit messages in coop-edit mode static int edittypes[] = { SV_EDITENT, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP }; if(cl) @@ -3934,6 +4153,13 @@ void process(ENetPacket *packet, int sender, int chan) senddemo(sender, getint(p)); break; + case SV_DEMOSIGNATURE: // client acknowledging a demo checksum + getint(p); + getip4(p); + p.get((uchar*)text, 64); + QUEUE_MSG; + break; + case SV_EXTENSION: { // AC server extensions @@ -4122,8 +4348,13 @@ void checkintermission() { sg->minremain = (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1) / 60000; sendf(-1, 1, "ri3", SV_TIMEUP, (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? sg->gamelimit : sg->gamemillis, sg->gamelimit); + if(demorecord && sg->gamemillis > 100000) sg->demosequence = demorecord->sequence; // after 100 seconds played, switch F10-default from "last game" to "this game" + } + if(!sg->interm && sg->minremain <= 0) + { // intermission starting here + sg->interm = sg->gamemillis + 10000; + demorecord_beginintermission(); } - if(!sg->interm && sg->minremain <= 0) sg->interm = sg->gamemillis + 10000; sg->forceintermission = false; } @@ -4146,7 +4377,7 @@ void sendworldstate() bool flush = buildworldstate(); lastsend += curtime - (curtime%40); if(flush) enet_host_flush(serverhost); - if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent + if(demorecord) sg->recordpackets = true; // enable after 'old' worldstate is sent } void loggamestatus(const char *reason) @@ -4330,9 +4561,8 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(sg->forceintermission || ((sg->smode > 1 || (gamemode == 0 && nonlocalclients)) && sg->gamemillis-diff > 0 && sg->gamemillis / 60000 != (sg->gamemillis - diff) / 60000)) checkintermission(); if(m_demo && !demoplayback) maprot.restart(); - else if(sg->interm && ( (scl.demo_interm && sending_demo) ? sg->gamemillis > (sg->interm<<1) : sg->gamemillis > sg->interm ) ) + else if(sg->interm && ( (scl.demo_interm) ? sg->gamemillis > (sg->interm<<1) : sg->gamemillis > sg->interm ) ) { - sending_demo = false; loggamestatus("game finished"); if(demorecord) enddemorecord(); sg->interm = sg->nextsendscore = 0; @@ -4346,6 +4576,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo resetserverifempty(); polldeferredprocessing(); + checkdemotransmissions(); if(!isdedicated) return; // below is network only @@ -4748,6 +4979,8 @@ void initserver(bool dedicated) readserverconfigsthread_sem = new sl_semaphore(0, NULL); sl_createthread(readserverconfigsthread, NULL); + sl_createthread(demoworkerthread, NULL); + for(;;) serverslice(5); } } diff --git a/source/src/server.h b/source/src/server.h index c9d681d1..ebddb468 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -430,15 +430,6 @@ struct clientidentity int clientnum; }; -struct demofile -{ - string info; - string file; - uchar *data; - int len; - vector clientssent; -}; - void startgame(const char *newname, int newmode, int newtime = -1, bool notify = true); void disconnect_client(int n, int reason = -1); void sendiplist(int receiver, int cn = -1); @@ -487,7 +478,7 @@ const char *messagenames[SV_NUM] = "SV_SWITCHNAME", "SV_SWITCHSKIN", "SV_SWITCHTEAM", "SV_CLIENT", "SV_EXTENSION", - "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS" + "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE", }; const char *entnames[] = diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 3a92fc26..f81af9fa 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -40,7 +40,7 @@ void voptimize() // reset vdeltas on non-hf cubes // these two are used by getmap/sendmap.. transfers compressed maps directly -void writemap(char *name, int msize, uchar *mdata) +void writemap(const char *name, int msize, uchar *mdata) { setnames(name); backup(cgzname, bakname); @@ -51,7 +51,7 @@ void writemap(char *name, int msize, uchar *mdata) conoutf("wrote map %s as file %s", name, cgzname); } -uchar *readmap(char *name, int *size, int *revision) +uchar *readmap(const char *name, int *size, int *revision) { setnames(name); uchar *data = (uchar *)loadfile(cgzname, size); @@ -61,7 +61,7 @@ uchar *readmap(char *name, int *size, int *revision) return data; } -void writecfggz(char *name, int size, int sizegz, uchar *data) +void writecfggz(const char *name, int size, int sizegz, uchar *data) { if(size < 1 || !sizegz || size > MAXCFGFILESIZE) return; setnames(name); @@ -87,7 +87,7 @@ void writecfggz(char *name, int size, int sizegz, uchar *data) #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) -uchar *readmcfggz(char *name, int *size, int *sizegz) +uchar *readmcfggz(const char *name, int *size, int *sizegz) { setnames(name); uchar *gzbuf = new uchar[GZBUFSIZE]; From 7cddb6c7c6b24541f2613cbc5ec14fd8ab875754 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 13 Sep 2016 14:58:56 +0200 Subject: [PATCH 35/45] add debug logging to demo recording fix server hanging after 5th demo fix incomplete demo slot cleanup fix demo file size reporting (enddemorecord can't do it accurately, because the file is not complete, yet - so, round to megabytes, where the error doesn't show...) include exact demo file size in "listdemos" report --- config/serverparameters.cfg | 8 ++++++-- source/src/clients2c.cpp | 4 ++++ source/src/protocol.h | 1 + source/src/server.cpp | 23 +++++++++++++++++------ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg index 8dd9f41c..0fd8a85e 100644 --- a/config/serverparameters.cfg +++ b/config/serverparameters.cfg @@ -57,13 +57,17 @@ use_hx_modeinfo:weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|te // **** Demo recording settings **** +// demo_debug Excessive logging during demo recording (FIXME) +// integer [0..1], default 1, 0:disabled, 1:enabled +//demo_debug:1 + // demo_filenameformat Demo file format string // string [0..63 chars], default "%w_%h_%n_%Mmin_%G" //demo_filenameformat:%w_%h_%n_%Mmin_%G // demo_max_number Maximum number of demo files in RAM -// integer [3..16], default 5 -//demo_max_number:5 +// integer [5..23], default 7 +//demo_max_number:7 // demo_path Demo path (and filename) prefix // string [0..63 chars], default "" diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 6d83fd26..0fee862a 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1414,12 +1414,16 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { int seq = getint(p); getstring(text, p); // info + filtertext(text, text, FTXT__DEMOINFO, MAXMAPNAMELEN + 64); + int len = getint(p); // len + concatformatstring(text, ", %.2f%s", len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); conoutf("%d. %s", seq, text); mline &m = demo_mlines.add(); formatstring(m.name)("%d. %s", seq, text); formatstring(m.cmd)("getdemo %d", seq); menuitemmanual(downloaddemomenu, m.name, m.cmd); getstring(text, p); // mapname + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); getint(p); // gmode getint(p); // timeplayed getint(p); // timeremain diff --git a/source/src/protocol.h b/source/src/protocol.h index bf66834d..d9d8f2bf 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -216,6 +216,7 @@ enum #define FTXT__MAPNAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER) #define FTXT__MDLATTR (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS) #define FTXT__DEMONAME (FTXT_NOCOLOR | FTXT_MAPNAME | FTXT_ALLOWSLASH) +#define FTXT__DEMOINFO (FTXT_NOCOLOR | FTXT_NOWHITE | FTXT_ALLOWBLANKS) #define FTXT__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR) #define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE) #define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL) diff --git a/source/src/server.cpp b/source/src/server.cpp index 85b34cc9..9f883f4a 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -756,13 +756,14 @@ void sendspawn(client *c) // simultaneous demo recording, fully buffered #define DEMORINGBUFSIZE (1<<18) // 256KB -#define MAXDEMOS 16 +#define MAXDEMOS 24 -SERVPAR(demo_max_number, 3, 5, MAXDEMOS - 1, "DMaximum number of demo files in RAM"); +SERVPAR(demo_max_number, 5, 7, MAXDEMOS - 1, "DMaximum number of demo files in RAM"); SERVPARLIST(demo_save, 0, 1, 0, endis, "DWrite demos to file"); SERVSTR(demo_path, "", 0, 63, FTXT__DEMONAME, "DDemo path (and filename) prefix"); SERVSTR(demo_filenameformat, "%w_%h_%n_%Mmin_%G", 0, 63, FTXT__FORMATSTRING, "DDemo file format string"); SERVSTR(demo_timestampformat, "%Y%m%d_%H%M", 0, 23, FTXT__FORMATSTRING, "DDemo timestamp format string"); +SERVPARLIST(demo_debug, 0, 1, 0, endis, "DExcessive logging during demo recording (FIXME)"); typedef ringbuf demoringbuf; typedef vector demobuf; @@ -812,7 +813,7 @@ demo_s *initdemoslot() // fetch empty demo slot, clean up surplus slots int age = (demosequencecounter - demos[i].sequence + demosequencewraparound) % demosequencewraparound; if(age > oldestage) { - age = oldestage; + oldestage = age; oldestdemo = i; } } @@ -854,6 +855,7 @@ demo_s *initdemoslot() // fetch empty demo slot, clean up surplus slots tryagain = false; loopi(MAXDEMOS) if(demos[i].sequence == d->sequence && demos + i != d) tryagain = true; } + if(demo_debug) mlog(ACLOG_INFO, "initdemoslot(): use slot %d, sequence #%d", n, d->sequence); return d; } @@ -875,6 +877,7 @@ int demoworkerthread(void *logfileprefix) // demo worker thread: compress demo d } if(s.finish && s.gz && s.rb && !s.rb->length()) { // close and copy zip file + if(demo_debug) tlog(ACLOG_INFO, "demoworkerthread(): close slot %d, sequence #%d", i, s.sequence); tigerhash_finish(s.tiger, s.tigerstate); s.tigerstate = NULL; string msg; @@ -910,6 +913,8 @@ int demoworkerthread(void *logfileprefix) // demo worker thread: compress demo d } if(s.finish || s.error || s.remove) { // cleanup demo slot + if(demo_debug) tlog(ACLOG_INFO, "demoworkerthread(): cleanup slot %d, sequence #%d", i, s.sequence); + if(s.error || s.remove) s.done = false; DELETEP(s.gz); DELETEP(s.dbs); if(s.db) @@ -924,6 +929,7 @@ int demoworkerthread(void *logfileprefix) // demo worker thread: compress demo d else delete s.rb; s.rb = NULL; } + s.finish = s.error = s.remove = false; if(!s.done) s.sequence = 0; } } @@ -974,6 +980,7 @@ void demorecord_beginintermission() memcpy(interm_tiger, demorecord->tiger, TIGERHASHSIZE); demorecord->tigerstate = tigerhash_init(demorecord->tiger); // start again to hash the rest of the demo + if(demo_debug) mlog(ACLOG_INFO, "demorecord_beginintermission(): sequence #%d", demorecord->sequence); sendf(-1, 1, "rim", SV_DEMOCHECKSUM, TIGERHASHSIZE, interm_tiger); } @@ -982,6 +989,7 @@ void enddemorecord() if(!demorecord) return; sg->recordpackets = false; + if(demo_debug) mlog(ACLOG_INFO, "enddemorecord(): sequence #%d", demorecord->sequence); if(sg->gamemillis < DEMO_MINTIME) { mlog(ACLOG_INFO, "Demo #%d discarded.", demorecord->sequence); @@ -991,11 +999,12 @@ void enddemorecord() { // finish recording, assemble game data, int len = demorecord->dbs ? demorecord->dbs->size() : 0; // (still a few bytes missing, sue me) int mr = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1)/60000; - formatstring(demorecord->info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), sg->smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); + formatstring(demorecord->info)("%s: %s, %s", asctimestr(), modestr(gamemode), sg->smapname); if(mr) concatformatstring(demorecord->info, ", %d mr", mr); - defformatstring(msg)("Demo #%d \"%s\" recorded\nPress F10 to download it from the server..", demorecord->sequence, demorecord->info); + defformatstring(info)("%s, %.2fMB", demorecord->info, (len + 10000) / (1024*1024.f)); + defformatstring(msg)("Demo #%d \"%s\" recorded\nPress F10 to download it from the server..", demorecord->sequence, info); sendservmsgverbose(msg); - mlog(ACLOG_INFO, "Demo \"%s\" recorded.", demorecord->info); + mlog(ACLOG_INFO, "Demo \"%s\" recorded.", info); copystring(demorecord->mapname, behindpath(sg->smapname)); demorecord->gmode = gamemode; @@ -1055,6 +1064,7 @@ void setupdemorecord() packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); welcomepacket(p, -1); writedemo(1, p.buf, p.len); + if(demo_debug) mlog(ACLOG_INFO, "setupdemorecord(): sequence #%d, %s on %s", demorecord->sequence, modestr(gamemode, false), behindpath(sg->smapname)); } int donedemosort(demo_s **a, demo_s **b) { return (*a)->timestamp - (*b)->timestamp; } @@ -1072,6 +1082,7 @@ void listdemos(int cn) demo_s &d = *donedemos[i]; putint(p, d.sequence); sendstring(d.info, p); + putint(p, d.len); sendstring(d.mapname, p); putint(p, d.gmode); putint(p, d.timeplayed); From 94d0203bc625a3907de37c6c472b114f100a824f Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 14 Sep 2016 21:55:26 +0200 Subject: [PATCH 36/45] cleanup pos packets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove roll * optimise for size * new protocol data type: unsigned integer with known size * highres yaw encoding * double resolution for pitch angles -30°..30° * half resolution for pitch angles -90°..-30° and 30°..90° * proper rounding for pitch and yaw values * same resolution for yaw and pitch for both pos packet types * restore transmission of the velocity vector --- source/src/client.cpp | 71 +++++++++++++--------------------- source/src/clients2c.cpp | 83 +++++++++++++++++----------------------- source/src/entity.h | 10 ++--- source/src/physics.cpp | 2 +- source/src/protocol.cpp | 52 +++++++++++++++++++++++++ source/src/protocol.h | 4 ++ source/src/protos.h | 8 ++++ source/src/server.cpp | 38 +++++++++--------- source/src/server.h | 4 +- source/src/weapon.cpp | 1 - 10 files changed, 154 insertions(+), 119 deletions(-) diff --git a/source/src/client.cpp b/source/src/client.cpp index 64faedcd..1d3b38e2 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -433,67 +433,51 @@ void c2sinfo(playerent *d) // send update to the server if(d->state==CS_ALIVE || d->state==CS_EDITING) { + ASSERT(!(d->crouching && d->onladder)); // onladder is never set while crouching - and we're going to rely on it ;) + ASSERT(!(d->move < -1 || d->move > 1 || d->strafe < -1 || d->strafe > 1)); packetbuf q(100); int cn = d->clientnum, x = (int)(d->o.x*DMF), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes y = (int)(d->o.y*DMF), z = (int)((d->o.z - d->eyeheight)*DMF), - ya = (int)((512 * d->yaw) / 360.0f), - pi = (int)((127 * d->pitch) / 90.0f), - r = (int)(31*d->roll/20), - dxt = (int)(d->vel.x*DVELF), - dyt = (int)(d->vel.y*DVELF), - dzt = (int)(d->vel.z*DVELF), - dx = dxt - d->vel_t.i[0], - dy = dyt - d->vel_t.i[1], - dz = dzt - d->vel_t.i[2], - // pack rest in 1 int: strafe:2, move:2, onfloor:1, onladder: 1 - f = (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | (((int)d->onladder)<<5) | ((d->lifesequence&1)<<6) | (((int)d->crouching)<<7), - g = (dx?1:0) | ((dy?1:0)<<1) | ((dz?1:0)<<2) | ((r?1:0)<<3) | (((int)d->scoping)<<4) | (((int)d->shoot)<<5); - d->vel_t.i[0] = dxt; - d->vel_t.i[1] = dyt; - d->vel_t.i[2] = dzt; - int usefactor = sfactor < 7 ? 7 : sfactor, sizexy = 1 << (usefactor + 4); + zsign = z < 0 ? 1 : 0, + ya = encodeyaw(d->yaw), + pi = encodepitch(d->pitch), + dx = (int)(d->vel.x*DVELF), + dy = (int)(d->vel.y*DVELF), + dz = (int)(d->vel.z*DVELF); + int f = (d->strafe + 4 + d->move * 3 + (d->onladder ? 9 : 0) + (d->crouching ? 18 : 0)) // pack 6 bit into 5 with ternary logic :) + | (((int)d->scoping)<<5) | ((d->lifesequence&1)<<6) | (((int)d->onfloor)<<7) | (((int)d->jumpd)<<8) | (((int)(dx||dy||dz))<<9) | (zsign << 10); // number of used bits: FLAGBITS + int usefactor = sfactor < 7 ? 7 : sfactor; + if(zsign) z = -z; if(cn >= 0 && cn < 32 && usefactor <= 7 + 3 && // map size 7..10 - x >= 0 && x < sizexy && - y >= 0 && y < sizexy && + !((x | y) & ~((1 << (usefactor + 4)) - 1)) && z >= -2047 && z <= 2047 && - ya >= 0 && ya < 512 && - pi >= -128 && pi <= 127 && - r >= -32 && r <= 31 && - dx >= -8 && dx <= 7 && // FIXME + dx >= -8 && dx <= 7 && dy >= -8 && dy <= 7 && dz >= -8 && dz <= 7) { // compact POS packet - bool noroll = !r, novel = !dx && !dy && !dz; bitbuf b(q); putint(q, SV_POSC); b.putbits(5, cn); b.putbits(2, usefactor - 7); b.putbits(usefactor + 4, x); b.putbits(usefactor + 4, y); - b.putbits(9, ya); - b.putbits(8, pi + 128); - b.putbits(1, noroll ? 1 : 0); - if(!noroll) b.putbits(6, r + 32); - b.putbits(1, novel ? 1 : 0); - if(!novel) + b.putbits(YAWBITS, ya); + b.putbits(PITCHBITS, pi); + b.putbits(FLAGBITS, f); + if(f & (1 << 9)) // hasvel { b.putbits(4, dx + 8); b.putbits(4, dy + 8); b.putbits(4, dz + 8); } - b.putbits(8, f); - b.putbits(1, z < 0 ? 1 : 0); - if(z < 0) z = -z; // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary - int s = (b.rembits() - 1 + 8) % 8; + int s = (b.rembits() - 1 + 8) % 8; // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary if(s < 3) s += 8; if(z >= (1 << s)) s = 11; b.putbits(1, s == 11 ? 1 : 0); b.putbits(s, z); - b.putbits(1, d->scoping ? 1 : 0); - b.putbits(1, d->shoot ? 1 : 0); } else { // classic POS packet @@ -502,17 +486,16 @@ void c2sinfo(playerent *d) // send update to the server putuint(q, x); putuint(q, y); putuint(q, z); - putuint(q, (int)d->yaw); - putint(q, (int)d->pitch); - putuint(q, g); - if (r) putint(q, (int)(125*d->roll/20)); - if (dx) putint(q, dx); - if (dy) putint(q, dy); - if (dz) putint(q, dz); - putuint(q, f); + putuintn(q, (ya << (uint64_t)(FLAGBITS + PITCHBITS)) | (pi << FLAGBITS) | f, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + if(f & (1<<9)) + { + putint(q, dx); + putint(q, dy); + putint(q, dz); + } } sendpackettoserv(0, q.finalize()); - d->shoot = false; + d->jumpd = false; } if(sendmapidenttoserver || messages.length() || totalmillis-lastping>250) diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 0fee862a..223a5836 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -116,10 +116,8 @@ void parsepositions(ucharbuf &p) case SV_POS: // position of another client case SV_POSC: { - int cn, f, g; + int cn, f, yaw, pitch, z; vec o, vel; - float yaw, pitch = 0; - bool scoping;//, shoot; if(type == SV_POSC) { bitbuf q(p); @@ -127,77 +125,68 @@ void parsepositions(ucharbuf &p) int usefactor = q.getbits(2) + 7; o.x = q.getbits(usefactor + 4) / DMF; o.y = q.getbits(usefactor + 4) / DMF; - yaw = q.getbits(9) * 360.0f / 512; - pitch = (q.getbits(8) - 128) * 90.0f / 127; - if(!q.getbits(1)) q.getbits(6); - if(!q.getbits(1)) + yaw = q.getbits(YAWBITS); + pitch = q.getbits(PITCHBITS); + f = q.getbits(FLAGBITS); + if(f & (1 << 9)) { vel.x = (q.getbits(4) - 8) / DVELF; vel.y = (q.getbits(4) - 8) / DVELF; vel.z = (q.getbits(4) - 8) / DVELF; } else vel.x = vel.y = vel.z = 0.0f; - f = q.getbits(8); - int negz = q.getbits(1); int full = q.getbits(1); int s = q.rembits(); if(s < 3) s += 8; if(full) s = 11; - int z = q.getbits(s); - if(negz) z = -z; - o.z = z / DMF; - scoping = ( q.getbits(1) ? true : false ); - q.getbits(1);//shoot = ( q.getbits(1) ? true : false ); + z = q.getbits(s); } else { cn = getint(p); - o.x = getuint(p)/DMF; - o.y = getuint(p)/DMF; - o.z = getuint(p)/DMF; - yaw = (float)getuint(p); - pitch = (float)getint(p); - g = getuint(p); - if ((g>>3) & 1) getint(p); - if (g & 1) vel.x = getint(p)/DVELF; else vel.x = 0; - if ((g>>1) & 1) vel.y = getint(p)/DVELF; else vel.y = 0; - if ((g>>2) & 1) vel.z = getint(p)/DVELF; else vel.z = 0; - scoping = ( (g>>4) & 1 ? true : false ); - //shoot = ( (g>>5) & 1 ? true : false ); // we are not using this yet - f = getuint(p); + o.x = getuint(p)/DMF; + o.y = getuint(p)/DMF; + z = getuint(p); + uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + yaw = (ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1); + pitch = (ff >> FLAGBITS) & ((1 << PITCHBITS) - 1); + f = ff & ((1 << FLAGBITS) - 1); + if(f & (1 << 9)) + { + vel.x = getint(p)/DVELF; + vel.y = getint(p)/DVELF; + vel.z = getint(p)/DVELF; + } + else vel.x = vel.y = vel.z = 0.0f; } + if(f & (1 << 10)) z = -z; + o.z = z / DMF; int seqcolor = (f>>6)&1; playerent *d = getclient(cn); if(!d || seqcolor!=(d->lifesequence&1)) continue; vec oldpos(d->o); float oldyaw = d->yaw, oldpitch = d->pitch; - loopi(3) - { - float dr = o.v[i] - d->o.v[i] + ( i == 2 ? d->eyeheight : 0); - if ( !dr ) d->vel.v[i] = 0.0f; - else if ( d->vel.v[i] ) d->vel.v[i] = dr * 0.05f + d->vel.v[i] * 0.95f; - d->vel.v[i] += vel.v[i]; - if ( i==2 && d->onfloor && d->vel.v[i] < 0.0f ) d->vel.v[i] = 0.0f; - } d->o = o; d->o.z += d->eyeheight; - d->yaw = yaw; - d->pitch = pitch; + d->yaw = decodeyaw(yaw); + d->pitch = decodepitch(pitch); if(d->weaponsel->type == GUN_SNIPER) { sniperrifle *sr = (sniperrifle *)d->weaponsel; - sr->scoped = d->scoping = scoping; + sr->scoped = d->scoping = (f & (1 << 5)) ? true : false; } - d->strafe = (f&3)==3 ? -1 : f&3; - f >>= 2; - d->move = (f&3)==3 ? -1 : f&3; - f >>= 2; - d->onfloor = f&1; - f >>= 1; - d->onladder = f&1; - f >>= 2; + d->roll = 0; + d->vel = vel; + int ft = f & 0x1f; + d->strafe = (ft % 3) - 1; + ft /= 3; + d->move = (ft % 3) - 1; + ft /= 3; + d->jumpd = (f & (1 << 8)) ? true : false; // FIXME + d->onfloor = f & (1 << 7) ? true : false; + d->onladder = ft == 1; d->last_pos = totalmillis; - updatecrouch(d, f&1); + updatecrouch(d, ft == 2); updateplayerpos(d); updatelagtime(d); extern int smoothmove, smoothdist; diff --git a/source/src/entity.h b/source/src/entity.h index 0614be06..5061ce4b 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -125,7 +125,7 @@ public: class physent : public worldobject { public: - vec o, vel, vel_t; // origin, velocity + vec o, vel; // origin, velocity vec deltapos, newpos; // movement interpolation float yaw, pitch, roll; // used as vec in one place float pitchvel; @@ -133,7 +133,7 @@ public: int timeinair; // used for fake gravity float radius, eyeheight, maxeyeheight, aboveeye; // bounding box size bool inwater; - bool onfloor, onladder, jumpnext, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping, shoot; + bool onfloor, onladder, jumpnext, jumpd, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping; int lastjump; float lastjumpheight; int lastsplash; @@ -143,7 +143,7 @@ public: int last_pos; physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(270), pitch(0), roll(0), pitchvel(0), - crouching(false), crouchedinair(false), trycrouch(false), cancollide(true), stuck(false), scoping(false), shoot(false), lastjump(0), lastjumpheight(200), lastsplash(0), state(CS_ALIVE), last_pos(0) + crouching(false), crouchedinair(false), trycrouch(false), cancollide(true), stuck(false), scoping(false), lastjump(0), lastjumpheight(200), lastsplash(0), state(CS_ALIVE), last_pos(0) { reset(); } @@ -158,10 +158,10 @@ public: void reset() { - vel.x = vel.y = vel.z = eyeheightvel = vel_t.x = vel_t.y = vel_t.z = 0.0f; + vel.x = vel.y = vel.z = eyeheightvel = 0.0f; move = strafe = 0; timeinair = lastjump = lastsplash = 0; - onfloor = onladder = inwater = jumpnext = crouching = crouchedinair = trycrouch = stuck = false; + onfloor = onladder = inwater = jumpnext = jumpd = crouching = crouchedinair = trycrouch = stuck = false; last_pos = 0; } diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 9c1bb2db..d30e0540 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -517,6 +517,7 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime) { if(pl->jumpnext) { + pl->jumpd = true; pl->jumpnext = false; bool doublejump = pl->lastjump && lastmillis - pl->lastjump < 250 && pl->strafe != 0 && pl->o.z - pl->eyeheight - pl->lastjumpheight > 0.2f; pl->lastjumpheight = pl->o.z - pl->eyeheight; @@ -898,7 +899,6 @@ COMMAND(crouch, "d"); void fixcamerarange(physent *cam) { - const float MAXPITCH = 90.0f; if(cam->pitch>MAXPITCH) cam->pitch = MAXPITCH; if(cam->pitch<-MAXPITCH) cam->pitch = -MAXPITCH; while(cam->yaw<0.0f) cam->yaw += 360.0f; diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 12fd77c5..c090c599 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -168,6 +168,24 @@ enet_uint32 getip4(ucharbuf &p) return ip; } +template +static inline void putuintn_(T &p, uint64_t val, int n) +{ + DEBUGVAR(val); + loopi(n) p.put(val >> (8 * i)); +} +void putuintn(ucharbuf &p, uint64_t val, int n) { putuintn_(p, val, n); } +void putuintn(packetbuf &p, uint64_t val, int n) { putuintn_(p, val, n); } +void putuintn(vector &p, uint64_t val, int n) { putuintn_(p, val, n); } + +uint64_t getuintn(ucharbuf &p, int n) +{ + uint64_t val = 0; + loopi(n) val |= p.get() << (8 * i); + DEBUGVAR(val); + return val; +} + #define GZMSGBUFSIZE ((MAXGZMSGSIZE * 11) / 10) static uchar *gzbuf = new uchar[GZMSGBUFSIZE]; // not thread-safe, but nesting is no problem @@ -526,6 +544,40 @@ char *gmode_enum(int gm, char *buf) // convert mode bitmask to string with sorte return buf; } +int encodepitch(float p) // pitch value quantisation: use double resolution for -30°..30° and half resolution for -90°..-30° and 30°..90° +{ + const int thres = (1 << 24) / 3; + int r = (int)(p * (1 << 24)) / MAXPITCH; + if(r > thres) r = r / 4 + (1 << 22); + else if(r < -thres) r = r / 4 - (1 << 22); + r += (1 << 23) + (1 << (23 - PITCHBITS)); + if(r < 0) r = 0; + else if(r >= (1 << 24)) r = (1 << 24) - 1; + return r >> (24 - PITCHBITS); +} + +float decodepitch(int r) +{ + const int thres = (1 << 22) + (1 << 22) / 3; + r = (r << (24 - PITCHBITS)) - (1 << 23); + if(r > thres) r = r * 4 - thres * 3; + else if(r < -thres) r = r * 4 + thres * 3; + float p = float(r) * MAXPITCH / (1 << 24); + return clamp(p, -MAXPITCH, MAXPITCH); +} + +int encodeyaw(float y) // yaw value quantisation: simple rounded integer +{ + int r = (int)floorf(y * (1 << YAWBITS) / 360.0f + 0.5f); + return r & ((1 << YAWBITS) - 1); // modulo 360 degrees +} + +float decodeyaw(int r) +{ + r &= (1 << YAWBITS) - 1; + return float(r) / (1 << YAWBITS) * 360.0f; +} + static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, diff --git a/source/src/protocol.h b/source/src/protocol.h index d9d8f2bf..ad82efdc 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -76,6 +76,10 @@ enum { EDITXY_HEIGHT = 0, EDITXY_TEX, EDITXY_TYPE, EDITXY_VDELTA, EDITXY_EQUALIS #define DMF 16.0f #define DNF 100.0f #define DVELF 4.0f +#define MAXPITCH 90.0f +#define PITCHBITS 8 +#define YAWBITS 10 +#define FLAGBITS 11 enum { DISC_PROTOCOL = 0, DISC_EOP, DISC_CN, DISC_TAGT, DISC_DUP, DISC_OVERFLOW, DISC_RANDOM, DISC_VOODOO, DISC_SYNC, DISC_AUTH, // div. protocol errors DISC_REFUSED = 16, DISC_BANNED, DISC_BLACKLISTED, DISC_WRONGPW, DISC_MAXCLIENTS, DISC_PRIVATE, DISC_OVERLOAD, // connection refused diff --git a/source/src/protos.h b/source/src/protos.h index ec6ddc52..fa0a57ac 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -955,6 +955,10 @@ extern int defaultgamelimit(int gamemode); extern int gmode_possible(bool hasffaspawns, bool hasteamspawns, bool hasflags); extern int gmode_parse(const char *list); extern char *gmode_enum(int gm, char *buf); +extern int encodepitch(float p); +extern float decodepitch(int r); +extern int encodeyaw(float y); +extern float decodeyaw(int r); // crypto #define TIGERHASHSIZE 24 @@ -1070,6 +1074,10 @@ extern void putip4(ucharbuf &p, enet_uint32 ip); extern void putip4(packetbuf &p, enet_uint32 ip); extern void putip4(vector &p, enet_uint32 ip); extern enet_uint32 getip4(ucharbuf &p); +extern void putuintn(ucharbuf &p, uint64_t val, int n); +extern void putuintn(packetbuf &p, uint64_t val, int n); +extern void putuintn(vector &p, uint64_t val, int n); +extern uint64_t getuintn(ucharbuf &p, int n); extern void putgzbuf(vector &d, vector &s); // zips a vector into a stream, stored in another vector extern ucharbuf *getgzbuf(ucharbuf &p); // fetch a gzipped buffer; needs to be freed properly later extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() diff --git a/source/src/server.cpp b/source/src/server.cpp index 9f883f4a..636039eb 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -3825,12 +3825,17 @@ void process(ENetPacket *packet, int sender, int chan) #endif return; } - loopi(3) cl->state.o[i] = getuint(p)/DMF; - cl->y = getuint(p); - cl->p = getint(p); - cl->g = getuint(p); - loopi(4) if ( (cl->g >> i) & 1 ) getint(p); - cl->f = getuint(p); + cl->state.o.x = getuint(p)/DMF; + cl->state.o.y = getuint(p)/DMF; + int z = getuint(p); + uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + cl->yaw = (int)decodeyaw((ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1)); + cl->pitch = (int)decodepitch((ff >> FLAGBITS) & ((1 << PITCHBITS) - 1)); + cl->f = ff & ((1 << FLAGBITS) - 1); + if(cl->f & (1 << 10)) z = -z; + cl->state.o.z = z/DMF; + if(cl->f & (1 << 9)) loopi(3) getint(p); + if(!cl->isonrightmap) break; if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING)) { @@ -3856,28 +3861,23 @@ void process(ENetPacket *packet, int sender, int chan) int usefactor = q.getbits(2) + 7; int xt = q.getbits(usefactor + 4); int yt = q.getbits(usefactor + 4); - cl->y = (q.getbits(9)*360)/512; - cl->p = ((q.getbits(8)-128)*90)/127; - if(!q.getbits(1)) q.getbits(6); - if(!q.getbits(1)) q.getbits(4 + 4 + 4); - cl->f = q.getbits(8); - int negz = q.getbits(1); + cl->yaw = (int)decodeyaw(q.getbits(YAWBITS)); + cl->pitch = (int)decodepitch(q.getbits(PITCHBITS)); + cl->f = q.getbits(FLAGBITS); + if(cl->f & (1 << 9)) q.getbits(4 + 4 + 4); int zfull = q.getbits(1); int s = q.rembits(); if(s < 3) s += 8; if(zfull) s = 11; int zt = q.getbits(s); - if(negz) zt = -zt; - int g1 = q.getbits(1); // scoping - int g2 = q.getbits(1); // shooting - cl->g = (g1<<4) | (g2<<5); + if(cl->f & (1 << 10)) zt = -zt; if(!sg->curmap) break; // no compact POS packets without loaded servermap if(!cl->isonrightmap || p.remaining() || p.overread()) { p.flags = 0; break; } if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (sg->curmap->sfactor < 7 ? 7 : sg->curmap->sfactor)) break; - cl->state.o[0] = xt / DMF; - cl->state.o[1] = yt / DMF; - cl->state.o[2] = zt / DMF; + cl->state.o.x = xt / DMF; + cl->state.o.y = yt / DMF; + cl->state.o.z = zt / DMF; if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING)) { cl->position.setsize(0); diff --git a/source/src/server.h b/source/src/server.h index ebddb468..65e3c946 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -298,7 +298,7 @@ struct client // server side version of "dynent" type vec spawnp; int nvotes; int input, inputmillis; - int ffire, wn, f, g, t, y, p; + int ffire, wn, f, t, yaw, pitch; int yb, pb, oy, op, lda, ldda, fam; int nt[10], np, lp, ls, lsm, ld, nd, nlt, lem, led; vec cp[10], dp[10], d0, lv, lt, le; @@ -339,7 +339,7 @@ struct client // server side version of "dynent" type spawnp = vec(-1e10f, -1e10f, -1e10f); lmillis = ldt = spj = 0; ffire = 0; - f = g = y = p = t = 0; + f = yaw = pitch = t = 0; yb = pb = oy = op = lda = ldda = fam = 0; np = lp = ls = lsm = ld = nd = nlt = lem = led = 0; d0 = lv = lt = le = vec(0,0,0); diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index f65bace0..ae14dbcc 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -934,7 +934,6 @@ int weapon::flashtime() const { return max((int)info.attackdelay, 120)/4; } void weapon::sendshoot(vec &from, vec &to, int millis) { if(owner!=player1) return; - owner->shoot = true; addmsg(SV_SHOOT, "ri2i3iv", millis, owner->weaponsel->type, (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); From 0b44cf1c9c54c752aca86300546e3b1e49056282 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 14 Sep 2016 21:56:27 +0200 Subject: [PATCH 37/45] reduce compact pos packet size by two more bit --- source/src/client.cpp | 3 +-- source/src/clients2c.cpp | 7 +++++-- source/src/protocol.cpp | 2 +- source/src/protocol.h | 2 +- source/src/server.cpp | 7 +++++-- source/src/server.h | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/source/src/client.cpp b/source/src/client.cpp index 1d3b38e2..8cc891fd 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -459,9 +459,8 @@ void c2sinfo(playerent *d) // send update to the server dz >= -8 && dz <= 7) { // compact POS packet bitbuf b(q); - putint(q, SV_POSC); + putint(q, SV_POSC + usefactor - 7); b.putbits(5, cn); - b.putbits(2, usefactor - 7); b.putbits(usefactor + 4, x); b.putbits(usefactor + 4, y); b.putbits(YAWBITS, ya); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 223a5836..de4c7ffd 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -115,14 +115,17 @@ void parsepositions(ucharbuf &p) { case SV_POS: // position of another client case SV_POSC: + case SV_POSC2: + case SV_POSC3: + case SV_POSC4: { int cn, f, yaw, pitch, z; vec o, vel; - if(type == SV_POSC) + if(type != SV_POS) { bitbuf q(p); cn = q.getbits(5); - int usefactor = q.getbits(2) + 7; + int usefactor = type - SV_POSC + 7; o.x = q.getbits(usefactor + 4) / DMF; o.y = q.getbits(usefactor + 4) / DMF; yaw = q.getbits(YAWBITS); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index c090c599..1f31f874 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -580,7 +580,7 @@ float decodeyaw(int r) static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { - SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, + SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSC2, 0, SV_POSC3, 0, SV_POSC4, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_AUTHT, 0, SV_AUTHREQ, 0, SV_AUTHTRY, 0, SV_AUTHANS, 0, SV_AUTHCHAL, 0, diff --git a/source/src/protocol.h b/source/src/protocol.h index ad82efdc..a02c9a7a 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -26,7 +26,7 @@ extern bool modprotocol; // network messages codes, c2s, c2c, s2c enum { - SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, + SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSC2, SV_POSC3, SV_POSC4, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_AUTHT, SV_AUTHREQ, SV_AUTHTRY, SV_AUTHANS, SV_AUTHCHAL, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, diff --git a/source/src/server.cpp b/source/src/server.cpp index 636039eb..de281d59 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -3377,7 +3377,7 @@ void process(ENetPacket *packet, int sender, int chan) type = checktype(getint(p), cl); #ifdef _DEBUG - if(type!=SV_POS && type!=SV_POSC && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) + if(type!=SV_POS && type!=SV_POSC && type!=SV_POSC2 && type!=SV_POSC3 && type!=SV_POSC4 && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) { DEBUGVAR(cl->name); if(type >= 0) { DEBUGVAR(messagenames[type]); } @@ -3847,6 +3847,9 @@ void process(ENetPacket *packet, int sender, int chan) } case SV_POSC: + case SV_POSC2: + case SV_POSC3: + case SV_POSC4: { bitbuf q(p); int cn = q.getbits(5); @@ -3858,7 +3861,7 @@ void process(ENetPacket *packet, int sender, int chan) #endif return; } - int usefactor = q.getbits(2) + 7; + int usefactor = type - SV_POSC + 7; int xt = q.getbits(usefactor + 4); int yt = q.getbits(usefactor + 4); cl->yaw = (int)decodeyaw(q.getbits(YAWBITS)); diff --git a/source/src/server.h b/source/src/server.h index 65e3c946..91f5b858 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -455,7 +455,7 @@ const char *modeinfologfilename = NULL; const char *messagenames[SV_NUM] = { - "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", + "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSC2", "SV_POSC3", "SV_POSC4", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL", From 9231b9f0aa796f47ec58116767b35a7da7cd8d06 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 14 Sep 2016 22:25:42 +0200 Subject: [PATCH 38/45] actually zoom the scope while spectating to show off the new highres pos angles :) --- source/src/rendergl.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 9379ce3d..975b534a 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -477,7 +477,9 @@ void fovchanged() float dynfov() { - if(player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped) return (float)scopefov; + bool isscoped = player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped; + if(camera1 != player1 && camera1->type < ENT_CAMERA) isscoped = ((playerent *)camera1)->scoping; + if(isscoped) return (float)scopefov; else if(player1->isspectating()) return (float)spectfov; else return (float)fov; } From 3549310e42052459b70fee15cbb580e41525466a Mon Sep 17 00:00:00 2001 From: ac-stef Date: Wed, 14 Sep 2016 22:50:07 +0200 Subject: [PATCH 39/45] better quantisation of position and velocity vectors --- source/src/client.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/src/client.cpp b/source/src/client.cpp index 8cc891fd..39bf0b65 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -437,15 +437,15 @@ void c2sinfo(playerent *d) // send update to the server ASSERT(!(d->move < -1 || d->move > 1 || d->strafe < -1 || d->strafe > 1)); packetbuf q(100); int cn = d->clientnum, - x = (int)(d->o.x*DMF), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes - y = (int)(d->o.y*DMF), - z = (int)((d->o.z - d->eyeheight)*DMF), + x = (int)(d->o.x*DMF + 0.5f), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes + y = (int)(d->o.y*DMF + 0.5f), + z = (int)floorf((d->o.z - d->eyeheight)*DMF + 0.5f), zsign = z < 0 ? 1 : 0, ya = encodeyaw(d->yaw), pi = encodepitch(d->pitch), - dx = (int)(d->vel.x*DVELF), - dy = (int)(d->vel.y*DVELF), - dz = (int)(d->vel.z*DVELF); + dx = (int)floorf(d->vel.x*DVELF + 0.5f), + dy = (int)floorf(d->vel.y*DVELF + 0.5f), + dz = (int)floorf(d->vel.z*DVELF + 0.5f); int f = (d->strafe + 4 + d->move * 3 + (d->onladder ? 9 : 0) + (d->crouching ? 18 : 0)) // pack 6 bit into 5 with ternary logic :) | (((int)d->scoping)<<5) | ((d->lifesequence&1)<<6) | (((int)d->onfloor)<<7) | (((int)d->jumpd)<<8) | (((int)(dx||dy||dz))<<9) | (zsign << 10); // number of used bits: FLAGBITS int usefactor = sfactor < 7 ? 7 : sfactor; From 2fa895ae908df531a4b305ed76439ecfe46dad2c Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 29 Apr 2017 20:29:48 +0200 Subject: [PATCH 40/45] use fov and roll values of remote player during spectating spectfovremote 0|1 '1' uses fov and scopefov of the spectated player, '0' uses spectfov and the local scopefov, default is '0' maxrollremote sets the maximum roll to be shown during spectating DOCREF spectfovremote --- source/src/client.cpp | 4 ++++ source/src/clients2c.cpp | 7 +++++-- source/src/entity.h | 3 ++- source/src/rendergl.cpp | 8 ++++++-- source/src/server.cpp | 8 ++++++++ source/src/server.h | 2 ++ 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/source/src/client.cpp b/source/src/client.cpp index 39bf0b65..34080850 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -564,6 +564,10 @@ void sendintro() connectrole = CR_DEFAULT; putint(p, player1->nextprimweap->type); loopi(2) putint(p, player1->skin(i)); + putint(p, player1->maxroll); + putint(p, player1->maxrolleffect); + putint(p, player1->ffov); + putint(p, player1->scopefov); sendpackettoserv(1, p.finalize()); } diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index de4c7ffd..f12a02f1 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -621,6 +621,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) getstring(text, p); loopi(2) getint(p); getint(p); + loopi(4) getint(p); if(!demo || !watchingdemo || demoprotocol > 1132) getint(p); break; } @@ -641,8 +642,10 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) loopi(2) d->setskin(i, getint(p)); d->team = getint(p); -// d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX); FIXME: uncomment on protocol bump + etc. -// d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); FIXME: uncomment on protocol bump + d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX); + d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); + d->ffov = (float)clamp(getint(p), 75, 120); + d->scopefov = (float)clamp(getint(p), 5, 60); if(!demo || !watchingdemo || demoprotocol > 1132) d->address = getint(p); // partial IP address diff --git a/source/src/entity.h b/source/src/entity.h index 5061ce4b..9f3a809d 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -433,6 +433,7 @@ public: int spectatemode, followplayercn; int eardamagemillis; float maxroll, maxrolleffect, movroll, effroll; // roll added by movement and damage + int ffov, scopefov; bool allowmove() { return state!=CS_DEAD || spectatemode==SM_FLY; } weapon *weapons[NUMGUNS]; @@ -451,7 +452,7 @@ public: bool nocorpse; playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), address(0), lifesequence(0), frags(0), flagscore(0), deaths(0), points(0), tks(0), lastpain(0), lastvoicecom(0), lastdeath(0), clientrole(CR_DEFAULT), - team(TEAM_SPECT), spectatemode(SM_NONE), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), + team(TEAM_SPECT), spectatemode(SM_NONE), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), ffov(0), scopefov(0), prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL), smoothmillis(-1), head(-1, -1, -1), ignored(false), muted(false), nocorpse(false) diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 975b534a..8120e1ec 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -469,18 +469,22 @@ void fovchanged(); FVARFP(fov, 75, 90, 120, fovchanged()); VARFP(scopefov, 5, 50, 60, fovchanged()); VARP(spectfov, 5, 110, 120); +VARP(spectfovremote, 0, 0, 1); // use spectfov or remote player's fov when spectating void fovchanged() { extern float autoscopesensscale; autoscopesensscale = tan(((float)scopefov)*0.5f*RAD)/tan(fov*0.5f*RAD); + player1->ffov = (int)fov; + player1->scopefov = scopefov; } float dynfov() { bool isscoped = player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped; + bool useremote = spectfovremote && camera1 != player1 && camera1->type == ENT_PLAYER && ((playerent *)camera1)->ffov && ((playerent *)camera1)->scopefov; if(camera1 != player1 && camera1->type < ENT_CAMERA) isscoped = ((playerent *)camera1)->scoping; - if(isscoped) return (float)scopefov; - else if(player1->isspectating()) return (float)spectfov; + if(isscoped) return (float) (useremote ? ((playerent *)camera1)->scopefov : scopefov); + else if(player1->isspectating()) return (float)(useremote ? ((playerent *)camera1)->ffov : spectfov); else return (float)fov; } diff --git a/source/src/server.cpp b/source/src/server.cpp index de281d59..4928c6dd 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -2959,6 +2959,10 @@ void putinitclient(client &c, packetbuf &p) putint(p, c.skin[TEAM_CLA]); putint(p, c.skin[TEAM_RVSF]); putint(p, c.team); + putint(p, c.maxroll); + putint(p, c.maxrolleffect); + putint(p, c.ffov); + putint(p, c.scopefov); putint(p, c.ip_censored); } @@ -3260,6 +3264,10 @@ void process(ENetPacket *packet, int sender, int chan) int wantrole = getint(p), np = getint(p); cl->state.nextprimary = np > 0 && np < NUMGUNS ? np : GUN_ASSAULT; loopi(2) cl->skin[i] = getint(p); + cl->maxroll = getint(p); + cl->maxrolleffect = getint(p); + cl->ffov = getint(p); + cl->scopefov = getint(p); int bantype = getbantype(sender); bool banned = bantype > BAN_NONE; bool srvfull = numnonlocalclients() > scl.maxclients; diff --git a/source/src/server.h b/source/src/server.h index 91f5b858..3509e6f5 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -259,6 +259,7 @@ struct client // server side version of "dynent" type string servinforesponse; string name; int team; + int maxroll, maxrolleffect, ffov, scopefov; char lang[3]; int ping; int skin[2]; @@ -359,6 +360,7 @@ struct client // server side version of "dynent" type vita = NULL; bottomRTT = ping = 9999; team = TEAM_SPECT; + maxroll = maxrolleffect = ffov = scopefov = 0; state.state = CS_SPECTATE; loopi(2) skin[i] = 0; position.setsize(0); From 69d8304315459a4276038a04bf1f04508e671193 Mon Sep 17 00:00:00 2001 From: aurhat Date: Sun, 11 Jun 2017 01:39:15 +0200 Subject: [PATCH 41/45] Fix akimbo damage --- source/src/server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/src/server.h b/source/src/server.h index 3509e6f5..c9f34d63 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -576,7 +576,7 @@ guninfo guns[NUMGUNS] = { "assault", "MTP-57 AR", S_ASSAULT, S_RASSAULT, 2000, 120, 22, 0, 0, 0, 18, 30, 20, 0, 2, 3, 25, 50, 115, 1, true }, { "cpistol", "nop", S_PISTOL, S_RPISTOL, 1400, 120, 19, 0, 0, 0, 35, 10, 15, 6, 5, 6, 35, 50, 125, 1, false }, // temporary { "grenade", "Grenades", S_NULL, S_NULL, 1000, 650, 200, 0, 20, 6, 1, 1, 1, 3, 1, 0, 0, 0, 0, 3, false }, - { "pistol", "Akimbo", S_PISTOL, S_RAKIMBO, 1400, 80, 19, 0, 0, 0, 50, 10, 20, 6, 5, 4, 15, 25, 115, 1, true }, + { "pistol", "Akimbo", S_PISTOL, S_RAKIMBO, 1400, 80, 18, 0, 0, 0, 50, 10, 20, 6, 5, 4, 15, 25, 115, 1, true }, }; const char *gunnames[NUMGUNS + 1]; From 3728773ec0ee7a465ba7acfd8e4a335687f01a51 Mon Sep 17 00:00:00 2001 From: aurhat Date: Sun, 11 Jun 2017 13:55:07 +0200 Subject: [PATCH 42/45] Remove akimboautoswitch The problem is, that some players (especially SMG users) got on purpose akimbo with intention to not use it, only in order to others don't get it. Such behaviour is directed "piggishly" against other players and impoverish the game/gameplay. Now, if people don't want to use some akimbo, they should just to not get it, for example jump over it. DOCREF akimboautoswitch --- source/src/weapon.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index ae14dbcc..d24f74de 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -5,7 +5,6 @@ #include "hudgun.h" VARP(autoreload, 0, 1, 1); -VARP(akimboautoswitch, 0, 1, 1); VARP(akimboendaction, 0, 3, 3); // 0: switch to knife, 1: stay with pistol (if has ammo), 2: switch to grenade (if possible), 3: switch to primary (if has ammo) - all fallback to previous one w/o ammo for target struct sgray { @@ -1669,8 +1668,7 @@ void akimbo::onammopicked() akimbomillis = lastmillis + 30000; if(owner==player1) { - // if(owner->weaponsel->type!=GUN_SNIPER && owner->weaponsel->type!=GUN_GRENADE) owner->weaponswitch(this); - if(akimboautoswitch || owner->weaponsel->type==GUN_PISTOL) owner->weaponswitch(this); // Give the client full control over akimbo auto-switching // Bukz 2011apr23 + owner->weaponswitch(this); addmsg(SV_AKIMBO, "ri", lastmillis); } } From 1fe478aae44fd1fd0a8e584f600ac3e92fc0d7ee Mon Sep 17 00:00:00 2001 From: ac-stef Date: Sat, 3 Jun 2017 23:53:57 +0200 Subject: [PATCH 43/45] cleanup protocol ...and get master to compile again DOCREF hudextras mapstats_hud nextGameMode --- config/menus_settings.cfg | 1 - source/enet/unix.c | 2 + source/src/Makefile | 1 - source/src/bot/bot_waypoint.cpp | 5 +- source/src/client.cpp | 211 +++------------ source/src/clientgame.cpp | 74 ++---- source/src/clients2c.cpp | 214 +++------------- source/src/command.cpp | 30 ++- source/src/cube.h | 2 +- source/src/entity.h | 19 +- source/src/master.cpp | 76 +++--- source/src/physics.cpp | 8 +- source/src/protocol.cpp | 18 +- source/src/protocol.h | 18 +- source/src/protos.h | 11 - source/src/renderhud.cpp | 46 ---- source/src/server.cpp | 303 ++-------------------- source/src/server.h | 57 +---- source/src/serverbrowser.cpp | 23 +- source/src/serverchecks.h | 440 -------------------------------- source/src/texture.cpp | 1 + source/src/vertmodel.h | 1 - source/src/world.cpp | 9 +- source/src/world.h | 22 -- source/src/worldio.cpp | 35 +-- 25 files changed, 237 insertions(+), 1390 deletions(-) diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg index dba8a349..39d0787f 100644 --- a/config/menus_settings.cfg +++ b/config/menus_settings.cfg @@ -144,7 +144,6 @@ menuitemslider [ Votes: ] 0 2 "$hidevote" ["Always show" "Show until you vot menuitemradio [ Graphics statistics: ] 0 2 "$showstats" [None "FPS Only" All] [ showstats $arg1 ] 1 menuitemslider [ Game time: ] 0 2 "$gametimedisplay" [None "Count backward" "Count forward"] [ gametimedisplay $arg1 ] 1 menuitemslider [ Wall clock: ] 0 6 [findlist $wallclockformatlist $wallclockformat] [None "HH:MM (local, 24h)" "HH:MM:SS (local, 24h)" "HH:MM (local, 12h)" "HH:MM:SS (local, 12h)" "HH:MM (UTC/GMT)" "HH:MM:SS (UTC/GMT)"] [ alias wallclockformat (at $wallclockformatlist $arg1)] 1 -menuitemslider [ Extra messages: ] 0 3 "$hudextras" ["No extra messages" "Show extra messages in console" "Pop up extra messages!" "SPAM EXTRA MESSAGES!"] [ hudextras $arg1 ] menuitemradio [ Hand alignment: ] 0 1 "$righthanded" [Left-handed Right-handed] [ righthanded $arg1 ] 1 menuitemslider [ Max-roll: ] 0 (getvarrange max maxroll) "$maxroll" 1 [ maxroll $arg1 ] menuitemslider [ Spectator max-roll: ] 0 (getvarrange max maxrollremote) "$maxrollremote" 2 [ maxrollremote $arg1 ] diff --git a/source/enet/unix.c b/source/enet/unix.c index be621915..cedaec5b 100644 --- a/source/enet/unix.c +++ b/source/enet/unix.c @@ -131,8 +131,10 @@ enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameL #else char * addr = inet_ntoa (* (struct in_addr *) & address -> host); if (addr != NULL) + { strncpy (name, addr, nameLength); name[nameLength - 1] = '\0'; + } else #endif return -1; diff --git a/source/src/Makefile b/source/src/Makefile index 0835506c..06bbfc69 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -131,7 +131,6 @@ SERVER_OBJS= \ tools-standalone.o \ wizard-standalone.o MASTER_OBJS= \ - crypto-standalone.o \ stream-standalone.o \ command-standalone.o \ master-standalone.o diff --git a/source/src/bot/bot_waypoint.cpp b/source/src/bot/bot_waypoint.cpp index 30551b70..8dbeec50 100644 --- a/source/src/bot/bot_waypoint.cpp +++ b/source/src/bot/bot_waypoint.cpp @@ -15,11 +15,8 @@ vec v_debuggoal = g_vecZero; CACWaypointClass WaypointClass; VAR(xhairwpsel, 0, 1, 1); -// FIXME: multiple selections support ? -// well, TBH I haven't groked this myself yet, but the following curselection segfaults the client, so I'm just putting in a sane-variant until someone groks it ;-) - ft 2011sep28 extern vector sels; -//#define curselection (xhairwpsel ? vec(sels.last().x, sels.last().y, S(sels.last().x, sels.last().y)->floor+2.0f) : vec(player1->o.x, player1->o.y, player1->o.z)) -#define curselection (vec(player1->o.x, player1->o.y, player1->o.z)) +#define curselection (xhairwpsel && sels.length() ? vec(sels.last().x, sels.last().y, S(sels.last().x, sels.last().y)->floor+2.0f) : vec(player1->o.x, player1->o.y, player1->o.z)) // Waypoint class begin diff --git a/source/src/client.cpp b/source/src/client.cpp index 34080850..67f19724 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -9,7 +9,6 @@ ENetHost *clienthost = NULL; ENetPeer *curpeer = NULL, *connpeer = NULL; int connmillis = 0, connattempts = 0, discmillis = 0; SVAR(curdemofile, "n/a"); -extern bool clfail, cllock; extern int searchlan; int getclientnum() { return player1 ? player1->clientnum : -1; } @@ -52,20 +51,12 @@ void abortconnect() connectrole = CR_DEFAULT; if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); connpeer = NULL; -#if 0 - if(!curpeer) - { - enet_host_destroy(clienthost); - clienthost = NULL; - } -#endif } void connectserv_(const char *servername, int serverport = 0, const char *password = NULL, int role = CR_DEFAULT) { if(serverport <= 0) serverport = CUBE_DEFAULT_SERVER_PORT; if(watchingdemo) enddemoplayback(); - if(!clfail && cllock && searchlan<2) return; if(connpeer) { @@ -119,6 +110,7 @@ void connectserv(char *servername, int *serverport, char *password) modprotocol = false; connectserv_(servername, *serverport, password); } +COMMANDN(connect, connectserv, "sis"); void connectadmin(char *servername, int *serverport, char *password) { @@ -126,18 +118,21 @@ void connectadmin(char *servername, int *serverport, char *password) if(!password[0]) return; connectserv_(servername, *serverport, password, CR_ADMIN); } +COMMAND(connectadmin, "sis"); void lanconnect() { modprotocol = false; connectserv_(NULL); } +COMMAND(lanconnect, ""); void modconnectserv(char *servername, int *serverport, char *password) { modprotocol = true; connectserv_(servername, *serverport, password); } +COMMANDN(modconnect, modconnectserv, "sis"); void modconnectadmin(char *servername, int *serverport, char *password) { @@ -145,17 +140,14 @@ void modconnectadmin(char *servername, int *serverport, char *password) if(!password[0]) return; connectserv_(servername, *serverport, password, CR_ADMIN); } +COMMAND(modconnectadmin, "sis"); void modlanconnect() { modprotocol = true; connectserv_(NULL); } - -void whereami() -{ - conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y); -} +COMMAND(modlanconnect, ""); void disconnect(int onlyclean, int async) { @@ -192,13 +184,6 @@ void disconnect(int onlyclean, int async) audiomgr.clearworldsounds(false); localdisconnect(); } -#if 0 - if(!connpeer && clienthost) - { - enet_host_destroy(clienthost); - clienthost = NULL; - } -#endif if(!onlyclean) localconnect(); exechook(HOOK_SP_MP, "onDisconnect", "%d", -1); } @@ -219,6 +204,9 @@ void trydisconnect() conoutf("attempting to disconnect..."); disconnect(0, !discmillis); } +COMMANDN(disconnect, trydisconnect, ""); + +// core game function: ingame chat void _toserver(char *text, int msg, int msgt) { @@ -226,25 +214,34 @@ void _toserver(char *text, int msg, int msgt) if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active if(toteam) text++; filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(servstate.mastermode == MM_MATCH && servstate.matchteamsize && !team_isactive(player1->team) && !(player1->team == TEAM_SPECT && player1->clientrole == CR_ADMIN)) toteam = true; // spect chat - if(*text) - { - if(msg == SV_TEXTME) conoutf("\f%d%s %s", toteam ? 1 : 0, colorname(player1), highlight(text)); - else conoutf("%s:\f%d %s", colorname(player1), toteam ? 1 : 0, highlight(text)); - addmsg(toteam ? msgt : msg, "rs", text); - } + if(*text) addmsg(toteam ? msgt : msg, "rs", text); } void toserver(char *text) { _toserver(text, SV_TEXT, SV_TEAMTEXT); } +COMMANDN(say, toserver, "c"); void toserverme(char *text) { _toserver(text, SV_TEXTME, SV_TEAMTEXTME); } +COMMANDN(me, toserverme, "c"); + +void pm(char *text) +{ + char *msg; + int cn = (int) strtol(text, &msg, 10); + if(msg != text && getclient(cn)) + { + filtertext(msg, msg, FTXT__CHAT | FTXT_CROPWHITE_LEAD); + if(*msg) addmsg(SV_TEXTPRIVATE, "ris", cn, msg); + } + else conoutf("\f3pm: invalid client number specified"); +} +COMMAND(pm, "c"); void echo(char *text) { @@ -256,8 +253,10 @@ void echo(char *text) } while(s); } +COMMAND(echo, "c"); VARP(allowhudechos, 0, 1, 1); + void hudecho(char *text) { char *b, *s = strtok_r(text, "\n", &b); @@ -269,65 +268,12 @@ void hudecho(char *text) } while(s); } - -void pm(char *text) -{ - if(!text || !text[0]) return; - int cn = -1; - char digit; - while ((digit = *text++) != '\0') - { - if (digit < '0' || digit > '9') break; - if(cn < 0) cn = 0; - else cn *= 10; - cn += digit - '0'; - } - playerent *to = getclient(cn); - if(!to) - { - conoutf("invalid client number specified"); - return; - } - - if(!isspace(digit)) { --text; } - - // FIXME: - /*if(!text || !text[0] || !isdigit(text[0])) return; - int cn = -1; - char *numend = strpbrk(text, " \t"); - if(!numend) return; - string cnbuf; - copystring(cnbuf, text, min(numend-text+1, MAXSTRLEN)); - cn = atoi(cnbuf); - playerent *to = getclient(cn); - if(!to) - { - conoutf("invalid client number specified"); - return; - } - - if(*numend) numend++;*/ - // :FIXME - - filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); - - addmsg(SV_TEXTPRIVATE, "ris", cn, text); - conoutf("to %s (PM):\f9 %s", colorname(to), highlight(text)); -} -COMMAND(pm, "c"); - -COMMAND(echo, "c"); COMMAND(hudecho, "c"); -COMMANDN(say, toserver, "c"); -COMMANDN(me, toserverme, "c"); -COMMANDN(connect, connectserv, "sis"); -COMMAND(connectadmin, "sis"); -COMMAND(lanconnect, ""); -COMMANDN(modconnect, modconnectserv, "sis"); -COMMAND(modconnectadmin, "sis"); -COMMAND(modlanconnect, ""); -COMMANDN(disconnect, trydisconnect, ""); + +void whereami() +{ + conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y); +} COMMAND(whereami, ""); void current_version(char *text) @@ -633,84 +579,13 @@ void gets2c() // get updates from the server } } -#if 0 -// for AUTH: - -vector authkeys; - -VARP(autoauth, 0, 1, 1); - -authkey *findauthkey(const char *desc) -{ - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, player1->name)) return authkeys[i]; - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i]; - return NULL; -} - -void addauthkey(const char *name, const char *key, const char *desc) -{ - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i); - if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc)); -} - -bool _hasauthkey(const char *name, const char *desc) -{ - if(!name[0] && !desc[0]) return authkeys.length() > 0; - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true; - return false; -} - -void genauthkey(const char *secret) -{ - if(!secret[0]) { conoutf("you must specify a secret password"); return; } - vector privkey, pubkey; - genprivkey(secret, privkey, pubkey); - conoutf("private key: %s", privkey.getbuf()); - conoutf("public key: %s", pubkey.getbuf()); -} - -void saveauthkeys() -{ - if(authkeys.length()) - { - stream *f = openfile("config/auth.cfg", "w"); - if(!f) { conoutf("failed to open config/auth.cfg for writing"); return; } - loopv(authkeys) - { - authkey *a = authkeys[i]; - f->printf("authkey \"%s\" \"%s\" \"%s\"\n", a->name, a->key, a->desc); - } - conoutf("saved authkeys to config/auth.cfg"); - delete f; - } - else conoutf("you need to use 'addauthkey USER KEY DESC' first; one DESC 'public', one DESC empty(=private)"); -} - -bool tryauth(const char *desc) -{ - authkey *a = findauthkey(desc); - if(!a) return false; - a->lastauth = lastmillis; - addmsg(SV_AUTHTRY, "rss", a->desc, a->name); - return true; -} - -COMMANDN(authkey, addauthkey, "sss"); -COMMANDF(hasauthkey, "ss", (char *name, char *desc) { intret(_hasauthkey(name, desc) ? 1 : 0); }); -COMMAND(genauthkey, "s"); -COMMAND(saveauthkeys, ""); -COMMANDF(auth, "s", (char *desc) { intret(tryauth(desc)); }); - -// :for AUTH - -#endif - // sendmap/getmap commands, should be replaced by more intuitive map downloading vector securemaps; -void resetsecuremaps() { securemaps.deletearrays(); } -void securemap(char *map) { if(map) securemaps.add(newstring(map)); } +COMMANDF(resetsecuremaps, "", () { securemaps.deletearrays(); }); +COMMANDF(securemap, "s", (char *map) { if(map) securemaps.add(newstring(map)); }); + bool securemapcheck(const char *map, bool msg) { if(strstr(map, "maps/")==map || strstr(map, "maps\\")==map) map += strlen("maps/"); @@ -719,7 +594,7 @@ bool securemapcheck(const char *map, bool msg) if(msg) { conoutf("\f3Map \f4%s\f3 is secured. This means you CAN'T send, receive or overwrite it.", map); - if(connected) + if(curpeer) { conoutf("\f3If you get this error often, you (or the server) may be running an outdated game."); conoutf("\f3You can check for updates at \f1http://assault.cubers.net/download.html"); @@ -768,11 +643,12 @@ void sendmap(char *mapname) sendpackettoserv(2, p.finalize()); conoutf("sending map %s to server...", mapname); } +COMMAND(sendmap, "s"); void getmap(char *name, char *callback) { if((!name || !*name) - || (connected && !strcmp(name, getclientmap())) ) + || (curpeer && !strcmp(name, getclientmap())) ) { conoutf("requesting map from server..."); packetbuf p(10, ENET_PACKET_FLAG_RELIABLE); @@ -790,6 +666,7 @@ void getmap(char *name, char *callback) else conoutf("\f3map download failed"); } } +COMMAND(getmap, "ss"); void deleteservermap(char *mapname) { @@ -797,6 +674,7 @@ void deleteservermap(char *mapname) if(!*name || securemapcheck(name)) return; addmsg(SV_REMOVEMAP, "rs", name); } +COMMAND(deleteservermap, "s"); string demosubpath; void getdemo(int *idx, char *dsp) @@ -812,6 +690,7 @@ void getdemo(int *idx, char *dsp) else conoutf("getting demo %d...", *idx); addmsg(SV_GETDEMO, "ri", *idx); } +COMMAND(getdemo, "is"); void listdemos() { @@ -823,6 +702,7 @@ void listdemos() conoutf("listing demos..."); addmsg(SV_LISTDEMOS, "r"); } +COMMAND(listdemos, ""); void shiftgametime(int newmillis) { @@ -859,13 +739,6 @@ void rewinddemo(char *seconds) } COMMANDN(rewind, rewinddemo, "s"); -COMMAND(sendmap, "s"); -COMMAND(getmap, "ss"); -COMMAND(deleteservermap, "s"); -COMMAND(resetsecuremaps, ""); -COMMAND(securemap, "s"); -COMMAND(getdemo, "is"); -COMMAND(listdemos, ""); COMMANDF(watchingdemo, "", () { intret(watchingdemo); }); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 37862593..3f859330 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -5,7 +5,6 @@ int nextmode = 0; // nextmode becomes gamemode after next map load VAR(gamemode, 1, 0, 0); -VAR(nextGameMode, 1, 0, 0); VARP(modeacronyms, 0, 1, 1); flaginfo flaginfos[2]; @@ -15,8 +14,6 @@ mapdim_s clmapdims; // min/max X/Y and delta X/Y and min/max Z void mode(int *n) { nextmode = *n; - nextGameMode = nextmode; - if(m_mp(*n) || !multiplayer()) addmsg(SV_GAMEMODE, "ri", *n); } COMMAND(mode, "i"); @@ -112,6 +109,7 @@ void ignore(int *cn) playerent *d = getclient(*cn); if(d && d != player1) d->ignored = true; } +COMMAND(ignore, "i"); void listignored() { @@ -121,17 +119,20 @@ void listignored() if(*pl) conoutf("ignored players: %s", pl + 2); else conoutf("no players are ignored"); } +COMMAND(listignored, ""); void clearignored(int *cn) { loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->ignored = false; } +COMMAND(clearignored, "i"); void muteplayer(int *cn) { playerent *d = getclient(*cn); if(d && d != player1) d->muted = true; } +COMMAND(muteplayer, "i"); void listmuted() { @@ -141,17 +142,12 @@ void listmuted() if(*pl) conoutf("muted players: %s", pl + 2); else conoutf("no players are muted"); } +COMMAND(listmuted, ""); void clearmuted(char *cn) { loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->muted = false; } - -COMMAND(ignore, "i"); -COMMAND(listignored, ""); -COMMAND(clearignored, "i"); -COMMAND(muteplayer, "i"); -COMMAND(listmuted, ""); COMMAND(clearmuted, "i"); void newname(const char *name) @@ -168,6 +164,7 @@ void newname(const char *name) } else conoutf("your name is: %s", player1->name); } +COMMANDN(name, newname, "s"); SVARFF(curname, { curname = exchangestr(curname, player1->name); }, {} ); @@ -188,12 +185,14 @@ void newteam(char *name) } else conoutf("your team is: %s", team_string(player1->team)); } +COMMANDN(team, newteam, "s"); void benchme() { if(team_isactive(player1->team) && servstate.mastermode == MM_MATCH) addmsg(SV_SWITCHTEAM, "ri", team_tospec(player1->team)); } +COMMAND(benchme, ""); int _setskin(int s, int t) { @@ -214,14 +213,11 @@ void curmodeattr(char *attr) else if(!strcmp(attr, "bot")) { intret(m_botmode); return; } intret(0); } +COMMAND(curmodeattr, "s"); -COMMANDN(team, newteam, "s"); -COMMANDN(name, newname, "s"); -COMMAND(benchme, ""); COMMANDF(isclient, "i", (int *cn) { intret(getclient(*cn) != NULL ? 1 : 0); } ); COMMANDF(curmastermode, "", (void) { intret(servstate.mastermode); }); COMMANDF(curautoteam, "", (void) { intret(servstate.autoteam); }); -COMMAND(curmodeattr, "s"); COMMANDF(curmap, "", (void) { result(behindpath(getclientmap())); }); COMMANDF(curplayers, "", (void) { intret(players.length() + 1); }); VARP(showscoresondeath, 0, 1, 1); @@ -450,6 +446,7 @@ void deathstate(playerent *pl) if(editmode) toggleedit(true); if(pl->team == TEAM_SPECT) spectatemode(SM_FLY); else if(team_isspect(pl->team)) spectatemode(SM_FOLLOW1ST); + if(pl->spectatemode == SM_DEATHCAM) addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_DEATHCAM)); } else pl->resetinterp(); } @@ -650,6 +647,7 @@ void addsleep_(int *msec, char *cmd, int *persist) { addsleep(*msec, cmd, *persist != 0); } +COMMANDN(sleep, addsleep_, "isi"); void resetsleep(bool force) { @@ -662,7 +660,6 @@ void resetsleep(bool force) sleepssemaphore->post(); } -COMMANDN(sleep, addsleep_, "isi"); COMMANDF(resetsleeps, "", (void) { resetsleep(true); }); void updateworld(int curtime, int lastmillis) // main game update loop @@ -910,8 +907,6 @@ void dodamage(int damage, playerent *pl, playerent *actor, int gun, bool gib, bo { if(pl->state != CS_ALIVE || intermission) return; pl->lastpain = lastmillis; - // could the author of the FIXME below please elaborate what's to fix?! (ft:2011mar28) - // I suppose someone wanted to play the hitsound for player1 or spectated player (lucas:2011may22) playerent *h = player1->isspectating() && player1->followplayercn >= 0 && (player1->spectatemode == SM_FOLLOW1ST || player1->spectatemode == SM_FOLLOW3RD || player1->spectatemode == SM_FOLLOW3RD_TRANSPARENT) ? getclient(player1->followplayercn) : NULL; if(!h) h = player1; exechook(HOOK_SP, "onHit", "%d %d %d %d %d", actor->clientnum, pl->clientnum, damage, gun, gib ? 1 : 0); @@ -1156,28 +1151,11 @@ void resetmap(bool mrproper) int suicided = -1; -extern bool item_fail; -extern int MA, F2F, Ma, Hhits; -extern float Mh; - -VARP(mapstats_hud, 0, 0, 1); - -void showmapstats() -{ - conoutf("\f2Map Quality Stats"); - conoutf(" The mean height is: %.2f", Mh); - if (Hhits) conoutf(" Height check is: %d", Hhits); - if (MA) conoutf(" The max area is: %d (of %d)", MA, Ma); - if (m_flags && F2F < 1000) conoutf(" Flag-to-flag distance is: %d", (int)sqrtf(F2F)); - if (item_fail) conoutf(" There are one or more items too close to each other in this map"); -} -COMMAND(showmapstats, ""); - VARP(showmodedescriptions, 0, 1, 1); -extern bool canceldownloads; void startmap(const char *name, bool reset, bool norespawn) // called just after a map load { + extern bool canceldownloads; canceldownloads = false; copystring(clientmap, name); @@ -1191,14 +1169,13 @@ void startmap(const char *name, bool reset, bool norespawn) // called just aft suicided = -1; spawncycle = -1; lasthit = 0; - player1->followplayercn = -1; if(m_valid(gamemode) && !m_mp(gamemode)) respawnself(); else findplayerstart(player1); if(!reset) return; - player1->frags = player1->flagscore = player1->deaths = player1->lifesequence = player1->points = player1->tks = 0; - loopv(players) if(players[i]) players[i]->frags = players[i]->flagscore = players[i]->deaths = players[i]->lifesequence = players[i]->points = players[i]->tks = 0; + player1->startmap(); + loopv(players) if(players[i]) players[i]->startmap(); if(editmode) toggleedit(true); intermission = false; showscores(false); @@ -1274,6 +1251,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) } else hudoutf("\f2%s%s has %s flag", flagteam, colorname(act), teamstr); break; + case FM_LOST: case FM_DROP: { @@ -1292,6 +1270,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) if(firstperson) hudoutf("\f2you returned your flag"); else hudoutf("\f2%s returned %s flag", colorname(act), teamstr); break; + case FM_SCORE: audiomgr.playsound(S_FLAGSCORE, SP_HIGHEST); if(firstperson) @@ -1301,6 +1280,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) } else hudoutf("\f2%s scored for %s", colorname(act), neutral ? teamnames[act->team] : teammate ? "your team" : "the enemy team"); break; + case FM_KTFSCORE: { audiomgr.playsound(S_KTFSCORE, SP_HIGHEST); @@ -1317,6 +1297,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) case FM_SCOREFAIL: // sound? hudoutf("\f2%s failed to score (own team flag not taken)", firstperson ? "you" : colorname(act)); break; + case FM_RESET: audiomgr.playsound(S_FLAGRETURN, SP_HIGHEST); hudoutf("the server reset the flag"); @@ -1656,7 +1637,6 @@ void setadmin(int *claim, char *password) else if(*claim != 0 && *password) addmsg(SV_SETADMIN, "ris", *claim, genpwdhash(player1->name, password, sessionid)); } - COMMAND(setadmin, "is"); static vector mlines; @@ -1704,6 +1684,7 @@ playerent *updatefollowplayer(int shiftdirection) if(oldidx < 0) oldidx = 0; int idx = ((oldidx + shiftdirection) % available.length() + available.length()) % available.length(); + if(player1->followplayercn != available[idx]->clientnum) addmsg(SV_SPECTCN, "ri", available[idx]->clientnum); player1->followplayercn = available[idx]->clientnum; return players[player1->followplayercn]; } @@ -1714,7 +1695,6 @@ void spectate() if(!team_isspect(player1->team)) addmsg(SV_SWITCHTEAM, "ri", TEAM_SPECT); else tryrespawn(); } - COMMAND(spectate, ""); void setfollowplayer(int cn) @@ -1724,12 +1704,11 @@ void setfollowplayer(int cn) { if(!(m_teammode && player1->team != TEAM_SPECT && !watchingdemo && team_base(players[cn]->team) != team_base(player1->team))) { - player1->followplayercn = cn; + if(player1->followplayercn != cn) addmsg(SV_SPECTCN, "ri", (player1->followplayercn = cn)); if(player1->spectatemode == SM_FLY) player1->spectatemode = SM_FOLLOW1ST; } } } - COMMANDF(setfollowplayer, "i", (int *cn) { setfollowplayer(*cn); }); // set new spect mode @@ -1760,16 +1739,17 @@ void spectatemode(int mode) player1->resetinterp(); } else entinmap(player1); // or drop 'em at a random place + addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_FLY)); } break; } case SM_OVERVIEW: + addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_OVERVIEW)); break; default: break; } player1->spectatemode = mode; } - COMMANDF(spectatemode, "i", (int *mode) { spectatemode(*mode); }); void togglespect() // cycle through all spectating modes @@ -1784,15 +1764,13 @@ void togglespect() // cycle through all spectating modes spectatemode(mode); } } - COMMAND(togglespect, ""); -void changefollowplayer(int shift) +void changefollowplayer(int *shift) { - if(!m_botmode) updatefollowplayer(shift); + if(!m_botmode) updatefollowplayer(*shift); } - -COMMANDF(changefollowplayer, "i", (int *dir) { changefollowplayer(*dir); }); +COMMAND(changefollowplayer, "i"); void spectatecn() { @@ -1801,7 +1779,6 @@ void spectatecn() spectcn = player1->followplayercn; intret(spectcn); } - COMMAND(spectatecn, ""); void serverextension(char *ext, char *args) @@ -1811,5 +1788,4 @@ void serverextension(char *ext, char *args) if(n>0) addmsg(SV_EXTENSION, "rsis", ext, n, args); else addmsg(SV_EXTENSION, "rsi", ext, n); } - COMMAND(serverextension, "ss"); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index f12a02f1..e31356e2 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -29,10 +29,9 @@ VARP(autogetmap, 0, 1, 1); // only if the client doesn't have that map VARP(autogetnewmaprevisions, 0, 1, 1); bool localwrongmap = false; -int MA = 0, Hhits = 0; // flowtron: moved here + bool changemapserv(char *name, int mode, int download, int revision) // forced map change from the server { - MA = Hhits = 0; // reset for checkarea() gamemode = mode; if(m_demo) return true; if(m_coop) @@ -106,8 +105,6 @@ void updatelagtime(playerent *d) } } -extern void trydisconnect(); - void parsepositions(ucharbuf &p) { int type; @@ -228,75 +225,6 @@ void parsepositions(ucharbuf &p) } } -char *mlayout = NULL; -int Mv = 0, Ma = 0, F2F = 1000 * MINFF; // moved up:, MA = 0; -float Mh = 0; -extern int connected; -extern bool noflags; -bool item_fail = false; -int map_quality = MAP_IS_EDITABLE; - - -VARP(hudextras, 0, 0, 3); - -int teamworkid = -1; - -char *strcaps(const char *s, bool on) -{ - static string r; - char *o = r; - if(on) while(*s && o < &r[sizeof(r)-1]) *o++ = toupper(*s++); - else while(*s && o < &r[sizeof(r)-1]) *o++ = tolower(*s++); - *o = '\0'; - return r; -} - -void showhudextras(char hudextras, char value){ - void (*outf)(const char *s, ...) = (hudextras > 1 ? hudoutf : conoutf); - bool caps = hudextras < 3 ? false : true; - switch(value) - { - case HE_COMBO: - case HE_COMBO2: - case HE_COMBO3: - case HE_COMBO4: - case HE_COMBO5: - { - int n = value - HE_COMBO; - if (n > 3) outf("\f3%s",strcaps("monster combo!!!",caps)); // I expect to never see this one - else if (!n) outf("\f5%s",strcaps("combo", caps)); - else outf("\f5%s x%d",strcaps("multi combo", caps),n+1); - break; - } - case HE_TEAMWORK: - outf("\f5%s",strcaps("teamwork done", caps)); break; - case HE_FLAGDEFENDED: - outf("\f5%s",strcaps("you defended the flag", caps)); break; - case HE_FLAGCOVERED: - outf("\f5%s",strcaps("you covered the flag", caps)); break; - case HE_COVER: - if (teamworkid >= 0) - { - playerent *p = getclient(teamworkid); - if (!p || p == player1) teamworkid = -1; - else outf("\f5you covered %s",p->name); break; - } - default: - { - if (value >= HE_NUM) - { - teamworkid = value - HE_NUM; - playerent *p = getclient(teamworkid); - if (!p || p == player1) teamworkid = -1; - else outf("\f4you replied to %s",p->name); - } - else outf("\f3Update your client!"); - break; - } - } -#undef SSPAM -} - int lastspawn = 0; void onCallVote(int type, int vcn, char *text, char *a) @@ -310,8 +238,6 @@ void onChangeVote(int mod, int id) } VARP(voicecomsounds, 0, 1, 2); -bool medals_arrived=0; -medalsst a_medals[END_MDS]; struct session_s { @@ -332,22 +258,10 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { type = getint(p); - if(demo && watchingdemo && demoprotocol == 1132) - { - if(type > SV_IPLIST) --type; // SV_WHOIS removed - if(type >= SV_TEXTPRIVATE) ++type; // SV_TEXTPRIVATE added - if(type == SV_SWITCHNAME) // SV_SPECTCN removed - { - getint(p); - continue; - } - else if(type > SV_SWITCHNAME) --type; - } - #ifdef _DEBUG if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_PONG && type!=SV_CLIENT) { - DEBUGVAR(d); + DEBUGVAR(cn); ASSERT(type>=0 && typeignored) clientlogf("ignored: pm %s %s", colorname(d), text); + playerent *s = getclient(src), *d = getclient(dst); + if(!s) break; + if(s == player1 && d) conoutf("to %s (PM):\f9 %s", colorname(d), highlight(text)); + else if(s->ignored) clientlogf("ignored: pm %s %s", colorname(s), text); else { - conoutf("%s (PM):\f9 %s", colorname(d), highlight(text)); - lastpm = d->clientnum; - exechook(HOOK_SP_MP, "onPM", "%d [%s]", d->clientnum, text); + conoutf("%s (PM):\f9 %s", colorname(s), highlight(text)); + lastpm = s->clientnum; + exechook(HOOK_SP_MP, "onPM", "%d [%s]", s->clientnum, text); } break; } @@ -622,7 +537,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) loopi(2) getint(p); getint(p); loopi(4) getint(p); - if(!demo || !watchingdemo || demoprotocol > 1132) getint(p); + getint(p); break; } getstring(text, p); @@ -646,8 +561,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); d->ffov = (float)clamp(getint(p), 75, 120); d->scopefov = (float)clamp(getint(p), 5, 60); - - if(!demo || !watchingdemo || demoprotocol > 1132) d->address = getint(p); // partial IP address + d->address = getint(p); // partial IP address if(m_flags) loopi(2) { @@ -699,13 +613,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) case SV_SPAWNSTATE: { - if ( map_quality == MAP_IS_BAD ) - { - loopi(6+2*NUMGUNS) getint(p); - conoutf("map deemed unplayable - fix it before you can spawn"); - break; - } - if(editmode) toggleedit(true); showscores(false); setscope(false); @@ -725,7 +632,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) arenaintermission = 0; if(m_arena && !localwrongmap) { - if(connected) closemenu(NULL); + if(curpeer) closemenu(NULL); conoutf("new round starting... fight!"); hudeditf(HUDMSG_TIMER, "FIGHT!"); if(m_botmode) BotManager.RespawnBots(); @@ -737,6 +644,10 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_SPECTCN: + getint(p); + break; + case SV_SHOTFX: { int scn = getint(p), gun = getint(p); @@ -787,34 +698,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } - // for AUTH: WIP - - case SV_AUTHREQ: - { -// extern int autoauth; - getstring(text, p); - // if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text); - break; - } - - case SV_AUTHCHAL: - { - getstring(text, p); - // authkey *a = findauthkey(text); - // uint id = (uint)getint(p); - getstring(text, p); - // if(a && a->lastauth && lastmillis - a->lastauth < 60*1000) - { - // vector buf; - // answerchallenge(a->key, text, buf); - //conoutf("answering %u, challenge %s with %s", id, text, buf.getbuf()); - // addmsg(SV_AUTHANS, "rsis", a->desc, id, buf.getbuf()); - } - break; - } - - // :for AUTH - case SV_GIBDAMAGE: case SV_DAMAGE: { @@ -833,38 +716,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } - case SV_POINTS: - { - int count = getint(p); - if ( count > 0 ) { - loopi(count){ - int pcn = getint(p); int score = getint(p); - playerent *ppl = getclient(pcn); - if (!ppl) break; - ppl->points += score; - } - } else { - int medals = getint(p); - if(medals > 0) { -// medals_arrived=1; - loopi(medals) { - int mcn=getint(p); int mtype=getint(p); int mitem=getint(p); - a_medals[mtype].assigned=1; - a_medals[mtype].cn=mcn; - a_medals[mtype].item=mitem; - } - } - } - break; - } - - case SV_HUDEXTRAS: - { - char value = getint(p); - if (hudextras) showhudextras(hudextras, value); - break; - } - case SV_HITPUSH: { int gun = getint(p), damage = getint(p); @@ -892,9 +743,17 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { int cn = getint(p); if(p.overread() || cn<0) break; - int state = getint(p), lifesequence = getint(p), primary = getint(p), gunselect = getint(p), flagscore = getint(p), frags = getint(p), deaths = getint(p), health = getint(p), armour = getint(p), points = getint(p); - int teamkills = 0; - if(!demo || !watchingdemo || demoprotocol > 1132) teamkills = getint(p); + int state = getint(p), + lifesequence = getint(p), + primary = getint(p), + gunselect = getint(p), + flagscore = getint(p), + frags = getint(p), + deaths = getint(p), + health = getint(p), + armour = getint(p), + points = getint(p), + teamkills = getint(p); int ammo[NUMGUNS], mag[NUMGUNS]; loopi(NUMGUNS) ammo[i] = getint(p); loopi(NUMGUNS) mag[i] = getint(p); @@ -1068,11 +927,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) d->ping = getint(p); break; - case SV_GAMEMODE: - nextmode = getint(p); - if (nextmode >= GMODE_NUM) nextmode -= GMODE_NUM; - break; - case SV_TIMEUP: { int curgamemillis = getint(p); @@ -1433,16 +1287,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) bool demoplayback = false; string demofile; extern char *curdemofile; - if(demo && watchingdemo && demoprotocol == 1132) - { - watchingdemo = demoplayback = getint(p)!=0; - copystring(demofile, "n/a"); - } - else - { - getstring(demofile, p, MAXSTRLEN); - watchingdemo = demoplayback = demofile[0] != '\0'; - } + getstring(demofile, p, MAXSTRLEN); + watchingdemo = demoplayback = demofile[0] != '\0'; DELETEA(curdemofile); if(demoplayback) { diff --git a/source/src/command.cpp b/source/src/command.cpp index a79ef267..693ca127 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -11,6 +11,11 @@ inline bool identaccessdenied(ident *id); void cslimiterr(const char*msg); +#ifdef STANDALONE + #define FLAGMAPCONFIGERR(x) +#else + #define FLAGMAPCONFIGERR(x) flagmapconfigerror(x); +#endif char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); } @@ -381,7 +386,6 @@ char *parseexp(const char *&p, int right, int rec) // parse any nest screrr: scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); return NULL; } @@ -398,7 +402,6 @@ char *lookup(char *n, int levels) // find value of ide } conoutf("unknown alias lookup: %s", n); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); return n; } @@ -407,7 +410,7 @@ char *parseword(const char *&p, int arg, int *infix, int rec) p += strspn(p, " \t"); if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\r\0"); if(*p=='"') return parsequotes(p); - if(*p=='(' && !currentcontextisolated) return parseexp(p, ')', rec + 1); + if(*p=='(' && !currentcontextisolated) return parseexp(p, ')', rec + 1); // on isolated contexts, only quotes are parsed if(*p=='[' && !currentcontextisolated) return parseexp(p, ']', rec + 1); int lvls = currentcontextisolated ? 0 : strspn(p, "$"); if(lvls && (p[lvls]=='(' || p[lvls]=='[')) @@ -524,7 +527,7 @@ char *executeret(const char *p) // all evaluation happens here, { conoutf("unknown command: %s", c); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 4); } setretval(newstring(c)); } @@ -532,7 +535,7 @@ char *executeret(const char *p) // all evaluation happens here, { conoutf("not allowed in this execution context: %s", id->name); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 4); } else { @@ -591,7 +594,7 @@ char *executeret(const char *p) // all evaluation happens here, { i1 = i1minval ? id->minval : id->maxval; // clamp to valid range conoutf("valid range for %s is %d..%d", id->name, id->minval, id->maxval); - flagmapconfigerror(LWW_SCRIPTERR); + FLAGMAPCONFIGERR(LWW_SCRIPTERR); } *id->storage.i = i1; if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available @@ -608,7 +611,7 @@ char *executeret(const char *p) // all evaluation happens here, { f1 = f1minvalf ? id->minvalf : id->maxvalf; // clamp to valid range conoutf("valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf)); - flagmapconfigerror(LWW_SCRIPTERR * 2); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 2); } *id->storage.f = f1; if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available @@ -1306,7 +1309,6 @@ void addpunct(char *s, char *type) // Easily inject a string into various CubeSc else result(punct); } COMMAND(addpunct, "ss"); -#endif void toany(char *s, int (*c)(int)) { while(*s) { *s = (*c)(*s); s++; } } @@ -1331,6 +1333,8 @@ void sortlist(char *list) } COMMAND(sortlist, "c"); +#endif + void modifyvar(const char *name, int arg, char op) { ident *id = idents->access(name); @@ -1488,10 +1492,10 @@ void bnot_(int *a) { intret(~(*a)); } COMMANDN(!b, bnot_, "i"); COMMANDF(strcmp, "ss", (char *a, char *b) { intret((strcmp(a, b) == 0) ? 1 : 0); }); -COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); - #ifndef STANDALONE +COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); + COMMANDF(escape, "s", (const char *s) { result(escapestring(s));}); int sortident(ident **a, ident **b) { return strcasecmp((*a)->name, (*b)->name); } @@ -1677,6 +1681,7 @@ int popscontext() return execcontext; } +#ifndef STANDALONE void scriptcontext(char *context, char *idname) { if(contextsealed) return; @@ -1695,6 +1700,7 @@ void isolatecontext(char *context) COMMAND(isolatecontext, "s"); COMMANDF(sealcontexts, "",() { contextsealed = true; }); +#endif inline bool identaccessdenied(ident *id) // check if ident is allowed in current context { @@ -1711,8 +1717,10 @@ void scripterr() ASSERT(execcontext >= 0 && execcontext < IEXC_NUM); if(curcontext) conoutf("(%s: %s [%s])", curcontext, curinfo, contextnames[execcontext]); else conoutf("(from console or builtin [%s])", contextnames[execcontext]); +#ifndef STANDALONE clientlogf("exec nesting level: %d", executionstack.length()); clientlogf("%s", executionstack.length() ? executionstack.last() : ":::nevermind:::"); +#endif } void setcontext(const char *context, const char *info) @@ -1782,5 +1790,7 @@ void debugline(char *fname, char *line) // print one line to a logfile } } COMMAND(debugline, "ss"); + +COMMANDF(numberofidents, "", () { intret(idents->numelems); }); #endif #endif diff --git a/source/src/cube.h b/source/src/cube.h index ebfdf991..7c3cd54b 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -41,7 +41,7 @@ extern vector ents; // map entities extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world extern int lastmillis, totalmillis, skipmillis; // last time extern int curtime; // current frame time -extern int gamemode, nextmode; +extern int gamemode; extern int gamespeed; extern int xtraverts; extern float fovy, aspect; diff --git a/source/src/entity.h b/source/src/entity.h index 9f3a809d..c99e9226 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -115,14 +115,9 @@ enum { ENT_PLAYER = 0, ENT_BOT, ENT_CAMERA, ENT_BOUNCE }; enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATE }; enum { CR_DEFAULT = 0, CR_MASTER, CR_ADMIN, CR_OWNER, CR_NUM }; enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_OVERVIEW, SM_NUM }; +enum { FPCN_VOID = -4, FPCN_DEATHCAM = -2, FPCN_FLY = -2, FPCN_OVERVIEW = -1 }; -class worldobject -{ -public: - virtual ~worldobject() {}; -}; - -class physent : public worldobject +class physent { public: vec o, vel; // origin, velocity @@ -452,7 +447,7 @@ public: bool nocorpse; playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), address(0), lifesequence(0), frags(0), flagscore(0), deaths(0), points(0), tks(0), lastpain(0), lastvoicecom(0), lastdeath(0), clientrole(CR_DEFAULT), - team(TEAM_SPECT), spectatemode(SM_NONE), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), ffov(0), scopefov(0), + team(TEAM_SPECT), spectatemode(SM_NONE), followplayercn(FPCN_VOID), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), ffov(0), scopefov(0), prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL), smoothmillis(-1), head(-1, -1, -1), ignored(false), muted(false), nocorpse(false) @@ -503,6 +498,7 @@ public: void resetspec() { spectatemode = SM_NONE; + followplayercn = FPCN_VOID; } void respawn() @@ -552,6 +548,10 @@ public: t = team_base(t < 0 ? team : t); nextskin[t] = iabs(s) % maxskin[t]; } + void startmap() + { + frags = flagscore = deaths = lifesequence = points = tks = 0; + } }; @@ -648,6 +648,3 @@ public: void onmoved(const vec &dist); }; -enum {MD_FRAGS = 0, MD_DEATHS, END_MDS}; -struct medalsst {bool assigned; int cn; int item;}; - diff --git a/source/src/master.cpp b/source/src/master.cpp index 5ed629c5..d0a34dfe 100644 --- a/source/src/master.cpp +++ b/source/src/master.cpp @@ -30,13 +30,13 @@ void adduser(char *name, char *pubkey) name = newstring(name); userinfo &u = users[name]; u.name = name; - u.pubkey = parsepubkey(pubkey); +// u.pubkey = parsepubkey(pubkey); } COMMAND(adduser, "ss"); void clearusers() { - enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); +// enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); users.clear(); } COMMAND(clearusers, ""); @@ -149,7 +149,7 @@ struct messagebuf vector gameserverlists, gbanlists; bool updateserverlist = true; -struct client +struct mclient { ENetAddress address; ENetSocket socket; @@ -164,9 +164,9 @@ struct client bool shouldpurge; bool registeredserver; - client() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} + mclient() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} }; -vector clients; +vector mclients; ENetSocket serversocket = ENET_SOCKET_NULL; @@ -199,20 +199,20 @@ void conoutf(const char *fmt, ...) void purgeclient(int n) { - client &c = *clients[n]; + mclient &c = *mclients[n]; if(c.message) c.message->purge(); enet_socket_destroy(c.socket); - delete clients[n]; - clients.remove(n); + delete mclients[n]; + mclients.remove(n); } -void output(client &c, const char *msg, int len = 0) +void output(mclient &c, const char *msg, int len = 0) { if(!len) len = strlen(msg); c.output.put(msg, len); } -void outputf(client &c, const char *fmt, ...) +void outputf(mclient &c, const char *fmt, ...) { defvformatstring(msg, fmt, fmt); output(c, msg); @@ -303,9 +303,9 @@ void gengbanlist() if(m->refs > 0 && !m->endswith(*l)) m->concat(*l); } gbanlists.add(l); - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(c.servport >= 0 && !c.message) { c.message = l; @@ -314,7 +314,7 @@ void gengbanlist() } } -void addgameserver(client &c) +void addgameserver(mclient &c) { if(gameservers.length() >= SERVER_LIMIT) return; loopv(gameservers) @@ -342,11 +342,11 @@ void addgameserver(client &c) s.lastping = s.lastpong = 0; } -client *findclient(gameserver &s) +mclient *findclient(gameserver &s) { - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(s.address.host == c.address.host && s.port == c.servport) return &c; } @@ -355,7 +355,7 @@ client *findclient(gameserver &s) void servermessage(gameserver &s, const char *msg) { - client *c = findclient(s); + mclient *c = findclient(s); if(c) outputf(*c, msg); } @@ -377,7 +377,7 @@ void checkserverpongs() { if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong))) { - client *c = findclient(s); + mclient *c = findclient(s); if(c) { c->registeredserver = true; @@ -452,7 +452,7 @@ void messagebuf::purge() } // for AUTH: -void purgeauths(client &c) +void purgeauths(mclient &c) { int expired = 0; loopv(c.authreqs) @@ -460,7 +460,7 @@ void purgeauths(client &c) if(ENET_TIME_DIFFERENCE(servtime, c.authreqs[i].reqtime) >= AUTH_TIME) { outputf(c, "failauth %u\n", c.authreqs[i].id); - freechallenge(c.authreqs[i].answer); +// freechallenge(c.authreqs[i].answer); expired = i + 1; } else break; @@ -468,7 +468,7 @@ void purgeauths(client &c) if(expired > 0) c.authreqs.remove(0, expired); } -void reqauth(client &c, uint id, char *name) +void reqauth(mclient &c, uint id, char *name) { if(ENET_TIME_DIFFERENCE(servtime, c.lastauth) < AUTH_THROTTLE) return; @@ -498,22 +498,22 @@ void reqauth(client &c, uint id, char *name) if(c.authreqs.length() >= AUTH_LIMIT) { outputf(c, "failauth %u\n", c.authreqs[0].id); - freechallenge(c.authreqs[0].answer); +// freechallenge(c.authreqs[0].answer); c.authreqs.remove(0); } authreq &a = c.authreqs.add(); a.reqtime = servtime; a.id = id; - uint seed[3] = { (uint)starttime, (uint)servtime, randomMT() }; +// uint seed[3] = { (uint)starttime, (uint)servtime, randomMT() }; static vector buf; buf.setsize(0); - a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); +// a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); outputf(c, "chalauth %u %s\n", id, buf.getbuf()); } -void confauth(client &c, uint id, const char *val) +void confauth(mclient &c, uint id, const char *val) { purgeauths(c); @@ -521,7 +521,7 @@ void confauth(client &c, uint id, const char *val) { string ip; if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); - if(checkchallenge(val, c.authreqs[i].answer)) + if(0)//checkchallenge(val, c.authreqs[i].answer)) { outputf(c, "succauth %u\n", id); conoutf("succeeded %u from %s", id, ip); @@ -531,7 +531,7 @@ void confauth(client &c, uint id, const char *val) outputf(c, "failauth %u\n", id); conoutf("failed %u from %s", id, ip); } - freechallenge(c.authreqs[i].answer); +// freechallenge(c.authreqs[i].answer); c.authreqs.remove(i--); return; } @@ -539,7 +539,7 @@ void confauth(client &c, uint id, const char *val) } // :for AUTH -bool checkclientinput(client &c) +bool checkclientinput(mclient &c) { if(c.inputpos<0) return true; char *end = (char *)memchr(c.input, '\n', c.inputpos); @@ -601,9 +601,9 @@ void checkclients() ENET_SOCKETSET_EMPTY(writeset); ENET_SOCKETSET_ADD(readset, serversocket); ENET_SOCKETSET_ADD(readset, pingsocket); - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(c.message || c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket); else ENET_SOCKETSET_ADD(readset, c.socket); maxsock = max(maxsock, c.socket); @@ -615,29 +615,29 @@ void checkclients() { ENetAddress address; ENetSocket clientsocket = enet_socket_accept(serversocket, &address); - if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); + if(mclients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); else if(clientsocket!=ENET_SOCKET_NULL) { int dups = 0, oldest = -1; - loopv(clients) if(clients[i]->address.host == address.host) + loopv(mclients) if(mclients[i]->address.host == address.host) { dups++; - if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i; + if(oldest<0 || mclients[i]->connecttime < mclients[oldest]->connecttime) oldest = i; } if(dups >= DUP_LIMIT) purgeclient(oldest); - client *c = new client; + mclient *c = new mclient; c->address = address; c->socket = clientsocket; c->connecttime = servtime; c->lastinput = servtime; - clients.add(c); + mclients.add(c); } } - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if((c.message || c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket)) { const char *data = c.output.length() ? c.output.getbuf() : c.message->getbuf(); @@ -688,7 +688,7 @@ void checkclients() void banclients() { - loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i); + loopvrev(mclients) if(checkban(bans, mclients[i]->address.host)) purgeclient(i); } volatile bool reloadcfg = true; diff --git a/source/src/physics.cpp b/source/src/physics.cpp index d30e0540..d36392b2 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -354,7 +354,6 @@ bool collide(physent *d, bool spawn, float drop, float rise) VARFP(maxroll, 0, ROLLMOVDEF, ROLLMOVMAX, player1->maxroll = maxroll); VARFP(maxrolleffect, 0, ROLLEFFDEF, ROLLEFFMAX, player1->maxrolleffect = maxrolleffect); VARP(maxrollremote, 0, ROLLMOVDEF + ROLLEFFDEF, ROLLMOVMAX + ROLLEFFMAX); -//VAR(recoilbackfade, 0, 100, 1000); void resizephysent(physent *pl, int moveres, int curtime, float min, float max) { @@ -734,12 +733,7 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime) pl->pitch += pl->pitchvel*(curtime/1000.0f)*pl->maxspeed*(pl->crouching ? 0.75f : 1.0f); pl->pitchvel *= fric-3; pl->pitchvel /= fric; - /*extern int recoiltest; - if(recoiltest) - { - if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= recoilbackfade/100.0f; // slide back - } - else*/ if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= ((playerent *)pl)->weaponsel->info.recoilbackfade/100.0f; // slide back + if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= ((playerent *)pl)->weaponsel->info.recoilbackfade/100.0f; // slide back if(pl->pitchvel) fixcamerarange(pl); // fix pitch if necessary } diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 1f31f874..a919f1ca 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -304,14 +304,14 @@ char *filtertext(char *dst, const char *src, int flags, int len) filename = (flags & FTXT_FILENAME) != 0, // strict a-z, 0-9 and "-_.()" (also translates "[]" and "{}" to "()"), removes everything between '<' and '>' allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME or FTXT_MAPNAME mapname = (flags & FTXT_MAPNAME) != 0, // only allows lowercase chars, digits, '_', '-' and '.'; probably should be used in combination with TOLOWER - cropwhite = (flags & FTXT_CROPWHITE) != 0, // removes leading and trailing whitespace + cropwhitelead = (flags & FTXT_CROPWHITE_LEAD) != 0, // removes leading whitespace + cropwhitetrail = (flags & FTXT_CROPWHITE_TRAIL) != 0, // removes trailing whitespace pass = false; if(leet || mapname) nocolor = true; #ifdef FILENAMESALLLOWERCASE if(filename) tolow = true; #endif bool trans = toupp || tolow || leet || filename || fillblanks; - bool leadingwhite = cropwhite; char *lastwhite = NULL; bool insidepointybrackets = false; for(int c = *src; c; c = *++src) @@ -369,7 +369,7 @@ char *filtertext(char *dst, const char *src, int flags, int len) if(mapname && !isalnum(c) && !strchr("_-.", c) && !(allowslash && strchr("/\\", c))) continue; if(isspace(c)) { - if(leadingwhite) continue; + if(cropwhitelead) continue; if(nowhite && !((c == ' ' && allowblanks) || (c == '\n' && allownl)) && !pass) continue; if(!lastwhite) lastwhite = dst; } @@ -378,11 +378,11 @@ char *filtertext(char *dst, const char *src, int flags, int len) lastwhite = NULL; if(!pass && !isprint(c)) continue; } - leadingwhite = false; + cropwhitelead = false; *dst++ = c; if(!--len || !*src) break; } - if(cropwhite && lastwhite) *lastwhite = '\0'; + if(cropwhitetrail && lastwhite) *lastwhite = '\0'; *dst = '\0'; return res; } @@ -583,12 +583,12 @@ static const int msgsizes[] = // size inclusive message token, 0 f SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSC2, 0, SV_POSC3, 0, SV_POSC4, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, - SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_AUTHT, 0, SV_AUTHREQ, 0, SV_AUTHTRY, 0, SV_AUTHANS, 0, SV_AUTHCHAL, 0, + SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_GIBDIED, 5, SV_DIED, 5, SV_GIBDAMAGE, 7, SV_DAMAGE, 7, SV_HITPUSH, 6, SV_SHOTFX, 6, SV_THROWNADE, 8, SV_TRYSPAWN, 1, SV_SPAWNSTATE, 23, SV_SPAWN, 3, SV_SPAWNDENY, 2, SV_FORCEDEATH, 2, SV_RESUME, 0, SV_DISCSCORES, 0, SV_TIMEUP, 3, SV_EDITENT, 13, SV_ITEMACC, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, - SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_EDITMODE, 2, SV_EDITXY, 8, SV_EDITARCH, 56, SV_EDITBLOCK, 0, SV_EDITD, 6, SV_EDITE, 6, SV_NEWMAP, 2, SV_SENDMAP, 0, SV_RECVMAP, 1, SV_REMOVEMAP, 0, SV_SERVMSG, 0, SV_SERVMSGVERB, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, @@ -597,13 +597,13 @@ static const int msgsizes[] = // size inclusive message token, 0 f SV_SETADMIN, 0, SV_SERVOPINFO, 3, SV_CALLVOTE, 0, SV_CALLVOTESUC, 1, SV_CALLVOTEERR, 2, SV_VOTE, 2, SV_VOTERESULT, 2, SV_SETTEAM, 3, SV_TEAMDENY, 2, SV_SERVERMODE, 2, - SV_IPLIST, 0, + SV_IPLIST, 0, SV_SPECTCN, 2, SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0, SV_DEMOPLAYBACK, 3, SV_CONNECT, 0, SV_SWITCHNAME, 0, SV_SWITCHSKIN, 0, SV_SWITCHTEAM, 0, SV_CLIENT, 0, SV_EXTENSION, 0, - SV_MAPIDENT, 3, SV_HUDEXTRAS, 2, SV_POINTS, 0, SV_DEMOCHECKSUM, 0, SV_DEMOSIGNATURE, 0, + SV_MAPIDENT, 3, SV_DEMOCHECKSUM, 0, SV_DEMOSIGNATURE, 0, -1 }; diff --git a/source/src/protocol.h b/source/src/protocol.h index a02c9a7a..d50e11e4 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -28,12 +28,12 @@ enum { SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSC2, SV_POSC3, SV_POSC4, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, - SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_AUTHT, SV_AUTHREQ, SV_AUTHTRY, SV_AUTHANS, SV_AUTHCHAL, + SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME, SV_DISCSCORES, SV_TIMEUP, SV_EDITENT, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, - SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, + SV_PING, SV_PONG, SV_CLIENTPING, SV_EDITMODE, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP, SV_SENDMAP, SV_RECVMAP, SV_REMOVEMAP, SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, @@ -42,13 +42,13 @@ enum SV_SETADMIN, SV_SERVOPINFO, SV_CALLVOTE, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTE, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, - SV_IPLIST, + SV_IPLIST, SV_SPECTCN, SV_LISTDEMOS, SV_SENDDEMOLIST, SV_GETDEMO, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_CONNECT, SV_SWITCHNAME, SV_SWITCHSKIN, SV_SWITCHTEAM, SV_CLIENT, SV_EXTENSION, - SV_MAPIDENT, SV_HUDEXTRAS, SV_POINTS, SV_DEMOCHECKSUM, SV_DEMOSIGNATURE, + SV_MAPIDENT, SV_DEMOCHECKSUM, SV_DEMOSIGNATURE, SV_NUM }; @@ -69,7 +69,6 @@ enum { FA_PICKUP = 0, FA_STEAL, FA_DROP, FA_LOST, FA_RETURN, FA_SCORE, FA_NUM, F enum { FM_PICKUP = 0, FM_DROP, FM_LOST, FM_RETURN, FM_SCORE, FM_KTFSCORE, FM_SCOREFAIL, FM_RESET, FM_NUM }; enum { FTR_INFO = 0, FTR_PLAYERWISH, FTR_AUTOTEAM, FTR_SILENTFORCE, FTR_NUM }; // forceteam-reasons enum { SP_OK = 0, SP_REFILLMATCH, SP_OK_NUM, SP_WRONGMAP, SP_SPECT, SP_NUM }; // spawn permission ( &privstr, vector &pubstr); -extern bool hashstring(const char *str, char *result, int maxlen); -extern void answerchallenge(const char *privstr, const char *challenge, vector &answerstr); -extern void *parsepubkey(const char *pubstr); -extern void freepubkey(void *pubkey); -extern void *genchallenge(void *pubkey, const void *seed, int seedlen, vector &challengestr); -extern void freechallenge(void *answer); -extern bool checkchallenge(const char *answerstr, void *correct); -#endif // console extern void conoutf(const char *s, ...) PRINTFARGS(1, 2); diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index 56d48617..59bcd6c7 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -724,51 +724,6 @@ inline char rangecolor(int val, const char *colors, int thres1, int thres2, int return colors[3]; } -void drawmedals(float x, float y, int col, int row, Texture *tex) -{ - if(tex) - { - glPushAttrib(GL_COLOR_BUFFER_BIT); - glDisable(GL_BLEND); - drawicon(tex, x, y, 120, col, row, 1/4.0f); - glPopAttrib(); - } -} -const char *medal_str[] = -{ - "Best Fragger", "Dude that dies a lot" -}; //just some medals string tests, nothing serious -extern bool medals_arrived; -extern medalsst a_medals[END_MDS]; -void drawscores() -{ - static float time=0; - if(!medals_arrived) {time=0; return;} else if(time > 5){time=0; medals_arrived=0;} - static Texture *tex = NULL; - if(!tex) tex = textureload("packages/misc/nice_medals.png", 4); - time+=((float)(curtime))/1000; - float vw=VIRTW*7/4,vh=VIRTH*7/4; - glPushAttrib(GL_COLOR_BUFFER_BIT); - glLoadIdentity(); - glOrtho(0, vw, vh, 0, -1, 1); - int left = vw/4, top = vh/4; - blendbox(left, top, left*3, top*3, true, -1); - top+=10;left+=10;const float txtdx=160,txtdy=30,medalsdy=130; - glColor4f(1,1,1,1); - float desttime=0; - loopi(END_MDS) { - if(a_medals[i].assigned) { - desttime+=0.3; - if(time < desttime) continue; - drawmedals(left, top, 0, 0, tex); - playerent *mpl = getclient(a_medals[i].cn); - draw_textf("%s %s: %d", left+txtdx, top+txtdy, medal_str[i], mpl->name, a_medals[i].item); top+=medalsdy; - } - } - - glPopAttrib(); -} - string enginestateinfo = ""; COMMANDF(getEngineState, "", () { result(enginestateinfo); }); @@ -1025,7 +980,6 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat glPopMatrix(); } - drawscores(); if(!hidespecthud && spectating && player1->spectatemode!=SM_DEATHCAM) { glLoadIdentity(); diff --git a/source/src/server.cpp b/source/src/server.cpp index 4928c6dd..88eb4bac 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -99,7 +99,9 @@ serverinfofile serverinfomotd; bool isdedicated = false; ENetHost *serverhost = NULL; -int laststatus = 0, servmillis = 0, servclock = 0; +int laststatus = 0, // last per-minute log status + servmillis = 0, // millis since server start + servclock = 0; // unixtime in minutes vector clients; vector worldstates; @@ -642,6 +644,7 @@ void sendf(int cn, int chan, const char *format, ...) { case 'x': // exclude client from getting this message exclude = va_arg(args, int); + ASSERT(cn == -1); break; case 'v': // integer array of variable length @@ -693,42 +696,6 @@ void sendf(int cn, int chan, const char *format, ...) sendpacket(cn, chan, p.finalize(), exclude); } -void sendextras() -{ - if (sg->gamemillis < sg->nextsendscore ) return; - int count = 0, list[MAXCLIENTS]; - loopv(clients) - { - client &c = *clients[i]; - if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < sg->gamemillis) ) continue; - if ( c.md.combosend ) - { - sendf(c.clientnum, 1, "ri2", SV_HUDEXTRAS, min(c.md.combo,c.md.combofrags)-1 + HE_COMBO); - c.md.combosend = false; - } - if ( c.md.dpt ) - { - list[count] = i; - count++; - } - } - sg->nextsendscore = sg->gamemillis + 160; // about 4 cicles - if ( !count ) return; - - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_POINTS); - putint(p,count); - int *v = list; - loopi(count) - { - client &c = *clients[*v]; - putint(p,c.clientnum); putint(p,c.md.dpt); c.md.updated = false; c.md.upmillis = c.md.dpt = 0; - v++; - } - - sendpacket(-1, 1, p.finalize()); -} - void sendservmsg(const char *msg, int cn = -1) { sendf(cn, 1, "ris", SV_SERVMSG, msg); @@ -1220,8 +1187,7 @@ void setupdemoplayback() lilswap(&hdr.version, 1); lilswap(&hdr.protocol, 1); if(hdr.version!=DEMO_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.versionstate.flagscore += score; sendf(-1, 1, "riii", SV_FLAGCNT, actor, c->state.flagscore); - if (m_teammode) computeteamwork(c->team, c->clientnum); /** WIP */ } if(valid_client(actor)) { @@ -1489,7 +1454,6 @@ void flagaction(int flag, int action, int actor) mlog(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); break; } - flagpoints (&c, message); } else { @@ -1768,12 +1732,13 @@ void sendteamtext(char *text, int sender, int msgtype) ENetPacket *packet = p.finalize(); recordpacket(1, packet); int &st = clients[sender]->team; - loopv(clients) if(i!=sender) + loopv(clients) { int &rt = clients[i]->team; if((rt == TEAM_SPECT && clients[i]->role == CR_ADMIN) || // spect-admin reads all (team_isactive(st) && st == team_group(rt)) || // player to own team + own spects - (team_isspect(st) && team_isspect(rt))) // spectator to other spectators + (team_isspect(st) && team_isspect(rt)) || // spectator to other spectators + (i == sender)) // "local" echo sendpacket(i, 1, packet); } } @@ -1888,7 +1853,6 @@ void checkitemspawns(int diff) void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0)) { - if (!m_demo && !m_coop && !validdamage(target, actor, damage, gun, gib)) return; if ( m_arena && gun == GUN_GRENADE && sg->arenaroundstartmillis + 2000 > sg->gamemillis && target != actor ) return; clientstate &ts = target->state; ts.dodamage(damage, gun); @@ -1898,7 +1862,6 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, sendf(-1, 1, "ri7", gib ? SV_GIBDAMAGE : SV_DAMAGE, target->clientnum, actor->clientnum, gun, damage, ts.armour, ts.health); if(target!=actor) { - checkcombo (target, actor, damage, gun); if(!hitpush.iszero()) { vec v(hitpush); @@ -1913,7 +1876,6 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, int targethasflag = clienthasflag(target->clientnum); bool tk = false, suic = false; target->state.deaths++; - checkfrag(target, actor, gun, gib); if(target!=actor) { if(!isteam(target->team, actor->team)) actor->state.frags += gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN ? 2 : 1; @@ -2142,103 +2104,6 @@ void shuffleteams(int ftr = FTR_AUTOTEAM) } } -bool balanceteams(int ftr) // pro vs noobs never more -{ - if(sg->mastermode != MM_OPEN || totalclients < 3 ) return true; - int tsize[2] = {0, 0}, tscore[2] = {0, 0}; - int totalscore = 0, nplayers = 0; - int flagmult = (m_ctf ? 50 : (m_htf ? 25 : 12)); - - loopv(clients) if(clients[i]->type!=ST_EMPTY) - { - client *c = clients[i]; - if(c->isauthed && team_isactive(c->team)) - { - int time = servmillis - c->connectmillis + 5000; - if(time > sg->gamemillis ) time = sg->gamemillis + 5000; - tsize[c->team]++; - // effective score per minute, thanks to wtfthisgame for the nice idea - // in a normal game, normal players will do 500 points in 10 minutes - c->eff_score = c->state.points * 60 * 1000 / time + c->state.points / 6 + c->state.flagscore * flagmult; - tscore[c->team] += c->eff_score; - nplayers++; - totalscore += c->state.points; - } - } - - int h = 0, l = 1; - if ( tscore[1] > tscore[0] ) { h = 1; l = 0; } - if ( 2 * tscore[h] < 3 * tscore[l] || totalscore < nplayers * 100 ) return true; - if ( tscore[h] > 3 * tscore[l] && tscore[h] > 150 * nplayers ) - { -// sendf(-1, 1, "ri2", SV_SERVERMODE, sendservermode(false) | AT_SHUFFLE); - shuffleteams(); - return true; - } - - float diffscore = tscore[h] - tscore[l]; - - int besth = 0, hid = -1; - int bestdiff = 0, bestpair[2] = {-1, -1}; - if ( tsize[h] - tsize[l] > 0 ) // the h team has more players, so we will force only one player - { - loopv(clients) if( clients[i]->type!=ST_EMPTY ) - { - client *c = clients[i]; // loop for h - // client from the h team, not forced in this game, and without the flag - if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 ) - { - // do not exchange in the way that weaker team becomes the stronger or the change is less than 20% effective - if ( 2 * c->eff_score <= diffscore && 10 * c->eff_score >= diffscore && c->eff_score > besth ) - { - besth = c->eff_score; - hid = i; - } - } - } - if ( hid >= 0 ) - { - updateclientteam(hid, l, ftr); - clients[hid]->at3_lastforce = sg->gamemillis; - clients[hid]->state.forced = true; - return true; - } - } else { // the h score team has less or the same player number, so, lets exchange - loopv(clients) if(clients[i]->type!=ST_EMPTY) - { - client *c = clients[i]; // loop for h - if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 ) - { - loopvj(clients) if(clients[j]->type!=ST_EMPTY && j != i ) - { - client *cj = clients[j]; // loop for l - if( cj->isauthed && cj->team == l && !cj->state.forced && clienthasflag(j) < 0 ) - { - int pairdiff = 2 * (c->eff_score - cj->eff_score); - if ( pairdiff <= diffscore && 5 * pairdiff >= diffscore && pairdiff > bestdiff ) - { - bestdiff = pairdiff; - bestpair[h] = i; - bestpair[l] = j; - } - } - } - } - } - if ( bestpair[h] >= 0 && bestpair[l] >= 0 ) - { - updateclientteam(bestpair[h], l, ftr); - updateclientteam(bestpair[l], h, ftr); - clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = sg->gamemillis; - clients[bestpair[h]]->state.forced = clients[bestpair[l]]->state.forced = true; - return true; - } - } - return false; -} - -int lastbalance = 0, waitbalance = 2 * 60 * 1000; - bool refillteams(bool now, int ftr) // force only minimal amounts of players { if(sg->mastermode == MM_MATCH) return false; @@ -2310,21 +2175,6 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players } if(diffnum < 2) { - if ( ( sg->gamemillis - lastbalance ) > waitbalance && ( sg->gamelimit - sg->gamemillis ) > 4*60*1000 ) - { - if ( balanceteams (ftr) ) - { - waitbalance = 2 * 60 * 1000 + sg->gamemillis / 3; - switched = true; - } - else waitbalance = 20 * 1000; - lastbalance = sg->gamemillis; - } - else if ( lastbalance > sg->gamemillis ) - { - lastbalance = 0; - waitbalance = 2 * 60 * 1000; - } lasttime_eventeams = sg->gamemillis; } return switched; @@ -2830,71 +2680,6 @@ void disconnect_client(int n, int reason) if(*scoresaved && sg->mastermode == MM_MATCH) senddisconnectedscores(-1); } -// for AUTH: WIP - -client *findauth(uint id) -{ - loopv(clients) if(clients[i]->authreq == id) return clients[i]; - return NULL; -} - -void authfailed(uint id) -{ - client *cl = findauth(id); - if(!cl) return; - cl->authreq = 0; -} - -void authsucceeded(uint id) -{ - client *cl = findauth(id); - if(!cl) return; - cl->authreq = 0; - mlog(ACLOG_INFO, "player authenticated: %s", cl->name); - defformatstring(auth4u)("player authenticated: %s", cl->name); - sendf(-1, 1, "ris", SV_SERVMSG, auth4u); - //setmaster(cl, true, "", ci->authname);//TODO? compare to sauerbraten -} - -void authchallenged(uint id, const char *val) -{ - client *cl = findauth(id); - if(!cl) return; - sendf(cl->clientnum, 1, "risis", SV_AUTHCHAL, "", id, val); -} - -uint nextauthreq = 0; - -void tryauth(client *cl, const char *user) -{ - extern bool requestmasterf(const char *fmt, ...); - if(!nextauthreq) nextauthreq = 1; - cl->authreq = nextauthreq++; - filtertext(cl->authname, user, FTXT__AUTH, 100); - if(!requestmasterf("reqauth %u %s\n", cl->authreq, cl->authname)) - { - cl->authreq = 0; - sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server"); - } -} - -void answerchallenge(client *cl, uint id, char *val) -{ - if(cl->authreq != id) return; - extern bool requestmasterf(const char *fmt, ...); - for(char *s = val; *s; s++) - { - if(!isxdigit(*s)) { *s = '\0'; break; } - } - if(!requestmasterf("confauth %u %s\n", id, val)) - { - cl->authreq = 0; - sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server"); - } -} - -// :for AUTH - void sendiplist(int receiver, int cn) { if(!valid_client(receiver)) return; @@ -3079,7 +2864,7 @@ int checktype(int type, client *cl) if(type < 0 || type >= SV_NUM) return -1; // server only messages static int servtypes[] = { SV_SERVINFO, SV_WELCOME, SV_INITCLIENT, SV_POSN, SV_CDIS, SV_GIBDIED, SV_DIED, - SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_AUTHREQ, SV_AUTHCHAL, + SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_SPAWNSTATE, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME, SV_DISCSCORES, SV_TIMEUP, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG, SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, @@ -3087,7 +2872,7 @@ int checktype(int type, client *cl) SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, - SV_CLIENT, SV_HUDEXTRAS, SV_POINTS, SV_DEMOCHECKSUM }; + SV_CLIENT, SV_DEMOCHECKSUM }; // only allow edit messages in coop-edit mode static int edittypes[] = { SV_EDITENT, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP }; if(cl) @@ -3394,14 +3179,12 @@ void process(ENetPacket *packet, int sender, int chan) else protocoldebug(false); #endif - type = checkmessage(cl,type); switch(type) { case SV_TEAMTEXTME: case SV_TEAMTEXT: getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(*text) { bool canspeech = forbiddenlist.canspeech(text); @@ -3434,7 +3217,6 @@ void process(ENetPacket *packet, int sender, int chan) int mid1 = curmsg, mid2 = p.length(); getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(*text) { bool canspeech = forbiddenlist.canspeech(text); @@ -3445,6 +3227,8 @@ void process(ENetPacket *packet, int sender, int chan) mlog(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); QUEUE_STR(text); + ASSERT(SV_TEXT < 126 && SV_TEXTME < 126); + sendf(sender, 1, "riiuis", SV_CLIENT, sender, (int) strlen(text) + 2, type, text); // "local" echo } else // spect chat { @@ -3476,7 +3260,6 @@ void process(ENetPacket *packet, int sender, int chan) int targ = getint(p); getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(!valid_client(targ)) break; client *target = clients[targ]; @@ -3488,7 +3271,11 @@ void process(ENetPacket *packet, int sender, int chan) { bool allowed = !(sg->mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); mlog(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); - if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text); + if(allowed) + { + sendf(cl->clientnum, 1, "riiis", SV_TEXTPRIVATE, cl->clientnum, target->clientnum, text); // "local" echo + sendf(target->clientnum, 1, "riiis", SV_TEXTPRIVATE, cl->clientnum, -1, text); + } } else { @@ -3521,7 +3308,6 @@ void process(ENetPacket *packet, int sender, int chan) if ( cl->spam < 0 ) cl->spam = 0; cl->lastvc = servmillis; // register if ( cl->spam > 4 ) { cl->mute = servmillis + 10000; break; } // 5 vcs in less than 20 seconds... shut up please - if ( m_teammode ) checkteamplay(s,sender); // finally here we check the teamplay comm if ( type == SV_VOICECOM ) { QUEUE_MSG; } else sendvoicecomteam(s, sender); } @@ -3582,7 +3368,6 @@ void process(ENetPacket *packet, int sender, int chan) { int gunselect = getint(p); if(!valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; - if(!m_demo && !m_coop) checkweapon(type,gunselect); cl->state.gunselect = gunselect; QUEUE_MSG; break; @@ -3701,6 +3486,11 @@ void process(ENetPacket *packet, int sender, int chan) break; } + case SV_SPECTCN: + cl->spectcn = getint(p); + QUEUE_MSG; // FIXME: demo only + break; + case SV_SUICIDE: { gameevent &suicide = cl->addevent(); @@ -3728,21 +3518,19 @@ void process(ENetPacket *packet, int sender, int chan) loopk(3) { shot.shot.from[k] = cl->state.o.v[k] + ( k == 2 ? (((cl->f>>7)&1)?2.2f:4.2f) : 0); } loopk(3) { float v = getint(p)/DMF; shot.shot.to[k] = ((k<2 && v<0.0f)?0.0f:v); } int hits = getint(p); - int tcn = -1; loopk(hits) { gameevent &hit = cl->addevent(); hit.type = GE_HIT; - tcn = hit.hit.target = getint(p); + hit.hit.target = getint(p); hit.hit.lifesequence = getint(p); hit.hit.info = getint(p); loopk(3) hit.hit.dir[k] = getint(p)/DNF; } - if(!m_demo && !m_coop) checkshoot(sender, shot, hits, tcn); break; } - case SV_EXPLODE: // Brahma says: FIXME handle explosion by location and deal damage from server + case SV_EXPLODE: { gameevent &exp = cl->addevent(); exp.type = GE_EXPLODE; @@ -3779,37 +3567,6 @@ void process(ENetPacket *packet, int sender, int chan) break; } - // for AUTH: - - case SV_AUTHTRY: - { - string desc, name; - getstring(desc, p, sizeof(desc)); // unused for now - getstring(name, p, sizeof(name)); - if(!desc[0]) tryauth(cl, name); - break; - } - - case SV_AUTHANS: - { - string desc, ans; - getstring(desc, p, sizeof(desc)); // unused for now - uint id = (uint)getint(p); - getstring(ans, p, sizeof(ans)); - if(!desc[0]) answerchallenge(cl, id, ans); - break; - } - - - case SV_AUTHT: - { -/* int n = getint(p); - loopi(n) getint(p);*/ -// if (cl) disconnect_client(cl->clientnum, DISC_TAGT); // remove this in the future, when auth is complete - break; - } - // :for AUTH - case SV_PING: sendf(sender, 1, "ii", SV_PONG, getint(p)); break; @@ -3850,7 +3607,6 @@ void process(ENetPacket *packet, int sender, int chan) cl->position.setsize(0); while(curmsgposition.add(p.buf[curmsg++]); } - if(!m_demo && !m_coop) checkmove(cl); break; } @@ -3894,7 +3650,6 @@ void process(ENetPacket *packet, int sender, int chan) cl->position.setsize(0); while(curmsgposition.add(p.buf[curmsg++]); } - if(!m_demo && !m_coop) checkmove(cl); break; } @@ -4098,7 +3853,6 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p); strncpy(vi->text,text,128); filtertext(text, text, FTXT__KICKBANREASON); - trimtrailingwhitespace(text); vi->action = new kickaction(cn2boot, newstring(text, 128)); vi->boot = 1; break; @@ -4109,7 +3863,6 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p); strncpy(vi->text,text,128); filtertext(text, text, FTXT__KICKBANREASON); - trimtrailingwhitespace(text); vi->action = new banaction(cn2boot, newstring(text, 128)); vi->boot = 2; break; @@ -4568,7 +4321,6 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog if(m_arena) arenacheck(); else if(m_autospawn) autospawncheck(); - sendextras(); if(scl.afk_limit && sg->mastermode == MM_OPEN && next_afk_check < servmillis && sg->gamemillis > 20 * 1000) check_afk(); } @@ -4863,13 +4615,8 @@ void localconnect() void processmasterinput(const char *cmd, int cmdlen, const char *args) { -// AUTH WiP - uint id; string val; - if(sscanf(cmd, "failauth %u", &id) == 1) authfailed(id); - else if(sscanf(cmd, "succauth %u", &id) == 1) authsucceeded(id); - else if(sscanf(cmd, "chalauth %u %s", &id, val) == 2) authchallenged(id, val); - else if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans(); + if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans(); else if(sscanf(cmd, "addgban %s", val) == 1) addgban(val); } diff --git a/source/src/server.h b/source/src/server.h index c9f34d63..a9a29b9f 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -203,26 +203,6 @@ struct savedscore } }; -struct medals -{ - int dpt, lasthit, lastgun, ncovers, nhs; - int combohits, combo, combofrags, combotime, combodamage, ncombos; - int ask, askmillis, linked, linkmillis, linkreason, upmillis, flagmillis; - int totalhits, totalshots; - bool updated, combosend; - vec pos, flagpos; - void reset() - { - dpt = lasthit = lastgun = ncovers = nhs = 0; - combohits = combo = combofrags = combotime = combodamage = ncombos = 0; - askmillis = linkmillis = upmillis = flagmillis = 0; - linkreason = linked = ask = -1; - totalhits = totalshots = 0; - updated = combosend = false; - pos = flagpos = vec(-1e10f, -1e10f, -1e10f); - } -}; - #define VITANAMEHISTLEN 4 #define VITAIPHISTLEN 4 #define VITACCHISTLEN 4 @@ -265,7 +245,7 @@ struct client // server side version of "dynent" type int skin[2]; int vote; int role; - int connectmillis, lmillis, ldt, spj; + int connectmillis, ldt, spj; int mute, spam, lastvc; // server side voice comm spam control int acversion, acbuildtype; bool isauthed; // for passworded servers @@ -289,25 +269,15 @@ struct client // server side version of "dynent" type bool autospawn; int salt; string pwd; - uint authreq; // for AUTH - string authname; // for AUTH + int spectcn; int mapcollisions, farpickups; enet_uint32 bottomRTT; - medals md; bool upspawnp; int lag; vec spawnp; int nvotes; int input, inputmillis; int ffire, wn, f, t, yaw, pitch; - int yb, pb, oy, op, lda, ldda, fam; - int nt[10], np, lp, ls, lsm, ld, nd, nlt, lem, led; - vec cp[10], dp[10], d0, lv, lt, le; - float dda, tr, sda; - int ps, ph, tcn, bdt, pws; - float pr; - int yls, pls, tls; - int bs, bt, blg, bp; gameevent &addevent() { @@ -334,22 +304,12 @@ struct client // server side version of "dynent" type lastevent = 0; at3_lastforce = eff_score = 0; mapcollisions = farpickups = 0; - md.reset(); upspawnp = false; lag = 0; spawnp = vec(-1e10f, -1e10f, -1e10f); - lmillis = ldt = spj = 0; + ldt = spj = 0; ffire = 0; f = yaw = pitch = t = 0; - yb = pb = oy = op = lda = ldda = fam = 0; - np = lp = ls = lsm = ld = nd = nlt = lem = led = 0; - d0 = lv = lt = le = vec(0,0,0); - loopi(10) { cp[i] = dp[i] = vec(0,0,0); nt[i] = 0; } - dda = tr = sda = 0; - ps = ph = bdt = pws = 0; - tcn = -1; - pr = 0.0f; - yls = pls = tls = 0; } void reset() @@ -373,13 +333,10 @@ struct client // server side version of "dynent" type lastsaytext[0] = '\0'; saychars = 0; spawnindex = -1; - authreq = 0; // for AUTH mapchange(); freshgame = false; // don't spawn into running games mute = spam = lastvc = badspeech = badmillis = nvotes = 0; input = inputmillis = 0; - wn = -1; - bs = bt = blg = bp = 0; } void zap() @@ -460,12 +417,12 @@ const char *messagenames[SV_NUM] = "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSC2", "SV_POSC3", "SV_POSC4", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", - "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL", + "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE", "SV_TRYSPAWN", "SV_SPAWNSTATE", "SV_SPAWN", "SV_SPAWNDENY", "SV_FORCEDEATH", "SV_RESUME", "SV_DISCSCORES", "SV_TIMEUP", "SV_EDITENT", "SV_ITEMACC", "SV_MAPCHANGE", "SV_ITEMSPAWN", "SV_ITEMPICKUP", - "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_GAMEMODE", + "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_EDITMODE", "SV_EDITXY", "SV_EDITARCH", "SV_EDITBLOCK", "SV_EDITD", "SV_EDITE", "SV_NEWMAP", "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP", "SV_SERVMSG", "SV_SERVMSGVERB", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", @@ -474,13 +431,13 @@ const char *messagenames[SV_NUM] = "SV_SETADMIN", "SV_SERVOPINFO", "SV_CALLVOTE", "SV_CALLVOTESUC", "SV_CALLVOTEERR", "SV_VOTE", "SV_VOTERESULT", "SV_SETTEAM", "SV_TEAMDENY", "SV_SERVERMODE", - "SV_IPLIST", + "SV_IPLIST", "SV_SPECTCN", "SV_LISTDEMOS", "SV_SENDDEMOLIST", "SV_GETDEMO", "SV_SENDDEMO", "SV_DEMOPLAYBACK", "SV_CONNECT", "SV_SWITCHNAME", "SV_SWITCHSKIN", "SV_SWITCHTEAM", "SV_CLIENT", "SV_EXTENSION", - "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE", + "SV_MAPIDENT", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE", }; const char *entnames[] = diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 0562bee8..3c52a1ab 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1305,8 +1305,6 @@ void clearservers() #define RETRIEVELIMIT 5000 -bool cllock = false, clfail = false; - int progress_callback_retrieveservers(void *data, float progress) { if(progress < 0) show_out_of_renderloop_progress(progress + 1.0f, "waiting for response (esc to abort)"); @@ -1335,12 +1333,10 @@ void retrieveservers(vector &data) if(got < 0 || h.response != 200) data.setsize(0); h.outvec = NULL; // must not be cleaned up by httpget if(data.length()) data.add('\0'); - clfail = false; } else { conoutf("failed to resolve host %s", mastername); - clfail = true; } } else @@ -1349,10 +1345,8 @@ void retrieveservers(vector &data) if(sock == ENET_SOCKET_NULL) { conoutf("Master server is not replying."); - clfail = true; return; } - clfail = false; defformatstring(text)("retrieving servers from %s:%d... (esc to abort)", mastername, masterport); show_out_of_renderloop_progress(0, text); int starttime = SDL_GetTicks(), timeout = 0; @@ -1406,17 +1400,12 @@ VAR(msctrl, 1, 1, INT_MAX); void updatefrommaster(int *force) { static int lastupdate = 0; - if(lastupdate==0) cllock = true; if(!*force && lastupdate && totalmillis-lastupdate data; retrieveservers(data); - if(data.empty()) - { - if (!clfail) conoutf("Master server is not replying. \f1Get more information at http://masterserver.cubers.net/"); - cllock = !clfail; - } + if(data.empty()) conoutf("Master server is not replying. \f1Get more information at http://masterserver.cubers.net/"); else { // preserve currently connected server from deletion @@ -1425,12 +1414,10 @@ void updatefrommaster(int *force) if(curserver) copystring(curname, curserver->name); clearservers(); - if(!strncmp(data.getbuf(), "addserver", 9)) cllock = false; // the ms could reply other thing... but currently, this is useless - if(!cllock ) - { - execute(data.getbuf()); - if(curserver) addserver(curname, curserver->port, curserver->msweight); - } + + execute(data.getbuf()); + if(curserver) addserver(curname, curserver->port, curserver->msweight); + lastupdate = totalmillis; } } diff --git a/source/src/serverchecks.h b/source/src/serverchecks.h index 2fcd2414..3feed894 100644 --- a/source/src/serverchecks.h +++ b/source/src/serverchecks.h @@ -21,35 +21,12 @@ inline void checkclientpos(client *cl) } } -#define POW2XY(A,B) (pow2(A.x-B.x)+pow2(A.y-B.y)) - extern inline void addban(client *cl, int reason, int type = BAN_AUTO); #ifdef ACAC #include "anticheat.h" #endif -#define MINELINE 50 - -/** -This part is related to medals system. WIP - */ - -const char * medal_messages[] = { "defended the flag", "covered the flag stealer", "defended the dropped flag", "covered the flag keeper", "covered teammate" }; -enum { CTFLDEF, CTFLCOV, HTFLDEF, HTFLCOV, COVER, MEDALMESSAGENUM }; -inline void print_medal_messages(client *c, int n) -{ - if (n<0 || n>=MEDALMESSAGENUM) return; - xlog(ACLOG_VERBOSE, "[%s] %s %s", c->hostname, c->name, medal_messages[n]); -} - -inline void addpt(client *c, int points, int n = -1) { - c->state.points += points; - c->md.dpt += points; - c->md.updated = true; - c->md.upmillis = sg->gamemillis + 240; // about 2 AR shots - print_medal_messages(c,n); -} /** cnumber is the number of players in the game, at a max value of 12 */ #define CTFPICKPT cnumber // player picked the flag (ctf) @@ -81,353 +58,13 @@ inline void addpt(client *c, int points, int n = -1) { #define TKPT -20 // player tks #define FLAGTKPT -2*(10+cnumber) // player tks the flag keeper/stealer -void flagpoints(client *c, int message) -{ - float distance = 0; - int cnumber = totalclients < 13 ? totalclients : 12; - switch (message) - { - case FM_PICKUP: - c->md.flagpos.x = c->state.o.x; - c->md.flagpos.y = c->state.o.y; - c->md.flagmillis = servmillis; - if (m_ctf) addpt(c, CTFPICKPT); - break; - case FM_DROP: - if (m_ctf) addpt(c, CTFDROPPT); - break; - case FM_LOST: - if (m_htf) addpt(c, HTFLOSTPT); - else if (m_ctf) { - distance = sqrt(POW2XY(c->state.o,c->md.flagpos)); - if (distance > 200) distance = 200; // ~200 is the distance between the flags in ac_depot - addpt(c, CTFLOSTPT); - } - break; - case FM_RETURN: - addpt(c, CTFRETURNPT); - break; - case FM_SCORE: - if (m_ctf) { - distance = sqrt(POW2XY(c->state.o,c->md.flagpos)); - if (distance > 200) distance = 200; -#ifdef ACAC - if ( validflagscore(distance,c) ) -#endif - addpt(c, CTFSCOREPT); - } else addpt(c, HTFSCOREPT); - break; - case FM_KTFSCORE: - addpt(c, KTFSCOREPT); - break; - default: - break; - } -} -inline int minhits2combo(int gun) -{ - switch (gun) - { - case GUN_SUBGUN: - case GUN_AKIMBO: - return 4; - case GUN_GRENADE: - case GUN_ASSAULT: - return 3; - default: - return 2; - } -} -void checkcombo(client *target, client *actor, int damage, int gun) -{ - int diffhittime = servmillis - actor->md.lasthit; - actor->md.lasthit = servmillis; - if ((gun == GUN_SHOTGUN || gun == GUN_GRENADE) && damage < 20) - { - actor->md.lastgun = gun; - return; - } - if ( diffhittime < 750 ) { - if ( gun == actor->md.lastgun ) - { - if ( diffhittime * 2 < guns[gun].attackdelay * 3 ) - { - actor->md.combohits++; - actor->md.combotime+=diffhittime; - actor->md.combodamage+=damage; - int mh2c = minhits2combo(gun); - if ( actor->md.combohits > mh2c && actor->md.combohits % mh2c == 1 ) - { - if (actor->md.combo < 5) actor->md.combo++; - actor->md.ncombos++; - } - } - } - else - { - switch (gun) - { - case GUN_KNIFE: - case GUN_PISTOL: - if ( guns[actor->md.lastgun].isauto ) break; - case GUN_SNIPER: - if ( actor->md.lastgun > GUN_PISTOL ) break; - default: - actor->md.combohits++; - actor->md.combotime+=diffhittime; - actor->md.combodamage+=damage; - if (actor->md.combo < 5) actor->md.combo++; - actor->md.ncombos++; - break; - } - } - } - else - { - actor->md.combo = 0; - actor->md.combofrags = 0; - actor->md.combotime = 0; - actor->md.combodamage = 0; - actor->md.combohits = 0; - } - actor->md.lastgun = gun; -} -#define COVERDIST 2000 // about 45 cubes -#define REPLYDIST 8000 // about 90 cubes -float coverdist = COVERDIST; -int checkteamrequests(int sender) -{ - int dtime, besttime = -1; - int bestid = -1; - client *ant = clients[sender]; - loopv(clients) { - client *prot = clients[i]; - if ( i!=sender && prot->type != ST_EMPTY && prot->team == ant->team && - prot->state.state == CS_ALIVE && prot->md.askmillis > sg->gamemillis && prot->md.ask >= 0 ) { - float dist = POW2XY(ant->state.o,prot->state.o); - if ( dist < REPLYDIST && (dtime=prot->md.askmillis - sg->gamemillis) > besttime) { - bestid = i; - besttime = dtime; - } - } - } - if ( besttime >= 0 ) return bestid; - return -1; -} -/** WIP */ -void checkteamplay(int s, int sender) -{ - client *actor = clients[sender]; - - if ( actor->state.state != CS_ALIVE ) return; - switch(s){ - case S_IMONDEFENSE: // informs - actor->md.linkmillis = sg->gamemillis + 20000; - actor->md.linkreason = s; - break; - case S_COVERME: // demands - case S_STAYTOGETHER: - case S_STAYHERE: - actor->md.ask = s; - actor->md.askmillis = sg->gamemillis + 5000; - break; - case S_AFFIRMATIVE: // replies - case S_ALLRIGHTSIR: - case S_YES: - { - int id = checkteamrequests(sender); - if ( id >= 0 ) { - client *sgt = clients[id]; - actor->md.linked = id; - if ( actor->md.linkmillis < sg->gamemillis ) addpt(actor,REPLYPT); - actor->md.linkmillis = sg->gamemillis + 30000; - actor->md.linkreason = sgt->md.ask; - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, HE_NUM+id); - switch( actor->md.linkreason ) { // check demands - case S_STAYHERE: - actor->md.pos = sgt->state.o; - addpt(sgt,REPLYPT); - break; - } - } - break; - } - } -} - -void computeteamwork(int team, int exclude) // testing -{ - loopv(clients) - { - client *actor = clients[i]; - if ( i == exclude || actor->type == ST_EMPTY || actor->team != team || actor->state.state != CS_ALIVE || actor->md.linkmillis < sg->gamemillis ) continue; - vec position; - bool teamworkdone = false; - switch( actor->md.linkreason ) - { - case S_IMONDEFENSE: - position = actor->spawnp; - teamworkdone = true; - break; - case S_STAYTOGETHER: - if ( valid_client(actor->md.linked) ) position = clients[actor->md.linked]->state.o; - teamworkdone = true; - break; - case S_STAYHERE: - position = actor->md.pos; - teamworkdone = true; - break; - } - if ( teamworkdone ) - { - float dist = POW2XY(actor->state.o,position); - if (dist < COVERDIST) - { - addpt(actor,TWDONEPT); - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, HE_TEAMWORK); - } - } - } -} - -float a2c = 0, c2t = 0, a2t = 0; // distances: actor to covered, covered to target and actor to target - -inline bool testcover(int msg, int factor, client *actor) -{ - if ( a2c < coverdist && c2t < coverdist && a2t < coverdist ) - { - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, msg); - addpt(actor, factor); - actor->md.ncovers++; - return true; - } - return false; -} - -#define CALCCOVER(C) \ - a2c = POW2XY(actor->state.o,C);\ - c2t = POW2XY(C,target->state.o);\ - a2t = POW2XY(actor->state.o,target->state.o) - -bool validlink(client *actor, int cn) -{ - return actor->md.linked >= 0 && actor->md.linked == cn && sg->gamemillis < actor->md.linkmillis && valid_client(actor->md.linked); -} - -/** WIP */ -void checkcover(client *target, client *actor) -{ - int team = actor->team; - int oteam = team_opposite(team); - - bool covered = false; - int coverid = -1; - - int cnumber = totalclients < 13 ? totalclients : 12; - - if ( m_flags ) { - sflaginfo &f = sg->sflaginfos[team]; - sflaginfo &of = sg->sflaginfos[oteam]; - - if ( m_ctf ) - { - if ( f.state == CTFF_INBASE ) - { - CALCCOVER(f); - if ( testcover(HE_FLAGDEFENDED, CTFLDEFPT, actor) ) print_medal_messages(actor,CTFLDEF); - } - if ( of.state == CTFF_STOLEN && actor->clientnum != of.actor_cn ) - { - covered = true; coverid = of.actor_cn; - CALCCOVER(clients[of.actor_cn]->state.o); - if ( testcover(HE_FLAGCOVERED, CTFLCOVPT, actor) ) print_medal_messages(actor,CTFLCOV); - } - } - else if ( m_htf ) - { - if ( actor->clientnum != f.actor_cn ) - { - if ( f.state == CTFF_DROPPED ) - { - struct { short x, y; } nf; - nf.x = f.pos[0]; nf.y = f.pos[1]; - CALCCOVER(nf); - if ( testcover(HE_FLAGDEFENDED, HTFLDEFPT, actor) ) print_medal_messages(actor,HTFLDEF); - } - if ( f.state == CTFF_STOLEN ) - { - covered = true; coverid = f.actor_cn; - CALCCOVER(clients[f.actor_cn]->state.o); - if ( testcover(HE_FLAGCOVERED, HTFLCOVPT, actor) ) print_medal_messages(actor,HTFLCOV); - } - } - } - } - - if ( !(covered && actor->md.linked==coverid) && validlink(actor,actor->md.linked) ) - { - CALCCOVER(clients[actor->md.linked]->state.o); - if ( testcover(HE_COVER, COVERPT, actor) ) print_medal_messages(actor,COVER); - } - -} - -#undef CALCCOVER - -/** WiP */ -void checkfrag(client *target, client *actor, int gun, bool gib) -{ - int targethasflag = clienthasflag(target->clientnum); - int actorhasflag = clienthasflag(actor->clientnum); - int cnumber = totalclients < 13 ? totalclients : 12; - addpt(target,DEATHPT); - if(target!=actor) { - if(!isteam(target->team, actor->team)) { - - if (m_teammode) { - if(!m_flags) addpt(actor, TMBONUSPT); - else addpt(actor, FLBONUSPT); - - checkcover (target, actor); - if ( m_htf && actorhasflag >= 0 ) addpt(actor, HTFFRAGPT); - - if ( m_ctf && targethasflag >= 0 ) { - addpt(actor, CTFFRAGPT); - } - } - else addpt(actor, BONUSPT); - - if (gib && gun != GUN_GRENADE) { - if ( gun == GUN_SNIPER ) { - addpt(actor, HEADSHOTPT); - actor->md.nhs++; - } - else if ( gun == GUN_KNIFE ) addpt(actor, KNIFEPT); - else if ( gun == GUN_SHOTGUN ) addpt(actor, SHOTGPT); - } - else addpt(actor, FRAGPT); - - if ( actor->md.combo ) { - actor->md.combofrags++; - addpt(actor,COMBOPT); - actor->md.combosend = true; - } - - } else { - - if ( targethasflag >= 0 ) addpt(actor, FLAGTKPT); - else addpt(actor, TKPT); - - } - } -} int next_afk_check = 200; @@ -486,80 +123,3 @@ inline int check_pdist(client *c, float & dist) // pick up distance return 0; } -/** -If you read README.txt you must know that AC does not have cheat protection implemented. -However this file is the sketch to a very special kind of cheat detection tools in server side. - -This is not based in program tricks, i.e., encryption, secret bytes, nor monitoring/scanning tools. - -The idea behind these cheat detections is to check (or reproduce) the client data, and verify if -this data is expected or possible. Also, there is no need to check all clients all time, and -one coding this kind of check must pay a special attention to the lag effect and how it can -affect the data observed. This is not a trivial task, and probably it is the main reason why -such tools were never implemented. - -This part is here for compatibility purposes. -If you know nothing about these detections, please, just ignore it. -*/ - -inline void checkmove(client *cl) -{ - cl->ldt = sg->gamemillis - cl->lmillis; - cl->lmillis = sg->gamemillis; - if ( cl->ldt < 40 ) cl->ldt = 40; - cl->t += cl->ldt; - cl->spj = (( 7 * cl->spj + cl->ldt ) >> 3); - - if ( cl->input != cl->f ) - { - cl->input = cl->f; - cl->inputmillis = servmillis; - } - - -#ifdef ACAC - m_engine(cl); -#endif - - if ( !cl->upspawnp ) - { - cl->spawnp = cl->state.o; - cl->upspawnp = true; - cl->spj = cl->ldt = 40; - } - - return; -} - -inline void checkshoot(int & cn, gameevent & shot, int & hits, int & tcn) -{ -#ifdef ACAC - s_engine(cn, shot, hits, tcn); -#endif - return; -} - -inline void checkweapon(int & type, int & var) -{ -#ifdef ACAC - w_engine(type,var); -#endif - return; -} - -bool validdamage(client *&target, client *&actor, int &damage, int &gun, bool &gib) -{ -#ifdef ACAC - if (!d_engine(target, actor, damage, gun, gib)) return false; -#endif - return true; -} - -inline int checkmessage(client *c, int type) -{ -#ifdef ACAC - type = p_engine(c,type); -#endif - return type; -} - diff --git a/source/src/texture.cpp b/source/src/texture.cpp index 0b909727..8a121159 100644 --- a/source/src/texture.cpp +++ b/source/src/texture.cpp @@ -510,6 +510,7 @@ Texture *lookuptexture(int tex, Texture *failtex, bool trydl) { defformatstring(pname)("packages/textures/%s", path(s.name, true)); s.tex = textureload(pname, 0, true, true, s.scale, trydl); + if(s.tex != notexture) s.loaded = true; if(!trydl) { if(s.tex==notexture) s.tex = failtex; diff --git a/source/src/vertmodel.h b/source/src/vertmodel.h index 50fa0b16..ab88f49c 100644 --- a/source/src/vertmodel.h +++ b/source/src/vertmodel.h @@ -1256,7 +1256,6 @@ struct vertmodel : model void setskin(int tex = 0) { - //if(parts.length()!=1 || parts[0]->meshes.length()!=1) return; if(parts.length() < 1 || parts[0]->meshes.length() < 1) return; mesh &m = *parts[0]->meshes[0]; m.tex = tex; diff --git a/source/src/world.cpp b/source/src/world.cpp index d0a00ec8..f6eab2d5 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -692,9 +692,7 @@ bool empty_world(int factor, bool force) // main empty world creation routine if(clearmap && unsavededits) { xmapbackup("newmap", ""); unsavededits = 0; } if(copy) ow = blockcopy(shrink ? bs : be); - extern char *mlayout; DELETEA(world); - DELETEA(mlayout); setupworld(factor); loop(x,ssize) loop(y,ssize) @@ -1039,3 +1037,10 @@ void mapareacheck(char *what) // "vdelta" | "steepest" | "total" | "pprest" | "p COMMAND(mapareacheck, "s"); +void showmapstats() +{ + conoutf("\f2Map quality stats"); + conoutf("this map %s", rnd(3) ? "is great" : "sucks"); +} +COMMAND(showmapstats, ""); + diff --git a/source/src/world.h b/source/src/world.h index 39c2c2b8..13e5c666 100644 --- a/source/src/world.h +++ b/source/src/world.h @@ -97,21 +97,6 @@ struct _mapconfigdata } }; -struct mapstats -{ - struct header hdr; - int entcnt[MAXENTTYPES]; - int cgzsize; - uchar *enttypes; - short *entposs; - int spawns[3]; - int flags[2]; - int flagents[2]; - bool hasffaspawns; - bool hasteamspawns; - bool hasflags; -}; - struct mapdim_s { // 0 2 1 3 6 7 int x1, x2, y1, y2, minfloor, maxceil; // outer borders (points to last used cube) @@ -183,10 +168,3 @@ struct entitystats_s short first[MAXENTTYPES], last[MAXENTTYPES]; // first and last occurence of every basic ent type }; - -// legacy - -#define MAXMHEIGHT 30 -#define MAXMAREA 10000 -#define MAXHHITS 50000 // was 6000, which denied my fav. maps - jamz, 2012-06-12; 15000 denies sane map too - lucas -#define MINFF 2500 diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index f81af9fa..c5159956 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -920,10 +920,6 @@ VARF(mapoverride_limitwaveheight, 0, 0, 1, changemapflag(mapoverride_limitwavehe VARF(mapoverride_nostencilshadows, 0, 0, 1, changemapflag(mapoverride_nostencilshadows, MHF_DISABLESTENCILSHADOWS)); VAR(_ignoreillegalpaths, 0, 0, 1); -extern char *mlayout; -extern int Mv, Ma, Hhits; -extern float Mh; - static string lastloadedconfigfile; int load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! @@ -1077,34 +1073,15 @@ int load_world(char *mname) // still supports all map formats that have e res |= LWW_WORLDERROR * (iabs(we) & 0xf); delete[] smallworld; - - - DELETEA(mlayout); - mlayout = new char[cubicsize + 256]; - memset(mlayout, 0, cubicsize * sizeof(char)); - Mv = Ma = Hhits = 0; - char texuse[256]; - loopi(256) texuse[i] = 0; - loopk(cubicsize) + // check texture slot usage + char texuse[256] = { 0 }; + sqr *s = world; + loopirev(cubicsize) { - sqr *s = &world[k]; - if(SOLID(s)) mlayout[k] = 127; - else - { - mlayout[k] = s->floor; // FIXME - int diff = s->ceil - s->floor; - if(diff > 6) - { - if(diff > MAXMHEIGHT) Hhits += diff - MAXMHEIGHT; - Ma += 1; - Mv += diff; - } - texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; - } + if(!SOLID(s)) texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; texuse[s->wtex] = 1; + s++; } - Mh = Ma ? (float)Mv/Ma : 0; - c2skeepalive(); calclight(); conoutf("read map %s rev %d (%d milliseconds)", cgzname, hdr.maprevision, watch.elapsed()); From 3a320de948cd3f3abeac25e37c77ad7413675b95 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 6 Jun 2017 17:13:56 +0200 Subject: [PATCH 44/45] cleanup cpistol --- config/menus_settings.cfg | 3 +- config/scripts.cfg | 1 - source/src/bot/ac_bot.cpp | 2 +- source/src/bot/ac_bot.h | 2 +- source/src/bot/ac_bot_ai.cpp | 1 - source/src/clientgame.cpp | 3 - source/src/clients2c.cpp | 1 - source/src/console.cpp | 1 - source/src/entity.h | 2 +- source/src/hudgun.h | 1 - source/src/protos.h | 1 - source/src/renderhud.cpp | 4 +- source/src/rendermodel.cpp | 1 - source/src/server.cpp | 4 +- source/src/server.h | 6 +- source/src/weapon.cpp | 113 +---------------------------------- source/src/weapon.h | 12 ---- 17 files changed, 13 insertions(+), 145 deletions(-) diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg index 39d0787f..bcfa5e32 100644 --- a/config/menus_settings.cfg +++ b/config/menus_settings.cfg @@ -251,9 +251,8 @@ menuitemtextinput "Shotgun: " [ fragmessage SHOTGUN ] [ fragmessage SHOTGUN $arg menuitemtextinput "SMG: " [ fragmessage SUBGUN ] [ fragmessage SUBGUN $arg1 ] [] 15 menuitemtextinput "Sniper: " [ fragmessage SNIPER ] [ fragmessage SNIPER $arg1 ] [] 15 menuitemtextinput "AR: " [ fragmessage ASSAULT ] [ fragmessage ASSAULT $arg1 ] [] 15 -//menuitemtextinput "CPistol: " [ fragmessage CPISTOL ] [ fragmessage CPISTOL $arg1 ] [] 15 menuitemtextinput "Akimbo: " [ fragmessage AKIMBO ] [ fragmessage AKIMBO $arg1 ] [] 15 -menuitem [] -1 +menuitem "" -1 menuitem "\f9Gib messages: " -1 menuitemtextinput "Knife: " [ gibmessage KNIFE ] [ gibmessage KNIFE $arg1 ] [] 15 menuitemtextinput "Shotgun: " [ gibmessage SHOTGUN ] [ gibmessage SHOTGUN $arg1 ] [] 15 diff --git a/config/scripts.cfg b/config/scripts.cfg index dc24f9e6..d5cd1357 100644 --- a/config/scripts.cfg +++ b/config/scripts.cfg @@ -187,7 +187,6 @@ loop i $NUM_WEAPONS [ (concatword altaction_ $i) = quicknadethrow ] // Quick nad // Use format to define altaction_N aliases from now on, makes for less edits in the future if weapon IDs change. - Bukz // (format altaction_%1 (getgunindex knife)) = [ if (checkmag (currentprimary)) primary [ if (|| (checkmag PISTOL) (checkmag AKIMBO)) secondary [ if (checkmag GRENADE) grenades ] ] ] // Knife // uncomment to switch to primary/secondary/grenades with MOUSE2 depending on ammo content for each. (format altaction_%1 (getgunindex sniper)) = [ setscope 1; onrelease [ setscope 0 ] ] // Sniper -// (format altaction_%1 (getgunindex cpistol)) = [ setburst 1; onrelease [ setburst 0 ] ] // Combat pistol // TODO uncomment when this becomes a priority/testing, for now lets save an unused alias - Bukz // (format altaction_%1 (getgunindex grenades)) = attack // Grenade // with this comment, you throw the nade and the nade is unselected : Brahma oldsens = $sensitivity diff --git a/source/src/bot/ac_bot.cpp b/source/src/bot/ac_bot.cpp index 6199d44f..56d161a0 100644 --- a/source/src/bot/ac_bot.cpp +++ b/source/src/bot/ac_bot.cpp @@ -22,7 +22,7 @@ extern void spawnstate(playerent *d); void CACBot::Spawn() { // Init all bot variabeles - m_pMyEnt->nextprimary = 2 + rnd(5); // 2011jan18:ft: 2 == GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT - if CPISTOL is activated this needs a FIXME to rnd(6) + m_pMyEnt->nextprimary = 2 + rnd(5); // 2 == GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT m_pMyEnt->targetyaw = m_pMyEnt->targetpitch = 0.0f; m_pMyEnt->pBot = this; diff --git a/source/src/bot/ac_bot.h b/source/src/bot/ac_bot.h index 55d5cfe4..a2c8d8c9 100644 --- a/source/src/bot/ac_bot.h +++ b/source/src/bot/ac_bot.h @@ -14,7 +14,7 @@ #define AC_BOT_H //#define BOTDEBUG -#define MAX_WEAPONS 10 +#define MAX_WEAPONS 9 // NUM_GUNS class CACBot: public CBot { diff --git a/source/src/bot/ac_bot_ai.cpp b/source/src/bot/ac_bot_ai.cpp index e9234776..a17be56a 100644 --- a/source/src/bot/ac_bot_ai.cpp +++ b/source/src/bot/ac_bot_ai.cpp @@ -24,7 +24,6 @@ weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = { TYPE_AUTO, 0.0f, 25.0f, 3.0f, 60.0f, 10 }, // SUBGUN { TYPE_SNIPER, 10.0f, 125.0f, 3.0f, 200.0f, 3 }, // SNIPER { TYPE_AUTO, 20.0f, 80.0f, 3.0f, 150.0f, 6 }, // ASSAULT - { TYPE_AUTO, 0.0f, 0.0f, 3.0f, 0.0f, 6 }, // CPISTOL { TYPE_GRENADE, 5.0f, 30.0f, 3.0f, 50.0f, 1 }, // GRENADE { TYPE_AUTO, 0.0f, 50.0f, 3.0f, 50.0f, 0 } // AKIMBO }; diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 3f859330..97198667 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -442,7 +442,6 @@ void deathstate(playerent *pl) { if(showscoresondeath) showscores(true); setscope(false); - setburst(false); if(editmode) toggleedit(true); if(pl->team == TEAM_SPECT) spectatemode(SM_FLY); else if(team_isspect(pl->team)) spectatemode(SM_FOLLOW1ST); @@ -458,7 +457,6 @@ void spawnstate(playerent *d) // reset player state not persistent if(d==player1) { setscope(false); - setburst(false); } if(d->deaths==0) d->resetstats(); } @@ -790,7 +788,6 @@ void respawnself() } showscores(false); setscope(false); - setburst(false); lasthit = 0; spawnplayer(player1); player1->lifesequence++; diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index e31356e2..e8727333 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -616,7 +616,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) if(editmode) toggleedit(true); showscores(false); setscope(false); - setburst(false); player1->respawn(); player1->lifesequence = getint(p); player1->health = getint(p); diff --git a/source/src/console.cpp b/source/src/console.cpp index 02c3c5ff..cfc9ba17 100644 --- a/source/src/console.cpp +++ b/source/src/console.cpp @@ -304,7 +304,6 @@ void saycommand(char *init) // turns input to the comman { SDL_EnableUNICODE(saycommandon = (init!=NULL)); setscope(false); - setburst(false); if(!editmode) keyrepeat(saycommandon); copystring(cmdline.buf, init ? escapestring(init, false, true) : ""); DELETEA(cmdaction); diff --git a/source/src/entity.h b/source/src/entity.h index c99e9226..78f34190 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -53,7 +53,7 @@ struct entity : persistent_entity } }; -enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_CPISTOL, GUN_GRENADE, GUN_AKIMBO, NUMGUNS }; +enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS }; #define valid_weapon(g) ((g) >= GUN_KNIFE && (g) < NUMGUNS) #define reloadable_gun(g) ((g) != GUN_KNIFE && (g) != GUN_GRENADE) diff --git a/source/src/hudgun.h b/source/src/hudgun.h index 18fabcfc..5414719c 100644 --- a/source/src/hudgun.h +++ b/source/src/hudgun.h @@ -115,7 +115,6 @@ void preload_hudguns() { loopi(NUMGUNS) { - if(i == GUN_CPISTOL) continue; defformatstring(path)("weapons/%s", guns[i].modelname); loadmodel(path); } diff --git a/source/src/protos.h b/source/src/protos.h index 5a55fdfb..f48a5135 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -915,7 +915,6 @@ extern void addgib(playerent *d); extern playerent *playerincrosshair(); extern int magsize(int gun); extern void setscope(bool activate); -extern void setburst(bool activate); extern void intersectgeometry(const vec &from, vec &to); extern int intersect(playerent *d, const vec &from, const vec &to, vec *end = NULL); extern bool intersect(entity *e, const vec &from, const vec &to, vec *end = NULL); diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index 59bcd6c7..0b6b3191 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -187,7 +187,7 @@ void loadcrosshair(const char *type, const char *filename) } else if(strchr(type, '.')) { // old syntax "loadcrosshair filename type", remove this in 2020 - const char *oldcrosshairnames[CROSSHAIR_NUM + 1] = { "default", "teammate", "scope", "knife", "pistol", "carbine", "shotgun", "smg", "sniper", "ar", "cpistol", "grenades", "akimbo", "" }; + const char *oldcrosshairnames[CROSSHAIR_NUM + 1] = { "default", "teammate", "scope", "knife", "pistol", "carbine", "shotgun", "smg", "sniper", "ar", "grenades", "akimbo", "" }; index = getlistindex(filename, oldcrosshairnames, false, 0); if(index > 2) index -= 3; else index += NUMGUNS; @@ -338,7 +338,7 @@ void drawequipicons(playerent *p) // weapons int c = p->weaponsel->type != GUN_GRENADE ? p->weaponsel->type : getprevweaponsel(p), r = 0; - if(c==GUN_AKIMBO || c==GUN_CPISTOL) c = GUN_PISTOL; // same icon for akimb & pistol + if(c==GUN_AKIMBO) c = GUN_PISTOL; // same icon for akimb & pistol if(c>3) { c -= 4; r = 1; } if(p->weaponsel && valid_weapon(p->weaponsel->type)) drawequipicon(HUDPOS_WEAPON*2, 1650, c, r); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index c6be403a..9586e253 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -916,7 +916,6 @@ void preload_playermodels() if(dynshadow && playermdl) playermdl->genshadows(8.0f, 4.0f); loopi(NUMGUNS) { - if(i == GUN_CPISTOL) continue; defformatstring(vwep)("weapons/%s/world", guns[i].modelname); model *vwepmdl = loadmodel(vwep); if(dynshadow && vwepmdl) vwepmdl->genshadows(8.0f, 4.0f); diff --git a/source/src/server.cpp b/source/src/server.cpp index 88eb4bac..2b8bafaf 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -3367,7 +3367,7 @@ void process(ENetPacket *packet, int sender, int chan) case SV_WEAPCHANGE: { int gunselect = getint(p); - if(!valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; + if(!valid_weapon(gunselect)) break; cl->state.gunselect = gunselect; QUEUE_MSG; break; @@ -3466,7 +3466,7 @@ void process(ENetPacket *packet, int sender, int chan) { int ls = getint(p), gunselect = getint(p); if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD && cl->state.state!=CS_SPECTATE) || - ls!=cl->state.lifesequence || cl->state.lastspawn<0 || !valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; + ls!=cl->state.lifesequence || cl->state.lastspawn<0 || !valid_weapon(gunselect)) break; cl->state.lastspawn = -1; cl->state.spawn = sg->gamemillis; cl->autospawn = false; diff --git a/source/src/server.h b/source/src/server.h index a9a29b9f..e24d5d3f 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -504,7 +504,6 @@ itemstat ammostats[NUMGUNS] = { 60, 90, 90, S_ITEMAMMO }, // subgun { 10, 20, 15, S_ITEMAMMO }, // sniper { 40, 60, 60, S_ITEMAMMO }, // assault - { 30, 45, 75, S_ITEMAMMO }, // cpistol { 1, 0, 3, S_ITEMAMMO }, // grenade {100, 0, 100, S_ITEMAKIMBO} // akimbo }; @@ -531,7 +530,6 @@ guninfo guns[NUMGUNS] = { "subgun", "A-ARD/10 SMG", S_SUBGUN, S_RSUBGUN, 1650, 80, 16, 0, 0, 0, 45, 15, 30, 1, 2, 5, 25, 50, 188, 1, true }, { "sniper", "AD-81 SR", S_SNIPER, S_RSNIPER, 1950, 1500, 82, 25, 0, 0, 50, 50, 5, 4, 4, 10, 85, 85, 100, 1, false }, { "assault", "MTP-57 AR", S_ASSAULT, S_RASSAULT, 2000, 120, 22, 0, 0, 0, 18, 30, 20, 0, 2, 3, 25, 50, 115, 1, true }, - { "cpistol", "nop", S_PISTOL, S_RPISTOL, 1400, 120, 19, 0, 0, 0, 35, 10, 15, 6, 5, 6, 35, 50, 125, 1, false }, // temporary { "grenade", "Grenades", S_NULL, S_NULL, 1000, 650, 200, 0, 20, 6, 1, 1, 1, 3, 1, 0, 0, 0, 0, 3, false }, { "pistol", "Akimbo", S_PISTOL, S_RAKIMBO, 1400, 80, 18, 0, 0, 0, 50, 10, 20, 6, 5, 4, 15, 25, 115, 1, true }, }; @@ -545,8 +543,8 @@ const char *rolenames[CR_NUM + 1] = { "unarmed", "master", "admin", "owner", "" const char *killmessages[2][NUMGUNS] = { - { "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "busted", "", "busted" }, - { "slashed", "", "", "splattered", "", "headshot", "", "", "gibbed", "" } + { "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "", "busted" }, + { "slashed", "", "", "splattered", "", "headshot", "", "gibbed", "" } }; #define C(x) (1<<(SC_##x)) diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index d24f74de..b3ec6b5d 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -14,7 +14,7 @@ struct sgray { sgray sgr[SGRAYS*3]; -int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, }; void updatelastaction(playerent *d, int millis = lastmillis) { @@ -868,7 +868,7 @@ VARP(accuracy,0,0,1); void r_accuracy(int h) { int i = player1->weaponsel->type; - if(accuracy && valid_weapon(i) && i != GUN_CPISTOL) + if(accuracy && valid_weapon(i)) { int x_offset = 2 * HUDPOS_X_BOTTOMLEFT, y_offset = 2 * (h - 1.75 * FONTH); string line; @@ -889,7 +889,7 @@ void r_accuracy(int h) void accuracyinfo() { vector lines; - loopi(NUMGUNS) if(i != GUN_CPISTOL && accuracym[i].shots) + loopi(NUMGUNS) if(accuracym[i].shots) { float acc = 100.0 * accuracym[i].hits / (float)accuracym[i].shots; string line; @@ -1075,7 +1075,6 @@ void weapon::equipplayer(playerent *pl) pl->weapons[GUN_GRENADE] = new grenades(pl); pl->weapons[GUN_KNIFE] = new knife(pl); pl->weapons[GUN_PISTOL] = new pistol(pl); - pl->weapons[GUN_CPISTOL] = new cpistol(pl); pl->weapons[GUN_CARBINE] = new carbine(pl); pl->weapons[GUN_SHOTGUN] = new shotgun(pl); pl->weapons[GUN_SNIPER] = new sniperrifle(pl); @@ -1538,111 +1537,6 @@ int assaultrifle::dynspread() { return shots > 2 ? 55 : ( info.spread + ( shots float assaultrifle::dynrecoil() { return info.recoil + (rnd(8)*-0.01f); } bool assaultrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; } -// combat pistol - -cpistol::cpistol(playerent *owner) : gun(owner, GUN_CPISTOL), bursting(false) {} -bool cpistol::selectable() { return false; /*return weapon::selectable() && !m_noprimary && this == owner->primweap;*/ } -void cpistol::onselecting() { weapon::onselecting(); bursting = false; } -void cpistol::ondeselecting() { bursting = false; } -bool cpistol::reload(bool autoreloaded) -{ - bool r = weapon::reload(autoreloaded); - if(owner==player1 && r) { bursting = false; } - return r; -} - -bool burst = false; -int burstshots = 0; - -bool cpistol::attack(vec &targ) // modded from gun::attack // FIXME -{ - int attackmillis = lastmillis-owner->lastaction - gunwait; - if(attackmillis<0) return false; - gunwait = reloading = 0; - - if (bursting) burst = true; - - if(!owner->attacking) - { - shots = 0; - checkautoreload(); - return false; - } - - attackmillis = lastmillis - min(attackmillis, curtime); - updatelastaction(owner, attackmillis); - if(!mag) - { - audiomgr.playsoundc(S_NOAMMO); - gunwait += 250; - owner->lastattackweapon = NULL; - shots = 0; - checkautoreload(); - return false; - } - - owner->lastattackweapon = this; - shots++; - if (burst) burstshots++; - - if(!burst) owner->attacking = false; - - vec from = owner->o; - vec to = targ; - from.z -= weaponbeloweye; - - attackphysics(from, to); - - hits.setsize(0); - raydamage(from, to, owner); - attackfx(from, to, 0); - - if ( burst && burstshots > 2 ) - { - gunwait = 500; - burstshots = 0; - burst = owner->attacking = false; - } - else if ( burst ) - { - gunwait = 80; - } - else - { - gunwait = info.attackdelay; - } - mag--; - - sendshoot(from, to, attackmillis); - return true; -} - -void cpistol::setburst(bool enable) -{ - if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE) - { - bursting = enable; - } -} - -void setburst(bool enable) -{ - if(player1->weaponsel->type != GUN_CPISTOL) return; - if(intermission) return; - cpistol *cp = (cpistol *)player1->weaponsel; - cp->setburst(enable); - if (!burst) - { - if ( enable && burstshots == 0 ) attack(true); - } - else - { - if ( burstshots == 0 ) burst = player1->attacking = enable; - } -} - -COMMAND(setburst, "d"); - // pistol pistol::pistol(playerent *owner) : gun(owner, GUN_PISTOL) {} @@ -1751,7 +1645,6 @@ void setscope(bool enable) COMMANDF(setscope, "i", (int *on) { setscope(*on != 0); }); - void shoot(playerent *p, vec &targ) { if(p->state!=CS_ALIVE || p->weaponchanging) return; diff --git a/source/src/weapon.h b/source/src/weapon.h index ab8ebf57..b69ea3ab 100644 --- a/source/src/weapon.h +++ b/source/src/weapon.h @@ -143,18 +143,6 @@ struct assaultrifle : gun bool selectable(); }; -struct cpistol : gun -{ - bool bursting; - cpistol(playerent *owner); - bool reload(bool autoreloaded); - bool selectable(); - void onselecting(); - void ondeselecting(); - bool attack(vec &targ); - void setburst(bool enable); -}; - struct pistol : gun { pistol(playerent *owner); From cb51e9f422bd6ea7bb7055602ea2973b01ab88c3 Mon Sep 17 00:00:00 2001 From: ac-stef Date: Tue, 27 Jun 2017 15:13:12 +0200 Subject: [PATCH 45/45] untangle entity rendering fix unbatched model roll --- source/src/entities.cpp | 171 +++++++++++++++++++------------------ source/src/protos.h | 1 + source/src/rendergl.cpp | 4 +- source/src/rendermodel.cpp | 9 +- 4 files changed, 95 insertions(+), 90 deletions(-) diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 43121f80..81461f62 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -113,103 +113,94 @@ void renderentarrow(const entity &e, const vec &dir, float radius) glEnable(GL_TEXTURE_2D); } -void renderentities() +void rendereditentities() { - int closest = editmode ? closestent() : -1; - if(editmode && !reflecting && !refracting && !stenciling) - { - static int lastsparkle = 0; - if(lastmillis - lastsparkle >= 20) + if(!editmode) return; + int closest = closestent(); + + static int lastsparkle = 0; + if(lastmillis - lastsparkle >= 20) + { // render edit mode sparklies + lastsparkle = lastmillis - (lastmillis%20); + loopv(ents) { - lastsparkle = lastmillis - (lastmillis%20); - loopv(ents) + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + if(edithideentmask & (1 << (e.type - 1))) continue; + vec v(e.x, e.y, e.z); + if(vec(v).sub(camera1->o).dot(camdir) < 0) continue; + int sc = PART_ECARROT; // use "carrot" for unknown types + if(i == closest) { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - if(edithideentmask & (1 << (e.type - 1))) continue; - vec v(e.x, e.y, e.z); - if(vec(v).sub(camera1->o).dot(camdir) < 0) continue; - int sc = PART_ECARROT; // use "carrot" for unknown types - if(i == closest) - { - sc = PART_ECLOSEST; // blue - } - else switch(e.type) - { - case LIGHT: sc = PART_ELIGHT; break; // white - case PLAYERSTART: sc = PART_ESPAWN; break; // green - case I_CLIPS: - case I_AMMO: - case I_GRENADE: sc = PART_EAMMO; break; // red - case I_HEALTH: - case I_HELMET: - case I_ARMOUR: - case I_AKIMBO: sc = PART_EPICKUP; break; // yellow - case MAPMODEL: - case SOUND: sc = PART_EMODEL; break; // magenta - case LADDER: - case CLIP: - case PLCLIP: sc = PART_ELADDER; break; // grey - case CTF_FLAG: sc = PART_EFLAG; break; // turquoise - default: break; - } - particle_splash(sc, i == closest ? 14 : 2, i == closest ? 50 : 40, v); + sc = PART_ECLOSEST; // blue } + else switch(e.type) + { + case LIGHT: sc = PART_ELIGHT; break; // white + case PLAYERSTART: sc = PART_ESPAWN; break; // green + case I_CLIPS: + case I_AMMO: + case I_GRENADE: sc = PART_EAMMO; break; // red + case I_HEALTH: + case I_HELMET: + case I_ARMOUR: + case I_AKIMBO: sc = PART_EPICKUP; break; // yellow + case MAPMODEL: + case SOUND: sc = PART_EMODEL; break; // magenta + case LADDER: + case CLIP: + case PLCLIP: sc = PART_ELADDER; break; // grey + case CTF_FLAG: sc = PART_EFLAG; break; // turquoise + default: break; + } + particle_splash(sc, i == closest ? 14 : 2, i == closest ? 50 : 40, v); } } + loopv(ents) { entity &e = ents[i]; - if(isitem(e.type)) - { - if((!OUTBORD(e.x, e.y) && e.spawned) || editmode) - { - renderent(e); - } - } - else if(editmode) - { - if(e.type==CTF_FLAG) + if(OUTBORD(e.x, e.y)) continue; + if(isitem(e.type)) renderent(e); + else switch(e.type) + { // render special representation for entities in edit mode + case CTF_FLAG: { defformatstring(path)("pickups/flags/%s", team_basestring(e.attr2)); rendermodel(path, ANIM_FLAG|ANIM_LOOP, 0, 0, vec(e.x, e.y, (float)S(e.x, e.y)->floor), 0, float(e.attr1) / ENTSCALE10, 0, 120.0f); + break; } - else if((e.type == CLIP || e.type == PLCLIP) && showclips && !stenciling) - { - renderclip(e.type, e.x, e.y, float(e.attr2) / ENTSCALE5, float(e.attr3) / ENTSCALE5, float(e.attr4) / ENTSCALE5, float(e.attr1) / ENTSCALE10, float(e.attr6) / (4 * ENTSCALE10), e.attr7); - } - else if(e.type == MAPMODEL && showclips && showmodelclipping && !stenciling) - { - mapmodelinfo *mmi = getmminfo(e.attr2); - if(mmi && mmi->h) + case CLIP: + case PLCLIP: + if(showclips) renderclip(e.type, e.x, e.y, float(e.attr2) / ENTSCALE5, float(e.attr3) / ENTSCALE5, float(e.attr4) / ENTSCALE5, float(e.attr1) / ENTSCALE10, float(e.attr6) / (4 * ENTSCALE10), e.attr7); + break; + + case MAPMODEL: + if(showclips && showmodelclipping) { - renderclip(e.type, e.x, e.y, mmi->rad, mmi->rad, mmi->h, mmi->zoff + float(e.attr3) / ENTSCALE5, 0, 0); + mapmodelinfo *mmi = getmminfo(e.attr2); + if(mmi && mmi->h) renderclip(e.type, e.x, e.y, mmi->rad, mmi->rad, mmi->h, mmi->zoff + float(e.attr3) / ENTSCALE5, 0, 0); } - } - else if(e.type == LADDER && showladderentities && !stenciling) - { - renderclip(e.type, e.x, e.y, 0, 0, e.attr1, 0, 0, 3); - } - else if(e.type == PLAYERSTART && showplayerstarts) - { - vec o(e.x, e.y, 0); - if(!OUTBORD(e.x, e.y)) o.z += S(e.x, e.y)->floor; - const char *skin; - switch(e.attr2) + break; + + case LADDER: + if(showladderentities) renderclip(e.type, e.x, e.y, 0, 0, e.attr1, 0, 0, 3); + break; + + case PLAYERSTART: + if(showplayerstarts) { - case 0: skin = "packages/models/playermodels/CLA/red.jpg"; break; - case 1: skin = "packages/models/playermodels/RVSF/blue.jpg"; break; - case 100: skin = "packages/models/playermodels/ffaspawn.jpg"; break; - default: skin = "packages/models/playermodels/unknownspawn.jpg"; break; + vec o(e.x, e.y, S(e.x, e.y)->floor); + const char *skin = "packages/models/playermodels/unknownspawn.jpg"; + switch(e.attr2) + { + case 0: skin = "packages/models/playermodels/CLA/red.jpg"; break; + case 1: skin = "packages/models/playermodels/RVSF/blue.jpg"; break; + case 100: skin = "packages/models/playermodels/ffaspawn.jpg"; break; + } + rendermodel("playermodels", ANIM_IDLE, -(int)textureload(skin)->id, 1.5f, o, 0, e.attr1 / ENTSCALE10 + 90, 0); } - rendermodel("playermodels", ANIM_IDLE, -(int)textureload(skin)->id, 1.5f, o, 0, e.attr1 / ENTSCALE10 + 90, 0); - } - } - if(editmode && i == closest && !stenciling) - { - switch(e.type) - { - case PLAYERSTART: + if(i == closest) { glColor3f(0, 1, 1); vec dir(0, -1, 0); @@ -217,11 +208,26 @@ void renderentities() renderentarrow(e, dir, 4); glColor3f(1, 1, 1); } - default: break; + break; + } + } +} + +void renderentities() +{ + if(editmode) return; + loopv(ents) + { + entity &e = ents[i]; + if(isitem(e.type)) + { + if(e.spawned && !OUTBORD(e.x, e.y)) + { + renderent(e); } } } - if(m_flags && !editmode) loopi(2) + if(m_flags) loopi(2) { flaginfo &f = flaginfos[i]; switch(f.state) @@ -234,6 +240,7 @@ void renderentities() rendermodel(path, ANIM_FLAG|ANIM_START|ANIM_DYNALLOC, 0, 0, vec(f.actor->o).add(vec(0, 0, 0.3f+(sinf(lastmillis/100.0f)+1)/10)), 0, lastmillis/2.5f, 0, 120.0f); } break; + case CTFF_INBASE: if(!clentstats.flags[i]) break; case CTFF_DROPPED: diff --git a/source/src/protos.h b/source/src/protos.h index f48a5135..b3390d12 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -926,6 +926,7 @@ extern int burstshotssettings[NUMGUNS]; // entities extern int edithideentmask; extern void pickupeffects(int n, playerent *d); +extern void rendereditentities(); extern void renderentities(); extern void rendermapmodels(); extern void resetpickups(int type = -1); diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 8120e1ec..8b3fce73 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -861,7 +861,6 @@ void drawminimap(int w, int h) glDepthFunc(GL_LESS); rendermapmodels(); renderzones(clmapdims.maxceil); - //renderentities();// IMHO better done by radar itself, if at all resettmu(0); float hf = waterlevel - 0.3f; renderwater(hf, 0, 0); @@ -1115,7 +1114,7 @@ void gl_drawframe(int w, int h, float changelod, float curfps, int elapsed) if(effective_stencilshadow && hasstencil && stencilbits >= 8) drawstencilshadows(); startmodelbatches(); - renderentities(); + rendereditentities(); endmodelbatches(); readdepth(w, h, worldpos); @@ -1125,6 +1124,7 @@ void gl_drawframe(int w, int h, float changelod, float curfps, int elapsed) endmodelbatches(); startmodelbatches(); + renderentities(); renderbounceents(); endmodelbatches(); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index 9586e253..63e70400 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -751,9 +751,6 @@ void endmodelbatches(bool flush) if(flush) clearmodelbatches(); } -const int dbgmbatch = 0; -//VAR(dbgmbatch, 0, 0, 1); - VARP(popdeadplayers, 0, 0, 1); void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, float roll, float yaw, float pitch, float speed, int basetime, playerent *d, modelattach *a, float scale) { @@ -797,7 +794,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL; } - if(numbatches>=0 && !dbgmbatch) + if(numbatches>=0) { batchedmodel &b = addbatchedmodel(m); b.o = o; @@ -819,7 +816,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl if(stenciling) { m->startrender(); - m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale); + m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, roll, yaw, pitch, d, a, scale); m->endrender(); return; } @@ -870,7 +867,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl glColor4f(color[0], color[1], color[2], m->translucency); } - m->render(anim, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale); + m->render(anim, varseed, speed, basetime, o, roll, yaw, pitch, d, a, scale); if(anim&ANIM_TRANSLUCENT) {