Skip to content

[Minor] Speed=0 extension #1808

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 16 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -386,6 +386,7 @@ This page lists all the individual contributions to the project by their author.
- Fix an unusual use of DeployFireWeapon for InfantryType
- Fix the fact that when the selected unit is in a rearmed state, it can unconditionally use attack mouse on the target
- Units can customize the attack voice that plays when using more weapons
- When the vehicle loses its target, customize whether its turret is allowed to rotate
- **NetsuNegi**:
- Forbidding parallel AI queues by type
- Jumpjet crash speed fix when crashing onto building
Expand Down
2 changes: 1 addition & 1 deletion YRpp
10 changes: 10 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,16 @@ FireUp= ; integer
FireUp.ResetInRetarget=true ; boolean
```

### Turret Response

- When the vehicle loses its target, customize whether its turret is allowed to rotate. when `Speed=0`, the default value is no; in other cases, it is yes.

In `rulesmd.ini`:
```ini
[SOMEVEHICLE] ; VehicleType
TurretResponse= ; boolean
```

## Warheads

```{hint}
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ 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)
- When the vehicle loses its target, customize whether its turret is allowed to rotate (by FlyStar)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
29 changes: 0 additions & 29 deletions src/Ext/Aircraft/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -824,32 +824,3 @@ DEFINE_HOOK(0x4157EB, AircraftClass_Mission_SpyPlaneOverfly_MaxCount, 0x6)

return 0;
}

DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove_Pickup, 0x5)
{
enum { SkipResponse = 0x709015 };

GET(TechnoClass*, pThis, ECX);

if (auto const pAircraft = abstract_cast<AircraftClass*>(pThis))
{
auto const pType = pAircraft->Type;

if (pType->Carryall
&& pAircraft->HasAnyLink()
&& generic_cast<FootClass*>(pAircraft->Destination))
{
auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType);

if (pTypeExt->VoicePickup.isset())
{
pThis->QueueVoice(pTypeExt->VoicePickup.Get());

R->EAX(1);
return SkipResponse;
}
}
}

return 0;
}
38 changes: 38 additions & 0 deletions src/Ext/Techno/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1202,3 +1202,41 @@ DEFINE_HOOK(0x4DF3A6, FootClass_UpdateAttackMove_Follow, 0x6)
}

#pragma endregion

DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove_Pickup, 0x5)
{
enum { SkipResponse = 0x709015 };

GET(TechnoClass*, pThis, ECX);

const AbstractType rtti = pThis->WhatAmI();

if (rtti == AbstractType::Aircraft)
{
auto const pAircraft = static_cast<AircraftClass*>(pThis);
auto const pType = pAircraft->Type;

if (pType->Carryall && pAircraft->HasAnyLink()
&& generic_cast<FootClass*>(pAircraft->Destination))
{
auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType);

if (pTypeExt->VoicePickup.isset())
{
pThis->QueueVoice(pTypeExt->VoicePickup.Get());

R->EAX(1);
return SkipResponse;
}
}
}
else if (rtti == AbstractType::Unit)
{
auto const pUnit = static_cast<UnitClass*>(pThis);

if (pUnit->Type->Speed == 0)
return SkipResponse;
}

return 0;
}
9 changes: 6 additions & 3 deletions src/Ext/TechnoType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->AttackMove_PursuitTarget.Read(exINI, pSection, "AttackMove.PursuitTarget");

this->InfantryAutoDeploy.Read(exINI, pSection, "InfantryAutoDeploy");

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

Expand Down Expand Up @@ -1190,14 +1190,14 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
if (GeneralUtils::IsValidString(pThis->PaletteFile) && !pThis->Palette)
Debug::Log("[Developer warning] [%s] has Palette=%s set but no palette file was loaded (missing file or wrong filename). Missing palettes cause issues with lighting recalculations.\n", pArtSection, pThis->PaletteFile);

