diff --git a/CREDITS.md b/CREDITS.md index 325b1a7c8a..99d06671e2 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -517,6 +517,9 @@ This page lists all the individual contributions to the project by their author. - Power plant damage factor - Allow faking digital display for `InfoType=Health` at disguise - Display banner improvement and doc + - Attached effect attach/discard by health + - Attached effect with `ExtraWarheads`, `KillWeapon` and `FeedbackWeapon` + - `AuxWeapon` - **NaotoYuuki** - Vertical & meteor trajectory projectile prototypes - **handama** - AI script action to `16005 Jump Back To Previous Script` - **TaranDahl (航味麻酱)**: diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index a4b754288b..feb80bb165 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -8,6 +8,7 @@ This page describes all the engine features that are either new and introduced b - Similar (but not identical) to [Ares' AttachEffect](https://ares-developers.github.io/Ares-docs/new/attacheffect.html), but with some differences and new features. The largest difference is that here attached effects are explicitly defined types. - `Duration` determines how long the effect lasts for. It can be overriden by `DurationOverrides` on TechnoTypes and Warheads. + - If `Duration.ApplyVersus.Warhead` is set, it can multiply the duration by the set warhead's versus against the target's armor type if it's not negative. Can't reduce duration to below 0 by a negative versus. - If `Duration.ApplyFirepowerMult` set to true, the duration will multiply the invoker's firepower multipliers if it's not negative. Can't reduce duration to below 0 by a negative firepower multiplier. - If `Duration.ApplyArmorMultOnTarget` set to true, the duration will divide the target's armor multipliers if it's not negative. This'll also include `ArmorMultiplier` from its own and ignore `ArmorMultiplier.Allow/DisallowWarheads`. Can't reduce duration to below 0 by a negative armor multiplier. - `Cumulative`, if set to true, allows the same type of effect to be applied on same object multiple times, up to `Cumulative.MaxCount` number or with no limit if `Cumulative.MaxCount` is a negative number. If the target already has `Cumulative.MaxCount` number of the same effect applied on it, trying to attach another will refresh duration of the attached instance with shortest remaining duration. @@ -20,6 +21,9 @@ This page describes all the engine features that are either new and introduced b - `inrange`: Discard if within weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `outofrange`: Discard if outside weapon range from current target. Distance can be overridden via `DiscardOn.RangeOverride`. - `firing`: Discard when firing a weapon. This counts special weapons that are not actually fired such as ones with `Spawner=true` or `DrainWeapon=true`. + - If `DiscardOn.CumulativeCount` is greater than 0, the effect is discarded when it has `Cumulative=yes` and been attached to the object more than this amount of times. + - If `DiscardOn.AbovePercent` or `DiscardOn.BelowPercent` is set, the effect is discarded when the object's health percentage is above/below that value. + - If `AffectAbovePercent` or `AffectBelowPercent` is set, the effect can be applied only when the object's health percentage is above/below that value. - If `PenetratesIronCurtain` is not set to true, the effect is not applied on currently invulnerable objects. - `PenetratesForceShield` can be used to set this separately for Force Shielded objects, defaults to value of `PenetratesIronCurtain`. - `Animation` defines animation to play in an indefinite loop for as long as the effect is active on the object it is attached to. @@ -34,6 +38,23 @@ This page describes all the engine features that are either new and introduced b - `ExpireWeapon.TriggerOn` determines the exact conditions upon which the weapon is fired, defaults to `expire` which means only if the effect naturally expires. - `ExpireWeapon.CumulativeOnlyOnce`, if set to true, makes it so that `Cumulative=true` attached effects only detonate the weapon once period, instead of once per active instance. On `remove` and `expire` condition this means it will only detonate after last instance has expired or been removed. - `ExpireWeapon.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the weapon's owner & invoker instead of the object the effect is attached to. + - Attached effect can allow the TechnoType's weapon to detonate multiple Warheads on impact by listing `ExtraWarheads`. The warheads are detonated at same location as the main one, after it in listed order. This only works in cases where a projectile has been fired by a weapon and still remembers it when it is detonated (due to currently existing technical limitations, this excludes `AirburstWeapon`). + - `ExtraWarheads.DamageOverrides` can be used to override the weapon's `Damage` for the extra Warhead detonations. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, WeaponType `Damage` is used. + - `ExtraWarheads.DetonationChances` can be used to customize the chance of each extra Warhead detonation occuring. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, every extra Warhead detonation is guaranteed to occur. + - `ExtraWarheads.FullDetonation` can be used to customize whether or not each individual Warhead is detonated fully (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, defaults to true. + - `ExtraWarheads.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the weapon's owner & invoker instead of the object the effect is attached to. Value from position matching the position from `ExtraWarheads` is used if found, or last listed value if not found. If list is empty, defaults to false. + - Note that the listed Warheads must be listed in `[Warheads]` for them to work. + - You can now specify an auxiliary weapon to be fired when a weapon is fired. + - `FireInTransport` setting of the auxiliary weapons are respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If auxiliary weapons are fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on auxiliary weapons. + - `AuxWeapon` is fired at the original target, or another nearby target if `AuxWeapon.Retarget` set to true. + - `AuxWeapon.Offset` defines the relative position to the firer that the auxiliary weapon will be fired from. `AuxWeapon.FireOnTurret` defines if the FLH is relative to the turret rather than the body. + - If `AuxWeapon.AllowZeroDamage` set to true, the auxiliary weapon will be fired even if its damage on the set target is 0. + - `AuxWeapon.ApplyFirepowerMult` determines whether or not the auxiliary weapon's damage should multiply the firer's firepower multipliers. + - `AuxWeapon.Retarget.AroundFirer` determines whether the original target or the firer will be the center of the retargeting. `AuxWeapon.Retarget.Range` determines the radius of the retargeting, default to the auxiliary weapon's `Range` if the center is the firer, and 0 if the center is the original target. + - `AuxWeapon.Retarget.Accuracy` defines the probability that the auxiliary weapon is fired to the original target. + - `AuxWeapon.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the weapon's owner & invoker instead of the object the effect is attached to. + - `FeedbackWeapon` is fired at the firer. + - `FeedbackWeapon.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the weapon's owner & invoker instead of the object the effect is attached to. - `Tint.Color` & `Tint.Intensity` can be used to set a color tint effect and additive lighting increase/decrease on the object the effect is attached to, respectively. - `Tint.VisibleToHouses` can be used to control which houses can see the tint effect. - `FirepowerMultiplier`, `ArmorMultiplier`, `SpeedMultiplier` and `ROFMultiplier` can be used to modify the object's firepower, armor strength, movement speed and weapon reload rate, respectively. @@ -46,8 +67,13 @@ 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. + - `KillWeapon` will be fired at the target TechnoType's location once it's killed by the attached object. + - `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed the attached object. If `KillWeapon.OnFirer.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the attached object. + - `KillWeapon.AffectsHouses` / `KillWeapon.OnFirer.AffectsHouses` and `KillWeapon.Affects` / `KillWeapon.OnFirer.Affects` can be used to filter which houses targets can belong to and which types of targets are be considered valid for `KillWeapon` and `KillWeapon.OnFirer` respectively. + - If a TechnoType has `SuppressKillWeapons` set to true, it will not trigger `KillWeapon` or `KillWeapon.OnFirer` upon being killed. `SuppressKillWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. - `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. + - If `RevengeWeapon.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - `RevengeWeapon.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the weapon's owner & invoker instead of the object the effect is attached to. - `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.Detonate` is set to true, the Warhead is fully detonated instead of used to simply deal damage. `ReflectDamage.Chance` determines the chance of reflection. `ReflectDamage.Multiplier` is a multiplier to the damage received and then reflected back, while `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. `SuppressReflectDamage.Types` can control which AttachEffectTypes' reflect damage is suppressed, if none are listed then all of them are suppressed. `SuppressReflectDamage.Groups` does the same thing but for all AttachEffectTypes in the listed groups. @@ -55,6 +81,7 @@ This page describes all the engine features that are either new and introduced b - `DisableWeapons` can be used to disable ability to fire any and all weapons. - On TechnoTypes with `OpenTopped=true`, `OpenTopped.CheckTransportDisableWeapons` can be set to true to make passengers not be able to fire out if transport's weapons are disabled by `DisableWeapons`. - `Unkillable` can be used to prevent the techno from being killed by taken damage (minimum health will be 1). + - `NegativeDamage.Multiplier` will be multiplied on the negative damage taken by the attached object. This includes both negative `Damage` and negative `Verses`. - It is possible to set groups for attach effect types by defining strings in `Groups`. - Groups can be used instead of types for removing effects and weapon filters. @@ -88,6 +115,7 @@ In `rulesmd.ini`: [SOMEATTACHEFFECT] ; AttachEffectType Duration=0 ; integer - game frames or negative value for indefinite duration +Duration.ApplyVersus.Warhead= ; WarheadType Duration.ApplyFirepowerMult=false ; boolean Duration.ApplyArmorMultOnTarget=false ; boolean Cumulative=false ; boolean @@ -95,6 +123,11 @@ Cumulative.MaxCount=-1 ; integer Powered=false ; boolean DiscardOn=none ; List of discard condition enumeration (none|entry|move|stationary|drain|inrange|outofrange) DiscardOn.RangeOverride= ; floating point value, distance in cells +DiscardOn.CumulativeCount=-1 ; integer +DiscardOn.AbovePercent= ; floating point value, percents or absolute (0.0-1.0) +DiscardOn.BelowPercent= ; floating point value, percents or absolute (0.0-1.0) +AffectAbovePercent= ; floating point value, percents or absolute (0.0-1.0) +AffectBelowPercent= ; floating point value, percents or absolute (0.0-1.0) PenetratesIronCurtain=false ; boolean PenetratesForceShield= ; boolean Animation= ; AnimationType @@ -109,6 +142,23 @@ ExpireWeapon= ; WeaponType ExpireWeapon.TriggerOn=expire ; List of expire weapon trigger condition enumeration (none|expire|remove|death|discard|all) ExpireWeapon.CumulativeOnlyOnce=false ; boolean ExpireWeapon.UseInvokerAsOwner=false ; boolean +ExtraWarheads= ; List of WarheadTypes +ExtraWarheads.DamageOverrides= ; List of integers +ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or absolute) +ExtraWarheads.FullDetonation= ; List of booleans +ExtraWarheads.UseInvokerAsOwner=false ; boolean +AuxWeapon= ; WeaponType +AuxWeapon.Offset=0,0,0 ; integer - Forward,Lateral,Height +AuxWeapon.FireOnTurret=false ; boolean +AuxWeapon.AllowZeroDamage=true ; boolean +AuxWeapon.ApplyFirepowerMult=true ; boolean +AuxWeapon.Retarget=false ; boolean +AuxWeapon.Retarget.Range= ; integer +AuxWeapon.Retarget.Accuracy=1.0 ; floating point value, percents or absolute (0.0-1.0) +AuxWeapon.Retarget.AroundFirer=false ; boolean +AuxWeapon.UseInvokerAsOwner=false ; boolean +FeedbackWeapon= ; WeaponType +FeedbackWeapon.UseInvokerAsOwner=false ; boolean Tint.Color= ; integer - R,G,B Tint.Intensity= ; floating point value Tint.VisibleToHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) @@ -129,8 +179,16 @@ Crit.Multiplier=1.0 ; floating point value Crit.ExtraChance=0.0 ; floating point value Crit.AllowWarheads= ; List of WarheadTypes Crit.DisallowWarheads= ; List of WarheadTypes +KillWeapon= ; WeaponType +KillWeapon.OnFirer= ; WeaponType +KillWeapon.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +KillWeapon.OnFirer.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +KillWeapon.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) +KillWeapon.OnFirer.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) +KillWeapon.OnFirer.RealLaunch=false ; boolean RevengeWeapon= ; WeaponType RevengeWeapon.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +RevengeWeapon.RealLaunch=false ; boolean RevengeWeapon.UseInvokerAsOwner=false ; boolean ReflectDamage=false ; boolean ReflectDamage.Warhead= ; WarheadType @@ -142,6 +200,7 @@ ReflectDamage.Override= ; integer ReflectDamage.UseInvokerAsOwner=false ; boolean DisableWeapons=false ; boolean Unkillable=false ; boolean +NegativeDamage.Multiplier=1.0 ; floating point value LaserTrail.Type= ; lasertrail type Groups= ; comma-separated list of strings (group IDs) @@ -1783,6 +1842,7 @@ RecountBurst= ; boolean - Similar to `DeathWeapon` in that it is fired after a TechnoType is killed, but with the difference that it will be fired on whoever dealt the damage that killed the TechnoType. If TechnoType died of sources other than direct damage dealt by another TechnoType, `RevengeWeapon` will not be fired. - `RevengeWeapon.AffectsHouses` can be used to filter which houses the damage that killed the TechnoType is allowed to come from to fire the weapon. + - If `RevengeWeapon.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - It is possible to grant revenge weapons through [attached effects](#attached-effects) as well. - If a Warhead has `SuppressRevengeWeapons` set to true, it will not trigger revenge weapons. `SuppressRevengeWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. @@ -1791,6 +1851,7 @@ In `rulesmd.ini`: [SOMETECHNO] ; TechnoType RevengeWeapon= ; WeaponType RevengeWeapon.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) +RevengeWeapon.RealLaunch=false ; boolean [SOMEWARHEAD] ; WarheadType SuppressRevengeWeapons=false ; boolean @@ -2174,7 +2235,7 @@ While this feature can provide better performance than a large `CellSpread` valu ### Fire weapon when Warhead kills something - `KillWeapon` will be fired at the target TechnoType's location once it's killed by this Warhead. -- `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed by this Warhead. If the source of this Warhead is not another TechnoType, `KillWeapon.OnFirer` will not be fired. +- `KillWeapon.OnFirer` will be fired at the attacker's location once the target TechnoType is killed by this Warhead. If the source of this Warhead is not another TechnoType, `KillWeapon.OnFirer` will not be fired. If `KillWeapon.OnFirer.RealLaunch` set to true, the weapon will be fired through a real projectile from the TechnoType to the killer. - `KillWeapon.AffectsHouses` / `KillWeapon.OnFirer.AffectsHouses` and `KillWeapon.Affects` / `KillWeapon.OnFirer.Affects` can be used to filter which houses targets can belong to and which types of targets are be considered valid for `KillWeapon` and `KillWeapon.OnFirer` respectively. - If the source of this Warhead is not another TechnoType, `KillWeapon` will be fired regardless of the target's house or type. - If a TechnoType has `SuppressKillWeapons` set to true, it will not trigger `KillWeapon` or `KillWeapon.OnFirer` upon being killed. `SuppressKillWeapons.Types` can be used to list WeaponTypes affected by this, if none are listed all WeaponTypes are affected. @@ -2188,6 +2249,7 @@ KillWeapon.AffectsHouses=all ; List of Affected House Enumeration (none KillWeapon.OnFirer.AffectsHouses=all ; List of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) KillWeapon.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) KillWeapon.OnFirer.Affects=all ; List of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) +KillWeapon.OnFirer.RealLaunch=false ; boolean [SOMETECHNO] ; TechnoType SuppressKillWeapons=false ; boolean @@ -2388,18 +2450,34 @@ ExtraWarheads.DetonationChances= ; List of floating-point values (percentage or ExtraWarheads.FullDetonation= ; List of booleans ``` -### Feedback weapon +### Auxiliary weapon ![image](_static/images/feedbackweapon.gif) *`FeedbackWeapon` used to apply healing aura upon firing a weapon in [Project Phantom](https://www.moddb.com/mods/project-phantom)* -- You can now specify an auxiliary weapon to be fired on the firer itself when a weapon is fired. - - `FireInTransport` setting of the feedback weapon is respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If feedback weapon is fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on feedback weapons. - -In `rulesmd.ini`: -```ini -[SOMEWEAPON] ; WeaponType -FeedbackWeapon= ; WeaponType +- You can now specify an auxiliary weapon to be fired when a weapon is fired. + - `FireInTransport` setting of the auxiliary weapons are respected to determine if it can be fired when the original weapon is fired from inside `OpenTopped=true` transport. If auxiliary weapons are fired, it is fired on the transport. `OpenToppedDamageMultiplier` is not applied on auxiliary weapons. +- `AuxWeapon` is fired at the original target, or another nearby target if `AuxWeapon.Retarget` set to true. + - `AuxWeapon.Offset` defines the relative position to the firer that the auxiliary weapon will be fired from. `AuxWeapon.FireOnTurret` defines if the FLH is relative to the turret rather than the body. + - If `AuxWeapon.AllowZeroDamage` set to true, the auxiliary weapon will be fired even if its damage on the set target is 0. + - `AuxWeapon.ApplyFirepowerMult` determines whether or not the auxiliary weapon's damage should multiply the firer's firepower multipliers. + - `AuxWeapon.Retarget.AroundFirer` determines whether the original target or the firer will be the center of the retargeting. `AuxWeapon.Retarget.Range` determines the radius of the retargeting, default to the auxiliary weapon's `Range` if the center is the firer, and 0 if the center is the original target. + - `AuxWeapon.Retarget.Accuracy` defines the probability that the auxiliary weapon is fired to the original target. +- `FeedbackWeapon` is fired at the firer. + +In `rulesmd.ini`: +```ini +[SOMEWEAPON] ; WeaponType +AuxWeapon= ; WeaponType +AuxWeapon.Offset=0,0,0 ; integer - Forward,Lateral,Height +AuxWeapon.FireOnTurret=false ; boolean +AuxWeapon.AllowZeroDamage=true ; boolean +AuxWeapon.ApplyFirepowerMult=true ; boolean +AuxWeapon.Retarget=false ; boolean +AuxWeapon.Retarget.Range= ; integer +AuxWeapon.Retarget.Accuracy=1.0 ; floating point value, percents or absolute (0.0-1.0) +AuxWeapon.Retarget.AroundFirer=false ; boolean +FeedbackWeapon= ; WeaponType ``` ### Keep Range After Firing diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 88566e1c14..23d847805d 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -410,6 +410,9 @@ New: - Several attackmove related enhancement (by TaranDahl) - Ground line for select box (by NetsuNegi) - Support for more optional weapons (by FlyStar) +- Attached effect attach/discard by health (by Ollerus) +- Attached effect with `ExtraWarheads`, `KillWeapon` and `FeedbackWeapon` (by Ollerus) +- [AuxWeapon](New-or-Enhanced-Logics.md#auxiliary-weapon) (by Ollerus) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Bullet/Body.cpp b/src/Ext/Bullet/Body.cpp index 4e49193ab6..79d6272f1d 100644 --- a/src/Ext/Bullet/Body.cpp +++ b/src/Ext/Bullet/Body.cpp @@ -150,6 +150,68 @@ void BulletExt::ExtData::InitializeLaserTrails() this->LaserTrails.emplace_back(std::make_unique(LaserTrailTypeClass::Array[idxTrail].get(), pOwner)); } +void BulletExt::ExtData::ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker) +{ + auto const pThis = this->OwnerObject(); + const int defaultDamage = pThis->WeaponType ? pThis->WeaponType->Damage : 0; + + for (size_t i = 0; i < exWH.size(); i++) + { + auto const pWH = exWH[i]; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + auto const pTarget = abstract_cast(pThis->Target); // must be check in every loop + + if (pTarget && !pWHExt->IsHealthInThreshold(pTarget)) + continue; + + int damage = defaultDamage; + size_t size = exWHOverrides.size(); + + if (size > i) + damage = exWHOverrides[i]; + else if (size > 0) + damage = exWHOverrides[size - 1]; + + bool detonate = true; + size = exWHChances.size(); + + if (size > i) + detonate = exWHChances[i] >= ScenarioClass::Instance->Random.RandomDouble(); + else if (size > 0) + detonate = exWHChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); + + if (!detonate) + continue; + + auto pFirer = pThis->Owner; + auto pHouse = pOwner; + + if (pInvoker) + { + size = exWHOwner.size(); + + if ((size > i && exWHOwner[i]) || (size > 0 && exWHOwner[size - 1])) + { + pFirer = pInvoker; + pHouse = pInvoker->Owner; + } + } + + bool isFull = true; + size = exWHFull.size(); + + if (size > i) + isFull = exWHFull[i]; + else if (size > 0) + isFull = exWHFull[size - 1]; + + if (isFull) + WarheadTypeExt::DetonateAt(pWH, coords, pFirer, damage, pHouse, pThis->Target); + else + pWHExt->DamageAreaWithTarget(coords, damage, pFirer, pWH, true, pHouse, pTarget); + } +} + static inline int SetBuildingFireAnimZAdjust(BuildingClass* pBuilding, int animY) { if (pBuilding->GetOccupantCount() > 0) diff --git a/src/Ext/Bullet/Body.h b/src/Ext/Bullet/Body.h index 86cc77a674..9b22d3866e 100644 --- a/src/Ext/Bullet/Body.h +++ b/src/Ext/Bullet/Body.h @@ -54,6 +54,7 @@ class BulletExt void InterceptBullet(TechnoClass* pSource, WeaponTypeClass* pWeapon); void ApplyRadiationToCell(CellStruct cell, int spread, int radLevel); void InitializeLaserTrails(); + void ApplyExtraWarheads(const std::vector& exWH, const std::vector& exWHOverrides, const std::vector& exWHChances, const std::vector& exWHFull, const std::vector& exWHOwner, const CoordStruct& coords, HouseClass* pOwner, TechnoClass* pInvoker = nullptr); private: template diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 37034db64e..fc10a811ff 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -345,6 +345,7 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) GET(BulletClass*, pThis, ESI); GET_BASE(CoordStruct*, coords, 0x8); + auto const pBulletExt = BulletExt::ExtMap.Find(pThis); auto const pTechno = pThis->Owner; auto const pOwner = pTechno ? pTechno->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse; @@ -352,49 +353,32 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) if (pThis->WeaponType) { auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pThis->WeaponType); - int defaultDamage = pThis->WeaponType->Damage; - for (size_t i = 0; i < pWeaponExt->ExtraWarheads.size(); i++) + if (pWeaponExt->ExtraWarheads.size() > 0) { - auto const pWH = pWeaponExt->ExtraWarheads[i]; - int damage = defaultDamage; - size_t size = pWeaponExt->ExtraWarheads_DamageOverrides.size(); - auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + std::vector vec; - if (auto const pTarget = abstract_cast(pThis->Target)) - { - if (!pWHExt->IsHealthInThreshold(pTarget)) - continue; - } - - if (size > i) - damage = pWeaponExt->ExtraWarheads_DamageOverrides[i]; - else if (size > 0) - damage = pWeaponExt->ExtraWarheads_DamageOverrides[size - 1]; - - bool detonate = true; - size = pWeaponExt->ExtraWarheads_DetonationChances.size(); - - if (size > i) - detonate = pWeaponExt->ExtraWarheads_DetonationChances[i] >= ScenarioClass::Instance->Random.RandomDouble(); - else if (size > 0) - detonate = pWeaponExt->ExtraWarheads_DetonationChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); - - bool isFull = true; - size = pWeaponExt->ExtraWarheads_FullDetonation.size(); + pBulletExt->ApplyExtraWarheads(pWeaponExt->ExtraWarheads, pWeaponExt->ExtraWarheads_DamageOverrides, + pWeaponExt->ExtraWarheads_DetonationChances, pWeaponExt->ExtraWarheads_FullDetonation, vec, *coords, pOwner); + } + } - if (size > i) - isFull = pWeaponExt->ExtraWarheads_FullDetonation[i]; - else if (size > 0) - isFull = pWeaponExt->ExtraWarheads_FullDetonation[size - 1]; + if (pThis->Owner) + { + auto const pExt = TechnoExt::ExtMap.Find(pThis->Owner); - if (!detonate) - continue; + if (pExt->AE.HasExtraWarheads) + { + for (auto const& pAE : pExt->AttachedEffects) + { + auto const pType = pAE->GetType(); - if (isFull) - WarheadTypeExt::DetonateAt(pWH, *coords, pTechno, damage, pOwner, pThis->Target); - else - WarheadTypeExt::ExtMap.Find(pWH)->DamageAreaWithTarget(*coords, damage, pTechno, pWH, true, pOwner, abstract_cast(pThis->Target)); + if (pType->ExtraWarheads.size() > 0) + { + pBulletExt->ApplyExtraWarheads(pType->ExtraWarheads, pType->ExtraWarheads_DamageOverrides, pType->ExtraWarheads_DetonationChances, + pType->ExtraWarheads_FullDetonation, pType->ExtraWarheads_UseInvokerAsOwner, *coords, pOwner, pAE->GetInvoker()); + } + } } } @@ -404,19 +388,7 @@ DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) auto const pTypeExt = BulletTypeExt::ExtMap.Find(pThis->Type); if (auto const pWeapon = pTypeExt->ReturnWeapon) - { - auto damage = pWeapon->Damage; - - if (pTypeExt->ReturnWeapon_ApplyFirepowerMult) - damage = static_cast(damage * pTechno->FirepowerMultiplier * TechnoExt::ExtMap.Find(pTechno)->AE.FirepowerMultiplier); - - if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pTechno, pTechno, - damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) - { - BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, pThis->Location, false); - BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, false, true); - } - } + TechnoExt::RealLaunch(pWeapon, pTechno, pTechno, pTypeExt->ReturnWeapon_ApplyFirepowerMult); } WarheadTypeExt::ExtMap.Find(pThis->WH)->InDamageArea = true; diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 01a018c293..6a7c6df608 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -43,7 +43,7 @@ void TechnoExt::ExtData::OnEarlyUpdate() this->ApplyMindControlRangeLimit(); this->UpdateRecountBurst(); this->UpdateRearmInEMPState(); - + if (this->AttackMoveFollowerTempCount) { this->AttackMoveFollowerTempCount--; @@ -1833,6 +1833,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() double armor = 1.0; double speed = 1.0; double ROF = 1.0; + double negativeDamage = 1.0; bool cloak = false; bool forceDecloak = false; bool disableWeapons = false; @@ -1842,6 +1843,9 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() bool reflectsDamage = false; bool hasOnFireDiscardables = false; bool hasRestrictedArmorMultipliers = false; + bool hasCritModifiers = false; + bool hasExtraWarheads = false; + bool hasFeedbackOrAuxWeapon = false; for (const auto& attachEffect : this->AttachedEffects) { @@ -1853,6 +1857,7 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() speed *= type->SpeedMultiplier; armor *= type->ArmorMultiplier; ROF *= type->ROFMultiplier; + negativeDamage *= type->NegativeDamage_Multiplier; cloak |= type->Cloakable; forceDecloak |= type->ForceDecloak; disableWeapons |= type->DisableWeapons; @@ -1862,12 +1867,16 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() reflectsDamage |= type->ReflectDamage; hasOnFireDiscardables |= (type->DiscardOn & DiscardCondition::Firing) != DiscardCondition::None; hasRestrictedArmorMultipliers |= (type->ArmorMultiplier != 1.0 && (type->ArmorMultiplier_AllowWarheads.size() > 0 || type->ArmorMultiplier_DisallowWarheads.size() > 0)); + hasCritModifiers |= (type->Crit_Multiplier != 1.0 || type->Crit_ExtraChance != 0.0); + hasExtraWarheads |= type->ExtraWarheads.size() > 0; + hasFeedbackOrAuxWeapon |= type->FeedbackWeapon != nullptr || type->AuxWeapon != nullptr; } pAE.FirepowerMultiplier = firepower; pAE.ArmorMultiplier = armor; pAE.SpeedMultiplier = speed; pAE.ROFMultiplier = ROF; + pAE.NegativeDamageMultiplier = negativeDamage; pAE.Cloakable = cloak; pAE.ForceDecloak = forceDecloak; pAE.DisableWeapons = disableWeapons; @@ -1877,6 +1886,9 @@ void TechnoExt::ExtData::RecalculateStatMultipliers() pAE.ReflectDamage = reflectsDamage; pAE.HasOnFireDiscardables = hasOnFireDiscardables; pAE.HasRestrictedArmorMultipliers = hasRestrictedArmorMultipliers; + pAE.HasCritModifiers = hasCritModifiers; + pAE.HasExtraWarheads = hasExtraWarheads; + pAE.HasFeedbackOrAuxWeapon = hasFeedbackOrAuxWeapon; if (forceDecloak && pThis->CloakState == CloakState::Cloaked) pThis->Uncloak(true); diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index dabaec75a4..240b48b0c8 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -173,6 +173,7 @@ class TechnoExt void ApplyMindControlRangeLimit(); int ApplyForceWeaponInRange(AbstractClass* pTarget); void UpdateTintValues(); + void ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractClass* pTarget, const CoordStruct& offset, int range, const double& accuracy, bool onTurret, bool retarget, bool aroundFirer, bool zeroDamage, bool firepowerMult, TechnoClass* pInvoker = nullptr); UnitTypeClass* GetUnitTypeExtra() const; @@ -264,4 +265,6 @@ class TechnoExt static void ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static void ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, WarheadTypeClass* pWH); static bool MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* const pTarget, WeaponTypeClass* const pWeaponType); + static bool IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting = true, bool allowZeroDamage = false); + static void RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult = true, TechnoClass* pFirer = nullptr); }; diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index bfecb225de..575dd6a611 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -582,15 +582,52 @@ DEFINE_HOOK(0x6FF43F, TechnoClass_FireAt_FeedbackWeapon, 0x6) { GET(TechnoClass*, pThis, ESI); GET(WeaponTypeClass*, pWeapon, EBX); + GET_BASE(AbstractClass*, pTarget, 0x8); + + auto const pExt = TechnoExt::ExtMap.Find(pThis); if (auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon)) { if (auto const pWeaponFeedback = pWeaponExt->FeedbackWeapon) { - if (pThis->InOpenToppedTransport && !pWeaponFeedback->FireInTransport) - return 0; + if (!pThis->InOpenToppedTransport || pWeaponFeedback->FireInTransport) + WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); + } + + if (auto const pAuxWeapon = pWeaponExt->AuxWeapon) + { + pExt->ApplyAuxWeapon(pAuxWeapon, pTarget, pWeaponExt->AuxWeapon_Offset, pWeaponExt->AuxWeapon_Retarget_Range, pWeaponExt->AuxWeapon_Retarget_Accuracy, pWeaponExt->AuxWeapon_FireOnTurret, + pWeaponExt->AuxWeapon_Retarget, pWeaponExt->AuxWeapon_Retarget_AroundFirer, pWeaponExt->AuxWeapon_AllowZeroDamage, pWeaponExt->AuxWeapon_ApplyFirepowerMult); + } + } + + if (pExt->AE.HasFeedbackOrAuxWeapon) + { + for (auto const& pAE : pExt->AttachedEffects) + { + auto const pAEType = pAE->GetType(); + + if (auto const pWeaponFeedback = pAEType->FeedbackWeapon) + { + if (pThis->InOpenToppedTransport && !pWeaponFeedback->FireInTransport) + continue; + + if (pAEType->FeedbackWeapon_UseInvokerAsOwner) + WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pAE->GetInvoker()); + else + WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); + } - WeaponTypeExt::DetonateAt(pWeaponFeedback, pThis, pThis); + if (auto const pAuxWeapon = pAEType->AuxWeapon) + { + TechnoClass* pInvoker = nullptr; + + if (pAEType->AuxWeapon_UseInvokerAsOwner) + pInvoker = pAE->GetInvoker(); + + pExt->ApplyAuxWeapon(pAuxWeapon, pTarget, pAEType->AuxWeapon_Offset, pAEType->AuxWeapon_Retarget_Range, pAEType->AuxWeapon_Retarget_Accuracy, pAEType->AuxWeapon_FireOnTurret, + pAEType->AuxWeapon_Retarget, pAEType->AuxWeapon_Retarget_AroundFirer, pAEType->AuxWeapon_AllowZeroDamage, pAEType->AuxWeapon_ApplyFirepowerMult, pInvoker); + } } } diff --git a/src/Ext/Techno/Hooks.ReceiveDamage.cpp b/src/Ext/Techno/Hooks.ReceiveDamage.cpp index fbd3ff6483..d095a99b2b 100644 --- a/src/Ext/Techno/Hooks.ReceiveDamage.cpp +++ b/src/Ext/Techno/Hooks.ReceiveDamage.cpp @@ -25,9 +25,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) const auto pSourceHouse = args->SourceHouse; const auto pTargetHouse = pThis->Owner; + int& damage = *args->Damage; // Calculate Damage Multiplier - if (!args->IgnoreDefenses && *args->Damage) + if (!args->IgnoreDefenses && damage) { double multiplier = 1.0; @@ -40,14 +41,14 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (multiplier != 1.0) { - const auto sgnDamage = *args->Damage > 0 ? 1 : -1; - const auto calculateDamage = static_cast(*args->Damage * multiplier); - *args->Damage = calculateDamage ? calculateDamage : sgnDamage; + const auto sgnDamage = damage > 0 ? 1 : -1; + const auto calculateDamage = static_cast(damage * multiplier); + damage = calculateDamage ? calculateDamage : sgnDamage; } } // Raise Combat Alert - if (pRules->CombatAlert && *args->Damage > 1) + if (pRules->CombatAlert && damage > 1) { auto raiseCombatAlert = [&]() { @@ -104,7 +105,7 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) // Shield Receive Damage if (!args->IgnoreDefenses) { - int nDamageLeft = *args->Damage; + int nDamageLeft = damage; if (const auto pShieldData = pExt->Shield.get()) { @@ -114,9 +115,9 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (nDamageLeft >= 0) { - *args->Damage = nDamageLeft; + damage = nDamageLeft; - if (auto pTag = pThis->AttachedTag) + if (const auto pTag = pThis->AttachedTag) pTag->RaiseEvent((TriggerEvent)PhobosTriggerEvent::ShieldBroken, pThis, CellStruct::Empty); } @@ -131,11 +132,17 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) >= pThis->Health) { // Update remaining damage and check if the target will die and should be avoided - *args->Damage = 0; + damage = 0; pThis->Health = 1; pThis->EstimatedHealth = 1; ReceiveDamageTemp::SkipLowDamageCheck = true; } + + if (pExt->AE.NegativeDamageMultiplier != 1.0 && nDamageLeft != 0 && pWHExt->CanTargetHouse(pSourceHouse, pThis) + && MapClass::GetTotalDamage(nDamageLeft, args->WH, pThis->GetTechnoType()->Armor, args->DistanceToEpicenter) < 0) + { + damage = static_cast(damage * pExt->AE.NegativeDamageMultiplier); + } } return 0; diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index fd494aaae9..0f0cd89d20 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -1,9 +1,13 @@ #include "Body.h" #include +#include +#include +#include #include #include +#include #include // Compares two weapons and returns index of which one is eligible to fire against current target (0 = first, 1 = second), or -1 if neither works. @@ -235,20 +239,60 @@ void TechnoExt::ApplyKillWeapon(TechnoClass* pThis, TechnoClass* pSource, Warhea auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); const bool hasFilters = pTypeExt->SuppressKillWeapons_Types.size() > 0; - // KillWeapon can be triggered without the source - if (pWHExt->KillWeapon && (!pSource || (EnumFunctions::CanTargetHouse(pWHExt->KillWeapon_AffectsHouses, pSource->Owner, pThis->Owner) - && EnumFunctions::IsTechnoEligible(pThis, pWHExt->KillWeapon_Affects)))) - { - if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon))) - WeaponTypeExt::DetonateAt(pWHExt->KillWeapon, pThis, pSource); - } + auto tryKillWeapon = [&](auto pWeapon, AffectedHouse affectsHouses, AffectedTarget affects, bool realLaunch, bool onFirer) + { + if (!pWeapon) + return; + + if (onFirer) + { + if (!pSource) + return; + + if (!(EnumFunctions::CanTargetHouse(affectsHouses, pSource->Owner, pThis->Owner) + && EnumFunctions::IsTechnoEligible(pThis, affects))) + { + return; + } + } + else + { + if (pSource && !(EnumFunctions::CanTargetHouse(affectsHouses, pSource->Owner, pThis->Owner) + && EnumFunctions::IsTechnoEligible(pThis, affects))) + { + return; + } + } + + if (pTypeExt->SuppressKillWeapons && (!hasFilters || pTypeExt->SuppressKillWeapons_Types.Contains(pWeapon))) + return; - // KillWeapon.OnFirer must have a source - if (pWHExt->KillWeapon_OnFirer && pSource && EnumFunctions::CanTargetHouse(pWHExt->KillWeapon_OnFirer_AffectsHouses, pSource->Owner, pThis->Owner) - && EnumFunctions::IsTechnoEligible(pThis, pWHExt->KillWeapon_OnFirer_Affects)) + if (onFirer) + { + if (realLaunch) + RealLaunch(pWeapon, pSource, pSource, true, pThis); + else + WeaponTypeExt::DetonateAt(pWeapon, pSource, pSource); + } + else + { + WeaponTypeExt::DetonateAt(pWeapon, pThis, pSource); + } + }; + + tryKillWeapon(pWHExt->KillWeapon, pWHExt->KillWeapon_AffectsHouses, pWHExt->KillWeapon_Affects, false, false); + tryKillWeapon(pWHExt->KillWeapon_OnFirer, pWHExt->KillWeapon_OnFirer_AffectsHouses, pWHExt->KillWeapon_OnFirer_Affects, pWHExt->KillWeapon_OnFirer_RealLaunch, true); + + auto const pExt = TechnoExt::ExtMap.Find(pSource); + + for (auto const& attachEffect : pExt->AttachedEffects) { - if (!pTypeExt->SuppressKillWeapons || (hasFilters && !pTypeExt->SuppressKillWeapons_Types.Contains(pWHExt->KillWeapon_OnFirer))) - WeaponTypeExt::DetonateAt(pWHExt->KillWeapon_OnFirer, pSource, pSource); + if (!attachEffect->IsActive()) + continue; + + auto const pType = attachEffect->GetType(); + tryKillWeapon(pType->KillWeapon, pType->KillWeapon_AffectsHouses, pType->KillWeapon_Affects, false, false); + tryKillWeapon(pType->KillWeapon_OnFirer, pType->KillWeapon_OnFirer_AffectsHouses, pType->KillWeapon_OnFirer_Affects, pType->KillWeapon_OnFirer_RealLaunch, true); } } @@ -262,7 +306,12 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War if (pTypeExt->RevengeWeapon && EnumFunctions::CanTargetHouse(pTypeExt->RevengeWeapon_AffectsHouses, pThis->Owner, pSource->Owner)) { if (!pWHExt->SuppressRevengeWeapons || (hasFilters && !pWHExt->SuppressRevengeWeapons_Types.Contains(pTypeExt->RevengeWeapon))) - WeaponTypeExt::DetonateAt(pTypeExt->RevengeWeapon, pSource, pThis); + { + if (pTypeExt->RevengeWeapon_RealLaunch) + RealLaunch(pTypeExt->RevengeWeapon, pThis, pSource); + else + WeaponTypeExt::DetonateAt(pTypeExt->RevengeWeapon, pSource, pThis); + } } for (auto& attachEffect : pExt->AttachedEffects) @@ -283,11 +332,19 @@ void TechnoExt::ApplyRevengeWeapon(TechnoClass* pThis, TechnoClass* pSource, War auto const pInvoker = attachEffect->GetInvoker(); if (pInvoker && EnumFunctions::CanTargetHouse(pType->RevengeWeapon_AffectsHouses, pInvoker->Owner, pSource->Owner)) - WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pInvoker); + { + if (pType->RevengeWeapon_RealLaunch) + RealLaunch(pType->RevengeWeapon, pInvoker, pSource); + else + WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pInvoker); + } } else if (EnumFunctions::CanTargetHouse(pType->RevengeWeapon_AffectsHouses, pThis->Owner, pSource->Owner)) { - WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pThis); + if (pType->RevengeWeapon_RealLaunch) + RealLaunch(pType->RevengeWeapon, pThis, pSource); + else + WeaponTypeExt::DetonateAt(pType->RevengeWeapon, pSource, pThis); } } } @@ -351,6 +408,38 @@ int TechnoExt::ExtData::ApplyForceWeaponInRange(AbstractClass* pTarget) return forceWeaponIndex; } +bool TechnoExt::IsAllowedSplitsTarget(TechnoClass* pSource, HouseClass* pOwner, WeaponTypeClass* pWeapon, TechnoClass* pTarget, bool useWeaponTargeting, bool allowZeroDamage) +{ + auto const pWH = pWeapon->Warhead; + + if (useWeaponTargeting) + { + auto const pType = pTarget->GetTechnoType(); + + if (!pType->LegalTarget || (!allowZeroDamage && GeneralUtils::GetWarheadVersusArmor(pWH, pType->Armor) == 0.0)) + return false; + + auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + + if (!EnumFunctions::CanTargetHouse(pWeaponExt->CanTargetHouses, pOwner, pTarget->Owner) + || !EnumFunctions::IsCellEligible(pTarget->GetCell(), pWeaponExt->CanTarget, true, true) + || !EnumFunctions::IsTechnoEligible(pTarget, pWeaponExt->CanTarget)) + { + return false; + } + + if (!pWeaponExt->HasRequiredAttachedEffects(pTarget, pSource)) + return false; + } + else + { + if (!WarheadTypeExt::ExtMap.Find(pWH)->CanTargetHouse(pOwner, pTarget)) + return false; + } + + return true; +} + bool TechnoExt::MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* const pTarget, WeaponTypeClass* const pWeaponType) { if (!pWeaponType || pWeaponType->NeverUse @@ -495,3 +584,104 @@ bool TechnoExt::MultiWeaponCanFire(TechnoClass* const pThis, AbstractClass* cons return true; } + +void TechnoExt::ExtData::ApplyAuxWeapon(WeaponTypeClass* pAuxWeapon, AbstractClass* pTarget, const CoordStruct& offset, int range, const double& accuracy, bool onTurret, bool retarget, bool aroundFirer, bool zeroDamage, bool firepowerMult, TechnoClass* pInvoker) +{ + auto const pThis = this->OwnerObject(); + + if (pThis->InOpenToppedTransport && !pAuxWeapon->FireInTransport) + return; + + TechnoClass* pTargetTechno = nullptr; + CellClass* pTargetCell = nullptr; + auto& random = ScenarioClass::Instance->Random; + + if (retarget && accuracy < random.RandomDouble()) + { + auto const coord = aroundFirer ? pThis->Location : pTarget->GetCoords(); + auto const cellSpread = range > 0 ? range : (aroundFirer ? pAuxWeapon->Range : 0); + + std::vector targets; + + for (auto const pTechno : Helpers::Alex::getCellSpreadItems(coord, cellSpread, true)) + { + if (pTechno->IsInPlayfield && pTechno->IsOnMap && pTechno->IsAlive && pTechno->Health > 0 && !pTechno->InLimbo && pTechno != pThis) + { + if ((pAuxWeapon->Projectile->AA || !pTechno->IsInAir()) && TechnoExt::IsAllowedSplitsTarget(pThis, pThis->Owner, pAuxWeapon, pTechno, true, zeroDamage)) + targets.push_back(pTechno); + } + } + + if (!targets.empty()) + { + pTargetTechno = targets[random.RandomRanged(0, targets.size() - 1)]; + } + else + { + auto const cellTarget = CellClass::Coord2Cell(coord); + const int x = random.RandomRanged(-cellSpread, cellSpread); + const int y = random.RandomRanged(-cellSpread, cellSpread); + const CellStruct cell = { static_cast(cellTarget.X + x), static_cast(cellTarget.Y + y) }; + pTargetCell = MapClass::Instance.GetCellAt(cell); + } + } + else + { + pTargetTechno = abstract_cast(pTarget); + + if (pTargetTechno && ((!pAuxWeapon->Projectile->AA && pTargetTechno->IsInAir()) || !TechnoExt::IsAllowedSplitsTarget(pThis, pThis->Owner, pAuxWeapon, pTargetTechno, true, zeroDamage))) + return; + + if (!pTargetTechno) + { + if (auto const pCell = abstract_cast(pTarget)) + pTargetCell = pCell; + else if (auto const pObject = abstract_cast(pTarget)) + pTargetCell = pObject->GetCell(); + + if (!EnumFunctions::IsCellEligible(pTargetCell, WeaponTypeExt::ExtMap.Find(pAuxWeapon)->CanTarget, true, true)) + return; + } + } + + if (!pTargetTechno && !pTargetCell) + return; + + auto const location = TechnoExt::GetFLHAbsoluteCoords(pThis, offset, onTurret); + auto damage = pAuxWeapon->Damage; + + if (firepowerMult) + damage = static_cast(damage * pThis->FirepowerMultiplier * this->AE.FirepowerMultiplier); + + BulletClass* pBullet = nullptr; + auto const pFirer = pInvoker ? pInvoker : pThis; + + if (pTargetTechno) + pBullet = pAuxWeapon->Projectile->CreateBullet(pTargetTechno, pFirer, damage, pAuxWeapon->Warhead, pAuxWeapon->Speed, pAuxWeapon->Bright); + else + pBullet = pAuxWeapon->Projectile->CreateBullet(pTargetCell, pFirer, damage, pAuxWeapon->Warhead, pAuxWeapon->Speed, pAuxWeapon->Bright); + + if (pBullet) + { + BulletExt::SimulatedFiringUnlimbo(pBullet, pThis->Owner, pAuxWeapon, location, true); + BulletExt::SimulatedFiringEffects(pBullet, pThis->Owner, nullptr, false, true); + } +} + +void TechnoExt::RealLaunch(WeaponTypeClass* pWeapon, TechnoClass* pSource, TechnoClass* pTarget, bool applyFirepowerMult, TechnoClass* pFirer) +{ + int damage = pWeapon->Damage; + + if (applyFirepowerMult) + damage = static_cast(damage * pSource->FirepowerMultiplier * TechnoExt::ExtMap.Find(pSource)->AE.FirepowerMultiplier); + + if (BulletClass* pBullet = pWeapon->Projectile->CreateBullet(pTarget, pSource, + damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright)) + { + if (!pFirer) + pFirer = pSource; + + BulletExt::SimulatedFiringUnlimbo(pBullet, pSource->Owner, pWeapon, pFirer->Location, true); + BulletExt::SimulatedFiringEffects(pBullet, pSource->Owner, nullptr, false, true); + } +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index effe270696..181656cf10 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -794,6 +794,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); + this->RevengeWeapon_RealLaunch.Read(exINI, pSection, "RevengeWeapon.RealLaunch"); this->RecountBurst.Read(exINI, pSection, "RecountBurst"); @@ -1397,6 +1398,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) + .Process(this->RevengeWeapon_RealLaunch) .Process(this->AttachEffects) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 8a93a15a53..262820485a 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -267,6 +267,7 @@ class TechnoTypeExt Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; + Valueable RevengeWeapon_RealLaunch; AEAttachInfoTypeClass AttachEffects; @@ -639,6 +640,7 @@ class TechnoTypeExt , RevengeWeapon {} , RevengeWeapon_AffectsHouses { AffectedHouse::All } + , RevengeWeapon_RealLaunch { false } , AttachEffects {} diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 889943c1a6..00d97b8a2a 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -280,6 +280,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->KillWeapon_OnFirer_AffectsHouses.Read(exINI, pSection, "KillWeapon.OnFirer.AffectsHouses"); this->KillWeapon_Affects.Read(exINI, pSection, "KillWeapon.Affects"); this->KillWeapon_OnFirer_Affects.Read(exINI, pSection, "KillWeapon.OnFirer.Affects"); + this->KillWeapon_OnFirer_RealLaunch.Read(exINI, pSection, "KillWeapon.OnFirer.RealLaunch"); this->ElectricAssaultLevel.Read(exINI, pSection, "ElectricAssaultLevel"); @@ -544,6 +545,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->KillWeapon_OnFirer_AffectsHouses) .Process(this->KillWeapon_Affects) .Process(this->KillWeapon_OnFirer_Affects) + .Process(this->KillWeapon_OnFirer_RealLaunch) .Process(this->ElectricAssaultLevel) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index d22bbd274b..d7d5952715 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -174,6 +174,7 @@ class WarheadTypeExt Valueable KillWeapon_OnFirer_AffectsHouses; Valueable KillWeapon_Affects; Valueable KillWeapon_OnFirer_Affects; + Valueable KillWeapon_OnFirer_RealLaunch; Valueable ElectricAssaultLevel; @@ -386,6 +387,7 @@ class WarheadTypeExt , KillWeapon_OnFirer_AffectsHouses { AffectedHouse::All } , KillWeapon_Affects { AffectedTarget::All } , KillWeapon_OnFirer_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_RealLaunch { false } { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index b86013bcf7..66d262a835 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -626,6 +626,10 @@ double WarheadTypeExt::ExtData::GetCritChance(TechnoClass* pFirer) const return critChance; auto const pExt = TechnoExt::ExtMap.Find(pFirer); + + if (!pExt->AE.HasCritModifiers) + return critChance; + double extraChance = 0.0; for (auto& attachEffect : pExt->AttachedEffects) diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 4cbe44eb78..1f9751cd0f 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -111,6 +111,15 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Burst_FireWithinSequence.Read(exINI, pSection, "Burst.FireWithinSequence"); this->AreaFire_Target.Read(exINI, pSection, "AreaFire.Target"); this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); + this->AuxWeapon.Read(exINI, pSection, "AuxWeapon"); + this->AuxWeapon_Offset.Read(exINI, pSection, "AuxWeapon.Offset"); + this->AuxWeapon_FireOnTurret.Read(exINI, pSection, "AuxWeapon.FireOnTurret"); + this->AuxWeapon_AllowZeroDamage.Read(exINI, pSection, "AuxWeapon.AllowZeroDamage"); + this->AuxWeapon_ApplyFirepowerMult.Read(exINI, pSection, "AuxWeapon.ApplyFirepowerMult"); + this->AuxWeapon_Retarget.Read(exINI, pSection, "AuxWeapon.Retarget"); + this->AuxWeapon_Retarget_AroundFirer.Read(exINI, pSection, "AuxWeapon.Retarget.AroundFirer"); + this->AuxWeapon_Retarget_Range.Read(exINI, pSection, "AuxWeapon.Retarget.Range"); + this->AuxWeapon_Retarget_Accuracy.Read(exINI, pSection, "AuxWeapon.Retarget.Accuracy"); this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->VisualScatter.Read(exINI, pSection, "VisualScatter"); this->ROF_RandomDelay.Read(exINI, pSection, "ROF.RandomDelay"); @@ -180,6 +189,15 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->Burst_FireWithinSequence) .Process(this->AreaFire_Target) .Process(this->FeedbackWeapon) + .Process(this->AuxWeapon) + .Process(this->AuxWeapon_Offset) + .Process(this->AuxWeapon_FireOnTurret) + .Process(this->AuxWeapon_AllowZeroDamage) + .Process(this->AuxWeapon_ApplyFirepowerMult) + .Process(this->AuxWeapon_Retarget) + .Process(this->AuxWeapon_Retarget_AroundFirer) + .Process(this->AuxWeapon_Retarget_Range) + .Process(this->AuxWeapon_Retarget_Accuracy) .Process(this->Laser_IsSingleColor) .Process(this->VisualScatter) .Process(this->ROF_RandomDelay) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 02d089cfad..0e378bbbc5 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -45,6 +45,15 @@ class WeaponTypeExt Valueable Burst_FireWithinSequence; Valueable AreaFire_Target; Valueable FeedbackWeapon; + Valueable AuxWeapon; + Valueable AuxWeapon_Offset; + Valueable AuxWeapon_FireOnTurret; + Valueable AuxWeapon_AllowZeroDamage; + Valueable AuxWeapon_ApplyFirepowerMult; + Valueable AuxWeapon_Retarget; + Valueable AuxWeapon_Retarget_AroundFirer; + Valueable AuxWeapon_Retarget_Range; + Valueable AuxWeapon_Retarget_Accuracy; Valueable Laser_IsSingleColor; Valueable VisualScatter; Nullable> ROF_RandomDelay; @@ -103,6 +112,15 @@ class WeaponTypeExt , Burst_FireWithinSequence { false } , AreaFire_Target { AreaFireTarget::Base } , FeedbackWeapon {} + , AuxWeapon {} + , AuxWeapon_Offset { {0, 0, 0} } + , AuxWeapon_FireOnTurret { false } + , AuxWeapon_AllowZeroDamage { true } + , AuxWeapon_ApplyFirepowerMult { true } + , AuxWeapon_Retarget { false } + , AuxWeapon_Retarget_AroundFirer { false } + , AuxWeapon_Retarget_Range { 0 } + , AuxWeapon_Retarget_Accuracy { 1.0 } , Laser_IsSingleColor { false } , VisualScatter { false } , ROF_RandomDelay {} diff --git a/src/New/Entity/AttachEffectClass.cpp b/src/New/Entity/AttachEffectClass.cpp index c4070f5eaf..1fc9475cc0 100644 --- a/src/New/Entity/AttachEffectClass.cpp +++ b/src/New/Entity/AttachEffectClass.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -52,12 +53,25 @@ AttachEffectClass::AttachEffectClass(AttachEffectTypeClass* pType, TechnoClass* if (this->InitialDelay <= 0) this->HasInitialized = true; - this->Duration = this->DurationOverride != 0 ? this->DurationOverride : pType->Duration; + int& duration = this->Duration; + duration = this->DurationOverride != 0 ? this->DurationOverride : pType->Duration; + auto const pTechnoExt = TechnoExt::ExtMap.Find(pTechno); - if (pType->Duration_ApplyFirepowerMult && this->Duration > 0 && pInvoker) - this->Duration = Math::max(static_cast(this->Duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); + if (pType->Duration_ApplyVersus_Warhead && duration > 0) + { + auto armor = pTechno->GetTechnoType()->Armor; + + if (auto const pShieldData = pTechnoExt->Shield.get()) + { + if (pShieldData->IsActive()) + armor = pShieldData->GetArmorType(); + } + + duration = Math::max(MapClass::GetTotalDamage(duration, pType->Duration_ApplyVersus_Warhead, armor, 0), 0); + } - const auto pTechnoExt = TechnoExt::ExtMap.Find(pTechno); + if (pType->Duration_ApplyFirepowerMult && duration > 0 && pInvoker) + duration = Math::max(static_cast(duration * pInvoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(pInvoker)->AE.FirepowerMultiplier), 0); if (pType->Duration_ApplyArmorMultOnTarget && this->Duration > 0) // count its own ArmorMultiplier as well this->Duration = Math::max(static_cast(this->Duration / pTechno->ArmorMultiplier / pTechnoExt->AE.ArmorMultiplier / pType->ArmorMultiplier), 0); @@ -453,16 +467,32 @@ void AttachEffectClass::SetAnimationTunnelState(bool visible) void AttachEffectClass::RefreshDuration(int durationOverride) { + int& duration = this->Duration; + if (durationOverride) - this->Duration = durationOverride; + duration = durationOverride; else - this->Duration = this->DurationOverride ? this->DurationOverride : this->Type->Duration; + duration = this->DurationOverride ? this->DurationOverride : this->Type->Duration; + + if (this->Type->Duration_ApplyVersus_Warhead && duration > 0) + { + auto const pTechnoExt = TechnoExt::ExtMap.Find(this->Techno); + auto armor = this->Techno->GetTechnoType()->Armor; - if (this->Type->Duration_ApplyFirepowerMult && this->Duration > 0 && this->Invoker) - this->Duration = Math::max(static_cast(this->Duration * this->Invoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(this->Invoker)->AE.FirepowerMultiplier), 0); + if (auto const pShieldData = pTechnoExt->Shield.get()) + { + if (pShieldData->IsActive()) + armor = pShieldData->GetArmorType(); + } + + duration = Math::max(MapClass::GetTotalDamage(duration, this->Type->Duration_ApplyVersus_Warhead, armor, 0), 0); + } + + if (this->Type->Duration_ApplyFirepowerMult && duration > 0 && this->Invoker) + duration = Math::max(static_cast(duration * this->Invoker->FirepowerMultiplier * TechnoExt::ExtMap.Find(this->Invoker)->AE.FirepowerMultiplier), 0); - if (this->Type->Duration_ApplyArmorMultOnTarget && this->Duration > 0) // no need to count its own effect again - this->Duration = Math::max(static_cast(this->Duration / this->Techno->ArmorMultiplier / TechnoExt::ExtMap.Find(this->Techno)->AE.ArmorMultiplier), 0); + if (this->Type->Duration_ApplyArmorMultOnTarget && duration > 0) // no need to count its own effect again + duration = Math::max(static_cast(duration / this->Techno->ArmorMultiplier / TechnoExt::ExtMap.Find(this->Techno)->AE.ArmorMultiplier), 0); if (this->Type->Animation_ResetOnReapply) { @@ -503,32 +533,53 @@ bool AttachEffectClass::ShouldBeDiscardedNow() return true; } - if (this->Type->DiscardOn == DiscardCondition::None) + auto const pType = this->Type; + auto const pTechno = this->Techno; + + if (pType->DiscardOn_AbovePercent > 0.0 && pTechno->GetHealthPercentage() >= pType->DiscardOn_AbovePercent) + { + this->LastDiscardCheckValue = true; + return true; + } + + if (pType->DiscardOn_BelowPercent > 0.0 && pTechno->GetHealthPercentage() <= pType->DiscardOn_BelowPercent) + { + this->LastDiscardCheckValue = true; + return true; + } + + if (pType->DiscardOn_CumulativeCount > 0 && pType->DiscardOn_CumulativeCount <= TechnoExt::ExtMap.Find(pTechno)->GetAttachedEffectCumulativeCount(pType)) + { + this->LastDiscardCheckValue = true; + return true; + } + + auto const discardOn = pType->DiscardOn; + + if (discardOn == DiscardCondition::None) { this->LastDiscardCheckValue = false; return false; } - auto const pTechno = this->Techno; - if (auto const pFoot = abstract_cast(pTechno)) { - bool isMoving = pFoot->Locomotor->Is_Really_Moving_Now(); + const bool isMoving = pFoot->Locomotor->Is_Really_Moving_Now(); - if (isMoving && (this->Type->DiscardOn & DiscardCondition::Move) != DiscardCondition::None) + if (isMoving && (discardOn & DiscardCondition::Move) != DiscardCondition::None) { this->LastDiscardCheckValue = true; return true; } - if (!isMoving && (this->Type->DiscardOn & DiscardCondition::Stationary) != DiscardCondition::None) + if (!isMoving && (discardOn & DiscardCondition::Stationary) != DiscardCondition::None) { this->LastDiscardCheckValue = true; return true; } } - if (pTechno->DrainingMe && (this->Type->DiscardOn & DiscardCondition::Drain) != DiscardCondition::None) + if (pTechno->DrainingMe && (discardOn & DiscardCondition::Drain) != DiscardCondition::None) { this->LastDiscardCheckValue = true; return true; @@ -536,27 +587,27 @@ bool AttachEffectClass::ShouldBeDiscardedNow() if (pTechno->Target) { - bool inRange = (this->Type->DiscardOn & DiscardCondition::InRange) != DiscardCondition::None; - bool outOfRange = (this->Type->DiscardOn & DiscardCondition::OutOfRange) != DiscardCondition::None; + const bool inRange = (discardOn & DiscardCondition::InRange) != DiscardCondition::None; + const bool outOfRange = (discardOn & DiscardCondition::OutOfRange) != DiscardCondition::None; if (inRange || outOfRange) { int distance = -1; - if (this->Type->DiscardOn_RangeOverride.isset()) + if (pType->DiscardOn_RangeOverride.isset()) { - distance = this->Type->DiscardOn_RangeOverride.Get(); + distance = pType->DiscardOn_RangeOverride.Get(); } else { - int weaponIndex = pTechno->SelectWeapon(pTechno->Target); + const int weaponIndex = pTechno->SelectWeapon(pTechno->Target); auto const pWeapon = pTechno->GetWeapon(weaponIndex)->WeaponType; if (pWeapon) distance = WeaponTypeExt::GetRangeWithModifiers(pWeapon, pTechno); } - int distanceFromTgt = pTechno->DistanceFrom(pTechno->Target); + const int distanceFromTgt = pTechno->DistanceFrom(pTechno->Target); if ((inRange && distanceFromTgt <= distance) || (outOfRange && distanceFromTgt >= distance)) { @@ -674,6 +725,12 @@ AttachEffectClass* AttachEffectClass::CreateAndAttach(AttachEffectTypeClass* pTy if (!pType || !pTarget) return nullptr; + if (pType->AffectAbovePercent > 0.0 && pTarget->GetHealthPercentage() < pType->AffectAbovePercent) + return nullptr; + + if (pType->AffectBelowPercent > 0.0 && pTarget->GetHealthPercentage() > pType->AffectBelowPercent) + return nullptr; + if (pTarget->IsIronCurtained()) { bool penetrates = pTarget->ForceShielded ? pType->PenetratesForceShield.Get(pType->PenetratesIronCurtain) : pType->PenetratesIronCurtain; diff --git a/src/New/Entity/AttachEffectClass.h b/src/New/Entity/AttachEffectClass.h index e68c14314a..c09f1802bc 100644 --- a/src/New/Entity/AttachEffectClass.h +++ b/src/New/Entity/AttachEffectClass.h @@ -96,6 +96,7 @@ struct AttachEffectTechnoProperties double ArmorMultiplier; double SpeedMultiplier; double ROFMultiplier; + double NegativeDamageMultiplier; bool Cloakable; bool ForceDecloak; bool DisableWeapons; @@ -105,12 +106,16 @@ struct AttachEffectTechnoProperties bool ReflectDamage; bool HasOnFireDiscardables; bool HasRestrictedArmorMultipliers; + bool HasCritModifiers; + bool HasExtraWarheads; + bool HasFeedbackOrAuxWeapon; AttachEffectTechnoProperties() : FirepowerMultiplier { 1.0 } , ArmorMultiplier { 1.0 } , SpeedMultiplier { 1.0 } , ROFMultiplier { 1.0 } + , NegativeDamageMultiplier { 1.0 } , Cloakable { false } , ForceDecloak { false } , DisableWeapons { false } @@ -120,5 +125,8 @@ struct AttachEffectTechnoProperties , ReflectDamage { false } , HasOnFireDiscardables { false } , HasRestrictedArmorMultipliers { false } + , HasCritModifiers { false } + , HasExtraWarheads { false } + , HasFeedbackOrAuxWeapon { false } { } }; diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 6cf4b91829..e5c34af61f 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -222,6 +222,9 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) const int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg); + if (shieldDamage < 0) + shieldDamage = static_cast(shieldDamage * TechnoExt::ExtMap.Find(pTechno)->AE.NegativeDamageMultiplier); + if (Phobos::DisplayDamageNumbers && shieldDamage != 0) GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, pTechno->GetRenderCoords(), TechnoExt::ExtMap.Find(pTechno)->DamageNumberOffset); diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index a6909945b3..e0bfd6cd4c 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -96,6 +96,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) INI_EX exINI(pINI); this->Duration.Read(exINI, pSection, "Duration"); + this->Duration_ApplyVersus_Warhead.Read(exINI, pSection, "Duration.ApplyVersus.Warhead"); this->Duration_ApplyFirepowerMult.Read(exINI, pSection, "Duration.ApplyFirepowerMult"); this->Duration_ApplyArmorMultOnTarget.Read(exINI, pSection, "Duration.ApplyArmorMultOnTarget"); this->Cumulative.Read(exINI, pSection, "Cumulative"); @@ -103,6 +104,11 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Powered.Read(exINI, pSection, "Powered"); this->DiscardOn.Read(exINI, pSection, "DiscardOn"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); + this->DiscardOn_AbovePercent.Read(exINI, pSection, "DiscardOn.AbovePercent"); + this->DiscardOn_BelowPercent.Read(exINI, pSection, "DiscardOn.BelowPercent"); + this->DiscardOn_CumulativeCount.Read(exINI, pSection, "DiscardOn.CumulativeCount"); + this->AffectAbovePercent.Read(exINI, pSection, "AffectAbovePercent"); + this->AffectBelowPercent.Read(exINI, pSection, "AffectBelowPercent"); this->PenetratesIronCurtain.Read(exINI, pSection, "PenetratesIronCurtain"); this->PenetratesForceShield.Read(exINI, pSection, "PenetratesForceShield"); @@ -120,6 +126,26 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->ExpireWeapon_CumulativeOnlyOnce.Read(exINI, pSection, "ExpireWeapon.CumulativeOnlyOnce"); this->ExpireWeapon_UseInvokerAsOwner.Read(exINI, pSection, "ExpireWeapon.UseInvokerAsOwner"); + this->ExtraWarheads.Read(exINI, pSection, "ExtraWarheads"); + this->ExtraWarheads_DamageOverrides.Read(exINI, pSection, "ExtraWarheads.DamageOverrides"); + this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); + this->ExtraWarheads_FullDetonation.Read(exINI, pSection, "ExtraWarheads.FullDetonation"); + this->ExtraWarheads_UseInvokerAsOwner.Read(exINI, pSection, "ExtraWarheads.UseInvokerAsOwner"); + + this->FeedbackWeapon.Read(exINI, pSection, "FeedbackWeapon"); + this->FeedbackWeapon_UseInvokerAsOwner.Read(exINI, pSection, "FeedbackWeapon.UseInvokerAsOwner"); + + this->AuxWeapon.Read(exINI, pSection, "AuxWeapon"); + this->AuxWeapon_Offset.Read(exINI, pSection, "AuxWeapon.Offset"); + this->AuxWeapon_FireOnTurret.Read(exINI, pSection, "AuxWeapon.FireOnTurret"); + this->AuxWeapon_AllowZeroDamage.Read(exINI, pSection, "AuxWeapon.AllowZeroDamage"); + this->AuxWeapon_ApplyFirepowerMult.Read(exINI, pSection, "AuxWeapon.ApplyFirepowerMult"); + this->AuxWeapon_Retarget.Read(exINI, pSection, "AuxWeapon.Retarget"); + this->AuxWeapon_Retarget_AroundFirer.Read(exINI, pSection, "AuxWeapon.Retarget.AroundFirer"); + this->AuxWeapon_Retarget_Range.Read(exINI, pSection, "AuxWeapon.Retarget.Range"); + this->AuxWeapon_Retarget_Accuracy.Read(exINI, pSection, "AuxWeapon.Retarget.Accuracy"); + this->AuxWeapon_UseInvokerAsOwner.Read(exINI, pSection, "AuxWeapon.UseInvokerAsOwner"); + this->Tint_Color.Read(exINI, pSection, "Tint.Color"); this->Tint_Intensity.Read(exINI, pSection, "Tint.Intensity"); this->Tint_VisibleToHouses.Read(exINI, pSection, "Tint.VisibleToHouses"); @@ -145,8 +171,17 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->Crit_AllowWarheads.Read(exINI, pSection, "Crit.AllowWarheads"); this->Crit_DisallowWarheads.Read(exINI, pSection, "Crit.DisallowWarheads"); + this->KillWeapon.Read(exINI, pSection, "KillWeapon"); + this->KillWeapon_OnFirer.Read(exINI, pSection, "KillWeapon.OnFirer"); + this->KillWeapon_AffectsHouses.Read(exINI, pSection, "KillWeapon.AffectsHouses"); + this->KillWeapon_OnFirer_AffectsHouses.Read(exINI, pSection, "KillWeapon.OnFirer.AffectsHouses"); + this->KillWeapon_Affects.Read(exINI, pSection, "KillWeapon.Affects"); + this->KillWeapon_OnFirer_Affects.Read(exINI, pSection, "KillWeapon.OnFirer.Affects"); + this->KillWeapon_OnFirer_RealLaunch.Read(exINI, pSection, "KillWeapon.OnFirer.RealLaunch"); + this->RevengeWeapon.Read(exINI, pSection, "RevengeWeapon"); this->RevengeWeapon_AffectsHouses.Read(exINI, pSection, "RevengeWeapon.AffectsHouses"); + this->RevengeWeapon_RealLaunch.Read(exINI, pSection, "RevengeWeapon.RealLaunch"); this->RevengeWeapon_UseInvokerAsOwner.Read(exINI, pSection, "RevengeWeapon.UseInvokerAsOwner"); this->ReflectDamage.Read(exINI, pSection, "ReflectDamage"); @@ -160,6 +195,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DisableWeapons.Read(exINI, pSection, "DisableWeapons"); this->Unkillable.Read(exINI, pSection, "Unkillable"); + this->NegativeDamage_Multiplier.Read(exINI, pSection, "NegativeDamage.Multiplier"); this->LaserTrail_Type.Read(exINI, pSection, "LaserTrail.Type"); // Groups @@ -172,6 +208,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) { Stm .Process(this->Duration) + .Process(this->Duration_ApplyVersus_Warhead) .Process(this->Duration_ApplyFirepowerMult) .Process(this->Duration_ApplyArmorMultOnTarget) .Process(this->Cumulative) @@ -179,6 +216,11 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Powered) .Process(this->DiscardOn) .Process(this->DiscardOn_RangeOverride) + .Process(this->DiscardOn_AbovePercent) + .Process(this->DiscardOn_BelowPercent) + .Process(this->DiscardOn_CumulativeCount) + .Process(this->AffectAbovePercent) + .Process(this->AffectBelowPercent) .Process(this->PenetratesIronCurtain) .Process(this->PenetratesForceShield) .Process(this->Animation) @@ -193,6 +235,23 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ExpireWeapon_TriggerOn) .Process(this->ExpireWeapon_CumulativeOnlyOnce) .Process(this->ExpireWeapon_UseInvokerAsOwner) + .Process(this->ExtraWarheads) + .Process(this->ExtraWarheads_DamageOverrides) + .Process(this->ExtraWarheads_DetonationChances) + .Process(this->ExtraWarheads_FullDetonation) + .Process(this->ExtraWarheads_UseInvokerAsOwner) + .Process(this->FeedbackWeapon) + .Process(this->FeedbackWeapon_UseInvokerAsOwner) + .Process(this->AuxWeapon) + .Process(this->AuxWeapon_Offset) + .Process(this->AuxWeapon_FireOnTurret) + .Process(this->AuxWeapon_AllowZeroDamage) + .Process(this->AuxWeapon_ApplyFirepowerMult) + .Process(this->AuxWeapon_Retarget) + .Process(this->AuxWeapon_Retarget_AroundFirer) + .Process(this->AuxWeapon_Retarget_Range) + .Process(this->AuxWeapon_Retarget_Accuracy) + .Process(this->AuxWeapon_UseInvokerAsOwner) .Process(this->Tint_Color) .Process(this->Tint_Intensity) .Process(this->Tint_VisibleToHouses) @@ -213,8 +272,16 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->Crit_ExtraChance) .Process(this->Crit_AllowWarheads) .Process(this->Crit_DisallowWarheads) + .Process(this->KillWeapon) + .Process(this->KillWeapon_OnFirer) + .Process(this->KillWeapon_AffectsHouses) + .Process(this->KillWeapon_OnFirer_AffectsHouses) + .Process(this->KillWeapon_Affects) + .Process(this->KillWeapon_OnFirer_Affects) + .Process(this->KillWeapon_OnFirer_RealLaunch) .Process(this->RevengeWeapon) .Process(this->RevengeWeapon_AffectsHouses) + .Process(this->RevengeWeapon_RealLaunch) .Process(this->RevengeWeapon_UseInvokerAsOwner) .Process(this->ReflectDamage) .Process(this->ReflectDamage_Warhead) @@ -226,6 +293,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->ReflectDamage_UseInvokerAsOwner) .Process(this->DisableWeapons) .Process(this->Unkillable) + .Process(this->NegativeDamage_Multiplier) .Process(this->LaserTrail_Type) .Process(this->Groups) ; diff --git a/src/New/Type/AttachEffectTypeClass.h b/src/New/Type/AttachEffectTypeClass.h index 36bb14fd1c..662ced9b25 100644 --- a/src/New/Type/AttachEffectTypeClass.h +++ b/src/New/Type/AttachEffectTypeClass.h @@ -42,6 +42,7 @@ class AttachEffectTypeClass final : public Enumerable public: Valueable Duration; + Valueable Duration_ApplyVersus_Warhead; Valueable Duration_ApplyFirepowerMult; Valueable Duration_ApplyArmorMultOnTarget; Valueable Cumulative; @@ -49,6 +50,11 @@ class AttachEffectTypeClass final : public Enumerable Valueable Powered; Valueable DiscardOn; Nullable DiscardOn_RangeOverride; + Valueable DiscardOn_AbovePercent; + Valueable DiscardOn_BelowPercent; + Valueable DiscardOn_CumulativeCount; + Valueable AffectAbovePercent; + Valueable AffectBelowPercent; Valueable PenetratesIronCurtain; Nullable PenetratesForceShield; Valueable Animation; @@ -63,6 +69,23 @@ class AttachEffectTypeClass final : public Enumerable Valueable ExpireWeapon_TriggerOn; Valueable ExpireWeapon_CumulativeOnlyOnce; Valueable ExpireWeapon_UseInvokerAsOwner; + ValueableVector ExtraWarheads; + ValueableVector ExtraWarheads_DamageOverrides; + ValueableVector ExtraWarheads_DetonationChances; + ValueableVector ExtraWarheads_FullDetonation; + ValueableVector ExtraWarheads_UseInvokerAsOwner; + Valueable FeedbackWeapon; + Valueable FeedbackWeapon_UseInvokerAsOwner; + Valueable AuxWeapon; + Valueable AuxWeapon_Offset; + Valueable AuxWeapon_FireOnTurret; + Valueable AuxWeapon_AllowZeroDamage; + Valueable AuxWeapon_ApplyFirepowerMult; + Valueable AuxWeapon_Retarget; + Valueable AuxWeapon_Retarget_AroundFirer; + Valueable AuxWeapon_Retarget_Range; + Valueable AuxWeapon_Retarget_Accuracy; + Valueable AuxWeapon_UseInvokerAsOwner; Nullable Tint_Color; Valueable Tint_Intensity; Valueable Tint_VisibleToHouses; @@ -83,8 +106,16 @@ class AttachEffectTypeClass final : public Enumerable Valueable Crit_ExtraChance; ValueableVector Crit_AllowWarheads; ValueableVector Crit_DisallowWarheads; + Valueable KillWeapon; + Valueable KillWeapon_OnFirer; + Valueable KillWeapon_AffectsHouses; + Valueable KillWeapon_OnFirer_AffectsHouses; + Valueable KillWeapon_Affects; + Valueable KillWeapon_OnFirer_Affects; + Valueable KillWeapon_OnFirer_RealLaunch; Valueable RevengeWeapon; Valueable RevengeWeapon_AffectsHouses; + Valueable RevengeWeapon_RealLaunch; Valueable RevengeWeapon_UseInvokerAsOwner; Valueable ReflectDamage; Nullable ReflectDamage_Warhead; @@ -96,12 +127,14 @@ class AttachEffectTypeClass final : public Enumerable Valueable ReflectDamage_UseInvokerAsOwner; Valueable DisableWeapons; Valueable Unkillable; + Valueable NegativeDamage_Multiplier; ValueableIdx LaserTrail_Type; std::vector Groups; AttachEffectTypeClass(const char* const pTitle) : Enumerable(pTitle) , Duration { 0 } + , Duration_ApplyVersus_Warhead {} , Duration_ApplyFirepowerMult { false } , Duration_ApplyArmorMultOnTarget { false } , Cumulative { false } @@ -109,6 +142,11 @@ class AttachEffectTypeClass final : public Enumerable , Powered { false } , DiscardOn { DiscardCondition::None } , DiscardOn_RangeOverride {} + , DiscardOn_AbovePercent { 0.0 } + , DiscardOn_BelowPercent { 0.0 } + , DiscardOn_CumulativeCount { -1 } + , AffectAbovePercent { 0.0 } + , AffectBelowPercent { 0.0 } , PenetratesIronCurtain { false } , PenetratesForceShield {} , Animation {} @@ -123,6 +161,23 @@ class AttachEffectTypeClass final : public Enumerable , ExpireWeapon_TriggerOn { ExpireWeaponCondition::Expire } , ExpireWeapon_CumulativeOnlyOnce { false } , ExpireWeapon_UseInvokerAsOwner { false } + , ExtraWarheads {} + , ExtraWarheads_DamageOverrides {} + , ExtraWarheads_DetonationChances {} + , ExtraWarheads_FullDetonation {} + , ExtraWarheads_UseInvokerAsOwner {} + , FeedbackWeapon {} + , FeedbackWeapon_UseInvokerAsOwner { false } + , AuxWeapon {} + , AuxWeapon_Offset { {0, 0, 0} } + , AuxWeapon_FireOnTurret { false } + , AuxWeapon_AllowZeroDamage { true } + , AuxWeapon_ApplyFirepowerMult { true } + , AuxWeapon_Retarget { false } + , AuxWeapon_Retarget_AroundFirer { false } + , AuxWeapon_Retarget_Range { 0 } + , AuxWeapon_Retarget_Accuracy { 1.0 } + , AuxWeapon_UseInvokerAsOwner { false } , Tint_Color {} , Tint_Intensity { 0.0 } , Tint_VisibleToHouses { AffectedHouse::All } @@ -143,8 +198,16 @@ class AttachEffectTypeClass final : public Enumerable , Crit_ExtraChance { 0.0 } , Crit_AllowWarheads {} , Crit_DisallowWarheads {} + , KillWeapon {} + , KillWeapon_OnFirer {} + , KillWeapon_AffectsHouses { AffectedHouse::All } + , KillWeapon_OnFirer_AffectsHouses { AffectedHouse::All } + , KillWeapon_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_Affects { AffectedTarget::All } + , KillWeapon_OnFirer_RealLaunch { false } , RevengeWeapon {} , RevengeWeapon_AffectsHouses { AffectedHouse::All } + , RevengeWeapon_RealLaunch { false } , ReflectDamage { false } , RevengeWeapon_UseInvokerAsOwner { false } , ReflectDamage_Warhead {} @@ -156,6 +219,7 @@ class AttachEffectTypeClass final : public Enumerable , ReflectDamage_UseInvokerAsOwner { false } , DisableWeapons { false } , Unkillable { false } + , NegativeDamage_Multiplier { 1.0 } , LaserTrail_Type { -1 } , Groups {} {};