Rotating Pickups/Code: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
<pre>
__TOC__
//================ Coder: Maestro ==============//
== Server ==
//
// Purpose: Make a healthkit that rotates
//
//=============================================================================//


#include "cbase.h" // The base for all entities
<source lang=cpp>
#include "player.h" // The player itself
#include "cbase.h"
#include "items.h" // Where we derive our class from
#include "items.h"
#include "engine/IEngineSound.h" // This takes care of the sounds


// memdbgon must be the last include file in a .cpp file!!!
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#include "tier0/memdbgon.h"


// This defines the size of a box around our pickup
#define PICKUP_DECAL "decals/item_base"
#define ITEM_PICKUP_BOX_BLOAT          24
#define PICKUP_MODEL "models/items/healthkit.mdl"
 
#define PICKUP_MIN_HEIGHT 50
// A full rotation divided by the number of seconds it takes to rotate
int PickupDecalIndex; // set by CRotatingPickup::Precache()
#define ITEM_ROTATION_RATE            ( 360.0f / 9.0f )
 
// Define what will be the default health inside
#define         DEFAULT_HEALTH_TO_GIVE        25
 
// Define what will be the default respawn time
#define        DEFAULT_RESPAWN_TIME          20


#define SF_SUPPRESS_PICKUP_DECAL 0x00000002


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Small rotating health kit. Heals the player when picked up.
// Rotating health kit. Heals the player when picked up.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CRotatingPickup : public CItem
class CRotatingPickup : public CItem
{
{
public:
DECLARE_CLASS( CRotatingPickup, CItem );
DECLARE_CLASS( CRotatingPickup, CItem );
DECLARE_DATADESC();
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
public:


CRotatingPickup();
CRotatingPickup();


void Spawn( void );
void Spawn();
void Precache( void );
void Activate();
void Precache();


bool MyTouch( CBasePlayer *pPlayer );
bool MyTouch( CBasePlayer *pPlayer );
void FallThink( void ) { return; } // Override the function that makes items fall to the ground
void RotateThink( void );


CBaseEntity* Respawn( void );
CBaseEntity* Respawn();
void Materialize( void );
void Materialize();


protected:
int m_iHealthToGive;
 
float m_fRespawnTime;
int m_iHealthToGive;
CNetworkVar(bool, m_bRespawning);
int m_iRespawnTime;
Vector RespawnPosition;


private:
private:
 
Vector MdlTop;
void UpdateSpawnPosition( Vector originalSpawnPosition );
 
};
};


Line 65: Line 50:


BEGIN_DATADESC( CRotatingPickup )
BEGIN_DATADESC( CRotatingPickup )
DEFINE_KEYFIELD( m_iHealthToGive, FIELD_INTEGER, "givehealth"),
DEFINE_KEYFIELD( m_fRespawnTime, FIELD_FLOAT, "respawntime"),
END_DATADESC()


DEFINE_KEYFIELD( m_iHealthToGive, FIELD_INTEGER, "health"),
IMPLEMENT_SERVERCLASS_ST( CRotatingPickup, DT_RotatingPickup )
DEFINE_KEYFIELD( m_iRespawnTime, FIELD_INTEGER, "respawntime"),
SendPropBool( SENDINFO( m_bRespawning )),
 
SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
DEFINE_THINKFUNC( RotateThink ),
END_SEND_TABLE()


END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Initialize member variables
//-----------------------------------------------------------------------------
CRotatingPickup::CRotatingPickup()
CRotatingPickup::CRotatingPickup()
{
{
m_bShouldFall = false;
if ( m_iHealthToGive <= 0 )
if ( m_iHealthToGive <= 0 )
m_iHealthToGive = DEFAULT_HEALTH_TO_GIVE;
m_iHealthToGive = 25;


if ( m_iRespawnTime <= 0 )
if ( m_fRespawnTime <= 0 )
m_iRespawnTime = DEFAULT_RESPAWN_TIME;
m_fRespawnTime = 20;
}
}


