diff --git a/CREDITS.md b/CREDITS.md index 8adc325ff5..f420351795 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -408,6 +408,7 @@ This page lists all the individual contributions to the project by their author. - Exclusive SuperWeapon Sidebar - Fix the bug that AlphaImage remained after unit entered tunnel - Weapon target filtering by health percentage + - Directional armor - **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 5050e1ef7a..78b6a067e1 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -1861,6 +1861,38 @@ FireUp= ; integer FireUp.ResetInRetarget=true ; boolean ``` +### Directional armor + +- The damage suffered by the vehicle can now be affected by the hit direction. +- The front and rear judgment ranges are always symmetrical. A front angle of 0.5 indicates that the front direction is the axis, and the 45 degree angle range on both sides belongs to the front judgment range. +A front angle of 1.0 indicates that the 90 degree angle range on both sides belongs to the front judgment range; +- The lateral range refers to the remaining angle range after excluding the front and back sides. +- The warhead needs to have `Directional=true` to enable this effect. +- `Directional.Multiplier` is an additional multiplier used to control the intensity of the effect. + +In `rulesmd.ini` +```ini +[CombatDamage] +DirectionalArmor=false ; boolean +DirectionalArmor.FrontMultiplier=1.0 ; float +DirectionalArmor.SideMultiplier=1.0 ; float +DirectionalArmor.BackMultiplier=1.0 ; float +DirectionalArmor.FrontField=0.5 ; float +DirectionalArmor.BackField=0.5 ; float + +[SOMEVEHICLE] ; VehicleType +DirectionalArmor= ; boolean +DirectionalArmor.FrontMultiplier= ; float +DirectionalArmor.SideMultiplier= ; float +DirectionalArmor.BackMultiplier= ; float +DirectionalArmor.FrontField= ; float +DirectionalArmor.BackField= ; float + +[SOMEWARHEAD] +Directional=false ; boolean +Directional.Multiplier=1.0 ; float +``` + ## Warheads ```{hint} diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 313bee6b4c..54ca9b9e68 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -391,6 +391,7 @@ New: - [Turretless vehicles with `Voxel=no` support use `FireUp` like infantry](New-or-Enhanced-Logics.md#turretless-shape-vehicle-fireup) (by FlyStar) - Infantry support `IsGattling=yes` (by FlyStar) - [Several new Infotypes, no display in specific status and a new single frame display method](User-Interface.md#digital-display) (by CrimRecya) +- Directional armor (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/Bullet/Trajectories/PhobosTrajectory.cpp b/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp index cd405e5825..7dc16a7ee9 100644 --- a/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp +++ b/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 9d081b5892..0f76f34530 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -219,6 +219,13 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->DamageAlliesMultiplier.Read(exINI, GameStrings::CombatDamage, "DamageAlliesMultiplier"); this->DamageEnemiesMultiplier.Read(exINI, GameStrings::CombatDamage, "DamageEnemiesMultiplier"); + this->DirectionalArmor.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor"); + this->DirectionalArmor_FrontMultiplier.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor.FrontMultiplier"); + this->DirectionalArmor_SideMultiplier.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor.SideMultiplier"); + this->DirectionalArmor_BackMultiplier.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor.BackMultiplier"); + this->DirectionalArmor_FrontField.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor.FrontField"); + this->DirectionalArmor_BackField.Read(exINI, GameStrings::CombatDamage, "DirectionalArmor.BackField"); + this->AircraftLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "AircraftLevelLightMultiplier"); this->JumpjetLevelLightMultiplier.Read(exINI, GameStrings::AudioVisual, "JumpjetLevelLightMultiplier"); @@ -462,6 +469,12 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->DamageOwnerMultiplier) .Process(this->DamageAlliesMultiplier) .Process(this->DamageEnemiesMultiplier) + .Process(this->DirectionalArmor) + .Process(this->DirectionalArmor_FrontMultiplier) + .Process(this->DirectionalArmor_SideMultiplier) + .Process(this->DirectionalArmor_BackMultiplier) + .Process(this->DirectionalArmor_FrontField) + .Process(this->DirectionalArmor_BackField) .Process(this->AircraftLevelLightMultiplier) .Process(this->JumpjetLevelLightMultiplier) .Process(this->VoxelLightSource) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index ddb1e9bc4a..b631a4a86d 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -176,6 +176,13 @@ class RulesExt Valueable DamageAlliesMultiplier; Valueable DamageEnemiesMultiplier; + Valueable DirectionalArmor; + Valueable DirectionalArmor_FrontMultiplier; + Valueable DirectionalArmor_SideMultiplier; + Valueable DirectionalArmor_BackMultiplier; + Valueable DirectionalArmor_FrontField; + Valueable DirectionalArmor_BackField; + Valueable AircraftLevelLightMultiplier; Valueable JumpjetLevelLightMultiplier; @@ -358,6 +365,14 @@ class RulesExt , DamageOwnerMultiplier { 1.0 } , DamageAlliesMultiplier { 1.0 } , DamageEnemiesMultiplier { 1.0 } + + , DirectionalArmor { false } + , DirectionalArmor_FrontMultiplier { 1.0 } + , DirectionalArmor_SideMultiplier { 1.0 } + , DirectionalArmor_BackMultiplier { 1.0 } + , DirectionalArmor_FrontField { 0.5 } + , DirectionalArmor_BackField { 0.5 } + , AircraftLevelLightMultiplier { 1.0 } , JumpjetLevelLightMultiplier { 0.0 } , VoxelLightSource { } diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index 36927edb83..93ee1997b7 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -29,6 +29,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) // Calculate Damage Multiplier if (!args->IgnoreDefenses && *args->Damage) { + const auto pTypeExt = pExt->TypeExtData; double multiplier = 1.0; if (!pSourceHouse || !pTargetHouse || !pSourceHouse->IsAlliedWith(pTargetHouse)) @@ -38,6 +39,21 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) else multiplier = pWHExt->DamageOwnerMultiplier.Get(pRules->DamageOwnerMultiplier); + if (pTypeExt->DirectionalArmor.Get(RulesExt::Global()->DirectionalArmor) && pThis->WhatAmI() == AbstractType::Unit && WarheadTypeExt::HitDirection >= 0 && args->DistanceToEpicenter <= 64) + { + const int tarFacing = pThis->PrimaryFacing.Current().GetValue<16>(); + const int angle = abs(WarheadTypeExt::HitDirection - tarFacing); + const int frontField = static_cast(16384 * pTypeExt->DirectionalArmor_FrontField.Get(RulesExt::Global()->DirectionalArmor_FrontField)); + const int backField = static_cast(16384 * pTypeExt->DirectionalArmor_BackField.Get(RulesExt::Global()->DirectionalArmor_BackField)); + + if (angle >= 32768 - frontField && angle <= 32768 + frontField) + multiplier *= pTypeExt->DirectionalArmor_FrontMultiplier.Get(RulesExt::Global()->DirectionalArmor_FrontMultiplier) * pWHExt->Directional_Multiplier; + else if ((angle < backField && angle >= 0) || (angle > 49152 + backField && angle <= 65536)) + multiplier *= pTypeExt->DirectionalArmor_BackMultiplier.Get(RulesExt::Global()->DirectionalArmor_BackMultiplier) * pWHExt->Directional_Multiplier; + else + multiplier *= pTypeExt->DirectionalArmor_SideMultiplier.Get(RulesExt::Global()->DirectionalArmor_SideMultiplier) * pWHExt->Directional_Multiplier; + } + if (multiplier != 1.0) { const auto sgnDamage = *args->Damage > 0 ? 1 : -1; diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index bb4ece8866..2bae5b179e 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -609,6 +609,13 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->KeepTargetOnMove_NoMorePursuit.Read(exINI, pSection, "KeepTargetOnMove.NoMorePursuit"); this->KeepTargetOnMove_ExtraDistance.Read(exINI, pSection, "KeepTargetOnMove.ExtraDistance"); + this->DirectionalArmor.Read(exINI, pSection, "DirectionalArmor"); + this->DirectionalArmor_FrontMultiplier.Read(exINI, pSection, "DirectionalArmor.FrontMultiplier"); + this->DirectionalArmor_SideMultiplier.Read(exINI, pSection, "DirectionalArmor.SideMultiplier"); + this->DirectionalArmor_BackMultiplier.Read(exINI, pSection, "DirectionalArmor.BackMultiplier"); + this->DirectionalArmor_FrontField.Read(exINI, pSection, "DirectionalArmor.FrontField"); + this->DirectionalArmor_BackField.Read(exINI, pSection, "DirectionalArmor.BackField"); + this->Power.Read(exINI, pSection, "Power"); this->AllowAirstrike.Read(exINI, pSection, "AllowAirstrike"); @@ -1187,6 +1194,13 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->KeepTargetOnMove_NoMorePursuit) .Process(this->KeepTargetOnMove_ExtraDistance) + .Process(this->DirectionalArmor) + .Process(this->DirectionalArmor_FrontMultiplier) + .Process(this->DirectionalArmor_SideMultiplier) + .Process(this->DirectionalArmor_BackMultiplier) + .Process(this->DirectionalArmor_FrontField) + .Process(this->DirectionalArmor_BackField) + .Process(this->Power) .Process(this->AllowAirstrike) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index def357ccc9..47bbd84c48 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -298,6 +298,13 @@ class TechnoTypeExt Valueable KeepTargetOnMove_NoMorePursuit; Valueable KeepTargetOnMove_ExtraDistance; + Nullable DirectionalArmor; + Nullable DirectionalArmor_FrontMultiplier; + Nullable DirectionalArmor_SideMultiplier; + Nullable DirectionalArmor_BackMultiplier; + Nullable DirectionalArmor_FrontField; + Nullable DirectionalArmor_BackField; + Valueable Power; Nullable AllowAirstrike; @@ -640,6 +647,13 @@ class TechnoTypeExt , KeepTargetOnMove_NoMorePursuit { true } , KeepTargetOnMove_ExtraDistance { Leptons(0) } + , DirectionalArmor {} + , DirectionalArmor_FrontMultiplier {} + , DirectionalArmor_SideMultiplier {} + , DirectionalArmor_BackMultiplier {} + , DirectionalArmor_FrontField {} + , DirectionalArmor_BackField {} + , Power { } , AllowAirstrike { } diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index e9ab4c68af..67f3c5797d 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -214,6 +214,9 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Shield_Respawn_Types.Read(exINI, pSection, "Shield.Respawn.Types"); this->Shield_SelfHealing_Types.Read(exINI, pSection, "Shield.SelfHealing.Types"); + this->Directional.Read(exINI, pSection, "Directional"); + this->Directional_Multiplier.Read(exINI, pSection, "Directional.Multiplier"); + this->NotHuman_DeathSequence.Read(exINI, pSection, "NotHuman.DeathSequence"); this->LaunchSW.Read(exINI, pSection, "LaunchSW"); this->LaunchSW_RealLaunch.Read(exINI, pSection, "LaunchSW.RealLaunch"); @@ -460,6 +463,9 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Shield_Respawn_Types) .Process(this->Shield_SelfHealing_Types) + .Process(this->Directional) + .Process(this->Directional_Multiplier) + .Process(this->SpawnsCrate_Types) .Process(this->SpawnsCrate_Weights) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index b6265fb55a..a994c0d5ad 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -113,6 +113,9 @@ class WarheadTypeExt NullableVector Shield_Respawn_Types; NullableVector Shield_SelfHealing_Types; + Valueable Directional; + Valueable Directional_Multiplier; + Valueable NotHuman_DeathSequence; ValueableIdxVector LaunchSW; Valueable LaunchSW_RealLaunch; @@ -292,6 +295,9 @@ class WarheadTypeExt , Shield_Respawn_Types {} , Shield_SelfHealing_Types {} + , Directional { false} + , Directional_Multiplier { 1.0 } + , SpawnsCrate_Types {} , SpawnsCrate_Weights {} @@ -421,6 +427,8 @@ class WarheadTypeExt }; static ExtContainer ExtMap; + static int HitDirection; + static bool LoadGlobals(PhobosStreamReader& Stm); static bool SaveGlobals(PhobosStreamWriter& Stm); diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 6f0bd4241f..a6c9e178e0 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -10,6 +10,7 @@ #include #pragma region Detonation +int WarheadTypeExt::HitDirection = -1; DEFINE_HOOK(0x46920B, BulletClass_Detonate, 0x6) { @@ -21,12 +22,58 @@ DEFINE_HOOK(0x46920B, BulletClass_Detonate, 0x6) auto const pOwner = pBullet->Owner; auto const pHouse = pOwner ? pOwner->Owner : nullptr; auto const pDecidedHouse = pHouse ? pHouse : pBulletExt->FirerHouse; + pWHExt->Detonate(pOwner, pDecidedHouse, pBulletExt, *pCoords); pWHExt->InDamageArea = false; return 0; } +DEFINE_HOOK(0x469A69, BulletClass_Detonate_DamageArea, 0x6) +{ + enum { SkipGameCode = 0x469A88 }; + + GET(BulletClass*, pBullet, ESI); + GET(TechnoClass*, pSourceTechno, EAX); + GET(int, damage, EDX); + GET_BASE(CoordStruct*, coords, 0x8); + const auto pBulletExt = BulletExt::ExtMap.Find(pBullet); + const auto pSourceHouse = pSourceTechno ? pSourceTechno->Owner : pBulletExt->FirerHouse; + const auto pWH = pBullet->WH; + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + + do + { + if (pWHExt->Directional) + { + if (pBullet->Type->Inviso) + { + if (pBullet->SourceCoords.X != pBullet->TargetCoords.X || pBullet->SourceCoords.Y != pBullet->TargetCoords.Y) + { + WarheadTypeExt::HitDirection = DirStruct(Math::atan2(static_cast(pBullet->SourceCoords.Y - pBullet->TargetCoords.Y), static_cast(pBullet->TargetCoords.X - pBullet->SourceCoords.X))).GetValue<16>(); + break; + } + } + else + { + if (pBullet->Velocity.X != 0.0 || pBullet->Velocity.Y != 0.0) + { + WarheadTypeExt::HitDirection = DirStruct((-1) * Math::atan2(pBullet->Velocity.Y, pBullet->Velocity.X)).GetValue<16>(); + break; + } + } + } + + WarheadTypeExt::HitDirection = -1; + } + while (false); + + R->EAX(MapClass::Instance.DamageArea(*coords, damage, pSourceTechno, pWH, true, pSourceHouse)); + WarheadTypeExt::HitDirection = -1; + + return SkipGameCode; +} + DEFINE_HOOK(0x489286, MapClass_DamageArea, 0x6) { GET_BASE(const WarheadTypeClass*, pWH, 0x0C);