From 979b04ed056bd71da9d6042985f82f6b365782fd Mon Sep 17 00:00:00 2001 From: ac_stef Date: Sun, 26 Apr 2009 15:13:29 +0000 Subject: [PATCH] new & extra complicated sendmap handling git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@4350 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- config/scripts.cfg | 2 +- docs/commandline.html | 83 ++++++++++++++++++- source/src/client.cpp | 22 ++--- source/src/protocol.cpp | 3 +- source/src/protocol.h | 6 +- source/src/protos.h | 9 +- source/src/server.cpp | 163 ++++++++++++++++++++++++++++--------- source/src/serveractions.h | 41 ++++++---- 8 files changed, 256 insertions(+), 73 deletions(-) diff --git a/config/scripts.cfg b/config/scripts.cfg index c7df1335..ade5e687 100644 --- a/config/scripts.cfg +++ b/config/scripts.cfg @@ -23,7 +23,7 @@ alias editmapmsg [ saycommand ( concatword [/mapmsg ] ["] (getmapmsg) ["] ) ] // Auto-completions alias mapcomplete [complete $arg1 "packages/maps/official packages/maps" cgz] -mapcommands = "map tdm coop dm lms ts ctf pf lss osok tosok htf vip tktf ktf" +mapcommands = "map tdm coop dm lms ts ctf pf lss osok tosok htf vip tktf ktf sendmap" loop i (listlen $mapcommands) [ mapcomplete (at $mapcommands $i) ] diff --git a/docs/commandline.html b/docs/commandline.html index f8aa6833..33a071db 100644 --- a/docs/commandline.html +++ b/docs/commandline.html @@ -416,7 +416,72 @@ - + + -PS + + + Sets the required role to call specific votes. The two possible player roles are 'player' and 'admin'. + 'S' is a string of characters to set vote role requirements per vote type. A lower case character + enables the vote call for normal players, an upper case character sets the vote call to require an + admin. If a character does not appear in the string, the default setting is used.
+ The following votes can be adjusted: + + + + + + + + + + + + + + + + + + + + + + + +
kkick players
bban players (and remove bans)
mchange mastermode (open/private)
fforce a player to the other team
aenable/disable autoteam
sshuffle the teams
rrecord a demo of the next game (if autorecording is disabled)
cclear all demos on server
dchange server description
evote for coopedit mode
pvote for a gamemode that is not supported by the map (default: no one can)
+ For example: -PbmA enables ban & mastermode to be voted by normal players but + only admins can change autoteam. The '-P' parameter can be used several times on the commandline. + For example, '-Pb -Pm -PA' is equivalent to '-PbmA'. + The default settings are: "fkBMasRCDE". + + + + + -MS + + + Sets the required role for sendmap actions. The two possible player roles are 'player' and 'admin'. + 'S' is a string of characters to set role requirements per action type. A lower case character + enables the sendmap action for normal players, an upper case character sets the sendmap action to require an + admin. If a character does not appear in the string, the default setting is used.
+ The following sendmap actions can be adjusted: + + + + + + + +
ccreate new map (initial map upload)
uupdate existing map
ddelete map (default: no one can)
+ For example: -MCuD enables updating maps for normal players but + only admins can create or delete maps. The '-M' parameter can be used several times on the commandline. + For example, '-MCb -Mu -MD' is equivalent to '-MCuD'. + The default settings are: "CU".
+ Note: Only maps in the 'incoming' folder are affected. Maps in 'official' and 'servermaps' are treated read only. + + + + -D @@ -428,7 +493,21 @@ - + + -Wf + + + Sets a path (and optional filename prefix) to write all recorded demos to. The example + -Wserverdemos/mydemo would save all demos to the directory + 'serverdemos' and prefix the filenames with 'mydemo'. The filename will contain the (optional) + prefix, the (encoded) time, game mode and map. To set a path without filename prefix, use + -Wpath/. A prefix without path can be set by + -Wprefix. The server owner has to take precautions that the demo files don't fill up the + filesystem - especially, if permanent demo recording is also enabled (switch '-D'). + + + + -SW diff --git a/source/src/client.cpp b/source/src/client.cpp index 5669b451..3a726708 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -465,7 +465,7 @@ cvector securemaps; void resetsecuremaps() { securemaps.deletecontentsa(); } void securemap(char *map) { if(map) securemaps.add(newstring(map)); } -bool securemapcheck(char *map, bool msg) +bool securemapcheck(const char *map, bool msg) { if(strstr(map, "maps/")==map || strstr(map, "maps\\")==map) map += strlen("maps/"); loopv(securemaps) if(!strcmp(securemaps[i], map)) @@ -476,21 +476,15 @@ bool securemapcheck(char *map, bool msg) return false; } - void sendmap(char *mapname) { - if(*mapname && gamemode==1) - { - save_world(mapname); - changemap(mapname); // FIXME!! - } - else mapname = getclientmap(); + if(!*mapname) mapname = getclientmap(); if(securemapcheck(mapname)) return; int mapsize, cfgsize, cfgsizegz; uchar *mapdata = readmap(path(mapname), &mapsize); - uchar *cfgdata = readmcfggz(path(mapname), &cfgsize, &cfgsizegz); if(!mapdata) return; + uchar *cfgdata = readmcfggz(path(mapname), &cfgsize, &cfgsizegz); if(!cfgdata) { cfgsize = 0; cfgsizegz = 0; } ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + mapsize + cfgsizegz, ENET_PACKET_FLAG_RELIABLE); @@ -520,8 +514,6 @@ void sendmap(char *mapname) enet_packet_resize(packet, p.length()); sendpackettoserv(2, packet); conoutf("sending map %s to server...", mapname); - s_sprintfd(msg)("[map %s uploaded to server, \"/getmap\" to receive it]", mapname); - toserver(msg); } void getmap() @@ -534,6 +526,13 @@ void getmap() sendpackettoserv(2, packet); } +void deleteservermap(char *mapname) +{ + const char *name = behindpath(mapname); + if(!*name || securemapcheck(name)) return; + addmsg(SV_REMOVEMAP, "rs", name); +} + void getdemo(int i) { if(i<=0) conoutf("getting demo..."); @@ -549,6 +548,7 @@ void listdemos() COMMAND(sendmap, ARG_1STR); COMMAND(getmap, ARG_NONE); +COMMAND(deleteservermap, ARG_1STR); COMMAND(resetsecuremaps, ARG_NONE); COMMAND(securemap, ARG_1STR); COMMAND(getdemo, ARG_1INT); diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 9e9105fb..6c0610e6 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -193,7 +193,8 @@ char msgsizesl[] = // size inclusive message token, 0 for variable SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, SV_EDITMODE, 2, SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, SV_NEWMAP, 2, - SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, + SV_SENDMAP, 0, SV_RECVMAP, 1, SV_REMOVEMAP, 0, + SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, SV_MODELSKIN, 2, SV_FLAGACTION, 3, SV_FLAGINFO, 0, SV_FLAGMSG, 0, SV_FLAGCNT, 3, SV_ARENAWIN, 2, diff --git a/source/src/protocol.h b/source/src/protocol.h index 949d82ee..08bc45a6 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -22,7 +22,8 @@ enum SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, SV_EDITMODE, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP, - SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, + SV_SENDMAP, SV_RECVMAP, SV_REMOVEMAP, + SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, SV_MODELSKIN, SV_FLAGACTION, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, @@ -52,7 +53,8 @@ const static char *messagenames[] = "SV_MAPCHANGE", "SV_ITEMSPAWN", "SV_ITEMPICKUP", "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_GAMEMODE", "SV_EDITMODE", "SV_EDITH", "SV_EDITT", "SV_EDITS", "SV_EDITD", "SV_EDITE", "SV_NEWMAP", - "SV_SENDMAP", "SV_RECVMAP", "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", + "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP", + "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", "SV_MODELSKIN", "SV_FLAGACTION", "SV_FLAGINFO", "SV_FLAGMSG", "SV_FLAGCNT", "SV_ARENAWIN", diff --git a/source/src/protos.h b/source/src/protos.h index 96a1d215..70107ada 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -332,7 +332,7 @@ extern void changemapserv(char *name, int mode, bool download = false); extern void changeteam(int team, bool respawn = true); extern void getmap(); extern void newteam(char *name); -extern bool securemapcheck(char *map, bool msg = true); +extern bool securemapcheck(const char *map, bool msg = true); extern void sendintro(); extern void getdemo(int i); extern void listdemos(); @@ -734,7 +734,7 @@ struct servercommandline int uprate, serverport, syslogfacility, maxdemos, maxclients, kickthreshold, banthreshold, verbose; const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile; bool demoeverymatch; - string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm; + string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), maxdemos(5), @@ -744,7 +744,7 @@ struct servercommandline demoeverymatch(true), clfilenesting(0) { - motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = '\0'; + motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = mapperm[0] = '\0'; } bool checkarg(const char *arg) @@ -753,7 +753,7 @@ struct servercommandline const char *a = arg + 2 + strspn(arg + 2, " "); int ai = atoi(a); switch(arg[1]) - { // todo: egjlqAEGHIJKLMOQTUYZ + { // todo: egjlqAEGHIJKLOQTUYZ case 'u': uprate = ai; break; case 'f': if(ai > 0 && ai < 65536) serverport = ai; break; case 'i': ip = a; break; @@ -791,6 +791,7 @@ struct servercommandline break; } case 'P': s_strcat(voteperm, a); break; + case 'M': s_strcat(mapperm, a); break; case 'V': verbose = 1; break; #ifdef STANDALONE case 'C': if(*a && clfilenesting < 3) diff --git a/source/src/server.cpp b/source/src/server.cpp index a34461b2..e779f3cf 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -24,6 +24,10 @@ void changeclientrole(int client, int role, char *pwd = NULL, bool force=false); int mapavailable(const char *mapname); void getservermap(void); mapstats *getservermapstats(const char *mapname, bool getlayout = false); +int findmappath(const char *mapname, char *filename = NULL); + +enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_OFFICIAL }; +#define readonlymap(x) ((x) >= MAP_CUSTOM) // eval x only once! servercontroller *svcctrl = NULL; struct log *logger = NULL; @@ -239,6 +243,7 @@ struct client // server side version of "dynent" type int connectmillis; bool isauthed; // for passworded servers bool haswelcome; + bool isonrightmap; bool timesync; int gameoffset, lastevent, lastvotecall; int demoflags; @@ -266,6 +271,7 @@ struct client // server side version of "dynent" type state.reset(); events.setsizenodelete(0); timesync = false; + isonrightmap = false; lastevent = 0; at3_lastforce = 0; mapcollisions = farpickups = 0; @@ -2482,38 +2488,41 @@ uchar *copydata = NULL; int mapavailable(const char *mapname) { return copydata && !strcmp(copyname, behindpath(mapname)) ? copymapsize : 0; } -bool sendmapserv(int n, string mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data) +bool sendmapserv(int n, const char *mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data) { string name; FILE *fp; bool written = false; - if(!mapname[0] || mapsize <= 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; - s_strcpy(copyname, mapname); - copymapsize = mapsize; - copycfgsize = cfgsize; - copycfgsizegz = cfgsizegz; - copysize = mapsize + cfgsizegz; - DELETEA(copydata); - copydata = new uchar[copysize]; - memcpy(copydata, data, copysize); + if(!mapname[0] || mapsize <= 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client + if(smode == GMODE_COOPEDIT && !strcmp(mapname, behindpath(smapname))) + { // update copybuf only in coopedit mode (and on same map) + s_strcpy(copyname, mapname); + copymapsize = mapsize; + copycfgsize = cfgsize; + copycfgsizegz = cfgsizegz; + copysize = mapsize + cfgsizegz; + DELETEA(copydata); + copydata = new uchar[copysize]; + memcpy(copydata, data, copysize); + } - s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cgz", behindpath(copyname)); + s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cgz", mapname); path(name); fp = fopen(name, "wb"); if(fp) { - fwrite(copydata, 1, copymapsize, fp); + fwrite(data, 1, mapsize, fp); fclose(fp); - s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cfg", behindpath(copyname)); + s_sprintf(name)(SERVERMAP_PATH_INCOMING "%s.cfg", mapname); path(name); fp = fopen(name, "wb"); if(fp) { - uchar *rawcfg = new uchar[copycfgsize]; - uLongf rawsize = copycfgsize; - if(uncompress(rawcfg, &rawsize, copydata + copymapsize, copycfgsizegz) == Z_OK && rawsize - copycfgsize == 0) - fwrite(rawcfg, 1, copycfgsize, fp); + uchar *rawcfg = new uchar[cfgsize]; + uLongf rawsize = cfgsize; + if(uncompress(rawcfg, &rawsize, data + mapsize, cfgsizegz) == Z_OK && rawsize - cfgsize == 0) + fwrite(rawcfg, 1, cfgsize, fp); fclose(fp); DELETEA(rawcfg); written = true; @@ -2539,26 +2548,37 @@ ENetPacket *getmapserv(int n) // provide maps by the server +int findmappath(const char *mapname, char *filename) +{ + string tempname; + if(!filename) filename = tempname; + const char *name = behindpath(mapname); + s_sprintf(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name); + path(filename); + int loc = MAP_NOTFOUND; + if(fileexists(filename, "r")) loc = MAP_OFFICIAL; + else + { + s_sprintf(filename)(SERVERMAP_PATH "%s.cgz", name); + path(filename); + if(fileexists(filename, "r")) loc = MAP_CUSTOM; + else + { + s_sprintf(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name); + path(filename); + if(fileexists(filename, "r")) loc = MAP_TEMP; + } + } + return loc; +} + mapstats *getservermapstats(const char *mapname, bool getlayout) { const char *name = behindpath(mapname); - s_sprintfd(filename)(SERVERMAP_PATH "%s.cgz", name); - path(filename); - bool found = fileexists(filename, "r"); - if(!found) - { - s_sprintf(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name); - path(filename); - found = fileexists(filename, "r"); - if(!found) - { - s_sprintf(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name); - path(filename); - found = fileexists(filename, "r"); - } - } + string filename; + int loc = findmappath(mapname, filename); if(getlayout) DELETEA(maplayout); - return found ? loadmapstats(filename, getlayout) : NULL; + return loc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout); } #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) @@ -2781,7 +2801,7 @@ int checktype(int type, client *cl) if(cl && cl->type==ST_LOCAL) return type; // only allow edit messages in coop-edit mode static int edittypes[] = { SV_EDITENT, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP }; - if(cl && smode!=1) loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return -1; + if(cl && smode!=GMODE_COOPEDIT) loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return -1; // server only messages static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ITEMACC, @@ -3263,6 +3283,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { getstring(text, p); filtertext(text, text); + const char *sentmap = behindpath(text), *reject = NULL; int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); @@ -3271,10 +3292,43 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 p.forceoverread(); break; } - if(sendmapserv(sender, text, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) + int mp = findmappath(sentmap); + if(readonlymap(mp)) { - logger->writeline(log::info,"[%s] %s sent map %s, %d + %d(%d) bytes written", - clients[sender]->hostname, clients[sender]->name, text, mapsize, cfgsize, cfgsizegz); + reject = "map is ro"; + s_sprintfd(msg)("\f3map upload rejected: map %s is readonly", sentmap); + sendservmsg(msg, sender); + } + else if(mp == MAP_NOTFOUND && !strchr(scl.mapperm, 'c') && cl->role < CR_ADMIN) // default: only admins can create maps + { + reject = "no permission for initial upload"; + sendservmsg("\f3initial map upload rejected: you need to be admin", sender); + } + else if(mp == MAP_TEMP && !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(sendmapserv(sender, sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) + { + logger->writeline(log::info,"[%s] %s sent map %s, %d + %d(%d) bytes written", + clients[sender]->hostname, clients[sender]->name, sentmap, mapsize, cfgsize, cfgsizegz); + s_sprintfd(msg)("%s (%d) up%sed map %s%s", clients[sender]->name, sender, mp == MAP_NOTFOUND ? "load": "dat", sentmap, + strcmp(sentmap, behindpath(smapname)) || smode == GMODE_COOPEDIT ? "" : " (restart game to use new map version)"); + sendservmsg(msg); + } + else + { + reject = "write failed (no 'incoming'?)"; + sendservmsg("\f3map upload failed", sender); + } + } + if (reject) + { + logger->writeline(log::info,"[%s] %s sent map %s, rejected: %s", + clients[sender]->hostname, clients[sender]->name, sentmap, reject); } p.len += mapsize + cfgsizegz; break; @@ -3299,6 +3353,40 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 break; } + case SV_REMOVEMAP: + { + getstring(text, p); + filtertext(text, text); + string oldname, newname; + const char *rmmap = behindpath(text), *reject = NULL; + int mp = findmappath(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 + { // don't really delete - just rename + s_sprintf(oldname)(SERVERMAP_PATH_INCOMING "%s.cgz", rmmap); + s_sprintf(newname)(SERVERMAP_PATH_INCOMING "%s.cgz.%d.deleted", rmmap, servmillis); + remove(newname); + rename(oldname, newname); + s_sprintf(oldname)(SERVERMAP_PATH_INCOMING "%s.cfg", rmmap); + s_sprintf(newname)(SERVERMAP_PATH_INCOMING "%s.cfg.%d.deleted", rmmap, servmillis); + remove(newname); + rename(oldname, newname); + s_sprintfd(msg)("map '%s' deleted", rmmap); + sendservmsg(msg, sender); + logger->writeline(log::info,"[%s] deleted map %s, backup %d", clients[sender]->hostname, rmmap, servmillis); + } + if (reject) + { + logger->writeline(log::info,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject); + s_sprintfd(msg)("\f3can't delete map '%s', %s", rmmap, reject); + sendservmsg(msg, sender); + } + break; + } + case SV_FLAGACTION: { int action = getint(p); @@ -3867,6 +3955,7 @@ void initserver(bool dedicated) if(scl.demoeverymatch) logger->writeline(log::info, "recording demo of every game (holding up to %d in memory)", scl.maxdemos); if(scl.demopath[0]) logger->writeline(log::info,"all recorded demos will be written to: \"%s\"", scl.demopath); if(scl.voteperm[0]) logger->writeline(log::info,"vote permission string: \"%s\"", scl.voteperm); + if(scl.mapperm[0]) logger->writeline(log::info,"map permission string: \"%s\"", scl.mapperm); logger->writeline(log::info,"server description: \"%s\"", scl.servdesc_full); if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logger->writeline(log::info,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); } diff --git a/source/src/serveractions.h b/source/src/serveractions.h index d86e777a..3c3f8cfe 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -3,7 +3,7 @@ enum { EE_LOCAL_SERV = 1, EE_DED_SERV = 1<<1 }; // execution environment int roleconf(int key) -{ // current defaults: "fGkBMasRCDxP" +{ // current defaults: "fGkBMasRCDE" if(strchr(scl.voteperm, tolower(key))) return CR_DEFAULT; if(strchr(scl.voteperm, toupper(key))) return CR_ADMIN; return (key) == tolower(key) ? CR_DEFAULT : CR_ADMIN; @@ -26,6 +26,7 @@ struct mapaction : serveraction { char *map; int mode; + bool mapok; void perform() { if(isdedicated && numclients() > 2 && smode >= 0 && smode != 1 && gamemillis > gamelimit/4) @@ -39,37 +40,45 @@ struct mapaction : serveraction resetmap(map, mode); } } - bool isvalid() { return serveraction::isvalid() && mode != GMODE_DEMO && map[0] && !(isdedicated && !m_mp(mode)); } + bool isvalid() { return serveraction::isvalid() && mode != GMODE_DEMO && mapok && !(isdedicated && !m_mp(mode)); } bool isdisabled() { return configsets.inrange(curcfgset) && !configsets[curcfgset].vote; } mapaction(char *map, int mode, int caller) : map(map), mode(mode) { if(isdedicated) { - bool notify = valid_client(caller) && clients[caller]->role == CR_DEFAULT; - mapstats *ms = getservermapstats(map); - if(strchr(scl.voteperm, 'X') && !ms) // admin needed for unknown maps + bool notify = valid_client(caller); + mapstats *ms = map[0] ? getservermapstats(map) : NULL; + mapok = ms != NULL; + if(!mapok) { - role = CR_ADMIN; if(notify) sendservmsg("the server does not have this map", caller); } - if(ms && !strchr(scl.voteperm, 'p')) // admin needed for mismatched modes - { + else + { // check, if map supports mode + if(mode == GMODE_COOPEDIT && !strchr(scl.voteperm, 'e')) role = CR_ADMIN; + bool romap = mode == GMODE_COOPEDIT && readonlymap(findmappath(map)); int smode = mode; // 'borrow' the mode macros by replacing a global by a local var bool spawns = (m_teammode && !m_ktf) ? ms->hasteamspawns : ms->hasffaspawns; bool flags = m_flags && !m_htf ? ms->hasflags : true; - if(!spawns || !flags) - { - role = CR_ADMIN; + if(!spawns || !flags || romap) + { // unsupported mode + if(strchr(scl.voteperm, 'P')) role = CR_ADMIN; + else if(!strchr(scl.voteperm, 'p')) mapok = false; // default: no one can vote for unsupported mode/map combinations s_sprintfd(msg)("\f3map \"%s\" does not support \"%s\": ", behindpath(map), modestr(mode, false)); - if(!spawns) s_strcat(msg, "player spawns"); - if(!spawns && !flags) s_strcat(msg, " and "); - if(!flags) s_strcat(msg, "flag bases"); - s_strcat(msg, " missing"); + if(romap) s_strcat(msg, "map is readonly"); + else + { + if(!spawns) s_strcat(msg, "player spawns"); + if(!spawns && !flags) s_strcat(msg, " and "); + if(!flags) s_strcat(msg, "flag bases"); + s_strcat(msg, " missing"); + } if(notify) sendservmsg(msg, caller); logger->writeline(log::info, "%s", msg); } } } + else mapok = true; area |= EE_LOCAL_SERV; // local too s_sprintf(desc)("load map '%s' in mode '%s'", map, modestr(mode)); } @@ -224,6 +233,7 @@ struct shuffleteamaction : serveraction struct recorddemoaction : enableaction { void perform() { demonextmatch = enable; } + bool isvalid() { return serveraction::isvalid() && !scl.demoeverymatch; } recorddemoaction(bool enable) : enableaction(enable) { role = roleconf('R'); @@ -233,6 +243,7 @@ struct recorddemoaction : enableaction struct stopdemoaction : serveraction { + bool isvalid() { return serveraction::isvalid() && (m_demo || !scl.demoeverymatch); } void perform() { if(m_demo) enddemoplayback();