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 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 @@ -386,6 +386,8 @@ 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 `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
- When the vehicle loses its target, you can customize whether to align the turret direction with the vehicle body
- **NetsuNegi**:
- Forbidding parallel AI queues by type
- Jumpjet crash speed fix when crashing onto building
Expand Down
2 changes: 1 addition & 1 deletion YRpp
1 change: 1 addition & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
- Fixed the bug that Locomotor warhead won't stop working when firer (except for vehicle) stop firing.
- 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.

## Fixes / interactions with other extensions

Expand Down
11 changes: 11 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,17 @@ FireUp= ; integer
FireUp.ResetInRetarget=true ; boolean
```

### Turret Response

- When the vehicle loses its target, you can customize whether to align the turret direction with the vehicle body.
- When `Speed=0` or TechnoTypes cells cannot move due to `MovementRestrictedTo`, the default value is no; in other cases, it is yes.

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

## Warheads

```{hint}
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,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, you can customize whether to align the turret direction with the vehicle body (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 All @@ -444,6 +445,7 @@ Vanilla fixes:

Phobos fixes:
- Fixed the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it (by NetsuNegi)
- 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 (by FlyStar)

Fixes / interactions with other extensions:
- Allowed `AuxBuilding` and Ares' `SW.Aux/NegBuildings` to count building upgrades (by Ollerus)
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;
}
29 changes: 29 additions & 0 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,35 @@ bool TechnoExt::IsHealthInThreshold(TechnoClass* pObject, double min, double max
return hp <= max && hp >= min;
}

bool TechnoExt::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;
}

// =============================
// load / save

Expand Down
1 change: 1 addition & 0 deletions src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ class TechnoExt
static bool IsHealthInThreshold(TechnoClass* pObject, double min, double max);
static UnitTypeClass* GetUnitTypeExtra(UnitClass* pUnit);
static AircraftTypeClass* GetAircraftTypeExtra(AircraftClass* pAircraft);
static bool CannotMove(UnitClass* pThis);

// WeaponHelpers.cpp
static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true);
Expand Down
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 (TechnoExt::TechnoExt::CannotMove(pUnit))
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
91 changes: 81 additions & 10 deletions src/Ext/Unit/Hooks.DisallowMoving.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,48 @@
#include "UnitClass.h"

#include <Utilities/GeneralUtils.h>
#include <Ext/TechnoType/Body.h>
#include <Ext/Techno/Body.h>

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

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

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

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

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

return pThis->Type->Speed == 0 ? 0x74408E : 0;
return TechnoExt::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 TechnoExt::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 TechnoExt::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 TechnoExt::CannotMove(pThis) ? 0x740727 : 0;
}

DEFINE_HOOK(0x740744, UnitClass_What_Action_DisallowMoving_2, 0x6)
Expand All @@ -55,7 +55,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 (TechnoExt::CannotMove(pThis))
{
if (result == Action::Move)
return ReturnNoMove;
Expand All @@ -72,12 +72,83 @@ 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(!TechnoExt::CannotMove(pThis)) ? 0x736AFB : 0;
}

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

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

DEFINE_HOOK_AGAIN(0x744103, UnitClass_Mission_DisallowMoving, 0x6) // UnitClass::Mission_AreaGuard
DEFINE_HOOK(0x73EFC4, UnitClass_Mission_DisallowMoving, 0x6) // UnitClass::Mission_Hunt
{
GET(UnitClass*, pThis, ESI);

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

R->EAX(pThis->Mission_Guard());
return R->Origin() == 0x744103 ? 0x744173 : 0x73F091;
}

return 0;
}

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

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

return 0;
}

namespace UnitApproachTargetTemp
{
int WeaponIndex;
}

DEFINE_HOOK(0x7414E0, UnitClass_ApproachTarget_DisallowMoving, 0xA)
{
GET(UnitClass*, pThis, ECX);

int weaponIndex = -1;

if (TechnoExt::CannotMove(pThis))
{
const auto pTarget = pThis->Target;
weaponIndex = pThis->SelectWeapon(pTarget);

if (!pThis->IsCloseEnough(pTarget, weaponIndex))
{
pThis->SetTarget(nullptr);
return 0x741690;
}
}

UnitApproachTargetTemp::WeaponIndex = weaponIndex;
return 0;
}

DEFINE_HOOK(0x7415A9, UnitClass_ApproachTarget_SetWeaponIndex, 0x6)
{
if (UnitApproachTargetTemp::WeaponIndex != -1)
{
GET(UnitClass*, pThis, ESI);

R->EDI(pThis);
R->EAX(UnitApproachTargetTemp::WeaponIndex);
UnitApproachTargetTemp::WeaponIndex = -1;

return 0x7415BA;
}

return 0;
}
Loading