You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
576 lines
15 KiB
576 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Handling for the base world item. Most of this was moved from items.cpp.
|
|
//
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "player.h"
|
|
#include "items.h"
|
|
#include "gamerules.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "iservervehicle.h"
|
|
#include "physics_saverestore.h"
|
|
#include "world.h"
|
|
|
|
#ifdef HL2MP
|
|
#include "hl2mp_gamerules.h"
|
|
#endif
|
|
|
|
#ifdef TF_DLL
|
|
#include "tf_player.h"
|
|
#include "entity_healthkit.h"
|
|
#include "particle_parse.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define ITEM_PICKUP_BOX_BLOAT 24
|
|
|
|
class CWorldItem : public CBaseAnimating
|
|
{
|
|
DECLARE_DATADESC();
|
|
public:
|
|
DECLARE_CLASS( CWorldItem, CBaseAnimating );
|
|
|
|
bool KeyValue( const char *szKeyName, const char *szValue );
|
|
void Spawn( void );
|
|
|
|
int m_iType;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(world_items, CWorldItem);
|
|
|
|
BEGIN_DATADESC( CWorldItem )
|
|
|
|
DEFINE_FIELD( m_iType, FIELD_INTEGER ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
bool CWorldItem::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if (FStrEq(szKeyName, "type"))
|
|
{
|
|
m_iType = atoi(szValue);
|
|
}
|
|
else
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CWorldItem::Spawn( void )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
switch (m_iType)
|
|
{
|
|
case 44: // ITEM_BATTERY:
|
|
pEntity = CBaseEntity::Create( "item_battery", GetLocalOrigin(), GetLocalAngles() );
|
|
break;
|
|
case 45: // ITEM_SUIT:
|
|
pEntity = CBaseEntity::Create( "item_suit", GetLocalOrigin(), GetLocalAngles() );
|
|
break;
|
|
}
|
|
|
|
if (!pEntity)
|
|
{
|
|
Warning("unable to create world_item %d\n", m_iType );
|
|
}
|
|
else
|
|
{
|
|
pEntity->m_target = m_target;
|
|
pEntity->SetName( GetEntityName() );
|
|
pEntity->ClearSpawnFlags();
|
|
pEntity->AddSpawnFlags( m_spawnflags );
|
|
}
|
|
|
|
UTIL_RemoveImmediate( this );
|
|
}
|
|
|
|
|
|
BEGIN_DATADESC( CItem )
|
|
|
|
DEFINE_FIELD( m_bActivateWhenAtRest, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vOriginalSpawnOrigin, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_vOriginalSpawnAngles, FIELD_VECTOR ),
|
|
DEFINE_PHYSPTR( m_pConstraint ),
|
|
|
|
// Function Pointers
|
|
DEFINE_ENTITYFUNC( ItemTouch ),
|
|
DEFINE_THINKFUNC( Materialize ),
|
|
DEFINE_THINKFUNC( ComeToRest ),
|
|
|
|
#if defined( HL2MP ) || defined( TF_DLL )
|
|
DEFINE_FIELD( m_flNextResetCheckTime, FIELD_TIME ),
|
|
DEFINE_THINKFUNC( FallThink ),
|
|
#endif
|
|
|
|
// Outputs
|
|
DEFINE_OUTPUT( m_OnPlayerTouch, "OnPlayerTouch" ),
|
|
DEFINE_OUTPUT( m_OnCacheInteraction, "OnCacheInteraction" ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CItem::CItem()
|
|
{
|
|
m_bActivateWhenAtRest = false;
|
|
}
|
|
|
|
bool CItem::CreateItemVPhysicsObject( void )
|
|
{
|
|
// Create the object in the physics system
|
|
int nSolidFlags = GetSolidFlags() | FSOLID_NOT_STANDABLE;
|
|
if ( !m_bActivateWhenAtRest )
|
|
{
|
|
nSolidFlags |= FSOLID_TRIGGER;
|
|
}
|
|
|
|
if ( VPhysicsInitNormal( SOLID_VPHYSICS, nSolidFlags, false ) == NULL )
|
|
{
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( nSolidFlags );
|
|
|
|
// If it's not physical, drop it to the floor
|
|
if (UTIL_DropToFloor(this, MASK_SOLID) == 0)
|
|
{
|
|
Warning( "Item %s fell out of level at %f,%f,%f\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z);
|
|
UTIL_Remove( this );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::Spawn( void )
|
|
{
|
|
if ( g_pGameRules->IsAllowedToSpawn( this ) == false )
|
|
{
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
|
|
SetMoveType( MOVETYPE_FLYGRAVITY );
|
|
SetSolid( SOLID_BBOX );
|
|
SetBlocksLOS( false );
|
|
AddEFlags( EFL_NO_ROTORWASH_PUSH );
|
|
|
|
if( IsX360() )
|
|
{
|
|
AddEffects( EF_ITEM_BLINK );
|
|
}
|
|
|
|
// This will make them not collide with the player, but will collide
|
|
// against other items + weapons
|
|
SetCollisionGroup( COLLISION_GROUP_WEAPON );
|
|
CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT );
|
|
SetTouch(&CItem::ItemTouch);
|
|
|
|
if ( CreateItemVPhysicsObject() == false )
|
|
return;
|
|
|
|
m_takedamage = DAMAGE_EVENTS_ONLY;
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
// Constrained start?
|
|
if ( HasSpawnFlags( SF_ITEM_START_CONSTRAINED ) )
|
|
{
|
|
//Constrain the weapon in place
|
|
IPhysicsObject *pReferenceObject, *pAttachedObject;
|
|
|
|
pReferenceObject = g_PhysWorldObject;
|
|
pAttachedObject = VPhysicsGetObject();
|
|
|
|
if ( pReferenceObject && pAttachedObject )
|
|
{
|
|
constraint_fixedparams_t fixed;
|
|
fixed.Defaults();
|
|
fixed.InitWithCurrentObjectState( pReferenceObject, pAttachedObject );
|
|
|
|
fixed.constraint.forceLimit = lbs2kg( 10000 );
|
|
fixed.constraint.torqueLimit = lbs2kg( 10000 );
|
|
|
|
m_pConstraint = physenv->CreateFixedConstraint( pReferenceObject, pAttachedObject, NULL, fixed );
|
|
|
|
m_pConstraint->SetGameData( (void *) this );
|
|
}
|
|
}
|
|
#endif //CLIENT_DLL
|
|
|
|
#if defined( HL2MP ) || defined( TF_DLL )
|
|
SetThink( &CItem::FallThink );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
#endif
|
|
}
|
|
|
|
unsigned int CItem::PhysicsSolidMaskForEntity( void ) const
|
|
{
|
|
return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_PLAYERCLIP;
|
|
}
|
|
|
|
void CItem::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->PickupObject( this );
|
|
}
|
|
}
|
|
|
|
extern int gEvilImpulse101;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Activate when at rest, but don't allow pickup until then
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::ActivateWhenAtRest( float flTime /* = 0.5f */ )
|
|
{
|
|
RemoveSolidFlags( FSOLID_TRIGGER );
|
|
m_bActivateWhenAtRest = true;
|
|
SetThink( &CItem::ComeToRest );
|
|
SetNextThink( gpGlobals->curtime + flTime );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Become touchable when we are at rest
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::OnEntityEvent( EntityEvent_t event, void *pEventData )
|
|
{
|
|
BaseClass::OnEntityEvent( event, pEventData );
|
|
|
|
switch( event )
|
|
{
|
|
case ENTITY_EVENT_WATER_TOUCH:
|
|
{
|
|
// Delay rest for a sec, to avoid changing collision
|
|
// properties inside a collision callback.
|
|
SetThink( &CItem::ComeToRest );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Become touchable when we are at rest
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::ComeToRest( void )
|
|
{
|
|
if ( m_bActivateWhenAtRest )
|
|
{
|
|
m_bActivateWhenAtRest = false;
|
|
AddSolidFlags( FSOLID_TRIGGER );
|
|
SetThink( NULL );
|
|
}
|
|
}
|
|
|
|
#if defined( HL2MP ) || defined( TF_DLL )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Items that have just spawned run this think to catch them when
|
|
// they hit the ground. Once we're sure that the object is grounded,
|
|
// we change its solid type to trigger and set it in a large box that
|
|
// helps the player get it.
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::FallThink ( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
#if defined( HL2MP )
|
|
bool shouldMaterialize = false;
|
|
IPhysicsObject *pPhysics = VPhysicsGetObject();
|
|
if ( pPhysics )
|
|
{
|
|
shouldMaterialize = pPhysics->IsAsleep();
|
|
}
|
|
else
|
|
{
|
|
shouldMaterialize = (GetFlags() & FL_ONGROUND) ? true : false;
|
|
}
|
|
|
|
if ( shouldMaterialize )
|
|
{
|
|
SetThink ( NULL );
|
|
|
|
m_vOriginalSpawnOrigin = GetAbsOrigin();
|
|
m_vOriginalSpawnAngles = GetAbsAngles();
|
|
|
|
HL2MPRules()->AddLevelDesignerPlacedObject( this );
|
|
}
|
|
#endif // HL2MP
|
|
|
|
#if defined( TF_DLL )
|
|
// We only come here if ActivateWhenAtRest() is never called,
|
|
// which is the case when creating currencypacks in MvM
|
|
if ( !( GetFlags() & FL_ONGROUND ) )
|
|
{
|
|
if ( !GetAbsVelocity().Length() && GetMoveType() == MOVETYPE_FLYGRAVITY )
|
|
{
|
|
// Mr. Game, meet Mr. Hammer. Mr. Hammer, meet the uncooperative Mr. Physics.
|
|
// Mr. Physics really doesn't want to give our friend the FL_ONGROUND flag.
|
|
// This means our wonderfully helpful radius currency collection code will be sad.
|
|
// So in the name of justice, we ask that this flag be delivered unto him.
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetGroundEntity( GetWorldEntity() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetThink( &CItem::ComeToRest );
|
|
}
|
|
#endif // TF
|
|
}
|
|
|
|
#endif // HL2MP, TF
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Used to tell whether an item may be picked up by the player. This
|
|
// accounts for solid obstructions being in the way.
|
|
// Input : *pItem - item in question
|
|
// *pPlayer - player attempting the pickup
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool UTIL_ItemCanBeTouchedByPlayer( CBaseEntity *pItem, CBasePlayer *pPlayer )
|
|
{
|
|
if ( pItem == NULL || pPlayer == NULL )
|
|
return false;
|
|
|
|
// For now, always allow a vehicle riding player to pick up things they're driving over
|
|
if ( pPlayer->IsInAVehicle() )
|
|
return true;
|
|
|
|
// Get our test positions
|
|
Vector vecStartPos;
|
|
IPhysicsObject *pPhysObj = pItem->VPhysicsGetObject();
|
|
if ( pPhysObj != NULL )
|
|
{
|
|
// Use the physics hull's center
|
|
QAngle vecAngles;
|
|
pPhysObj->GetPosition( &vecStartPos, &vecAngles );
|
|
}
|
|
else
|
|
{
|
|
// Use the generic bbox center
|
|
vecStartPos = pItem->CollisionProp()->WorldSpaceCenter();
|
|
}
|
|
|
|
#ifdef TF_DLL
|
|
//Plague powerup carrier collects health kits in a radius so we want to skip the occlusion trace
|
|
CTFPlayer *pTFPlayer = dynamic_cast<CTFPlayer*>( pPlayer );
|
|
if ( pTFPlayer && ( pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_PLAGUE ) )
|
|
{
|
|
CHealthKit *pHealthKit = dynamic_cast<CHealthKit*>( pItem );
|
|
if ( pHealthKit )
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
Vector vecEndPos = pPlayer->EyePosition();
|
|
|
|
// FIXME: This is the simple first try solution towards the problem. We need to take edges and shape more into account
|
|
// for this to be fully robust.
|
|
|
|
// Trace between to see if we're occluded
|
|
trace_t tr;
|
|
CTraceFilterSkipTwoEntities filter( pPlayer, pItem, COLLISION_GROUP_PLAYER_MOVEMENT );
|
|
UTIL_TraceLine( vecStartPos, vecEndPos, MASK_SOLID, &filter, &tr );
|
|
|
|
// Occluded
|
|
// FIXME: For now, we exclude starting in solid because there are cases where this doesn't matter
|
|
if ( tr.fraction < 1.0f )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Whether or not the item can be touched and picked up by the player, taking
|
|
// into account obstructions and other hinderances
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CItem::ItemCanBeTouchedByPlayer( CBasePlayer *pPlayer )
|
|
{
|
|
return UTIL_ItemCanBeTouchedByPlayer( this, pPlayer );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pOther -
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::ItemTouch( CBaseEntity *pOther )
|
|
{
|
|
// Vehicles can touch items + pick them up
|
|
if ( pOther->GetServerVehicle() )
|
|
{
|
|
pOther = pOther->GetServerVehicle()->GetPassenger();
|
|
if ( !pOther )
|
|
return;
|
|
}
|
|
|
|
// if it's not a player, ignore
|
|
if ( !pOther->IsPlayer() )
|
|
return;
|
|
|
|
CBasePlayer *pPlayer = (CBasePlayer *)pOther;
|
|
|
|
// Must be a valid pickup scenario (no blocking). Though this is a more expensive
|
|
// check than some that follow, this has to be first Obecause it's the only one
|
|
// that inhibits firing the output OnCacheInteraction.
|
|
if ( ItemCanBeTouchedByPlayer( pPlayer ) == false )
|
|
return;
|
|
|
|
m_OnCacheInteraction.FireOutput(pOther, this);
|
|
|
|
// Can I even pick stuff up?
|
|
if ( !pPlayer->IsAllowedToPickupWeapons() )
|
|
return;
|
|
|
|
// ok, a player is touching this item, but can he have it?
|
|
if ( !g_pGameRules->CanHaveItem( pPlayer, this ) )
|
|
{
|
|
// no? Ignore the touch.
|
|
return;
|
|
}
|
|
|
|
if ( MyTouch( pPlayer ) )
|
|
{
|
|
m_OnPlayerTouch.FireOutput(pOther, this);
|
|
|
|
#if TF_DLL
|
|
CHealthKit *pHealthKit = dynamic_cast<CHealthKit*>( this );
|
|
if ( pHealthKit )
|
|
{
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
|
|
if ( pTFPlayer && ( pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_PLAGUE ) )
|
|
{
|
|
DispatchParticleEffect( "plague_healthkit_pickup", GetAbsOrigin(), GetAbsAngles() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SetTouch( NULL );
|
|
SetThink( NULL );
|
|
|
|
// player grabbed the item.
|
|
g_pGameRules->PlayerGotItem( pPlayer, this );
|
|
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES )
|
|
{
|
|
Respawn();
|
|
}
|
|
else
|
|
{
|
|
UTIL_Remove( this );
|
|
|
|
#ifdef HL2MP
|
|
HL2MPRules()->RemoveLevelDesignerPlacedObject( this );
|
|
#endif
|
|
}
|
|
}
|
|
else if (gEvilImpulse101)
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
}
|
|
|
|
CBaseEntity* CItem::Respawn( void )
|
|
{
|
|
SetTouch( NULL );
|
|
AddEffects( EF_NODRAW );
|
|
|
|
VPhysicsDestroyObject();
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_TRIGGER );
|
|
|
|
UTIL_SetOrigin( this, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn.
|
|
SetAbsAngles( g_pGameRules->VecItemRespawnAngles( this ) );// set the angles.
|
|
|
|
#if !defined( TF_DLL )
|
|
UTIL_DropToFloor( this, MASK_SOLID );
|
|
#endif
|
|
|
|
RemoveAllDecals(); //remove any decals
|
|
|
|
SetThink ( &CItem::Materialize );
|
|
SetNextThink( gpGlobals->curtime + g_pGameRules->FlItemRespawnTime( this ) );
|
|
return this;
|
|
}
|
|
|
|
void CItem::Materialize( void )
|
|
{
|
|
CreateItemVPhysicsObject();
|
|
|
|
if ( IsEffectActive( EF_NODRAW ) )
|
|
{
|
|
// changing from invisible state to visible.
|
|
|
|
#ifdef HL2MP
|
|
EmitSound( "AlyxEmp.Charge" );
|
|
#else
|
|
EmitSound( "Item.Materialize" );
|
|
#endif
|
|
RemoveEffects( EF_NODRAW );
|
|
DoMuzzleFlash();
|
|
}
|
|
|
|
SetTouch( &CItem::ItemTouch );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::Precache()
|
|
{
|
|
BaseClass::Precache();
|
|
|
|
PrecacheScriptSound( "Item.Materialize" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPhysGunUser -
|
|
// PICKED_UP_BY_CANNON -
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
|
|
{
|
|
m_OnCacheInteraction.FireOutput(pPhysGunUser, this);
|
|
|
|
if ( reason == PICKED_UP_BY_CANNON )
|
|
{
|
|
// Expand the pickup box
|
|
CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT * 2 );
|
|
|
|
if( m_pConstraint != NULL )
|
|
{
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
m_pConstraint = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPhysGunUser -
|
|
// reason -
|
|
//-----------------------------------------------------------------------------
|
|
void CItem::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason )
|
|
{
|
|
// Restore the pickup box to the original
|
|
CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT );
|
|
}
|