dhewm3-sdk/d3xp/Grabber.cpp
dhewg afebd7e1e5 Untangle the epic precompiled.h mess
Don't include the lazy precompiled.h everywhere, only what's
required for the compilation unit.
platform.h needs to be included instead to provide all essential
defines and types.
All includes use the relative path to the neo or the game
specific root.
Move all idlib related includes from idlib/Lib.h to precompiled.h.
precompiled.h still exists for the MFC stuff in tools/.
Add some missing header guards.
2018-08-20 01:46:28 +02:00

740 lines
20 KiB
C++

/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 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 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.
===========================================================================
*/
#ifdef _D3XP
#include "sys/platform.h"
#include "gamesys/SysCvar.h"
#include "ai/AI.h"
#include "Player.h"
#include "Moveable.h"
#include "Misc.h"
#include "Grabber.h"
#define MAX_DRAG_TRACE_DISTANCE 384.0f
#define TRACE_BOUNDS_SIZE 3.f
#define HOLD_DISTANCE 72.f
#define FIRING_DELAY 1000.0f
#define DRAG_FAIL_LEN 64.f
#define THROW_SCALE 1000
#define MAX_PICKUP_VELOCITY 1500 * 1500
#define MAX_PICKUP_SIZE 96
/*
===============================================================================
Allows entities to be dragged through the world with physics.
===============================================================================
*/
CLASS_DECLARATION( idEntity, idGrabber )
END_CLASS
/*
==============
idGrabber::idGrabber
==============
*/
idGrabber::idGrabber( void ) {
dragEnt = NULL;
owner = NULL;
beam = NULL;
beamTarget = NULL;
oldUcmdFlags = 0;
shakeForceFlip = false;
holdingAF = false;
endTime = 0;
lastFiredTime = -FIRING_DELAY;
dragFailTime = 0;
startDragTime = 0;
warpId = -1;
dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
}
/*
==============
idGrabber::~idGrabber
==============
*/
idGrabber::~idGrabber( void ) {
StopDrag( true );
if ( beam ) {
delete beam;
}
if ( beamTarget ) {
delete beamTarget;
}
}
/*
==============
idGrabber::Save
==============
*/
void idGrabber::Save( idSaveGame *savefile ) const {
dragEnt.Save( savefile );
savefile->WriteStaticObject( drag );
savefile->WriteVec3( saveGravity );
savefile->WriteInt( id );
savefile->WriteVec3( localPlayerPoint );
owner.Save( savefile );
savefile->WriteBool( holdingAF );
savefile->WriteBool( shakeForceFlip );
savefile->WriteInt( endTime );
savefile->WriteInt( lastFiredTime );
savefile->WriteInt( dragFailTime );
savefile->WriteInt( startDragTime );
savefile->WriteFloat( dragTraceDist );
savefile->WriteInt( savedContents );
savefile->WriteInt( savedClipmask );
savefile->WriteObject( beam );
savefile->WriteObject( beamTarget );
savefile->WriteInt( warpId );
}
/*
==============
idGrabber::Restore
==============
*/
void idGrabber::Restore( idRestoreGame *savefile ) {
//Spawn the beams
Initialize();
dragEnt.Restore( savefile );
savefile->ReadStaticObject( drag );
savefile->ReadVec3( saveGravity );
savefile->ReadInt( id );
// Restore the drag force's physics object
if ( dragEnt.IsValid() ) {
drag.SetPhysics( dragEnt.GetEntity()->GetPhysics(), id, dragEnt.GetEntity()->GetPhysics()->GetOrigin() );
}
savefile->ReadVec3( localPlayerPoint );
owner.Restore( savefile );
savefile->ReadBool( holdingAF );
savefile->ReadBool( shakeForceFlip );
savefile->ReadInt( endTime );
savefile->ReadInt( lastFiredTime );
savefile->ReadInt( dragFailTime );
savefile->ReadInt( startDragTime );
savefile->ReadFloat( dragTraceDist );
savefile->ReadInt( savedContents );
savefile->ReadInt( savedClipmask );
savefile->ReadObject( reinterpret_cast<idClass *&>(beam) );
savefile->ReadObject( reinterpret_cast<idClass *&>(beamTarget) );
savefile->ReadInt( warpId );
}
/*
==============
idGrabber::Initialize
==============
*/
void idGrabber::Initialize( void ) {
if ( !gameLocal.isMultiplayer ) {
idDict args;
if ( !beamTarget ) {
args.SetVector( "origin", vec3_origin );
args.SetBool( "start_off", true );
beamTarget = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
}
if ( !beam ) {
args.Clear();
args.Set( "target", beamTarget->name.c_str() );
args.SetVector( "origin", vec3_origin );
args.SetBool( "start_off", true );
args.Set( "width", "6" );
args.Set( "skin", "textures/smf/flareSizeable" );
args.Set( "_color", "0.0235 0.843 0.969 0.2" );
beam = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
beam->SetShaderParm( 6, 1.0f );
}
endTime = 0;
dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
}
else {
beam = NULL;
beamTarget = NULL;
endTime = 0;
dragTraceDist = MAX_DRAG_TRACE_DISTANCE;
};
}
/*
==============
idGrabber::SetDragDistance
==============
*/
void idGrabber::SetDragDistance( float dist ) {
dragTraceDist = dist;
}
/*
==============
idGrabber::StartDrag
==============
*/
void idGrabber::StartDrag( idEntity *grabEnt, int id ) {
int clipModelId = id;
idPlayer *thePlayer = owner.GetEntity();
holdingAF = false;
dragFailTime = gameLocal.slow.time;
startDragTime = gameLocal.slow.time;
oldUcmdFlags = thePlayer->usercmd.flags;
// set grabbed state for networking
grabEnt->SetGrabbedState( true );
// This is the new object to drag around
dragEnt = grabEnt;
// Show the beams!
UpdateBeams();
if ( beam ) {
beam->Show();
}
if ( beamTarget ) {
beamTarget->Show();
}
// Move the object to the fast group (helltime)
grabEnt->timeGroup = TIME_GROUP2;
// Handle specific class types
if ( grabEnt->IsType( idProjectile::Type ) ) {
idProjectile* p = (idProjectile*)grabEnt;
p->CatchProjectile( thePlayer, "_catch" );
// Make the projectile non-solid to other projectiles/enemies (special hack for helltime hunter)
if ( !idStr::Cmp( grabEnt->GetEntityDefName(), "projectile_helltime_killer" ) ) {
savedContents = CONTENTS_PROJECTILE;
savedClipmask = MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE;
} else {
savedContents = grabEnt->GetPhysics()->GetContents();
savedClipmask = grabEnt->GetPhysics()->GetClipMask();
}
grabEnt->GetPhysics()->SetContents( 0 );
grabEnt->GetPhysics()->SetClipMask( CONTENTS_SOLID|CONTENTS_BODY );
} else if ( grabEnt->IsType( idExplodingBarrel::Type ) ) {
idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(grabEnt);
ebarrel->StartBurning();
} else if ( grabEnt->IsType( idAFEntity_Gibbable::Type ) ) {
holdingAF = true;
clipModelId = 0;
if ( grabbableAI( grabEnt->spawnArgs.GetString( "classname" ) ) ) {
idAI *aiEnt = static_cast<idAI*>(grabEnt);
aiEnt->StartRagdoll();
}
} else if ( grabEnt->IsType( idMoveableItem::Type ) ) {
grabEnt->PostEventMS( &EV_Touch, 250, thePlayer, 0 );
}
// Get the current physics object to manipulate
idPhysics *phys = grabEnt->GetPhysics();
// Turn off gravity on object
saveGravity = phys->GetGravity();
phys->SetGravity( vec3_origin );
// hold it directly in front of player
localPlayerPoint = ( thePlayer->firstPersonViewAxis[0] * HOLD_DISTANCE ) * thePlayer->firstPersonViewAxis.Transpose();
// Set the ending time for the hold
endTime = gameLocal.time + g_grabberHoldSeconds.GetFloat() * 1000;
// Start up the Force_Drag to bring it in
drag.Init( g_grabberDamping.GetFloat() );
drag.SetPhysics( phys, clipModelId, thePlayer->firstPersonViewOrigin + localPlayerPoint * thePlayer->firstPersonViewAxis);
// start the screen warp
warpId = thePlayer->playerView.AddWarp( phys->GetOrigin(), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 160, 2000 );
}
/*
==============
idGrabber::StopDrag
==============
*/
void idGrabber::StopDrag( bool dropOnly ) {
idPlayer *thePlayer = owner.GetEntity();
if ( beam ) {
beam->Hide();
}
if ( beamTarget ) {
beamTarget->Hide();
}
if ( dragEnt.IsValid() ) {
idEntity *ent = dragEnt.GetEntity();
// set grabbed state for networking
ent->SetGrabbedState( false );
// If a cinematic has started, allow dropped object to think in cinematics
if ( gameLocal.inCinematic ) {
ent->cinematic = true;
}
// Restore Gravity
ent->GetPhysics()->SetGravity( saveGravity );
// Move the object back to the slow group (helltime)
ent->timeGroup = TIME_GROUP1;
if ( holdingAF ) {
idAFEntity_Gibbable *af = static_cast<idAFEntity_Gibbable *>(ent);
idPhysics_AF *af_Phys = static_cast<idPhysics_AF*>(af->GetPhysics());
if ( grabbableAI( ent->spawnArgs.GetString( "classname" ) ) ) {
idAI *aiEnt = static_cast<idAI*>(ent);
aiEnt->Damage( thePlayer, thePlayer, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
}
af->SetThrown( !dropOnly );
// Reset timers so that it isn't forcibly put to rest in mid-air
af_Phys->PutToRest();
af_Phys->Activate();
af_Phys->SetTimeScaleRamp( MS2SEC(gameLocal.slow.time) - 1.5f, MS2SEC(gameLocal.slow.time) + 1.0f );
}
// If the object isn't near its goal, just drop it in place.
if ( !ent->IsType( idProjectile::Type ) && ( dropOnly || drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) ) {
ent->GetPhysics()->SetLinearVelocity( vec3_origin );
thePlayer->StartSoundShader( declManager->FindSound( "grabber_maindrop" ), SND_CHANNEL_WEAPON, 0, false, NULL );
if ( ent->IsType( idExplodingBarrel::Type ) ) {
idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
ebarrel->SetStability( true );
ebarrel->StopBurning();
}
} else {
// Shoot the object forward
ent->ApplyImpulse( thePlayer, 0, ent->GetPhysics()->GetOrigin(), thePlayer->firstPersonViewAxis[0] * THROW_SCALE * ent->GetPhysics()->GetMass() );
thePlayer->StartSoundShader( declManager->FindSound( "grabber_release" ), SND_CHANNEL_WEAPON, 0, false, NULL );
// Orient projectiles away from the player
if ( ent->IsType( idProjectile::Type ) ) {
idPlayer *player = owner.GetEntity();
idAngles ang = player->firstPersonViewAxis[0].ToAngles();
ang.pitch += 90.f;
ent->GetPhysics()->SetAxis( ang.ToMat3() );
ent->GetPhysics()->SetAngularVelocity( vec3_origin );
// Restore projectile contents
ent->GetPhysics()->SetContents( savedContents );
ent->GetPhysics()->SetClipMask( savedClipmask );
} else if ( ent->IsType( idMoveable::Type ) ) {
// Turn on damage for this object
idMoveable *obj = static_cast<idMoveable*>(ent);
obj->EnableDamage( true, 2.5f );
obj->SetAttacker( thePlayer );
if ( ent->IsType( idExplodingBarrel::Type ) ) {
idExplodingBarrel *ebarrel = static_cast<idExplodingBarrel*>(ent);
ebarrel->SetStability( false );
}
} else if ( ent->IsType( idMoveableItem::Type ) ) {
ent->GetPhysics()->SetClipMask( MASK_MONSTERSOLID );
}
}
// Remove the Force_Drag's control of the entity
drag.RemovePhysics( ent->GetPhysics() );
}
if ( warpId != -1 ) {
thePlayer->playerView.FreeWarp( warpId );
warpId = -1;
}
lastFiredTime = gameLocal.time;
dragEnt = NULL;
endTime = 0;
}
/*
==============
idGrabber::Update
==============
*/
int idGrabber::Update( idPlayer *player, bool hide ) {
trace_t trace;
idEntity *newEnt;
// pause before allowing refire
if ( lastFiredTime + FIRING_DELAY > gameLocal.time ) {
return 3;
}
// Dead players release the trigger
if ( hide || player->health <= 0 ) {
StopDrag( true );
if ( hide ) {
lastFiredTime = gameLocal.time - FIRING_DELAY + 250;
}
return 3;
}
// Check if object being held has been removed (dead demon, projectile, etc.)
if ( endTime > gameLocal.time ) {
bool abort = !dragEnt.IsValid();
if ( !abort && dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
idProjectile *proj = (idProjectile *)dragEnt.GetEntity();
if ( proj->GetProjectileState() >= 3 ) {
abort = true;
}
}
if ( !abort && dragEnt.GetEntity()->IsHidden() ) {
abort = true;
}
// Not in multiplayer :: Pressing "reload" lets you carefully drop an item
if ( !gameLocal.isMultiplayer && !abort && (( player->usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldUcmdFlags & UCF_IMPULSE_SEQUENCE )) && (player->usercmd.impulse == IMPULSE_13) ) {
abort = true;
}
if ( abort ) {
StopDrag( true );
return 3;
}
}
owner = player;
// if no entity selected for dragging
if ( !dragEnt.GetEntity() ) {
idBounds bounds;
idVec3 end = player->firstPersonViewOrigin + player->firstPersonViewAxis[0] * dragTraceDist;
bounds.Zero();
bounds.ExpandSelf( TRACE_BOUNDS_SIZE );
gameLocal.clip.TraceBounds( trace, player->firstPersonViewOrigin, end, bounds, MASK_SHOT_RENDERMODEL|CONTENTS_PROJECTILE|CONTENTS_MOVEABLECLIP, player );
// If the trace hit something
if ( trace.fraction < 1.0f ) {
newEnt = gameLocal.entities[ trace.c.entityNum ];
// if entity is already being grabbed then bypass
if ( gameLocal.isMultiplayer && newEnt->IsGrabbed() ) {
return 0;
}
// Check if this is a valid entity to hold
if ( newEnt && ( newEnt->IsType( idMoveable::Type ) ||
newEnt->IsType( idMoveableItem::Type ) ||
newEnt->IsType( idProjectile::Type ) ||
newEnt->IsType( idAFEntity_Gibbable::Type ) ) &&
newEnt->noGrab == false &&
newEnt->GetPhysics()->GetBounds().GetRadius() < MAX_PICKUP_SIZE &&
newEnt->GetPhysics()->GetLinearVelocity().LengthSqr() < MAX_PICKUP_VELOCITY ) {
bool validAF = true;
if ( newEnt->IsType( idAFEntity_Gibbable::Type ) ) {
idAFEntity_Gibbable *afEnt = static_cast<idAFEntity_Gibbable*>(newEnt);
if ( grabbableAI( newEnt->spawnArgs.GetString( "classname" ) ) ) {
// Make sure it's also active
if ( !afEnt->IsActive() ) {
validAF = false;
}
} else if ( !afEnt->IsActiveAF() ) {
validAF = false;
}
}
if ( validAF && player->usercmd.buttons & BUTTON_ATTACK ) {
// Grab this entity and start dragging it around
StartDrag( newEnt, trace.c.id );
} else if ( validAF ) {
// A holdable object is ready to be grabbed
return 1;
}
}
}
}
// check backwards server time in multiplayer
bool allow = true;
if ( gameLocal.isMultiplayer ) {
// if we've marched backwards
if ( gameLocal.slow.time < startDragTime ) {
allow = false;
}
}
// if there is an entity selected for dragging
if ( dragEnt.GetEntity() && allow ) {
idPhysics *entPhys = dragEnt.GetEntity()->GetPhysics();
idVec3 goalPos;
// If the player lets go of attack, or time is up
if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) {
StopDrag( false );
return 3;
}
if ( gameLocal.time > endTime ) {
StopDrag( true );
return 3;
}
// Check if the player is standing on the object
if ( !holdingAF ) {
idBounds playerBounds;
idBounds objectBounds = entPhys->GetAbsBounds();
idVec3 newPoint = player->GetPhysics()->GetOrigin();
// create a bounds at the players feet
playerBounds.Clear();
playerBounds.AddPoint( newPoint );
newPoint.z -= 1.f;
playerBounds.AddPoint( newPoint );
playerBounds.ExpandSelf( 8.f );
// If it intersects the object bounds, then drop it
if ( playerBounds.IntersectsBounds( objectBounds ) ) {
StopDrag( true );
return 3;
}
}
// Shake the object at the end of the hold
if ( g_grabberEnableShake.GetBool() && !gameLocal.isMultiplayer ) {
ApplyShake();
}
// Set and evaluate drag force
goalPos = player->firstPersonViewOrigin + localPlayerPoint * player->firstPersonViewAxis;
drag.SetGoalPosition( goalPos );
drag.Evaluate( gameLocal.time );
// If an object is flying too fast toward the player, stop it hard
if ( g_grabberHardStop.GetBool() ) {
idPlane theWall;
idVec3 toPlayerVelocity, objectCenter;
float toPlayerSpeed;
toPlayerVelocity = -player->firstPersonViewAxis[0];
toPlayerSpeed = entPhys->GetLinearVelocity() * toPlayerVelocity;
if ( toPlayerSpeed > 64.f ) {
objectCenter = entPhys->GetAbsBounds().GetCenter();
theWall.SetNormal( player->firstPersonViewAxis[0] );
theWall.FitThroughPoint( goalPos );
if ( theWall.Side( objectCenter, 0.1f ) == PLANESIDE_BACK ) {
int i, num;
num = entPhys->GetNumClipModels();
for ( i=0; i<num; i++ ) {
entPhys->SetLinearVelocity( vec3_origin, i );
}
}
}
// Make sure the object isn't spinning too fast
const float MAX_ROTATION_SPEED = 12.f;
idVec3 angVel = entPhys->GetAngularVelocity();
float rotationSpeed = angVel.LengthFast();
if ( rotationSpeed > MAX_ROTATION_SPEED ) {
angVel.NormalizeFast();
angVel *= MAX_ROTATION_SPEED;
entPhys->SetAngularVelocity( angVel );
}
}
// Orient projectiles away from the player
if ( dragEnt.GetEntity()->IsType( idProjectile::Type ) ) {
idAngles ang = player->firstPersonViewAxis[0].ToAngles();
ang.pitch += 90.f;
entPhys->SetAxis( ang.ToMat3() );
}
// Some kind of effect from gun to object?
UpdateBeams();
// If the object is stuck away from its intended position for more than 500ms, let it go.
if ( drag.GetDistanceToGoal() > DRAG_FAIL_LEN ) {
if ( dragFailTime < (gameLocal.slow.time - 500) ) {
StopDrag( true );
return 3;
}
} else {
dragFailTime = gameLocal.slow.time;
}
// Currently holding an object
return 2;
}
// Not holding, nothing to hold
return 0;
}
/*
======================
idGrabber::UpdateBeams
======================
*/
void idGrabber::UpdateBeams( void ) {
jointHandle_t muzzle_joint;
idVec3 muzzle_origin;
idMat3 muzzle_axis;
renderEntity_t *re;
if ( !beam ) {
return;
}
if ( dragEnt.IsValid() ) {
idPlayer *thePlayer = owner.GetEntity();
if ( beamTarget ) {
beamTarget->SetOrigin( dragEnt.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter() );
}
muzzle_joint = thePlayer->weapon.GetEntity()->GetAnimator()->GetJointHandle( "particle_upper" );
if ( muzzle_joint != INVALID_JOINT ) {
thePlayer->weapon.GetEntity()->GetJointWorldTransform( muzzle_joint, gameLocal.time, muzzle_origin, muzzle_axis );
} else {
muzzle_origin = thePlayer->GetPhysics()->GetOrigin();
}
beam->SetOrigin( muzzle_origin );
re = beam->GetRenderEntity();
re->origin = muzzle_origin;
beam->UpdateVisuals();
beam->Present();
}
}
/*
==============
idGrabber::ApplyShake
==============
*/
void idGrabber::ApplyShake( void ) {
float u = 1 - (float)( endTime - gameLocal.time ) / ( g_grabberHoldSeconds.GetFloat() * 1000 );
if ( u >= 0.8f ) {
idVec3 point, impulse;
float shakeForceMagnitude = 450.f;
float mass = dragEnt.GetEntity()->GetPhysics()->GetMass();
shakeForceFlip = !shakeForceFlip;
// get point to rotate around
point = dragEnt.GetEntity()->GetPhysics()->GetOrigin();
point.y += 1;
// Articulated figures get less violent shake
if ( holdingAF ) {
shakeForceMagnitude = 120.f;
}
// calc impulse
if ( shakeForceFlip ) {
impulse.Set( 0, 0, shakeForceMagnitude * u * mass );
}
else {
impulse.Set( 0, 0, -shakeForceMagnitude * u * mass );
}
dragEnt.GetEntity()->ApplyImpulse( NULL, 0, point, impulse );
}
}
/*
==============
idGrabber::grabbableAI
==============
*/
bool idGrabber::grabbableAI( const char *aiName ) {
// skip "monster_"
aiName += 8;
if (!idStr::Cmpn( aiName, "flying_lostsoul", 15 ) ||
!idStr::Cmpn( aiName, "demon_trite", 11 ) ||
!idStr::Cmp( aiName, "flying_forgotten" ) ||
!idStr::Cmp( aiName, "demon_cherub" ) ||
!idStr::Cmp( aiName, "demon_tick" )) {
return true;
}
return false;
}
#endif