Hey while using this framework I ran into a couple of problems/bugs I want to list here and how I fixed them.
First inputs can't access member variables of the custom entity class.
PHP Code:
void CMyCustomEnt::Input(inputdata_t &inputdata){
Msg("I am at %X = %i\n", &m_iNom, m_iNom);
}
Since the engine uses the CBaseEntity pointer to call the function the offset of m_iNom is added to the wrong pointer.
This can be fixed if we implement our own "input execution loop". For this I changed:
PHP Code:
#undef DEFINE_INPUTFUNC
#define DEFINE_INPUTFUNC( fieldtype, inputname, inputfunc ) { fieldtype, #inputfunc, { 0, 0 }, 1, FTYPEDESC_INPUT, inputname, NULL, (inputfunc_t)(&classNameTypedef::inputfunc) }
form CEntity.h line 105 to:
PHP Code:
typedef void (CEntity::*inputfunc_centity_t)(inputdata_t &data);
#undef DEFINE_INPUTFUNC
#define DEFINE_INPUTFUNC( fieldtype, inputname, inputfunc ) { fieldtype, #inputfunc, { NULL, NULL }, 1, FTYPEDESC_INPUT, inputname, NULL, (inputfunc_t)static_cast <inputfunc_centity_t> (&classNameTypedef::inputfunc) }
Next I had to change CEntity::GetDataDescMap() form CEntity.cpp line 99 into:
PHP Code:
datamap_t* CEntity::GetDataDescMap()
{
datamap_t* base = NULL;
if (!m_bInGetDataDescMap)
{
base = SH_MCALL(BaseEntity(), GetDataDescMap)();
m_DataMap.baseMap = base;
return &m_DataMap;
}
SET_META_RESULT(MRES_IGNORED);
SH_GLOB_SHPTR->DoRecall();
SourceHook::EmptyClass *thisptr = reinterpret_cast<SourceHook::EmptyClass*>(SH_GLOB_SHPTR->GetIfacePtr());
base = (thisptr->*(__SoureceHook_FHM_GetRecallMFPGetDataDescMap(thisptr)))();
m_DataMap.baseMap = base;
RETURN_META_VALUE(MRES_SUPERCEDE, &m_DataMap);
}
Now I copied some variant_t functions form the engine which will be used for the loop:
From game/server/cbase.cpp line 1303 I copied variant_t::Convert( fieldtype_t newType ). In this function line 1438 to 1449 had to be changed to:
PHP Code:
case FIELD_EHANDLE:
{
// convert the string to an entity by locating it by classname << But the engine uses FindEntityByName
CEntity *ent = NULL;
if (iszVal != NULL_STRING)
{
// FIXME: do we need to pass an activator in here?
CEntity *pEnt;
for (int i = 0; i <= MAX_EDICTS; i++)
{
pEnt = CEntity::Instance(i);
if (!pEnt)
{
continue;
}
char name[128];
if (strcmp(name, pEnt->GetTargetName()) == 0)
{
ent = pEnt;
}
}
}
SetEntity(ent->BaseEntity());
return true;
}
From game/server/cbase.cpp line 1269 I copied variant_t::SetOther( void *data )
From game/server/variant_t.cpp line 13 I copied variant_t::SetEntity( CBaseEntity *val )
All the functions need to be paste to util.cpp, I also had to define:
PHP Code:
CBaseEntityList * g_pEntityList;
which isn't used in these functions but it caused a linker error for me when its not there.
In the last step I copied the "input execution loop" in CBaseEntity::AcceptInput(...) from game/server/baseentity.cpp line 3885 to 3965.
And pasted it in CEntity.cpp CEntity::AcceptInput before line 541 !!!
The function had to be modified so it uses the CEntity pointer and not the CBaseEntity one!
Important parts are:
PHP Code:
inputfunc_centity_t pfnInput = (inputfunc_centity_t)dmap->dataDesc[i].inputFunc;
and
PHP Code:
if (strcmp(dmap->dataClassName, "CEntity") == 0)
break;
So that we jump back to the the engine one...
PHP Code:
bool CEntity::AcceptInput(const char *szInputName, CEntity *pActivator, CEntity *pCaller, variant_t Value, int outputID)
{
for (datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap)
{
// search through all the actions in the data description, looking for a match
for (int i = 0; i < dmap->dataNumFields; i++)
{
if (dmap->dataDesc[i].flags & FTYPEDESC_INPUT)
{
if (!Q_stricmp(dmap->dataDesc[i].externalName, szInputName))
{
// found a match
// convert the value if necessary
if (Value.FieldType() != dmap->dataDesc[i].fieldType)
{
if (!(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING)) // allow empty strings
{
if (!Value.Convert((fieldtype_t)dmap->dataDesc[i].fieldType))
{
// bad conversion
Warning("!! ERROR: bad input/output link:\n!! %s(%s,%s) doesn't match type from %s(%s)\n",
GetClassname(), GetClassname(), szInputName,
(pCaller != NULL) ? pCaller->GetClassname() : "<null>",
(pCaller != NULL) ? pCaller->GetTargetName() : "<null>");
return false;
}
}
}
// call the input handler, or if there is none just set the value
inputfunc_centity_t pfnInput = (inputfunc_centity_t)dmap->dataDesc[i].inputFunc;
if (pfnInput)
{
// Package the data into a struct for passing to the input handler.
inputdata_t data;
data.pActivator = pActivator->BaseEntity();
data.pCaller = pCaller->BaseEntity();
data.value = Value;
data.nOutputID = outputID;
(this->*pfnInput)(data);
}
else if (dmap->dataDesc[i].flags & FTYPEDESC_KEY)
{
// set the value directly
Value.SetOther(((char*)this) + dmap->dataDesc[i].fieldOffset[TD_OFFSET_NORMAL]);
// TODO: if this becomes evil and causes too many full entity updates, then we should make
// a macro like this:
//
// define MAKE_INPUTVAR(x) void Note##x##Modified() { x.GetForModify(); }
//
// Then the datadesc points at that function and we call it here. The only pain is to add
// that function for all the DEFINE_INPUT calls.
//NetworkStateChanged(); //Comment this out!
}
return true;
}
}
}
if (strcmp(dmap->dataClassName, "CEntity") == 0)
break;
}
if (!m_bInAcceptInput)
{
return SH_MCALL(BaseEntity(), AcceptInput)(szInputName, *pActivator, *pCaller, Value, outputID);
}
/**
* This gets the award for the worst hack so far. Detects the end of waiting for players and probably lots of other things.
* Forces players out of vehicles.
.... and so on .....
Now with these changes you can also easily implement custom keyvalues: Hook CBaseEntity::KeyValue(const char*, const char*) and get the function ParseKeyvalue from game/server/saverestore_gamedll.cpp you also need to copy some UTIL_ functions...
Then in CEntity::KeyValue add these before if(!m_bInKeyValue):
PHP Code:
for (datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap)
{
if (ParseKeyvalue(this, dmap->dataDesc, dmap->dataNumFields, szKeyName, szValue))
{
return true;
}
if (strcmp(dmap->dataClassName, "CEntity") == 0)
break;
}
The last bug(problem) I ran into is that when I used ent_create <mycustomentity> the custom classname isn't correctly applied.
The problem is that the string pasted to ent_create is freed after the execution of it and the classname is set at the end of CEntityManager::Create with exactly the pointer to this string...
I fixed it by using the PooledString system like this:
PHP Code:
void CEntity::SetClassname(const char *pClassName)
{
string_t newName = AllocPooledString(pClassName);
m_iClassname = newName;
}