Skip to content

[Customized] Customized Vehicle Turret Rotation #1480

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

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
73ace93
Core
CrimRecya Dec 26, 2024
91dad80
Merge branch 'develop' into develop-UnitTurret
CrimRecya Dec 29, 2024
8f6b30a
Fix merge
CrimRecya Dec 29, 2024
78e83ab
std::abs
CrimRecya Dec 29, 2024
43765db
Add minimum angle limit and fix a movement problem
CrimRecya Jan 5, 2025
f4bba49
Doc
CrimRecya Jan 15, 2025
6c0cb30
No manually calculate
CrimRecya Jan 15, 2025
dba39e6
Merge branch 'develop' into develop-UnitTurret
CrimRecya Jan 15, 2025
c9c6a45
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Mar 22, 2025
dff6205
Update and fix 1 frame delay & moving convulsion
CrimRecya Mar 22, 2025
a7aa101
Fix YRpp
CrimRecya Mar 22, 2025
5c41b48
New `Turret.BodyFoundation`
CrimRecya Mar 22, 2025
b39bc61
Fix doc
CrimRecya Mar 22, 2025
8a92636
Fix stuck turret
CrimRecya Mar 22, 2025
289a7e9
Fix hook
CrimRecya Apr 25, 2025
e4cd4a9
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Apr 25, 2025
bfac1bb
Fit with techno attachment
CrimRecya Apr 28, 2025
78a6335
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Apr 28, 2025
ab7d4c6
Fix a typo
CrimRecya Apr 30, 2025
2a94d01
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Apr 30, 2025
13326e3
New func and optimize
CrimRecya Jul 1, 2025
f23bde8
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Jul 1, 2025
b2d90f5
Fix a typo
CrimRecya Jul 1, 2025
6b29479
Simplified
CrimRecya Aug 12, 2025
3e9f75b
Merge remote-tracking branch 'upstream/develop' into develop-UnitTurret
CrimRecya Aug 12, 2025
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 @@ -530,6 +530,8 @@ This page lists all the individual contributions to the project by their author.
- Burst without delay
- Fix an issue that if the garrison unload occupants when there is no open space around it would result in the disappearance of the occupants
- Fix an issue where Ares' `Convert.Deploy` triggers repeatedly when the unit is turning or moving
- Customized Vehicle Turret Rotation
- Extra threat value coefficient related to orientation
- **Ollerus**:
- Build limit group enhancement
- Customizable rocker amplitude
Expand Down
1 change: 1 addition & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
<ClCompile Include="src\Ext\Techno\Hooks.ReceiveDamage.cpp" />
<ClCompile Include="src\Ext\Techno\Hooks.TargetEvaluation.cpp" />
<ClCompile Include="src\Ext\Techno\Hooks.Transport.cpp" />
<ClCompile Include="src\Ext\Techno\Hooks.Facing.cpp" />
<ClCompile Include="src\Ext\TerrainType\Body.cpp" />
<ClCompile Include="src\Ext\TerrainType\Hooks.cpp" />
<ClCompile Include="src\Ext\TerrainType\Hooks.Passable.cpp" />
Expand Down
58 changes: 58 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,22 @@ RateDown.Cover.Value=0 ; integer
RateDown.Cover.AmmoBelow=-2 ; integer
```

### Extra threat value coefficient related to orientation

- Now, you can enable extra threat value coefficients related to the target direction through `TargetExtraThreat`.
- `TargetExtraThreat.Angles` needs to be filled in from small to large. When the angle between the orientation and the target direction starts to be smaller than one of the values, the threat coefficient will be multiplied by the corresponding value in `TargetExtraThreat.Multipliers`. When the ultimate threat to the target is 0, the techno will not actively attack the target.
- `TargetExtraThreat.Multipliers` must be used in conjunction with `TargetExtraThreat.Angles` and can have an additional factor representing the threat value coefficient when the maximum angle is exceeded.
- `TargetExtraThreat.Turret` controls whether the orientation of the turret will be prioritized when calculating the orientation of the techno. If you want to use it in conjunction with `Turret.Restriction`, setting it to false would have the best effect.

In `rulesmd.ini`:
```ini
[SOMETECHNO] ; TechnoType
TargetExtraThreat=false ; boolean
TargetExtraThreat.Angles= ; List of floating point value
TargetExtraThreat.Multipliers= ; List of floating point value
TargetExtraThreat.Turret=true ; boolean
```

### Firing offsets for specific Burst shots

- You can now specify separate firing offsets for each of the shots fired by weapon with `Burst` via using `(Elite)(Prone/Deployed)PrimaryFire|SecondaryFire|WeaponX|FLH.BurstN` keys, depending on which weapons your TechnoType makes use of. *N* in `BurstN` is zero-based burst shot index, and the values are parsed sequentially until no value for either regular or elite weapon is present, with elite weapon defaulting to regular weapon FLH if only it is missing. If no burst-index specific value is available, value from the base key (f.ex `PrimaryFireFLH`) is used.
Expand Down Expand Up @@ -2006,6 +2022,48 @@ AmphibiousEnter= ; boolean, default to [General] -> AmphibiousEnter
AmphibiousUnload= ; boolean, default to [General] -> AmphibiousUnload
```

