Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Simple Fix] Draw visual effects for airburst weapons #1466

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ This page lists all the individual contributions to the project by their author.
- Fix `LimboKill` not working reliably
- Allow using waypoints, area guard and attack move with aircraft
- Fix `Stop` command not working so well in some cases
- Draw visual effects for airburst weapons
- **Ollerus**
- Build limit group enhancement
- Customizable rocker amplitude
Expand Down
1 change: 1 addition & 0 deletions docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
- Aircraft no longer briefly pause in the air before returning.
- Aircraft with `AirportBound=no` continue moving forward.
- Unit `Speed` setting now accepts floating-point values. Internally parsed values are clamped down to maximum of 100, multiplied by 256 and divided by 100, the result (which at this point is converted to an integer) then clamped down to maximum of 255 giving effective internal speed value range of 0 to 255, e.g leptons traveled per game frame.
- `AirburstWeapon` now supports `IsLaser` (with Ares `LaserThickness`), `IsElectricBolt` (with Phobos `Bolt.Disable1`, `Bolt.Disable2`, `Bolt.Disable3`, `Bolt.Arcs`, without Ares `Bolt.Color1` 、`Bolt.Color2` 、`Bolt.Color3`), `IsRadBeam` (with Ares `Beam.Color` 、`Beam.Duration` 、`Beam.Amplitude` 、`Beam.IsHouseColor`), and `AttachedParticleSystem`.
- Subterranean movement now benefits from speed multipliers from all sources such as veterancy, AttachEffect etc.

## Fixes / interactions with other extensions
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ New:
- Allow infantry to use land sequences in water (by Starkku)
- `<Player @ X>` can now be used as owner for pre-placed objects on skirmish and multiplayer maps (by Starkku)
- Allow customizing charge turret delays per burst on a weapon (by Starkku)
- Draw visual effects for airburst weapons (by CrimRecya)
- Unit `Speed` setting now accepts floating point values (by Starkku)

Vanilla fixes:
Expand Down
185 changes: 185 additions & 0 deletions src/Ext/Bullet/Body.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Body.h"

#include <Ext/Anim/Body.h>
#include <Ext/RadSite/Body.h>
#include <Ext/WeaponType/Body.h>
#include <Ext/TechnoType/Body.h>
Expand Down Expand Up @@ -155,6 +156,190 @@ void BulletExt::ExtData::InitializeLaserTrails()
}
}

