From cd3023eeba7b347b755a15277dd9b63fe9e07a45 Mon Sep 17 00:00:00 2001 From: Marco Cawthorne Date: Sat, 22 Apr 2023 02:37:17 -0700 Subject: [PATCH] Server: add MapTweaks. A new feature that allows tinkerers to rewrite entity classnames under certain conditions (RFC) --- src/server/entry.qc | 4 + src/server/include.src | 1 + src/server/maptweaks.qc | 209 ++++++++++++++++++++++++++++++ src/shared/NSClientPlayer.h | 1 - src/shared/NSSurfacePropEntity.h | 7 + src/shared/NSSurfacePropEntity.qc | 11 ++ 6 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 src/server/maptweaks.qc diff --git a/src/server/entry.qc b/src/server/entry.qc index d5e12ce6..6f29add7 100644 --- a/src/server/entry.qc +++ b/src/server/entry.qc @@ -491,6 +491,7 @@ worldspawn(void) lightstyle(12, "mmnnmmnnnmmnn"); lightstyle(63, "a"); Skill_Init(); + MapTweaks_Init(); precache_model("models/error.vvm"); @@ -769,6 +770,9 @@ to remove in case we won't initialize it. void CheckSpawn(void() spawnfunc) { + if (MapTweak_EntitySpawn(self)) + return; + if (spawnfunc) { spawnfunc(); self._mapspawned = true; diff --git a/src/server/include.src b/src/server/include.src index 2ed3ebf5..9096581f 100644 --- a/src/server/include.src +++ b/src/server/include.src @@ -12,5 +12,6 @@ vote.qc weapons.qc modelevent.qc mapcycle.qc +maptweaks.qc entry.qc #endlist diff --git a/src/server/maptweaks.qc b/src/server/maptweaks.qc new file mode 100644 index 00000000..5b7e25d6 --- /dev/null +++ b/src/server/maptweaks.qc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2016-2022 Vera Visions LLC. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +/* early maptweaks implementation (RFC) + + design notes: + + needed system that handles entity renaming + or edits in an easy to understand syntax + + replaces .ent file overrides for a lot of tasks + and can apply rules on all relevant maps based on + custom filters, either via serverinfo or via cvar. + + TODO: immediately throw out things where cvar/infokey + checks fail and don't even cache them. +*/ + +typedef struct +{ + string cvarCheck; + string serverinfoCheck; + string itemTable; +} mapTweak_t; + +mapTweak_t *g_mapTweakTable; +var int g_mapTweakCount; + +void +MapTweaks_Init(void) +{ + filestream tweakFile; + string tempString; + string newCvar, newInfo, newItem; + int atTweak = 0i; + + tweakFile = fopen("scripts/maptweaks.txt", FILE_READ); + g_mapTweakCount = 0; + newCvar = newInfo = newItem = ""; + + /* count valid entries. */ + if (tweakFile >= 0) { + while ((tempString = fgets(tweakFile))) { + if (tokenize_console(tempString) == 1) { + if (argv(0) == "}") + g_mapTweakCount += 1; + } + } + } else { + return; + } + + g_mapTweakTable = memalloc(sizeof(mapTweak_t) * g_mapTweakCount); + fseek(tweakFile, 0); + + while ((tempString = fgets(tweakFile))) { + int segments = tokenize_console(tempString); + if (segments == 1) { + if (argv(0) == "}") { + g_mapTweakTable[atTweak].cvarCheck = newCvar; + g_mapTweakTable[atTweak].serverinfoCheck = newInfo; + g_mapTweakTable[atTweak].itemTable = newItem; + newCvar = newInfo = newItem = ""; + atTweak++; + } else if (argv(0) == "{") { + /* ??? */ + } + } else if (segments == 4) { + switch (argv(0)) { + case "when-cvar": + newCvar = strcat(newCvar, argv(1), " ", argv(2), " ", argv(3), " "); + break; + case "when-serverinfo": + newInfo = strcat(newInfo, argv(1), " ", argv(2), " ", argv(3), " "); + break; + } + } else if (segments == 3) { + switch (argv(0)) { + case "replace": + newItem = strcat(newItem, argv(1), " ", argv(2), " "); + break; + } + } + } + + fclose(tweakFile); +} + +static bool +MapTweak_Check(int id) +{ + int segments = tokenize(g_mapTweakTable[id].cvarCheck); + + /* cvars first */ + for (int i = 0; i < segments; i += 3) { + string cvarName = argv(i); + string checkType = argv(i + 1); + float cvarValue = stof(argv(i + 2)); + + switch (checkType) { + case "equals": + if not (cvar(cvarName) == cvarValue) + return false; + break; + case "less-than": + if not (cvar(cvarName) < cvarValue) + return false; + break; + case "greater-than": + if not (cvar(cvarName) > cvarValue) + return false; + break; + case "is-not": + if not (cvar(cvarName) != cvarValue) + return false; + break; + } + } + + segments = tokenize(g_mapTweakTable[id].serverinfoCheck); + + /* infokeys second */ + for (int i = 0; i < segments; i += 3) { + string infoName = argv(i); + string checkType = argv(i + 1); + float infoValue = stof(argv(i + 2)); + + switch (checkType) { + case "equals": + if not (serverkeyfloat(infoName) == infoValue) + return false; + break; + case "less-than": + if not (serverkeyfloat(infoName) < infoValue) + return false; + break; + case "greater-than": + if not (serverkeyfloat(infoName) > infoValue) + return false; + break; + case "is-not": + if not (serverkeyfloat(infoName) != infoValue) + return false; + break; + } + } + + return true; +} + +static void +MapTweak_FinishSpawn(entity targetEntity, string newClassname) +{ + entity oldSelf = self; + self = targetEntity; + + if (!isfunction(newClassname)) { + self.classname = strcat("spawnfunc_", newClassname); + } else { + self.classname = newClassname; + } + + callfunction(self.classname); + self = oldSelf; +} + +bool +MapTweak_EntitySpawn(entity targetEntity) +{ + string classCheck = targetEntity.classname; + + if (g_mapTweakCount <= 0) + return false; + + for (int i = 0; i < g_mapTweakCount; i++) { + int segments = tokenize(g_mapTweakTable[i].itemTable); + + for (int y = 0; y < segments; y += 2) { + string newEnt, oldEnt; + + oldEnt = argv(y); + newEnt = argv(y + 1); + + if (classCheck == oldEnt) { + if (MapTweak_Check(i) == true) { + MapTweak_FinishSpawn(targetEntity, newEnt); + return true; + } else { + break; + } + } + } + } + + return false; +} \ No newline at end of file diff --git a/src/shared/NSClientPlayer.h b/src/shared/NSClientPlayer.h index 07cf1ddf..336d74fe 100644 --- a/src/shared/NSClientPlayer.h +++ b/src/shared/NSClientPlayer.h @@ -110,7 +110,6 @@ private: #endif PREDICTED_FLOAT(health) - PREDICTED_FLOAT(armor) PREDICTED_FLOAT_N(colormap) PREDICTED_FLOAT_N(gflags) diff --git a/src/shared/NSSurfacePropEntity.h b/src/shared/NSSurfacePropEntity.h index cf9f6367..0b503e27 100644 --- a/src/shared/NSSurfacePropEntity.h +++ b/src/shared/NSSurfacePropEntity.h @@ -46,6 +46,8 @@ class NSSurfacePropEntity:NSRenderableEntity private: float m_flBurnNext; + PREDICTED_FLOAT(armor) + #ifdef SERVER /* fire/burning */ entity m_eBurner; @@ -126,6 +128,11 @@ public: /** Returns the maximum health the entity can have. */ nonvirtual float GetMaxHealth(void); + /** Sets the current armor of the entity. */ + nonvirtual void SetArmor(float); + /** Returns the current armor of the entity. */ + nonvirtual float GetArmor(void); + /** Returns the health the entity spawned with at map load */ nonvirtual float GetSpawnHealth(void); /** Returns if the entity has prop data information set. */ diff --git a/src/shared/NSSurfacePropEntity.qc b/src/shared/NSSurfacePropEntity.qc index c51b5c1d..03cd889c 100644 --- a/src/shared/NSSurfacePropEntity.qc +++ b/src/shared/NSSurfacePropEntity.qc @@ -402,6 +402,17 @@ NSSurfacePropEntity::GetSpawnHealth(void) return m_oldHealth; } +void +NSSurfacePropEntity::SetArmor(float new_armor) +{ + armor = new_armor; +} +float +NSSurfacePropEntity::GetArmor(void) +{ + return armor; +} + bool NSSurfacePropEntity::HasPropData(void) {