mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-12 21:41:48 +00:00
1532 lines
35 KiB
C++
1532 lines
35 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "Game_local.h"
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
idMoveable
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
const idEventDef EV_BecomeNonSolid( "becomeNonSolid" );
|
|
const idEventDef EV_SetOwnerFromSpawnArgs( "<setOwnerFromSpawnArgs>" );
|
|
const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' );
|
|
const idEventDef EV_EnableDamage( "enableDamage", "f" );
|
|
|
|
CLASS_DECLARATION( idEntity, idMoveable )
|
|
EVENT( EV_Activate, idMoveable::Event_Activate )
|
|
EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid )
|
|
EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs )
|
|
EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest )
|
|
EVENT( EV_EnableDamage, idMoveable::Event_EnableDamage )
|
|
END_CLASS
|
|
|
|
|
|
static const float BOUNCE_SOUND_MIN_VELOCITY = 80.0f;
|
|
static const float BOUNCE_SOUND_MAX_VELOCITY = 200.0f;
|
|
|
|
/*
|
|
================
|
|
idMoveable::idMoveable
|
|
================
|
|
*/
|
|
idMoveable::idMoveable()
|
|
{
|
|
minDamageVelocity = 100.0f;
|
|
maxDamageVelocity = 200.0f;
|
|
nextCollideFxTime = 0;
|
|
nextDamageTime = 0;
|
|
nextSoundTime = 0;
|
|
initialSpline = NULL;
|
|
initialSplineDir = vec3_zero;
|
|
explode = false;
|
|
unbindOnDeath = false;
|
|
allowStep = false;
|
|
canDamage = false;
|
|
attacker = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::~idMoveable
|
|
================
|
|
*/
|
|
idMoveable::~idMoveable()
|
|
{
|
|
delete initialSpline;
|
|
initialSpline = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Spawn
|
|
================
|
|
*/
|
|
void idMoveable::Spawn()
|
|
{
|
|
idTraceModel trm;
|
|
float density, friction, bouncyness, mass;
|
|
int clipShrink;
|
|
idStr clipModelName;
|
|
|
|
// check if a clip model is set
|
|
spawnArgs.GetString( "clipmodel", "", clipModelName );
|
|
if( !clipModelName[0] )
|
|
{
|
|
clipModelName = spawnArgs.GetString( "model" ); // use the visual model
|
|
}
|
|
|
|
if( !collisionModelManager->TrmFromModel( clipModelName, trm ) )
|
|
{
|
|
gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() );
|
|
return;
|
|
}
|
|
|
|
// if the model should be shrinked
|
|
clipShrink = spawnArgs.GetInt( "clipshrink" );
|
|
if( clipShrink != 0 )
|
|
{
|
|
trm.Shrink( clipShrink * CM_CLIP_EPSILON );
|
|
}
|
|
|
|
// get rigid body properties
|
|
spawnArgs.GetFloat( "density", "0.5", density );
|
|
density = idMath::ClampFloat( 0.001f, 1000.0f, density );
|
|
spawnArgs.GetFloat( "friction", "0.05", friction );
|
|
friction = idMath::ClampFloat( 0.0f, 1.0f, friction );
|
|
spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness );
|
|
bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness );
|
|
explode = spawnArgs.GetBool( "explode" );
|
|
unbindOnDeath = spawnArgs.GetBool( "unbindondeath" );
|
|
|
|
fxCollide = spawnArgs.GetString( "fx_collide" );
|
|
nextCollideFxTime = 0;
|
|
|
|
fl.takedamage = true;
|
|
damage = spawnArgs.GetString( "def_damage", "" );
|
|
monsterDamage = spawnArgs.GetString( "monster_damage", "" );
|
|
fl.networkSync = true;
|
|
attacker = NULL;
|
|
canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true;
|
|
minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "300" ); // _D3XP
|
|
maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "700" ); // _D3XP
|
|
nextDamageTime = 0;
|
|
nextSoundTime = 0;
|
|
|
|
health = spawnArgs.GetInt( "health", "0" );
|
|
spawnArgs.GetString( "broken", "", brokenModel );
|
|
|
|
if( health )
|
|
{
|
|
if( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) )
|
|
{
|
|
gameLocal.Error( "idMoveable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString( 0 ), brokenModel.c_str() );
|
|
}
|
|
}
|
|
|
|
// setup the physics
|
|
physicsObj.SetSelf( this );
|
|
physicsObj.SetClipModel( new( TAG_PHYSICS_CLIP_MOVER ) idClipModel( trm ), density );
|
|
physicsObj.GetClipModel()->SetMaterial( GetRenderModelMaterial() );
|
|
physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
|
|
physicsObj.SetAxis( GetPhysics()->GetAxis() );
|
|
physicsObj.SetBouncyness( bouncyness );
|
|
physicsObj.SetFriction( 0.6f, 0.6f, friction );
|
|
physicsObj.SetGravity( gameLocal.GetGravity() );
|
|
physicsObj.SetContents( CONTENTS_SOLID );
|
|
physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
|
|
SetPhysics( &physicsObj );
|
|
|
|
if( spawnArgs.GetFloat( "mass", "10", mass ) )
|
|
{
|
|
physicsObj.SetMass( mass );
|
|
}
|
|
|
|
if( spawnArgs.GetBool( "nodrop" ) )
|
|
{
|
|
physicsObj.PutToRest();
|
|
}
|
|
else
|
|
{
|
|
physicsObj.DropToFloor();
|
|
}
|
|
|
|
if( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) )
|
|
{
|
|
physicsObj.DisableImpact();
|
|
}
|
|
|
|
if( spawnArgs.GetBool( "nonsolid" ) )
|
|
{
|
|
BecomeNonSolid();
|
|
}
|
|
|
|
allowStep = spawnArgs.GetBool( "allowStep", "1" );
|
|
|
|
PostEventMS( &EV_SetOwnerFromSpawnArgs, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Save
|
|
================
|
|
*/
|
|
void idMoveable::Save( idSaveGame* savefile ) const
|
|
{
|
|
|
|
savefile->WriteString( brokenModel );
|
|
savefile->WriteString( damage );
|
|
savefile->WriteString( monsterDamage );
|
|
savefile->WriteObject( attacker );
|
|
savefile->WriteString( fxCollide );
|
|
savefile->WriteInt( nextCollideFxTime );
|
|
savefile->WriteFloat( minDamageVelocity );
|
|
savefile->WriteFloat( maxDamageVelocity );
|
|
savefile->WriteBool( explode );
|
|
savefile->WriteBool( unbindOnDeath );
|
|
savefile->WriteBool( allowStep );
|
|
savefile->WriteBool( canDamage );
|
|
savefile->WriteInt( nextDamageTime );
|
|
savefile->WriteInt( nextSoundTime );
|
|
savefile->WriteInt( initialSpline != NULL ? initialSpline->GetTime( 0 ) : -1 );
|
|
savefile->WriteVec3( initialSplineDir );
|
|
|
|
savefile->WriteStaticObject( physicsObj );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Restore
|
|
================
|
|
*/
|
|
void idMoveable::Restore( idRestoreGame* savefile )
|
|
{
|
|
int initialSplineTime;
|
|
|
|
savefile->ReadString( brokenModel );
|
|
savefile->ReadString( damage );
|
|
savefile->ReadString( monsterDamage );
|
|
savefile->ReadObject( reinterpret_cast<idClass*&>( attacker ) );
|
|
savefile->ReadString( fxCollide );
|
|
savefile->ReadInt( nextCollideFxTime );
|
|
savefile->ReadFloat( minDamageVelocity );
|
|
savefile->ReadFloat( maxDamageVelocity );
|
|
savefile->ReadBool( explode );
|
|
savefile->ReadBool( unbindOnDeath );
|
|
savefile->ReadBool( allowStep );
|
|
savefile->ReadBool( canDamage );
|
|
savefile->ReadInt( nextDamageTime );
|
|
savefile->ReadInt( nextSoundTime );
|
|
savefile->ReadInt( initialSplineTime );
|
|
savefile->ReadVec3( initialSplineDir );
|
|
|
|
if( initialSplineTime != -1 )
|
|
{
|
|
InitInitialSpline( initialSplineTime );
|
|
}
|
|
else
|
|
{
|
|
initialSpline = NULL;
|
|
}
|
|
|
|
savefile->ReadStaticObject( physicsObj );
|
|
RestorePhysics( &physicsObj );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Hide
|
|
================
|
|
*/
|
|
void idMoveable::Hide()
|
|
{
|
|
idEntity::Hide();
|
|
physicsObj.SetContents( 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Show
|
|
================
|
|
*/
|
|
void idMoveable::Show()
|
|
{
|
|
idEntity::Show();
|
|
if( !spawnArgs.GetBool( "nonsolid" ) )
|
|
{
|
|
physicsObj.SetContents( CONTENTS_SOLID );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idMoveable::Collide
|
|
=================
|
|
*/
|
|
bool idMoveable::Collide( const trace_t& collision, const idVec3& velocity )
|
|
{
|
|
float v, f;
|
|
idVec3 dir;
|
|
idEntity* ent;
|
|
|
|
v = -( velocity * collision.c.normal );
|
|
if( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime )
|
|
{
|
|
f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
|
|
if( StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, false, NULL ) )
|
|
{
|
|
// don't set the volume unless there is a bounce sound as it overrides the entire channel
|
|
// which causes footsteps on ai's to not honor their shader parms
|
|
SetSoundVolume( f );
|
|
}
|
|
nextSoundTime = gameLocal.time + 500;
|
|
}
|
|
|
|
// _D3XP :: changes relating to the addition of monsterDamage
|
|
if( !common->IsClient() && canDamage && gameLocal.time > nextDamageTime )
|
|
{
|
|
bool hasDamage = damage.Length() > 0;
|
|
bool hasMonsterDamage = monsterDamage.Length() > 0;
|
|
|
|
if( hasDamage || hasMonsterDamage )
|
|
{
|
|
ent = gameLocal.entities[ collision.c.entityNum ];
|
|
if( ent && v > minDamageVelocity )
|
|
{
|
|
f = v > maxDamageVelocity ? 1.0f : idMath::Sqrt( v - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) );
|
|
dir = velocity;
|
|
dir.NormalizeFast();
|
|
if( ent->IsType( idAI::Type ) && hasMonsterDamage )
|
|
{
|
|
if( attacker )
|
|
{
|
|
ent->Damage( this, attacker, dir, monsterDamage, f, INVALID_JOINT );
|
|
}
|
|
else
|
|
{
|
|
ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, monsterDamage, f, INVALID_JOINT );
|
|
}
|
|
}
|
|
else if( hasDamage )
|
|
{
|
|
// in multiplayer, scale damage wrt mass of object
|
|
if( common->IsMultiplayer() )
|
|
{
|
|
f *= GetPhysics()->GetMass() * g_moveableDamageScale.GetFloat();
|
|
}
|
|
|
|
if( attacker )
|
|
{
|
|
ent->Damage( this, attacker, dir, damage, f, INVALID_JOINT );
|
|
}
|
|
else
|
|
{
|
|
ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT );
|
|
}
|
|
}
|
|
|
|
nextDamageTime = gameLocal.time + 1000;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( this->IsType( idExplodingBarrel::Type ) )
|
|
{
|
|
idExplodingBarrel* ebarrel = static_cast<idExplodingBarrel*>( this );
|
|
|
|
if( !ebarrel->IsStable() )
|
|
{
|
|
PostEventSec( &EV_Explode, 0.04f );
|
|
}
|
|
}
|
|
|
|
if( fxCollide.Length() && gameLocal.time > nextCollideFxTime )
|
|
{
|
|
idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false );
|
|
nextCollideFxTime = gameLocal.time + 3500;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idMoveable::Killed
|
|
============
|
|
*/
|
|
void idMoveable::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location )
|
|
{
|
|
if( unbindOnDeath )
|
|
{
|
|
Unbind();
|
|
}
|
|
|
|
if( brokenModel != "" )
|
|
{
|
|
SetModel( brokenModel );
|
|
}
|
|
|
|
if( explode )
|
|
{
|
|
if( brokenModel == "" )
|
|
{
|
|
PostEventMS( &EV_Remove, 1000 );
|
|
}
|
|
}
|
|
|
|
if( renderEntity.gui[ 0 ] )
|
|
{
|
|
renderEntity.gui[ 0 ] = NULL;
|
|
}
|
|
|
|
ActivateTargets( this );
|
|
|
|
fl.takedamage = false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::AllowStep
|
|
================
|
|
*/
|
|
bool idMoveable::AllowStep() const
|
|
{
|
|
return allowStep;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::BecomeNonSolid
|
|
================
|
|
*/
|
|
void idMoveable::BecomeNonSolid()
|
|
{
|
|
// set CONTENTS_RENDERMODEL so bullets still collide with the moveable
|
|
physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL );
|
|
physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::EnableDamage
|
|
================
|
|
*/
|
|
void idMoveable::EnableDamage( bool enable, float duration )
|
|
{
|
|
if( canDamage == enable )
|
|
{
|
|
return;
|
|
}
|
|
|
|
canDamage = enable;
|
|
if( duration )
|
|
{
|
|
PostEventSec( &EV_EnableDamage, duration, ( /*_D3XP*/enable ) ? 0.0f : 1.0f );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::InitInitialSpline
|
|
================
|
|
*/
|
|
void idMoveable::InitInitialSpline( int startTime )
|
|
{
|
|
int initialSplineTime;
|
|
|
|
initialSpline = GetSpline();
|
|
initialSplineTime = spawnArgs.GetInt( "initialSplineTime", "300" );
|
|
|
|
if( initialSpline != NULL )
|
|
{
|
|
initialSpline->MakeUniform( initialSplineTime );
|
|
initialSpline->ShiftTime( startTime - initialSpline->GetTime( 0 ) );
|
|
initialSplineDir = initialSpline->GetCurrentFirstDerivative( startTime );
|
|
initialSplineDir *= physicsObj.GetAxis().Transpose();
|
|
initialSplineDir.Normalize();
|
|
BecomeActive( TH_THINK );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::FollowInitialSplinePath
|
|
================
|
|
*/
|
|
bool idMoveable::FollowInitialSplinePath()
|
|
{
|
|
if( initialSpline != NULL )
|
|
{
|
|
if( gameLocal.time < initialSpline->GetTime( initialSpline->GetNumValues() - 1 ) )
|
|
{
|
|
idVec3 splinePos = initialSpline->GetCurrentValue( gameLocal.time );
|
|
idVec3 linearVelocity = ( splinePos - physicsObj.GetOrigin() ) * com_engineHz_latched;
|
|
physicsObj.SetLinearVelocity( linearVelocity );
|
|
|
|
idVec3 splineDir = initialSpline->GetCurrentFirstDerivative( gameLocal.time );
|
|
idVec3 dir = initialSplineDir * physicsObj.GetAxis();
|
|
idVec3 angularVelocity = dir.Cross( splineDir );
|
|
angularVelocity.Normalize();
|
|
angularVelocity *= idMath::ACos16( dir * splineDir / splineDir.Length() ) * com_engineHz_latched;
|
|
physicsObj.SetAngularVelocity( angularVelocity );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
delete initialSpline;
|
|
initialSpline = NULL;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::ClientThink
|
|
================
|
|
*/
|
|
void idMoveable::ClientThink( const int curTime, const float fraction, const bool predict )
|
|
{
|
|
InterpolatePhysicsOnly( fraction );
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Think
|
|
================
|
|
*/
|
|
void idMoveable::Think()
|
|
{
|
|
if( thinkFlags & TH_THINK )
|
|
{
|
|
if( !FollowInitialSplinePath() )
|
|
{
|
|
BecomeInactive( TH_THINK );
|
|
}
|
|
}
|
|
idEntity::Think();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::GetRenderModelMaterial
|
|
================
|
|
*/
|
|
const idMaterial* idMoveable::GetRenderModelMaterial() const
|
|
{
|
|
if( renderEntity.customShader )
|
|
{
|
|
return renderEntity.customShader;
|
|
}
|
|
if( renderEntity.hModel && renderEntity.hModel->NumSurfaces() )
|
|
{
|
|
return renderEntity.hModel->Surface( 0 )->shader;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idMoveable::WriteToSnapshot( idBitMsg& msg ) const
|
|
{
|
|
physicsObj.WriteToSnapshot( msg );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idMoveable::ReadFromSnapshot( const idBitMsg& msg )
|
|
{
|
|
physicsObj.ReadFromSnapshot( msg );
|
|
if( msg.HasChanged() )
|
|
{
|
|
UpdateVisuals();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_BecomeNonSolid
|
|
================
|
|
*/
|
|
void idMoveable::Event_BecomeNonSolid()
|
|
{
|
|
BecomeNonSolid();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::SetAttacker
|
|
================
|
|
*/
|
|
void idMoveable::SetAttacker( idEntity* ent )
|
|
{
|
|
attacker = ent;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_Activate
|
|
================
|
|
*/
|
|
void idMoveable::Event_Activate( idEntity* activator )
|
|
{
|
|
float delay;
|
|
idVec3 init_velocity, init_avelocity;
|
|
|
|
Show();
|
|
|
|
if( !spawnArgs.GetInt( "notPushable" ) )
|
|
{
|
|
physicsObj.EnableImpact();
|
|
}
|
|
|
|
physicsObj.Activate();
|
|
|
|
spawnArgs.GetVector( "init_velocity", "0 0 0", init_velocity );
|
|
spawnArgs.GetVector( "init_avelocity", "0 0 0", init_avelocity );
|
|
|
|
delay = spawnArgs.GetFloat( "init_velocityDelay", "0" );
|
|
if( delay == 0.0f )
|
|
{
|
|
physicsObj.SetLinearVelocity( init_velocity );
|
|
}
|
|
else
|
|
{
|
|
PostEventSec( &EV_SetLinearVelocity, delay, init_velocity );
|
|
}
|
|
|
|
delay = spawnArgs.GetFloat( "init_avelocityDelay", "0" );
|
|
if( delay == 0.0f )
|
|
{
|
|
physicsObj.SetAngularVelocity( init_avelocity );
|
|
}
|
|
else
|
|
{
|
|
PostEventSec( &EV_SetAngularVelocity, delay, init_avelocity );
|
|
}
|
|
|
|
InitInitialSpline( gameLocal.time );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_SetOwnerFromSpawnArgs
|
|
================
|
|
*/
|
|
void idMoveable::Event_SetOwnerFromSpawnArgs()
|
|
{
|
|
idStr owner;
|
|
|
|
if( spawnArgs.GetString( "owner", "", owner ) )
|
|
{
|
|
ProcessEvent( &EV_SetOwner, gameLocal.FindEntity( owner ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_IsAtRest
|
|
================
|
|
*/
|
|
void idMoveable::Event_IsAtRest()
|
|
{
|
|
idThread::ReturnInt( physicsObj.IsAtRest() );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_EnableDamage
|
|
================
|
|
*/
|
|
void idMoveable::Event_EnableDamage( float enable )
|
|
{
|
|
// clear out attacker
|
|
attacker = NULL;
|
|
|
|
canDamage = ( enable != 0.0f );
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
idBarrel
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_DECLARATION( idMoveable, idBarrel )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
idBarrel::idBarrel
|
|
================
|
|
*/
|
|
idBarrel::idBarrel()
|
|
{
|
|
radius = 1.0f;
|
|
barrelAxis = 0;
|
|
lastOrigin.Zero();
|
|
lastAxis.Identity();
|
|
additionalRotation = 0.0f;
|
|
additionalAxis.Identity();
|
|
fl.networkSync = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::Save
|
|
================
|
|
*/
|
|
void idBarrel::Save( idSaveGame* savefile ) const
|
|
{
|
|
savefile->WriteFloat( radius );
|
|
savefile->WriteInt( barrelAxis );
|
|
savefile->WriteVec3( lastOrigin );
|
|
savefile->WriteMat3( lastAxis );
|
|
savefile->WriteFloat( additionalRotation );
|
|
savefile->WriteMat3( additionalAxis );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::Restore
|
|
================
|
|
*/
|
|
void idBarrel::Restore( idRestoreGame* savefile )
|
|
{
|
|
savefile->ReadFloat( radius );
|
|
savefile->ReadInt( barrelAxis );
|
|
savefile->ReadVec3( lastOrigin );
|
|
savefile->ReadMat3( lastAxis );
|
|
savefile->ReadFloat( additionalRotation );
|
|
savefile->ReadMat3( additionalAxis );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::BarrelThink
|
|
================
|
|
*/
|
|
void idBarrel::BarrelThink()
|
|
{
|
|
bool wasAtRest, onGround;
|
|
float movedDistance, rotatedDistance, angle;
|
|
idVec3 curOrigin, gravityNormal, dir;
|
|
idMat3 curAxis, axis;
|
|
|
|
wasAtRest = IsAtRest();
|
|
|
|
// run physics
|
|
RunPhysics();
|
|
|
|
// only need to give the visual model an additional rotation if the physics were run
|
|
if( !wasAtRest )
|
|
{
|
|
|
|
// current physics state
|
|
onGround = GetPhysics()->HasGroundContacts();
|
|
curOrigin = GetPhysics()->GetOrigin();
|
|
curAxis = GetPhysics()->GetAxis();
|
|
|
|
// if the barrel is on the ground
|
|
if( onGround )
|
|
{
|
|
gravityNormal = GetPhysics()->GetGravityNormal();
|
|
|
|
dir = curOrigin - lastOrigin;
|
|
dir -= gravityNormal * dir * gravityNormal;
|
|
movedDistance = dir.LengthSqr();
|
|
|
|
// if the barrel moved and the barrel is not aligned with the gravity direction
|
|
if( movedDistance > 0.0f && idMath::Fabs( gravityNormal * curAxis[barrelAxis] ) < 0.7f )
|
|
{
|
|
|
|
// barrel movement since last think frame orthogonal to the barrel axis
|
|
movedDistance = idMath::Sqrt( movedDistance );
|
|
dir *= 1.0f / movedDistance;
|
|
movedDistance = ( 1.0f - idMath::Fabs( dir * curAxis[barrelAxis] ) ) * movedDistance;
|
|
|
|
// get rotation about barrel axis since last think frame
|
|
angle = lastAxis[( barrelAxis + 1 ) % 3] * curAxis[( barrelAxis + 1 ) % 3];
|
|
angle = idMath::ACos( angle );
|
|
// distance along cylinder hull
|
|
rotatedDistance = angle * radius;
|
|
|
|
// if the barrel moved further than it rotated about it's axis
|
|
if( movedDistance > rotatedDistance )
|
|
{
|
|
|
|
// additional rotation of the visual model to make it look
|
|
// like the barrel rolls instead of slides
|
|
angle = 180.0f * ( movedDistance - rotatedDistance ) / ( radius * idMath::PI );
|
|
if( gravityNormal.Cross( curAxis[barrelAxis] ) * dir < 0.0f )
|
|
{
|
|
additionalRotation += angle;
|
|
}
|
|
else
|
|
{
|
|
additionalRotation -= angle;
|
|
}
|
|
dir = vec3_origin;
|
|
dir[barrelAxis] = 1.0f;
|
|
additionalAxis = idRotation( vec3_origin, dir, additionalRotation ).ToMat3();
|
|
}
|
|
}
|
|
}
|
|
|
|
// save state for next think
|
|
lastOrigin = curOrigin;
|
|
lastAxis = curAxis;
|
|
}
|
|
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::Think
|
|
================
|
|
*/
|
|
void idBarrel::Think()
|
|
{
|
|
if( thinkFlags & TH_THINK )
|
|
{
|
|
if( !FollowInitialSplinePath() )
|
|
{
|
|
BecomeInactive( TH_THINK );
|
|
}
|
|
}
|
|
|
|
BarrelThink();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::GetPhysicsToVisualTransform
|
|
================
|
|
*/
|
|
bool idBarrel::GetPhysicsToVisualTransform( idVec3& origin, idMat3& axis )
|
|
{
|
|
origin = vec3_origin;
|
|
axis = additionalAxis;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::Spawn
|
|
================
|
|
*/
|
|
void idBarrel::Spawn()
|
|
{
|
|
const idBounds& bounds = GetPhysics()->GetBounds();
|
|
|
|
// radius of the barrel cylinder
|
|
radius = ( bounds[1][0] - bounds[0][0] ) * 0.5f;
|
|
|
|
// always a vertical barrel with cylinder axis parallel to the z-axis
|
|
barrelAxis = 2;
|
|
|
|
lastOrigin = GetPhysics()->GetOrigin();
|
|
lastAxis = GetPhysics()->GetAxis();
|
|
|
|
additionalRotation = 0.0f;
|
|
additionalAxis.Identity();
|
|
|
|
fl.networkSync = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idBarrel::ClientThink
|
|
================
|
|
*/
|
|
void idBarrel::ClientThink( const int curTime, const float fraction, const bool predict )
|
|
{
|
|
InterpolatePhysics( fraction );
|
|
Present();
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
idExplodingBarrel
|
|
|
|
===============================================================================
|
|
*/
|
|
const idEventDef EV_Respawn( "<respawn>" );
|
|
const idEventDef EV_TriggerTargets( "<triggertargets>" );
|
|
|
|
CLASS_DECLARATION( idBarrel, idExplodingBarrel )
|
|
EVENT( EV_Activate, idExplodingBarrel::Event_Activate )
|
|
EVENT( EV_Respawn, idExplodingBarrel::Event_Respawn )
|
|
EVENT( EV_Explode, idExplodingBarrel::Event_Explode )
|
|
EVENT( EV_TriggerTargets, idExplodingBarrel::Event_TriggerTargets )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::idExplodingBarrel
|
|
================
|
|
*/
|
|
idExplodingBarrel::idExplodingBarrel()
|
|
{
|
|
spawnOrigin.Zero();
|
|
spawnAxis.Zero();
|
|
state = NORMAL;
|
|
isStable = true;
|
|
particleModelDefHandle = -1;
|
|
lightDefHandle = -1;
|
|
memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
|
|
memset( &light, 0, sizeof( light ) );
|
|
particleTime = 0;
|
|
lightTime = 0;
|
|
time = 0.0f;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::~idExplodingBarrel
|
|
================
|
|
*/
|
|
idExplodingBarrel::~idExplodingBarrel()
|
|
{
|
|
if( particleModelDefHandle >= 0 )
|
|
{
|
|
gameRenderWorld->FreeEntityDef( particleModelDefHandle );
|
|
}
|
|
if( lightDefHandle >= 0 )
|
|
{
|
|
gameRenderWorld->FreeLightDef( lightDefHandle );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Save
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Save( idSaveGame* savefile ) const
|
|
{
|
|
savefile->WriteVec3( spawnOrigin );
|
|
savefile->WriteMat3( spawnAxis );
|
|
|
|
savefile->WriteInt( state );
|
|
savefile->WriteInt( particleModelDefHandle );
|
|
savefile->WriteInt( lightDefHandle );
|
|
|
|
savefile->WriteRenderEntity( particleRenderEntity );
|
|
savefile->WriteRenderLight( light );
|
|
|
|
savefile->WriteInt( particleTime );
|
|
savefile->WriteInt( lightTime );
|
|
savefile->WriteFloat( time );
|
|
|
|
savefile->WriteBool( isStable );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Restore
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Restore( idRestoreGame* savefile )
|
|
{
|
|
savefile->ReadVec3( spawnOrigin );
|
|
savefile->ReadMat3( spawnAxis );
|
|
|
|
savefile->ReadInt( ( int& )state );
|
|
savefile->ReadInt( ( int& )particleModelDefHandle );
|
|
savefile->ReadInt( ( int& )lightDefHandle );
|
|
|
|
savefile->ReadRenderEntity( particleRenderEntity );
|
|
savefile->ReadRenderLight( light );
|
|
|
|
savefile->ReadInt( particleTime );
|
|
savefile->ReadInt( lightTime );
|
|
savefile->ReadFloat( time );
|
|
|
|
savefile->ReadBool( isStable );
|
|
|
|
if( lightDefHandle != -1 )
|
|
{
|
|
lightDefHandle = gameRenderWorld->AddLightDef( &light );
|
|
}
|
|
if( particleModelDefHandle != -1 )
|
|
{
|
|
particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Spawn
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Spawn()
|
|
{
|
|
health = spawnArgs.GetInt( "health", "5" );
|
|
fl.takedamage = true;
|
|
isStable = true;
|
|
fl.networkSync = true;
|
|
spawnOrigin = GetPhysics()->GetOrigin();
|
|
spawnAxis = GetPhysics()->GetAxis();
|
|
state = NORMAL;
|
|
particleModelDefHandle = -1;
|
|
lightDefHandle = -1;
|
|
lightTime = 0;
|
|
particleTime = 0;
|
|
time = spawnArgs.GetFloat( "time" );
|
|
memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
|
|
memset( &light, 0, sizeof( light ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::UpdateLight
|
|
================
|
|
*/
|
|
void idExplodingBarrel::UpdateLight()
|
|
{
|
|
if( lightDefHandle >= 0 )
|
|
{
|
|
if( state == BURNING )
|
|
{
|
|
// ramp the color up over 250 ms
|
|
float pct = ( gameLocal.serverTime - lightTime ) / 250.f;
|
|
if( pct > 1.0f )
|
|
{
|
|
pct = 1.0f;
|
|
}
|
|
light.origin = physicsObj.GetAbsBounds().GetCenter();
|
|
light.axis = mat3_identity;
|
|
light.shaderParms[ SHADERPARM_RED ] = pct;
|
|
light.shaderParms[ SHADERPARM_GREEN ] = pct;
|
|
light.shaderParms[ SHADERPARM_BLUE ] = pct;
|
|
light.shaderParms[ SHADERPARM_ALPHA ] = pct;
|
|
gameRenderWorld->UpdateLightDef( lightDefHandle, &light );
|
|
}
|
|
else
|
|
{
|
|
if( gameLocal.serverTime - lightTime > 250 )
|
|
{
|
|
gameRenderWorld->FreeLightDef( lightDefHandle );
|
|
lightDefHandle = -1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::ClientThink
|
|
================
|
|
*/
|
|
void idExplodingBarrel::ClientThink( const int curTime, const float fraction, const bool predict )
|
|
{
|
|
UpdateLight();
|
|
InterpolatePhysics( fraction );
|
|
Present();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Think
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Think()
|
|
{
|
|
idBarrel::BarrelThink();
|
|
|
|
UpdateLight();
|
|
|
|
if( !common->IsClient() && state != BURNING && state != EXPLODING )
|
|
{
|
|
BecomeInactive( TH_THINK );
|
|
return;
|
|
}
|
|
|
|
if( particleModelDefHandle >= 0 )
|
|
{
|
|
particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
|
|
particleRenderEntity.axis = mat3_identity;
|
|
gameRenderWorld->UpdateEntityDef( particleModelDefHandle, &particleRenderEntity );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::SetStability
|
|
================
|
|
*/
|
|
void idExplodingBarrel::SetStability( bool stability )
|
|
{
|
|
isStable = stability;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::IsStable
|
|
================
|
|
*/
|
|
bool idExplodingBarrel::IsStable()
|
|
{
|
|
return isStable;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::StartBurning
|
|
================
|
|
*/
|
|
void idExplodingBarrel::StartBurning()
|
|
{
|
|
state = BURNING;
|
|
AddParticles( "barrelfire.prt", true );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::StartBurning
|
|
================
|
|
*/
|
|
void idExplodingBarrel::StopBurning()
|
|
{
|
|
state = NORMAL;
|
|
|
|
if( particleModelDefHandle >= 0 )
|
|
{
|
|
gameRenderWorld->FreeEntityDef( particleModelDefHandle );
|
|
particleModelDefHandle = -1;
|
|
|
|
particleTime = 0;
|
|
memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::AddParticles
|
|
================
|
|
*/
|
|
void idExplodingBarrel::AddParticles( const char* name, bool burn )
|
|
{
|
|
if( name && *name )
|
|
{
|
|
int explicitTimeGroup = timeGroup;
|
|
SetTimeState explicitTS( explicitTimeGroup );
|
|
if( particleModelDefHandle >= 0 )
|
|
{
|
|
gameRenderWorld->FreeEntityDef( particleModelDefHandle );
|
|
}
|
|
memset( &particleRenderEntity, 0, sizeof( particleRenderEntity ) );
|
|
idRenderModel* modelDef = renderModelManager->FindModel( name );
|
|
if( modelDef )
|
|
{
|
|
particleRenderEntity.origin = physicsObj.GetAbsBounds().GetCenter();
|
|
particleRenderEntity.axis = mat3_identity;
|
|
particleRenderEntity.hModel = modelDef;
|
|
float rgb = ( burn ) ? 0.0f : 1.0f;
|
|
particleRenderEntity.shaderParms[ SHADERPARM_RED ] = rgb;
|
|
particleRenderEntity.shaderParms[ SHADERPARM_GREEN ] = rgb;
|
|
particleRenderEntity.shaderParms[ SHADERPARM_BLUE ] = rgb;
|
|
particleRenderEntity.shaderParms[ SHADERPARM_ALPHA ] = rgb;
|
|
particleRenderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.realClientTime );
|
|
particleRenderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = ( burn ) ? 1.0f : gameLocal.random.RandomInt( 90 );
|
|
particleRenderEntity.timeGroup = explicitTimeGroup;
|
|
particleModelDefHandle = gameRenderWorld->AddEntityDef( &particleRenderEntity );
|
|
if( burn )
|
|
{
|
|
BecomeActive( TH_THINK );
|
|
}
|
|
particleTime = gameLocal.realClientTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::AddLight
|
|
================
|
|
*/
|
|
void idExplodingBarrel::AddLight( const char* name, bool burn )
|
|
{
|
|
if( lightDefHandle >= 0 )
|
|
{
|
|
gameRenderWorld->FreeLightDef( lightDefHandle );
|
|
}
|
|
memset( &light, 0, sizeof( light ) );
|
|
light.axis = mat3_identity;
|
|
light.lightRadius.x = spawnArgs.GetFloat( "light_radius" );
|
|
light.lightRadius.y = light.lightRadius.z = light.lightRadius.x;
|
|
light.origin = physicsObj.GetOrigin();
|
|
light.origin.z += 128;
|
|
light.pointLight = true;
|
|
light.shader = declManager->FindMaterial( name );
|
|
light.shaderParms[ SHADERPARM_RED ] = 2.0f;
|
|
light.shaderParms[ SHADERPARM_GREEN ] = 2.0f;
|
|
light.shaderParms[ SHADERPARM_BLUE ] = 2.0f;
|
|
light.shaderParms[ SHADERPARM_ALPHA ] = 2.0f;
|
|
lightDefHandle = gameRenderWorld->AddLightDef( &light );
|
|
lightTime = gameLocal.serverTime;
|
|
BecomeActive( TH_THINK );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::ExplodingEffects
|
|
================
|
|
*/
|
|
void idExplodingBarrel::ExplodingEffects()
|
|
{
|
|
const char* temp;
|
|
|
|
StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
|
|
|
|
temp = spawnArgs.GetString( "model_damage" );
|
|
if( *temp != '\0' )
|
|
{
|
|
SetModel( temp );
|
|
Show();
|
|
}
|
|
|
|
temp = spawnArgs.GetString( "model_detonate" );
|
|
if( *temp != '\0' )
|
|
{
|
|
AddParticles( temp, false );
|
|
}
|
|
|
|
temp = spawnArgs.GetString( "mtr_lightexplode" );
|
|
if( *temp != '\0' )
|
|
{
|
|
AddLight( temp, false );
|
|
}
|
|
|
|
temp = spawnArgs.GetString( "mtr_burnmark" );
|
|
if( *temp != '\0' )
|
|
{
|
|
gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 128.0f, true, 96.0f, temp );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Killed
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location )
|
|
{
|
|
|
|
if( IsHidden() || state == EXPLODING || state == BURNING )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Clients don't process killed
|
|
if( common->IsClient() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float f = spawnArgs.GetFloat( "burn" );
|
|
if( f > 0.0f && state == NORMAL )
|
|
{
|
|
state = BURNING;
|
|
PostEventSec( &EV_Explode, f );
|
|
StartSound( "snd_burn", SND_CHANNEL_ANY, 0, false, NULL );
|
|
AddParticles( spawnArgs.GetString( "model_burn", "" ), true );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
state = EXPLODING;
|
|
if( common->IsServer() )
|
|
{
|
|
idBitMsg msg;
|
|
byte msgBuf[MAX_EVENT_PARAM_SIZE];
|
|
|
|
msg.InitWrite( msgBuf, sizeof( msgBuf ) );
|
|
msg.WriteLong( gameLocal.time );
|
|
ServerSendEvent( EVENT_EXPLODE, &msg, false );
|
|
}
|
|
}
|
|
|
|
// do this before applying radius damage so the ent can trace to any damagable ents nearby
|
|
Hide();
|
|
physicsObj.SetContents( 0 );
|
|
|
|
const char* splash = spawnArgs.GetString( "def_splash_damage", "damage_explosion" );
|
|
if( splash != NULL && *splash != '\0' )
|
|
{
|
|
gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, attacker, this, this, splash );
|
|
}
|
|
|
|
ExplodingEffects( );
|
|
|
|
//FIXME: need to precache all the debris stuff here and in the projectiles
|
|
const idKeyValue* kv = spawnArgs.MatchPrefix( "def_debris" );
|
|
// bool first = true;
|
|
while( kv != NULL )
|
|
{
|
|
const idDict* debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false );
|
|
if( debris_args )
|
|
{
|
|
idEntity* ent;
|
|
idVec3 dir;
|
|
idDebris* debris;
|
|
//if ( first ) {
|
|
dir = physicsObj.GetAxis()[1];
|
|
// first = false;
|
|
//} else {
|
|
dir.x += gameLocal.random.CRandomFloat() * 4.0f;
|
|
dir.y += gameLocal.random.CRandomFloat() * 4.0f;
|
|
//dir.z = gameLocal.random.RandomFloat() * 8.0f;
|
|
//}
|
|
dir.Normalize();
|
|
|
|
gameLocal.SpawnEntityDef( *debris_args, &ent, false );
|
|
if( ent == NULL || !ent->IsType( idDebris::Type ) )
|
|
{
|
|
gameLocal.Error( "'projectile_debris' is not an idDebris" );
|
|
break;
|
|
}
|
|
|
|
debris = static_cast<idDebris*>( ent );
|
|
debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() );
|
|
debris->Launch();
|
|
debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f;
|
|
debris->UpdateVisuals();
|
|
|
|
}
|
|
kv = spawnArgs.MatchPrefix( "def_debris", kv );
|
|
}
|
|
|
|
physicsObj.PutToRest();
|
|
CancelEvents( &EV_Explode );
|
|
CancelEvents( &EV_Activate );
|
|
|
|
f = spawnArgs.GetFloat( "respawn" );
|
|
if( f > 0.0f )
|
|
{
|
|
PostEventSec( &EV_Respawn, f );
|
|
}
|
|
else
|
|
{
|
|
PostEventMS( &EV_Remove, 5000 );
|
|
}
|
|
|
|
if( spawnArgs.GetBool( "triggerTargets" ) )
|
|
{
|
|
ActivateTargets( this );
|
|
}
|
|
|
|
// Any time a barrel explodes, attribute it towards the 'Boomtastic' achievement, since there's no way a barrel can explode without player interference
|
|
idPlayer* player = gameLocal.GetLocalPlayer();
|
|
if( player != NULL && !common->IsMultiplayer() )
|
|
{
|
|
player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_DESTROY_BARRELS );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Damage
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir,
|
|
const char* damageDefName, const float damageScale, const int location )
|
|
{
|
|
|
|
const idDict* damageDef = gameLocal.FindEntityDefDict( damageDefName );
|
|
if( damageDef == NULL )
|
|
{
|
|
gameLocal.Error( "Unknown damageDef '%s'\n", damageDefName );
|
|
return;
|
|
}
|
|
if( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL )
|
|
{
|
|
PostEventMS( &EV_Explode, 400 );
|
|
}
|
|
else
|
|
{
|
|
idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Event_TriggerTargets
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Event_TriggerTargets()
|
|
{
|
|
ActivateTargets( this );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Event_Explode
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Event_Explode()
|
|
{
|
|
if( state == NORMAL || state == BURNING )
|
|
{
|
|
state = BURNEXPIRED;
|
|
Killed( NULL, attacker, 0, vec3_zero, 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::Event_Respawn
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Event_Respawn()
|
|
{
|
|
int i;
|
|
int minRespawnDist = spawnArgs.GetInt( "respawn_range", "256" );
|
|
if( minRespawnDist )
|
|
{
|
|
float minDist = -1;
|
|
for( i = 0; i < gameLocal.numClients; i++ )
|
|
{
|
|
if( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) )
|
|
{
|
|
continue;
|
|
}
|
|
idVec3 v = gameLocal.entities[ i ]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
|
|
float dist = v.Length();
|
|
if( minDist < 0 || dist < minDist )
|
|
{
|
|
minDist = dist;
|
|
}
|
|
}
|
|
if( minDist < minRespawnDist )
|
|
{
|
|
PostEventSec( &EV_Respawn, spawnArgs.GetInt( "respawn_again", "10" ) );
|
|
return;
|
|
}
|
|
}
|
|
const char* temp = spawnArgs.GetString( "model" );
|
|
if( temp != NULL && *temp != '\0' )
|
|
{
|
|
SetModel( temp );
|
|
}
|
|
health = spawnArgs.GetInt( "health", "5" );
|
|
fl.takedamage = true;
|
|
physicsObj.SetOrigin( spawnOrigin );
|
|
physicsObj.SetAxis( spawnAxis );
|
|
physicsObj.SetContents( CONTENTS_SOLID );
|
|
physicsObj.DropToFloor();
|
|
state = NORMAL;
|
|
Show();
|
|
UpdateVisuals();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::Event_Activate
|
|
================
|
|
*/
|
|
void idExplodingBarrel::Event_Activate( idEntity* activator )
|
|
{
|
|
Killed( activator, activator, 0, vec3_origin, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::WriteToSnapshot
|
|
================
|
|
*/
|
|
void idExplodingBarrel::WriteToSnapshot( idBitMsg& msg ) const
|
|
{
|
|
idMoveable::WriteToSnapshot( msg );
|
|
msg.WriteBits( IsHidden(), 1 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idMoveable::ReadFromSnapshot
|
|
================
|
|
*/
|
|
void idExplodingBarrel::ReadFromSnapshot( const idBitMsg& msg )
|
|
{
|
|
|
|
idMoveable::ReadFromSnapshot( msg );
|
|
if( msg.ReadBits( 1 ) )
|
|
{
|
|
Hide();
|
|
}
|
|
else
|
|
{
|
|
Show();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idExplodingBarrel::ClientReceiveEvent
|
|
================
|
|
*/
|
|
bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg& msg )
|
|
{
|
|
|
|
switch( event )
|
|
{
|
|
case EVENT_EXPLODE:
|
|
{
|
|
if( gameLocal.realClientTime - msg.ReadLong() < spawnArgs.GetInt( "explode_lapse", "1000" ) )
|
|
{
|
|
ExplodingEffects( );
|
|
}
|
|
return true;
|
|
}
|
|
default:
|
|
{
|
|
return idBarrel::ClientReceiveEvent( event, time, msg );
|
|
}
|
|
}
|
|
}
|