Skip to content
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

Units Rotate Turret When in Idle Action #1308

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
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
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ This page lists all the individual contributions to the project by their author.
- Allow to change the speed of gas particles
- **CrimRecya**
- Fix `LimboKill` not working reliably
- Units Rotate Turret When in Idle Action
- **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 @@ -126,6 +126,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
23 changes: 23 additions & 0 deletions docs/User-Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,29 @@ In `rulesmd.ini`:
SelectionFlashDuration=0 ; integer, number of frames
```

### Units Rotate Turret When in Idle Action

- Now unit with turret without `TurretSpins=true` can looks more vivid when it is in idle.
- `UnitIdleRotateTurret` controls whether units can rotate their turrets when in idle. Defaults to `[AudioVisual]` -> `UnitIdleRotateTurret`.
- `UnitIdlePointToMouse` controls whether units will turn their turrets to your mouse when in idle. Defaults to `[AudioVisual]` -> `UnitIdlePointToMouse`.
- `UnitIdleActionRestartMin` and `UnitIdleActionRestartMax` control the delay from idle to action occurrence together.
- `UnitIdleActionIntervalMin` and `UnitIdleActionIntervalMax` control the delay between every idle actions together.

In `rulesmd.ini`:
```ini
[AudioVisual]
UnitIdleRotateTurret=false ; boolean
UnitIdlePointToMouse=false ; boolean
UnitIdleActionRestartMin=150 ; integer, number of frames
UnitIdleActionRestartMax=300 ; integer, number of frames
UnitIdleActionIntervalMin=150 ; integer, number of frames
UnitIdleActionIntervalMax=450 ; integer, number of frames