//-----------------------------------------------------------------------------
void CRotatingPickup::Spawn()
// Purpose:
//-----------------------------------------------------------------------------
void CRotatingPickup::Spawn( void )
{
{
BaseClass::Spawn(); //Spawn the baseclass
// CItem is designed for Vphys objects, so we need to undo a couple of things its spawn() does
Vector OriginalLocation = GetAbsOrigin();
BaseClass::Spawn();
VPhysicsDestroyObject();
SetAbsOrigin(OriginalLocation);


Precache(); // Make sure the assets are loaded
UseClientSideAnimation();
SetModel(PICKUP_MODEL);


SetMoveType( MOVETYPE_NONE ); // It will only rotate, not move
SetMoveType(MOVETYPE_NONE);
SetSolid( SOLID_BBOX ); // It is solid
SetCollisionGroup( COLLISION_GROUP_WEAPON ); // And it can collide with stuff


CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT ); // Create a collision trigger around the object
// Grab the highest point on the model before we change the bounding box
SetTouch(&CRotatingPickup::ItemTouch); // ItemTouch is a function in our base class that takes care of touches
MdlTop = GetAbsOrigin();
 
MdlTop.z += GetModelPtr()->hull_max().z;
UpdateSpawnPosition( GetAbsOrigin() ); // We update our position relative to the ground
 
SetSolid(SOLID_NONE);
// Set the x angle, since it will never change
CollisionProp()->UseTriggerBounds(true,6); // Reign in the volume added to the trigger collision box
QAngle angle = GetAbsAngles();
Vector OBBSize = Vector(CollisionProp()->OBBSize().Length() / 2); // need to use length as the model will be rotated at 45 degrees on clients
angle.x = 45;
SetSize(-OBBSize,OBBSize); // Resize the bounding box
SetAbsAngles( angle );
 
AddEffects(EF_NOSHADOW);
m_takedamage = DAMAGE_EVENTS_ONLY;
 
SetModel( "models/items/healthkit.mdl" ); // Set the model we'll use
 
// Start thinking in 0.01 seconds
SetThink( &CRotatingPickup::RotateThink );
SetNextThink( gpGlobals->curtime + 0.01f );
}
}


//-----------------------------------------------------------------------------
void CRotatingPickup::Activate()
// Purpose: Make sure the engine loads the sounds and models before they are used
//-----------------------------------------------------------------------------
void CRotatingPickup::Precache( void )
{
{
PrecacheModel( "models/items/healthkit.mdl" ); // Change this to get another model
BaseClass::Activate();
PrecacheScriptSound( "HealthKit.Touch" ); // scripts/game_sounds_items.txt
 
// Ensure minimum distance above a standable surfare
trace_t tr;
UTIL_TraceLine(MdlTop,MdlTop + Vector(0,0,-PICKUP_MIN_HEIGHT),MASK_PLAYERSOLID,this,COLLISION_GROUP_NONE,&tr); // measuring from MdlTop
if(tr.DidHit())
{
if ( !HasSpawnFlags( SF_SUPPRESS_PICKUP_DECAL ) )
engine->StaticDecal(tr.endpos,PickupDecalIndex,0,0,false); // mark the location of the pickup
SetAbsOrigin( GetAbsOrigin() + ( Vector(0,0,PICKUP_MIN_HEIGHT*(1-tr.fraction)) ) );
}
}
}


