From 22aec668c869b82a5eb68c088fc19bd71a903dc2 Mon Sep 17 00:00:00 2001 From: Coronia <2217891145@qq.com> Date: Mon, 16 Sep 2024 00:59:21 +0800 Subject: [PATCH] implement block system --- .github/actions/build-phobos/action.yml | 2 +- CREDITS.md | 3 + Phobos.vcxproj | 2 + docs/Fixed-or-Improved-Logics.md | 14 +- docs/New-or-Enhanced-Logics.md | 106 +++++++- docs/Whats-New.md | 7 +- src/Ext/Anim/Body.cpp | 8 +- src/Ext/SWType/FireSuperWeapon.cpp | 30 ++- src/Ext/Techno/Body.Update.cpp | 2 +- src/Ext/Techno/Body.cpp | 279 +++++++++++++++++++++ src/Ext/Techno/Body.h | 4 + src/Ext/Techno/Hooks.Firing.cpp | 16 ++ src/Ext/Techno/Hooks.Shield.cpp | 8 +- src/Ext/Techno/Hooks.cpp | 21 +- src/Ext/TechnoType/Body.cpp | 6 + src/Ext/TechnoType/Body.h | 3 + src/Ext/TechnoType/Hooks.cpp | 77 ++++-- src/Ext/WarheadType/Body.cpp | 28 +++ src/Ext/WarheadType/Body.h | 24 ++ src/Ext/WeaponType/Body.cpp | 2 + src/Ext/WeaponType/Body.h | 2 + src/Misc/Hooks.BugFixes.cpp | 13 + src/Misc/Hooks.UI.cpp | 2 +- src/New/Entity/ShieldClass.cpp | 8 +- src/New/Type/Affiliated/BlockTypeClass.cpp | 139 ++++++++++ src/New/Type/Affiliated/BlockTypeClass.h | 48 ++++ src/New/Type/AttachEffectTypeClass.cpp | 9 + src/New/Type/AttachEffectTypeClass.h | 8 + src/New/Type/ShieldTypeClass.cpp | 3 + src/New/Type/ShieldTypeClass.h | 3 + src/Phobos.INI.cpp | 3 + src/Phobos.h | 1 + src/Utilities/Container.h | 37 ++- 33 files changed, 853 insertions(+), 65 deletions(-) create mode 100644 src/New/Type/Affiliated/BlockTypeClass.cpp create mode 100644 src/New/Type/Affiliated/BlockTypeClass.h diff --git a/.github/actions/build-phobos/action.yml b/.github/actions/build-phobos/action.yml index 87bf5079cc..d818a510a3 100644 --- a/.github/actions/build-phobos/action.yml +++ b/.github/actions/build-phobos/action.yml @@ -26,7 +26,7 @@ runs: - name: Upload Artifact if: ${{success()}} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: compiled-dll-${{github.sha}} path: | diff --git a/CREDITS.md b/CREDITS.md index 5fa07e8307..8f8bd51dd3 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -353,9 +353,12 @@ This page lists all the individual contributions to the project by their author. - Re-enable the Veinhole Monster and Weeds from TS - Recreate the weed-charging of SWs like the TS Chemical Missile - Allow to change the speed of gas particles +- **CrimRecya** + - Fix `LimboKill` not working reliably - **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/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 13392a53c7..f5f3cabe27 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -165,6 +165,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Building upgrades now consistently use building's `PowerUpN` animation settings corresponding to the upgrade's `PowersUpToLevel` where possible. - Subterranean units are no longer allowed to perform deploy functions like firing weapons or `IsSimpleDeployer` while burrowed or burrowing, they will instead emerge first like they do for transport unloading. - The otherwise unused setting `[AI]` -> `PowerSurplus` (defaults to 50) which determines how much surplus power AI players will strive to have can be restored by setting `[AI]` -> `EnablePowerSurplus` to true. +- Planning paths are now shown for all units under player control or when `[GlobalControls]->DebugPlanningPaths=yes` in singleplayer game modes. +- Fixed `Temporal=true` Warheads potentially crashing game if used to attack `Slaved=true` infantry. ## Fixes / interactions with other extensions @@ -1253,7 +1255,7 @@ DecloakDamagedTargets=true ; boolean - You can now make Warheads behave in nonprovocative fashion. Warheads with `Nonprovocative=true` exhibit following behaviours: - They will not generate any EVA announcements upon hitting targets, be it for attacking ore miners, base buildings or ally base buildings. - - They will not spring 'attacked' / 'attacked by' events. Note that if the Warhead deals actual damage, events that check for that can still be sprung. + - They will not spring 'attacked' / 'attacked by' events. Note that if the Warhead deals actual damage, events that check for that can still be sprung. - They will not evoke defense response from AI players when used to attack base buildings, `ToProtect=true` TechnoTypes or members of TeamTypes with `Whiner=true`. - They will not evoke retaliation from TechnoTypes hit by the Warhead. @@ -1333,6 +1335,16 @@ In `rulesmd.ini` KickOutPassengers=true ; boolean ``` +### Disable FireOnce resetting infantry sequence + +- It is now possible to disable `FireOnce=true` weapon resetting infantry sequences after firing via `FireOnce.ResetSequence`. Target will be forgotten like before, the firing sequence will simply continue playing after firing if there are any frames left. + +In `rulesmd.ini` +```ini +[SOMEWEAPON] +FireOnce.ResetSequence=true ; boolean +``` + ### Single-color lasers ![image](_static/images/issinglecolor.gif) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 9f9e818b8e..a175b9db28 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -40,7 +40,9 @@ 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. - - `RevengeWeapon` can be used to temporarily grant the specified weapon as a [revenge weapon](#revenge-weapon) for the attached object. + - `Block.Multiplier` 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. - Warheads can prevent reflect damage from occuring by setting `SuppressReflectDamage` to true. `SuppressReflectDamage.Types` can control which AttachEffectTypes' reflect damage is suppressed, if none are listed then all of them are suppressed. @@ -68,7 +70,7 @@ This page describes all the engine features that are either new and introduced b - `AttachEffect.(Required|Disallowed)MinCounts & (Required|Disallowed)MaxCounts` can be used to set the minimum and maximum number of instances required / disallowed to be on the Techno for `Cumulative=true` types (ignored for other types) respectively. - `AttachEffect.IgnoreFromSameSource` can be set to true to ignore effects that have been attached by the firer of the weapon and its Warhead. - `AttachEffect.CheckOnFirer` is set to true makes it so that the required / disallowed attached effects are checked from the firer of the weapon instead of the target. - + In `rulesmd.ini`: ```ini [AttachEffectTypes] @@ -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.Multiplier=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 @@ -324,6 +331,8 @@ Shield.AbsorbPercent= ; floating point value Shield.PassPercent= ; floating point value Shield.ReceivedDamage.Minimum= ; integer Shield.ReceivedDamage.Maximum= ; integer +Shield.ReceivedDamage.MinMultiplier=1.0 ; floating point value +Shield.ReceivedDamage.MaxMultiplier=1.0 ; floating point value Shield.Respawn.Duration=0 ; integer, game frames Shield.Respawn.Amount=0.0 ; floating point value, percents or absolute Shield.Respawn.Rate=-1.0 ; floating point value, ingame minutes @@ -399,6 +408,7 @@ Shield.InheritStateOnReplace=false ; boolean - `Shield.AbsorbPercent` overrides the `AbsorbPercent` value set in the ShieldType that is being damaged. - `Shield.PassPercent` overrides the `PassPercent` value set in the ShieldType that is being damaged. - `Shield.ReceivedDamage.Minimum` & `Shield.ReceivedDamage.Maximum` override the values set in in the ShieldType that is being damaged. + - `Shield.ReceivedDamage.MinMultiplier` and `Shield.ReceivedDamage.MinMultiplier` are multipliers to the effective `Shield.ReceivedDamage.Minimum` and `Shield.ReceivedDamage.Maximum` respectively that are applied when the Warhead deals damage to a shield. - `Shield.Respawn.Rate` & `Shield.Respawn.Amount` override ShieldType `Respawn.Rate` and `Respawn.Amount` for duration of `Shield.Respawn.Duration` amount of frames. Negative rate & zero or lower amount default to ShieldType values. If `Shield.Respawn.RestartTimer` is set, currently running shield respawn timer is reset, otherwise the timer's duration is adjusted in proportion to the new `Shield.Respawn.Rate` (e.g timer will be same percentage through before and after) without restarting the timer. If the effect expires while respawn timer is running, remaining time is adjusted to proportionally match ShieldType `Respawn.Rate`. Re-applying the effect resets the duration to `Shield.Respawn.Duration` - `Shield.SelfHealing.Rate` & `Shield.SelfHealing.Amount` override ShieldType `SelfHealing.Rate` and `SelfHealing.Amount` for duration of `Shield.SelfHealing.Duration` amount of frames. Negative rate & zero or lower amount default to ShieldType values. If `Shield.SelfHealing.RestartTimer` is set, currently running self-healing timer is restarted, otherwise timer's duration is adjusted in proportion to the new `Shield.SelfHealing.Rate` (e.g timer will be same percentage through before and after) without restarting the timer. If the effect expires while self-healing timer is running, remaining time is adjusted to proportionally match ShieldType `SelfHealing.Rate`. Re-applying the effect resets the duration to `Shield.SelfHealing.Duration`. - Additionally `Shield.SelfHealing.RestartInCombat` & `Shield.SelfHealing.RestartInCombatDelay` can be used to override ShieldType settings. @@ -411,6 +421,93 @@ Shield.InheritStateOnReplace=false ; boolean - `Shield.MinimumReplaceDelay` can be used to control how long after the shield has been broken (in game frames) can it be replaced. If not enough frames have passed, it won't be replaced. - If `Shield.InheritStateOnReplace` is set, shields replaced via `Shield.ReplaceOnly` inherit the current strength (relative to ShieldType `Strength`) of the previous shield and whether or not the shield was currently broken. Self-healing and respawn timers are always reset. +### Block damage + +- You can now make techno have a chance to block the incoming damage, which will make it multiply by a set percentage. +- `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. 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` determines if the block can be triggered when the damage is equal to 0. `Block.CanActive.NegativeDamage` determines if the block can be triggered when the damage is below 0. +- `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. If both the techno and the warhead has block settings, it'll use the ones from techno by default. If `Block.WarheadOverride` set to true, it'll use the settings from warhead to override if they're set, while keeping the unset settings as techno's. + - `Block.WarheadOverride.All`, if set to true along with `Block.WarheadOverride`, will use the block settings from warhead to fully override techno's, even if they're unset. + - 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. +- `ImmuneToBlock` can be set on WarheadTypes to make them can't trigger a block. +- `Block.IgnoreAttachEffect` can be set on WarheadTypes to make their attack ignore modifiers of `Block.Chances` and `Block.DamageMultipliers` from the attached effects on the techno. +- `Block.Multiplier` 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. + +In `rulesmd.ini`: +```ini +[SOMETECHNO] ; TechnoType +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.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.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 +ImmuneToBlock=false ; boolean +Block.WarheadOverride=false ; boolean +Block.WarheadOverride.All=false ; boolean +Block.IgnoreAttachEffect=true ; boolean +Block.Multiplier=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 +``` + ## Animations ### Anim-to-Unit @@ -430,7 +527,7 @@ Shield.InheritStateOnReplace=false ; boolean - `CreateUnit.ConsiderPathfinding`, if set to true, will consider whether or not the cell where the animation is located is occupied by other objects or impassable to the vehicle being created and will attempt to find a nearby cell that is not. Otherwise the vehicle will be created at the animation's location despite these obstacles if possible. - `CreateUnit.SpawnAnim` can be used to play another animation at created unit's location after it has appeared. This animation has same owner and invoker as the parent animation. - `CreateUnit.SpawnHeight` can be set to override the animation's height when determining where to spawn the created unit. Has no effect if `CreateUnit.AlwaysSpawnOnGround` is set to true. - + In `artmd.ini`: ```ini [SOMEANIM] ; AnimationType @@ -679,7 +776,7 @@ Trajectory.Speed=100.0 ; floating point value - `Trajectory.Straight.PassThrough` enables special case logic where the projectile does not detonate in contact with the target but ínstead travels up to a distance defined by `Trajectory.Straight.DetonationDistance`. Note that the firing angle of the projectile is adjusted with this in mind, making it fire straight ahead if the target is on same elevation. In `rulesmd.ini`: -```ini +```ini [SOMEPROJECTILE] ; Projectile Trajectory=Straight ; Trajectory type Trajectory.Straight.DetonationDistance=0.4 ; floating point value @@ -952,6 +1049,7 @@ AutoFire.TargetSelf=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 21fb678592..0bcbeeb094 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -429,7 +429,7 @@ New: - Promotion animation (by Fryone) - Allow different technos to share build limit in a group (by ststl & Ollerus) - Map events `604-605` for checking if a specific Techno enters in a cell (by FS-21) -- Waypoint path is drawn for all units, even those not under player control if `DebugKeysEnabled=yes` (by Trsdy) +- Waypoint path is drawn for all units under player control or if `[GlobalControls]->DebugPlanningPaths=yes` (by Trsdy) - `RemoveDisguise` now works on vehicle disguises (by Trsdy) - Allow anchoring extended tooltips to the left side of the sidebar (by Trsdy) - Toggle to allow spawned aircraft to attack immediately after being spawned (by Starkku) @@ -450,6 +450,8 @@ New: - Forbidding parallel AI queues for specific TechnoTypes (by Starkku) - 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) @@ -521,7 +523,7 @@ Vanilla fixes: - Fixed disguised units not using the correct palette if target has custom palette (by NetsuNegi) - Building upgrades now consistently use building's `PowerUpN` animation settings corresponding to the upgrade's `PowersUpToLevel` where possible (by Starkku) - Subterranean units are no longer allowed to perform deploy functions like firing weapons or `IsSimpleDeployer` while burrowed or burrowing, they will instead emerge first like they do for transport unloading (by Starkku) -- Subterranean units no longer draw an incorrectly positioned shadow when burrowing etc. (by Starkku) +- Fixed `Temporal=true` Warheads potentially crashing game if used to attack `Slaved=true` infantry (by Starkku) Phobos fixes: - Fixed a few errors of calling for superweapon launch by `LaunchSW` or building infiltration (by Trsdy) @@ -563,6 +565,7 @@ Phobos fixes: - Fixed frame by frame hotkey description to read `TXT_FRAME_BY_FRAME_DESC` instead of `TXT_DISPLAY_DAMAGE_DESC` (by DeathFishAtEase) - Buildings considered vehicles (`ConsideredVehicle=true` or not set in conjunction with `UndeploysInto` & 1x1 foundation) are now considered units by affected target enum checks (by Starkku) - Fixed Phobos Warhead effects not reliably being applied on damage area as opposed to full weapon-based Warhead detonation (by Starkku) +- Fix `LimboKill` not working reliably (by CrimRecya) Fixes / interactions with other extensions: - `IsSimpleDeployer` units with Hover locomotor and `DeployToLand` no longer get stuck after deploying or play their move sound indefinitely (by Starkku) diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index 68eb7bf94e..9fb253a058 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -281,10 +281,10 @@ DEFINE_HOOK_AGAIN(0x422126, AnimClass_CTOR, 0x5) DEFINE_HOOK_AGAIN(0x422707, AnimClass_CTOR, 0x5) DEFINE_HOOK(0x4228D2, AnimClass_CTOR, 0x5) { + GET(AnimClass*, pItem, ESI); + if (!Phobos::IsLoadingSaveGame) { - GET(AnimClass*, pItem, ESI); - auto const callerAddress = CTORTemp::callerAddress; // Do this here instead of using a duplicate hook in SyncLogger.cpp @@ -296,10 +296,10 @@ DEFINE_HOOK(0x4228D2, AnimClass_CTOR, 0x5) Debug::Log("Attempting to create animation with null Type (Caller: %08x)!\n", callerAddress); return 0; } - - AnimExt::ExtMap.Allocate(pItem); } + AnimExt::ExtMap.Allocate(pItem); + return 0; } diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index 519f39d4a8..2241a2e409 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -120,20 +120,6 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID) } } -inline void LimboDelete(BuildingClass* pBuilding, HouseClass* pTargetHouse) -{ - auto pOwnerExt = HouseExt::ExtMap.Find(pTargetHouse); - - // Remove building from list of owned limbo buildings - auto& vec = pOwnerExt->OwnedLimboDeliveredBuildings; - vec.erase(std::remove(vec.begin(), vec.end(), pBuilding), vec.end()); - - pBuilding->Stun(); - pBuilding->Limbo(); - pBuilding->RegisterDestruction(nullptr); - pBuilding->UnInit(); -} - void SWTypeExt::ExtData::ApplyLimboDelivery(HouseClass* pHouse) { // random mode @@ -174,13 +160,25 @@ void SWTypeExt::ExtData::ApplyLimboKill(HouseClass* pHouse) if (EnumFunctions::CanTargetHouse(this->LimboKill_Affected, pHouse, pTargetHouse)) { auto const pHouseExt = HouseExt::ExtMap.Find(pTargetHouse); + auto& vec = pHouseExt->OwnedLimboDeliveredBuildings; - for (const auto& pBuilding : pHouseExt->OwnedLimboDeliveredBuildings) + for (auto it = vec.begin(); it != vec.end(); ) { + BuildingClass* const pBuilding = *it; auto const pBuildingExt = BuildingExt::ExtMap.Find(pBuilding); if (pBuildingExt->LimboID == limboKillID) - LimboDelete(pBuilding, pTargetHouse); + { + it = vec.erase(it); + pBuilding->Stun(); + pBuilding->Limbo(); + pBuilding->RegisterDestruction(nullptr); + pBuilding->UnInit(); + } + else + { + ++it; + } } } } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 5eff39794d..4adfa8df6b 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -192,7 +192,7 @@ bool TechnoExt::ExtData::CheckDeathConditions(bool isInLimbo) // death if listed technos exist if (!pTypeExt->AutoDeath_TechnosExist.empty()) { - if (existTechnoTypes(pTypeExt->AutoDeath_TechnosExist, pTypeExt->AutoDeath_TechnosExist_Houses, pTypeExt->AutoDeath_TechnosExist_Any, pTypeExt->AutoDeath_TechnosDontExist_AllowLimboed)) + if (existTechnoTypes(pTypeExt->AutoDeath_TechnosExist, pTypeExt->AutoDeath_TechnosExist_Houses, pTypeExt->AutoDeath_TechnosExist_Any, pTypeExt->AutoDeath_TechnosExist_AllowLimboed)) { TechnoExt::KillSelf(pThis, howToDie, pVanishAnim, isInLimbo); diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 82368ad6e2..9038c1b8cb 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -465,6 +466,283 @@ int TechnoExt::ExtData::GetAttachedEffectCumulativeCount(AttachEffectTypeClass* return foundCount; } +int TechnoExt::CalculateBlockDamage(TechnoClass* pThis, args_ReceiveDamage* args) +{ + int damage = *args->Damage; + const auto pWH = args->WH; + const auto pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + + if (pWHExt->ImmuneToBlock) + return damage; + + const auto pExt = TechnoExt::ExtMap.Find(pThis); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + const auto pWHBlockType = pWHExt->BlockType.get(); + const auto pBlockType = pWHExt->Block_WarheadOverride && pWHExt->Block_WarheadOverride_All ? pTypeExt->BlockType.get() : pWHBlockType; + bool override = pWHExt->Block_WarheadOverride && pBlockType != pWHBlockType; + std::vector blockChances = pBlockType->Block_Chances; + std::vector blockDamageMultipliers = pBlockType->Block_DamageMultipliers; + + if (override) + { + blockChances = !pWHBlockType->Block_Chances.empty() ? pWHBlockType->Block_Chances : blockChances; + blockDamageMultipliers = !pWHBlockType->Block_DamageMultipliers.empty() ? pWHBlockType->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); + + if (override) + { + blockAffectBelowPercents = !pWHBlockType->Block_AffectBelowPercents.empty() ? pWHBlockType->Block_AffectBelowPercents : blockAffectBelowPercents; + blockAffectsHouses = pWHBlockType->Block_AffectsHouses.isset() ? pWHBlockType->Block_AffectsHouses.Get() : blockAffectsHouses; + blockCanActiveZeroDamage = pWHBlockType->Block_CanActive_ZeroDamage.isset() ? pWHBlockType->Block_CanActive_ZeroDamage : blockCanActiveZeroDamage; + blockCanActiveNegativeDamage = pWHBlockType->Block_CanActive_NegativeDamage.isset() ? pWHBlockType->Block_CanActive_NegativeDamage : blockCanActiveNegativeDamage; + blockCanActivePowered = pWHBlockType->Block_CanActive_Powered.isset() ? pWHBlockType->Block_CanActive_Powered : blockCanActivePowered; + blockCanActiveNoFirer = pWHBlockType->Block_CanActive_NoFirer.isset() ? pWHBlockType->Block_CanActive_NoFirer : blockCanActiveNoFirer; + blockCanActiveShieldActive = pWHBlockType->Block_CanActive_ShieldActive.isset() ? pWHBlockType->Block_CanActive_ShieldActive : blockCanActiveShieldActive; + blockCanActiveShieldInactive = pWHBlockType->Block_CanActive_ShieldInactive.isset() ? pWHBlockType->Block_CanActive_ShieldInactive : blockCanActiveShieldInactive; + } + + 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_Multiplier + pWHExt->Block_ExtraChance < dice) + return damage; + } + else if (blockChances.size() <= level || blockChances[level] * pWHExt->Block_Multiplier + 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; + } + + 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 (override) + { + blockAnims = !pWHBlockType->Block_Anims.empty() ? pWHBlockType->Block_Anims : blockAnims; + blockWeapon = pWHBlockType->Block_Weapon.isset() ? pWHBlockType->Block_Weapon.Get() : blockWeapon; + blockFlash = pWHBlockType->Block_Flash.isset() ? pWHBlockType->Block_Flash.Get() : blockFlash; + blockReflectDamage = pWHBlockType->Block_ReflectDamage.isset() ? pWHBlockType->Block_ReflectDamage.Get() : blockReflectDamage; + blockReflectDamageChance = pWHBlockType->Block_ReflectDamage_Chance.isset() ? pWHBlockType->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 (override) + { + size = pWHBlockType->Block_Flash_FixedSize.isset() ? pWHBlockType->Block_Flash_FixedSize.Get() : size; + blockFlashRed = pWHBlockType->Block_Flash_Red.isset() ? pWHBlockType->Block_Flash_Red.Get() : blockFlashRed; + blockFlashGreen = pWHBlockType->Block_Flash_Green.isset() ? pWHBlockType->Block_Flash_Green.Get() : blockFlashGreen; + blockFlashBlue = pWHBlockType->Block_Flash_Blue.isset() ? pWHBlockType->Block_Flash_Blue.Get() : blockFlashBlue; + blockFlashBlack = pWHBlockType->Block_Flash_Black.isset() ? pWHBlockType->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 (override) + { + pWHRef = pWHBlockType->Block_ReflectDamage_Warhead.isset() ? pWHBlockType->Block_ReflectDamage_Warhead.Get() : pWHRef; + blockReflectDamageOverride = pWHBlockType->Block_ReflectDamage_Override.isset() ? pWHBlockType->Block_ReflectDamage_Override : blockReflectDamageOverride; + blockReflectDamageAffectsHouses = pWHBlockType->Block_ReflectDamage_AffectsHouses.isset() ? pWHBlockType->Block_ReflectDamage_AffectsHouses.Get() : blockReflectDamageAffectsHouses; + blockReflectDamageMultiplier = pWHBlockType->Block_ReflectDamage_Multiplier.isset() ? pWHBlockType->Block_ReflectDamage_Multiplier.Get() : blockReflectDamageMultiplier; + blockReflectDamageWHDetonate = pWHBlockType->Block_ReflectDamage_Warhead_Detonate.isset() ? pWHBlockType->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_Multiplier + pWHExt->Block_ExtraChance)); + else if (blockDamageMultipliers.size() > level) + damage = static_cast(damage * (blockDamageMultipliers[level] * pWHExt->Block_Multiplier + 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); + const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + 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_Multiplier == 1.0 && pType->Block_ExtraChance == 0.0) + continue; + + for (auto& chance : blockChance) + { + chance = chance * Math::max(pType->Block_Multiplier, 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 @@ -489,6 +767,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->IsBurrowed) .Process(this->HasBeenPlacedOnMap) .Process(this->DeployFireTimer) + .Process(this->SkipTargetChangeResetSequence) .Process(this->ForceFullRearmDelay) .Process(this->CanCloakDuringRearm) .Process(this->WHAnimRemainingCreationInterval) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 39e777208f..b51b7496f2 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -42,6 +42,7 @@ class TechnoExt bool IsBurrowed; bool HasBeenPlacedOnMap; // Set to true on first Unlimbo() call. CDTimerClass DeployFireTimer; + bool SkipTargetChangeResetSequence; bool ForceFullRearmDelay; bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon. int WHAnimRemainingCreationInterval; @@ -75,6 +76,7 @@ class TechnoExt , IsBurrowed { false } , HasBeenPlacedOnMap { false } , DeployFireTimer {} + , SkipTargetChangeResetSequence { false } , ForceFullRearmDelay { false } , CanCloakDuringRearm { false } , WHAnimRemainingCreationInterval { 0 } @@ -166,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.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 1d804f1dd8..8cd47933d1 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -509,6 +509,22 @@ DEFINE_HOOK(0x6FF43F, TechnoClass_FireAt_FeedbackWeapon, 0x6) return 0; } +DEFINE_HOOK(0x6FF905, TechnoClass_FireAt_FireOnce, 0x6) +{ + GET(TechnoClass*, pThis, ESI); + + if (auto const pInf = abstract_cast(pThis)) + { + GET(WeaponTypeClass*, pWeapon, EBX); + auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + + if (!pWeaponExt->FireOnce_ResetSequence) + TechnoExt::ExtMap.Find(pInf)->SkipTargetChangeResetSequence = true; + } + + return 0; +} + DEFINE_HOOK(0x6FF660, TechnoClass_FireAt_Interceptor, 0x6) { GET(TechnoClass* const, pSource, ESI); 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/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index e2c8c22264..5ecf15359d 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -596,6 +596,21 @@ DEFINE_HOOK(0x6F9FA9, TechnoClass_AI_PromoteAnim, 0x6) return aresProcess(); } -// TunnelLocomotionClass_IsToHaveShadow, skip shadow on all but idle. -// TODO: Investigate if it is possible to fix the shadows not tilting on the burrowing etc. states. -DEFINE_JUMP(LJMP, 0x72A070, 0x72A07F); +DEFINE_HOOK(0x51B20E, InfantryClass_AssignTarget_FireOnce, 0x6) +{ + enum { SkipGameCode = 0x51B255 }; + + GET(InfantryClass*, pThis, ESI); + GET(AbstractClass*, pTarget, EBX); + + auto const pExt = TechnoExt::ExtMap.Find(pThis); + + if (!pTarget && pExt->SkipTargetChangeResetSequence) + { + pThis->IsFiring = false; + pExt->SkipTargetChangeResetSequence = false; + return SkipGameCode; + } + + return 0; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index aef8425b07..fcb365ce2f 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -474,6 +474,10 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->DroppodType.reset(); } + if (this->BlockType == nullptr) + this->BlockType = std::make_unique(this->OwnerObject()); + 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 +692,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Wake) .Process(this->Wake_Grapple) .Process(this->Wake_Sinking) + + .Process(this->BlockType) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index b390188549..ff03237a89 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; @@ -429,6 +431,7 @@ class TechnoTypeExt , SpawnHeight {} , LandingDir {} , DroppodType {} + , BlockType {} , Convert_HumanToComputer { } , Convert_ComputerToHuman { } diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index e177510592..cfd9a1b36a 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -21,6 +21,7 @@ #include #include #include +#include DEFINE_HOOK(0x6F64A9, TechnoClass_DrawHealthBar_Hide, 0x5) { @@ -301,6 +302,60 @@ constexpr double Pade2_2(double in) * (12. - 6 * s + s * s) / (12. + 6 * s + s * s); } +// We need to handle Ares turrets/barrels/waterimage/nospawnalt +struct DummyExtHere // TODO: move it +{ + char _[0xA4]; + std::vector ChargerTurrets; + std::vector ChargerBarrels; + char __[0x120]; + UnitTypeClass* WaterImage; + VoxelStruct NoSpawnAltVXL; +}; + +Matrix3D* __stdcall TunnelLocomotionClass_ShadowMatrix(ILocomotion* iloco, Matrix3D* ret,VoxelIndexKey* key) +{ + __assume(iloco != nullptr); + auto tLoco = static_cast(iloco); + *ret = tLoco->LocomotionClass::Shadow_Matrix(key); + if (tLoco->State != TunnelLocomotionClass::State::Idle) + { + double theta = 0.; + switch (tLoco->State) + { + case TunnelLocomotionClass::State::DiggingIn: + if (key)key->Invalidate(); + theta = Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= 1.0 - double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + case TunnelLocomotionClass::State::DugIn: + theta = Math::HalfPi; + break; + case TunnelLocomotionClass::State::PreDigOut: + theta = -Math::HalfPi; + break; + case TunnelLocomotionClass::State::DiggingOut: + if (key)key->Invalidate(); + theta = -Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + case TunnelLocomotionClass::State::DugOut: + if (key)key->Invalidate(); + theta = Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + default:break; + } + ret->ScaleX((float)Math::cos(theta));// I know it's ugly + } + return ret; +} + +DEFINE_JUMP(VTABLE, 0x7F5A4C, GET_OFFSET(TunnelLocomotionClass_ShadowMatrix)); + DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) { GET(UnitClass*, pThis, EBP); @@ -355,17 +410,6 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) shadow_matrix.Scale((float)Pade2_2(baseScale_log)); } - // We need to handle Ares turrets/barrels - struct DummyExtHere - { - char _[0xA4]; - std::vector ChargerTurrets; - std::vector ChargerBarrels; - char __[0x120]; - UnitTypeClass* WaterImage; - VoxelStruct NoSpawnAltVXL; - }; - auto GetMainVoxel = [&]() { if (pType->NoSpawnAlt && pThis->SpawnManager && pThis->SpawnManager->CountDockedSpawns() == 0) @@ -636,7 +680,7 @@ DEFINE_JUMP(CALL6, 0x4148AB, 0x5F4300); DEFINE_JUMP(CALL6, 0x4147F3, 0x5F4300); */ -DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x7) +DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x6) { GET(FootClass*, pThis, EBX);//Maybe Techno later GET(VoxelStruct*, pVXL, EBP); @@ -660,9 +704,12 @@ DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x7) if (who_are_you[0] == UnitTypeClass::AbsVTable) pType = reinterpret_cast(who_are_you);//you are someone else else - return pThis->TurretAnimFrame % hva->FrameCount; - // you might also be SpawnAlt voxel, but I can't know - // otherwise what would you expect me to do, shift back to ares typeext base and check if ownerobject is technotype? + { + // guess what, someone actually has a multisection nospawnalt + if (!(AresHelper::CanUseAres && pVXL == &reinterpret_cast(pType->align_2FC)->NoSpawnAltVXL)) + return pThis->TurretAnimFrame % hva->FrameCount; + } + // you might also be WaterImage or sth else, but I don't want to care anymore, go fuck yourself } // Main body sections diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index f94a1ba724..1ae6953950 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -204,6 +204,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Shield_PassPercent.Read(exINI, pSection, "Shield.PassPercent"); this->Shield_ReceivedDamage_Minimum.Read(exINI, pSection, "Shield.ReceivedDamage.Minimum"); this->Shield_ReceivedDamage_Maximum.Read(exINI, pSection, "Shield.ReceivedDamage.Maximum"); + this->Shield_ReceivedDamage_MinMultiplier.Read(exINI, pSection, "Shield.ReceivedDamage.MinMultiplier"); + this->Shield_ReceivedDamage_MaxMultiplier.Read(exINI, pSection, "Shield.ReceivedDamage.MaxMultiplier"); this->Shield_Respawn_Duration.Read(exINI, pSection, "Shield.Respawn.Duration"); this->Shield_Respawn_Amount.Read(exINI, pSection, "Shield.Respawn.Amount"); this->Shield_Respawn_Rate_InMinutes.Read(exINI, pSection, "Shield.Respawn.Rate"); @@ -264,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->ImmuneToBlock.Read(exINI, pSection, "ImmuneToBlock"); + this->Block_WarheadOverride.Read(exINI, pSection, "Block.WarheadOverride"); + this->Block_WarheadOverride_All.Read(exINI, pSection, "Block.WarheadOverride.All"); + this->Block_IgnoreAttachEffect.Read(exINI, pSection, "Block.IgnoreAttachEffect"); + this->Block_Multiplier.Read(exINI, pSection, "Block.Multiplier"); + 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"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); + // Block + if (this->BlockType == nullptr) + this->BlockType = std::make_unique(this->OwnerObject()); + this->BlockType->LoadFromINI(pINI, pSection); + #ifdef LOCO_TEST_WARHEADS // Enable warheads parsing this->InflictLocomotor.Read(exINI, pSection, "InflictLocomotor"); this->RemoveInflictedLocomotor.Read(exINI, pSection, "RemoveInflictedLocomotor"); @@ -415,6 +431,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Shield_PassPercent) .Process(this->Shield_ReceivedDamage_Minimum) .Process(this->Shield_ReceivedDamage_Maximum) + .Process(this->Shield_ReceivedDamage_MinMultiplier) + .Process(this->Shield_ReceivedDamage_MaxMultiplier) .Process(this->Shield_Respawn_Duration) .Process(this->Shield_Respawn_Amount) .Process(this->Shield_Respawn_Rate) @@ -481,6 +499,16 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Nonprovocative) + .Process(this->BlockType) + .Process(this->ImmuneToBlock) + .Process(this->Block_WarheadOverride) + .Process(this->Block_WarheadOverride_All) + .Process(this->Block_IgnoreAttachEffect) + .Process(this->Block_Multiplier) + .Process(this->Block_ExtraChance) + .Process(this->Block_DamageMult_Multiplier) + .Process(this->Block_DamageMult_Bonus) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 39810cc886..50bb9cd36a 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -79,6 +79,8 @@ class WarheadTypeExt Nullable Shield_PassPercent; Nullable Shield_ReceivedDamage_Minimum; Nullable Shield_ReceivedDamage_Maximum; + Valueable Shield_ReceivedDamage_MinMultiplier; + Valueable Shield_ReceivedDamage_MaxMultiplier; Valueable Shield_Respawn_Duration; Nullable Shield_Respawn_Amount; @@ -147,6 +149,16 @@ class WarheadTypeExt Valueable SuppressReflectDamage; ValueableVector SuppressReflectDamage_Types; + std::unique_ptr BlockType; + Valueable ImmuneToBlock; + Valueable Block_WarheadOverride; + Valueable Block_WarheadOverride_All; + Valueable Block_IgnoreAttachEffect; + Valueable Block_Multiplier; + Valueable Block_ExtraChance; + Valueable Block_DamageMult_Multiplier; + Valueable Block_DamageMult_Bonus; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -227,6 +239,8 @@ class WarheadTypeExt , Shield_PassPercent {} , Shield_ReceivedDamage_Minimum {} , Shield_ReceivedDamage_Maximum {} + , Shield_ReceivedDamage_MinMultiplier { 1.0 } + , Shield_ReceivedDamage_MaxMultiplier { 1.0 } , Shield_Respawn_Duration { 0 } , Shield_Respawn_Amount { 0.0 } @@ -296,6 +310,16 @@ class WarheadTypeExt , SuppressReflectDamage { false } , SuppressReflectDamage_Types {} + , BlockType {} + , ImmuneToBlock { false } + , Block_WarheadOverride { false } + , Block_WarheadOverride_All { false } + , Block_IgnoreAttachEffect { true } + , Block_Multiplier { 1.0 } + , Block_ExtraChance { 0.0 } + , Block_DamageMult_Multiplier { 1.0 } + , Block_DamageMult_Bonus { 0.0 } + , AffectsEnemies { true } , AffectsOwner {} , EffectsRequireVerses { true } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index f3c45295bb..ad123810c6 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -95,6 +95,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->ROF_RandomDelay.Read(exINI, pSection, "ROF.RandomDelay"); this->OmniFire_TurnToTarget.Read(exINI, pSection, "OmniFire.TurnToTarget"); + this->FireOnce_ResetSequence.Read(exINI, pSection, "FireOnce.ResetSequence"); this->ExtraWarheads.Read(exINI, pSection, "ExtraWarheads"); this->ExtraWarheads_DamageOverrides.Read(exINI, pSection, "ExtraWarheads.DamageOverrides"); this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); @@ -137,6 +138,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->Laser_IsSingleColor) .Process(this->ROF_RandomDelay) .Process(this->OmniFire_TurnToTarget) + .Process(this->FireOnce_ResetSequence) .Process(this->ExtraWarheads) .Process(this->ExtraWarheads_DamageOverrides) .Process(this->ExtraWarheads_DetonationChances) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 7f70011e83..79da4f0b8f 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -40,6 +40,7 @@ class WeaponTypeExt Valueable Laser_IsSingleColor; Nullable> ROF_RandomDelay; Valueable OmniFire_TurnToTarget; + Valueable FireOnce_ResetSequence; ValueableVector ExtraWarheads; ValueableVector ExtraWarheads_DamageOverrides; ValueableVector ExtraWarheads_DetonationChances; @@ -78,6 +79,7 @@ class WeaponTypeExt , Laser_IsSingleColor { false } , ROF_RandomDelay {} , OmniFire_TurnToTarget { false } + , FireOnce_ResetSequence { true } , ExtraWarheads {} , ExtraWarheads_DamageOverrides {} , ExtraWarheads_DetonationChances {} diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index d595935b42..0760e4d0f7 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -918,3 +918,16 @@ DEFINE_HOOK(0x705D74, TechnoClass_GetRemapColour_DisguisePalette, 0x8) return SkipGameCode; } + +// Fixes an edge case crash caused by temporal targeting enslaved infantry. +DEFINE_HOOK(0x71ADE4, TemporalClass_Release_SlaveTargetFix, 0x5) +{ + enum { ReturnFromFunction = 0x71AE47 }; + + GET(TemporalClass* const, pThis, ESI); + + if (!pThis->Target) + return ReturnFromFunction; + + return 0; +} diff --git a/src/Misc/Hooks.UI.cpp b/src/Misc/Hooks.UI.cpp index 95d64b576c..f482ac8dc2 100644 --- a/src/Misc/Hooks.UI.cpp +++ b/src/Misc/Hooks.UI.cpp @@ -377,7 +377,7 @@ DEFINE_HOOK(0x604985, GetDialogUIStatusLabels_ShowBriefing, 0x5) bool __fastcall Fake_HouseIsAlliedWith(HouseClass* pThis, void*, HouseClass* CurrentPlayer) { - return Phobos::Config::DevelopmentCommands + return (Phobos::Config::ShowPlanningPath && SessionClass::IsSingleplayer()) || pThis->IsControlledByCurrentPlayer() || pThis->IsAlliedWith(CurrentPlayer); } diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 241a432dcb..4d105f7809 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -189,9 +189,11 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) } int originalShieldDamage = shieldDamage; - - shieldDamage = Math::clamp(shieldDamage, pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum), - pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum)); + int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum); + int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum); + int minDmg = static_cast(min * pWHExt->Shield_ReceivedDamage_MinMultiplier); + int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); + shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg); if (Phobos::DisplayDamageNumbers && shieldDamage != 0) GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, this->Techno->GetRenderCoords(), TechnoExt::ExtMap.Find(this->Techno)->DamageNumberOffset); diff --git a/src/New/Type/Affiliated/BlockTypeClass.cpp b/src/New/Type/Affiliated/BlockTypeClass.cpp new file mode 100644 index 0000000000..06f6e4e345 --- /dev/null +++ b/src/New/Type/Affiliated/BlockTypeClass.cpp @@ -0,0 +1,139 @@ +#include "BlockTypeClass.h" + +#include +#include + +BlockTypeClass::BlockTypeClass(TechnoTypeClass* pOwnerType) + : Block_Chances { } + , Block_DamageMultipliers { } + , Block_AffectBelowPercents { } + , Block_AffectsHouses { AffectedHouse::All } + , Block_CanActive_NoFirer { true } + , Block_CanActive_Powered { false } + , Block_CanActive_ShieldActive { true } + , Block_CanActive_ShieldInactive { true } + , Block_CanActive_ZeroDamage { false } + , Block_CanActive_NegativeDamage { false } + , Block_Flash { false } + , Block_Flash_FixedSize { } + , Block_Flash_Red { true } + , Block_Flash_Green { true } + , Block_Flash_Blue { true } + , Block_Flash_Black { false } + , Block_Anims { } + , Block_Weapon { } + , Block_ReflectDamage { false } + , Block_ReflectDamage_Chance { 1.0 } + , Block_ReflectDamage_Warhead { } + , Block_ReflectDamage_Warhead_Detonate { false } + , Block_ReflectDamage_Multiplier { 1.0 } + , Block_ReflectDamage_Override { } + , Block_ReflectDamage_AffectsHouses { } +{ +} + +BlockTypeClass::BlockTypeClass(WarheadTypeClass* pOwnerType) + : Block_Chances { } + , Block_DamageMultipliers { } + , Block_AffectBelowPercents { } + , Block_AffectsHouses { } + , Block_CanActive_NoFirer { } + , Block_CanActive_Powered { } + , Block_CanActive_ShieldActive { } + , Block_CanActive_ShieldInactive { } + , Block_CanActive_ZeroDamage { } + , Block_CanActive_NegativeDamage { } + , Block_Flash { } + , Block_Flash_FixedSize { } + , Block_Flash_Red { } + , Block_Flash_Green { } + , Block_Flash_Blue { } + , Block_Flash_Black { } + , Block_Anims { } + , Block_Weapon { } + , Block_ReflectDamage { } + , Block_ReflectDamage_Chance { } + , Block_ReflectDamage_Warhead { } + , Block_ReflectDamage_Warhead_Detonate { } + , Block_ReflectDamage_Multiplier { } + , Block_ReflectDamage_Override { } + , Block_ReflectDamage_AffectsHouses { } +{ +} + +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_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_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..f751bec5f3 --- /dev/null +++ b/src/New/Type/Affiliated/BlockTypeClass.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class BlockTypeClass +{ +public: + + BlockTypeClass() = default; + BlockTypeClass(TechnoTypeClass* pOwnerType); + BlockTypeClass(WarheadTypeClass* pOwnerType); + + 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_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..9adf16d544 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_Multiplier.Read(exINI, pSection, "Block.Multiplier"); + 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_Multiplier) + .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..df2146868f 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_Multiplier; + 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_Multiplier { 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; diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index fdf0992d42..daeda05cca 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -38,6 +38,7 @@ bool Phobos::Config::ToolTipDescriptions = true; bool Phobos::Config::ToolTipBlur = false; bool Phobos::Config::PrioritySelectionFiltering = true; bool Phobos::Config::DevelopmentCommands = true; +bool Phobos::Config::ShowPlanningPath = false; bool Phobos::Config::ArtImageSwap = false; bool Phobos::Config::ShowPlacementPreview = false; bool Phobos::Config::DigitalDisplay_Enable = false; @@ -211,6 +212,8 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6) #ifndef DEBUG Phobos::Config::DevelopmentCommands = pINI_RULESMD->ReadBool("GlobalControls", "DebugKeysEnabled", Phobos::Config::DevelopmentCommands); #endif + Phobos::Config::ShowPlanningPath = pINI_RULESMD->ReadBool("GlobalControls", "DebugPlanningPaths", Phobos::Config::ShowPlanningPath); + return 0; } diff --git a/src/Phobos.h b/src/Phobos.h index 49d25200c6..26e0493112 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -90,6 +90,7 @@ class Phobos static bool ShowPowerDelta; static bool ShowHarvesterCounter; static bool ShowWeedsCounter; + static bool ShowPlanningPath; }; class Misc diff --git a/src/Utilities/Container.h b/src/Utilities/Container.h index 6ca0002fd6..6a1a343285 100644 --- a/src/Utilities/Container.h +++ b/src/Utilities/Container.h @@ -286,6 +286,7 @@ class Container map_type Items; base_type* SavingObject; + extension_type_ptr SavingExtPointer; IStream* SavingStream; const char* Name; @@ -361,10 +362,6 @@ class Container extension_type_ptr TryAllocate(base_type_ptr key, bool bCond, const std::string_view& nMessage) { - // Do not allow allocation when loading save games. - if (Phobos::IsLoadingSaveGame) - return nullptr; - if (!key || (!bCond && !nMessage.empty())) { Debug::Log("%s \n", nMessage.data()); @@ -376,10 +373,6 @@ class Container extension_type_ptr TryAllocate(base_type_ptr key) { - // Do not allow allocation when loading save games. - if (Phobos::IsLoadingSaveGame) - return nullptr; - if (!key) { Debug::Log("Attempted to allocate %s from nullptr!\n", typeid(extension_type).name()); @@ -400,6 +393,22 @@ class Container return this->Items.find(key); } + // Only used on loading, does not check if key is nullptr. + extension_type_ptr FindOrAllocate(base_type_ptr key) + { + extension_type_ptr value = nullptr; + + if constexpr (HasOffset) + value = GetExtensionPointer(key); + else + value = this->Items.find(key); + + if (!value) + value = Allocate(key); + + return value; + } + void Remove(base_type_ptr key) { if (auto Item = Find(key)) @@ -442,6 +451,10 @@ class Container this->SavingObject = key; this->SavingStream = pStm; + + // Loading the base type data might override the ext pointer stored on it so it needs to be saved. + if constexpr (HasOffset) + this->SavingExtPointer = GetExtensionPointer(key); } void SaveStatic() @@ -466,6 +479,10 @@ class Container { if (this->SavingObject && this->SavingStream) { + // Restore stored ext pointer data. + if constexpr (HasOffset) + SetExtensionPointer(this->SavingObject, this->SavingExtPointer); + //Debug::Log("[LoadStatic] Loading object %p as '%s'\n", this->SavingObject, this->Name); if (!this->Load(this->SavingObject, this->SavingStream)) Debug::FatalErrorAndExit("LoadStatic - Loading object %p as '%s' failed!\n", this->SavingObject, this->Name); @@ -550,8 +567,8 @@ class Container return nullptr; } - extension_type_ptr buffer = this->Allocate(key); - + // get or allocate the value data + extension_type_ptr buffer = this->FindOrAllocate(key); if (!buffer) { Debug::Log("LoadKey - Could not find or allocate value.\n");