Changing prop models dynamically

From Valve Developer Community
Jump to: navigation, search

One of the major issues I've seen with the Source engine (and with most engines) is the inability to change the model of a prop on-the-fly. Why is this a problem? Well, presently, the issue, for me, has to do with lights, as most of my issues tend to be. In this case, I have a light fixture that's red at one point, but needs to be changed to white when the light being emitted is also changed.

But wait, why not just make another skin for the model? Because that doesn't cover alternate cases, when I want a completely different model. Moving on.

I was pleasantly surprised to find that this is incredibly easy, as things go. It's just small change to a few places, some copy/paste of Valve's existing code, and BAM!, it's done. So, here's the solution:

Modifying the Source Code

First, open up game/server/props.h. Find class CBaseProp : public CBaseAnimating and replace the entire class declaration with this:

class CBaseProp : public CBaseAnimating
{
public:
	DECLARE_CLASS( CBaseProp, CBaseAnimating );
	DECLARE_DATADESC();

	void Spawn( void );
	void Precache( void );
	void Activate( void );
	bool KeyValue( const char *szKeyName, const char *szValue );
	void CalculateBlockLOS( void );
	int  ParsePropData( void );
	
	void DrawDebugGeometryOverlays( void );

	void InputSetModel( inputdata_t &inputdata );

	// Don't treat as a live target
	virtual bool IsAlive( void ) { return false; }
	virtual bool OverridePropdata() { return true; }
};

What this does: Two things: First, it adds the DATADESC. Second, it adds an input handling function.

Next, open up game/server/props.cpp. Find void CBaseProp::Spawn( void ) and above it, add this:

// DATADESC!
BEGIN_DATADESC( CBaseProp )
	// Inputs...  So lonely; only thing here...
	DEFINE_INPUTFUNC( FIELD_STRING,	"SetModel",	InputSetModel ),
END_DATADESC()

What this does: DATADESC is used for things like inputs, outputs, and other such things.

Next, find void CBaseProp::DrawDebugGeometryOverlays( void ) and below it, add this:

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CBaseProp::InputSetModel( inputdata_t &inputdata )
{
	// I borrowed this from CBaseProp::Spawn. - HDC
	char *szModel = (char*)inputdata.value.String();
	if (!szModel || !*szModel)
	{
		Warning( "prop at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
		UTIL_Remove( this );
		return;
	}

	PrecacheModel( szModel );
	Precache();
	SetModel( szModel );

	int iResult = ParsePropData();
	if ( !OverridePropdata() )
	{
		if ( iResult == PARSE_FAILED_BAD_DATA )
		{
			DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has an invalid prop_data type. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel );
			UTIL_Remove( this );
			return;
		}
	}
}

What this does

This is basically a slimmed-down version of what all props do when they spawn, with the only important change being that, instead of using the entity's normal model, it uses the one specified by the parameter passed to it by the input. This code also takes the liberty of removing the "protection" that keeps models ONLY rendering in the types of entities for which they're designed. This is great, in theory, but in practice, things marked as being both static and dynamic often only work with one. So out that goes!