[SOMETECHNO] ; TechnoType
UnitIdleRotateTurret= ; boolean
UnitIdlePointToMouse= ; boolean
```

## Hotkey Commands

### `[ ]` Display Damage Numbers
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ New:
- Map events `604-605` for checking if a specific Techno enters in a cell (by FS-21)
- Waypoint path is drawn for all units under player control or if `[GlobalControls]->DebugPlanningPaths=yes` (by Trsdy)
- `RemoveDisguise` now works on vehicle disguises (by Trsdy)
- Units Rotate Turret When in Idle Action (by CrimRecya)
- Allow anchoring extended tooltips to the left side of the sidebar (by Trsdy)
- Toggle to allow spawned aircraft to attack immediately after being spawned (by Starkku)
- `ZAdjust` for OverlayTypes (by Starkku)
Expand Down
13 changes: 13 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ 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->UnitIdleRotateTurret.Read(exINI, GameStrings::AudioVisual, "UnitIdleRotateTurret");
this->UnitIdlePointToMouse.Read(exINI, GameStrings::AudioVisual, "UnitIdlePointToMouse");
this->UnitIdleActionRestartMin.Read(exINI, GameStrings::AudioVisual, "UnitIdleActionRestartMin");
this->UnitIdleActionRestartMax.Read(exINI, GameStrings::AudioVisual, "UnitIdleActionRestartMax");
this->UnitIdleActionIntervalMin.Read(exINI, GameStrings::AudioVisual, "UnitIdleActionIntervalMin");
this->UnitIdleActionIntervalMax.Read(exINI, GameStrings::AudioVisual, "UnitIdleActionIntervalMax");

this->AircraftLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "AircraftLevelLightMultiplier");
this->JumpjetLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "JumpjetLevelLightMultiplier");

Expand Down Expand Up @@ -376,6 +383,12 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->ShowDesignatorRange)
.Process(this->DropPodTrailer)
.Process(this->PodImage)
.Process(this->UnitIdleRotateTurret)
.Process(this->UnitIdlePointToMouse)
.Process(this->UnitIdleActionRestartMin)
.Process(this->UnitIdleActionRestartMax)
.Process(this->UnitIdleActionIntervalMin)
.Process(this->UnitIdleActionIntervalMax)
.Process(this->AircraftLevelLightMultiplier)
.Process(this->JumpjetLevelLightMultiplier)
.Process(this->VoxelLightSource)
Expand Down
13 changes: 13 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ class RulesExt
// Nullable<Vector3D<float>> VoxelShadowLightSource;
Valueable<bool> UseFixedVoxelLighting;

Valueable<bool> UnitIdleRotateTurret;
Valueable<bool> UnitIdlePointToMouse;
Valueable<int> UnitIdleActionRestartMin;
Valueable<int> UnitIdleActionRestartMax;
Valueable<int> UnitIdleActionIntervalMin;
Valueable<int> UnitIdleActionIntervalMax;

Valueable<bool> GatherWhenMCVDeploy;
Valueable<bool> AIFireSale;
Valueable<int> AIFireSaleDelay;
Expand Down Expand Up @@ -271,6 +278,12 @@ class RulesExt
, ShowDesignatorRange { true }
, DropPodTrailer { }
, PodImage { }
, UnitIdleRotateTurret { false }
, UnitIdlePointToMouse { false }
, UnitIdleActionRestartMin { 150 }
, UnitIdleActionRestartMax { 300 }
, UnitIdleActionIntervalMin { 150 }
, UnitIdleActionIntervalMax { 450 }
, AircraftLevelLightMultiplier { 1.0 }
, JumpjetLevelLightMultiplier { 0.0 }
, VoxelLightSource { }
Expand Down
114 changes: 114 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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 @@ -588,6 +590,118 @@ 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();
const auto turret = &pThis->SecondaryFacing;

if (this->UnitIdleActionTimer.Completed()) // Set first direction
{
this->UnitIdleActionTimer.Stop();
this->UnitIdleActionGapTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->UnitIdleActionIntervalMin, RulesExt::Global()->UnitIdleActionIntervalMax));
bool noNeedTurnForward = false;

if (const auto pUnit = abstract_cast<UnitClass*>(pThis))
noNeedTurnForward = pUnit->BunkerLinkedItem || !pUnit->Type->Speed || (pUnit->Type->IsSimpleDeployer && pUnit->Deployed);
else if (pThis->WhatAmI() == AbstractType::Building)
noNeedTurnForward = true;

const auto extraRadian = ScenarioClass::Instance->Random.RandomDouble() - 0.5;

DirStruct unitIdleFacingDirection;
unitIdleFacingDirection.SetRadian<32>(pThis->PrimaryFacing.Current().GetRadian<32>() + (noNeedTurnForward ? extraRadian * Math::TwoPi : extraRadian));

this->StopRotateWithNewROT(ScenarioClass::Instance->Random.RandomRanged(2,4) >> 1);
turret->SetDesired(unitIdleFacingDirection);
}
else if (this->UnitIdleActionGapTimer.IsTicking()) // Check change direction
{
if (!this->UnitIdleActionGapTimer.HasTimeLeft()) // Set next direction
{
this->UnitIdleActionGapTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->UnitIdleActionIntervalMin, RulesExt::Global()->UnitIdleActionIntervalMax));
bool noNeedTurnForward = false;

if (const auto pUnit = abstract_cast<UnitClass*>(pThis))
noNeedTurnForward = pUnit->BunkerLinkedItem || !pUnit->Type->Speed || (pUnit->Type->IsSimpleDeployer && pUnit->Deployed);
else if (pThis->WhatAmI() == AbstractType::Building)
noNeedTurnForward = true;

const auto extraRadian = ScenarioClass::Instance->Random.RandomDouble() - 0.5;

DirStruct unitIdleFacingDirection;
unitIdleFacingDirection.SetRadian<32>(pThis->PrimaryFacing.Current().GetRadian<32>() + (noNeedTurnForward ? extraRadian * Math::TwoPi : extraRadian));

this->StopRotateWithNewROT(ScenarioClass::Instance->Random.RandomRanged(2,4) >> 1);
turret->SetDesired(unitIdleFacingDirection);
}
}
else if (!this->UnitIdleActionTimer.IsTicking()) // In idle now
{
this->UnitIdleActionTimer.Start(ScenarioClass::Instance->Random.RandomRanged(RulesExt::Global()->UnitIdleActionRestartMin, RulesExt::Global()->UnitIdleActionRestartMax));
bool noNeedTurnForward = false;

if (const auto pUnit = abstract_cast<UnitClass*>(pThis))
noNeedTurnForward = pUnit->BunkerLinkedItem || !pUnit->Type->Speed || (pUnit->Type->IsSimpleDeployer && pUnit->Deployed);
else if (pThis->WhatAmI() == AbstractType::Building)
noNeedTurnForward = true;

if (!noNeedTurnForward)
turret->SetDesired(pThis->PrimaryFacing.Current());
}
}

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

if (pThis->IsSelected)
{
if (this->TypeExtData->UnitIdleRotateTurret.Get(RulesExt::Global()->UnitIdleRotateTurret))
this->StopIdleAction();

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 * 1.2307692307692307692307692307692); // ((Unsorted::LeptonsPerCell / 2) / Unsorted::LevelHeight)
turret->SetDesired(pThis->GetTargetDirection(MapClass::Instance->GetCellAt(CoordStruct { mouseCoords.X - offset, mouseCoords.Y - offset, 0 })));
}
}
else if (this->UnitIdleIsSelected) // Immediately stop when is not selected
{
this->UnitIdleIsSelected = false;
this->StopRotateWithNewROT();
}
}

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::ApplyGainedSelfHeal(TechnoClass* pThis)
{
if (!RulesExt::Global()->GainSelfHealAllowMultiplayPassive && pThis->Owner->Type->MultiplayPassive)
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 @@ -496,6 +496,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->FiringObstacleCell)
.Process(this->IsDetachingForCloak)
.Process(this->OriginalPassengerOwner)
Expand Down
10 changes: 10 additions & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ class TechnoExt
bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon.
int WHAnimRemainingCreationInterval;
bool CanCurrentlyDeployIntoBuilding; // Only set on UnitClass technos with DeploysInto set in multiplayer games, recalculated once per frame so no need to serialize.
bool UnitIdleIsSelected;
CDTimerClass UnitIdleActionTimer;
CDTimerClass UnitIdleActionGapTimer;
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 @@ -86,6 +89,9 @@ class TechnoExt
, CanCloakDuringRearm { false }
, WHAnimRemainingCreationInterval { 0 }
, CanCurrentlyDeployIntoBuilding { false }
, UnitIdleIsSelected { false }
, UnitIdleActionTimer {}
, UnitIdleActionGapTimer {}
, FiringObstacleCell {}
, IsDetachingForCloak { false }
, OriginalPassengerOwner {}
Expand Down Expand Up @@ -115,6 +121,10 @@ 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 StopRotateWithNewROT(int ROT = -1);

virtual ~ExtData() override;
virtual void InvalidatePointer(void* ptr, bool bRemoved) override { }
Expand Down
98 changes: 98 additions & 0 deletions src/Ext/Techno/Hooks.Facing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include "Body.h"

#include <JumpjetLocomotionClass.h>

#pragma region UnitsFacing

// Would it be better to rewrite the entire UpdateRotation() ?
DEFINE_HOOK(0x736AEA, UnitClass_UpdateRotation_ApplyUnitIdleAction, 0x6)
{
enum { SkipGameCode = 0x736BE2 };

GET(UnitClass* const, pThis, ESI);

const auto pExt = TechnoExt::ExtMap.Find(pThis);

// Turning to target?
if (pThis->SecondaryFacing.IsRotating())
{
// Repeatedly check TurretSpins and IsRotating() seems unnecessary
pThis->unknown_bool_6AF = true;

if (!pExt || (!pExt->UnitIdleActionTimer.IsTicking() && !pExt->UnitIdleActionGapTimer.IsTicking() && !pExt->UnitIdleIsSelected))
return SkipGameCode;
}

const bool canCheck = pExt && pExt->TypeExtData;
const auto currentMission = pThis->CurrentMission;

// Busy in attacking or driver dead?
if (pThis->Target || (Unsorted::CurrentFrame - pThis->unknown_int_120) < (RulesClass::Instance->GuardAreaTargetingDelay + 5) || (currentMission == Mission::Harmless && pThis->Owner == HouseClass::FindSpecial()))
{
if (canCheck && pExt->TypeExtData->UnitIdleRotateTurret.Get(RulesExt::Global()->UnitIdleRotateTurret))
pExt->StopIdleAction();

return SkipGameCode;
}

const auto pWeaponStruct = pThis->GetTurretWeapon();

// Turret locked?
if (pWeaponStruct && pWeaponStruct->WeaponType && pWeaponStruct->TurretLocked)
{
if (canCheck && pExt->TypeExtData->UnitIdleRotateTurret.Get(RulesExt::Global()->UnitIdleRotateTurret))
pExt->StopIdleAction();

if (!pThis->BunkerLinkedItem && pThis->Type->Speed && (!pThis->Type->IsSimpleDeployer || !pThis->Deployed))
pThis->SecondaryFacing.SetDesired(pThis->PrimaryFacing.Current());

return SkipGameCode;
}

// Point to mouse
if (canCheck && SessionClass::IsSingleplayer() && pThis->Owner->IsControlledByCurrentPlayer())
{
if (pExt->TypeExtData->UnitIdlePointToMouse.Get(RulesExt::Global()->UnitIdlePointToMouse))
pExt->ManualIdleAction();

if (pExt->UnitIdleIsSelected)
return SkipGameCode;
}

const auto pDestination = pThis->Destination;
// Bugfix: Align jumpjet turret's facing with body's
// When jumpjets arrived at their FootClass::Destination, they seems stuck at the Move mission
// and therefore the turret facing was set to DirStruct{atan2(0,0)}==DirType::East at 0x736BBB
// that's why they will come back to normal when giving stop command explicitly
// so the best way is to fix the Mission if necessary, but I don't know how to do it
// so I skipped jumpjets check temporarily
if (pDestination && !locomotion_cast<JumpjetLocomotionClass*>(pThis->Locomotor))
{
if (canCheck && pExt->TypeExtData->UnitIdleRotateTurret.Get(RulesExt::Global()->UnitIdleRotateTurret))
pExt->StopIdleAction();

if (!pThis->BunkerLinkedItem && pThis->Type->Speed && (!pThis->Type->IsSimpleDeployer || !pThis->Deployed))
pThis->SecondaryFacing.SetDesired(pThis->GetTargetDirection(pDestination));

return SkipGameCode;
}

// Idle main
if (canCheck && pExt->TypeExtData->UnitIdleRotateTurret.Get(RulesExt::Global()->UnitIdleRotateTurret))
{
if (currentMission == Mission::Guard || currentMission == Mission::Sticky)
{
pExt->ApplyIdleAction();
return SkipGameCode;
}

pExt->StopIdleAction();
}

if (!pThis->BunkerLinkedItem && pThis->Type->Speed && (!pThis->Type->IsSimpleDeployer || !pThis->Deployed))
pThis->SecondaryFacing.SetDesired(pThis->PrimaryFacing.Current());

return SkipGameCode;
}

#pragma endregion
8 changes: 8 additions & 0 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,10 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->Wake_Grapple.Read(exINI, pSection, "Wake.Grapple");
this->Wake_Sinking.Read(exINI, pSection, "Wake.Sinking");

this->UnitIdleRotateTurret.Read(exINI, pSection, "UnitIdleRotateTurret");
this->UnitIdlePointToMouse.Read(exINI, pSection, "UnitIdlePointToMouse");
this->TurretROT.Read(exINI, pSection, "TurretROT");

// Ares 0.2
this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius");

Expand Down Expand Up @@ -827,6 +831,10 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->Wake)
.Process(this->Wake_Grapple)
.Process(this->Wake_Sinking)

.Process(this->UnitIdleRotateTurret)
.Process(this->UnitIdlePointToMouse)
.Process(this->TurretROT)
;
}
void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
Expand Down
Loading