diff --git a/CREDITS.md b/CREDITS.md index 538c7fb5ee..d12176371b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -565,6 +565,7 @@ This page lists all the individual contributions to the project by their author. - Fix an issue that units' `LaserTrails` will always lags behind by one frame - Fix an issue that the currently hovered planning node not update up-to-date, such as using hotkeys to select technos - Allow the aircraft to enter area guard mission and not crash immediately without any airport + - Multiple Barrels and Turrets - **Ollerus**: - Build limit group enhancement - Customizable rocker amplitude @@ -647,6 +648,7 @@ This page lists all the individual contributions to the project by their author. - Fix an issue where the vanilla script ignores jumpjets - CellSpread in cylinder shape - CellSpread damage check if victim is in air or on floor + - Multiple Barrels and Turrets - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 2e81cc12c3..1b84f86da3 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2167,6 +2167,35 @@ JumpjetTilt.SidewaysRotationFactor=1.0 ; floating point value JumpjetTilt.SidewaysSpeedFactor=1.0 ; floating point value ``` +#### Multiple Barrels and Turrets + +![image](_static/images/multiturret-01.png) +*Vehicle with multiple turrets* + +- Vehicles can now draw and fire turrets at multiple positions, only supports regular VXL vehicles. + - `ExtraTurretCount` controls how many extra turrets a unit has. `ExtraTurretOffsetX` controls the offset position of the X-th extra turret of the unit relative to the center of the unit. X is 0-based. + - `ExtraBarrelCount` controls how many extra barrels each turret has. `BarrelOffset` controls the offset distance of the main barrel relative to the center of the turret along the Y-axis (left and right). `ExtraBarrelOffsetX` controls the offset distance of the X-th extra barrel of each turret relative to the center of the turret along the Y-axis (left and right). X is 0-based. + - `BarrelOverTurret` controls whether the barrel layer is always above the turret layer. Otherwise, the layer relationship will be judged according to the direction of the turret. + - When a unit fires, it will first use the original turret to fire `BurstPerTurret` times, then switch to the 0th additional turret to fire `BurstPerTurret` times, then switch to the 1st, and so on. +- The firing FLH is also calculated relative to the firing turret's position. If the unit's turret or barrel has recoil effects set, the turret/barrel index will be automatically calculated based on `Burst`. +- The firing position is essentially determined by the current `Burst`. If you want the rear turrets to fire, you need weapons with high `Burst`. For example, a battleship with 2 additional turrets (3 turrets total) and `BurstPerTurret=3` would need at least `Burst=9` to allow all turrets to fire completely once. The same applies to barrels on each turret. + +In `artmd.ini`: +```ini +[SOMEVEHICLE] ; VehicleType +BarrelOverTurret= ; boolean +BarrelOffset=0 ; integer +ExtraBarrelCount=0 ; integer +ExtraBarrelOffsetX=0 ; integer +ExtraTurretCount=0 ; integer +ExtraTurretOffsetX=0,0,0 ; FLH coordinates +BurstPerTurret=0 ; integer +``` + +```{note} +This is essentially designed to restore multi-turrets in RA1, not to help you make a Zantos. Therefore, features like different turrets using different weapons/targeting separately/calculating cooldown separately/having separate health are not within the scope of this function. Please use techno attachment for such requirements. +``` + ### Turret Response - When the vehicle loses its target, you can customize whether to align the turret direction with the vehicle body. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 14aff0e43d..62adaed5a4 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -466,6 +466,7 @@ New: - [Customize type selection for IFV](Fixed-or-Improved-Logics.md#customize-type-selection-for-ifv) (by NetsuNegi) - CellSpread in cylinder shape (by TaranDahl) - CellSpread damage check if victim is in air or on floor (by TaranDahl) +- Multiple Barrels and Turrets (by TaranDahl & CrimRecya) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Techno/Body.Internal.cpp b/src/Ext/Techno/Body.Internal.cpp index 9a6a420049..7f8faf99c1 100644 --- a/src/Ext/Techno/Body.Internal.cpp +++ b/src/Ext/Techno/Body.Internal.cpp @@ -34,37 +34,56 @@ void TechnoExt::ObjectKilledBy(TechnoClass* pVictim, TechnoClass* pKiller) } } -// reversed from 6F3D60 -CoordStruct TechnoExt::GetFLHAbsoluteCoords(TechnoClass* pThis, CoordStruct pCoord, bool isOnTurret) +Matrix3D TechnoExt::GetTransform(TechnoClass* pThis, VoxelIndexKey* pKey, bool isShadow) { - auto const pType = pThis->GetTechnoType(); - auto const pFoot = abstract_cast(pThis); Matrix3D mtx; + auto const pFoot = abstract_cast(pThis); - // Step 1: get body transform matrix if (pFoot && pFoot->Locomotor) - mtx = pFoot->Locomotor->Draw_Matrix(nullptr); + mtx = isShadow ? pFoot->Locomotor->Shadow_Matrix(pKey) : pFoot->Locomotor->Draw_Matrix(pKey); else // no locomotor means no rotation or transform of any kind (f.ex. buildings) - Kerbiter mtx.MakeIdentity(); - // Steps 2-3: turret offset and rotation - if (isOnTurret && (pType->Turret || !pFoot)) // If building has no turret, it's TurretFacing is TargetDirection + return mtx; +} + +Matrix3D TechnoExt::TransformFLHForTurret(TechnoClass* pThis, Matrix3D mtx, bool isOnTurret, double factor, int turIdx) +{ + auto const pType = pThis->GetTechnoType(); + const bool isFoot = (pThis->AbstractFlags & AbstractFlags::Foot) != AbstractFlags::None; + + // turret offset and rotation + if (isOnTurret && (pType->Turret || !isFoot)) // If building has no turret, it's TurretFacing is TargetDirection { - TechnoTypeExt::ApplyTurretOffset(pType, &mtx); + TechnoTypeExt::ApplyTurretOffset(pType, &mtx, factor, turIdx); const double turretRad = pThis->TurretFacing().GetRadian<32>(); // For BuildingClass turret facing is equal to primary facing - const float angle = pFoot ? (float)(turretRad - pThis->PrimaryFacing.Current().GetRadian<32>()) : (float)(turretRad); + const float angle = isFoot ? (float)(turretRad - pThis->PrimaryFacing.Current().GetRadian<32>()) : (float)(turretRad); mtx.RotateZ(angle); } - // Step 4: apply FLH offset - mtx.Translate((float)pCoord.X, (float)pCoord.Y, (float)pCoord.Z); + return mtx; +} - auto const result = mtx.GetTranslation(); +Matrix3D TechnoExt::GetFLHMatrix(TechnoClass* pThis, const CoordStruct& flh, bool isOnTurret, double factor, bool isShadow, int turIdx) +{ + Matrix3D transform = TechnoExt::GetTransform(pThis, nullptr, isShadow); + Matrix3D mtx = TechnoExt::TransformFLHForTurret(pThis, transform, isOnTurret, factor, turIdx); + + // apply FLH offset + mtx.Translate((float)(flh.X * factor), (float)(flh.Y * factor), (float)(flh.Z * factor)); - // Step 5: apply as an offset to global object coords + return mtx; +} + +// reversed from 6F3D60 +CoordStruct TechnoExt::GetFLHAbsoluteCoords(TechnoClass* pThis, const CoordStruct& flh, bool isOnTurret, int turIdx) +{ + auto result = TechnoExt::GetFLHMatrix(pThis, flh, isOnTurret, 1.0, false, turIdx).GetTranslation(); + + // apply as an offset to global object coords // Resulting coords are mirrored along X axis, so we mirror it back auto const location = pThis->GetRenderCoords() + CoordStruct { (int)result.X, -(int)result.Y, (int)result.Z }; @@ -172,6 +191,57 @@ void TechnoExt::ExtData::InitializeAttachEffects() AttachEffectClass::Attach(pThis, pThis->Owner, pThis, pThis, pTypeExt->AttachEffects); } +void TechnoExt::ExtData::InitializeRecoilData() +{ + const auto pTypeExt = this->TypeExtData; + const auto pType = pTypeExt->OwnerObject(); + + if (!pType->TurretRecoil) + return; + + if (pTypeExt->ExtraTurretCount) + { + if (static_cast(this->ExtraTurretRecoil.size()) < pTypeExt->ExtraTurretCount) + this->ExtraTurretRecoil.resize(pTypeExt->ExtraTurretCount); + + const auto& refData = pType->TurretAnimData; + + for (auto& data : this->ExtraTurretRecoil) + { + data.Turret.Travel = refData.Travel; + data.Turret.CompressFrames = refData.CompressFrames; + data.Turret.RecoverFrames = refData.RecoverFrames; + data.Turret.HoldFrames = refData.HoldFrames; + data.TravelPerFrame = 0.0; + data.TravelSoFar = 0.0; + data.State = RecoilData::RecoilState::Inactive; + data.TravelFramesLeft = 0; + } + } + + if (pTypeExt->ExtraTurretCount || pTypeExt->ExtraBarrelCount) + { + const auto dataCount = (pTypeExt->ExtraBarrelCount + 1) * (pTypeExt->ExtraTurretCount + 1) - 1; + + if (static_cast(this->ExtraBarrelRecoil.size()) < dataCount) + this->ExtraBarrelRecoil.resize(dataCount); + + const auto& refData = pType->BarrelAnimData; + + for (auto& data : this->ExtraBarrelRecoil) + { + data.Turret.Travel = refData.Travel; + data.Turret.CompressFrames = refData.CompressFrames; + data.Turret.RecoverFrames = refData.RecoverFrames; + data.Turret.HoldFrames = refData.HoldFrames; + data.TravelPerFrame = 0.0; + data.TravelSoFar = 0.0; + data.State = RecoilData::RecoilState::Inactive; + data.TravelFramesLeft = 0; + } + } +} + // Gets tint colors for invulnerability, airstrike laser target and berserk, depending on parameters. int TechnoExt::GetTintColor(TechnoClass* pThis, bool invulnerability, bool airstrike, bool berserk) { diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 6379f7e5d5..f340bc0253 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -43,6 +43,7 @@ void TechnoExt::ExtData::OnEarlyUpdate() this->ApplyMindControlRangeLimit(); this->UpdateRecountBurst(); this->UpdateRearmInEMPState(); + this->UpdateRecoilData(); if (this->AttackMoveFollowerTempCount) this->AttackMoveFollowerTempCount--; @@ -1071,6 +1072,7 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) pThis->BarrelFacing.SetCurrent(DirStruct(0x4000 - (pCurrentType->FireAngle << 8))); // Reset recoil data + this->InitializeRecoilData(); { auto& turretRecoil = pThis->TurretRecoil.Turret; const auto& turretAnimData = pCurrentType->TurretAnimData; @@ -2136,3 +2138,54 @@ void TechnoExt::ExtData::UpdateTintValues() calculateTint(Drawing::RGB_To_Int(pShieldType->Tint_Color), static_cast(pShieldType->Tint_Intensity * 1000), pShieldType->Tint_VisibleToHouses); } } + +void TechnoExt::ExtData::RecordRecoilData() +{ + const auto pThis = this->OwnerObject(); + const auto pTypeExt = this->TypeExtData; + + if (auto turretIndex = pTypeExt->BurstPerTurret + ? ((pThis->CurrentBurstIndex / pTypeExt->BurstPerTurret) % (pTypeExt->ExtraTurretCount + 1)) + : 0) + { + turretIndex -= 1; + this->ExtraTurretRecoil[turretIndex].TravelSoFar = 0.0; + this->ExtraTurretRecoil[turretIndex].Fire(); + } + else + { + pThis->TurretRecoil.TravelSoFar = 0.0; + pThis->TurretRecoil.Fire(); + } + + if (auto barrelIndex = (pTypeExt->ExtraTurretCount || pTypeExt->ExtraBarrelCount) + ? (pThis->CurrentBurstIndex % ((pTypeExt->ExtraBarrelCount + 1) * (pTypeExt->ExtraTurretCount + 1))) + : 0) + { + barrelIndex -= 1; + this->ExtraBarrelRecoil[barrelIndex].TravelSoFar = 0.0; + this->ExtraBarrelRecoil[barrelIndex].Fire(); + } + else + { + pThis->BarrelRecoil.TravelSoFar = 0.0; + pThis->BarrelRecoil.Fire(); + } +} + +void TechnoExt::ExtData::UpdateRecoilData() +{ + if (!this->TypeExtData->OwnerObject()->TurretRecoil) + return; + + const auto pThis = this->OwnerObject(); + + pThis->TurretRecoil.Update(); + pThis->BarrelRecoil.Update(); + + for (auto& data : this->ExtraTurretRecoil) + data.Update(); + + for (auto& data : this->ExtraBarrelRecoil) + data.Update(); +} diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 121ece2f9a..c24a489343 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -932,6 +932,8 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->SpecialTracked) .Process(this->FallingDownTracked) .Process(this->JumpjetStraightAscend) + .Process(this->ExtraTurretRecoil) + .Process(this->ExtraBarrelRecoil) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 89ed549edf..5298cbdfe7 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -103,6 +103,9 @@ class TechnoExt bool JumpjetStraightAscend; // Is set to true jumpjet units will ascend straight and do not adjust rotation or position during it. + std::vector ExtraTurretRecoil; + std::vector ExtraBarrelRecoil; + ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } , Shield {} @@ -169,6 +172,8 @@ class TechnoExt , SpecialTracked { false } , FallingDownTracked { false } , JumpjetStraightAscend { false } + , ExtraTurretRecoil {} + , ExtraBarrelRecoil {} { } void OnEarlyUpdate(); @@ -207,6 +212,9 @@ class TechnoExt int ApplyForceWeaponInRange(AbstractClass* pTarget); void ResetDelayedFireTimer(); void UpdateTintValues(); + void InitializeRecoilData(); + void UpdateRecoilData(); + void RecordRecoilData(); void AmmoAutoConvertActions(); @@ -254,7 +262,11 @@ class TechnoExt static bool HasAvailableDock(TechnoClass* pThis); static bool HasRadioLinkWithDock(TechnoClass* pThis); - static CoordStruct GetFLHAbsoluteCoords(TechnoClass* pThis, CoordStruct flh, bool turretFLH = false); + + static Matrix3D GetTransform(TechnoClass* pThis, VoxelIndexKey* pKey = nullptr, bool isShadow = false); + static Matrix3D GetFLHMatrix(TechnoClass* pThis, const CoordStruct& flh, bool isOnTurret, double factor = 1.0, bool isShadow = false, int turIdx = -1); + static Matrix3D TransformFLHForTurret(TechnoClass* pThis, Matrix3D mtx, bool isOnTurret, double factor = 1.0, int turIdx = -1); + static CoordStruct GetFLHAbsoluteCoords(TechnoClass* pThis, const CoordStruct& flh, bool isOnTurret = false, int turIdx = -1); static CoordStruct GetBurstFLH(TechnoClass* pThis, int weaponIndex, bool& FLHFound); static CoordStruct GetSimpleFLH(InfantryClass* pThis, int weaponIndex, bool& FLHFound); diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 9f38a1b6e8..26c22d0b93 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -716,7 +716,13 @@ DEFINE_HOOK(0x6FF0DD, TechnoClass_FireAt_TurretRecoil, 0x6) GET_STACK(WeaponTypeClass* const, pWeapon, STACK_OFFSET(0xB0, -0x70)); - return WeaponTypeExt::ExtMap.Find(pWeapon)->TurretRecoil_Suppress ? SkipGameCode : 0; + if (!WeaponTypeExt::ExtMap.Find(pWeapon)->TurretRecoil_Suppress) + { + GET(TechnoClass* const, pThis, ESI); + TechnoExt::ExtMap.Find(pThis)->RecordRecoilData(); + } + + return SkipGameCode; } DEFINE_HOOK(0x6FF905, TechnoClass_FireAt_FireOnce, 0x6) @@ -912,6 +918,7 @@ DEFINE_HOOK(0x6F3AEB, TechnoClass_GetFLH, 0x6) bool allowOnTurret = true; CoordStruct flh = CoordStruct::Empty; + auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); if (weaponIndex >= 0) { @@ -933,7 +940,6 @@ DEFINE_HOOK(0x6F3AEB, TechnoClass_GetFLH, 0x6) else { const int index = -weaponIndex - 1; - auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); if (index < static_cast(pTypeExt->AlternateFLHs.size())) flh = pTypeExt->AlternateFLHs[index]; @@ -942,7 +948,12 @@ DEFINE_HOOK(0x6F3AEB, TechnoClass_GetFLH, 0x6) allowOnTurret = false; } - *pCoords = TechnoExt::GetFLHAbsoluteCoords(pThis, flh, allowOnTurret); + auto turIdx = -1; + + if (pTypeExt->BurstPerTurret > 0) + turIdx = ((pThis->CurrentBurstIndex / pTypeExt->BurstPerTurret) % (pTypeExt->ExtraTurretCount + 1)) - 1; + + *pCoords = TechnoExt::GetFLHAbsoluteCoords(pThis, flh, allowOnTurret, turIdx); R->EAX(pCoords); return SkipGameCode; diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index f61b0162b2..d5ccfcd674 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -221,6 +221,7 @@ DEFINE_HOOK(0x6F42F7, TechnoClass_Init, 0x2) pExt->InitializeAttachEffects(); pExt->InitializeDisplayInfo(); pExt->InitializeLaserTrails(); + pExt->InitializeRecoilData(); if (!pExt->AE.HasTint && (!pShieldType || !pShieldType->HasTint() || pShieldType->Strength <= 0)) pExt->UpdateTintValues(); diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index faa10a6775..8cae15b930 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -18,10 +18,10 @@ TechnoTypeExt::ExtContainer TechnoTypeExt::ExtMap; bool TechnoTypeExt::SelectWeaponMutex = false; -void TechnoTypeExt::ExtData::ApplyTurretOffset(Matrix3D* mtx, double factor) +void TechnoTypeExt::ExtData::ApplyTurretOffset(Matrix3D* mtx, double factor, int turIdx) { // Does not verify if the offset actually has all values parsed as it makes no difference, it will be 0 for the unparsed ones either way. - const auto offset = this->TurretOffset.GetEx(); + const auto offset = turIdx < 0 ? static_cast(this->TurretOffset.GetEx()) : &this->ExtraTurretOffsets[turIdx]; const float x = static_cast(offset->X * factor); const float y = static_cast(offset->Y * factor); const float z = static_cast(offset->Z * factor); @@ -1208,6 +1208,44 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AlternateFLHs.emplace_back(alternateFLH); } + // Extra barrel/turret offsets + this->BarrelOverTurret.Read(exArtINI, pArtSection, "BarrelOverTurret"); + this->BarrelOffset.Read(exArtINI, pArtSection, "BarrelOffset"); + this->ExtraBarrelCount.Read(exArtINI, pArtSection, "ExtraBarrelCount"); + + if (this->ExtraBarrelCount > 0) + { + for (int i = 0; i < this->ExtraBarrelCount; ++i) + { + Valueable extraBarrelOffset; + _snprintf_s(tempBuffer, sizeof(tempBuffer), "ExtraBarrelOffset%u", i); + extraBarrelOffset.Read(exArtINI, pArtSection, tempBuffer); + this->ExtraBarrelOffsets.emplace_back(extraBarrelOffset.Get()); + } + } + else + { + this->ExtraBarrelCount = 0; + } + + this->ExtraTurretCount.Read(exArtINI, pArtSection, "ExtraTurretCount"); + + if (this->ExtraTurretCount > 0) + { + for (int i = 0; i < this->ExtraTurretCount; ++i) + { + Valueable extraTurretOffset; + _snprintf_s(tempBuffer, sizeof(tempBuffer), "ExtraTurretOffset%u", i); + extraTurretOffset.Read(exArtINI, pArtSection, tempBuffer); + this->ExtraTurretOffsets.emplace_back(extraTurretOffset.Get()); + } + this->BurstPerTurret.Read(exArtINI, pArtSection, "BurstPerTurret"); + } + else + { + this->ExtraTurretCount = 0; + } + // Parasitic types this->AttachEffects.LoadFromINI(pINI, pSection); @@ -1698,6 +1736,14 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->InfantryAutoDeploy) .Process(this->TurretResponse) + + .Process(this->BarrelOverTurret) + .Process(this->BarrelOffset) + .Process(this->ExtraBarrelCount) + .Process(this->ExtraBarrelOffsets) + .Process(this->ExtraTurretCount) + .Process(this->ExtraTurretOffsets) + .Process(this->BurstPerTurret) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 35c5ac4966..7f824a4f5d 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -445,6 +445,14 @@ class TechnoTypeExt Nullable TurretResponse; + Nullable BarrelOverTurret; + Valueable BarrelOffset; + Valueable ExtraBarrelCount; + std::vector ExtraBarrelOffsets; + Valueable ExtraTurretCount; + std::vector ExtraTurretOffsets; + Valueable BurstPerTurret; + ExtData(TechnoTypeClass* OwnerObject) : Extension(OwnerObject) , HealthBar_Hide { false } , HealthBar_HidePips { false } @@ -840,6 +848,15 @@ class TechnoTypeExt , InfantryAutoDeploy {} , TurretResponse {} + + , BarrelOverTurret { } + , BarrelOffset { 0 } + , ExtraBarrelCount { 0 } + , ExtraBarrelOffsets { } + , ExtraTurretCount { 0 } + , ExtraTurretOffsets { } + , BurstPerTurret { 0 } + { } virtual ~ExtData() = default; @@ -853,7 +870,7 @@ class TechnoTypeExt void LoadFromINIByWhatAmI(INI_EX& exINI, const char* pSection, INI_EX& exArtINI, const char* pArtSection); - void ApplyTurretOffset(Matrix3D* mtx, double factor = 1.0); + void ApplyTurretOffset(Matrix3D* mtx, double factor = 1.0, int turIdx = -1); void CalculateSpawnerRange(); bool IsSecondary(int nWeaponIndex); @@ -883,7 +900,7 @@ class TechnoTypeExt static ExtContainer ExtMap; static bool SelectWeaponMutex; - static void ApplyTurretOffset(TechnoTypeClass* pType, Matrix3D* mtx, double factor = 1.0); + static void ApplyTurretOffset(TechnoTypeClass* pType, Matrix3D* mtx, double factor = 1.0, int turIdx = -1); static TechnoTypeClass* GetTechnoType(ObjectTypeClass* pType); static TechnoClass* CreateUnit(CreateUnitTypeClass* pCreateUnit, DirType facing, DirType* secondaryFacing, diff --git a/src/Ext/TechnoType/Hooks.MatrixOp.cpp b/src/Ext/TechnoType/Hooks.MatrixOp.cpp index a9c575e15a..bc23ea769f 100644 --- a/src/Ext/TechnoType/Hooks.MatrixOp.cpp +++ b/src/Ext/TechnoType/Hooks.MatrixOp.cpp @@ -18,9 +18,9 @@ DEFINE_REFERENCE(double, Pixel_Per_Lepton, 0xB1D008) #pragma region FLH_Turrets -void TechnoTypeExt::ApplyTurretOffset(TechnoTypeClass* pType, Matrix3D* mtx, double factor) +void TechnoTypeExt::ApplyTurretOffset(TechnoTypeClass* pType, Matrix3D* mtx, double factor, int turIdx) { - TechnoTypeExt::ExtMap.Find(pType)->ApplyTurretOffset(mtx, factor); + TechnoTypeExt::ExtMap.Find(pType)->ApplyTurretOffset(mtx, factor, turIdx); } DEFINE_HOOK(0x6F3E6E, TechnoClass_ActionLines_TurretMultiOffset, 0x0) @@ -35,14 +35,16 @@ DEFINE_HOOK(0x6F3E6E, TechnoClass_ActionLines_TurretMultiOffset, 0x0) DEFINE_HOOK(0x73B780, UnitClass_DrawVXL_TurretMultiOffset, 0x0) { - GET(TechnoTypeClass*, technoType, EAX); + enum { CleanFlag = 0x73B78A, SkipFlag = 0x73B790 }; - auto const pTypeData = TechnoTypeExt::ExtMap.Find(technoType); + GET(TechnoTypeClass* const, pDrawType, EBX); - if (*pTypeData->TurretOffset.GetEx() == CoordStruct { 0, 0, 0 }) - return 0x73B78A; + auto const pDrawTypeExt = TechnoTypeExt::ExtMap.Find(pDrawType); - return 0x73B790; + return (*pDrawTypeExt->TurretOffset.GetEx() == CoordStruct::Empty + && pDrawTypeExt->ExtraTurretCount <= 0 + && pDrawTypeExt->ExtraBarrelCount <= 0) + ? CleanFlag : SkipFlag; } struct AresTechnoTypeExt @@ -75,108 +77,204 @@ DEFINE_HOOK(0x73BA12, UnitClass_DrawAsVXL_RewriteTurretDrawing, 0x6) // base matrix const auto mtx = Matrix3D::VoxelDefaultMatrix * drawMatrix; + const auto pExt = TechnoExt::ExtMap.Find(pThis); const auto pDrawTypeExt = TechnoTypeExt::ExtMap.Find(pDrawType); const bool notChargeTurret = pThis->Type->TurretCount <= 0 || pThis->Type->IsGattling; auto getTurretVoxel = [pDrawType, notChargeTurret, currentTurretNumber]() -> VoxelStruct* - { - if (notChargeTurret) - return &pDrawType->TurretVoxel; + { + if (notChargeTurret) + return &pDrawType->TurretVoxel; - // Not considering the situation where there is no Ares and the limit is exceeded - if (currentTurretNumber < 18 || !AresHelper::CanUseAres) - return &pDrawType->ChargerTurrets[currentTurretNumber]; + // Not considering the situation where there is no Ares and the limit is exceeded + if (currentTurretNumber < 18 || !AresHelper::CanUseAres) + return &pDrawType->ChargerTurrets[currentTurretNumber]; - auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); - return &aresTypeExt->ChargerTurrets[currentTurretNumber - 18]; - }; + auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); + return &aresTypeExt->ChargerTurrets[currentTurretNumber - 18]; + }; const auto pTurretVoxel = getTurretVoxel(); - // When in recoiling or have no cache, need to recalculate drawing matrix - const bool inRecoil = pDrawType->TurretRecoil && (pThis->TurretRecoil.State != RecoilData::RecoilState::Inactive || pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive); - const bool shouldRedraw = !haveTurretCache || haveBar && !haveBarrelCache || inRecoil; - - // When in recoiling, need to bypass cache and draw without saving - const auto turKey = inRecoil ? -1 : flags; - const auto turCache = inRecoil ? nullptr : &pDrawType->VoxelTurretWeaponCache; + auto getBarrelVoxel = [pDrawType, notChargeTurret, currentTurretNumber]() -> VoxelStruct* + { + if (notChargeTurret) + return &pDrawType->BarrelVoxel; - auto getTurretMatrix = [=, &mtx]() -> Matrix3D - { - auto mtxTurret = mtx; - pDrawTypeExt->ApplyTurretOffset(&mtxTurret, Pixel_Per_Lepton); - mtxTurret.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); + // Not considering the situation where there is no Ares and the limit is exceeded + if (currentTurretNumber < 18 || !AresHelper::CanUseAres) + return &pDrawType->ChargerBarrels[currentTurretNumber]; - if (pThis->TurretRecoil.State != RecoilData::RecoilState::Inactive) - mtxTurret.TranslateX(-pThis->TurretRecoil.TravelSoFar); + auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); + return &aresTypeExt->ChargerBarrels[currentTurretNumber - 18]; + }; + const auto pBarrelVoxel = haveBar ? getBarrelVoxel() : nullptr; - return mtxTurret; - }; - auto mtxTurret = shouldRedraw ? getTurretMatrix() : mtx; constexpr BlitterFlags blit = BlitterFlags::Alpha | BlitterFlags::Flat; + // When in recoiling or have no cache, need to recalculate drawing matrix + const bool shouldRedraw = !haveTurretCache || haveBar && !haveBarrelCache; - // Only when there is a barrel will its calculation and drawing be considered - if (haveBar) - { - auto drawBarrel = [=, &mtxTurret, &mtx]() + // The orientation of the turret can affect the layer order of the barrel and turret + const auto turretDir = pThis->SecondaryFacing.Current().GetFacing<4>(); + const bool barrelOverTechno = pDrawTypeExt->BarrelOverTurret.Get(turretDir != 0 && turretDir != 3); + + auto drawTurret = [=, &mtx](int turIdx) { - // When in recoiling, need to bypass cache and draw without saving - const auto brlKey = inRecoil ? -1 : flags; - const auto brlCache = inRecoil ? nullptr : &pDrawType->VoxelTurretBarrelCache; + const auto pTurData = pDrawType->TurretRecoil ? ((turIdx < 0) ? &pThis->TurretRecoil : &pExt->ExtraTurretRecoil[turIdx]) : nullptr; + const bool turretInRecoil = pTurData && pTurData->State != RecoilData::RecoilState::Inactive; + + // When in recoiling or is not main turret, need to bypass cache and draw without saving + const bool turShouldRedraw = turretInRecoil || turIdx >= 0; + const auto turKey = turShouldRedraw ? -1 : flags; + const auto turCache = turShouldRedraw ? nullptr : &pDrawType->VoxelTurretWeaponCache; + + auto shouldCalculateMatrix = [=]() + { + if (!haveBar) + return false; + + if (pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive) + return true; - auto getBarrelMatrix = [=, &mtxTurret, &mtx]() -> Matrix3D + return pDrawTypeExt->ExtraBarrelCount.Get() > 0; + }; + auto getTurretMatrix = [=, &mtx]() -> Matrix3D + { + auto mtx_turret = mtx; + pDrawTypeExt->ApplyTurretOffset(&mtx_turret, Pixel_Per_Lepton, turIdx); + mtx_turret.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); + + if (turretInRecoil) + mtx_turret.TranslateX(-pTurData->TravelSoFar); + + return mtx_turret; + }; + auto mtx_turret = (shouldRedraw || turShouldRedraw || shouldCalculateMatrix()) ? getTurretMatrix() : mtx; + + auto drawBarrel = [=, &mtx_turret, &mtx](int brlIdx) + { + const auto idx = brlIdx + ((turIdx + 1) * (pDrawTypeExt->ExtraBarrelCount.Get() + 1)); + const auto pBrlData = pDrawType->TurretRecoil ? ((idx < 0) ? &pThis->BarrelRecoil : &pExt->ExtraBarrelRecoil[idx]) : nullptr; + const bool barrelInRecoil = pBrlData && pBrlData->State != RecoilData::RecoilState::Inactive; + + // When in recoiling or is not main barrel, need to bypass cache and draw without saving + const bool brlShouldRedraw = turretInRecoil || barrelInRecoil || idx >= 0; + const auto brlKey = brlShouldRedraw ? -1 : flags; + const auto brlCache = brlShouldRedraw ? nullptr : &pDrawType->VoxelTurretBarrelCache; + + auto getBarrelMatrix = [=, &mtx_turret, &mtx]() -> Matrix3D + { + auto mtx_barrel = mtx_turret; + mtx_barrel.Translate(-mtx.Row[0].W, -mtx.Row[1].W, -mtx.Row[2].W); + mtx_barrel.RotateY(static_cast(-pThis->BarrelFacing.Current().GetRadian<32>())); + const auto offset = ((brlIdx < 0) ? pDrawTypeExt->BarrelOffset.Get() : pDrawTypeExt->ExtraBarrelOffsets[brlIdx]); + mtx_barrel.TranslateY(static_cast(Pixel_Per_Lepton * offset)); + + if (barrelInRecoil) + mtx_barrel.TranslateX(-pBrlData->TravelSoFar); + + mtx_barrel.Translate(mtx.Row[0].W, mtx.Row[1].W, mtx.Row[2].W); + return mtx_barrel; + }; + auto mtx_barrel = (shouldRedraw || brlShouldRedraw) ? getBarrelMatrix() : mtx; + + // draw barrel + pThis->Draw_A_VXL(pBarrelVoxel, hvaFrameIdx, brlKey, brlCache, rect, center, &mtx_barrel, brightness, blit, 0); + }; + + auto drawBarrels = [&drawBarrel, pDrawTypeExt, turretDir]() + { + const auto exBrlCount = pDrawTypeExt->ExtraBarrelCount.Get(); + + if (exBrlCount > 0) + { + std::vector barrels; + barrels.emplace_back(-1); + + for (int i = 0; i < exBrlCount; ++i) + barrels.emplace_back(i); + + const auto barrelsSize = barrels.size(); + const bool faceRight = turretDir == 0 || turretDir == 1; + std::sort(&barrels[0], &barrels[barrelsSize], [pDrawTypeExt, faceRight](const auto& idxA, const auto& idxB) + { + const auto offsetA = idxA < 0 ? pDrawTypeExt->BarrelOffset.Get() : pDrawTypeExt->ExtraBarrelOffsets[idxA]; + const auto offsetB = idxB < 0 ? pDrawTypeExt->BarrelOffset.Get() : pDrawTypeExt->ExtraBarrelOffsets[idxB]; + + return faceRight ? (offsetA > offsetB) : (offsetA <= offsetB); + }); + + for (const auto& i : barrels) + drawBarrel(i); + } + else + { + drawBarrel(-1); + } + }; + + if (barrelOverTechno) { - auto mtxBarrel = mtxTurret; - mtxBarrel.Translate(-mtx.Row[0].W, -mtx.Row[1].W, -mtx.Row[2].W); - mtxBarrel.RotateY(static_cast(-pThis->BarrelFacing.Current().GetRadian<32>())); + // draw turret + pThis->Draw_A_VXL(pTurretVoxel, hvaFrameIdx, turKey, turCache, rect, center, &mtx_turret, brightness, blit, 0); - if (pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive) - mtxBarrel.TranslateX(-pThis->BarrelRecoil.TravelSoFar); + if (haveBar) + drawBarrels(); + } + else + { + if (haveBar) + drawBarrels(); - mtxBarrel.Translate(mtx.Row[0].W, mtx.Row[1].W, mtx.Row[2].W); - return mtxBarrel; - }; - auto mtxBarrel = shouldRedraw ? getBarrelMatrix() : mtx; + // draw turret + pThis->Draw_A_VXL(pTurretVoxel, hvaFrameIdx, turKey, turCache, rect, center, &mtx_turret, brightness, blit, 0); + } + }; - auto getBarrelVoxel = [pDrawType, notChargeTurret, currentTurretNumber]() -> VoxelStruct* + auto drawTurrets = [&drawTurret, pThis, pDrawTypeExt]() + { + const auto exTurCount = pDrawTypeExt->ExtraTurretCount.Get(); + + if (exTurCount > 0) { - if (notChargeTurret) - return &pDrawType->BarrelVoxel; + std::vector turrets; + turrets.emplace_back(-1); - // Not considering the situation where there is no Ares and the limit is exceeded - if (currentTurretNumber < 18 || !AresHelper::CanUseAres) - return &pDrawType->ChargerBarrels[currentTurretNumber]; + for (int i = 0; i < exTurCount; ++i) + turrets.emplace_back(i); - auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); - return &aresTypeExt->ChargerBarrels[currentTurretNumber - 18]; - }; - const auto pBarrelVoxel = getBarrelVoxel(); + const auto turretsSize = turrets.size(); + std::sort(&turrets[0], &turrets[turretsSize], [pThis, pDrawTypeExt](const auto& idxA, const auto& idxB) + { + const auto pOffsetA = idxA < 0 ? static_cast(pDrawTypeExt->TurretOffset.GetEx()) : &pDrawTypeExt->ExtraTurretOffsets[idxA]; + const auto pOffsetB = idxB < 0 ? static_cast(pDrawTypeExt->TurretOffset.GetEx()) : &pDrawTypeExt->ExtraTurretOffsets[idxB]; - // draw barrel - pThis->Draw_A_VXL(pBarrelVoxel, hvaFrameIdx, brlKey, brlCache, rect, center, &mtxBarrel, brightness, blit, 0); - }; + if (pOffsetA->Z < pOffsetB->Z) + return true; - const auto turretDir = pThis->SecondaryFacing.Current().GetFacing<4>(); + if (pOffsetA->Z > pOffsetB->Z) + return false; - // The orientation of the turret can affect the layer order of the barrel and turret - if (turretDir != 0 && turretDir != 3) - { - // draw turret - pThis->Draw_A_VXL(pTurretVoxel, hvaFrameIdx, turKey, turCache, rect, center, &mtxTurret, brightness, blit, 0); + const auto pointA = TacticalClass::Instance->CoordsToClient(TechnoExt::GetFLHAbsoluteCoords(pThis, *pOffsetA)).first; + const auto pointB = TacticalClass::Instance->CoordsToClient(TechnoExt::GetFLHAbsoluteCoords(pThis, *pOffsetB)).first; - drawBarrel(); - } - else - { - drawBarrel(); + if (pointA.Y < pointB.Y) + return true; - // draw turret - pThis->Draw_A_VXL(pTurretVoxel, hvaFrameIdx, turKey, turCache, rect, center, &mtxTurret, brightness, blit, 0); - } - } - else - { - pThis->Draw_A_VXL(pTurretVoxel, hvaFrameIdx, turKey, turCache, rect, center, &mtxTurret, brightness, blit, 0); - } + if (pointA.Y > pointB.Y) + return false; + + return pointA.X <= pointB.X; + }); + + for (const auto& i : turrets) + drawTurret(i); + } + else + { + drawTurret(-1); + } + }; + drawTurrets(); return SkipGameCode; } @@ -542,19 +640,22 @@ DEFINE_FUNCTION_JUMP(VTABLE, 0x7F5A4C, TunnelLocomotionClass_ShadowMatrix); DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) { - GET(UnitClass*, pThis, EBP); enum { SkipDrawing = 0x73C5C9 }; + + GET(UnitClass* const, pThis, EBP); + auto const loco = pThis->Locomotor.GetInterfacePtr(); + if (pThis->CloakState != CloakState::Uncloaked || pThis->Type->NoShadow || !loco->Is_To_Have_Shadow()) return SkipDrawing; REF_STACK(Matrix3D, shadowMatrix, STACK_OFFSET(0x1C4, -0x130)); GET_STACK(VoxelIndexKey, vxlIndexKey, STACK_OFFSET(0x1C4, -0x1B0)); - LEA_STACK(RectangleStruct*, bnd, STACK_OFFSET(0x1C4, 0xC)); - LEA_STACK(Point2D*, pt, STACK_OFFSET(0x1C4, -0x1A4)); + LEA_STACK(RectangleStruct* const, bnd, STACK_OFFSET(0x1C4, 0xC)); + LEA_STACK(Point2D* const, pt, STACK_OFFSET(0x1C4, -0x1A4)); GET_STACK(Surface* const, surface, STACK_OFFSET(0x1C4, -0x1A8)); - GET(UnitTypeClass*, pDrawType, EBX); + GET(TechnoTypeClass* const, pDrawType, EBX); // This is not necessarily pThis->Type : UnloadingClass or WaterImage // This is the very reason I need to do this here, there's no less hacky way to get this Type from those inner calls @@ -607,14 +708,14 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) } return &pDrawType->MainVoxel; }; - auto const main_vxl = GetMainVoxel(); auto shadowPoint = loco->Shadow_Point(); - auto why = *pt + shadowPoint; + auto shadowCenter = *pt + shadowPoint; float arf = pThis->AngleRotatedForwards; float ars = pThis->AngleRotatedSideways; + // lazy, don't want to hook inside Shadow_Matrix if (std::abs(ars) >= 0.005 || std::abs(arf) >= 0.005) { @@ -664,147 +765,147 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) if (height > 0) shadowPoint.Y += 1; - if (!pDrawType->UseTurretShadow) + const bool notUseTurretShadow = pDrawType->WhatAmI() != AbstractType::UnitType || !static_cast(pDrawType)->UseTurretShadow; + + if (notUseTurretShadow) { if (pDrawTypeExt->ShadowIndices.empty()) { if (pDrawType->ShadowIndex >= 0 && pDrawType->ShadowIndex < main_vxl->HVA->LayerCount) { - pThis->DrawVoxelShadow( - main_vxl, - pDrawType->ShadowIndex, - vxlIndexKey, - &pDrawType->VoxelShadowCache, - bnd, - &why, - &mtx, - true, - surface, - shadowPoint - ); + pThis->DrawVoxelShadow(main_vxl, pDrawType->ShadowIndex, vxlIndexKey, + &pDrawType->VoxelShadowCache, bnd, &shadowCenter, &mtx, true, surface, shadowPoint); } } else { for (auto& [index, _] : pDrawTypeExt->ShadowIndices) { - pThis->DrawVoxelShadow( - main_vxl, - index, - index == pDrawType->ShadowIndex ? vxlIndexKey : VoxelIndexKey(-1), - &pDrawType->VoxelShadowCache, - bnd, - &why, - &mtx, - index == pDrawType->ShadowIndex, - surface, - shadowPoint - ); + pThis->DrawVoxelShadow(main_vxl, index, index == pDrawType->ShadowIndex ? vxlIndexKey : VoxelIndexKey(-1), + &pDrawType->VoxelShadowCache, bnd, &shadowCenter, &mtx, index == pDrawType->ShadowIndex, surface, shadowPoint); } } } - if (main_vxl == &pDrawType->TurretVoxel || (!pDrawType->UseTurretShadow && !pDrawTypeExt->TurretShadow.Get(RulesExt::Global()->DrawTurretShadow))) + if (main_vxl == &pDrawType->TurretVoxel || (notUseTurretShadow && !pDrawTypeExt->TurretShadow.Get(RulesExt::Global()->DrawTurretShadow))) return SkipDrawing; - auto GetTurretVoxel = [pDrawType](int idx) ->VoxelStruct* - { - if (pDrawType->TurretCount == 0 || pDrawType->IsGattling || idx < 0) - return &pDrawType->TurretVoxel; + auto getTurretVoxel = [pDrawType](int idx) ->VoxelStruct* + { + if (pDrawType->TurretCount == 0 || pDrawType->IsGattling || idx < 0) + return &pDrawType->TurretVoxel; - if (idx < 18) - return &pDrawType->ChargerTurrets[idx]; + if (idx < 18) + return &pDrawType->ChargerTurrets[idx]; - if (AresHelper::CanUseAres) - { - auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); - return &aresTypeExt->ChargerTurrets[idx - 18]; - } + if (AresHelper::CanUseAres) + { + auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); + return &aresTypeExt->ChargerTurrets[idx - 18]; + } - return nullptr; - }; + return nullptr; + }; + const auto pTurretVoxel = getTurretVoxel(pThis->CurrentTurretNumber); - auto GetBarrelVoxel = [pDrawType](int idx)->VoxelStruct* - { - if (pDrawType->TurretCount == 0 || pDrawType->IsGattling || idx < 0) - return &pDrawType->BarrelVoxel; + if (!(pTurretVoxel && pTurretVoxel->VXL && pTurretVoxel->HVA)) + return SkipDrawing; - if (idx < 18) - return &pDrawType->ChargerBarrels[idx]; + if (vxlIndexKey.Is_Valid_Key()) + vxlIndexKey.MinorVoxel.TurretFacing = pThis->SecondaryFacing.Current().GetFacing<32>(); - if (AresHelper::CanUseAres) + auto getBarrelVoxel = [pDrawType](int idx)->VoxelStruct* { - auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); - return &aresTypeExt->ChargerBarrels[idx - 18]; - } + if (pDrawType->TurretCount == 0 || pDrawType->IsGattling || idx < 0) + return &pDrawType->BarrelVoxel; - return nullptr; - }; + if (idx < 18) + return &pDrawType->ChargerBarrels[idx]; - pDrawTypeExt->ApplyTurretOffset(&mtx, Pixel_Per_Lepton); - mtx.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); + if (AresHelper::CanUseAres) + { + auto* aresTypeExt = reinterpret_cast(pDrawType->align_2FC); + return &aresTypeExt->ChargerBarrels[idx - 18]; + } - const bool inRecoil = pDrawType->TurretRecoil && pThis->TurretRecoil.State != RecoilData::RecoilState::Inactive; - if (inRecoil) - mtx.TranslateX(-pThis->TurretRecoil.TravelSoFar); + return nullptr; + }; + const auto pBarrelVoxel = getBarrelVoxel(pThis->CurrentTurretNumber); - const auto tur = GetTurretVoxel(pThis->CurrentTurretNumber); - if (!(tur && tur->VXL && tur->HVA)) - return SkipDrawing; + const auto haveBar = pBarrelVoxel && pBarrelVoxel->VXL && pBarrelVoxel->HVA && !pBarrelVoxel->VXL->Initialized; + auto pCache = &pDrawType->VoxelShadowCache; + const auto pExt = TechnoExt::ExtMap.Find(pThis); - const auto bar = GetBarrelVoxel(pThis->CurrentTurretNumber); - const auto haveBar = bar && bar->VXL && bar->HVA && !bar->VXL->Initialized; - if (vxlIndexKey.Is_Valid_Key()) - vxlIndexKey.MinorVoxel.TurretFacing = pThis->SecondaryFacing.Current().GetFacing<32>(); + // Not available under multiple turrets/barrels due to different base positions + if (notUseTurretShadow) + pCache = (haveBar || pTurretVoxel != &pDrawType->TurretVoxel) ? nullptr : reinterpret_cast(&pDrawType->VoxelTurretBarrelCache); - auto* cache = &pDrawType->VoxelShadowCache; - if (!pDrawType->UseTurretShadow) - { - if (haveBar) + auto drawTurretShadow = [&](int turIdx) { - cache = nullptr; - } - else + auto mtx_turret = mtx; + pDrawTypeExt->ApplyTurretOffset(&mtx_turret, Pixel_Per_Lepton, turIdx); + mtx_turret.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); + + const auto pTurData = pDrawType->TurretRecoil ? ((turIdx >= 0) ? &pExt->ExtraTurretRecoil[turIdx] : &pThis->TurretRecoil) : nullptr; + const auto turretInRecoil = pTurData && pTurData->State != RecoilData::RecoilState::Inactive; + const auto shouldRedraw = turretInRecoil || turIdx >= 0; + + if (turretInRecoil) + mtx_turret.TranslateX(-pTurData->TravelSoFar); + + pThis->DrawVoxelShadow(pTurretVoxel, 0, (shouldRedraw ? VoxelIndexKey(-1) : vxlIndexKey), (shouldRedraw ? nullptr : pCache), + bnd, &shadowCenter, &mtx_turret, (!shouldRedraw && pCache != nullptr), surface, shadowPoint); + + if (!haveBar) + return; + + auto drawBarrelShadow = [=, &mtx_turret, &mtx, &shadowCenter](int brlIdx) + { + const auto idx = brlIdx + ((turIdx + 1) * (pDrawTypeExt->ExtraBarrelCount + 1)); + const auto pBrlData = pDrawType->TurretRecoil ? ((idx >= 0) ? &pExt->ExtraBarrelRecoil[idx] : &pThis->BarrelRecoil) : nullptr; + const auto barrelInRecoil = pBrlData && pBrlData->State != RecoilData::RecoilState::Inactive; + + auto mtx_barrel = mtx_turret; + mtx_barrel.Translate(-mtx.Row[0].W, -mtx.Row[1].W, -mtx.Row[2].W); + mtx_barrel.RotateY(static_cast(-pThis->BarrelFacing.Current().GetRadian<32>())); + const auto offset = ((brlIdx >= 0) ? pDrawTypeExt->ExtraBarrelOffsets[brlIdx] : pDrawTypeExt->BarrelOffset.Get()); + mtx_barrel.TranslateY(static_cast(Pixel_Per_Lepton * offset)); + + if (barrelInRecoil) + mtx_barrel.TranslateX(-pBrlData->TravelSoFar); + + mtx_barrel.Translate(mtx.Row[0].W, mtx.Row[1].W, mtx.Row[2].W); + pThis->DrawVoxelShadow(pBarrelVoxel, 0, VoxelIndexKey(-1), nullptr, bnd, &shadowCenter, &mtx_barrel, false, surface, shadowPoint); + }; + + auto drawBarrelsShadow = [&drawBarrelShadow, pDrawTypeExt]() + { + drawBarrelShadow(-1); + + const auto exBrlCount = pDrawTypeExt->ExtraBarrelCount.Get(); + + if (exBrlCount > 0) + { + for (int i = 0; i < exBrlCount; ++i) + drawBarrelShadow(i); + } + }; + drawBarrelsShadow(); + }; + + auto drawTurretsShadow = [&drawTurretShadow, pDrawTypeExt]() { - cache = tur != &pDrawType->TurretVoxel - ? nullptr // man what can I say, you are fucked, for now - : reinterpret_cast(&pDrawType->VoxelTurretBarrelCache); // excuse me - } - } + drawTurretShadow(-1); - pThis->DrawVoxelShadow( - tur, - 0, - (inRecoil ? VoxelIndexKey(-1) : vxlIndexKey), - (inRecoil ? nullptr : cache), - bnd, - &why, - &mtx, - (!inRecoil && cache != nullptr), - surface, - shadowPoint - ); - - if (haveBar)// you are utterly fucked, for now - { - if (pDrawType->TurretRecoil && pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive) - mtx.TranslateX(-pThis->BarrelRecoil.TravelSoFar); - - mtx.ScaleX(static_cast(Math::cos(-pThis->BarrelFacing.Current().GetRadian<32>()))); - - pThis->DrawVoxelShadow( - bar, - 0, - VoxelIndexKey(-1), - nullptr, - bnd, - &why, - &mtx, - false, - surface, - shadowPoint - ); - } + const auto exTurCount = pDrawTypeExt->ExtraTurretCount.Get(); + + if (exTurCount > 0) + { + for (int i = 0; i < exTurCount; ++i) + drawTurretShadow(i); + } + }; + drawTurretsShadow(); return SkipDrawing; }