### Customized Vehicle Turret Rotation

- When `ExpandTurretRotation` is set to true, the following functions will be enabled.
- Units with turret without `TurretSpins=true` can looks more vivid when it is in idle.
- `Turret.IdleRotate` controls whether units can rotate their turrets when in idle.
- `Turret.PointToMouse` controls whether units will turn their turrets to your mouse when in idle.
- At the present moment, this only functions in singleplayer.
- `Turret.IdleRestartMin` and `Turret.IdleRestartMax` control the delay from idle to action occurrence together.
- `Turret.IdleIntervalMin` and `Turret.IdleIntervalMax` control the delay between every idle actions together.
- The turret and body of the units can now be rotated under control.
- `Turret.Restriction` defines the angle at which the turret can be turned to both sides.
- `Turret.ExtraAngle` defines the additional rotation angle of the turret, and `Turret.Restriction` will also rotate with this value. The positive number is clockwise and the negative number is counterclockwise, that is, what angle the turret should use by default or face the target.
- `Turret.BodyFoundation` controls whether the unit's turret will rotate in the direction of body rotation.
- `Turret.BodyOrientation` controls whether the body needs to aim at the target when firing.
- `Turret.BodyOrientationAngle` defines the additional rotation angle of the body when aiming at the target. The positive number is clockwise and the negative number is counterclockwise, that is, what angle the body should use to face the target.
- `Turret.BodyOrientationSymmetric` controls whether both sides of the additional body rotation angle can be used.

In `rulesmd.ini`:
```ini
[General]
ExpandTurretRotation=false ; boolean

[AudioVisual]
Turret.IdleRotate=false ; boolean
Turret.PointToMouse=false ; boolean
Turret.BodyFoundation=false ; boolean
Turret.IdleRestartMin=150 ; integer, number of frames
Turret.IdleRestartMax=300 ; integer, number of frames
Turret.IdleIntervalMin=150 ; integer, number of frames
Turret.IdleIntervalMax=450 ; integer, number of frames

[SOMEVEHICLE] ; VehicleType, with `Turret=yes`
Turret.IdleRotate= ; boolean, default to [AudioVisual] -> Turret.IdleRotate
Turret.PointToMouse= ; boolean, default to [AudioVisual] -> Turret.PointToMouse
Turret.Restriction=180.0 ; floating point value
Turret.ExtraAngle=0 ; floating point value
Turret.BodyFoundation= ; boolean, default to [AudioVisual] -> Turret.BodyFoundation
Turret.BodyOrientation=false ; boolean
Turret.BodyOrientationAngle=0 ; floating point value
Turret.BodyOrientationSymmetric=true ; boolean
```

### Damaged unit image changes

