Skip to content

[Vanilla Enhancement] Restore turret recoil effect #1625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
- Fixed the bug that hover vehicle will sink if destroyed on bridge.
- Fixed the fact that when the selected unit is in a rearmed state, it can unconditionally use attack mouse on the target.
- When `Speed=0` or the TechnoTypes cell cannot move due to `MovementRestrictedTo`, vehicles cannot attack targets beyond the weapon's range. `Area Guard` and `Hunt` missions will also become ineffective.
- Fixed an issue that barrel anim data will be incorrectly overwritten by turret anim data if the techno's section exists in the map file.

## Fixes / interactions with other extensions

Expand Down Expand Up @@ -1840,6 +1841,10 @@ BarrelRecoverFrames=1 ; integer, game frames
TurretRecoil.Suppress=no ; boolean
```

```{note}
This is not a 1:1 restoration but a separate thing, not like it was in *Tiberian Sun*.
```

### Customize harvester dump amount

- Now you can limit how much ore the harvester can dump out per time, like it in Tiberium Sun.
Expand Down
154 changes: 79 additions & 75 deletions src/Ext/TechnoType/Hooks.MatrixOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,17 @@ DEFINE_HOOK(0x73BA12, UnitClass_DrawAsVXL_RewriteTurretDrawing, 0x6)
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<DummyTypeExtHere*>(pDrawType->align_2FC);
return &aresTypeExt->ChargerTurrets[currentTurretNumber - 18];
};
auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pDrawType->align_2FC);
return &aresTypeExt->ChargerTurrets[currentTurretNumber - 18];
};
const auto pTurretVoxel = getTurretVoxel();

// When in recoiling or have no cache, need to recalculate drawing matrix
Expand All @@ -101,16 +101,16 @@ DEFINE_HOOK(0x73BA12, UnitClass_DrawAsVXL_RewriteTurretDrawing, 0x6)
const auto turCache = inRecoil ? nullptr : reinterpret_cast<IndexClass<int, int>*>(&pDrawType->VoxelTurretWeaponCache);

auto getTurretMatrix = [=, &mtx]() -> Matrix3D
{
auto mtx_turret = mtx;
pDrawTypeExt->ApplyTurretOffset(&mtx_turret, Pixel_Per_Lepton);
mtx_turret.RotateZ(static_cast<float>(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>()));
{
auto mtx_turret = mtx;
pDrawTypeExt->ApplyTurretOffset(&mtx_turret, Pixel_Per_Lepton);
mtx_turret.RotateZ(static_cast<float>(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>()));

if (pThis->TurretRecoil.State != RecoilData::RecoilState::Inactive)
mtx_turret.TranslateX(-pThis->TurretRecoil.TravelSoFar);
if (pThis->TurretRecoil.State != RecoilData::RecoilState::Inactive)
mtx_turret.TranslateX(-pThis->TurretRecoil.TravelSoFar);

return mtx_turret;
};
return mtx_turret;
};
auto mtx_turret = shouldRedraw ? getTurretMatrix() : mtx;

// 10240u -> (BlitterFlags::Alpha | BlitterFlags::Flat);
Expand All @@ -119,42 +119,42 @@ DEFINE_HOOK(0x73BA12, UnitClass_DrawAsVXL_RewriteTurretDrawing, 0x6)
if (haveBar)
{
auto drawBarrel = [=, &mtx_turret, &mtx]()
{
// When in recoiling, need to bypass cache and draw without saving
const auto brlKey = inRecoil ? -1 : flags;
const auto brlCache = inRecoil ? nullptr : reinterpret_cast<IndexClass<int, int>*>(&pDrawType->VoxelTurretBarrelCache);

auto getBarrelMatrix = [=, &mtx_turret, &mtx]() -> Matrix3D
{
// When in recoiling, need to bypass cache and draw without saving
const auto brlKey = inRecoil ? -1 : flags;
const auto brlCache = inRecoil ? nullptr : reinterpret_cast<IndexClass<int, int>*>(&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<float>(-pThis->BarrelFacing.Current().GetRadian<32>()));

if (pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive)
mtx_barrel.TranslateX(-pThis->BarrelRecoil.TravelSoFar);

mtx_barrel.Translate(mtx.Row[0].W, mtx.Row[1].W, mtx.Row[2].W);
return mtx_barrel;
};
auto mtx_barrel = shouldRedraw ? getBarrelMatrix() : mtx;

auto getBarrelVoxel = [pDrawType, notChargeTurret, currentTurretNumber]() -> VoxelStruct*
{
if (notChargeTurret)
return &pDrawType->BarrelVoxel;

// Not considering the situation where there is no Ares and the limit is exceeded
if (currentTurretNumber < 18 || !AresHelper::CanUseAres)
return &pDrawType->ChargerBarrels[currentTurretNumber];

auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pDrawType->align_2FC);
return &aresTypeExt->ChargerBarrels[currentTurretNumber - 18];
};
const auto pBarrelVoxel = getBarrelVoxel();

// draw barrel
pThis->Draw_A_VXL(pBarrelVoxel, hvaFrameIdx, brlKey, brlCache, rect, center, &mtx_barrel, brightness, 10240u, 0);
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<float>(-pThis->BarrelFacing.Current().GetRadian<32>()));

if (pThis->BarrelRecoil.State != RecoilData::RecoilState::Inactive)
mtx_barrel.TranslateX(-pThis->BarrelRecoil.TravelSoFar);

mtx_barrel.Translate(mtx.Row[0].W, mtx.Row[1].W, mtx.Row[2].W);
return mtx_barrel;
};
auto mtx_barrel = shouldRedraw ? getBarrelMatrix() : mtx;

auto getBarrelVoxel = [pDrawType, notChargeTurret, currentTurretNumber]() -> VoxelStruct*
{
if (notChargeTurret)
return &pDrawType->BarrelVoxel;

// Not considering the situation where there is no Ares and the limit is exceeded
if (currentTurretNumber < 18 || !AresHelper::CanUseAres)
return &pDrawType->ChargerBarrels[currentTurretNumber];

auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pDrawType->align_2FC);
return &aresTypeExt->ChargerBarrels[currentTurretNumber - 18];
};
const auto pBarrelVoxel = getBarrelVoxel();

// draw barrel
pThis->Draw_A_VXL(pBarrelVoxel, hvaFrameIdx, brlKey, brlCache, rect, center, &mtx_barrel, brightness, 10240u, 0);
};

const auto turretDir = pThis->SecondaryFacing.Current().GetFacing<4>();

Expand Down Expand Up @@ -670,6 +670,7 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5)
if (uTypeExt->ShadowIndices.empty())
{
if (pType->ShadowIndex >= 0 && pType->ShadowIndex < main_vxl->HVA->LayerCount)
{
pThis->DrawVoxelShadow(
main_vxl,
pType->ShadowIndex,
Expand All @@ -682,10 +683,12 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5)
surface,
shadow_point
);
}
}
else
{
for (auto& [index, _] : uTypeExt->ShadowIndices)
{
pThis->DrawVoxelShadow(
main_vxl,
index,
Expand All @@ -698,45 +701,46 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5)
surface,
shadow_point
);
}
}
}

if (main_vxl == &pType->TurretVoxel || (!pType->UseTurretShadow && !uTypeExt->TurretShadow.Get(RulesExt::Global()->DrawTurretShadow)))
return SkipDrawing;

auto GetTurretVoxel = [pType](int idx) ->VoxelStruct*
{
if (pType->TurretCount == 0 || pType->IsGattling || idx < 0)
return &pType->TurretVoxel;
{
if (pType->TurretCount == 0 || pType->IsGattling || idx < 0)
return &pType->TurretVoxel;

if (idx < 18)
return &pType->ChargerTurrets[idx];
if (idx < 18)
return &pType->ChargerTurrets[idx];

if (AresHelper::CanUseAres)
{
auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pType->align_2FC);
return &aresTypeExt->ChargerTurrets[idx - 18];
}
if (AresHelper::CanUseAres)
{
auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pType->align_2FC);
return &aresTypeExt->ChargerTurrets[idx - 18];
}

return nullptr;
};
return nullptr;
};

auto GetBarrelVoxel = [pType](int idx)->VoxelStruct*
{
if (pType->TurretCount == 0 || pType->IsGattling || idx < 0)
return &pType->BarrelVoxel;
{
if (pType->TurretCount == 0 || pType->IsGattling || idx < 0)
return &pType->BarrelVoxel;

if (idx < 18)
return &pType->ChargerBarrels[idx];
if (idx < 18)
return &pType->ChargerBarrels[idx];

if (AresHelper::CanUseAres)
{
auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pType->align_2FC);
return &aresTypeExt->ChargerBarrels[idx - 18];
}
if (AresHelper::CanUseAres)
{
auto* aresTypeExt = reinterpret_cast<DummyTypeExtHere*>(pType->align_2FC);
return &aresTypeExt->ChargerBarrels[idx - 18];
}

return nullptr;
};
return nullptr;
};

uTypeExt->ApplyTurretOffset(&mtx, Pixel_Per_Lepton);
mtx.RotateZ(static_cast<float>(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>()));
Expand Down
Loading