dhewm3-sdk/game/Projectile.cpp
Daniel Gibson 351a1744a7 Fix Assertion in Engine when shooting Arrows (from Rivensin)
it was caused by decals of size 0 (when the arrow hits the floor)

at least in Rivensin this caused trouble, so it seems likely that it's
responsible for some of the reported Dentonmod crashes as well
2021-01-18 03:42:58 +01:00

2651 lines
74 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.
===========================================================================
*/
#include "sys/platform.h"
#include "renderer/ModelManager.h"
#include "gamesys/SysCvar.h"
#include "script/Script_Thread.h"
#include "ai/AI.h"
#include "Player.h"
#include "Mover.h"
#include "SmokeParticles.h"
#include "framework/DeclEntityDef.h"
#include "Projectile.h"
#ifdef _DENTONMOD
#include "BrittleFracture.h"
#include "Moveable.h"
#endif
/*
===============================================================================
idProjectile
===============================================================================
*/
static const int BFG_DAMAGE_FREQUENCY = 333;
static const float BOUNCE_SOUND_MIN_VELOCITY = 100.0f; //200
static const float BOUNCE_SOUND_MAX_VELOCITY = 600.0f;//400
const idEventDef EV_Explode( "<explode>", NULL );
const idEventDef EV_Fizzle( "<fizzle>", NULL );
const idEventDef EV_RadiusDamage( "<radiusdmg>", "e" );
const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' );
CLASS_DECLARATION( idEntity, idProjectile )
EVENT( EV_Explode, idProjectile::Event_Explode )
EVENT( EV_Fizzle, idProjectile::Event_Fizzle )
EVENT( EV_Touch, idProjectile::Event_Touch )
EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage )
EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState )
END_CLASS
/*
================
idProjectile::idProjectile
================
*/
idProjectile::idProjectile( void ) {
owner = NULL;
lightDefHandle = -1;
thrust = 0.0f;
thrust_end = 0;
smokeFly = NULL;
smokeFlyTime = 0;
state = SPAWNED;
lightOffset = vec3_zero;
lightStartTime = 0;
lightEndTime = 0;
lightColor = vec3_zero;
state = SPAWNED;
damagePower = 1.0f;
damageDef = NULL; // new
memset( &projectileFlags, 0, sizeof( projectileFlags ) );
memset( &renderLight, 0, sizeof( renderLight ) );
#ifdef _DENTONMOD
tracerEffect = NULL;
#endif
// note: for net_instanthit projectiles, we will force this back to false at spawn time
fl.networkSync = true;
netSyncPhysics = false;
}
/*
================
idProjectile::Spawn
================
*/
void idProjectile::Spawn( void ) {
physicsObj.SetSelf( this );
physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
physicsObj.SetContents( 0 );
physicsObj.SetClipMask( 0 );
physicsObj.PutToRest();
SetPhysics( &physicsObj );
}
/*
================
idProjectile::Save
================
*/
void idProjectile::Save( idSaveGame *savefile ) const {
owner.Save( savefile );
projectileFlags_s flags = projectileFlags;
LittleBitField( &flags, sizeof( flags ) );
savefile->Write( &flags, sizeof( flags ) );
savefile->WriteFloat( thrust );
savefile->WriteInt( thrust_end );
savefile->WriteRenderLight( renderLight );
savefile->WriteInt( (int)lightDefHandle );
savefile->WriteVec3( lightOffset );
savefile->WriteInt( lightStartTime );
savefile->WriteInt( lightEndTime );
savefile->WriteVec3( lightColor );
savefile->WriteParticle( smokeFly );
savefile->WriteInt( smokeFlyTime );
savefile->WriteInt( (int)state );
savefile->WriteFloat( damagePower );
savefile->WriteStaticObject( physicsObj );
savefile->WriteStaticObject( thruster );
}
/*
================
idProjectile::Restore
================
*/
void idProjectile::Restore( idRestoreGame *savefile ) {
owner.Restore( savefile );
savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
LittleBitField( &projectileFlags, sizeof( projectileFlags ) );
savefile->ReadFloat( thrust );
savefile->ReadInt( thrust_end );
savefile->ReadRenderLight( renderLight );
savefile->ReadInt( (int &)lightDefHandle );
savefile->ReadVec3( lightOffset );
savefile->ReadInt( lightStartTime );
savefile->ReadInt( lightEndTime );
savefile->ReadVec3( lightColor );
savefile->ReadParticle( smokeFly );
savefile->ReadInt( smokeFlyTime );
savefile->ReadInt( (int &)state );
savefile->ReadFloat( damagePower );
savefile->ReadStaticObject( physicsObj );
RestorePhysics( &physicsObj );
savefile->ReadStaticObject( thruster );
thruster.SetPhysics( &physicsObj );
if ( smokeFly != NULL ) {
idVec3 dir;
dir = physicsObj.GetLinearVelocity();
dir.NormalizeFast();
gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
//Reinitialize the damage Def--- By Clone JC Denton
damageDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_damage" ) );
}
/*
================
idProjectile::GetOwner
================
*/
idEntity *idProjectile::GetOwner( void ) const {
return owner.GetEntity();
}
/*
================
idProjectile::Create
================
*/
void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
idDict args;
idStr shaderName;
idVec3 light_color;
// idVec3 light_offset; // was delcared unnecessarily, removed by Clone JC Denton
idVec3 tmp;
idMat3 axis;
Unbind();
// align z-axis of model with the direction
axis = dir.ToMat3();
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
physicsObj.SetOrigin( start );
physicsObj.SetAxis( axis );
physicsObj.GetClipModel()->SetOwner( owner );
this->owner = owner;
memset( &renderLight, 0, sizeof( renderLight ) );
shaderName = spawnArgs.GetString( "mtr_light_shader" );
if ( *(const char *)shaderName ) {
renderLight.shader = declManager->FindMaterial( shaderName, false );
renderLight.pointLight = true;
renderLight.lightRadius[0] =
renderLight.lightRadius[1] =
renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
spawnArgs.GetVector( "light_color", "1 1 1", light_color );
renderLight.shaderParms[0] = light_color[0];
renderLight.shaderParms[1] = light_color[1];
renderLight.shaderParms[2] = light_color[2];
renderLight.shaderParms[3] = 1.0f;
}
spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );
lightStartTime = 0;
lightEndTime = 0;
smokeFlyTime = 0;
damagePower = 1.0f;
damageDef = NULL; // New
UpdateVisuals();
state = CREATED;
if ( spawnArgs.GetBool( "net_fullphysics" ) ) {
netSyncPhysics = true;
}
}
/*
=================
idProjectile::~idProjectile
=================
*/
idProjectile::~idProjectile() {
StopSound( SND_CHANNEL_ANY, false );
FreeLightDef();
#ifdef _DENTONMOD
if( tracerEffect ) {
delete tracerEffect;
}
#endif
}
/*
=================
idProjectile::FreeLightDef
=================
*/
void idProjectile::FreeLightDef( void ) {
if ( lightDefHandle != -1 ) {
gameRenderWorld->FreeLightDef( lightDefHandle );
lightDefHandle = -1;
}
}
/*
=================
idProjectile::Launch
=================
*/
void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) {
float fuse;
float endthrust;
idVec3 velocity;
idAngles angular_velocity;
float linear_friction;
float angular_friction;
float contact_friction;
float bounce;
float mass;
float speed;
float gravity;
idVec3 gravVec;
idVec3 tmp;
idMat3 axis;
int contents;
int clipMask;
// allow characters to throw projectiles during cinematics, but not the player
if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) {
cinematic = owner.GetEntity()->cinematic;
} else {
cinematic = false;
}
thrust = spawnArgs.GetFloat( "thrust" );
endthrust = spawnArgs.GetFloat( "thrust_end" );
spawnArgs.GetVector( "velocity", "0 0 0", velocity );
speed = velocity.Length() * launchPower;
damagePower = dmgPower;
spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
linear_friction = spawnArgs.GetFloat( "linear_friction" );
angular_friction = spawnArgs.GetFloat( "angular_friction" );
contact_friction = spawnArgs.GetFloat( "contact_friction" );
bounce = spawnArgs.GetFloat( "bounce" );
mass = spawnArgs.GetFloat( "mass" );
gravity = spawnArgs.GetFloat( "gravity" );
fuse = spawnArgs.GetFloat( "fuse" );
projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" );
projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" );
projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" );
if ( mass <= 0 ) {
gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
}
thrust *= mass;
thrust_end = SEC2MS( endthrust ) + gameLocal.time;
lightStartTime = 0;
lightEndTime = 0;
if ( health ) {
fl.takedamage = true;
}
gravVec = gameLocal.GetGravity();
gravVec.NormalizeFast();
Unbind();
// align z-axis of model with the direction
axis = dir.ToMat3();
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
contents = 0;
clipMask = MASK_SHOT_RENDERMODEL;
if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
contents |= CONTENTS_TRIGGER;
}
if ( !spawnArgs.GetBool( "no_contents" ) ) {
contents |= CONTENTS_PROJECTILE;
clipMask |= CONTENTS_PROJECTILE;
}
// don't do tracers on client, we don't know origin and direction
// if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
physicsObj.SetMass( mass );
physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
if ( contact_friction == 0.0f ) {
physicsObj.NoContact();
}
physicsObj.SetBouncyness( bounce );
physicsObj.SetGravity( gravVec * gravity );
physicsObj.SetContents( contents );
physicsObj.SetClipMask( clipMask );
physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity );
physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
physicsObj.SetOrigin( start );
physicsObj.SetAxis( axis );
thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) );
// Find and store the damage def only once- --- New
// place this line before checking the fuse- for beam weapons
damageDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_damage" ) );
if ( !gameLocal.isClient ) {
if ( fuse <= 0 ) {
// run physics for 1 second
RunPhysics();
PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
fuse -= timeSinceFire;
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
PostEventSec( &EV_Explode, fuse );
} else {
fuse -= timeSinceFire;
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
PostEventSec( &EV_Fizzle, fuse );
}
}
const idDict *damageDict = damageDef != NULL ? &damageDef->dict : &spawnArgs;
idStr sound;
if ( spawnArgs.GetBool( "tracers" ) ) {
if( !damageDict->GetString( "snd_tracer", "", sound ) ){
spawnArgs.GetString( "snd_tracer", "", sound );
}
#ifdef _DENTONMOD
if( spawnArgs.GetBool( "launchFromBarrel") ) {
idStr tracerModel;
if( spawnArgs.GetString( "beam_skin", NULL ) != NULL ) { // See if there's a beam_skin
tracerEffect = new dnBarrelLaunchedBeamTracer( this );
}
else if ( tracerEffect == NULL && spawnArgs.GetString( "model_tracer", "", tracerModel ) ){
SetModel( tracerModel );
}
}
#else
idStr tracerModel;
if ( spawnArgs.GetString( "model_tracer", "", tracerModel ) ){
SetModel( tracerModel );
}
#endif
} else {
if( !damageDict->GetString( "snd_fly", "", sound ) ){
spawnArgs.GetString( "snd_fly", "", sound );
}
}
StartSoundShader(declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
//StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
smokeFlyTime = 0;
const char *smokeName = spawnArgs.GetString( "smoke_fly" );
if ( *smokeName != '\0' ) {
smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
smokeFlyTime = gameLocal.time;
}
// used for the plasma bolts but may have other uses as well
if ( projectileFlags.randomShaderSpin ) {
float f = gameLocal.random.RandomFloat();
f *= 0.5f;
renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
}
UpdateVisuals();
state = LAUNCHED;
}
/*
================
idProjectile::Think
================
*/
void idProjectile::Think( void ) {
#ifdef _DENTONMOD
if( state != EXPLODED ) { // update & run physics until projectile is not exploded. -Clone JC Denton
#endif
if( thinkFlags & TH_THINK ) {
if ( thrust && ( gameLocal.time < thrust_end ) ) {
// evaluate force
thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust );
thruster.Evaluate( gameLocal.time );
}
}
// run physics until projectile is not exploded. -Clone JC Denton
RunPhysics();
#ifdef _DENTONMOD
}
if( tracerEffect ) {
tracerEffect->Think();
}
#endif
Present();
// add the particles
if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) {
idVec3 dir = -GetPhysics()->GetLinearVelocity();
dir.Normalize();
if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3() ) ) {
smokeFlyTime = gameLocal.time;
}
}
// add the light
if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
renderLight.axis = GetPhysics()->GetAxis();
if ( ( lightDefHandle != -1 ) ) {
if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) {
idVec3 color( 0, 0, 0 );
if ( gameLocal.time < lightEndTime ) {
float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
color.Lerp( lightColor, color, frac );
}
renderLight.shaderParms[SHADERPARM_RED] = color.x;
renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
}
gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
} else {
lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
}
}
}
/*
=================
idProjectile::Collide
=================
*/
bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
idEntity *ent;
idEntity *ignore;
const char *damageDefName;
idVec3 dir;
float push;
float damageScale;
/*idDict &damageArgs;
if ( damageDef != NULL ) {
damageArgs = damageDef->dict;
}
else {
damageArgs = spawnArgs;
}*/
if ( state == EXPLODED || state == FIZZLED ) {
return true;
}
// predict the explosion
if ( gameLocal.isClient ) {
if ( ClientPredictionCollide( this, damageDef!=NULL ? damageDef->dict: spawnArgs, collision, velocity, !spawnArgs.GetBool( "net_instanthit" ) ) ) {
Explode( collision, NULL );
return true;
}
return false;
}
#ifdef _DENTONMOD
if( tracerEffect!= NULL && tracerEffect->IsType( dnRailBeam::Type() ) ) {
static_cast<dnRailBeam *>( tracerEffect )->Create( collision.c.point );
}
#endif
// remove projectile when a 'noimpact' surface is hit
if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
PostEventMS( &EV_Remove, 0 );
common->DPrintf( "Projectile collision no impact\n" );
return true;
}
// get the entity the projectile collided with
ent = gameLocal.entities[ collision.c.entityNum ];
if ( ent == owner.GetEntity() ) {
assert( 0 );
return true;
}
// just get rid of the projectile when it hits a player in noclip
if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
PostEventMS( &EV_Remove, 0 );
return true;
}
// direction of projectile
dir = velocity;
dir.Normalize();
// projectiles can apply an additional impulse next to the rigid body physics impulse
if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) {
ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir );
}
// MP: projectiles open doors
if ( gameLocal.isMultiplayer && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
ent->ProcessEvent( &EV_Activate , this );
}
if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
if ( !projectileFlags.detonate_on_actor ) {
return false;
}
} else {
if ( !projectileFlags.detonate_on_world ) {
if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) {
float len = velocity.Length();
if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) );
StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL );
}
}
return false;
}
}
SetOrigin( collision.endpos );
SetAxis( collision.endAxis );
// unlink the clip model because we no longer need it
GetPhysics()->UnlinkClip();
// damageDefName = spawnArgs.GetString( "def_damage" );
if (damageDef != NULL) {
damageDefName = damageDef->GetName();
}
else {
damageDefName = NULL;
}
ignore = NULL;
// if the projectile causes a damage effect
// The Damage effects were previously applied after applying damage but we are applying before it
// because we wanted it so in idAnimatedEntity::AddLocalDamageEffect - By Clone JCD
if ( spawnArgs.GetBool( "impact_damage_effect" ) ) {
// if the hit entity has a special damage effect
#ifdef _DENTONMOD
StopSound( SND_CHANNEL_BODY, false ); // stop projectile flying sound upon impact, useful when is a looping sound.
// FIXME: need to restart this sound when projectile is bouncing off of surfaces
if ( (ent->IsType(idBrittleFracture::Type) || ent->IsType(idAnimatedEntity::Type) || ent->IsType(idMoveable::Type) || ent->IsType(idMoveableItem::Type)) && ent->spawnArgs.GetBool( "bleed", "1" ) ) { // This ensures that if an entity does not have bleed key defined, it will be considered true by default
projectileFlags.impact_fx_played = true;
ent->AddDamageEffect( collision, velocity, damageDefName, this );
#else
if ( ent->spawnArgs.GetBool( "bleed" ) ) {
ent->AddDamageEffect( collision, velocity, damageDefName );
#endif
} else {
AddDefaultDamageEffect( collision, velocity );
}
}
// if the hit entity takes damage
if ( ent->fl.takedamage ) {
if ( damagePower ) {
damageScale = damagePower;
} else {
damageScale = 1.0f;
}
// if the projectile owner is a player
if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
// if the projectile hit an actor
if ( ent->IsType( idActor::Type ) ) {
idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
player->AddProjectileHits( 1 );
damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
}
}
if ( damageDefName[0] != '\0' ) {
ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
if ( spawnArgs.GetBool ("ignore_splash_damage", "1") ) { // Added by Clone JCD for letting projectile def decide the ignore behaviour.
ignore = ent;
}
}
}
Explode( collision, ignore );
return true;
}
/*
=================
idProjectile::DefaultDamageEffect
=================
*/
void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) {
const char *decal, *sound, *typeName;
surfTypes_t materialType;
if ( collision.c.material != NULL ) {
materialType = collision.c.material->GetSurfaceType();
} else {
materialType = SURFTYPE_METAL;
}
// get material type name
typeName = gameLocal.sufaceTypeNames[ materialType ];
// play impact sound
sound = projectileDef.GetString( va( "snd_%s", typeName ) );
if ( *sound == '\0' ) {
sound = projectileDef.GetString( "snd_metal" );
}
if ( *sound == '\0' ) {
sound = projectileDef.GetString( "snd_impact" );
}
if ( *sound != '\0' ) {
soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
}
// project decal
// Note that decal info is taken from projectile def, as projectDecal and projectOverlay work differently.
decal =projectileDef.GetString( va( "mtr_wound_%s", typeName ) );
if ( g_debugDamage.GetBool() && collision.c.material != NULL ) { // If this check is not performed game may crash at ocassions
gameLocal.Printf("\n Collision Material Type: %s", typeName);
gameLocal.Printf("\n File: %s", collision.c.material->GetFileName ());
gameLocal.Printf("\n Collision material: %s", collision.c.material->ImageName());
}
if ( *decal == '\0' ) {
decal = projectileDef.GetString( "mtr_wound" ); // Default decal
}
if ( *decal != '\0' ) {
float size;
if ( !projectileDef.GetFloat( va( "size_wound_%s", typeName ), "6.0", size ) ) { // If Material Specific decal size not found, look for default size
size = projectileDef.GetFloat( "size_wound", "6.0" );
}
// decals with size 0 don't make sense and even cause trouble by triggering assertions because the winding ends up broken or sth
if(size > 0.0f)
gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, size, decal );
}
}
/*
=================
idProjectile::AddDefaultDamageEffect
=================
*/
void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) {
#ifdef _DENTONMOD
DefaultDamageEffect( this, damageDef!=NULL? damageDef->dict : spawnArgs, collision, velocity );
#else
DefaultDamageEffect( this, spawnArgs, collision, velocity );
#endif
if ( gameLocal.isServer && fl.networkSync ) {
idBitMsg msg;
byte msgBuf[MAX_EVENT_PARAM_SIZE];
int excludeClient;
if ( spawnArgs.GetBool( "net_instanthit" ) ) {
excludeClient = owner.GetEntityNum();
} else {
excludeClient = -1;
}
msg.Init( msgBuf, sizeof( msgBuf ) );
msg.BeginWriting();
msg.WriteFloat( collision.c.point[0] );
msg.WriteFloat( collision.c.point[1] );
msg.WriteFloat( collision.c.point[2] );
msg.WriteDir( collision.c.normal, 24 );
msg.WriteInt( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 );
msg.WriteFloat( velocity[0], 5, 10 );
msg.WriteFloat( velocity[1], 5, 10 );
msg.WriteFloat( velocity[2], 5, 10 );
ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excludeClient );
}
}
/*
================
idProjectile::Killed
================
*/
void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = GetPhysics()->GetAxis();
collision.endpos = GetPhysics()->GetOrigin();
collision.c.point = GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
Explode( collision, NULL );
physicsObj.ClearContacts();
physicsObj.PutToRest();
} else {
Fizzle();
}
}
/*
================
idProjectile::Fizzle
================
*/
void idProjectile::Fizzle( void ) {
if ( state == EXPLODED || state == FIZZLED ) {
return;
}
StopSound( SND_CHANNEL_BODY, false );
StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
// fizzle FX
const char *psystem = spawnArgs.GetString( "smoke_fuse" );
if ( psystem && *psystem ) {
//FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem );
}
// we need to work out how long the effects last and then remove them at that time
// for example, bullets have no real effects
if ( smokeFly && smokeFlyTime ) {
smokeFlyTime = 0;
}
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.GetClipModel()->Unlink();
physicsObj.PutToRest();
#ifdef _DENTONMOD
BecomeInactive(TH_PHYSICS); // This causes the physics not to update when it's fizzled
#endif
Hide();
FreeLightDef();
state = FIZZLED;
if ( gameLocal.isClient ) {
return;
}
CancelEvents( &EV_Fizzle );
PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
}
/*
================
idProjectile::Event_RadiusDamage
================
*/
void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
if ( splash_damage[0] != '\0' ) {
gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower );
}
}
/*
================
idProjectile::Event_RadiusDamage
================
*/
void idProjectile::Event_GetProjectileState( void ) {
idThread::ReturnInt( state );
}
/*
================
idProjectile::Explode
================
*/
void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
const char *fxname, *light_shader, *sndExplode;
float light_fadetime;
idVec3 normal;
int removeTime;
if ( state == EXPLODED || state == FIZZLED ) {
return;
}
// stop sound
StopSound( SND_CHANNEL_BODY2, false );
// play explode sound
switch ( ( int ) damagePower ) {
case 2: sndExplode = "snd_explode2"; break;
case 3: sndExplode = "snd_explode3"; break;
case 4: sndExplode = "snd_explode4"; break;
default: sndExplode = "snd_explode"; break;
}
StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL );
// we need to work out how long the effects last and then remove them at that time
// for example, bullets have no real effects
if ( smokeFly && smokeFlyTime ) {
smokeFlyTime = 0;
}
Hide();
FreeLightDef();
if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
GetPhysics()->SetAxis( normal.ToMat3() );
}
else { //Added by Clone JCD for setting proper direction of fx.
GetPhysics()->SetAxis( collision.c.normal.ToMat3() );
}
// GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal ); // Actual effect starts a little away object.
GetPhysics()->SetOrigin( collision.endpos + 0.5f * collision.c.normal );// By Clone JC Denton
// default remove time
removeTime = spawnArgs.GetInt( "remove_time", "1500" );
// change the model, usually to a PRT
fxname = NULL;
if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) {
fxname = g_testParticleName.GetString();
} else {
fxname = spawnArgs.GetString( "model_detonate" );
}
if (!projectileFlags.impact_fx_played) { // New flag added by Clone JCD,this wont play damage effects when model_detonate key is in place.
// which is esp. useful for exploding projectiles like rockets, grenades etc.
if ( !( fxname && *fxname ) ) {
// fx shall be played from def from now on------- By Clone JCD
if (damageDef != NULL) {
int type = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL;
if ( type == SURFTYPE_NONE ) {
type = SURFTYPE_METAL;
}
const char *materialType = gameLocal.sufaceTypeNames[ type ];
fxname = damageDef->dict.GetString( va( "smoke_wound_%s", materialType ) );
if ( *fxname == '\0' ) {
fxname = damageDef->dict.GetString( "smoke_wound" );
}
}
}
}
if ( fxname && *fxname ) {
#ifdef _DENTONMOD
if( tracerEffect!= NULL && tracerEffect->IsType( dnBeamTracer::Type() ) ){ // check whether we used beam model as tracer
memset( &renderEntity, 0, sizeof(renderEntity) );
}
#endif
SetModel( fxname );
renderEntity.shaderParms[SHADERPARM_RED] =
renderEntity.shaderParms[SHADERPARM_GREEN] =
renderEntity.shaderParms[SHADERPARM_BLUE] =
renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f;
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat();
Show();
removeTime = ( removeTime > 3000 ) ? removeTime : 3000;
}
// explosion light
light_shader = spawnArgs.GetString( "mtr_explode_light_shader" );
if ( *light_shader ) {
renderLight.shader = declManager->FindMaterial( light_shader, false );
renderLight.pointLight = true;
renderLight.lightRadius[0] =
renderLight.lightRadius[1] =
renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" );
spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor );
renderLight.shaderParms[SHADERPARM_RED] = lightColor.x;
renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y;
renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z;
renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f;
renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" );
lightStartTime = gameLocal.time;
lightEndTime = gameLocal.time + SEC2MS( light_fadetime );
BecomeActive( TH_THINK );
}
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.PutToRest();
#ifdef _DENTONMOD
if ( tracerEffect )
{
if ( tracerEffect->IsType( dnSpeedTracer::Type() ) && !static_cast<dnSpeedTracer *>(tracerEffect)->IsDead() ) {
BecomeActive( TH_UPDATEPARTICLES );
}
else if( !tracerEffect->IsType( dnRailBeam::Type() ) ) {
delete tracerEffect;
tracerEffect = NULL;
}
}
#endif
state = EXPLODED;
if ( gameLocal.isClient ) {
return;
}
// alert the ai
gameLocal.AlertAI( owner.GetEntity() );
// bind the projectile to the impact entity if necesary
if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
Bind( gameLocal.entities[collision.c.entityNum], true );
}
// splash damage
if ( !projectileFlags.noSplashDamage ) {
float delay = spawnArgs.GetFloat( "delay_splash" );
if ( delay ) {
if ( removeTime < delay * 1000 ) {
removeTime = ( delay + 0.10 ) * 1000;
}
PostEventSec( &EV_RadiusDamage, delay, ignore );
} else {
Event_RadiusDamage( ignore );
}
}
// spawn debris entities
int fxdebris = spawnArgs.GetInt( "debris_count" );
if ( fxdebris ) {
// const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false );
const idDict *debris = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_debris"), false );
if ( debris ) {
int amount = gameLocal.random.RandomInt( fxdebris );
for ( int i = 0; i < amount; i++ ) {
idEntity *ent;
idVec3 dir;
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, &ent, false );
if ( !ent || !ent->IsType( idDebris::Type ) ) {
gameLocal.Error( "'projectile_debris' is not an idDebris" );
}
idDebris *debris = static_cast<idDebris *>(ent);
debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
debris->Launch();
}
}
// debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false );
debris = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_shrapnel"), false );
if ( debris ) {
int amount = gameLocal.random.RandomInt( fxdebris );
for ( int i = 0; i < amount; i++ ) {
idEntity *ent;
idVec3 dir;
dir.x = gameLocal.random.CRandomFloat() * 8.0f;
dir.y = gameLocal.random.CRandomFloat() * 8.0f;
dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f;
dir.Normalize();
gameLocal.SpawnEntityDef( *debris, &ent, false );
if ( !ent || !ent->IsType( idDebris::Type ) ) {
gameLocal.Error( "'projectile_shrapnel' is not an idDebris" );
}
idDebris *debris = static_cast<idDebris *>(ent);
debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
debris->Launch();
}
}
}
CancelEvents( &EV_Explode );
PostEventMS( &EV_Remove, removeTime );
}
/*
================
idProjectile::GetVelocity
================
*/
idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
idVec3 velocity;
projectile->GetVector( "velocity", "0 0 0", velocity );
return velocity;
}
/*
================
idProjectile::GetGravity
================
*/
idVec3 idProjectile::GetGravity( const idDict *projectile ) {
float gravity;
gravity = projectile->GetFloat( "gravity" );
return idVec3( 0, 0, -gravity );
}
/*
================
idProjectile::Event_Explode
================
*/
void idProjectile::Event_Explode( void ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = GetPhysics()->GetAxis();
collision.endpos = GetPhysics()->GetOrigin();
collision.c.point = GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
AddDefaultDamageEffect( collision, collision.c.normal );
Explode( collision, NULL );
}
/*
================
idProjectile::Event_Fizzle
================
*/
void idProjectile::Event_Fizzle( void ) {
Fizzle();
}
/*
================
idProjectile::Event_Touch
================
*/
void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
if ( IsHidden() ) {
return;
}
if ( other != owner.GetEntity() ) {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = GetPhysics()->GetAxis();
collision.endpos = GetPhysics()->GetOrigin();
collision.c.point = GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
AddDefaultDamageEffect( collision, collision.c.normal );
Explode( collision, NULL );
}
}
/*
=================
idProjectile::ClientPredictionCollide
=================
*/
bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) {
idEntity *ent;
// remove projectile when a 'noimpact' surface is hit
if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
return false;
}
// get the entity the projectile collided with
ent = gameLocal.entities[ collision.c.entityNum ];
if ( ent == NULL ) {
return false;
}
// don't do anything if hitting a noclip player
if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
return false;
}
if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
if ( !projectileDef.GetBool( "detonate_on_actor" ) ) {
return false;
}
} else {
if ( !projectileDef.GetBool( "detonate_on_world" ) ) {
return false;
}
}
// if the projectile causes a damage effect
if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) {
// if the hit entity does not have a special damage effect
if ( !ent->spawnArgs.GetBool( "bleed" ) ) {
// predict damage effect
DefaultDamageEffect( soundEnt, projectileDef, collision, velocity );
}
}
return true;
}
/*
================
idProjectile::ClientPredictionThink
================
*/
void idProjectile::ClientPredictionThink( void ) {
if ( !renderEntity.hModel ) {
return;
}
Think();
}
/*
================
idProjectile::WriteToSnapshot
================
*/
void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteBits( owner.GetSpawnId(), 32 );
msg.WriteBits( state, 3 );
msg.WriteBits( fl.hidden, 1 );
if ( netSyncPhysics ) {
msg.WriteBits( 1, 1 );
physicsObj.WriteToSnapshot( msg );
} else {
msg.WriteBits( 0, 1 );
const idVec3 &origin = physicsObj.GetOrigin();
const idVec3 &velocity = physicsObj.GetLinearVelocity();
msg.WriteFloat( origin.x );
msg.WriteFloat( origin.y );
msg.WriteFloat( origin.z );
msg.WriteDeltaFloat( 0.0f, velocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, velocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
msg.WriteDeltaFloat( 0.0f, velocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
}
}
/*
================
idProjectile::ReadFromSnapshot
================
*/
void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) {
projectileState_t newState;
owner.SetSpawnId( msg.ReadBits( 32 ) );
newState = (projectileState_t) msg.ReadBits( 3 );
if ( msg.ReadBits( 1 ) ) {
Hide();
} else {
Show();
}
while( state != newState ) {
switch( state ) {
case SPAWNED: {
Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) );
break;
}
case CREATED: {
// the right origin and direction are required if you want bullet traces
Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin );
break;
}
case LAUNCHED: {
if ( newState == FIZZLED ) {
Fizzle();
} else {
trace_t collision;
memset( &collision, 0, sizeof( collision ) );
collision.endAxis = GetPhysics()->GetAxis();
collision.endpos = GetPhysics()->GetOrigin();
collision.c.point = GetPhysics()->GetOrigin();
collision.c.normal.Set( 0, 0, 1 );
Explode( collision, NULL );
}
break;
}
case FIZZLED:
case EXPLODED: {
StopSound( SND_CHANNEL_BODY2, false );
gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
state = SPAWNED;
break;
}
}
}
if ( msg.ReadBits( 1 ) ) {
physicsObj.ReadFromSnapshot( msg );
} else {
idVec3 origin;
idVec3 velocity;
idVec3 tmp;
idMat3 axis;
origin.x = msg.ReadFloat();
origin.y = msg.ReadFloat();
origin.z = msg.ReadFloat();
velocity.x = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
velocity.y = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
velocity.z = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS );
physicsObj.SetOrigin( origin );
physicsObj.SetLinearVelocity( velocity );
// align z-axis of model with the direction
velocity.NormalizeFast();
axis = velocity.ToMat3();
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
physicsObj.SetAxis( axis );
}
if ( msg.HasChanged() ) {
UpdateVisuals();
}
}
/*
================
idProjectile::ClientReceiveEvent
================
*/
bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
trace_t collision;
idVec3 velocity;
switch( event ) {
case EVENT_DAMAGE_EFFECT: {
memset( &collision, 0, sizeof( collision ) );
collision.c.point[0] = msg.ReadFloat();
collision.c.point[1] = msg.ReadFloat();
collision.c.point[2] = msg.ReadFloat();
collision.c.normal = msg.ReadDir( 24 );
int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadInt() );
collision.c.material = ( index != -1 ) ? static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL;
velocity[0] = msg.ReadFloat( 5, 10 );
velocity[1] = msg.ReadFloat( 5, 10 );
velocity[2] = msg.ReadFloat( 5, 10 );
// DefaultDamageEffect( this, spawnArgs, collision, velocity );
DefaultDamageEffect( this, damageDef!=NULL? damageDef->dict: spawnArgs, collision, velocity ); // new
return true;
}
default:
break;
}
return idEntity::ClientReceiveEvent( event, time, msg );
}
/*
===============================================================================
idGuidedProjectile
===============================================================================
*/
CLASS_DECLARATION( idProjectile, idGuidedProjectile )
END_CLASS
/*
================
idGuidedProjectile::idGuidedProjectile
================
*/
idGuidedProjectile::idGuidedProjectile( void ) {
enemy = NULL;
speed = 0.0f;
turn_max = 0.0f;
clamp_dist = 0.0f;
rndScale = ang_zero;
rndAng = ang_zero;
rndUpdateTime = 0;
angles = ang_zero;
burstMode = false;
burstDist = 0;
burstVelocity = 0.0f;
unGuided = false;
}
/*
=================
idGuidedProjectile::~idGuidedProjectile
=================
*/
idGuidedProjectile::~idGuidedProjectile( void ) {
}
/*
================
idGuidedProjectile::Spawn
================
*/
void idGuidedProjectile::Spawn( void ) {
}
/*
================
idGuidedProjectile::Save
================
*/
void idGuidedProjectile::Save( idSaveGame *savefile ) const {
enemy.Save( savefile );
savefile->WriteFloat( speed );
savefile->WriteAngles( rndScale );
savefile->WriteAngles( rndAng );
savefile->WriteInt( rndUpdateTime );
savefile->WriteFloat( turn_max );
savefile->WriteFloat( clamp_dist );
savefile->WriteAngles( angles );
savefile->WriteBool( burstMode );
savefile->WriteBool( unGuided );
savefile->WriteFloat( burstDist );
savefile->WriteFloat( burstVelocity );
}
/*
================
idGuidedProjectile::Restore
================
*/
void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
enemy.Restore( savefile );
savefile->ReadFloat( speed );
savefile->ReadAngles( rndScale );
savefile->ReadAngles( rndAng );
savefile->ReadInt( rndUpdateTime );
savefile->ReadFloat( turn_max );
savefile->ReadFloat( clamp_dist );
savefile->ReadAngles( angles );
savefile->ReadBool( burstMode );
savefile->ReadBool( unGuided );
savefile->ReadFloat( burstDist );
savefile->ReadFloat( burstVelocity );
}
/*
================
idGuidedProjectile::GetSeekPos
================
*/
void idGuidedProjectile::GetSeekPos( idVec3 &out ) {
idEntity *enemyEnt = enemy.GetEntity();
if ( enemyEnt ) {
if ( enemyEnt->IsType( idActor::Type ) ) {
out = static_cast<idActor *>(enemyEnt)->GetEyePosition();
out.z -= 12.0f;
} else {
out = enemyEnt->GetPhysics()->GetOrigin();
}
} else {
out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f;
}
}
/*
================
idGuidedProjectile::Think
================
*/
void idGuidedProjectile::Think( void ) {
idVec3 dir;
idVec3 seekPos;
idVec3 velocity;
idVec3 nose;
idVec3 tmp;
idMat3 axis;
idAngles dirAng;
idAngles diff;
float dist;
float frac;
int i;
if ( state == LAUNCHED && !unGuided ) {
GetSeekPos( seekPos );
if ( rndUpdateTime < gameLocal.time ) {
rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat();
rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat();
rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat();
rndUpdateTime = gameLocal.time + 200;
}
nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
dir = seekPos - nose;
dist = dir.Normalize();
dirAng = dir.ToAngles();
// make it more accurate as it gets closer
frac = dist / clamp_dist;
if ( frac > 1.0f ) {
frac = 1.0f;
}
diff = dirAng - angles + rndAng * frac;
// clamp the to the max turn rate
diff.Normalize180();
for( i = 0; i < 3; i++ ) {
if ( diff[ i ] > turn_max ) {
diff[ i ] = turn_max;
} else if ( diff[ i ] < -turn_max ) {
diff[ i ] = -turn_max;
}
}
angles += diff;
// make the visual model always points the dir we're traveling
dir = angles.ToForward();
velocity = dir * speed;
if ( burstMode && dist < burstDist ) {
unGuided = true;
velocity *= burstVelocity;
}
physicsObj.SetLinearVelocity( velocity );
// align z-axis of model with the direction
axis = dir.ToMat3();
tmp = axis[2];
axis[2] = axis[0];
axis[0] = -tmp;
GetPhysics()->SetAxis( axis );
}
idProjectile::Think();
}
/*
=================
idGuidedProjectile::Launch
=================
*/
void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
if ( owner.GetEntity() ) {
if ( owner.GetEntity()->IsType( idAI::Type ) ) {
enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
} else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
trace_t tr;
idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
idVec3 start = player->GetEyePosition();
idVec3 end = start + player->viewAxis[0] * 1000.0f;
gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
if ( tr.fraction < 1.0f ) {
enemy = gameLocal.GetTraceEntity( tr );
}
// ignore actors on the player's team
if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
enemy = player->EnemyWithMostHealth();
}
}
}
const idVec3 &vel = physicsObj.GetLinearVelocity();
angles = vel.ToAngles();
speed = vel.Length();
rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / ( float )USERCMD_HZ;
clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
burstMode = spawnArgs.GetBool( "burstMode" );
unGuided = false;
burstDist = spawnArgs.GetFloat( "burstDist", "64" );
burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
UpdateVisuals();
}
/*
===============================================================================
idSoulCubeMissile
===============================================================================
*/
CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile )
END_CLASS
/*
================
idSoulCubeMissile::Spawn( void )
================
*/
void idSoulCubeMissile::Spawn( void ) {
startingVelocity.Zero();
endingVelocity.Zero();
accelTime = 0.0f;
launchTime = 0;
killPhase = false;
returnPhase = false;
smokeKillTime = 0;
smokeKill = NULL;
}
/*
=================
idSoulCubeMissile::~idSoulCubeMissile
=================
*/
idSoulCubeMissile::~idSoulCubeMissile() {
}
/*
================
idSoulCubeMissile::Save
================
*/
void idSoulCubeMissile::Save( idSaveGame *savefile ) const {
savefile->WriteVec3( startingVelocity );
savefile->WriteVec3( endingVelocity );
savefile->WriteFloat( accelTime );
savefile->WriteInt( launchTime );
savefile->WriteBool( killPhase );
savefile->WriteBool( returnPhase );
savefile->WriteVec3( destOrg);
savefile->WriteInt( orbitTime );
savefile->WriteVec3( orbitOrg );
savefile->WriteInt( smokeKillTime );
savefile->WriteParticle( smokeKill );
}
/*
================
idSoulCubeMissile::Restore
================
*/
void idSoulCubeMissile::Restore( idRestoreGame *savefile ) {
savefile->ReadVec3( startingVelocity );
savefile->ReadVec3( endingVelocity );
savefile->ReadFloat( accelTime );
savefile->ReadInt( launchTime );
savefile->ReadBool( killPhase );
savefile->ReadBool( returnPhase );
savefile->ReadVec3( destOrg);
savefile->ReadInt( orbitTime );
savefile->ReadVec3( orbitOrg );
savefile->ReadInt( smokeKillTime );
savefile->ReadParticle( smokeKill );
}
/*
================
idSoulCubeMissile::KillTarget
================
*/
void idSoulCubeMissile::KillTarget( const idVec3 &dir ) {
idEntity *ownerEnt;
const char *smokeName;
idActor *act;
ReturnToOwner();
if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) {
act = static_cast<idActor*>( enemy.GetEntity() );
killPhase = true;
orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter();
orbitTime = gameLocal.time;
smokeKillTime = 0;
smokeName = spawnArgs.GetString( "smoke_kill" );
if ( *smokeName != '\0' ) {
smokeKill = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
smokeKillTime = gameLocal.time;
}
ownerEnt = owner.GetEntity();
if ( ( act->health > 0 ) && ownerEnt && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) {
static_cast<idPlayer *>( ownerEnt )->GiveHealthPool( act->health );
}
act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT );
act->GetAFPhysics()->SetTimeScale( 0.25 );
StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
}
}
/*
================
idSoulCubeMissile::Think
================
*/
void idSoulCubeMissile::Think( void ) {
float pct;
idVec3 seekPos;
idEntity *ownerEnt;
if ( state == LAUNCHED ) {
if ( killPhase ) {
// orbit the mob, cascading down
if ( gameLocal.time < orbitTime + 1500 ) {
if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity ) ) {
smokeKillTime = gameLocal.time;
}
}
} else {
if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) {
pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 );
speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length();
}
}
idGuidedProjectile::Think();
GetSeekPos( seekPos );
if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) {
if ( returnPhase ) {
StopSound( SND_CHANNEL_ANY, false );
StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL );
Hide();
PostEventSec( &EV_Remove, 2.0f );
ownerEnt = owner.GetEntity();
if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( NULL );
}
state = FIZZLED;
} else if ( !killPhase ){
KillTarget( physicsObj.GetAxis()[0] );
}
}
}
}
/*
================
idSoulCubeMissile::GetSeekPos
================
*/
void idSoulCubeMissile::GetSeekPos( idVec3 &out ) {
if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) {
idActor *act = static_cast<idActor*>( owner.GetEntity() );
out = act->GetEyePosition();
return;
}
if ( destOrg != vec3_zero ) {
out = destOrg;
return;
}
idGuidedProjectile::GetSeekPos( out );
}
/*
================
idSoulCubeMissile::Event_ReturnToOwner
================
*/
void idSoulCubeMissile::ReturnToOwner() {
speed *= 0.65f;
killPhase = false;
returnPhase = true;
smokeFlyTime = 0;
}
/*
=================
idSoulCubeMissile::Launch
=================
*/
void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
idVec3 newStart;
idVec3 offs;
idEntity *ownerEnt;
// push it out a little
newStart = start + dir * spawnArgs.GetFloat( "launchDist" );
offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" );
newStart += offs;
idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) {
destOrg = start + dir * 256.0f;
} else {
destOrg.Zero();
}
physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate
startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" );
endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" );
accelTime = spawnArgs.GetFloat( "accelTime", "5" );
physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] );
launchTime = gameLocal.time;
killPhase = false;
UpdateVisuals();
ownerEnt = owner.GetEntity();
if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( this );
}
}
/*
===============================================================================
idBFGProjectile
===============================================================================
*/
const idEventDef EV_RemoveBeams( "<removeBeams>", NULL );
CLASS_DECLARATION( idProjectile, idBFGProjectile )
EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams )
END_CLASS
/*
=================
idBFGProjectile::idBFGProjectile
=================
*/
idBFGProjectile::idBFGProjectile() {
memset( &secondModel, 0, sizeof( secondModel ) );
secondModelDefHandle = -1;
nextDamageTime = 0;
}
/*
=================
idBFGProjectile::~idBFGProjectile
=================
*/
idBFGProjectile::~idBFGProjectile() {
FreeBeams();
if ( secondModelDefHandle >= 0 ) {
gameRenderWorld->FreeEntityDef( secondModelDefHandle );
secondModelDefHandle = -1;
}
}
/*
================
idBFGProjectile::Spawn
================
*/
void idBFGProjectile::Spawn( void ) {
beamTargets.Clear();
memset( &secondModel, 0, sizeof( secondModel ) );
secondModelDefHandle = -1;
const char *temp = spawnArgs.GetString( "model_two" );
if ( temp && *temp ) {
secondModel.hModel = renderModelManager->FindModel( temp );
secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
secondModel.shaderParms[ SHADERPARM_RED ] =
secondModel.shaderParms[ SHADERPARM_GREEN ] =
secondModel.shaderParms[ SHADERPARM_BLUE ] =
secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
secondModel.noSelfShadow = true;
secondModel.noShadow = true;
}
nextDamageTime = 0;
damageFreq = NULL;
}
/*
================
idBFGProjectile::Save
================
*/
void idBFGProjectile::Save( idSaveGame *savefile ) const {
int i;
savefile->WriteInt( beamTargets.Num() );
for ( i = 0; i < beamTargets.Num(); i++ ) {
beamTargets[i].target.Save( savefile );
savefile->WriteRenderEntity( beamTargets[i].renderEntity );
savefile->WriteInt( beamTargets[i].modelDefHandle );
}
savefile->WriteRenderEntity( secondModel );
savefile->WriteInt( secondModelDefHandle );
savefile->WriteInt( nextDamageTime );
savefile->WriteString( damageFreq );
}
/*
================
idBFGProjectile::Restore
================
*/
void idBFGProjectile::Restore( idRestoreGame *savefile ) {
int i, num;
savefile->ReadInt( num );
beamTargets.SetNum( num );
for ( i = 0; i < num; i++ ) {
beamTargets[i].target.Restore( savefile );
savefile->ReadRenderEntity( beamTargets[i].renderEntity );
savefile->ReadInt( beamTargets[i].modelDefHandle );
if ( beamTargets[i].modelDefHandle >= 0 ) {
beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity );
}
}
savefile->ReadRenderEntity( secondModel );
savefile->ReadInt( secondModelDefHandle );
savefile->ReadInt( nextDamageTime );
savefile->ReadString( damageFreq );
if ( secondModelDefHandle >= 0 ) {
secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
}
}
/*
=================
idBFGProjectile::FreeBeams
=================
*/
void idBFGProjectile::FreeBeams() {
for ( int i = 0; i < beamTargets.Num(); i++ ) {
if ( beamTargets[i].modelDefHandle >= 0 ) {
gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle );
beamTargets[i].modelDefHandle = -1;
}
}
idPlayer *player = gameLocal.GetLocalPlayer();
if ( player ) {
player->playerView.EnableBFGVision( false );
}
}
/*
================
idBFGProjectile::Think
================
*/
void idBFGProjectile::Think( void ) {
if ( state == LAUNCHED ) {
// update beam targets
for ( int i = 0; i < beamTargets.Num(); i++ ) {
if ( beamTargets[i].target.GetEntity() == NULL ) {
continue;
}
idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast<idPlayer*>( beamTargets[i].target.GetEntity() ) : NULL;
idVec3 org = beamTargets[i].target.GetEntity()->GetPhysics()->GetAbsBounds().GetCenter();
beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin();
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x;
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y;
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z;
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
if ( gameLocal.time > nextDamageTime ) {
bool bfgVision = true;
if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) {
org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
org.Normalize();
beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT );
} else {
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] =
beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f;
bfgVision = false;
}
if ( player ) {
player->playerView.EnableBFGVision( bfgVision );
}
nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
}
gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity );
}
if ( secondModelDefHandle >= 0 ) {
secondModel.origin = GetPhysics()->GetOrigin();
gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel );
}
idAngles ang;
ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f;
ang.yaw = ang.pitch;
ang.roll = 0.0f;
SetAngles( ang );
ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f;
ang.yaw = ang.pitch;
ang.roll = 0.0f;
secondModel.axis = ang.ToMat3();
UpdateVisuals();
}
idProjectile::Think();
}
/*
=================
idBFGProjectile::Launch
=================
*/
void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) {
idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower );
// dmgPower * radius is the target acquisition area
// acquisition should make sure that monsters are not dormant
// which will cut down on hitting monsters not actively fighting
// but saves on the traces making sure they are visible
// damage is not applied until the projectile explodes
idEntity * ent;
idEntity * entityList[ MAX_GENTITIES ];
int numListedEntities;
idBounds bounds;
idVec3 damagePoint;
float radius;
spawnArgs.GetFloat( "damageRadius", "512", radius );
bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius );
float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" );
const char *skin = spawnArgs.GetString( "skin_beam" );
memset( &secondModel, 0, sizeof( secondModel ) );
secondModelDefHandle = -1;
const char *temp = spawnArgs.GetString( "model_two" );
if ( temp && *temp ) {
secondModel.hModel = renderModelManager->FindModel( temp );
secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
secondModel.shaderParms[ SHADERPARM_RED ] =
secondModel.shaderParms[ SHADERPARM_GREEN ] =
secondModel.shaderParms[ SHADERPARM_BLUE ] =
secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
secondModel.noSelfShadow = true;
secondModel.noShadow = true;
secondModel.origin = GetPhysics()->GetOrigin();
secondModel.axis = GetPhysics()->GetAxis();
secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
}
idVec3 delta( 15.0f, 15.0f, 15.0f );
//physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero );
// get all entities touching the bounds
numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES );
for ( int e = 0; e < numListedEntities; e++ ) {
ent = entityList[ e ];
assert( ent );
if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) {
continue;
}
if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) {
continue;
}
if ( ent->IsType( idPlayer::Type ) ) {
idPlayer *player = static_cast<idPlayer*>( ent );
player->playerView.EnableBFGVision( true );
}
beamTarget_t bt;
memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
bt.renderEntity.origin = GetPhysics()->GetOrigin();
bt.renderEntity.axis = GetPhysics()->GetAxis();
bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
bt.renderEntity.callback = NULL;
bt.renderEntity.numJoints = 0;
bt.renderEntity.joints = NULL;
bt.renderEntity.bounds.Clear();
bt.renderEntity.customSkin = declManager->FindSkin( skin );
bt.target = ent;
bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
beamTargets.Append( bt );
}
if ( numListedEntities ) {
StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL );
}
damageFreq = spawnArgs.GetString( "def_damageFreq" );
nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
UpdateVisuals();
}
/*
================
idProjectile::Event_RemoveBeams
================
*/
void idBFGProjectile::Event_RemoveBeams() {
FreeBeams();
UpdateVisuals();
}
/*
================
idProjectile::Explode
================
*/
void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
int i;
idVec3 dmgPoint;
idVec3 dir;
float beamWidth;
float damageScale;
const char *damage;
idPlayer * player;
idEntity * ownerEnt;
ownerEnt = owner.GetEntity();
if ( ownerEnt && ownerEnt->IsType( idPlayer::Type ) ) {
player = static_cast< idPlayer * >( ownerEnt );
} else {
player = NULL;
}
beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" );
damage = spawnArgs.GetString( "def_damage" );
for ( i = 0; i < beamTargets.Num(); i++ ) {
if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) {
continue;
}
if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) {
continue;
}
beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth;
// if the hit entity takes damage
if ( damagePower ) {
damageScale = damagePower;
} else {
damageScale = 1.0f;
}
// if the projectile owner is a player
if ( player ) {
// if the projectile hit an actor
if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) {
player->SetLastHitTime( gameLocal.time );
player->AddProjectileHits( 1 );
damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
}
}
if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) {
dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
dir.Normalize();
beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT );
}
}
if ( secondModelDefHandle >= 0 ) {
gameRenderWorld->FreeEntityDef( secondModelDefHandle );
secondModelDefHandle = -1;
}
/* if ( ignore == NULL ) { // This causes trouble like splash damage not working at all
projectileFlags.noSplashDamage = true;
}*/
if ( !gameLocal.isClient ) {
if ( ignore != NULL ) {
PostEventMS( &EV_RemoveBeams, 750 );
} else {
PostEventMS( &EV_RemoveBeams, 0 );
}
}
return idProjectile::Explode( collision, ignore );
}
/*
===============================================================================
idDebris
===============================================================================
*/
CLASS_DECLARATION( idEntity, idDebris )
EVENT( EV_Explode, idDebris::Event_Explode )
EVENT( EV_Fizzle, idDebris::Event_Fizzle )
END_CLASS
/*
================
idDebris::Spawn
================
*/
void idDebris::Spawn( void ) {
owner = NULL;
smokeFly = NULL;
smokeFlyTime = 0;
nextSoundTime = 0; // BY Clone JCD
soundTimeDifference = 0; //
}
/*
================
idDebris::Create
================
*/
void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) {
Unbind();
GetPhysics()->SetOrigin( start );
GetPhysics()->SetAxis( axis );
GetPhysics()->SetContents( 0 );
this->owner = owner;
smokeFly = NULL;
smokeFlyTime = 0;
nextSoundTime = 0; // BY Clone JCD
soundTimeDifference = 0; //
sndBounce = NULL;
UpdateVisuals();
}
/*
=================
idDebris::idDebris
=================
*/
idDebris::idDebris( void ) {
owner = NULL;
smokeFly = NULL;
smokeFlyTime = 0;
sndBounce = NULL;
nextSoundTime = 0; // BY Clone JCD
soundTimeDifference = 0; //
}
/*
=================
idDebris::~idDebris
=================
*/
idDebris::~idDebris( void ) {
}
/*
=================
idDebris::Save
=================
*/
void idDebris::Save( idSaveGame *savefile ) const {
owner.Save( savefile );
savefile->WriteStaticObject( physicsObj );
savefile->WriteParticle( smokeFly );
savefile->WriteInt( smokeFlyTime );
// savefile->WriteInt( nextSoundTime ); // No need to store this value, BY Clone JCD
savefile->WriteInt( soundTimeDifference ); //
savefile->WriteSoundShader( sndBounce );
}
/*
=================
idDebris::Restore
=================
*/
void idDebris::Restore( idRestoreGame *savefile ) {
owner.Restore( savefile );
savefile->ReadStaticObject( physicsObj );
RestorePhysics( &physicsObj );
savefile->ReadParticle( smokeFly );
savefile->ReadInt( smokeFlyTime );
//savefile->ReadInt( nextSoundTime ); // No need to store this value, BY Clone JCD
savefile->ReadInt( soundTimeDifference ); //
savefile->ReadSoundShader( sndBounce );
}
/*
=================
idDebris::Launch
=================
*/
void idDebris::Launch( void ) {
float fuse;
idVec3 velocity;
#ifdef _DENTONMOD
idVec3 angular_velocity_vect;
#else
idAngles angular_velocity;
#endif
float linear_friction;
float angular_friction;
float contact_friction;
float bounce;
float mass;
float gravity;
idVec3 gravVec;
bool randomVelocity;
idMat3 axis;
renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
spawnArgs.GetVector( "velocity", "0 0 0", velocity );
#ifdef _DENTONMOD
angular_velocity_vect = spawnArgs.GetAngles( "angular_velocity", "0 0 0").ToAngularVelocity();
#else
spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
#endif
linear_friction = spawnArgs.GetFloat( "linear_friction" );
angular_friction = spawnArgs.GetFloat( "angular_friction" );
contact_friction = spawnArgs.GetFloat( "contact_friction" );
bounce = spawnArgs.GetFloat( "bounce" );
mass = spawnArgs.GetFloat( "mass" );
gravity = spawnArgs.GetFloat( "gravity" );
fuse = spawnArgs.GetFloat( "fuse" );
randomVelocity = spawnArgs.GetBool ( "random_velocity" );
continuousSmoke = spawnArgs.GetBool ( "smoke_continuous" );
if ( mass <= 0 ) {
gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
}
if ( randomVelocity ) {
#ifdef _DENTONMOD
float rand = spawnArgs.GetFloat("linear_velocity_rand", "0.35");
// sets velocity randomly between ((1-rand)*100)% and ((1+rand)*100)%
// e.g.1: if rand = 0.2, velocity will be randomly set between 80% and 120%
// e.g.2: if rand = 0.3, velocity will be randomly set between 70% and 130%
// and so on.
velocity.x *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
velocity.y *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
velocity.z *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
// do not perform following calculations unless there's key in decl that says so.
if( spawnArgs.GetFloat( "angular_velocity_rand", "0.0", rand) && rand > 0.0f ) {
angular_velocity_vect.x *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
angular_velocity_vect.y *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
angular_velocity_vect.z *= gameLocal.random.RandomFloat()*rand*2.0 + 1.0 - rand;
}
#else
velocity.x *= gameLocal.random.RandomFloat() + 0.5f;
velocity.y *= gameLocal.random.RandomFloat() + 0.5f;
velocity.z *= gameLocal.random.RandomFloat() + 0.5f;
#endif
}
if ( health ) {
fl.takedamage = true;
}
gravVec = gameLocal.GetGravity();
gravVec.NormalizeFast();
axis = GetPhysics()->GetAxis();
Unbind();
physicsObj.SetSelf( this );
// check if a clip model is set
const char *clipModelName;
idTraceModel trm;
spawnArgs.GetString( "clipmodel", "", &clipModelName );
if ( !clipModelName[0] ) {
clipModelName = spawnArgs.GetString( "model" ); // use the visual model
}
// load the trace model
if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
// default to a box
physicsObj.SetClipBox( renderEntity.bounds, 1.0f );
} else {
physicsObj.SetClipModel( new idClipModel( trm ), 1.0f );
}
physicsObj.GetClipModel()->SetOwner( owner.GetEntity() );
physicsObj.SetMass( mass );
physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
if ( contact_friction == 0.0f ) {
physicsObj.NoContact();
}
physicsObj.SetBouncyness( bounce );
physicsObj.SetGravity( gravVec * gravity );
physicsObj.SetContents( 0 );
physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
#ifdef _DENTONMOD
// Make sure that the linear velocity is added with
// owner's linear velocity for more accurate physics simulation.
idEntity *ownerEnt = owner.GetEntity();
if( ownerEnt != NULL ) {
physicsObj.SetLinearVelocity( (axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ]) + ownerEnt->GetPhysics()->GetLinearVelocity());
}
else {
physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] );
}
physicsObj.SetAngularVelocity( angular_velocity_vect * axis );
#else
physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] );
physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
#endif
physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
physicsObj.SetAxis( axis );
SetPhysics( &physicsObj );
if ( !gameLocal.isClient ) {
if ( fuse <= 0 ) {
// run physics for 1 second
RunPhysics();
PostEventMS( &EV_Remove, 0 );
} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
RunPhysics();
PostEventSec( &EV_Explode, fuse );
} else {
if ( fuse < 0.0f ) {
fuse = 0.0f;
}
PostEventSec( &EV_Fizzle, fuse );
}
}
StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
smokeFly = NULL;
smokeFlyTime = 0;
const char *smokeName = spawnArgs.GetString( "smoke_fly" );
if ( *smokeName != '\0' ) {
smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
smokeFlyTime = gameLocal.time;
gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
const char *sndName = spawnArgs.GetString( "snd_bounce" );
nextSoundTime = 0; // BY Clone JCD
soundTimeDifference = spawnArgs.GetInt ( "next_sound_time" ); //
if ( *sndName != '\0' ) {
sndBounce = declManager->FindSound( sndName );
}
UpdateVisuals();
}
/*
================
idDebris::Think
================
*/
void idDebris::Think( void ) {
// run physics
RunPhysics();
Present();
if ( smokeFly && smokeFlyTime && !IsHidden()) { // Emit particles only when visible
idVec3 dir = -GetPhysics()->GetLinearVelocity();
dir.Normalize();
if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3() ) ) {
if( continuousSmoke ) {
smokeFlyTime = gameLocal.time; // Emit particles continuously - Clone JC Denton
}
else {
smokeFlyTime = 0;
}
}
}
}
/*
================
idDebris::Killed
================
*/
void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
Explode();
} else {
Fizzle();
}
}
/*
=================
idDebris::Collide
=================
*/
bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) {
if (sndBounce != NULL ){
if ( !soundTimeDifference ) {
StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL );
sndBounce = NULL;
return false;
}
if ( gameLocal.time > nextSoundTime ){
float v = -( velocity * collision.c.normal );
if ( v > BOUNCE_SOUND_MIN_VELOCITY ) {
float 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 ( StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL ) )
SetSoundVolume( f );
}
else {
float f = ( 0.5f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) );
if ( StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL ) )
SetSoundVolume( f );
sndBounce = NULL;
return false;
}
nextSoundTime = gameLocal.time + soundTimeDifference;
}
}
return false;
}
/*
================
idDebris::Fizzle
================
*/
void idDebris::Fizzle( void ) {
if ( IsHidden() ) {
// already exploded
return;
}
StopSound( SND_CHANNEL_ANY, false );
StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
// fizzle FX
const char *smokeName = spawnArgs.GetString( "smoke_fuse" );
if ( *smokeName != '\0' ) {
smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
smokeFlyTime = gameLocal.time;
gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.PutToRest();
#ifdef _DENTONMOD
BecomeInactive(TH_PHYSICS); // This causes the physics not to update after explosion
#endif
Hide();
if ( gameLocal.isClient ) {
return;
}
CancelEvents( &EV_Fizzle );
PostEventMS( &EV_Remove, 0 );
}
/*
================
idDebris::Explode
================
*/
void idDebris::Explode( void ) {
if ( IsHidden() ) {
// already exploded
return;
}
StopSound( SND_CHANNEL_ANY, false );
StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
Hide();
// these must not be "live forever" particle systems
smokeFly = NULL;
smokeFlyTime = 0;
const char *smokeName = spawnArgs.GetString( "smoke_detonate" );
if ( *smokeName != '\0' ) {
smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
smokeFlyTime = gameLocal.time;
gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
fl.takedamage = false;
physicsObj.SetContents( 0 );
physicsObj.PutToRest();
#ifdef _DENTONMOD
BecomeInactive(TH_PHYSICS); // This causes the physics not to update after explosion
#endif
CancelEvents( &EV_Explode );
PostEventMS( &EV_Remove, 0 );
}
/*
================
idDebris::Event_Explode
================
*/
void idDebris::Event_Explode( void ) {
Explode();
}
/*
================
idDebris::Event_Fizzle
================
*/
void idDebris::Event_Fizzle( void ) {
Fizzle();
}