| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 |
- /*
- ** Command & Conquer Renegade(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program 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.
- **
- ** This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
- */
- /***********************************************************************************************
- *** Confidential - Westwood Studios ***
- ***********************************************************************************************
- * *
- * Project Name : Commando *
- * *
- * $Archive:: /Commando/Code/Combat/bullet.cpp $*
- * *
- * $Author:: Byon_g $*
- * *
- * $Modtime:: 3/13/02 5:22p $*
- * *
- * $Revision:: 164 $*
- * *
- *---------------------------------------------------------------------------------------------*
- * Functions: *
- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
- #include "bullet.h"
- #include "projectile.h"
- #include "combat.h" // for collision groups (should be damage.h?)
- #include "pscene.h"
- #include "weaponmanager.h"
- #include "assets.h"
- #include "debug.h"
- #include "armedgameobj.h"
- #include "crandom.h"
- #include "surfaceeffects.h"
- #include "mesh.h"
- #include "wwaudio.h"
- #include "explosion.h"
- #include "building.h"
- #include "persistfactory.h"
- #include "wwphysids.h"
- #include "damageablestaticphys.h"
- #include "wwprofile.h"
- #include "part_emt.h"
- #include "realcrc.h"
- #include "soldier.h"
- #include "segline.h"
- #include "timeddecophys.h"
- #include "simplegameobj.h"
- #include "c4.h"
- /*
- ** Constants for this module
- */
- const float _INSTANT_BULLET_THRESHHOLD = 400; // If speed is >= _INSTANT_BULLET_THRESHHOLD m/s, treat it an an instant bullet
- /**
- ** BeamEffectManagerClass
- ** This is a static object that simply manages the active beam effect. Its job is to cache un-used beams
- ** so that we aren't allocating them at run time and to add new beam effects to the scene when needed.
- */
- class BeamEffectManagerClass : public CombatPhysObserverClass
- {
- public:
- void Init( void ) {}
- void Shutdown( void );
- /*
- ** Create a beam effect and put it in the scene. Whenever possible, this function
- ** tries to recycle a previously created beam effect
- */
- void Create_Beam_Effect(const AmmoDefinitionClass * ammo_def,const Vector3 & p0,const Vector3 & p1);
- /*
- ** PhysObserver Interface - whenever a beam expires, we put it into a list so we can
- ** recycle it next time we need a beam effect
- */
- virtual void Object_Removed_From_Scene(PhysClass * observed_obj);
- private:
- void Internal_Get_New_Beam(SegmentedLineClass ** beam_model,TimedDecorationPhysClass ** beam_wrapper);
- RefMultiListClass<TimedDecorationPhysClass> UnusedBeamList;
- };
- static BeamEffectManagerClass _TheBeamEffectManager;
- void BeamEffectManagerClass::Shutdown( void )
- {
- while (UnusedBeamList.Peek_Head()) {
- UnusedBeamList.Release_Head();
- }
- }
- void BeamEffectManagerClass::Create_Beam_Effect(const AmmoDefinitionClass * ammo_def,const Vector3 & p0,const Vector3 & p1)
- {
- SegmentedLineClass * beam_model = NULL;
- TimedDecorationPhysClass * beam_wrapper = NULL;
- Internal_Get_New_Beam(&beam_model,&beam_wrapper);
- // line effect from "start" to "data.Position"
- static Vector3 points[2];
- points[0] = p0;
- points[1] = p1;
- const int BEAM_SUBDIVISION = 4;
- const float BASE_AMPLITUDE = 0.3f;
- const float AMPLITUDE_PER_METER = 0.04f;
- /*
- ** User controlled settings
- */
- TextureClass * beam_texture = Get_Texture_From_Filename(ammo_def->BeamTexture);
- beam_model->Set_Texture(beam_texture);
- REF_PTR_RELEASE(beam_texture);
- beam_model->Set_Points(2,points);
- beam_model->Set_Width(ammo_def->BeamWidth);
- beam_model->Set_Color(ammo_def->BeamColor);
- beam_model->Set_UV_Offset_Rate(Vector2(0,1.0f / ammo_def->BeamTime));
- if (ammo_def->BeamSubdivisionEnabled) {
- beam_model->Set_Noise_Amplitude((BASE_AMPLITUDE + AMPLITUDE_PER_METER * (p1-p0).Quick_Length()) * ammo_def->BeamSubdivisionScale);
- beam_model->Set_Subdivision_Levels(BEAM_SUBDIVISION);
- } else {
- beam_model->Set_Noise_Amplitude(0.0f);
- beam_model->Set_Subdivision_Levels(0);
- }
- /*
- ** Constants; I don't let the user control these for now
- */
- beam_model->Set_Texture_Tile_Factor(1.0f); // Only relevant for TILED_TEXTURE_MAP mode
- beam_model->Set_Texture_Mapping_Mode(SegLineRendererClass::UNIFORM_WIDTH_TEXTURE_MAP);
- beam_model->Set_Opacity(1.0f);
- beam_model->Set_Shader(ShaderClass::_PresetAdditiveShader);
- beam_model->Set_Merge_Abort_Factor(1.0f);
- beam_model->Set_Merge_Intersections(false);
- beam_model->Set_Disable_Sorting(false);
- beam_model->Set_Freeze_Random(ammo_def->BeamSubdivisionFrozen);
- beam_model->Set_End_Caps(ammo_def->BeamEndCaps);
- beam_model->Reset_Line();
- beam_wrapper->Set_Lifetime(ammo_def->BeamTime);
- beam_wrapper->Set_Transform(Matrix3D(1));
- beam_wrapper->Enable_Is_Pre_Lit(true);
- COMBAT_SCENE->Add_Dynamic_Object(beam_wrapper);
- REF_PTR_RELEASE(beam_wrapper);
- REF_PTR_RELEASE(beam_model);
- REF_PTR_RELEASE(beam_texture);
- }
- void BeamEffectManagerClass::Object_Removed_From_Scene(PhysClass * observed_obj)
- {
- /*
- ** One of the "beams" that we are observing expired and was removed from the scene so
- ** lets put it in the list for future re-use
- */
- UnusedBeamList.Add((TimedDecorationPhysClass *)observed_obj);
- }
- void BeamEffectManagerClass::Internal_Get_New_Beam(SegmentedLineClass ** set_beam_model,TimedDecorationPhysClass ** set_beam_wrapper)
- {
- if (UnusedBeamList.Is_Empty()) {
- SegmentedLineClass * beam_model = NEW_REF(SegmentedLineClass,());
- TimedDecorationPhysClass * beam_wrapper = NEW_REF(TimedDecorationPhysClass,());
- beam_wrapper->Enable_Dont_Save(true);
- beam_wrapper->Set_Collision_Group(UNCOLLIDEABLE_GROUP);
- beam_wrapper->Set_Model(beam_model);
- beam_wrapper->Set_Observer(this);
- *set_beam_model = beam_model;
- *set_beam_wrapper = beam_wrapper; // refs returned to the caller
- } else {
- *set_beam_wrapper = UnusedBeamList.Remove_Head(); // refs returned to the caller
- *set_beam_model = (SegmentedLineClass *)((*set_beam_wrapper)->Get_Model());
- }
- }
- /*
- ** Core Bullet Data for simulation
- */
- class BulletDataClass {
- public:
- BulletDataClass( const AmmoDefinitionClass * def = NULL, const ArmedGameObj * owner = NULL,
- const Vector3 & position = Vector3(0,0,0), const Vector3 & velocity = Vector3(0,0,0) ) :
- AmmoDefinition( def ),
- Owner( owner ),
- Position( position ),
- Velocity( velocity ),
- SoftPierceCount( 0 ),
- Destroy( false ),
- DidExplode( false ),
- LastHitID( 0 ),
- GrenadeSafetyTimer( 0 )
- {}
- ArmedGameObj * Get_Owner( void ) const { return (ArmedGameObj *)Owner.Get_Ptr(); }
- CollisionReactionType Bullet_Collision_Occurred( const CollisionEventClass & event );
- ExpirationReactionType Bullet_Expired();
- const AmmoDefinitionClass *AmmoDefinition;
- GameObjReference Owner;
- Vector3 Position;
- Vector3 Velocity;
- int SoftPierceCount;
- bool Destroy;
- bool DidExplode;
- int LastHitID;
- float GrenadeSafetyTimer;
- };
- /*
- ** Bullet_Collision_Occured
- */
- CollisionReactionType BulletDataClass::Bullet_Collision_Occurred( const CollisionEventClass & event )
- {
- WWPROFILE( "Bullet Collision Occurred" );
- WWASSERT(event.OtherObj);
- WWASSERT(event.CollisionResult != NULL);
- // WWDEBUG_SAY(( "Bullet Collision\n" ));
- //
- // Pre-calculate some useful variables
- //
- CollisionReactionType return_value = COLLISION_REACTION_DEFAULT;
- OffenseObjectClass offense( AmmoDefinition->Damage, (int)AmmoDefinition->Warhead, Get_Owner() );
- offense.EnableClientDamage = true; // Only bullets apply to client damage;
- const Vector3 & collision_point = event.CollisionResult->ContactPoint;
- const Vector3 & collision_normal = event.CollisionResult->Normal;
- PhysicalGameObj * other = NULL;
- BuildingGameObj * building = NULL;
- if ( event.OtherObj->Get_Observer() != NULL ) {
- other = ((CombatPhysObserverClass *)event.OtherObj->Get_Observer())->As_PhysicalGameObj();
- building = ((CombatPhysObserverClass *)event.OtherObj->Get_Observer())->As_BuildingGameObj();
- }
- //
- // If the bullet hits its owner or its owner's vehicle, allow it to continue
- // Also, if it is a simple "Hidden Object", allow the bullet to continue
- //
- if ( other != NULL && Get_Owner() != NULL ) {
- SimpleGameObj * simple = other->As_SimpleGameObj();
- VehicleGameObj * vehicle = other->As_VehicleGameObj();
- SoldierGameObj * soldier = Get_Owner()->As_SoldierGameObj();
- if ( vehicle != NULL && soldier != NULL && vehicle == soldier->Get_Vehicle() ) {
- // Debug_Say(( "Bullet Hitting its owner's vehicle\n" ));
- return COLLISION_REACTION_NO_BOUNCE;
- }
- if ( other == Get_Owner() ) {
- // Debug_Say(( "Bullet Hitting its owner\n" ));
- return COLLISION_REACTION_NO_BOUNCE;
- }
- if ((simple != NULL) && (simple->Is_Hidden_Object())) {
- return COLLISION_REACTION_NO_BOUNCE;
- }
- }
- //
- // Apply Graphical Effects.
- // If the mesh is shatterable it is shattered, Apply a surface effect depending on the surface type
- // that was collided with. In the case that the mesh was shattered, we disable surface effect decals.
- //
- bool shattered = false;
- if ((event.CollidedRenderObj != NULL) && (event.CollidedRenderObj->Class_ID() == RenderObjClass::CLASSID_MESH)) {
- MeshClass * mesh = (MeshClass *)event.CollidedRenderObj;
- if ((mesh->Get_W3D_Flags() & W3D_MESH_FLAG_SHATTERABLE) && (mesh->Is_Not_Hidden_At_All())) {
- PhysicsSceneClass::Get_Instance()->Shatter_Mesh( mesh,
- collision_point,
- collision_normal,
- Velocity );
- // remeber that we shattered the mesh so that we don't create decals later
- shattered = true;
- // Bullets always pass through shattered objects because we sometimes have two-layered
- // windows that have different materials on the two sides. We don't want bullets to ever
- // shatter only one "side" of a window!
- return_value = COLLISION_REACTION_NO_BOUNCE;
- }
- }
- // Ignore backfaces (after shattering for windows)
- float dot = Vector3::Dot_Product( event.CollisionResult->Normal, Velocity );
- if ( dot > 0 ) {
- return COLLISION_REACTION_NO_BOUNCE;
- }
- //
- // Apply Damage
- // There are several types of objects that can be damaged: normal dynamic game objects, damageable static
- // physics objects, and buildings. A building is damaged whenever any of its meshes or aggregates are
- // hit by a bullet.
- // Also, we only apply damage if the mesh wasn't shattered. Bullets pretend like nothing happened
- // if they shatter something.
- //
- if (!shattered) {
- const char * collided_obj_name = NULL;
- if ( event.CollidedRenderObj != NULL ) {
- collided_obj_name = event.CollidedRenderObj->Get_Name();
- }
- if ( other ) { // if hitting a game object,
- if ( other->Get_ID() == LastHitID ) {
- // Debug_Say(( "Double Hit\n" ));
- return COLLISION_REACTION_NO_BOUNCE;
- }
- if ( GrenadeSafetyTimer > 0 ) {
- // only if the other is not an enemy
- if ( other && Get_Owner() && !other->Is_Enemy( Get_Owner() ) ) {
- return COLLISION_REACTION_DEFAULT; // Just bounce
- }
- }
- LastHitID = other->Get_ID();
- // Apply Bullet Damage to a game object
- // Allow it to happen for clients, so we can get the repair effect
- // if ( CombatManager::I_Am_Server() ) {
- other->Apply_Damage_Extended( offense, 1.0f, Velocity, collided_obj_name );
- // If the bullet is hitting C4, also apply damage to the object the C4 is
- // attached to. This is a fix for the "C4 armor" exploit.
- if (other->As_C4GameObj() != NULL) {
- ScriptableGameObj * host = other->As_C4GameObj()->Get_Stuck_Object();
- if ((host != NULL) && (host->As_PhysicalGameObj() != NULL)) {
- host->As_PhysicalGameObj()->Apply_Damage_Extended( offense, 1.0f, Velocity, NULL );
- }
- }
- // }
- if ( ( !other->Is_Soft() ) ||
- ( ++SoftPierceCount >= AmmoDefinition->SoftPierceLimit ) ) {
- if ( AmmoDefinition->ExplosionDefID ) {
- ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, collision_point, Get_Owner(), -collision_normal, other );
- }
- Destroy = true;
- return_value = COLLISION_REACTION_STOP_MOTION;
- } else {
- return_value = COLLISION_REACTION_NO_BOUNCE;
- }
- } else { // if hitting terain
- if ( GrenadeSafetyTimer > 0 ) {
- Debug_Say(( "Safety\n" ));
- return COLLISION_REACTION_DEFAULT; // Just bounce
- }
- // If hitting a building, apply damage to it
- if ( building != NULL ) { // && CombatManager::I_Am_Server() ) {
- building->Apply_Damage_Building( offense, event.OtherObj->As_StaticPhysClass() );
- }
- // Check for damage to a DamageableStaticPhys object
- // For now I'm using the persist factory chunk-ID for RTTI... If a better solution
- // turns up we should change this.
- if ( event.OtherObj->Get_Factory().Chunk_ID() == PHYSICS_CHUNKID_DAMAGEABLESTATICPHYS
- // && CombatManager::I_Am_Server()
- )
- {
- DamageableStaticPhysClass * damphys = (DamageableStaticPhysClass *)event.OtherObj;
- OffenseObjectClass offense( AmmoDefinition->Damage, (int)AmmoDefinition->Warhead, Get_Owner() );
- offense.EnableClientDamage = true; // Only bullets apply to client damage;
- damphys->Apply_Damage_Static( offense );
- }
- // check for a non-stopping surface
- if ( !SurfaceEffectsManager::Does_Surface_Stop_Bullets( event.CollisionResult->SurfaceType ) ) {
- return_value = COLLISION_REACTION_NO_BOUNCE;
- } else {
- // If the ammo explodes when it hits terrain, then create an explosion and kill the bullet
- if ( AmmoDefinition->TerrainActivated ) {
- if ( AmmoDefinition->ExplosionDefID ) {
- ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, collision_point, Get_Owner(), -collision_normal, building );
- DidExplode = true;
- }
- Destroy = true;
- return_value = COLLISION_REACTION_STOP_MOTION;
- }
- }
- }
- }
- // Only make a bullet hit if it doesn't make an explosion
- if ( !DidExplode ) {
- Matrix3D surface_tm;
- surface_tm.Look_At(collision_point,collision_point-collision_normal,FreeRandom.Get_Float(0,DEG_TO_RADF(360)));
- int my_type = AmmoDefinition->HitterType;
- bool allow_decals = (shattered == false);
- if ( (float)AmmoDefinition->Damage < 0.0f ) {
- allow_decals = false;
- }
- SurfaceEffectsManager::Apply_Effect( event.CollisionResult->SurfaceType, my_type, surface_tm, event.OtherObj, Get_Owner(), allow_decals );
- }
- return return_value;
- }
- ExpirationReactionType BulletDataClass::Bullet_Expired( void )
- {
- Destroy = true;
- if ( AmmoDefinition->TimeActivated && AmmoDefinition->ExplosionDefID && !DidExplode ) {
- ExplosionManager::Create_Explosion_At( AmmoDefinition->ExplosionDefID, Position, Get_Owner() );
- }
- return EXPIRATION_DENIED; // Don't let it die, I'll pull it from the scene later
- }
- /*
- ** BulletClass (for non-instant bullets)
- */
- class BulletClass : public CombatPhysObserverClass, public MultiListObjectClass, public PostLoadableClass {
- public:
- ~BulletClass( void );
- void Init( const BulletDataClass & data, float progress_time, const Vector3 & target, DamageableGameObj * target_object = NULL );
- void Shutdown( void );
- void Think( void );
- // Save / Load
- virtual bool Save( ChunkSaveClass & csave );
- virtual bool Load( ChunkLoadClass & cload );
- virtual void On_Post_Load(void);
- bool Is_Valid( void ) { return BulletData.AmmoDefinition != NULL; }
- // Collision
- virtual CollisionReactionType Collision_Occurred( const CollisionEventClass & event );
- virtual ExpirationReactionType Object_Expired( PhysClass * observed_obj );
- private:
- BulletDataClass BulletData;
- ProjectileClass * Projectile; // for physics
- Vector3 TargetVector;
- GameObjReference TargetObject;
- float TrackingErrorTimer;
- Vector3 TrackingError;
- long ModelNameCRC;
- BulletClass( void );
- friend class BulletManager;
- };
- BulletClass::BulletClass( void ) :
- Projectile( NULL ),
- TargetVector( 0,0,0 ),
- TrackingErrorTimer( 0 ),
- TrackingError( 0,0,0 ),
- ModelNameCRC( 0 )
- {
- WWASSERT( Projectile == NULL );
- Projectile = NEW_REF( ProjectileClass, () );
- Projectile->Set_Collision_Group( BULLET_COLLISION_GROUP );
- }
- BulletClass::~BulletClass( void )
- {
- if ( Projectile != NULL ) {
- Projectile->Set_Observer(NULL);
- Projectile->Release_Ref();
- Projectile = NULL;
- }
- }
- void BulletClass::Init( const BulletDataClass & data, float progress_time, const Vector3 & target, DamageableGameObj * target_object )
- {
- WWPROFILE( "Bullet Init" );
- BulletData.AmmoDefinition = data.AmmoDefinition;
- BulletData.Owner = data.Get_Owner();
- BulletData.SoftPierceCount = data.SoftPierceCount;
- BulletData.Destroy = data.Destroy;
- BulletData.LastHitID = 0; // We haven't hit anyone yet.
- BulletData.DidExplode = false;
- BulletData.GrenadeSafetyTimer = data.GrenadeSafetyTimer;
- if ( data.AmmoDefinition ) {
- BulletData.GrenadeSafetyTimer = data.AmmoDefinition->GrenadeSafetyTime;
- }
- TargetVector = target;
- TargetObject = target_object;
- TrackingErrorTimer = 0;
- TrackingError = Vector3( 0,0,0 );
- Projectile->Set_Observer( this );
- Projectile->Set_Bounce_Count( BulletData.AmmoDefinition->MaxBounces );
- if ( BulletData.AmmoDefinition ) {
- Projectile->Set_Gravity_Multiplier( BulletData.AmmoDefinition->Gravity );
- Projectile->Set_Elasticity( BulletData.AmmoDefinition->Elasticity );
- }
- Projectile->Enable_Is_Pre_Lit(true); // bullets don't get lighting.
- if ( Projectile->Peek_Model() != NULL &&
- Projectile->Peek_Model()->Get_Name() != NULL &&
- BulletData.AmmoDefinition->ModelName.Compare_No_Case( Projectile->Peek_Model()->Get_Name() ) == 0 ) {
- // We don't need to do anything
- } else {
- RenderObjClass * model = NULL;
- model = WW3DAssetManager::Get_Instance ()->Create_Render_Obj( BulletData.AmmoDefinition->ModelName );
- // If no name is given, lets create the NULL render obj
- if ( model == NULL ) {
- Debug_Say(( "Bullet Not Found \"%s\" \n", BulletData.AmmoDefinition->ModelName ));
- model = WW3DAssetManager::Get_Instance ()->Create_Render_Obj( "NULL" );
- }
- Projectile->Set_Model( model );
- if (model) {
- if ( BulletData.AmmoDefinition->ModelName.Compare_No_Case( model->Get_Name() ) != 0 ) {
- Debug_Say(( "Possible bullet twiddler!! %s %s\n", BulletData.AmmoDefinition->ModelName, model->Get_Name() ));
- }
- // ModelNameCRC = CRC_Stringi( model->Get_Name() );
- ModelNameCRC = CRC_Stringi( BulletData.AmmoDefinition->ModelName );
- model->Release_Ref();
- }
- else {
- ModelNameCRC = 0xFFFFFFFF;
- }
- }
- if (Projectile->Get_Gravity_Multiplier() < 0.1f) {
- Projectile->Set_Orientation_Mode_Aligned_Fixed();
- } else {
- Projectile->Set_Orientation_Mode_Aligned();
- }
- COMBAT_SCENE->Add_Dynamic_Object( Projectile );
- float duration = (float) BulletData.AmmoDefinition->Range / (float) BulletData.AmmoDefinition->Velocity;
- if (duration <= 0.0) {
- Debug_Say(( "NULL DURATION\n" ));
- }
- Projectile->Set_Lifetime( duration * 1.2f );
- Projectile->Set_Position( data.Position );
- WWASSERT(data.Velocity.Is_Valid());
- Projectile->Set_Velocity( data.Velocity );
- // If there are any emitters in this model, reset them for our new position
- RenderObjClass * model = Projectile->Peek_Model();
- model->Restart();
- // Timestep last, incase it gets shutdown during this call
- if ( data.Get_Owner() != NULL ) {
- data.Get_Owner()->Peek_Physical_Object()->Inc_Ignore_Counter();
- }
- Projectile->Timestep( progress_time );
- if ( data.Get_Owner() != NULL ) {
- data.Get_Owner()->Peek_Physical_Object()->Dec_Ignore_Counter();
- }
- }
- void BulletClass::Shutdown( void )
- {
- if ( Projectile != NULL ) {
- COMBAT_SCENE->Remove_Object( Projectile );
- Projectile->Set_Observer( NULL );
- }
- BulletData.AmmoDefinition = NULL;
- BulletData.Owner = NULL;
- }
- /*
- ** BulletClass Save and Load
- */
- enum {
- CHUNKID_VARIABLES = 910991544,
- CHUNKID_OWNER,
- CHUNKID_TARGET_OBJECT,
- MICROCHUNKID_AMMO_DEFINITION_ID = 1,
- MICROCHUNKID_PROJECTILE,
- MICROCHUNKID_SOFT_PIERCE_COUNT,
- MICROCHUNKID_DESTROY,
- MICROCHUNKID_TARGET_VECTOR,
- MICROCHUNKID_TRACKING_ERROR_TIMER,
- MICROCHUNKID_TRACKING_ERROR,
- MICROCHUNKID_MODEL_NAME_CRC,
- };
- bool BulletClass::Save( ChunkSaveClass & csave )
- {
- csave.Begin_Chunk( CHUNKID_VARIABLES );
- WWASSERT( BulletData.AmmoDefinition != NULL );
- int def_id = BulletData.AmmoDefinition->Get_ID();
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_AMMO_DEFINITION_ID, def_id );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_PROJECTILE, Projectile );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_SOFT_PIERCE_COUNT, BulletData.SoftPierceCount );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_DESTROY, BulletData.Destroy );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TARGET_VECTOR, TargetVector );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TRACKING_ERROR_TIMER, TrackingErrorTimer );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_TRACKING_ERROR, TrackingError );
- WRITE_MICRO_CHUNK( csave, MICROCHUNKID_MODEL_NAME_CRC, ModelNameCRC );
- csave.End_Chunk();
- if ( BulletData.Get_Owner() != NULL ) {
- csave.Begin_Chunk( CHUNKID_OWNER );
- BulletData.Owner.Save( csave );
- csave.End_Chunk();
- }
- if ( TargetObject.Get_Ptr() != NULL ) {
- csave.Begin_Chunk( CHUNKID_TARGET_OBJECT );
- TargetObject.Save( csave );
- csave.End_Chunk();
- }
- return true;
- }
- bool BulletClass::Load( ChunkLoadClass & cload )
- {
- while (cload.Open_Chunk()) {
- switch(cload.Cur_Chunk_ID()) {
- case CHUNKID_VARIABLES:
- {
- int def_id = 0;
- while (cload.Open_Micro_Chunk()) {
- switch(cload.Cur_Micro_Chunk_ID()) {
- READ_MICRO_CHUNK( cload, MICROCHUNKID_AMMO_DEFINITION_ID, def_id );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_PROJECTILE, Projectile );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_SOFT_PIERCE_COUNT, BulletData.SoftPierceCount );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_DESTROY, BulletData.Destroy );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_TARGET_VECTOR, TargetVector );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_TRACKING_ERROR_TIMER, TrackingErrorTimer );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_TRACKING_ERROR, TrackingError );
- READ_MICRO_CHUNK( cload, MICROCHUNKID_MODEL_NAME_CRC, ModelNameCRC );
- default:
- Debug_Say(("Unhandled Micro Chunk:%d File:%s Line:%d\r\n",cload.Cur_Micro_Chunk_ID(),__FILE__,__LINE__));
- break;
- }
- cload.Close_Micro_Chunk();
- }
- WWASSERT( BulletData.AmmoDefinition == NULL );
- BulletData.AmmoDefinition = WeaponManager::Find_Ammo_Definition( def_id );
- WWASSERT( BulletData.AmmoDefinition != NULL );
- WWASSERT( Projectile != NULL );
- if ( Projectile != NULL ) {
- REQUEST_REF_COUNTED_POINTER_REMAP( (RefCountClass **)&Projectile );
- }
- break;
- }
- case CHUNKID_OWNER:
- BulletData.Owner.Load( cload );
- break;
- case CHUNKID_TARGET_OBJECT:
- TargetObject.Load( cload );
- break;
- default:
- Debug_Say(("Unhandled Chunk:%d File:%s Line:%d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
- break;
- }
- cload.Close_Chunk();
- }
- SaveLoadSystemClass::Register_Post_Load_Callback(this);
- return true;
- }
- void BulletClass::On_Post_Load (void)
- {
- // Plug ourselves back into the physics object as an observer
- WWASSERT(Projectile != NULL);
- Projectile->Set_Observer(this);
- return ;
- }
- CollisionReactionType BulletClass::Collision_Occurred( const CollisionEventClass & event )
- {
- // Copy the data from the Projectile
- Projectile->Get_Velocity( &BulletData.Velocity );
- Projectile->Get_Position( &BulletData.Position );
- CollisionReactionType result = BulletData.Bullet_Collision_Occurred( event );
- if ( BulletData.Destroy ) {
- Shutdown();
- }
- return result;
- }
- ExpirationReactionType BulletClass::Object_Expired(PhysClass * observed_obj)
- {
- // Copy the data from the Projectile
- Projectile->Get_Velocity( &BulletData.Velocity );
- Projectile->Get_Position( &BulletData.Position );
- ExpirationReactionType result = BulletData.Bullet_Expired();
- if ( BulletData.Destroy ) {
- Shutdown();
- }
- return result;
- }
- #define RANDOM_VECTOR( spread ) Vector3( FreeRandom.Get_Float( -(spread), (spread) ), \
- FreeRandom.Get_Float( -(spread), (spread) ), \
- FreeRandom.Get_Float( 0, (spread) ) )
- void BulletClass::Think( void )
- {
- WWPROFILE( "Bullet Think" );
- // Count down safety timer
- BulletData.GrenadeSafetyTimer -= TimeManager::Get_Frame_Seconds();
- if ( BulletData.GrenadeSafetyTimer < 0 ) {
- BulletData.GrenadeSafetyTimer = 0;
- }
- // Handle Homing Bullets
- if ( BulletData.AmmoDefinition->IsTracking ) {
- // Track your target object
- DamageableGameObj * target_obj = (DamageableGameObj *)TargetObject.Get_Ptr();
- if ( target_obj != NULL ) {
- if ( target_obj->As_PhysicalGameObj() ) {
- TargetVector = target_obj->As_PhysicalGameObj()->Get_Bullseye_Position();
- } else {
- target_obj->Get_Position( &TargetVector );
- }
- }
- Vector3 target_vector = TargetVector;
- Vector3 obj_pos;
- Projectile->Get_Transform().Get_Translation( &obj_pos );
- // Debug_Say(( "Bullet Tracking %f %f %f\n", obj_pos.X, obj_pos.Y, obj_pos.Z ));
- target_vector -= obj_pos;
- // Find distance to target
- float target_distance = target_vector.Length();
- TrackingErrorTimer -= TimeManager::Get_Frame_Seconds();
- if ( TrackingErrorTimer < 0 ) {
- TrackingErrorTimer = FreeRandom.Get_Float( 0.1f, 0.4f );
- TrackingError = RANDOM_VECTOR( BulletData.AmmoDefinition->RandomTrackingScale * target_distance / 2 );
- }
- target_vector += TrackingError;
- target_vector.Normalize();
- Vector3 current_vector;
- Projectile->Get_Velocity( ¤t_vector );
- current_vector.Normalize();
- Vector3 cross = Vector3::Cross_Product( current_vector, target_vector );
- // only re-aim if cross product not near zero
- if ( cross.Length() > WWMATH_EPSILON ) {
- float dot = Vector3::Dot_Product( target_vector, current_vector );
- if ( WWMath::Fabs(dot) >= 1.0f ) {
- // No turning needed
- } else {
- float angle = WWMath::Fast_Acos( dot );
- float allowed_turn = BulletData.AmmoDefinition->TurnRate * TimeManager::Get_Frame_Seconds();
- if ( angle > allowed_turn ) {
- angle = allowed_turn;
- }
- cross.Normalize();
- Matrix3D tm(cross,angle);
- Vector3 current_vector;
- Projectile->Get_Velocity( ¤t_vector );
- WWASSERT(current_vector.Is_Valid());
- current_vector = tm.Rotate_Vector( current_vector );
- WWASSERT(current_vector.Is_Valid());
- Projectile->Set_Velocity( current_vector );
- }
- }
- }
- }
- /*
- ** Instant Bullet Code
- */
- void Simulate_Instant_Bullet( BulletDataClass & data, float progress_time )
- {
- WWPROFILE("Simulate_Instant_Bullet");
- // WWASSERT(data.Position.Is_Valid());
- // WWASSERT(data.Velocity.Is_Valid());
- Vector3 start = data.Position;
- Vector3 end = data.Velocity;
- end.Normalize();
- end = start + (end * data.AmmoDefinition->Range);
- // This is that last object we choose to ignore
- PhysClass *blocker = NULL;
- // Always ignore the owner
- if ( data.Get_Owner() != NULL ) {
- if ( data.Get_Owner()->Peek_Physical_Object() != NULL ) {
- data.Get_Owner()->Peek_Physical_Object()->Inc_Ignore_Counter();
- }
- }
- bool done = false;
- int hit_counter = 0;
- const int MAX_HITS = 5;
- while ( !done && (hit_counter < MAX_HITS)) {
- hit_counter++;
- // Check for collisions in the path of the object
- CastResultStruct res;
- LineSegClass ray( data.Position, end );
- PhysRayCollisionTestClass raytest(ray,&res,BULLET_COLLISION_GROUP,COLLISION_TYPE_PROJECTILE);
- {WWPROFILE("Cast_Ray");
- PhysicsSceneClass::Get_Instance()->Cast_Ray(raytest);
- }
- // If the result was a "startbad", we are done
- if (raytest.Result->StartBad) {
- // Debug_Say(( "Simulate_Instant_Bullet Startbad\n" ));
- done = true;
- } else {
- if (raytest.Result->Fraction < 1.0f) {
- // If there was a collision, set us at the point of collision
- // Also, fill in the cast result struct.
- ray.Compute_Point(raytest.Result->Fraction,&(raytest.Result->ContactPoint));
- data.Position = raytest.Result->ContactPoint;
- // Notify the parties involved
- WWASSERT(raytest.CollidedPhysObj != NULL);
- CollisionReactionType reaction = COLLISION_REACTION_DEFAULT;
- CollisionEventClass event;
- event.CollisionResult = raytest.Result;
- event.CollidedRenderObj = raytest.CollidedRenderObj;
- event.OtherObj = raytest.CollidedPhysObj;
- reaction |= data.Bullet_Collision_Occurred(event);
- if ( reaction & COLLISION_REACTION_NO_BOUNCE ) {
- // We were requested to fly through. Mark the current blocker as ignore
- // so we collide with him no more this pass
- if ( blocker ) { // Stop ignoring the last blocker
- blocker->Dec_Ignore_Counter();
- }
- blocker = raytest.CollidedPhysObj;
- if ( blocker ) { // Start ignoring this blocker
- blocker->Inc_Ignore_Counter();
- }
- } else {
- done = true;
- }
- } else {
- done = true;
- data.Position = end;
- }
- }
- }
- if ( blocker ) { // Stop ignoring the last blocker
- blocker->Dec_Ignore_Counter();
- }
- // stop ignoring the owner
- if ( data.Get_Owner() != NULL ) {
- if ( data.Get_Owner()->Peek_Physical_Object() != NULL ) {
- data.Get_Owner()->Peek_Physical_Object()->Dec_Ignore_Counter();
- }
- }
- // If not destroyed, Expire..
- if ( !data.Destroy ) {
- data.Bullet_Expired();
- }
- // If the definition requests a beam effect, launch it
- if (data.AmmoDefinition->BeamEnabled) {
- _TheBeamEffectManager.Create_Beam_Effect(data.AmmoDefinition,start,data.Position);
- }
- }
- /*
- ** BulletManager
- */
- MultiListClass<BulletClass> LiveBulletList;
- MultiListClass<BulletClass> DeadBulletList;
- void BulletManager::Init( void )
- {
- _TheBeamEffectManager.Init();
- }
- void BulletManager::Shutdown( void )
- {
- while ( !LiveBulletList.Is_Empty() ) {
- delete LiveBulletList.Remove_Head();
- }
- while ( !DeadBulletList.Is_Empty() ) {
- delete DeadBulletList.Remove_Head();
- }
- _TheBeamEffectManager.Shutdown();
- }
- void BulletManager::Update( void )
- {
- MultiListIterator<BulletClass> it( &LiveBulletList );;
- while (!it.Is_Done()) {
- BulletClass * bullet = it.Peek_Obj();
- if (bullet->BulletData.Destroy) {
- bullet->Shutdown();
- it.Remove_Current_Object();
- DeadBulletList.Add( bullet );
- } else {
- bullet->Think();
- it.Next();
- }
- }
- }
- #define BULLET_SPEED_CHEAT 0
- void BulletManager::Create_Bullet( const AmmoDefinitionClass * def, const Vector3 & position,
- const Vector3 & velocity, const ArmedGameObj * owner, float progress_time, const Vector3 & target,
- DamageableGameObj * target_obj )
- {
- WWASSERT(velocity.Is_Valid());
- BulletDataClass data( def, owner, position, velocity );
- #if BULLET_SPEED_CHEAT
- if ( (float)def->Velocity >= _INSTANT_BULLET_THRESHHOLD/3 ) {
- #else
- if ( (float)def->Velocity >= _INSTANT_BULLET_THRESHHOLD ) {
- #endif
- Simulate_Instant_Bullet( data, progress_time );
- return;
- }
- WWPROFILE( "Create Bullet" );
- BulletClass * bullet = NULL;
- // Find a bullet
- long crc = CRC_Stringi( def->ModelName );
- MultiListIterator<BulletClass> it( &DeadBulletList );;
- while ( !it.Is_Done() && bullet == NULL ) {
- BulletClass * test_bullet = it.Peek_Obj();
- if ( test_bullet->ModelNameCRC == crc ) {
- bullet = test_bullet;
- it.Remove_Current_Object();
- } else {
- it.Next();
- }
- }
- if ( bullet == NULL ) {
- bullet = new BulletClass();
- }
- if ( bullet != NULL ) {
- bullet->Init( data, progress_time, target, target_obj );
- LiveBulletList.Add( bullet );
- }
- }
- /*
- ** Save & Load
- */
- enum {
- CHUNKID_BULLET = 916991653,
- };
- bool BulletManager::Save( ChunkSaveClass &csave )
- {
- MultiListIterator<BulletClass> it( &LiveBulletList );;
- while (!it.Is_Done()) {
- BulletClass * bullet = it.Peek_Obj();
- // Only save valid bullets
- if ( bullet->Is_Valid() ) {
- csave.Begin_Chunk( CHUNKID_BULLET );
- bullet->Save( csave );
- csave.End_Chunk();
- }
- it.Next();
- }
- return true;
- }
- bool BulletManager::Load( ChunkLoadClass &cload )
- {
- while (cload.Open_Chunk()) {
- switch(cload.Cur_Chunk_ID()) {
- case CHUNKID_BULLET:
- {
- BulletClass * bullet = new BulletClass();
- bullet->Load( cload );
- LiveBulletList.Add( bullet );
- break;
- }
- default:
- Debug_Say(( "Unrecognized BulletManager chunkID %d\n", cload.Cur_Chunk_ID() ));
- break;
- }
- cload.Close_Chunk();
- }
- return true;
- }
|