diff --git a/CREDITS.md b/CREDITS.md index ca1a66355c..2825b42f12 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -46,6 +46,7 @@ This page lists all the individual contributions to the project by their author. - Customizable ElectricBolt Arcs - Ability to disable shadow for debris & meteor animations - Voxel light source position customization + - Voxel light source position and tilting fix - **Uranusian (Thrifinesma)**: - Mind Control enhancement - Custom warhead splash list @@ -176,6 +177,7 @@ This page lists all the individual contributions to the project by their author. - Trailer animation owner inheritance - Warhead detonation on all objects on map - Animated TerrainTypes extension + - TerrainType damage & crumbling frames - Exploding unit passenger killing customization - Railgun particle target coordinate fix - Building target coordinate offset fix @@ -233,6 +235,12 @@ This page lists all the individual contributions to the project by their author. - Extra tint intensity for Iron Curtain & Force Shield - Option to enable parsing 8-bit RGB values from `[ColorAdd]` instead of RGB565 - Customizing height at which subterranean units travel + - AI superweapon delay timer customization + - Disabling `MultipleFactory` bonus from specific BuildingType + - Customizable ChronoSphere teleport delays for units + - Allowed and disallowed types for `FactoryPlant` + - Forbidding parallel AI queues for specific TechnoTypes + - Nonprovocative Warheads - **Morton (MortonPL)**: - `XDrawOffset` for animations - Shield passthrough & absorption @@ -318,6 +326,7 @@ This page lists all the individual contributions to the project by their author. - **NetsuNegi** - Forbidding parallel AI queues by type - Jumpjet crash speed fix when crashing onto building + - Disguised units not using the correct palette if target has custom palette bugfix - **Apollo** - Translucent SHP drawing patches - **ststl** - Customizable ShowTimer priority of superweapons diff --git a/YRpp b/YRpp index 46b8caab8c..d107962d13 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 46b8caab8c7eac29ab64d83cb0dde197955b92ab +Subproject commit d107962d130d898132c182540a2c218f23548b2f diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index f1c92479c2..13392a53c7 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -82,7 +82,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho ![Waving trees](_static/images/tree-shake.gif) *Animated trees used in [Ion Shock](https://www.moddb.com/mods/tiberian-war-ionshock)* -- `IsAnimated`, `AnimationRate` and `AnimationProbability` now work on TerrainTypes without `SpawnsTiberium` set to true. Note that this might impact performance. - Fixed transports recursively put into each other not having a correct killer set after second transport when being killed by something. ![image](_static/images/translucency-fix.png) @@ -162,9 +161,14 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - OverlayTypes now read and use `ZAdjust` if specified in their `artmd.ini` entry. - Setting `[AudioVisual]` -> `ColorAddUse8BitRGB` to true makes game treat values from `[ColorAdd]` as 8-bit RGB (0-255) instead of RGB565 (0-31 for red & blue, 0-63 for green). This works for `LaserTargetColor`, `IronCurtainColor`, `BerserkColor` and `ForceShieldColor`. - Weapons with `AA=true` Projectile can now correctly fire at air units when both firer and target are over a bridge. +- Fixed disguised units not using the correct palette if target has custom palette. +- 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. ## Fixes / interactions with other extensions +- `IsSimpleDeployer` units with Hover locomotor and `DeployToLand` no longer get stuck after deploying or play their move sound indefinitely. - All forms of type conversion (including Ares') now correctly update the warp-in delay if unit with teleport `Locomotor` was converted while the delay was active. - All forms of type conversion (including Ares') now correctly update `MoveSound` if a moving unit has their type changed. - All forms of type conversion (including Ares') now correctly update `OpenTopped` state of passengers in transport that is converted. @@ -299,6 +303,18 @@ In `rulesmd.ini`: AllowAirstrike= ; boolean ``` +### Allowed / disallowed types for FactoryPlant + +- It is now possible to customize which TechnoTypes benefit from bonuses of a `FactoryPlant=true` building by listing them on `FactoryPlant.AllowTypes` and/or `FactoryPlant.DisallowTypes`. + - `FactoryPlant.Multiplier` (Ares feature) is still applied on the bonuses if they are in effect. + +In `rulesmd.ini`: +```ini +[SOMEBUILDING] ; BuildingType +FactoryPlant.AllowTypes= ; List of TechnoTypes +FactoryPlant.DisallowTypes= ; List of TechnoTypes +``` + ### Apply ZShapePointMove during buildups - By default buildings do not apply `ZShapePointMove` (which offsets the 'z shape' applied on buildings which is used to adjust them in depth buffer and is used to fix issues related to that such as corners of buildings getting cut off when drawn) when buildup is being displayed. This behaviour can now be toggled by setting `ZShapePointMove.OnBuildup`. @@ -359,6 +375,16 @@ In `rulesmd.ini`: SellBuildupLength=23 ; integer, number of buildup frames to play ``` +### Exclude Factory from providing multiple factory bonus + +- It is now possible to exclude a building with `Factory` from counting towards `MultipleFactory` bonus. + +In `rulesmd.ini`: +```ini +[SOMEBUILDING] ; BuildingType +ExcludeFromMultipleFactoryBonus=false ; boolean +``` + ## Particle systems ### Fire particle target coordinate adjustment when firer rotates @@ -463,6 +489,25 @@ ChronoSparkleDisplayDelay=24 ; integer, game frames ChronoSparkleBuildingDisplayPositions=occupantslots ; list of chrono sparkle position enum (building | occupants | occupantslots | all) ``` +### Customizable ChronoSphere teleport delays for units + +- It is now possible to customize (globally and per TechnoType) the warp-in delay for units teleporting through `Type=ChronoSphere/Warp` Superweapon, both before and after the jump. + +In `rulesmd.ini`: +```ini +[General] +ChronoSphereDelay=60 ; integer, game frames +ChronoSpherePreDelay=0 ; integer, game frames + +[SOMETECHNO] ; TechnoType +ChronoSphereDelay= ; integer, game frames +ChronoSpherePreDelay= ; integer, game frames +``` + +```{warning} +Due to technical constraints, these settings do not apply to buildings teleported by Ares' customizable ChronoSphere SW. They only have a pre-teleport delay equal to `[General]` -> ChronoDelay. +``` + ### Customizable veterancy insignias - You can now customize veterancy insignia of TechnoTypes. @@ -475,7 +520,6 @@ ChronoSparkleBuildingDisplayPositions=occupantslots ; list of chrono sparkle po - Position for insignias can be adjusted by setting `DrawInsignia.AdjustPos.Infantry` for infantry, `DrawInsignia.AdjustPos.Buildings` for buildings, and `DrawInsignia.AdjustPos.Units` for others. - `DrawInsignia.AdjustPos.BuildingsAnchor` can be set to an anchor point to anchor the insignia position relative to the building's selection bracket. By default the insignia position is not anchored to the selection bracket. - In `rulesmd.ini`: ```ini [General] @@ -771,22 +815,32 @@ ShadowIndex.Frame=0 ; integer (HVA animation frame index) ShadowIndices.Frame= ; list of integers (HVA animation frame indices) ``` -### Voxel light source position customization +### Voxel light source customization + +- Vanilla game applies some weird unnecessary math which resulted in the voxel light source being "nudged" up by a bit and light being applied incorrectly on tilted voxels. It is now possible to fix that. + +```{note} +Please note that enabling this will remove the vertical offset vanilla engine applies to the light source position. Assuming vanilla lighting this will make the light shine even more from below the ground than it was before, so it is recommended to turn the Z value up in value of `VoxelLightSource`. +``` ![image](_static/images/VoxelLightSourceComparison.png) -*New lighting with `VoxelLightSource=0.02,-0.69,0.36` vs default lighting, Prism Tank voxel by [CCS_qkl](https://bbs.ra2diy.com/home.php?mod=space&uid=20016&do=index)* +*Applying `VoxelLightSource=0.02,-0.69,0.36` (assuming `UseFixedVoxelLighting=false`) vs default lighting, Prism Tank voxel by [CCS_qkl](https://bbs.ra2diy.com/home.php?mod=space&uid=20016&do=index)* - It is now possible to change the position of the light relative to the voxels. This allows for better lighting to be set up. - Only the direction of the light is accounted, the distance to the voxel is not accounted. + - Vanilla light (assuming `UseFixedVoxelLighting=false`) is located roughly at `VoxelLightSource=0.201,-0.907,-0.362`. In `rulesmd.ini`: ```ini [AudioVisual] -VoxelLightSource= ; X,Y,Z - position of the light in the world relative to each voxel, floating point values +UseFixedVoxelLighting=false ; boolean, whether to fix the lighting +VoxelLightSource= ; X,Y,Z - position of the light in the world relative to each voxel, floating point values ``` ```{hint} In order to easily preview the light source settings use the [VXL Viewer and VPL Generator tool by thomassneddon](https://github.com/ThomasSneddon/vxl-renderer/releases). To use the tool unpack it somewhere, then drag the main VXL file of a voxel that you will use to preview onto it (auxilliary VXL and HVA files must be in the same folder). + +Keep in mind that the tool doesn't account for `UseFixedVoxelLighting=true` as of yet, so the values shown in tool need to be offset when putting in the game with with fixed voxel lighting. ``` ### Voxel shadow scaling in air @@ -809,6 +863,7 @@ ShadowSizeCharacteristicHeight= ; integer, height in leptons - You can now set if specific types of factories do not have AI production cloning issue instead of Ares' indiscriminate behavior of `AllowParallelAIQueues=no`. - If `AllowParallelAIQueues=no` (*Ares feature*) is set, the tags have no effect. +- You can also exclude specific TechnoTypes from being built in parallel by AI by setting `ForbidParallelAIQueues` to true on a TechnoType. In `rulesmd.ini` ```ini @@ -819,10 +874,24 @@ ForbidParallelAIQueues.Vehicle=no ; boolean ForbidParallelAIQueues.Navy=no ; boolean ForbidParallelAIQueues.Aircraft=no ; boolean ForbidParallelAIQueues.Building=no ; boolean + +[SOMETECHNO] ; TechnoType +ForbidParallelAIQueues=false ; boolean ``` ## Terrains +### Animated TerrainTypes + +- By default `IsAnimated`, `AnimationRate` and `AnimationProbability` only work on TerrainTypes with `SpawnsTiberium` set to true. This restriction has now been lifted. + - Length of the animation can now be customized by setting `AnimationLength` as well, defaulting to half (or quarter if [damaged frames](#damaged-frames-and-crumbling-animation) are enabled) the number of frames in TerrainType's image. + +In `rulesmd.ini`: +```ini +[SOMETERRAINTYPE] ; TerrainType +AnimationLength= ; integer, number of frames +``` + ### Customizable ore spawners ![image](_static/images/ore-01.png) @@ -841,6 +910,38 @@ SpawnsTiberium.GrowthStage=3 ; integer - single or comma-sep. range SpawnsTiberium.CellsPerAnim=1 ; integer - single or comma-sep. range ``` +### Custom palette + +- You can now specify custom palette for TerrainTypes in similar manner as TechnoTypes can. + - Note that this palette behaves like an object palette and does not use tint etc. that have been applied to the tile the TerrainType resides on like a TerrainType using tile palette would. + +In `artmd.ini`: +```ini +[SOMETERRAINTYPE] ; TerrainType +Palette= ; filename - excluding .pal extension and three-character theater-specific suffix +``` + +### Damaged frames and crumbling animation + +- By default game shows damage frame only for TerrainTypes alive at only 1 point of health left. Because none of the original game TerrainType assets were made with this in mind, the logic is now locked behind a new key `HasDamagedFrames`. + - Instead of showing at 1 point of HP left, TerrainTypes switch to damaged frames once their health reaches `[AudioVisual]` -> `ConditionYellow.Terrain` percentage of their maximum health. Defaults to `ConditionYellow` if not set. +- In addition, TerrainTypes can now show 'crumbling' animation after their health has reached zero and before they are deleted from the map by setting `HasCrumblingFrames` to true. + - Crumbling frames start from first frame after both regular & damaged frames and ends at halfway point of the frames in TerrainType's image. + - Note that the number of regular & damage frames considered for this depends on value of `HasDamagedFrames` and for `IsAnimated` TerrainTypes, `AnimationLength` (see [Animated TerrainTypes](#animated-terraintypes). Exercise caution and ensure there are correct amount of frames to display. + - Sound event from `CrumblingSound` (if set) is played when crumbling animation starts playing. + - [Destroy animation & sound](New-or-Enhanced-Logics.md#destroy-animation-sound) only play after crumbling animation has finished. + +In `rulesmd.ini`: +```ini +[AudioVisual] +ConditionYellow.Terrain= ; floating-point value + +[SOMETERRAINTYPE] ; TerrainType +HasDamagedFrames=false ; boolean +HasCrumblingFrames=false +CrumblingSound= ; Sound +``` + ### Minimap color customization - TerrainTypes can now be made to display on minimap with different colors by setting `MinimapColor`. @@ -1148,6 +1249,20 @@ In `rulesmd.ini`: DecloakDamagedTargets=true ; boolean ``` +### Nonprovocative Warheads + +- 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 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. + +In `rulesmd.ini`: +```ini +[SOMEWARHEAD] ; WarheadType +Nonprovocative=false ; boolean +``` + ### Restricting screen shaking to current view - You can now specify whether or not the warhead can only shake screen (`ShakeX/Ylo/hi`) if it is detonated while visible on current screen view. diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index a2130fe06e..0db1dd46bf 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -77,7 +77,8 @@ This page describes all the engine features that are either new and introduced b - `AttachEffect.Required/DisallowedGroups` have the same effect except applied with/to all types that have one of the listed groups in their `Groups` listing. - `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] @@ -162,6 +163,7 @@ AttachEffect.RequiredMaxCounts= ; integer - maximum required inst AttachEffect.DisallowedMinCounts= ; integer - minimum disallowed instance count (comma-separated) for cumulative types in order from first to last. AttachEffect.DisallowedMaxCounts= ; integer - maximum disallowed instance count (comma-separated) for cumulative types in order from first to last. AttachEffect.IgnoreFromSameSource=false ; boolean +AttachEffect.CheckOnFirer=false ; boolean [SOMEWARHEAD] AttachEffect.AttachTypes= ; List of AttachEffectTypes @@ -182,7 +184,7 @@ SuppressReflectDamage.Types= ; List of AttachEffectTypes - Any weapon can now have a custom radiation type. More details on radiation [here](https://www.modenc.renegadeprojects.com/Radiation). - There are several new properties available to all radiation types. - `RadApplicationDelay.Building` can be set to value higher than 0 to allow radiation to damage buildings. How many times a single radiation site can deal this damage to same building (every cell of the foundation is hit by all radiation sites on a cell) can be customized with `RadBuildingDamageMaxCount`, negative values mean no limit. - - `RadSiteWarhead.Detonate` can be set to make `RadSiteWarhead` detonate on affected objects rather than only be used to dealt direct damage. This enables most Warhead effects, display of animations etc. + - `RadSiteWarhead.Detonate` can be set to make `RadSiteWarhead` detonate on affected objects rather than only be used to dealt direct damage. This enables most Warhead effects, display of animations etc. If `RadSiteWarhead.Detonate.Full` is set to false instead, instead of full Warhead detonation it only applies area damage and Phobos Warhead effects. - `RadHasOwner`, if set to true, makes damage dealt by the radiation count as having been dealt by the house that fired the projectile that created the radiation field. This means that Warhead controls such as `AffectsAllies` will be respected and any units killed will count towards that player's destroyed units count. - `RadHasInvoker`, if set to true, makes the damage dealt by the radiation count as having been dealt by the TechnoType (the 'invoker') that fired the projectile that created the radiation field. In addition to the effects of `RadHasOwner`, this will also grant experience from units killed by the radiation to the invoker. Note that if the invoker dies at any point during the radiation's lifetime it continues to behave as if not having an invoker. - By default `UseGlobalRadApplicationDelay` is set to true. This makes game always use `RadApplicationDelay` and `RadApplicationDelay.Building` from `[Radiation]` rather than specific radiation types. This is a performance-optimizing measure that should be disabled if a radiation type declares different application delay. @@ -212,6 +214,7 @@ RadTintFactor=1.0 ; floating point value RadColor=0,255,0 ; integer - R,G,B RadSiteWarhead=RadSite ; WarheadType RadSiteWarhead.Detonate=false ; boolean +RadSiteWarhead.Detonate.Full=true ; boolean RadHasOwner=false ; boolean RadHasInvoker=false ; boolean ``` @@ -337,6 +340,7 @@ Shield.Penetrate=false ; boolean Shield.Break=false ; boolean Shield.BreakAnim= ; Animation Shield.HitAnim= ; Animation +Shield.SkipHitAnim=false ; boolean Shield.HitFlash=true ; boolean Shield.BreakWeapon= ; WeaponType Shield.AbsorbPercent= ; floating point value @@ -413,6 +417,7 @@ Shield.InheritStateOnReplace=false ; boolean - `Shield.Break` allows the warhead to always break shields of TechnoTypes. This is done before damage is dealt. - `Shield.BreakAnim` will be displayed instead of ShieldType `BreakAnim` if the shield is broken by the Warhead, either through damage or `Shield.Break`. - `Shield.HitAnim` will be displayed instead of ShieldType `HitAnim` if set when Warhead hits the shield. + - If `Shield.SkipHitAnim` is set to true, no hit anim is shown when the Warhead damages the shield whatsoever. - `Shield.BreakWeapon` will be fired instead of ShieldType `BreakWeapon` if the shield is broken by the Warhead, either through damage or `Shield.Break`. - `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. @@ -764,6 +769,16 @@ This currently has same limitations as `AirburstWeapon` in that it does not prop ## Super Weapons +### AI Superweapon delay timer + +- By default AI houses only process superweapon logic e.g checks if it can fire any superweapons firing them at randomized intervals of 106 to 112 game frames. This behaviour can now be customized by setting explicit delay, or disabling it entirely. Values of 0 and below disable the delay and cause AI houses to check superweapons on every game frame. + +In `rulesmd.ini`: +```ini +[General] +AISuperWeaponDelay= ; integer, game frames +``` + ### Convert TechnoType - Warheads can now change TechnoTypes of affected units to other Types in the same category (infantry to infantry, vehicles to vehicles, aircraft to aircraft). @@ -862,17 +877,19 @@ SW.Next.RandomWeightsN= ; List of integers. - Any superweapon can now detonate a Warhead or a weapon at superweapon's target cell. - If both `Detonate.Warhead` and `Detonate.Weapon` are set, latter takes precedence. + - `Detonate.Warhead.Full` customizes whether or not the Warhead is detonated fully (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. - `Detonate.Damage`, if not set, defaults to weapon `Damage` for `Detonate.Weapon` and 0 for `Detonate.Warhead`. - Both the weapon and Warhead behave as if fired by whatever building fired the Superweapon. This respects controls like `SW.RangeMinimum/Maximum` (similar to Ares' GenericWarhead superweapon in this regard). If firing building could not be found, the house the Superweapon belonged to is still used to deal damage and apply Phobos-introduced Warhead effects. - If `Detonate.AtFirer` is set to true, the weapon or Warhead is detonated at the firing building instead of the superweapon's target cell. If there is no firer, no detonation will occur. In `rulesmd.ini`: ```ini -[SOMESW] ; Super Weapon -Detonate.Warhead= ; WarheadType -Detonate.Weapon= ; WeaponType -Detonate.Damage= ; integer -Detonate.AtFirer=false ; boolean +[SOMESW] ; Super Weapon +Detonate.Warhead= ; WarheadType +Detonate.Warhead.Full=true ; boolean +Detonate.Weapon= ; WeaponType +Detonate.Damage= ; integer +Detonate.AtFirer=false ; boolean ``` ## Technos @@ -1317,11 +1334,13 @@ RemoveMindControl=false ; boolean - Warheads can now apply additional chance-based damage or Warhead detonation ('critical hits') with the ability to customize chance, damage, affected targets, affected target HP threshold and animations of critical hit. - `Crit.Chance` determines chance for a critical hit to occur. By default this is checked once when the Warhead is detonated and every target that is susceptible to critical hits will be affected. If `Crit.ApplyChancePerTarget` is set, then whether or not the chance roll is successful is determined individually for each target. - `Crit.ExtraDamage` determines the damage dealt by the critical hit. If `Crit.Warhead` is set, the damage is used to detonate the specified Warhead on each affected target, otherwise the damage is directly dealt based on current Warhead's `Verses` settings. + - `Crit.Warhead` can be used to set a Warhead to detonate instead of using current Warhead. `Crit.Warhead.FullDetonation` controls whether or not the Warhead is detonated fully on the targets (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. - `Crit.Affects` can be used to customize types of targets that this Warhead can deal critical hits against. Critical hits cannot affect empty cells or cells containing only TerrainTypes, overlays etc. - `Crit.AffectsHouses` can be used to customize houses that this Warhead can deal critical hits against. - `Crit.AffectBelowPercent` can be used to set minimum percentage of their maximum `Strength` that targets must have left to be affected by a critical hit. - `Crit.AnimList` can be used to set a list of animations used instead of Warhead's `AnimList` if Warhead deals a critical hit to even one target. If `Crit.AnimList.PickRandom` is set (defaults to `AnimList.PickRandom`) then the animation is chosen randomly from the list. - `Crit.AnimOnAffectedTargets`, if set, makes the animation(s) from `Crit.AnimList` play on each affected target *in addition* to animation from Warhead's `AnimList` playing as normal instead of replacing `AnimList` animation. + - `Crit.ActiveChanceAnims` can be used to set animation to be always displayed at the Warhead's detonation coordinates if the current Warhead has a chance to critically hit. If more than one animation is listed, a random one is selected. - `Crit.SuppressWhenIntercepted`, if set, prevents critical hits from occuring at all if the warhead was detonated from a [projectile that was intercepted](#projectile-interception-logic). - `ImmuneToCrit` can be set on TechnoTypes and ShieldTypes to make them immune to critical hits. @@ -1332,11 +1351,13 @@ Crit.Chance=0.0 ; floating point value, percents or absolute Crit.ApplyChancePerTarget=false ; boolean Crit.ExtraDamage=0 ; integer Crit.Warhead= ; Warhead +Crit.Warhead.FullDetonation=true ; boolean Crit.Affects=all ; list of Affected Target Enumeration (none|land|water|infantry|units|buildings|all) Crit.AffectsHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) Crit.AffectBelowPercent=1.0 ; floating point value, percents or absolute (0.0-1.0) Crit.AnimList= ; list of animations Crit.AnimList.PickRandom= ; boolean +Crit.ActiveChanceAnims= ; list of animations Crit.AnimOnAffectedTargets=false ; boolean Crit.SuppressWhenIntercepted=false ; boolean @@ -1405,6 +1426,7 @@ While this feature can provide better performance than a large `CellSpread` valu ``` - Setting `DetonateOnAllMapObjects` to true allows a Warhead that is detonated by a projectile (for an example, this excludes things like animation `Warhead` and Ares' GenericWarhead superweapon but includes `Crit.Warhead` and animation `Weapon`) and consequently any `Airburst/ShrapnelWeapon` that may follow to detonate on each object currently alive and existing on the map regardless of its actual target, with optional filters. Note that this is done immediately prior Warhead detonation so after `PreImpactAnim` *(Ares feature)* has been displayed. + - `DetonateOnAllMapObjects.Full` customizes whether or not the Warhead is detonated fully on the targets (as part of a dummy weapon) or simply deals area damage and applies Phobos' Warhead effects. - `DetonateOnAllMapObjects.AffectTargets` is used to filter which types of targets (TechnoTypes) are considered valid and must be set to a valid value other than `none` for this feature to work. Only `none`, `all`, `aircraft`, `buildings`, `infantry` and `units` are valid values. This is set to `none` by default as inclusion of all object types can be performance-heavy. - `DetonateOnAllMapObjects.AffectHouses` is used to filter which houses targets can belong to be considered valid and must be set to a valid value other than `none` for this feature to work. Only applicable if the house that fired the projectile is known. This is set to `none` by default as inclusion of all houses can be performance-heavy. - `DetonateOnAllMapObjects.AffectTypes` can be used to list specific TechnoTypes to be considered as valid targets. If any valid TechnoTypes are listed, then only matching objects will be targeted. Note that `DetonateOnAllMapObjects.AffectTargets` and `DetonateOnAllMapObjects.AffectHouses` take priority over this setting. @@ -1415,6 +1437,7 @@ While this feature can provide better performance than a large `CellSpread` valu ```ini [SOMEWARHEAD] ; Warhead DetonateOnAllMapObjects=false ; boolean +DetonateOnAllMapObjects.Full=true ; boolean DetonateOnAllMapObjects.AffectTargets=none ; list of Affected Target Enumeration (none|aircraft|buildings|infantry|units|all) DetonateOnAllMapObjects.AffectHouses=none ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) DetonateOnAllMapObjects.AffectTypes= ; list of TechnoType names @@ -1577,6 +1600,7 @@ Burst.FireWithinSequence=false ; boolean - It is now possible to have same weapon 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. - Note that the listed Warheads must be listed in `[Warheads]` for them to work. In `rulesmd.ini`: @@ -1585,6 +1609,7 @@ In `rulesmd.ini`: ExtraWarheads= ; list of WarheadTypes ExtraWarheads.DamageOverrides= ; list of integers ExtraWarheads.DetonationChances= ; list of floating-point values (percentage or absolute) +ExtraWarheads.FullDetonation= ; list of booleans ``` ### Feedback weapon diff --git a/docs/User-Interface.md b/docs/User-Interface.md index 5adfc92b77..6a900a6545 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -277,14 +277,23 @@ SelectionFlashDuration=0 ; integer, number of frames - These vanilla CSF entries will be used: `TXT_SAVING_GAME`, `TXT_GAME_WAS_SAVED` and `TXT_ERROR_SAVING_GAME`. - The save should be looks like `Allied Mission 25: Esther's Money - QuickSaved` +### `[ ]` Save Variables + +- Save local & global variables to an INI file. See [this](Miscellanous.html#save-variables-to-file) for details. +- For localization add `TXT_SAVE_VARIABLES` and `TXT_SAVE_VARIABLES_DESC` into your `.csf` file. + ### `[ ]` Toggle Designator Range - Switches on/off super weapon designator range indicator. See [this](#show-designator--inhibitor-range) for details. - For localization add `TXT_DESIGNATOR_RANGE` and `TXT_DESIGNATOR_RANGE_DESC` into your `.csf` file. ### `[ ]` Toggle Digital Display -- Switches on/off [digital gisplay types](#digital-display). +- Switches on/off [digital display types](#digital-display). - For localization add `TXT_DIGITAL_DISPLAY` and `TXT_DIGITAL_DISPLAY_DESC` into your `.csf` file. +### `[ ]` Toggle Frame By Frame Mode +- Switches on/off [frame by frame mode](Miscellanous.html#frame-step-in). +- For localization add `TXT_FRAME_BY_FRAME` and `TXT_FRAME_BY_FRAME_DESC` into your `.csf` file. + ## Loading screen - PCX files can now be used as loadscreen images. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index c16fc38ef2..9ed71e3024 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -10,6 +10,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] ### From vanilla +- `PowersUpNAnim` is now used instead of the upgrade building's image file for upgrade animation if set. Note that displaying a damaged version will still require setting `PowerUp1DamagedAnim` explicitly in all cases, as the fallback to upgrade building image does not extend to it, nor would it be safe to add. - `[CrateRules]` -> `FreeMCV` now controls whether or not player is forced to receive unit from `[General]` -> `BaseUnit` from goodie crate if they own no buildings or any existing `BaseUnit` vehicles and own more than `[CrateRules]` -> `FreeMCV.CreditsThreshold` (defaults to 1500) credits. - Translucent RLE SHPs will now be drawn using a more precise and performant algorithm that has no green tint and banding. Can be disabled with `rulesmd.ini->[General]->FixTransparencyBlitters=no`. - Iron Curtain status is now preserved by default when converting between TechnoTypes via `DeploysInto`/`UndeploysInto`. This behavior can be turned off per-TechnoType and global basis using `[SOMETECHNOTYPE]/[CombatDamage]->IronCurtain.KeptOnDeploy=no`. @@ -20,6 +21,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] #### From post-0.3 devbuilds +- Affected target enum (`CanTarget`, `Crit.Affects` et al) now considers buildings considered vehicles (`ConsideredVehicle=true` or not set in conjunction with `UndeploysInto` & 1x1 foundation) as units instead of buildings. - If `CreateUnit.AlwaysSpawnOnGround` is set to false, jumpjet vehicles created will now automatically take off instead of staying on ground. Set to true to force spawn on ground. - Digital display `Offset` and `Offset.ShieldDelta` Y-axis coordinates now work in inverted fashion (negative goes up, positive goes down) to be consistent with how pixel offsets work elsewhere in the game. - Phobos Warhead effects combined with `CellSpread` now correctly apply to buildings if any of the foundation cells are hit instead of only the top-left most cell (cell #0). @@ -438,6 +440,16 @@ New: - Option for Warhead damage to penetrate Iron Curtain or Force Shield (by Starkku) - Option for Warhead to remove all shield types at once (by Starkku) - Allow customizing voxel light source position (by Kerbiter, Morton, based on knowledge of thomassnedon) +- Option to fix voxel light source being offset and incorrectly tilting on slopes (by Kerbiter) +- AI superweapon delay timer customization (by Starkku) +- Disabling `MultipleFactory` bonus from specific BuildingType (by Starkku) +- Customizable ChronoSphere teleport delays for units (by Starkku) +- Allowed and disallowed types for `FactoryPlant` (by Starkku) +- Customizable damage & 'crumbling' (destruction) frames for TerrainTypes (by Starkku) +- Custom object palettes for TerrainTypes (by Starkku) +- Forbidding parallel AI queues for specific TechnoTypes (by Starkku) +- Nonprovocative Warheads (by Starkku) +- Option to restore `PowerSurplus` setting for AI (by Starkku) - Minimum & maximum values for received damage and speed (by Ollerus) Vanilla fixes: @@ -507,6 +519,10 @@ Vanilla fixes: - Units with `Sensors=true` will no longer reveal ally buildings (by Starkku) - Air units are now reliably included by target scan with large range and Warhead detonation by large `CellSpread` (by Starkku) - Weapons with `AA=true` Projectile can now correctly fire at air units when both firer and target are over a bridge (by Starkku) +- 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) Phobos fixes: - Fixed a few errors of calling for superweapon launch by `LaunchSW` or building infiltration (by Trsdy) @@ -545,8 +561,12 @@ Phobos fixes: - Fixed radiation site damage not taking the radiation level reduction into accord (by Starkku) - Correctly update laser trail position while techno is cloaked even if trail is not drawn (by Starkku) - Fixed `Shield.Respawn.Amount` not defaulting to shield type default if not set (by Starkku) +- 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) 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) - All forms of type conversion (including Ares') now correctly update the warp-in delay if unit with teleport `Locomotor` was converted while the delay was active (by Starkku) - All forms of type conversion (including Ares') now correctly update `MoveSound` if a moving unit has their type changed (by Starkku) - All forms of type conversion (including Ares') now correctly update `OpenTopped` state of passengers in transport that is converted (by Starkku) diff --git a/src/Commands/FrameByFrame.cpp b/src/Commands/FrameByFrame.cpp index b53ba32167..ce73765a7b 100644 --- a/src/Commands/FrameByFrame.cpp +++ b/src/Commands/FrameByFrame.cpp @@ -26,7 +26,7 @@ const wchar_t* FrameByFrameCommandClass::GetUICategory() const const wchar_t* FrameByFrameCommandClass::GetUIDescription() const { - return GeneralUtils::LoadStringUnlessMissing("TXT_DISPLAY_DAMAGE_DESC", L"Enter or exit frame by frame mode."); + return GeneralUtils::LoadStringUnlessMissing("TXT_FRAME_BY_FRAME_DESC", L"Enter or exit frame by frame mode."); } void FrameByFrameCommandClass::Execute(WWKey eInput) const diff --git a/src/Commands/ObjectInfo.cpp b/src/Commands/ObjectInfo.cpp index c3569379f7..afd2dfb7ff 100644 --- a/src/Commands/ObjectInfo.cpp +++ b/src/Commands/ObjectInfo.cpp @@ -56,14 +56,30 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const buffer[0] = 0; }; - auto printFoots = [&append, &display](FootClass* pFoot) + auto getTargetInfo = [](TechnoClass* pCurrent, AbstractClass* pTarget, int& distance, const char*& ID, CellStruct& mapCoords) + { + if (auto const pObject = abstract_cast(pTarget)) + { + mapCoords = pObject->GetMapCoords(); + ID = pObject->GetType()->get_ID(); + } + else if (auto const pCell = abstract_cast(pTarget)) + { + mapCoords = pCell->MapCoords; + ID = "Cell"; + } + + distance = pCurrent->DistanceFrom(pTarget) / Unsorted::LeptonsPerCell; + }; + + auto printFoots = [&append, &display, &getTargetInfo](FootClass* pFoot) { append("[Phobos] Dump ObjectInfo runs.\n"); auto pType = pFoot->GetTechnoType(); append("ID = %s, ", pType->ID); append("Owner = %s (%s), ", pFoot->Owner->get_ID(), pFoot->Owner->PlainName); append("Location = (%d, %d), ", pFoot->GetMapCoords().X, pFoot->GetMapCoords().Y); - append("Current Mission = %d (%s)\n", pFoot->CurrentMission, MissionControlClass::FindName(pFoot->CurrentMission)); + append("Mission = %d (%s), Status = %d\n", pFoot->CurrentMission, MissionControlClass::FindName(pFoot->CurrentMission), pFoot->MissionStatus); if (pFoot->BelongsToATeam()) { @@ -110,19 +126,18 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const { auto mapCoords = CellStruct::Empty; auto ID = "N/A"; + int distance = 0; + getTargetInfo(pFoot, pFoot->Target, distance, ID, mapCoords); + append("Target = %s, Distance = %d, Location = (%d, %d)\n", ID, distance, mapCoords.X, mapCoords.Y); + } - if (auto const pObject = abstract_cast(pFoot->Target)) - { - mapCoords = pObject->GetMapCoords(); - ID = pObject->GetType()->get_ID(); - } - else if (auto const pCell = abstract_cast(pFoot->Target)) - { - mapCoords = pCell->MapCoords; - ID = "Cell"; - } - - append("Target = %s, Distance = %d, Location = (%d, %d)\n", ID, (pFoot->DistanceFrom(pFoot->Target) / Unsorted::LeptonsPerCell), mapCoords.X, mapCoords.Y); + if (pFoot->Destination) + { + auto mapCoords = CellStruct::Empty; + auto ID = "N/A"; + int distance = 0; + getTargetInfo(pFoot, pFoot->Destination, distance, ID, mapCoords); + append("Destination = %s, Distance = %d, Location = (%d, %d)\n", ID, distance, mapCoords.X, mapCoords.Y); } append("Current HP = (%d / %d)", pFoot->Health, pType->Strength); @@ -140,13 +155,14 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const display(); }; - auto printBuilding = [&append, &display](BuildingClass* pBuilding) + auto printBuilding = [&append, &display, &getTargetInfo](BuildingClass* pBuilding) { append("[Phobos] Dump ObjectInfo runs.\n"); auto pType = pBuilding->Type; append("ID = %s, ", pType->ID); append("Owner = %s (%s), ", pBuilding->Owner->get_ID(), pBuilding->Owner->PlainName); - append("Location = (%d, %d)\n", pBuilding->GetMapCoords().X, pBuilding->GetMapCoords().Y); + append("Location = (%d, %d), ", pBuilding->GetMapCoords().X, pBuilding->GetMapCoords().Y); + append("Mission = %d (%s), Status = %d\n", pBuilding->CurrentMission, MissionControlClass::FindName(pBuilding->CurrentMission), pBuilding->MissionStatus); if (pBuilding->Factory && pBuilding->Factory->Object) { @@ -188,19 +204,9 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const { auto mapCoords = CellStruct::Empty; auto ID = "N/A"; - - if (auto const pObject = abstract_cast(pBuilding->Target)) - { - mapCoords = pObject->GetMapCoords(); - ID = pObject->GetType()->get_ID(); - } - else if (auto const pCell = abstract_cast(pBuilding->Target)) - { - mapCoords = pCell->MapCoords; - ID = "Cell"; - } - - append("Target = %s, Distance = %d, Location = (%d, %d)\n", ID, (pBuilding->DistanceFrom(pBuilding->Target) / Unsorted::LeptonsPerCell), mapCoords.X, mapCoords.Y); + int distance = 0; + getTargetInfo(pBuilding, pBuilding->Target, distance, ID, mapCoords); + append("Target = %s, Distance = %d, Location = (%d, %d)\n", ID, distance, mapCoords.X, mapCoords.Y); } append("Current HP = (%d / %d)\n", pBuilding->Health, pBuilding->Type->Strength); diff --git a/src/Ext/Anim/Hooks.AnimCreateUnit.cpp b/src/Ext/Anim/Hooks.AnimCreateUnit.cpp index 619d7d7095..8a2fc3d2d0 100644 --- a/src/Ext/Anim/Hooks.AnimCreateUnit.cpp +++ b/src/Ext/Anim/Hooks.AnimCreateUnit.cpp @@ -75,8 +75,14 @@ DEFINE_HOOK(0x424932, AnimClass_AI_CreateUnit_ActualAffects, 0x6) auto pCell = pThis->GetCell(); CoordStruct location = pThis->GetCoords(); + auto origLocation = location; - pThis->UnmarkAllOccupationBits(location); + if (pCell) + origLocation = pCell->GetCoordsWithBridge(); + else + origLocation.Z = MapClass::Instance->GetCellFloorHeight(location); + + pThis->UnmarkAllOccupationBits(origLocation); bool allowBridges = GroundType::Array[static_cast(LandType::Clear)].Cost[static_cast(unit->SpeedType)] > 0.0; bool isBridge = allowBridges && pCell->ContainsBridge(); @@ -95,9 +101,14 @@ DEFINE_HOOK(0x424932, AnimClass_AI_CreateUnit_ActualAffects, 0x6) { isBridge = allowBridges && pCell->ContainsBridge(); int bridgeZ = isBridge ? CellClass::BridgeHeight : 0; - int baseHeight = pTypeExt->CreateUnit_SpawnHeight.isset() ? pTypeExt->CreateUnit_SpawnHeight : pThis->GetCoords().Z; + int baseHeight = pThis->GetCoords().Z; int zCoord = pTypeExt->CreateUnit_AlwaysSpawnOnGround ? INT32_MIN : baseHeight; - location.Z = Math::max(MapClass::Instance->GetCellFloorHeight(location) + bridgeZ, zCoord); + int cellFloorHeight = MapClass::Instance->GetCellFloorHeight(location) + bridgeZ; + + if (!pTypeExt->CreateUnit_AlwaysSpawnOnGround && pTypeExt->CreateUnit_SpawnHeight.isset()) + location.Z = cellFloorHeight + pTypeExt->CreateUnit_SpawnHeight; + else + location.Z = Math::max(cellFloorHeight, zCoord); if (auto pTechno = static_cast(unit->CreateObject(decidedOwner))) { @@ -166,7 +177,9 @@ DEFINE_HOOK(0x424932, AnimClass_AI_CreateUnit_ActualAffects, 0x6) pJJLoco->IsMoving = true; pJJLoco->DestinationCoords = location; pJJLoco->CurrentHeight = pType->JumpjetHeight; - AircraftTrackerClass::Instance->Add(pTechno); + + if (!inAir) + AircraftTrackerClass::Instance->Add(pTechno); } else if (inAir) { diff --git a/src/Ext/Anim/Hooks.cpp b/src/Ext/Anim/Hooks.cpp index a70c75f513..1c7ecd4dd3 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -85,41 +85,55 @@ DEFINE_HOOK(0x42453E, AnimClass_AI_Damage, 0x6) return SkipDamage; TechnoClass* pInvoker = nullptr; - HouseClass* pInvokerHouse = nullptr; + HouseClass* pOwner = nullptr; if (pTypeExt->Damage_DealtByInvoker) { auto const pExt = AnimExt::ExtMap.Find(pThis); pInvoker = pExt->Invoker; + pOwner = pExt->InvokerHouse; if (!pInvoker) { pInvoker = pThis->OwnerObject ? abstract_cast(pThis->OwnerObject) : nullptr; - pInvokerHouse = !pInvoker ? pExt->InvokerHouse : nullptr; + + if (pInvoker) + pOwner = pInvoker->Owner; } } if (pTypeExt->Weapon) { - WeaponTypeExt::DetonateAt(pTypeExt->Weapon, pThis->GetCoords(), pInvoker, appliedDamage, pInvokerHouse); + WeaponTypeExt::DetonateAt(pTypeExt->Weapon, pThis->GetCoords(), pInvoker, appliedDamage, pOwner); } else { - auto pWarhead = pThis->Type->Warhead; - - if (!pWarhead) - pWarhead = strcmp(pThis->Type->get_ID(), "INVISO") ? RulesClass::Instance->FlameDamage2 : RulesClass::Instance->C4Warhead; - - auto pOwner = pInvoker ? pInvoker->Owner : nullptr; - if (!pOwner) { if (pThis->Owner) + { pOwner = pThis->Owner; + } + else if (pInvoker) + { + pOwner = pInvoker->Owner; + } else if (pThis->OwnerObject) + { pOwner = pThis->OwnerObject->GetOwningHouse(); + } + else if (pThis->IsBuildingAnim) + { + auto const pBuilding = AnimExt::ExtMap.Find(pThis)->ParentBuilding; + pOwner = pBuilding ? pBuilding->Owner : nullptr; + } } + auto pWarhead = pThis->Type->Warhead; + + if (!pWarhead) + pWarhead = strcmp(pThis->Type->get_ID(), "INVISO") ? RulesClass::Instance->FlameDamage2 : RulesClass::Instance->C4Warhead; + MapClass::DamageArea(pThis->GetCoords(), appliedDamage, pInvoker, pWarhead, true, pOwner); } @@ -213,7 +227,7 @@ DEFINE_HOOK(0x424CF1, AnimClass_Start_DetachedReport, 0x6) } // 0x422CD8 is in an alternate code path only used by anims with ID RING1, unused normally but covering it just because -DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_XDrawOffset, 0x6) +DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_XDrawOffset, 0x6) DEFINE_HOOK(0x423122, AnimClass_DrawIt_XDrawOffset, 0x6) { GET(AnimClass* const, pThis, ESI); diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index 5f52d26ab3..4b785fb181 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -360,6 +360,7 @@ void BuildingExt::ExtData::Serialize(T& Stm) .Process(this->CurrentAirFactory) .Process(this->AccumulatedIncome) .Process(this->CurrentLaserWeaponIndex) + .Process(this->PoweredUpToLevel) ; } diff --git a/src/Ext/Building/Body.h b/src/Ext/Building/Body.h index 33d13c1a5f..9b0b947ddd 100644 --- a/src/Ext/Building/Body.h +++ b/src/Ext/Building/Body.h @@ -36,6 +36,7 @@ class BuildingExt BuildingClass* CurrentAirFactory; int AccumulatedIncome; OptionalStruct CurrentLaserWeaponIndex; + int PoweredUpToLevel; // Distinct from UpgradeLevel, and set to highest PowersUpToLevel out of applied upgrades regardless of how many are currently applied to this building. ExtData(BuildingClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } @@ -48,6 +49,7 @@ class BuildingExt , CurrentAirFactory { nullptr } , AccumulatedIncome { 0 } , CurrentLaserWeaponIndex {} + , PoweredUpToLevel { 0 } { } void DisplayIncomeString(); diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index 1b14db540f..e8612abe93 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -114,10 +114,12 @@ DEFINE_HOOK(0x4401BB, BuildingClass_AI_PickWithFreeDocks, 0x6) GET(BuildingClass*, pBuilding, ESI); auto pRulesExt = RulesExt::Global(); - if (pRulesExt->AllowParallelAIQueues && !pRulesExt->ForbidParallelAIQueues_Aircraft) - return 0; - HouseClass* pOwner = pBuilding->Owner; + int index = pOwner->ProducingAircraftTypeIndex; + auto const pType = index >= 0 ? AircraftTypeClass::Array()->GetItem(index) : nullptr; + + if (pRulesExt->AllowParallelAIQueues && !pRulesExt->ForbidParallelAIQueues_Aircraft && (!pType || !TechnoTypeExt::ExtMap.Find(pType)->ForbidParallelAIQueues)) + return 0; if (pOwner->Type->MultiplayPassive || pOwner->IsCurrentPlayer() @@ -201,27 +203,56 @@ DEFINE_HOOK(0x4502F4, BuildingClass_Update_Factory_Phobos, 0x6) { enum { Skip = 0x4503CA }; + + TechnoTypeClass* pType = nullptr; + int index = -1; + switch (pThis->Type->Factory) { case AbstractType::BuildingType: if (pRulesExt->ForbidParallelAIQueues_Building) return Skip; + + index = pOwner->ProducingBuildingTypeIndex; + pType = index >= 0 ? BuildingTypeClass::Array()->GetItem(index) : nullptr; break; case AbstractType::InfantryType: if (pRulesExt->ForbidParallelAIQueues_Infantry) return Skip; + + index = pOwner->ProducingInfantryTypeIndex; + pType = index >= 0 ? InfantryTypeClass::Array()->GetItem(index) : nullptr; break; case AbstractType::AircraftType: if (pRulesExt->ForbidParallelAIQueues_Aircraft) return Skip; + + index = pOwner->ProducingAircraftTypeIndex; + pType = index >= 0 ? AircraftTypeClass::Array()->GetItem(index) : nullptr; break; case AbstractType::UnitType: if (pThis->Type->Naval ? pRulesExt->ForbidParallelAIQueues_Navy : pRulesExt->ForbidParallelAIQueues_Vehicle) return Skip; + + if (pThis->Type->Naval) + { + auto const pExt = HouseExt::ExtMap.Find(pOwner); + index = pExt->ProducingNavalUnitTypeIndex; + } + else + { + index = pOwner->ProducingUnitTypeIndex; + } + + pType = index >= 0 ? UnitTypeClass::Array()->GetItem(index) : nullptr; + break; default: break; } + + if (pType && TechnoTypeExt::ExtMap.Find(pType)->ForbidParallelAIQueues) + return Skip; } } @@ -248,31 +279,32 @@ DEFINE_HOOK(0x4CA07A, FactoryClass_AbandonProduction_Phobos, 0x8) return 0; auto const pOwnerExt = HouseExt::ExtMap.Find(pFactory->Owner); + bool forbid = TechnoTypeExt::ExtMap.Find(pTechno->GetTechnoType())->ForbidParallelAIQueues; switch (pTechno->WhatAmI()) { case AbstractType::Building: - if (pRulesExt->ForbidParallelAIQueues_Building) + if (pRulesExt->ForbidParallelAIQueues_Building || forbid) pOwnerExt->Factory_BuildingType = nullptr; break; case AbstractType::Unit: if (!pTechno->GetTechnoType()->Naval) { - if (pRulesExt->ForbidParallelAIQueues_Vehicle) + if (pRulesExt->ForbidParallelAIQueues_Vehicle || forbid) pOwnerExt->Factory_VehicleType = nullptr; } else { - if (pRulesExt->ForbidParallelAIQueues_Navy) + if (pRulesExt->ForbidParallelAIQueues_Navy || forbid) pOwnerExt->Factory_NavyType = nullptr; } break; case AbstractType::Infantry: - if (pRulesExt->ForbidParallelAIQueues_Infantry) + if (pRulesExt->ForbidParallelAIQueues_Infantry || forbid) pOwnerExt->Factory_InfantryType = nullptr; break; case AbstractType::Aircraft: - if (pRulesExt->ForbidParallelAIQueues_Aircraft) + if (pRulesExt->ForbidParallelAIQueues_Aircraft || forbid) pOwnerExt->Factory_AircraftType = nullptr; break; default: @@ -287,11 +319,11 @@ DEFINE_HOOK(0x444119, BuildingClass_KickOutUnit_UnitType_Phobos, 0x6) GET(UnitClass*, pUnit, EDI); GET(BuildingClass*, pFactory, ESI); - auto pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner); + auto const pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner); - if (pUnit->Type->Naval) + if (pUnit->Type->Naval && pHouseExt->Factory_NavyType == pFactory) pHouseExt->Factory_NavyType = nullptr; - else + else if (pHouseExt->Factory_VehicleType == pFactory) pHouseExt->Factory_VehicleType = nullptr; return 0; @@ -299,27 +331,36 @@ DEFINE_HOOK(0x444119, BuildingClass_KickOutUnit_UnitType_Phobos, 0x6) DEFINE_HOOK(0x444131, BuildingClass_KickOutUnit_InfantryType_Phobos, 0x6) { - GET(HouseClass*, pHouse, EAX); + GET(BuildingClass*, pFactory, ESI); + + auto const pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner); - HouseExt::ExtMap.Find(pHouse)->Factory_InfantryType = nullptr; + if (pHouseExt->Factory_InfantryType == pFactory) + pHouseExt->Factory_InfantryType = nullptr; return 0; } DEFINE_HOOK(0x44531F, BuildingClass_KickOutUnit_BuildingType_Phobos, 0xA) { - GET(HouseClass*, pHouse, EAX); + GET(BuildingClass*, pFactory, ESI); + + auto const pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner); - HouseExt::ExtMap.Find(pHouse)->Factory_BuildingType = nullptr; + if (pHouseExt->Factory_BuildingType == pFactory) + pHouseExt->Factory_BuildingType = nullptr; return 0; } DEFINE_HOOK(0x443CCA, BuildingClass_KickOutUnit_AircraftType_Phobos, 0xA) { - GET(HouseClass*, pHouse, EDX); + GET(BuildingClass*, pFactory, ESI); + + auto const pHouseExt = HouseExt::ExtMap.Find(pFactory->Owner); - HouseExt::ExtMap.Find(pHouse)->Factory_AircraftType = nullptr; + if (pHouseExt->Factory_AircraftType == pFactory) + pHouseExt->Factory_AircraftType = nullptr; return 0; } @@ -396,3 +437,70 @@ DEFINE_HOOK(0x4511D6, BuildingClass_AnimationAI_SellBuildup, 0x7) return pTypeExt->SellBuildupLength == pThis->Animation.Value ? Continue : Skip; } + +#pragma region FactoryPlant + +DEFINE_HOOK(0x441501, BuildingClass_Unlimbo_FactoryPlant, 0x6) +{ + enum { Skip = 0x441553 }; + + GET(BuildingClass*, pThis, ESI); + + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->FactoryPlant_AllowTypes.size() > 0 || pTypeExt->FactoryPlant_DisallowTypes.size() > 0) + { + auto const pHouseExt = HouseExt::ExtMap.Find(pThis->Owner); + pHouseExt->RestrictedFactoryPlants.push_back(pThis); + + return Skip; + } + + return 0; +} + +DEFINE_HOOK(0x448A31, BuildingClass_Captured_FactoryPlant1, 0x6) +{ + enum { Skip = 0x448A78 }; + + GET(BuildingClass*, pThis, ESI); + + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->FactoryPlant_AllowTypes.size() > 0 || pTypeExt->FactoryPlant_DisallowTypes.size() > 0) + { + auto const pHouseExt = HouseExt::ExtMap.Find(pThis->Owner); + + if (!pHouseExt->RestrictedFactoryPlants.empty()) + { + auto& vec = pHouseExt->RestrictedFactoryPlants; + vec.erase(std::remove(vec.begin(), vec.end(), pThis), vec.end()); + } + + return Skip; + } + + return 0; +} + +DEFINE_HOOK(0x449149, BuildingClass_Captured_FactoryPlant2, 0x6) +{ + enum { Skip = 0x449197 }; + + GET(BuildingClass*, pThis, ESI); + GET(HouseClass*, pNewOwner, EBP); + + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->FactoryPlant_AllowTypes.size() > 0 || pTypeExt->FactoryPlant_DisallowTypes.size() > 0) + { + auto const pHouseExt = HouseExt::ExtMap.Find(pNewOwner); + pHouseExt->RestrictedFactoryPlants.push_back(pThis); + + return Skip; + } + + return 0; +} + +#pragma endregion diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index 6863d35cf6..ddc1ba01e9 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -131,6 +131,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->CanC4_AllowZeroDamage.Read(exINI, pSection, "CanC4.AllowZeroDamage"); this->InitialStrength_Cloning.Read(exINI, pSection, "InitialStrength.Cloning"); + this->ExcludeFromMultipleFactoryBonus.Read(exINI, pSection, "ExcludeFromMultipleFactoryBonus"); this->Grinding_AllowAllies.Read(exINI, pSection, "Grinding.AllowAllies"); this->Grinding_AllowOwner.Read(exINI, pSection, "Grinding.AllowOwner"); @@ -148,6 +149,9 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ConsideredVehicle.Read(exINI, pSection, "ConsideredVehicle"); this->SellBuildupLength.Read(exINI, pSection, "SellBuildupLength"); + this->FactoryPlant_AllowTypes.Read(exINI, pSection, "FactoryPlant.AllowTypes"); + this->FactoryPlant_DisallowTypes.Read(exINI, pSection, "FactoryPlant.DisallowTypes"); + if (pThis->NumberOfDocks > 0) { this->AircraftDockingDirs.clear(); @@ -233,6 +237,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->AllowAirstrike) .Process(this->CanC4_AllowZeroDamage) .Process(this->InitialStrength_Cloning) + .Process(this->ExcludeFromMultipleFactoryBonus) .Process(this->Refinery_UseStorage) .Process(this->Grinding_AllowAllies) .Process(this->Grinding_AllowOwner) @@ -259,6 +264,8 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->ZShapePointMove_OnBuildup) .Process(this->SellBuildupLength) .Process(this->AircraftDockingDirs) + .Process(this->FactoryPlant_AllowTypes) + .Process(this->FactoryPlant_DisallowTypes) ; } diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index d31cacc10a..5d702b14c1 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -32,6 +32,7 @@ class BuildingTypeExt Valueable CanC4_AllowZeroDamage; Valueable Refinery_UseStorage; Valueable> InitialStrength_Cloning; + Valueable ExcludeFromMultipleFactoryBonus; ValueableIdx Grinding_Sound; Valueable Grinding_Weapon; @@ -64,6 +65,9 @@ class BuildingTypeExt std::vector> AircraftDockingDirs; + ValueableVector FactoryPlant_AllowTypes; + ValueableVector FactoryPlant_DisallowTypes; + ExtData(BuildingTypeClass* OwnerObject) : Extension(OwnerObject) , PowersUp_Owner { AffectedHouse::Owner } , PowersUp_Buildings {} @@ -75,6 +79,7 @@ class BuildingTypeExt , AllowAirstrike {} , CanC4_AllowZeroDamage { false } , InitialStrength_Cloning { { 1.0, 0.0 } } + , ExcludeFromMultipleFactoryBonus { false } , Refinery_UseStorage { false } , Grinding_AllowAllies { false } , Grinding_AllowOwner { true } @@ -101,6 +106,8 @@ class BuildingTypeExt , ZShapePointMove_OnBuildup { false } , SellBuildupLength { 23 } , AircraftDockingDirs {} + , FactoryPlant_AllowTypes {} + , FactoryPlant_DisallowTypes {} { } // Ares 0.A functions diff --git a/src/Ext/BuildingType/Hooks.Upgrade.cpp b/src/Ext/BuildingType/Hooks.Upgrade.cpp index e895adbc78..000af33eb0 100644 --- a/src/Ext/BuildingType/Hooks.Upgrade.cpp +++ b/src/Ext/BuildingType/Hooks.Upgrade.cpp @@ -135,28 +135,97 @@ DEFINE_HOOK(0x4F7877, HouseClass_CanBuild_UpgradesInteraction_WithoutAres, 0x5) #pragma region UpgradeAnimLogic -// Parse Powered(Light|Effect|Special) keys for upgrade anims. -DEFINE_HOOK(0x4648B3, BuildingTypeClass_ReadINI_PowerUpAnims, 0x5) +// Always parse all info for PowerUp anims if building can have even one upgrade, including power settings. +DEFINE_HOOK(0x464749, BuildingTypeClass_ReadINI_PowerUpAnims, 0x6) { + enum { SkipGameCode = 0x46492E }; + GET(BuildingTypeClass*, pThis, EBP); - GET(int, index, EBX); auto const pINI = &CCINIClass::INI_Art(); - auto const animData = &pThis->BuildingAnim[index - 1]; + int index = 1; char buffer[0x20]; - sprintf_s(buffer, "PowerUp%01dPowered", index); - animData->Powered = pINI->ReadBool(pThis->ImageFile, buffer, animData->Powered); + while (index - 1 < 3) + { + auto const animData = &pThis->BuildingAnim[index - 1]; + + sprintf_s(buffer, "PowerUp%01dAnim", index); + pINI->GetString(pThis->ImageFile, buffer, animData->Anim); + + sprintf_s(buffer, "PowerUp%01dDamagedAnim", index); + pINI->GetString(pThis->ImageFile, buffer, animData->Damaged); + + sprintf_s(buffer, "PowerUp%01dLocXX", index); + animData->Position.X = pINI->ReadInteger(pThis->ImageFile, buffer, animData->Position.X); + + sprintf_s(buffer, "PowerUp%01dLocYY", index); + animData->Position.Y = pINI->ReadInteger(pThis->ImageFile, buffer, animData->Position.Y); + + sprintf_s(buffer, "PowerUp%01dLocZZ", index); + animData->ZAdjust = pINI->ReadInteger(pThis->ImageFile, buffer, animData->ZAdjust); + + sprintf_s(buffer, "PowerUp%01dYSort", index); + animData->YSort = pINI->ReadInteger(pThis->ImageFile, buffer, animData->YSort); + + sprintf_s(buffer, "PowerUp%01dPowered", index); + animData->Powered = pINI->ReadBool(pThis->ImageFile, buffer, animData->Powered); + + sprintf_s(buffer, "PowerUp%01dPoweredLight", index); + animData->PoweredLight = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredLight); + + sprintf_s(buffer, "PowerUp%01dPoweredEffect", index); + animData->PoweredEffect = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredEffect); + + sprintf_s(buffer, "PowerUp%01dPoweredSpecial", index); + animData->PoweredSpecial = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredSpecial); + + index++; + } + + return SkipGameCode; +} + +DEFINE_HOOK(0x440988, BuildingClass_Unlimbo_UpgradeAnims, 0x7) +{ + enum { SkipGameCode = 0x4409C7 }; + + GET(BuildingClass*, pThis, ESI); + GET(BuildingClass*, pTarget, EDI); + + auto const pTargetExt = BuildingExt::ExtMap.Find(pTarget); + pTargetExt->PoweredUpToLevel = pTarget->UpgradeLevel + 1; + int animIndex = pTarget->UpgradeLevel; + + if (pThis->Type->PowersUpToLevel > 0) + { + pTargetExt->PoweredUpToLevel = Math::max(pThis->Type->PowersUpToLevel, pTargetExt->PoweredUpToLevel); + animIndex = pTargetExt->PoweredUpToLevel - 1; + } + + auto const animData = &pTarget->Type->BuildingAnim[animIndex]; + + // Only copy image name to BuildingType anim struct if it is not already set. + if (!GeneralUtils::IsValidString(animData->Anim)) + strncpy(animData->Anim, pThis->Type->ImageFile, 16u); + + return SkipGameCode; +} + +DEFINE_HOOK(0x451630, BuildingClass_CreateUpgradeAnims_AnimIndex, 0x7) +{ + enum { SkipGameCode = 0x451638 }; - sprintf_s(buffer, "PowerUp%01dPoweredLight", index); - animData->PoweredLight = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredLight); + GET(BuildingClass*, pThis, EBP); - sprintf_s(buffer, "PowerUp%01dPoweredEffect", index); - animData->PoweredEffect = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredEffect); + int animIndex = BuildingExt::ExtMap.Find(pThis)->PoweredUpToLevel - 1; - sprintf_s(buffer, "PowerUp%01dPoweredSpecial", index); - animData->PoweredSpecial = pINI->ReadBool(pThis->ImageFile, buffer, animData->PoweredSpecial); + if (animIndex) + { + R->EAX(animIndex); + return SkipGameCode; + } return 0; } @@ -168,9 +237,9 @@ static __forceinline bool AllowUpgradeAnim(BuildingClass* pBuilding, BuildingAni if (pType->Upgrades != 0 && anim >= BuildingAnimSlot::Upgrade1 && anim <= BuildingAnimSlot::Upgrade3 && !pBuilding->Anims[int(anim)]) { - int upgradeLevel = pBuilding->UpgradeLevel - 1; + int animIndex = BuildingExt::ExtMap.Find(pBuilding)->PoweredUpToLevel - 1; - if (upgradeLevel < 0 || (int)anim != upgradeLevel) + if (animIndex < 0 || (int)anim != animIndex) return false; auto const animData = pType->BuildingAnim[int(anim)]; diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index b2020675c9..af2065deda 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -53,84 +53,101 @@ DEFINE_HOOK(0x469A75, BulletClass_Logics_DamageHouse, 0x7) return 0; } +#pragma region DetonateOnAllMapObjects + +static __forceinline void TryDetonateFull(BulletClass* pThis, TechnoClass* pTechno, WarheadTypeExt::ExtData* pWHExt, HouseClass* pOwner) +{ + if (pWHExt->EligibleForFullMapDetonation(pTechno, pOwner)) + { + pThis->Target = pTechno; + pThis->Location = pTechno->GetCoords(); + pThis->Detonate(pTechno->GetCoords()); + } +} + +static __forceinline void TryDetonateDamageArea(BulletClass* pThis, TechnoClass* pTechno, WarheadTypeExt::ExtData* pWHExt, HouseClass* pOwner) +{ + if (pWHExt->EligibleForFullMapDetonation(pTechno, pOwner)) + { + int damage = (pThis->Health * pThis->DamageMultiplier) >> 8; + pWHExt->DamageAreaWithTarget(pTechno->GetCoords(), damage, pThis->Owner, pThis->WH, true, pOwner, pTechno); + } +} + DEFINE_HOOK(0x4690C1, BulletClass_Logics_DetonateOnAllMapObjects, 0x8) { enum { ReturnFromFunction = 0x46A2FB }; GET(BulletClass*, pThis, ESI); - if (auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH)) + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis->WH); + + if (pWHExt->DetonateOnAllMapObjects && !pWHExt->WasDetonatedOnAllMapObjects && + pWHExt->DetonateOnAllMapObjects_AffectTargets != AffectedTarget::None && + pWHExt->DetonateOnAllMapObjects_AffectHouses != AffectedHouse::None) { - if (pWHExt->DetonateOnAllMapObjects && !pWHExt->WasDetonatedOnAllMapObjects && - pWHExt->DetonateOnAllMapObjects_AffectTargets != AffectedTarget::None && - pWHExt->DetonateOnAllMapObjects_AffectHouses != AffectedHouse::None) + pWHExt->WasDetonatedOnAllMapObjects = true; + auto const originalLocation = pThis->Location; + auto const pOriginalTarget = pThis->Target; + auto const pExt = BulletExt::ExtMap.Find(pThis); + auto pOwner = pThis->Owner ? pThis->Owner->Owner : pExt->FirerHouse; + + auto copy_dvc = [](const DynamicVectorClass&dvc) { - pWHExt->WasDetonatedOnAllMapObjects = true; - auto const originalLocation = pThis->Location; - auto const pOriginalTarget = pThis->Target; - auto const pExt = BulletExt::ExtMap.Find(pThis); - auto pOwner = pThis->Owner ? pThis->Owner->Owner : pExt->FirerHouse; + std::vector vec(dvc.Count); + std::copy(dvc.begin(), dvc.end(), vec.begin()); + return vec; + }; - auto tryDetonate = [pThis, pWHExt, pOwner](TechnoClass* pTechno) - { - if (pWHExt->EligibleForFullMapDetonation(pTechno, pOwner)) - { - pThis->Target = pTechno; - pThis->Location = pTechno->GetCoords(); - pThis->Detonate(pTechno->GetCoords()); - } - }; + void (*tryDetonate)(BulletClass*, TechnoClass*, WarheadTypeExt::ExtData*, HouseClass*) = TryDetonateFull; - auto copy_dvc = [](const DynamicVectorClass& dvc) - { - std::vector vec(dvc.Count); - std::copy(dvc.begin(), dvc.end(), vec.begin()); - return vec; - }; + if (!pWHExt->DetonateOnAllMapObjects_Full) + tryDetonate = TryDetonateDamageArea; - if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Aircraft) != AffectedTarget::None) - { - auto const aircraft = copy_dvc(*AircraftClass::Array); + if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Aircraft) != AffectedTarget::None) + { + auto const aircraft = copy_dvc(*AircraftClass::Array); - for (auto pAircraft : aircraft) - tryDetonate(pAircraft); - } + for (auto pAircraft : aircraft) + tryDetonate(pThis, pAircraft, pWHExt, pOwner); + } - if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Building) != AffectedTarget::None) - { - auto const buildings = copy_dvc(*BuildingClass::Array); + if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Building) != AffectedTarget::None) + { + auto const buildings = copy_dvc(*BuildingClass::Array); - for (auto pBuilding : buildings) - tryDetonate(pBuilding); - } + for (auto pBuilding : buildings) + tryDetonate(pThis, pBuilding, pWHExt, pOwner); + } - if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Infantry) != AffectedTarget::None) - { - auto const infantry = copy_dvc(*InfantryClass::Array); + if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Infantry) != AffectedTarget::None) + { + auto const infantry = copy_dvc(*InfantryClass::Array); - for (auto pInf : infantry) - tryDetonate(pInf); - } + for (auto pInf : infantry) + tryDetonate(pThis, pInf, pWHExt, pOwner); + } - if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Unit) != AffectedTarget::None) - { - auto const units = copy_dvc(*UnitClass::Array); + if ((pWHExt->DetonateOnAllMapObjects_AffectTargets & AffectedTarget::Unit) != AffectedTarget::None) + { + auto const units = copy_dvc(*UnitClass::Array); - for (auto const pUnit : units) - tryDetonate(pUnit); - } + for (auto const pUnit : units) + tryDetonate(pThis, pUnit, pWHExt, pOwner); + } - pThis->Target = pOriginalTarget; - pThis->Location = originalLocation; - pWHExt->WasDetonatedOnAllMapObjects = false; + pThis->Target = pOriginalTarget; + pThis->Location = originalLocation; + pWHExt->WasDetonatedOnAllMapObjects = false; - return ReturnFromFunction; - } + return ReturnFromFunction; } return 0; } +#pragma endregion + DEFINE_HOOK(0x469D1A, BulletClass_Logics_Debris_Checks, 0x6) { enum { SkipGameCode = 0x469EBA, SetDebrisCount = 0x469D36 }; @@ -270,8 +287,7 @@ DEFINE_HOOK(0x469C46, BulletClass_Logics_DamageAnimSelected, 0x8) return SkipGameCode; } -DEFINE_HOOK_AGAIN(0x46A2FB, BulletClass_Logics_Extras, 0x5) -DEFINE_HOOK(0x46A290, BulletClass_Logics_Extras, 0x5) +DEFINE_HOOK(0x469AA4, BulletClass_Logics_Extras, 0x5) { GET(BulletClass*, pThis, ESI); GET_BASE(CoordStruct*, coords, 0x8); @@ -302,8 +318,26 @@ DEFINE_HOOK(0x46A290, BulletClass_Logics_Extras, 0x5) if (size > 0) detonate = pWeaponExt->ExtraWarheads_DetonationChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); + bool isFull = true; + size = pWeaponExt->ExtraWarheads_FullDetonation.size(); + + if (size > i) + isFull = pWeaponExt->ExtraWarheads_FullDetonation[i]; + if (size > 0) + isFull = pWeaponExt->ExtraWarheads_FullDetonation[size - 1]; + if (detonate) - WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner, pThis->Target); + { + if (isFull) + { + WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner, pThis->Target); + } + else + { + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + pWHExt->DamageAreaWithTarget(*coords, damage, pThis->Owner, pWH, true, pOwner, abstract_cast(pThis->Target)); + } + } } } @@ -319,12 +353,14 @@ DEFINE_HOOK(0x46A290, BulletClass_Logics_Extras, 0x5) { pBullet->WeaponType = pWeapon; auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); - pBulletExt->FirerHouse = BulletExt::ExtMap.Find(pThis)->FirerHouse; + pBulletExt->FirerHouse = pBulletExt->FirerHouse; pBullet->MoveTo(pThis->Location, BulletVelocity::Empty); } } } + WarheadTypeExt::ExtMap.Find(pThis->WH)->InDamageArea = true; + return 0; } diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index 749183d883..67cf9d0c54 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -494,6 +494,113 @@ int HouseExt::ExtData::CountOwnedPresentAndLimboed(TechnoTypeClass* pTechnoType) return count; } +void HouseExt::ExtData::UpdateNonMFBFactoryCounts(AbstractType rtti, bool remove, bool isNaval) +{ + int* count = nullptr; + + switch (rtti) + { + case AbstractType::Aircraft: + case AbstractType::AircraftType: + count = &this->NumAirpads_NonMFB; + break; + case AbstractType::Building: + case AbstractType::BuildingType: + count = &this->NumConYards_NonMFB; + break; + case AbstractType::Infantry: + case AbstractType::InfantryType: + count = &this->NumBarracks_NonMFB; + break; + case AbstractType::Unit: + case AbstractType::UnitType: + count = isNaval ? &this->NumShipyards_NonMFB : &this->NumWarFactories_NonMFB; + break; + default: + break; + } + + if (count) + *count += remove ? -1 : 1; +} + +int HouseExt::ExtData::GetFactoryCountWithoutNonMFB(AbstractType rtti, bool isNaval) +{ + auto const pThis = this->OwnerObject(); + int count = 0; + + switch (rtti) + { + case AbstractType::Aircraft: + case AbstractType::AircraftType: + count = pThis->NumAirpads - this->NumAirpads_NonMFB; + break; + case AbstractType::Building: + case AbstractType::BuildingType: + count = pThis->NumConYards - this->NumConYards_NonMFB; + break; + case AbstractType::Infantry: + case AbstractType::InfantryType: + count = pThis->NumBarracks - this->NumBarracks_NonMFB; + break; + case AbstractType::Unit: + case AbstractType::UnitType: + if (isNaval) + count = pThis->NumShipyards - this->NumShipyards_NonMFB; + else + count = pThis->NumWarFactories - this->NumWarFactories_NonMFB; + break; + default: + break; + } + + return Math::max(count, 0); +} + +float HouseExt::ExtData::GetRestrictedFactoryPlantMult(TechnoTypeClass* pTechnoType) const +{ + float mult = 1.0; + auto const pTechnoTypeExt = TechnoTypeExt::ExtMap.Find(pTechnoType); + + for (auto const pBuilding : this->RestrictedFactoryPlants) + { + auto const pTypeExt = BuildingTypeExt::ExtMap.Find(pBuilding->Type); + + if (pTypeExt->FactoryPlant_AllowTypes.size() > 0 && !pTypeExt->FactoryPlant_AllowTypes.Contains(pTechnoType)) + continue; + + if (pTypeExt->FactoryPlant_DisallowTypes.size() > 0 && pTypeExt->FactoryPlant_DisallowTypes.Contains(pTechnoType)) + continue; + + float currentMult = 1.0f; + + switch (pTechnoType->WhatAmI()) + { + case AbstractType::BuildingType: + if (((BuildingTypeClass*)pTechnoType)->BuildCat == BuildCat::Combat) + currentMult -= pBuilding->Type->DefensesCostBonus; + else + currentMult -= pBuilding->Type->BuildingsCostBonus; + break; + case AbstractType::AircraftType: + currentMult -= pBuilding->Type->AircraftCostBonus; + break; + case AbstractType::InfantryType: + currentMult -= pBuilding->Type->InfantryCostBonus; + break; + case AbstractType::UnitType: + currentMult -= pBuilding->Type->UnitsCostBonus; + break; + default: + break; + } + + mult *= (1.0f - currentMult * pTechnoTypeExt->FactoryPlant_Multiplier); + } + + return mult; +} + void HouseExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) { const char* pSection = this->OwnerObject()->PlainName; @@ -529,9 +636,16 @@ void HouseExt::ExtData::Serialize(T& Stm) .Process(this->Factory_VehicleType) .Process(this->Factory_NavyType) .Process(this->Factory_AircraftType) + .Process(this->AISuperWeaponDelayTimer) .Process(this->RepairBaseNodes) + .Process(this->RestrictedFactoryPlants) .Process(this->LastBuiltNavalVehicleType) .Process(this->ProducingNavalUnitTypeIndex) + .Process(this->NumAirpads_NonMFB) + .Process(this->NumBarracks_NonMFB) + .Process(this->NumWarFactories_NonMFB) + .Process(this->NumConYards_NonMFB) + .Process(this->NumShipyards_NonMFB) ; } @@ -567,11 +681,21 @@ void HouseExt::ExtData::InvalidatePointer(void* ptr, bool bRemoved) AnnounceInvalidPointer(Factory_NavyType, ptr); AnnounceInvalidPointer(Factory_AircraftType, ptr); - if (!OwnedLimboDeliveredBuildings.empty() && ptr != nullptr) + if (ptr != nullptr) { - auto& vec = this->OwnedLimboDeliveredBuildings; - vec.erase(std::remove(vec.begin(), vec.end(), reinterpret_cast(ptr)), vec.end()); + if (!OwnedLimboDeliveredBuildings.empty()) + { + auto& vec = this->OwnedLimboDeliveredBuildings; + vec.erase(std::remove(vec.begin(), vec.end(), reinterpret_cast(ptr)), vec.end()); + } + + if (!RestrictedFactoryPlants.empty()) + { + auto& vec = this->RestrictedFactoryPlants; + vec.erase(std::remove(vec.begin(), vec.end(), reinterpret_cast(ptr)), vec.end()); + } } + } // ============================= @@ -591,6 +715,10 @@ DEFINE_HOOK(0x4F6532, HouseClass_CTOR, 0x5) GET(HouseClass*, pItem, EAX); HouseExt::ExtMap.TryAllocate(pItem); + + if (RulesExt::Global()->EnablePowerSurplus) + pItem->PowerSurplus = RulesClass::Instance->PowerSurplus; + return 0; } @@ -599,6 +727,7 @@ DEFINE_HOOK(0x4F7371, HouseClass_DTOR, 0x6) GET(HouseClass*, pItem, ESI); HouseExt::ExtMap.Remove(pItem); + return 0; } @@ -616,12 +745,14 @@ DEFINE_HOOK(0x503040, HouseClass_SaveLoad_Prefix, 0x5) DEFINE_HOOK(0x504069, HouseClass_Load_Suffix, 0x7) { HouseExt::ExtMap.LoadStatic(); + return 0; } DEFINE_HOOK(0x5046DE, HouseClass_Save_Suffix, 0x7) { HouseExt::ExtMap.SaveStatic(); + return 0; } @@ -654,7 +785,7 @@ CanBuildResult HouseExt::BuildLimitGroupCheck(const HouseClass* pThis, const Tec if (!pItemExt->BuildLimitGroup_ExtraLimit_Types.empty() && !pItemExt->BuildLimitGroup_ExtraLimit_Nums.empty()) { - for (size_t i = 0; i < pItemExt->BuildLimitGroup_ExtraLimit_Types.size(); i ++) + for (size_t i = 0; i < pItemExt->BuildLimitGroup_ExtraLimit_Types.size(); i++) { int count = 0; auto pTmpType = pItemExt->BuildLimitGroup_ExtraLimit_Types[i]; @@ -685,7 +816,7 @@ CanBuildResult HouseExt::BuildLimitGroupCheck(const HouseClass* pThis, const Tec { bool reachedLimit = false; - for (size_t i = 0; i < std::min(pItemExt->BuildLimitGroup_Types.size(), pItemExt->BuildLimitGroup_Nums.size()); i ++) + for (size_t i = 0; i < std::min(pItemExt->BuildLimitGroup_Types.size(), pItemExt->BuildLimitGroup_Nums.size()); i++) { TechnoTypeClass* pType = pItemExt->BuildLimitGroup_Types[i]; const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); @@ -738,7 +869,7 @@ CanBuildResult HouseExt::BuildLimitGroupCheck(const HouseClass* pThis, const Tec } else { - for (size_t i = 0; i < std::min(pItemExt->BuildLimitGroup_Types.size(), limits.size()); i ++) + for (size_t i = 0; i < std::min(pItemExt->BuildLimitGroup_Types.size(), limits.size()); i++) { TechnoTypeClass* pType = pItemExt->BuildLimitGroup_Types[i]; const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); @@ -794,7 +925,7 @@ void RemoveProduction(const HouseClass* pHouse, const TechnoTypeClass* pType, in if (num >= 0) queued = Math::min(num, queued); - for (int i = 0; i < queued; i ++) + for (int i = 0; i < queued; i++) { pFactory->RemoveOneFromQueue(pType); } @@ -812,7 +943,7 @@ bool HouseExt::ReachedBuildLimit(const HouseClass* pHouse, const TechnoTypeClass if (!pTypeExt->BuildLimitGroup_ExtraLimit_Types.empty() && !pTypeExt->BuildLimitGroup_ExtraLimit_Nums.empty()) { - for (size_t i = 0; i < pTypeExt->BuildLimitGroup_ExtraLimit_Types.size(); i ++) + for (size_t i = 0; i < pTypeExt->BuildLimitGroup_ExtraLimit_Types.size(); i++) { auto pTmpType = pTypeExt->BuildLimitGroup_ExtraLimit_Types[i]; const auto pBuildingType = abstract_cast(pTmpType); @@ -884,7 +1015,7 @@ bool HouseExt::ReachedBuildLimit(const HouseClass* pHouse, const TechnoTypeClass bool reached = true; bool realReached = true; - for (size_t i = 0; i < size; i ++) + for (size_t i = 0; i < size; i++) { TechnoTypeClass* pTmpType = pTypeExt->BuildLimitGroup_Types[i]; const auto pTmpTypeExt = TechnoTypeExt::ExtMap.Find(pTmpType); diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index 9f703532ea..a872b10457 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -35,12 +35,24 @@ class HouseExt BuildingClass* Factory_NavyType; BuildingClass* Factory_AircraftType; + CDTimerClass AISuperWeaponDelayTimer; + //Read from INI bool RepairBaseNodes[3]; + // FactoryPlants with Allow/DisallowTypes set. + std::vector RestrictedFactoryPlants; + int LastBuiltNavalVehicleType; int ProducingNavalUnitTypeIndex; + // Factories that exist but don't count towards multiple factory bonus. + int NumAirpads_NonMFB; + int NumBarracks_NonMFB; + int NumWarFactories_NonMFB; + int NumConYards_NonMFB; + int NumShipyards_NonMFB; + ExtData(HouseClass* OwnerObject) : Extension(OwnerObject) , PowerPlantEnhancers {} , OwnedLimboDeliveredBuildings {} @@ -53,15 +65,25 @@ class HouseExt , Factory_VehicleType { nullptr } , Factory_NavyType { nullptr } , Factory_AircraftType { nullptr } + , AISuperWeaponDelayTimer {} , RepairBaseNodes { false,false,false } + , RestrictedFactoryPlants {} , LastBuiltNavalVehicleType { -1 } , ProducingNavalUnitTypeIndex { -1 } + , NumAirpads_NonMFB { 0 } + , NumBarracks_NonMFB { 0 } + , NumWarFactories_NonMFB { 0 } + , NumConYards_NonMFB { 0 } + , NumShipyards_NonMFB { 0 } { } bool OwnsLimboDeliveredBuilding(BuildingClass* pBuilding); void AddToLimboTracking(TechnoTypeClass* pTechnoType); void RemoveFromLimboTracking(TechnoTypeClass* pTechnoType); int CountOwnedPresentAndLimboed(TechnoTypeClass* pTechnoType); + void UpdateNonMFBFactoryCounts(AbstractType rtti, bool remove, bool isNaval); + int GetFactoryCountWithoutNonMFB(AbstractType rtti, bool isNaval); + float GetRestrictedFactoryPlantMult(TechnoTypeClass* pTechnoType) const; virtual ~ExtData() = default; diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index 5cff2ce743..ad359b511a 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -271,3 +271,69 @@ DEFINE_HOOK(0x50B669, HouseClass_ShouldDisableCameo, 0x5) return 0; } + +DEFINE_HOOK(0x4FD77C, HouseClass_ExpertAI_Superweapons, 0x5) +{ + enum { SkipSWProcess = 0x4FD7A0 }; + + if (RulesExt::Global()->AISuperWeaponDelay.isset()) + return SkipSWProcess; + + return 0; +} + +DEFINE_HOOK(0x4F9038, HouseClass_AI_Superweapons, 0x5) +{ + GET(HouseClass*, pThis, ESI); + + if (!RulesExt::Global()->AISuperWeaponDelay.isset() || pThis->IsControlledByHuman() || pThis->Type->MultiplayPassive) + return 0; + + int delay = RulesExt::Global()->AISuperWeaponDelay.Get(); + + if (delay > 0) + { + auto const pExt = HouseExt::ExtMap.Find(pThis); + + if (pExt->AISuperWeaponDelayTimer.HasTimeLeft()) + return 0; + + pExt->AISuperWeaponDelayTimer.Start(delay); + } + + if (!SessionClass::IsCampaign() || pThis->IQLevel2 >= RulesClass::Instance->SuperWeapons) + pThis->AI_TryFireSW(); + + return 0; +} + +DEFINE_HOOK_AGAIN(0x4FFA99, HouseClass_ExcludeFromMultipleFactoryBonus, 0x6) +DEFINE_HOOK(0x4FF9C9, HouseClass_ExcludeFromMultipleFactoryBonus, 0x6) +{ + GET(BuildingClass*, pBuilding, ESI); + + if (BuildingTypeExt::ExtMap.Find(pBuilding->Type)->ExcludeFromMultipleFactoryBonus) + { + GET(HouseClass*, pThis, EDI); + GET(bool, isNaval, ECX); + + auto const pExt = HouseExt::ExtMap.Find(pThis); + pExt->UpdateNonMFBFactoryCounts(pBuilding->Type->Factory, R->Origin() == 0x4FF9C9, isNaval); + } + + return 0; +} + +DEFINE_HOOK(0x500910, HouseClass_GetFactoryCount, 0x5) +{ + enum { SkipGameCode = 0x50095D }; + + GET(HouseClass*, pThis, ECX); + GET_STACK(AbstractType, rtti, 0x4); + GET_STACK(bool, isNaval, 0x8); + + auto const pExt = HouseExt::ExtMap.Find(pThis); + R->EAX(pExt->GetFactoryCountWithoutNonMFB(rtti, isNaval)); + + return SkipGameCode; +} diff --git a/src/Ext/OverlayType/Body.cpp b/src/Ext/OverlayType/Body.cpp index 7c8c762f0e..6df7573165 100644 --- a/src/Ext/OverlayType/Body.cpp +++ b/src/Ext/OverlayType/Body.cpp @@ -35,29 +35,17 @@ void OverlayTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ZAdjust.Read(exArtINI, pArtSection, "ZAdjust"); this->PaletteFile.Read(pArtINI, pArtSection, "Palette"); - - BuildPalette(); + this->Palette = GeneralUtils::BuildPalette(this->PaletteFile); if (GeneralUtils::IsValidString(this->PaletteFile) && !this->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, this->PaletteFile); } -void OverlayTypeExt::ExtData::BuildPalette() -{ - if (GeneralUtils::IsValidString(this->PaletteFile)) - { - char pFilename[0x20]; - strcpy_s(pFilename, this->PaletteFile.data()); - - this->Palette = ColorScheme::GeneratePalette(pFilename); - } -} - void OverlayTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) { Extension::LoadFromStream(Stm); this->Serialize(Stm); - this->BuildPalette(); + this->Palette = GeneralUtils::BuildPalette(this->PaletteFile); } void OverlayTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) diff --git a/src/Ext/OverlayType/Body.h b/src/Ext/OverlayType/Body.h index 78b3000064..3c96aaebfa 100644 --- a/src/Ext/OverlayType/Body.h +++ b/src/Ext/OverlayType/Body.h @@ -39,7 +39,6 @@ class OverlayTypeExt private: template void Serialize(T& Stm); - void BuildPalette(); }; class ExtContainer final : public Container diff --git a/src/Ext/OverlayType/Hooks.cpp b/src/Ext/OverlayType/Hooks.cpp index 93eb567f2f..ce2b31e345 100644 --- a/src/Ext/OverlayType/Hooks.cpp +++ b/src/Ext/OverlayType/Hooks.cpp @@ -31,15 +31,15 @@ DEFINE_HOOK(0x47F974, CellClass_DrawOverlay_Walls, 0x5) int colorSchemeIndex = HouseClass::CurrentPlayer->ColorSchemeIndex; if (wallOwnerIndex >= 0) - colorSchemeIndex = HouseClass::Array->GetItem(wallOwnerIndex)->ColorSchemeIndex; + colorSchemeIndex = HouseClass::Array->Items[wallOwnerIndex]->ColorSchemeIndex; LightConvertClass* pConvert = nullptr; auto const pTypeExt = OverlayTypeExt::ExtMap.Find(pOverlayType); if (pTypeExt->Palette) - pConvert = pTypeExt->Palette->GetItem(colorSchemeIndex)->LightConvert; + pConvert = pTypeExt->Palette->Items[colorSchemeIndex]->LightConvert; else - pConvert = ColorScheme::Array->GetItem(colorSchemeIndex)->LightConvert; + pConvert = ColorScheme::Array->Items[colorSchemeIndex]->LightConvert; DSurface::Temp->DrawSHP(pConvert, pShape, pThis->OverlayData, &pLocation, pBounds, BlitterFlags(0x4E00), 0, -2 - zAdjust, ZGradient::Deg90, pThis->Intensity_Normal, 0, 0, 0, 0, 0); diff --git a/src/Ext/RadSite/Body.cpp b/src/Ext/RadSite/Body.cpp index 4c1340abe8..4faf54352e 100644 --- a/src/Ext/RadSite/Body.cpp +++ b/src/Ext/RadSite/Body.cpp @@ -13,7 +13,7 @@ void RadSiteExt::ExtData::Initialize() bool RadSiteExt::ExtData::ApplyRadiationDamage(TechnoClass* pTarget, int& damage, int distance) { - auto pWarhead = this->Type->GetWarhead(); + auto const pWarhead = this->Type->GetWarhead(); if (!this->Type->GetWarheadDetonate()) { @@ -22,7 +22,15 @@ bool RadSiteExt::ExtData::ApplyRadiationDamage(TechnoClass* pTarget, int& damage } else { - WarheadTypeExt::DetonateAt(pWarhead, pTarget, this->RadInvoker, damage); + if (this->Type->GetWarheadDetonateFull()) + { + WarheadTypeExt::DetonateAt(pWarhead, pTarget, this->RadInvoker, damage, this->RadHouse); + } + else + { + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); + pWHExt->DamageAreaWithTarget(pTarget->GetCoords(), damage, this->RadInvoker, pWarhead, true, this->RadHouse, pTarget); + } if (!pTarget->IsAlive) return false; diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 8ce48ee29a..336cae4272 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -74,13 +74,17 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->DisguiseBlinkingVisibility.Read(exINI, GameStrings::General, "DisguiseBlinkingVisibility"); this->ChronoSparkleDisplayDelay.Read(exINI, GameStrings::General, "ChronoSparkleDisplayDelay"); this->ChronoSparkleBuildingDisplayPositions.Read(exINI, GameStrings::General, "ChronoSparkleBuildingDisplayPositions"); + this->ChronoSpherePreDelay.Read(exINI, GameStrings::General, "ChronoSpherePreDelay"); + this->ChronoSphereDelay.Read(exINI, GameStrings::General, "ChronoSphereDelay"); this->AIChronoSphereSW.Read(exINI, GameStrings::General, "AIChronoSphereSW"); this->AIChronoWarpSW.Read(exINI, GameStrings::General, "AIChronoWarpSW"); this->SubterraneanHeight.Read(exINI, GameStrings::General, "SubterraneanHeight"); + this->AISuperWeaponDelay.Read(exINI, GameStrings::General, "AISuperWeaponDelay"); this->UseGlobalRadApplicationDelay.Read(exINI, GameStrings::Radiation, "UseGlobalRadApplicationDelay"); this->RadApplicationDelay_Building.Read(exINI, GameStrings::Radiation, "RadApplicationDelay.Building"); this->RadBuildingDamageMaxCount.Read(exINI, GameStrings::Radiation, "RadBuildingDamageMaxCount"); this->RadSiteWarhead_Detonate.Read(exINI, GameStrings::Radiation, "RadSiteWarhead.Detonate"); + this->RadSiteWarhead_Detonate_Full.Read(exINI, GameStrings::Radiation, "RadSiteWarhead.Detonate.Full"); this->RadHasOwner.Read(exINI, GameStrings::Radiation, "RadHasOwner"); this->RadHasInvoker.Read(exINI, GameStrings::Radiation, "RadHasInvoker"); this->VeinholeWarhead.Read(exINI, GameStrings::CombatDamage, "VeinholeWarhead"); @@ -91,6 +95,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->PlacementPreview.Read(exINI, GameStrings::AudioVisual, "PlacementPreview"); this->PlacementPreview_Translucency.Read(exINI, GameStrings::AudioVisual, "PlacementPreview.Translucency"); + this->ConditionYellow_Terrain.Read(exINI, GameStrings::AudioVisual, "ConditionYellow.Terrain"); this->Shield_ConditionYellow.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionYellow"); this->Shield_ConditionRed.Read(exINI, GameStrings::AudioVisual, "Shield.ConditionRed"); this->Pips_Shield.Read(exINI, GameStrings::AudioVisual, "Pips.Shield"); @@ -137,6 +142,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->ForbidParallelAIQueues_Navy.Read(exINI, "GlobalControls", "ForbidParallelAIQueues.Navy"); this->ForbidParallelAIQueues_Vehicle.Read(exINI, "GlobalControls", "ForbidParallelAIQueues.Vehicle"); + this->EnablePowerSurplus.Read(exINI, GameStrings::AI, "EnablePowerSurplus"); + this->IronCurtain_KeptOnDeploy.Read(exINI, GameStrings::CombatDamage, "IronCurtain.KeptOnDeploy"); this->IronCurtain_EffectOnOrganics.Read(exINI, GameStrings::CombatDamage, "IronCurtain.EffectOnOrganics"); this->IronCurtain_KillOrganicsWarhead.Read(exINI, GameStrings::CombatDamage, "IronCurtain.KillOrganicsWarhead"); @@ -183,6 +190,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->ReplaceVoxelLightSources(); + this->UseFixedVoxelLighting.Read(exINI, GameStrings::AudioVisual, "UseFixedVoxelLighting"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -259,13 +268,17 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->DisguiseBlinkingVisibility) .Process(this->ChronoSparkleDisplayDelay) .Process(this->ChronoSparkleBuildingDisplayPositions) + .Process(this->ChronoSpherePreDelay) + .Process(this->ChronoSphereDelay) .Process(this->AIChronoSphereSW) .Process(this->AIChronoWarpSW) .Process(this->SubterraneanHeight) + .Process(this->AISuperWeaponDelay) .Process(this->UseGlobalRadApplicationDelay) .Process(this->RadApplicationDelay_Building) .Process(this->RadBuildingDamageMaxCount) .Process(this->RadSiteWarhead_Detonate) + .Process(this->RadSiteWarhead_Detonate_Full) .Process(this->RadHasOwner) .Process(this->RadHasInvoker) .Process(this->JumpjetCrash) @@ -276,6 +289,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->PlacementGrid_TranslucencyWithPreview) .Process(this->PlacementPreview) .Process(this->PlacementPreview_Translucency) + .Process(this->ConditionYellow_Terrain) .Process(this->Shield_ConditionYellow) .Process(this->Shield_ConditionRed) .Process(this->Pips_Shield) @@ -306,6 +320,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->ForbidParallelAIQueues_Infantry) .Process(this->ForbidParallelAIQueues_Navy) .Process(this->ForbidParallelAIQueues_Vehicle) + .Process(this->EnablePowerSurplus) .Process(this->IronCurtain_KeptOnDeploy) .Process(this->IronCurtain_EffectOnOrganics) .Process(this->IronCurtain_KillOrganicsWarhead) @@ -347,6 +362,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->PodImage) .Process(this->VoxelLightSource) // .Process(this->VoxelShadowLightSource) + .Process(this->UseFixedVoxelLighting) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 72dc09ab64..b6dc6a00ac 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -40,13 +40,17 @@ class RulesExt Valueable DisguiseBlinkingVisibility; Valueable ChronoSparkleDisplayDelay; Valueable ChronoSparkleBuildingDisplayPositions; + Valueable ChronoSpherePreDelay; + Valueable ChronoSphereDelay; ValueableIdx AIChronoSphereSW; ValueableIdx AIChronoWarpSW; Valueable SubterraneanHeight; + Nullable AISuperWeaponDelay; Valueable UseGlobalRadApplicationDelay; Valueable RadApplicationDelay_Building; Valueable RadBuildingDamageMaxCount; Valueable RadSiteWarhead_Detonate; + Valueable RadSiteWarhead_Detonate_Full; Valueable RadHasOwner; Valueable RadHasInvoker; Valueable JumpjetCrash; @@ -61,6 +65,7 @@ class RulesExt Valueable PlacementPreview; TranslucencyLevel PlacementPreview_Translucency; + Nullable ConditionYellow_Terrain; Nullable Shield_ConditionYellow; Nullable Shield_ConditionRed; Valueable> Pips_Shield; @@ -94,6 +99,8 @@ class RulesExt Valueable ForbidParallelAIQueues_Navy; Valueable ForbidParallelAIQueues_Vehicle; + Valueable EnablePowerSurplus; + Valueable DisplayIncome; Valueable DisplayIncome_AllowAI; Valueable DisplayIncome_Houses; @@ -142,6 +149,7 @@ class RulesExt Nullable> VoxelLightSource; // Nullable> VoxelShadowLightSource; + Valueable UseFixedVoxelLighting; ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } @@ -152,13 +160,17 @@ class RulesExt , DisguiseBlinkingVisibility { AffectedHouse::Owner } , ChronoSparkleDisplayDelay { 24 } , ChronoSparkleBuildingDisplayPositions { ChronoSparkleDisplayPosition::OccupantSlots } + , ChronoSpherePreDelay { 60 } + , ChronoSphereDelay { 0 } , AIChronoSphereSW {} , AIChronoWarpSW {} , SubterraneanHeight { -256 } + , AISuperWeaponDelay {} , UseGlobalRadApplicationDelay { true } , RadApplicationDelay_Building { 0 } , RadBuildingDamageMaxCount { -1 } , RadSiteWarhead_Detonate { false } + , RadSiteWarhead_Detonate_Full { true } , RadHasOwner { false } , RadHasInvoker { false } , JumpjetCrash { 5.0 } @@ -202,6 +214,9 @@ class RulesExt , ForbidParallelAIQueues_Infantry { false } , ForbidParallelAIQueues_Navy { false } , ForbidParallelAIQueues_Vehicle { false } + + , EnablePowerSurplus { false } + , IronCurtain_KeptOnDeploy { true } , IronCurtain_EffectOnOrganics { IronCurtainEffect::Kill } , IronCurtain_KillOrganicsWarhead { } @@ -243,6 +258,7 @@ class RulesExt , PodImage { } , VoxelLightSource { } // , VoxelShadowLightSource { } + , UseFixedVoxelLighting { false } { } virtual ~ExtData() = default; diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 650445a641..93ebecc40f 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -36,6 +36,7 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->Detonate_Warhead) .Process(this->Detonate_Weapon) .Process(this->Detonate_Damage) + .Process(this->Detonate_Warhead_Full) .Process(this->Detonate_AtFirer) .Process(this->SW_Next) .Process(this->SW_Next_RealLaunch) @@ -97,7 +98,6 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) char tempBuffer[32]; // LimboDelivery.RandomWeights - // so you know what's going on by not clearing the vector, do you? for (size_t i = 0; ; ++i) { ValueableVector weights; @@ -107,8 +107,12 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) if (!weights.size()) break; - this->LimboDelivery_RandomWeightsData.push_back(std::move(weights)); + if (this->LimboDelivery_RandomWeightsData.size() > i) + this->LimboDelivery_RandomWeightsData[i] = std::move(weights); + else + this->LimboDelivery_RandomWeightsData.push_back(std::move(weights)); } + ValueableVector weights; weights.Read(exINI, pSection, "LimboDelivery.RandomWeights"); if (weights.size()) @@ -129,8 +133,12 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) if (!weights2.size()) break; - this->SW_Next_RandomWeightsData.push_back(std::move(weights2)); + if (this->SW_Next_RandomWeightsData.size() > i) + this->SW_Next_RandomWeightsData[i] = std::move(weights2); + else + this->SW_Next_RandomWeightsData.push_back(std::move(weights2)); } + ValueableVector weights2; weights2.Read(exINI, pSection, "SW.Next.RandomWeights"); if (weights2.size()) @@ -144,6 +152,7 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Detonate_Warhead.Read(exINI, pSection, "Detonate.Warhead"); this->Detonate_Weapon.Read(exINI, pSection, "Detonate.Weapon"); this->Detonate_Damage.Read(exINI, pSection, "Detonate.Damage"); + this->Detonate_Warhead_Full.Read(exINI, pSection, "Detonate.Warhead.Full"); this->Detonate_AtFirer.Read(exINI, pSection, "Detonate.AtFirer"); // Convert.From & Convert.To diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index da5f69a4f8..82bd9ad3f4 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -56,6 +56,7 @@ class SWTypeExt Valueable Detonate_Warhead; Valueable Detonate_Weapon; Nullable Detonate_Damage; + Valueable Detonate_Warhead_Full; Valueable Detonate_AtFirer; Valueable ShowDesignatorRange; @@ -95,6 +96,7 @@ class SWTypeExt , Detonate_Warhead {} , Detonate_Weapon {} , Detonate_Damage {} + , Detonate_Warhead_Full { true } , Detonate_AtFirer { false } , SW_Next {} , SW_Next_RealLaunch { true } diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index 01b0fc8a12..519f39d4a8 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -216,15 +216,15 @@ void SWTypeExt::ExtData::ApplyDetonation(HouseClass* pHouse, const CellStruct& c return; } - HouseClass* pFirerHouse = nullptr; - - if (!pFirer) - pFirerHouse = pHouse; - if (pWeapon) - WeaponTypeExt::DetonateAt(pWeapon, coords, pFirer, this->Detonate_Damage.Get(pWeapon->Damage), pFirerHouse); + WeaponTypeExt::DetonateAt(pWeapon, coords, pFirer, this->Detonate_Damage.Get(pWeapon->Damage), pHouse); else - WarheadTypeExt::DetonateAt(this->Detonate_Warhead, coords, pFirer, this->Detonate_Damage.Get(0), pFirerHouse); + { + if (this->Detonate_Warhead_Full) + WarheadTypeExt::DetonateAt(this->Detonate_Warhead, coords, pFirer, this->Detonate_Damage.Get(0), pHouse); + else + MapClass::DamageArea(coords, this->Detonate_Damage.Get(0), pFirer, this->Detonate_Warhead, true, pHouse); + } } void SWTypeExt::ExtData::ApplySWNext(SuperClass* pSW, const CellStruct& cell) diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 20e29842d4..3cf902f5bd 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -510,7 +510,7 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* pCurrentType) } if (pOldType->Locomotor == LocomotionClass::CLSIDs::Teleport && pCurrentType->Locomotor != LocomotionClass::CLSIDs::Teleport && pThis->WarpingOut) - this->HasCarryoverWarpInDelay = true; + this->HasRemainingWarpInDelay = true; } } diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index f76c15660f..82368ad6e2 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -23,7 +23,7 @@ TechnoExt::ExtData::~ExtData() vec.erase(std::remove(vec.begin(), vec.end(), this), vec.end()); } - if (pThis->Transporter && pThis->WhatAmI() != AbstractType::Aircraft && pThis->WhatAmI() != AbstractType::Building + if (pThis->WhatAmI() != AbstractType::Aircraft && pThis->WhatAmI() != AbstractType::Building && pType->Ammo > 0 && pTypeExt->ReloadInTransport) { auto& vec = ScenarioExt::Global()->TransportReloaders; @@ -494,8 +494,9 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->WHAnimRemainingCreationInterval) .Process(this->FiringObstacleCell) .Process(this->OriginalPassengerOwner) - .Process(this->HasCarryoverWarpInDelay) + .Process(this->HasRemainingWarpInDelay) .Process(this->LastWarpInDelay) + .Process(this->IsBeingChronoSphered) ; } diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index ca4dcf8f29..39e777208f 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -51,8 +51,9 @@ class TechnoExt // Used for Passengers.SyncOwner.RevertOnExit instead of TechnoClass::InitialOwner / OriginallyOwnedByHouse, // as neither is guaranteed to point to the house the TechnoClass had prior to entering transport and cannot be safely overridden. HouseClass* OriginalPassengerOwner; - bool HasCarryoverWarpInDelay; // Converted from object with Teleport Locomotor to one with a different Locomotor while still phasing in. - int LastWarpInDelay; // Last-warp in delay for this unit, used by HasCarryoverWarpInDelay. + bool HasRemainingWarpInDelay; // Converted from object with Teleport Locomotor to one with a different Locomotor while still phasing in OR set if ChronoSphereDelay > 0. + int LastWarpInDelay; // Last-warp in delay for this unit, used by HasCarryoverWarpInDelay. + bool IsBeingChronoSphered; // Set to true on units currently being ChronoSphered, does not apply to Ares-ChronoSphere'd buildings or Chrono reinforcements. ExtData(TechnoClass* OwnerObject) : Extension(OwnerObject) , TypeExtData { nullptr } @@ -80,8 +81,9 @@ class TechnoExt , CanCurrentlyDeployIntoBuilding { false } , FiringObstacleCell {} , OriginalPassengerOwner {} - , HasCarryoverWarpInDelay { false } + , HasRemainingWarpInDelay { false } , LastWarpInDelay { 0 } + , IsBeingChronoSphered { false} { } void OnEarlyUpdate(); @@ -159,6 +161,11 @@ class TechnoExt static int GetCustomTintColor(TechnoClass* pThis); static int GetCustomTintIntensity(TechnoClass* pThis); static void ApplyCustomTintValues(TechnoClass* pThis, int& color, int& intensity); + static Point2D GetScreenLocation(TechnoClass* pThis); + static Point2D GetFootSelectBracketPosition(TechnoClass* pThis, Anchor anchor); + static Point2D GetBuildingSelectBracketPosition(TechnoClass* pThis, BuildingSelectBracketPosition bracketPosition); + static void ProcessDigitalDisplays(TechnoClass* pThis); + static void GetValuesForDisplay(TechnoClass* pThis, DisplayInfoType infoType, int& value, int& maxValue); // WeaponHelpers.cpp static int PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, AbstractClass* pTarget, int weaponIndexOne, int weaponIndexTwo, bool allowFallback = true, bool allowAAFallback = true); @@ -168,9 +175,5 @@ class TechnoExt static WeaponTypeClass* GetDeployFireWeapon(TechnoClass* pThis); static WeaponTypeClass* GetCurrentWeapon(TechnoClass* pThis, int& weaponIndex, bool getSecondary = false); static WeaponTypeClass* GetCurrentWeapon(TechnoClass* pThis, bool getSecondary = false); - static Point2D GetScreenLocation(TechnoClass* pThis); - static Point2D GetFootSelectBracketPosition(TechnoClass* pThis, Anchor anchor); - 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 GetWeaponIndexAgainstWall(TechnoClass* pThis, OverlayTypeClass* pWallOverlayType); }; diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 575fd7d619..3b7840ac4a 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -37,7 +37,7 @@ DEFINE_HOOK(0x6F3339, TechnoClass_WhatWeaponShouldIUse_Interceptor, 0x8) DEFINE_HOOK(0x6F33CD, TechnoClass_WhatWeaponShouldIUse_ForceFire, 0x6) { - enum { Secondary = 0x6F3745 }; + enum { ReturnWeaponIndex = 0x6F37AF }; GET(TechnoClass*, pThis, ESI); GET_STACK(AbstractClass*, pTarget, STACK_OFFSET(0x18, 0x4)); @@ -46,20 +46,22 @@ DEFINE_HOOK(0x6F33CD, TechnoClass_WhatWeaponShouldIUse_ForceFire, 0x6) { auto const pWeaponPrimary = pThis->GetWeapon(0)->WeaponType; auto const pWeaponSecondary = pThis->GetWeapon(1)->WeaponType; - const auto pPrimaryExt = WeaponTypeExt::ExtMap.Find(pWeaponPrimary); + auto const pPrimaryExt = WeaponTypeExt::ExtMap.Find(pWeaponPrimary); - if (pWeaponSecondary && !EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true, true)) + if (pWeaponSecondary && (!EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true, true) + || (pPrimaryExt->AttachEffect_CheckOnFirer && !pPrimaryExt->HasRequiredAttachedEffects(pThis, pThis)))) { - return Secondary; + R->EAX(1); + return ReturnWeaponIndex; } else if (pCell->OverlayTypeIndex != -1) { auto const pOverlayType = OverlayTypeClass::Array()->GetItem(pCell->OverlayTypeIndex); - if (pOverlayType->Wall && pCell->OverlayData >> 4 != pOverlayType->DamageLevels && !pWeaponPrimary->Warhead->Wall && - pWeaponSecondary && pWeaponSecondary->Warhead->Wall && !TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->NoSecondaryWeaponFallback) + if (pOverlayType->Wall && pCell->OverlayData >> 4 != pOverlayType->DamageLevels) { - return Secondary; + R->EAX(TechnoExt::GetWeaponIndexAgainstWall(pThis, pOverlayType)); + return ReturnWeaponIndex; } } } @@ -814,35 +816,39 @@ DEFINE_HOOK(0x5223B3, InfantryClass_Approach_Target_DeployFireWeapon, 0x6) #pragma region WallWeaponStuff -WeaponStruct* __fastcall TechnoClass_GetWeaponAgainstWallWrapper(TechnoClass* pThis, void* _, int weaponIndex) +DEFINE_HOOK(0x70095A, TechnoClass_WhatAction_WallWeapon, 0x6) { - auto const weaponPrimary = pThis->GetWeapon(0); - - if (!weaponPrimary->WeaponType->Warhead->Wall) - { - auto const weaponSecondary = pThis->GetWeapon(1); + GET(TechnoClass*, pThis, ESI); + GET_STACK(OverlayTypeClass*, pOverlayTypeClass, STACK_OFFSET(0x2C, -0x18)); - if (weaponSecondary && weaponSecondary->WeaponType && weaponSecondary->WeaponType->Warhead->Wall && - !TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->NoSecondaryWeaponFallback) - { - return weaponSecondary; - } - } + int weaponIndex = TechnoExt::GetWeaponIndexAgainstWall(pThis, pOverlayTypeClass); + R->EAX(pThis->GetWeapon(weaponIndex)); - return weaponPrimary; + return 0; } +DEFINE_HOOK(0x51C1F1, InfantryClass_CanEnterCell_WallWeapon, 0x5) +{ + enum { SkipGameCode = 0x51C1FE }; + + GET(InfantryClass*, pThis, EBP); + GET(OverlayTypeClass*, pOverlayTypeClass, ESI); -DEFINE_JUMP(CALL6, 0x51C1F8, GET_OFFSET(TechnoClass_GetWeaponAgainstWallWrapper)); // InfantryClass_CanEnterCell -DEFINE_JUMP(CALL6, 0x73F49B, GET_OFFSET(TechnoClass_GetWeaponAgainstWallWrapper)); // UnitClass_CanEnterCell + R->EAX(pThis->GetWeapon(TechnoExt::GetWeaponIndexAgainstWall(pThis, pOverlayTypeClass))); -DEFINE_HOOK(0x70095A, TechnoClass_WhatAction_WallWeapon, 0x6) + return SkipGameCode; +} + +DEFINE_HOOK(0x73F495, UnitClass_CanEnterCell_WallWeapon, 0x6) { - GET(TechnoClass*, pThis, ESI); + enum { SkipGameCode = 0x73F4A1 }; - R->EAX(TechnoClass_GetWeaponAgainstWallWrapper(pThis, nullptr, 0)); + GET(UnitClass*, pThis, EBX); + GET(OverlayTypeClass*, pOverlayTypeClass, ESI); - return 0; + R->EAX(pThis->GetWeapon(TechnoExt::GetWeaponIndexAgainstWall(pThis, pOverlayTypeClass))); + + return SkipGameCode; } namespace CellEvalTemp @@ -873,3 +879,4 @@ DEFINE_JUMP(CALL6, 0x6F8CE3, GET_OFFSET(TechnoClass_EvaluateCellGetWeaponWrapper DEFINE_JUMP(CALL6, 0x6F8DD2, GET_OFFSET(TechnoClass_EvaluateCellGetWeaponRangeWrapper)); #pragma endregion + diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp index d74b667b7f..8b389c0a8b 100644 --- a/src/Ext/Techno/Hooks.Shield.cpp +++ b/src/Ext/Techno/Hooks.Shield.cpp @@ -22,8 +22,10 @@ DEFINE_HOOK(0x701900, TechnoClass_ReceiveDamage_Shield, 0x6) if (!args->IgnoreDefenses) { - *args->Damage = Math::clamp(*args->Damage, Math::max(pTypeExt->ReceivedDamage_Minimum, pExt->AE.ReceivedDamage_Minimum), - Math::min(pTypeExt->ReceivedDamage_Maximum, pExt->AE.ReceivedDamage_Maximum)); + if (*args->Damage != 0) + *args->Damage = Math::clamp(*args->Damage, Math::max(pTypeExt->ReceivedDamage_Minimum, pExt->AE.ReceivedDamage_Minimum), + Math::min(pTypeExt->ReceivedDamage_Maximum, pExt->AE.ReceivedDamage_Maximum)); + int nDamageLeft = *args->Damage; if (const auto pShieldData = pExt->Shield.get()) diff --git a/src/Ext/Techno/Hooks.Tint.cpp b/src/Ext/Techno/Hooks.Tint.cpp index d009850f1e..d383deeb52 100644 --- a/src/Ext/Techno/Hooks.Tint.cpp +++ b/src/Ext/Techno/Hooks.Tint.cpp @@ -47,7 +47,7 @@ DEFINE_HOOK(0x73BF95, UnitClass_DrawAsVoxel_Tint, 0x7) intensity = pThis->GetInvulnerabilityTintIntensity(intensity); int color = TechnoExt::GetTintColor(pThis, isInvulnerable, false, pThis->Berzerk); - color |= TechnoExt::GetCustomTintColor(pThis); + TechnoExt::ApplyCustomTintValues(pThis, color, intensity); R->ESI(color); return SkipGameCode; diff --git a/src/Ext/Techno/Hooks.WeaponEffects.cpp b/src/Ext/Techno/Hooks.WeaponEffects.cpp index 7797fb2c1f..232535c5c9 100644 --- a/src/Ext/Techno/Hooks.WeaponEffects.cpp +++ b/src/Ext/Techno/Hooks.WeaponEffects.cpp @@ -162,13 +162,15 @@ DEFINE_HOOK(0x70CA8B, TechnoClass_Railgun_AmbientDamageIgnoreTarget2, 0x6) return 0; } -DEFINE_HOOK(0x70CBE0, TechnoClass_Railgun_AmbientDamageWarhead, 0x5) +DEFINE_HOOK(0x70CBDA, TechnoClass_Railgun_AmbientDamageWarhead, 0x6) { + enum { SkipGameCode = 0x70CBE0 }; + GET(WeaponTypeClass*, pWeapon, EDI); R->EDX(WeaponTypeExt::ExtMap.Find(pWeapon)->AmbientDamage_Warhead.Get(pWeapon->Warhead)); - return 0; + return SkipGameCode; } // Do not adjust map coordinates for railgun or fire stream particles that are below cell coordinates. diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index f7bc6243b4..e2c8c22264 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -595,3 +595,7 @@ 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); diff --git a/src/Ext/Techno/WeaponHelpers.cpp b/src/Ext/Techno/WeaponHelpers.cpp index 3a83370f71..5310fdf18c 100644 --- a/src/Ext/Techno/WeaponHelpers.cpp +++ b/src/Ext/Techno/WeaponHelpers.cpp @@ -1,5 +1,7 @@ #include "Body.h" +#include + #include #include @@ -41,15 +43,15 @@ int TechnoExt::PickWeaponIndex(TechnoClass* pThis, TechnoClass* pTargetTechno, A } else if (auto const pFirstExt = WeaponTypeExt::ExtMap.Find(pWeaponOne)) { - bool secondaryIsAA = pTargetTechno && pTargetTechno->IsInAir() && pWeaponTwo->Projectile->AA; + bool secondIsAA = pTargetTechno && pTargetTechno->IsInAir() && pWeaponTwo->Projectile->AA; + bool firstAllowedAE = pFirstExt->HasRequiredAttachedEffects(pTargetTechno, pThis); - if (!allowFallback && (!allowAAFallback || !secondaryIsAA) && !TechnoExt::CanFireNoAmmoWeapon(pThis, 1)) + if (!allowFallback && (!allowAAFallback || !secondIsAA) && !TechnoExt::CanFireNoAmmoWeapon(pThis, 1) && firstAllowedAE) return weaponIndexOne; if ((pTargetCell && !EnumFunctions::IsCellEligible(pTargetCell, pFirstExt->CanTarget, true, true)) || (pTargetTechno && (!EnumFunctions::IsTechnoEligible(pTargetTechno, pFirstExt->CanTarget) || - !EnumFunctions::CanTargetHouse(pFirstExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner) || - !pFirstExt->HasRequiredAttachedEffects(pTargetTechno, pThis)))) + !EnumFunctions::CanTargetHouse(pFirstExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner) || !firstAllowedAE))) { return weaponIndexTwo; } @@ -162,3 +164,37 @@ WeaponTypeClass* TechnoExt::GetCurrentWeapon(TechnoClass* pThis, bool getSeconda int weaponIndex = 0; return TechnoExt::GetCurrentWeapon(pThis, weaponIndex, getSecondary); } + +// Gets weapon index for a weapon to use against wall overlay. +int TechnoExt::GetWeaponIndexAgainstWall(TechnoClass* pThis, OverlayTypeClass* pWallOverlayType) +{ + auto const pTechnoType = pThis->GetTechnoType(); + int weaponIndex = -1; + auto pWeapon = TechnoExt::GetCurrentWeapon(pThis, weaponIndex); + + if ((pTechnoType->TurretCount > 0 && !pTechnoType->IsGattling) || !pWallOverlayType || !pWallOverlayType->Wall) + return weaponIndex; + else if (weaponIndex == -1) + return 0; + + auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + bool aeForbidsPrimary = pWeaponExt && pWeaponExt->AttachEffect_CheckOnFirer && !pWeaponExt->HasRequiredAttachedEffects(pThis, pThis); + + if (!pWeapon || (!pWeapon->Warhead->Wall && (!pWeapon->Warhead->Wood || pWallOverlayType->Armor != Armor::Wood)) || TechnoExt::CanFireNoAmmoWeapon(pThis, 1) || aeForbidsPrimary) + { + int weaponIndexSec = -1; + pWeapon = TechnoExt::GetCurrentWeapon(pThis, weaponIndexSec, true); + pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + bool aeForbidsSecondary = pWeaponExt && pWeaponExt->AttachEffect_CheckOnFirer && !pWeaponExt->HasRequiredAttachedEffects(pThis, pThis); + + if (pWeapon && (pWeapon->Warhead->Wall || (pWeapon->Warhead->Wood && pWallOverlayType->Armor == Armor::Wood) + && (!TechnoTypeExt::ExtMap.Find(pTechnoType)->NoSecondaryWeaponFallback || aeForbidsPrimary)) && !aeForbidsSecondary) + { + return weaponIndexSec; + } + + return weaponIndex; + } + + return weaponIndex; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 55cadaba55..8e2fd31268 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -131,6 +131,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->UIDescription.Read(exINI, pSection, "UIDescription"); this->LowSelectionPriority.Read(exINI, pSection, "LowSelectionPriority"); this->MindControlRangeLimit.Read(exINI, pSection, "MindControlRangeLimit"); + this->FactoryPlant_Multiplier.Read(exINI, pSection, "FactoryPlant.Multiplier"); this->Spawner_LimitRange.Read(exINI, pSection, "Spawner.LimitRange"); this->Spawner_ExtraLimitRange.Read(exINI, pSection, "Spawner.ExtraLimitRange"); @@ -152,6 +153,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->InitialStrength = Math::clamp(this->InitialStrength, 1, pThis->Strength); this->ReloadInTransport.Read(exINI, pSection, "ReloadInTransport"); + this->ForbidParallelAIQueues.Read(exINI, pSection, "ForbidParallelAIQueues"); this->ShieldType.Read(exINI, pSection, "ShieldType"); this->Ammo_AddOnDeploy.Read(exINI, pSection, "Ammo.AddOnDeploy"); @@ -191,6 +193,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ChronoMinimumDelay.Read(exINI, pSection, "ChronoMinimumDelay"); this->ChronoRangeMinimum.Read(exINI, pSection, "ChronoRangeMinimum"); this->ChronoDelay.Read(exINI, pSection, "ChronoDelay"); + this->ChronoSpherePreDelay.Read(exINI, pSection, "ChronoSpherePreDelay"); + this->ChronoSphereDelay.Read(exINI, pSection, "ChronoSphereDelay"); this->WarpInWeapon.Read(exINI, pSection, "WarpInWeapon"); this->WarpInMinRangeWeapon.Read(exINI, pSection, "WarpInMinRangeWeapon"); @@ -488,6 +492,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->UIDescription) .Process(this->LowSelectionPriority) .Process(this->MindControlRangeLimit) + .Process(this->FactoryPlant_Multiplier) .Process(this->InterceptorType) @@ -511,6 +516,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->NoManualMove) .Process(this->InitialStrength) .Process(this->ReloadInTransport) + .Process(this->ForbidParallelAIQueues) .Process(this->ShieldType) .Process(this->PassengerDeletionType) @@ -549,6 +555,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->ChronoMinimumDelay) .Process(this->ChronoRangeMinimum) .Process(this->ChronoDelay) + .Process(this->ChronoSpherePreDelay) + .Process(this->ChronoSphereDelay) .Process(this->WarpInWeapon) .Process(this->WarpInMinRangeWeapon) .Process(this->WarpOutWeapon) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 42f40c063b..220c84f51a 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -33,6 +33,7 @@ class TechnoTypeExt Valueable RadarJamRadius; Nullable InhibitorRange; Nullable DesignatorRange; + Valueable FactoryPlant_Multiplier; Valueable MindControlRangeLimit; std::unique_ptr InterceptorType; @@ -53,6 +54,7 @@ class TechnoTypeExt Valueable NoManualMove; Nullable InitialStrength; Valueable ReloadInTransport; + Valueable ForbidParallelAIQueues; Valueable ShieldType; std::unique_ptr PassengerDeletionType; @@ -93,6 +95,8 @@ class TechnoTypeExt Nullable ChronoMinimumDelay; Nullable ChronoRangeMinimum; Nullable ChronoDelay; + Nullable ChronoSpherePreDelay; + Nullable ChronoSphereDelay; Valueable WarpInWeapon; Nullable WarpInMinRangeWeapon; @@ -268,6 +272,7 @@ class TechnoTypeExt , RadarJamRadius { 0 } , InhibitorRange {} , DesignatorRange { } + , FactoryPlant_Multiplier { 1.0 } , MindControlRangeLimit {} , InterceptorType { nullptr } @@ -288,6 +293,7 @@ class TechnoTypeExt , NoManualMove { false } , InitialStrength {} , ReloadInTransport { false } + , ForbidParallelAIQueues { false } , ShieldType {} , PassengerDeletionType { nullptr } @@ -299,6 +305,8 @@ class TechnoTypeExt , ChronoMinimumDelay {} , ChronoRangeMinimum {} , ChronoDelay {} + , ChronoSpherePreDelay {} + , ChronoSphereDelay {} , WarpInWeapon {} , WarpInMinRangeWeapon {} , WarpOutWeapon {} diff --git a/src/Ext/TechnoType/Hooks.Teleport.cpp b/src/Ext/TechnoType/Hooks.Teleport.cpp index 8c8a80321d..84c341ba47 100644 --- a/src/Ext/TechnoType/Hooks.Teleport.cpp +++ b/src/Ext/TechnoType/Hooks.Teleport.cpp @@ -153,3 +153,39 @@ DEFINE_HOOK(0x729B5D, TunnelLocomotionClass_DrawMatrix_Tilt, 0x8) // DEFINE_JUMP(VTABLE, 0x7F5A4C, 0x5142A0);//TunnelLocomotionClass_Shadow_Matrix : just use hover's to save my time // Since I've already invalidated the key for tilted vxls when reimplementing the shadow drawing code, this is no longer necessary + +DEFINE_HOOK(0x7197E4, TeleportLocomotionClass_Process_ChronospherePreDelay, 0x6) +{ + GET(TeleportLocomotionClass*, pThis, ESI); + + auto const pExt = TechnoExt::ExtMap.Find(pThis->Owner); + pExt->IsBeingChronoSphered = true; + R->ECX(pExt->TypeExtData->ChronoSpherePreDelay.Get(RulesExt::Global()->ChronoSpherePreDelay)); + + return 0; +} + +DEFINE_HOOK(0x719BD9, TeleportLocomotionClass_Process_ChronosphereDelay2, 0x6) +{ + GET(TeleportLocomotionClass*, pThis, ESI); + + auto const pExt = TechnoExt::ExtMap.Find(pThis->Owner); + + if (!pExt->IsBeingChronoSphered) + return 0; + + int delay = pExt->TypeExtData->ChronoSphereDelay.Get(RulesExt::Global()->ChronoSphereDelay); + + if (delay > 0) + { + pThis->Owner->WarpingOut = true; + pExt->HasRemainingWarpInDelay = true; + pExt->LastWarpInDelay = Math::max(delay, pExt->LastWarpInDelay); + } + else + { + pExt->IsBeingChronoSphered = false; + } + + return 0; +} diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index 808573f431..e177510592 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -11,6 +11,7 @@ #include "Body.h" #include #include +#include #include #include #include @@ -193,92 +194,6 @@ DEFINE_HOOK(0x700C58, TechnoClass_CanPlayerMove_NoManualMove, 0x6) return 0; } -DEFINE_HOOK(0x73CF46, UnitClass_Draw_It_KeepUnitVisible, 0x6) -{ - enum { KeepUnitVisible = 0x73CF62 }; - - GET(UnitClass*, pThis, ESI); - - if (pThis->Deploying || pThis->Undeploying) - { - auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); - - if (pTypeExt->DeployingAnim_KeepUnitVisible) - return KeepUnitVisible; - } - - return 0; -} - -// Ares hooks in at 739B8A, this goes before it and skips it if needed. -DEFINE_HOOK(0x739B7C, UnitClass_Deploy_DeployDir, 0x6) -{ - enum { SkipAnim = 0x739C70, PlayAnim = 0x739B9E }; - - GET(UnitClass*, pThis, ESI); - - if (!pThis->InAir) - { - if (pThis->Type->DeployingAnim) - { - if (TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->DeployingAnim_AllowAnyDirection) - return PlayAnim; - - return 0; - } - - pThis->Deployed = true; - } - - return SkipAnim; -} - -DEFINE_HOOK_AGAIN(0x739D8B, UnitClass_DeployUndeploy_DeployAnim, 0x5) -DEFINE_HOOK(0x739BA8, UnitClass_DeployUndeploy_DeployAnim, 0x5) -{ - enum { Deploy = 0x739C20, DeployUseUnitDrawer = 0x739C0A, Undeploy = 0x739E04, UndeployUseUnitDrawer = 0x739DEE }; - - GET(UnitClass*, pThis, ESI); - - bool isDeploying = R->Origin() == 0x739BA8; - - if (auto const pExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())) - { - if (auto const pAnim = GameCreate(pThis->Type->DeployingAnim, - pThis->Location, 0, 1, 0x600, 0, - !isDeploying ? pExt->DeployingAnim_ReverseForUndeploy : false)) - { - pThis->DeployAnim = pAnim; - pAnim->SetOwnerObject(pThis); - - if (pExt->DeployingAnim_UseUnitDrawer) - return isDeploying ? DeployUseUnitDrawer : UndeployUseUnitDrawer; - } - else - { - pThis->DeployAnim = nullptr; - } - } - - return isDeploying ? Deploy : Undeploy; -} - -DEFINE_HOOK_AGAIN(0x739E81, UnitClass_DeployUndeploy_DeploySound, 0x6) -DEFINE_HOOK(0x739C86, UnitClass_DeployUndeploy_DeploySound, 0x6) -{ - enum { DeployReturn = 0x739CBF, UndeployReturn = 0x739EB8 }; - - GET(UnitClass*, pThis, ESI); - - bool isDeploying = R->Origin() == 0x739C86; - bool isDoneWithDeployUndeploy = isDeploying ? pThis->Deployed : !pThis->Deployed; - - if (isDoneWithDeployUndeploy) - return 0; // Only play sound when done with deploying or undeploying. - - return isDeploying ? DeployReturn : UndeployReturn; -} - // Issue #503 // Author : Otamaa DEFINE_HOOK(0x4AE670, DisplayClass_GetToolTip_EnemyUIName, 0x8) @@ -847,3 +762,31 @@ DEFINE_HOOK(0x737F05, UnitClass_ReceiveDamage_SinkingWake, 0x6) return 0x737F0B; } + +DEFINE_HOOK(0x711F39, TechnoTypeClass_CostOf_FactoryPlant, 0x8) +{ + GET(TechnoTypeClass*, pThis, ESI); + GET(HouseClass*, pHouse, EDI); + REF_STACK(float, mult, STACK_OFFSET(0x10, -0x8)); + + auto const pHouseExt = HouseExt::ExtMap.Find(pHouse); + + if (pHouseExt->RestrictedFactoryPlants.size() > 0) + mult *= pHouseExt->GetRestrictedFactoryPlantMult(pThis); + + return 0; +} + +DEFINE_HOOK(0x711FDF, TechnoTypeClass_RefundAmount_FactoryPlant, 0x8) +{ + GET(TechnoTypeClass*, pThis, ESI); + GET(HouseClass*, pHouse, EDI); + REF_STACK(float, mult, STACK_OFFSET(0x10, -0x4)); + + auto const pHouseExt = HouseExt::ExtMap.Find(pHouse); + + if (pHouseExt->RestrictedFactoryPlants.size() > 0) + mult *= pHouseExt->GetRestrictedFactoryPlantMult(pThis); + + return 0; +} diff --git a/src/Ext/TerrainType/Body.cpp b/src/Ext/TerrainType/Body.cpp index 0b2badef00..c0a655f609 100644 --- a/src/Ext/TerrainType/Body.cpp +++ b/src/Ext/TerrainType/Body.cpp @@ -1,5 +1,6 @@ #include "Body.h" +#include #include #include #include @@ -18,6 +19,14 @@ int TerrainTypeExt::ExtData::GetCellsPerAnim() return GeneralUtils::GetRangedRandomOrSingleValue(this->SpawnsTiberium_CellsPerAnim.Get()); } +void TerrainTypeExt::ExtData::PlayDestroyEffects(const CoordStruct& coords) +{ + VocClass::PlayIndexAtPos(this->DestroySound, coords); + + if (auto const pAnimType = this->DestroyAnim) + GameCreate(pAnimType, coords); +} + void TerrainTypeExt::Remove(TerrainClass* pTerrain) { if (!pTerrain) @@ -46,6 +55,11 @@ void TerrainTypeExt::ExtData::Serialize(T& Stm) .Process(this->MinimapColor) .Process(this->IsPassable) .Process(this->CanBeBuiltOn) + .Process(this->HasDamagedFrames) + .Process(this->HasCrumblingFrames) + .Process(this->CrumblingSound) + .Process(this->AnimationLength) + .Process(this->PaletteFile) ; } @@ -71,14 +85,29 @@ void TerrainTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->IsPassable.Read(exINI, pSection, "IsPassable"); this->CanBeBuiltOn.Read(exINI, pSection, "CanBeBuiltOn"); + this->HasDamagedFrames.Read(exINI, pSection, "HasDamagedFrames"); + this->HasCrumblingFrames.Read(exINI, pSection, "HasCrumblingFrames"); + this->CrumblingSound.Read(exINI, pSection, "CrumblingSound"); + this->AnimationLength.Read(exINI, pSection, "AnimationLength"); + //Strength is already part of ObjecTypeClass::ReadIni Duh! //this->TerrainStrength.Read(exINI, pSection, "Strength"); + + auto const pArtINI = &CCINIClass::INI_Art(); + auto pArtSection = pThis->ImageFile; + + this->PaletteFile.Read(pArtINI, pArtSection, "Palette"); + this->Palette = GeneralUtils::BuildPalette(this->PaletteFile); + + if (GeneralUtils::IsValidString(this->PaletteFile) && !this->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, this->PaletteFile); } void TerrainTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) { Extension::LoadFromStream(Stm); this->Serialize(Stm); + this->Palette = GeneralUtils::BuildPalette(this->PaletteFile); } void TerrainTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) diff --git a/src/Ext/TerrainType/Body.h b/src/Ext/TerrainType/Body.h index 7d14426044..560b603549 100644 --- a/src/Ext/TerrainType/Body.h +++ b/src/Ext/TerrainType/Body.h @@ -26,6 +26,13 @@ class TerrainTypeExt Nullable MinimapColor; Valueable IsPassable; Valueable CanBeBuiltOn; + Valueable HasDamagedFrames; + Valueable HasCrumblingFrames; + ValueableIdx CrumblingSound; + Nullable AnimationLength; + + PhobosFixedString<32u> PaletteFile; + DynamicVectorClass* Palette; // Intentionally not serialized - rebuilt from the palette file on load. ExtData(TerrainTypeClass* OwnerObject) : Extension(OwnerObject) , SpawnsTiberium_Type { 0 } @@ -37,6 +44,12 @@ class TerrainTypeExt , MinimapColor {} , IsPassable { false } , CanBeBuiltOn { false } + , HasDamagedFrames { false } + , HasCrumblingFrames { false } + , CrumblingSound {} + , AnimationLength {} + , PaletteFile {} + , Palette {} { } virtual ~ExtData() = default; @@ -50,6 +63,7 @@ class TerrainTypeExt int GetTiberiumGrowthStage(); int GetCellsPerAnim(); + void PlayDestroyEffects(const CoordStruct& coords); private: template diff --git a/src/Ext/TerrainType/Hooks.cpp b/src/Ext/TerrainType/Hooks.cpp index e05c757700..9e77ce4515 100644 --- a/src/Ext/TerrainType/Hooks.cpp +++ b/src/Ext/TerrainType/Hooks.cpp @@ -1,17 +1,20 @@ #include "Body.h" +#include #include +#include +#include #include #include -#include -#include +#include #include namespace TerrainTypeTemp { TerrainTypeClass* pCurrentType = nullptr; TerrainTypeExt::ExtData* pCurrentExt = nullptr; + double PriorHealthRatio = 0.0; } DEFINE_HOOK(0x71C84D, TerrainClass_AI_Animated, 0x6) @@ -22,7 +25,9 @@ DEFINE_HOOK(0x71C84D, TerrainClass_AI_Animated, 0x6) if (pThis->Type->IsAnimated) { - if (pThis->Animation.Value == pThis->Type->GetImage()->Frames / 2) + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + + if (pThis->Animation.Value == pTypeExt->AnimationLength.Get(pThis->Type->GetImage()->Frames / (2 * (pTypeExt->HasDamagedFrames + 1)))) { pThis->Animation.Value = 0; pThis->Animation.Start(0); @@ -30,7 +35,6 @@ DEFINE_HOOK(0x71C84D, TerrainClass_AI_Animated, 0x6) // Spawn tiberium if enabled. if (pThis->Type->SpawnsTiberium) { - auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); auto pCell = pThis->GetCell(); int cellCount = pTypeExt->GetCellsPerAnim(); @@ -51,6 +55,90 @@ DEFINE_HOOK(0x71C84D, TerrainClass_AI_Animated, 0x6) return SkipGameCode; } +DEFINE_HOOK(0x71C812, TerrainClass_AI_Crumbling, 0x6) +{ + enum { ReturnFromFunction = 0x71C839, SkipCheck = 0x71C7C2 }; + + GET(TerrainClass*, pThis, ESI); + + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->HasDamagedFrames && pThis->Health > 0) + { + if (!pThis->Type->IsAnimated && !pThis->Type->IsFlammable) + LogicClass::Instance->Remove(pThis); + + pThis->IsCrumbling = false; + + return SkipCheck; + } + + int animationLength = pTypeExt->AnimationLength.Get(pThis->Type->GetImage()->Frames / (2 * (pTypeExt->HasDamagedFrames + 1))); + int currentStage = pThis->Animation.Value + (pThis->Type->IsAnimated ? animationLength * (pTypeExt->HasDamagedFrames + 1) : 0 + pTypeExt->HasDamagedFrames); + + if (currentStage + 1 == pThis->Type->GetImage()->Frames / 2) + { + pTypeExt->PlayDestroyEffects(pThis->GetCoords()); + TerrainTypeExt::Remove(pThis); + } + + return ReturnFromFunction; +} + +DEFINE_HOOK(0x71C1FE, TerrainClass_Draw_PickFrame, 0x6) +{ + enum { SkipGameCode = 0x71C234 }; + + GET(int, frame, EBX); + + GET(TerrainClass*, pThis, ESI); + + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + bool isDamaged = pTypeExt->HasDamagedFrames && pThis->GetHealthPercentage() <= RulesExt::Global()->ConditionYellow_Terrain.Get(RulesClass::Instance->ConditionYellow); + + if (pThis->Type->IsAnimated) + { + int animLength = pTypeExt->AnimationLength.Get(pThis->Type->GetImage()->Frames / (2 * (pTypeExt->HasDamagedFrames + 1))); + + if (pTypeExt->HasCrumblingFrames && pThis->IsCrumbling) + frame = (animLength * (pTypeExt->HasDamagedFrames + 1)) + 1 + pThis->Animation.Value; + else + frame = pThis->Animation.Value + (isDamaged * animLength); + } + else + { + if (pTypeExt->HasCrumblingFrames && pThis->IsCrumbling) + frame = 1 + pThis->Animation.Value; + else if (isDamaged) + frame = 1; + } + + R->EBX(frame); + return SkipGameCode; +} + +DEFINE_HOOK(0x71C2BC, TerrainClass_Draw_Palette, 0x6) +{ + GET(TerrainClass*, pThis, ESI); + + auto const pCell = pThis->GetCell(); + int wallOwnerIndex = pCell->WallOwnerIndex; + int colorSchemeIndex = HouseClass::CurrentPlayer->ColorSchemeIndex; + + if (wallOwnerIndex >= 0) + colorSchemeIndex = HouseClass::Array->Items[wallOwnerIndex]->ColorSchemeIndex; + + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + + if (pTypeExt->Palette) + { + R->EDX(pTypeExt->Palette->Items[colorSchemeIndex]->LightConvert); + R->EBP(pCell->Intensity_Normal); + } + + return 0; +} + // Overrides Ares hook at 0x5F4FF9, required for animated terrain cause game & Ares check SpawnsTiberium instead of IsAnimated DEFINE_HOOK(0x5F4FEF, ObjectClass_Unlimbo_UpdateTerrain, 0x6) { @@ -122,6 +210,51 @@ DEFINE_HOOK(0x48381D, CellClass_SpreadTiberium_CellSpread, 0x6) return 0; } +DEFINE_HOOK(0x71C6EE, TerrainClass_FireOut_Crumbling, 0x6) +{ + enum { StartCrumbling = 0x71C6F8, Skip = 0x71C72B }; + + GET(TerrainClass*, pThis, ESI); + + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + + if (!pThis->IsCrumbling && pTypeExt->HasCrumblingFrames) + { + // Needs to be added to the logic layer for the anim to work. + LogicClass::Instance->AddObject(pThis, false); + VocClass::PlayIndexAtPos(pTypeExt->CrumblingSound, pThis->GetCoords()); + + return StartCrumbling; + } + + return Skip; +} + +DEFINE_HOOK(0x71B965, TerrainClass_TakeDamage_SetContext, 0x8) +{ + GET(TerrainClass*, pThis, ESI); + + TerrainTypeTemp::PriorHealthRatio = pThis->GetHealthPercentage(); + + return 0; +} + +DEFINE_HOOK(0x71B98B, TerrainClass_TakeDamage_RefreshDamageFrame, 0x7) +{ + GET(TerrainClass*, pThis, ESI); + + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + double condYellow = RulesExt::Global()->ConditionYellow_Terrain.Get(RulesClass::Instance->ConditionYellow); + + if (!pThis->Type->IsAnimated && pTypeExt->HasDamagedFrames && TerrainTypeTemp::PriorHealthRatio > condYellow && pThis->GetHealthPercentage() <= condYellow) + { + pThis->IsCrumbling = true; // Dirty hack to get game to redraw the art reliably. + LogicClass::Instance->AddObject(pThis, false); + } + + return 0; +} + //This one on Very end of it , let everything play first DEFINE_HOOK(0x71BB2C, TerrainClass_TakeDamage_NowDead_Add, 0x6) { @@ -129,15 +262,22 @@ DEFINE_HOOK(0x71BB2C, TerrainClass_TakeDamage_NowDead_Add, 0x6) //saved for later usage ! //REF_STACK(args_ReceiveDamage const, ReceiveDamageArgs, STACK_OFFSET(0x3C, 0x4)); - if (auto const pTerrainExt = TerrainTypeExt::ExtMap.Find(pThis->Type)) + auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pThis->Type); + + // Skip over the removal of the tree as well as destroy sound/anim (for now) if the tree has crumble animation. + if (pThis->IsCrumbling && pTypeExt->HasCrumblingFrames) { - auto const nCoords = pThis->GetCoords(); - VocClass::PlayIndexAtPos(pTerrainExt->DestroySound, nCoords); + // Needs to be added to the logic layer for the anim to work. + LogicClass::Instance->AddObject(pThis, false); + VocClass::PlayIndexAtPos(pTypeExt->CrumblingSound, pThis->GetCoords()); + pThis->Mark(MarkType::Change); + pThis->Disappear(true); - if (pTerrainExt->DestroyAnim) - GameCreate(pTerrainExt->DestroyAnim, nCoords); + return 0x71BB79; } + pTypeExt->PlayDestroyEffects(pThis->GetCoords()); + return 0; } @@ -195,3 +335,4 @@ DEFINE_HOOK(0x568432, MapClass_PlaceDown_0x0TerrainTypes, 0x8) return 0; } + diff --git a/src/Ext/Unit/Hooks.Unload.cpp b/src/Ext/Unit/Hooks.Unload.cpp index 98a5af5dc7..31a759b779 100644 --- a/src/Ext/Unit/Hooks.Unload.cpp +++ b/src/Ext/Unit/Hooks.Unload.cpp @@ -1,7 +1,9 @@ +#include #include #include +#include -#include +#include #include #include #include @@ -106,3 +108,172 @@ DEFINE_HOOK(0x73DE78, UnitClass_Unload_ChangeAmmo, 0x6) // converters UnitDeployConvertHelpers::ChangeAmmoOnUnloading(R); return Continue; } + +#pragma region SimpleDeployer + +namespace SimpleDeployerTemp +{ + bool HoverDeployedToLand = false; +} + +DEFINE_HOOK(0x73CF46, UnitClass_Draw_It_KeepUnitVisible, 0x6) +{ + enum { KeepUnitVisible = 0x73CF62 }; + + GET(UnitClass*, pThis, ESI); + + if (pThis->Deploying || pThis->Undeploying) + { + auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + + if (pTypeExt->DeployingAnim_KeepUnitVisible) + return KeepUnitVisible; + } + + return 0; +} + +DEFINE_HOOK(0x73D6E6, UnitClass_Unload_Subterranean, 0x6) +{ + enum { ReturnFromFunction = 0x73DFB0 }; + + GET(UnitClass*, pThis, ESI); + + if (pThis->Type->Locomotor == LocomotionClass::CLSIDs::Tunnel) + { + auto const pLoco = static_cast(pThis->Locomotor.GetInterfacePtr()); + + if (pLoco->State != TunnelLocomotionClass::State::Idle) + return ReturnFromFunction; + } + + return 0; +} + +// Ares hooks in at 739B8A, this goes before it and skips it if needed. +DEFINE_HOOK(0x739B7C, UnitClass_Deploy_DeployDir, 0x6) +{ + enum { SkipAnim = 0x739C70, PlayAnim = 0x739B9E }; + + GET(UnitClass*, pThis, ESI); + + if (!pThis->InAir) + { + if (pThis->Type->DeployingAnim) + { + if (TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->DeployingAnim_AllowAnyDirection) + return PlayAnim; + + return 0; + } + + pThis->Deployed = true; + } + + return SkipAnim; +} + +DEFINE_HOOK_AGAIN(0x739D8B, UnitClass_DeployUndeploy_DeployAnim, 0x5) +DEFINE_HOOK(0x739BA8, UnitClass_DeployUndeploy_DeployAnim, 0x5) +{ + enum { Deploy = 0x739C20, DeployUseUnitDrawer = 0x739C0A, Undeploy = 0x739E04, UndeployUseUnitDrawer = 0x739DEE }; + + GET(UnitClass*, pThis, ESI); + + bool isDeploying = R->Origin() == 0x739BA8; + + if (auto const pExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())) + { + if (auto const pAnim = GameCreate(pThis->Type->DeployingAnim, + pThis->Location, 0, 1, 0x600, 0, + !isDeploying ? pExt->DeployingAnim_ReverseForUndeploy : false)) + { + pThis->DeployAnim = pAnim; + pAnim->SetOwnerObject(pThis); + + if (pExt->DeployingAnim_UseUnitDrawer) + return isDeploying ? DeployUseUnitDrawer : UndeployUseUnitDrawer; + } + else + { + pThis->DeployAnim = nullptr; + } + } + + return isDeploying ? Deploy : Undeploy; +} + +DEFINE_HOOK_AGAIN(0x739E81, UnitClass_DeployUndeploy_DeploySound, 0x6) +DEFINE_HOOK(0x739C86, UnitClass_DeployUndeploy_DeploySound, 0x6) +{ + enum { DeployReturn = 0x739CBF, UndeployReturn = 0x739EB8 }; + + GET(UnitClass*, pThis, ESI); + + bool isDeploying = R->Origin() == 0x739C86; + bool isDoneWithDeployUndeploy = isDeploying ? pThis->Deployed : !pThis->Deployed; + + if (isDoneWithDeployUndeploy) + return 0; // Only play sound when done with deploying or undeploying. + + return isDeploying ? DeployReturn : UndeployReturn; +} + +DEFINE_HOOK(0x739CBF, UnitClass_Deploy_DeployToLandHover, 0x5) +{ + GET(UnitClass*, pThis, ESI); + + if (pThis->Deployed && pThis->Type->DeployToLand && pThis->Type->Locomotor == LocomotionClass::CLSIDs::Hover) + SimpleDeployerTemp::HoverDeployedToLand = true; + + return 0; +} + +DEFINE_HOOK_AGAIN(0x73DED8, UnitClass_Unload_DeployToLandHover, 0x7) +DEFINE_HOOK(0x73E5B1, UnitClass_Unload_DeployToLandHover, 0x8) +{ + if (SimpleDeployerTemp::HoverDeployedToLand) + { + GET(UnitClass*, pThis, ESI); + + // Ares' DeployToLand 'fix' for Hover IsSimpleDeployer vehicles does not set/reset certain values + // and has a chance to get stuck in Unload mission as a result, following should remedy that. + SimpleDeployerTemp::HoverDeployedToLand = false; + pThis->SetHeight(0); + pThis->InAir = false; + pThis->ForceMission(Mission::Guard); + } + + return 0; +} + +// Do not display hover bobbing when landed during deploying. +DEFINE_HOOK(0x513D2C, HoverLocomotionClass_ProcessBobbing_DeployToLand, 0x6) +{ + enum { SkipBobbing = 0x513F2A }; + + GET(LocomotionClass*, pThis, ECX); + + if (auto const pUnit = abstract_cast(pThis->Owner)) + { + if (pUnit->Deploying && pUnit->Type->DeployToLand) + return SkipBobbing; + } + + return 0; +} + +// DeployToLand units increment WalkingFramesSoFar on every frame, on hover units this causes weird behaviour with move sounds etc. +DEFINE_HOOK(0x4DA9C1, FootClass_AI_DeployToLand, 0x6) +{ + enum { SkipGameCode = 0x4DAA01 }; + + GET(FootClass*, pThis, ESI); + + if (pThis->GetTechnoType()->Locomotor == LocomotionClass::CLSIDs::Hover) + return SkipGameCode; + + return 0; +} + +#pragma endregion diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 94563b0114..f94a1ba724 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -122,6 +122,15 @@ bool WarheadTypeExt::ExtData::EligibleForFullMapDetonation(TechnoClass* pTechno, return true; } +// Wrapper for MapClass::DamageArea() that sets a pointer in WarheadTypeExt::ExtData that is used to figure 'intended' target of the Warhead detonation, if set and there's no CellSpread. +DamageAreaResult WarheadTypeExt::ExtData::DamageAreaWithTarget(const CoordStruct& coords, int damage, TechnoClass* pSource, WarheadTypeClass* pWH, bool affectsTiberium, HouseClass* pSourceHouse, TechnoClass* pTarget) +{ + this->DamageAreaTarget = pTarget; + auto result = MapClass::DamageArea(coords, damage, pSource, pWH, true, pSourceHouse); + this->DamageAreaTarget = nullptr; + return result; +} + // ============================= // load / save @@ -171,10 +180,12 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Crit_ApplyChancePerTarget.Read(exINI, pSection, "Crit.ApplyChancePerTarget"); this->Crit_ExtraDamage.Read(exINI, pSection, "Crit.ExtraDamage"); this->Crit_Warhead.Read(exINI, pSection, "Crit.Warhead"); + this->Crit_Warhead_FullDetonation.Read(exINI, pSection, "Crit.Warhead.FullDetonation"); this->Crit_Affects.Read(exINI, pSection, "Crit.Affects"); this->Crit_AffectsHouses.Read(exINI, pSection, "Crit.AffectsHouses"); this->Crit_AnimList.Read(exINI, pSection, "Crit.AnimList"); this->Crit_AnimList_PickRandom.Read(exINI, pSection, "Crit.AnimList.PickRandom"); + this->Crit_ActiveChanceAnims.Read(exINI, pSection, "Crit.ActiveChanceAnims"); this->Crit_AnimOnAffectedTargets.Read(exINI, pSection, "Crit.AnimOnAffectedTargets"); this->Crit_AffectBelowPercent.Read(exINI, pSection, "Crit.AffectBelowPercent"); this->Crit_SuppressWhenIntercepted.Read(exINI, pSection, "Crit.SuppressWhenIntercepted"); @@ -186,6 +197,7 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Shield_Break.Read(exINI, pSection, "Shield.Break"); this->Shield_BreakAnim.Read(exINI, pSection, "Shield.BreakAnim"); this->Shield_HitAnim.Read(exINI, pSection, "Shield.HitAnim"); + this->Shield_SkipHitAnim.Read(exINI, pSection, "Shield.SkipHitAnim"); this->Shield_HitFlash.Read(exINI, pSection, "Shield.HitFlash"); this->Shield_BreakWeapon.Read(exINI, pSection, "Shield.BreakWeapon"); this->Shield_AbsorbPercent.Read(exINI, pSection, "Shield.AbsorbPercent"); @@ -231,12 +243,15 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Debris_Conventional.Read(exINI, pSection, "Debris.Conventional"); this->DetonateOnAllMapObjects.Read(exINI, pSection, "DetonateOnAllMapObjects"); + this->DetonateOnAllMapObjects_Full.Read(exINI, pSection, "DetonateOnAllMapObjects.Full"); this->DetonateOnAllMapObjects_RequireVerses.Read(exINI, pSection, "DetonateOnAllMapObjects.RequireVerses"); this->DetonateOnAllMapObjects_AffectTargets.Read(exINI, pSection, "DetonateOnAllMapObjects.AffectTargets"); this->DetonateOnAllMapObjects_AffectHouses.Read(exINI, pSection, "DetonateOnAllMapObjects.AffectHouses"); this->DetonateOnAllMapObjects_AffectTypes.Read(exINI, pSection, "DetonateOnAllMapObjects.AffectTypes"); this->DetonateOnAllMapObjects_IgnoreTypes.Read(exINI, pSection, "DetonateOnAllMapObjects.IgnoreTypes"); + this->Nonprovocative.Read(exINI, pSection, "Nonprovocative"); + this->AttachEffect_AttachTypes.Read(exINI, pSection, "AttachEffect.AttachTypes"); this->AttachEffect_RemoveTypes.Read(exINI, pSection, "AttachEffect.RemoveTypes"); exINI.ParseStringList(this->AttachEffect_RemoveGroups, pSection, "AttachEffect.RemoveGroups"); @@ -377,10 +392,12 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Crit_ApplyChancePerTarget) .Process(this->Crit_ExtraDamage) .Process(this->Crit_Warhead) + .Process(this->Crit_Warhead_FullDetonation) .Process(this->Crit_Affects) .Process(this->Crit_AffectsHouses) .Process(this->Crit_AnimList) .Process(this->Crit_AnimList_PickRandom) + .Process(this->Crit_ActiveChanceAnims) .Process(this->Crit_AnimOnAffectedTargets) .Process(this->Crit_AffectBelowPercent) .Process(this->Crit_SuppressWhenIntercepted) @@ -391,6 +408,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Shield_Break) .Process(this->Shield_BreakAnim) .Process(this->Shield_HitAnim) + .Process(this->Shield_SkipHitAnim) + .Process(this->Shield_HitFlash) .Process(this->Shield_BreakWeapon) .Process(this->Shield_AbsorbPercent) .Process(this->Shield_PassPercent) @@ -436,6 +455,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Debris_Conventional) .Process(this->DetonateOnAllMapObjects) + .Process(this->DetonateOnAllMapObjects_Full) .Process(this->DetonateOnAllMapObjects_RequireVerses) .Process(this->DetonateOnAllMapObjects_AffectTargets) .Process(this->DetonateOnAllMapObjects_AffectHouses) @@ -459,6 +479,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->InflictLocomotor) .Process(this->RemoveInflictedLocomotor) + .Process(this->Nonprovocative) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) @@ -468,6 +490,7 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->RemainingAnimCreationInterval) .Process(this->PossibleCellSpreadDetonate) .Process(this->Reflected) + .Process(this->DamageAreaTarget) ; } diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 142d5359c8..39810cc886 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -51,15 +51,16 @@ class WarheadTypeExt Valueable Rocker_AmplitudeMultiplier; Nullable Rocker_AmplitudeOverride; - Valueable Crit_Chance; Valueable Crit_ApplyChancePerTarget; Valueable Crit_ExtraDamage; Valueable Crit_Warhead; + Valueable Crit_Warhead_FullDetonation; Valueable Crit_Affects; Valueable Crit_AffectsHouses; ValueableVector Crit_AnimList; Nullable Crit_AnimList_PickRandom; + ValueableVector Crit_ActiveChanceAnims; Valueable Crit_AnimOnAffectedTargets; Valueable Crit_AffectBelowPercent; Valueable Crit_SuppressWhenIntercepted; @@ -70,6 +71,7 @@ class WarheadTypeExt Valueable Shield_Break; Valueable Shield_BreakAnim; Valueable Shield_HitAnim; + Valueable Shield_SkipHitAnim; Valueable Shield_HitFlash; Nullable Shield_BreakWeapon; @@ -119,6 +121,7 @@ class WarheadTypeExt Valueable Debris_Conventional; Valueable DetonateOnAllMapObjects; + Valueable DetonateOnAllMapObjects_Full; Valueable DetonateOnAllMapObjects_RequireVerses; Valueable DetonateOnAllMapObjects_AffectTargets; Valueable DetonateOnAllMapObjects_AffectHouses; @@ -130,6 +133,8 @@ class WarheadTypeExt Valueable InflictLocomotor; Valueable RemoveInflictedLocomotor; + Valueable Nonprovocative; + ValueableVector AttachEffect_AttachTypes; ValueableVector AttachEffect_RemoveTypes; std::vector AttachEffect_RemoveGroups; @@ -151,11 +156,13 @@ class WarheadTypeExt double Crit_RandomBuffer; double Crit_CurrentChance; bool Crit_Active; + bool InDamageArea; bool WasDetonatedOnAllMapObjects; bool Splashed; bool Reflected; int RemainingAnimCreationInterval; bool PossibleCellSpreadDetonate; + TechnoClass* DamageAreaTarget; private: Valueable Shield_Respawn_Rate_InMinutes; @@ -197,10 +204,12 @@ class WarheadTypeExt , Crit_ApplyChancePerTarget { false } , Crit_ExtraDamage { 0 } , Crit_Warhead {} + , Crit_Warhead_FullDetonation { true } , Crit_Affects { AffectedTarget::All } , Crit_AffectsHouses { AffectedHouse::All } , Crit_AnimList {} , Crit_AnimList_PickRandom {} + , Crit_ActiveChanceAnims {} , Crit_AnimOnAffectedTargets { false } , Crit_AffectBelowPercent { 1.0 } , Crit_SuppressWhenIntercepted { false } @@ -211,6 +220,7 @@ class WarheadTypeExt , Shield_Break { false } , Shield_BreakAnim {} , Shield_HitAnim {} + , Shield_SkipHitAnim { false } , Shield_HitFlash { true } , Shield_BreakWeapon {} , Shield_AbsorbPercent {} @@ -260,6 +270,7 @@ class WarheadTypeExt , Debris_Conventional { false } , DetonateOnAllMapObjects { false } + , DetonateOnAllMapObjects_Full { true } , DetonateOnAllMapObjects_RequireVerses { false } , DetonateOnAllMapObjects_AffectTargets { AffectedTarget::None } , DetonateOnAllMapObjects_AffectHouses { AffectedHouse::None } @@ -271,6 +282,8 @@ class WarheadTypeExt , InflictLocomotor { false } , RemoveInflictedLocomotor { false } + , Nonprovocative { false } + , AttachEffect_AttachTypes {} , AttachEffect_RemoveTypes {} , AttachEffect_RemoveGroups {} @@ -290,11 +303,13 @@ class WarheadTypeExt , Crit_RandomBuffer { 0.0 } , Crit_CurrentChance { 0.0 } , Crit_Active { false } + , InDamageArea { true } , WasDetonatedOnAllMapObjects { false } , Splashed { false } , Reflected { false } , RemainingAnimCreationInterval { 0 } , PossibleCellSpreadDetonate {false} + , DamageAreaTarget {} { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); @@ -320,6 +335,7 @@ class WarheadTypeExt // Detonate.cpp void Detonate(TechnoClass* pOwner, HouseClass* pHouse, BulletExt::ExtData* pBullet, CoordStruct coords); void InterceptBullets(TechnoClass* pOwner, WeaponTypeClass* pWeapon, CoordStruct coords); + DamageAreaResult DamageAreaWithTarget(const CoordStruct& coords, int damage, TechnoClass* pSource, WarheadTypeClass* pWH, bool affectsTiberium, HouseClass* pSourceHouse, TechnoClass* pTarget); private: void DetonateOnOneUnit(HouseClass* pHouse, TechnoClass* pTarget, TechnoClass* pOwner = nullptr, bool bulletWasIntercepted = false); void ApplyRemoveDisguise(HouseClass* pHouse, TechnoClass* pTarget); diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index 9d4b48fb92..356e1426a1 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -111,6 +111,12 @@ void WarheadTypeExt::ExtData::Detonate(TechnoClass* pOwner, HouseClass* pHouse, if (!this->Crit_ApplyChancePerTarget) this->Crit_RandomBuffer = ScenarioClass::Instance->Random.RandomDouble(); + if (this->Crit_ActiveChanceAnims.size() > 0 && this->Crit_CurrentChance > 0.0) + { + int idx = ScenarioClass::Instance->Random.RandomRanged(0, this->Crit_ActiveChanceAnims.size() - 1); + GameCreate(this->Crit_ActiveChanceAnims[idx], coords); + } + bool bulletWasIntercepted = pBulletExt && pBulletExt->InterceptedStatus == InterceptedStatus::Intercepted; const float cellSpread = this->OwnerObject()->CellSpread; @@ -129,6 +135,11 @@ void WarheadTypeExt::ExtData::Detonate(TechnoClass* pOwner, HouseClass* pHouse, this->DetonateOnOneUnit(pHouse, pTarget, pOwner, bulletWasIntercepted); } } + else if (this->DamageAreaTarget) + { + if (coords.DistanceFrom(this->DamageAreaTarget->GetCoords()) < Unsorted::LeptonsPerCell / 4) + this->DetonateOnOneUnit(pHouse, this->DamageAreaTarget, pOwner, bulletWasIntercepted); + } } } @@ -331,7 +342,12 @@ void WarheadTypeExt::ExtData::ApplyCrit(HouseClass* pHouse, TechnoClass* pTarget auto damage = this->Crit_ExtraDamage.Get(); if (this->Crit_Warhead) - WarheadTypeExt::DetonateAt(this->Crit_Warhead, pTarget, pOwner, damage); + { + if (this->Crit_Warhead_FullDetonation) + WarheadTypeExt::DetonateAt(this->Crit_Warhead, pTarget, pOwner, damage, pHouse); + else + this->DamageAreaWithTarget(pTarget->GetCoords(), damage, pOwner, this->Crit_Warhead, true, pHouse, pTarget); + } else pTarget->ReceiveDamage(&damage, 0, this->OwnerObject(), pOwner, false, false, pHouse); } diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 8bab5400d0..9f98297c31 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -9,60 +9,37 @@ #include #include -#pragma region DETONATION - -namespace Detonation -{ - bool InDamageArea = true; -} +#pragma region Detonation DEFINE_HOOK(0x46920B, BulletClass_Detonate, 0x6) { GET(BulletClass* const, pBullet, ESI); + GET_BASE(const CoordStruct*, pCoords, 0x8); - auto const pWH = pBullet ? pBullet->WH : nullptr; - - if (auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH)) - { - GET_BASE(const CoordStruct*, pCoords, 0x8); - auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); - auto const pOwner = pBullet->Owner; - auto const pHouse = pOwner ? pOwner->Owner : nullptr; - auto const pDecidedHouse = pHouse ? pHouse : pBulletExt->FirerHouse; - - pWHExt->Detonate(pOwner, pDecidedHouse, pBulletExt, *pCoords); - } - - Detonation::InDamageArea = false; + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pBullet->WH); + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); + auto const pOwner = pBullet->Owner; + auto const pHouse = pOwner ? pOwner->Owner : nullptr; + auto const pDecidedHouse = pHouse ? pHouse : pBulletExt->FirerHouse; + pWHExt->Detonate(pOwner, pDecidedHouse, pBulletExt, *pCoords); + pWHExt->InDamageArea = false; return 0; } -DEFINE_HOOK(0x46A290, BulletClass_Detonate_Return, 0x5) -{ - Detonation::InDamageArea = true; - return 0; -} - DEFINE_HOOK(0x489286, MapClass_DamageArea, 0x6) { - if (Detonation::InDamageArea) - { - // GET(const int, Damage, EDX); - // GET_BASE(const bool, AffectsTiberium, 0x10); - - GET_BASE(const WarheadTypeClass*, pWH, 0x0C); - - if (auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH)) - { - GET(const CoordStruct*, pCoords, ECX); - GET_BASE(TechnoClass*, pOwner, 0x08); - GET_BASE(HouseClass*, pHouse, 0x14); + GET_BASE(const WarheadTypeClass*, pWH, 0x0C); + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); - auto const pDecidedHouse = !pHouse && pOwner ? pOwner->Owner : pHouse; + if (pWHExt->InDamageArea) + { + GET(const CoordStruct*, pCoords, ECX); + GET_BASE(TechnoClass*, pOwner, 0x08); + GET_BASE(HouseClass*, pHouse, 0x14); - pWHExt->Detonate(pOwner, pDecidedHouse, nullptr, *pCoords); - } + auto const pDecidedHouse = !pHouse && pOwner ? pOwner->Owner : pHouse; + pWHExt->Detonate(pOwner, pDecidedHouse, nullptr, *pCoords); } return 0; @@ -74,9 +51,9 @@ DEFINE_HOOK(0x48A551, WarheadTypeClass_AnimList_SplashList, 0x6) GET(WarheadTypeClass* const, pThis, ESI); GET(int, nDamage, EDI); - auto pWHExt = WarheadTypeExt::ExtMap.Find(pThis); + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis); + auto const animTypes = pWHExt->SplashList.GetElements(RulesClass::Instance->SplashList); pWHExt->Splashed = true; - auto animTypes = pWHExt->SplashList.GetElements(RulesClass::Instance->SplashList); int idx = pWHExt->SplashList_PickRandom ? ScenarioClass::Instance->Random.RandomRanged(0, animTypes.size() - 1) : @@ -89,22 +66,24 @@ DEFINE_HOOK(0x48A551, WarheadTypeClass_AnimList_SplashList, 0x6) DEFINE_HOOK(0x48A5BD, SelectDamageAnimation_PickRandom, 0x6) { GET(WarheadTypeClass* const, pThis, ESI); - auto pWHExt = WarheadTypeExt::ExtMap.Find(pThis); + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis); - return pWHExt && pWHExt->AnimList_PickRandom ? 0x48A5C7 : 0; + return pWHExt->AnimList_PickRandom ? 0x48A5C7 : 0; } DEFINE_HOOK(0x48A5B3, SelectDamageAnimation_CritAnim, 0x6) { GET(WarheadTypeClass* const, pThis, ESI); - auto pWHExt = WarheadTypeExt::ExtMap.Find(pThis); + GET(int, nDamage, EDI); + + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pThis); - if (pWHExt && pWHExt->Crit_Active && pWHExt->Crit_AnimList.size() && !pWHExt->Crit_AnimOnAffectedTargets) + if (pWHExt->Crit_Active && pWHExt->Crit_AnimList.size() && !pWHExt->Crit_AnimOnAffectedTargets) { - GET(int, nDamage, ECX); int idx = pThis->EMEffect || pWHExt->Crit_AnimList_PickRandom.Get(pWHExt->AnimList_PickRandom) ? ScenarioClass::Instance->Random.RandomRanged(0, pWHExt->Crit_AnimList.size() - 1) : std::min(pWHExt->Crit_AnimList.size() * 25 - 1, (size_t)nDamage) / 25; + R->EAX(pWHExt->Crit_AnimList[idx]); return 0x48A5AD; } @@ -268,4 +247,99 @@ DEFINE_HOOK(0x489B49, MapClass_DamageArea_Rocker, 0xA) _asm fld rocker return 0x489B53; -} \ No newline at end of file +} + +#pragma region Nonprovocative + +// Do not retaliate against being hit by these Warheads. +DEFINE_HOOK(0x708B0B, TechnoClass_AllowedToRetaliate_Nonprovocative, 0x5) +{ + enum { SkipEvents = 0x708B17 }; + + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x18, 0x8)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +// Do not spring 'attacked' trigger events by these Warheads. +DEFINE_HOOK(0x5F57CF, ObjectClass_ReceiveDamage_Nonprovocative, 0x6) +{ + enum { SkipEvents = 0x5F580C }; + + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x24, 0xC)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +// Do not consider ToProtect technos hit by weapon as having been attacked e.g provoking response from AI. +DEFINE_HOOK(0x7027E6, TechnoClass_ReceiveDamage_Nonprovocative, 0x8) +{ + enum { SkipGameCode = 0x7027EE }; + + GET(TechnoClass*, pThis, ESI); + GET(TechnoClass*, pSource, EAX); + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0xC4, 0xC)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + + if (!pTypeExt->Nonprovocative) + { + pThis->BaseIsAttacked(pSource); + + return SkipGameCode; + } + + return SkipGameCode; +} + +// Do not consider Whiner=true team members hit by weapon as having been attacked e.g provoking response from AI. +DEFINE_HOOK(0x4D7493, FootClass_ReceiveDamage_Nonprovocative, 0x5) +{ + enum { SkipChecks = 0x4D74CD, SkipEvents = 0x4D74A3 }; + + GET(TechnoClass*, pSource, EBX); + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x1C, 0xC)); + + if (!pSource) + return SkipChecks; + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +// Suppress harvester under attack notification - Also covered by Ares' Malicious. +DEFINE_HOOK(0x7384BD, UnitClass_ReceiveDamage_Nonprovocative, 0x6) +{ + enum { SkipEvents = 0x738535 }; + + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x44, 0xC)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +// Do not consider buildings hit by weapon as having been attacked e.g provoking response from AI. +DEFINE_HOOK(0x442290, BuildingClass_ReceiveDamage_Nonprovocative1, 0x6) +{ + enum { SkipEvents = 0x4422C1 }; + + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x9C, 0xC)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +// Suppress all events and alerts that come from attacking a building, unlike Ares' Malicious this includes all EVA notifications AND events +DEFINE_HOOK(0x442956, BuildingClass_ReceiveDamage_Nonprovocative2, 0x6) +{ + enum { SkipEvents = 0x442980 }; + + GET_STACK(WarheadTypeClass*, pWarhead, STACK_OFFSET(0x9C, 0xC)); + + auto const pTypeExt = WarheadTypeExt::ExtMap.Find(pWarhead); + return pTypeExt->Nonprovocative ? SkipEvents : 0; +} + +#pragma endregion diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index ee174ef929..f3c45295bb 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -4,7 +4,7 @@ WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; -bool WeaponTypeExt::ExtData::HasRequiredAttachedEffects(TechnoClass* pTechno, TechnoClass* pFirer) const +bool WeaponTypeExt::ExtData::HasRequiredAttachedEffects(TechnoClass* pTarget, TechnoClass* pFirer) const { bool hasRequiredTypes = this->AttachEffect_RequiredTypes.size() > 0; bool hasDisallowedTypes = this->AttachEffect_DisallowedTypes.size() > 0; @@ -13,6 +13,14 @@ bool WeaponTypeExt::ExtData::HasRequiredAttachedEffects(TechnoClass* pTechno, Te if (hasRequiredTypes || hasDisallowedTypes || hasRequiredGroups || hasDisallowedGroups) { + auto pTechno = pTarget; + + if (this->AttachEffect_CheckOnFirer && pFirer) + pTechno = pFirer; + + if (!pTechno) + return true; + auto const pTechnoExt = TechnoExt::ExtMap.Find(pTechno); if (hasDisallowedTypes && pTechnoExt->HasAttachedEffects(this->AttachEffect_DisallowedTypes, false, this->AttachEffect_IgnoreFromSameSource, pFirer, this->OwnerObject()->Warhead, &this->AttachEffect_DisallowedMinCounts, &this->AttachEffect_DisallowedMaxCounts)) @@ -90,6 +98,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) 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->AmbientDamage_Warhead.Read(exINI, pSection, "AmbientDamage.Warhead"); this->AmbientDamage_IgnoreTarget.Read(exINI, pSection, "AmbientDamage.IgnoreTarget"); this->AttachEffect_RequiredTypes.Read(exINI, pSection, "AttachEffect.RequiredTypes"); @@ -100,6 +109,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->AttachEffect_RequiredMaxCounts.Read(exINI, pSection, "AttachEffect.RequiredMaxCounts"); this->AttachEffect_DisallowedMinCounts.Read(exINI, pSection, "AttachEffect.DisallowedMinCounts"); this->AttachEffect_DisallowedMaxCounts.Read(exINI, pSection, "AttachEffect.DisallowedMaxCounts"); + this->AttachEffect_CheckOnFirer.Read(exINI, pSection, "AttachEffect.CheckOnFirer"); this->AttachEffect_IgnoreFromSameSource.Read(exINI, pSection, "AttachEffect.IgnoreFromSameSource"); this->KickOutPassengers.Read(exINI, pSection, "KickOutPassengers"); } @@ -130,6 +140,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->ExtraWarheads) .Process(this->ExtraWarheads_DamageOverrides) .Process(this->ExtraWarheads_DetonationChances) + .Process(this->ExtraWarheads_FullDetonation) .Process(this->AmbientDamage_Warhead) .Process(this->AmbientDamage_IgnoreTarget) .Process(this->AttachEffect_RequiredTypes) @@ -140,6 +151,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->AttachEffect_RequiredMaxCounts) .Process(this->AttachEffect_DisallowedMinCounts) .Process(this->AttachEffect_DisallowedMaxCounts) + .Process(this->AttachEffect_CheckOnFirer) .Process(this->AttachEffect_IgnoreFromSameSource) .Process(this->KickOutPassengers) ; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 9a47bcef9d..7f70011e83 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -43,6 +43,7 @@ class WeaponTypeExt ValueableVector ExtraWarheads; ValueableVector ExtraWarheads_DamageOverrides; ValueableVector ExtraWarheads_DetonationChances; + ValueableVector ExtraWarheads_FullDetonation; Nullable AmbientDamage_Warhead; Valueable AmbientDamage_IgnoreTarget; ValueableVector AttachEffect_RequiredTypes; @@ -53,6 +54,7 @@ class WeaponTypeExt ValueableVector AttachEffect_RequiredMaxCounts; ValueableVector AttachEffect_DisallowedMinCounts; ValueableVector AttachEffect_DisallowedMaxCounts; + Valueable AttachEffect_CheckOnFirer; Valueable AttachEffect_IgnoreFromSameSource; Valueable KickOutPassengers; @@ -79,6 +81,7 @@ class WeaponTypeExt , ExtraWarheads {} , ExtraWarheads_DamageOverrides {} , ExtraWarheads_DetonationChances {} + , ExtraWarheads_FullDetonation {} , AmbientDamage_Warhead {} , AmbientDamage_IgnoreTarget { false } , AttachEffect_RequiredTypes {} @@ -89,6 +92,7 @@ class WeaponTypeExt , AttachEffect_RequiredMaxCounts {} , AttachEffect_DisallowedMinCounts {} , AttachEffect_DisallowedMaxCounts {} + , AttachEffect_CheckOnFirer { false } , AttachEffect_IgnoreFromSameSource { false } , KickOutPassengers { true } { } diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 1078e4e82f..86892cb36a 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -514,8 +515,8 @@ static DamageAreaResult __fastcall _BombClass_Detonate_DamageArea { auto const pThisBomb = FetchBomb::pThisBomb; auto nCoord = *pCoord; - auto nDamageAreaResult = MapClass::Instance()->DamageArea - (nCoord, nDamage, pSource, pWarhead, pWarhead->Tiberium, pThisBomb->OwnerHouse); + auto nDamageAreaResult = WarheadTypeExt::ExtMap.Find(pWarhead)->DamageAreaWithTarget + (nCoord, nDamage, pSource, pWarhead, pWarhead->Tiberium, pThisBomb->OwnerHouse, abstract_cast(pThisBomb->Target)); auto nLandType = MapClass::Instance()->GetCellAt(nCoord)->LandType; if (auto pAnimType = MapClass::SelectDamageAnimation(nDamage, pWarhead, nLandType, nCoord)) @@ -871,7 +872,8 @@ DEFINE_HOOK(0x7195BF, TeleportLocomotionClass_Process_WarpInDelay, 0x6) GET(FootClass*, pLinkedTo, ECX); auto const pLoco = static_cast(pThis); - TechnoExt::ExtMap.Find(pLinkedTo)->LastWarpInDelay = pLoco->Timer.GetTimeLeft(); + auto const pExt = TechnoExt::ExtMap.Find(pLinkedTo); + pExt->LastWarpInDelay = Math::max(pLoco->Timer.GetTimeLeft(), pExt->LastWarpInDelay); return 0; } @@ -882,7 +884,7 @@ DEFINE_HOOK(0x4DA53E, FootClass_AI_WarpInDelay, 0x6) auto const pExt = TechnoExt::ExtMap.Find(pThis); - if (pExt->HasCarryoverWarpInDelay) + if (pExt->HasRemainingWarpInDelay) { if (pExt->LastWarpInDelay) { @@ -890,7 +892,8 @@ DEFINE_HOOK(0x4DA53E, FootClass_AI_WarpInDelay, 0x6) } else { - pExt->HasCarryoverWarpInDelay = false; + pExt->HasRemainingWarpInDelay = false; + pExt->IsBeingChronoSphered = false; pThis->WarpingOut = false; } } @@ -899,3 +902,35 @@ DEFINE_HOOK(0x4DA53E, FootClass_AI_WarpInDelay, 0x6) } #pragma endregion + +// this fella was { 0, 0, 1 } before and somehow it also breaks both the light position a bit and how the lighting is applied when voxels rotate - Kerbiter +DEFINE_HOOK(0x753D86, VoxelCalcNormals_NullAdditionalVector, 0x0) +{ + REF_STACK(Vector3D, secondaryLightVector, STACK_OFFSET(0xD8, -0xC0)) + + if (RulesExt::Global()->UseFixedVoxelLighting) + secondaryLightVector = { 0, 0, 0 }; + else + secondaryLightVector = { 0, 0, 1 }; + + return 0x753D9E; +} + +DEFINE_HOOK(0x705D74, TechnoClass_GetRemapColour_DisguisePalette, 0x8) +{ + enum { SkipGameCode = 0x705D7C }; + + GET(TechnoClass* const, pThis, ESI); + + auto pTechnoType = pThis->GetTechnoType(); + + if (!pThis->IsClearlyVisibleTo(HouseClass::CurrentPlayer)) + { + if (const auto pDisguise = TechnoTypeExt::GetTechnoType(pThis->Disguise)) + pTechnoType = pDisguise; + } + + R->EAX(pTechnoType); + + return SkipGameCode; +} diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 52d93b8441..241a432dcb 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -257,7 +257,9 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) MapClass::FlashbangWarheadAt(size, args->WH, this->Techno->Location, true, flags); } - this->WeaponNullifyAnim(pWHExt->Shield_HitAnim); + if (!pWHExt->Shield_SkipHitAnim) + this->WeaponNullifyAnim(pWHExt->Shield_HitAnim); + this->HP = -residueDamage; this->UpdateIdleAnim(); diff --git a/src/New/Type/AttachEffectTypeClass.cpp b/src/New/Type/AttachEffectTypeClass.cpp index 858cf7e0b0..d339e49c0b 100644 --- a/src/New/Type/AttachEffectTypeClass.cpp +++ b/src/New/Type/AttachEffectTypeClass.cpp @@ -102,6 +102,7 @@ void AttachEffectTypeClass::LoadFromINI(CCINIClass* pINI) this->DiscardOn.Read(exINI, pSection, "DiscardOn"); this->DiscardOn_RangeOverride.Read(exINI, pSection, "DiscardOn.RangeOverride"); this->PenetratesIronCurtain.Read(exINI, pSection, "PenetratesIronCurtain"); + this->PenetratesForceShield.Read(exINI, pSection, "PenetratesForceShield"); this->Animation.Read(exINI, pSection, "Animation"); this->CumulativeAnimations.Read(exINI, pSection, "CumulativeAnimations"); @@ -173,6 +174,7 @@ void AttachEffectTypeClass::Serialize(T& Stm) .Process(this->DiscardOn) .Process(this->DiscardOn_RangeOverride) .Process(this->PenetratesIronCurtain) + .Process(this->PenetratesForceShield) .Process(this->Animation) .Process(this->CumulativeAnimations) .Process(this->Animation_ResetOnReapply) diff --git a/src/New/Type/RadTypeClass.cpp b/src/New/Type/RadTypeClass.cpp index b80eb775c6..7e03618c66 100644 --- a/src/New/Type/RadTypeClass.cpp +++ b/src/New/Type/RadTypeClass.cpp @@ -33,6 +33,7 @@ void RadTypeClass::LoadFromINI(CCINIClass* pINI) this->Color.Read(exINI, section, "RadColor"); this->SiteWarhead.Read(exINI, section, "RadSiteWarhead"); this->SiteWarhead_Detonate.Read(exINI, section, "RadSiteWarhead.Detonate"); + this->SiteWarhead_Detonate_Full.Read(exINI, section, "RadSiteWarhead.Detonate.Full"); this->HasOwner.Read(exINI, section, "RadHasOwner"); this->HasInvoker.Read(exINI, section, "RadHasInvoker"); } @@ -54,6 +55,7 @@ void RadTypeClass::Serialize(T& Stm) .Process(this->Color) .Process(this->SiteWarhead) .Process(this->SiteWarhead_Detonate) + .Process(this->SiteWarhead_Detonate_Full) .Process(this->HasOwner) .Process(this->HasInvoker) ; diff --git a/src/New/Type/RadTypeClass.h b/src/New/Type/RadTypeClass.h index c3fea147b7..f3691f94db 100644 --- a/src/New/Type/RadTypeClass.h +++ b/src/New/Type/RadTypeClass.h @@ -23,6 +23,7 @@ class RadTypeClass final : public Enumerable Nullable Color; Nullable SiteWarhead; Nullable SiteWarhead_Detonate; + Nullable SiteWarhead_Detonate_Full; Nullable HasOwner; Nullable HasInvoker; @@ -42,6 +43,7 @@ class RadTypeClass final : public Enumerable , Color { } , SiteWarhead { } , SiteWarhead_Detonate { } + , SiteWarhead_Detonate_Full { } , HasOwner { } , HasInvoker { } { } @@ -60,6 +62,11 @@ class RadTypeClass final : public Enumerable return this->SiteWarhead_Detonate.Get(RulesExt::Global()->RadSiteWarhead_Detonate); } + bool GetWarheadDetonateFull() const + { + return this->SiteWarhead_Detonate_Full.Get(RulesExt::Global()->RadSiteWarhead_Detonate_Full); + } + const ColorStruct& GetColor() const { return *this->Color.GetEx(&RulesClass::Instance->RadColor); diff --git a/src/Utilities/EnumFunctions.cpp b/src/Utilities/EnumFunctions.cpp index 9e95f3b301..dabb7c1d27 100644 --- a/src/Utilities/EnumFunctions.cpp +++ b/src/Utilities/EnumFunctions.cpp @@ -48,7 +48,10 @@ bool EnumFunctions::IsTechnoEligible(TechnoClass* const pTechno, AffectedTarget else return (allowed & AffectedTarget::Aircraft) != AffectedTarget::None; case AbstractType::Building: - return (allowed & AffectedTarget::Building) != AffectedTarget::None; + if (pTechno->IsStrange()) + return (allowed & AffectedTarget::Unit) != AffectedTarget::None; + else + return (allowed & AffectedTarget::Building) != AffectedTarget::None; } } else diff --git a/src/Utilities/GeneralUtils.cpp b/src/Utilities/GeneralUtils.cpp index 6c1a7d4eb6..ff9c10d5b2 100644 --- a/src/Utilities/GeneralUtils.cpp +++ b/src/Utilities/GeneralUtils.cpp @@ -6,6 +6,7 @@ #include #include +#include bool GeneralUtils::IsValidString(const char* str) { @@ -217,6 +218,19 @@ void GeneralUtils::DisplayDamageNumberString(int damage, DamageDisplayType type, offset = offset + width; } +DynamicVectorClass* GeneralUtils::BuildPalette(const char* paletteFileName) +{ + if (GeneralUtils::IsValidString(paletteFileName)) + { + char pFilename[0x20]; + strcpy_s(pFilename, paletteFileName); + + return ColorScheme::GeneratePalette(pFilename); + } + + return nullptr; +} + // Gets integer representation of color from ColorAdd corresponding to given index, or 0 if there's no color found. // Code is pulled straight from game's draw functions that deal with the tint colors. int GeneralUtils::GetColorFromColorAdd(int colorIndex) diff --git a/src/Utilities/GeneralUtils.h b/src/Utilities/GeneralUtils.h index 06bc5cc1ae..eec2a2bac9 100644 --- a/src/Utilities/GeneralUtils.h +++ b/src/Utilities/GeneralUtils.h @@ -37,6 +37,7 @@ class GeneralUtils static CoordStruct CalculateCoordsFromDistance(CoordStruct currentCoords, CoordStruct targetCoords, int distance); static void DisplayDamageNumberString(int damage, DamageDisplayType type, CoordStruct coords, int& offset); static int GetColorFromColorAdd(int colorIndex); + static DynamicVectorClass* BuildPalette(const char* paletteFileName); template static constexpr T FastPow(T x, size_t n)