Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
98 changes: 84 additions & 14 deletions src/Ext/Techno/Body.Internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<FootClass*, true>(pThis);
Matrix3D mtx;
auto const pFoot = abstract_cast<FootClass*, true>(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 };

Expand Down Expand Up @@ -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<int>(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<int>(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)
{
Expand Down
53 changes: 53 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void TechnoExt::ExtData::OnEarlyUpdate()
this->ApplyMindControlRangeLimit();
this->UpdateRecountBurst();
this->UpdateRearmInEMPState();
this->UpdateRecoilData();

if (this->AttackMoveFollowerTempCount)
this->AttackMoveFollowerTempCount--;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -2136,3 +2138,54 @@ void TechnoExt::ExtData::UpdateTintValues()
calculateTint(Drawing::RGB_To_Int(pShieldType->Tint_Color), static_cast<int>(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();
}
2 changes: 2 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
;
}

Expand Down
14 changes: 13 additions & 1 deletion src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<RecoilData> ExtraTurretRecoil;
std::vector<RecoilData> ExtraBarrelRecoil;

ExtData(TechnoClass* OwnerObject) : Extension<TechnoClass>(OwnerObject)
, TypeExtData { nullptr }
, Shield {}
Expand Down Expand Up @@ -169,6 +172,8 @@ class TechnoExt
, SpecialTracked { false }
, FallingDownTracked { false }
, JumpjetStraightAscend { false }
, ExtraTurretRecoil {}
, ExtraBarrelRecoil {}
{ }

void OnEarlyUpdate();
Expand Down Expand Up @@ -207,6 +212,9 @@ class TechnoExt
int ApplyForceWeaponInRange(AbstractClass* pTarget);
void ResetDelayedFireTimer();
void UpdateTintValues();
void InitializeRecoilData();
void UpdateRecoilData();
void RecordRecoilData();

void AmmoAutoConvertActions();

Expand Down Expand Up @@ -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);
Expand Down
17 changes: 14 additions & 3 deletions src/Ext/Techno/Hooks.Firing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand All @@ -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<int>(pTypeExt->AlternateFLHs.size()))
flh = pTypeExt->AlternateFLHs[index];
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading