#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(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 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 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 proj = act) { if (mgr.GetPlayer().GetUniqueId() == proj->GetOwnerId()) x400_24_hitByPlayerProjectile = true; } } std::experimental::optional 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 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 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 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(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>& 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 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 wp = mgr.GetObjectById(x2dc_destObj)) for (const auto& conn : wp->GetConnectionList()) if (conn.x0_state == EScriptObjectState::Arrived && conn.x4_msg == EScriptObjectMessage::Action) if (TCastToPtr 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 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 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 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 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 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 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 CPatterned::GetDestWaypoints(CStateManager& mgr) const { std::pair ret {}; if (TCastToPtr wp = mgr.ObjectById(x2dc_destObj)) { ret.first = wp.GetPtr(); ret.second = TCastToPtr(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(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(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; } }