this->LoadFromINIByWhatAmI(exArtINI, pArtSection);
this->LoadFromINIByWhatAmI(exINI, pSection, exArtINI, pArtSection);

// VoiceIFVRepair from Ares 0.2
this->VoiceIFVRepair.Read(exINI, pSection, "VoiceIFVRepair");
this->ParseVoiceWeaponAttacks(exINI, pSection, this->VoiceWeaponAttacks, this->VoiceEliteWeaponAttacks);
}

void TechnoTypeExt::ExtData::LoadFromINIByWhatAmI(INI_EX& exArtINI, const char* pArtSection)
void TechnoTypeExt::ExtData::LoadFromINIByWhatAmI(INI_EX& exINI, const char* pSection, INI_EX& exArtINI, const char* pArtSection)
{
AbstractType abs = this->OwnerObject()->WhatAmI();

Expand All @@ -1207,6 +1207,7 @@ void TechnoTypeExt::ExtData::LoadFromINIByWhatAmI(INI_EX& exArtINI, const char*
{
this->FireUp.Read(exArtINI, pArtSection, "FireUp");
this->FireUp_ResetInRetarget.Read(exArtINI, pArtSection, "FireUp.ResetInRetarget");
this->TurretResponse.Read(exINI, pSection, "TurretResponse");
//this->SecondaryFire.Read(exArtINI, pArtSection, "SecondaryFire");
break;
}
Expand Down Expand Up @@ -1591,6 +1592,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm)
.Process(this->VoiceEliteWeaponAttacks)

.Process(this->InfantryAutoDeploy)

.Process(this->TurretResponse)
;
}
void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
Expand Down
6 changes: 5 additions & 1 deletion src/Ext/TechnoType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ class TechnoTypeExt

Nullable<bool> InfantryAutoDeploy;

Nullable<bool> TurretResponse;

ExtData(TechnoTypeClass* OwnerObject) : Extension<TechnoTypeClass>(OwnerObject)
, HealthBar_Hide { false }
, UIDescription {}
Expand Down Expand Up @@ -785,6 +787,8 @@ class TechnoTypeExt
, VoiceEliteWeaponAttacks {}

, InfantryAutoDeploy {}

, TurretResponse {}
{ }

virtual ~ExtData() = default;
Expand All @@ -796,7 +800,7 @@ class TechnoTypeExt
virtual void LoadFromStream(PhobosStreamReader& Stm) override;
virtual void SaveToStream(PhobosStreamWriter& Stm) override;

void LoadFromINIByWhatAmI(INI_EX& exArtINI, const char* pArtSection);
void LoadFromINIByWhatAmI(INI_EX& exINI, const char* pSection, INI_EX& exArtINI, const char* pArtSection);

void ApplyTurretOffset(Matrix3D* mtx, double factor = 1.0);
void CalculateSpawnerRange();
Expand Down
108 changes: 99 additions & 9 deletions src/Ext/Unit/Hooks.DisallowMoving.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,71 @@
#include <Utilities/GeneralUtils.h>
#include <Ext/TechnoType/Body.h>

bool CannotMove(UnitClass* pThis)
{
const auto pType = pThis->Type;

if (pType->Speed == 0)
return true;

if (!pThis->IsInAir())
{
LandType landType = pThis->GetCell()->LandType;
const LandType movementRestrictedTo = pType->MovementRestrictedTo;

if (pThis->OnBridge
&& (landType == LandType::Water || landType == LandType::Beach))
{
landType = LandType::Road;
}

if (movementRestrictedTo != LandType::None && movementRestrictedTo != landType && landType != LandType::Tunnel)
return true;
}

return false;
}

DEFINE_HOOK(0x740A93, UnitClass_Mission_Move_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x740AEF : 0;
return CannotMove(pThis) ? 0x740AEF : 0;
}

DEFINE_HOOK(0x741AA7, UnitClass_Assign_Destination_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, EBP);

