Files
DoomRTX/neo/prey/Prey/game_forcefield.cpp
2026-05-05 15:01:01 -07:00

626 lines
17 KiB
C++

//**************************************************************************
//**
//** GAME_FORCEFIELD.CPP
//**
//** Game code for the forcefield
//**
//**************************************************************************
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
CLASS_DECLARATION( idEntity, hhForceField )
EVENT( EV_Activate, hhForceField::Event_Activate )
END_CLASS
/*
===========
hhForceField::Spawn
===========
*/
void hhForceField::Spawn(void) {
fl.takedamage = true;
SetShaderParm( SHADERPARM_TIMEOFFSET, 1.0f );
SetShaderParm( SHADERPARM_MODE, 0.0f );
fade = 0.0f;
activationRate = spawnArgs.GetFloat( "activationRate" );
deactivationRate = spawnArgs.GetFloat( "deactivationRate" );
undamageFadeRate = spawnArgs.GetFloat( "undamageFadeRate" );
// BecomeActive( TH_THINK|TH_TICKER );
cachedContents = CONTENTS_FORCEFIELD | CONTENTS_BLOCK_RADIUSDAMAGE | CONTENTS_SHOOTABLE;
physicsObj.SetSelf( this );
if (spawnArgs.GetBool("isSimpleBox")) {
// Simple boxes are cheaper and can be bound to other objects
physicsObj.SetClipModel( new idClipModel(idTraceModel(GetPhysics()->GetBounds())), 1.0f );
physicsObj.SetContents( cachedContents );
physicsObj.SetOrigin( GetOrigin() );
physicsObj.SetAxis( GetAxis() );
SetPhysics( &physicsObj );
}
else {
// Non-simple has real per-poly collision with it's model, uses default static physics because we don't have a tracemodel.
// This loses the activation of contacts on the object (so some things may not fall through when disabled), but in my tests,
// the spirit proxy falls through fine. NOTE: To fix the movables, etc. not falling when these are turned off, I'm manually
// Activating appropriate physics that overlap the bounds when Turned off.
// physicsObj.SetClipModel( new idClipModel(GetPhysics()->GetClipModel()), 1.0f );
// physicsObj.SetContents( cachedContents );
// physicsObj.SetOrigin( GetOrigin() );
// physicsObj.SetAxis( GetAxis() );
// SetPhysics( &physicsObj );
// Apparently the flags need to be on the material to make projectiles hit these.
GetPhysics()->SetContents(cachedContents);
}
EnterOnState();
damagedState = false;
nextCollideFxTime = gameLocal.time;
fl.networkSync = true; //always want to sync over the net
}
void hhForceField::Save( idSaveGame *savefile ) const {
savefile->WriteInt( fieldState );
savefile->WriteBool( damagedState );
savefile->WriteFloat( activationRate );
savefile->WriteFloat( deactivationRate );
savefile->WriteFloat( undamageFadeRate );
savefile->WriteInt( applyImpulseAttempts );
savefile->WriteInt( cachedContents );
savefile->WriteFloat( fade );
savefile->WriteInt( nextCollideFxTime );
savefile->WriteStaticObject( physicsObj );
}
void hhForceField::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( reinterpret_cast<int &> ( fieldState ) );
savefile->ReadBool( damagedState );
savefile->ReadFloat( activationRate );
savefile->ReadFloat( deactivationRate );
savefile->ReadFloat( undamageFadeRate );
savefile->ReadInt( applyImpulseAttempts );
savefile->ReadInt( cachedContents );
savefile->ReadFloat( fade );
savefile->ReadInt( nextCollideFxTime );
savefile->ReadStaticObject( physicsObj );
// Only restore physics if we were using it before
if (spawnArgs.GetBool("isSimpleBox")) {
RestorePhysics( &physicsObj );
}
}
void hhForceField::WriteToSnapshot( idBitMsgDelta &msg ) const {
physicsObj.WriteToSnapshot(msg);
msg.WriteBits(damagedState, 1);
msg.WriteFloat(activationRate);
msg.WriteFloat(deactivationRate);
msg.WriteFloat(undamageFadeRate);
msg.WriteBits(applyImpulseAttempts, 32);
msg.WriteBits(cachedContents, 32);
msg.WriteFloat(fade);
msg.WriteBits(nextCollideFxTime, 32);
msg.WriteBits(fieldState, 4);
msg.WriteBits(IsHidden(), 1);
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]);
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]);
}
void hhForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) {
physicsObj.ReadFromSnapshot(msg);
damagedState = !!msg.ReadBits(1);
activationRate = msg.ReadFloat();
deactivationRate = msg.ReadFloat();
undamageFadeRate = msg.ReadFloat();
applyImpulseAttempts = msg.ReadBits(32);
cachedContents = msg.ReadBits(32);
fade = msg.ReadFloat();
nextCollideFxTime = msg.ReadBits(32);
fieldState = (States)msg.ReadBits(4);
bool hidden = !!msg.ReadBits(1);
if (IsHidden() != hidden) {
if (hidden) {
Hide();
SetSkinByName( spawnArgs.GetString("skin_off" ) );
GetPhysics()->SetContents( 0 );
}
else {
Show();
SetSkinByName( NULL );
GetPhysics()->SetContents( cachedContents );
}
}
float f;
bool changed = false;
f = msg.ReadFloat();
changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f));
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f;
f = msg.ReadFloat();
changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f));
renderEntity.shaderParms[SHADERPARM_MODE] = f;
if (changed) {
UpdateModel();
UpdateVisuals();
}
}
void hhForceField::ClientPredictionThink( void ) {
idEntity::ClientPredictionThink();
}
/*
===========
hhForceField::ApplyImpulse
===========
*/
void hhForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
if (!ent->IsType(idActor::Type)) {
// Don't play sound for actor footsteps, landings. They play their own localized sounds for
// the sake of very large forcefields.
StartSound( "snd_impulse", SND_CHANNEL_ANY );
}
EnterDamagedState();
// Apply the hit effect
trace_t trace;
idStr fxCollide = spawnArgs.GetString( "fx_collide" );
if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) {
// Trace to find normal
idVec3 dir = force;
dir.NormalizeFast();
dir *= 200;
idVec3 start = point-dir;
idVec3 end = point+dir;
memset(&trace, 0, sizeof(trace));
gameLocal.clip.TracePoint(trace, start, end, CONTENTS_FORCEFIELD, ent);
if (trace.fraction < 1.0f) {
// Spawn fx oriented to normal of collision
hhFxInfo fxInfo;
fxInfo.SetNormal( trace.c.normal );
BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo, 0, false ); //rww - changed to not broadcast
nextCollideFxTime = gameLocal.time + 200;
}
}
}
/*
===========
hhForceField::Ticker
===========
*/
void hhForceField::Ticker( void ) {
if( fieldState == StatePreTurningOn ) {
idEntity* entityList[MAX_GENTITIES];
idEntity* entity = NULL;
idPhysics* physics = NULL;
int numEntities = 0;
int numEntitiesImpulseAppliedTo = 0;
idVec3 force = DetermineForce() * GetAxis();
numEntities = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SOLID, entityList, MAX_GENTITIES );
for( int ix = 0; ix < numEntities; ++ix ) {
entity = entityList[ix];
if( !entity ) {
continue;
}
if( entity == this ) {
continue;
}
//Removing mass from any calculations regarding impulse
entity->ApplyImpulse( this, 0, entity->GetOrigin(), force * entity->GetPhysics()->GetMass() );
numEntitiesImpulseAppliedTo++;
}
--applyImpulseAttempts;
if( !numEntitiesImpulseAppliedTo || !applyImpulseAttempts ) {
fieldState = StateTurningOn;
GetPhysics()->SetContents( cachedContents );
SetSkin( NULL );
StartSound( "snd_start", SND_CHANNEL_ANY );
Show();
EnterTurningOnState();
}
} else if( fieldState == StateTurningOn ) {
float deltaTime = MS2SEC( gameLocal.msec );
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] += activationRate * deltaTime;
if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] >= 1.0f ) {
EnterOnState();
}
UpdateVisuals();
} else if( fieldState == StateTurningOff ) {
float deltaTime = MS2SEC( gameLocal.msec );
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] -= deactivationRate * deltaTime;
if( renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] <= 0.0f ) {
EnterOffState();
}
UpdateVisuals();
}
if( damagedState ) {
float deltaTime = MS2SEC( gameLocal.msec );
// Fade parm back to normal
fade -= undamageFadeRate * deltaTime;
if ( fade <= 0.0f ) { // Finished fading
fade = 0;
damagedState = false;
}
SetShaderParm( SHADERPARM_MODE, fade );
}
if (!damagedState && (fieldState==StateOn || fieldState==StateOff)) {
BecomeInactive( TH_TICKER );
}
}
/*
===========
hhForceField::DetermineThinnestAxis
===========
*/
int hhForceField::DetermineThinnestAxis() {
int best = 0;
idBounds bounds = GetPhysics()->GetBounds();
for( int i = 1; i < 3; ++i ) {
if ( bounds[1][ i ] - bounds[0][ i ] < bounds[1][ best ] - bounds[0][ best ] ) {
best = i;
}
}
return best;
}
/*
===========
hhForceField::DetermineForce
===========
*/
idVec3 hhForceField::DetermineForce() {
idVec3 force( vec3_origin );
float forceMagnitude = spawnArgs.GetFloat( "forceMagnitude" );
int dir = spawnArgs.GetBool( "positiveDir", "1" ) ? 1.0f : -1.0f;
force[DetermineThinnestAxis()] = forceMagnitude * dir;
return force;
}
/*
===========
hhForceField::IsAtRest
Used to activate entites at rest
===========
*/
bool hhForceField::IsAtRest( int id ) const {
return ( fieldState != StateOff );
}
/*
===========
hhForceField::Event_Activate
===========
*/
void hhForceField::Event_Activate( idEntity *activator ) {
switch( fieldState ) {
case StatePreTurningOn:
EnterTurningOffState();
break;
case StateTurningOff:
EnterPreTurningOnState();
break;
case StateTurningOn:
EnterTurningOffState();
break;
case StateOn:
EnterTurningOffState();
break;
case StateOff:
EnterPreTurningOnState();
break;
}
}
/*
===========
hhForceField::EnterDamagedState
===========
*/
void hhForceField::EnterDamagedState() {
damagedState = true;
fade = 1.0f;
SetShaderParm( SHADERPARM_MODE, fade ); // Instantly change to the new shader
BecomeActive( TH_TICKER );
}
/*
===========
hhForceField::EnterPreTurningOnState
===========
*/
void hhForceField::EnterPreTurningOnState() {
fieldState = StatePreTurningOn;
applyImpulseAttempts = spawnArgs.GetInt( "applyImpulseAttempts" );
BecomeActive( TH_TICKER );
}
/*
===========
hhForceField::EnterOnState
===========
*/
void hhForceField::EnterOnState() {
fieldState = StateOn;
StartSound( "snd_loop", SND_CHANNEL_IDLE );
//HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within
if (gameLocal.isMultiplayer) {
gameLocal.KillBoxMasked( this, CONTENTS_BODY );
}
//HUMANHEAD END
}
/*
===========
hhForceField::EnterTurningOnState
===========
*/
void hhForceField::EnterTurningOnState() {
GetPhysics()->SetContents( cachedContents );
SetSkin( NULL );
StartSound( "snd_start", SND_CHANNEL_ANY );
Show();
BecomeActive( TH_TICKER );
}
/*
===========
hhForceField::EnterTurningOffState
===========
*/
void hhForceField::EnterTurningOffState() {
fieldState = StateTurningOff;
StopSound( SND_CHANNEL_IDLE );
StartSound( "snd_stop", SND_CHANNEL_ANY );
BecomeActive( TH_TICKER );
}
/*
===========
hhForceField::EnterOffState
===========
*/
void hhForceField::EnterOffState() {
fieldState = StateOff;
if (spawnArgs.GetBool("isSimpleBox")) {
GetPhysics()->ActivateContactEntities();
}
else {
// To allow complex shaped forcefields, we need to manually activate the physics of any contacts here
idClipModel *clipModels[ MAX_GENTITIES ];
idClipModel *cm;
idBounds bounds = GetPhysics()->GetAbsBounds();
bounds.ExpandSelf(bounds.GetRadius()*0.1f); // Expand the bounds by 10% to catch things on the surface
int num = gameLocal.clip.ClipModelsTouchingBounds( bounds, MASK_ALL, clipModels, MAX_GENTITIES );
for ( int i=0;i<num;i++ ) {
cm = clipModels[ i ];
idEntity *hit = cm->GetEntity();
if (hit && hit->GetPhysics()->IsAtRest() &&
(hit->GetPhysics()->IsType(idPhysics_RigidBody::Type) || hit->GetPhysics()->IsType(idPhysics_AF::Type)) ) {
if ( !hit->IsType( hhVehicle::Type ) ) { //HUMANHEAD jsh PCF 5/26/06: fix 30hz vehicle console jittering
hit->GetPhysics()->Activate();
}
}
}
}
// HUMANHEAD PCF pdm 04/27/06: Unbind any bound projectiles
idEntity *ent;
idEntity *next;
for( ent = teamChain; ent != NULL; ent = next ) {
next = ent->GetTeamChain();
if ( ent && ent->IsType( hhProjectile::Type ) ) {
ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines
next = teamChain;
//HUMANHEAD bjk PCF (4-28-06) - explode crawlers
if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) {
static_cast<hhProjectile*>(ent)->PostEventSec( &EV_Explode, 0.2f );
}
}
}
GetPhysics()->SetContents( 0 );
Hide();
SetSkinByName( spawnArgs.GetString("skin_off" ) ); // Set the force field to completely not draw
}
//--------------------------------------------------------------------------
// hhShuttleForcefield
//--------------------------------------------------------------------------
CLASS_DECLARATION( idEntity, hhShuttleForceField )
EVENT( EV_Activate, hhShuttleForceField::Event_Activate )
END_CLASS
void hhShuttleForceField::Spawn() {
nextCollideFxTime = gameLocal.time;
GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
// Start in the on state
fieldState = StateOn;
fade.Init(gameLocal.time, 0, 1.0f, 1.0f);
SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time));
fl.networkSync = true; //rww
}
void hhShuttleForceField::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ) {
trace_t trace;
idStr fxCollide = spawnArgs.GetString( "fx_collide" );
if ( fxCollide.Length() && gameLocal.time > nextCollideFxTime && point != GetOrigin()) {
// Trace to find normal
idVec3 dir = force;
dir.NormalizeFast();
dir *= 200;
idVec3 start = point-dir;
idVec3 end = point+dir;
memset(&trace, 0, sizeof(trace));
gameLocal.clip.TracePoint(trace, start, end, CONTENTS_SOLID, ent);
if (trace.fraction < 1.0f) {
// Spawn fx oriented to normal of collision
hhFxInfo fxInfo;
fxInfo.SetNormal( trace.c.normal );
BroadcastFxInfo( fxCollide, trace.c.point, mat3_identity, &fxInfo );
nextCollideFxTime = gameLocal.time + 200;
StartSound( "snd_collide", SND_CHANNEL_ANY, 0, true, NULL );
ActivatePrefixed( "triggerCollide", this ); // bg: Feedback hook.
}
}
}
void hhShuttleForceField::Ticker() {
SetShaderParm(SHADERPARM_TIMEOFFSET, fade.GetCurrentValue(gameLocal.time));
if (fade.IsDone(gameLocal.time)) {
if (fieldState == StateTurningOn) {
// Entering On state
fieldState = StateOn;
GetPhysics()->SetContents(CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
}
else if (fieldState == StateTurningOff) {
// Entering Off state
fieldState = StateOff;
GetPhysics()->SetContents(0);
}
BecomeInactive(TH_TICKER);
}
}
void hhShuttleForceField::Event_Activate(idEntity *activator) {
int duration = SEC2MS(spawnArgs.GetFloat("fade_duration"));
float currentFade = fade.GetCurrentValue(gameLocal.time);
if (fieldState == StateOn || fieldState == StateTurningOn) {
// Entering TurningOff state
fade.Init(gameLocal.time, duration, currentFade, 0.0f);
fieldState = StateTurningOff;
}
else if (fieldState == StateOff || fieldState == StateTurningOff) {
// Entering TurningOn state
fade.Init(gameLocal.time, duration, currentFade, 1.0f);
fieldState = StateTurningOn;
//HUMANHEAD PCF rww 05/15/06 - prevent stuck-in-forcefield by killing everything within
if (gameLocal.isMultiplayer) {
gameLocal.KillBoxMasked( this, CONTENTS_BODY );
}
//HUMANHEAD END
}
BecomeActive( TH_TICKER );
}
void hhShuttleForceField::Save( idSaveGame *savefile ) const {
savefile->WriteInt( nextCollideFxTime );
savefile->WriteInt( fieldState );
savefile->WriteFloat( fade.GetStartTime() ); // idInterpolate<float>
savefile->WriteFloat( fade.GetDuration() );
savefile->WriteFloat( fade.GetStartValue() );
savefile->WriteFloat( fade.GetEndValue() );
}
void hhShuttleForceField::Restore( idRestoreGame *savefile ) {
float set;
savefile->ReadInt( nextCollideFxTime );
savefile->ReadInt( reinterpret_cast<int &> ( fieldState ) );
savefile->ReadFloat( set ); // idInterpolate<float>
fade.SetStartTime( set );
savefile->ReadFloat( set );
fade.SetDuration( set );
savefile->ReadFloat( set );
fade.SetStartValue(set);
savefile->ReadFloat( set );
fade.SetEndValue( set );
}
void hhShuttleForceField::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteBits(GetPhysics()->GetContents(), 32);
msg.WriteBits(nextCollideFxTime, 32);
msg.WriteBits(fieldState, 8);
msg.WriteFloat(fade.GetStartTime());
msg.WriteFloat(fade.GetDuration());
msg.WriteFloat(fade.GetStartValue());
msg.WriteFloat(fade.GetEndValue());
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]);
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]);
}
void hhShuttleForceField::ReadFromSnapshot( const idBitMsgDelta &msg ) {
int contents = msg.ReadBits(32);
if (contents != GetPhysics()->GetContents()) {
GetPhysics()->SetContents(contents);
}
nextCollideFxTime = msg.ReadBits(32);
fieldState = (States)msg.ReadBits(8);
fade.SetStartTime(msg.ReadFloat());
fade.SetDuration(msg.ReadFloat());
fade.SetStartValue(msg.ReadFloat());
fade.SetEndValue(msg.ReadFloat());
float f;
bool changed = false;
f = msg.ReadFloat();
changed = (changed || (renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] != f));
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = f;
f = msg.ReadFloat();
changed = (changed || (renderEntity.shaderParms[SHADERPARM_MODE] != f));
renderEntity.shaderParms[SHADERPARM_MODE] = f;
if (changed) {
UpdateVisuals();
}
}
void hhShuttleForceField::ClientPredictionThink( void ) {
idEntity::ClientPredictionThink();
}