Skip to content

[Vanilla Enhancement] Vehicle disguise to vehicle #1803

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 5 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 @@ -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)
{
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;
}

DEFINE_HOOK(0x73C725, UnitClass_DrawSHP_HasTurret, 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;
}
}

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;
}
}

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