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 @@
+ |
-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();
|