void CRotatingPickup::UpdateSpawnPosition( Vector originalPosition )
void CRotatingPickup::Precache()
{
{
// Create local variables
PrecacheModel( PICKUP_MODEL );
trace_t tr; // The trace
PrecacheScriptSound( "HealthKit.Touch" );
Vector end, dir, final; // The vectors
PrecacheScriptSound( "AlyxEmp.Charge" );
QAngle down; // The angles
PickupDecalIndex = UTIL_PrecacheDecal(PICKUP_DECAL, true );
 
down.y = -90; //Make angle point down
 
AngleVectors( down, &dir); //Make the vector point to the angle
 
end = originalPosition + dir * MAX_TRACE_LENGTH; // Get the end point
 
// Trace a line down to the ground
UTIL_TraceLine( originalPosition, end, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
 
final = tr.endpos; // final is now the position of the ground right beneath our entity
 
final.z += 50; // Add 50 units in height
 
SetAbsOrigin( final ); // Update our position to be 50 units above the ground
RespawnPosition = final; // Store our position for easy access later
}
}


//-----------------------------------------------------------------------------
// Called from CItem::ItemTouch()
// Purpose: Give the player health and plays a sound
// Input  : *pPlayer -
// Output :
//-----------------------------------------------------------------------------
bool CRotatingPickup::MyTouch( CBasePlayer *pPlayer )
bool CRotatingPickup::MyTouch( CBasePlayer *pPlayer )
{
{
//Check the pointer and check if the player needs more health
if ( pPlayer && pPlayer->GetHealth() < pPlayer->GetMaxHealth() )  
if ( pPlayer && pPlayer->GetHealth() < pPlayer->GetMaxHealth() )  
{
{
pPlayer->TakeHealth( m_iHealthToGive, DMG_GENERIC );
pPlayer->TakeHealth( m_iHealthToGive, DMG_GENERIC );
CSingleUserRecipientFilter PlayerFilter( pPlayer );
PlayerFilter.MakeReliable();


// This code is related to the hud
UserMessageBegin( PlayerFilter, "ItemPickup" );
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
 
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( GetClassname() );
WRITE_STRING( GetClassname() );
MessageEnd();
MessageEnd();
EmitSound( PlayerFilter, pPlayer->entindex(), "HealthKit.Touch" ); // this should be done by the HUD really


// Output the sound sound
Respawn();
CPASAttenuationFilter filter( pPlayer, "HealthKit.Touch" );
EmitSound( filter, pPlayer->entindex(), "HealthKit.Touch" );
 
//Msg("A player picked up something!\n" ); //Uncomment this line to get a note in the console when picked up
 
Respawn(); // Respawn our pickup
 
return true;
return true;
}
}
Line 186: Line 138:
}
}


//-----------------------------------------------------------------------------
// Disappear
// Purpose: Initiate the respawning process
CBaseEntity* CRotatingPickup::Respawn()
//-----------------------------------------------------------------------------
CBaseEntity* CRotatingPickup::Respawn( void )
{
{
// It can't be touched and it can't be seen
SetTouch(NULL);
SetTouch( NULL );
m_bRespawning = true;
AddEffects( EF_NODRAW );
SetThink ( &CRotatingPickup::Materialize );
SetNextThink( gpGlobals->curtime + m_fRespawnTime );


//Reset the movetypes
return this;
SetMoveType( MOVETYPE_NONE );
}
SetSolid( SOLID_BBOX );
SetCollisionGroup( COLLISION_GROUP_WEAPON );


UTIL_SetOrigin( this, RespawnPosition ); //Get our respawn position from earlier
// Reappear
void CRotatingPickup::Materialize()
{
EmitSound("AlyxEmp.Charge");
m_bRespawning = false;
SetTouch(&CItem::ItemTouch);
}
</source>


// Reset the angles
== Client ==
QAngle angle = GetAbsAngles();
angle.x = 45;
SetAbsAngles( angle );


RemoveAllDecals(); //Remove any decals
<source lang=cpp>
#include "cbase.h"


//Start thinking when the pickup should appear again
// memdbgon must be the last include file in a .cpp file!!!
SetThink ( &CRotatingPickup::Materialize );
#include "tier0/memdbgon.h"
SetNextThink( gpGlobals->curtime + m_iRespawnTime );


return this;
#define ITEM_ROTATION_RATE ( 360.0f / 4.0f )
}
#define PICKUP_MIN_HEIGHT 50


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Finalize the respawning process
// Rotating health kit. Heals the player when picked up.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CRotatingPickup::Materialize( void )
class C_RotatingPickup : public C_BaseAnimating
{
{
if ( IsEffectActive( EF_NODRAW ) )
DECLARE_CLASS( C_RotatingPickup, C_BaseAnimating );
{
DECLARE_CLIENTCLASS();
//Changing from invisible state to visible.
public:
RemoveEffects( EF_NODRAW );
C_RotatingPickup() {
ClientRotAng = QAngle(45,0,0);
m_bRespawning = m_bRespawning_Cache = false;
}
}
void Spawn() { ClientThink(); }


EmitSound( "AlyxEmp.Charge" ); //Emit a sound
bool IsRespawning();
void ClientThink();
void PostDataUpdate(DataUpdateType_t updateType);
bool ShouldDraw();


SetTouch( &CRotatingPickup::ItemTouch ); //Reset our think functions
bool m_bRespawning;
bool m_bRespawning_Cache;


SetThink( &CRotatingPickup::RotateThink ); // Start rotating again
private:
SetNextThink( gpGlobals->curtime + 0.01f ); // Think in 0.01 sec
QAngle ClientRotAng; // m_angRotation is stomped sometimes (CItem returning the ent to spawn position?)
};
 
LINK_ENTITY_TO_CLASS( item_rotating, C_RotatingPickup );
 
IMPLEMENT_CLIENTCLASS_DT( C_RotatingPickup, DT_RotatingPickup,CRotatingPickup )
RecvPropBool( RECVINFO(m_bRespawning) ),
END_RECV_TABLE()
 
inline bool C_RotatingPickup::IsRespawning()
{
return m_bRespawning;
}
}


//-----------------------------------------------------------------------------
void C_RotatingPickup::ClientThink()
// Purpose: Make our model rotate
//-----------------------------------------------------------------------------
void CRotatingPickup::RotateThink( void )
{
{
// This makes sure the model rotates independent of the fps
if (IsAbsQueriesValid())
float dt = gpGlobals->curtime - GetLastThink();  
{
// Rotate
ClientRotAng.y += ITEM_ROTATION_RATE * gpGlobals->frametime;
if ( ClientRotAng.y >= 360 )
ClientRotAng.y -= 360;


QAngle angles = GetAbsAngles(); //Get the current angles
SetAbsAngles( ClientRotAng );
}
 
SetNextClientThink(CLIENT_THINK_ALWAYS);
}


// Set the angles according to the rotation rate and fps
void C_RotatingPickup::PostDataUpdate(DataUpdateType_t updateType)
angles.y += ( ITEM_ROTATION_RATE * dt );  
{
if (m_bRespawning_Cache != m_bRespawning)
{
// Appear/disappear
UpdateVisibility();
ClientRotAng.y = 0;
m_bRespawning_Cache = m_bRespawning;
}


if ( angles.y >= 360 ) // If the rotation is more than 360,
return BaseClass::PostDataUpdate(updateType);
angles.y -= 360; // subtract 360 to avoid large variables
}


SetAbsAngles( angles ); // Set the angles now
bool C_RotatingPickup::ShouldDraw()
SetNextThink( gpGlobals->curtime + 0.01f ); // Think again in 0.01 sec
{
return !IsRespawning() && BaseClass::ShouldDraw();
}
}
</pre>
</source>


== See Also ==
== FGD ==


*[[Rotating Pickups|Rotating Pickups]]
<source lang=php>
@PointClass base(Item) studio("models/items/healthkit.mdl") = item_rotating: "A spinning health kit"
[
givehealth(integer) : "Health to give" : 25
respawntime(float) : "Respawn time" : 20
spawnflags(Flags) =
[
2 : "Suppress location decal" : 0
]
]
</source>

Revision as of 14:53, 3 April 2011

Server

#include "cbase.h"
#include "items.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define PICKUP_DECAL "decals/item_base"
#define PICKUP_MODEL "models/items/healthkit.mdl"
#define PICKUP_MIN_HEIGHT 50
int PickupDecalIndex; // set by CRotatingPickup::Precache()

#define SF_SUPPRESS_PICKUP_DECAL	0x00000002

//-----------------------------------------------------------------------------
// Rotating health kit. Heals the player when picked up.
//-----------------------------------------------------------------------------
class CRotatingPickup : public CItem
{
	DECLARE_CLASS( CRotatingPickup, CItem );
	DECLARE_DATADESC();
	DECLARE_SERVERCLASS();
public:

	CRotatingPickup();

	void	Spawn();
	void	Activate();
	void	Precache();

	bool	MyTouch( CBasePlayer *pPlayer );

	CBaseEntity*	Respawn();
	void			Materialize();

	int	m_iHealthToGive;
	float m_fRespawnTime;
	CNetworkVar(bool, m_bRespawning);

private:
	Vector MdlTop;
};

LINK_ENTITY_TO_CLASS( item_rotating, CRotatingPickup );

PRECACHE_REGISTER( item_rotating );

BEGIN_DATADESC( CRotatingPickup )
	DEFINE_KEYFIELD( m_iHealthToGive, FIELD_INTEGER, "givehealth"),
	DEFINE_KEYFIELD( m_fRespawnTime, FIELD_FLOAT, "respawntime"),
END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CRotatingPickup, DT_RotatingPickup )
	SendPropBool( SENDINFO( m_bRespawning )),
	SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
