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
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ This page lists all the individual contributions to the project by their author.
- Fix the bug that Locomotor warhead won't stop working when firer (except for vehicle) stop firing
- Fix the bug that hover vehicle will sink if destroyed on bridge
- Customize squid grapple animation
- Vehicle disguise to vehicle
- **Apollo** - Translucent SHP drawing patches
- **ststl**:
- Customizable `ShowTimer` priority of superweapons
Expand Down
23 changes: 12 additions & 11 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -687,17 +687,6 @@ In `rulesmd.ini`:
SlavesFreeSound= ; Sound entry, default to [AudioVisual] -> SlavesFreeSound
```

### Default disguise for individual InfantryTypes

- Infantry can now have its `DefaultDisguise` overridden per-type.
- This tag's priority is higher than Ares' per-side `DefaultDisguise`.

In `rulesmd.ini`:
```ini
[SOMEINFANTRY] ; InfantryType
DefaultDisguise= ; InfantryType
```

### Random death animaton for NotHuman infantry

- Infantry with `NotHuman=yes` can now play random death anim sequence between `Die1` to `Die5` instead of the hardcoded `Die1`.
Expand Down Expand Up @@ -1976,6 +1965,18 @@ In `rulesmd.ini`:
Convert.ResetMindControl=false ; boolean
```

### Default disguise for individual InfantryTypes or UnitTypes

- Infantry can now have its `DefaultDisguise` overridden per-type.
- This tag's priority is higher than Ares' per-side `DefaultDisguise`.
- Now you can make vehicle disguise to other vehicles, like spy.

In `rulesmd.ini`:
```ini
[SOMETECHNO] ; InfantryType or UnitType
DefaultDisguise= ; InfantryType or UnitType
```

## Terrain

### Destroy animation & sound
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)
- Vehicle disguise to vehicle (by NetsuNegi)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
261 changes: 254 additions & 7 deletions src/Ext/Techno/Hooks.Misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,28 +473,47 @@ DEFINE_HOOK(0x7295E2, TunnelLocomotionClass_ProcessStateDigging_SubterraneanHeig

DEFINE_HOOK(0x522790, InfantryClass_ClearDisguise_DefaultDisguise, 0x6)
{
enum { SetDisguise = 0x5227BF };

GET(InfantryClass*, pThis, ECX);
auto const pExt = TechnoTypeExt::ExtMap.Find(pThis->Type);
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Type);
const auto pDefault = pTypeExt->DefaultDisguise.Get();

if (pExt->DefaultDisguise)
if (pDefault && pDefault->WhatAmI() == AbstractType::InfantryType)
{
pThis->Disguise = pExt->DefaultDisguise;
pThis->Disguise = pDefault;
pThis->DisguisedAsHouse = pThis->Owner;
pThis->Disguised = true;
return 0x5227BF;
return SetDisguise;
}

pThis->Disguised = false;

return 0;
}

DEFINE_HOOK(0x746720, UnitClass_ClearDisguise_DefaultDisguise, 0x5)
{
enum { SetDisguise = 0x746747 };

GET(UnitClass*, pThis, ECX);
const auto pType = pThis->Type;

if (!pType->PermaDisguise)
return 0;

const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType);
const auto pDefault = pTypeExt->DefaultDisguise.Get();
pThis->Disguise = pDefault && pDefault->WhatAmI() == AbstractType::UnitType ? pDefault : pType;
pThis->DisguisedAsHouse = pThis->Owner;
pThis->Disguised = true;
return SetDisguise;
}