- When a unit is damaged (health points percentage is lower than `[AudioVisual] -> ConditionYellow` percentage), it now may use different image set by `Image.ConditionYellow` VehicleType.
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ New:
- [Units can customize the attack voice that plays when using more weapons](New-or-Enhanced-Logics.md#multi-voiceattack) (by FlyStar)
- Customize squid grapple animation (by NetsuNegi)
- [Auto deploy for GI-like infantry](Fixed-or-Improved-Logics.md#auto-deploy-for-gi-like-infantry) (by TaranDahl)
- Customized Vehicle Turret Rotation (by CrimRecya)
- Extra threat value coefficient related to orientation (by 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
17 changes: 17 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,15 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)
this->Vehicles_DefaultDigitalDisplayTypes.Read(exINI, GameStrings::AudioVisual, "Vehicles.DefaultDigitalDisplayTypes");
this->Aircraft_DefaultDigitalDisplayTypes.Read(exINI, GameStrings::AudioVisual, "Aircraft.DefaultDigitalDisplayTypes");

this->ExpandTurretRotation.Read(exINI, GameStrings::General, "ExpandTurretRotation");
this->Turret_IdleRotate.Read(exINI, GameStrings::AudioVisual, "Turret.IdleRotate");
this->Turret_PointToMouse.Read(exINI, GameStrings::AudioVisual, "Turret.PointToMouse");
this->Turret_BodyFoundation.Read(exINI, GameStrings::AudioVisual, "Turret.BodyFoundation");
this->Turret_IdleRestartMin.Read(exINI, GameStrings::AudioVisual, "Turret.IdleRestartMin");
this->Turret_IdleRestartMax.Read(exINI, GameStrings::AudioVisual, "Turret.IdleRestartMax");
this->Turret_IdleIntervalMin.Read(exINI, GameStrings::AudioVisual, "Turret.IdleIntervalMin");
this->Turret_IdleIntervalMax.Read(exINI, GameStrings::AudioVisual, "Turret.IdleIntervalMax");

this->DefaultInfantrySelectBox.Read(exINI, GameStrings::AudioVisual, "DefaultInfantrySelectBox");
this->DefaultUnitSelectBox.Read(exINI, GameStrings::AudioVisual, "DefaultUnitSelectBox");

Expand Down Expand Up @@ -486,6 +495,14 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->Infantry_DefaultDigitalDisplayTypes)
.Process(this->Vehicles_DefaultDigitalDisplayTypes)
.Process(this->Aircraft_DefaultDigitalDisplayTypes)
.Process(this->ExpandTurretRotation)
.Process(this->Turret_IdleRotate)
.Process(this->Turret_PointToMouse)
.Process(this->Turret_BodyFoundation)
.Process(this->Turret_IdleRestartMin)
.Process(this->Turret_IdleRestartMax)
.Process(this->Turret_IdleIntervalMin)
.Process(this->Turret_IdleIntervalMax)
.Process(this->DefaultInfantrySelectBox)
.Process(this->DefaultUnitSelectBox)
.Process(this->VisualScatter_Min)
Expand Down
17 changes: 17 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ class RulesExt
ValueableVector<DigitalDisplayTypeClass*> Vehicles_DefaultDigitalDisplayTypes;
ValueableVector<DigitalDisplayTypeClass*> Aircraft_DefaultDigitalDisplayTypes;

Valueable<bool> ExpandTurretRotation;
Valueable<bool> Turret_IdleRotate;
Valueable<bool> Turret_PointToMouse;
Valueable<bool> Turret_BodyFoundation;
Valueable<int> Turret_IdleRestartMin;
Valueable<int> Turret_IdleRestartMax;
Valueable<int> Turret_IdleIntervalMin;
Valueable<int> Turret_IdleIntervalMax;

Valueable<SelectBoxTypeClass*> DefaultInfantrySelectBox;
Valueable<SelectBoxTypeClass*> DefaultUnitSelectBox;

Expand Down Expand Up @@ -374,6 +383,14 @@ class RulesExt
, Infantry_DefaultDigitalDisplayTypes {}
, Vehicles_DefaultDigitalDisplayTypes {}
, Aircraft_DefaultDigitalDisplayTypes {}
, ExpandTurretRotation { false }
, Turret_IdleRotate { false }
, Turret_PointToMouse { false }
, Turret_BodyFoundation { false }
, Turret_IdleRestartMin { 150 }
, Turret_IdleRestartMax { 300 }
, Turret_IdleIntervalMin { 150 }
, Turret_IdleIntervalMax { 450 }
, DefaultInfantrySelectBox {}
, DefaultUnitSelectBox {}
, VisualScatter_Min { Leptons(8) }
Expand Down
131 changes: 131 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <Utilities/EnumFunctions.h>
#include <Utilities/AresFunctions.h>

#include <WWMouseClass.h>
#include <TacticalClass.h>

// TechnoClass_AI_0x6F9E50
// It's not recommended to do anything more here it could have a better place for performance consideration
Expand Down Expand Up @@ -1298,6 +1300,135 @@ void TechnoExt::ExtData::UpdateMindControlAnim()
}
}

