Introducing a copy of the id Tech 4 EntityDef system into Nuclide.
This commit is contained in:
parent
054e703177
commit
d45d2f1dc4
|
@ -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)
|
|
@ -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.
|
2
Doxyfile
2
Doxyfile
|
@ -888,8 +888,10 @@ INPUT = src/ \
|
||||||
Documentation/Materials/MatShaders.md \
|
Documentation/Materials/MatShaders.md \
|
||||||
Documentation/Materials/MatGoldSrc.md \
|
Documentation/Materials/MatGoldSrc.md \
|
||||||
Documentation/Shaders/ \
|
Documentation/Shaders/ \
|
||||||
|
Documentation/EntityDef.md \
|
||||||
Documentation/Surf_data.md \
|
Documentation/Surf_data.md \
|
||||||
Documentation/Prop_data.md \
|
Documentation/Prop_data.md \
|
||||||
|
Documentation/MapTweaks.md \
|
||||||
Documentation/Sound/ \
|
Documentation/Sound/ \
|
||||||
Documentation/Models/ \
|
Documentation/Models/ \
|
||||||
Documentation/History.md \
|
Documentation/History.md \
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -491,6 +491,8 @@ worldspawn(void)
|
||||||
lightstyle(12, "mmnnmmnnnmmnn");
|
lightstyle(12, "mmnnmmnnnmmnn");
|
||||||
lightstyle(63, "a");
|
lightstyle(63, "a");
|
||||||
Skill_Init();
|
Skill_Init();
|
||||||
|
|
||||||
|
EntityDef_Init();
|
||||||
MapTweaks_Init();
|
MapTweaks_Init();
|
||||||
|
|
||||||
precache_model("models/error.vvm");
|
precache_model("models/error.vvm");
|
||||||
|
@ -770,6 +772,8 @@ to remove in case we won't initialize it.
|
||||||
void
|
void
|
||||||
CheckSpawn(void() spawnfunc)
|
CheckSpawn(void() spawnfunc)
|
||||||
{
|
{
|
||||||
|
if (EntityDef_SpawnClassname(self.classname))
|
||||||
|
return;
|
||||||
if (MapTweak_EntitySpawn(self))
|
if (MapTweak_EntitySpawn(self))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ vote.qc
|
||||||
weapons.qc
|
weapons.qc
|
||||||
modelevent.qc
|
modelevent.qc
|
||||||
mapcycle.qc
|
mapcycle.qc
|
||||||
|
entityDef.qc
|
||||||
maptweaks.qc
|
maptweaks.qc
|
||||||
entry.qc
|
entry.qc
|
||||||
#endlist
|
#endlist
|
||||||
|
|
|
@ -167,6 +167,9 @@ MapTweak_FinishSpawn(entity targetEntity, string newClassname)
|
||||||
entity oldSelf = self;
|
entity oldSelf = self;
|
||||||
self = targetEntity;
|
self = targetEntity;
|
||||||
|
|
||||||
|
if (EntityDef_SpawnClassname(newClassname))
|
||||||
|
return;
|
||||||
|
|
||||||
if (!isfunction(newClassname)) {
|
if (!isfunction(newClassname)) {
|
||||||
self.classname = strcat("spawnfunc_", newClassname);
|
self.classname = strcat("spawnfunc_", newClassname);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue