#include "MW4Headers.hpp" #include "RTXWeaponSub.hpp" #include "WeaponMover.hpp" #include "SubsystemClassData.hpp" #include "GUIWeaponManager.hpp" #include #include //############################################################################# //########################## 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 ); */