static inline int SetBuildingFireAnimZAdjust(BuildingClass* pBuilding, int animY)
{
if (pBuilding->GetOccupantCount() > 0)
return -200;

const auto renderCoords = pBuilding->GetRenderCoords();
const auto zAdj = (animY - renderCoords.Y) / -4;
return (zAdj >= 0) ? 0 : zAdj;
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringAnim(BulletClass* pBullet, HouseClass* pHouse, ObjectClass* pAttach)
{
const auto pWeapon = pBullet->WeaponType;
const auto animCounts = pWeapon->Anim.Count;

if (animCounts <= 0)
return;

const auto pFirer = pBullet->Owner;
const auto pAnimType = pWeapon->Anim[(animCounts % 8 == 0) ? // Have direction ?
(static_cast<int>((Math::atan2(pBullet->Velocity.Y , pBullet->Velocity.X) / Math::TwoPi + 1.5) * animCounts - (animCounts / 8) + 0.5) % animCounts) : // Calculate direction :
ScenarioClass::Instance->Random.RandomRanged(0 , animCounts - 1)]; // Simple random;

if (!pAnimType)
return;

const auto pAnim = GameCreate<AnimClass>(pAnimType, pBullet->SourceCoords);

pAnim->Owner = pHouse;
AnimExt::ExtMap.Find(pAnim)->SetInvoker(pFirer, pHouse);

if (pAttach)
{
if (pAttach->WhatAmI() == AbstractType::Building)
pAnim->ZAdjust = SetBuildingFireAnimZAdjust(static_cast<BuildingClass*>(pAttach), pBullet->SourceCoords.Y);
else
pAnim->SetOwnerObject(pAttach);
}
else if (const auto pBuilding = abstract_cast<BuildingClass*>(pFirer))
{
pAnim->ZAdjust = SetBuildingFireAnimZAdjust(pBuilding, pBullet->SourceCoords.Y);
}
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringReport(BulletClass* pBullet)
{
const auto pWeapon = pBullet->WeaponType;

if (pWeapon->Report.Count <= 0)
return;

const auto pFirer = pBullet->Owner;
const auto reportIndex = pWeapon->Report[(pFirer ? pFirer->unknown_short_3C8 : ScenarioClass::Instance->Random.Random()) % pWeapon->Report.Count];

if (reportIndex != -1)
VocClass::PlayAt(reportIndex, pBullet->Location, nullptr);
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pHouse)
{
const auto pWeapon = pBullet->WeaponType;

if (!pWeapon->IsLaser)
return;

const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);

if (pWeapon->IsHouseColor || pWeaponExt->Laser_IsSingleColor)
{
const auto black = ColorStruct { 0, 0, 0 };
const auto pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, pBullet->TargetCoords, ((pWeapon->IsHouseColor && pHouse) ? pHouse->LaserColor : pWeapon->LaserInnerColor), black, black, pWeapon->LaserDuration);
pLaser->IsHouseColor = true;
pLaser->Thickness = pWeaponExt->LaserThickness;
pLaser->IsSupported = (pLaser->Thickness > 3);
}
else
{
const auto pLaser = GameCreate<LaserDrawClass>(pBullet->SourceCoords, pBullet->TargetCoords, pWeapon->LaserInnerColor, pWeapon->LaserOuterColor, pWeapon->LaserOuterSpread, pWeapon->LaserDuration);
pLaser->IsHouseColor = false;
pLaser->Thickness = 3;
pLaser->IsSupported = false;
}
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringElectricBolt(BulletClass* pBullet)
{
const auto pWeapon = pBullet->WeaponType;

if (!pWeapon->IsElectricBolt)
return;

if (auto const pEBolt = GameCreate<EBolt>())
{
pEBolt->AlternateColor = pWeapon->IsAlternateColor;
//TODO Weapon's Bolt.Color1, Bolt.Color2, Bolt.Color3(Ares)
WeaponTypeExt::BoltWeaponMap[pEBolt] = WeaponTypeExt::ExtMap.Find(pWeapon);
pEBolt->Fire(pBullet->SourceCoords, pBullet->TargetCoords, 0);
}
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringRadBeam(BulletClass* pBullet, HouseClass* pHouse)
{
const auto pWeapon = pBullet->WeaponType;

if (!pWeapon->IsRadBeam)
return;

const bool isTemporal = pWeapon->Warhead && pWeapon->Warhead->Temporal;

if (const auto pRadBeam = RadBeam::Allocate(isTemporal ? RadBeamType::Temporal : RadBeamType::RadBeam))
{
pRadBeam->SetCoordsSource(pBullet->SourceCoords);
pRadBeam->SetCoordsTarget(pBullet->TargetCoords);

const auto pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon);

pRadBeam->Color = (pWeaponExt->Beam_IsHouseColor && pHouse) ? pHouse->LaserColor : pWeaponExt->Beam_Color.Get(isTemporal ? RulesClass::Instance->ChronoBeamColor : RulesClass::Instance->RadColor);
pRadBeam->Period = pWeaponExt->Beam_Duration;
pRadBeam->Amplitude = pWeaponExt->Beam_Amplitude;
}
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
inline void BulletExt::SimulatedFiringParticleSystem(BulletClass* pBullet, HouseClass* pHouse)
{
if (const auto pPSType = pBullet->WeaponType->AttachedParticleSystem)
GameCreate<ParticleSystemClass>(pPSType, pBullet->SourceCoords, pBullet->Target, pBullet->Owner, pBullet->TargetCoords, pHouse);
}

// Make sure pBullet is not empty before call
void BulletExt::SimulatedFiringUnlimbo(BulletClass* pBullet, HouseClass* pHouse, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords, bool randomVelocity)
{
// Weapon
pBullet->WeaponType = pWeapon;

// Range
int projectileRange = WeaponTypeExt::ExtMap.Find(pWeapon)->ProjectileRange.Get();
pBullet->Range = projectileRange;

// House
BulletExt::ExtMap.Find(pBullet)->FirerHouse = pHouse;

// Velocity
auto velocity = BulletVelocity::Empty;

if (randomVelocity)
{
DirStruct dir;
dir.SetValue<5>(ScenarioClass::Instance->Random.RandomRanged(0, 31));

const auto cos_factor = -2.44921270764e-16; // cos(1.5 * Math::Pi * 1.00001)
const auto flatSpeed = cos_factor * pBullet->Speed;

const auto radians = dir.GetRadian<32>();
velocity = BulletVelocity { Math::cos(radians) * flatSpeed, Math::sin(radians) * flatSpeed, static_cast<double>(-pBullet->Speed) };
}

// Unlimbo
pBullet->MoveTo(sourceCoords, velocity);
}

// Make sure pBullet and pBullet->WeaponType is not empty before call
void BulletExt::SimulatedFiringEffects(BulletClass* pBullet, HouseClass* pHouse, ObjectClass* pAttach, bool firingEffect, bool visualEffect)
{
if (firingEffect)
{
BulletExt::SimulatedFiringAnim(pBullet, pHouse, pAttach);
BulletExt::SimulatedFiringReport(pBullet);
}

if (visualEffect)
{
BulletExt::SimulatedFiringLaser(pBullet, pHouse);
BulletExt::SimulatedFiringElectricBolt(pBullet);
BulletExt::SimulatedFiringRadBeam(pBullet, pHouse);
BulletExt::SimulatedFiringParticleSystem(pBullet, pHouse);
}
}

// =============================
// load / save

Expand Down
9 changes: 9 additions & 0 deletions src/Ext/Bullet/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,13 @@ class BulletExt
};

static ExtContainer ExtMap;

static void SimulatedFiringUnlimbo(BulletClass* pBullet, HouseClass* pHouse, WeaponTypeClass* pWeapon, const CoordStruct& sourceCoords, bool randomVelocity);
static void SimulatedFiringEffects(BulletClass* pBullet, HouseClass* pHouse, ObjectClass* pAttach, bool firingEffect, bool visualEffect);
static inline void SimulatedFiringAnim(BulletClass* pBullet, HouseClass* pHouse, ObjectClass* pAttach);
static inline void SimulatedFiringReport(BulletClass* pBullet);
static inline void SimulatedFiringLaser(BulletClass* pBullet, HouseClass* pHouse);
static inline void SimulatedFiringElectricBolt(BulletClass* pBullet);
static inline void SimulatedFiringRadBeam(BulletClass* pBullet, HouseClass* pHouse);
static inline void SimulatedFiringParticleSystem(BulletClass* pBullet, HouseClass* pHouse);
};
41 changes: 15 additions & 26 deletions src/Ext/Bullet/Hooks.DetonateLogics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,15 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
if ((pType->Airburst || pTypeExt->Splits) && pWeapon)
{
auto const pSource = pThis->Owner;
auto const pOwner = pSource ? pSource->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse;
auto pOwner = pSource ? pSource->Owner : BulletExt::ExtMap.Find(pThis)->FirerHouse;

if (!pOwner || pOwner->Defeated)
{
if (const auto pNeutral = HouseClass::FindNeutral())
pOwner = pNeutral;
else
return SkipGameCode;
}

auto& random = ScenarioClass::Instance->Random;
int clusterCount = pType->Cluster;
Expand Down Expand Up @@ -530,12 +538,11 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
}
}

