From 45d2b78d3f21c9a48bcf32103f74b6198b42b7af Mon Sep 17 00:00:00 2001 From: NetsuNegi39 Date: Sat, 9 Aug 2025 13:11:18 +0800 Subject: [PATCH 1/2] init --- CREDITS.md | 1 + docs/New-or-Enhanced-Logics.md | 10 ++ docs/Whats-New.md | 1 + src/Ext/Techno/Hooks.Misc.cpp | 258 ++++++++++++++++++++++++++++++++- src/Ext/Techno/Hooks.cpp | 37 +++-- src/Ext/TechnoType/Body.cpp | 2 + src/Ext/TechnoType/Body.h | 4 +- 7 files changed, 298 insertions(+), 15 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 625a910865..fd780bca40 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -436,6 +436,7 @@ This page lists all the individual contributions to the project by their author. - Fix the bug that `IsLocomotor=yes` warhead rendering hover units unselectable and undamageable on elevated bridge - 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 + - 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 0933d855d4..4188fd8b1f 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2075,6 +2075,16 @@ FireUp= ; integer FireUp.ResetInRetarget=true ; boolean ``` +### Vehicle disguise to vehicle + +- Now you can make vehicle disguise to other vehicles, like spy. + +In `rulesmd.ini`: +```ini +[SOMEVEHICLE] ; VehicleType +DefaultVehicleDisguise= ; vehicle type +``` + ## Warheads ```{hint} diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 81b6324d09..451a31a25c 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -426,6 +426,7 @@ New: - [Additional attached animation position customizations](Fixed-or-Improved-Logics.md#attached-animation-position-customization) (by Starkku) - Use `SkipCrushSlowdown=true` to avoid the bug related to `Accelerates=true` and `MovementZone=CrushAll` (by TaranDahl) - Units can customize the attack voice that plays when using more weapons (by FlyStar) +- 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..d4a9c081d5 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -473,15 +473,17 @@ 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); - if (pExt->DefaultDisguise) + if (const auto pDefault = pTypeExt->DefaultDisguise.Get()) { - pThis->Disguise = pExt->DefaultDisguise; + pThis->Disguise = pDefault; pThis->DisguisedAsHouse = pThis->Owner; pThis->Disguised = true; - return 0x5227BF; + return SetDisguise; } pThis->Disguised = false; @@ -489,12 +491,30 @@ DEFINE_HOOK(0x522790, InfantryClass_ClearDisguise_DefaultDisguise, 0x6) 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->DefaultVehicleDisguise.Get(); + pThis->Disguise = pDefault ? 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 +524,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 f318c5207f..cbdbce19a6 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -245,21 +245,40 @@ 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()); - // 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 (const auto pDefault = pTypeExt->DefaultVehicleDisguise.Get()) + { + pThis->Disguise = pDefault; + pThis->DisguisedAsHouse = pThis->Owner; + pThis->Disguised = true; + return DefaultDisguise; + } + + break; + + case AbstractType::Infantry: + if (const auto pDefault = pTypeExt->DefaultDisguise.Get()) + { + pThis->Disguise = pDefault; + pThis->DisguisedAsHouse = pThis->Owner; + pThis->Disguised = true; + return DefaultDisguise; + } + + break; + + default: + break; } pThis->Disguised = false; - return 0; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index e14d778d06..3927ffd580 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -691,6 +691,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->NotHuman_RandomDeathSequence.Read(exINI, pSection, "NotHuman.RandomDeathSequence"); this->DefaultDisguise.Read(exINI, pSection, "DefaultDisguise"); + this->DefaultVehicleDisguise.Read(exINI, pSection, "DefaultVehicleDisguise"); this->UseDisguiseMovementSpeed.Read(exINI, pSection, "UseDisguiseMovementSpeed"); this->OpenTopped_RangeBonus.Read(exINI, pSection, "OpenTopped.RangeBonus"); @@ -1317,6 +1318,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->DestroyAnim_Random) .Process(this->NotHuman_RandomDeathSequence) .Process(this->DefaultDisguise) + .Process(this->DefaultVehicleDisguise) .Process(this->UseDisguiseMovementSpeed) .Process(this->WeaponBurstFLHs) .Process(this->EliteWeaponBurstFLHs) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 94f4db6c47..ebfdcc5769 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -140,6 +140,7 @@ class TechnoTypeExt Valueable NotHuman_RandomDeathSequence; Valueable DefaultDisguise; + Valueable DefaultVehicleDisguise; Valueable UseDisguiseMovementSpeed; Nullable OpenTopped_RangeBonus; @@ -482,7 +483,8 @@ class TechnoTypeExt , DestroyAnim_Random { true } , NotHuman_RandomDeathSequence { false } - , DefaultDisguise {} + , DefaultDisguise { nullptr } + , DefaultVehicleDisguise { nullptr } , UseDisguiseMovementSpeed {} , OpenTopped_RangeBonus {} From 3898511275f28b0970530e6112c9f919a3680563 Mon Sep 17 00:00:00 2001 From: NetsuNegi39 Date: Tue, 12 Aug 2025 09:33:07 +0800 Subject: [PATCH 2/2] update --- docs/New-or-Enhanced-Logics.md | 33 ++++++++++++--------------------- src/Ext/Techno/Hooks.Misc.cpp | 9 ++++----- src/Ext/Techno/Hooks.cpp | 6 +++--- src/Ext/TechnoType/Body.cpp | 2 -- src/Ext/TechnoType/Body.h | 4 +--- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 4188fd8b1f..b319611776 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 @@ -2075,16 +2076,6 @@ FireUp= ; integer FireUp.ResetInRetarget=true ; boolean ``` -### Vehicle disguise to vehicle - -- Now you can make vehicle disguise to other vehicles, like spy. - -In `rulesmd.ini`: -```ini -[SOMEVEHICLE] ; VehicleType -DefaultVehicleDisguise= ; vehicle type -``` - ## Warheads ```{hint} diff --git a/src/Ext/Techno/Hooks.Misc.cpp b/src/Ext/Techno/Hooks.Misc.cpp index d4a9c081d5..3b29bcf7f1 100644 --- a/src/Ext/Techno/Hooks.Misc.cpp +++ b/src/Ext/Techno/Hooks.Misc.cpp @@ -477,8 +477,9 @@ DEFINE_HOOK(0x522790, InfantryClass_ClearDisguise_DefaultDisguise, 0x6) GET(InfantryClass*, pThis, ECX); const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Type); + const auto pDefault = pTypeExt->DefaultDisguise.Get(); - if (const auto pDefault = pTypeExt->DefaultDisguise.Get()) + if (pDefault && pDefault->WhatAmI() == AbstractType::InfantryType) { pThis->Disguise = pDefault; pThis->DisguisedAsHouse = pThis->Owner; @@ -486,8 +487,6 @@ DEFINE_HOOK(0x522790, InfantryClass_ClearDisguise_DefaultDisguise, 0x6) return SetDisguise; } - pThis->Disguised = false; - return 0; } @@ -502,8 +501,8 @@ DEFINE_HOOK(0x746720, UnitClass_ClearDisguise_DefaultDisguise, 0x5) return 0; const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); - const auto pDefault = pTypeExt->DefaultVehicleDisguise.Get(); - pThis->Disguise = pDefault ? pDefault : 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; diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index cf0ec3ef11..ce983936f9 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -249,11 +249,12 @@ DEFINE_HOOK(0x6F421C, TechnoClass_Init_DefaultDisguise, 0x6) GET(TechnoClass*, pThis, ESI); const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + const auto pDefault = pTypeExt->DefaultDisguise.Get(); switch (pThis->WhatAmI()) { case AbstractType::Unit: - if (const auto pDefault = pTypeExt->DefaultVehicleDisguise.Get()) + if (pDefault && pDefault->WhatAmI() == AbstractType::UnitType) { pThis->Disguise = pDefault; pThis->DisguisedAsHouse = pThis->Owner; @@ -264,7 +265,7 @@ DEFINE_HOOK(0x6F421C, TechnoClass_Init_DefaultDisguise, 0x6) break; case AbstractType::Infantry: - if (const auto pDefault = pTypeExt->DefaultDisguise.Get()) + if (pDefault && pDefault->WhatAmI() == AbstractType::InfantryType) { pThis->Disguise = pDefault; pThis->DisguisedAsHouse = pThis->Owner; @@ -278,7 +279,6 @@ DEFINE_HOOK(0x6F421C, TechnoClass_Init_DefaultDisguise, 0x6) break; } - pThis->Disguised = false; return 0; } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 6e536fe2b1..f32b4669f9 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -691,7 +691,6 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->NotHuman_RandomDeathSequence.Read(exINI, pSection, "NotHuman.RandomDeathSequence"); this->DefaultDisguise.Read(exINI, pSection, "DefaultDisguise"); - this->DefaultVehicleDisguise.Read(exINI, pSection, "DefaultVehicleDisguise"); this->UseDisguiseMovementSpeed.Read(exINI, pSection, "UseDisguiseMovementSpeed"); this->OpenTopped_RangeBonus.Read(exINI, pSection, "OpenTopped.RangeBonus"); @@ -1320,7 +1319,6 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->DestroyAnim_Random) .Process(this->NotHuman_RandomDeathSequence) .Process(this->DefaultDisguise) - .Process(this->DefaultVehicleDisguise) .Process(this->UseDisguiseMovementSpeed) .Process(this->WeaponBurstFLHs) .Process(this->EliteWeaponBurstFLHs) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 059888f41d..c2804691c3 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -139,8 +139,7 @@ class TechnoTypeExt Valueable DestroyAnim_Random; Valueable NotHuman_RandomDeathSequence; - Valueable DefaultDisguise; - Valueable DefaultVehicleDisguise; + Valueable DefaultDisguise; Valueable UseDisguiseMovementSpeed; Nullable OpenTopped_RangeBonus; @@ -486,7 +485,6 @@ class TechnoTypeExt , NotHuman_RandomDeathSequence { false } , DefaultDisguise { nullptr } - , DefaultVehicleDisguise { nullptr } , UseDisguiseMovementSpeed {} , OpenTopped_RangeBonus {}