#include "StdAfxSA.h" #include #include #include #include "VehicleSA.h" #include "TimerSA.h" #include "DelimStringReader.h" static constexpr float PHOENIX_FLUTTER_PERIOD = 70.0f; static constexpr float PHOENIX_FLUTTER_AMP = 0.13f; static constexpr float SWEEPER_BRUSH_SPEED = 0.3f; std::vector vecRotorExceptions; float CAutomobile::ms_engineCompSpeed; static bool ShouldIgnoreRotor( int32_t id ) { return std::find( vecRotorExceptions.begin(), vecRotorExceptions.end(), id ) != vecRotorExceptions.end(); } static void* varVehicleRender = AddressByVersion(0x6D0E60, 0x6D1680, 0x70C0B0); WRAPPER void CVehicle::Render() { VARJMP(varVehicleRender); } static void* varIsLawEnforcementVehicle = AddressByVersion(0x6D2370, 0x6D2BA0, 0x70D8C0); WRAPPER bool CVehicle::IsLawEnforcementVehicle() { VARJMP(varIsLawEnforcementVehicle); } void (CAutomobile::*CAutomobile::orgPreRender)(); static int32_t random(int32_t from, int32_t to) { return from + ( Int32Rand() % (to-from) ); } static RwObject* GetCurrentAtomicObject( RwFrame* frame ) { RwObject* obj = nullptr; RwFrameForAllObjects( frame, [&obj]( RwObject* object ) -> RwObject* { if ( RpAtomicGetFlags(object) & rpATOMICRENDER ) { obj = object; return nullptr; } return object; } ); return obj; } static RwFrame* GetFrameFromName( RwFrame* topFrame, const char* name ) { class GetFramePredicate { public: RwFrame* foundFrame = nullptr; GetFramePredicate( const char* name ) : m_name( name ) { } RwFrame* operator() ( RwFrame* frame ) { if ( strcmp( m_name, GetFrameNodeName(frame) ) == 0 ) { foundFrame = frame; return nullptr; } RwFrameForAllChildren( frame, *this ); return foundFrame != nullptr ? nullptr : frame; } private: const char* const m_name; }; GetFramePredicate p( name ); RwFrameForAllChildren( topFrame, p ); return p.foundFrame; } void ReadRotorFixExceptions(const wchar_t* pPath) { const size_t SCRATCH_PAD_SIZE = 32767; WideDelimStringReader reader( SCRATCH_PAD_SIZE ); GetPrivateProfileSectionW( L"RotorFixExceptions", reader.GetBuffer(), reader.GetSize(), pPath ); while ( const wchar_t* str = reader.GetString() ) { int32_t toList = wcstol( str, nullptr, 0 ); if ( toList > 0 ) vecRotorExceptions.push_back( toList ); } } void CVehicle::SetComponentAtomicAlpha(RpAtomic* pAtomic, int nAlpha) { RpGeometry* pGeometry = RpAtomicGetGeometry(pAtomic); pGeometry->flags |= rpGEOMETRYMODULATEMATERIALCOLOR; RpGeometryForAllMaterials( pGeometry, [nAlpha] (RpMaterial* material) { material->color.alpha = RwUInt8(nAlpha); return material; } ); } bool CVehicle::CustomCarPlate_TextureCreate(CVehicleModelInfo* pModelInfo) { char PlateText[CVehicleModelInfo::PLATE_TEXT_LEN+1]; const char* pOverrideText = pModelInfo->GetCustomCarPlateText(); if ( pOverrideText ) strncpy_s(PlateText, pOverrideText, CVehicleModelInfo::PLATE_TEXT_LEN); else CCustomCarPlateMgr::GeneratePlateText(PlateText, CVehicleModelInfo::PLATE_TEXT_LEN); PlateText[CVehicleModelInfo::PLATE_TEXT_LEN] = '\0'; PlateTexture = CCustomCarPlateMgr::CreatePlateTexture(PlateText, pModelInfo->m_nPlateType); if ( pModelInfo->m_nPlateType != -1 ) PlateDesign = pModelInfo->m_nPlateType; else if ( IsLawEnforcementVehicle() ) PlateDesign = CCustomCarPlateMgr::GetMapRegionPlateDesign(); else PlateDesign = random(0, 20) == 0 ? int8_t(random(0, 3)) : CCustomCarPlateMgr::GetMapRegionPlateDesign(); assert(PlateDesign >= 0 && PlateDesign < 3); pModelInfo->m_plateText[0] = '\0'; pModelInfo->m_nPlateType = -1; return true; } void CVehicle::CustomCarPlate_BeforeRenderingStart(CVehicleModelInfo* pModelInfo) { for ( size_t i = 0; i < pModelInfo->m_apPlateMaterials->m_numPlates; i++ ) { RpMaterialSetTexture(pModelInfo->m_apPlateMaterials->m_plates[i], PlateTexture); } for ( size_t i = 0; i < pModelInfo->m_apPlateMaterials->m_numPlatebacks; i++ ) { CCustomCarPlateMgr::SetupMaterialPlatebackTexture(pModelInfo->m_apPlateMaterials->m_platebacks[i], PlateDesign); } } void CVehicle::SetComponentRotation( RwFrame* component, eRotAxis axis, float angle, bool absolute ) { if ( component == nullptr ) return; CMatrix matrix( RwFrameGetMatrix(component) ); if ( absolute ) { if ( axis == ROT_AXIS_X ) matrix.SetRotateXOnly(angle); else if ( axis == ROT_AXIS_Y ) matrix.SetRotateYOnly(angle); else if ( axis == ROT_AXIS_Z ) matrix.SetRotateZOnly(angle); } else { const CVector pos = matrix.GetPos(); matrix.SetTranslateOnly(0.0f, 0.0f, 0.0f); if ( axis == ROT_AXIS_X ) matrix.RotateX(angle); else if ( axis == ROT_AXIS_Y ) matrix.RotateY(angle); else if ( axis == ROT_AXIS_Z ) matrix.RotateZ(angle); matrix.GetPos() += pos; } matrix.UpdateRW(); } void CHeli::Render() { double dRotorsSpeed, dMovingRotorSpeed; bool bDisplayRotors = !ShouldIgnoreRotor( FLAUtils::GetExtendedID( &m_nModelIndex ) ); bool bHasMovingRotor = m_pCarNode[13] != nullptr && bDisplayRotors; bool bHasMovingRotor2 = m_pCarNode[15] != nullptr && bDisplayRotors; m_nTimeTillWeNeedThisCar = CTimer::m_snTimeInMilliseconds + 3000; if ( m_fRotorSpeed > 0.0 ) dRotorsSpeed = std::min(1.7 * (1.0/0.22) * m_fRotorSpeed, 1.5); else dRotorsSpeed = 0.0; dMovingRotorSpeed = dRotorsSpeed - 0.4; if ( dMovingRotorSpeed < 0.0 ) dMovingRotorSpeed = 0.0; int nStaticRotorAlpha = static_cast(std::min((1.5-dRotorsSpeed) * 255.0, 255.0)); int nMovingRotorAlpha = static_cast(std::min(dMovingRotorSpeed * 175.0, 175.0)); if ( m_pCarNode[12] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[12] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingRotor ? nStaticRotorAlpha : 255); } if ( m_pCarNode[14] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[14] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingRotor2 ? nStaticRotorAlpha : 255); } if ( m_pCarNode[13] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[13] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingRotor ? nMovingRotorAlpha : 0); } if ( m_pCarNode[15] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[15] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingRotor2 ? nMovingRotorAlpha : 0); } CEntity::Render(); } void CPlane::Render() { double dRotorsSpeed, dMovingRotorSpeed; bool bDisplayRotors = !ShouldIgnoreRotor( FLAUtils::GetExtendedID( &m_nModelIndex ) ); bool bHasMovingProp = m_pCarNode[13] != nullptr && bDisplayRotors; bool bHasMovingProp2 = m_pCarNode[15] != nullptr && bDisplayRotors; m_nTimeTillWeNeedThisCar = CTimer::m_snTimeInMilliseconds + 3000; if ( m_fPropellerSpeed > 0.0 ) dRotorsSpeed = std::min(1.7 * (1.0/0.31) * m_fPropellerSpeed, 1.5); else dRotorsSpeed = 0.0; dMovingRotorSpeed = dRotorsSpeed - 0.4; if ( dMovingRotorSpeed < 0.0 ) dMovingRotorSpeed = 0.0; int nStaticRotorAlpha = static_cast(std::min((1.5-dRotorsSpeed) * 255.0, 255.0)); int nMovingRotorAlpha = static_cast(std::min(dMovingRotorSpeed * 175.0, 175.0)); if ( m_pCarNode[12] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[12] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingProp ? nStaticRotorAlpha : 255); } if ( m_pCarNode[14] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[14] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingProp2 ? nStaticRotorAlpha : 255); } if ( m_pCarNode[13] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[13] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingProp ? nMovingRotorAlpha : 0); } if ( m_pCarNode[15] != nullptr ) { RpAtomic* pOutAtomic = (RpAtomic*)GetCurrentAtomicObject( m_pCarNode[15] ); if ( pOutAtomic != nullptr ) SetComponentAtomicAlpha(pOutAtomic, bHasMovingProp2 ? nMovingRotorAlpha : 0); } CVehicle::Render(); } void CPlane::Fix_SilentPatch() { // Reset bouncing panels // No reset on Vortex const int32_t extID = FLAUtils::GetExtendedID( &m_nModelIndex ); for ( ptrdiff_t i = extID == 539 ? 1 : 0; i < 3; i++ ) { m_aBouncingPanel[i].m_nNodeIndex = -1; } } void CAutomobile::PreRender() { // For rotating engine components ms_engineCompSpeed = m_nVehicleFlags.bEngineOn ? CTimer::m_fTimeStep : 0.0f; (this->*(orgPreRender))(); if ( FLAUtils::GetExtendedID( &m_nModelIndex ) == 603 ) { ProcessPhoenixBlower( 603 ); } if ( FLAUtils::GetExtendedID( &m_nModelIndex ) == 574 ) { ProcessSweeper(); } } void CAutomobile::Fix_SilentPatch() { ResetFrames(); // Reset bouncing panels const int32_t extID = FLAUtils::GetExtendedID( &m_nModelIndex ); for ( ptrdiff_t i = (extID == 525 && m_pCarNode[21]) || (extID == 531 && m_pCarNode[17]) ? 1 : 0; i < 3; i++ ) { // Towtruck/Tractor fix m_aBouncingPanel[i].m_nNodeIndex = -1; } } void CAutomobile::ResetFrames() { RpClump* pOrigClump = reinterpret_cast(ms_modelInfoPtrs[ FLAUtils::GetExtendedID( &m_nModelIndex ) ]->pRwObject); if ( pOrigClump != nullptr ) { // Instead of setting frame rotation to (0,0,0) like R* did, obtain the original frame matrix from CBaseNodelInfo clump for ( ptrdiff_t i = 8; i < 25; i++ ) { if ( m_pCarNode[i] != nullptr ) { // Find a frame in CBaseModelInfo object RwFrame* origFrame = GetFrameFromName( RpClumpGetFrame(pOrigClump), GetFrameNodeName(m_pCarNode[i]) ); if ( origFrame != nullptr ) { // Found a frame, reset it *RwFrameGetMatrix(m_pCarNode[i]) = *RwFrameGetMatrix(origFrame); RwMatrixUpdate(RwFrameGetMatrix(m_pCarNode[i])); } } } } } void CAutomobile::ProcessPhoenixBlower( int32_t modelID ) { if ( m_pCarNode[20] == nullptr ) return; RpClump* pOrigClump = reinterpret_cast(ms_modelInfoPtrs[ modelID ]->pRwObject); if ( pOrigClump != nullptr ) { RwFrame* origFrame = GetFrameFromName( RpClumpGetFrame(pOrigClump), GetFrameNodeName(m_pCarNode[20]) ); if ( origFrame != nullptr ) { *RwFrameGetMatrix(m_pCarNode[20]) = *RwFrameGetMatrix(origFrame); } } float finalAngle = 0.0f; if ( m_fGasPedal > 0.0f ) { if ( m_fSpecialComponentAngle < 1.3f ) { finalAngle = m_fSpecialComponentAngle = std::min( m_fSpecialComponentAngle + 0.1f * CTimer::m_fTimeStep, 1.3f ); } else { finalAngle = m_fSpecialComponentAngle + (std::sin( (CTimer::m_snTimeInMilliseconds % 10000) / PHOENIX_FLUTTER_PERIOD ) * PHOENIX_FLUTTER_AMP); } } else { if ( m_fSpecialComponentAngle > 0.0f ) { finalAngle = m_fSpecialComponentAngle = std::max( m_fSpecialComponentAngle - 0.05f * CTimer::m_fTimeStep, 0.0f ); } } SetComponentRotation( m_pCarNode[20], ROT_AXIS_X, finalAngle, false ); } void CAutomobile::ProcessSweeper() { if ( !m_nVehicleFlags.bEngineOn ) return; if ( GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PHYSICS || GetStatus() == STATUS_SIMPLE ) { if ( m_pCarNode[20] == nullptr ) { m_pCarNode[20] = GetFrameFromName( RpClumpGetFrame(m_pRwObject), "misca" ); } if ( m_pCarNode[21] == nullptr ) { m_pCarNode[21] = GetFrameFromName( RpClumpGetFrame(m_pRwObject), "miscb" ); } const float angle = CTimer::m_fTimeStep * SWEEPER_BRUSH_SPEED; SetComponentRotation( m_pCarNode[20], ROT_AXIS_Z, angle, false ); SetComponentRotation( m_pCarNode[21], ROT_AXIS_Z, -angle, false ); } }