Files
firestorm/Gameleap/code/mw4/Code/MW4/RTXWeaponSub.cpp
T
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
Complete disaster-recovery snapshot: engine/game source, game data assets,
VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive.
Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false,
no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2026-06-24 21:28:16 -05:00

637 lines
17 KiB
C++

#include "MW4Headers.hpp"
#include "RTXWeaponSub.hpp"
#include "WeaponMover.hpp"
#include "SubsystemClassData.hpp"
#include "GUIWeaponManager.hpp"
#include <Adept\Site.hpp>
#include <Adept\Map.hpp>
//#############################################################################
//########################## ExecutionStateEngine #######################
//#############################################################################
RTXWeaponSub::ExecutionStateEngine::ClassData*
RTXWeaponSub::ExecutionStateEngine::DefaultData = NULL;
const StateEngine::StateEntry
RTXWeaponSub::ExecutionStateEngine::StateEntries[]=
{
STATE_ENTRY(RTXWeaponSub__ExecutionStateEngine, Jammed),
STATE_ENTRY(RTXWeaponSub__ExecutionStateEngine, Recharging)
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::ExecutionStateEngine::InitializeClass()
{
Check_Object(StateEngine::DefaultData);
Verify(!DefaultData);
DefaultData =
new ClassData(
RTXWeaponSub__ExecutionStateEngineClassID,
"RTXWeaponSub::ExecutionStateEngine",
BaseClass::DefaultData,
ELEMENTS(StateEntries), StateEntries,
(Entity::ExecutionStateEngine::Factory)Make,
(Entity::ExecutionStateEngine::FactoryRequest::Factory)
&FactoryRequest::ConstructFactoryRequest
);
Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::ExecutionStateEngine*
RTXWeaponSub::ExecutionStateEngine::Make(
RTXWeaponSub *weapon,
FactoryRequest *request
)
{
Check_Object(weapon);
Check_Object(request);
gos_PushCurrentHeap(Heap);
RTXWeaponSub::ExecutionStateEngine *engine =
new RTXWeaponSub::ExecutionStateEngine(DefaultData, weapon, request);
gos_PopCurrentHeap();
return engine;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
RTXWeaponSub::ExecutionStateEngine::RequestState(
int new_state,
void* data
)
{
Check_Object(this);
Check_Object(owningEntity);
//
//------------------------------
// Quick exit if no state change
//------------------------------
//
if (new_state == currentState)
return currentState;
//
//-------------------------------------------------------
// If we are currently jammed, ignore a firing request
//-------------------------------------------------------
//
if ( currentState == JammedState && ( new_state == FiringState || new_state == ReloadingState ) )
{
return currentState;
}
if( new_state == FiringState && currentState == NeverExecuteState )
{
// Set the initial time of fire
RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );
owner->m_fireStartTime = owner->m_fireEndTime = gos_GetElapsedTime();
}
if (new_state == JammedState)
{
RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );
Check_Object(owner);
owner->StartJamState();
}
if (new_state == RechargingState)
{
RTXWeaponSub* owner = Cast_Object( RTXWeaponSub*, owningEntity );
Check_Object(owner);
owner->m_enterRecharge = gos_GetElapsedTime();
}
//
//
//---------------------
// Deal with our states
//---------------------
//
return ( BaseClass::RequestState(new_state, data) );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::ExecutionStateEngine::TestInstance() const
{
Verify(IsDerivedFrom(DefaultData));
}
//#############################################################################
//############################### RTXWeaponSub ##########################
//#############################################################################
RTXWeaponSub::ClassData*
RTXWeaponSub::DefaultData = NULL;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::ExecutionStateEngine::TerminateClass()
{
Unregister_Object(DefaultData);
delete DefaultData;
DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::InitializeClass()
{
Verify(!DefaultData);
DefaultData =
new ClassData(
RTXWeaponSubClassID,
"MechWarrior4::RTXWeaponSub",
ProjectileWeapon::DefaultData,
0, NULL,
(Entity::Factory)Make,
(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
ExecutionStateEngine::DefaultData,
(Entity::GameModel::Factory)GameModel::ConstructGameModel,
NULL,
(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
(Entity::GameModel::ModelWrite)GameModel::WriteToText,
(Entity::GameModel::ModelSave)GameModel::SaveGameModel,
(Subsystem::StreamCreate) CreateStream
);
Register_Object(DefaultData);
DIRECT_GAME_MODEL_ATTRIBUTE(
RTXWeaponSub__GameModel,
TimeToPotentialJam,
m_timeToPotentialJam,
Scalar
);
DIRECT_GAME_MODEL_ATTRIBUTE(
RTXWeaponSub__GameModel,
TimeToJam,
m_timeToJam,
Scalar
);
DIRECT_GAME_MODEL_ATTRIBUTE(
RTXWeaponSub__GameModel,
TimeToUnjam,
m_timeToUnjam,
Scalar
);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::TerminateClass()
{
Unregister_Object(DefaultData);
delete DefaultData;
DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub*
RTXWeaponSub::Make(
CreateMessage *message,
ReplicatorID *base_id
)
{
Check_Object(message);
gos_PushCurrentHeap(Heap);
RTXWeaponSub *new_entity =
new RTXWeaponSub(DefaultData, message, base_id, NULL);
gos_PopCurrentHeap();
Check_Object(new_entity);
return new_entity;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::RTXWeaponSub(
ClassData *class_data,
CreateMessage *message,
ReplicatorID *base_id,
ElementRenderer::Element *element
):
ProjectileWeapon(class_data, message, base_id, element),
m_jamStartTime( 0 ),
m_fireStartTime( 0 ),
m_fireEndTime( 0 )
{
Check_Pointer(this);
Check_Object(message);
m_jamThisFrame = false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
RTXWeaponSub::~RTXWeaponSub()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::CreateProjectile(Time till)
{
Check_Object(this);
const GameModel *model = GetGameModel();
Check_Object(model);
if(model->projectileModelResource != ResourceID::Null)
{
//
//---------------------------------
//Get the ClassID from the Resource
//---------------------------------
//
ClassID class_ID;
class_ID = GetClassIDFromDataListID(model->projectileModelResource);
Verify(class_ID != NullClassID);
ReplicatorID site_id = ReplicatorID::Null;
ReplicatorID target_id = ReplicatorID::Null;
Normal3D target_normal;
Motion3D initial_velocity = Motion3D::Identity;
Motion3D initial_acceleration = Motion3D::Identity;
Point3D site_local_to_world;
//
//-------------------------
//Initialize the velocities
//-------------------------
//
Check_Object(sitePointer);
site_local_to_world = sitePointer->GetLocalToWorld();
Vector3D projectile_direction;
Point3D target_point;
targetQuery.m_line->FindEnd(&target_point);
projectile_direction.Subtract(
target_point,
site_local_to_world
);
Check_Object(sitePointer);
site_local_to_world = sitePointer->GetLocalToWorld();
LinearMatrix4D direction_matrix = LinearMatrix4D::Identity;
direction_matrix.BuildTranslation((Stuff::Point3D)site_local_to_world);
direction_matrix.AlignLocalAxisToWorldVector(projectile_direction, Z_Axis, Y_Axis, X_Axis);
Vector3D initial_scale = model->initialLinearVelocity;
if(GetParentAsVehicle())
{
initial_scale.z += GetParentAsVehicle()->currentSpeedMPS;
}
initial_velocity.linearMotion.Multiply(
initial_scale,
direction_matrix
);
initial_acceleration.linearMotion = model->initialLinearAcceleration;
//
//------------------------------------
//Create the WeaponMover CreateMessage
//------------------------------------
//
WeaponMover::CreateMessage projectile_create_message(
sizeof(WeaponMover::CreateMessage),
DefaultEventPriority,
CreateMessage::DefaultFlags,
class_ID,
WeaponMover::DefaultFlags,
model->projectileModelResource,
direction_matrix,
0.0f,
WeaponMover::ExecutionStateEngine::StraightLineMotionState,
NameTable::NullObjectID,
Entity::DefaultAlignment,
initial_velocity,
initial_acceleration,
model->damageAmount,
model->heatToDeal,
model->maxDistance,
// MSL 5.03 Min Distance
// model->minDistance,
GetParentVehicle()->GetReplicatorID(),
model->splashRadius,
model->percentageOfDamageToDirectHit,
model->minPercentageOfDamageToSphereHit,
model->maxPercentageOfDamageToSphereHit,
model->itemID
);
MemoryStream stream(&projectile_create_message, projectile_create_message.messageLength);
Entity *entity;
ReplicatorID projectile_id = Connection::Hermit->GetNextReplicatorID();
entity = CreateEntity(&stream, &projectile_id, false);
Check_Object(entity);
Map::GetInstance()->AddChild(entity);
if (vehicleInterface)
{
vehicleInterface->PlayForce (VehicleInterface::FireProjectileForce);
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Adding function for this, so we can update Jam at this time.
// this allows the server to jam and tell the client that it did.
//
//
void
RTXWeaponSub::Fire(Adept::Entity::CollisionQuery *query,
Stuff::Time time_locked,
const Stuff::Point3D& lockedTargetEntityPartOffset)
{
// MSL 5.03
BaseClass::Fire(query, time_locked, lockedTargetEntityPartOffset);
if (executionState->GetState() == ExecutionStateEngine::FiringState)
{
//
// Only jam if we are the person firing, or we are the server
//
// If we don't restrict this, then another client may refuse to show the shots because it thinks that the
// gun is jammed when it isn't
//
Check_Object(GetParentVehicle());
bool fServer = Network::GetInstance()->AmIServer();
bool fLocalPlayer = (GetReplicatorID().connectionID == Connection::Local->GetID());
//
//
//
if (fServer || fLocalPlayer)
{
const GameModel* model = GetGameModel();
Check_Object(model);
Check_Object(executionState);
// if we fired, check for jam
m_fireEndTime = gos_GetElapsedTime();
double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;
//
// For servers, the rules are going to be different. If the fire time is at the absolute max, plus a buffer for lag
// then we will gaurantee a jam on the server, and have it refresh.
//
// This allows for some tolerance of various net traffic since we'd still prefer that the
// client handle this, but puts a cap on what we tolerate from a potential cheater.
if ( fLocalPlayer)
{
if ( cumulativeFiringTime >= model->m_timeToPotentialJam)
{
Stuff::Scalar idPermutation = (replicatorID.localID % 3)/3.0f; // have weapon's ID permutate it's jam time
Stuff::Scalar r = (Stuff::Scalar)fmod( gos_GetElapsedTime()+idPermutation, 1.0 );
Stuff::Scalar e = (Stuff::Scalar)(
( cumulativeFiringTime - model->m_timeToPotentialJam )
/ model->m_timeToJam );
// Potential to jam:
if ( r < e )
{
m_jamThisFrame = true;
}
}
}
else if (fServer)
{
if (cumulativeFiringTime >= (model->m_timeToJam + model->m_timeToPotentialJam + 2.0f))
{
m_jamThisFrame = true;
}
}
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::PreCollisionExecute(Time till)
{
Check_Object(this);
PRECOLLISION_LOGIC("Subsystem::RTXWeaponSub");
const GameModel* model = GetGameModel();
Check_Object(model);
Check_Object(executionState);
Stuff::Scalar timeSlice = GetTimeSlice( till );
int pre_state = executionState->GetState();
switch (pre_state)
{
case ExecutionStateEngine::FiringState:
{
UsePostCollision();
m_PlayedRechargeSound = false;
break;
}
case ExecutionStateEngine::ReloadingState:
{
Scalar count = model->reloadTime - GetTimeParameter(till);
Min_Clamp(count, 0.0f);
SetCount(count);
if (model->reloadTime > 0.49f)
{
if ((!m_PlayedRechargeSound) && (GetTimeParameter(till) >= model->reloadTime-0.5f))
{
if (GetParentVehicle ())
if (GetParentVehicle()->vehicleInterface)
{
GetParentVehicle ()->vehicleInterface->ReactToEvent (VehicleInterface::RECHARGE_PROJECTILE);
}
m_PlayedRechargeSound = true;
}
}
if(GetTimeParameter(till) > model->reloadTime)
{
executionState->RequestState(ExecutionStateEngine::RechargingState);
if(guiWeapon.GetCurrent())
guiWeapon.GetCurrent()->Ready();
m_PlayedRechargeSound = false;
}
IgnorePostCollision();
}
break;
case ExecutionStateEngine::RechargingState:
{
// In order to make the server and client agree on cumulative time more, I'll put a buffer in.
// but if we don't get a new fire request with .25 seconds of entering this state we'll assume recharge.
if ( (gos_GetElapsedTime() - m_enterRecharge ) >= 0.25f)
{
m_fireStartTime += timeSlice*2;
m_fireEndTime += timeSlice;
double cumulativeFiringTime = m_fireEndTime - m_fireStartTime;
if ( cumulativeFiringTime <= 0 )
{
executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
}
}
}
break;
case ExecutionStateEngine::JammedState:
{
if( gos_GetElapsedTime() - m_jamStartTime > model->m_timeToUnjam )
{
executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
if(guiWeapon.GetCurrent())
guiWeapon.GetCurrent()->Ready();
}
else
{
// Update the gui time
if(guiWeapon.GetCurrent())
{
guiWeapon.GetCurrent()->SetJamCount( (Stuff::Scalar)(gos_GetElapsedTime() - m_jamStartTime) );
}
}
IgnorePostCollision();
break;
}
}
Weapon::PreCollisionExecute(till);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::PostCollisionExecute(Time till)
{
Check_Object(this);
POSTCOLLISION_LOGIC("Subsystem::RTXWeaponSub");
bool jammed = false;
if ( executionState->GetState() == ExecutionStateEngine::FiringState )
{
if (m_jamThisFrame)
{
executionState->RequestState(ExecutionStateEngine::JammedState);
jammed = true;
}
}
if ( !jammed )
ProjectileWeapon::PostCollisionExecute(till);
m_jamThisFrame = false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// This weapon needs to override this function. Unlike the normal projectile classes, this one requires that it
// be able to fire while in the "recharging" state.
//
bool
RTXWeaponSub::ReadyToFire()
{
Check_Object(this);
Check_Object(executionState);
// if this is the local mech, always true in single player.
if (GetReplicatorID().connectionID == Connection::Local->GetID())
{
if (CanFireFromCurrentArm() == false)
{
return (false);
}
bool result;
result = (executionState->GetState()==ExecutionStateEngine::NeverExecuteState ||
executionState->GetState()==ExecutionStateEngine::RechargingState)
&& DoesHaveAmmo();
return result;
}
if (executionState->GetState()==ExecutionStateEngine::ReloadingState)
{
const GameModel* model = GetGameModel();
// 10% of the recharging plus the server-client packet time difference
if (gos_GetElapsedTime() - lastParameterization >= (model->reloadTime - (0.1f * model->reloadTime)))
{
executionState->RequestState(ExecutionStateEngine::RechargingState);
}
return DoesHaveAmmo();
}
bool result;
result = (executionState->GetState()==ExecutionStateEngine::NeverExecuteState ||
executionState->GetState()==ExecutionStateEngine::RechargingState) &&
DoesHaveAmmo();
return result;
}
void RTXWeaponSub::StartJamState()
{
const GameModel *model = GetGameModel();
Check_Object(model);
m_jamStartTime = gos_GetElapsedTime();
if ( guiWeapon.GetCurrent() )
{
guiWeapon.GetCurrent()->RecoverFromJam();
guiWeapon.GetCurrent()->SetJamCount( model->m_timeToUnjam );
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
RTXWeaponSub::TestInstance() const
{
Verify(IsDerivedFrom(DefaultData));
}
/*
sprintf( buffer, "Cumulative = %d (start=%d, end=%d)\n", (int)(m_fireEndTime - m_fireStartTime), (int)m_fireStartTime, (int)m_fireEndTime );
Spew( 0, buffer );
sprintf( buffer, "Jammed = %d\n", (int)(gos_GetElapsedTime() - m_jamStartTime) );
Spew( 0,buffer );
sprintf( buffer, "Cumulative = %d (start=%d, end=%d)\n", (int)(m_fireEndTime - m_fireStartTime), (int)m_fireStartTime, (int)m_fireEndTime );
Spew( 0, buffer );
*/