mirror of https://github.com/AxioDL/metaforce.git
1922 lines
68 KiB
C++
1922 lines
68 KiB
C++
#include "CPatterned.hpp"
|
|
#include "Runtime/CStateManager.hpp"
|
|
#include "CPatternedInfo.hpp"
|
|
#include "TCastTo.hpp"
|
|
#include "CActorParameters.hpp"
|
|
#include "Character/CPASAnimParmData.hpp"
|
|
#include "GameGlobalObjects.hpp"
|
|
#include "CSimplePool.hpp"
|
|
#include "CPlayer.hpp"
|
|
#include "Weapon/CGameProjectile.hpp"
|
|
#include "Character/CAnimData.hpp"
|
|
#include "TCastTo.hpp"
|
|
#include "MP1/World/CSpacePirate.hpp"
|
|
#include "MP1/World/CMetroid.hpp"
|
|
#include "World/CStateMachine.hpp"
|
|
#include "CExplosion.hpp"
|
|
#include "Graphics/CSkinnedModel.hpp"
|
|
#include "CPathFindSearch.hpp"
|
|
#include "Camera/CFirstPersonCamera.hpp"
|
|
#include "World/CScriptWaypoint.hpp"
|
|
#include "World/CScriptActorKeyframe.hpp"
|
|
#include "Weapon/CEnergyProjectile.hpp"
|
|
|
|
namespace urde
|
|
{
|
|
|
|
const zeus::CColor CPatterned::skDamageColor(0.5f, 0.f, 0.f);
|
|
CMaterialList gkPatternedGroundMaterialList(EMaterialTypes::Character, EMaterialTypes::Solid,
|
|
EMaterialTypes::Orbit, EMaterialTypes::GroundCollider,
|
|
EMaterialTypes::Target);
|
|
CMaterialList gkPatternedFlyerMaterialList(EMaterialTypes::Character, EMaterialTypes::Solid,
|
|
EMaterialTypes::Orbit, EMaterialTypes::Target);
|
|
|
|
CPatterned::CPatterned(ECharacter character, TUniqueId uid, std::string_view name, CPatterned::EFlavorType flavor,
|
|
const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData,
|
|
const CPatternedInfo& pInfo, CPatterned::EMovementType moveType,
|
|
CPatterned::EColliderType colliderType, EBodyType bodyType, const CActorParameters& actorParms,
|
|
EKnockBackVariant kbVariant)
|
|
: CAi(uid, pInfo.xf8_active, name, info, xf, std::move(mData),
|
|
zeus::CAABox(pInfo.xcc_bodyOrigin - zeus::CVector3f{pInfo.xc4_halfExtent, pInfo.xc4_halfExtent, 0.f},
|
|
pInfo.xcc_bodyOrigin +
|
|
zeus::CVector3f{pInfo.xc4_halfExtent, pInfo.xc4_halfExtent, pInfo.xc8_height}),
|
|
pInfo.x0_mass, pInfo.x54_healthInfo, pInfo.x5c_damageVulnerability,
|
|
moveType == EMovementType::Flyer ? gkPatternedFlyerMaterialList : gkPatternedGroundMaterialList,
|
|
pInfo.xfc_stateMachineId, actorParms, pInfo.xd8_stepUpHeight, 0.8f),
|
|
x2fc_minAttackRange(pInfo.x18_minAttackRange),
|
|
x300_maxAttackRange(pInfo.x1c_maxAttackRange),
|
|
x304_averageAttackTime(pInfo.x20_averageAttackTime),
|
|
x308_attackTimeVariation(pInfo.x24_attackTimeVariation),
|
|
x34c_character(character),
|
|
x388_anim(pInfo.GetAnimationParameters().GetInitialAnimation()),
|
|
x3b4_speed(pInfo.x4_speed),
|
|
x3b8_turnSpeed(pInfo.x8_turnSpeed),
|
|
x3bc_detectionRange(pInfo.xc_detectionRange),
|
|
x3c0_detectionHeightRange(pInfo.x10_detectionHeightRange),
|
|
x3c4_detectionAngle(std::cos(zeus::degToRad(pInfo.x14_dectectionAngle))),
|
|
x3c8_leashRadius(pInfo.x28_leashRadius),
|
|
x3cc_playerLeashRadius(pInfo.x2c_playerLeashRadius),
|
|
x3d0_playerLeashTime(pInfo.x30_playerLeashTime),
|
|
x3d8_xDamageThreshold(pInfo.xdc_xDamage),
|
|
x3dc_frozenXDamageThreshold(pInfo.xe0_frozenXDamage),
|
|
x3e0_xDamageDelay(pInfo.xe4_xDamageDelay),
|
|
x3fc_flavor(flavor),
|
|
x460_knockBackController(kbVariant)
|
|
{
|
|
x328_25_verticalMovement = moveType == EMovementType::Flyer;
|
|
x328_27_onGround = moveType != EMovementType::Flyer;
|
|
x328_28_prevOnGround = true;
|
|
x328_30_lookAtDeathDir = true;
|
|
x329_24_ = true;
|
|
x400_25_alive = true;
|
|
x400_31_isFlyer = moveType == CPatterned::EMovementType::Flyer;
|
|
x402_29_drawParticles = true;
|
|
x402_30_updateThermalFrozenState = x402_31_thawed = actorParms.HasThermalHeat();
|
|
x403_25_enableStateMachine = true;
|
|
x403_26_stateControlledMassiveDeath = true;
|
|
x404_contactDamage = pInfo.x34_contactDamageInfo;
|
|
x424_damageWaitTime = pInfo.x50_damageWaitTime;
|
|
x454_deathSfx = pInfo.xe8_deathSfx;
|
|
x458_iceShatterSfx = pInfo.x134_iceShatterSfx;
|
|
x4f4_intoFreezeDur = pInfo.x100_intoFreezeDur;
|
|
x4f8_outofFreezeDur = pInfo.x104_outofFreezeDur;
|
|
x4fc_ = pInfo.x108_;
|
|
x508_colliderType = colliderType;
|
|
x50c_baseDamageMag = actorParms.GetThermalMag();
|
|
x514_deathExplosionOffset = pInfo.x110_particle1Scale;
|
|
x540_iceDeathExplosionOffset = pInfo.x124_particle2Scale;
|
|
|
|
if (pInfo.x11c_particle1.IsValid())
|
|
x520_deathExplosionParticle = { g_SimplePool->GetObj({FOURCC('PART'), pInfo.x11c_particle1})};
|
|
|
|
if (pInfo.x120_electric.IsValid())
|
|
x530_deathExplosionElectric = { g_SimplePool->GetObj({FOURCC('ELSC'), pInfo.x120_electric})};
|
|
|
|
if (pInfo.x130_particle2.IsValid())
|
|
x54c_iceDeathExplosionParticle = { g_SimplePool->GetObj({FOURCC('PART'), pInfo.x130_particle2})};
|
|
|
|
if (x404_contactDamage.GetRadius() > 0.f)
|
|
x404_contactDamage.SetRadius(0.f);
|
|
|
|
xe6_29_renderParticleDBInside = false;
|
|
x402_27_noXrayModel = !x64_modelData->HasModel(CModelData::EWhichModel::XRay);
|
|
BuildBodyController(bodyType);
|
|
}
|
|
|
|
void CPatterned::Accept(urde::IVisitor& visitor)
|
|
{
|
|
visitor.Visit(this);
|
|
}
|
|
|
|
void CPatterned::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr)
|
|
{
|
|
CAi::AcceptScriptMsg(msg, uid, mgr);
|
|
|
|
switch (msg)
|
|
{
|
|
case EScriptObjectMessage::Registered:
|
|
{
|
|
if (x508_colliderType != EColliderType::One)
|
|
{
|
|
CMaterialList include = GetMaterialFilter().GetIncludeList();
|
|
CMaterialList exclude = GetMaterialFilter().GetExcludeList();
|
|
include.Remove(EMaterialTypes::Character);
|
|
exclude.Add(EMaterialTypes::Character);
|
|
SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));
|
|
}
|
|
|
|
if (HasModelData() && GetModelData()->HasAnimData() && GetModelData()->GetAnimationData()->GetIceModel())
|
|
{
|
|
const auto& baseAABB = GetBaseBoundingBox();
|
|
float diagExtent = (baseAABB.max - baseAABB.min).magnitude() * 0.5f;
|
|
x510_vertexMorph = std::make_shared<CVertexMorphEffect>(zeus::CVector3f::skRight,
|
|
zeus::CVector3f{}, diagExtent, 0.f, *mgr.GetActiveRandom());
|
|
}
|
|
|
|
xf8_25_angularEnabled = true;
|
|
break;
|
|
}
|
|
case EScriptObjectMessage::OnFloor:
|
|
{
|
|
if (!x328_25_verticalMovement)
|
|
{
|
|
x150_momentum = {};
|
|
AddMaterial(EMaterialTypes::GroundCollider, mgr);
|
|
}
|
|
x328_27_onGround = true;
|
|
break;
|
|
}
|
|
case EScriptObjectMessage::Falling:
|
|
{
|
|
if (!x328_25_verticalMovement)
|
|
{
|
|
if (x450_bodyController->GetPercentageFrozen() == 0.f)
|
|
{
|
|
x150_momentum = {0.f, 0.f, -GetWeight()};
|
|
RemoveMaterial(EMaterialTypes::GroundCollider, mgr);
|
|
}
|
|
}
|
|
x328_27_onGround = false;
|
|
break;
|
|
}
|
|
case EScriptObjectMessage::Activate:
|
|
x3a0_latestLeashPosition = GetTranslation();
|
|
break;
|
|
case EScriptObjectMessage::Deleted:
|
|
if (x330_stateMachineState.GetActorState() != nullptr)
|
|
x330_stateMachineState.GetActorState()->CallFunc(mgr, *this, EStateMsg::Deactivate, 0.f);
|
|
break;
|
|
case EScriptObjectMessage::Damage:
|
|
{
|
|
if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid))
|
|
{
|
|
const CDamageInfo& info = proj->GetDamageInfo();
|
|
if (info.GetWeaponMode().GetType() == EWeaponType::Wave)
|
|
{
|
|
if (x460_knockBackController.x81_26_enableShock && info.GetWeaponMode().IsComboed() && HealthInfo(mgr))
|
|
{
|
|
x401_31_nextPendingShock = true;
|
|
KnockBack(GetTransform().frontVector(), mgr, info, EKnockBackType::Radius, false,
|
|
info.GetKnockBackPower());
|
|
x460_knockBackController.DeferKnockBack(EWeaponType::Wave);
|
|
}
|
|
}
|
|
else if (info.GetWeaponMode().GetType() == EWeaponType::Plasma)
|
|
{
|
|
if (x460_knockBackController.x81_27_enableBurn && info.GetWeaponMode().IsCharged() && HealthInfo(mgr))
|
|
{
|
|
KnockBack(GetTransform().frontVector(), mgr, info, EKnockBackType::Radius,
|
|
false, info.GetKnockBackPower());
|
|
x460_knockBackController.DeferKnockBack(EWeaponType::Plasma);
|
|
}
|
|
}
|
|
if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId())
|
|
x400_24_hitByPlayerProjectile = true;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptObjectMessage::InvulnDamage:
|
|
{
|
|
if (TCastToConstPtr<CGameProjectile> proj = mgr.GetObjectById(uid))
|
|
{
|
|
if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId())
|
|
x400_24_hitByPlayerProjectile = true;
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::UpdateThermalFrozenState(bool thawed)
|
|
{
|
|
x402_31_thawed = thawed;
|
|
if (x403_24_keepThermalVisorState)
|
|
return;
|
|
xe6_27_thermalVisorFlags = u8(thawed ? 2 : 1);
|
|
}
|
|
|
|
void CPatterned::Think(float dt, CStateManager& mgr)
|
|
{
|
|
if (!GetActive())
|
|
return;
|
|
|
|
if (x402_30_updateThermalFrozenState)
|
|
UpdateThermalFrozenState(x450_bodyController->GetPercentageFrozen() == 0.f);
|
|
|
|
if (x64_modelData->GetAnimationData()->GetIceModel())
|
|
x510_vertexMorph->Update(dt);
|
|
|
|
if (x402_26_dieIf80PercFrozen)
|
|
{
|
|
float froz = x450_bodyController->GetPercentageFrozen();
|
|
if (froz > 0.8f)
|
|
x400_29_pendingMassiveFrozenDeath = true;
|
|
}
|
|
|
|
if (!x400_25_alive)
|
|
{
|
|
if ((x400_28_pendingMassiveDeath || x400_29_pendingMassiveFrozenDeath) && x3e0_xDamageDelay <= 0.f)
|
|
{
|
|
if (x400_29_pendingMassiveFrozenDeath)
|
|
{
|
|
SendScriptMsgs(EScriptObjectState::AboutToMassivelyDie, mgr, EScriptObjectMessage::None);
|
|
MassiveFrozenDeath(mgr);
|
|
}
|
|
else
|
|
{
|
|
SendScriptMsgs(EScriptObjectState::AboutToMassivelyDie, mgr, EScriptObjectMessage::None);
|
|
MassiveDeath(mgr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x3e0_xDamageDelay -= dt;
|
|
if (x403_26_stateControlledMassiveDeath && x330_stateMachineState.GetName())
|
|
{
|
|
bool isDead = x330_stateMachineState.GetName() == "Dead"sv;
|
|
if (isDead && x330_stateMachineState.x8_time > 15.f)
|
|
MassiveDeath(mgr);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateAlphaDelta(dt, mgr);
|
|
|
|
x3e4_lastHP = HealthInfo(mgr)->GetHP();
|
|
if (!x330_stateMachineState.x4_state)
|
|
x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), "Start"sv);
|
|
|
|
zeus::CVector3f diffVec = x4e4_latestPredictedTranslation - GetTranslation();
|
|
if (!x328_25_verticalMovement)
|
|
diffVec.z() = 0.f;
|
|
|
|
if (diffVec.magSquared() > (0.1f * dt))
|
|
x4f0_predictedLeashTime += dt;
|
|
else
|
|
x4f0_predictedLeashTime = 0.f;
|
|
|
|
if (x460_knockBackController.x81_26_enableShock)
|
|
{
|
|
/* Shock on logical falling edge */
|
|
if (!x401_31_nextPendingShock && x402_24_pendingShock)
|
|
Shock(0.5f + mgr.GetActiveRandom()->Range(0.f, 0.5f), 0.2f);
|
|
x402_24_pendingShock = x401_31_nextPendingShock;
|
|
x401_31_nextPendingShock = false;
|
|
|
|
if (x450_bodyController->IsElectrocuting())
|
|
{
|
|
mgr.GetActorModelParticles()->StartElectric(*this);
|
|
if (x3f0_pendingShockDamage > 0.f && x400_25_alive)
|
|
{
|
|
CDamageInfo dInfo({{EWeaponType::Wave}, x3f0_pendingShockDamage, 0.f, 0.f}, dt);
|
|
mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo,
|
|
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x3f0_pendingShockDamage != 0.f)
|
|
{
|
|
x450_bodyController->StopElectrocution();
|
|
mgr.GetActorModelParticles()->StopElectric(*this);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (x450_bodyController->IsOnFire())
|
|
{
|
|
if (x400_25_alive)
|
|
{
|
|
mgr.GetActorModelParticles()->LightDudeOnFire(*this);
|
|
CDamageInfo dInfo({{EWeaponType::Plasma}, x3ec_pendingFireDamage, 0.f, 0.f}, dt);
|
|
mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo,
|
|
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (x3ec_pendingFireDamage > 0.f)
|
|
x3ec_pendingFireDamage = 0.f;
|
|
if (x450_bodyController->IsFrozen())
|
|
mgr.GetActorModelParticles()->StopThermalHotParticles(*this);
|
|
}
|
|
|
|
if (x401_27_phazingOut || x401_28_burning)
|
|
x3e8_alphaDelta = -0.33333334f;
|
|
|
|
if (x401_30_pendingDeath)
|
|
{
|
|
x401_30_pendingDeath = false;
|
|
Death(mgr, GetTransform().frontVector(), EScriptObjectState::DeathRattle);
|
|
}
|
|
|
|
float thinkDt = (x400_25_alive ? dt : dt * CalcDyingThinkRate());
|
|
|
|
x450_bodyController->Update(thinkDt, mgr);
|
|
x450_bodyController->MultiplyPlaybackRate(x3b4_speed);
|
|
SAdvancementDeltas deltas = UpdateAnimation(thinkDt, mgr, !x450_bodyController->IsFrozen());
|
|
x434_posDelta = deltas.x0_posDelta;
|
|
x440_rotDelta = deltas.xc_rotDelta;
|
|
|
|
if (x403_25_enableStateMachine && x450_bodyController->GetPercentageFrozen() < 1.f)
|
|
x330_stateMachineState.Update(mgr, *this, thinkDt);
|
|
|
|
ThinkAboutMove(thinkDt);
|
|
|
|
x460_knockBackController.Update(thinkDt, mgr, *this);
|
|
x4e4_latestPredictedTranslation = GetTranslation() + PredictMotion(thinkDt).x0_translation;
|
|
x328_26_longJump = false;
|
|
if (x420_curDamageRemTime > 0.f)
|
|
x420_curDamageRemTime -= dt;
|
|
|
|
if (x401_28_burning && x3f4_burnThinkRateTimer > dt)
|
|
x3f4_burnThinkRateTimer -= dt;
|
|
|
|
xd0_damageMag = x50c_baseDamageMag;
|
|
UpdateDamageColor(dt);
|
|
|
|
if (!x450_bodyController->IsFrozen())
|
|
{
|
|
if (x3a0_latestLeashPosition == zeus::CVector3f())
|
|
x3a0_latestLeashPosition = GetTranslation();
|
|
|
|
if (x3cc_playerLeashRadius != 0.f)
|
|
{
|
|
zeus::CVector3f diffVec = (GetTranslation() - mgr.GetPlayer().GetTranslation());
|
|
if (diffVec.magSquared() > x3cc_playerLeashRadius)
|
|
x3d4_curPlayerLeashTime += dt;
|
|
else
|
|
x3d4_curPlayerLeashTime = 0.f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RemoveEmitter();
|
|
}
|
|
|
|
if (x2f8_waypointPauseRemTime > 0.f)
|
|
x2f8_waypointPauseRemTime -= dt;
|
|
}
|
|
|
|
void CPatterned::Touch(CActor& act, CStateManager& mgr)
|
|
{
|
|
if (!x400_25_alive)
|
|
return;
|
|
|
|
if (TCastToPtr<CGameProjectile> proj = act)
|
|
{
|
|
if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId())
|
|
x400_24_hitByPlayerProjectile = true;
|
|
}
|
|
}
|
|
|
|
std::experimental::optional<zeus::CAABox> CPatterned::GetTouchBounds() const
|
|
{
|
|
return GetBoundingBox();
|
|
}
|
|
|
|
bool CPatterned::CanRenderUnsorted(const urde::CStateManager& mgr) const
|
|
{
|
|
return x64_modelData->GetAnimationData()->GetParticleDB().AreAnySystemsDrawnWithModel() ? false :
|
|
CActor::CanRenderUnsorted(mgr);
|
|
}
|
|
|
|
zeus::CVector3f CPatterned::GetAimPosition(const urde::CStateManager& mgr, float dt) const
|
|
{
|
|
zeus::CVector3f offset;
|
|
if (dt > 0.f)
|
|
offset = PredictMotion(dt).x0_translation;
|
|
|
|
CSegId segId = GetModelData()->GetAnimationData()->GetLocatorSegId("lockon_target_LCTR"sv);
|
|
if (segId != 0xFF)
|
|
{
|
|
zeus::CTransform xf = GetModelData()->GetAnimationData()->GetLocatorTransform(segId, nullptr);
|
|
zeus::CVector3f scaledOrigin = GetModelData()->GetScale() * xf.origin;
|
|
if (GetTouchBounds())
|
|
return offset + GetTouchBounds()->clampToBox(x34_transform * scaledOrigin);
|
|
|
|
zeus::CAABox aabox = GetBaseBoundingBox();
|
|
|
|
zeus::CAABox primBox(aabox.min + GetPrimitiveOffset(), aabox.max + GetPrimitiveOffset());
|
|
|
|
return offset + (x34_transform * primBox.clampToBox(scaledOrigin));
|
|
}
|
|
|
|
return offset + GetBoundingBox().center();
|
|
}
|
|
|
|
zeus::CTransform CPatterned::GetLctrTransform(std::string_view name) const
|
|
{
|
|
return x34_transform * GetScaledLocatorTransform(name);
|
|
}
|
|
|
|
zeus::CTransform CPatterned::GetLctrTransform(CSegId id) const
|
|
{
|
|
zeus::CTransform xf = x64_modelData->GetAnimationData()->GetLocatorTransform(id, nullptr);
|
|
return x34_transform * zeus::CTransform(xf.buildMatrix3f(), x64_modelData->GetScale() * xf.origin);
|
|
}
|
|
|
|
void CPatterned::DeathDelete(CStateManager& mgr)
|
|
{
|
|
SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);
|
|
if (x450_bodyController->IsElectrocuting())
|
|
{
|
|
x3f0_pendingShockDamage = 0.f;
|
|
x450_bodyController->StopElectrocution();
|
|
mgr.GetActorModelParticles()->StopElectric(*this);
|
|
}
|
|
mgr.FreeScriptObject(GetUniqueId());
|
|
}
|
|
|
|
void CPatterned::Death(CStateManager& mgr, const zeus::CVector3f& dir, EScriptObjectState state)
|
|
{
|
|
if (x400_25_alive)
|
|
{
|
|
if (!x450_bodyController->IsOnFire())
|
|
{
|
|
x402_25_lostMassiveFrozenHP = (x3e4_lastHP - HealthInfo(mgr)->GetHP()) >=
|
|
x3dc_frozenXDamageThreshold;
|
|
if (x402_25_lostMassiveFrozenHP && x54c_iceDeathExplosionParticle &&
|
|
x450_bodyController->GetPercentageFrozen() > 0.8f)
|
|
{
|
|
x400_29_pendingMassiveFrozenDeath = true;
|
|
}
|
|
else if ((x3e4_lastHP - HealthInfo(mgr)->GetHP()) >= x3d8_xDamageThreshold)
|
|
{
|
|
x400_28_pendingMassiveDeath = true;
|
|
}
|
|
}
|
|
if (x400_28_pendingMassiveDeath || x400_29_pendingMassiveFrozenDeath)
|
|
{
|
|
if (x328_30_lookAtDeathDir && x3e0_xDamageDelay <= 0.f && dir != zeus::CVector3f::skZero)
|
|
{
|
|
SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() - dir) *
|
|
zeus::CTransform::RotateX(zeus::degToRad(45.f)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), "Dead"sv);
|
|
RemoveMaterial(EMaterialTypes::GroundCollider, mgr);
|
|
x328_25_verticalMovement = false;
|
|
}
|
|
x400_25_alive = false;
|
|
if (x450_bodyController->HasBodyState(pas::EAnimationState::Hurled) &&
|
|
x450_bodyController->GetBodyType() == EBodyType::Flyer)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCHurledCmd(-dir, zeus::CVector3f::skZero));
|
|
}
|
|
else if (x450_bodyController->HasBodyState(pas::EAnimationState::Fall))
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCKnockDownCmd(-dir, pas::ESeverity::One));
|
|
}
|
|
if (state != EScriptObjectState::Any)
|
|
{
|
|
SendScriptMsgs(state, mgr, EScriptObjectMessage::None);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPatterned::KnockBack(const zeus::CVector3f& backVec, CStateManager& mgr, const CDamageInfo& info,
|
|
EKnockBackType type, bool inDeferred, float magnitude)
|
|
{
|
|
CHealthInfo* hInfo = HealthInfo(mgr);
|
|
if (!x401_27_phazingOut && !x401_28_burning && hInfo)
|
|
{
|
|
x460_knockBackController.KnockBack(backVec, mgr, *this, info, type, magnitude);
|
|
if (x450_bodyController->IsFrozen() && x460_knockBackController.GetActiveParms().xc_intoFreezeDur >= 0.f)
|
|
x450_bodyController->FrozenBreakout();
|
|
switch (x460_knockBackController.GetActiveParms().x4_animFollowup)
|
|
{
|
|
case EKnockBackAnimationFollowUp::Freeze:
|
|
Freeze(mgr, zeus::CVector3f::skZero, zeus::CUnitVector3f(x34_transform.transposeRotate(backVec)),
|
|
x460_knockBackController.GetActiveParms().x8_followupDuration);
|
|
break;
|
|
case EKnockBackAnimationFollowUp::PhazeOut:
|
|
PhazeOut(mgr);
|
|
break;
|
|
case EKnockBackAnimationFollowUp::Shock:
|
|
Shock(x460_knockBackController.GetActiveParms().x8_followupDuration, -1.f);
|
|
break;
|
|
case EKnockBackAnimationFollowUp::Burn:
|
|
Burn(x460_knockBackController.GetActiveParms().x8_followupDuration, 0.25f);
|
|
break;
|
|
case EKnockBackAnimationFollowUp::LaggedBurnDeath:
|
|
x401_29_laggedBurnDeath = true;
|
|
case EKnockBackAnimationFollowUp::BurnDeath:
|
|
Burn(x460_knockBackController.GetActiveParms().x8_followupDuration, -1.f);
|
|
Death(mgr, zeus::CVector3f::skZero, EScriptObjectState::DeathRattle);
|
|
x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;
|
|
x400_27_fadeToDeath = x401_28_burning = true;
|
|
x3f4_burnThinkRateTimer = 1.5f;
|
|
x402_29_drawParticles = false;
|
|
x450_bodyController->DouseFlames();
|
|
mgr.GetActorModelParticles()->StopThermalHotParticles(*this);
|
|
mgr.GetActorModelParticles()->StartBurnDeath(*this);
|
|
if (!x401_29_laggedBurnDeath)
|
|
{
|
|
mgr.GetActorModelParticles()->EnsureFirePopLoaded(*this);
|
|
mgr.GetActorModelParticles()->EnsureIceBreakLoaded(*this);
|
|
}
|
|
break;
|
|
case EKnockBackAnimationFollowUp::Death:
|
|
Death(mgr, zeus::CVector3f::skZero, EScriptObjectState::DeathRattle);
|
|
break;
|
|
case EKnockBackAnimationFollowUp::ExplodeDeath:
|
|
Death(mgr, zeus::CVector3f::skZero, EScriptObjectState::DeathRattle);
|
|
if (GetDeathExplosionParticle() || x530_deathExplosionElectric)
|
|
MassiveDeath(mgr);
|
|
else if (x450_bodyController->IsFrozen())
|
|
x450_bodyController->FrozenBreakout();
|
|
break;
|
|
case EKnockBackAnimationFollowUp::IceDeath:
|
|
Death(mgr, zeus::CVector3f::skZero, EScriptObjectState::DeathRattle);
|
|
if (x54c_iceDeathExplosionParticle)
|
|
MassiveFrozenDeath(mgr);
|
|
else if (x450_bodyController->IsFrozen())
|
|
x450_bodyController->FrozenBreakout();
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPatterned::TakeDamage(const zeus::CVector3f&, float arg)
|
|
{
|
|
x428_damageCooldownTimer = 0.33f;
|
|
}
|
|
|
|
bool CPatterned::FixedRandom(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.GetRandom() < x330_stateMachineState.x14_;
|
|
}
|
|
|
|
bool CPatterned::Random(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.GetRandom() < arg;
|
|
}
|
|
|
|
bool CPatterned::CodeTrigger(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.x18_24_codeTrigger;
|
|
}
|
|
|
|
bool CPatterned::FixedDelay(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.GetTime() > x330_stateMachineState.GetDelay();
|
|
}
|
|
|
|
bool CPatterned::RandomDelay(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.GetTime() > arg * x330_stateMachineState.GetRandom();
|
|
}
|
|
|
|
bool CPatterned::Delay(CStateManager&, float arg)
|
|
{
|
|
return x330_stateMachineState.GetTime() > arg;
|
|
}
|
|
|
|
bool CPatterned::PatrolPathOver(CStateManager&, float arg)
|
|
{
|
|
return x2dc_destObj == kInvalidUniqueId;
|
|
}
|
|
|
|
bool CPatterned::Stuck(CStateManager&, float arg)
|
|
{
|
|
return x4f0_predictedLeashTime > 0.2f;
|
|
}
|
|
|
|
bool CPatterned::AnimOver(CStateManager&, float arg)
|
|
{
|
|
return x32c_animState == EAnimState::Over;
|
|
}
|
|
|
|
bool CPatterned::InPosition(CStateManager&, float arg)
|
|
{
|
|
return x328_24_inPosition;
|
|
}
|
|
|
|
bool CPatterned::HasPatrolPath(CStateManager& mgr, float arg)
|
|
{
|
|
return GetWaypointForState(mgr, EScriptObjectState::Patrol,
|
|
EScriptObjectMessage::Follow) != kInvalidUniqueId;
|
|
}
|
|
|
|
bool CPatterned::Attacked(CStateManager&, float arg)
|
|
{
|
|
return x400_24_hitByPlayerProjectile;
|
|
}
|
|
|
|
bool CPatterned::PatternShagged(CStateManager&, float arg)
|
|
{
|
|
return x400_30_patternShagged;
|
|
}
|
|
|
|
bool CPatterned::PatternOver(CStateManager&, float arg)
|
|
{
|
|
return x38c_patterns.size() <= x39c_curPattern;
|
|
}
|
|
|
|
bool CPatterned::HasRetreatPattern(CStateManager& mgr, float arg)
|
|
{
|
|
return GetWaypointForState(mgr, EScriptObjectState::Retreat,
|
|
EScriptObjectMessage::Follow) != kInvalidUniqueId;
|
|
}
|
|
|
|
bool CPatterned::HasAttackPattern(CStateManager& mgr, float arg)
|
|
{
|
|
return GetWaypointForState(mgr, EScriptObjectState::Attack,
|
|
EScriptObjectMessage::Follow) != kInvalidUniqueId;
|
|
}
|
|
|
|
bool CPatterned::NoPathNodes(CStateManager&, float arg)
|
|
{
|
|
if (CPathFindSearch* search = GetSearchPath())
|
|
return search->OnPath(GetTranslation()) != CPathFindSearch::EResult::Success;
|
|
return true;
|
|
}
|
|
|
|
static const float skActorApproachDistance = 3.f;
|
|
|
|
bool CPatterned::PathShagged(CStateManager&, float arg)
|
|
{
|
|
if (CPathFindSearch* search = GetSearchPath())
|
|
{
|
|
if (search->IsShagged())
|
|
return true;
|
|
if (search->GetCurrentWaypoint() > 0 && x401_24_pathOverCount == 0)
|
|
{
|
|
zeus::CVector3f origPoint = GetTranslation() + 0.3f * zeus::CVector3f::skUp;
|
|
zeus::CVector3f point = origPoint;
|
|
search->GetSplinePoint(point, GetTranslation());
|
|
return (point - origPoint).magSquared() > 4.f *
|
|
skActorApproachDistance * skActorApproachDistance;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::PathFound(CStateManager&, float arg)
|
|
{
|
|
if (CPathFindSearch* search = GetSearchPath())
|
|
if (!search->IsShagged())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::PathOver(CStateManager&, float arg)
|
|
{
|
|
if (CPathFindSearch* search = GetSearchPath())
|
|
if (x328_25_verticalMovement || x328_27_onGround)
|
|
if (!search->IsShagged() && search->IsOver())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::Landed(CStateManager&, float arg)
|
|
{
|
|
bool ret = x328_27_onGround && !x328_28_prevOnGround;
|
|
x328_28_prevOnGround = x328_27_onGround;
|
|
return ret;
|
|
}
|
|
|
|
bool CPatterned::PlayerSpot(CStateManager& mgr, float arg)
|
|
{
|
|
if (mgr.GetPlayer().GetMorphballTransitionState() ==
|
|
CPlayer::EPlayerMorphBallState::Unmorphed)
|
|
{
|
|
zeus::CVector3f aimPosition = mgr.GetPlayer().GetAimPosition(mgr, 0.f);
|
|
zeus::CVector3f center = GetBoundingBox().center();
|
|
zeus::CVector3f aimToCenter = center - aimPosition;
|
|
float aimToCenterMag = aimToCenter.magnitude();
|
|
zeus::CVector3f aimToCenterNorm = aimToCenter * (1.f / aimToCenterMag);
|
|
zeus::CVector3f screenSpace = mgr.GetCameraManager()->
|
|
GetFirstPersonCamera()->ConvertToScreenSpace(center);
|
|
if (screenSpace.z() > 0.f &&
|
|
screenSpace.x() * screenSpace.x() < 1.f &&
|
|
screenSpace.y() * screenSpace.y() < 1.f)
|
|
{
|
|
CRayCastResult res =
|
|
mgr.RayStaticIntersection(aimPosition, aimToCenterNorm, aimToCenterMag,
|
|
CMaterialFilter::MakeInclude(EMaterialTypes::Solid));
|
|
return res.IsInvalid();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::SpotPlayer(CStateManager& mgr, float arg)
|
|
{
|
|
zeus::CVector3f gunToPlayer = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetGunEyePos();
|
|
float lookDot = gunToPlayer.dot(x34_transform.basis[1]);
|
|
if (lookDot > 0.f)
|
|
return lookDot * lookDot > gunToPlayer.magSquared() * x3c4_detectionAngle;
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::Leash(CStateManager&, float arg)
|
|
{
|
|
bool ret = x3d4_curPlayerLeashTime > x3d0_playerLeashTime;
|
|
if (ret)
|
|
{
|
|
float posToLeashMagSq = (x3a0_latestLeashPosition - GetTranslation()).magSquared();
|
|
if (posToLeashMagSq > x3c8_leashRadius * x3c8_leashRadius)
|
|
return true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool CPatterned::InDetectionRange(CStateManager& mgr, float arg)
|
|
{
|
|
zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - GetTranslation();
|
|
if (delta.magSquared() < x3bc_detectionRange * x3bc_detectionRange)
|
|
{
|
|
if (x3c0_detectionHeightRange > 0.f)
|
|
return delta.z() * delta.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CPatterned::InMaxRange(CStateManager& mgr, float arg)
|
|
{
|
|
return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() <
|
|
x300_maxAttackRange * x300_maxAttackRange;
|
|
}
|
|
|
|
bool CPatterned::TooClose(CStateManager& mgr, float arg)
|
|
{
|
|
return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() <
|
|
x2fc_minAttackRange * x2fc_minAttackRange;
|
|
}
|
|
|
|
bool CPatterned::InRange(CStateManager& mgr, float arg)
|
|
{
|
|
float range = 0.5f * (x2fc_minAttackRange + x300_maxAttackRange);
|
|
return (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() < range * range;
|
|
}
|
|
|
|
bool CPatterned::OffLine(CStateManager&, float arg)
|
|
{
|
|
zeus::CVector3f curLine = GetTranslation() - x2ec_reflectedDestPos;
|
|
zeus::CVector3f pathLine = x2e0_destPos - x2ec_reflectedDestPos;
|
|
float distSq;
|
|
if (curLine.dot(pathLine) <= 0.f)
|
|
{
|
|
distSq = curLine.magSquared();
|
|
}
|
|
else
|
|
{
|
|
pathLine.normalize();
|
|
distSq = (curLine - pathLine.dot(curLine) * pathLine).magSquared();
|
|
zeus::CVector3f delta = GetTranslation() - x2e0_destPos;
|
|
if (pathLine.dot(delta) > 0.f)
|
|
distSq = delta.magSquared();
|
|
}
|
|
return distSq > arg * arg;
|
|
}
|
|
|
|
void CPatterned::PathFind(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
if (CPathFindSearch* search = GetSearchPath())
|
|
{
|
|
u32 curWp = search->GetCurrentWaypoint();
|
|
const auto& waypoints = search->GetWaypoints();
|
|
switch (msg)
|
|
{
|
|
case EStateMsg::Activate:
|
|
{
|
|
if (search->Search(GetTranslation(), x2e0_destPos) == CPathFindSearch::EResult::Success)
|
|
{
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
zeus::CVector3f destPos;
|
|
if (curWp + 1 < waypoints.size())
|
|
destPos = waypoints[curWp + 1];
|
|
else
|
|
destPos = waypoints[curWp];
|
|
SetDestPos(destPos);
|
|
x328_24_inPosition = false;
|
|
ApproachDest(mgr);
|
|
}
|
|
break;
|
|
}
|
|
case EStateMsg::Update:
|
|
{
|
|
if (curWp < waypoints.size() - 1)
|
|
{
|
|
if (x328_24_inPosition || x328_27_onGround)
|
|
x401_24_pathOverCount += 1;
|
|
zeus::CVector3f biasedPos = GetTranslation() + 0.3f * zeus::CVector3f::skUp;
|
|
x2ec_reflectedDestPos = biasedPos - (x2e0_destPos - biasedPos);
|
|
ApproachDest(mgr);
|
|
zeus::CVector3f biasedForward = x34_transform.basis[1] * x64_modelData->GetScale().y() + biasedPos;
|
|
search->GetSplinePointWithLookahead(biasedForward, biasedPos, 3.f * x64_modelData->GetScale().y());
|
|
SetDestPos(biasedForward);
|
|
if (search->SegmentOver(biasedPos))
|
|
search->SetCurrentWaypoint(search->GetCurrentWaypoint() + 1);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPatterned::Dead(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case EStateMsg::Activate:
|
|
x31c_faceVec = zeus::CVector3f::skZero;
|
|
break;
|
|
case EStateMsg::Update:
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::Die));
|
|
if (!x400_27_fadeToDeath)
|
|
{
|
|
if (x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsDead())
|
|
{
|
|
x400_27_fadeToDeath = true;
|
|
x3e8_alphaDelta = -0.333333f;
|
|
RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid,
|
|
EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);
|
|
AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::TargetPlayer(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
if (msg == EStateMsg::Activate)
|
|
{
|
|
x2dc_destObj = mgr.GetPlayer().GetUniqueId();
|
|
SetDestPos(mgr.GetPlayer().GetTranslation());
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
x328_24_inPosition = false;
|
|
}
|
|
}
|
|
|
|
void CPatterned::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
if (msg == EStateMsg::Activate)
|
|
{
|
|
x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);
|
|
if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x2dc_destObj))
|
|
SetDestPos(act->GetTranslation());
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
x328_24_inPosition = false;
|
|
}
|
|
}
|
|
|
|
void CPatterned::FollowPattern(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case EStateMsg::Activate:
|
|
SetupPattern(mgr);
|
|
if (x328_29_noPatternShagging || !IsPatternObstructed(mgr, GetTranslation(), x2e0_destPos))
|
|
{
|
|
ApproachDest(mgr);
|
|
}
|
|
else
|
|
{
|
|
x39c_curPattern = x38c_patterns.size();
|
|
x400_30_patternShagged = true;
|
|
}
|
|
break;
|
|
case EStateMsg::Update:
|
|
if (x328_24_inPosition)
|
|
{
|
|
x39c_curPattern += 1;
|
|
UpdatePatternDestPos(mgr);
|
|
if (!x328_29_noPatternShagging && IsPatternObstructed(mgr, GetTranslation(), x2e0_destPos))
|
|
{
|
|
x39c_curPattern = x38c_patterns.size();
|
|
x400_30_patternShagged = true;
|
|
}
|
|
else if (x39c_curPattern < x38c_patterns.size())
|
|
{
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
x328_24_inPosition = false;
|
|
x3b0_moveSpeed = x38c_patterns[x39c_curPattern].GetSpeed();
|
|
x380_behaviour = EBehaviour(x38c_patterns[x39c_curPattern].GetBehaviour());
|
|
x30c_behaviourOrient = EBehaviourOrient(x38c_patterns[x39c_curPattern].GetBehaviourOrient());
|
|
x384_behaviourModifiers = EBehaviourModifiers(x38c_patterns[x39c_curPattern].GetBehaviourModifiers());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdatePatternDestPos(mgr);
|
|
}
|
|
ApproachDest(mgr);
|
|
break;
|
|
case EStateMsg::Deactivate:
|
|
x38c_patterns.clear();
|
|
x400_30_patternShagged = false;
|
|
}
|
|
}
|
|
|
|
void CPatterned::Patrol(CStateManager& mgr, EStateMsg msg, float dt)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case EStateMsg::Activate:
|
|
if (x3ac_lastPatrolDest == kInvalidUniqueId)
|
|
{
|
|
x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Patrol, EScriptObjectMessage::Follow);
|
|
x30c_behaviourOrient = EBehaviourOrient::MoveDir;
|
|
x3b0_moveSpeed = 1.f;
|
|
if (x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj))
|
|
{
|
|
x30c_behaviourOrient = EBehaviourOrient(wp->GetBehaviourOrient());
|
|
x3b0_moveSpeed = wp->GetSpeed();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
x2dc_destObj = x3ac_lastPatrolDest;
|
|
}
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
x328_24_inPosition = false;
|
|
x2d8_patrolState = EPatrolState::Patrol;
|
|
x2f8_waypointPauseRemTime = 0.f;
|
|
break;
|
|
case EStateMsg::Update:
|
|
switch (x2d8_patrolState)
|
|
{
|
|
case EPatrolState::Patrol:
|
|
if (x328_24_inPosition && x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj))
|
|
{
|
|
if (wp->GetPause() > 0.f)
|
|
{
|
|
x2f8_waypointPauseRemTime = wp->GetPause();
|
|
x2d8_patrolState = EPatrolState::Pause;
|
|
}
|
|
}
|
|
}
|
|
if (x2dc_destObj == kInvalidUniqueId)
|
|
x2d8_patrolState = EPatrolState::Done;
|
|
UpdateDest(mgr);
|
|
ApproachDest(mgr);
|
|
break;
|
|
case EPatrolState::Pause:
|
|
if (x2f8_waypointPauseRemTime <= 0.f)
|
|
x2d8_patrolState = EPatrolState::Patrol;
|
|
break;
|
|
case EPatrolState::Done:
|
|
if (x2dc_destObj != kInvalidUniqueId)
|
|
x2d8_patrolState = EPatrolState::Patrol;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case EStateMsg::Deactivate:
|
|
x3ac_lastPatrolDest = x2dc_destObj;
|
|
x2d8_patrolState = EPatrolState::Invalid;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::TryCommand(CStateManager& mgr, pas::EAnimationState state,
|
|
CPatternedTryFunc func, int arg)
|
|
{
|
|
if (state == x450_bodyController->GetCurrentStateId())
|
|
x32c_animState = EAnimState::Repeat;
|
|
else if (x32c_animState == EAnimState::One)
|
|
(this->*func)(mgr, arg);
|
|
else
|
|
x32c_animState = EAnimState::Over;
|
|
}
|
|
|
|
void CPatterned::TryLoopReaction(CStateManager& mgr, int arg)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCLoopReactionCmd(pas::EReactionType(arg)));
|
|
}
|
|
|
|
void CPatterned::TryProjectileAttack(CStateManager&, int arg)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity(arg), x2e0_destPos, false));
|
|
}
|
|
|
|
void CPatterned::TryGenerate(CStateManager& mgr, int arg)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType(arg), x2e0_destPos, true));
|
|
}
|
|
|
|
void CPatterned::BuildBodyController(EBodyType bodyType)
|
|
{
|
|
if (x450_bodyController)
|
|
return;
|
|
|
|
x450_bodyController.reset(new CBodyController(*this, x3b8_turnSpeed, bodyType));
|
|
auto anim = x450_bodyController->GetPASDatabase().FindBestAnimation(CPASAnimParmData(24,
|
|
CPASAnimParm::FromEnum(0)), -1);
|
|
x460_knockBackController.x81_26_enableShock = anim.first > 0.f;
|
|
}
|
|
|
|
void CPatterned::GenerateDeathExplosion(CStateManager& mgr)
|
|
{
|
|
if (auto particle = GetDeathExplosionParticle())
|
|
{
|
|
zeus::CTransform xf(GetTransform());
|
|
xf.origin = GetTransform() * (x64_modelData->GetScale() * x514_deathExplosionOffset);
|
|
CExplosion* explo = new CExplosion(*particle, mgr.AllocateUniqueId(), true,
|
|
CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), "", xf, 1,
|
|
zeus::CVector3f::skOne, zeus::CColor::skWhite);
|
|
mgr.AddObject(explo);
|
|
}
|
|
else if (x530_deathExplosionElectric)
|
|
{
|
|
zeus::CTransform xf(GetTransform());
|
|
xf.origin = GetTransform() * (x64_modelData->GetScale() * x514_deathExplosionOffset);
|
|
CExplosion* explo = new CExplosion(*x530_deathExplosionElectric, mgr.AllocateUniqueId(), true,
|
|
CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), "", xf, 1,
|
|
zeus::CVector3f::skOne, zeus::CColor::skWhite);
|
|
mgr.AddObject(explo);
|
|
}
|
|
}
|
|
|
|
void CPatterned::MassiveDeath(CStateManager& mgr)
|
|
{
|
|
CSfxManager::AddEmitter(x454_deathSfx, GetTranslation(), zeus::CVector3f::skZero,
|
|
true, false, 0x7f, kInvalidAreaId);
|
|
if (!x401_28_burning)
|
|
{
|
|
SendScriptMsgs(EScriptObjectState::MassiveDeath, mgr, EScriptObjectMessage::None);
|
|
GenerateDeathExplosion(mgr);
|
|
}
|
|
DeathDelete(mgr);
|
|
x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;
|
|
}
|
|
|
|
void CPatterned::GenerateIceDeathExplosion(CStateManager& mgr)
|
|
{
|
|
if (x54c_iceDeathExplosionParticle)
|
|
{
|
|
zeus::CTransform xf(GetTransform());
|
|
xf.origin = GetTransform() * (x64_modelData->GetScale() * x540_iceDeathExplosionOffset);
|
|
CExplosion* explo = new CExplosion(*x54c_iceDeathExplosionParticle, mgr.AllocateUniqueId(), true,
|
|
CEntityInfo(GetAreaIdAlways(), CEntity::NullConnectionList), "", xf, 1,
|
|
zeus::CVector3f::skOne, zeus::CColor::skWhite);
|
|
mgr.AddObject(explo);
|
|
}
|
|
}
|
|
|
|
void CPatterned::MassiveFrozenDeath(CStateManager& mgr)
|
|
{
|
|
if (x458_iceShatterSfx == 0xffff)
|
|
x458_iceShatterSfx = x454_deathSfx;
|
|
CSfxManager::AddEmitter(x458_iceShatterSfx, GetTranslation(), zeus::CVector3f::skZero,
|
|
true, false, 0x7f, kInvalidAreaId);
|
|
SendScriptMsgs(EScriptObjectState::MassiveFrozenDeath, mgr, EScriptObjectMessage::None);
|
|
GenerateIceDeathExplosion(mgr);
|
|
float toPlayerDist = (mgr.GetPlayer().GetTranslation() - GetTranslation()).magnitude();
|
|
if (toPlayerDist < 40.f)
|
|
mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildPatternedExplodeShakeData(
|
|
GetTranslation(), 0.25f, 0.3f, 40.f), true);
|
|
DeathDelete(mgr);
|
|
x400_28_pendingMassiveDeath = x400_29_pendingMassiveFrozenDeath = false;
|
|
}
|
|
|
|
void CPatterned::Burn(float duration, float damage)
|
|
{
|
|
switch (GetDamageVulnerability()->GetVulnerability(CWeaponMode(EWeaponType::Plasma), false))
|
|
{
|
|
case EVulnerability::Weak:
|
|
x450_bodyController->SetOnFire(1.5f * duration);
|
|
x3ec_pendingFireDamage = 1.5f * damage;
|
|
break;
|
|
case EVulnerability::Normal:
|
|
x450_bodyController->SetOnFire(duration);
|
|
x3ec_pendingFireDamage = damage;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::Shock(float duration, float damage)
|
|
{
|
|
switch (GetDamageVulnerability()->GetVulnerability(CWeaponMode(EWeaponType::Wave), false))
|
|
{
|
|
case EVulnerability::Weak:
|
|
x450_bodyController->SetElectrocuting(1.5f * duration);
|
|
x3f0_pendingShockDamage = 1.5f * damage;
|
|
break;
|
|
case EVulnerability::Normal:
|
|
x450_bodyController->SetElectrocuting(duration);
|
|
x3f0_pendingShockDamage = damage;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::Freeze(CStateManager& mgr, const zeus::CVector3f& pos,
|
|
const zeus::CUnitVector3f& dir, float frozenDur)
|
|
{
|
|
if (x402_25_lostMassiveFrozenHP)
|
|
x402_26_dieIf80PercFrozen = true;
|
|
bool playSfx = false;
|
|
if (x450_bodyController->IsFrozen())
|
|
{
|
|
x450_bodyController->Freeze(x460_knockBackController.GetActiveParms().xc_intoFreezeDur,
|
|
frozenDur, x4f8_outofFreezeDur);
|
|
mgr.GetActorModelParticles()->EnsureElectricLoaded(*this);
|
|
playSfx = true;
|
|
}
|
|
else if (!x450_bodyController->IsElectrocuting() &&
|
|
!x450_bodyController->IsOnFire())
|
|
{
|
|
x450_bodyController->Freeze(x4f4_intoFreezeDur, frozenDur, x4f8_outofFreezeDur);
|
|
if (x510_vertexMorph)
|
|
x510_vertexMorph->Reset(dir, pos, x4f4_intoFreezeDur);
|
|
playSfx = true;
|
|
}
|
|
|
|
if (playSfx)
|
|
{
|
|
u16 sfx;
|
|
if (x460_knockBackController.GetVariant() != EKnockBackVariant::Small &&
|
|
CPatterned::CastTo<MP1::CMetroid>(mgr.GetObjectById(GetUniqueId())))
|
|
sfx = SFXsfx0701;
|
|
else
|
|
sfx = SFXsfx0708;
|
|
CSfxManager::AddEmitter(sfx, GetTranslation(), zeus::CVector3f::skZero,
|
|
true, false, 0x7f, kInvalidAreaId);
|
|
}
|
|
}
|
|
|
|
zeus::CVector3f CPatterned::GetGunEyePos() const
|
|
{
|
|
zeus::CVector3f origin = GetTranslation();
|
|
zeus::CAABox baseBox = GetBaseBoundingBox();
|
|
origin.z() = 0.6f * (baseBox.max.z() - baseBox.min.z()) + origin.z();
|
|
return origin;
|
|
}
|
|
|
|
void CPatterned::SetupPlayerCollision(bool v)
|
|
{
|
|
CMaterialList include = GetMaterialFilter().GetIncludeList();
|
|
CMaterialList exclude = GetMaterialFilter().GetExcludeList();
|
|
CMaterialList* modList = (v ? &exclude : &include);
|
|
modList->Add(EMaterialTypes::Player);
|
|
SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(include, exclude));
|
|
}
|
|
|
|
void CPatterned::LaunchProjectile(
|
|
const zeus::CTransform& gunXf, CStateManager& mgr, int maxAllowed, EProjectileAttrib attrib,
|
|
bool playerHoming, const std::experimental::optional<TLockedToken<CGenDescription>>& visorParticle,
|
|
u16 visorSfx, bool sendCollideMsg, const zeus::CVector3f& scale)
|
|
{
|
|
CProjectileInfo* pInfo = GetProjectileInfo();
|
|
if (pInfo->Token().IsLoaded())
|
|
{
|
|
if (mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, maxAllowed))
|
|
{
|
|
TUniqueId homingId = playerHoming ? mgr.GetPlayer().GetUniqueId() : kInvalidUniqueId;
|
|
CEnergyProjectile* newProjectile = new CEnergyProjectile(true, pInfo->Token(), EWeaponType::AI,
|
|
gunXf, EMaterialTypes::Character, pInfo->GetDamage(), mgr.AllocateUniqueId(), GetAreaIdAlways(),
|
|
GetUniqueId(), homingId, attrib, false, scale, visorParticle, visorSfx, sendCollideMsg);
|
|
mgr.AddObject(newProjectile);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPatterned::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt)
|
|
{
|
|
switch (type)
|
|
{
|
|
case EUserEventType::Projectile:
|
|
{
|
|
zeus::CTransform lctrXf = GetLctrTransform(node.GetLocatorName());
|
|
zeus::CVector3f aimPos = mgr.GetPlayer().GetAimPosition(mgr, 0.f);
|
|
if ((aimPos - lctrXf.origin).normalized().dot(lctrXf.basis[1]) > 0.f)
|
|
{
|
|
zeus::CTransform gunXf = zeus::lookAt(lctrXf.origin, aimPos);
|
|
LaunchProjectile(gunXf, mgr, 1, EProjectileAttrib::None, false, {}, 0xffff, false, zeus::CVector3f::skOne);
|
|
}
|
|
else
|
|
{
|
|
LaunchProjectile(lctrXf, mgr, 1, EProjectileAttrib::None, false, {}, 0xffff, false, zeus::CVector3f::skOne);
|
|
}
|
|
break;
|
|
}
|
|
case EUserEventType::DamageOn:
|
|
{
|
|
zeus::CTransform lctrXf = GetLctrTransform(node.GetLocatorName());
|
|
zeus::CVector3f xfOrigin = x34_transform * (x64_modelData->GetScale() * lctrXf.origin);
|
|
zeus::CVector3f margin = zeus::CVector3f(1.f, 1.f, 0.5f) * x64_modelData->GetScale();
|
|
if (zeus::CAABox(xfOrigin - margin, xfOrigin + margin).intersects(mgr.GetPlayer().GetBoundingBox()))
|
|
{
|
|
mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(),
|
|
GetContactDamage(), CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}),
|
|
zeus::CVector3f::skZero);
|
|
}
|
|
break;
|
|
}
|
|
case EUserEventType::Delete:
|
|
{
|
|
if (!x400_25_alive)
|
|
{
|
|
if (!x400_27_fadeToDeath)
|
|
{
|
|
x3e8_alphaDelta = -0.333333f;
|
|
x400_27_fadeToDeath = true;
|
|
}
|
|
RemoveMaterial(EMaterialTypes::Character, EMaterialTypes::Solid,
|
|
EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);
|
|
AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);
|
|
}
|
|
else
|
|
{
|
|
DeathDelete(mgr);
|
|
}
|
|
break;
|
|
}
|
|
case EUserEventType::BreakLockOn:
|
|
{
|
|
RemoveMaterial(EMaterialTypes::Character,
|
|
EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);
|
|
break;
|
|
}
|
|
case EUserEventType::BecomeShootThrough:
|
|
{
|
|
AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr);
|
|
break;
|
|
}
|
|
case EUserEventType::RemoveCollision:
|
|
{
|
|
RemoveMaterial(EMaterialTypes::Solid, mgr);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
CActor::DoUserAnimEvent(mgr, node, type, dt);
|
|
}
|
|
|
|
void CPatterned::UpdateAlphaDelta(float dt, CStateManager& mgr)
|
|
{
|
|
if (x3e8_alphaDelta == 0.f)
|
|
return;
|
|
|
|
float alpha = dt * x3e8_alphaDelta + x42c_color.a();
|
|
if (alpha > 1.f)
|
|
{
|
|
alpha = 1.f;
|
|
x3e8_alphaDelta = 0.f;
|
|
}
|
|
else if (alpha < 0.f)
|
|
{
|
|
alpha = 0.f;
|
|
x3e8_alphaDelta = 0.f;
|
|
if (x400_27_fadeToDeath)
|
|
DeathDelete(mgr);
|
|
}
|
|
x94_simpleShadow->SetUserAlpha(alpha);
|
|
SetModelAlpha(alpha);
|
|
x64_modelData->AnimationData()->GetParticleDB().
|
|
SetModulationColorAllActiveEffects(zeus::CColor(1.f, alpha));
|
|
}
|
|
|
|
float CPatterned::CalcDyingThinkRate()
|
|
{
|
|
float f0 = (x401_28_burning ? (x3f4_burnThinkRateTimer / 1.5f) : 1.f);
|
|
return zeus::max(0.1f, f0);
|
|
}
|
|
|
|
void CPatterned::UpdateDamageColor(float dt)
|
|
{
|
|
if (x428_damageCooldownTimer > 0.f)
|
|
{
|
|
x428_damageCooldownTimer = std::max(0.f, x428_damageCooldownTimer - dt);
|
|
float alpha = x42c_color.a();
|
|
x42c_color = zeus::CColor::lerp(zeus::CColor::skBlack, x430_damageColor,
|
|
std::min(x428_damageCooldownTimer / 0.33f, 1.f));
|
|
x42c_color.a() = alpha;
|
|
if (!x450_bodyController->IsFrozen())
|
|
xd0_damageMag = x50c_baseDamageMag + x428_damageCooldownTimer;
|
|
}
|
|
}
|
|
|
|
TUniqueId CPatterned::GetWaypointForState(CStateManager& mgr, EScriptObjectState state,
|
|
EScriptObjectMessage msg) const
|
|
{
|
|
rstl::reserved_vector<TUniqueId, 12> ids;
|
|
for (const auto& conn : GetConnectionList())
|
|
{
|
|
if (conn.x0_state == state && conn.x4_msg == msg)
|
|
{
|
|
TUniqueId id = mgr.GetIdForScript(conn.x8_objId);
|
|
if (const CEntity* ent = mgr.GetObjectById(id))
|
|
if (ent->GetActive())
|
|
ids.push_back(id);
|
|
}
|
|
}
|
|
|
|
if (!ids.empty())
|
|
return ids[mgr.GetActiveRandom()->Next() % ids.size()];
|
|
|
|
return kInvalidUniqueId;
|
|
}
|
|
|
|
void CPatterned::UpdateActorKeyframe(CStateManager& mgr) const
|
|
{
|
|
if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj))
|
|
for (const auto& conn : wp->GetConnectionList())
|
|
if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Action)
|
|
if (TCastToPtr<CScriptActorKeyframe> kf = mgr.ObjectById(mgr.GetIdForScript(conn.x8_objId)))
|
|
if (kf->GetActive() && kf->IsPassive())
|
|
kf->UpdateEntity(GetUniqueId(), mgr);
|
|
}
|
|
|
|
pas::EStepDirection CPatterned::GetStepDirection(const zeus::CVector3f& moveVec) const
|
|
{
|
|
zeus::CVector3f localMove = x34_transform.transposeRotate(moveVec);
|
|
float angle = zeus::CVector3f::getAngleDiff(localMove, zeus::CVector3f::skForward);
|
|
if (angle < zeus::degToRad(45.f))
|
|
return pas::EStepDirection::Forward;
|
|
else if (angle > zeus::degToRad(135.f))
|
|
return pas::EStepDirection::Backward;
|
|
else if (localMove.dot(zeus::CVector3f::skRight) > 0.f)
|
|
return pas::EStepDirection::Right;
|
|
else
|
|
return pas::EStepDirection::Left;
|
|
}
|
|
|
|
bool CPatterned::IsPatternObstructed(CStateManager& mgr,
|
|
const zeus::CVector3f& p0, const zeus::CVector3f& p1) const
|
|
{
|
|
CMaterialFilter filter = CMaterialFilter::MakeInclude(EMaterialTypes::Character);
|
|
zeus::CVector3f delta = p1 - p0;
|
|
rstl::reserved_vector<TUniqueId, 1024> nearList;
|
|
bool ret = false;
|
|
if (delta.canBeNormalized())
|
|
{
|
|
zeus::CVector3f deltaNorm = delta.normalized();
|
|
float deltaMag = delta.magnitude();
|
|
mgr.BuildNearList(nearList, p0, deltaNorm, deltaMag, filter, this);
|
|
TUniqueId bestId = kInvalidUniqueId;
|
|
CRayCastResult res =
|
|
mgr.RayWorldIntersection(bestId, p0, deltaNorm, deltaMag,
|
|
CMaterialFilter::MakeInclude(EMaterialTypes::Solid), nearList);
|
|
ret = res.IsValid();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CPatterned::UpdateDest(CStateManager& mgr)
|
|
{
|
|
if (x328_24_inPosition && x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj))
|
|
{
|
|
UpdateActorKeyframe(mgr);
|
|
x2dc_destObj = wp->NextWaypoint(mgr);
|
|
if (x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
x328_24_inPosition = false;
|
|
if (TCastToConstPtr<CScriptWaypoint> wp2 = mgr.GetObjectById(x2dc_destObj))
|
|
{
|
|
x3b0_moveSpeed = wp->GetSpeed();
|
|
x30c_behaviourOrient = EBehaviourOrient(wp->GetBehaviourOrient());
|
|
if (wp->GetBehaviourModifiers() & 0x2)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCJumpCmd(wp2->GetTranslation(), pas::EJumpType::Normal));
|
|
}
|
|
else if (wp->GetBehaviourModifiers() & 0x4)
|
|
{
|
|
TUniqueId wp3Id = wp2->NextWaypoint(mgr);
|
|
if (wp3Id != kInvalidUniqueId)
|
|
{
|
|
if (TCastToConstPtr<CScriptWaypoint> wp3 = mgr.GetObjectById(wp3Id))
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCJumpCmd(wp2->GetTranslation(), wp3->GetTranslation(),
|
|
pas::EJumpType::Normal));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mgr.SendScriptMsg(wp.GetPtr(), GetUniqueId(), EScriptObjectMessage::Arrived);
|
|
}
|
|
}
|
|
|
|
if (x2dc_destObj != kInvalidUniqueId)
|
|
if (TCastToConstPtr<CActor> act = mgr.GetObjectById(x2dc_destObj))
|
|
SetDestPos(act->GetTranslation());
|
|
}
|
|
|
|
void CPatterned::ApproachDest(CStateManager& mgr)
|
|
{
|
|
zeus::CVector3f faceVec = mgr.GetPlayer().GetTranslation() - GetTranslation();
|
|
zeus::CVector3f moveVec = x2e0_destPos - GetTranslation();
|
|
if (!x328_25_verticalMovement)
|
|
moveVec.z() = 0.f;
|
|
zeus::CVector3f pathLine = x2e0_destPos - x2ec_reflectedDestPos;
|
|
if (pathLine.dot(moveVec) <= 0.f)
|
|
x328_24_inPosition = true;
|
|
else if (moveVec.magSquared() < 3.f * 3.f)
|
|
moveVec = pathLine;
|
|
if (!x328_24_inPosition)
|
|
{
|
|
if (moveVec.canBeNormalized())
|
|
moveVec.normalize();
|
|
switch (x30c_behaviourOrient)
|
|
{
|
|
case EBehaviourOrient::MoveDir:
|
|
faceVec = moveVec;
|
|
break;
|
|
case EBehaviourOrient::Destination:
|
|
if (x39c_curPattern && x39c_curPattern < x38c_patterns.size())
|
|
{
|
|
faceVec = x38c_patterns[x39c_curPattern].GetForward();
|
|
}
|
|
else if (x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(x2dc_destObj))
|
|
faceVec = wp->GetTransform().basis[1];
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
x31c_faceVec = faceVec;
|
|
x310_moveVec = x3b0_moveSpeed * moveVec;
|
|
pas::EStepDirection stepDir;
|
|
if (!KnockbackWhenFrozen())
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCLocomotionCmd(x310_moveVec, x31c_faceVec, 1.f));
|
|
}
|
|
else if (x30c_behaviourOrient == EBehaviourOrient::MoveDir ||
|
|
!x450_bodyController->HasBodyState(pas::EAnimationState::Step))
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCLocomotionCmd(x310_moveVec, zeus::CVector3f::skZero, 1.f));
|
|
}
|
|
else if ((stepDir = GetStepDirection(x310_moveVec)) != pas::EStepDirection::Forward)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCStepCmd(stepDir, pas::EStepType::Normal));
|
|
}
|
|
else
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(
|
|
CBCLocomotionCmd(x310_moveVec, zeus::CVector3f::skZero, 1.f));
|
|
}
|
|
x450_bodyController->GetCommandMgr().DeliverTargetVector(x31c_faceVec);
|
|
}
|
|
else if (x450_bodyController->GetBodyStateInfo().GetMaxSpeed() > FLT_EPSILON)
|
|
{
|
|
x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(
|
|
(x138_velocity.magnitude() / x450_bodyController->GetBodyStateInfo().GetMaxSpeed()) *
|
|
x34_transform.basis[1], zeus::CVector3f::skZero, 1.f));
|
|
}
|
|
}
|
|
|
|
std::pair<CScriptWaypoint*, CScriptWaypoint*> CPatterned::GetDestWaypoints(CStateManager& mgr) const
|
|
{
|
|
std::pair<CScriptWaypoint*, CScriptWaypoint*> ret {};
|
|
if (TCastToPtr<CScriptWaypoint> wp = mgr.ObjectById(x2dc_destObj))
|
|
{
|
|
ret.first = wp.GetPtr();
|
|
ret.second = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(wp->FollowWaypoint(mgr))).GetPtr();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
zeus::CQuaternion CPatterned::FindPatternRotation(const zeus::CVector3f& dir) const
|
|
{
|
|
zeus::CVector3f wpDeltaFlat = x368_destWPDelta;
|
|
wpDeltaFlat.z() = 0.f;
|
|
wpDeltaFlat.normalize();
|
|
zeus::CVector3f dirFlat = dir;
|
|
dirFlat.z() = 0.f;
|
|
dirFlat.normalize();
|
|
|
|
zeus::CQuaternion q;
|
|
if ((wpDeltaFlat - dirFlat).magSquared() > 3.99f)
|
|
q.rotateZ(zeus::degToRad(180.f));
|
|
else
|
|
q = zeus::CQuaternion::shortestRotationArc(wpDeltaFlat, dirFlat);
|
|
|
|
if (x328_25_verticalMovement)
|
|
{
|
|
q = zeus::CQuaternion::shortestRotationArc(
|
|
(q * zeus::CQuaternion(0.f, x368_destWPDelta) * q.inverse()).getImaginary().normalized(),
|
|
dir.normalized()) * q;
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
zeus::CVector3f CPatterned::FindPatternDir(CStateManager& mgr) const
|
|
{
|
|
zeus::CVector3f ret;
|
|
switch (x378_patternOrient)
|
|
{
|
|
case EPatternOrient::StartToPlayerStart:
|
|
ret = x35c_patternStartPlayerPos - x350_patternStartPos;
|
|
break;
|
|
case EPatternOrient::StartToPlayer:
|
|
ret = mgr.GetPlayer().GetTranslation() - x350_patternStartPos;
|
|
break;
|
|
case EPatternOrient::ReversePlayerForward:
|
|
ret = -mgr.GetPlayer().GetTransform().basis[1];
|
|
break;
|
|
case EPatternOrient::Forward:
|
|
ret = GetTransform().basis[1];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CPatterned::UpdatePatternDestPos(CStateManager& mgr)
|
|
{
|
|
if (x39c_curPattern < x38c_patterns.size())
|
|
{
|
|
if (x368_destWPDelta != zeus::CVector3f::skZero)
|
|
{
|
|
zeus::CVector3f patternDir = FindPatternDir(mgr);
|
|
SetDestPos(FindPatternRotation(patternDir).transform(x38c_patterns[x39c_curPattern].GetPos()));
|
|
if (x37c_patternFit == EPatternFit::Zero)
|
|
{
|
|
float magSq;
|
|
if (x328_25_verticalMovement)
|
|
magSq = patternDir.magSquared() / x368_destWPDelta.magSquared();
|
|
else
|
|
magSq = patternDir.toVec2f().magSquared() / x368_destWPDelta.toVec2f().magSquared();
|
|
SetDestPos(std::sqrt(magSq) * x2e0_destPos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetDestPos(x38c_patterns[x39c_curPattern].GetPos());
|
|
}
|
|
}
|
|
|
|
switch (x374_patternTranslate)
|
|
{
|
|
case EPatternTranslate::RelativeStart:
|
|
SetDestPos(x2e0_destPos + x350_patternStartPos);
|
|
break;
|
|
case EPatternTranslate::RelativePlayerStart:
|
|
SetDestPos(x2e0_destPos + x35c_patternStartPlayerPos);
|
|
break;
|
|
case EPatternTranslate::RelativePlayer:
|
|
SetDestPos(x2e0_destPos + mgr.GetPlayer().GetTranslation());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CPatterned::SetupPattern(CStateManager& mgr)
|
|
{
|
|
EScriptObjectState state = GetDesiredAttackState(mgr);
|
|
x2dc_destObj = GetWaypointForState(mgr, state, EScriptObjectMessage::Follow);
|
|
if (x2dc_destObj == kInvalidUniqueId && state != EScriptObjectState::Attack)
|
|
x2dc_destObj = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);
|
|
x38c_patterns.clear();
|
|
if (x2dc_destObj != kInvalidUniqueId)
|
|
{
|
|
x350_patternStartPos = GetTranslation();
|
|
x35c_patternStartPlayerPos = mgr.GetPlayer().GetTranslation();
|
|
auto destWPs = GetDestWaypoints(mgr);
|
|
if (destWPs.first)
|
|
{
|
|
x374_patternTranslate = EPatternTranslate(destWPs.first->GetPatternTranslate());
|
|
x378_patternOrient = EPatternOrient(destWPs.first->GetPatternOrient());
|
|
x37c_patternFit = EPatternFit(destWPs.first->GetPatternFit());
|
|
if (destWPs.second)
|
|
x368_destWPDelta = destWPs.second->GetTranslation() - destWPs.first->GetTranslation();
|
|
else
|
|
x368_destWPDelta = zeus::CVector3f::skZero;
|
|
|
|
int numPatterns = 0;
|
|
CScriptWaypoint* curWp = destWPs.first;
|
|
do
|
|
{
|
|
++numPatterns;
|
|
curWp = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(curWp->NextWaypoint(mgr))).GetPtr();
|
|
if (!curWp)
|
|
break;
|
|
} while (curWp->GetUniqueId() != destWPs.first->GetUniqueId());
|
|
x38c_patterns.reserve(numPatterns);
|
|
|
|
zeus::CVector3f basePos;
|
|
switch (x374_patternTranslate)
|
|
{
|
|
case EPatternTranslate::RelativePlayerStart:
|
|
if (destWPs.second)
|
|
basePos = destWPs.second->GetTranslation();
|
|
break;
|
|
case EPatternTranslate::Absolute:
|
|
break;
|
|
default:
|
|
basePos = destWPs.first->GetTranslation();
|
|
break;
|
|
}
|
|
|
|
curWp = destWPs.first;
|
|
do
|
|
{
|
|
zeus::CVector3f wpForward = curWp->GetTransform().basis[1];
|
|
if (x368_destWPDelta != zeus::CVector3f::skZero)
|
|
wpForward = FindPatternRotation(FindPatternDir(mgr)).transform(wpForward);
|
|
x38c_patterns.emplace_back(curWp->GetTranslation() - basePos, wpForward,
|
|
curWp->GetSpeed(), curWp->GetBehaviour(),
|
|
curWp->GetBehaviourOrient(), curWp->GetBehaviourModifiers(),
|
|
curWp->GetAnimation());
|
|
curWp = TCastToPtr<CScriptWaypoint>(mgr.ObjectById(curWp->NextWaypoint(mgr))).GetPtr();
|
|
if (!curWp)
|
|
break;
|
|
} while (curWp->GetUniqueId() != destWPs.first->GetUniqueId());
|
|
}
|
|
}
|
|
|
|
x400_30_patternShagged = false;
|
|
x39c_curPattern = 0;
|
|
x328_24_inPosition = false;
|
|
x2ec_reflectedDestPos = GetTranslation();
|
|
if (!x38c_patterns.empty())
|
|
{
|
|
x3b0_moveSpeed = x38c_patterns.front().GetSpeed();
|
|
x380_behaviour = EBehaviour(x38c_patterns.front().GetBehaviour());
|
|
x30c_behaviourOrient = EBehaviourOrient(x38c_patterns.front().GetBehaviourOrient());
|
|
x384_behaviourModifiers = EBehaviourModifiers(x38c_patterns.front().GetBehaviourModifiers());
|
|
}
|
|
}
|
|
|
|
EScriptObjectState CPatterned::GetDesiredAttackState(CStateManager& mgr) const
|
|
{
|
|
float deltaMagSq = (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared();
|
|
if (deltaMagSq < x2fc_minAttackRange * x2fc_minAttackRange)
|
|
return EScriptObjectState::Retreat;
|
|
else if (deltaMagSq > x300_maxAttackRange * x300_maxAttackRange)
|
|
return EScriptObjectState::CloseIn;
|
|
else
|
|
return EScriptObjectState::Attack;
|
|
}
|
|
|
|
float CPatterned::GetAnimationDistance(const CPASAnimParmData& data) const
|
|
{
|
|
auto bestAnim =
|
|
x64_modelData->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(data, -1);
|
|
float dist = 1.f;
|
|
if (bestAnim.first > FLT_EPSILON)
|
|
{
|
|
dist = x64_modelData->GetAnimationData()->GetAnimationDuration(bestAnim.second) *
|
|
x64_modelData->GetAnimationData()->GetAverageVelocity(bestAnim.second);
|
|
}
|
|
return dist;
|
|
}
|
|
|
|
void CPatterned::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum)
|
|
{
|
|
if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal)
|
|
{
|
|
SetCalculateLighting(false);
|
|
x90_actorLights->BuildConstantAmbientLighting(zeus::CColor::skWhite);
|
|
}
|
|
else
|
|
SetCalculateLighting(true);
|
|
|
|
zeus::CColor col = x42c_color;
|
|
u8 alpha = GetModelAlphau8(mgr);
|
|
if (x402_27_noXrayModel && mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay)
|
|
alpha = 76;
|
|
|
|
if (alpha < 255)
|
|
{
|
|
if (col.r() == 0.f && col.g() == 0.f && col.b() == 0.f)
|
|
col = zeus::CColor::skWhite; /* Not being damaged */
|
|
|
|
if (x401_29_laggedBurnDeath)
|
|
{
|
|
int stripedAlpha = 255;
|
|
if (alpha > 127)
|
|
stripedAlpha = (alpha - 128) * 2;
|
|
xb4_drawFlags = CModelFlags(3, 0, 3, zeus::CColor(0.f, (stripedAlpha * stripedAlpha) / 65025.f));
|
|
}
|
|
else if (x401_28_burning)
|
|
{
|
|
xb4_drawFlags = CModelFlags(5, 0, 3, zeus::CColor(0.f, 1.f));
|
|
}
|
|
else
|
|
{
|
|
zeus::CColor col2 = col;
|
|
col2.a() = alpha / 255.f;
|
|
xb4_drawFlags = CModelFlags(5, 0, 3, col2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (col.r() != 0.f || col.g() != 0.f || col.b() != 0.f)
|
|
{
|
|
/* Being damaged */
|
|
zeus::CColor col2 = col;
|
|
col2.a() = alpha / 255.f;
|
|
xb4_drawFlags = CModelFlags(2, 0, 3, col2);
|
|
}
|
|
else
|
|
{
|
|
xb4_drawFlags = CModelFlags(0, 0, 3, zeus::CColor::skWhite);
|
|
}
|
|
}
|
|
|
|
CActor::PreRender(mgr, frustum);
|
|
}
|
|
|
|
void CPatterned::RenderIceModelWithFlags(const CModelFlags& flags) const
|
|
{
|
|
CModelFlags useFlags = flags;
|
|
useFlags.x1_matSetIdx = 0;
|
|
CAnimData* animData = x64_modelData->AnimationData();
|
|
if (CMorphableSkinnedModel* iceModel = animData->IceModel().GetObj())
|
|
animData->Render(*iceModel, useFlags, {*x510_vertexMorph}, iceModel->GetMorphMagnitudes());
|
|
}
|
|
|
|
void CPatterned::Render(const CStateManager& mgr) const
|
|
{
|
|
int mask = 0;
|
|
int target = 0;
|
|
if (x402_29_drawParticles)
|
|
{
|
|
mgr.GetCharacterRenderMaskAndTarget(x402_31_thawed, mask, target);
|
|
x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirstMasked(mask, target);
|
|
}
|
|
if ((mgr.GetThermalDrawFlag() == EThermalDrawFlag::Cold && !x402_31_thawed) ||
|
|
(mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot && x402_31_thawed) ||
|
|
mgr.GetThermalDrawFlag() == EThermalDrawFlag::Bypass)
|
|
{
|
|
if (x401_28_burning)
|
|
{
|
|
const CTexture* ashy = mgr.GetActorModelParticles()->GetAshyTexture(*this);
|
|
u8 alpha = GetModelAlphau8(mgr);
|
|
if (ashy && ((!x401_29_laggedBurnDeath && alpha <= 255) || alpha <= 127))
|
|
{
|
|
if (xe5_31_pointGeneratorParticles)
|
|
mgr.SetupParticleHook(*this);
|
|
zeus::CColor addColor;
|
|
if (x401_29_laggedBurnDeath)
|
|
{
|
|
addColor = zeus::CColor::skClear;
|
|
}
|
|
else
|
|
{
|
|
addColor = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot ?
|
|
zeus::CColor::skWhite : zeus::CColor::skBlack;
|
|
}
|
|
x64_modelData->DisintegrateDraw(mgr, GetTransform(), *ashy, addColor,
|
|
alpha * (x401_29_laggedBurnDeath ? 0.00787402f : 0.00392157f));
|
|
if (xe5_31_pointGeneratorParticles)
|
|
{
|
|
CSkinnedModel::ClearPointGeneratorFunc();
|
|
mgr.GetActorModelParticles()->Render(*this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPhysicsActor::Render(mgr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPhysicsActor::Render(mgr);
|
|
}
|
|
|
|
if (x450_bodyController->IsFrozen() && !x401_28_burning)
|
|
{
|
|
RenderIceModelWithFlags(CModelFlags(0, 0, 3, zeus::CColor::skWhite));
|
|
}
|
|
}
|
|
|
|
if (x402_29_drawParticles)
|
|
{
|
|
x64_modelData->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLastMasked(mask, target);
|
|
}
|
|
}
|
|
|
|
void CPatterned::ThinkAboutMove(float dt)
|
|
{
|
|
bool doMove = true;
|
|
if (!x328_25_verticalMovement && !x328_27_onGround)
|
|
{
|
|
x310_moveVec.zeroOut();
|
|
doMove = false;
|
|
}
|
|
|
|
if (doMove && x39c_curPattern < x38c_patterns.size())
|
|
{
|
|
zeus::CVector3f frontVec = GetTransform().frontVector();
|
|
zeus::CVector3f x31cCpy = x31c_faceVec;
|
|
if (x31c_faceVec.magSquared() > 0.1f)
|
|
x31cCpy.normalize();
|
|
float mag = frontVec.dot(x31cCpy);
|
|
|
|
switch (x3f8_moveState)
|
|
{
|
|
case EMoveState::Zero:
|
|
if (!x328_26_longJump)
|
|
break;
|
|
case EMoveState::One:
|
|
doMove = false;
|
|
if (mag > 0.85f)
|
|
{
|
|
doMove = true;
|
|
x3f8_moveState = EMoveState::Two;
|
|
break;
|
|
}
|
|
x3f8_moveState = EMoveState::One;
|
|
break;
|
|
case EMoveState::Two:
|
|
x3f8_moveState = EMoveState::Three;
|
|
case EMoveState::Three:
|
|
doMove = true;
|
|
if (!x328_26_longJump)
|
|
{
|
|
x3f8_moveState = EMoveState::Zero;
|
|
break;
|
|
}
|
|
if (mag > 0.9f)
|
|
x3f8_moveState = EMoveState::Four;
|
|
break;
|
|
case EMoveState::Four:
|
|
x328_24_inPosition = true;
|
|
doMove = false;
|
|
x3f8_moveState = EMoveState::Zero;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!x401_26_disableMove && doMove)
|
|
{
|
|
const CBodyState* state = x450_bodyController->GetBodyStateInfo().GetCurrentState();
|
|
if (state->ApplyAnimationDeltas() && !zeus::close_enough(x2e0_destPos - GetTranslation(), {}))
|
|
MoveToOR((x64_modelData->GetScale() * x434_posDelta) * x55c_moveScale, dt);
|
|
}
|
|
|
|
RotateToOR(x440_rotDelta, dt);
|
|
}
|
|
|
|
void CPatterned::PhazeOut(CStateManager& mgr)
|
|
{
|
|
if (!x400_27_fadeToDeath)
|
|
SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);
|
|
x401_27_phazingOut = true;
|
|
x450_bodyController->SetPlaybackRate(0.f);
|
|
x64_modelData->AnimationData()->GetParticleDB().SetUpdatesEnabled(false);
|
|
}
|
|
|
|
bool CPatterned::ApplyBoneTracking() const
|
|
{
|
|
if (x400_25_alive)
|
|
return x460_knockBackController.GetFlinchRemTime() <= 0.f;
|
|
|
|
return false;
|
|
}
|
|
}
|