END_SEND_TABLE()

CRotatingPickup::CRotatingPickup()
{
	if ( m_iHealthToGive <= 0 )
		m_iHealthToGive = 25;

	if ( m_fRespawnTime <= 0 )
		m_fRespawnTime = 20;
}

void CRotatingPickup::Spawn()
{
	// CItem is designed for Vphys objects, so we need to undo a couple of things its spawn() does
	Vector OriginalLocation = GetAbsOrigin();
		BaseClass::Spawn();
	VPhysicsDestroyObject();
	SetAbsOrigin(OriginalLocation);

	UseClientSideAnimation();
	SetModel(PICKUP_MODEL);

	SetMoveType(MOVETYPE_NONE);

	// Grab the highest point on the model before we change the bounding box
	MdlTop = GetAbsOrigin();
	MdlTop.z += GetModelPtr()->hull_max().z;
			
	SetSolid(SOLID_NONE);
	CollisionProp()->UseTriggerBounds(true,6); // Reign in the volume added to the trigger collision box
	Vector OBBSize = Vector(CollisionProp()->OBBSize().Length() / 2); // need to use length as the model will be rotated at 45 degrees on clients
	SetSize(-OBBSize,OBBSize); // Resize the bounding box
	
	AddEffects(EF_NOSHADOW);	
}