void TechnoExt::ExtData::StopIdleAction()
{
if (this->UnitIdleActionTimer.IsTicking())
this->UnitIdleActionTimer.Stop();

if (this->UnitIdleActionGapTimer.IsTicking())
{
this->UnitIdleActionGapTimer.Stop();
const auto pTypeExt = this->TypeExtData;
this->StopRotateWithNewROT(pTypeExt->TurretROT.Get(pTypeExt->OwnerObject()->ROT));
}
}

void TechnoExt::ExtData::ApplyIdleAction()
{
const auto pThis = this->OwnerObject();
auto shouldNotTurn = [pThis]() -> bool
{
if (const auto pUnit = abstract_cast<UnitClass*, true>(pThis))
return pUnit->BunkerLinkedItem || !pUnit->Type->Speed || (pUnit->Type->IsSimpleDeployer && pUnit->Deployed);

return pThis->WhatAmI() == AbstractType::Building;
};

if (this->UnitIdleActionTimer.Completed()) // Set first direction
{
this->UnitIdleActionTimer.Stop();
this->UnitIdleActionGapTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->Turret_IdleIntervalMin, RulesExt::Global()->Turret_IdleIntervalMax));
const short raw = static_cast<short>(ScenarioClass::Instance->Random.RandomRanged(0, 65535) - 32768);
this->StopRotateWithNewROT(ScenarioClass::Instance->Random.RandomRanged(2,4) >> 1);
this->SetTurretDir(DirStruct { (this->TypeExtData->GetTurretLimitedRaw(shouldNotTurn() ? raw : (raw / 4)) + static_cast<short>(pThis->PrimaryFacing.Current().Raw)) });
return;
}
else if (this->UnitIdleActionGapTimer.IsTicking()) // Check change direction
{
if (!this->UnitIdleActionGapTimer.HasTimeLeft()) // Set next direction
{
this->UnitIdleActionGapTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->Turret_IdleIntervalMin, RulesExt::Global()->Turret_IdleIntervalMax));
const short raw = static_cast<short>(ScenarioClass::Instance->Random.RandomRanged(0, 65535) - 32768);
this->StopRotateWithNewROT(ScenarioClass::Instance->Random.RandomRanged(2,4) >> 1);
this->SetTurretDir(DirStruct { (this->TypeExtData->GetTurretLimitedRaw(shouldNotTurn() ? raw : (raw / 4)) + static_cast<short>(pThis->PrimaryFacing.Current().Raw)) });
return;
}
}
else if (!this->UnitIdleActionTimer.IsTicking()) // In idle now
{
this->UnitIdleActionTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->Turret_IdleRestartMin, RulesExt::Global()->Turret_IdleRestartMax));

if (!shouldNotTurn())
{
this->SetTurretDir(pThis->PrimaryFacing.Current());
return;
}
}

this->UpdateIdleDir();
}

void TechnoExt::ExtData::ManualIdleAction()
{
const auto pThis = this->OwnerObject();

if (pThis->IsSelected)
{
this->CheckIdleAction();
this->UnitIdleIsSelected = true;
const auto mouseCoords = TacticalClass::Instance->ClientToCoords(WWMouseClass::Instance->XY1);

if (mouseCoords != CoordStruct::Empty) // Mouse in tactical
{
const auto offset = -static_cast<int>(pThis->GetCoords().Z * ((Unsorted::LeptonsPerCell / 2.0) / Unsorted::LevelHeight));
const auto targetDir = pThis->GetTargetDirection(MapClass::Instance.GetCellAt(CoordStruct { mouseCoords.X - offset, mouseCoords.Y - offset, 0 }));
this->SetTurretDir(targetDir, true);
}
}
else if (this->UnitIdleIsSelected) // Immediately stop when is not selected
{
this->UnitIdleIsSelected = false;
this->StopRotateWithNewROT();
}
}

