diff --git a/CREDITS.md b/CREDITS.md index c8555f9fa7..8f8bd51dd3 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -358,6 +358,7 @@ This page lists all the individual contributions to the project by their author. - **Ollerus** - Build limit group enhancement - Customizable rocker amplitude + - Damage blocking system - **handama** - AI script action to jump back to previous script - **Ares developers** - YRpp and Syringe which are used, save/load, project foundation and generally useful code from Ares diff --git a/Phobos.vcxproj b/Phobos.vcxproj index da0fa52603..fbab7b0269 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -20,6 +20,7 @@ + @@ -183,6 +184,7 @@ + diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index f42cf5bb76..0526a7a913 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -40,6 +40,8 @@ This page describes all the engine features that are either new and introduced b - On TechnoTypes with `OpenTopped=true`, `OpenTopped.UseTransportRangeModifiers` can be set to true to make passengers firing out use the transport's active range bonuses instead. - `Crit.Multiplier` and `Crit.ExtraChance` can be used to multiply the [critical hit](#chance-based-extra-damage-or-warhead-detonation--critical-hits) chance or grant a fixed bonus to it for the object the effect is attached to, respectively. - `Crit.AllowWarheads` can be used to list only Warheads that can benefit from this critical hit chance multiplier and `Crit.DisallowWarheads` weapons that are not allowed to, respectively. + - `Block.ChanceMultiplier` and `Block.ExtraChance` can be used to multiply the [block](#block-damage) chance or grant a fixed bonus to it for the object the effect is attached to, respectively. + - `Block.DamageMult.Multiplier` and `Block.DamageMult.Bonus` can be used to multiply the [block](#block-damage) damage multiplier or grant a fixed bonus to it for the object the effect is attached to, respectively. - `RevengeWeapon` can be used to temporarily grant the specified weapon as a [revenge weapon](#revenge-weapon) for the attached object. - `RevengeWeapon.AffectsHouses` customizes which houses can trigger the revenge weapon. - `ReflectDamage` can be set to true to have any positive damage dealt to the object the effect is attached to be reflected back to the attacker. `ReflectDamage.Warhead` determines which Warhead is used to deal the damage, defaults to `[CombatDamage]`->`C4Warhead`. If `ReflectDamage.Warhead` is set to true, the Warhead is fully detonated instead of used to simply deal damage. `ReflectDamage.Multiplier` is a multiplier to the damage received and then reflected back. Already reflected damage cannot be further reflected back. @@ -111,6 +113,10 @@ Crit.Multiplier=1.0 ; floating point value Crit.ExtraChance=0.0 ; floating point value Crit.AllowWarheads= ; list of WarheadTypes Crit.DisallowWarheads= ; list of WarheadTypes +Block.ChanceMultiplier=1.0 ; floating point value +Block.ExtraChance=0.0 ; floating point value +Block.DamageMult.Multiplier=1.0 ; floating point value +Block.DamageMult.Bonus=0.0 ; floating point value RevengeWeapon= ; WeaponType RevengeWeapon.AffectsHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) ReflectDamage=false ; boolean @@ -308,6 +314,7 @@ ImmuneToCrit=no ; boolean Tint.Color= ; integer - R,G,B Tint.Intensity=0.0 ; floating point value Tint.VisibleToHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +CanBlock=true ; boolean [SOMETECHNO] ; TechnoType ShieldType=SOMESHIELDTYPE ; ShieldType; none by default @@ -954,7 +961,108 @@ AutoFire=false ; boolean AutoFire.TargetSelf=false ; boolean ``` +### Block damage + +- You can now make techno have a chance to block the incoming damage, which will make it multiply by a set percentage. + - `CanBlock`, if set to false, make this TechnoType can't trigger a block, even if the block settings are from warheads, or affected by [attached effects](#attached-effects) with block chance modifiers. +- `Block.Chances` determines chance for a block to occur. Value from position matching the position from `Block.AffectBelowPercents` is used if found, or 0.0 if not found. +- `Block.DamageMultipliers` determines the multiplier of received damage. Value from position matching the position from `Block.AffectBelowPercents` is used if found, or 1.0 if not found. +- `Block.AffectBelowPercents`, if set to a single value, determines minimum percentage of their maximum `Strength` that targets must have left to be affected by a critical hit. If set to a list of values, it'll further determine `Block.Chances` and `Block.DamageMultipliers` when target health is below the certain percentage listed here. +- `Block.AffectsHouses` can be used to customize houses that damage from this firer can be blocked. Notice that not all damage has a firer, such as damage dealt by anims. +- Following conditions determine whether a block can be triggered. + - `Block.CanActive.NoFirer` determines if the block can be triggered when the damage has no firer. + - `Block.CanActive.Powered` determines if the block can be triggered when the techno is deactivated (`PoweredUnit` or affected by EMP) or on low power. + - `Block.CanActive.ShieldActive` determines if the block can be triggered when the techno has an active [shield](#shields). If you only want it to be triggered by certain type of shield, you can further set `CanBlock` for them. + - `Block.CanActive.ShieldInactive` determines if the block can be triggered when the techno doesn't have an active shield. + - `Block.CanActive.ZeroDamage` and `Block.CanActive.NegativeDamage` determine if the block can be triggered when the damage is equal to or below 0. determines if the block can be triggered when the damage is below 0, respectively. + - `Block.CanActive.Move` and `Block.CanActive.Stationary`determines the block can be triggered when the techno is moving or not, respectively. +- `Block.Flash`, if set to true, makes it so that a light flash is generated when a block is triggered. Size of the flash is determined by damage dealt (based on original damage instead of the blocked one), unless `Block.Flash.FixedSize` is set to a number, in which case that value is used instead (range of values that produces visible effect are increments of 4 from 81 to 252, anything higher or below does not have effect). Color can be customized via `Block.Flash.Red/Green/Blue`. If `Block.Flash.Black` is set to true, the generated flash will be black regardless of other color settings. +- `Block.Anims` can be used to set animation to be displayed if a block is triggered. If more than one animation is listed, a random one is selected. +- `Block.Weapon`, if set, will be fired at the techno once a block is triggered. +- `Block.ReflectDamage` can be set to true to have positive damage dealt to the object to be reflected back to the attacker, if a block is triggered and there is a firer of the damage. `Block.ReflectDamage.Warhead` determines which Warhead is used to deal the damage, defaults to `[CombatDamage]`->`C4Warhead`. If `Block.ReflectDamage.Warhead.Detonate` is set to true, the Warhead is fully detonated instead of used to simply deal damage. `Block.ReflectDamage.Chance` determines the chance of reflection. - `Block.ReflectDamage.AffectsHouses` customizes which houses can trigger the reflect damage, default to `Block.AffectsHouses`. `Block.ReflectDamage.Multiplier` is a multiplier to the damage received and then reflected back (based on original damage instead of the blocked one), while `Block.ReflectDamage.Override` directly overrides the damage. Already reflected damage cannot be further reflected back. + - Warheads can prevent reflect damage from occuring by setting `SuppressReflectDamage` to true. + +- Warheads can also use the above settings to determine their own block behaviors when hitting the target. + - Despite all block settings can be overridden, if you want to override `Block.Chances` or `Block.DamageMultipliers`, it's recommended to override `Block.AffectBelowPercents` in the meantime since they're lists with values in correpsonding positions. +- `Block.BasedOnWarhead`, if set to true, makes the warhead block settings as a base when calculating a block. If set to false, block settings on techno is used as a base. +- `Block.AllowOverride`, if set to true, allows the block settings from techno or warhead to override the other, if set. If `Block.BasedOnWarhead` is set to true, then it'll uses the set values from techno to override warheads', while keeping the unset values as the same. If it's set to false, then it'll do the override from warhead to techno with the same rule. +- `Block.IgnoreAttachEffect` can be set on WarheadTypes to make their attack ignore modifiers of `Block.Chances` and `Block.DamageMultipliers` from the [attached effects](#attached-effects) on the techno. +- `Block.ChanceMultiplier` and `Block.ExtraChance` can be set on WarheadTypes to multiply the block chance or grant a fixed bonus to it when these warheads hit their targets, respectively. +- `Block.DamageMult.Multiplier` and `Block.DamageMult.Bonus` can be set on WarheadTypes to multiply the block damage multiplier or grant a fixed bonus to it when these warheads hit their targets, respectively. +- `ImmuneToBlock` can be set on WarheadTypes to make them can't trigger a block. + +In `rulesmd.ini`: +```ini +[SOMETECHNO] ; TechnoType +CanBlock=true ; boolean +Block.Chances= ; list of floating-point values (percentage or absolute) (0.0-1.0) +Block.DamageMultipliers= ; list of floating-point values (percentage or absolute) +Block.AffectBelowPercents= ; list of floating-point values (percentage or absolute) (0.0-1.0) +Block.AffectsHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +Block.CanActive.NoFirer=true ; boolean +Block.CanActive.Powered=false ; boolean +Block.CanActive.ShieldActive=true ; boolean +Block.CanActive.ShieldInactive=true ; boolean +Block.CanActive.ZeroDamage=false ; boolean +Block.CanActive.NegativeDamage=false ; boolean +Block.CanActive.Move=true ; boolean +Block.CanActive.Stationary=true ; boolean +Block.Flash=false ; boolean +Block.Flash.FixedSize= ; integer +Block.Flash.Red=true ; boolean +Block.Flash.Green=true ; boolean +Block.Flash.Blue=true ; boolean +Block.Flash.Black=false ; boolean +Block.Anims= ; list of animations +Block.Weapon= ; WeaponType +Block.ReflectDamage=false ; boolean +Block.ReflectDamage.Chance=1.0 ; floating point value +Block.ReflectDamage.Warhead= ; WarheadType +Block.ReflectDamage.Warhead.Detonate=false ; WarheadType +Block.ReflectDamage.Multiplier=1.0 ; floating point value, percents or absolute +Block.ReflectDamage.AffectsHouses= ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +Block.ReflectDamage.Override= ; integer + +[SOMEWARHEAD] ; WarheadType +Block.Chances= ; list of floating-point values (percentage or absolute) (0.0-1.0) +Block.DamageMultipliers= ; list of floating-point values (percentage or absolute) +Block.AffectBelowPercents= ; list of floating-point values (percentage or absolute) (0.0-1.0) +Block.AffectsHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +Block.CanActive.NoFirer=true ; boolean +Block.CanActive.Powered=false ; boolean +Block.CanActive.ShieldActive=true ; boolean +Block.CanActive.ShieldInactive=true ; boolean +Block.CanActive.ZeroDamage=false ; boolean +Block.CanActive.NegativeDamage=false ; boolean +Block.CanActive.Move=true ; boolean +Block.CanActive.Stationary=true ; boolean +Block.Flash=false ; boolean +Block.Flash.FixedSize= ; integer +Block.Flash.Red=true ; boolean +Block.Flash.Green=true ; boolean +Block.Flash.Blue=true ; boolean +Block.Flash.Black=false ; boolean +Block.Anims= ; list of animations +Block.Weapon= ; WeaponType +Block.ReflectDamage=false ; boolean +Block.ReflectDamage.Chance=1.0 ; floating point value +Block.ReflectDamage.Warhead= ; WarheadType +Block.ReflectDamage.Warhead.Detonate=false ; WarheadType +Block.ReflectDamage.Multiplier=1.0 ; floating point value, percents or absolute +Block.ReflectDamage.AffectsHouses= ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +Block.ReflectDamage.Override= ; integer +Block.BasedOnWarhead=false ; boolean +Block.AllowOverride=true ; boolean +Block.IgnoreAttachEffect=true ; boolean +Block.ChanceMultiplier=1.0 ; floating point value +Block.ExtraChance=0.0 ; floating point value +Block.DamageMult.Multiplier=1.0 ; floating point value +Block.DamageMult.Bonus=0.0 ; floating point value +ImmuneToBlock=false ; boolean +``` + ### Build limit group + - You can now make different technos share build limit in a group. - `BuildLimitGroup.Types` determines the technos that'll be used for build limit conditions of the selected techno. Note that the limit won't be applied to technos in the list. To do this, you'll have to manually define the limit per techno. - `BuildLimitGroup.Nums` determines the amount of technos that would reach the build limit. If using a single integer, it'll use the sum of all technos in the group to calculate build limit. If using a list of integers with the same size of `BuildLimitGroup.Types`, it'll calculate build limit per techno with value matching the position in `BuildLimitGroup.Types` is used for that type. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index dd8bb1a747..0bcbeeb094 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -451,6 +451,7 @@ New: - Nonprovocative Warheads (by Starkku) - Option to restore `PowerSurplus` setting for AI (by Starkku) - `FireOnce` infantry sequence reset toggle (by Starkku) +- Damage blocking system (by Ollerus) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 2a0d4bf97d..deaa15ce0d 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -465,6 +466,301 @@ int TechnoExt::ExtData::GetAttachedEffectCumulativeCount(AttachEffectTypeClass* return foundCount; } +int TechnoExt::CalculateBlockDamage(TechnoClass* pThis, args_ReceiveDamage* args) +{ + int damage = *args->Damage; + const auto pWHExt = WarheadTypeExt::ExtMap.Find(args->WH); + + if (pWHExt->ImmuneToBlock) + return damage; + + const auto pExt = TechnoExt::ExtMap.Find(pThis); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + if (!pTypeExt->CanBlock) + return damage; + + const auto pBlockType = pWHExt->Block_BasedOnWarhead ? pWHExt->BlockType.get() : pTypeExt->BlockType.get(); + const auto pOtherBlock = !pWHExt->Block_BasedOnWarhead ? pWHExt->BlockType.get() : pTypeExt->BlockType.get(); + std::vector blockChances = pBlockType->Block_Chances; + std::vector blockDamageMultipliers = pBlockType->Block_DamageMultipliers; + + if (pWHExt->Block_AllowOverride) + { + blockChances = !pOtherBlock->Block_Chances.empty() ? pOtherBlock->Block_Chances : blockChances; + blockDamageMultipliers = !pOtherBlock->Block_DamageMultipliers.empty() ? pOtherBlock->Block_DamageMultipliers : blockDamageMultipliers; + } + + if (!pWHExt->Block_IgnoreAttachEffect) + { + std::pair, std::vector> blockPair = TechnoExt::GetBlockChanceAndDamageMult(pThis, blockChances, blockDamageMultipliers); + blockChances = blockPair.first; + blockDamageMultipliers = blockPair.second; + } + + if ((blockChances.size() == 1 && blockChances[0] + pWHExt->Block_ExtraChance > 0.0) || blockChances.size() > 1) + { + // handle block conditions first + auto blockAffectBelowPercents = pBlockType->Block_AffectBelowPercents; + auto blockAffectsHouses = pBlockType->Block_AffectsHouses.Get(AffectedHouse::All); + bool blockCanActiveZeroDamage = pBlockType->Block_CanActive_ZeroDamage.Get(false); + bool blockCanActiveNegativeDamage = pBlockType->Block_CanActive_NegativeDamage.Get(false); + bool blockCanActivePowered = pBlockType->Block_CanActive_Powered.Get(false); + bool blockCanActiveNoFirer = pBlockType->Block_CanActive_NoFirer.Get(true); + bool blockCanActiveShieldActive = pBlockType->Block_CanActive_ShieldActive.Get(true); + bool blockCanActiveShieldInactive = pBlockType->Block_CanActive_ShieldInactive.Get(true); + bool blockCanActiveMove = pBlockType->Block_CanActive_Move.Get(true); + bool blockCanActiveStationary = pBlockType->Block_CanActive_Stationary.Get(true); + + if (pWHExt->Block_AllowOverride) + { + blockAffectBelowPercents = !pOtherBlock->Block_AffectBelowPercents.empty() ? pOtherBlock->Block_AffectBelowPercents : blockAffectBelowPercents; + blockAffectsHouses = pOtherBlock->Block_AffectsHouses.isset() ? pOtherBlock->Block_AffectsHouses.Get() : blockAffectsHouses; + blockCanActiveZeroDamage = pOtherBlock->Block_CanActive_ZeroDamage.isset() ? pOtherBlock->Block_CanActive_ZeroDamage : blockCanActiveZeroDamage; + blockCanActiveNegativeDamage = pOtherBlock->Block_CanActive_NegativeDamage.isset() ? pOtherBlock->Block_CanActive_NegativeDamage : blockCanActiveNegativeDamage; + blockCanActivePowered = pOtherBlock->Block_CanActive_Powered.isset() ? pOtherBlock->Block_CanActive_Powered : blockCanActivePowered; + blockCanActiveNoFirer = pOtherBlock->Block_CanActive_NoFirer.isset() ? pOtherBlock->Block_CanActive_NoFirer : blockCanActiveNoFirer; + blockCanActiveShieldActive = pOtherBlock->Block_CanActive_ShieldActive.isset() ? pOtherBlock->Block_CanActive_ShieldActive : blockCanActiveShieldActive; + blockCanActiveShieldInactive = pOtherBlock->Block_CanActive_ShieldInactive.isset() ? pOtherBlock->Block_CanActive_ShieldInactive : blockCanActiveShieldInactive; + blockCanActiveMove = pOtherBlock->Block_CanActive_Move.isset() ? pOtherBlock->Block_CanActive_Move : blockCanActiveMove; + blockCanActiveStationary = pOtherBlock->Block_CanActive_Stationary.isset() ? pOtherBlock->Block_CanActive_Stationary : blockCanActiveStationary; + } + + if (blockAffectBelowPercents.size() > 0 && pThis->GetHealthPercentage() > blockAffectBelowPercents[0]) + return damage; + + if (damage == 0 && !blockCanActiveZeroDamage) + return 0; + else if (damage < 0 && !blockCanActiveNegativeDamage) + return damage; + + unsigned int level = 0; + + if (blockAffectBelowPercents.size() > 0) + { + for (; level < blockAffectBelowPercents.size() - 1; level++) + { + if (pThis->GetHealthPercentage() > blockAffectBelowPercents[level + 1]) + break; + } + } + + double dice = ScenarioClass::Instance->Random.RandomDouble(); + + if (blockChances.size() == 1) + { + if (blockChances[0] * pWHExt->Block_ChanceMultiplier + pWHExt->Block_ExtraChance < dice) + return damage; + } + else if (blockChances.size() <= level || blockChances[level] * pWHExt->Block_ChanceMultiplier + pWHExt->Block_ExtraChance < dice) + { + return damage; + } + + if (blockCanActivePowered) + { + bool isActive = !(pThis->Deactivated || pThis->IsUnderEMP()); + + if (isActive && pThis->WhatAmI() == AbstractType::Building) + { + auto const pBuilding = static_cast(pThis); + isActive = pBuilding->IsPowerOnline(); + } + + if (!isActive) + return damage; + } + + if (auto const pFoot = abstract_cast(pThis)) + { + if (pFoot->Locomotor->Is_Moving()) + { + if (!blockCanActiveMove) + return damage; + } + else if (!blockCanActiveStationary) + { + return damage; + } + } + + const auto pFirer = args->Attacker; + + if (pFirer) + { + if (pFirer->Owner && !EnumFunctions::CanTargetHouse(blockAffectsHouses, pFirer->Owner, pThis->Owner)) + return damage; + } + else if (!blockCanActiveNoFirer) + { + return damage; + } + + const auto pShieldData = pExt->Shield.get(); + + if (pShieldData && pShieldData->IsActive()) + { + if (!blockCanActiveShieldActive || !pShieldData->GetType()->CanBlock) + return damage; + } + else if (!blockCanActiveShieldInactive) + { + return damage; + } + + // a block is triggered + auto blockAnims = pBlockType->Block_Anims; + auto blockWeapon = pBlockType->Block_Weapon.Get(); + bool blockFlash = pBlockType->Block_Flash.Get(false); + bool blockReflectDamage = pBlockType->Block_ReflectDamage.Get(false); + double blockReflectDamageChance = pBlockType->Block_ReflectDamage_Chance.Get(1.0); + + if (pWHExt->Block_AllowOverride) + { + blockAnims = !pOtherBlock->Block_Anims.empty() ? pOtherBlock->Block_Anims : blockAnims; + blockWeapon = pOtherBlock->Block_Weapon.isset() ? pOtherBlock->Block_Weapon.Get() : blockWeapon; + blockFlash = pOtherBlock->Block_Flash.isset() ? pOtherBlock->Block_Flash.Get() : blockFlash; + blockReflectDamage = pOtherBlock->Block_ReflectDamage.isset() ? pOtherBlock->Block_ReflectDamage.Get() : blockReflectDamage; + blockReflectDamageChance = pOtherBlock->Block_ReflectDamage_Chance.isset() ? pOtherBlock->Block_ReflectDamage_Chance.Get() : blockReflectDamageChance; + } + + if (blockAnims.size() > 0) + { + int idx = ScenarioClass::Instance->Random.RandomRanged(0, blockAnims.size() - 1); + GameCreate(blockAnims[idx], pThis->Location); + } + + if (blockFlash) + { + int size = pBlockType->Block_Flash_FixedSize.Get(damage * 2); + SpotlightFlags flags = SpotlightFlags::NoColor; + bool blockFlashRed = pBlockType->Block_Flash_Red.Get(true); + bool blockFlashGreen = pBlockType->Block_Flash_Green.Get(true); + bool blockFlashBlue = pBlockType->Block_Flash_Blue.Get(true); + bool blockFlashBlack = pBlockType->Block_Flash_Black.Get(false); + + if (pWHExt->Block_AllowOverride) + { + size = pOtherBlock->Block_Flash_FixedSize.isset() ? pOtherBlock->Block_Flash_FixedSize.Get() : size; + blockFlashRed = pOtherBlock->Block_Flash_Red.isset() ? pOtherBlock->Block_Flash_Red.Get() : blockFlashRed; + blockFlashGreen = pOtherBlock->Block_Flash_Green.isset() ? pOtherBlock->Block_Flash_Green.Get() : blockFlashGreen; + blockFlashBlue = pOtherBlock->Block_Flash_Blue.isset() ? pOtherBlock->Block_Flash_Blue.Get() : blockFlashBlue; + blockFlashBlack = pOtherBlock->Block_Flash_Black.isset() ? pOtherBlock->Block_Flash_Black.Get() : blockFlashBlack; + } + + if (blockFlashBlack) + { + flags = SpotlightFlags::NoColor; + } + else + { + if (!blockFlashRed) + flags = SpotlightFlags::NoRed; + if (!blockFlashGreen) + flags |= SpotlightFlags::NoGreen; + if (!blockFlashBlue) + flags |= SpotlightFlags::NoBlue; + } + + MapClass::FlashbangWarheadAt(size, args->WH, pThis->Location, true, flags); + } + + if (blockReflectDamage && blockReflectDamageChance >= ScenarioClass::Instance->Random.RandomDouble() + && damage > 0 && pFirer && !pWHExt->SuppressReflectDamage && !pWHExt->Reflected) + { + auto pWHRef = pBlockType->Block_ReflectDamage_Warhead.Get(RulesClass::Instance->C4Warhead); + auto blockReflectDamageAffectsHouses = pBlockType->Block_ReflectDamage_AffectsHouses.Get(blockAffectsHouses); + Nullable blockReflectDamageOverride = pBlockType->Block_ReflectDamage_Override; + double blockReflectDamageMultiplier = pBlockType->Block_ReflectDamage_Multiplier.Get(1.0); + bool blockReflectDamageWHDetonate = pBlockType->Block_ReflectDamage_Warhead_Detonate.Get(false); + + if (pWHExt->Block_AllowOverride) + { + pWHRef = pOtherBlock->Block_ReflectDamage_Warhead.isset() ? pOtherBlock->Block_ReflectDamage_Warhead.Get() : pWHRef; + blockReflectDamageOverride = pOtherBlock->Block_ReflectDamage_Override.isset() ? pOtherBlock->Block_ReflectDamage_Override : blockReflectDamageOverride; + blockReflectDamageAffectsHouses = pOtherBlock->Block_ReflectDamage_AffectsHouses.isset() ? pOtherBlock->Block_ReflectDamage_AffectsHouses.Get() : blockReflectDamageAffectsHouses; + blockReflectDamageMultiplier = pOtherBlock->Block_ReflectDamage_Multiplier.isset() ? pOtherBlock->Block_ReflectDamage_Multiplier.Get() : blockReflectDamageMultiplier; + blockReflectDamageWHDetonate = pOtherBlock->Block_ReflectDamage_Warhead_Detonate.isset() ? pOtherBlock->Block_ReflectDamage_Warhead_Detonate.Get() : blockReflectDamageWHDetonate; + } + + int damageRef = blockReflectDamageOverride.Get(static_cast(damage * blockReflectDamageMultiplier)); + + if (EnumFunctions::CanTargetHouse(blockReflectDamageAffectsHouses, pThis->Owner, pFirer->Owner)) + { + auto const pWHExtRef = WarheadTypeExt::ExtMap.Find(pWHRef); + pWHExtRef->Reflected = true; + + if (blockReflectDamageWHDetonate) + WarheadTypeExt::DetonateAt(pWHRef, pFirer, pThis, damageRef, pThis->Owner); + else + pFirer->ReceiveDamage(&damage, 0, pWHRef, pThis, false, false, pThis->Owner); + + pWHExtRef->Reflected = false; + } + } + + if (blockDamageMultipliers.size() == 1) + damage = static_cast(damage * (blockDamageMultipliers[0] * pWHExt->Block_ChanceMultiplier + pWHExt->Block_ExtraChance)); + else if (blockDamageMultipliers.size() > level) + damage = static_cast(damage * (blockDamageMultipliers[level] * pWHExt->Block_ChanceMultiplier + pWHExt->Block_ExtraChance)); + + if (blockWeapon) + TechnoExt::FireWeaponAtSelf(pThis, blockWeapon); + } + + return damage; +} + +std::pair, std::vector> TechnoExt::GetBlockChanceAndDamageMult(TechnoClass* pThis, std::vector blockChance, std::vector blockDamageMult) +{ + const auto pExt = TechnoExt::ExtMap.Find(pThis); + + if (blockChance.size() == 0) + blockChance.push_back(0.0); + + if (blockDamageMult.size() == 0) + blockDamageMult.push_back(0.0); + + double extraChance = 0.0; + double extraDamageMult = 0.0; + + for (auto& attachEffect : pExt->AttachedEffects) + { + if (!attachEffect->IsActive()) + continue; + + auto const pType = attachEffect->GetType(); + + if (pType->Block_ChanceMultiplier == 1.0 && pType->Block_ExtraChance == 0.0) + continue; + + for (auto& chance : blockChance) + { + chance = chance * Math::max(pType->Block_ChanceMultiplier, 0); + } + + for (auto& extraDamage : blockDamageMult) + { + extraDamage = static_cast(extraDamage * pType->Block_DamageMult_Multiplier); + } + + extraChance += pType->Block_ExtraChance; + extraDamageMult += pType->Block_DamageMult_Bonus; + } + + for (auto& chance : blockChance) + { + chance += extraChance; + } + + for (auto& extraDamage : blockDamageMult) + { + extraDamage += extraDamageMult; + } + + return std::pair, std::vector>(blockChance, blockDamageMult); +} + // ============================= // load / save diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 0a90b4f688..b51b7496f2 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -168,6 +168,8 @@ class TechnoExt static Point2D GetBuildingSelectBracketPosition(TechnoClass* pThis, BuildingSelectBracketPosition bracketPosition); static void ProcessDigitalDisplays(TechnoClass* pThis); static void GetValuesForDisplay(TechnoClass* pThis, DisplayInfoType infoType, int& value, int& maxValue); + static int CalculateBlockDamage(TechnoClass* pThis, args_ReceiveDamage* args); + static std::pair, std::vector> GetBlockChanceAndDamageMult(TechnoClass* pThis, std::vector blockChance, std::vector blockDamageMult); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp index 1fb90ba80c..296c7beacc 100644 --- a/src/Ext/Techno/Hooks.Shield.cpp +++ b/src/Ext/Techno/Hooks.Shield.cpp @@ -17,12 +17,12 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) GET(TechnoClass*, pThis, ECX); LEA_STACK(args_ReceiveDamage*, args, 0x4); - const auto pExt = TechnoExt::ExtMap.Find(pThis); - - int nDamageLeft = *args->Damage; - if (!args->IgnoreDefenses) { + const auto pExt = TechnoExt::ExtMap.Find(pThis); + *args->Damage = TechnoExt::CalculateBlockDamage(pThis, args); + int nDamageLeft = *args->Damage; + if (const auto pShieldData = pExt->Shield.get()) { if (!pShieldData->IsActive()) diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index aef8425b07..7ca9a215b9 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -318,6 +318,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Wake.Read(exINI, pSection, "Wake"); this->Wake_Grapple.Read(exINI, pSection, "Wake.Grapple"); this->Wake_Sinking.Read(exINI, pSection, "Wake.Sinking"); + this->CanBlock.Read(exINI, pSection, "CanBlock"); // Ares 0.2 this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius"); @@ -474,6 +475,10 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DroppodType.reset(); } + if (this->BlockType == nullptr) + this->BlockType = std::make_unique(); + this->BlockType->LoadFromINI(pINI, pSection); + if (GeneralUtils::IsValidString(pThis->PaletteFile) && !pThis->Palette) Debug::Log("[Developer warning] [%s] has Palette=%s set but no palette file was loaded (missing file or wrong filename). Missing palettes cause issues with lighting recalculations.\n", pArtSection, pThis->PaletteFile); } @@ -688,6 +693,9 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Wake) .Process(this->Wake_Grapple) .Process(this->Wake_Sinking) + + .Process(this->BlockType) + .Process(this->CanBlock) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index b390188549..da6964ff15 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -12,6 +12,7 @@ #include #include #include +#include class Matrix3D; @@ -59,6 +60,7 @@ class TechnoTypeExt Valueable ShieldType; std::unique_ptr PassengerDeletionType; std::unique_ptr DroppodType; + std::unique_ptr BlockType; Valueable Ammo_AddOnDeploy; Valueable Ammo_AutoDeployMinimumAmount; @@ -233,6 +235,8 @@ class TechnoTypeExt Nullable Wake_Grapple; Nullable Wake_Sinking; + Valueable CanBlock; + struct LaserTrailDataEntry { ValueableIdx idxType; @@ -429,6 +433,7 @@ class TechnoTypeExt , SpawnHeight {} , LandingDir {} , DroppodType {} + , BlockType {} , Convert_HumanToComputer { } , Convert_ComputerToHuman { } @@ -461,6 +466,8 @@ class TechnoTypeExt , Wake { } , Wake_Grapple { } , Wake_Sinking { } + + , CanBlock { true } { } virtual ~ExtData() = default; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 281a42af65..bd6b634c7c 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -266,9 +266,23 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SuppressReflectDamage.Read(exINI, pSection, "SuppressReflectDamage"); this->SuppressReflectDamage_Types.Read(exINI, pSection, "SuppressReflectDamage.Types"); + this->Block_BasedOnWarhead.Read(exINI, pSection, "Block.BasedOnWarhead"); + this->Block_AllowOverride.Read(exINI, pSection, "Block.AllowOverride"); + this->Block_IgnoreAttachEffect.Read(exINI, pSection, "Block.IgnoreAttachEffect"); + this->Block_ChanceMultiplier.Read(exINI, pSection, "Block.ChanceMultiplier"); + this->Block_ExtraChance.Read(exINI, pSection, "Block.ExtraChance"); + this->Block_DamageMult_Multiplier.Read(exINI, pSection, "Block.DamageMult.Multiplier"); + this->Block_DamageMult_Bonus.Read(exINI, pSection, "Block.DamageMult.Bonus"); + this->ImmuneToBlock.Read(exINI, pSection, "ImmuneToBlock"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); + // Block + if (this->BlockType == nullptr) + this->BlockType = std::make_unique(); + this->BlockType->LoadFromINI(pINI, pSection); + #ifdef LOCO_TEST_WARHEADS // Enable warheads parsing this->InflictLocomotor.Read(exINI, pSection, "InflictLocomotor"); this->RemoveInflictedLocomotor.Read(exINI, pSection, "RemoveInflictedLocomotor"); @@ -485,6 +499,16 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Nonprovocative) + .Process(this->BlockType) + .Process(this->Block_BasedOnWarhead) + .Process(this->Block_AllowOverride) + .Process(this->Block_IgnoreAttachEffect) + .Process(this->Block_ChanceMultiplier) + .Process(this->Block_ExtraChance) + .Process(this->Block_DamageMult_Multiplier) + .Process(this->Block_DamageMult_Bonus) + .Process(this->ImmuneToBlock) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 90b9968f7d..7d078871b1 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -149,6 +149,16 @@ class WarheadTypeExt Valueable SuppressReflectDamage; ValueableVector SuppressReflectDamage_Types; + std::unique_ptr BlockType; + Valueable Block_BasedOnWarhead; + Valueable Block_AllowOverride; + Valueable Block_IgnoreAttachEffect; + Valueable Block_ChanceMultiplier; + Valueable Block_ExtraChance; + Valueable Block_DamageMult_Multiplier; + Valueable Block_DamageMult_Bonus; + Valueable ImmuneToBlock; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -300,6 +310,16 @@ class WarheadTypeExt , SuppressReflectDamage { false } , SuppressReflectDamage_Types {} + , BlockType {} + , Block_BasedOnWarhead { false } + , Block_AllowOverride { true } + , Block_IgnoreAttachEffect { true } + , Block_ChanceMultiplier { 1.0 } + , Block_ExtraChance { 0.0 } + , Block_DamageMult_Multiplier { 1.0 } + , Block_DamageMult_Bonus { 0.0 } + , ImmuneToBlock { false } + , AffectsEnemies { true } , AffectsOwner {} , EffectsRequireVerses { true } diff --git a/src/New/Type/Affiliated/BlockTypeClass.cpp b/src/New/Type/Affiliated/BlockTypeClass.cpp new file mode 100644 index 0000000000..876608d3ab --- /dev/null +++ b/src/New/Type/Affiliated/BlockTypeClass.cpp @@ -0,0 +1,85 @@ +#include "BlockTypeClass.h" + +#include +#include + +void BlockTypeClass::LoadFromINI(CCINIClass* pINI, const char* pSection) +{ + INI_EX exINI(pINI); + + this->Block_Chances.Read(exINI, pSection, "Block.Chances"); + this->Block_DamageMultipliers.Read(exINI, pSection, "Block.DamageMultipliers"); + this->Block_AffectBelowPercents.Read(exINI, pSection, "Block.AffectBelowPercents"); + this->Block_AffectsHouses.Read(exINI, pSection, "Block.AffectsHouses"); + this->Block_CanActive_NoFirer.Read(exINI, pSection, "Block.CanActive.NoFirer"); + this->Block_CanActive_Powered.Read(exINI, pSection, "Block.CanActive.Powered"); + this->Block_CanActive_ShieldActive.Read(exINI, pSection, "Block.CanActive.ShieldActive"); + this->Block_CanActive_ShieldInactive.Read(exINI, pSection, "Block.CanActive.ShieldInactive"); + this->Block_CanActive_ZeroDamage.Read(exINI, pSection, "Block.CanActive.ZeroDamage"); + this->Block_CanActive_NegativeDamage.Read(exINI, pSection, "Block.CanActive.NegativeDamage"); + this->Block_CanActive_Move.Read(exINI, pSection, "Block.CanActive.Move"); + this->Block_CanActive_Stationary.Read(exINI, pSection, "Block.CanActive.Stationary"); + this->Block_Flash.Read(exINI, pSection, "Block.Flash"); + this->Block_Flash_FixedSize.Read(exINI, pSection, "Block.Flash.FixedSize"); + this->Block_Flash_Red.Read(exINI, pSection, "Block.Flash.Red"); + this->Block_Flash_Green.Read(exINI, pSection, "Block.Flash.Green"); + this->Block_Flash_Blue.Read(exINI, pSection, "Block.Flash.Blue"); + this->Block_Flash_Black.Read(exINI, pSection, "Block.Flash.Black"); + this->Block_Anims.Read(exINI, pSection, "Block.Anims"); + this->Block_Weapon.Read(exINI, pSection, "Block.Weapon"); + this->Block_ReflectDamage.Read(exINI, pSection, "Block.ReflectDamage"); + this->Block_ReflectDamage_Chance.Read(exINI, pSection, "Block.ReflectDamage.Chance"); + this->Block_ReflectDamage_Warhead.Read(exINI, pSection, "Block.ReflectDamage.Warhead"); + this->Block_ReflectDamage_Warhead_Detonate.Read(exINI, pSection, "Block.ReflectDamage.Warhead.Detonate"); + this->Block_ReflectDamage_Multiplier.Read(exINI, pSection, "Block.ReflectDamage.Multiplier"); + this->Block_ReflectDamage_Override.Read(exINI, pSection, "Block.ReflectDamage.Override"); + this->Block_ReflectDamage_AffectsHouses.Read(exINI, pSection, "Block.ReflectDamage.AffectsHouses"); +} + +#pragma region(save/load) + +template +bool BlockTypeClass::Serialize(T& stm) +{ + return stm + .Process(this->Block_Chances) + .Process(this->Block_DamageMultipliers) + .Process(this->Block_AffectBelowPercents) + .Process(this->Block_AffectsHouses) + .Process(this->Block_CanActive_NoFirer) + .Process(this->Block_CanActive_Powered) + .Process(this->Block_CanActive_ShieldActive) + .Process(this->Block_CanActive_ShieldInactive) + .Process(this->Block_CanActive_ZeroDamage) + .Process(this->Block_CanActive_NegativeDamage) + .Process(this->Block_CanActive_Move) + .Process(this->Block_CanActive_Stationary) + .Process(this->Block_Flash) + .Process(this->Block_Flash_FixedSize) + .Process(this->Block_Flash_Red) + .Process(this->Block_Flash_Green) + .Process(this->Block_Flash_Blue) + .Process(this->Block_Flash_Black) + .Process(this->Block_Anims) + .Process(this->Block_Weapon) + .Process(this->Block_ReflectDamage) + .Process(this->Block_ReflectDamage_Chance) + .Process(this->Block_ReflectDamage_Warhead) + .Process(this->Block_ReflectDamage_Warhead_Detonate) + .Process(this->Block_ReflectDamage_Multiplier) + .Process(this->Block_ReflectDamage_AffectsHouses) + .Process(this->Block_ReflectDamage_Override) + .Success(); +} + +bool BlockTypeClass::Load(PhobosStreamReader& stm, bool registerForChange) +{ + return this->Serialize(stm); +} + +bool BlockTypeClass::Save(PhobosStreamWriter& stm) const +{ + return const_cast(this)->Serialize(stm); +} + +#pragma endregion(save/load) diff --git a/src/New/Type/Affiliated/BlockTypeClass.h b/src/New/Type/Affiliated/BlockTypeClass.h new file mode 100644 index 0000000000..8639d39835 --- /dev/null +++ b/src/New/Type/Affiliated/BlockTypeClass.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class BlockTypeClass +{ +public: + + BlockTypeClass() = default; + + ValueableVector Block_Chances; + ValueableVector Block_DamageMultipliers; + ValueableVector Block_AffectBelowPercents; + Nullable Block_AffectsHouses; + Nullable Block_CanActive_NoFirer; + Nullable Block_CanActive_Powered; + Nullable Block_CanActive_ShieldActive; + Nullable Block_CanActive_ShieldInactive; + Nullable Block_CanActive_ZeroDamage; + Nullable Block_CanActive_NegativeDamage; + Nullable Block_CanActive_Move; + Nullable Block_CanActive_Stationary; + Nullable Block_Flash; + Nullable Block_Flash_FixedSize; + Nullable Block_Flash_Red; + Nullable Block_Flash_Green; + Nullable Block_Flash_Blue; + Nullable Block_Flash_Black; + ValueableVector Block_Anims; + Nullable Block_Weapon; + Nullable Block_ReflectDamage; + Nullable Block_ReflectDamage_Chance; + Nullable Block_ReflectDamage_Warhead; + Nullable Block_ReflectDamage_Warhead_Detonate; + Nullable Block_ReflectDamage_Multiplier; + Nullable Block_ReflectDamage_Override; + Nullable Block_ReflectDamage_AffectsHouses; + + void LoadFromINI(CCINIClass* pINI, const char* pSection); + bool Load(PhobosStreamReader& stm, bool registerForChange); + bool Save(PhobosStreamWriter& stm) const; + +private: + + template + bool Serialize(T& stm); +}; diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index a5bc36b051..c0dcc70611 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -139,6 +139,11 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Crit_AllowWarheads.Read(exINI, pSection, "Crit.AllowWarheads"); this->Crit_DisallowWarheads.Read(exINI, pSection, "Crit.DisallowWarheads"); + this->Block_ChanceMultiplier.Read(exINI, pSection, "Block.ChanceMultiplier"); + this->Block_ExtraChance.Read(exINI, pSection, "Block.ExtraChance"); + this->Block_DamageMult_Multiplier.Read(exINI, pSection, "Block.DamageMult.Multiplier"); + this->Block_DamageMult_Bonus.Read(exINI, pSection, "Block.DamageMult.Bonus"); + this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); @@ -195,6 +200,10 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Crit_ExtraChance) .Process(this->Crit_AllowWarheads) .Process(this->Crit_DisallowWarheads) + .Process(this->Block_ChanceMultiplier) + .Process(this->Block_ExtraChance) + .Process(this->Block_DamageMult_Multiplier) + .Process(this->Block_DamageMult_Bonus) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) .Process(this->ReflectDamage) diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 8ac5174672..fccfb1e46b 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -48,6 +48,10 @@ class AttachEffectTypeClass final : public Enumerable Valueable Crit_ExtraChance; ValueableVector Crit_AllowWarheads; ValueableVector Crit_DisallowWarheads; + Valueable Block_ChanceMultiplier; + Valueable Block_ExtraChance; + Valueable Block_DamageMult_Multiplier; + Valueable Block_DamageMult_Bonus; Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; Valueable ReflectDamage; @@ -96,6 +100,10 @@ class AttachEffectTypeClass final : public Enumerable , Crit_ExtraChance { 0.0 } , Crit_AllowWarheads {} , Crit_DisallowWarheads {} + , Block_ChanceMultiplier { 1.0 } + , Block_ExtraChance { 0.0 } + , Block_DamageMult_Multiplier { 1.0 } + , Block_DamageMult_Bonus{ 0.0 } , RevengeWeapon {} , RevengeWeapon_AffectsHouses{ AffectedHouse::All } , ReflectDamage { false } diff --git a/src/New/Type/ShieldTypeClass.cpp b/src/New/Type/ShieldTypeClass.cpp index f913e64476..fdd7f829ad 100644 --- a/src/New/Type/ShieldTypeClass.cpp +++ b/src/New/Type/ShieldTypeClass.cpp @@ -94,6 +94,8 @@ void ShieldTypeClass::LoadFromINI(CCINIClass* pINI) this->Tint_Color.Read(exINI, pSection, "Tint.Color"); this->Tint_Intensity.Read(exINI, pSection, "Tint.Intensity"); this->Tint_VisibleToHouses.Read(exINI, pSection, "Tint.VisibleToHouses"); + + this->CanBlock.Read(exINI, pSection, "CanBlock"); } template @@ -141,6 +143,7 @@ void ShieldTypeClass::Serialize(T& Stm) .Process(this->Tint_Color) .Process(this->Tint_Intensity) .Process(this->Tint_VisibleToHouses) + .Process(this->CanBlock) ; } diff --git a/src/New/Type/ShieldTypeClass.h b/src/New/Type/ShieldTypeClass.h index 2dfc4d5e59..2e194017ce 100644 --- a/src/New/Type/ShieldTypeClass.h +++ b/src/New/Type/ShieldTypeClass.h @@ -56,6 +56,8 @@ class ShieldTypeClass final : public Enumerable Valueable Tint_Intensity; Valueable Tint_VisibleToHouses; + Valueable CanBlock; + public: ShieldTypeClass(const char* const pTitle) : Enumerable(pTitle) , Strength { 0 } @@ -100,6 +102,7 @@ class ShieldTypeClass final : public Enumerable , Tint_Color {} , Tint_Intensity { 0.0 } , Tint_VisibleToHouses { AffectedHouse::All } + , CanBlock { true } { }; virtual ~ShieldTypeClass() override = default;