void CRotatingPickup::Activate()
{
	BaseClass::Activate();

	// Ensure minimum distance above a standable surfare
	trace_t tr;
	UTIL_TraceLine(MdlTop,MdlTop + Vector(0,0,-PICKUP_MIN_HEIGHT),MASK_PLAYERSOLID,this,COLLISION_GROUP_NONE,&tr); // measuring from MdlTop
	if(tr.DidHit())
	{
		if ( !HasSpawnFlags( SF_SUPPRESS_PICKUP_DECAL ) )
			engine->StaticDecal(tr.endpos,PickupDecalIndex,0,0,false); // mark the location of the pickup
		SetAbsOrigin( GetAbsOrigin() + ( Vector(0,0,PICKUP_MIN_HEIGHT*(1-tr.fraction)) ) );
	}
}

void CRotatingPickup::Precache()
{
	PrecacheModel( PICKUP_MODEL );
	PrecacheScriptSound( "HealthKit.Touch" );
	PrecacheScriptSound( "AlyxEmp.Charge" );
	PickupDecalIndex = UTIL_PrecacheDecal(PICKUP_DECAL, true );
}

// Called from CItem::ItemTouch()
bool CRotatingPickup::MyTouch( CBasePlayer *pPlayer )
{
	if ( pPlayer && pPlayer->GetHealth() < pPlayer->GetMaxHealth() ) 
	{
		pPlayer->TakeHealth( m_iHealthToGive, DMG_GENERIC );
		
		CSingleUserRecipientFilter PlayerFilter( pPlayer );
		PlayerFilter.MakeReliable();

		UserMessageBegin( PlayerFilter, "ItemPickup" );
		WRITE_STRING( GetClassname() );
		MessageEnd();
		EmitSound( PlayerFilter, pPlayer->entindex(), "HealthKit.Touch" ); // this should be done by the HUD really

		Respawn();
		return true;
	}

	return false;
}

