diff --git a/CREDITS.md b/CREDITS.md
index dad7ba28ca..eae9cbd85f 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -391,6 +391,7 @@ This page lists all the individual contributions to the project by their author.
- No turret unit turn to the target
- Damage multiplier for different houses
- Extended gattling rate down logic
+ - New Engrave trajectory
- **Ollerus**
- Build limit group enhancement
- Customizable rocker amplitude
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 2e0629bf7c..86e02e22ab 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -38,6 +38,7 @@
+
@@ -200,6 +201,7 @@
+
diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md
index 709bf536fc..1cc62431dc 100644
--- a/docs/New-or-Enhanced-Logics.md
+++ b/docs/New-or-Enhanced-Logics.md
@@ -727,6 +727,7 @@ Currently interceptor weapons with projectiles that do not have `Inviso=true` wi
- The speed of the projectile is defined by `Trajectory.Speed`, which unlike `Speed` used by `ROT` > 0 projectiles is defined on projectile not weapon.
- In `Trajectory=Straight`, it refers to the whole distance speed of the projectile and it has no restrictions.
- In `Trajectory=Bombard`, it refers to the initial speed of the projectile and it has no restrictions.
+ - In `Trajectory=Engrave`, it refers to the horizontal engrave speed of the projectile and it cannot exceed 128. Recommend set as about 40.
- In `Trajectory=Parabola`, it refers to the horizontal velocity of the projectile and is only used for modes 0, 3, or 5 and it has no restrictions.
In `rulesmd.ini`:
@@ -917,6 +918,77 @@ Trajectory.Parabola.AxisOfRotation=0,0,1 ; integer - Forward,Lateral,Heig
- Certainly, `Gravity` can also affect the trajectory.
```
+#### Engrave trajectory
+
+- Visually, like the thermal lance. Calling it 'trajectory' may not be appropriate. It does not read the settings on the weapon.
+ - `Trajectory.Engrave.SourceCoord` controls the starting point of engraving line segment. Taking the target as the coordinate center. Specifically, it will start from the firing position when set to 0,0 . The height of the point will always at ground level, unless `Trajectory.Engrave.ConfineOnGround` is set to false.
+ - `Trajectory.Engrave.TargetCoord` controls the end point of engraving line segment. Taking the target as the coordinate center. The height of the point will always at ground level, unless `Trajectory.Engrave.ConfineOnGround` is set to false.
+ - `Trajectory.Engrave.MirrorCoord` controls whether `Trajectory.Engrave.SourceCoord` and `Trajectory.Engrave.TargetCoord` need to mirror the lateral value to adapt to the current FLH.
+ - `Trajectory.Engrave.UseDisperseCoord` controls whether the emission position of the engrave laser need to replaced with the FLH of its superior's dispersed trajectory, which set `Trajectory.Disperse.RecordSourceCoord` to true.
+ - `Trajectory.Engrave.ApplyRangeModifiers` controls whether any applicable weapon range modifiers from the firer are applied to the engrave process.
+ - `Trajectory.Engrave.AllowFirerTurning` controls whether the projectile allow for significant changes in the orientation of the firer, otherwise it will disappear.
+ - `Trajectory.Engrave.Duration` controls the duration of the entire engrave process. Set to 0 will automatically use `Trajectory.Engrave.SourceCoord` and `Trajectory.Engrave.TargetCoord` to calculate the process duration.
+ - `Trajectory.Engrave.IsLaser` controls whether laser drawing is required.
+ - `Trajectory.Engrave.IsIntense` controls whether the engrave laser will be brighter and thicker. Need to set `Trajectory.Engrave.IsHouseColor` or `Trajectory.Engrave.IsSingleColor` to true.
+ - `Trajectory.Engrave.IsHouseColor` controls whether set the engrave laser to draw using player's team color. These lasers respect `Trajectory.Engrave.LaserThickness` and `Trajectory.Engrave.IsIntense`.
+ - `Trajectory.Engrave.IsSingleColor` controls whether set the engrave laser to draw using only `Trajectory.Engrave.LaserInnerColor`. These lasers respect `Trajectory.Engrave.LaserThickness` and `Trajectory.Engrave.IsIntense`.
+ - `Trajectory.Engrave.LaserInnerColor` controls the inner color of the engrave laser.
+ - `Trajectory.Engrave.LaserOuterColor` controls the outer color of the engrave laser.
+ - `Trajectory.Engrave.LaserOuterSpread` controls the spread color of the engrave laser.
+ - `Trajectory.Engrave.LaserThickness` controls the thickness of the engrave laser. Need to set `Trajectory.Engrave.IsHouseColor` or `Trajectory.Engrave.IsSingleColor` to true.
+ - `Trajectory.Engrave.LaserDuration` controls the duration of the engrave laser.
+ - `Trajectory.Engrave.LaserDelay` controls how often to draw the engrave laser.
+ - `Trajectory.Engrave.DamageDelay` controls how often to detonate warheads.
+ - `Trajectory.Engrave.ProximityImpact` controls the initial proximity fuse times. When there are enough remaining times and the projectile approaches another valid target, it will detonate a warhead defined by `Trajectory.Engrave.ProximityWarhead` on it. If the number of times is exhausted, the engraving process can still continue, but it will not detonate additional warhead as a result. This function can be cancelled by setting to 0. A negative integer means unlimited times. (You can use this to cause non repeated damage to all units encountered during the flight of the projectile.)
+ - `Trajectory.Engrave.ProximityWarhead` defines the warhead detonated by `Trajectory.Engrave.ProximityImpact`, and `Trajectory.Engrave.ProximityDamage` defines the damage caused by `Trajectory.Engrave.ProximityWarhead`.
+ - `Trajectory.Engrave.ProximityRadius` controls the range of proximity fuse. It can NOT be set as a negative integer.
+ - `Trajectory.Engrave.ProximityDirect` controls whether let the target receive damage instead of detonating the warhead.
+ - `Trajectory.Engrave.ProximityMedial` controls whether to detonate `Trajectory.Engrave.ProximityWarhead` at the bullet's location rather than the proximity target's location.
+ - `Trajectory.Engrave.ProximityAllies` controls whether allies will also trigger the proximity fuse.
+ - `Trajectory.Engrave.ProximityFlight` controls whether to count units in the air.
+ - `Trajectory.Engrave.ProximitySuicide` controls whether the projectile will self destruct after the number of proximity fuse times has been exhausted. If `Trajectory.Engrave.ProximityImpact` set to 0, this will not be enabled.
+ - `Trajectory.Engrave.ConfineOnGround` controls whether the height of the projectile will always at ground level.
+
+
+In `rulesmd.ini`:
+```ini
+[SOMEPROJECTILE] ; Projectile
+Trajectory=Engrave ; Trajectory type
+Trajectory.Engrave.SourceCoord=0,0 ; integer - Forward,Lateral
+Trajectory.Engrave.TargetCoord=0,0 ; integer - Forward,Lateral
+Trajectory.Engrave.MirrorCoord=true ; boolean
+Trajectory.Engrave.UseDisperseCoord=false ; boolean
+Trajectory.Engrave.ApplyRangeModifiers=false ; boolean
+Trajectory.Engrave.AllowFirerTurning=true ; boolean
+Trajectory.Engrave.Duration=0 ; integer
+Trajectory.Engrave.IsLaser=true ; boolean
+Trajectory.Engrave.IsIntense=false ; boolean
+Trajectory.Engrave.IsHouseColor=false ; boolean
+Trajectory.Engrave.IsSingleColor=false ; boolean
+Trajectory.Engrave.LaserInnerColor=0,0,0 ; integer - Red,Green,Blue
+Trajectory.Engrave.LaserOuterColor=0,0,0 ; integer - Red,Green,Blue
+Trajectory.Engrave.LaserOuterSpread=0,0,0 ; integer - Red,Green,Blue
+Trajectory.Engrave.LaserThickness=3 ; integer
+Trajectory.Engrave.LaserDuration=1 ; integer
+Trajectory.Engrave.LaserDelay=1 ; integer
+Trajectory.Engrave.DamageDelay=2 ; integer
+Trajectory.Engrave.ProximityImpact=0 ; integer
+Trajectory.Engrave.ProximityWarhead= ; WarheadType
+Trajectory.Engrave.ProximityDamage=0 ; integer
+Trajectory.Engrave.ProximityRadius=0.7 ; floating point value
+Trajectory.Engrave.ProximityDirect=false ; boolean
+Trajectory.Engrave.ProximityMedial=false ; boolean
+Trajectory.Engrave.ProximityAllies=false ; boolean
+Trajectory.Engrave.ProximityFlight=false ; boolean
+Trajectory.Engrave.ProximitySuicide=false ; boolean
+Trajectory.Engrave.ConfineOnGround=true ; boolean
+```
+
+```{note}
+- It's best not to let it be intercepted.
+- Make sure you set a low `Trajectory.Engrave.ProximityRadius` value unless necessary.
+```
+
### Shrapnel enhancements

diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 025e74b725..bc4727b49b 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -328,9 +328,11 @@ New:
- Damage multiplier for different houses (by CrimRecya)
- Customizable duration for electric bolts (by Starkku)
- Extended gattling rate down logic (by CrimRecya)
+- New Engrave trajectory (by CrimRecya)
Vanilla fixes:
- Prevent the units with locomotors that cause problems from entering the tank bunker (by TaranDahl)
+- Fix an issue where a unit will leave an impassable invisible barrier in its original position when it is teleported by ChronoSphere onto an uncrushable unit and self destruct (by NetsuNegi)
Fixes / interactions with other extensions:
- Allowed `AuxBuilding` and Ares' `SW.Aux/NegBuildings` to count building upgrades (by Ollerus)
diff --git a/src/Ext/Bullet/Hooks.cpp b/src/Ext/Bullet/Hooks.cpp
index c876848656..4ceeb5d3b1 100644
--- a/src/Ext/Bullet/Hooks.cpp
+++ b/src/Ext/Bullet/Hooks.cpp
@@ -295,6 +295,7 @@ constexpr bool CheckTrajectoryCanNotAlwaysSnap(const TrajectoryFlag flag)
return flag != TrajectoryFlag::Invalid;
/* return flag == TrajectoryFlag::Straight
|| flag == TrajectoryFlag::Bombard
+ || flag == TrajectoryFlag::Engrave;
|| flag == TrajectoryFlag::Parabola;*/
}
diff --git a/src/Ext/Bullet/Trajectories/EngraveTrajectory.cpp b/src/Ext/Bullet/Trajectories/EngraveTrajectory.cpp
new file mode 100644
index 0000000000..28c366e06d
--- /dev/null
+++ b/src/Ext/Bullet/Trajectories/EngraveTrajectory.cpp
@@ -0,0 +1,632 @@
+#include "EngraveTrajectory.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+std::unique_ptr EngraveTrajectoryType::CreateInstance() const
+{
+ return std::make_unique(this);
+}
+
+template
+void EngraveTrajectoryType::Serialize(T& Stm)
+{
+ Stm
+ .Process(this->SourceCoord)
+ .Process(this->TargetCoord)
+ .Process(this->MirrorCoord)
+ .Process(this->UseDisperseCoord)
+ .Process(this->ApplyRangeModifiers)
+ .Process(this->AllowFirerTurning)
+ .Process(this->Duration)
+ .Process(this->IsLaser)
+ .Process(this->IsIntense)
+ .Process(this->IsHouseColor)
+ .Process(this->IsSingleColor)
+ .Process(this->LaserInnerColor)
+ .Process(this->LaserOuterColor)
+ .Process(this->LaserOuterSpread)
+ .Process(this->LaserThickness)
+ .Process(this->LaserDuration)
+ .Process(this->LaserDelay)
+ .Process(this->DamageDelay)
+ .Process(this->ProximityImpact)
+ .Process(this->ProximityWarhead)
+ .Process(this->ProximityDamage)
+ .Process(this->ProximityRadius)
+ .Process(this->ProximityDirect)
+ .Process(this->ProximityMedial)
+ .Process(this->ProximityAllies)
+ .Process(this->ProximityFlight)
+ .Process(this->ProximitySuicide)
+ .Process(this->ConfineOnGround)
+ ;
+}
+
+bool EngraveTrajectoryType::Load(PhobosStreamReader& Stm, bool RegisterForChange)
+{
+ this->PhobosTrajectoryType::Load(Stm, false);
+ this->Serialize(Stm);
+ return true;
+}
+
+bool EngraveTrajectoryType::Save(PhobosStreamWriter& Stm) const
+{
+ this->PhobosTrajectoryType::Save(Stm);
+ const_cast(this)->Serialize(Stm);
+ return true;
+}
+
+void EngraveTrajectoryType::Read(CCINIClass* const pINI, const char* pSection)
+{
+ INI_EX exINI(pINI);
+
+ this->Trajectory_Speed = Math::min(128.0, this->Trajectory_Speed);
+ this->SourceCoord.Read(exINI, pSection, "Trajectory.Engrave.SourceCoord");
+ this->TargetCoord.Read(exINI, pSection, "Trajectory.Engrave.TargetCoord");
+ this->MirrorCoord.Read(exINI, pSection, "Trajectory.Engrave.MirrorCoord");
+ this->UseDisperseCoord.Read(exINI, pSection, "Trajectory.Engrave.UseDisperseCoord");
+ this->ApplyRangeModifiers.Read(exINI, pSection, "Trajectory.Engrave.ApplyRangeModifiers");
+ this->AllowFirerTurning.Read(exINI, pSection, "Trajectory.Engrave.AllowFirerTurning");
+ this->Duration.Read(exINI, pSection, "Trajectory.Engrave.Duration");
+ this->IsLaser.Read(exINI, pSection, "Trajectory.Engrave.IsLaser");
+ this->IsIntense.Read(exINI, pSection, "Trajectory.Engrave.IsIntense");
+ this->IsHouseColor.Read(exINI, pSection, "Trajectory.Engrave.IsHouseColor");
+ this->IsSingleColor.Read(exINI, pSection, "Trajectory.Engrave.IsSingleColor");
+ this->LaserInnerColor.Read(exINI, pSection, "Trajectory.Engrave.LaserInnerColor");
+ this->LaserOuterColor.Read(exINI, pSection, "Trajectory.Engrave.LaserOuterColor");
+ this->LaserOuterSpread.Read(exINI, pSection, "Trajectory.Engrave.LaserOuterSpread");
+ this->LaserThickness.Read(exINI, pSection, "Trajectory.Engrave.LaserThickness");
+ this->LaserThickness = Math::max(1, this->LaserThickness);
+ this->LaserDuration.Read(exINI, pSection, "Trajectory.Engrave.LaserDuration");
+ this->LaserDuration = Math::max(1, this->LaserDuration);
+ this->LaserDelay.Read(exINI, pSection, "Trajectory.Engrave.LaserDelay");
+ this->LaserDelay = Math::max(1, this->LaserDelay);
+ this->DamageDelay.Read(exINI, pSection, "Trajectory.Engrave.DamageDelay");
+ this->DamageDelay = Math::max(1, this->DamageDelay);
+ this->ProximityImpact.Read(exINI, pSection, "Trajectory.Engrave.ProximityImpact");
+ this->ProximityWarhead.Read(exINI, pSection, "Trajectory.Engrave.ProximityWarhead");
+ this->ProximityDamage.Read(exINI, pSection, "Trajectory.Engrave.ProximityDamage");
+ this->ProximityRadius.Read(exINI, pSection, "Trajectory.Engrave.ProximityRadius");
+ this->ProximityDirect.Read(exINI, pSection, "Trajectory.Engrave.ProximityDirect");
+ this->ProximityMedial.Read(exINI, pSection, "Trajectory.Engrave.ProximityMedial");
+ this->ProximityAllies.Read(exINI, pSection, "Trajectory.Engrave.ProximityAllies");
+ this->ProximityFlight.Read(exINI, pSection, "Trajectory.Engrave.ProximityFlight");
+ this->ProximitySuicide.Read(exINI, pSection, "Trajectory.Engrave.ProximitySuicide");
+ this->ConfineOnGround.Read(exINI, pSection, "Trajectory.Engrave.ConfineOnGround");
+}
+
+template
+void EngraveTrajectory::Serialize(T& Stm)
+{
+ Stm
+ .Process(this->Type)
+ .Process(this->SourceCoord)
+ .Process(this->TargetCoord)
+ .Process(this->Duration)
+ .Process(this->LaserTimer)
+ .Process(this->DamageTimer)
+ .Process(this->TechnoInTransport)
+ .Process(this->NotMainWeapon)
+ .Process(this->FLHCoord)
+ .Process(this->BuildingCoord)
+ .Process(this->StartCoord)
+ .Process(this->ProximityImpact)
+ .Process(this->TheCasualty)
+ ;
+}
+
+bool EngraveTrajectory::Load(PhobosStreamReader& Stm, bool RegisterForChange)
+{
+ this->Serialize(Stm);
+ return true;
+}
+
+bool EngraveTrajectory::Save(PhobosStreamWriter& Stm) const
+{
+ const_cast(this)->Serialize(Stm);
+ return true;
+}
+
+void EngraveTrajectory::OnUnlimbo(BulletClass* pBullet, CoordStruct* pCoord, BulletVelocity* pVelocity)
+{
+ const auto pType = this->Type;
+ this->LaserTimer.Start(0);
+ this->DamageTimer.Start(0);
+ this->FLHCoord = pBullet->SourceCoords;
+ const auto pTechno = pBullet->Owner;
+
+ // When the launcher exists, the launcher's location will be used to calculate the direction of the coordinate axis instead of bullet's SourceCoords
+ if (pTechno)
+ {
+ for (auto pTrans = pTechno->Transporter; pTrans; pTrans = pTrans->Transporter)
+ this->TechnoInTransport = pTrans->UniqueID;
+
+ this->GetTechnoFLHCoord(pBullet, pTechno);
+ this->CheckMirrorCoord(pTechno);
+
+ const auto theSource = pTechno->GetCoords();
+ const auto rotateAngle = Math::atan2(pBullet->TargetCoords.Y - theSource.Y , pBullet->TargetCoords.X - theSource.X);
+ this->SetEngraveDirection(pBullet, rotateAngle);
+ }
+ else
+ {
+ this->NotMainWeapon = true;
+
+ const auto rotateAngle = Math::atan2(pBullet->TargetCoords.Y - pBullet->SourceCoords.Y , pBullet->TargetCoords.X - pBullet->SourceCoords.X);
+ this->SetEngraveDirection(pBullet, rotateAngle);
+ }
+
+ // Substitute the speed to calculate velocity
+ auto coordDistance = pBullet->Velocity.Magnitude();
+ pBullet->Velocity *= (coordDistance > 1e-10) ? (pType->Trajectory_Speed / coordDistance) : 0;
+
+ // Calculate additional range
+ if (pType->ApplyRangeModifiers && pTechno)
+ {
+ if (const auto pWeapon = pBullet->WeaponType)
+ coordDistance = static_cast(WeaponTypeExt::GetRangeWithModifiers(pWeapon, pTechno, static_cast(coordDistance)));
+ }
+
+ // Automatically calculate duration
+ if (this->Duration <= 0)
+ this->Duration = static_cast(coordDistance / pType->Trajectory_Speed) + 1;
+}
+
+bool EngraveTrajectory::OnAI(BulletClass* pBullet)
+{
+ const auto pTechno = pBullet->Owner;
+
+ if (!this->NotMainWeapon && this->InvalidFireCondition(pBullet, pTechno))
+ return true;
+
+ if (--this->Duration < 0 || this->PlaceOnCorrectHeight(pBullet))
+ return true;
+
+ const auto pOwner = pTechno ? pTechno->Owner : BulletExt::ExtMap.Find(pBullet)->FirerHouse;
+
+ if (this->Type->IsLaser && this->LaserTimer.Completed())
+ this->DrawEngraveLaser(pBullet, pTechno, pOwner);
+
+ if (this->DamageTimer.Completed())
+ this->DetonateLaserWarhead(pBullet, pTechno, pOwner);
+
+ if (this->ProximityImpact != 0 && this->Type->ProximityRadius.Get() > 0)
+ this->PrepareForDetonateAt(pBullet, pOwner);
+
+ return false;
+}
+
+void EngraveTrajectory::OnAIPreDetonate(BulletClass* pBullet)
+{
+ //Prevent damage again.
+ pBullet->Health = 0;
+ pBullet->Limbo();
+ pBullet->UnInit();
+}
+
+void EngraveTrajectory::OnAIVelocity(BulletClass* pBullet, BulletVelocity* pSpeed, BulletVelocity* pPosition)
+{
+ pSpeed->Z += BulletTypeExt::GetAdjustedGravity(pBullet->Type);
+}
+
+TrajectoryCheckReturnType EngraveTrajectory::OnAITargetCoordCheck(BulletClass* pBullet)
+{
+ return TrajectoryCheckReturnType::SkipGameCheck;
+}
+
+TrajectoryCheckReturnType EngraveTrajectory::OnAITechnoCheck(BulletClass* pBullet, TechnoClass* pTechno)
+{
+ return TrajectoryCheckReturnType::SkipGameCheck;
+}
+
+void EngraveTrajectory::GetTechnoFLHCoord(BulletClass* pBullet, TechnoClass* pTechno)
+{
+ const auto pExt = TechnoExt::ExtMap.Find(pTechno);
+
+ // Record the launch location, the building has an additional offset
+ if (!pExt || !pExt->LastWeaponType || pExt->LastWeaponType->Projectile != pBullet->Type)
+ {
+ this->NotMainWeapon = true;
+ return;
+ }
+ else if (pTechno->WhatAmI() == AbstractType::Building)
+ {
+ const auto pBuilding = static_cast(pTechno);
+ Matrix3D mtx;
+ mtx.MakeIdentity();
+
+ if (pTechno->HasTurret())
+ {
+ TechnoTypeExt::ApplyTurretOffset(pBuilding->Type, &mtx);
+ mtx.RotateZ(static_cast(pTechno->TurretFacing().GetRadian<32>()));
+ }
+
+ mtx.Translate(static_cast(pExt->LastWeaponFLH.X), static_cast(pExt->LastWeaponFLH.Y), static_cast(pExt->LastWeaponFLH.Z));
+ const auto result = mtx.GetTranslation();
+ this->BuildingCoord = pBullet->SourceCoords - pBuilding->GetCoords() - CoordStruct { static_cast(result.X), -static_cast(result.Y), static_cast(result.Z) };
+ }
+
+ this->FLHCoord = pExt->LastWeaponFLH;
+}
+
+inline void EngraveTrajectory::CheckMirrorCoord(TechnoClass* pTechno)
+{
+ if (this->NotMainWeapon || !(pTechno->CurrentBurstIndex % 2))
+ return;
+
+ if (this->Type->MirrorCoord)
+ {
+ this->SourceCoord.Y = -(this->SourceCoord.Y);
+ this->TargetCoord.Y = -(this->TargetCoord.Y);
+ }
+}
+
+void EngraveTrajectory::SetEngraveDirection(BulletClass* pBullet, double rotateAngle)
+{
+ auto theSource = pBullet->SourceCoords;
+ auto theTarget = pBullet->TargetCoords;
+
+ // Special case: Starting from the launch position
+ if (this->SourceCoord.X != 0 || this->SourceCoord.Y != 0)
+ {
+ theSource = theTarget;
+ theSource.X += static_cast(this->SourceCoord.X * Math::cos(rotateAngle) + this->SourceCoord.Y * Math::sin(rotateAngle));
+ theSource.Y += static_cast(this->SourceCoord.X * Math::sin(rotateAngle) - this->SourceCoord.Y * Math::cos(rotateAngle));
+ }
+
+ if (this->Type->ConfineOnGround)
+ theSource.Z = this->GetFloorCoordHeight(pBullet, theSource);
+
+ this->StartCoord = theSource;
+ pBullet->SetLocation(theSource);
+
+ theTarget.X += static_cast(this->TargetCoord.X * Math::cos(rotateAngle) + this->TargetCoord.Y * Math::sin(rotateAngle));
+ theTarget.Y += static_cast(this->TargetCoord.X * Math::sin(rotateAngle) - this->TargetCoord.Y * Math::cos(rotateAngle));
+
+ pBullet->Velocity.X = theTarget.X - theSource.X;
+ pBullet->Velocity.Y = theTarget.Y - theSource.Y;
+ pBullet->Velocity.Z = 0;
+}
+
+bool EngraveTrajectory::InvalidFireCondition(BulletClass* pBullet, TechnoClass* pTechno)
+{
+ if (!pTechno)
+ return true;
+
+ for (auto pTrans = pTechno->Transporter; pTrans; pTrans = pTrans->Transporter)
+ pTechno = pTrans;
+
+ if (!TechnoExt::IsActive(pTechno) || (this->TechnoInTransport && this->TechnoInTransport != pTechno->UniqueID))
+ return true;
+
+ if (this->Type->AllowFirerTurning)
+ return false;
+
+ const auto SourceCrd = pTechno->GetCoords();
+ const auto TargetCrd = pBullet->TargetCoords;
+
+ const auto rotateAngle = Math::atan2(TargetCrd.Y - SourceCrd.Y , TargetCrd.X - SourceCrd.X);
+ const auto tgtDir = DirStruct(-rotateAngle);
+
+ const auto& face = pTechno->HasTurret() ? pTechno->SecondaryFacing : pTechno->PrimaryFacing;
+ const auto curDir = face.Current();
+
+ return (std::abs(static_cast(static_cast(tgtDir.Raw) - static_cast(curDir.Raw))) >= 4096);
+}
+
+int EngraveTrajectory::GetFloorCoordHeight(BulletClass* pBullet, const CoordStruct& coord)
+{
+ const auto pCell = MapClass::Instance->GetCellAt(coord);
+ const auto onFloor = MapClass::Instance->GetCellFloorHeight(coord);
+ const auto onBridge = pCell->ContainsBridge() ? onFloor + CellClass::BridgeHeight : onFloor;
+
+ return (pBullet->SourceCoords.Z >= onBridge || pBullet->TargetCoords.Z >= onBridge) ? onBridge : onFloor;
+}
+
+bool EngraveTrajectory::PlaceOnCorrectHeight(BulletClass* pBullet)
+{
+ if (!this->Type->ConfineOnGround)
+ return false;
+
+ auto bulletCoords = pBullet->Location;
+ CoordStruct futureCoords
+ {
+ bulletCoords.X + static_cast(pBullet->Velocity.X),
+ bulletCoords.Y + static_cast(pBullet->Velocity.Y),
+ bulletCoords.Z + static_cast(pBullet->Velocity.Z)
+ };
+
+ // Calculate where will be located in the next frame
+ const auto checkDifference = this->GetFloorCoordHeight(pBullet, futureCoords) - futureCoords.Z;
+
+ // When crossing the cliff, directly move the position of the bullet, otherwise change the vertical velocity
+ if (std::abs(checkDifference) >= 384)
+ {
+ if (pBullet->Type->SubjectToCliffs)
+ return true;
+
+ if (checkDifference > 0)
+ {
+ bulletCoords.Z += checkDifference;
+ pBullet->SetLocation(bulletCoords);
+ }
+ else
+ {
+ const auto nowDifference = bulletCoords.Z - this->GetFloorCoordHeight(pBullet, bulletCoords);
+
+ if (nowDifference >= 256)
+ {
+ bulletCoords.Z -= nowDifference;
+ pBullet->SetLocation(bulletCoords);
+ }
+ }
+ }
+ else
+ {
+ pBullet->Velocity.Z += checkDifference;
+ }
+
+ return false;
+}
+
+void EngraveTrajectory::DrawEngraveLaser(BulletClass* pBullet, TechnoClass* pTechno, HouseClass* pOwner)
+{
+ const auto pType = this->Type;
+ this->LaserTimer.Start(pType->LaserDelay);
+ auto fireCoord = pBullet->SourceCoords;
+
+ for (auto pTrans = pTechno->Transporter; pTrans; pTrans = pTrans->Transporter)
+ pTechno = pTrans;
+
+ // Considering that the CurrentBurstIndex may be different, it is not possible to call existing functions
+ if (!this->NotMainWeapon && pTechno && !pTechno->InLimbo)
+ {
+ if (pTechno->WhatAmI() != AbstractType::Building)
+ {
+ fireCoord = TechnoExt::GetFLHAbsoluteCoords(pTechno, this->FLHCoord, pTechno->HasTurret());
+ }
+ else
+ {
+ const auto pBuilding = static_cast(pTechno);
+ Matrix3D mtx;
+ mtx.MakeIdentity();
+
+ if (pTechno->HasTurret())
+ {
+ TechnoTypeExt::ApplyTurretOffset(pBuilding->Type, &mtx);
+ mtx.RotateZ(static_cast(pTechno->TurretFacing().GetRadian<32>()));
+ }
+
+ mtx.Translate(static_cast(this->FLHCoord.X), static_cast(this->FLHCoord.Y), static_cast(this->FLHCoord.Z));
+ const auto result = mtx.GetTranslation();
+ fireCoord = pBuilding->GetCoords() + this->BuildingCoord + CoordStruct { static_cast(result.X), -static_cast(result.Y), static_cast(result.Z) };
+ }
+ }
+
+ // Draw laser from head to tail
+ if (pType->IsHouseColor || pType->IsSingleColor)
+ {
+ const auto pLaser = GameCreate(fireCoord, pBullet->Location, ((pType->IsHouseColor && pOwner) ? pOwner->LaserColor : pType->LaserInnerColor), ColorStruct { 0, 0, 0 }, ColorStruct { 0, 0, 0 }, pType->LaserDuration);
+ pLaser->IsHouseColor = true;
+ pLaser->Thickness = pType->LaserThickness;
+ pLaser->IsSupported = pType->IsIntense;
+ }
+ else
+ {
+ const auto pLaser = GameCreate(fireCoord, pBullet->Location, pType->LaserInnerColor, pType->LaserOuterColor, pType->LaserOuterSpread, pType->LaserDuration);
+ pLaser->IsHouseColor = false;
+ pLaser->Thickness = 3;
+ pLaser->IsSupported = false;
+ }
+}
+
+inline void EngraveTrajectory::DetonateLaserWarhead(BulletClass* pBullet, TechnoClass* pTechno, HouseClass* pOwner)
+{
+ this->DamageTimer.Start(this->Type->DamageDelay);
+ WarheadTypeExt::DetonateAt(pBullet->WH, pBullet->Location, pTechno, pBullet->Health, pOwner);
+}
+
+// Select suitable targets and choose the closer targets then attack each target only once.
+void EngraveTrajectory::PrepareForDetonateAt(BulletClass* pBullet, HouseClass* pOwner)
+{
+ const auto pType = this->Type;
+ const auto pWH = pType->ProximityWarhead;
+
+ if (!pWH)
+ return;
+
+ // Step 1: Find valid targets on the ground within range.
+ const auto radius = pType->ProximityRadius.Get();
+ std::vector recCellClass = PhobosTrajectoryType::GetCellsInProximityRadius(pBullet, radius);
+ const size_t cellSize = recCellClass.size() * 2;
+ size_t vectSize = cellSize;
+ size_t thisSize = 0;
+
+ const CoordStruct velocityCrd
+ {
+ static_cast(pBullet->Velocity.X),
+ static_cast(pBullet->Velocity.Y),
+ static_cast(pBullet->Velocity.Z)
+ };
+ const auto velocitySq = velocityCrd.MagnitudeSquared();
+ const auto pTarget = pBullet->Target;
+
+ std::vector validTechnos;
+ validTechnos.reserve(vectSize);
+
+ for (const auto& pRecCell : recCellClass)
+ {
+ auto pObject = pRecCell->GetContent();
+
+ while (pObject)
+ {
+ const auto pTechno = abstract_cast(pObject);
+ pObject = pObject->NextObject;
+
+ if (!pTechno || !pTechno->IsAlive || !pTechno->IsOnMap || pTechno->Health <= 0 || pTechno->InLimbo || pTechno->IsSinking)
+ continue;
+
+ const auto technoType = pTechno->WhatAmI();
+
+ if (technoType == AbstractType::Building && static_cast(pTechno)->Type->InvisibleInGame)
+ continue;
+
+ // Not directly harming friendly forces
+ if (!pType->ProximityAllies && pOwner && pOwner->IsAlliedWith(pTechno->Owner) && pTechno != pTarget)
+ continue;
+
+ // Check distance
+ const auto targetCrd = pTechno->GetCoords();
+ const auto pathCrd = targetCrd - this->StartCoord;
+
+ if (pathCrd * velocityCrd < 0) // In front of the techno
+ continue;
+
+ const auto distanceCrd = targetCrd - pBullet->Location;
+ const auto nextDistanceCrd = distanceCrd - velocityCrd;
+
+ if (nextDistanceCrd * velocityCrd > 0) // Behind the bullet
+ continue;
+
+ const auto cross = distanceCrd.CrossProduct(nextDistanceCrd).MagnitudeSquared();
+ const auto distance = (velocitySq > 1e-10) ? sqrt(cross / velocitySq) : distanceCrd.Magnitude();
+
+ if (technoType != AbstractType::Building && distance > radius) // In the cylinder
+ continue;
+
+ if (thisSize >= vectSize)
+ {
+ vectSize += cellSize;
+ validTechnos.reserve(vectSize);
+ }
+
+ validTechnos.push_back(pTechno);
+ thisSize += 1;
+ }
+ }
+
+ // Step 2: Find valid targets in the air within range if necessary.
+ if (pType->ProximityFlight)
+ {
+ const auto airTracker = &AircraftTrackerClass::Instance;
+ airTracker->FillCurrentVector(MapClass::Instance->GetCellAt(pBullet->Location + velocityCrd * 0.5),
+ Game::F2I(sqrt(radius * radius + (velocitySq / 4)) / Unsorted::LeptonsPerCell));
+
+ for (auto pTechno = airTracker->Get(); pTechno; pTechno = airTracker->Get())
+ {
+ if (!pTechno->IsAlive || !pTechno->IsOnMap || pTechno->Health <= 0 || pTechno->InLimbo || pTechno->IsSinking)
+ continue;
+
+ // Not directly harming friendly forces
+ if (!pType->ProximityAllies && pOwner && pOwner->IsAlliedWith(pTechno->Owner) && pTechno != pTarget)
+ continue;
+
+ // Check distance
+ const auto targetCrd = pTechno->GetCoords();
+ const auto pathCrd = targetCrd - this->StartCoord;
+
+ if (pathCrd * velocityCrd < 0) // In front of the techno
+ continue;
+
+ const auto distanceCrd = targetCrd - pBullet->Location;
+ const auto nextDistanceCrd = distanceCrd - velocityCrd;
+
+ if (nextDistanceCrd * velocityCrd > 0) // Behind the bullet
+ continue;
+
+ const auto cross = distanceCrd.CrossProduct(nextDistanceCrd).MagnitudeSquared();
+ const auto distance = (velocitySq > 1e-10) ? sqrt(cross / velocitySq) : distanceCrd.Magnitude();
+
+ if (distance > radius) // In the cylinder
+ continue;
+
+ if (thisSize >= vectSize)
+ {
+ vectSize += cellSize;
+ validTechnos.reserve(vectSize);
+ }
+
+ validTechnos.push_back(pTechno);
+ thisSize += 1;
+ }
+ }
+
+ // Step 3: Record each target without repetition.
+ std::vector casualtyChecked;
+ casualtyChecked.reserve(std::max(validTechnos.size(), this->TheCasualty.size()));
+
+ if (const auto pFirer = pBullet->Owner)
+ this->TheCasualty[pFirer->UniqueID] = 20;
+
+ // Update Record
+ for (const auto& [ID, remainTime] : this->TheCasualty)
+ {
+ if (remainTime > 0)
+ this->TheCasualty[ID] = remainTime - 1;
+ else
+ casualtyChecked.push_back(ID);
+ }
+
+ for (const auto& ID : casualtyChecked)
+ this->TheCasualty.erase(ID);
+
+ std::vector validTargets;
+
+ // checking for duplicate
+ for (const auto& pTechno : validTechnos)
+ {
+ if (!this->TheCasualty.contains(pTechno->UniqueID))
+ validTargets.push_back(pTechno);
+
+ this->TheCasualty[pTechno->UniqueID] = 20;
+ }
+
+ // Step 4: Detonate warheads in sequence based on distance.
+ const auto targetsSize = validTargets.size();
+
+ if (this->ProximityImpact > 0 && static_cast(targetsSize) > this->ProximityImpact)
+ {
+ std::sort(&validTargets[0], &validTargets[targetsSize],[this](TechnoClass* pTechnoA, TechnoClass* pTechnoB)
+ {
+ const auto distanceA = pTechnoA->GetCoords().DistanceFromSquared(this->StartCoord);
+ const auto distanceB = pTechnoB->GetCoords().DistanceFromSquared(this->StartCoord);
+
+ // Distance priority
+ if (distanceA < distanceB)
+ return true;
+
+ if (distanceA > distanceB)
+ return false;
+
+ return pTechnoA->UniqueID < pTechnoB->UniqueID;
+ });
+ }
+
+ for (const auto& pTechno : validTargets)
+ {
+ // Cause damage
+ auto damage = pType->ProximityDamage;
+
+ if (pType->ProximityDirect)
+ pTechno->ReceiveDamage(&damage, 0, pWH, pBullet->Owner, false, false, pOwner);
+ else if (pType->ProximityMedial)
+ WarheadTypeExt::DetonateAt(pWH, pBullet->Location, pBullet->Owner, damage, pOwner);
+ else
+ WarheadTypeExt::DetonateAt(pWH, pTechno->GetCoords(), pBullet->Owner, damage, pOwner, pTechno);
+
+ if (this->ProximityImpact > 0 && --this->ProximityImpact == 0)
+ {
+ if (pType->ProximitySuicide)
+ this->Duration = 0;
+
+ break;
+ }
+ }
+}
diff --git a/src/Ext/Bullet/Trajectories/EngraveTrajectory.h b/src/Ext/Bullet/Trajectories/EngraveTrajectory.h
new file mode 100644
index 0000000000..c4fe89d1ce
--- /dev/null
+++ b/src/Ext/Bullet/Trajectories/EngraveTrajectory.h
@@ -0,0 +1,136 @@
+#pragma once
+
+#include "PhobosTrajectory.h"
+
+class EngraveTrajectoryType final : public PhobosTrajectoryType
+{
+public:
+ EngraveTrajectoryType() : PhobosTrajectoryType()
+ , SourceCoord { { 0, 0 } }
+ , TargetCoord { { 0, 0 } }
+ , MirrorCoord { true }
+ , UseDisperseCoord { false }
+ , ApplyRangeModifiers { false }
+ , AllowFirerTurning { true }
+ , Duration { 0 }
+ , IsLaser { true }
+ , IsIntense { false }
+ , IsHouseColor { false }
+ , IsSingleColor { false }
+ , LaserInnerColor { { 0, 0, 0 } }
+ , LaserOuterColor { { 0, 0, 0 } }
+ , LaserOuterSpread { { 0, 0, 0 } }
+ , LaserThickness { 3 }
+ , LaserDuration { 1 }
+ , LaserDelay { 1 }
+ , DamageDelay { 2 }
+ , ProximityImpact { 0 }
+ , ProximityWarhead {}
+ , ProximityDamage { 0 }
+ , ProximityRadius { Leptons(179) }
+ , ProximityDirect { false }
+ , ProximityMedial { false }
+ , ProximityAllies { false }
+ , ProximityFlight { false }
+ , ProximitySuicide { false }
+ , ConfineOnGround { true }
+ { }
+
+ virtual bool Load(PhobosStreamReader& Stm, bool RegisterForChange) override;
+ virtual bool Save(PhobosStreamWriter& Stm) const override;
+ virtual std::unique_ptr CreateInstance() const override;
+ virtual void Read(CCINIClass* const pINI, const char* pSection) override;
+ virtual TrajectoryFlag Flag() const override { return TrajectoryFlag::Engrave; }
+
+ Valueable SourceCoord;
+ Valueable TargetCoord;
+ Valueable MirrorCoord;
+ Valueable UseDisperseCoord;
+ Valueable ApplyRangeModifiers;
+ Valueable AllowFirerTurning;
+ Valueable Duration;
+ Valueable IsLaser;
+ Valueable IsIntense;
+ Valueable IsHouseColor;
+ Valueable IsSingleColor;
+ Valueable LaserInnerColor;
+ Valueable LaserOuterColor;
+ Valueable LaserOuterSpread;
+ Valueable LaserThickness;
+ Valueable LaserDuration;
+ Valueable LaserDelay;
+ Valueable DamageDelay;
+ Valueable ProximityImpact;
+ Valueable ProximityWarhead;
+ Valueable ProximityDamage;
+ Valueable ProximityRadius;
+ Valueable ProximityDirect;
+ Valueable ProximityMedial;
+ Valueable ProximityAllies;
+ Valueable ProximityFlight;
+ Valueable ProximitySuicide;
+ Valueable ConfineOnGround;
+
+private:
+ template
+ void Serialize(T& Stm);
+};
+
+class EngraveTrajectory final : public PhobosTrajectory
+{
+public:
+ EngraveTrajectory(noinit_t) { }
+
+ EngraveTrajectory(EngraveTrajectoryType const* trajType) : Type { trajType }
+ , SourceCoord { trajType->SourceCoord.Get() }
+ , TargetCoord { trajType->TargetCoord.Get() }
+ , Duration { trajType->Duration }
+ , LaserTimer {}
+ , DamageTimer {}
+ , TechnoInTransport { 0 }
+ , NotMainWeapon { false }
+ , FLHCoord {}
+ , BuildingCoord {}
+ , StartCoord {}
+ , ProximityImpact { trajType->ProximityImpact }
+ , TheCasualty {}
+ { }
+
+ virtual bool Load(PhobosStreamReader& Stm, bool RegisterForChange) override;
+ virtual bool Save(PhobosStreamWriter& Stm) const override;
+ virtual TrajectoryFlag Flag() const override { return TrajectoryFlag::Engrave; }
+ virtual void OnUnlimbo(BulletClass* pBullet, CoordStruct* pCoord, BulletVelocity* pVelocity) override;
+ virtual bool OnAI(BulletClass* pBullet) override;
+ virtual void OnAIPreDetonate(BulletClass* pBullet) override;
+ virtual void OnAIVelocity(BulletClass* pBullet, BulletVelocity* pSpeed, BulletVelocity* pPosition) override;
+ virtual TrajectoryCheckReturnType OnAITargetCoordCheck(BulletClass* pBullet) override;
+ virtual TrajectoryCheckReturnType OnAITechnoCheck(BulletClass* pBullet, TechnoClass* pTechno) override;
+
+ const EngraveTrajectoryType* Type;
+ Point2D SourceCoord;
+ Point2D TargetCoord;
+ int Duration;
+ CDTimerClass LaserTimer;
+ CDTimerClass DamageTimer;
+ DWORD TechnoInTransport;
+ bool NotMainWeapon;
+ CoordStruct FLHCoord;
+ CoordStruct BuildingCoord;
+ CoordStruct StartCoord;
+ int ProximityImpact;
+ std::map TheCasualty; // Only for recording existence
+
+ void SetEngraveDirection(BulletClass* pBullet, double rotateAngle);
+private:
+ template
+ void Serialize(T& Stm);
+
+ void GetTechnoFLHCoord(BulletClass* pBullet, TechnoClass* pTechno);
+ inline void CheckMirrorCoord(TechnoClass* pTechno);
+ bool InvalidFireCondition(BulletClass* pBullet, TechnoClass* pTechno);
+ int GetFloorCoordHeight(BulletClass* pBullet, const CoordStruct& coord);
+ bool PlaceOnCorrectHeight(BulletClass* pBullet);
+ void DrawEngraveLaser(BulletClass* pBullet, TechnoClass* pTechno, HouseClass* pOwner);
+ inline void DetonateLaserWarhead(BulletClass* pBullet, TechnoClass* pTechno, HouseClass* pOwner);
+ void PrepareForDetonateAt(BulletClass* pBullet, HouseClass* pOwner);
+};
diff --git a/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp b/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp
index 6d5e10a818..0033628767 100644
--- a/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp
+++ b/src/Ext/Bullet/Trajectories/PhobosTrajectory.cpp
@@ -7,6 +7,7 @@
#include "StraightTrajectory.h"
#include "BombardTrajectory.h"
+#include "EngraveTrajectory.h"
#include "ParabolaTrajectory.h"
TrajectoryTypePointer::TrajectoryTypePointer(TrajectoryFlag flag)
@@ -19,6 +20,9 @@ TrajectoryTypePointer::TrajectoryTypePointer(TrajectoryFlag flag)
case TrajectoryFlag::Bombard:
_ptr = std::make_unique();
return;
+ case TrajectoryFlag::Engrave:
+ _ptr = std::make_unique();
+ return;
case TrajectoryFlag::Parabola:
_ptr = std::make_unique();
return;
@@ -37,6 +41,7 @@ namespace detail
{
{"Straight", TrajectoryFlag::Straight},
{"Bombard" ,TrajectoryFlag::Bombard},
+ {"Engrave" ,TrajectoryFlag::Engrave},
{"Parabola", TrajectoryFlag::Parabola},
};
for (auto [name, flag] : FlagNames)
@@ -116,6 +121,9 @@ bool TrajectoryPointer::Load(PhobosStreamReader& Stm, bool registerForChange)
case TrajectoryFlag::Bombard:
_ptr = std::make_unique(noinit_t {});
break;
+ case TrajectoryFlag::Engrave:
+ _ptr = std::make_unique(noinit_t {});
+ break;
case TrajectoryFlag::Parabola:
_ptr = std::make_unique(noinit_t {});
break;
diff --git a/src/Ext/Bullet/Trajectories/PhobosTrajectory.h b/src/Ext/Bullet/Trajectories/PhobosTrajectory.h
index e92617bed9..65126018a5 100644
--- a/src/Ext/Bullet/Trajectories/PhobosTrajectory.h
+++ b/src/Ext/Bullet/Trajectories/PhobosTrajectory.h
@@ -10,6 +10,7 @@ enum class TrajectoryFlag : int
Invalid = -1,
Straight = 0,
Bombard = 1,
+ Engrave = 3,
Parabola = 4
};
diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp
index 2fc559db2d..824018f73e 100644
--- a/src/Ext/Techno/Body.cpp
+++ b/src/Ext/Techno/Body.cpp
@@ -553,6 +553,8 @@ void TechnoExt::ExtData::Serialize(T& Stm)
.Process(this->LastRearmWasFullDelay)
.Process(this->CanCloakDuringRearm)
.Process(this->WHAnimRemainingCreationInterval)
+ .Process(this->LastWeaponType)
+ .Process(this->LastWeaponFLH)
.Process(this->FiringObstacleCell)
.Process(this->IsDetachingForCloak)
.Process(this->LastTargetID)
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index a4bcd1eb6c..9942b427e5 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -50,6 +50,8 @@ class TechnoExt
bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon.
int WHAnimRemainingCreationInterval;
bool CanCurrentlyDeployIntoBuilding; // Only set on UnitClass technos with DeploysInto set in multiplayer games, recalculated once per frame so no need to serialize.
+ WeaponTypeClass* LastWeaponType;
+ CoordStruct LastWeaponFLH;
CellClass* FiringObstacleCell; // Set on firing if there is an obstacle cell between target and techno, used for updating WaveClass target etc.
bool IsDetachingForCloak; // Used for checking animation detaching, set to true before calling Detach_All() on techno when this anim is attached to and to false after when cloaking only.
DWORD LastTargetID;
@@ -93,6 +95,8 @@ class TechnoExt
, CanCloakDuringRearm { false }
, WHAnimRemainingCreationInterval { 0 }
, CanCurrentlyDeployIntoBuilding { false }
+ , LastWeaponType {}
+ , LastWeaponFLH {}
, FiringObstacleCell {}
, IsDetachingForCloak { false }
, LastTargetID { 0xFFFFFFFF }
diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp
index 3f1eba405d..af71f9c3df 100644
--- a/src/Ext/Techno/Hooks.Firing.cpp
+++ b/src/Ext/Techno/Hooks.Firing.cpp
@@ -410,6 +410,16 @@ DEFINE_HOOK(0x6FCBE6, TechnoClass_CanFire_BridgeAAFix, 0x6)
#pragma endregion
#pragma region TechnoClass_Fire
+DEFINE_HOOK(0x6FDD7D, TechnoClass_FireAt_UpdateWeaponType, 0x5)
+{
+ GET(WeaponTypeClass* const, pWeapon, EBX);
+ GET(TechnoClass* const, pThis, ESI);
+
+ if (const auto pExt = TechnoExt::ExtMap.Find(pThis))
+ pExt->LastWeaponType = pWeapon;
+
+ return 0;
+}
DEFINE_HOOK(0x6FDDC0, TechnoClass_FireAt_DiscardAEOnFire, 0x6)
{
@@ -670,15 +680,27 @@ namespace BurstFLHTemp
DEFINE_HOOK(0x6F3B37, TechnoClass_GetFLH_BurstFLH_1, 0x7)
{
GET(TechnoClass*, pThis, EBX);
+ GET(int, OriginalX, ECX);
+ GET(int, OriginalY, EBP);
+ GET(int, OriginalZ, EAX);
GET_STACK(int, weaponIndex, STACK_OFFSET(0xD8, 0x8));
if (weaponIndex < 0)
+ {
+ auto currentPassenger = pThis->Passengers.FirstPassenger;
+ const auto passengerIndex = -weaponIndex - 1;
+
+ for (int i = 0; i < passengerIndex && currentPassenger; i++)
+ currentPassenger = abstract_cast(currentPassenger->NextObject);
+
+ if (const auto pPassengerExt = TechnoExt::ExtMap.Find(currentPassenger))
+ pPassengerExt->LastWeaponFLH = { OriginalX, OriginalY, OriginalZ };
+
return 0;
+ }
bool FLHFound = false;
- CoordStruct FLH = CoordStruct::Empty;
-
- FLH = TechnoExt::GetBurstFLH(pThis, weaponIndex, FLHFound);
+ auto FLH = TechnoExt::GetBurstFLH(pThis, weaponIndex, FLHFound);
BurstFLHTemp::FLHFound = FLHFound;
if (!FLHFound)
@@ -689,10 +711,17 @@ DEFINE_HOOK(0x6F3B37, TechnoClass_GetFLH_BurstFLH_1, 0x7)
if (FLHFound)
{
+ if (const auto pExt = TechnoExt::ExtMap.Find(pThis))
+ pExt->LastWeaponFLH = FLH;
+
R->ECX(FLH.X);
R->EBP(FLH.Y);
R->EAX(FLH.Z);
}
+ else if (const auto pExt = TechnoExt::ExtMap.Find(pThis))
+ {
+ pExt->LastWeaponFLH = { OriginalX, ((pThis->CurrentBurstIndex % 2 == 1) ? -OriginalY : OriginalY), OriginalZ };
+ }
return 0;
}