diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 12625c45..b8291984 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -646,16 +646,12 @@ void flagmsg(int flag, int action) void dropflag() { tryflagdrop(false); } COMMAND(dropflag, ARG_NONE); -votedisplayinfo *newvotedisplayinfo(playerent *owner, int type, char *arg1, char *arg2) +char *votestring(int type, char *arg1, char *arg2) { - if(type < 0 || type >= SA_NUM) return NULL; - votedisplayinfo *v = new votedisplayinfo(); - v->owner = owner; - v->type = type; - v->millis = lastmillis + (30+10)*1000; const char *msgs[] = { "kick player %s", "ban player %s", "remove all bans", "set mastermode to %s", "%s autoteam", "force player %s to the enemy team", "give admin to player %s", "load map %s in mode %s", "%s demo recording for the next match", "stop demo recording", "clear all demos"}; const char *msg = msgs[type]; - switch(v->type) + char *out = newstring(_MAXDEFSTR); + switch(type) { case SA_KICK: case SA_BAN: @@ -665,23 +661,36 @@ votedisplayinfo *newvotedisplayinfo(playerent *owner, int type, char *arg1, char int cn = atoi(arg1); playerent *p = (cn == getclientnum() ? player1 : getclient(cn)); if(!p) return NULL; - s_sprintf(v->desc)(msg, colorname(p)); + s_sprintf(out)(msg, colorname(p)); break; } case SA_MASTERMODE: - s_sprintf(v->desc)(msg, atoi(arg1) == 0 ? "Open" : "Private"); + s_sprintf(out)(msg, atoi(arg1) == 0 ? "Open" : "Private"); break; case SA_AUTOTEAM: case SA_RECORDDEMO: - s_sprintf(v->desc)(msg, atoi(arg1) == 0 ? "disable" : "enable"); + s_sprintf(out)(msg, atoi(arg1) == 0 ? "disable" : "enable"); break; case SA_MAP: - s_sprintf(v->desc)(msg, arg1, modestr(atoi(arg2))); + s_sprintf(out)(msg, arg1, modestr(atoi(arg2))); break; default: - s_sprintf(v->desc)(msg, arg1, arg2); + s_sprintf(out)(msg, arg1, arg2); break; } + return out; +} + +votedisplayinfo *newvotedisplayinfo(playerent *owner, int type, char *arg1, char *arg2) +{ + if(type < 0 || type >= SA_NUM) return NULL; + votedisplayinfo *v = new votedisplayinfo(); + v->owner = owner; + v->type = type; + v->millis = lastmillis + (30+10)*1000; + char *votedesc = votestring(type, arg1, arg2); + s_strcpy(v->desc, votedesc); + DELETEA(votedesc); return v; } @@ -754,7 +763,6 @@ void callvotesuc() void callvoteerr(int e) { if(e < 0 || e >= VOTEE_NUM) return; - const char *verr[VOTEE_NUM] = { "voting is currently disabled", "there is already a vote pending", "you have already voted", "you can't vote that often", "this vote is not allowed in the current environment (singleplayer/multiplayer)" }; conoutf("\f3could not vote: %s", verr[e]); DELETEP(calledvote); } diff --git a/source/src/entity.h b/source/src/entity.h index 16d5df9b..63213406 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -88,7 +88,7 @@ static inline int magsize(int gun) { return guns[gun].magsize; } 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 }; +enum { CR_DEFAULT = 0, CR_ADMIN }; enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_NUM }; struct physent diff --git a/source/src/log.cpp b/source/src/log.cpp new file mode 100644 index 00000000..08f6410e --- /dev/null +++ b/source/src/log.cpp @@ -0,0 +1,54 @@ +// server-side logging of events + +#include "pch.h" +#include "cube.h" + +struct filelog : log +{ + FILE *file; + + filelog(char *filepath) + { + file = fopen(filepath, "w"); + } + + ~filelog() + { + if(file) fclose(file); + } + + virtual void writeline(int level, char *msg, ...) + { + if(!enabled) return; + + s_sprintfdv(sf, msg); + s_sprintfd(out)("%s\n", sf); + filtertext(out, out); + + if(console) + { + puts(out); + fflush(stdout); + } + if(file) + { + fwrite(out, sizeof(char), strlen(out), file); + fflush(file); + } + } +}; + + +struct syslog : log +{ + virtual void writeline(int level, char *msg, ...) + { + + } +}; + + +struct log *newlogger() +{ + return new filelog("serverlog.txt"); +} \ No newline at end of file diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index bb90c5b4..9abd6dae 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -106,8 +106,11 @@ const char *modeacronymnames[] = "OSOK", "TOSOK", "BOSOK" }; +const char *voteerrors[] = { "voting is currently disabled", "there is already a vote pending", "already voted", "can't vote that often", "this vote is not allowed in the current environment (singleplayer/multiplayer)" }; + const char *modestr(int n) { return (n>=0 && (size_t)n < sizeof(modenames)/sizeof(modenames[0])) ? modenames[n] : "unknown"; } const char *acronymmodestr(int n) { return (n>=0 && (size_t)n < sizeof(modeacronymnames)/sizeof(modeacronymnames[0])) ? modeacronymnames[n] : "unknown"; } +const char *voteerrorstr(int n) { return (n>=0 && (size_t)n < sizeof(voteerrors)/sizeof(voteerrors[0])) ? voteerrors[n] : "unknown"; } char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes { diff --git a/source/src/protos.h b/source/src/protos.h index 28c1f5e2..83db3b1e 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -524,6 +524,7 @@ extern void scrolldoc(int i); extern void localservertoclient(int chan, uchar *buf, int len); extern const char *modestr(int n); extern const char *acronymmodestr(int n); +extern const char *voteerrorstr(int n); extern void fatal(const char *s, ...); extern void initserver(bool dedicated, int uprate, const char *sdesc, const char *ip, int port, const char *master, const char *passwd, int maxcl, const char *maprot, const char *adminpwd, const char *srvmsg, int scthreshold); extern void cleanupserver(); @@ -549,6 +550,7 @@ extern bool valid_client(int cn); extern void extinfo_cnbuf(ucharbuf &p, int cn); extern void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENetAddress &addr, ENetBuffer &buf, int len); extern void extinfo_teamscorebuf(ucharbuf &p); +extern char *votestring(int type, char *arg1, char *arg2); // demo struct demoheader @@ -557,3 +559,18 @@ struct demoheader int version, protocol; }; +// logging + +struct log +{ + bool console, enabled; + enum level { info = 0, warning, error }; + + log() : console(true), enabled(true) {}; + virtual ~log() {}; + + virtual void writeline(int level, char *msg, ...) = 0; +}; + +extern struct log *newlogger(); + diff --git a/source/src/scoreboard.cpp b/source/src/scoreboard.cpp index 911666be..e2eef4e1 100644 --- a/source/src/scoreboard.cpp +++ b/source/src/scoreboard.cpp @@ -84,8 +84,7 @@ void renderscore(void *menu, playerent *d) { const char *status = ""; static color localplayerc(0.2f, 0.2f, 0.2f, 0.2f); - if(d->clientrole==CR_MASTER) status = "\f0"; - else if(d->clientrole==CR_ADMIN) status = "\f3"; + if(d->clientrole==CR_ADMIN) status = "\f3"; else if(d->state==CS_DEAD) status = "\f4"; s_sprintfd(lag)("%d", d->plag); sline &line = scorelines.add(); diff --git a/source/src/server.cpp b/source/src/server.cpp index 2559d53f..446b3a2a 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -16,6 +16,7 @@ extern void resetmap(const char *newname, int newmode, int newtime = -1, bool notify = true); servercontroller *svcctrl = NULL; +struct log *logger = NULL; #define valid_flag(f) (f >= 0 && f < 2) @@ -826,6 +827,7 @@ void flagaction(int flag, int action, int sender) if(f.state == CTFF_STOLEN) return; f.state = CTFF_STOLEN; f.actor_cn = sender; + logger->writeline(log::info,"[%s] %s stole the flag", clients[sender]->hostname, clients[sender]->name); break; } case SV_FLAGDROP: @@ -833,6 +835,7 @@ void flagaction(int flag, int action, int sender) if(f.state!=CTFF_STOLEN || (sender != -1 && f.actor_cn != sender)) return; f.state = CTFF_DROPPED; loopi(3) f.pos[i] = clients[sender]->state.o[i]; + logger->writeline(log::info,"[%s] %s dropped the flag", clients[sender]->hostname, clients[sender]->name); break; } case SV_FLAGRETURN: @@ -840,18 +843,21 @@ void flagaction(int flag, int action, int sender) if(f.state!=CTFF_DROPPED) return; f.state = CTFF_INBASE; f.actor_cn = sender; + logger->writeline(log::info,"[%s] %s returned the flag", clients[sender]->hostname, clients[sender]->name); break; } case SV_FLAGRESET: { if(sender != -1 && f.actor_cn != sender) return; f.state = CTFF_INBASE; + logger->writeline(log::info,"The server reset the flag"); break; } case SV_FLAGSCORE: { if(f.state != CTFF_STOLEN) return; f.state = CTFF_INBASE; + logger->writeline(log::info, "[%s] %s scored with the flag for %s", clients[sender]->hostname, clients[sender]->name, clients[sender]->team); break; } default: return; @@ -972,8 +978,8 @@ void disconnect_client(int n, int reason = -1) client &c = *clients[n]; savedscore *sc = findscore(c, true); if(sc) sc->save(c.state); - if(reason>=0) printf("disconnecting client (%s) [%s]\n", c.hostname, disc_reason(reason)); - else printf("disconnected client (%s)\n", c.hostname); + if(reason>=0) printf("[%s] disconnecting client %s (%s) \n", c.hostname, c.name, disc_reason(reason)); + else printf("[%s] disconnected client %s \n", c.hostname, c.name); c.peer->data = (void *)-1; if(reason>=0) enet_peer_disconnect(c.peer, reason); clients[n]->zap(); @@ -1063,6 +1069,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, target->position.setsizenodelete(0); ts.state = CS_DEAD; ts.lastdeath = gamemillis; + logger->writeline(log::info, "[%s] %s %s %s", actor->hostname, actor->name, gib ? "gibbed" : "fragged", target->name); // don't issue respawn yet until DEATHMILLIS has elapsed // ts.respawn(); @@ -1070,171 +1077,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, } } -void processevent(client *c, explodeevent &e) -{ - clientstate &gs = c->state; - switch(e.gun) - { - case GUN_GRENADE: - if(!gs.grenades.remove(e.id)) return; - break; - - default: - return; - } - for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) - { - hitevent &h = c->events[i].hit; - if(!clients.inrange(h.target)) continue; - client *target = clients[h.target]; - if(target->type==ST_EMPTY || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>EXPDAMRAD) continue; - - int j = 1; - for(j = 1; jevents[j].hit.target==h.target) break; - if(jstate; - int wait = e.millis - gs.lastshot; - if(!gs.isalive(gamemillis) || - e.gun=NUMGUNS || - waitgamemillis) gs.gunwait[e.gun] /= 2; - sendf(-1, 1, "ri9x", 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), - c->clientnum); - switch(e.gun) - { - case GUN_GRENADE: gs.grenades.add(e.id); break; - default: - { - int totalrays = 0, maxrays = e.gun==GUN_SHOTGUN ? SGRAYS : 1; - for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) - { - hitevent &h = c->events[i].hit; - if(!clients.inrange(h.target)) continue; - client *target = clients[h.target]; - if(target->type==ST_EMPTY || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence) continue; - - int rays = e.gun==GUN_SHOTGUN ? h.info : 1; - if(rays<1) continue; - totalrays += rays; - if(totalrays>maxrays) continue; - - bool gib = false; - if(e.gun==GUN_KNIFE) gib = true; - else if(e.gun==GUN_SNIPER) gib = h.info!=0; - int damage = rays*guns[e.gun].damage; - if(e.gun==GUN_SNIPER && gib) damage *= 3; - serverdamage(target, c, damage, e.gun, gib, h.dir); - } - break; - } - } -} - -void processevent(client *c, suicideevent &e) -{ - clientstate &gs = c->state; - if(gs.state!=CS_ALIVE) return; - gs.frags--; - gs.deaths++; - sendf(-1, 1, "ri4", SV_DIED, c->clientnum, c->clientnum, gs.frags); - c->position.setsizenodelete(0); - gs.state = CS_DEAD; - gs.respawn(); -} - -void processevent(client *c, pickupevent &e) -{ - clientstate &gs = c->state; - if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; - serverpickup(e.ent, c->clientnum); -} - -void processevent(client *c, reloadevent &e) -{ - clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || - e.gun=NUMGUNS || - !reloadable_gun(e.gun) || - gs.ammo[e.gun]<=0) - 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; - - gs.mag[e.gun] += numbullets; - gs.ammo[e.gun] -= numbullets; - - int wait = e.millis - gs.lastshot; - if(gs.gunwait[e.gun] && waitstate; - if(!gs.isalive(gamemillis) || gs.akimbos<=0) return; - gs.akimbos--; - gs.akimbomillis = e.millis+30000; -} - -void clearevent(client *c) -{ - int n = 1; - while(nevents.length() && c->events[n].type==GE_HIT) n++; - c->events.remove(0, n); -} - -void processevents() -{ - loopv(clients) - { - client *c = clients[i]; - if(c->type==ST_EMPTY) continue; - while(c->events.length()) - { - gameevent &e = c->events[0]; - if(e.typegamemillis) break; - if(e.shot.millislastevent) { clearevent(c); continue; } - c->lastevent = e.shot.millis; - } - switch(e.type) - { - case GE_SHOT: processevent(c, e.shot); break; - case GE_EXPLODE: processevent(c, e.explode); break; - case GE_AKIMBO: processevent(c, e.akimbo); break; - case GE_RELOAD: processevent(c, e.reload); break; - // untimed events - case GE_SUICIDE: processevent(c, e.suicide); break; - case GE_PICKUP: processevent(c, e.pickup); break; - } - clearevent(c); - } - } -} +#include "serverevents.h" struct configset { @@ -1420,6 +1263,7 @@ void changeclientrole(int client, int role, char *pwd = NULL, bool force=false) if(role > CR_DEFAULT) loopv(clients) clients[i]->role = CR_DEFAULT; clients[client]->role = role; sendserveropinfo(-1); + logger->writeline(log::info,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name, role == CR_ADMIN ? "admin" : "normal player"); } else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force } @@ -1499,28 +1343,27 @@ bool callvote(voteinfo *v) // true if a regular vote was called else { // regular vote + int voteerror = -1; + + if(curvote) + voteerror = VOTEE_CUR; + else if(configsets.length() && curcfgset < configsets.length() && !configsets[curcfgset].vote && clients[v->owner]->role == CR_DEFAULT) + voteerror = VOTEE_DISABLED; + else if(clients[v->owner]->lastvotecall && servmillis - clients[v->owner]->lastvotecall < 60*1000 && clients[v->owner]->role != CR_ADMIN && numclients()>1) + voteerror = VOTEE_MAX; + + if(voteerror >= 0) { - if(curvote) - { - sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, VOTEE_CUR); - return false; - } - else if(configsets.length() && curcfgset < configsets.length() && !configsets[curcfgset].vote && clients[v->owner]->role == CR_DEFAULT) - { - sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, VOTEE_DISABLED); - return false; - } - else if(clients[v->owner]->lastvotecall && servmillis - clients[v->owner]->lastvotecall < 60*1000 && clients[v->owner]->role != CR_ADMIN && numclients()>1) - { - sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, VOTEE_MAX); - return false; - } - else - { - curvote = v; - clients[v->owner]->lastvotecall = servmillis; - return true; - } + sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, voteerror); + logger->writeline(log::info, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(voteerror)); + return false; + } + else + { + curvote = v; + clients[v->owner]->lastvotecall = servmillis; + logger->writeline(log::info, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action->desc ? v->action->desc : "[unknown]"); + return true; } } } @@ -1699,10 +1542,15 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 disconnect_client(i, DISC_MAXCLIENTS); // disconnect someone else to fit maxclients again break; } + logger->writeline(log::info, "[%s] client %s logged in using the admin password", cl->hostname, cl->name); } else if(serverpassword[0]) { - if(!strcmp(text, serverpassword)) cl->isauthed = true; + if(!strcmp(text, serverpassword)) + { + cl->isauthed = true; + logger->writeline(log::info, "[%s] client %s logged in", cl->hostname, cl->name); + } else disconnect_client(sender, DISC_WRONGPW); } else if(mastermode==MM_PRIVATE) disconnect_client(sender, DISC_MASTERMODE); @@ -1732,6 +1580,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 case SV_TEAMTEXT: getstring(text, p); filtertext(text, text); + if(cl) logger->writeline(log::info, "[%s] %s says to team %s: '%s'", cl->hostname, cl->name, cl->team, text); sendteamtext(text, sender); break; @@ -1739,6 +1588,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 QUEUE_MSG; getstring(text, p); filtertext(text, text); + if(cl) logger->writeline(log::info, "[%s] %s says: '%s'", cl->hostname, cl->name, text); QUEUE_STR(text); break; @@ -1773,6 +1623,8 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 getstring(text, p); filtertext(cl->team, text, false, MAXTEAMLEN); QUEUE_STR(text); + if(!newclient && strcmp(cl->name, text)) + logger->writeline(log::info,"[%s] %s changed his name to %s", cl->hostname, cl->name, text); getint(p); QUEUE_MSG; break; @@ -2006,7 +1858,8 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 case SV_CALLVOTE: { voteinfo *vi = new voteinfo; - switch(getint(p)) + int type = getint(p); + switch(type) { case SA_MAP: { @@ -2223,7 +2076,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo c.connectmillis = servmillis; char hn[1024]; s_strcpy(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); - printf("client connected (%s)\n", c.hostname); + logger->writeline(log::info,"[%s] client connected", c.hostname); break; } @@ -2376,6 +2229,10 @@ void initserver(bool dedicated, int uprate, const char *sdesc, const char *ip, i maxclients = maxcl > 0 ? min(maxcl, MAXCLIENTS) : DEFAULTCLIENTS; servermsinit(master ? master : AC_MASTER_URI, ip, CUBE_SERVINFO_PORT(serverport), sdesc, dedicated); + logger = newlogger(); + logger->enabled = dedicated; // log on ded servers only + logger->writeline(log::info, "logging local AssaultCube Server now.."); + if(isdedicated = dedicated) { ENetAddress address = { ENET_HOST_ANY, serverport }; @@ -2458,3 +2315,4 @@ int main(int argc, char **argv) return EXIT_SUCCESS; } #endif + diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 9bb3a1e9..e7ce51b0 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -6,10 +6,11 @@ struct serveraction { int role; // required client role int area; // only on ded servers - virtual ~serveraction() {} + char *desc; + virtual ~serveraction() { DELETEA(desc); } virtual void perform() = 0; virtual bool isvalid() { return true; } - serveraction() : role(CR_DEFAULT), area(EE_DED_SERV) {} + serveraction() : role(CR_DEFAULT), area(EE_DED_SERV), desc(NULL) {} }; struct mapaction : serveraction @@ -18,7 +19,11 @@ struct mapaction : serveraction int mode; void perform() { resetmap(map, mode); } bool isvalid() { return serveraction::isvalid() && mode != GMODE_DEMO; } - mapaction(char *map, int mode) : map(map), mode(mode) { area |= EE_LOCAL_SERV; } // local too + mapaction(char *map, int mode) : map(map), mode(mode) + { + area |= EE_LOCAL_SERV; // local too + s_sprintf(desc)("load map '%s' in mode '%d'", map, modestr(mode)); + } ~mapaction() { DELETEA(map); } }; @@ -26,7 +31,11 @@ struct demoplayaction : serveraction { char *map; void perform() { resetmap(map, GMODE_DEMO); } - demoplayaction(char *map) : map(map) { area = EE_LOCAL_SERV; } // only local + demoplayaction(char *map) : map(map) + { + area = EE_LOCAL_SERV; // only local + } + ~demoplayaction() { DELETEA(map); } }; @@ -41,7 +50,10 @@ struct playeraction : serveraction struct forceteamaction : playeraction { void perform() { forceteam(cn, team_opposite(team_int(clients[cn]->team)), true); } - forceteamaction(int cn) : playeraction(cn) {} + forceteamaction(int cn) : playeraction(cn) + { + if(isvalid()) s_sprintf(desc)("force player %s to the enemy team", clients[cn]->name); + } }; struct giveadminaction : playeraction @@ -49,14 +61,17 @@ struct giveadminaction : playeraction void perform() { changeclientrole(cn, CR_ADMIN, NULL, true); } giveadminaction(int cn) : playeraction(cn) { - role = CR_ADMIN; + role = CR_ADMIN; } }; struct kickaction : playeraction { - void perform() { disconnect(DISC_MKICK); } - kickaction(int cn) : playeraction(cn) {} + void perform() { disconnect(DISC_MKICK); } + kickaction(int cn) : playeraction(cn) + { + if(isvalid()) s_sprintf(desc)("kick player %s", clients[cn]->name); + } }; struct banaction : playeraction @@ -70,6 +85,7 @@ struct banaction : playeraction banaction(int cn) : playeraction(cn) { role = CR_ADMIN; + if(isvalid()) s_sprintf(desc)("ban player %s", clients[cn]->name); } }; @@ -79,6 +95,7 @@ struct removebansaction : serveraction removebansaction() { role = CR_ADMIN; + s_strcpy(desc, "remove all bans"); } }; @@ -90,6 +107,7 @@ struct mastermodeaction : serveraction mastermodeaction(int mode) : mode(mode) { role = CR_ADMIN; + if(isvalid()) s_sprintf(desc)("change mastermode to '%s'", mode == MM_OPEN ? "open" : "private"); } }; @@ -106,7 +124,10 @@ struct autoteamaction : enableaction sendf(-1, 1, "ri2", SV_AUTOTEAM, (autoteam = enable) == 1 ? 1 : 0); if(m_teammode && enable) shuffleteams(); } - autoteamaction(bool enable) : enableaction(enable) {} + autoteamaction(bool enable) : enableaction(enable) + { + if(isvalid()) s_sprintf(desc)("%s autoteam", enable ? "enable" : "disable"); + } }; struct recorddemoaction : enableaction @@ -115,6 +136,7 @@ struct recorddemoaction : enableaction recorddemoaction(bool enable) : enableaction(enable) { role = CR_ADMIN; + if(isvalid()) s_sprintf(desc)("%s demorecord", enable ? "enable" : "disable"); } }; @@ -128,6 +150,7 @@ struct stopdemoaction : serveraction stopdemoaction() { role = CR_ADMIN; + s_strcpy(desc, "stop demo"); } }; @@ -138,6 +161,7 @@ struct cleardemosaction : serveraction cleardemosaction(int demo) : demo(demo) { role = CR_ADMIN; + if(isvalid()) s_sprintf(desc)("clear demo %d", demo); } }; diff --git a/source/src/serverevents.h b/source/src/serverevents.h new file mode 100644 index 00000000..b2452e8b --- /dev/null +++ b/source/src/serverevents.h @@ -0,0 +1,169 @@ +// processing of server events + +void processevent(client *c, explodeevent &e) +{ + clientstate &gs = c->state; + switch(e.gun) + { + case GUN_GRENADE: + if(!gs.grenades.remove(e.id)) return; + break; + + default: + return; + } + for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) + { + hitevent &h = c->events[i].hit; + if(!clients.inrange(h.target)) continue; + client *target = clients[h.target]; + if(target->type==ST_EMPTY || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>EXPDAMRAD) continue; + + int j = 1; + for(j = 1; jevents[j].hit.target==h.target) break; + if(jstate; + int wait = e.millis - gs.lastshot; + if(!gs.isalive(gamemillis) || + e.gun=NUMGUNS || + waitgamemillis) gs.gunwait[e.gun] /= 2; + sendf(-1, 1, "ri9x", 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), + c->clientnum); + switch(e.gun) + { + case GUN_GRENADE: gs.grenades.add(e.id); break; + default: + { + int totalrays = 0, maxrays = e.gun==GUN_SHOTGUN ? SGRAYS : 1; + for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) + { + hitevent &h = c->events[i].hit; + if(!clients.inrange(h.target)) continue; + client *target = clients[h.target]; + if(target->type==ST_EMPTY || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence) continue; + + int rays = e.gun==GUN_SHOTGUN ? h.info : 1; + if(rays<1) continue; + totalrays += rays; + if(totalrays>maxrays) continue; + + bool gib = false; + if(e.gun==GUN_KNIFE) gib = true; + else if(e.gun==GUN_SNIPER) gib = h.info!=0; + int damage = rays*guns[e.gun].damage; + if(e.gun==GUN_SNIPER && gib) damage *= 3; + serverdamage(target, c, damage, e.gun, gib, h.dir); + } + break; + } + } +} + +void processevent(client *c, suicideevent &e) +{ + clientstate &gs = c->state; + if(gs.state!=CS_ALIVE) return; + gs.frags--; + gs.deaths++; + sendf(-1, 1, "ri4", SV_DIED, c->clientnum, c->clientnum, gs.frags); + c->position.setsizenodelete(0); + gs.state = CS_DEAD; + gs.respawn(); + logger->writeline(log::info, "[%s] %s suicided", c->hostname, c->name); +} + +void processevent(client *c, pickupevent &e) +{ + clientstate &gs = c->state; + if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; + serverpickup(e.ent, c->clientnum); +} + +void processevent(client *c, reloadevent &e) +{ + clientstate &gs = c->state; + if(!gs.isalive(gamemillis) || + e.gun=NUMGUNS || + !reloadable_gun(e.gun) || + gs.ammo[e.gun]<=0) + 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; + + gs.mag[e.gun] += numbullets; + gs.ammo[e.gun] -= numbullets; + + int wait = e.millis - gs.lastshot; + if(gs.gunwait[e.gun] && waitstate; + if(!gs.isalive(gamemillis) || gs.akimbos<=0) return; + gs.akimbos--; + gs.akimbomillis = e.millis+30000; +} + +void clearevent(client *c) +{ + int n = 1; + while(nevents.length() && c->events[n].type==GE_HIT) n++; + c->events.remove(0, n); +} + +void processevents() +{ + loopv(clients) + { + client *c = clients[i]; + if(c->type==ST_EMPTY) continue; + while(c->events.length()) + { + gameevent &e = c->events[0]; + if(e.typegamemillis) break; + if(e.shot.millislastevent) { clearevent(c); continue; } + c->lastevent = e.shot.millis; + } + switch(e.type) + { + case GE_SHOT: processevent(c, e.shot); break; + case GE_EXPLODE: processevent(c, e.explode); break; + case GE_AKIMBO: processevent(c, e.akimbo); break; + case GE_RELOAD: processevent(c, e.reload); break; + // untimed events + case GE_SUICIDE: processevent(c, e.suicide); break; + case GE_PICKUP: processevent(c, e.pickup); break; + } + clearevent(c); + } + } +} +