Read entities within the BSP itself.

NOT YET COMPLETE, HIGHLY UNSTABLE!

Disabled by default, prone to crashing under Windows.
Set CVar "monster_entity_config" to 0 (or 2) to enable reading from BSP.
This commit is contained in:
Giegue
2023-03-29 02:48:18 -03:00
parent 074635bf8d
commit 79d4b3b21d
5 changed files with 363 additions and 27 deletions

View File

@@ -16,6 +16,7 @@
#include "ripent.h"
extern cvar_t *dllapi_log;
extern cvar_t *monster_entity_config;
extern monster_type_t monster_types[];
extern int monster_spawn_count;
@@ -403,6 +404,314 @@ void scan_monster_cfg(FILE *fp)
}
}
void scan_monster_bsp(void)
{
// TODO: code duplication galore! optimize this for T5 milestone. -Giegue
epair_t *kv_pair;
pKVD data[MAX_KEYVALUES];
int kvd_index;
int duplicate_ent;
bool use_monstermod;
int classname_kvdI, mIndex;
float x, y, z;
bool badent, monster, node;
// go through all entities
for (int ent = 1; ent < num_entities; ent++)
{
kv_pair = entities[ent].epairs;
kvd_index = 0;
duplicate_ent = 0;
use_monstermod = true;
classname_kvdI = 0;
badent = monster = node = false;
// examine all keys
while (kv_pair != NULL)
{
if (strcmp(kv_pair->key, "classname") == 0)
{
// the entity we are trying to spawn could already exist within the game
// use the engine's CREATE_NAMED_ENTITY to see if it's valid or not
//
// if it is valid, this entity already exists and we should ignore it
edict_t *existsGAME = CREATE_NAMED_ENTITY( MAKE_STRING( kv_pair->value ) );
if ( !FNullEnt( existsGAME ) )
{
for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++)
{
if (strcmp(kv_pair->value, monster_types[mIndex].name) == 0)
{
// the entity exists BOTH in the game and monstermod!
// keep track of it
duplicate_ent = ent;
break;
}
}
UTIL_Remove( existsGAME ); // get rid of the temporary entity
use_monstermod = false; // use game entity
}
}
else if (duplicate_ent && strcmp(kv_pair->key, "use_monstermod") == 0)
{
if (atoi(kv_pair->value) == 1)
{
// EXPLICITY requested to use the monstermod entity
use_monstermod = true;
}
}
strcpy(data[kvd_index].key, kv_pair->key);
strcpy(data[kvd_index].value, kv_pair->value);
kvd_index++;
kv_pair = kv_pair->next;
}
// spawn a monstermod entity?
if (use_monstermod)
{
// find classname keyvalue
for (int i = 0; i < kvd_index; i++)
{
if (strcmp(data[i].key, "classname") == 0)
{
for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++)
{
if (strcmp(data[i].value, monster_types[mIndex].name) == 0)
{
// Match found, check if it's a node
if (strcmp(monster_types[mIndex].name, "info_node") == 0)
{
// Normal node
if (node_spawn_count == MAX_NODES)
{
LOG_MESSAGE(PLID, "ERROR: can't add node, reached MAX_NODES!");
badent = true;
}
else
node = true;
}
else if (strcmp(monster_types[mIndex].name, "info_node_air") == 0)
{
// Aerial node
if (node_spawn_count == MAX_NODES)
{
LOG_MESSAGE(PLID, "ERROR: can't add node, reached MAX_NODES!");
badent = true;
}
else
{
node_spawnpoint[node_spawn_count].is_air_node = true;
node = true;
}
}
else
{
// Assume it's a monster and add it to the list
// (Extra entities are built as CMBaseMonster)
if (monster_spawn_count == MAX_MONSTERS)
{
LOG_MESSAGE(PLID, "ERROR: can't add entity, reached MAX_MONSTERS!");
badent = true;
}
else
{
monster_spawnpoint[monster_spawn_count].monster = mIndex;
monster_types[mIndex].need_to_precache = true;
monster = true;
}
}
classname_kvdI = i;
break;
}
}
if (monster_types[mIndex].name[0] == 0)
{
LOG_MESSAGE(PLID, "unknown classname: %s", data[i].value);
badent = true;
}
}
}
if (!badent)
{
// Make room for entity-specific keyvalues.
if (monster)
{
// Can I use malloc/calloc again or you are going to crash cuz you feel like it? >.>
monster_spawnpoint[monster_spawn_count].keyvalue = (pKVD*)calloc(MAX_KEYVALUES, sizeof(*monster_spawnpoint[monster_spawn_count].keyvalue));
}
// process entity keyvalues
for (int i = 0; i < kvd_index; i++)
{
// duplicates are overwritten
if (strcmp(data[i].key, "origin") == 0)
{
if (sscanf(data[i].value, "%f %f %f", &x, &y, &z) != 3)
{
LOG_MESSAGE(PLID, "ERROR: invalid origin: %s", data[i].value); // print conflictive line
// reset origin to g_vecZero
LOG_MESSAGE(PLID, "ERROR: entity will spawn at 0 0 0");
x = y = z = 0;
}
if (monster)
{
monster_spawnpoint[monster_spawn_count].origin[0] = x;
monster_spawnpoint[monster_spawn_count].origin[1] = y;
monster_spawnpoint[monster_spawn_count].origin[2] = z;
}
else if (node)
{
node_spawnpoint[node_spawn_count].origin[0] = x;
node_spawnpoint[node_spawn_count].origin[1] = y;
node_spawnpoint[node_spawn_count].origin[2] = z;
}
}
else if (strcmp(data[i].key, "angles") == 0)
{
if (monster)
{
if (sscanf(data[i].value, "%f %f %f", &x, &y, &z) != 3)
{
LOG_MESSAGE(PLID, "ERROR: invalid angles: %s", data[i].value); // print conflictive line
// reset angles to g_vecZero
LOG_MESSAGE(PLID, "ERROR: entity angles will be set to 0 0 0");
x = y = z = 0;
}
monster_spawnpoint[monster_spawn_count].angles[0] = x;
monster_spawnpoint[monster_spawn_count].angles[1] = y;
monster_spawnpoint[monster_spawn_count].angles[2] = z;
}
}
else if (strcmp(data[i].key, "spawnflags") == 0)
{
if (monster)
{
if (sscanf(data[i].value, "%f", &x) != 1)
{
LOG_MESSAGE(PLID, "ERROR: invalid spawnflags: %s", data[i].value); // print conflictive line
// default to no spawnflags
LOG_MESSAGE(PLID, "ERROR: entity spawnflags will be set to none (0)");
x = 0;
}
monster_spawnpoint[monster_spawn_count].spawnflags = x;
}
}
else if (strcmp(data[i].key, "model") == 0)
{
if (monster)
{
// only applicable for normal monsters
if (strcmp(data[classname_kvdI].value, "monstermaker") != 0)
{
// precache the custom model here
PRECACHE_MODEL( data[i].value );
// the entity will need the keyvalue
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].key, data[i].key);
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].value, data[i].value);
}
}
}
else if (strcmp(data[i].key, "new_model") == 0)
{
if (monster)
{
// only applicable for monstermaker entity
if (strcmp(data[classname_kvdI].value, "monstermaker") == 0)
{
// precache the custom model
PRECACHE_MODEL( data[i].value );
// the entity will need the keyvalue as well
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].key, data[i].key);
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].value, data[i].value);
}
}
}
else if (strcmp(data[i].key, "monstertype") == 0)
{
if (monster)
{
// this keyvalue is only valid for monstermaker entity
if (strcmp(data[classname_kvdI].value, "monstermaker") == 0)
{
// process the entity precache here
for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++)
{
if (strcmp(data[i].value, monster_types[mIndex].name) == 0)
{
monster_types[mIndex].need_to_precache = TRUE;
break; // only one monster at a time
}
}
// pass the keyvalue to the entity
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].key, data[i].key);
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].value, data[i].value);
}
}
}
else if (strcmp(data[i].key, "message") == 0)
{
if (monster)
{
// only applicable for ambient_music
if (strcmp(data[classname_kvdI].value, "ambient_music") == 0)
{
// precache the sound here
PRECACHE_GENERIC(data[i].value);
// the entity will need the keyvalue
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].key, data[i].key);
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].value, data[i].value);
}
}
}
else
{
// We do not know this keyvalue, but an specific entity might use it.
// Save it for later
if (monster)
{
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].key, data[i].key);
strcpy(monster_spawnpoint[monster_spawn_count].keyvalue[i].value, data[i].value);
}
}
}
if (monster)
{
// Spawn right away
monster_spawnpoint[monster_spawn_count].need_to_respawn = TRUE;
monster_spawn_count++;
}
else if (node)
{
// Increase node count
node_spawn_count++;
}
// Log on? Print all the entities that were added
if (dllapi_log->value)
{
// Classname only, or we will flood the server!
LOG_CONSOLE(PLID, "[DEBUG] Added entity: %s", data[classname_kvdI].value);
}
}
}
}
}
bool process_monster_cfg(void)
{
char game_dir[256];
@@ -427,32 +736,49 @@ bool process_monster_cfg(void)
strcat(BSPfilename, ".bsp");
strcat(CFGfilename, "_monster.cfg");
LoadBSPFile(BSPfilename);
ParseEntities();
LOG_MESSAGE(PLID, "It works! LoadBSPFile: Parsed '%i' entities", num_entities);
// check if the map specific filename exists...
if (access(CFGfilename, 0) == 0)
// process config files?
// -1 = don't process monster config, dynamic spawns only
// 0 = read entities from BSP file
// 1 = read entities from CFG file
// 2 = read entities from both, BSP first, then CFG file
if (monster_entity_config->value >= 0)
{
if (dllapi_log->value)
// read from bsp? (mode 0 or 2)
if (monster_entity_config->value != 1)
{
//META_CONS("[MONSTER] Processing config file=%s", filename);
LOG_MESSAGE(PLID, "Processing config file '%s'", CFGfilename);
LoadBSPFile(BSPfilename);
ParseEntities();
scan_monster_bsp();
}
if ((fp = fopen(CFGfilename, "r")) == NULL)
// read from cfg? (mode 1 or 2)
if (monster_entity_config->value > 0)
{
//META_CONS("[MONSTER] ERROR: Could not open \"%s\"!", filename);
LOG_MESSAGE(PLID, "ERROR: Could not open \"%s\" file!", CFGfilename);
return TRUE; // error
// check if the map specific filename exists...
if (access(CFGfilename, 0) == 0)
{
if (dllapi_log->value)
{
//META_CONS("[MONSTER] Processing config file=%s", filename);
LOG_MESSAGE(PLID, "Processing config file '%s'", CFGfilename);
}
if ((fp = fopen(CFGfilename, "r")) == NULL)
{
//META_CONS("[MONSTER] ERROR: Could not open \"%s\"!", filename);
LOG_MESSAGE(PLID, "ERROR: Could not open \"%s\" file!", CFGfilename);
return TRUE; // error
}
scan_monster_cfg(fp);
fclose(fp);
}
}
scan_monster_cfg(fp);
fclose(fp);
}
return FALSE; // all ok
}