etqw-sdk/source/game/Moveable.cpp

672 lines
17 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "Moveable.h"
#include "ContentMask.h"
#include "Projectile.h"
#include "Player.h"
/*
================
sdMoveableNetworkData::~sdMoveableNetworkData
================
*/
sdMoveableNetworkData::~sdMoveableNetworkData( void ) {
delete physicsData;
}
/*
================
sdMoveableNetworkData::MakeDefault
================
*/
void sdMoveableNetworkData::MakeDefault( void ) {
if ( physicsData ) {
physicsData->MakeDefault();
}
}
/*
================
sdMoveableNetworkData::Write
================
*/
void sdMoveableNetworkData::Write( idFile* file ) const {
if ( physicsData ) {
physicsData->Write( file );
}
}
/*
================
sdMoveableNetworkData::Read
================
*/
void sdMoveableNetworkData::Read( idFile* file ) {
if ( physicsData ) {
physicsData->Read( file );
}
}
/*
================
sdMoveableBroadcastData::~sdMoveableBroadcastData
================
*/
sdMoveableBroadcastData::~sdMoveableBroadcastData( void ) {
delete physicsData;
}
/*
================
sdMoveableBroadcastData::MakeDefault
================
*/
void sdMoveableBroadcastData::MakeDefault( void ) {
if ( physicsData ) {
physicsData->MakeDefault();
}
hidden = false;
}
/*
================
sdMoveableBroadcastData::Write
================
*/
void sdMoveableBroadcastData::Write( idFile* file ) const {
if ( physicsData ) {
physicsData->Write( file );
}
file->WriteBool( hidden );
}
/*
================
sdMoveableBroadcastData::Read
================
*/
void sdMoveableBroadcastData::Read( idFile* file ) {
if ( physicsData ) {
physicsData->Read( file );
}
file->ReadBool( hidden );
}
/*
===============================================================================
idMoveable
===============================================================================
*/
const idEventDef EV_BecomeNonSolid( "becomeNonSolid", '\0', DOC_TEXT( "Makes the object non-solid to players/vehicles, bullets will still collide however." ), 0, NULL );
const idEventDef EV_EnableDamage( "enableDamage", '\0', DOC_TEXT( "Enables/disables doing damage to objects it hits." ), 1, NULL, "b", "value", "Whether to enable/disable." );
CLASS_DECLARATION( idEntity, idMoveable )
EVENT( EV_Activate, idMoveable::Event_Activate )
EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid )
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( void ) {
minDamageVelocity = 100.0f;
maxDamageVelocity = 200.0f;
nextCollideFxTime = 0;
nextDamageTime = 0;
nextSoundTime = 0;
explode = false;
unbindOnDeath = false;
allowStep = false;
canDamage = false;
waterEffects = NULL;
}
/*
================
idMoveable::~idMoveable
================
*/
idMoveable::~idMoveable( void ) {
delete waterEffects;
}
/*
================
idMoveable::Spawn
================
*/
void idMoveable::Spawn( void ) {
idTraceModel trm;
float density, friction, bouncyness, linearFriction, angularFriction;
int clipShrink;
const char* clipModelName = GetClipModelName();
if ( !gameLocal.clip.LoadTraceModel( clipModelName, trm ) ) {
gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName );
return;
}
// if the model should be shrunk
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 );
spawnArgs.GetFloat( "linear_friction", "0.6", linearFriction );
linearFriction = idMath::ClampFloat( 0.0f, 1.0f, linearFriction );
spawnArgs.GetFloat( "angular_friction", "0.6", angularFriction );
angularFriction = idMath::ClampFloat( 0.0f, 1.0f, angularFriction );
explode = spawnArgs.GetBool( "explode" );
unbindOnDeath = spawnArgs.GetBool( "unbindondeath" );
nextCollideFxTime = 0;
fl.takedamage = true;
damageDecl = DAMAGE_FOR_NAME_UNSAFE( spawnArgs.GetString( "dmg_damage", "" ) );
canDamage = spawnArgs.GetBool( "damageWhenActive" ) ? false : true;
minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "100" );
maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "200" );
nextDamageTime = 0;
nextSoundTime = 0;
// setup the physics
physicsObj.SetSelf( this );
physicsObj.SetClipModel( new idClipModel( trm, false ), density, 0 );
physicsObj.GetClipModel( 0 )->SetMaterial( GetRenderModelMaterial() );
physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
physicsObj.SetAxis( GetPhysics()->GetAxis() );
physicsObj.SetBouncyness( bouncyness );
physicsObj.SetFriction( linearFriction, angularFriction, friction );
physicsObj.SetGravity( gameLocal.GetGravity() );
physicsObj.SetContents( CONTENTS_SOLID, 0 );
physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_SLIDEMOVER | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_FORCEFIELD, 0 );
physicsObj.SetMass( spawnArgs.GetFloat( "mass", "10" ), 0 );
physicsObj.SetBuoyancy( spawnArgs.GetFloat( "buoyancy", "10" ) );
SetPhysics( &physicsObj );
if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) {
physicsObj.DisableImpact();
}
if ( spawnArgs.GetBool( "nonsolid" ) ) {
BecomeNonSolid();
}
allowStep = spawnArgs.GetBool( "allowStep", "1" );
waterEffects = sdWaterEffects::SetupFromSpawnArgs( spawnArgs );
}
/*
================
idMoveable::Hide
================
*/
void idMoveable::Hide( void ) {
idEntity::Hide();
physicsObj.SetContents( 0, 0 );
}
/*
================
idMoveable::Show
================
*/
void idMoveable::Show( void ) {
idEntity::Show();
if ( !spawnArgs.GetBool( "nonsolid" ) ) {
physicsObj.SetContents( CONTENTS_SOLID, 0 );
}
}
/*
=================
idMoveable::Collide
=================
*/
bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity, int bodyId ) {
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_ANY, 0, NULL ) ) {
SetSoundVolume( f );
}
nextSoundTime = gameLocal.time + 500;
}
if ( canDamage && damageDecl && gameLocal.time > nextDamageTime ) {
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();
ent->Damage( this, this, dir, damageDecl, f, NULL );
nextDamageTime = gameLocal.time + 1000;
}
}
return false;
}
/*
============
idMoveable::Killed
============
*/
void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location, const sdDeclDamage* damageDecl ) {
if ( unbindOnDeath ) {
Unbind();
}
const char* brokenModel = spawnArgs.GetString( "model_broken" );
if ( *brokenModel ) {
SetModel( brokenModel );
} else {
if ( explode ) {
PostEventMS( &EV_Remove, 1000 );
}
}
fl.takedamage = false;
}
/*
================
idMoveable::AllowStep
================
*/
bool idMoveable::AllowStep( void ) const {
return allowStep;
}
/*
================
idMoveable::BecomeNonSolid
================
*/
void idMoveable::BecomeNonSolid( void ) {
// set CONTENTS_RENDERMODEL so bullets still collide with the moveable
physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL, 0 );
physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_FORCEFIELD, 0 );
}
/*
================
idMoveable::EnableDamage
================
*/
void idMoveable::EnableDamage( bool enable, float duration ) {
canDamage = enable;
if ( duration ) {
PostEventSec( &EV_EnableDamage, duration, ( !enable ) ? 0.0f : 1.0f );
}
}
/*
================
idMoveable::GetRenderModelMaterial
================
*/
const idMaterial *idMoveable::GetRenderModelMaterial( void ) const {
if ( renderEntity.customShader ) {
return renderEntity.customShader;
}
if ( renderEntity.hModel && renderEntity.hModel->NumSurfaces() ) {
return renderEntity.hModel->Surface( 0 )->material;
}
return NULL;
}
/*
================
idMoveable::Event_BecomeNonSolid
================
*/
void idMoveable::Event_BecomeNonSolid( void ) {
BecomeNonSolid();
}
/*
================
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 );
}
}
/*
================
idMoveable::Event_EnableDamage
================
*/
void idMoveable::Event_EnableDamage( bool enable ) {
canDamage = enable;
}
/*
================
idMoveable::CheckWater
================
*/
void idMoveable::CheckWater( const idVec3& waterBodyOrg, const idMat3& waterBodyAxis, idCollisionModel* waterBodyModel ) {
if ( waterEffects ) {
waterEffects->SetOrigin( GetPhysics()->GetOrigin() );
waterEffects->SetAxis( GetPhysics()->GetAxis() );
waterEffects->SetVelocity( GetPhysics()->GetLinearVelocity() );
waterEffects->CheckWater( this, waterBodyOrg, waterBodyAxis, waterBodyModel );
}
}
/*
================
idMoveable::CheckNetworkStateChanges
================
*/
bool idMoveable::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_BASE( sdMoveableNetworkData );
NET_CHECK_STATE_PHYSICS;
return false;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_BASE( sdMoveableBroadcastData );
NET_CHECK_STATE_PHYSICS;
if ( baseData.hidden != fl.hidden ) {
return true;
}
return false;
}
return false;
}
/*
================
idMoveable::WriteNetworkState
================
*/
void idMoveable::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdMoveableNetworkData );
NET_WRITE_STATE_PHYSICS;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdMoveableBroadcastData );
NET_WRITE_STATE_PHYSICS;
newData.hidden = fl.hidden;
msg.WriteBool( newData.hidden );
}
}
/*
================
idMoveable::ApplyNetworkState
================
*/
void idMoveable::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) {
if ( mode == NSM_VISIBLE ) {
NET_GET_NEW( sdMoveableNetworkData );
NET_APPLY_STATE_PHYSICS;
} else if ( mode == NSM_BROADCAST ) {
NET_GET_NEW( sdMoveableBroadcastData );
NET_APPLY_STATE_PHYSICS;
if ( newData.hidden ) {
Hide();
} else {
Show();
}
}
}
/*
================
idMoveable::ReadNetworkState
================
*/
void idMoveable::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const {
if ( mode == NSM_VISIBLE ) {
NET_GET_STATES( sdMoveableNetworkData );
NET_READ_STATE_PHYSICS;
}
if ( mode == NSM_BROADCAST ) {
NET_GET_STATES( sdMoveableBroadcastData );
NET_READ_STATE_PHYSICS;
newData.hidden = msg.ReadBool();
}
}
/*
================
idMoveable::CreateNetworkStructure
================
*/
sdEntityStateNetworkData* idMoveable::CreateNetworkStructure( networkStateMode_t mode ) const {
if ( mode == NSM_VISIBLE ) {
sdMoveableNetworkData* newData = new sdMoveableNetworkData();
newData->physicsData = GetPhysics()->CreateNetworkStructure( mode );
return newData;
}
if ( mode == NSM_BROADCAST ) {
sdMoveableBroadcastData* newData = new sdMoveableBroadcastData();
newData->physicsData = GetPhysics()->CreateNetworkStructure( mode );
return newData;
}
return NULL;
}
/*
===============================================================================
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();
}
/*
================
idBarrel::BarrelThink
================
*/
void idBarrel::BarrelThink( void ) {
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( void ) {
BarrelThink();
}
/*
================
idBarrel::GetPhysicsToVisualTransform
================
*/
bool idBarrel::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
origin = vec3_origin;
axis = additionalAxis;
return true;
}
/*
================
idBarrel::Spawn
================
*/
void idBarrel::Spawn( void ) {
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();
}