|
|
|
|
@@ -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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|