CFuncMoveLinear ParentingFix: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (If this isn't done, the game will crash when trying to change position after loading a save.)
(Changed to a more lightweight fix from Mapbase)
Line 1: Line 1:
{{warning|This has not been fully tested. Two func_movelinear entities, one parented to the other, has been tested. These fixes were setup under the Source2007 code base.}}
The issue is that two parts of {{ent|func_movelinear}}'s code use {{ent|GetAbsOrigin()}}, the position relative to the world, instead of {{ent|GetLocalOrigin()}}, the position relative to the world ''or parent''. All of these changes are done in <code>func_movelinear.cpp</code> and <code>func_movelinear.h</code>.


Add the following lines to func_movelinear.h under the declaration of m_flMoveDistance:
The first offender is this bit of code in <code>Spawn()</code>:
<source lang=cpp>
<source lang=cpp>
EHANDLE m_hPosition1; // Used to mark end position
m_vecPosition1 = GetAbsOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
EHANDLE m_hPosition2; // Used to mark start position
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
m_vecFinalDest = GetAbsOrigin();
</source>
</source>


Add these definitions to the DATADESC:
Replace that with this code:
<source lang=cpp>
<source lang=cpp>
DEFINE_KEYFIELD( m_hPosition1, FIELD_EHANDLE, "Position1" ),
m_vecPosition1 = GetLocalOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
DEFINE_KEYFIELD( m_hPosition2, FIELD_EHANDLE, "Position1" ),
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
m_vecFinalDest = GetLocalOrigin();
m_vecReference = GetLocalOrigin();
</source>
</source>


Locate these lines in func_movelinear.cpp:
Notice the usage of <code>m_vecReference</code>. That will be declared later in the article.
<source lang=cpp>
m_vecPosition1 = GetAbsOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
</source>


Replace them with this:
The other offender is <code>MoveDone()</code>:
<source lang=cpp>
<source lang=cpp>
m_hPosition1 = CreateEntityByName( "info_target" );
if ( GetAbsOrigin() == m_vecPosition2 )
m_hPosition2 = CreateEntityByName( "info_target" );
 
m_vecPosition1 = GetAbsOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
 
// Update position reference entities
if ( m_hPosition1 != NULL )
{
{
m_hPosition1->SetAbsOrigin( m_vecPosition1 );
m_OnFullyOpen.FireOutput( this, this );
if ( GetParent() )
m_hPosition1->SetParent( GetParent() );
}
}
 
else if ( GetAbsOrigin() == m_vecPosition1 )
if ( m_hPosition2 != NULL )
{
{
m_hPosition2->SetAbsOrigin( m_vecPosition2 );
m_OnFullyClosed.FireOutput( this, this );
if ( GetParent() )
m_hPosition2->SetParent( GetParent() );
}
}
</source>
</source>


Replace the function CFuncMoveLinear::MoveDone with the following code:
Replace that with this code:
<source lang=cpp>
<source lang=cpp>
void CFuncMoveLinear::MoveDone( void )
if ( GetLocalOrigin() == m_vecPosition2 )
{
// Stop sounds at the next think, rather than here as another
// SetPosition call might immediately follow the end of this move
SetThink(&CFuncMoveLinear::StopMoveSound);
SetNextThink( gpGlobals->curtime + 0.1f );
BaseClass::MoveDone();
 
if ( GetLocalOrigin() == m_hPosition2->GetLocalOrigin() )
{
{
m_OnFullyOpen.FireOutput( this, this );
m_OnFullyOpen.FireOutput( this, this );
}
}
else if ( GetLocalOrigin() == m_hPosition1->GetLocalOrigin() )
else if ( GetLocalOrigin() == m_vecPosition1 )
{
{
m_OnFullyClosed.FireOutput( this, this );
m_OnFullyClosed.FireOutput( this, this );
}
}
}
</source>
</source>


Replace the functions Use, SetPosition, InputOpen, and InputClose with the following:
Even though the usage of <code>GetAbsOrigin()</code> is the main problem and the entity should now work properly when used with the <code>parentname</code> keyvalue, the entity may still stop working properly if <code>SetParent</code> or <code>ClearParent</code> are used, as the position the end points are relative to isn't actually saved and is lost when being re/unparented, causing inconsistencies with the <code>func_movelinear</code>'s end points.
 
First, add this to <code>func_movelinear.h</code>, perhaps under the declaration of <code>ShouldSavePhysics()</code>.
<source lang=cpp>
<source lang=cpp>
void CFuncMoveLinear::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
void SetParent( CBaseEntity* pNewParent, int iAttachment = -1 );
{
</source>
if ( useType != USE_SET ) // Momentary buttons will pass down a float in here
return;


if ( value > 1.0 )
In the same file, add these lines under the declaration of <code>m_flMoveDistance</code>:
value = 1.0;
<source lang=cpp>
Vector move = m_hPosition1->GetLocalOrigin() + (value * (m_hPosition2->GetLocalOrigin() - m_hPosition1->GetLocalOrigin()));
// For the parenting fix.
// Prevents position inconsistencies when changing parent.
Vector delta = move - GetLocalOrigin();
Vector m_vecReference;
float speed = delta.Length() * 10;
</source>


MoveTo(move, speed);
Now, in <code>func_movelinear.cpp</code, add this definition to the entity's <code>DATADESC</code>, preferably under <code>m_flMoveDistance</code>:
}
<source lang=cpp>
DEFINE_FIELD( m_vecReference, FIELD_VECTOR ),
</source>


That <code>m_vecReference</code> variable was used earlier in the article as part of <code>Spawn()</code>. It saves the place where the <code>func_movelinear</code> started for when the entity is parented to something else, which ensures the end points are transferred correctly.


Now, let's add that <code>SetParent()</code> we declared:
<source lang=cpp>
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Sets the position as a value from [0..1].
// Purpose: Sets the movement parent of this entity. This entity will be moved
// to a local coordinate calculated from its current absolute offset
// from the parent entity and will then follow the parent entity.
// Input  : pParentEntity - This entity's new parent in the movement hierarchy.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CFuncMoveLinear::SetPosition( float flPosition )
void CFuncMoveLinear::SetParent( CBaseEntity *pParentEntity, int iAttachment )
{
Vector vTargetPos = m_hPosition1->GetLocalOrigin() + ( flPosition * (m_hPosition2->GetLocalOrigin() - m_hPosition1->GetLocalOrigin()));
if ((vTargetPos - GetLocalOrigin()).Length() > 0.001)
{
MoveTo(vTargetPos, m_flSpeed);
}
}
 
 
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CFuncMoveLinear::InputOpen( inputdata_t &inputdata )
{
{
if (GetLocalOrigin() != m_hPosition2->GetLocalOrigin())
Vector oldLocal = GetLocalOrigin();
{
MoveTo(m_hPosition2->GetLocalOrigin(), m_flSpeed);
}
}


BaseClass::SetParent( pParentEntity, iAttachment );


//------------------------------------------------------------------------------
// SOLID_NONE indicates we haven't spawned yet
// Purpose:
if (GetSolid() != SOLID_NONE)
//------------------------------------------------------------------------------
void CFuncMoveLinear::InputClose( inputdata_t &inputdata )
{
if (GetLocalOrigin() != m_hPosition1->GetLocalOrigin())
{
{
MoveTo(m_hPosition1->GetLocalOrigin(), m_flSpeed);
m_vecReference = ((m_vecReference - oldLocal) + GetLocalOrigin());
m_vecPosition1 = m_vecReference - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
m_vecFinalDest = m_vecReference - m_vecFinalDest;
}
}
}
}
</source>
</source>
That should be everything to get <code>func_movelinear</code> to work while parented. It might be simpler than you'd expect, but please keep an eye out for any problems, as this was mostly just tested on one map with a bunch of {{ent|ent_fire}} usage.

Revision as of 16:18, 21 September 2019

The issue is that two parts of func_movelinear's code use GetAbsOrigin(), the position relative to the world, instead of GetLocalOrigin(), the position relative to the world or parent. All of these changes are done in func_movelinear.cpp and func_movelinear.h.

The first offender is this bit of code in Spawn():

	m_vecPosition1 = GetAbsOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
	m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
	m_vecFinalDest = GetAbsOrigin();

Replace that with this code:

	m_vecPosition1 = GetLocalOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
	m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
	m_vecFinalDest = GetLocalOrigin();
	m_vecReference = GetLocalOrigin();

Notice the usage of m_vecReference. That will be declared later in the article.

The other offender is MoveDone():

	if ( GetAbsOrigin() == m_vecPosition2 )
	{
		m_OnFullyOpen.FireOutput( this, this );
	}
	else if ( GetAbsOrigin() == m_vecPosition1 )
	{
		m_OnFullyClosed.FireOutput( this, this );
	}

Replace that with this code:

	if ( GetLocalOrigin() == m_vecPosition2 )
	{
		m_OnFullyOpen.FireOutput( this, this );
	}
	else if ( GetLocalOrigin() == m_vecPosition1 )
	{
		m_OnFullyClosed.FireOutput( this, this );
	}

Even though the usage of GetAbsOrigin() is the main problem and the entity should now work properly when used with the parentname keyvalue, the entity may still stop working properly if SetParent or ClearParent are used, as the position the end points are relative to isn't actually saved and is lost when being re/unparented, causing inconsistencies with the func_movelinear's end points.

First, add this to func_movelinear.h, perhaps under the declaration of ShouldSavePhysics().

void		SetParent( CBaseEntity* pNewParent, int iAttachment = -1 );

In the same file, add these lines under the declaration of m_flMoveDistance:

	// For the parenting fix.
	// Prevents position inconsistencies when changing parent.
	Vector		m_vecReference;

Now, in func_movelinear.cpp</code, add this definition to the entity's DATADESC, preferably under m_flMoveDistance:

	DEFINE_FIELD( m_vecReference, FIELD_VECTOR ),

That m_vecReference variable was used earlier in the article as part of Spawn(). It saves the place where the func_movelinear started for when the entity is parented to something else, which ensures the end points are transferred correctly.

Now, let's add that SetParent() we declared:

//-----------------------------------------------------------------------------
// Purpose: Sets the movement parent of this entity. This entity will be moved
//			to a local coordinate calculated from its current absolute offset
//			from the parent entity and will then follow the parent entity.
// Input  : pParentEntity - This entity's new parent in the movement hierarchy.
//-----------------------------------------------------------------------------
void CFuncMoveLinear::SetParent( CBaseEntity *pParentEntity, int iAttachment )
{
	Vector oldLocal = GetLocalOrigin();

	BaseClass::SetParent( pParentEntity, iAttachment );

	// SOLID_NONE indicates we haven't spawned yet
	if (GetSolid() != SOLID_NONE)
	{
		m_vecReference = ((m_vecReference - oldLocal) + GetLocalOrigin());
		m_vecPosition1 = m_vecReference - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition);
		m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance);
		m_vecFinalDest = m_vecReference - m_vecFinalDest;
	}
}

That should be everything to get func_movelinear to work while parented. It might be simpler than you'd expect, but please keep an eye out for any problems, as this was mostly just tested on one map with a bunch of ent_fire usage.