diff --git a/config/forbidden.cfg b/config/forbidden.cfg
deleted file mode 100644
index 06034320..00000000
--- a/config/forbidden.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-// Put in this file the words you consider to be inappropriate in your server
-// The system automatically pass all uppercase letters to lowercase
-// You can put pairs of words which should not come together in the same phrase
-// so "banana caramelada" will block "eu tenho uma banana, que eh muito caramelada"
-// but will not block "minha banana eh grande"
-// Below it is presented some default examples
-
-fuck
-bitch
-8==
-==8
-your mother
-suck dick
-big cock
-// from TyD nickblacklist
-asshole // "ass" is not blacklisted
-horny
-hurensohn // "Son of a bitch" in german.
-masturb // See "wank".
-necrop // Ban names that mention some form of necrophilia.
-pedop
-penis
-pussy
-filho puta // "whore" or "slut" in portugese.
-redtube
-porntube
-youporn
diff --git a/config/menus_multiplayer.cfg b/config/menus_multiplayer.cfg
index 04cfc0c9..6d5c98e2 100644
--- a/config/menus_multiplayer.cfg
+++ b/config/menus_multiplayer.cfg
@@ -78,6 +78,7 @@ menuinitselection 13
//// In-Game admin //// (In-game menu that opens if when you press escape if you're admin state and connected to a game.)
newmenu [In-game admin]
+menuadminonly 1
menuinit [
resetcurmenu
menuitem "Set mode/map\i\3" [ showmenu Gamemode ]
@@ -158,6 +159,7 @@ menuitem [Turn auto-team off] [ autoteam 0 ]
//// In-Game admin > Mastermode ////
newmenu Mastermode
+menuadminonly 2
menuitem [Unlock the server (open)] [ mastermode 0 ]
menuitem [Lock the server (private)] [ mastermode 1 ]
menuitem [Lock the server (match mode)] [ mastermode 2; showmenu [Match Settings] ]
@@ -165,17 +167,18 @@ menuitem [Lock the server (match mode)] [ mastermode 2; showmenu [Match Settings
//// In-Game admin > Match Settings ////
newmenu [Match Settings]
+menuadminonly 2
menuinit [ ]
-menuitemslider "Recoil Multiplier: " 50 500 "$matchrecoilmul" 10 [ matchrecoilmul $arg1 ]
-menuitemslider "Spread Multiplier: " 50 500 "$matchspreadmul" 10 [ matchspreadmul $arg1 ]
-menuitemslider "Speed Multiplier: " 50 500 "$matchspeedmul" 10 [ matchspeedmul $arg1 ]
-menuitemslider "Gravity Multiplier: " 50 500 "$matchgravity" 10 [ matchgravity $arg1 ]
-menuitemslider "Friction Multiplier: " 10 500 "$matchfriction" 10 [ matchfriction $arg1 ]
+menuitemslider "Recoil Multiplier: " 50 500 "$matchrecoilmul" 10 [ matchset matchrecoilmul $arg1 ]
+menuitemslider "Spread Multiplier: " 50 500 "$matchspreadmul" 10 [ matchset matchspreadmul $arg1 ]
+menuitemslider "Speed Multiplier: " 50 500 "$matchspeedmul" 10 [ matchset matchspeedmul $arg1 ]
+menuitemslider "Gravity Multiplier: " 50 500 "$matchgravity" 10 [ matchset matchgravity $arg1 ]
+menuitemslider "Friction Multiplier: " 10 500 "$matchfriction" 10 [ matchset matchfriction $arg1 ]
menuitem "" -1
-menuitemcheckbox "Enable Assists: " "$matchassist" [ matchassist $arg1 ]
-menuitemcheckbox "Explosive Deaths: " "$matchdeathexplosion" [ matchdeathexplosion $arg1 ]
-menuitemslider " Blast Radius %: " 10 300 "$matchdeathradius" 10 [ matchdeathradius $arg1 ]
-menuitemslider " Blast Power %: " 10 500 "$matchdeathpower" 10 [ matchdeathpower $arg1 ]
+menuitemcheckbox "Enable Assists: " "$matchassist" [ matchset matchassist $arg1 ]
+menuitemcheckbox "Explosive Deaths: " "$matchdeathexplosion" [ matchset matchdeathexplosion $arg1 ]
+menuitemslider " Blast Radius %: " 10 300 "$matchdeathradius" 10 [ matchset matchdeathradius $arg1 ]
+menuitemslider " Blast Power %: " 10 500 "$matchdeathpower" 10 [ matchset matchdeathpower $arg1 ]
menuitem "" -1
menuitem "Friendly Fire\i\3" [ showmenu [Match Friendly Fire] ]
menuitem "" -1
@@ -183,22 +186,24 @@ menuitem "Spawn Weapons\i\3" [ showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "\f2Start Bot Match" [ startbotmap ]
menuitem "" -1
-menuitem "Reset to defaults (100%)" [ matchrecoilmul 100; matchspreadmul 100; matchspeedmul 100; matchgravity 100; matchfriction 100; matchdeathradius 100; matchdeathpower 100; matchassist 1; matchfriendlyfire 1; matchdeathexplosion 0; matchspawnprimary -1; matchspawnsecondary -1; matchspawntertiary -1; matchspawnquaternary -1; showmenu [Match Settings] ]
+menuitem "Reset to defaults" [ matchsetreset; showmenu [Match Settings] ]
menuitem "Back" [ showmenu Mastermode ]
//// In-Game admin > Match Settings > Friendly Fire ////
newmenu [Match Friendly Fire]
+menuadminonly 2
menuinit [ ]
-menuitem "Off (no damage)" [ matchfriendlyfire 0; showmenu [Match Settings] ]
-menuitem "On (normal)" [ matchfriendlyfire 1; showmenu [Match Settings] ]
-menuitem "Off + no shoot teammates" [ matchfriendlyfire 2; showmenu [Match Settings] ]
+menuitem "Off (no damage)" [ matchset matchfriendlyfire 0; showmenu [Match Settings] ]
+menuitem "On (normal)" [ matchset matchfriendlyfire 1; showmenu [Match Settings] ]
+menuitem "Off + no shoot teammates" [ matchset matchfriendlyfire 2; showmenu [Match Settings] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Settings] ]
//// In-Game admin > Match Settings > Spawn Weapons ////
newmenu [Match Spawn Weapons]
+menuadminonly 2
menuinit [ ]
menutitle "Spawn weapons (primary = what you hold, others = extra guns)"
menuitem "Primary Weapon\i\3" [ showmenu [Match Spawn Primary] ]
@@ -211,64 +216,68 @@ menuitem "Back" [ showmenu [Match Settings] ]
//// In-Game admin > Match Settings > Spawn Weapons > Primary ////
newmenu [Match Spawn Primary]
+menuadminonly 2
menuinit [ ]
-menuitem "Default (your own choice)" [ matchspawnprimary -1; showmenu [Match Spawn Weapons] ]
-menuitem "Knife" [ matchspawnprimary 0; showmenu [Match Spawn Weapons] ]
-menuitem "Pistol" [ matchspawnprimary 1; showmenu [Match Spawn Weapons] ]
-menuitem "Carbine" [ matchspawnprimary 2; showmenu [Match Spawn Weapons] ]
-menuitem "Shotgun" [ matchspawnprimary 3; showmenu [Match Spawn Weapons] ]
-menuitem "Subgun" [ matchspawnprimary 4; showmenu [Match Spawn Weapons] ]
-menuitem "Sniper" [ matchspawnprimary 5; showmenu [Match Spawn Weapons] ]
-menuitem "Assault" [ matchspawnprimary 6; showmenu [Match Spawn Weapons] ]
-menuitem "Grenade" [ matchspawnprimary 7; showmenu [Match Spawn Weapons] ]
+menuitem "Default (your own choice)" [ matchset matchspawnprimary -1; showmenu [Match Spawn Weapons] ]
+menuitem "Knife" [ matchset matchspawnprimary 0; showmenu [Match Spawn Weapons] ]
+menuitem "Pistol" [ matchset matchspawnprimary 1; showmenu [Match Spawn Weapons] ]
+menuitem "Carbine" [ matchset matchspawnprimary 2; showmenu [Match Spawn Weapons] ]
+menuitem "Shotgun" [ matchset matchspawnprimary 3; showmenu [Match Spawn Weapons] ]
+menuitem "Subgun" [ matchset matchspawnprimary 4; showmenu [Match Spawn Weapons] ]
+menuitem "Sniper" [ matchset matchspawnprimary 5; showmenu [Match Spawn Weapons] ]
+menuitem "Assault" [ matchset matchspawnprimary 6; showmenu [Match Spawn Weapons] ]
+menuitem "Grenade" [ matchset matchspawnprimary 7; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
//// In-Game admin > Match Settings > Spawn Weapons > Secondary ////
newmenu [Match Spawn Secondary]
+menuadminonly 2
menuinit [ ]
-menuitem "None" [ matchspawnsecondary -1; showmenu [Match Spawn Weapons] ]
-menuitem "Knife" [ matchspawnsecondary 0; showmenu [Match Spawn Weapons] ]
-menuitem "Pistol" [ matchspawnsecondary 1; showmenu [Match Spawn Weapons] ]
-menuitem "Carbine" [ matchspawnsecondary 2; showmenu [Match Spawn Weapons] ]
-menuitem "Shotgun" [ matchspawnsecondary 3; showmenu [Match Spawn Weapons] ]
-menuitem "Subgun" [ matchspawnsecondary 4; showmenu [Match Spawn Weapons] ]
-menuitem "Sniper" [ matchspawnsecondary 5; showmenu [Match Spawn Weapons] ]
-menuitem "Assault" [ matchspawnsecondary 6; showmenu [Match Spawn Weapons] ]
-menuitem "Grenade" [ matchspawnsecondary 7; showmenu [Match Spawn Weapons] ]
+menuitem "None" [ matchset matchspawnsecondary -1; showmenu [Match Spawn Weapons] ]
+menuitem "Knife" [ matchset matchspawnsecondary 0; showmenu [Match Spawn Weapons] ]
+menuitem "Pistol" [ matchset matchspawnsecondary 1; showmenu [Match Spawn Weapons] ]
+menuitem "Carbine" [ matchset matchspawnsecondary 2; showmenu [Match Spawn Weapons] ]
+menuitem "Shotgun" [ matchset matchspawnsecondary 3; showmenu [Match Spawn Weapons] ]
+menuitem "Subgun" [ matchset matchspawnsecondary 4; showmenu [Match Spawn Weapons] ]
+menuitem "Sniper" [ matchset matchspawnsecondary 5; showmenu [Match Spawn Weapons] ]
+menuitem "Assault" [ matchset matchspawnsecondary 6; showmenu [Match Spawn Weapons] ]
+menuitem "Grenade" [ matchset matchspawnsecondary 7; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
//// In-Game admin > Match Settings > Spawn Weapons > Tertiary ////
newmenu [Match Spawn Tertiary]
+menuadminonly 2
menuinit [ ]
-menuitem "None" [ matchspawntertiary -1; showmenu [Match Spawn Weapons] ]
-menuitem "Knife" [ matchspawntertiary 0; showmenu [Match Spawn Weapons] ]
-menuitem "Pistol" [ matchspawntertiary 1; showmenu [Match Spawn Weapons] ]
-menuitem "Carbine" [ matchspawntertiary 2; showmenu [Match Spawn Weapons] ]
-menuitem "Shotgun" [ matchspawntertiary 3; showmenu [Match Spawn Weapons] ]
-menuitem "Subgun" [ matchspawntertiary 4; showmenu [Match Spawn Weapons] ]
-menuitem "Sniper" [ matchspawntertiary 5; showmenu [Match Spawn Weapons] ]
-menuitem "Assault" [ matchspawntertiary 6; showmenu [Match Spawn Weapons] ]
-menuitem "Grenade" [ matchspawntertiary 7; showmenu [Match Spawn Weapons] ]
+menuitem "None" [ matchset matchspawntertiary -1; showmenu [Match Spawn Weapons] ]
+menuitem "Knife" [ matchset matchspawntertiary 0; showmenu [Match Spawn Weapons] ]
+menuitem "Pistol" [ matchset matchspawntertiary 1; showmenu [Match Spawn Weapons] ]
+menuitem "Carbine" [ matchset matchspawntertiary 2; showmenu [Match Spawn Weapons] ]
+menuitem "Shotgun" [ matchset matchspawntertiary 3; showmenu [Match Spawn Weapons] ]
+menuitem "Subgun" [ matchset matchspawntertiary 4; showmenu [Match Spawn Weapons] ]
+menuitem "Sniper" [ matchset matchspawntertiary 5; showmenu [Match Spawn Weapons] ]
+menuitem "Assault" [ matchset matchspawntertiary 6; showmenu [Match Spawn Weapons] ]
+menuitem "Grenade" [ matchset matchspawntertiary 7; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
//// In-Game admin > Match Settings > Spawn Weapons > Quaternary ////
newmenu [Match Spawn Quaternary]
+menuadminonly 2
menuinit [ ]
-menuitem "None" [ matchspawnquaternary -1; showmenu [Match Spawn Weapons] ]
-menuitem "Knife" [ matchspawnquaternary 0; showmenu [Match Spawn Weapons] ]
-menuitem "Pistol" [ matchspawnquaternary 1; showmenu [Match Spawn Weapons] ]
-menuitem "Carbine" [ matchspawnquaternary 2; showmenu [Match Spawn Weapons] ]
-menuitem "Shotgun" [ matchspawnquaternary 3; showmenu [Match Spawn Weapons] ]
-menuitem "Subgun" [ matchspawnquaternary 4; showmenu [Match Spawn Weapons] ]
-menuitem "Sniper" [ matchspawnquaternary 5; showmenu [Match Spawn Weapons] ]
-menuitem "Assault" [ matchspawnquaternary 6; showmenu [Match Spawn Weapons] ]
-menuitem "Grenade" [ matchspawnquaternary 7; showmenu [Match Spawn Weapons] ]
+menuitem "None" [ matchset matchspawnquaternary -1; showmenu [Match Spawn Weapons] ]
+menuitem "Knife" [ matchset matchspawnquaternary 0; showmenu [Match Spawn Weapons] ]
+menuitem "Pistol" [ matchset matchspawnquaternary 1; showmenu [Match Spawn Weapons] ]
+menuitem "Carbine" [ matchset matchspawnquaternary 2; showmenu [Match Spawn Weapons] ]
+menuitem "Shotgun" [ matchset matchspawnquaternary 3; showmenu [Match Spawn Weapons] ]
+menuitem "Subgun" [ matchset matchspawnquaternary 4; showmenu [Match Spawn Weapons] ]
+menuitem "Sniper" [ matchset matchspawnquaternary 5; showmenu [Match Spawn Weapons] ]
+menuitem "Assault" [ matchset matchspawnquaternary 6; showmenu [Match Spawn Weapons] ]
+menuitem "Grenade" [ matchset matchspawnquaternary 7; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
diff --git a/config/servercmdline.txt b/config/servercmdline.txt
index fdf24932..8c1655d7 100644
--- a/config/servercmdline.txt
+++ b/config/servercmdline.txt
@@ -15,7 +15,7 @@
// example MOTD:
-o \f3server using example cmdline file (config/servercmdline.txt)\n\f2gassy server 🥶
-
+ -f 27015
// commonly used commandline switches:
@@ -51,7 +51,6 @@
// -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 // permission string (read docs/commandline.html)
// k vote to kick players
// b vote to ban players (and remove bans)
diff --git a/config/serverpwd.cfg b/config/serverpwd.cfg
index 024cf88b..9066826b 100644
--- a/config/serverpwd.cfg
+++ b/config/serverpwd.cfg
@@ -6,3 +6,4 @@
// password [denyadmin]
// optional: if denyadmin is set to '1', the password can only be used to connect to the server in case of ban, not to claim admin
+awesomesauce
diff --git a/source/codeblocks/AssaultCube.cbp b/source/codeblocks/AssaultCube.cbp
index 9b57f9f8..b13a7836 100755
--- a/source/codeblocks/AssaultCube.cbp
+++ b/source/codeblocks/AssaultCube.cbp
@@ -185,7 +185,6 @@
-
diff --git a/source/src/client.cpp b/source/src/client.cpp
index 4e2e8f2e..04501116 100644
--- a/source/src/client.cpp
+++ b/source/src/client.cpp
@@ -184,6 +184,7 @@ void disconnect(int onlyclean, int async)
kickallbots();
loopv(players) zapplayer(players[i]);
clearvote();
+ matchsettings_reset();
audiomgr.clearworldsounds(false);
localdisconnect();
}
diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp
index 5b3255c8..edc81f82 100644
--- a/source/src/clientgame.cpp
+++ b/source/src/clientgame.cpp
@@ -59,6 +59,8 @@ bool duplicatename(playerent *d, char *name = NULL)
char *colorname(playerent *d, char *name, const char *prefix)
{
if(!name) name = d->name;
+ static char unnamed[] = "unnamed";
+ if(!name[0]) name = unnamed; // still joining, real name not received yet: never display a bare "(cn)"
bool isdname = duplicatename(d, name);
if(name[0] && !isdname && !getigraph(name + 1)) return name;
static string cname[4];
diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp
index 78325984..543dc6ee 100644
--- a/source/src/clients2c.cpp
+++ b/source/src/clients2c.cpp
@@ -749,6 +749,17 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false)
break;
}
+ case SV_MATCHSETTINGS: // the server's match settings (sent when they change, and when we join)
+ {
+ int n = getint(p);
+ loopi(n)
+ {
+ int v = getint(p);
+ if(i < NUMMATCHSETTINGS) matchsetting_apply(i, v);
+ }
+ break;
+ }
+
case SV_DEATHBLAST: // "explosive deaths": the damage comes as normal SV_DAMAGE messages, this is just the visual/sound
{
int cn = getint(p);
diff --git a/source/src/entity.h b/source/src/entity.h
index e14587c7..83ee4932 100644
--- a/source/src/entity.h
+++ b/source/src/entity.h
@@ -87,6 +87,16 @@ extern int matchdeathradius, matchdeathpower;
#define DEATHBLASTRAD (EXPDAMRAD*matchdeathradius/100.0f)
#define DEATHBLASTDMG (guns[GUN_GRENADE].damage*3/4*matchdeathpower/100)
+// Match settings. Online they belong to the server: only an admin can change them (SV_MATCHSET), the server keeps
+// the values, sends them to every client when they change and to everybody who joins (SV_MATCHSETTINGS).
+// Offline/bot games have no server, so there you simply set them yourself. Defined in server.cpp (which is part of the client too).
+struct matchsettingdef { const char *name; int *var; int min, def, max; };
+#define NUMMATCHSETTINGS 14
+extern matchsettingdef matchsettings[NUMMATCHSETTINGS];
+extern int matchsynced[NUMMATCHSETTINGS]; // client side: the values the server last announced
+extern void matchsettings_reset(); // everything back to the defaults
+extern bool matchsetting_apply(int idx, int val); // clamps and stores, true if the value changed
+
struct itemstat { int add, start, max, sound; };
extern itemstat ammostats[NUMGUNS];
extern itemstat powerupstats[I_ARMOUR-I_HEALTH+1];
diff --git a/source/src/main.cpp b/source/src/main.cpp
index 3aae61a2..540d892a 100644
--- a/source/src/main.cpp
+++ b/source/src/main.cpp
@@ -1407,6 +1407,7 @@ int main(int argc, char **argv)
banmenu = addmenu("ban player", NULL, true, refreshsopmenu);
forceteammenu = addmenu("force team", NULL, true, refreshsopmenu);
giveadminmenu = addmenu("give admin", NULL, true, refreshsopmenu);
+ ((gmenu *)giveadminmenu)->minrole = CR_ADMIN;
docmenu = addmenu("reference", NULL, true, renderdocmenu);
applymenu = addmenu("apply", "apply changes now?", true, refreshapplymenu);
downloaddemomenu = addmenu("Download demo", NULL, true);
diff --git a/source/src/menus.cpp b/source/src/menus.cpp
index 489226d3..437e2b35 100644
--- a/source/src/menus.cpp
+++ b/source/src/menus.cpp
@@ -39,6 +39,11 @@ void showmenu(const char *name, bool top)
}
gmenu *m = menus.access(name);
if(!m) return;
+ if(m->minrole > CR_DEFAULT && multiplayer(NULL) && player1->clientrole < m->minrole)
+ { // admin-only menu (offline/bot games have no admins, there you're the host and may open everything)
+ conoutf("\f3this menu is for server admins only");
+ return;
+ }
if(!top && curmenu)
{
if(curmenu==m) return;
@@ -803,6 +808,7 @@ void *addmenu(const char *name, const char *title, bool allowinput, void (__cdec
m->header = m->footer = NULL;
m->menusel = 0;
}
+ m->minrole = CR_DEFAULT;
m->title = title;
DELSTRING(m->mdl);
m->allowinput = allowinput;
@@ -904,6 +910,14 @@ void menuinit(char *initaction)
}
COMMAND(menuinit, "s");
+// menuadminonly [role]: the menu that was just created with newmenu can only be opened by admins online (or by players with at least the given role)
+void menuadminonly(int *role)
+{
+ if(!lastmenu) return;
+ lastmenu->minrole = *role > 0 ? *role : CR_ADMIN;
+}
+COMMAND(menuadminonly, "i");
+
void menuinitselection(int *line)
{
if(!lastmenu) return;
diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp
index 25070312..d3a010e7 100644
--- a/source/src/protocol.cpp
+++ b/source/src/protocol.cpp
@@ -621,6 +621,7 @@ static const int msgsizes[] = // size inclusive message token, 0 f
SV_PAUSEMODE, 1,
SV_GETVITA, 2, SV_VITADATA, 0,
SV_DEATHBLAST, 2,
+ SV_MATCHSET, 3,
-1
};
diff --git a/source/src/protocol.h b/source/src/protocol.h
index cb6de117..c4e1d4dd 100644
--- a/source/src/protocol.h
+++ b/source/src/protocol.h
@@ -7,7 +7,7 @@
#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 1302 // bump when protocol changes (use negative numbers for mods!) - FORK VERSION
+#define PROTOCOL_VERSION 1303 // bump when protocol changes (use negative numbers for mods!) - FORK VERSION
#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
@@ -52,6 +52,7 @@ enum
SV_PAUSEMODE,
SV_GETVITA, SV_VITADATA,
SV_DEATHBLAST,
+ SV_MATCHSET, SV_MATCHSETTINGS,
SV_NUM
};
@@ -225,7 +226,7 @@ enum
#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__PLAYERNAME (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_UTF8)
#define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE_LEAD | FTXT_CROPWHITE_TRAIL)
#define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL)
#define FTXT__MOTD (FTXT_TABTOBLANK)
diff --git a/source/src/protos.h b/source/src/protos.h
index 6a550585..fc71c52f 100644
--- a/source/src/protos.h
+++ b/source/src/protos.h
@@ -244,8 +244,9 @@ struct gmenu
int xoffs, yoffs;
char *previewtexture, *previewtexturetitle;
mdirlist *dirlist;
+ int minrole; // CR_DEFAULT = anybody; online, menus with a higher minrole can only be opened by players with at least that role
- gmenu() : name(NULL), title(NULL), header(NULL), footer(NULL), menuselinit(-1), initaction(NULL), usefont(NULL),
+ gmenu() : name(NULL), title(NULL), header(NULL), footer(NULL), menuselinit(-1), initaction(NULL), usefont(NULL), minrole(CR_DEFAULT),
allowblink(false), persistentselection(false), synctabstops(false), hasdesc(false), headfootheap(false),
mdl(NULL), footlen(0), xoffs(0), yoffs(0), previewtexture(NULL), previewtexturetitle(NULL), dirlist(NULL) {}
virtual ~gmenu()
@@ -1177,7 +1178,7 @@ struct serverconfigfile
struct servercommandline
{
int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time;
- const char *ip, *master, *logident, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *service, *logfilepath, *parfilepath, *ssk, *vitabasename;
+ const char *ip, *master, *logident, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *service, *logfilepath, *parfilepath, *ssk, *vitabasename;
bool logtimestamp, demo_interm, loggamestatus;
string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm;
int clfilenesting;
@@ -1187,7 +1188,7 @@ struct servercommandline
maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000),
ip(""), master(NULL), logident(""), adminpasswd(""), demopath("demos/"),
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"),
+ infopath("config/serverinfo"), motdpath("config/motd"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"),
logtimestamp(false), demo_interm(false), loggamestatus(true),
clfilenesting(0)
{
@@ -1279,7 +1280,7 @@ struct servercommandline
case 'I': infopath = a; break;
case 'o': filterrichtext(motd, a); break;
case 'O': motdpath = a; break;
- case 'g': forbidden = a; break;
+ case 'g': break; // removed: server profanity filter (forbidden words file)
case 'n':
{
char *t = servdesc_full;
diff --git a/source/src/server.cpp b/source/src/server.cpp
index 152fc33d..6623ede2 100644
--- a/source/src/server.cpp
+++ b/source/src/server.cpp
@@ -44,6 +44,60 @@ int matchdeathradius = 100; // blast radius, % of a grenade's
extern int matchdeathpower;
int matchdeathpower = 100; // blast damage, % of the default (75% of a grenade's damage at the centre)
+// movement/weapon feel: the clients apply these, the server only hands them out
+int matchrecoilmul = 100, matchspreadmul = 100, matchspeedmul = 100, matchgravity = 100, matchfriction = 100;
+
+// The order of this table is part of the protocol (SV_MATCHSET sends an index, SV_MATCHSETTINGS all values in this order).
+matchsettingdef matchsettings[NUMMATCHSETTINGS] =
+{
+ { "matchrecoilmul", &matchrecoilmul, 0, 100, 500 },
+ { "matchspreadmul", &matchspreadmul, 0, 100, 500 },
+ { "matchspeedmul", &matchspeedmul, 0, 100, 500 },
+ { "matchgravity", &matchgravity, 0, 100, 500 },
+ { "matchfriction", &matchfriction, 10, 100, 500 },
+ { "matchassist", &matchassist, 0, 1, 1 },
+ { "matchfriendlyfire", &matchfriendlyfire, 0, 1, 2 },
+ { "matchdeathexplosion", &matchdeathexplosion, 0, 0, 1 },
+ { "matchdeathradius", &matchdeathradius, 10, 100, 500 },
+ { "matchdeathpower", &matchdeathpower, 0, 100, 500 },
+ { "matchspawnprimary", &matchspawnprimary, -1, -1, NUMGUNS-1 },
+ { "matchspawnsecondary", &matchspawnsecondary, -1, -1, NUMGUNS-1 },
+ { "matchspawntertiary", &matchspawntertiary, -1, -1, NUMGUNS-1 },
+ { "matchspawnquaternary", &matchspawnquaternary,-1, -1, NUMGUNS-1 }
+};
+int matchsynced[NUMMATCHSETTINGS];
+
+void matchsettings_reset()
+{
+ loopi(NUMMATCHSETTINGS) matchsynced[i] = *matchsettings[i].var = matchsettings[i].def;
+}
+
+bool matchsetting_apply(int idx, int val)
+{
+ if(idx < 0 || idx >= NUMMATCHSETTINGS) return false;
+ matchsettingdef &m = matchsettings[idx];
+ val = clamp(val, m.min, m.max);
+ matchsynced[idx] = val;
+ if(*m.var == val) return false;
+ *m.var = val;
+ return true;
+}
+static int __matchsettingsinit = (matchsettings_reset(), 0);
+
+void putmatchsettings(packetbuf &p)
+{
+ putint(p, SV_MATCHSETTINGS);
+ putint(p, NUMMATCHSETTINGS);
+ loopi(NUMMATCHSETTINGS) putint(p, *matchsettings[i].var);
+}
+
+void sendmatchsettings()
+{
+ packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+ putmatchsettings(p);
+ sendpacket(-1, 1, p.finalize());
+}
+
struct servergame
{
int mastermode;
@@ -119,7 +173,6 @@ servermaprot maprot;
serveripblacklist ipblacklist;
serveripcclist geoiplist;
servernickblacklist nickblacklist;
-serverforbiddenlist forbiddenlist;
serverpasswords passwords;
serverinfofile serverinfoinfo;
serverinfofile serverinfomotd;
@@ -2966,6 +3019,7 @@ void disconnect_client(int n, int reason)
if(reason>=0) enet_peer_disconnect(c.peer, reason);
clients[n]->zap();
sendf(-1, 1, "rii", SV_CDIS, n);
+ if(numclients() == 0) matchsettings_reset();
if(curvote) curvote->evaluate();
if(*scoresaved && sg->mastermode == MM_MATCH) senddisconnectedscores(-1);
}
@@ -3125,6 +3179,7 @@ void welcomepacket(packetbuf &p, int n)
putint(p, sendservermode(false));
putint(p, SV_PAUSEMODE);
putint(p, sg->sispaused);
+ putmatchsettings(p);
const char *motd = scl.motd[0] ? scl.motd : serverinfomotd.getmsg();
if(*motd)
{
@@ -3164,7 +3219,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_DEMOCHECKSUM, SV_PAUSEMODE, SV_DEATHBLAST };
+ SV_CLIENT, SV_DEMOCHECKSUM, SV_PAUSEMODE, SV_DEATHBLAST, SV_MATCHSETTINGS };
// 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)
@@ -3500,26 +3555,17 @@ void process(ENetPacket *packet, int sender, int chan)
filtertext(text, text, FTXT__CHAT);
if(*text)
{
- bool canspeech = forbiddenlist.canspeech(text);
- if(!spamdetect(cl, text) && canspeech) // team chat
+ if(!spamdetect(cl, text)) // team chat
{
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
{
- 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)
- {
- sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
- if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
- }
- else
- {
- sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
- kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
- }
+ mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', SPAM detected", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "",
+ cl->name, team_string(cl->team), text);
+ sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
+ if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
}
}
break;
@@ -3532,8 +3578,7 @@ void process(ENetPacket *packet, int sender, int chan)
filtertext(text, text, FTXT__CHAT);
if(*text)
{
- bool canspeech = forbiddenlist.canspeech(text);
- if(!spamdetect(cl, text) && canspeech)
+ if(!spamdetect(cl, text))
{
if(sg->mastermode != MM_MATCH || !sg->matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat
{
@@ -3551,18 +3596,10 @@ void process(ENetPacket *packet, int sender, int chan)
}
else
{
- 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)
- {
- sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
- if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
- }
- else
- {
- sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
- kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
- }
+ mlog(ACLOG_INFO, "[%s] %s%s says: '%s', SPAM detected", cl->hostname, type == SV_TEXTME ? "(me) " : "",
+ cl->name, text);
+ sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
+ if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
}
}
break;
@@ -3579,8 +3616,7 @@ void process(ENetPacket *packet, int sender, int chan)
if(*text)
{
- bool canspeech = forbiddenlist.canspeech(text);
- if(!spamdetect(cl, text) && canspeech)
+ if(!spamdetect(cl, text))
{
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");
@@ -3592,17 +3628,9 @@ void process(ENetPacket *packet, int sender, int chan)
}
else
{
- 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);
- if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
- }
- else
- {
- sendservmsg("\f3Watch your language! Your message was not delivered.", sender);
- kick_abuser(cl->clientnum, cl->badmillis, cl->badspeech, 3);
- }
+ mlog(ACLOG_INFO, "[%s] %s says to %s: '%s', SPAM detected", cl->hostname, cl->name, target->name, text);
+ sendservmsg("\f3Please do not spam; your message was not delivered.", sender);
+ if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM);
}
}
}
@@ -4292,6 +4320,31 @@ void process(ENetPacket *packet, int sender, int chan)
case SV_PAUSEMODE:
break;
+ case SV_MATCHSET: // admin changes a match setting (idx -1 = reset everything to the defaults)
+ {
+ int idx = getint(p), val = getint(p);
+ if(!cl || cl->role < CR_ADMIN)
+ {
+ if(cl) { mlog(ACLOG_INFO, "[%s] player %s tried to change a match setting without being admin", cl->hostname, cl->name); sendservmsg("\f3only an admin can change match settings", sender); }
+ break;
+ }
+ defformatstring(msg)("\f2%s", cl->name);
+ if(idx == -1)
+ {
+ matchsettings_reset();
+ concatstring(msg, " reset all match settings to the defaults");
+ }
+ else
+ {
+ if(idx < 0 || idx >= NUMMATCHSETTINGS || !matchsetting_apply(idx, val)) break;
+ concatformatstring(msg, " set %s to %d", matchsettings[idx].name, *matchsettings[idx].var);
+ }
+ mlog(ACLOG_INFO, "[%s] %s", cl->hostname, msg + 2);
+ sendmatchsettings();
+ sendservmsg(msg);
+ break;
+ }
+
case SV_GETVITA:
{
int targetcn = getint(p);
@@ -5079,7 +5132,6 @@ void initserver(bool dedicated)
ipblacklist.init(scl.blfile);
geoiplist.init(scl.geoipfile);
nickblacklist.init(scl.nbfile);
- 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", demo_max_number);
diff --git a/source/src/server.h b/source/src/server.h
index 545d0b3a..d7cb0059 100644
--- a/source/src/server.h
+++ b/source/src/server.h
@@ -457,7 +457,7 @@ const char *messagenames[SV_NUM] =
"SV_EXTENSION",
"SV_MAPIDENT", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE",
"SV_PAUSEMODE",
- "SV_GETVITA", "SV_VITADATA", "SV_DEATHBLAST"
+ "SV_GETVITA", "SV_VITADATA", "SV_DEATHBLAST", "SV_MATCHSET", "SV_MATCHSETTINGS"
};
const char *entnames[] =
diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h
index 088c1732..c9120bb1 100644
--- a/source/src/serverfiles.h
+++ b/source/src/serverfiles.h
@@ -1400,99 +1400,6 @@ struct servernickblacklist : serverconfigfile
}
};
-#define FORBIDDENSIZE 15
-
-bool issimilar (char s, char d)
-{
- s = tolower(s); d = tolower(d);
- if ( s == d ) return true;
- switch (d)
- {
- case 'a': if ( s == '@' || s == '4' ) return true; break;
- case 'c': if ( s == 'k' ) return true; break;
- case 'e': if ( s == '3' ) return true; break;
- case 'i': if ( s == '!' || s == '1' ) return true; break;
- case 'o': if ( s == '0' ) return true; break;
- case 's': if ( s == '$' || s == '5' ) return true; break;
- case 't': if ( s == '7' ) return true; break;
- case 'u': if ( s == '#' ) return true; break;
- }
- return false;
-}
-
-bool findpattern (char *s, char *d) // returns true if there is more than 80% of similarity
-{
- int len, hit = 0;
- if (!d || (len = strlen(d)) < 1) return false;
- char *dp = d, *s_end = s + strlen(s);
- while (s != s_end)
- {
- if ( *s == ' ' ) // spaces separate words
- {
- if ( !issimilar(*(s+1),*dp) ) { dp = d; hit = 0; } // d e t e c t i t
- }
- else if ( issimilar(*s,*dp) ) { dp++; hit++; } // hit!
- else if ( hit > 0 ) // this is not a pair, but there is a previous pattern
- {
- if (*s == '.' || *s == *(s-1) || issimilar(*(s+1),*dp) ); // separator or typo (do nothing)
- else if ( issimilar(*(s+1),*(dp+1)) || *s == '*' ) dp++; // wild card or typo
- else hit--; // maybe this is nothing
- }
- else dp = d; // nothing here
- s++; // walk on the string
- if ( hit && 5 * hit > 4 * len ) return true; // found it!
- }
- return false;
-}
-
-struct serverforbiddenlist : serverconfigfile
-{
- int num;
- char entries[100][2][FORBIDDENSIZE+1]; // 100 entries and 2 words per entry is more than enough
-
- void clear()
- {
- num = 0;
- memset(entries,'\0',2*100*(FORBIDDENSIZE+1));
- }
-
- void addentry(char *s)
- {
- int len = strlen(s);
- if ( len > 128 || len < 3 ) return;
- int n = 0;
- string s1, s2;
- char *c1 = s1, *c2 = s2;
- if (num < 100 && (n = sscanf(s,"%s %s",s1,s2)) > 0 ) // no warnings
- {
- copystring(entries[num][0], c1, FORBIDDENSIZE);
- if ( n > 1 ) copystring(entries[num][1], c2, FORBIDDENSIZE);
- else entries[num][1][0]='\0';
- num++;
- }
- }
-
- void read()
- {
- char *l, *p = buf;
- mlog(ACLOG_VERBOSE,"reading forbidden list '%s'", filename);
- while(p < buf + filelen)
- {
- l = p; p += strlen(p) + 1;
- addentry(l);
- }
- }
-
- bool canspeech(char *s)
- {
- for (int i=0; i : change a match setting. Online this is a request to the server, which only admins may make.
+void matchset(char *name, int *val)
+{
+ int idx = -1;
+ loopi(NUMMATCHSETTINGS) if(!strcmp(name, matchsettings[i].name)) { idx = i; break; }
+ if(idx < 0) { conoutf("unknown match setting \"%s\"", name); return; }
+ if(multiplayer(NULL))
+ {
+ if(player1->clientrole < CR_ADMIN) { conoutf("\f3only the server admin can change match settings"); return; }
+ addmsg(SV_MATCHSET, "rii", idx, *val);
+ }
+ else matchsetting_apply(idx, *val);
+}
+COMMAND(matchset, "si");
+
+void matchsetreset()
+{
+ if(multiplayer(NULL))
+ {
+ if(player1->clientrole < CR_ADMIN) { conoutf("\f3only the server admin can change match settings"); return; }
+ addmsg(SV_MATCHSET, "rii", -1, 0);
+ }
+ else matchsettings_reset();
+}
+COMMAND(matchsetreset, "");
// "explosive deaths": effect shown where somebody died (also used for the blast sent by the server)
void deathblasteffect(const vec &pos)
diff --git a/source/vcpp/assaultcube.vcxproj b/source/vcpp/assaultcube.vcxproj
index e7ef1c3d..da1bf793 100644
--- a/source/vcpp/assaultcube.vcxproj
+++ b/source/vcpp/assaultcube.vcxproj
@@ -465,7 +465,6 @@
-
diff --git a/source/vcpp/assaultcube.vcxproj.filters b/source/vcpp/assaultcube.vcxproj.filters
index e01c9948..4af0af45 100644
--- a/source/vcpp/assaultcube.vcxproj.filters
+++ b/source/vcpp/assaultcube.vcxproj.filters
@@ -168,9 +168,6 @@
cfg
-
- cfg
-
cfg