diff --git a/CREDITS.md b/CREDITS.md index bdb498d967..61472e62e8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -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 diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 0ed79088c9..9d474ce1e0 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -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`. @@ -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 diff --git a/docs/Whats-New.md b/docs/Whats-New.md index e6dee4bdbe..fcf8e2e1c3 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -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) diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index 517110b81a..3b29bcf7f1 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -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); @@ -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(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(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(0x1C, 0); + + return 0x73BC35; + } + else + { + return 0x73BD79; + } + } + + return 0; +} + +DEFINE_JUMP(LJMP, 0x706724, 0x706731); + +#pragma endregion + #pragma endregion #pragma region AttackMindControlledDelay diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index e29baed8f5..ce983936f9 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -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; } diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 75d46d3169..c2804691c3 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -139,7 +139,7 @@ class TechnoTypeExt Valueable DestroyAnim_Random; Valueable NotHuman_RandomDeathSequence; - Valueable DefaultDisguise; + Valueable DefaultDisguise; Valueable UseDisguiseMovementSpeed; Nullable OpenTopped_RangeBonus; @@ -484,7 +484,7 @@ class TechnoTypeExt , DestroyAnim_Random { true } , NotHuman_RandomDeathSequence { false } - , DefaultDisguise {} + , DefaultDisguise { nullptr } , UseDisguiseMovementSpeed {} , OpenTopped_RangeBonus {}