void TechnoExt::ExtData::CheckIdleAction()
{
if (this->TypeExtData->Turret_IdleRotate.Get(RulesExt::Global()->Turret_IdleRotate))
this->StopIdleAction();
}

void TechnoExt::ExtData::UpdateIdleDir()
{
if (const auto pUnit = abstract_cast<UnitClass*, true>(this->OwnerObject()))
{
const auto pTypeExt = this->TypeExtData;

if (static_cast<int>(pTypeExt->Turret_Restriction.Get().Raw) < 32768)
{
const auto rotate = pTypeExt->Turret_ExtraAngle.Get();
const auto dir = pUnit->SecondaryFacing.Desired();
pTypeExt->SetTurretLimitedDir(pUnit, DirStruct { static_cast<short>(pUnit, dir.Raw) - static_cast<short>(rotate.Raw) });
}
}
}

void TechnoExt::ExtData::SetTurretDir(DirStruct desiredDir, bool limited)
{
const auto pThis = this->OwnerObject();
const auto pUnit = abstract_cast<UnitClass*, true>(pThis);

if (!pUnit)
pThis->PrimaryFacing.SetDesired(desiredDir);
else if (limited)
this->TypeExtData->SetTurretLimitedDir(pUnit, desiredDir);
else
pThis->SecondaryFacing.SetDesired(this->TypeExtData->GetTurretDesiredDir(desiredDir));
}

void TechnoExt::ExtData::StopRotateWithNewROT(int ROT)
{
const auto turret = &this->OwnerObject()->SecondaryFacing;

const auto currentFacingDirection = turret->Current();
turret->DesiredFacing = currentFacingDirection;
turret->StartFacing = currentFacingDirection;
turret->RotationTimer.Start(0);

if (ROT >= 0)
turret->SetROT(ROT);
}

void TechnoExt::ExtData::UpdateRecountBurst()
{
const auto pThis = this->OwnerObject();
Expand Down
3 changes: 3 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,9 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->LastRearmWasFullDelay)
.Process(this->CanCloakDuringRearm)
.Process(this->WHAnimRemainingCreationInterval)
.Process(this->UnitIdleIsSelected)
.Process(this->UnitIdleActionTimer)
.Process(this->UnitIdleActionGapTimer)
.Process(this->LastWeaponType)
.Process(this->FiringObstacleCell)
.Process(this->IsDetachingForCloak)
Expand Down
13 changes: 13 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class TechnoExt
bool LastRearmWasFullDelay;
bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon.
int WHAnimRemainingCreationInterval;
bool UnitIdleIsSelected;
CDTimerClass UnitIdleActionTimer;
CDTimerClass UnitIdleActionGapTimer;
WeaponTypeClass* LastWeaponType;
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only.
Expand Down Expand Up @@ -118,6 +121,9 @@ class TechnoExt
, LastRearmWasFullDelay { false }
, CanCloakDuringRearm { false }
, WHAnimRemainingCreationInterval { 0 }
, UnitIdleIsSelected { false }
, UnitIdleActionTimer {}
, UnitIdleActionGapTimer {}
, LastWeaponType {}
, FiringObstacleCell {}
, IsDetachingForCloak { false }
Expand Down Expand Up @@ -178,6 +184,13 @@ class TechnoExt
void UpdateSelfOwnedAttachEffects();
bool HasAttachedEffects(std::vector<AttachEffectTypeClass*> attachEffectTypes, bool requireAll, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource, std::vector<int> const* minCounts, std::vector<int> const* maxCounts) const;
int GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource = false, TechnoClass* pInvoker = nullptr, AbstractClass* pSource = nullptr) const;
void StopIdleAction();
void ApplyIdleAction();
void ManualIdleAction();
void CheckIdleAction();
void UpdateIdleDir();
void SetTurretDir(DirStruct desiredDir, bool limited = false);
void StopRotateWithNewROT(int ROT = -1);
void InitializeDisplayInfo();
void ApplyMindControlRangeLimit();
int ApplyForceWeaponInRange(AbstractClass* pTarget);
Expand Down
Loading
Loading