return pThis->Type->Speed == 0 ? 0x743173 : 0;
return CannotMove(pThis) ? 0x743173 : 0;
}

DEFINE_HOOK(0x743B4B, UnitClass_Scatter_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, EBP);

return pThis->Type->Speed == 0 ? 0x74408E : 0;
return CannotMove(pThis) ? 0x74408E : 0;
}

DEFINE_HOOK(0x74038F, UnitClass_What_Action_ObjectClass_DisallowMoving_1, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x7403A3 : 0;
return CannotMove(pThis) ? 0x7403A3 : 0;
}

DEFINE_HOOK(0x7403B7, UnitClass_What_Action_ObjectClass_DisallowMoving_2, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x7403C1 : 0;
return CannotMove(pThis) ? 0x7403C1 : 0;
}

DEFINE_HOOK(0x740709, UnitClass_What_Action_DisallowMoving_1, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x740727 : 0;
return CannotMove(pThis) ? 0x740727 : 0;
}

DEFINE_HOOK(0x740744, UnitClass_What_Action_DisallowMoving_2, 0x6)
Expand All @@ -55,7 +80,7 @@ DEFINE_HOOK(0x740744, UnitClass_What_Action_DisallowMoving_2, 0x6)
GET(UnitClass*, pThis, ESI);
GET_STACK(Action, result, 0x30);

if (pThis->Type->Speed == 0)
if (CannotMove(pThis))
{
if (result == Action::Move)
return ReturnNoMove;
Expand All @@ -72,12 +97,77 @@ DEFINE_HOOK(0x736B60, UnitClass_Rotation_AI_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x736AFB : 0;
return !TechnoTypeExt::ExtMap.Find(pThis->Type)->TurretResponse.Get(pThis->Type->Speed != 0) ? 0x736AFB : 0;
}

DEFINE_HOOK(0x73891D, UnitClass_Active_Click_With_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, ESI);

return pThis->Type->Speed == 0 ? 0x738927 : 0;
return CannotMove(pThis) ? 0x738927 : 0;
}

DEFINE_HOOK_AGAIN(0x73F08A, UnitClass_Mission_DisallowMoving, 0x7) //UnitClass::Mission_Hunt
DEFINE_HOOK(0x74416C, UnitClass_Mission_DisallowMoving, 0x7) //UnitClass::Mission_AreaGuard
{
GET(UnitClass*, pThis, ESI);

DWORD address = R->Origin();

if (CannotMove(pThis))
{
pThis->QueueMission(Mission::Guard, false);
pThis->NextMission();

R->EAX(pThis->FootClass::Mission_Guard());
}
else if (address == 0x74416C)
{
R->EAX(pThis->FootClass::Mission_AreaGuard());
}
else
{
R->EAX(pThis->FootClass::Mission_Hunt());
}

return R->Origin() + 0x7;
}

DEFINE_HOOK(0x74132B, UnitClass_GetFireError_DisallowMoving, 0x7)
{
GET(UnitClass*, pThis, ESI);
GET(FireError, result, EAX);

if (result == FireError::RANGE && CannotMove(pThis))
R->EAX(FireError::ILLEGAL);

return 0;
}

DEFINE_HOOK(0x736E34, UnitClass_UpdateFiring_DisallowMoving, 0x6)
{
GET(UnitClass*, pThis, ESI);
GET(AbstractClass*, pTarget, EAX);
GET(int, nWeaponIndex, EDI);

const auto pType = pThis->Type;

if (CannotMove(pThis))
{
if (!pThis->IsCloseEnough(pTarget, nWeaponIndex))
{
if (pType->IsGattling)
pThis->GattlingRateDown(1);

pThis->SetTarget(nullptr);
return 0x737140;
}
else
{
R->EAX(pThis->GetFireError(pTarget, nWeaponIndex, false));
return 0x736E40;
}
}

return 0;
}
Loading