DEFINE_HOOK(0x74691D, UnitClass_UpdateDisguise_EMP, 0x6)
{
GET(UnitClass*, pThis, ESI);
// Remove mirage disguise if under emp or being flipped, approximately 15 deg
// Deactivated mirage should still be able to keep disguise
if (pThis->IsUnderEMP() || std::abs(pThis->AngleRotatedForwards) > 0.25 || std::abs(pThis->AngleRotatedSideways) > 0.25)
if (pThis->Deactivated || pThis->IsUnderEMP() || std::abs(pThis->AngleRotatedForwards) > 0.25 || std::abs(pThis->AngleRotatedSideways) > 0.25)
{
pThis->ClearDisguise();
R->EAX(pThis->MindControlRingAnim);
Expand All @@ -504,6 +523,234 @@ DEFINE_HOOK(0x74691D, UnitClass_UpdateDisguise_EMP, 0x6)
return 0x746931;
}

DEFINE_HOOK(0x7466DC, UnitClass_DisguiseAs_DisguiseAsVehicle, 0x6)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEFINE_HOOK(0x7466D8, UnitClass_DisguiseAs_DisguiseAsVehicle, 0xA) may be better?

{
enum { SkipGameCode = 0x746712 };

GET(UnitClass*, pThis, EDI);
GET(UnitClass*, pTarget, ESI);
const bool targetDisguised = pTarget->IsDisguised();

pThis->Disguise = targetDisguised ? pTarget->GetDisguise(true) : pTarget->Type;
pThis->DisguisedAsHouse = targetDisguised ? pTarget->GetDisguiseHouse(true) : pTarget->Owner;
reinterpret_cast<void(__thiscall*)(TechnoClass*, AbstractClass*)>(0x70E280)(pThis, pTarget);//pThis->TechnoClass::DisguiseAs(pTarget);
return SkipGameCode;
}

DEFINE_HOOK(0x746AFF, UnitClass_Desguise_Update_MoveToClear, 0xA)
{
enum { DontClearDisguise = 0x746A9C };

GET(TechnoClass*, pThis, ESI);

const auto pDisguise = pThis->Disguise;
return pDisguise && pDisguise->WhatAmI() == UnitTypeClass::AbsID ? DontClearDisguise : 0;
}

DEFINE_HOOK(0x74659B, UnitClass_RemoveGunner_ClearDisguise, 0x6)
{
GET(UnitClass*, pThis, EDI);

if (!pThis->IsDisguised())
return 0;

if (const auto pWeapon = pThis->GetWeapon(pThis->CurrentWeaponNumber)->WeaponType)
{
const auto pWarhead = pWeapon->Warhead;

if (pWarhead && pWarhead->MakesDisguise)
return 0;
}

pThis->ClearDisguise();
return 0;
}

#pragma region UnitClass DrawSHP

DEFINE_HOOK(0x73C655, UnitClass_DrawSHP_TechnoType, 0x6)
{
enum { ApplyDisguiseType = 0x73C65B };

GET(UnitClass*, pThis, EBP);

if (pThis->IsDisguised() && !pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer))
{
const auto pTargetType = pThis->GetDisguise(true);

if (pTargetType && pTargetType->WhatAmI() == UnitTypeClass::AbsID)
{
R->ECX(pTargetType);
return ApplyDisguiseType;
}
}

return 0;
}

DEFINE_HOOK(0x73C69D, UnitClass_DrawSHP_TechnoType2, 0x6)
{
enum { ApplyDisguiseType = 0x73C6A3 };

GET(UnitClass*, pThis, EBP);

if (pThis->IsDisguised() && !pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer))
{
const auto pTargetType = pThis->GetDisguise(true);

if (pTargetType && pTargetType->WhatAmI() == UnitTypeClass::AbsID)
{
R->ECX(pThis->GetDisguise(true));
return ApplyDisguiseType;
}
}

return 0;
}

DEFINE_HOOK(0x73C702, UnitClass_DrawSHP_TechnoType3, 0x6)
{
enum { ApplyDisguiseType = 0x73C708 };

GET(UnitClass*, pThis, EBP);

if (pThis->IsDisguised() && !pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer))
{
const auto pTargetType = pThis->GetDisguise(true);

if (pTargetType && pTargetType->WhatAmI() == UnitTypeClass::AbsID)
{
R->ECX(pThis->GetDisguise(true));
return ApplyDisguiseType;
}
}

return 0;
}
Comment on lines +571 to +629
Copy link
Contributor

@CrimRecya CrimRecya Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look the same, I think they should be able to be encapsulated into a function ObjectTypeClass* GetUnitDisguiseAs(UnitClass* pThis)?


DEFINE_HOOK(0x73C725, UnitClass_DrawSHP_HasTurret, 0x5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's 0x6 instead of 0x5.

{
enum { SkipDrawTurret = 0x73CE0D };

GET(UnitClass*, pThis, EBP);

if (pThis->IsDisguised() && !pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer))
{
const auto pDisguise = pThis->GetDisguise(true);

if (pDisguise)
{
const auto pTargetType = TechnoTypeExt::GetTechnoType(pDisguise);

if (pTargetType && !pTargetType->Turret)
return SkipDrawTurret;
}
}

return 0;
}

#pragma endregion

#pragma region UnitClass DrawVoxel

