Introducing a copy of the id Tech 4 EntityDef system into Nuclide.

This commit is contained in:
Marco Cawthorne 2023-04-30 18:01:26 -07:00
parent 054e703177
commit d45d2f1dc4
Signed by: eukara
GPG Key ID: CE2032F0A2882A22
7 changed files with 330 additions and 0 deletions

View File

@ -0,0 +1,45 @@
# EntityDef
## Overview
In **id Tech 4**, we have been introduced to external entity definitions. They are pretty straightforward by merely being a set of default values for an existing entity class.
This can be used in a variety of ways.
- Create new entity classes with new behaviour without any code
- Create a base class that other entityDefs can inherit from
- Create prefabs that get updated across all maps
This system can also be used in combination with [MapTweaks](Documentation/MapTweaks.md), where an entityDef is used instead of a base class depending on the parameters you decide to test.
## Syntax
Let's take a look at an example **EntityDef**:
```
entityDef item_health_small {
"spawnclass" "item_health"
"spawnflags" "1"
}
```
This is from the port of *Deathmatch Classic*, and will ensure that **item_health_small** from *Quake III Arena* gets spawned as an **item_health** with its *spawnflags* set to the one that will make it a small health pickup.
You can have as many key/value pairs as you like. However, you can only specify one *spawnclass* and you cannot do circular inheritance.
## Special Keys
These are reserved keys and are meant to be used by the **build_game.sh** script to generate an entities.def file for your game.
| Key | Description |
|--------------------|------------------------------------------------------------------------|
| editor_usage | Description text used by the editor. |
| editor_model | Model to use in the editor. |
| editor_var **KEY** | Description text for the specified key. |
| editor_color | Normalized color vector defining the bounding box color in the editor. |
| editor_mins | Vector defining the mins of the entity bounding box. |
| editor_maxs | Vector defining the maxs of the entity bounding box. |
# References
- [id.sdk page on Entity Defs](http://icculus.org/~marco/notmine/id-dev/www.iddevnet.com/doom3/entitydefs.html)

View File

@ -0,0 +1,36 @@
# MapTweaks
## Overview
This is a very customizable system that applies changes to levels/maps depending on a variable amount of parameters. It was invented specifically for Nuclide and designed to work together with [EntityDefs](Documentation/EntityDef.md).
## Syntax
All MapTweaks are defined within `scripts/maptweaks.txt`.
Let's take a look at an example **MapTweak**:
```
hldm_tweak
{
when-cvar deathmatch equals 2
when-serverinfo *bspversion equals 30
replace weapon_gauss info_null
replace weapon_egon info_null
}
```
The `hldm_tweaks` is just a user-defined name. It doesn't affect functionality.
The `when-cvar` and `when-serverinfo` lines are **checks**. each one is checked individually and only if all are positive will the `replace` lines take effect.
You can have as many lines in there as you like.
Other than `equals`, you can also use one of the following keywords when comparing values:
- **less-than**
- **greater-than**
- **is-not**
At this time, `when-cvar` and `when-serverinfo` only do comparisons on numbers. So you cannot check for strings at this time.

View File

@ -888,8 +888,10 @@ INPUT = src/ \
Documentation/Materials/MatShaders.md \
Documentation/Materials/MatGoldSrc.md \
Documentation/Shaders/ \
Documentation/EntityDef.md \
Documentation/Surf_data.md \
Documentation/Prop_data.md \
Documentation/MapTweaks.md \
Documentation/Sound/ \
Documentation/Models/ \
Documentation/History.md \

239
src/server/entityDef.qc Normal file
View File

@ -0,0 +1,239 @@
/*
* Copyright (c) 2023 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.
*/
/* entityDef implementation
these definitions are a further abstraction from how we view
entity definitions. this system tries to be mostly compatible
with the def system in id Tech 4 (Doom 3, Quake 4, Prey, etc.)
however, we are not aiming for full compatibility right now as
that will require further abstraction.
that said, the origin of this idea dates way back to when
Team Fortress Software created MapC for Team Fortress 2 when
it was originally on Quake II's engine.
http://www.teamfortress.com/tfii/mc2mapc.html (go to the wayback machine for this)
the gist is, that an entity def can set a base spawnclass (e.g. func_door)
and populate it with key/value pairs. the amount of code the programmers
has to implement is massively reduced and we can create prefabs much easier
as a result.
overview:
entityDef func_illusionary {
"spawnclass" "func_wall"
"solid" "0"
"movetype"
"0"
}
*/
/* games can feel free to set this to whatever you need. */
#ifndef ENTITYDEF_MAX
#define ENTITYDEF_MAX 128
#endif
typedef struct
{
string entClass;
string spawnClass;
string spawnData;
string inheritKeys;
} entityDef_t;
entityDef_t g_entDefTable[ENTITYDEF_MAX];
var int g_entDefCount;
void
EntityDef_ReadFile(string filePath)
{
filestream defFile;
string tempString = "";
entityDef_t currentDef;
int braceDepth = 0i;
string lastWord = __NULL__;
currentDef.entClass = "";
currentDef.spawnClass = "";
currentDef.spawnData = "";
currentDef.inheritKeys = "";
/* bounds check */
if (g_entDefCount >= ENTITYDEF_MAX) {
error(sprintf("EntityDef_ReadFile: reached limit of %d defs\n", ENTITYDEF_MAX));
}
/* open file */
defFile = fopen(filePath, FILE_READ);
if (defFile < 0) {
error(sprintf("EntityDef_ReadFile: unable to read %S\n", filePath));
}
/* line by line */
while ((tempString = fgets(defFile))) {
int lineSegments = tokenize_console(tempString);
/* word for word */
for (int i = 0i; i < lineSegments; i++) {
string word = argv(i);
switch (word) {
case "{":
braceDepth++;
break;
case "}":
braceDepth--;
/* we've reached the end of a definition */
if (braceDepth == 0) {
/* we have something somewhat valid I guess */
if (currentDef.entClass != "" && currentDef.spawnClass != "") {
g_entDefTable[g_entDefCount].entClass = currentDef.entClass;
g_entDefTable[g_entDefCount].spawnClass = currentDef.spawnClass;
g_entDefTable[g_entDefCount].spawnData = currentDef.spawnData;
g_entDefTable[g_entDefCount].inheritKeys = currentDef.inheritKeys;
/* increment the def count */
if (g_entDefCount < ENTITYDEF_MAX)
g_entDefCount++;
}
currentDef.entClass = "";
currentDef.spawnClass = "";
currentDef.spawnData = "";
currentDef.inheritKeys = "";
}
break;
default:
/* anything outside braces defines the classname for the next def */
if (braceDepth == 0 && lastWord == "entityDef") {
currentDef.entClass = word;
} else if (braceDepth == 1) {
/* spawnclass is reserved and the next keyword specs it */
if (word == "spawnclass") {
currentDef.spawnClass = argv(i+1);
i++;
} else if (word == "inherit") {
currentDef.inheritKeys = argv(i+1);
i++;
} else if (substring(word, 0, 7) == "editor_") {
/* do nothing */
i++;
} else { /* rest gets dumped into spawndata */
currentDef.spawnData = strcat(currentDef.spawnData, "\"", word, "\"", " ");
}
}
}
lastWord = word;
}
}
/* clean up */
fclose(defFile);
}
void
EntityDef_Init(void)
{
searchhandle pm;
pm = search_begin("def/*.def", TRUE, TRUE);
for (int i = 0; i < search_getsize(pm); i++) {
EntityDef_ReadFile(search_getfilename(pm, i));
}
search_end(pm);
#if 0
for (int i = 0i; i < g_entDefCount; i++) {
int numKeys = tokenize_console(g_entDefTable[i].spawnData);
print(sprintf("edef %i: %S\n", i, g_entDefTable[i].entClass));
print(sprintf("\tspawnclass: %S\n", g_entDefTable[i].spawnClass));
print(sprintf("\tinheritKeys: %S\n", g_entDefTable[i].inheritKeys));
print(sprintf("\tspawnData:\n", g_entDefTable[i].spawnData));
for (int c = 0; c < numKeys; c+=2) {
print(sprintf("\t\t%S %S\n", argv(c), argv(c+1)));
}
}
#endif
}
static void
EntityDef_PrepareEntity(entity target, int id)
{
string spawnClass;
int spawnWords = 0i;
NSEntity targetEnt = (NSEntity)target;
entity oldSelf = self;
/* first we spawn it as the base spawnclass */
if (!isfunction(g_entDefTable[id].spawnClass)) {
spawnClass = strcat("spawnfunc_", g_entDefTable[id].spawnClass);
} else {
spawnClass = g_entDefTable[id].spawnClass;
}
/* init */
self = target;
callfunction(spawnClass);
self = oldSelf;
/* first load all keys we inherit from the 'inherited' class */
for (int x = 0; x < g_entDefCount; x++) {
/* found the thing we're supposed to inherit */
if (g_entDefTable[x].entClass == g_entDefTable[id].inheritKeys) {
spawnWords = tokenize_console(g_entDefTable[x].spawnData);
for (int i = 0; i < spawnWords; i+= 2) {
targetEnt.SpawnKey(argv(i), argv(i+1));
}
}
}
/* now we load the field overrides from the entDef */
spawnWords = tokenize_console(g_entDefTable[id].spawnData);
for (int i = 0; i < spawnWords; i+= 2) {
targetEnt.SpawnKey(argv(i), argv(i+1));
}
/* now we load our own spawndata, which starts and ends with braces */
spawnWords = tokenize_console(__fullspawndata);
for (int i = 1; i < (spawnWords - 1); i+= 2) {
/* ignore this, always */
if (argv(i) != "classname")
targetEnt.SpawnKey(argv(i), argv(i+1));
}
targetEnt.Spawned();
targetEnt.Respawn();
/* now we rename the classname for better visibility */
self.classname = g_entDefTable[id].entClass;
__fullspawndata = "";
}
bool
EntityDef_SpawnClassname(string className)
{
for (int i = 0i; i < g_entDefCount; i++) {
if (className == g_entDefTable[i].entClass) {
EntityDef_PrepareEntity(self, i);
return true;
}
}
return false;
}

View File

@ -491,6 +491,8 @@ worldspawn(void)
lightstyle(12, "mmnnmmnnnmmnn");
lightstyle(63, "a");
Skill_Init();
EntityDef_Init();
MapTweaks_Init();
precache_model("models/error.vvm");
@ -770,6 +772,8 @@ to remove in case we won't initialize it.
void
CheckSpawn(void() spawnfunc)
{
if (EntityDef_SpawnClassname(self.classname))
return;
if (MapTweak_EntitySpawn(self))
return;

View File

@ -12,6 +12,7 @@ vote.qc
weapons.qc
modelevent.qc
mapcycle.qc
entityDef.qc
maptweaks.qc
entry.qc
#endlist

View File

@ -167,6 +167,9 @@ MapTweak_FinishSpawn(entity targetEntity, string newClassname)
entity oldSelf = self;
self = targetEntity;
if (EntityDef_SpawnClassname(newClassname))
return;
if (!isfunction(newClassname)) {
self.classname = strcat("spawnfunc_", newClassname);
} else {