mirror of
https://github.com/AxioDL/metaforce.git
synced 2025-05-13 16:31:21 +00:00
This member function alters instance state in a few implementations, so it shouldn't be made const. The state manager parameter also shouldn't be const. Retrieved data from the post constructed instance is further modified in some implementations. This removes the constness on this parameter in order to fix more const_cast usages in a follow-up change.
375 lines
16 KiB
C++
375 lines
16 KiB
C++
#include "Runtime/Weapon/CEnergyProjectile.hpp"
|
|
|
|
#include "Runtime/CSimplePool.hpp"
|
|
#include "Runtime/CStateManager.hpp"
|
|
#include "Runtime/GameGlobalObjects.hpp"
|
|
#include "Runtime/Camera/CGameCamera.hpp"
|
|
#include "Runtime/Particle/CDecalManager.hpp"
|
|
#include "Runtime/World/CExplosion.hpp"
|
|
#include "Runtime/World/CGameLight.hpp"
|
|
#include "Runtime/World/CIceImpact.hpp"
|
|
#include "Runtime/World/CPlayer.hpp"
|
|
#include "Runtime/World/CScriptPlatform.hpp"
|
|
#include "Runtime/World/CWorld.hpp"
|
|
|
|
#include "TCastTo.hpp" // Generated file, do not modify include path
|
|
|
|
namespace urde {
|
|
|
|
CEnergyProjectile::CEnergyProjectile(bool active, const TToken<CWeaponDescription>& desc, EWeaponType type,
|
|
const zeus::CTransform& xf, EMaterialTypes excludeMat, const CDamageInfo& damage,
|
|
TUniqueId uid, TAreaId aid, TUniqueId owner, TUniqueId homingTarget,
|
|
EProjectileAttrib attribs, bool underwater, const zeus::CVector3f& scale,
|
|
const std::optional<TLockedToken<CGenDescription>>& visorParticle,
|
|
u16 visorSfx, bool sendCollideMsg)
|
|
: CGameProjectile(active, desc, "GameProjectile", type, xf, excludeMat, damage, uid, aid, owner, homingTarget, attribs,
|
|
underwater, scale, visorParticle, visorSfx, sendCollideMsg)
|
|
, x2ec_dir(xf.basis[1])
|
|
, x2f8_mag(x2ec_dir.magnitude())
|
|
, x2fc_camShake(CCameraShakeData::BuildProjectileCameraShake(0.5f, 0.75f)) {
|
|
xe6_27_thermalVisorFlags = 2;
|
|
}
|
|
|
|
void CEnergyProjectile::PlayImpactSound(const zeus::CVector3f& pos, EWeaponCollisionResponseTypes type) {
|
|
s32 sfxId = x170_projectile.GetSoundIdForCollision(type);
|
|
if (sfxId >= 0) {
|
|
CAudioSys::C3DEmitterParmData parmData = {};
|
|
parmData.x18_maxDist = x170_projectile.GetAudibleRange();
|
|
parmData.x1c_distComp = x170_projectile.GetAudibleFallOff();
|
|
parmData.x20_flags = 0x1; // Continuous parameter update
|
|
parmData.x24_sfxId = CSfxManager::TranslateSFXID(u16(sfxId));
|
|
parmData.x26_maxVol = 1.f;
|
|
parmData.x27_minVol = 0.16f;
|
|
parmData.x29_prio = 0x7f;
|
|
CSfxHandle hnd = CSfxManager::AddEmitter(parmData, true, 0x7f, false, kInvalidAreaId);
|
|
if (x2e4_26_waterUpdate)
|
|
CSfxManager::PitchBend(hnd, -1.f);
|
|
}
|
|
}
|
|
|
|
void CEnergyProjectile::ChangeProjectileOwner(TUniqueId owner, CStateManager& mgr) {
|
|
if (TCastToConstPtr<CActor> act = mgr.GetObjectById(owner)) {
|
|
float rDam = g_tweakPlayerGun->GetRichochetDamage(u32(x110_origDamageInfo.GetWeaponMode().GetType()));
|
|
x110_origDamageInfo.MultiplyDamageAndRadius(rDam);
|
|
mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);
|
|
xec_ownerId = owner;
|
|
mgr.AddWeaponId(xec_ownerId, xf0_weaponType);
|
|
|
|
/* Can now damage Player */
|
|
xf8_filter.ExcludeList().Add(EMaterialTypes::Character);
|
|
xf8_filter.ExcludeList().Remove(EMaterialTypes::Player);
|
|
xf8_filter = CMaterialFilter::MakeIncludeExclude(xf8_filter.GetIncludeList(), xf8_filter.GetExcludeList());
|
|
}
|
|
}
|
|
|
|
void CEnergyProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {
|
|
switch (msg) {
|
|
case EScriptObjectMessage::Deleted:
|
|
if (x2e4_24_active)
|
|
mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);
|
|
if (x2e8_sfx) {
|
|
CSfxManager::RemoveEmitter(x2e8_sfx);
|
|
x2e8_sfx.reset();
|
|
}
|
|
break;
|
|
case EScriptObjectMessage::Registered: {
|
|
if (CElementGen* ps1 = x170_projectile.GetAttachedPS1())
|
|
if (ps1->SystemHasLight())
|
|
CreateProjectileLight("ProjectileLight_GameProjectile", ps1->GetLight(), mgr);
|
|
TLockedToken<CWeaponDescription> desc = x170_projectile.GetWeaponDescription();
|
|
s32 sfx = desc->xa8_PJFX;
|
|
if (sfx != -1) {
|
|
float range = 50.f;
|
|
float falloff = 0.2f;
|
|
if (CRealElement* rnge = desc->xac_RNGE.get())
|
|
rnge->GetValue(0, range);
|
|
if (CRealElement* foff = desc->xb0_FOFF.get())
|
|
foff->GetValue(0, falloff);
|
|
|
|
CAudioSys::C3DEmitterParmData parmData = {};
|
|
parmData.x0_pos = x170_projectile.GetTranslation();
|
|
parmData.xc_dir = x170_projectile.GetVelocity();
|
|
parmData.x18_maxDist = range;
|
|
parmData.x1c_distComp = falloff;
|
|
parmData.x20_flags = 0x9; // Continuous parameter update, doppler
|
|
parmData.x24_sfxId = CSfxManager::TranslateSFXID(sfx);
|
|
parmData.x26_maxVol = 1.f;
|
|
parmData.x27_minVol = 0.16f;
|
|
parmData.x29_prio = 0x7f;
|
|
x2e8_sfx = CSfxManager::AddEmitter(parmData, true, 0x7f, false, kInvalidAreaId);
|
|
}
|
|
mgr.AddWeaponId(xec_ownerId, xf0_weaponType);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
CGameProjectile::AcceptScriptMsg(msg, sender, mgr);
|
|
}
|
|
|
|
void CEnergyProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); }
|
|
|
|
/* Material surface types only (not meta flags) */
|
|
static constexpr u64 kCheckMaterial = 0xE3FFFE;
|
|
|
|
void CEnergyProjectile::ResolveCollisionWithWorld(const CRayCastResult& res, CStateManager& mgr) {
|
|
EWeaponCollisionResponseTypes crType = CCollisionResponseData::GetWorldCollisionResponseType(
|
|
CMaterialList::BitPosition((res.GetMaterial().GetValue() & 0xffffffff) & kCheckMaterial));
|
|
if ((xe8_projectileAttribs & (EProjectileAttrib::Wave | EProjectileAttrib::ComboShot)) !=
|
|
(EProjectileAttrib::Wave | EProjectileAttrib::ComboShot)) {
|
|
/* Not wavebuster */
|
|
if (Explode(res.GetPoint(), res.GetPlane().normal(), crType, mgr, CDamageVulnerability::NormalVulnerabilty(),
|
|
kInvalidUniqueId))
|
|
mgr.ApplyDamageToWorld(xec_ownerId, *this, res.GetPoint(), x12c_curDamageInfo, xf8_filter);
|
|
x2c2_lastResolvedObj = kInvalidUniqueId;
|
|
}
|
|
}
|
|
|
|
void CEnergyProjectile::ResolveCollisionWithActor(const CRayCastResult& res, CActor& act, CStateManager& mgr) {
|
|
x2c2_lastResolvedObj = act.GetUniqueId();
|
|
EWeaponCollisionResponseTypes crType = act.GetCollisionResponseType(
|
|
res.GetPoint(), x34_transform.basis[1].normalized(), x12c_curDamageInfo.GetWeaponMode(), xe8_projectileAttribs);
|
|
act.Touch(*this, mgr);
|
|
const CDamageVulnerability* dVuln = act.GetDamageVulnerability();
|
|
if (!Explode(res.GetPoint(), res.GetPlane().normal(), crType, mgr, *dVuln, act.GetUniqueId())) {
|
|
mgr.SendScriptMsg(&act, GetUniqueId(), EScriptObjectMessage::Touched);
|
|
act.SendScriptMsgs(EScriptObjectState::ReflectedDamage, mgr, EScriptObjectMessage::None);
|
|
} else {
|
|
CGameProjectile::ResolveCollisionWithActor(res, act, mgr);
|
|
ApplyDamageToActors(mgr, x12c_curDamageInfo);
|
|
}
|
|
if (TCastToPtr<CEnergyProjectile> proj = act) {
|
|
proj->Explode(GetTranslation(), x34_transform.basis[1], EWeaponCollisionResponseTypes::OtherProjectile, mgr,
|
|
*GetDamageVulnerability(), GetUniqueId());
|
|
}
|
|
}
|
|
|
|
void CEnergyProjectile::Think(float dt, CStateManager& mgr) {
|
|
CWeapon::Think(dt, mgr);
|
|
if (mgr.GetWorld()->GetCurrentAreaId() != GetAreaIdAlways() &&
|
|
(xe8_projectileAttribs & EProjectileAttrib::ArmCannon) == EProjectileAttrib::ArmCannon)
|
|
mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId());
|
|
|
|
UpdateProjectileMovement(dt, mgr);
|
|
TUniqueId id = kInvalidUniqueId;
|
|
CRayCastResult res = DoCollisionCheck(id, mgr);
|
|
if (res.IsValid()) {
|
|
if (TCastToPtr<CActor> act = mgr.ObjectById(id))
|
|
ResolveCollisionWithActor(res, *act, mgr);
|
|
else
|
|
ResolveCollisionWithWorld(res, mgr);
|
|
}
|
|
|
|
x170_projectile.UpdateParticleFX();
|
|
if (x2e4_24_active && x3d0_26_)
|
|
Explode(GetTranslation(), zeus::skUp, EWeaponCollisionResponseTypes::Default, mgr,
|
|
CDamageVulnerability::NormalVulnerabilty(), kInvalidUniqueId);
|
|
|
|
if (x2c8_projectileLight != kInvalidUniqueId) {
|
|
if (TCastToPtr<CGameLight> light = mgr.ObjectById(x2c8_projectileLight)) {
|
|
light->SetTransform(GetTransform());
|
|
light->SetTranslation(GetTranslation());
|
|
if (CElementGen* ps1 = x170_projectile.GetAttachedPS1())
|
|
if (ps1->SystemHasLight())
|
|
light->SetLight(ps1->GetLight());
|
|
}
|
|
}
|
|
|
|
if (x2e8_sfx) {
|
|
CSfxManager::UpdateEmitter(x2e8_sfx, x170_projectile.GetTranslation(), x170_projectile.GetVelocity(), 1.f);
|
|
CSfxManager::PitchBend(x2e8_sfx, x2e4_26_waterUpdate ? -1.f : 0.f);
|
|
}
|
|
|
|
x3d4_curTime += dt;
|
|
if (x3d4_curTime > 45.f || x170_projectile.IsSystemDeletable() || x3d0_24_dead)
|
|
mgr.FreeScriptObject(GetUniqueId());
|
|
}
|
|
|
|
void CEnergyProjectile::Render(const CStateManager& mgr) const {
|
|
SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(fmt("CEnergyProjectile::Render WPSC_{}"), x2cc_wpscId).c_str(), zeus::skOrange);
|
|
|
|
CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);
|
|
if (visor == CPlayerState::EPlayerVisor::Combat) {
|
|
if ((xe8_projectileAttribs & EProjectileAttrib::Charged) == EProjectileAttrib::Charged ||
|
|
(xe8_projectileAttribs & EProjectileAttrib::ComboShot) == EProjectileAttrib::ComboShot) {
|
|
float warpTime = 1.f - float(x170_projectile.GameTime());
|
|
if (warpTime > 0.f)
|
|
mgr.DrawSpaceWarp(GetTranslation(), warpTime * 0.75f);
|
|
}
|
|
}
|
|
|
|
if (visor == CPlayerState::EPlayerVisor::XRay) {
|
|
CElementGen::SetSubtractBlend((xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice);
|
|
CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);
|
|
x170_projectile.RenderParticles();
|
|
CGameProjectile::Render(mgr);
|
|
mgr.SetupFogForArea(GetAreaIdAlways());
|
|
CElementGen::SetSubtractBlend(false);
|
|
} else if ((xe8_projectileAttribs & EProjectileAttrib::Ice) == EProjectileAttrib::Ice &&
|
|
mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot) {
|
|
CElementGen::SetSubtractBlend(true);
|
|
x170_projectile.RenderParticles();
|
|
CGameProjectile::Render(mgr);
|
|
mgr.SetupFogForArea(GetAreaIdAlways());
|
|
CElementGen::SetSubtractBlend(false);
|
|
} else {
|
|
CGameProjectile::Render(mgr);
|
|
}
|
|
}
|
|
|
|
void CEnergyProjectile::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) {
|
|
auto bounds = x170_projectile.GetBounds();
|
|
if (bounds && !frustum.aabbFrustumTest(*bounds))
|
|
return;
|
|
|
|
CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetActiveVisor(mgr);
|
|
if (visor != CPlayerState::EPlayerVisor::XRay &&
|
|
((xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice ||
|
|
mgr.GetThermalDrawFlag() != EThermalDrawFlag::Hot)) {
|
|
x170_projectile.AddToRenderer();
|
|
}
|
|
EnsureRendered(mgr);
|
|
}
|
|
|
|
void CEnergyProjectile::Touch(CActor& act, CStateManager& mgr) {
|
|
// Empty
|
|
}
|
|
|
|
bool CEnergyProjectile::Explode(const zeus::CVector3f& pos, const zeus::CVector3f& normal,
|
|
EWeaponCollisionResponseTypes type, CStateManager& mgr,
|
|
const CDamageVulnerability& dVuln, TUniqueId hitActor) {
|
|
zeus::CVector3f offsetPos = pos + normal * 0.01f;
|
|
bool done = true;
|
|
bool retargetPlayer = false;
|
|
bool deflect = false;
|
|
zeus::CVector3f targetPos;
|
|
EVulnerability vulnType = dVuln.GetVulnerability(x12c_curDamageInfo.GetWeaponMode(), false);
|
|
if (vulnType == EVulnerability::Deflect) {
|
|
deflect = true;
|
|
EDeflectType deflectType = dVuln.GetDeflectionType(x12c_curDamageInfo.GetWeaponMode());
|
|
switch (deflectType) {
|
|
case EDeflectType::None:
|
|
deflect = false;
|
|
break;
|
|
case EDeflectType::Two:
|
|
case EDeflectType::Three:
|
|
if (deflectType != EDeflectType::Two ||
|
|
(xf0_weaponType != EWeaponType::Missile &&
|
|
(xe8_projectileAttribs & EProjectileAttrib::ComboShot) != EProjectileAttrib::ComboShot))
|
|
if (xf8_filter.GetExcludeList().HasMaterial(EMaterialTypes::Player))
|
|
retargetPlayer = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (retargetPlayer) {
|
|
float ang = mgr.GetActiveRandom()->Range(0.f, 2.f * M_PIF);
|
|
float y = std::sin(ang);
|
|
float x = std::cos(ang);
|
|
targetPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f) + zeus::CVector3f(x, 0.f, y);
|
|
ChangeProjectileOwner(hitActor, mgr);
|
|
}
|
|
}
|
|
|
|
if (vulnType != EVulnerability::Immune && !deflect) {
|
|
deflect =
|
|
(type == EWeaponCollisionResponseTypes::Unknown15 || type == EWeaponCollisionResponseTypes::EnemyShielded ||
|
|
(type >= EWeaponCollisionResponseTypes::Unknown69 && type <= EWeaponCollisionResponseTypes::AtomicAlphaReflect));
|
|
}
|
|
|
|
SetTranslation(offsetPos);
|
|
|
|
if (deflect) {
|
|
done = false;
|
|
x2c0_homingTargetId = kInvalidUniqueId;
|
|
x3d0_25_ = false;
|
|
} else {
|
|
StopProjectile(mgr);
|
|
if (x3d0_27_camShakeDirty) {
|
|
x2fc_camShake.SetSfxPositionAndDistance(pos, 50.f);
|
|
mgr.GetCameraManager()->AddCameraShaker(x2fc_camShake, false);
|
|
}
|
|
}
|
|
|
|
PlayImpactSound(pos, type);
|
|
mgr.InformListeners(pos, EListenNoiseType::ProjectileExplode);
|
|
if (auto particle = x170_projectile.CollisionOccured(type, !done, retargetPlayer, offsetPos, normal, targetPos)) {
|
|
zeus::CTransform particleXf = zeus::lookAt(zeus::skZero3f, normal);
|
|
particleXf.origin = offsetPos;
|
|
if (xf0_weaponType != EWeaponType::Power || !xf8_filter.GetExcludeList().HasMaterial(EMaterialTypes::Player) ||
|
|
!x2e4_27_inWater) {
|
|
if (auto decal = x170_projectile.GetDecalForCollision(type)) {
|
|
CDecalManager::AddDecal(*decal, particleXf,
|
|
(xe8_projectileAttribs & EProjectileAttrib::Ice) != EProjectileAttrib::Ice, mgr);
|
|
}
|
|
zeus::CVector3f scale = zeus::skOne3f;
|
|
bool camClose = false;
|
|
if (mgr.GetPlayer().GetCameraState() == CPlayer::EPlayerCameraState::FirstPerson) {
|
|
float mag = (offsetPos - mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation()).magnitude();
|
|
if (mag < 4.f) {
|
|
scale = zeus::CVector3f(0.75f * (mag * 0.25f) + 0.25f);
|
|
camClose = true;
|
|
}
|
|
}
|
|
u32 explodeFlags = 0x8;
|
|
if ((xe8_projectileAttribs & EProjectileAttrib::Ice) == EProjectileAttrib::Ice)
|
|
explodeFlags |= 0x4;
|
|
if (camClose)
|
|
explodeFlags |= 0x2;
|
|
CEntityInfo explosionInfo(GetAreaIdAlways(), CEntity::NullConnectionList);
|
|
CExplosion* explosion =
|
|
new CExplosion(*particle, mgr.AllocateUniqueId(), true, explosionInfo, "Projectile collision response",
|
|
particleXf, explodeFlags, scale, zeus::skWhite);
|
|
mgr.AddObject(explosion);
|
|
if (TCastToPtr<CActor> hActor = mgr.ObjectById(hitActor)) {
|
|
bool validPlat = false;
|
|
CScriptPlatform* plat = TCastToPtr<CScriptPlatform>(hActor.GetPtr()).GetPtr();
|
|
validPlat = plat != nullptr;
|
|
if (!validPlat && hActor->GetMaterialList().HasMaterial(EMaterialTypes::Bomb)) {
|
|
for (CEntity* ent : mgr.GetPlatformAndDoorObjectList()) {
|
|
if (TCastToPtr<CScriptPlatform> otherPlat = ent) {
|
|
if (otherPlat->IsSlave(hitActor)) {
|
|
plat = otherPlat.GetPtr();
|
|
validPlat = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (validPlat)
|
|
plat->AddSlave(explosion->GetUniqueId(), mgr);
|
|
}
|
|
} else {
|
|
x3d0_24_dead = true;
|
|
}
|
|
|
|
if ((xe8_projectileAttribs & (EProjectileAttrib::ComboShot | EProjectileAttrib::Ice)) ==
|
|
(EProjectileAttrib::ComboShot | EProjectileAttrib::Ice)) {
|
|
/* Ice Spreader */
|
|
TLockedToken<CGenDescription> iceSpreadParticle = g_SimplePool->GetObj("IceSpread1");
|
|
u32 flags = (xe6_27_thermalVisorFlags & 0x2) == 0 ? 1 : 0;
|
|
flags |= 0x2;
|
|
CIceImpact* iceImpact =
|
|
new CIceImpact(iceSpreadParticle, mgr.AllocateUniqueId(), GetAreaIdAlways(), true, "Ice spread explosion",
|
|
particleXf, flags, zeus::skOne3f, zeus::skWhite);
|
|
mgr.AddObject(iceImpact);
|
|
}
|
|
}
|
|
|
|
return done;
|
|
}
|
|
|
|
void CEnergyProjectile::StopProjectile(CStateManager& mgr) {
|
|
DeleteProjectileLight(mgr);
|
|
mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType);
|
|
x2e4_24_active = false;
|
|
x68_material = CMaterialList();
|
|
mgr.UpdateActorInSortedLists(*this);
|
|
if (x2e8_sfx) {
|
|
CSfxManager::RemoveEmitter(x2e8_sfx);
|
|
x2e8_sfx.reset();
|
|
}
|
|
}
|
|
|
|
} // namespace urde
|