DEFINE_HOOK_AGAIN(0x73B765, UnitClass_DrawVoxel_TurretFacing, 0x5)
DEFINE_HOOK_AGAIN(0x73BA78, UnitClass_DrawVoxel_TurretFacing, 0x6)
DEFINE_HOOK_AGAIN(0x73BD8B, UnitClass_DrawVoxel_TurretFacing, 0x5)
DEFINE_HOOK(0x73BDA3, UnitClass_DrawVoxel_TurretFacing, 0x5)
{
GET(UnitClass*, pThis, EBP);

if (!pThis->Type->Turret && pThis->IsDisguised() && !pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer))
{
const auto pTargetType = TechnoTypeExt::GetTechnoType(pThis->GetDisguise(true));

if (pTargetType && pTargetType->Turret)
{
GET(DirStruct*, dir, EAX);
*dir = pThis->PrimaryFacing.Current();
}
}

return 0;
}

DEFINE_HOOK(0x73B8E3, UnitClass_DrawVoxel_HasChargeTurret, 0x5)
{
GET(UnitClass*, pThis, EBP);
GET(UnitTypeClass*, pType, EBX);

if (pType != pThis->Type)
{
if (pType->TurretCount > 0 && !pType->IsGattling)
return 0x73B8EC;
else
return 0x73B92F;
}
else
{
if (!pType->HasMultipleTurrets() || pType->IsGattling)
return 0x73B92F;
else
return 0x73B8FC;
}
Comment on lines +683 to +696
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can make it clearer

	if (!pType->HasMultipleTurrets() || pType->IsGattling)
		return 0x73B92F;

	return pType != pThis->Type ? 0x73B8EC : 0x73B8FC;

}

DEFINE_HOOK(0x73BC28, UnitClass_DrawVoxel_HasChargeTurret2, 0x5)
{
GET(UnitClass*, pThis, EBP);
GET(UnitTypeClass*, pType, EBX);

if (pType != pThis->Type)
{
if (pType->TurretCount > 0 && !pType->IsGattling)
{
if (pThis->CurrentTurretNumber < 0)
R->Stack<int>(0x1C, 0);

return 0x73BC35;
}
else
{
return 0x73BD79;
}
}
else
{
if (!pType->HasMultipleTurrets() || pType->IsGattling)
return 0x73BD79;
else
return 0x73BC49;
}
Comment on lines +704 to +724
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, it can also return 0x73BD79; first if (!pType->HasMultipleTurrets() || pType->IsGattling)

}

DEFINE_HOOK(0x73BA63, UnitClass_DrawVoxel_TurretOffset, 0x5)
{
GET(UnitClass*, pThis, EBP);
GET(UnitTypeClass*, pType, EBX);

if (pType != pThis->Type)
{
if (pType->TurretCount > 0 && !pType->IsGattling)
{
if (pThis->CurrentTurretNumber < 0)
R->Stack<int>(0x1C, 0);

return 0x73BC35;
}
else
{
return 0x73BD79;
}
}

return 0;
}

DEFINE_JUMP(LJMP, 0x706724, 0x706731);

#pragma endregion

#pragma endregion

#pragma region AttackMindControlledDelay
Expand Down
39 changes: 29 additions & 10 deletions src/Ext/Techno/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,20 +245,39 @@ DEFINE_HOOK(0x6F42F7, TechnoClass_Init, 0x2)

DEFINE_HOOK(0x6F421C, TechnoClass_Init_DefaultDisguise, 0x6)
{
GET(TechnoClass*, pThis, ESI);
enum { DefaultDisguise = 0x6F4277 };

auto const pExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
GET(TechnoClass*, pThis, ESI);
const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType());
const auto pDefault = pTypeExt->DefaultDisguise.Get();

// mirage is not here yet
if (pThis->WhatAmI() == AbstractType::Infantry && pExt->DefaultDisguise)
switch (pThis->WhatAmI())
{
pThis->Disguise = pExt->DefaultDisguise;
pThis->DisguisedAsHouse = pThis->Owner;
pThis->Disguised = true;
return 0x6F4277;
}
case AbstractType::Unit:
if (pDefault && pDefault->WhatAmI() == AbstractType::UnitType)
{
pThis->Disguise = pDefault;
pThis->DisguisedAsHouse = pThis->Owner;
pThis->Disguised = true;
return DefaultDisguise;
}

break;

pThis->Disguised = false;
case AbstractType::Infantry:
if (pDefault && pDefault->WhatAmI() == AbstractType::InfantryType)
{
pThis->Disguise = pDefault;
pThis->DisguisedAsHouse = pThis->Owner;
pThis->Disguised = true;
return DefaultDisguise;
}

break;

default:
break;
}

return 0;
}
Expand Down
Loading
Loading