diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000000..965dbb2866 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,10 @@ +reviews: + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: false + collapse_walkthrough: false + auto_review: + enabled: false +chat: + auto_reply: false \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md index 4a5f919fce..cc91afe7a8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -252,7 +252,7 @@ This page lists all the individual contributions to the project by their author. - Facing towards target even if not omni-firing - Turret direction in idle state fix - Sensor fix - - Allow to tilt on ground + - Allow to tilt regardless of TiltCrashJumpjet - Forbid firing when crashing - OmniFire.TurnToTarget - Object Self-destruction logic @@ -289,9 +289,11 @@ This page lists all the individual contributions to the project by their author. - Permanent healthbar display on units targeted by temporal weapons fix - Powered anims on buildings cease playing upon capture by different house fix - TechnoType conversion placeholder + - TechnoType conversion upon ownership change - EIP 00529A14 crash fix on Linux - Teleport timer reset after load game fix - - Teleport and Tunnel loco visual tilt fix + - Teleport, Tunnel and Fly loco visual tilt fix + - Turret/Barrel/NoSpawnAlt/Multi-section voxel shadow, dynamic voxel shadow - Skip units' turret rotation and jumpjets' wobbling under EMP - Droppod properties dehardcode - Waypoint entering building together with engineer/agent bug fix @@ -300,7 +302,9 @@ This page lists all the individual contributions to the project by their author. - **FlyStar** - Campaign load screen PCX support - New condition for automatic self-destruction logic when TechnoTypes exist/don't exist -- **NetsuNegi** - Forbidding parallel AI queues by type +- **NetsuNegi** + - Forbidding parallel AI queues by type + - Jumpjet crash speed fix when crashing onto building - **Apollo** - Translucent SHP drawing patches - **ststl** - Customizable ShowTimer priority of superweapons @@ -319,6 +323,9 @@ This page lists all the individual contributions to the project by their author. - Flashing Technos on selecting - **ZivDero** - Allow giving ownership of buildings to players in Skirmish and MP using + - Re-enable the Veinhole Monster and Weeds from TS + - Recreate the weed-charging of SWs like the TS Chemical Missile + - Allow to change the speed of gas particles - **Ares developers** - YRpp and Syringe which are used, save/load, project foundation and generally useful code from Ares - unfinished RadTypes code diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 7cd780d98d..1ff7abb8a7 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -39,6 +39,8 @@ + + @@ -61,6 +63,7 @@ + @@ -189,6 +192,7 @@ + diff --git a/YRpp b/YRpp index 95aa94ee31..599fb0c1c1 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 95aa94ee3106e13e35da7be7d515a85aa5b5eb9b +Subproject commit 599fb0c1c12e962d8417b88b12713715f34b58b0 diff --git a/docs/AI-Scripting-and-Mapping.md b/docs/AI-Scripting-and-Mapping.md index f23f26aff6..6573c23de7 100644 --- a/docs/AI-Scripting-and-Mapping.md +++ b/docs/AI-Scripting-and-Mapping.md @@ -69,7 +69,7 @@ Ranking.OverParMessage= ; CSF entry key ### Show briefing dialog on startup -- You can now have the briefing dialog screen show up on singleplayer campaign mission startup by setting `ShowBriefing` to true in map file's `[Basic]` section, or in the map file's section in `missionmd.ini` (latter takes precedence over former if available). +- You can now have the briefing dialog screen show up on singleplayer campaign mission startup by setting `ShowBriefing` to true in map file's `[Basic]` section, or in the map file's section in `missionmd.ini` (latter takes precedence over former if available). This can be disabled by user by setting `ShowBriefing` to false in `Ra2MD.ini`. - `BriefingTheme` (In order of precedence from highest to lowest: `missionmd.ini`, map file, side entry in `rulesmd.ini`) can be used to define a custom theme to play on this briefing screen. If not set, the loading screen theme will keep playing until the scenario starts properly. - String labels for the startup briefing dialog screen's resume button as well as the button's status bar text can be customized by setting `ShowBriefingResumeButtonLabel` and `ShowBriefingResumeButtonStatusLabel` respectively. They default to the same labels used by the briefing screen dialog when opened otherwise. @@ -100,6 +100,12 @@ ShowBriefingResumeButtonLabel=GUI:Resume ; CSF entry key ShowBriefingResumeButtonStatusLabel=STT:BriefingButtonReturn ; CSF entry key ``` +In `RA2MD.ini`: +```ini +[Phobos] +ShowBriefing=true ; boolean +``` + ## Script Actions ### `10000-10999` Ingame Actions diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 08a0122712..86728fc259 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -59,8 +59,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - `IsSimpleDeployer` units now only play `DeploySound` and `UndeploySound` once, when done with (un)deploying instead of repeating it over duration of turning and/or `DeployingAnim`. - AITrigger can now recognize Building Upgrades as legal condition. - `EWGates` and `NSGates` now will link walls like `xxGateOne` and `xxGateTwo` do. -- Fixed the bug when occupied building's `MuzzleFlashX` is drawn on the center of the building when `X` goes past 10. -- Fixed jumpjet units that are `Crashable` not crashing to ground properly if destroyed while being pulled by a `Locomotor` warhead. - Fixed interaction of `UnitAbsorb` & `InfantryAbsorb` with `Grinding` buildings. The keys will now make the building only accept appropriate types of objects. - Fixed missing 'no enter' cursor for VehicleTypes being unable to enter a `Grinding` building. - Fixed Engineers being able to enter `Grinding` buildings even when they shouldn't (such as ally building at full HP). @@ -102,7 +100,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Animation with `Tiled=yes` now supports `CustomPalette`. - Attempted to avoid units from retaining previous orders (attack,grind,garrison,etc) after changing ownership (mind-control,abduction,etc). - Fixed buildings' `NaturalParticleSystem` being created for in-map pre-placed structures. -- Fixed jumpjet units being unable to visually tilt or be flipped over on the ground if `TiltCrashJumpjet=no`. +- Fixed jumpjet units being unable to visually tilt or be flipped if `TiltCrashJumpjet=no`. - Unlimited (more than 5) `AlternateFLH` entries for units. - Warheads spawning debris now use `MaxDebris` as an actual cap for number of debris to spawn instead of `MaxDebris` - 1. If both `Primary` and `Secondary` weapons can fire at air targets (projectile has `AA=true`), `Primary` can now be picked instead of always forcing `Secondary`. Also applies to `IsGattling=true`, with odd-numbered and even-numbered `WeaponX` slots instead of `Primary` and `Secondary`, respectively. @@ -142,14 +140,17 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Fix [EIP 00529A14](https://modenc.renegadeprojects.com/Internal_Error/YR#eip_00529A14) when attempting to read `[Header]` section of campaign maps. - Units will no longer rotate its turret under EMP. - Jumpjets will no longer wobble under EMP. +- Removed jumpjet units' deceleration when crashing onto buildings. - Fixed `AmbientDamage` when used with `IsRailgun=yes` being cut off by elevation changes. - Fixed railgun and fire particles being cut off by elevation changes. - Fixed teleport units' (for example CLEG) frozen-still timer being cleared after load game. - Fixed teleport units being unable to visually tilt on slopes. -- Fixed units with Teleport or Tunnel locomotor being unable to be visually flipped like other locomotors do. +- Fixed rockets' shadow location. +- Fixed units with Teleport, Tunnel or Fly locomotor being unable to be visually flipped like other locomotors do. - Aircraft docking on buildings now respect `[AudioVisual]`->`PoseDir` as the default setting and do not always land facing north or in case of pre-placed buildings, the building's direction. - Spawned aircraft now align with the spawner's facing when landing. - Fixed the bug that waypointing unarmed infantries with agent/engineer/occupier to a spyable/capturable/occupiable building triggers EnteredBy event by executing capture mission. +- `PowerUpN` building animations can now use `Powered` & `PoweredLight/Effect/Special` keys. ## Fixes / interactions with other extensions @@ -158,6 +159,16 @@ This page describes all ingame logics that are fixed or improved in Phobos witho ## Aircraft +### Carryall pickup voice + +- It is now possible to override `VoiceMove` for `Carryall=true` aircraft for when commanding it to pick up vehicles by setting `VoicePickup`. + +In `rulesmd.ini`: +```ini +[SOMEAIRCRAFT] ; AircraftType +VoicePickup= ; Sound +``` + ### Fixed spawn distance & spawn height for airstrike / SpyPlane aircraft - It is now possible to have aircraft spawned from `(Elite)AirstrikeTeamType` or `Type=SpyPlane` superweapons to be created at fixed distance from their intended target/destination instead of from edge of the map by setting `SpawnDistanceFromTarget`. @@ -309,6 +320,7 @@ ConsideredVehicle= ; boolean - `Grinding.PlayDieSound` controls if the units' `DieSound` and `VoiceDie` are played when entering the grinder. Default to `yes`. - `Grinding.Sound` is a sound played by when object is grinded by the building. If not set, defaults to `[AudioVisual]`->`EnterGrinderSound`. - `Grinding.Weapon` is a weapon fired at the building & by the building when it grinds an object. Will only be fired if at least weapon's `ROF` amount of frames have passed since it was last fired. + - `Grinding.Weapon.RequiredCredits` can be set to have the weapon require accumulated credits from grinding to fire. Accumulated credits for this purpose are reset every time when the weapon fires. - For money string indication upon grinding, please refer to [`DisplayIncome`](User-Interface.md/#Visual-indication-of-income-from-grinders-and-refineries). In `rulesmd.ini`: @@ -321,6 +333,7 @@ Grinding.DisallowTypes= ; List of InfantryTypes / VehicleTypes Grinding.PlayDieSound=true ; boolean Grinding.Sound= ; Sound Grinding.Weapon= ; WeaponType +Grinding.Weapon.RequiredCredits=0 ; integer ``` ### Customizable selling buildup sequence length for buildings that can undeploy @@ -345,6 +358,18 @@ In `rulesmd.ini`: AdjustTargetCoordsOnRotation=true ; boolean ``` +## Particles + +### Customizable gas particle speed + +- Gas particles can now drift at a custom speed. + +In `rulesmd.ini`: +```ini +[GASPARTICLE] ; Particle with BehavesLike=Gas +Gas.MaxDriftSpeed=2 ; integer (TS default is 5) +``` + ## Projectiles ### Cluster scatter distance customization @@ -353,7 +378,7 @@ AdjustTargetCoordsOnRotation=true ; boolean In `rulesmd.ini`: ```ini -[SOMEPROJECTILE] ; Projectile +[SOMEPROJECTILE] ; Projectile ClusterScatter.Min=1.0 ; float, distance in cells ClusterScatter.Max=2.0 ; float, distance in cells ``` @@ -541,14 +566,18 @@ In `rulesmd.ini`: Storage.TiberiumIndex=-1 ; integer, [Tiberiums] list index ``` -### Exploding unit passenger killing customization +### Exploding object customizations - By default `Explodes=true` TechnoTypes have all of their passengers killed when they are destroyed. This behaviour can now be disabled by setting `Explodes.KillPassengers=false`. +- BuildingTypes with `Explodes=true` can by default explode even when they are still being built or sold. This can be disabled by setting `Explodes.DuringBuildup` to false. This causes them to behave as if `Explodes` was set to false while being built up or sold. In `rulesmd.ini`: ```ini [SOMETECHNO] ; TechnoType Explodes.KillPassengers=true ; boolean + +[SOMEBUILDING] ; BuildingType +Explodes.DuringBuildup=true ; boolean ``` ### IronCurtain effects on organics customization @@ -612,6 +641,8 @@ Powered.KillSpawns=false ; boolean - `Pips.Tiberiums.Frames` can be used to list frames (zero-based) of `pips.shp` (for buildings) or `pips2.shp` (for others) used for tiberium types, in the listed order corresponding to tiberium type index. Defaults to 5 for tiberium type index 1, otherwise 2. - `Pips.Tiberiums.EmptyFrame` can be used to set the frame for empty slots, defaults to 0. - `Pips.Tiberiums.DisplayOrder` controls in which order the tiberium type pips are displayed, takes a list of tiberium type indices. Any tiberium type not listed will be displayed in sequential order after the listed ones. + - `Pips.Tiberiums.WeedFrame` controls which frame is displayed on Technos with `Weeder=yes`, takes a (zero-based) index of a frame in `pips.shp` (for buildings) or `pips2.shp` (for others). Defaults to 1. + - `Pips.Tiberiums.WeedEmptyFrame` can be used to set the frame for empty weed slots, defaults to 0. In `rulesmd.ini`: ```ini @@ -623,6 +654,8 @@ Pips.Ammo.Buildings.Size=4,2 ; X,Y, increment in pixels to next pip Pips.Tiberiums.EmptyFrame=0 ; integer, frame of pips.shp (buildings) or pips2.shp (others) (zero-based) Pips.Tiberiums.Frames=2,5,2,2 ; list of integers, frames of pips.shp (buildings) or pips2.shp (others) (zero-based) Pips.Tiberiums.DisplayOrder=0,2,3,1 ; list of integers, tiberium type indices +Pips.Tiberiums.WeedEmptyFrame=0 ; integer, frame of pips.shp (buildings) or pips2.shp (others) (zero-based) +Pips.Tiberiums.WeedFrame=1 ; integer, frame of pips.shp (buildings) or pips2.shp (others) (zero-based) [SOMETECHNO] ; TechnoType AmmoPipFrame=13 ; integer, frame of pips2.shp (zero-based) @@ -656,11 +689,30 @@ NoWobbles=false ; boolean ### Voxel body multi-section shadows - It is also now possible for vehicles and aircraft to display shadows for multiple sections of the voxel body at once, instead of just one section specified by `ShadowIndex`, by specifying the section indices in `ShadowIndices` (which defaults to `ShadowIndex`) in unit's `artmd.ini` entry. + - `ShadowIndex.Frame` and `ShadowIndices.Frame` can be used to customize which frame of the HVA animation for the section from `ShadowIndex` and `ShadowIndices` is used to display the shadow, respectively. -1 is special value which means currently shown frame is used, and `ShadowIndices.Frame` defaults to this. In `artmd.ini`: ```ini -[SOMETECHNO] ; TechnoType -ShadowIndices= ; list of integers (voxel section indices) +[SOMETECHNO] ; TechnoType +ShadowIndices= ; list of integers (voxel section indices) +ShadowIndex.Frame=0 ; integer (HVA animation frame index) +ShadowIndices.Frame= ; list of integers (HVA animation frame indices) +``` + +### Voxel shadow scaling in air + +- It is now possible to adjust how voxel air units (`VehicleType` & `AircraftType`) shadows scale in air. By default the shadows scale by `AirShadowBaseScale` (defaults to 0.5) amount if unit is `ConsideredAircraft=true`. + - If `HeightShadowScaling=true`, the shadow is scaled by amount that is determined by following formula: `Max(AirShadowBaseScale ^ (currentHeight / ShadowSizeCharacteristicHeight), HeightShadowScaling.MinScale)`, where `currentHeight` is unit's current height in leptons, `ShadowSizeCharacteristicHeight` overrideable value that defaults to the maximum cruise height (`JumpjetHeight`, `FlightLevel` etc) and `HeightShadowScaling.MinScale` sets a floor for the scale. + +In `rulesmd.ini`: +```ini +[AudioVisual] +AirShadowBaseScale=0.5 ; floating point value +HeightShadowScaling=false ; boolean +HeightShadowScaling.MinScale=0.0 ; floating point value + +[SOMETECHNO] ; TechnoType +ShadowSizeCharacteristicHeight= ; integer, height in leptons ``` ### Forbid parallel AI queues @@ -805,6 +857,7 @@ IronCurtain.KeptOnDeploy= ; boolean, default to [CombatDamage]->IronCurtain.K ### Voxel turret shadow - Vehicle voxel turrets can now draw shadows if `[AudioVisual]` -> `DrawTurretShadow` is set to true. This can be overridden per VehicleType by setting `TurretShadow` in the vehicle's `artmd.ini` section. + In `rulesmd.ini`: ```ini [AudioVisual] @@ -849,6 +902,89 @@ Ammo.AddOnDeploy=0 ; integer ``` +## Veinholes & Weeds + +### Veinholes + +- Veinhole monsters now work like they used to in Tiberian Sun. +- Their core parameters are still loaded from `[General]` +- The Warhead used by veins is specified under `[CombatDamage]`. The warhead has to be properly listed under `[Warheads]` as well. The warhead has to have `Veinhole=yes` set. +- Veinholes are hardcoded to use several overlay types. +- The vein attack animation specified under `[AudioVisual]` is what deals the damage. The animation has to be properly listed under `[Animations]` as well. +- Units can be made immune to veins the same way as in Tiberian Sun. +- The monster itself is represented by the `VEINTREE` TerrainType, which has `IsVeinhole=true` set. Its strength is what determines the strength of the Veinhole. + +```{note} +Everything listed below functions identically to Tiberian Sun. +Many of the tags from Tiberian Sun have been re-enabled. The values provided below are identical to those found in TS and YR rules. You can read more about them on ModENC: +[VeinholeGrowthRate](https://modenc.renegadeprojects.com/VeinholeGrowthRate), [VeinholeShrinkRate](https://modenc.renegadeprojects.com/VeinholeShrinkRate), [MaxVeinholeGrowth](https://modenc.renegadeprojects.com/MaxVeinholeGrowth), [VeinDamage](https://modenc.renegadeprojects.com/VeinDamage), [VeinholeTypeClass](https://modenc.renegadeprojects.com/VeinholeTypeClass), +[VeinholeWarhead](https://modenc.renegadeprojects.com/VeinholeWarhead), [Veinhole](https://modenc.renegadeprojects.com/Veinhole), [VeinAttack](https://modenc.renegadeprojects.com/VeinAttack), [ImmuneToVeins](https://modenc.renegadeprojects.com/ImmuneToVeins), [IsVeinhole](https://modenc.renegadeprojects.com/IsVeinhole) +``` + +In `rulesmd.ini`: +```ini +[General] +VeinholeGrowthRate=300 ; integer +VeinholeShrinkRate=100 ; integer +MaxVeinholeGrowth=2000 ; integer +VeinDamage=5 ; integer +VeinholeTypeClass=VEINTREE ; TerrainType + +[CombatDamage] +VeinholeWarhead=VeinholeWH ; Warhead + +[VeinholeWH] +Veinhole=yes + +[AudioVisual] +VeinAttack=VEINATAC ; Animation + +[TechnoType] +EliteAbilities=VEIN_PROOF +ImmuneToVeins=yes + +[VEINTREE] +IsVeinhole=true +Strength=1000 ; integer - the strength of the Veinhole +``` + +```{warning} +The game expects certain overlays related to Veinholes to have certain indices, they are listed below. Please keep in mind that the indices in the OverlayTypes list are 0-based, formed internally by the game, and the identifiers left of "=" don't matter. Vanilla `rulesmd.ini` already has the required overlays listed at the correct indices. +``` + +In `rulesmd.ini`: +```ini +[OverlayTypes] +126=VEINS ; The veins (weeds) +167=VEINHOLE ; The Veinhole itself +178=VEINHOLEDUMMY ; A technical overlay +``` + + +### Weeds & Weed Eaters + +- Vehicles with `Weeder=yes` can now collect weeds. The weeds can then be deposited into a building with `Weeder=yes`. +- Weeds are not stored in a building's storage, but rather in a House's storage. The weed capacity is listed under `[General]->WeedCapacity`. +- Weeders now show the ore gathering animation. It can be customized the same way as for harvesters. +- Weeders can use the Teleport locomotor like chrono miners. + +### Weed-consuming superweapons + +- Superweapons can consume weeds to recharge, like the Chemical Missile special in Tiberian Sun. + +```{note} +As the code for the Chemical Missile had been removed, setting `Type=ChemMissile` will not work. +``` + +In `rulesmd.ini`: +```ini +[SuperWeaponType] +UseWeeds=no ; boolean - should the SW use weeds to recharge? +UseWeeds.Amount= ; integer - how many? default is General->WeedCapacity +UseWeeds.StorageTimer=no ; boolean - should the counter on the sidebar display the % of weeds stored? +UseWeeds.ReadinessAnimationPercentage=0.9 ; double - when this many weeds % are stored, the SW will show it's ready on the building (open nuke/open chrono, etc.) +``` + ## VoxelAnims ### Customizable debris & meteor impact and warhead detonation behaviour diff --git a/docs/Miscellanous.md b/docs/Miscellanous.md index c1a859d254..0d2556c904 100644 --- a/docs/Miscellanous.md +++ b/docs/Miscellanous.md @@ -106,7 +106,7 @@ CustomGSN.DefaultDelay=N ; integer between 0 and 6 ; where N = 0, 1, 2, 3, 4, 5, 6 ``` -In `ra2md.ini`: +In `RA2MD.ini`: ```ini [Phobos] CampaignDefaultGameSpeed=4 ; integer diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 31417d8627..43bd72e61f 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -484,7 +484,7 @@ Currently interceptor weapons with projectiles that do not have `Inviso=true` wi ### Projectile trajectories - Projectiles can now have customizable trajectories. - - `Trajectory` should not be combined with original game's projectile trajectory logics (`Arcing`, `ROT` or `Inviso`). + - `Trajectory` should not be combined with original game's projectile trajectory logics (`Arcing`, `ROT`, `Vertical` or `Inviso`). Attempt to do so will result in the other logics being disabled and a warning being written to log file. - Initial speed of the projectile is defined by `Trajectory.Speed`, which unlike `Speed` used by `ROT` > 0 projectiles is defined on projectile not weapon. In `rulesmd.ini`: @@ -822,7 +822,7 @@ ForceWeapon.Disguised=-1 ; integer. 0 for primary weapon, 1 for secondary ### Make units try turning to target when firing with `OmniFire=yes` - The unit will try to turn the body to target even firing with `OmniFire=yes` - - Recommended for jumpjets if you want it to turn to target when firing. + - Jumpjets are recommended to have the same value of body `ROT` and `JumpjetTurnRate` In `rulesmd.ini`: ```ini @@ -995,6 +995,16 @@ IsVoiceCreatedGlobal=false ; boolean VoiceCreated= ; sound entry ``` +### Convert TechnoType on owner house change +- You can now change a unit's type when changing ownership from human to computer or from computer to human. + +In `rulesmd.ini`: +```ini +[SOMETECHNO] +Convert.HumanToComputer = ; TechnoType +Convert.ComputerToHuman = ; TechnoType +``` + ## Terrain ### Destroy animation & sound @@ -1050,7 +1060,7 @@ MindControl.Threshold.Inverse=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.Affects` can be used to customize types of targets that this Warhead can deal critical hits against. + - `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. @@ -1065,7 +1075,7 @@ Crit.Chance=0.0 ; floating point value, percents or absolute Crit.ApplyChancePerTarget=false ; boolean Crit.ExtraDamage=0 ; integer Crit.Warhead= ; Warhead -Crit.Affects=all ; list of Affected Target Enumeration (none|land|water|empty|infantry|units|buildings|all) +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 @@ -1118,6 +1128,7 @@ Convert.To= ; TechnoType Convert.AffectedHouses=all ; list of Affected House Enumeration (none|owner/self|allies/ally|team|enemies/enemy|all) ``` + ### Custom 'SplashList' on Warheads ![image](_static/images/splashlist-01.gif) @@ -1281,13 +1292,15 @@ Burst.FireWithinSequence=false ; boolean ### Extra warhead detonations - 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. If not, weapon `Damage` is used. + - `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. In `rulesmd.ini`: ```ini -[SOMEWEAPON] ; WeaponType -ExtraWarheads= ; list of WarheadTypes -ExtraWarheads.DamageOverrides= ; list of integers +[SOMEWEAPON] ; WeaponType +ExtraWarheads= ; list of WarheadTypes +ExtraWarheads.DamageOverrides= ; list of integers +ExtraWarheads.DetonationChances= ; list of floating-point values (percentage or absolute) ``` ### Feedback weapon diff --git a/docs/User-Interface.md b/docs/User-Interface.md index df4632fa99..3d990b4d16 100644 --- a/docs/User-Interface.md +++ b/docs/User-Interface.md @@ -84,7 +84,7 @@ DigitalDisplay.Disable=false ; boolean DigitalDisplayTypes= ; list of DigitalDisplayTypes ``` -In `Ra2MD.ini`: +In `RA2MD.ini`: ```ini [Phobos] DigitalDisplay.Enable=false ; boolean @@ -110,7 +110,7 @@ ShowDesignatorRange=true ; boolean ShowDesignatorRange=true ; boolean ``` -In `Ra2MD.ini`: +In `RA2MD.ini`: ```ini [Phobos] ShowDesignatorRange=false ; boolean @@ -208,7 +208,7 @@ The `PlacementPreview.Palette` option is not used when `PlacementPreview.Remap=y - This behavior is designed to be toggleable by users. For now you can only do that externally via client or manually. -In `ra2md.ini`: +In `RA2MD.ini`: ```ini [Phobos] ShowPlacementPreview=yes ; boolean diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f6dfe3f5ca..a60d05e076 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -1,6 +1,6 @@ # What's New -This page lists the history of changes across stable Phobos releases and also all the stuff that requires modders to change something in their mods to accomodate. +This page lists the history of changes across stable Phobos releases and also all the stuff that requires modders to change something in their mods to accommodate. ## Migrating @@ -19,6 +19,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] #### From post-0.3 devbuilds +- `ExtraWarheads.DamageOverrides` now falls back to last listed value if list is shorter than `ExtraWarheads` for all Warhead detonations exceeding the length. - Air and Top layer contents are no longer sorted, animations in these layers no longer respect `YSortAdjust`. Animations attached to flying units now get their layer updated immediately after parent unit, if they are on same layer they will draw above the parent unit. - `AnimList.ShowOnZeroDamage` has been renamed to `CreateAnimsOnZeroDamage` to make it more clear it applies to both `AnimList` and splash animations. - INI inclusion and inheritance are now turned off by default and need to be turned on via command line flags `-Include` and `-Inheritance`. @@ -44,7 +45,7 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] - `Gravity=0` is not supported anymore as it will cause the projectile to fly backwards and be unable to hit the target which is not at the same height. Use `Straight` Trajectory instead. See [here](New-or-Enhanced-Logics.md#projectile-trajectories). - Automatic self-destruction logic logic has been reimplemented, `Death.NoAmmo`, `Death.Countdown` and `Death.Peaceful` tags have been remade/renamed and require adjustments to function. - `DetachedFromOwner` on weapons is deprecated. This has been replaced by `AllowDamageOnSelf` on warheads. -- Timed jump script actions now take the time measured in ingame seconds instead of frames. Divide your value by 15 to accomodate to this change. +- Timed jump script actions now take the time measured in ingame seconds instead of frames. Divide your value by 15 to accommodate to this change. - [Placement Preview](User-Interface.md#placement-preview) logic has been adjusted, `BuildingPlacementPreview.DefaultTranslucentLevel`, `BuildingPlacementGrid.TranslucentLevel`, `PlacementPreview.Show`, `PlacementPreview.TranslucentLevel` and `ShowBuildingPlacementPreview` tags have been remade/renamed and require adjustments to function. In addition, you must explicitly enable this feature by specifying `[AudioVisual]->PlacementPreview=yes`. - Existing script actions were renumbered, please use the migration utility to change the numbers to the correct ones. - `DiskLaser.Radius` values were misinterpreted by a factor of 1/2π. The default radius is now 240, please multiply your customized radii by 2π. @@ -60,6 +61,25 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] - Key `rulesmd.ini->[SOMETECHNOTYPE]->Deployed.RememberTarget` is deprecated and can be removed now, the bugfix for `DeployToFire` deployers is now always on. +### New user settings in RA2MD.ini + +- These are new user setting keys added by various features in Phobos. Most of them can be found in either in [user inteface](User-Interface.md) or [miscellaneous](Miscellanous.md) sections. Search functionality can be used to find them quickly if needed. + +```ini +[Phobos] +CampaignDefaultGameSpeed=4 ; integer +ShowBriefing=true ; boolean +DigitalDisplay.Enable=false ; boolean +ShowDesignatorRange=false ; boolean +PrioritySelectionFiltering=true ; boolean +ShowPlacementPreview=yes ; boolean +RealTimeTimers=false ; boolean +RealTimeTimers.Adaptive=false ; boolean +ToolTipDescriptions=true ; boolean +ToolTipBlur=false ; boolean +SaveGameOnScenarioStart=true ; boolean +``` + ### For Map Editor (Final Alert 2)
@@ -321,9 +341,10 @@ New: - `UndeploysInto` building selling buildup sequence length customization (by Starkku) - Allow overriding `Shield.AffectTypes` for each Warhead shield interaction (by Starkku) - TechnoType conversion warhead & superweapon (by Morton) +- TechnoType conversion on ownership change (by Trsdy) - Unlimited skirmish colors (by Morton) - Example custom locomotor that circles around the target (*NOTE: For developer use only*) (by Kerbiter, CCHyper, with help from Otamaa; based on earlier experiment by CnCVK) -- Vehicle voxel turret shadows & body multi-section shadows (by TwinkleStar) +- Vehicle voxel turret shadows & body multi-section shadows (by TwinkleStar & Trsdy) - Crushing tilt and slowdown customization (by Starkku) - Extra warhead detonations on weapon (by Starkku) - Chrono sparkle animation display customization and improvements (by Starkku) @@ -361,6 +382,14 @@ New: - Allow customizing aircraft landing direction per aircraft or per dock (by Starkku) - Allow animations to play sounds detached from audio event handler (by Starkku) - Game save option when starting campaigns (by Trsdy) +- Carryall pickup voice (by Starkku) +- Option to have `Grinding.Weapon` require accumulated credits from grinding (by Starkku) +- Re-enable the Veinhole Monster and Weeds from TS (by ZivDero) +- Recreate the weed-charging of SWs like the TS Chemical Missile (by ZivDero) +- Allow to change the speed of gas particles (by ZivDero) +- Allow upgrade animations to use `Powered` & `PoweredLight/Effect/Special` keys (by Starkku) +- Toggle for `Explodes=true` BuildingTypes to not explode during buildup or being sold (by Starkku) +- Toggleable height-based shadow scaling for voxel air units (by Trsdy & Starkku) Vanilla fixes: - Allow AI to repair structures built from base nodes/trigger action 125/SW delivery in single player missions (by Trsdy) @@ -419,6 +448,7 @@ Vanilla fixes: - Aircraft docking on buildings now respect `[AudioVisual]`->`PoseDir` as the default setting and do not always land facing north or in case of pre-placed buildings, the building's direction (by Starkku) - Spawned aircraft now align with the spawner's facing when landing (by Starkku) - Fixed infantries attempted to entering buildings when waypointing together with engineer/agent/occupier/etc (by Trsdy) +- Fixed jumpjet crash speed when crashing onto buildings (by NetsuNegi) Phobos fixes: - Fixed a few errors of calling for superweapon launch by `LaunchSW` or building infiltration (by Trsdy) @@ -451,6 +481,7 @@ Phobos fixes: - Fixed a desync error caused by air/top layer sorting (by Starkku) - Fixed heal / repair weapons being unable to remove parasites from shielded targets if they were unable to heal / repair the parent unit (by Starkku) - Fixed `Inviso=true` interceptor projectiles applying damage on interceptable, armor type-having projectiles twice (by Starkku) +- Fixed `AutoDeath` causing crashes when used to kill a parasite unit inside an another unit (by Starkku) Fixes / interactions with other extensions: - All forms of type conversion (including Ares') now correctly update `OpenTopped` state of passengers in transport that is converted (by Starkku) diff --git a/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po b/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po index 05b933ae7e..e775db7bc9 100644 --- a/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po +++ b/docs/locale/zh_CN/LC_MESSAGES/User-Interface.po @@ -204,8 +204,8 @@ msgid "" msgstr "`PlacementPreview.ShapeFrame`默认为建筑art节中`Buildup`的最后一非影子帧。如果没有`Buildup`则会选取默认图像第一帧(其中不会包含动画和Bibs)。" #: ../User-Interface.md:91 -msgid "In `ra2md.ini`:" -msgstr "在`ra2md.ini`中:" +msgid "In `RA2MD.ini`:" +msgstr "在`RA2MD.ini`中:" #: ../User-Interface.md:97 msgid "Hotkey Commands" diff --git a/src/Commands/Commands.cpp b/src/Commands/Commands.cpp index 1aebdff24f..215c38b232 100644 --- a/src/Commands/Commands.cpp +++ b/src/Commands/Commands.cpp @@ -1,3 +1,4 @@ +#include #include "Commands.h" #include "ObjectInfo.h" @@ -14,21 +15,26 @@ DEFINE_HOOK(0x533066, CommandClassCallback_Register, 0x6) { // Load it after Ares' - MakeCommand(); MakeCommand(); MakeCommand(); - MakeCommand(); MakeCommand(); MakeCommand(); - MakeCommand(); - - MakeCommand(); - MakeCommand>(); // Single step in - MakeCommand>(); // Speed 1 - MakeCommand>(); // Speed 2 - MakeCommand>(); // Speed 3 - MakeCommand>(); // Speed 4 - MakeCommand>(); // Speed 5 +#ifndef DEBUG + Phobos::Config::DevelopmentCommands = CCINIClass::INI_Rules->ReadBool("GlobalControls", "DebugKeysEnabled", Phobos::Config::DevelopmentCommands); +#endif + if (Phobos::Config::DevelopmentCommands) + { + MakeCommand(); + MakeCommand(); + MakeCommand(); + MakeCommand(); + MakeCommand>(); // Single step in + MakeCommand>(); // Speed 1 + MakeCommand>(); // Speed 2 + MakeCommand>(); // Speed 3 + MakeCommand>(); // Speed 4 + MakeCommand>(); // Speed 5 + } return 0; } diff --git a/src/Commands/Commands.h b/src/Commands/Commands.h index bcdebcff82..939ec73225 100644 --- a/src/Commands/Commands.h +++ b/src/Commands/Commands.h @@ -7,25 +7,6 @@ #include #include -class PhobosCommandClass : public CommandClass -{ -protected: - bool CheckDebugDeactivated() const - { - if (!Phobos::Config::DevelopmentCommands) - { - if (const wchar_t* text = StringTable::LoadString("TXT_COMMAND_DISABLED")) - { - wchar_t msg[0x100] = L"\0"; - wsprintfW(msg, text, this->GetUIName()); - MessageListClass::Instance->PrintMessage(msg); - } - return true; - } - return false; - } -}; - template void MakeCommand() { diff --git a/src/Commands/DamageDisplay.cpp b/src/Commands/DamageDisplay.cpp index 488eb30ae6..33ae3548de 100644 --- a/src/Commands/DamageDisplay.cpp +++ b/src/Commands/DamageDisplay.cpp @@ -25,8 +25,5 @@ const wchar_t* DamageDisplayCommandClass::GetUIDescription() const void DamageDisplayCommandClass::Execute(WWKey eInput) const { - if (this->CheckDebugDeactivated()) - return; - Phobos::DisplayDamageNumbers = !Phobos::DisplayDamageNumbers; } diff --git a/src/Commands/DamageDisplay.h b/src/Commands/DamageDisplay.h index 7aae06cee7..a42204eeb8 100644 --- a/src/Commands/DamageDisplay.h +++ b/src/Commands/DamageDisplay.h @@ -3,7 +3,7 @@ #include "Commands.h" // Display damage strings -class DamageDisplayCommandClass : public PhobosCommandClass +class DamageDisplayCommandClass : public CommandClass { public: virtual const char* GetName() const override; diff --git a/src/Commands/Dummy.h b/src/Commands/Dummy.h index 69e3c0e837..c0527e9c45 100644 --- a/src/Commands/Dummy.h +++ b/src/Commands/Dummy.h @@ -3,7 +3,7 @@ #include "Commands.h" // The example command class -class DummyCommandClass : public PhobosCommandClass +class DummyCommandClass : public CommandClass { public: // CommandClass diff --git a/src/Commands/FrameByFrame.cpp b/src/Commands/FrameByFrame.cpp index b618143b91..b53ba32167 100644 --- a/src/Commands/FrameByFrame.cpp +++ b/src/Commands/FrameByFrame.cpp @@ -31,9 +31,6 @@ const wchar_t* FrameByFrameCommandClass::GetUIDescription() const void FrameByFrameCommandClass::Execute(WWKey eInput) const { - if (this->CheckDebugDeactivated()) - return; - if (!SessionClass::IsSingleplayer()) return; diff --git a/src/Commands/FrameByFrame.h b/src/Commands/FrameByFrame.h index 898624ccec..29c190b7ac 100644 --- a/src/Commands/FrameByFrame.h +++ b/src/Commands/FrameByFrame.h @@ -2,7 +2,7 @@ #include "Commands.h" -class FrameByFrameCommandClass : public PhobosCommandClass +class FrameByFrameCommandClass : public CommandClass { public: static size_t FrameStepCount; diff --git a/src/Commands/FrameStep.h b/src/Commands/FrameStep.h index 94a9d0ecef..370cc742ef 100644 --- a/src/Commands/FrameStep.h +++ b/src/Commands/FrameStep.h @@ -5,7 +5,7 @@ #include "FrameByFrame.h" template -class FrameStepCommandClass : public PhobosCommandClass +class FrameStepCommandClass : public CommandClass { virtual const char* GetName() const override; virtual const wchar_t* GetUIName() const override; diff --git a/src/Commands/NextIdleHarvester.h b/src/Commands/NextIdleHarvester.h index 598dc0629d..c8bdfcfb83 100644 --- a/src/Commands/NextIdleHarvester.h +++ b/src/Commands/NextIdleHarvester.h @@ -3,7 +3,7 @@ #include "Commands.h" // Select next idle harvester -class NextIdleHarvesterCommandClass : public PhobosCommandClass +class NextIdleHarvesterCommandClass : public CommandClass { public: // CommandClass diff --git a/src/Commands/ObjectInfo.cpp b/src/Commands/ObjectInfo.cpp index 79ef8e8c36..3c73192f19 100644 --- a/src/Commands/ObjectInfo.cpp +++ b/src/Commands/ObjectInfo.cpp @@ -36,9 +36,6 @@ const wchar_t* ObjectInfoCommandClass::GetUIDescription() const void ObjectInfoCommandClass::Execute(WWKey eInput) const { - if (this->CheckDebugDeactivated()) - return; - char buffer[0x800] = { 0 }; auto append = [&buffer](const char* pFormat, ...) @@ -158,6 +155,19 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const append("\n"); } + if (pBuilding->Type->Upgrades) + { + append("Upgrades (%d/%d): ", pBuilding->UpgradeLevel, pBuilding->Type->Upgrades); + for (int i = 0; i < 3; i++) + { + if (i != 0) + append(", "); + + append("Slot %d = %s", i+1, pBuilding->Upgrades[i] ? pBuilding->Upgrades[i]->get_ID() : ""); + } + append("\n"); + } + if (pBuilding->Type->Ammo > 0) append("Ammo = (%d / %d)\n", pBuilding->Ammo, pBuilding->Type->Ammo); diff --git a/src/Commands/ObjectInfo.h b/src/Commands/ObjectInfo.h index 4af44b52c5..d82a685480 100644 --- a/src/Commands/ObjectInfo.h +++ b/src/Commands/ObjectInfo.h @@ -3,7 +3,7 @@ #include "Commands.h" // #53 New debug feature for AI scripts -class ObjectInfoCommandClass : public PhobosCommandClass +class ObjectInfoCommandClass : public CommandClass { public: // CommandClass diff --git a/src/Commands/QuickSave.cpp b/src/Commands/QuickSave.cpp index 5e756bd7a9..3af97cd4ca 100644 --- a/src/Commands/QuickSave.cpp +++ b/src/Commands/QuickSave.cpp @@ -64,6 +64,6 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const } else { - PrintMessage(StringTable::LoadString("MSG:NotAvailableInMultiplayer")); + PrintMessage(GeneralUtils::LoadStringUnlessMissing("MSG:NotAvailableInMultiplayer", L"QuickSave is not available in multiplayer")); } } diff --git a/src/Commands/QuickSave.h b/src/Commands/QuickSave.h index 8804081736..b82ce35995 100644 --- a/src/Commands/QuickSave.h +++ b/src/Commands/QuickSave.h @@ -3,7 +3,7 @@ #include "Commands.h" // Quicksave current game -class QuickSaveCommandClass : public PhobosCommandClass +class QuickSaveCommandClass : public CommandClass { public: // CommandClass diff --git a/src/Commands/SaveVariablesToFile.cpp b/src/Commands/SaveVariablesToFile.cpp index 96ea84179f..8e4d93b7a9 100644 --- a/src/Commands/SaveVariablesToFile.cpp +++ b/src/Commands/SaveVariablesToFile.cpp @@ -25,9 +25,6 @@ const wchar_t* SaveVariablesToFileCommandClass::GetUIDescription() const void SaveVariablesToFileCommandClass::Execute(WWKey eInput) const { - if (this->CheckDebugDeactivated()) - return; - MessageListClass::Instance->PrintMessage( L"Variables saved.", RulesClass::Instance->MessageDelay, diff --git a/src/Commands/SaveVariablesToFile.h b/src/Commands/SaveVariablesToFile.h index 226bd5f878..ef7b747c05 100644 --- a/src/Commands/SaveVariablesToFile.h +++ b/src/Commands/SaveVariablesToFile.h @@ -3,7 +3,7 @@ #include "Commands.h" // Display damage strings -class SaveVariablesToFileCommandClass : public PhobosCommandClass +class SaveVariablesToFileCommandClass : public CommandClass { public: virtual const char* GetName() const override; diff --git a/src/Commands/ToggleDesignatorRange.h b/src/Commands/ToggleDesignatorRange.h index bd758c2862..1f1f5eeb01 100644 --- a/src/Commands/ToggleDesignatorRange.h +++ b/src/Commands/ToggleDesignatorRange.h @@ -3,7 +3,7 @@ #include "Commands.h" // Display damage strings -class ToggleDesignatorRangeCommandClass : public PhobosCommandClass +class ToggleDesignatorRangeCommandClass : public CommandClass { public: virtual const char* GetName() const override; diff --git a/src/Commands/ToggleDigitalDisplay.h b/src/Commands/ToggleDigitalDisplay.h index 4ee18cae30..4146a0d6e9 100644 --- a/src/Commands/ToggleDigitalDisplay.h +++ b/src/Commands/ToggleDigitalDisplay.h @@ -3,7 +3,7 @@ #include "Commands.h" // Display damage strings -class ToggleDigitalDisplayCommandClass : public PhobosCommandClass +class ToggleDigitalDisplayCommandClass : public CommandClass { public: virtual const char* GetName() const override; diff --git a/src/Ext/Aircraft/Body.cpp b/src/Ext/Aircraft/Body.cpp index 45186b137f..f1672abc78 100644 --- a/src/Ext/Aircraft/Body.cpp +++ b/src/Ext/Aircraft/Body.cpp @@ -66,16 +66,11 @@ DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock) if (auto pOwner = pThis->SpawnOwner) return pOwner->PrimaryFacing.Current().GetDir(); - bool isAirportBound = true; - if (pDock || pThis->HasAnyLink()) { - auto pBuilding = pDock; - - if (!pDock) - pBuilding = abstract_cast(pThis->GetNthLink(0)); + auto pLink = pThis->GetNthLink(0); - if (pBuilding) + if (auto pBuilding = pDock ? pDock : abstract_cast(pLink)) { auto const pBuildingTypeExt = BuildingTypeExt::ExtMap.Find(pBuilding->Type); int docks = pBuilding->Type->NumberOfDocks; @@ -89,19 +84,15 @@ DirType AircraftExt::GetLandingDir(AircraftClass* pThis, BuildingClass* pDock) else if (docks > 0 && !pBuildingTypeExt->AircraftDockingDirs[0].empty()) return pBuildingTypeExt->AircraftDockingDirs[0].get(); } - } - else if (!pThis->Type->AirportBound) - { - isAirportBound = false; + else if (!pThis->Type->AirportBound) + return pLink->PrimaryFacing.Current().GetDir(); } int landingDir = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType())->LandingDir.Get((int)poseDir); - if (isAirportBound) - return static_cast(Math::clamp(landingDir, 0, 255)); - else if (landingDir < 0) + if (!pThis->Type->AirportBound && landingDir < 0) return pThis->PrimaryFacing.Current().GetDir(); - return poseDir; + return static_cast(Math::clamp(landingDir, 0, 255)); } diff --git a/src/Ext/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp index 19719a31c8..c63bde4033 100644 --- a/src/Ext/Aircraft/Hooks.cpp +++ b/src/Ext/Aircraft/Hooks.cpp @@ -167,3 +167,25 @@ DEFINE_HOOK(0x44402E, BuildingClass_ExitObject_PoseDir2, 0x5) #pragma endregion +DEFINE_HOOK(0x4CF68D, FlyLocomotionClass_DrawMatrix_OnAirport, 0x5) +{ + GET(ILocomotion*, iloco, ESI); + __assume(iloco != nullptr); + auto loco = static_cast(iloco); + auto pThis = static_cast(loco->LinkedTo); + if (loco->AirportBound && loco->CurrentSpeed == 0.0 && pThis->GetHeight() <= 0) + { + float ars = pThis->AngleRotatedSideways; + float arf = pThis->AngleRotatedForwards; + if (std::abs(ars) > 0.005 || std::abs(arf) > 0.005) + { + LEA_STACK(Matrix3D*, mat, STACK_OFFSET(0x38, -0x30)); + mat->TranslateZ(float(std::abs(Math::sin(ars)) * pThis->Type->VoxelScaleX + + std::abs(Math::sin(arf)) * pThis->Type->VoxelScaleY)); + R->ECX(pThis); + return 0x4CF6AD; + } + } + + return 0; +} diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index 5e107ee536..e5d3ec467e 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -97,6 +97,39 @@ HouseClass* AnimExt::GetOwnerHouse(AnimClass* pAnim, HouseClass* pDefaultOwner) return pTechnoOwner ? pTechnoOwner : pDefaultOwner; } +void AnimExt::VeinAttackAI(AnimClass* pAnim) +{ + CellStruct pCoordinates = pAnim->GetMapCoords(); + CellClass* pCell = MapClass::Instance->GetCellAt(pCoordinates); + ObjectClass* pOccupier = pCell->FirstObject; + constexpr unsigned char fullyFlownWeedStart = 0x30; // Weeds starting from this overlay frame are fully grown + constexpr unsigned int weedOverlayIndex = 126; + + if (!pOccupier || pOccupier->GetHeight() > 0 || pCell->OverlayTypeIndex != weedOverlayIndex + || pCell->OverlayData < fullyFlownWeedStart || pCell->SlopeIndex) + { + pAnim->UnableToContinue = true; + } + + if (Unsorted::CurrentFrame % 2 == 0) + { + while (pOccupier != nullptr) + { + ObjectClass* pNext = pOccupier->NextObject; + int damage = RulesClass::Instance->VeinDamage; + TechnoClass* pTechno = abstract_cast(pOccupier); + + if (pTechno && !pTechno->GetTechnoType()->ImmuneToVeins && !pTechno->HasAbility(Ability::VeinProof) + && pTechno->Health > 0 && pTechno->IsAlive && pTechno->GetHeight() <= 5) + { + pTechno->ReceiveDamage(&damage, 0, RulesExt::Global()->VeinholeWarhead, nullptr, false, false, nullptr); + } + + pOccupier = pNext; + } + } +} + void AnimExt::HandleDebrisImpact(AnimTypeClass* pExpireAnim, AnimTypeClass* pWakeAnim, Iterator splashAnims, HouseClass* pOwner, WarheadTypeClass* pWarhead, int nDamage, CellClass* pCell, CoordStruct nLocation, bool heightFlag, bool isMeteor, bool warheadDetonate, bool explodeOnWater, bool splashAnimsPickRandom) { @@ -132,7 +165,7 @@ void AnimExt::HandleDebrisImpact(AnimTypeClass* pExpireAnim, AnimTypeClass* pWak if (pWakeAnim) pWakeAnimToUse = pWakeAnim; - if (splashAnims.size() > 0) + if (!splashAnims.empty()) { auto nIndexR = (splashAnims.size() - 1); auto nIndex = splashAnimsPickRandom ? diff --git a/src/Ext/Anim/Body.h b/src/Ext/Anim/Body.h index 8643979102..4db09de6d2 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -93,6 +93,8 @@ class AnimExt static bool SetAnimOwnerHouseKind(AnimClass* pAnim, HouseClass* pInvoker, HouseClass* pVictim, bool defaultToVictimOwner = true, bool defaultToInvokerOwner = false); static HouseClass* GetOwnerHouse(AnimClass* pAnim, HouseClass* pDefaultOwner = nullptr); + static void VeinAttackAI(AnimClass* pAnim); + static void HandleDebrisImpact(AnimTypeClass* pExpireAnim, AnimTypeClass* pWakeAnim, Iterator splashAnims, HouseClass* pOwner, WarheadTypeClass* pWarhead, int nDamage, CellClass* pCell, CoordStruct nLocation, bool heightFlag, bool isMeteor, bool warheadDetonate, bool explodeOnWater, bool splashAnimsPickRandom); }; diff --git a/src/Ext/Anim/Hooks.cpp b/src/Ext/Anim/Hooks.cpp index 414f7d421e..7326337bfb 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -280,6 +280,8 @@ DEFINE_HOOK(0x423365, AnimClass_DrawIt_ExtraShadow, 0x8) if (!pTypeExt->ExtraShadow) return SkipExtraShadow; + + return DrawExtraShadow; } return SkipExtraShadow; diff --git a/src/Ext/Building/Body.cpp b/src/Ext/Building/Body.cpp index a61eed4fa4..07a52c24fe 100644 --- a/src/Ext/Building/Body.cpp +++ b/src/Ext/Building/Body.cpp @@ -245,12 +245,15 @@ bool BuildingExt::DoGrindingExtras(BuildingClass* pBuilding, TechnoClass* pTechn auto const pTypeExt = pExt->TypeExtData; pExt->AccumulatedIncome += refund; + pExt->GrindingWeapon_AccumulatedCredits += refund; - if (pTypeExt->Grinding_Weapon.isset() - && Unsorted::CurrentFrame >= pExt->GrindingWeapon_LastFiredFrame + pTypeExt->Grinding_Weapon.Get()->ROF) + if (pTypeExt->Grinding_Weapon.isset() && + Unsorted::CurrentFrame >= pExt->GrindingWeapon_LastFiredFrame + pTypeExt->Grinding_Weapon.Get()->ROF && + pExt->GrindingWeapon_AccumulatedCredits >= pTypeExt->Grinding_Weapon_RequiredCredits) { TechnoExt::FireWeaponAtSelf(pBuilding, pTypeExt->Grinding_Weapon.Get()); pExt->GrindingWeapon_LastFiredFrame = Unsorted::CurrentFrame; + pExt->GrindingWeapon_AccumulatedCredits = 0; } if (pTypeExt->Grinding_Sound.isset()) @@ -338,6 +341,7 @@ void BuildingExt::ExtData::Serialize(T& Stm) .Process(this->IsCreatedFromMapFile) .Process(this->LimboID) .Process(this->GrindingWeapon_LastFiredFrame) + .Process(this->GrindingWeapon_AccumulatedCredits) .Process(this->CurrentAirFactory) .Process(this->AccumulatedIncome) .Process(this->CurrentLaserWeaponIndex) @@ -413,7 +417,7 @@ DEFINE_HOOK(0x453E20, BuildingClass_SaveLoad_Prefix, 0x5) return 0; } -DEFINE_HOOK(0x454174, BuildingClass_Load, 0xA) +DEFINE_HOOK(0x454174, BuildingClass_Load_LightSource, 0xA) { GET(BuildingClass*, pThis, EDI); diff --git a/src/Ext/Building/Body.h b/src/Ext/Building/Body.h index 13f00c90bb..5f20ae79f2 100644 --- a/src/Ext/Building/Body.h +++ b/src/Ext/Building/Body.h @@ -31,6 +31,7 @@ class BuildingExt bool IsCreatedFromMapFile; int LimboID; int GrindingWeapon_LastFiredFrame; + int GrindingWeapon_AccumulatedCredits; BuildingClass* CurrentAirFactory; int AccumulatedIncome; OptionalStruct CurrentLaserWeaponIndex; @@ -42,6 +43,7 @@ class BuildingExt , IsCreatedFromMapFile { false } , LimboID { -1 } , GrindingWeapon_LastFiredFrame { 0 } + , GrindingWeapon_AccumulatedCredits { 0 } , CurrentAirFactory { nullptr } , AccumulatedIncome { 0 } , CurrentLaserWeaponIndex {} diff --git a/src/Ext/Building/Hooks.cpp b/src/Ext/Building/Hooks.cpp index e8acb056ca..509bb0da51 100644 --- a/src/Ext/Building/Hooks.cpp +++ b/src/Ext/Building/Hooks.cpp @@ -192,11 +192,7 @@ DEFINE_HOOK(0x4502F4, BuildingClass_Update_Factory_Phobos, 0x6) break; } - if (!currFactory) - { - Game::RaiseError(E_POINTER); - } - else if (!*currFactory) + if (!*currFactory) { *currFactory = pThis; return 0; diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index edd4713fa4..a5060d2a2e 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -139,6 +139,7 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Grinding_Sound.Read(exINI, pSection, "Grinding.Sound"); this->Grinding_PlayDieSound.Read(exINI, pSection, "Grinding.PlayDieSound"); this->Grinding_Weapon.Read(exINI, pSection, "Grinding.Weapon"); + this->Grinding_Weapon_RequiredCredits.Read(exINI, pSection, "Grinding.Weapon.RequiredCredits"); this->DisplayIncome.Read(exINI, pSection, "DisplayIncome"); this->DisplayIncome_Houses.Read(exINI, pSection, "DisplayIncome.Houses"); @@ -240,6 +241,7 @@ void BuildingTypeExt::ExtData::Serialize(T& Stm) .Process(this->Grinding_Sound) .Process(this->Grinding_PlayDieSound) .Process(this->Grinding_Weapon) + .Process(this->Grinding_Weapon_RequiredCredits) .Process(this->DisplayIncome) .Process(this->DisplayIncome_Houses) .Process(this->DisplayIncome_Offset) diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index d796be94dc..aaecb9bd01 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -35,6 +35,7 @@ class BuildingTypeExt NullableIdx Grinding_Sound; Nullable Grinding_Weapon; + Valueable Grinding_Weapon_RequiredCredits; ValueableVector Grinding_AllowTypes; ValueableVector Grinding_DisallowTypes; Valueable Grinding_AllowAllies; @@ -82,6 +83,7 @@ class BuildingTypeExt , Grinding_Sound {} , Grinding_PlayDieSound { true } , Grinding_Weapon {} + , Grinding_Weapon_RequiredCredits { 0 } , DisplayIncome { } , DisplayIncome_Houses { } , DisplayIncome_Offset { { 0,0 } } diff --git a/src/Ext/BuildingType/Hooks.Upgrade.cpp b/src/Ext/BuildingType/Hooks.Upgrade.cpp index 4074ef5015..26446a3dba 100644 --- a/src/Ext/BuildingType/Hooks.Upgrade.cpp +++ b/src/Ext/BuildingType/Hooks.Upgrade.cpp @@ -122,3 +122,71 @@ DEFINE_HOOK(0x4F7877, HouseClass_CanBuild_UpgradesInteraction_WithoutAres, 0x5) } #pragma endregion + +#pragma region UpgradeAnimLogic + +// Parse Powered(Light|Effect|Special) keys for upgrade anims. +DEFINE_HOOK(0x4648B3, BuildingTypeClass_ReadINI_PowerUpAnims, 0x5) +{ + GET(BuildingTypeClass*, pThis, EBP); + GET(int, index, EBX); + + auto const pINI = &CCINIClass::INI_Art(); + auto const animData = &pThis->BuildingAnim[index - 1]; + + char buffer[0x20]; + + 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); + + return 0; +} + +// Don't allow upgrade anims to be created if building is not upgraded or they require power to be shown and the building isn't powered. +static __forceinline bool AllowUpgradeAnim(BuildingClass* pBuilding, BuildingAnimSlot anim) +{ + auto const pType = pBuilding->Type; + + if (pType->Upgrades != 0 && anim >= BuildingAnimSlot::Upgrade1 && anim <= BuildingAnimSlot::Upgrade3 && !pBuilding->Anims[int(anim)]) + { + int upgradeLevel = pBuilding->UpgradeLevel - 1; + + if (upgradeLevel < 0 || (int)anim != upgradeLevel) + return false; + + auto const animData = pType->BuildingAnim[int(anim)]; + + if (((pType->Powered && pType->PowerDrain > 0 && (animData.PoweredLight || animData.PoweredEffect)) || + (pType->PoweredSpecial && animData.PoweredSpecial)) && + !(pBuilding->CurrentMission != Mission::Construction && pBuilding->CurrentMission != Mission::Selling && pBuilding->IsPowerOnline())) + { + return false; + } + } + + return true; +} + +DEFINE_HOOK(0x45189D, BuildingClass_AnimUpdate_Upgrades, 0x6) +{ + enum { SkipAnim = 0x451B2C }; + + GET(BuildingClass*, pThis, ESI); + GET_STACK(BuildingAnimSlot, anim, STACK_OFFSET(0x34, 0x8)); + + if (!AllowUpgradeAnim(pThis, anim)) + return SkipAnim; + + return 0; +} + +#pragma endregion diff --git a/src/Ext/Bullet/Hooks.DetonateLogics.cpp b/src/Ext/Bullet/Hooks.DetonateLogics.cpp index 17a2d21264..ebfc0cb2e5 100644 --- a/src/Ext/Bullet/Hooks.DetonateLogics.cpp +++ b/src/Ext/Bullet/Hooks.DetonateLogics.cpp @@ -272,11 +272,23 @@ DEFINE_HOOK(0x46A290, BulletClass_Logics_Extras, 0x5) auto const pWH = pWeaponExt->ExtraWarheads[i]; auto const pOwner = pThis->Owner ? pThis->Owner->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse; int damage = defaultDamage; + size_t size = pWeaponExt->ExtraWarheads_DamageOverrides.size(); - if (pWeaponExt->ExtraWarheads_DamageOverrides.size() > i) + if (size > i) damage = pWeaponExt->ExtraWarheads_DamageOverrides[i]; + else if (size > 0) + damage = pWeaponExt->ExtraWarheads_DamageOverrides[size - 1]; - WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner); + bool detonate = true; + size = pWeaponExt->ExtraWarheads_DetonationChances.size(); + + if (size > i) + detonate = pWeaponExt->ExtraWarheads_DetonationChances[i] >= ScenarioClass::Instance->Random.RandomDouble(); + if (size > 0) + detonate = pWeaponExt->ExtraWarheads_DetonationChances[size - 1] >= ScenarioClass::Instance->Random.RandomDouble(); + + if (detonate) + WarheadTypeExt::DetonateAt(pWH, *coords, pThis->Owner, damage, pOwner); } } diff --git a/src/Ext/BulletType/Body.cpp b/src/Ext/BulletType/Body.cpp index 91278c3cb2..f7caa01a8c 100644 --- a/src/Ext/BulletType/Body.cpp +++ b/src/Ext/BulletType/Body.cpp @@ -63,6 +63,42 @@ void BulletTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) pSection = pThis->ImageFile; this->LaserTrail_Types.Read(exArtINI, pSection, "LaserTrail.Types"); + + this->TrajectoryValidation(); +} + +void BulletTypeExt::ExtData::TrajectoryValidation() const +{ + auto pThis = this->OwnerObject(); + const char* pSection = pThis->ID; + + // Trajectory validation combined with other projectile behaviour. + if (this->TrajectoryType) + { + if (pThis->Arcing) + { + Debug::Log("[Developer warning] [%s] has Trajectory set together with Arcing. Arcing has been set to false.\n", pSection); + pThis->Arcing = false; + } + + if (pThis->Inviso) + { + Debug::Log("[Developer warning] [%s] has Trajectory set together with Inviso. Inviso has been set to false.\n", pSection); + pThis->Inviso = false; + } + + if (pThis->ROT) + { + Debug::Log("[Developer warning] [%s] has Trajectory set together with ROT value other than 0. ROT has been set to 0.\n", pSection); + pThis->ROT = 0; + } + + if (pThis->Vertical) + { + Debug::Log("[Developer warning] [%s] has Trajectory set together with Vertical. Vertical has been set to false.\n", pSection); + pThis->Vertical = false; + } + } } template diff --git a/src/Ext/BulletType/Body.h b/src/Ext/BulletType/Body.h index fc259f94f5..1d17d39626 100644 --- a/src/Ext/BulletType/Body.h +++ b/src/Ext/BulletType/Body.h @@ -86,6 +86,8 @@ class BulletTypeExt private: template void Serialize(T& Stm); + + void TrajectoryValidation() const; }; class ExtContainer final : public Container { diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index 4ec1639554..91fa947181 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -222,6 +222,17 @@ DEFINE_HOOK(0x7015C9, TechnoClass_Captured_UpdateTracking, 0x6) pNewOwnerExt->OwnedAutoDeathObjects.push_back(pExt); } + if (auto pMe = generic_cast(pThis)) + { + bool I_am_human = pThis->Owner->IsControlledByHuman(); + bool You_are_human = pNewOwner->IsControlledByHuman(); + auto pConvertTo = (I_am_human && !You_are_human) ? pExt->TypeExtData->Convert_HumanToComputer.Get() : + (!I_am_human && You_are_human) ? pExt->TypeExtData->Convert_ComputerToHuman.Get() : nullptr; + + if (pConvertTo && pConvertTo->WhatAmI() == pType->WhatAmI()) + TechnoExt::ConvertToType(pMe, pConvertTo); + } + return 0; } diff --git a/src/Ext/OverlayType/Body.cpp b/src/Ext/OverlayType/Body.cpp index f30da5352f..ae9ff93bc6 100644 --- a/src/Ext/OverlayType/Body.cpp +++ b/src/Ext/OverlayType/Body.cpp @@ -15,7 +15,6 @@ void OverlayTypeExt::ExtData::Serialize(T& Stm) { Stm .Process(this->PaletteFile) - .Process(this->Palette) ; } @@ -35,6 +34,11 @@ void OverlayTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->PaletteFile.Read(pArtINI, pArtSection, "Palette"); + BuildPalette(); +} + +void OverlayTypeExt::ExtData::BuildPalette() +{ if (GeneralUtils::IsValidString(this->PaletteFile)) { char pFilename[0x20]; @@ -48,6 +52,7 @@ void OverlayTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) { Extension::LoadFromStream(Stm); this->Serialize(Stm); + this->BuildPalette(); } void OverlayTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) diff --git a/src/Ext/OverlayType/Body.h b/src/Ext/OverlayType/Body.h index eb41abc9aa..c659a52beb 100644 --- a/src/Ext/OverlayType/Body.h +++ b/src/Ext/OverlayType/Body.h @@ -18,7 +18,7 @@ class OverlayTypeExt { public: PhobosFixedString<32u> PaletteFile; - DynamicVectorClass* Palette; + DynamicVectorClass* Palette; // Intentionally not serialized - rebuilt from the palette file on load. ExtData(OverlayTypeClass* OwnerObject) : Extension(OwnerObject) , PaletteFile {} @@ -37,6 +37,7 @@ class OverlayTypeExt private: template void Serialize(T& Stm); + void BuildPalette(); }; class ExtContainer final : public Container diff --git a/src/Ext/ParticleType/Body.cpp b/src/Ext/ParticleType/Body.cpp new file mode 100644 index 0000000000..e9c8a2bfaf --- /dev/null +++ b/src/Ext/ParticleType/Body.cpp @@ -0,0 +1,104 @@ +#include "Body.h" + +ParticleTypeExt::ExtContainer ParticleTypeExt::ExtMap; + +// ============================= +// load / save + +template +void ParticleTypeExt::ExtData::Serialize(T& Stm) +{ + Stm + .Process(this->Gas_MaxDriftSpeed) + ; +} + +void ParticleTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) +{ + auto pThis = this->OwnerObject(); + const char* pSection = pThis->ID; + + if (!pINI->GetSection(pSection)) + return; + + INI_EX exINI(pINI); + + this->Gas_MaxDriftSpeed.Read(exINI, pSection, "Gas.MaxDriftSpeed"); +} + +void ParticleTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) +{ + Extension::LoadFromStream(Stm); + this->Serialize(Stm); +} + +void ParticleTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm) +{ + Extension::SaveToStream(Stm); + this->Serialize(Stm); +} + +bool ParticleTypeExt::LoadGlobals(PhobosStreamReader& Stm) +{ + return Stm + .Success(); +} + +bool ParticleTypeExt::SaveGlobals(PhobosStreamWriter& Stm) +{ + return Stm + .Success(); +} + +// ============================= +// container + +ParticleTypeExt::ExtContainer::ExtContainer() : Container("ParticleTypeClass") { } +ParticleTypeExt::ExtContainer::~ExtContainer() = default; + +// ============================= +// container hooks + +DEFINE_HOOK(0x644DBB, ParticleTypeClass_CTOR, 0x5) +{ + GET(ParticleTypeClass*, pItem, ESI); + + ParticleTypeExt::ExtMap.TryAllocate(pItem); + + return 0; +} + +DEFINE_HOOK_AGAIN(0x6457A0, ParticleTypeClass_SaveLoad_Prefix, 0x5) +DEFINE_HOOK(0x645660, ParticleTypeClass_SaveLoad_Prefix, 0x7) +{ + GET_STACK(ParticleTypeClass*, pItem, 0x4); + GET_STACK(IStream*, pStm, 0x8); + + ParticleTypeExt::ExtMap.PrepareStream(pItem, pStm); + + return 0; +} + +DEFINE_HOOK(0x64578C, ParticleTypeClass_Load_Suffix, 0x5) +{ + ParticleTypeExt::ExtMap.LoadStatic(); + + return 0; +} + +DEFINE_HOOK(0x64580A, ParticleTypeClass_Save_Suffix, 0x7) +{ + ParticleTypeExt::ExtMap.SaveStatic(); + + return 0; +} + +DEFINE_HOOK(0x6453FF, ParticleTypeClass_LoadFromINI, 0x6) +{ + GET(ParticleTypeClass*, pItem, ESI); + GET_STACK(CCINIClass*, pINI, STACK_OFFSET(0xDC, 0x4)); + + ParticleTypeExt::ExtMap.LoadFromINI(pItem, pINI); + + return 0; +} diff --git a/src/Ext/ParticleType/Body.h b/src/Ext/ParticleType/Body.h new file mode 100644 index 0000000000..91f24b4298 --- /dev/null +++ b/src/Ext/ParticleType/Body.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include +#include +#include +#include + +class ParticleTypeExt +{ +public: + using base_type = ParticleTypeClass; + + static constexpr DWORD Canary = 0xEAFEEAFE; + static constexpr size_t ExtPointerOffset = 0x18; + + class ExtData final : public Extension + { + public: + Valueable Gas_MaxDriftSpeed; + + ExtData(ParticleTypeClass* OwnerObject) : Extension(OwnerObject) + , Gas_MaxDriftSpeed { 2 } + { } + + virtual ~ExtData() = default; + + virtual void LoadFromINIFile(CCINIClass* pINI) override; + + virtual void InvalidatePointer(void* ptr, bool bRemoved) override { } + + virtual void LoadFromStream(PhobosStreamReader& Stm) override; + virtual void SaveToStream(PhobosStreamWriter& Stm) override; + + private: + template + void Serialize(T& Stm); + }; + + class ExtContainer final : public Container + { + public: + ExtContainer(); + ~ExtContainer(); + }; + + static ExtContainer ExtMap; + + static bool LoadGlobals(PhobosStreamReader& Stm); + static bool SaveGlobals(PhobosStreamWriter& Stm); +}; diff --git a/src/Ext/ParticleType/Hooks.cpp b/src/Ext/ParticleType/Hooks.cpp new file mode 100644 index 0000000000..5c17c2280d --- /dev/null +++ b/src/Ext/ParticleType/Hooks.cpp @@ -0,0 +1,26 @@ +#include "Body.h" + +#include + +DEFINE_HOOK(0x62BE30, ParticleClass_Gas_AI_DriftSpeed, 0x5) +{ + enum { ContinueAI = 0x62BE60 }; + + GET(ParticleClass*, pParticle, EBP); + + auto pExt = ParticleTypeExt::ExtMap.Find(pParticle->Type); + int maxDriftSpeed = pExt->Gas_MaxDriftSpeed; + int minDriftSpeed = -maxDriftSpeed; + + if (pParticle->Velocity.X > maxDriftSpeed) + pParticle->Velocity.X = maxDriftSpeed; + else if (pParticle->Velocity.X < minDriftSpeed) + pParticle->Velocity.X = minDriftSpeed; + + if (pParticle->Velocity.Y > maxDriftSpeed) + pParticle->Velocity.Y = maxDriftSpeed; + else if (pParticle->Velocity.Y < minDriftSpeed) + pParticle->Velocity.Y = minDriftSpeed; + + return ContinueAI; +} diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 3daed98a6e..58bb324be9 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -80,6 +80,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->RadWarhead_Detonate.Read(exINI, GameStrings::Radiation, "RadSiteWarhead.Detonate"); this->RadHasOwner.Read(exINI, GameStrings::Radiation, "RadHasOwner"); this->RadHasInvoker.Read(exINI, GameStrings::Radiation, "RadHasInvoker"); + this->VeinholeWarhead.Read(exINI, GameStrings::CombatDamage, "VeinholeWarhead"); this->MissingCameo.Read(pINI, GameStrings::AudioVisual, "MissingCameo"); this->PlacementGrid_Translucency.Read(exINI, GameStrings::AudioVisual, "PlacementGrid.Translucency"); @@ -103,9 +104,11 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->Pips_Generic_Buildings_Size.Read(exINI, GameStrings::AudioVisual, "Pips.Generic.Buildings.Size"); this->Pips_Ammo_Size.Read(exINI, GameStrings::AudioVisual, "Pips.Ammo.Size"); this->Pips_Ammo_Buildings_Size.Read(exINI, GameStrings::AudioVisual, "Pips.Ammo.Buildings.Size"); - this->Pips_Tiberiums_EmptyFrame.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.EmptyFrame"); this->Pips_Tiberiums_Frames.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.Frames"); + this->Pips_Tiberiums_EmptyFrame.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.EmptyFrame"); this->Pips_Tiberiums_DisplayOrder.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.DisplayOrder"); + this->Pips_Tiberiums_WeedFrame.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.WeedFrame"); + this->Pips_Tiberiums_WeedEmptyFrame.Read(exINI, GameStrings::AudioVisual, "Pips.Tiberiums.WeedEmptyFrame"); this->ToolTip_Background_Color.Read(exINI, GameStrings::AudioVisual, "ToolTip.Background.Color"); this->ToolTip_Background_Opacity.Read(exINI, GameStrings::AudioVisual, "ToolTip.Background.Opacity"); this->ToolTip_Background_BlurSize.Read(exINI, GameStrings::AudioVisual, "ToolTip.Background.BlurSize"); @@ -114,6 +117,13 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->AnimRemapDefaultColorScheme.Read(exINI, GameStrings::AudioVisual, "AnimRemapDefaultColorScheme"); this->TimerBlinkColorScheme.Read(exINI, GameStrings::AudioVisual, "TimerBlinkColorScheme"); this->ShowDesignatorRange.Read(exINI, GameStrings::AudioVisual, "ShowDesignatorRange"); + NullableAirShadowBaseScale; + AirShadowBaseScale.Read(exINI, GameStrings::AudioVisual, "AirShadowBaseScale"); + if (AirShadowBaseScale.isset() && AirShadowBaseScale.Get() > 0) + this->AirShadowBaseScale_log = -std::log(std::min(AirShadowBaseScale.Get(), 1.0)); + + this->HeightShadowScaling.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling"); + this->HeightShadowScaling_MinScale.Read(exINI, GameStrings::AudioVisual, "HeightShadowScaling.MinScale"); this->AllowParallelAIQueues.Read(exINI, "GlobalControls", "AllowParallelAIQueues"); this->ForbidParallelAIQueues_Aircraft.Read(exINI, "GlobalControls", "ForbidParallelAIQueues.Infantry"); @@ -230,6 +240,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->RadHasInvoker) .Process(this->JumpjetCrash) .Process(this->JumpjetNoWobbles) + .Process(this->VeinholeWarhead) .Process(this->MissingCameo) .Process(this->PlacementGrid_Translucency) .Process(this->PlacementGrid_TranslucencyWithPreview) @@ -251,9 +262,14 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->Pips_Generic_Buildings_Size) .Process(this->Pips_Ammo_Size) .Process(this->Pips_Ammo_Buildings_Size) - .Process(this->Pips_Tiberiums_EmptyFrame) .Process(this->Pips_Tiberiums_Frames) + .Process(this->Pips_Tiberiums_EmptyFrame) .Process(this->Pips_Tiberiums_DisplayOrder) + .Process(this->Pips_Tiberiums_WeedFrame) + .Process(this->Pips_Tiberiums_WeedEmptyFrame) + .Process(this->AirShadowBaseScale_log) + .Process(this->HeightShadowScaling) + .Process(this->HeightShadowScaling_MinScale) .Process(this->AllowParallelAIQueues) .Process(this->ForbidParallelAIQueues_Aircraft) .Process(this->ForbidParallelAIQueues_Building) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 00ef1f8bb8..c134e46ed8 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -47,6 +47,8 @@ class RulesExt Valueable JumpjetCrash; Valueable JumpjetNoWobbles; + Nullable VeinholeWarhead; + PhobosFixedString<32u> MissingCameo; TranslucencyLevel PlacementGrid_Translucency; @@ -70,9 +72,15 @@ class RulesExt Valueable Pips_Generic_Buildings_Size; Valueable Pips_Ammo_Size; Valueable Pips_Ammo_Buildings_Size; - Valueable Pips_Tiberiums_EmptyFrame; ValueableVector Pips_Tiberiums_Frames; + Valueable Pips_Tiberiums_EmptyFrame; ValueableVector Pips_Tiberiums_DisplayOrder; + Valueable Pips_Tiberiums_WeedFrame; + Valueable Pips_Tiberiums_WeedEmptyFrame; + + Valueable HeightShadowScaling; + Valueable HeightShadowScaling_MinScale; + double AirShadowBaseScale_log; Valueable AllowParallelAIQueues; Valueable ForbidParallelAIQueues_Aircraft; @@ -127,6 +135,7 @@ class RulesExt , RadHasInvoker { false } , JumpjetCrash { 5.0 } , JumpjetNoWobbles { false } + , VeinholeWarhead {} , MissingCameo { GameStrings::XXICON_SHP() } , PlacementGrid_Translucency { 0 } @@ -149,9 +158,16 @@ class RulesExt , Pips_Generic_Buildings_Size { { 4, 2 } } , Pips_Ammo_Size { { 4, 0 } } , Pips_Ammo_Buildings_Size { { 4, 2 } } - , Pips_Tiberiums_EmptyFrame { 0 } , Pips_Tiberiums_Frames {} + , Pips_Tiberiums_EmptyFrame { 0 } , Pips_Tiberiums_DisplayOrder {} + , Pips_Tiberiums_WeedFrame { 1 } + , Pips_Tiberiums_WeedEmptyFrame { 0 } + + , HeightShadowScaling { false } + , HeightShadowScaling_MinScale { 0.0 } + , AirShadowBaseScale_log { 0.693376137 } + , AllowParallelAIQueues { true } , ForbidParallelAIQueues_Aircraft { false } , ForbidParallelAIQueues_Building { false } diff --git a/src/Ext/SWType/Body.cpp b/src/Ext/SWType/Body.cpp index 18366f0f45..6058134f8e 100644 --- a/src/Ext/SWType/Body.cpp +++ b/src/Ext/SWType/Body.cpp @@ -45,6 +45,10 @@ void SWTypeExt::ExtData::Serialize(T& Stm) .Process(this->ShowTimer_Priority) .Process(this->Convert_Pairs) .Process(this->ShowDesignatorRange) + .Process(this->UseWeeds) + .Process(this->UseWeeds_Amount) + .Process(this->UseWeeds_StorageTimer) + .Process(this->UseWeeds_ReadinessAnimationPercentage) ; } @@ -144,6 +148,11 @@ void SWTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::Owner); this->ShowDesignatorRange.Read(exINI, pSection, "ShowDesignatorRange"); + + this->UseWeeds.Read(exINI, pSection, "UseWeeds"); + this->UseWeeds_Amount.Read(exINI, pSection, "UseWeeds.Amount"); + this->UseWeeds_StorageTimer.Read(exINI, pSection, "UseWeeds.StorageTimer"); + this->UseWeeds_ReadinessAnimationPercentage.Read(exINI, pSection, "UseWeeds.ReadinessAnimationPercentage"); } void SWTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/SWType/Body.h b/src/Ext/SWType/Body.h index 945cd2154a..6c82e61bea 100644 --- a/src/Ext/SWType/Body.h +++ b/src/Ext/SWType/Body.h @@ -63,6 +63,11 @@ class SWTypeExt std::vector Convert_Pairs; + Valueable UseWeeds; + Valueable UseWeeds_Amount; + Valueable UseWeeds_StorageTimer; + Valueable UseWeeds_ReadinessAnimationPercentage; + ExtData(SuperWeaponTypeClass* OwnerObject) : Extension(OwnerObject) , Money_Amount { 0 } , SW_Inhibitors {} @@ -98,6 +103,10 @@ class SWTypeExt , ShowTimer_Priority { 0 } , Convert_Pairs {} , ShowDesignatorRange { true } + , UseWeeds { false } + , UseWeeds_Amount { RulesClass::Instance->WeedCapacity } + , UseWeeds_StorageTimer { false } + , UseWeeds_ReadinessAnimationPercentage { 0.9 } { } // Ares 0.A functions diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index d3baed400a..19c80762d2 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -247,7 +247,7 @@ void SWTypeExt::ExtData::ApplySWNext(SuperClass* pSW, const CellStruct& cell) int oldstart = pSuper->RechargeTimer.StartTime; int oldleft = pSuper->RechargeTimer.TimeLeft; pSuper->SetReadiness(true); - pSuper->Launch(cell, true); + pSuper->Launch(cell, pHouse->IsCurrentPlayer()); pSuper->Reset(); if (!this->SW_Next_RealLaunch) { diff --git a/src/Ext/SWType/Hooks.cpp b/src/Ext/SWType/Hooks.cpp index 3e0fc992dd..cb0369c5ef 100644 --- a/src/Ext/SWType/Hooks.cpp +++ b/src/Ext/SWType/Hooks.cpp @@ -74,3 +74,111 @@ DEFINE_HOOK(0x6DBE74, Tactical_SuperLinesCircles_ShowDesignatorRange, 0x7) return 0; } + +DEFINE_HOOK(0x6CBEF4, SuperClass_AnimStage_UseWeeds, 0x6) +{ + enum + { + Ready = 0x6CBFEC, + NotReady = 0x6CC064, + ProgressInEax = 0x6CC066 + }; + + constexpr int maxCounterFrames = 54; + + GET(SuperClass*, pSuper, ECX); + GET(SuperWeaponTypeClass*, pSWType, EBX); + + auto pExt = SWTypeExt::ExtMap.Find(pSWType); + + if (pExt->UseWeeds) + { + if (pSuper->IsReady) + return Ready; + + if (pExt->UseWeeds_StorageTimer) + { + int progress = static_cast(pSuper->Owner->OwnedWeed.GetTotalAmount() * maxCounterFrames / pExt->UseWeeds_Amount); + if (progress > maxCounterFrames) + progress = maxCounterFrames; + + R->EAX(progress); + return ProgressInEax; + } + else + { + return NotReady; + } + } + + return 0; +} + +DEFINE_HOOK(0x6CBD2C, SuperClass_AI_UseWeeds, 0x6) +{ + enum + { + NothingChanged = 0x6CBE9D, + SomethingChanged = 0x6CBD48, + Charged = 0x6CBD73 + }; + + enum + { + SWReadyTimer = 0, + SWAlmostReadyTimer = 15, + SWNotReadyTimer = 915 + }; + + GET(SuperClass*, pSuper, ESI); + + auto pExt = SWTypeExt::ExtMap.Find(pSuper->Type); + + if (pExt->UseWeeds) + { + if (pSuper->Type->ShowTimer) + pSuper->Type->ShowTimer = false; + + if (pSuper->Owner->OwnedWeed.GetTotalAmount() >= pExt->UseWeeds_Amount) + { + pSuper->Owner->OwnedWeed.RemoveAmount(static_cast(pExt->UseWeeds_Amount), 0); + pSuper->RechargeTimer.Start(SWReadyTimer); // The Armageddon is here + return Charged; + } + + if (pSuper->Owner->OwnedWeed.GetTotalAmount() >= pExt->UseWeeds_ReadinessAnimationPercentage * pExt->UseWeeds_Amount) + { + pSuper->RechargeTimer.Start(SWAlmostReadyTimer); // The end is nigh! + } + else + { + pSuper->RechargeTimer.Start(SWNotReadyTimer); // 61 seconds > 60 seconds (animation activation threshold) + } + + int animStage = pSuper->AnimStage(); + if (pSuper->CameoChargeState != animStage) + { + pSuper->CameoChargeState = animStage; + return SomethingChanged; + } + + return NothingChanged; + } + + return 0; +} + +// This is pointless for SWs using weeds because their charge is tied to weed storage. +DEFINE_HOOK(0x6CC1E6, SuperClass_SetSWCharge_UseWeeds, 0x5) +{ + enum { Skip = 0x6CC251 }; + + GET(SuperClass*, pSuper, EDI); + + auto pExt = SWTypeExt::ExtMap.Find(pSuper->Type); + + if (pExt->UseWeeds) + return Skip; + + return 0; +} diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 33477166ee..9bbab3d268 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -22,7 +22,14 @@ void TechnoExt::ExtData::OnEarlyUpdate() if (!this->TypeExtData || this->TypeExtData->OwnerObject() != pType) this->UpdateTypeData(pType); - this->IsInTunnel = false; // TechnoClass::AI is only called when not in tunnel. + // Update tunnel state on exit, TechnoClass::AI is only called when not in tunnel. + if (this->IsInTunnel) + { + this->IsInTunnel = false; + + if (const auto pShieldData = this->Shield.get()) + pShieldData->SetAnimationVisibility(true); + } if (this->CheckDeathConditions()) return; @@ -640,6 +647,13 @@ void TechnoExt::KillSelf(TechnoClass* pThis, AutoDeathBehavior deathOption, Anim { if (isInLimbo) { + // Remove parasite units first before deleting them. + if (auto const pFoot = abstract_cast(pThis)) + { + if (pFoot->ParasiteImUsing && pFoot->ParasiteImUsing->Victim) + pFoot->ParasiteImUsing->ExitUnit(); + } + pThis->RegisterKill(pThis->Owner); pThis->UnInit(); return; diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index dded71a8bc..68f9e78d1f 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -273,7 +273,7 @@ DEFINE_HOOK(0x6FC339, TechnoClass_CanFire, 0x6) CellClass* pTargetCell = nullptr; // AAOnly doesn't need to be checked if LandTargeting=1. - if ((!pTechno || pTechno->GetTechnoType()->LandTargeting != LandTargetingType::Land_Not_OK) && pWeapon->Projectile->AA && pTarget && !pTarget->IsInAir()) + if (pThis->GetTechnoType()->LandTargeting != LandTargetingType::Land_Not_OK && pWeapon->Projectile->AA && pTarget && !pTarget->IsInAir()) { auto const pBulletTypeExt = BulletTypeExt::ExtMap.Find(pWeapon->Projectile); diff --git a/src/Ext/Techno/Hooks.Pips.cpp b/src/Ext/Techno/Hooks.Pips.cpp index fc9bb61879..064274c7d7 100644 --- a/src/Ext/Techno/Hooks.Pips.cpp +++ b/src/Ext/Techno/Hooks.Pips.cpp @@ -162,44 +162,99 @@ DEFINE_HOOK(0x70A1F6, TechnoClass_DrawPips_Tiberium, 0x6) GET(int, yOffset, ESI); Point2D position = { offset->X, offset->Y }; - int totalStorage = pThis->GetTechnoType()->Storage; + const int totalStorage = pThis->GetTechnoType()->Storage; - std::vector tibPipCounts(TiberiumClass::Array.get()->Count); + std::vector pipsToDraw; - for (size_t i = 0; i < tibPipCounts.size(); i++) + bool isWeeder = false; + switch (pThis->WhatAmI()) { - tibPipCounts[i] = static_cast(pThis->Tiberium.GetAmount(i) / totalStorage * maxPips + 0.5); + case AbstractType::Building: + isWeeder = static_cast(pThis)->Type->Weeder; + break; + case AbstractType::Unit: + isWeeder = static_cast(pThis)->Type->Weeder; + break; + default: + break; } - auto const& tibDisplayOrders = RulesExt::Global()->Pips_Tiberiums_DisplayOrder.size() ? RulesExt::Global()->Pips_Tiberiums_DisplayOrder : std::vector { 0, 2, 3, 1 }; - auto const& tibFrames = RulesExt::Global()->Pips_Tiberiums_Frames; + if (isWeeder) + { + const int fullWeedFrames = pThis->WhatAmI() == AbstractType::Building ? + static_cast(pThis->Owner->GetWeedStoragePercentage() * maxPips + 0.5) : + static_cast(pThis->Tiberium.GetTotalAmount() / totalStorage * maxPips + 0.5); - for (int i = 0; i < maxPips; i++) + for (int i = 0; i < maxPips; i++) + { + if (i < fullWeedFrames) + pipsToDraw.push_back(RulesExt::Global()->Pips_Tiberiums_WeedFrame); + else + pipsToDraw.push_back(RulesExt::Global()->Pips_Tiberiums_WeedEmptyFrame); + } + } + else { - int frame = RulesExt::Global()->Pips_Tiberiums_EmptyFrame; + std::vector tiberiumPipCounts(TiberiumClass::Array->Count); + + for (size_t i = 0; i < tiberiumPipCounts.size(); i++) + { + tiberiumPipCounts[i] = static_cast(pThis->Tiberium.GetAmount(i) / totalStorage * maxPips + 0.5); + } + + auto const rawPipOrder = RulesExt::Global()->Pips_Tiberiums_DisplayOrder.empty() ? std::vector{ 0, 2, 3, 1 } : RulesExt::Global()->Pips_Tiberiums_DisplayOrder; + auto const& pipFrames = RulesExt::Global()->Pips_Tiberiums_Frames; + int const emptyFrame = RulesExt::Global()->Pips_Tiberiums_EmptyFrame; + + std::vector pipOrder; - for (size_t orderIndex = 0; orderIndex < tibPipCounts.size(); orderIndex++) + // First make a new vector, removing all the duplicate and invalid tiberiums + for (int index : rawPipOrder) { - size_t tibTypeIndex = orderIndex; + if (std::find(pipOrder.begin(), pipOrder.end(), index) == pipOrder.end() && + index >= 0 && index < TiberiumClass::Array->Count) + { + pipOrder.push_back(index); + } + } - if (orderIndex < tibDisplayOrders.size()) - tibTypeIndex = tibDisplayOrders.at(orderIndex); + // Then add any tiberium types that are missing + for (int i = 0; i < TiberiumClass::Array->Count; i++) + { + if (std::find(pipOrder.begin(), pipOrder.end(), i) == pipOrder.end()) + { + pipOrder.push_back(i); + } + } + - if (tibPipCounts[tibTypeIndex] > 0) + for (int i = 0; i < maxPips; i++) + { + for (const int index : pipOrder) { - tibPipCounts[tibTypeIndex]--; + if (tiberiumPipCounts[index] > 0) + { + tiberiumPipCounts[index]--; - if (tibTypeIndex >= tibFrames.size()) - frame = tibTypeIndex == 1 ? 5 : 2; - else - frame = tibFrames.at(tibTypeIndex); + if (static_cast(index) >= pipFrames.size()) + pipsToDraw.push_back(index == 1 ? 5 : 2); + else + pipsToDraw.push_back(pipFrames.at(index)); - break; + break; + } } + + if (pipsToDraw.size() <= static_cast(i)) + pipsToDraw.push_back(emptyFrame); } + } - DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, shape, frame, - &position, rect, BlitterFlags(0x600), 0, 0, ZGradient::Ground, 1000, 0, 0, 0, 0, 0); + for (int pip : pipsToDraw) + { + DSurface::Temp->DrawSHP(FileSystem::PALETTE_PAL, shape, pip, + &position, rect, BlitterFlags::Centered | BlitterFlags::bf_400, 0, 0, + ZGradient::Ground, 1000, 0, nullptr, 0, 0, 0); position.X += offset->Width; position.Y += yOffset; diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index 283774e5d9..cd647ab6e4 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -1,3 +1,4 @@ +#include #include #include "Body.h" @@ -158,14 +159,20 @@ DEFINE_HOOK(0x701DFF, TechnoClass_ReceiveDamage_FlyingStrings, 0x7) return 0; } -DEFINE_HOOK(0x70265F, TechnoClass_ReceiveDamage_Explodes, 0x6) +DEFINE_HOOK(0x702603, TechnoClass_ReceiveDamage_Explodes, 0x6) { - enum { SkipKillingPassengers = 0x702669 }; + enum { SkipExploding = 0x702672, SkipKillingPassengers = 0x702669 }; GET(TechnoClass*, pThis, ESI); const auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + if (pThis->WhatAmI() == AbstractType::Building) + { + if (!pTypeExt->Explodes_DuringBuildup && (pThis->CurrentMission == Mission::Construction || pThis->CurrentMission == Mission::Selling)) + return SkipExploding; + } + if (!pTypeExt->Explodes_KillPassengers) return SkipKillingPassengers; @@ -452,3 +459,29 @@ DEFINE_HOOK(0x5F46AE, ObjectClass_Select, 0x7) return 0; } + +DEFINE_HOOK(0x708FC0, TechnoClass_ResponseMove_Pickup, 0x5) +{ + enum { SkipResponse = 0x709015 }; + + GET(TechnoClass*, pThis, ECX); + + if (auto const pAircraft = abstract_cast(pThis)) + { + if (pAircraft->Type->Carryall && pAircraft->HasAnyLink() && + pAircraft->Destination && (pAircraft->Destination->AbstractFlags & AbstractFlags::Foot) != AbstractFlags::None) + { + auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pAircraft->Type); + + if (pTypeExt->VoicePickup.isset()) + { + pThis->QueueVoice(pTypeExt->VoicePickup.Get()); + + R->EAX(1); + return SkipResponse; + } + } + } + + return 0; +} diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 725c5e870d..28780eac46 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -159,8 +159,6 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Ammo_DeployUnlockMinimumAmount.Read(exINI, pSection, "Ammo.DeployUnlockMinimumAmount"); this->Ammo_DeployUnlockMaximumAmount.Read(exINI, pSection, "Ammo.DeployUnlockMaximumAmount"); - this->VoiceCantDeploy.Read(exINI, pSection, "VoiceCantDeploy"); - this->AutoDeath_Behavior.Read(exINI, pSection, "AutoDeath.Behavior"); this->AutoDeath_VanishAnimation.Read(exINI, pSection, "AutoDeath.VanishAnimation"); this->AutoDeath_OnAmmoDepletion.Read(exINI, pSection, "AutoDeath.OnAmmoDepletion"); @@ -180,6 +178,8 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->EVA_Sold.Read(exINI, pSection, "EVA.Sold"); this->VoiceCreated.Read(exINI, pSection, "VoiceCreated"); + this->VoicePickup.Read(exINI, pSection, "VoicePickup"); + this->CameoPriority.Read(exINI, pSection, "CameoPriority"); this->WarpOut.Read(exINI, pSection, "WarpOut"); @@ -220,6 +220,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->NoSecondaryWeaponFallback_AllowAA.Read(exINI, pSection, "NoSecondaryWeaponFallback.AllowAA"); this->JumpjetRotateOnCrash.Read(exINI, pSection, "JumpjetRotateOnCrash"); + this->ShadowSizeCharacteristicHeight.Read(exINI, pSection, "ShadowSizeCharacteristicHeight"); this->DeployingAnim_AllowAnyDirection.Read(exINI, pSection, "DeployingAnim.AllowAnyDirection"); this->DeployingAnim_KeepUnitVisible.Read(exINI, pSection, "DeployingAnim.KeepUnitVisible"); @@ -241,6 +242,7 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->IronCurtain_KillWarhead.Read(exINI, pSection, "IronCurtain.KillWarhead"); this->Explodes_KillPassengers.Read(exINI, pSection, "Explodes.KillPassengers"); + this->Explodes_DuringBuildup.Read(exINI, pSection, "Explodes.DuringBuildup"); this->DeployFireWeapon.Read(exINI, pSection, "DeployFireWeapon"); this->TargetZoneScanType.Read(exINI, pSection, "TargetZoneScanType"); @@ -274,6 +276,9 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->SpawnHeight.Read(exINI, pSection, "SpawnHeight"); this->LandingDir.Read(exINI, pSection, "LandingDir"); + this->Convert_HumanToComputer.Read(exINI, pSection, "Convert.HumanToComputer"); + this->Convert_ComputerToHuman.Read(exINI, pSection, "Convert.ComputerToHuman"); + // Ares 0.2 this->RadarJamRadius.Read(exINI, pSection, "RadarJamRadius"); @@ -323,7 +328,21 @@ void TechnoTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->TurretOffset.Read(exArtINI, pArtSection, "TurretOffset"); this->TurretShadow.Read(exArtINI, pArtSection, "TurretShadow"); - this->ShadowIndices.Read(exArtINI, pArtSection, "ShadowIndices"); + ValueableVector shadow_indices; + shadow_indices.Read(exArtINI, pArtSection, "ShadowIndices"); + ValueableVector shadow_indices_frame; + shadow_indices_frame.Read(exArtINI, pArtSection, "ShadowIndices.Frame"); + if (shadow_indices_frame.size() != shadow_indices.size()) + { + if (!shadow_indices_frame.empty()) + Debug::LogGame("[Developer warning] %s ShadowIndices.Frame size (%d) does not match ShadowIndices size (%d) \n" + , pSection, shadow_indices_frame.size(), shadow_indices.size()); + shadow_indices_frame.resize(shadow_indices.size(), -1); + } + for (size_t i = 0; i < shadow_indices.size(); i++) + this->ShadowIndices[shadow_indices[i]] = shadow_indices_frame[i]; + + this->ShadowIndex_Frame.Read(exArtINI, pArtSection, "ShadowIndex.Frame"); this->LaserTrailData.clear(); for (size_t i = 0; ; ++i) @@ -436,6 +455,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->TurretOffset) .Process(this->TurretShadow) .Process(this->ShadowIndices) + .Process(this->ShadowIndex_Frame) .Process(this->Spawner_LimitRange) .Process(this->Spawner_ExtraLimitRange) .Process(this->Spawner_DelayFrames) @@ -455,7 +475,6 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->Ammo_AutoDeployMaximumAmount) .Process(this->Ammo_DeployUnlockMinimumAmount) .Process(this->Ammo_DeployUnlockMaximumAmount) - .Process(this->VoiceCantDeploy) .Process(this->AutoDeath_Behavior) .Process(this->AutoDeath_VanishAnimation) @@ -476,6 +495,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->EVA_Sold) .Process(this->VoiceCreated) + .Process(this->VoicePickup) .Process(this->WarpOut) .Process(this->WarpIn) @@ -516,7 +536,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->NoAmmoWeapon) .Process(this->NoAmmoAmount) .Process(this->JumpjetRotateOnCrash) - + .Process(this->ShadowSizeCharacteristicHeight) .Process(this->DeployingAnim_AllowAnyDirection) .Process(this->DeployingAnim_KeepUnitVisible) .Process(this->DeployingAnim_ReverseForUndeploy) @@ -531,7 +551,6 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->SelfHealGainType) .Process(this->Passengers_SyncOwner) .Process(this->Passengers_SyncOwner_RevertOnExit) - .Process(this->Explodes_KillPassengers) .Process(this->PronePrimaryFireFLH) .Process(this->ProneSecondaryFireFLH) @@ -547,6 +566,7 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->IronCurtain_KillWarhead) .Process(this->Explodes_KillPassengers) + .Process(this->Explodes_DuringBuildup) .Process(this->DeployFireWeapon) .Process(this->TargetZoneScanType) @@ -583,6 +603,8 @@ void TechnoTypeExt::ExtData::Serialize(T& Stm) .Process(this->SpawnHeight) .Process(this->LandingDir) .Process(this->DroppodType) + .Process(this->Convert_HumanToComputer) + .Process(this->Convert_ComputerToHuman) ; } void TechnoTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 384b7caf3c..1e0c798b0d 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -38,7 +38,8 @@ class TechnoTypeExt Valueable> TurretOffset; Nullable TurretShadow; - ValueableVector ShadowIndices; + Valueable ShadowIndex_Frame; + std::map ShadowIndices; Valueable Spawner_LimitRange; Valueable Spawner_ExtraLimitRange; Nullable Spawner_DelayFrames; @@ -60,7 +61,6 @@ class TechnoTypeExt Valueable Ammo_AutoDeployMaximumAmount; Valueable Ammo_DeployUnlockMinimumAmount; Valueable Ammo_DeployUnlockMaximumAmount; - NullableIdx VoiceCantDeploy; Nullable AutoDeath_Behavior; Nullable AutoDeath_VanishAnimation; @@ -81,6 +81,7 @@ class TechnoTypeExt NullableIdx EVA_Sold; NullableIdx VoiceCreated; + NullableIdx VoicePickup; // Used by carryalls instead of VoiceMove if set. Nullable WarpOut; Nullable WarpIn; @@ -127,6 +128,7 @@ class TechnoTypeExt Valueable NoAmmoAmount; Valueable JumpjetRotateOnCrash; + Nullable ShadowSizeCharacteristicHeight; Valueable DeployingAnim_AllowAnyDirection; Valueable DeployingAnim_KeepUnitVisible; @@ -149,6 +151,7 @@ class TechnoTypeExt Nullable IronCurtain_Effect; Nullable IronCurtain_KillWarhead; Valueable Explodes_KillPassengers; + Valueable Explodes_DuringBuildup; Nullable DeployFireWeapon; Valueable TargetZoneScanType; @@ -185,6 +188,9 @@ class TechnoTypeExt Nullable SpawnHeight; Nullable LandingDir; + Valueable Convert_HumanToComputer; + Valueable Convert_ComputerToHuman; + struct LaserTrailDataEntry { ValueableIdx idxType; @@ -225,6 +231,7 @@ class TechnoTypeExt , TurretOffset { { 0, 0, 0 } } , TurretShadow { } , ShadowIndices { } + , ShadowIndex_Frame { 0 } , Spawner_LimitRange { false } , Spawner_ExtraLimitRange { 0 } , Spawner_DelayFrames {} @@ -276,7 +283,7 @@ class TechnoTypeExt , NoAmmoWeapon { -1 } , NoAmmoAmount { 0 } , JumpjetRotateOnCrash { true } - + , ShadowSizeCharacteristicHeight { } , DeployingAnim_AllowAnyDirection { false } , DeployingAnim_KeepUnitVisible { false } , DeployingAnim_ReverseForUndeploy { true } @@ -307,6 +314,9 @@ class TechnoTypeExt , EVA_Sold {} , EnemyUIName {} + , VoiceCreated {} + , VoicePickup {} + , ForceWeapon_Naval_Decloaked { -1 } , ForceWeapon_Cloaked { -1 } , ForceWeapon_Disguised { -1 } @@ -328,6 +338,7 @@ class TechnoTypeExt , IronCurtain_KillWarhead {} , Explodes_KillPassengers { true } + , Explodes_DuringBuildup { true } , DeployFireWeapon {} , TargetZoneScanType { TargetZoneScanType::Same } @@ -364,6 +375,8 @@ class TechnoTypeExt , SpawnHeight {} , LandingDir {} , DroppodType {} + , Convert_HumanToComputer { } + , Convert_ComputerToHuman { } { } virtual ~ExtData() = default; diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index 21f5c84f0b..436c775bfc 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -16,6 +16,9 @@ #include #include +#include +#include +#include DEFINE_HOOK(0x6F64A9, TechnoClass_DrawHealthBar_Hide, 0x5) { @@ -334,32 +337,155 @@ DEFINE_HOOK(0x6B0C2C, SlaveManagerClass_FreeSlaves_SlavesFreeSound, 0x5) return 0x6B0C65; } -DEFINE_HOOK(0x4DB157, FootClass_DrawVoxelShadow_TurretShadow, 0x8) +// 2nd order Pade approximant just in case someone complains about performance +constexpr double Pade2_2(double in) { - GET(FootClass*, pThis, ESI); - GET_STACK(Point2D, pos, STACK_OFFSET(0x18, 0x28)); - GET_STACK(Surface*, pSurface, STACK_OFFSET(0x18, 0x24)); - GET_STACK(bool, a9, STACK_OFFSET(0x18, 0x20)); - GET_STACK(Matrix3D*, pMatrix, STACK_OFFSET(0x18, 0x1C)); - GET_STACK(RectangleStruct*, bound, STACK_OFFSET(0x18, 0x14)); - GET_STACK(Point2D, a3, STACK_OFFSET(0x18, -0x10)); - GET_STACK(decltype(ObjectTypeClass::VoxelShadowCache)*, shadow_cache, STACK_OFFSET(0x18, 0x10)); - GET_STACK(VoxelIndexKey, index_key, STACK_OFFSET(0x18, 0xC)); - GET_STACK(int, shadow_index, STACK_OFFSET(0x18, 0x8)); - GET_STACK(VoxelStruct*, main_vxl, STACK_OFFSET(0x18, 0x4)); + const double s = in - static_cast(in); + return GeneralUtils::FastPow(0.36787944117144233, static_cast(in)) + * (12. - 6 * s + s * s) / (12. + 6 * s + s * s); +} - auto pType = pThis->GetTechnoType(); - auto const pTypeExt = TechnoTypeExt::ExtMap.Find(pType); +DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) +{ + GET(UnitClass*, pThis, EBP); + enum { SkipDrawing = 0x73C5C9 }; + auto const loco = pThis->Locomotor.GetInterfacePtr(); + if (pThis->Type->NoShadow || !loco->Is_To_Have_Shadow()) + return SkipDrawing; + + REF_STACK(Matrix3D, shadow_matrix, STACK_OFFSET(0x1C4, -0x130)); + GET_STACK(VoxelIndexKey, vxl_index_key, STACK_OFFSET(0x1C4, -0x1B0)); + LEA_STACK(RectangleStruct*, bounding, STACK_OFFSET(0x1C4, 0xC)); + LEA_STACK(Point2D*, floor, STACK_OFFSET(0x1C4, -0x1A4)); + GET_STACK(Surface* const, surface, STACK_OFFSET(0x1C4, -0x1A8)); + + GET(UnitTypeClass*, pType, EBX); + // This is not necessarily pThis->Type : UnloadingClass or WaterImage + // This is the very reason I need to do this here, there's no less hacky way to get this Type from those inner calls + + const auto uTypeExt = TechnoTypeExt::ExtMap.Find(pType); + const auto jjloco = locomotion_cast(loco); + const auto height = pThis->GetHeight(); + const double baseScale_log = RulesExt::Global()->AirShadowBaseScale_log; // -ln(baseScale) precomputed + + if (RulesExt::Global()->HeightShadowScaling && height > 0) + { + const double minScale = RulesExt::Global()->HeightShadowScaling_MinScale; + if (jjloco) + { + const float cHeight = (float)uTypeExt->ShadowSizeCharacteristicHeight.Get(jjloco->Height); + if (cHeight > 0) + { + shadow_matrix.Scale((float)std::max(Pade2_2(baseScale_log * height / cHeight), minScale)); + + if (jjloco->State != JumpjetLocomotionClass::State::Hovering) + vxl_index_key = std::bit_cast(-1); + } + } + else + { + const float cHeight = (float)uTypeExt->ShadowSizeCharacteristicHeight.Get(RulesClass::Instance->CruiseHeight); + + if (cHeight > 0) + { + shadow_matrix.Scale((float)std::max(Pade2_2(baseScale_log * height / cHeight), minScale)); + vxl_index_key = std::bit_cast(-1); + } + } + } + else if (!RulesExt::Global()->HeightShadowScaling && pThis->Type->ConsideredAircraft) + { + shadow_matrix.Scale((float)Pade2_2(baseScale_log)); + } // We need to handle Ares turrets/barrels struct DummyExtHere { - char before[0xA4]; + char _[0xA4]; std::vector ChargerTurrets; std::vector ChargerBarrels; + char __[0x120]; + UnitTypeClass* WaterImage; + VoxelStruct NoSpawnAltVXL; + }; + + auto GetMainVoxel = [&]() + { + if (pType->NoSpawnAlt && pThis->SpawnManager && pThis->SpawnManager->CountDockedSpawns() == 0) + { + if (CAN_USE_ARES && AresHelper::CanUseAres) + { + vxl_index_key = std::bit_cast(-1);// I'd just assume most of the time we have spawn + return &reinterpret_cast(pType->align_2FC)->NoSpawnAltVXL; + } + return &pType->TurretVoxel; + } + return &pType->MainVoxel; }; + auto const main_vxl = GetMainVoxel(); + + // TODO : adjust shadow point according to height + // There was a bit deviation that I cannot decipher, might need help with that + // But it turns out it has basically no visual difference + + auto shadow_point = loco->Shadow_Point(); + auto why = *floor + shadow_point; + + float arf = pThis->AngleRotatedForwards; + float ars = pThis->AngleRotatedSideways; + // lazy, don't want to hook inside Shadow_Matrix + if (std::abs(ars) >= 0.005 || std::abs(arf) >= 0.005) + { + // index key is already invalid + shadow_matrix.TranslateX(float(Math::sgn(arf) * pType->VoxelScaleX * (1 - Math::cos(arf)))); + shadow_matrix.TranslateY(float(Math::sgn(-ars) * pType->VoxelScaleY * (1 - Math::cos(ars)))); + shadow_matrix.ScaleX((float)Math::cos(arf)); + shadow_matrix.ScaleY((float)Math::cos(ars)); + } + + auto mtx = Matrix3D::VoxelDefaultMatrix() * shadow_matrix; + if (height > 0) + shadow_point.Y += 1; + + if (uTypeExt->ShadowIndices.empty()) + { + if (pType->ShadowIndex >= 0 && pType->ShadowIndex < main_vxl->HVA->LayerCount) + pThis->DrawVoxelShadow( + main_vxl, + pType->ShadowIndex, + vxl_index_key, + &pType->VoxelShadowCache, + bounding, + &why, + &mtx, + true, + surface, + shadow_point + ); + } + else + { + for (auto& [index, _] : uTypeExt->ShadowIndices) + pThis->DrawVoxelShadow( + main_vxl, + index, + vxl_index_key, + &pType->VoxelShadowCache, + bounding, + &why, + &mtx, + true, + surface, + shadow_point + ); + } + + if (!uTypeExt->TurretShadow.Get(RulesExt::Global()->DrawTurretShadow) || main_vxl == &pType->TurretVoxel) + return SkipDrawing; + + auto GetTurretVoxel = [pType](int idx) ->VoxelStruct* { if (pType->TurretCount == 0 || pType->IsGattling || idx < 0) @@ -394,35 +520,222 @@ DEFINE_HOOK(0x4DB157, FootClass_DrawVoxelShadow_TurretShadow, 0x8) return nullptr; }; + Matrix3D rot = Matrix3D::GetIdentity(); + uTypeExt->ApplyTurretOffset(&rot, Pixel_Per_Lepton); + rot.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); + auto tur_mtx = mtx * rot; // unfortunately we won't have TurretVoxelScaleX/Y given the amount of work + auto tur = GetTurretVoxel(pThis->CurrentTurretNumber); - if (tur && pTypeExt->TurretShadow.Get(RulesExt::Global()->DrawTurretShadow) && tur->VXL && tur->HVA) + // sorry but you're fucked + if (tur && tur->VXL && tur->HVA) + pThis->DrawVoxelShadow( + tur, + 0, + std::bit_cast(-1), // no cache, no use for valid key + nullptr, // no cache atm + bounding, + &why, + &tur_mtx, + false, + surface, + shadow_point + ); + + auto bar = GetBarrelVoxel(pThis->CurrentTurretNumber); + + // and you are utterly fucked + if (bar && bar->VXL && bar->HVA) + pThis->DrawVoxelShadow( + bar, + 0, + std::bit_cast(-1), // no cache, no use + nullptr,//no cache atm + bounding, + &why, + &tur_mtx, + false, + surface, + shadow_point + ); + + // Add caches in Ext if necessary, remember not to serialize these shit + // IndexClass VoxelTurretShadowCache {}; + // IndexClass VoxelBarrelShadowCache {}; + + return SkipDrawing; +} + +DEFINE_HOOK(0x4147F9, AircraftClass_Draw_Shadow, 0x6) +{ + GET(AircraftClass*, pThis, EBP); + GET(const int, height, EBX); + REF_STACK(VoxelIndexKey, key, STACK_OFFSET(0xCC, -0xBC)); + GET_STACK(Point2D, flor, STACK_OFFSET(0xCC, -0xAC)); + GET_STACK(RectangleStruct*, bound, STACK_OFFSET(0xCC, 0x10)); + enum { FinishDrawing = 0x4148A5 }; + + const auto loco = pThis->Locomotor.GetInterfacePtr(); + if (pThis->Type->NoShadow || !loco->Is_To_Have_Shadow() || pThis->IsSinking) + return FinishDrawing; + + auto shadow_mtx = loco->Shadow_Matrix(&key); + const auto aTypeExt = TechnoTypeExt::ExtMap.Find(pThis->Type); + + if (auto const flyLoco = locomotion_cast(loco)) { - auto mtx = Matrix3D::GetIdentity(); - pTypeExt->ApplyTurretOffset(&mtx, Pixel_Per_Lepton); - mtx.TranslateZ(-tur->HVA->Matrixes[0].GetZVal()); - mtx.RotateZ(static_cast(pThis->SecondaryFacing.Current().GetRadian<32>() - pThis->PrimaryFacing.Current().GetRadian<32>())); - mtx = *pMatrix * mtx; + const double baseScale_log = RulesExt::Global()->AirShadowBaseScale_log; - pThis->DrawVoxelShadow(tur, 0, index_key, nullptr, bound, &a3, &mtx, a9, pSurface, pos); + if (RulesExt::Global()->HeightShadowScaling) + { + const double minScale = RulesExt::Global()->HeightShadowScaling_MinScale; + const float cHeight = (float)aTypeExt->ShadowSizeCharacteristicHeight.Get(flyLoco->FlightLevel); - auto bar = GetBarrelVoxel(pThis->CurrentTurretNumber); + if (cHeight > 0) + { + shadow_mtx.Scale((float)std::max(Pade2_2(baseScale_log * height / cHeight), minScale)); + key = std::bit_cast(-1); // I'm sorry + } + } + else if (pThis->Type->ConsideredAircraft) + { + shadow_mtx.Scale((float)Pade2_2(baseScale_log)); + } - if (bar && bar->VXL && bar->HVA) - pThis->DrawVoxelShadow(bar, 0, index_key, nullptr, bound, &a3, &mtx, a9, pSurface, pos); + if (pThis->IsCrashing) + { + double arf = pThis->AngleRotatedForwards; + if (flyLoco->CurrentSpeed > pThis->Type->PitchSpeed) + arf += pThis->Type->PitchAngle; + shadow_mtx.ScaleY((float)Math::cos(pThis->AngleRotatedSideways)); + shadow_mtx.ScaleX((float)Math::cos(arf)); + } } + else if (height > 0) + { + // You must be Rocket, otherwise GO FUCK YOURSELF + shadow_mtx.ScaleX((float)Math::cos(static_cast(loco)->CurrentPitch)); + key = std::bit_cast(-1); + } + + shadow_mtx = Matrix3D::VoxelDefaultMatrix() * shadow_mtx; + + auto const main_vxl = &pThis->Type->MainVoxel; - if (!pTypeExt->ShadowIndices.size()) + if (aTypeExt->ShadowIndices.empty()) { - pThis->DrawVoxelShadow(main_vxl, shadow_index, index_key, shadow_cache, bound, &a3, pMatrix, a9, pSurface, pos); + auto const shadow_index = pThis->Type->ShadowIndex; + if (shadow_index >= 0 && shadow_index < main_vxl->HVA->LayerCount) + pThis->DrawVoxelShadow(main_vxl, + shadow_index, + key, + &pThis->Type->VoxelShadowCache, + bound, + &flor, + &shadow_mtx, + true, + nullptr, + { 0, 0 } + ); } else { - for (auto index : pTypeExt->ShadowIndices) + for (auto& [index, _] : aTypeExt->ShadowIndices) + pThis->DrawVoxelShadow(main_vxl, + index, + key, + &pThis->Type->VoxelShadowCache, + bound, + &flor, + &shadow_mtx, + true, + nullptr, + { 0, 0 } + ); + } + + return FinishDrawing; +} + +// Shadow_Point of RocketLoco was forgotten to be set to {0,0}. It was an oversight. +// Anyway we don't need to call it from Fly or Rocket loco anymore +// DEFINE_JUMP(VTABLE, 0x7F0B4C, 0x4CF940); + +DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x7) +{ + GET(FootClass*, pThis, EBX);//Maybe Techno later + GET(VoxelStruct*, pVXL, EBP); + GET_STACK(Matrix3D*, pMat, STACK_OFFSET(0xE8, 0xC)); + GET_STACK(int, shadow_index_now, STACK_OFFSET(0xE8, 0x18));// it's used later, otherwise I could have chosen the frame index earlier + + REF_STACK(Matrix3D, matRet, STACK_OFFSET(0xE8, -0x60)); + auto pType = pThis->GetTechnoType(); + + auto pTypeExt = TechnoTypeExt::ExtMap.Find(pType); + + auto hva = pVXL->HVA; + + auto ChooseFrame = [&]()->int //Don't want to use goto + { + // Turret or Barrel + if (pVXL != &pType->MainVoxel) { - pThis->DrawVoxelShadow(main_vxl, index, index_key, shadow_cache, bound, &a3, pMatrix, a9, pSurface, pos); + // verify just in case: + auto who_are_you = reinterpret_cast(reinterpret_cast(pVXL) - (offsetof(TechnoTypeClass, MainVoxel))); + if (who_are_you[0] == UnitTypeClass::AbsVTable) + pType = reinterpret_cast(who_are_you);//you are someone else + else + return pThis->TurretAnimFrame % hva->FrameCount; + // you might also be SpawnAlt voxel, but I can't know + // otherwise what would you expect me to do, shift back to ares typeext base and check if ownerobject is technotype? } - } - return 0x4DB195; + // Main body sections + auto& shadowIndices = pTypeExt->ShadowIndices; + if (shadowIndices.empty()) + { + // Only ShadowIndex + if (pType->ShadowIndex == shadow_index_now) + { + int shadow_index_frame = pTypeExt->ShadowIndex_Frame; + if (shadow_index_frame > -1) + return shadow_index_frame % hva->FrameCount; + } + else + { + // WHO THE HELL ARE YOU??? + return 0; + } + } + else + { + int idx_of_now = shadowIndices[shadow_index_now]; + if (idx_of_now > -1) + return idx_of_now % hva->FrameCount; + } + + return pThis->WalkedFramesSoFar % hva->FrameCount; + }; + + + Matrix3D hvamat = hva->Matrixes[shadow_index_now + hva->LayerCount * ChooseFrame()]; + + // A nasty temporary backward compatibility option + if (hva->LayerCount > 1 || pType->Turret) + // NEEDS IMPROVEMENT : Choose the proper Z offset to shift the sections to the same level + hvamat.TranslateZ( + -hvamat.GetZVal() + - pVXL->VXL->TailerData->Bounds[0].Z + ); + + matRet = *pMat * hvamat; + + // Recover vanilla instructions + if (pThis->GetTechnoType()->UseBuffer) + *reinterpret_cast(0xB43180) = 1; + + REF_STACK(Matrix3D, b, STACK_OFFSET(0xE8, -0x90)); + b.MakeIdentity();// we don't do scaling here anymore + + return 0x707331; } diff --git a/src/Ext/TerrainType/Hooks.Passable.cpp b/src/Ext/TerrainType/Hooks.Passable.cpp index 9b3124df47..9dffa82953 100644 --- a/src/Ext/TerrainType/Hooks.Passable.cpp +++ b/src/Ext/TerrainType/Hooks.Passable.cpp @@ -55,28 +55,23 @@ DEFINE_HOOK(0x7002E9, TechnoClass_WhatAction_PassableTerrain, 0x5) } // Passable TerrainTypes Hook #3 - Count passable TerrainTypes as completely passable. -DEFINE_HOOK(0x483D87, CellClass_CheckPassability_PassableTerrain, 0x5) +DEFINE_HOOK(0x483DDF, CellClass_CheckPassability_PassableTerrain, 0x6) { - enum { SkipToNextObject = 0x483DCD, ReturnFromFunction = 0x483E25, BreakFromLoop = 0x483DDF }; + enum { ReturnFromFunction = 0x483E25 }; GET(CellClass*, pThis, EDI); - GET(ObjectClass*, pObject, ESI); + GET(TerrainClass*, pTerrain, ESI); - if (auto const pTerrain = abstract_cast(pObject)) + if (auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type)) { - if (auto const pTypeExt = TerrainTypeExt::ExtMap.Find(pTerrain->Type)) + if (pTypeExt->IsPassable) { - if (pTypeExt->IsPassable) - { - pThis->Passability = 0; - return ReturnFromFunction; - } + pThis->Passability = PassabilityType::Passable; + return ReturnFromFunction; } - - return BreakFromLoop; } - return SkipToNextObject; + return 0; } // Passable TerrainTypes Hook #4 - Make passable for vehicles. diff --git a/src/Ext/Unit/Hooks.Jumpjet.cpp b/src/Ext/Unit/Hooks.Jumpjet.cpp index 43cea1aee1..c2e73dccfd 100644 --- a/src/Ext/Unit/Hooks.Jumpjet.cpp +++ b/src/Ext/Unit/Hooks.Jumpjet.cpp @@ -4,9 +4,10 @@ #include #include -// Bugfix: Jumpjet turn to target when attacking - -// Jumpjets stuck at FireError::FACING because WW didn't use a correct facing +// Misc jumpjet facing, turning, drawing fix, Misc loco drawing fix -- Author: Trsdy +// Jumpjets stuck at FireError::FACING because Jumpjet has its own facing just for JumpjetTurnRate +// We should not touch the linked unit's PrimaryFacing when it's moving and just let the loco sync this shit in 54D692 +// The body facing never actually turns, it just syncs DEFINE_HOOK(0x736F78, UnitClass_UpdateFiring_FireErrorIsFACING, 0x6) { GET(UnitClass* const, pThis, ESI); @@ -79,7 +80,24 @@ DEFINE_HOOK(0x736EE9, UnitClass_UpdateFiring_FireErrorIsOK, 0x6) return 0; } -DEFINE_HOOK(0x54D208, JumpjetLocomotionClass_ProcessMove_EMPWobble, 0x5) +void __stdcall JumpjetLocomotionClass_DoTurn(ILocomotion* iloco, DirStruct dir) +{ + __assume(iloco != nullptr); + // This seems to be used only when unloading shit on the ground + // Rewrite just in case + auto pThis = static_cast(iloco); + pThis->LocomotionFacing.SetDesired(dir); + pThis->LinkedTo->PrimaryFacing.SetDesired(dir); +} +DEFINE_JUMP(VTABLE, 0x7ECDB4, GET_OFFSET(JumpjetLocomotionClass_DoTurn)) + +DEFINE_HOOK(0x54D326, JumpjetLocomotionClass_MovementAI_CrashSpeedFix, 0x6) +{ + GET(JumpjetLocomotionClass*, pThis, ESI); + return pThis->LinkedTo->IsCrashing ? 0x54D350 : 0; +} + +DEFINE_HOOK(0x54D208, JumpjetLocomotionClass_MovementAI_EMPWobble, 0x5) { GET(JumpjetLocomotionClass* const, pThis, ESI); enum { ZeroWobble = 0x54D22C }; @@ -109,10 +127,9 @@ DEFINE_HOOK(0x736BA3, UnitClass_UpdateRotation_TurretFacing_Jumpjet, 0x6) // When jumpjets arrived at their FootClass::Destination, they seems stuck at the Move mission // and therefore the turret facing was set to DirStruct{atan2(0,0)}==DirType::East at 0x736BBB // that's why they will come back to normal when giving stop command explicitly - auto pType = pThis->Type; // so the best way is to fix the Mission if necessary, but I don't know how to do it - // so I skipped jumpjets check temporarily, and in most cases Jumpjet/BallonHover should cover most of it - if (!pType->TurretSpins && (pType->JumpJet || pType->BalloonHover)) + // so I skipped jumpjets check temporarily + if (!pThis->Type->TurretSpins && locomotion_cast(pThis->Locomotor)) return SkipCheckDestination; return 0; @@ -147,30 +164,29 @@ DEFINE_HOOK(0x54CB0E, JumpjetLocomotionClass_State5_CrashSpin, 0x7) return pTypeExt->JumpjetRotateOnCrash ? 0 : 0x54CB3E; } - -// These are subject to changes if someone wants to properly implement jumpjet tilting -DEFINE_HOOK(0x54DCCF, JumpjetLocomotionClass_DrawMatrix_TiltCrashJumpjet, 0x5) +// We no longer explicitly check TiltCrashJumpjet when drawing, do it when crashing +DEFINE_HOOK(0x70B649, TechnoClass_RigidBodyDynamics_NoTiltCrashBlyat, 0x6) { - GET(ILocomotion*, iloco, ESI); - __assume(iloco != nullptr); - //if (static_cast(iloco)->State < JumpjetLocomotionClass::State::Crashing) - if (static_cast(iloco)->State == JumpjetLocomotionClass::State::Grounded) - return 0x54DCE8; + GET(FootClass*, pThis, ESI); + + if (locomotion_cast(pThis->Locomotor) && !pThis->GetTechnoType()->TiltCrashJumpjet) + return 0x70BCA4; return 0; } -/* +DEFINE_JUMP(LJMP, 0x54DCCF, 0x54DCE8);//JumpjetLocomotionClass_DrawMatrix_NoTiltCrashJumpjetHereBlyat +// and the tilt center looked bad visually DEFINE_HOOK(0x54DD3D, JumpjetLocomotionClass_DrawMatrix_AxisCenterInAir, 0x5) { GET(ILocomotion*, iloco, ESI); __assume(iloco != nullptr); - auto state = static_cast(iloco)->State; - if (state && state < JumpjetLocomotionClass::State::Crashing) - return 0x54DE88; - return 0; + + if (static_cast(iloco)->State == JumpjetLocomotionClass::State::Grounded) + return 0; + + return 0x54DE88; } -*/ FireError __stdcall JumpjetLocomotionClass_Can_Fire(ILocomotion* pThis) { @@ -183,8 +199,6 @@ FireError __stdcall JumpjetLocomotionClass_Can_Fire(ILocomotion* pThis) DEFINE_JUMP(VTABLE, 0x7ECDF4, GET_OFFSET(JumpjetLocomotionClass_Can_Fire)); -//TODO : Issue #690 #655 - // Fix initial facing when jumpjet locomotor is being attached DEFINE_HOOK(0x54AE44, JumpjetLocomotionClass_LinkToObject_FixFacing, 0x7) { diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index 34647180ff..662be0d040 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -83,7 +83,7 @@ void WarheadTypeExt::ExtData::Detonate(TechnoClass* pOwner, HouseClass* pHouse, // and therefore it will reuse the vanilla routine, which will crash inside of it pSuper->SetReadiness(true); // TODO: Can we use ClickFire instead of Launch? - pSuper->Launch(cell, true); + pSuper->Launch(cell, pHouse->IsCurrentPlayer()); pSuper->Reset(); if (!this->LaunchSW_RealLaunch) diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 15b4a76143..bb1c4ad5cf 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -183,16 +183,50 @@ DEFINE_HOOK(0x48A4F3, SelectDamageAnimation_NegativeZeroDamage, 0x6) return SkipGameCode; } -DEFINE_HOOK(0x4891AF, GetTotalDamage_NegativeDamageModifiers, 0x6) +#pragma region NegativeDamageModifiers + +namespace NegativeDamageTemp +{ + bool ApplyNegativeDamageModifiers = false; +} + +DEFINE_HOOK(0x4891AF, GetTotalDamage_NegativeDamageModifiers1, 0x6) { enum { ApplyModifiers = 0x4891C6 }; GET(WarheadTypeClass* const, pWarhead, EDI); + GET(int, damage, ESI); auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWarhead); - if (pWHExt->ApplyModifiersOnNegativeDamage) + if (damage < 0 && pWHExt->ApplyModifiersOnNegativeDamage) + { + NegativeDamageTemp::ApplyNegativeDamageModifiers = true; return ApplyModifiers; + } return 0; } + +DEFINE_HOOK(0x48922D, GetTotalDamage_NegativeDamageModifiers2, 0x5) +{ + enum { SkipGameCode = 0x489235 }; + + GET(int, damage, ESI); + + if (NegativeDamageTemp::ApplyNegativeDamageModifiers) + { + NegativeDamageTemp::ApplyNegativeDamageModifiers = false; + R->ECX(damage); + + } + else + { + R->ECX(damage < 0 ? 0 : damage); + } + + + return SkipGameCode; +} + +#pragma endregion diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 6fbf40d362..646e392fdc 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -59,6 +59,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->OmniFire_TurnToTarget.Read(exINI, pSection, "OmniFire.TurnToTarget"); this->ExtraWarheads.Read(exINI, pSection, "ExtraWarheads"); this->ExtraWarheads_DamageOverrides.Read(exINI, pSection, "ExtraWarheads.DamageOverrides"); + this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); this->AmbientDamage_Warhead.Read(exINI, pSection, "AmbientDamage.Warhead"); this->AmbientDamage_IgnoreTarget.Read(exINI, pSection, "AmbientDamage.IgnoreTarget"); } @@ -86,6 +87,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->OmniFire_TurnToTarget) .Process(this->ExtraWarheads) .Process(this->ExtraWarheads_DamageOverrides) + .Process(this->ExtraWarheads_DetonationChances) .Process(this->AmbientDamage_Warhead) .Process(this->AmbientDamage_IgnoreTarget) ; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 955a31bef5..484655352a 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -39,6 +39,7 @@ class WeaponTypeExt Valueable OmniFire_TurnToTarget; ValueableVector ExtraWarheads; ValueableVector ExtraWarheads_DamageOverrides; + ValueableVector ExtraWarheads_DetonationChances; Nullable AmbientDamage_Warhead; Valueable AmbientDamage_IgnoreTarget; @@ -62,6 +63,7 @@ class WeaponTypeExt , OmniFire_TurnToTarget { false } , ExtraWarheads {} , ExtraWarheads_DamageOverrides {} + , ExtraWarheads_DetonationChances {} , AmbientDamage_Warhead {} , AmbientDamage_IgnoreTarget { false } { } diff --git a/src/Misc/Hooks.UI.cpp b/src/Misc/Hooks.UI.cpp index 8afd4f7c71..01a1f24deb 100644 --- a/src/Misc/Hooks.UI.cpp +++ b/src/Misc/Hooks.UI.cpp @@ -257,7 +257,7 @@ DEFINE_HOOK(0x683E41, ScenarioClass_Start_ShowBriefing, 0x6) GET_STACK(bool, showBriefing, STACK_OFFSET(0xFC, -0xE9)); // Don't show briefing dialog for non-campaign games etc. - if (!ScenarioExt::Global()->ShowBriefing || !showBriefing || !SessionClass::IsCampaign()) + if (!Phobos::Config::ShowBriefing || !ScenarioExt::Global()->ShowBriefing || !showBriefing || !SessionClass::IsCampaign()) return 0; BriefingTemp::ShowBriefing = true; diff --git a/src/Misc/Hooks.VeinholeMonster.cpp b/src/Misc/Hooks.VeinholeMonster.cpp new file mode 100644 index 0000000000..257129fcd4 --- /dev/null +++ b/src/Misc/Hooks.VeinholeMonster.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/// +/// Veinhole Monster +/// + +// Loads the veinhole monster art +// Call removed from YR by WW +DEFINE_HOOK(0x4AD097, DisplayClass_ReadIni_LoadVeinholeArt, 0x6) +{ + int theater = static_cast(ScenarioClass::Instance->Theater); + VeinholeMonsterClass::LoadVeinholeArt(theater); + + return 0; +} + +// Applies damage to the veinhole monster +DEFINE_HOOK(0x489671, Damage_at_Cell_Update_Veinhole, 0x6) +{ + GET(OverlayTypeClass*, pOverlay, EAX); + GET(WarheadTypeClass*, pWH, ESI); + GET_STACK(CellStruct, pCell, STACK_OFFSET(0xE0, -0x4C)); + GET_STACK(int, damage, STACK_OFFSET(0xE0, -0xBC)); + GET_STACK(ObjectClass*, pAttacker, STACK_OFFSET(0xE0, 0x8)); + GET_STACK(HouseClass*, pAttackingHouse, STACK_OFFSET(0xE0, 0x14)); + + if (pOverlay->IsVeinholeMonster) + { + if (VeinholeMonsterClass* pVeinhole = VeinholeMonsterClass::GetVeinholeMonsterFrom(&pCell)) + pVeinhole->ReceiveDamage(&damage, 0, pWH, pAttacker, false, false, pAttackingHouse); + } + + return 0; +} + +DEFINE_HOOK(0x6D4656, TacticalClass_Draw_Veinhole, 0x5) +{ + enum { ContinueDraw = 0x6D465B }; + + VeinholeMonsterClass::DrawAll(); + IonBlastClass::DrawAll(); + + return ContinueDraw; +} + +DEFINE_HOOK(0x5349A5, Map_ClearVectors_Veinhole, 0x5) +{ + VeinholeMonsterClass::DeleteAll(); + VeinholeMonsterClass::DeleteVeinholeGrowthData(); + return 0; +} + +DEFINE_HOOK(0x55B4E1, LogicClass_Update_Veinhole, 0x5) +{ + VeinholeMonsterClass::UpdateAllVeinholes(); + return 0; +} + +// Handles the veins' attack animation +DEFINE_HOOK(0x4243BC, AnimClass_Update_VeinholeAttack, 0x6) +{ + GET(AnimClass*, pAnim, ESI); + + if (pAnim->Type->IsVeins) + AnimExt::VeinAttackAI(pAnim); + + return 0; +} + +/// +/// Weeder +/// + +// These 2 I am not sure, maybe they have smth to do with AI, maybe they are for the unit queue at the refinery +DEFINE_HOOK(0x736823, UnitClass_Update_WeederMissionMove, 0x6) +{ + enum + { + Continue = 0x736831, + Skip = 0x736981 + }; + + GET(UnitTypeClass*, pUnitType, EAX); + + if (pUnitType->Harvester || pUnitType->Weeder) + return Continue; + + return Skip; +} + +DEFINE_HOOK(0x7368C6, UnitClass_Update_WeederMissionMove2, 0x6) +{ + enum + { + Continue = 0x7368D4, + Skip = 0x736981 + }; + + GET(BuildingTypeClass*, pBuildingType, EDX); + + if (pBuildingType->Refinery || pBuildingType->Weeder) + return Continue; + + return Skip; +} + +// Not sure if necessary +/* +// These 2 have something to do with ZAdjustment when unloading +DEFINE_HOOK(0x7043E7, TechnoClass_Get_ZAdjustment_Weeder, 0x6) +{ + enum + { + Continue = 0x7043F1, + Skip = 0x704421 + }; + + GET(UnitTypeClass*, pUnitType, ECX); + + if (pUnitType->Harvester || pUnitType->Weeder) + return Continue; + + return Skip; +} + +DEFINE_HOOK(0x70440C, TechnoClass_Get_ZAdjustment_Weeder2, 0x6) +{ + enum + { + Continue = 0x704416, + Skip = 0x704421 + }; + + GET(BuildingTypeClass*, pBuildingType, EAX); + + if (pBuildingType->Refinery || pBuildingType->Weeder) + return Continue; + + return Skip; +} + +DEFINE_HOOK(0x741C32, UnitClass_SetDestination_SpecialAnim_Weeder, 0x6) +{ + enum + { + CheckSpecialAnimExists = 0x741C3C, + Skip = 0x741C4F + }; + + GET(UnitTypeClass*, pUnitType, ECX); + + if (pUnitType->Harvester || pUnitType->Weeder) + return CheckSpecialAnimExists; + + return Skip; +} +*/ + +DEFINE_HOOK(0x73D0DB, UnitClass_DrawAt_Weeder_Oregath, 0x6) +{ + enum + { + DrawOregath = 0x73D0E9, + Skip = 0x73D298 + }; + + GET(UnitClass*, pUnit, ESI); + + if (pUnit->Type->Harvester || pUnit->Type->Weeder || pUnit->IsHarvesting) + return DrawOregath; + + return Skip; +} +/* +DEFINE_HOOK(0x73D2A6, UnitClass_DrawAt_Weeder_UnloadingClass, 0x6) +{ + enum + { + ShowUnloadingClass = 0x73D2B0, + Skip = 0x73D2CA + }; + + GET(UnitTypeClass*, pUnitType, EAX); + + if (pUnitType->Harvester || pUnitType->Weeder) + return ShowUnloadingClass; + + return Skip; +} +*/ + +// Enables the weeder to harvest veins +DEFINE_HOOK(0x73D49E, UnitClass_Harvesting_Weeder, 0x7) +{ + enum + { + Harvest = 0x73D4DA, + Skip = 0x73D5FE + }; + + GET(UnitClass*, pUnit, ESI); + GET(CellClass*, pCell, EBP); + constexpr unsigned char weedOverlayData = 0x30; + + bool harvesterCanHarvest = pUnit->Type->Harvester && pCell->LandType == LandType::Tiberium; + bool weederCanWeed = pUnit->Type->Weeder && pCell->LandType == LandType::Weeds && pCell->OverlayData >= weedOverlayData; + + + if ((harvesterCanHarvest || weederCanWeed) && pUnit->GetStoragePercentage() < 1.0) + return Harvest; + + return Skip; +} + +// Not sure if necessary +/* +DEFINE_HOOK(0x73E005, UnitClass_Unload_WeederAnim, 0x6) +{ + enum + { + ProceedWithAnim = 0x73E013, + Skip = 0x73E093 + }; + + GET(UnitTypeClass*, pUnitType, ECX); + + if (pUnitType->Harvester || pUnitType->Weeder) + return ProceedWithAnim; + + return Skip; +} +*/ + +// This lets the weeder actually enter the waste facility and unload +// WW removed weeders from this check in YR +DEFINE_HOOK(0x43C788, BuildingClass_ReceivedRadioCommand_Weeder_CompleteEnter, 0x6) +{ + enum + { + CompleteEnter = 0x43C796, + Skip = 0x43CE43 + }; + + GET(BuildingTypeClass*, pBuildingType, EAX); + + if (pBuildingType->DockUnload || pBuildingType->Weeder) + return CompleteEnter; + + return Skip; +} + +// This assigns the weeder to the "Harvest" mission when it is granted as a free unit +// Ares made the weeder receive the "Guard" command instead +DEFINE_HOOK(0x446EAD, BuildingClass_GrandOpening_FreeWeeder_Mission, 0x6) +{ + GET(UnitClass*, pUnit, EDI); + + if (pUnit->Type->Weeder) + pUnit->ForceMission(Mission::Harvest); + + pUnit->NextMission(); + + return 0x446EB7; +} + +// Teleport cooldown for weeders +DEFINE_HOOK(0x719580, TeleportLocomotion_Weeder, 0x6) +{ + enum + { + Skip = 0x7195A0, + TeleportChargeTimer = 0x7195BC + }; + + GET(TeleportLocomotionClass*, pTeleport, ESI); + + if (pTeleport->Owner->WhatAmI() == AbstractType::Unit) + { + UnitClass* pUnit = (UnitClass*)pTeleport->Owner; + if (pUnit->Type->Harvester || pUnit->Type->Weeder) + return Skip; + } + + return TeleportChargeTimer; +} + +// DockUnload bypass for Weeders when teleporting +DEFINE_HOOK(0x7424BD, UnitClass_AssignDestination_Weeder_Teleport, 0x6) +{ + GET(BuildingTypeClass*, pDestination, ECX); + + return pDestination->DockUnload || pDestination->Weeder ? 0x7424CB : 0x7425DB; +} + +//// Skip check for Weeder so that weeders go through teleport stuff +//DEFINE_JUMP(LJMP, 0x73E844, 0x73E793) +// +// +//DEFINE_HOOK(0x73E84A, UnitClass_Mission_Harvest, 0x6) +//{ +// GET(UnitClass*, pUnit, EBP); +// +// bool isOnTiberium; +// if (pUnit->Type->Weeder) +// isOnTiberium = pUnit->MoveToWeed(RulesClass::Instance->TiberiumLongScan / Unsorted::LeptonsPerCell); +// else +// isOnTiberium = pUnit->MoveToTiberium(RulesClass::Instance->TiberiumLongScan / Unsorted::LeptonsPerCell); +// +// R->EBX(isOnTiberium); +// return 0x73E86B; +//} + +DEFINE_HOOK(0x73E9A0, UnitClass_Weeder_StopHarvesting, 0x6) +{ + enum + { + StopHarvesting = 0x73E9CA, + Skip = 0x73EA8D + }; + + GET(UnitClass*, pUnit, EBP); + + if ((pUnit->Type->Harvester || pUnit->Type->Weeder) && pUnit->GetStoragePercentage() == 1.0) + { + return StopHarvesting; + } + + return Skip; +} diff --git a/src/New/Type/Affiliated/DroppodTypeClass.cpp b/src/New/Type/Affiliated/DroppodTypeClass.cpp index e60737de2a..676232925c 100644 --- a/src/New/Type/Affiliated/DroppodTypeClass.cpp +++ b/src/New/Type/Affiliated/DroppodTypeClass.cpp @@ -70,7 +70,11 @@ DEFINE_HOOK(0x4B5B70, DroppodLocomotionClass_ILoco_Process, 0x5) __assume(iloco != nullptr); auto const lThis = static_cast(iloco); auto const pLinked = lThis->LinkedTo; - const auto podType = TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType())->DroppodType.get(); + auto const linkedExt = TechnoExt::ExtMap.Find(pLinked); + const auto podType = linkedExt->TypeExtData->DroppodType.get(); + + if (!podType) + return 0;//You're not welcome CoordStruct oldLoc = pLinked->Location; @@ -137,6 +141,10 @@ DEFINE_HOOK(0x4B5B70, DroppodLocomotionClass_ILoco_Process, 0x5) if (dWpn && podType->Weapon_HitLandOnly) WeaponTypeExt::DetonateAt(dWpn, pLinked->Location, pLinked, pLinked->Owner); + auto& vec = linkedExt->LaserTrails; + if (!vec.empty()) + vec.erase(std::remove_if(vec.begin(), vec.end(), [](auto& trail) { return trail.Type->DroppodOnly; })); + pLinked->Mark(MarkType::Down); pLinked->SetHeight(0); pLinked->EnterIdleMode(false, true); @@ -162,13 +170,17 @@ DEFINE_HOOK(0x4B607D, DroppodLocomotionClass_ILoco_MoveTo, 0x8) GET(ILocomotion*, iloco, EDI); REF_STACK(CoordStruct, to, STACK_OFFSET(0x1C, 0x8)); __assume(iloco != nullptr); + auto const lThis = static_cast(iloco); auto const pLinked = lThis->LinkedTo; + const auto podType= TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType())->DroppodType.get(); + + if (!podType) + return 0; lThis->DestinationCoords = to; lThis->DestinationCoords.Z = MapClass::Instance->GetCellFloorHeight(to); - const auto podType = TechnoTypeExt::ExtMap.Find(pLinked->GetTechnoType())->DroppodType.get(); const int height = podType->Height.Get(RulesClass::Instance->DropPodHeight); const double angle = podType->Angle.Get(RulesClass::Instance->DropPodAngle); diff --git a/src/New/Type/Affiliated/TypeConvertGroup.cpp b/src/New/Type/Affiliated/TypeConvertGroup.cpp index 9cd91be5c7..d0ce1a70ee 100644 --- a/src/New/Type/Affiliated/TypeConvertGroup.cpp +++ b/src/New/Type/Affiliated/TypeConvertGroup.cpp @@ -7,7 +7,7 @@ void TypeConvertGroup::Convert(FootClass* pTargetFoot, const std::vectorOwner)) + if (pOwner && !EnumFunctions::CanTargetHouse(affectedHouses, pOwner, pTargetFoot->Owner)) continue; if (fromTypes.size()) diff --git a/src/New/Type/DigitalDisplayTypeClass.cpp b/src/New/Type/DigitalDisplayTypeClass.cpp index 3124a53a49..8d9929c9c1 100644 --- a/src/New/Type/DigitalDisplayTypeClass.cpp +++ b/src/New/Type/DigitalDisplayTypeClass.cpp @@ -137,8 +137,8 @@ void DigitalDisplayTypeClass::DisplayShape(Point2D& position, int length, int va } case TextAlign::Center: { - position.X -= valueString.length() * spacing.X / 2; - position.Y += valueString.length() * spacing.Y / 2; + position.X -= static_cast(valueString.length()) * spacing.X / 2; + position.Y += static_cast(valueString.length()) * spacing.Y / 2; break; } case TextAlign::Right: diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index b504863125..cfe8ba463f 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -43,6 +43,7 @@ bool Phobos::Config::SkirmishUnlimitedColors = false; bool Phobos::Config::ShowDesignatorRange = false; bool Phobos::Config::SaveVariablesOnScenarioEnd = false; bool Phobos::Config::SaveGameOnScenarioStart = true; +bool Phobos::Config::ShowBriefing = true; bool Phobos::Misc::CustomGS = false; int Phobos::Misc::CustomGS_ChangeInterval[7] = { -1, -1, -1, -1, -1, -1, -1 }; @@ -59,6 +60,7 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) Phobos::Config::RealTimeTimers_Adaptive = CCINIClass::INI_RA2MD->ReadBool("Phobos", "RealTimeTimers.Adaptive", false); Phobos::Config::DigitalDisplay_Enable = CCINIClass::INI_RA2MD->ReadBool("Phobos", "DigitalDisplay.Enable", false); Phobos::Config::SaveGameOnScenarioStart = CCINIClass::INI_RA2MD->ReadBool("Phobos", "SaveGameOnScenarioStart", true); + Phobos::Config::ShowBriefing = CCINIClass::INI_RA2MD->ReadBool("Phobos", "ShowBriefing", true); CCINIClass* pINI_UIMD = CCINIClass::LoadINIFile(GameStrings::UIMD_INI); @@ -187,17 +189,6 @@ DEFINE_HOOK(0x5FACDF, OptionsClass_LoadSettings_LoadPhobosSettings, 0x5) return 0; } -DEFINE_HOOK(0x66E9DF, RulesClass_Process_Phobos, 0x8) -{ -#ifndef DEBUG - GET(CCINIClass*, rulesINI, EDI); - - Phobos::Config::DevelopmentCommands = rulesINI->ReadBool("GlobalControls", "DebugKeysEnabled", Phobos::Config::DevelopmentCommands); -#endif - - return 0; -} - DEFINE_HOOK(0x55DBF5, MainLoop_SaveGame, 0xA) { return Phobos::Config::SaveGameOnScenarioStart ? 0 : 0x55DC99; diff --git a/src/Phobos.h b/src/Phobos.h index 6090d14a48..ce57102e09 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -80,6 +80,7 @@ class Phobos static bool ShowDesignatorRange; static bool SaveVariablesOnScenarioEnd; static bool SaveGameOnScenarioStart; + static bool ShowBriefing; }; class Misc diff --git a/src/Phobos.version.h b/src/Phobos.version.h index 10eed04f5c..457826cbd2 100644 --- a/src/Phobos.version.h +++ b/src/Phobos.version.h @@ -23,7 +23,7 @@ #pragma endregion // Build number. Incremented on each released build. -#define BUILD_NUMBER 38 +#define BUILD_NUMBER 39 // Nightly defines GIT_COMMIT and GIT_BRANCH in GH Actions diff --git a/src/Utilities/GeneralUtils.h b/src/Utilities/GeneralUtils.h index 5a075cd96f..ea244a1a4f 100644 --- a/src/Utilities/GeneralUtils.h +++ b/src/Utilities/GeneralUtils.h @@ -38,7 +38,7 @@ class GeneralUtils static void DisplayDamageNumberString(int damage, DamageDisplayType type, CoordStruct coords, int& offset); template - static T FastPow(T x, size_t n) + static constexpr T FastPow(T x, size_t n) { // Real fast pow calc x^n in O(log(n)) T result = 1;