diff --git a/src/dlls/cmbase.h b/src/dlls/cmbase.h index 191b1c3..4f33789 100644 --- a/src/dlls/cmbase.h +++ b/src/dlls/cmbase.h @@ -609,3 +609,42 @@ template T * CreateClassPtr( T *a ) return a; } +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity +// Use this for non-monsters entities +// +// WARNING: This does not check for free edicts! +// +template T * CreateNormalClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + if (pev != NULL) + return NULL; // don't allocate if pointer already provided + + // allocate entity... + edict_t *temp_edict; + int edict_index; + + // allocate private data + a = new T; + + if ((temp_edict = a->CreateEntity("info_target")) == NULL) + { + (*g_engfuncs.pfnServerPrint)("[MONSTER] ERROR: NULL Ent in CreateNormalClassPtr!\n" ); + delete a; + return NULL; + } + + edict_index = (*g_engfuncs.pfnIndexOfEdict)(temp_edict); + + // store the class pointer to the edict pev... + pev = VARS(temp_edict); + a->pev = pev; + + // get the private data + a = (T *)pev->euser4; + + return a; +} diff --git a/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp index cf94264..94e463c 100644 --- a/src/dlls/dllapi.cpp +++ b/src/dlls/dllapi.cpp @@ -48,6 +48,9 @@ #include "decals.h" #include "shake.h" #include "skill.h" +#include "nodes.h" + +extern CGraph WorldGraph; extern globalvars_t *gpGlobals; extern enginefuncs_t g_engfuncs; @@ -108,7 +111,12 @@ monster_type_t monster_types[]= // These are just names. But to keep it consistent // with the new KVD format, ensure these are exactly // like an actual, entity classname. - "monster_alien_grunt", FALSE, + + // We are going to use this as a list of what entities + // can be spawned. Monsters should go first. + // DO NOT ALTER THE ORDER OF ELEMENTS! + + "monster_alien_grunt", FALSE, // Monsters "monster_apache", FALSE, "monster_barney", FALSE, "monster_bigmomma", FALSE, @@ -122,6 +130,8 @@ monster_type_t monster_types[]= "monster_scientist", FALSE, "monster_snark", FALSE, "monster_zombie", FALSE, + "info_node", FALSE, // Nodes + "info_node_air", FALSE, "", FALSE }; @@ -131,6 +141,9 @@ int monster_ents_used = 0; monster_spawnpoint_t monster_spawnpoint[MAX_MONSTERS]; int monster_spawn_count = 0; +node_spawnpoint_t node_spawnpoint[MAX_NODES]; +int node_spawn_count = 0; + float check_respawn_time; bool process_monster_cfg(void); @@ -779,6 +792,102 @@ void MonsterCommand(void) } } +void SpawnViewerCommand(void) +{ + int index; + + // debug command to spawn a node_viewer at a player's location + if (CMD_ARGC() >= 2) + { + // check for a valid player name or index... + const char *parg2 = CMD_ARGV(1); + int player_index = -1; + edict_t *pPlayer; + const char *player_name; + + if (*parg2 == '#') // player index + { + if (sscanf(&parg2[1], "%d", &player_index) != 1) + player_index = -1; + + if ((player_index < 1) || (player_index > gpGlobals->maxClients)) + { + //META_CONS("[MONSTER] invalid player index! (%d to %d allowed)", 1, gpGlobals->maxClients); + LOG_MESSAGE(PLID, "invalid player index! (%d to %d allowed)", 1, gpGlobals->maxClients); + player_index = -1; + return; + } + } + else + { + for (index = 1; index <= gpGlobals->maxClients; index++) + { + pPlayer = INDEXENT(index); + + if (pPlayer && !pPlayer->free) + { + if (stricmp(STRING(pPlayer->v.netname), parg2) == 0) + { + player_index = index; // found the matching player name + break; + } + } + } + + if (player_index == -1) + { + //META_CONS("[MONSTER] can't find player named \"%s\"!", parg2); + LOG_MESSAGE(PLID, "can't find player named \"%s\"!", parg2); + return; + } + } + + if (player_index != -1) + { + pPlayer = INDEXENT(player_index); + + if ((pPlayer == NULL) || (pPlayer->free)) + { + //META_CONS("[MONSTER] player index %d is not a valid player!", player_index); + LOG_MESSAGE(PLID, "player index %d is not a valid player!", player_index); + return; + } + + player_name = STRING(pPlayer->v.netname); + + if (player_name[0] == 0) + { + //META_CONS("[MONSTER] player index %d is not a valid player!", player_index); + LOG_MESSAGE(PLID, "player index %d is not a valid player!", player_index); + return; + } + + if (!UTIL_IsAlive(pPlayer)) + { + //META_CONS("[MONSTER] player \"%s\" is not alive or is an observer!", player_name); + LOG_MESSAGE(PLID, "player \"%s\" is not alive or is an observer!", player_name); + return; + } + + Vector origin = pPlayer->v.origin; + + CMBaseEntity *pViewer = CreateClassPtr((CNodeViewer *)NULL); + if (pViewer == NULL) + { + //META_CONS("[MONSTER] ERROR: Error Creating Node!" ); + LOG_MESSAGE(PLID, "ERROR: Error Creating Viewer!"); + return; + } + + pViewer->pev->origin = origin; + pViewer->Spawn(); + return; + } + } + + LOG_MESSAGE(PLID, "usage: node_viewer player_name | #player_index"); + LOG_MESSAGE(PLID, "spawns a node viewer at the player's location"); +} void mmGameDLLInit( void ) { @@ -810,13 +919,39 @@ int mmDispatchSpawn( edict_t *pent ) world_precache(); monster_spawn_count = 0; - + node_spawn_count = 0; + monster_skill_init(); process_monster_precache_cfg(); process_monster_cfg(); + + // node support. -Giegue + // init the WorldGraph. + WorldGraph.InitGraph(); + // make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + { + // NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes(); + } + else + { + // Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + { + // couldn't load, so alloc and prepare to build a graph. + ALERT ( at_console, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes(); + } + else + { + ALERT ( at_console, "\n*Graph Loaded!\n" ); + } + } + check_respawn_time = 0.0; for (index = 0; index < MAX_MONSTER_ENTS; index++) @@ -850,7 +985,21 @@ void mmDispatchThink( edict_t *pent ) RETURN_META(MRES_SUPERCEDE); } } - + + // Manually call think on these other entities + if (FClassnameIs( pent, "testhull" )) + { + // Ensure you do think... + CMBaseEntity::Instance(pent)->Think(); + RETURN_META(MRES_SUPERCEDE); + } + + if (FClassnameIs( pent, "node_viewer" )) + { + CMBaseEntity::Instance(pent)->Think(); + RETURN_META(MRES_SUPERCEDE); + } + RETURN_META(MRES_IGNORED); } // HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) @@ -899,6 +1048,7 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); (g_engfuncs.pfnAddServerCommand)("monster", MonsterCommand); + (g_engfuncs.pfnAddServerCommand)("node_viewer", SpawnViewerCommand); for (index = 0; monster_types[index].name[0]; index++) { @@ -938,7 +1088,31 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) } monster_ents_used = 0; - + + // spawn nodes + for (index = 0; index < node_spawn_count; index++) + { + CMBaseEntity *pNode; + pNode = CreateNormalClassPtr((CNodeEnt *)NULL); + + if (pNode == NULL) + { + //META_CONS("[MONSTER] ERROR: Error Creating Node!" ); + LOG_MESSAGE(PLID, "ERROR: Error Creating Node!"); + } + else + { + pNode->pev->origin = node_spawnpoint[index].origin; + + if (node_spawnpoint[index].is_air_node) + pNode->pev->classname = MAKE_STRING("info_node_air"); + else + pNode->pev->classname = MAKE_STRING("info_node"); + + pNode->Spawn(); + } + } + RETURN_META(MRES_IGNORED); } diff --git a/src/dlls/monster_config.cpp b/src/dlls/monster_config.cpp index d3bc441..86d05f5 100644 --- a/src/dlls/monster_config.cpp +++ b/src/dlls/monster_config.cpp @@ -18,7 +18,7 @@ extern cvar_t *dllapi_log; extern monster_type_t monster_types[]; extern int monster_spawn_count; - +extern int node_spawn_count; bool get_input(FILE *fp, char *input) { @@ -72,9 +72,11 @@ void scan_monster_cfg(FILE *fp) // Let's make a full rework of this. -Giegue char input[1024]; float x, y, z; + bool badent, monster, node; while (get_input(fp, input)) { + badent = monster = node = FALSE; if (input[0] == '{') { // Proper start, initialize entity creation @@ -86,88 +88,167 @@ void scan_monster_cfg(FILE *fp) // It's the end of the entity structure? if (input[0] == '}') { - // Done. Let's process the keyvalues. - for (int i = 0; i < kvd_index; i++) + // Check if the classname of whatever we want to spawn is valid. + if (strcmp(data[kvd_index-1].key, "classname") == 0) { - float x, y, z; - // Any unknown keyvalue is ignored. - // Any duplicate keyvalue is overwritten. - - if (strcmp(data[i].key, "origin") == 0) + int mIndex; + for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++) { - if (sscanf(data[i].value, "%f %f %f", &x, &y, &z) != 3) + if (strcmp(data[kvd_index-1].value, monster_types[mIndex].name) == 0) { - LOG_MESSAGE(PLID, "ERROR: invalid origin: %s", input); // print conflictive line + // Now that I think about it this looks slow and bad code >.> - // reset origin to g_vecZero - LOG_MESSAGE(PLID, "ERROR: entity will spawn at 0 0 0"); - x = y = z = 0; - } - 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 (strcmp(data[i].key, "delay") == 0) - { - // ToDo: Remove this keyvalue. - // Monsters spawned directly should not respawn. - if (sscanf(data[i].value, "%f", &x) != 1) - { - LOG_MESSAGE(PLID, "ERROR: invalid delay: %s", input); // print conflictive line - - // default to 30 seconds - LOG_MESSAGE(PLID, "ERROR: entity respawn frequency will be set to 30 seconds"); - x = 30; - } - monster_spawnpoint[monster_spawn_count].delay = x; - } - else if (strcmp(data[i].key, "angles") == 0) - { - if (sscanf(data[i].value, "%f %f %f", &x, &y, &z) != 3) - { - LOG_MESSAGE(PLID, "ERROR: invalid angles: %s", input); // 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, "classname") == 0) - { - int mIndex; - for (mIndex = 0; monster_types[mIndex].name[0]; mIndex++) - { - if (strcmp(data[i].value, monster_types[mIndex].name) == 0) + // A match is found. What is this? + if (strncmp(monster_types[mIndex].name, "monster", 7) == 0) { - monster_spawnpoint[monster_spawn_count].monster = mIndex; - monster_types[mIndex].need_to_precache = TRUE; - break; + // It's a monster, add it to the list + if (monster_spawn_count == MAX_MONSTERS) + { + // Ouch! Not enough room. + LOG_MESSAGE(PLID, "ERROR: can't add monster, reached MAX_MONSTERS!"); // It will get spammy, sadly. + badent = TRUE; + } + else + { + monster_spawnpoint[monster_spawn_count].monster = mIndex; + monster_types[mIndex].need_to_precache = TRUE; + monster = TRUE; + } + } + else if (strcmp(monster_types[mIndex].name, "info_node") == 0) + { + // Normal node + if (node_spawn_count == MAX_NODES) + { + // The map can't be THAT big can it? + LOG_MESSAGE(PLID, "ERROR: can't add node, reached MAX_NODES!"); // zee spam bOi + badent = TRUE; + } + else + node = TRUE; + } + else if (strcmp(monster_types[mIndex].name, "info_node_air") == 0) + { + // Aerial node + if (node_spawn_count == MAX_NODES) + { + // Ctrl+C --> Ctrl+V + LOG_MESSAGE(PLID, "ERROR: can't add node, reached MAX_NODES!"); // poppo was here. + badent = TRUE; + } + else + { + node_spawnpoint[node_spawn_count].is_air_node = TRUE; + node = TRUE; + } + } + break; + } + } + if (monster_types[mIndex].name[0] == 0) + { + LOG_MESSAGE(PLID, "ERROR: unknown classname: %s", input); // print conflictive line + LOG_MESSAGE(PLID, "ERROR: nothing will spawn here!"); + badent = TRUE; + } + } + else + { + // What are you doing?! + LOG_MESSAGE(PLID, "ERROR: BAD ENTITY STRUCTURE! Last line was %s", input); // print conflictive line + LOG_MESSAGE(PLID, "ERROR: nothing will spawn here!"); + badent = TRUE; + } + + if (!badent) + { + // Done. Let's process the keyvalues. + for (int i = 0; i < (kvd_index-1); i++) + { + // Any unknown keyvalue is ignored. + // Any duplicate keyvalue is 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", input); // 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; } } - if (monster_types[mIndex].name[0] == 0) + else if (strcmp(data[i].key, "delay") == 0) { - LOG_MESSAGE(PLID, "ERROR: unknown classname: %s", input); // print conflictive line - LOG_MESSAGE(PLID, "ERROR: nothing will spawn here!"); + // ToDo: Remove this keyvalue. + // Monsters spawned directly should not respawn. + if (monster) + { + if (sscanf(data[i].value, "%f", &x) != 1) + { + LOG_MESSAGE(PLID, "ERROR: invalid delay: %s", input); // print conflictive line + + // default to 30 seconds + LOG_MESSAGE(PLID, "ERROR: entity respawn frequency will be set to 30 seconds"); + x = 30; + } + monster_spawnpoint[monster_spawn_count].delay = x; + } } + 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", input); // 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; + } + } + } + + if (monster) + { + // Init monster + monster_spawnpoint[monster_spawn_count].respawn_time = gpGlobals->time + 0.1; // spawn (nearly) 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! + // No, I'm not making this idiotproof. Classname should be the last KVD entry on an entity! + LOG_CONSOLE(PLID, "[DEBUG] Added entity: %s", data[kvd_index-1].value); } } - // Init monster - monster_spawnpoint[monster_spawn_count].respawn_time = gpGlobals->time + 0.1; // spawn (nearly) right away - monster_spawnpoint[monster_spawn_count].need_to_respawn = TRUE; - - // Log on? Print all the entities that were added - if (dllapi_log->value) - { - // Classname only, or we will flood the server! - // No, I'm not making this idiotproof. Classname should be the last KVD entry on an entity! - LOG_CONSOLE(PLID, "[DEBUG] Added entity: %s", data[kvd_index-1].value); - } - - monster_spawn_count++; free( data ); break; } diff --git a/src/dlls/monster_plugin.h b/src/dlls/monster_plugin.h index ce578b1..c101829 100644 --- a/src/dlls/monster_plugin.h +++ b/src/dlls/monster_plugin.h @@ -40,6 +40,18 @@ typedef struct { #define MAX_MONSTERS 100 extern monster_spawnpoint_t monster_spawnpoint[MAX_MONSTERS]; +// this is here to store if a node we want to spawn is an ordinary one, or a flying one +typedef struct +{ + Vector origin; + bool is_air_node; +} node_spawnpoint_t; + +// nodes.cpp defines 1024 max nodes, but that amount is likely to trigger a +// no free edicts crash if the server num_edicts is low. Increase if needed. +#define MAX_NODES 256 +extern node_spawnpoint_t node_spawnpoint[MAX_NODES]; + extern DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball extern DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud extern DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion diff --git a/src/dlls/nodes.cpp b/src/dlls/nodes.cpp index 8280b37..cb12def 100644 --- a/src/dlls/nodes.cpp +++ b/src/dlls/nodes.cpp @@ -1452,7 +1452,8 @@ void CTestHull :: Spawn( entvars_t *pevMasterNode ) { SET_MODEL(ENT(pev), "models/player.mdl"); UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); - + + pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; pev->effects = 0; @@ -1474,6 +1475,8 @@ void CTestHull :: Spawn( entvars_t *pevMasterNode ) // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. pev->rendermode = kRenderTransTexture; pev->renderamt = 0; + + pev->classname = MAKE_STRING("testhull"); } //========================================================= @@ -1482,13 +1485,15 @@ void CTestHull :: Spawn( entvars_t *pevMasterNode ) //========================================================= void CTestHull::DropDelay ( void ) { - UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); - + // Do NOT uncomment or you'll get a "Tried to create a message with a bogus message type ( 0 )" crash! + // Left here only because it's on the original HLSDK, and for comedy purposes. -Giegue + //UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + UTIL_SetOrigin ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); SetThink ( &CTestHull::CallBuildNodeGraph ); - pev->nextthink = gpGlobals->time + 1; + pev->nextthink = gpGlobals->time + 2; // think called earlier, so add extra second. -Giegue } //========================================================= @@ -1525,15 +1530,13 @@ void CNodeEnt :: Spawn( void ) return; } - // Give time to the nodes to spawn and get added to the worldgraph, - // TestHull is spawned after map start, not before. -Giegue - /*if ( WorldGraph.m_cNodes == 0 ) + if ( WorldGraph.m_cNodes == 0 ) { // this is the first node to spawn, spawn the test hull entity that builds and walks the node tree - CTestHull *pHull = CreateClassPtr((CTestHull *)NULL); + CTestHull *pHull = CreateNormalClassPtr((CTestHull *)NULL); pHull->Spawn( pev ); - }*/ - + } + if ( WorldGraph.m_cNodes >= MAX_NODES ) { ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); @@ -1552,7 +1555,7 @@ void CNodeEnt :: Spawn( void ) WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; WorldGraph.m_cNodes++; - + REMOVE_ENTITY( edict() ); } @@ -1665,7 +1668,7 @@ void CTestHull :: BuildNodeGraph( void ) if ( !file ) {// file error ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); - + if ( pTempPool ) { free ( pTempPool ); @@ -2464,7 +2467,7 @@ int CGraph :: FLoadGraph ( char *szMapName ) // Set the graph present flag, clear the pointers set flag // m_fGraphPresent = TRUE; - m_fGraphPointersSet = FALSE; + m_fGraphPointersSet = TRUE; // what if...? FREE_FILE(aMemFile); @@ -3472,26 +3475,6 @@ EnoughSaid: // to current location (typically the player). It then draws // as many connects as it can per frame, trying not to overflow the buffer //========================================================= -class CNodeViewer : public CMBaseEntity -{ -public: - void Spawn( void ); - - int m_iBaseNode; - int m_iDraw; - int m_nVisited; - int m_aFrom[128]; - int m_aTo[128]; - int m_iHull; - int m_afNodeType; - Vector m_vecColor; - - void FindNodeConnections( int iNode ); - void AddNode( int iFrom, int iTo ); - void EXPORT DrawThink( void ); - -}; - void CNodeViewer::Spawn( ) { /*CNodeViewer *pViewer = CreateClassPtr((CNodeViewer *)NULL); @@ -3567,6 +3550,8 @@ void CNodeViewer::Spawn( ) m_iDraw = 0; SetThink( &CNodeViewer::DrawThink ); pev->nextthink = gpGlobals->time; + + pev->classname = MAKE_STRING( "node_viewer" ); } diff --git a/src/dlls/nodes.h b/src/dlls/nodes.h index 66cbc6b..930afd2 100644 --- a/src/dlls/nodes.h +++ b/src/dlls/nodes.h @@ -270,6 +270,29 @@ class CNodeEnt : public CMBaseEntity }; +//========================================================= +// Node viewer +//========================================================= +class CNodeViewer : public CMBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; + //========================================================= // CStack - last in, first out. //=========================================================