int projectileRange = WeaponTypeExt::ExtMap.Find(pWeapon)->ProjectileRange.Get();
auto const pTypeSplits = pWeapon->Projectile;
int damage = pWeapon->Damage;

if (pTypeExt->AirburstWeapon_ApplyFirepowerMult && pThis->Owner)
damage = static_cast<int>(damage * pThis->Owner->FirepowerMultiplier * TechnoExt::ExtMap.Find(pThis->Owner)->AE.FirepowerMultiplier);
if (pTypeExt->AirburstWeapon_ApplyFirepowerMult && pSource)
damage = static_cast<int>(damage * pSource->FirepowerMultiplier * TechnoExt::ExtMap.Find(pSource)->AE.FirepowerMultiplier);

for (int i = 0; i < clusterCount; ++i)
{
Expand All @@ -550,7 +557,7 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)
int index = random.RandomRanged(0, targets.Count - 1);
pTarget = targets.GetItem(index);

if (pTarget == pThis->Owner)
if (pTarget == pSource)
{
if (random.RandomDouble() > pTypeExt->RetargetSelf_Probability)
{
Expand All @@ -564,28 +571,10 @@ DEFINE_HOOK(0x469EC0, BulletClass_Logics_AirburstWeapon, 0x6)

if (pTarget)
{

if (auto const pBullet = pTypeSplits->CreateBullet(pTarget, pThis->Owner, damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright))
if (auto const pBullet = pTypeSplits->CreateBullet(pTarget, pSource, damage, pWeapon->Warhead, pWeapon->Speed, pWeapon->Bright))
{
BulletExt::ExtMap.Find(pBullet)->FirerHouse = pOwner;
pBullet->WeaponType = pWeapon;
pBullet->Range = projectileRange;

DirStruct dir;
dir.SetValue<5>(random.RandomRanged(0, 31));

auto const radians = dir.GetRadian<32>();
auto const sin_rad = Math::sin(radians);
auto const cos_rad = Math::cos(radians);
auto const cos_factor = -2.44921270764e-16; // cos(1.5 * Math::Pi * 1.00001)
auto const flatSpeed = cos_factor * pBullet->Speed;

BulletVelocity velocity;
velocity.X = cos_rad * flatSpeed;
velocity.Y = sin_rad * flatSpeed;
velocity.Z = -pBullet->Speed;

pBullet->MoveTo(pThis->Location, velocity);
BulletExt::SimulatedFiringUnlimbo(pBullet, pOwner, pWeapon, pThis->Location, true);
BulletExt::SimulatedFiringEffects(pBullet, pOwner, nullptr, false, true);
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/Ext/WeaponType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
this->AttachEffect_CheckOnFirer.Read(exINI, pSection, "AttachEffect.CheckOnFirer");
this->AttachEffect_IgnoreFromSameSource.Read(exINI, pSection, "AttachEffect.IgnoreFromSameSource");
this->KickOutPassengers.Read(exINI, pSection, "KickOutPassengers");

this->Beam_Color.Read(exINI, pSection, "Beam.Color");
this->Beam_Duration.Read(exINI, pSection, "Beam.Duration");
this->Beam_Amplitude.Read(exINI, pSection, "Beam.Amplitude");
this->Beam_IsHouseColor.Read(exINI, pSection, "Beam.IsHouseColor");
this->LaserThickness.Read(exINI, pSection, "LaserThickness");
}

template <typename T>
Expand Down Expand Up @@ -160,6 +166,11 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm)
.Process(this->AttachEffect_CheckOnFirer)
.Process(this->AttachEffect_IgnoreFromSameSource)
.Process(this->KickOutPassengers)
.Process(this->Beam_Color)
.Process(this->Beam_Duration)
.Process(this->Beam_Amplitude)
.Process(this->Beam_IsHouseColor)
.Process(this->LaserThickness)
;
};

Expand Down
14 changes: 14 additions & 0 deletions src/Ext/WeaponType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <BulletClass.h>
#include <WeaponTypeClass.h>
#include <DiskLaserClass.h>
#include <EBolt.h>
#include <Helpers/Macro.h>
#include <Utilities/Container.h>
#include <Utilities/TemplateDef.h>
Expand Down Expand Up @@ -61,6 +62,12 @@ class WeaponTypeExt
Valueable<bool> AttachEffect_IgnoreFromSameSource;
Valueable<bool> KickOutPassengers;

Nullable<ColorStruct> Beam_Color;
Valueable<int> Beam_Duration;
Valueable<double> Beam_Amplitude;
Valueable<bool> Beam_IsHouseColor;
Valueable<int> LaserThickness;

ExtData(WeaponTypeClass* OwnerObject) : Extension<WeaponTypeClass>(OwnerObject)
, DiskLaser_Radius { DiskLaserClass::Radius }
, ProjectileRange { Leptons(100000) }
Expand Down Expand Up @@ -101,6 +108,11 @@ class WeaponTypeExt
, AttachEffect_CheckOnFirer { false }
, AttachEffect_IgnoreFromSameSource { false }
, KickOutPassengers { true }
, Beam_Color {}
, Beam_Duration { 15 }
, Beam_Amplitude { 40.0 }
, Beam_IsHouseColor { false }
, LaserThickness { 3 }
{ }

int GetBurstDelay(int burstIndex) const;
Expand Down Expand Up @@ -136,6 +148,8 @@ class WeaponTypeExt
static bool SaveGlobals(PhobosStreamWriter& Stm);

static double OldRadius;
static PhobosMap<EBolt*, const WeaponTypeExt::ExtData*> BoltWeaponMap;
static const WeaponTypeExt::ExtData* BoltWeaponType;

static void DetonateAt(WeaponTypeClass* pThis, AbstractClass* pTarget, TechnoClass* pOwner, HouseClass* pFiringHouse = nullptr);
static void DetonateAt(WeaponTypeClass* pThis, AbstractClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr);
Expand Down
Loading
Loading