// Disappear
CBaseEntity* CRotatingPickup::Respawn()
{
	SetTouch(NULL);
	m_bRespawning = true;
	
	SetThink ( &CRotatingPickup::Materialize );
	SetNextThink( gpGlobals->curtime + m_fRespawnTime );

	return this;
}

// Reappear
void CRotatingPickup::Materialize()
{
	EmitSound("AlyxEmp.Charge");
	m_bRespawning = false;
	SetTouch(&CItem::ItemTouch);
}

Client

#include "cbase.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define ITEM_ROTATION_RATE	( 360.0f / 4.0f )
#define PICKUP_MIN_HEIGHT 50

//-----------------------------------------------------------------------------
// Rotating health kit. Heals the player when picked up.
//-----------------------------------------------------------------------------
class C_RotatingPickup : public C_BaseAnimating
{
	DECLARE_CLASS( C_RotatingPickup, C_BaseAnimating );
	DECLARE_CLIENTCLASS();
public:	
	C_RotatingPickup() {
		ClientRotAng = QAngle(45,0,0);
		m_bRespawning = m_bRespawning_Cache = false;
	}
	void Spawn() { ClientThink(); }

	bool IsRespawning();
	void ClientThink();
	void PostDataUpdate(DataUpdateType_t updateType);
	bool ShouldDraw();

	bool	m_bRespawning;
	bool	m_bRespawning_Cache;

private:
	QAngle		ClientRotAng; // m_angRotation is stomped sometimes (CItem returning the ent to spawn position?)
};

LINK_ENTITY_TO_CLASS( item_rotating, C_RotatingPickup );

IMPLEMENT_CLIENTCLASS_DT( C_RotatingPickup, DT_RotatingPickup,CRotatingPickup )
	RecvPropBool( RECVINFO(m_bRespawning) ),
END_RECV_TABLE()

inline bool C_RotatingPickup::IsRespawning()
{
	return m_bRespawning;
}

void C_RotatingPickup::ClientThink()
{
	if (IsAbsQueriesValid())
	{
		// Rotate
		ClientRotAng.y += ITEM_ROTATION_RATE * gpGlobals->frametime;
		if ( ClientRotAng.y >= 360 )
			ClientRotAng.y -= 360;

		SetAbsAngles( ClientRotAng );
	}

	SetNextClientThink(CLIENT_THINK_ALWAYS);
}

void C_RotatingPickup::PostDataUpdate(DataUpdateType_t updateType)
{
	if (m_bRespawning_Cache != m_bRespawning)
	{
		// Appear/disappear
		UpdateVisibility();
		ClientRotAng.y = 0;
		m_bRespawning_Cache = m_bRespawning;
	}

	return BaseClass::PostDataUpdate(updateType);
}

bool C_RotatingPickup::ShouldDraw()
{
	return !IsRespawning() && BaseClass::ShouldDraw();
}

FGD

@PointClass base(Item) studio("models/items/healthkit.mdl") = item_rotating: "A spinning health kit"
[
	givehealth(integer) : "Health to give" : 25
	respawntime(float) : "Respawn time" : 20
	
	spawnflags(Flags) =
	[
		2 : "Suppress location decal" : 0
	]
]