F4MP/codigos originales/tiltedcode/Code/client/Services/Generic/CombatService.cpp
2026-01-06 18:53:59 +01:00

250 lines
8.9 KiB
C++

#include <Services/CombatService.h>
#include <Services/TransportService.h>
#include <Events/UpdateEvent.h>
#include <Events/ProjectileLaunchedEvent.h>
#include <Events/HitEvent.h>
#include <Messages/ProjectileLaunchRequest.h>
#include <Messages/NotifyProjectileLaunch.h>
#include <Systems/ModSystem.h>
#include <World.h>
#include <Projectiles/Projectile.h>
#include <Forms/TESObjectWEAP.h>
#include <Forms/TESAmmo.h>
#include <Games/ActorExtension.h>
CombatService::CombatService(World& aWorld, TransportService& aTransport, entt::dispatcher& aDispatcher)
: m_world(aWorld)
, m_transport(aTransport)
{
m_updateConnection = aDispatcher.sink<UpdateEvent>().connect<&CombatService::OnUpdate>(this);
m_hitConnection = aDispatcher.sink<HitEvent>().connect<&CombatService::OnHitEvent>(this);
m_localComponentRemoved = m_world.on_destroy<LocalComponent>().connect<&CombatService::OnLocalComponentRemoved>(this);
m_projectileLaunchedConnection = aDispatcher.sink<ProjectileLaunchedEvent>().connect<&CombatService::OnProjectileLaunchedEvent>(this);
m_projectileLaunchConnection = aDispatcher.sink<NotifyProjectileLaunch>().connect<&CombatService::OnNotifyProjectileLaunch>(this);
}
void CombatService::OnUpdate(const UpdateEvent& acEvent) const noexcept
{
RunTargetUpdates(static_cast<float>(acEvent.Delta));
}
void CombatService::OnLocalComponentRemoved(entt::registry& aRegistry, entt::entity aEntity) const noexcept
{
m_world.remove<CombatComponent>(aEntity);
}
void CombatService::OnProjectileLaunchedEvent(const ProjectileLaunchedEvent& acEvent) const noexcept
{
ModSystem& modSystem = m_world.Get().GetModSystem();
uint32_t shooterFormId = acEvent.ShooterID;
auto view = m_world.view<FormIdComponent, LocalComponent>();
const auto shooterEntityIt = std::find_if(std::begin(view), std::end(view), [shooterFormId, view](entt::entity entity) { return view.get<FormIdComponent>(entity).Id == shooterFormId; });
if (shooterEntityIt == std::end(view))
return;
LocalComponent& localComponent = view.get<LocalComponent>(*shooterEntityIt);
ProjectileLaunchRequest request{};
request.OriginX = acEvent.Origin.x;
request.OriginY = acEvent.Origin.y;
request.OriginZ = acEvent.Origin.z;
modSystem.GetServerModId(acEvent.ProjectileBaseID, request.ProjectileBaseID);
modSystem.GetServerModId(acEvent.WeaponID, request.WeaponID);
modSystem.GetServerModId(acEvent.AmmoID, request.AmmoID);
request.ShooterID = localComponent.Id;
request.ZAngle = acEvent.ZAngle;
request.XAngle = acEvent.XAngle;
request.YAngle = acEvent.YAngle;
modSystem.GetServerModId(acEvent.ParentCellID, request.ParentCellID);
modSystem.GetServerModId(acEvent.SpellID, request.SpellID);
request.CastingSource = acEvent.CastingSource;
request.Area = acEvent.Area;
request.Power = acEvent.Power;
request.Scale = acEvent.Scale;
request.AlwaysHit = acEvent.AlwaysHit;
request.NoDamageOutsideCombat = acEvent.NoDamageOutsideCombat;
request.AutoAim = acEvent.AutoAim;
request.DeferInitialization = acEvent.DeferInitialization;
request.ForceConeOfFire = acEvent.ForceConeOfFire;
request.UnkBool1 = acEvent.UnkBool1;
request.UnkBool2 = acEvent.UnkBool2;
m_transport.Send(request);
}
void CombatService::OnNotifyProjectileLaunch(const NotifyProjectileLaunch& acMessage) const noexcept
{
ModSystem& modSystem = World::Get().GetModSystem();
auto remoteView = m_world.view<RemoteComponent, FormIdComponent>();
const auto remoteIt = std::find_if(std::begin(remoteView), std::end(remoteView), [remoteView, Id = acMessage.ShooterID](auto entity) { return remoteView.get<RemoteComponent>(entity).Id == Id; });
if (remoteIt == std::end(remoteView))
{
spdlog::warn("Shooter with remote id {:X} not found.", acMessage.ShooterID);
return;
}
FormIdComponent formIdComponent = remoteView.get<FormIdComponent>(*remoteIt);
Projectile::LaunchData launchData{};
// Projectile::Launch relies on pParentCell being valid.
// TODO: it's possible that more of these values must have a value, should probably check for that in the game code.
const uint32_t cParentCellId = modSystem.GetGameId(acMessage.ParentCellID);
launchData.pParentCell = Cast<TESObjectCELL>(TESForm::GetById(cParentCellId));
if (!launchData.pParentCell)
{
spdlog::warn("Cannot launch projectile, invalid parent cell: {:X}", cParentCellId);
return;
}
launchData.pShooter = Cast<TESObjectREFR>(TESForm::GetById(formIdComponent.Id));
launchData.Origin.x = acMessage.OriginX;
launchData.Origin.y = acMessage.OriginY;
launchData.Origin.z = acMessage.OriginZ;
const uint32_t cProjectileBaseId = modSystem.GetGameId(acMessage.ProjectileBaseID);
launchData.pProjectileBase = TESForm::GetById(cProjectileBaseId);
const uint32_t cFromWeaponId = modSystem.GetGameId(acMessage.WeaponID);
launchData.pFromWeapon = Cast<TESObjectWEAP>(TESForm::GetById(cFromWeaponId));
const uint32_t cFromAmmoId = modSystem.GetGameId(acMessage.AmmoID);
launchData.pFromAmmo = Cast<TESAmmo>(TESForm::GetById(cFromAmmoId));
launchData.fZAngle = acMessage.ZAngle;
launchData.fXAngle = acMessage.XAngle;
launchData.fYAngle = acMessage.YAngle;
const uint32_t cSpellId = modSystem.GetGameId(acMessage.SpellID);
launchData.pSpell = Cast<MagicItem>(TESForm::GetById(cSpellId));
launchData.eCastingSource = (MagicSystem::CastingSource)acMessage.CastingSource;
launchData.iArea = acMessage.Area;
launchData.fPower = acMessage.Power;
launchData.fScale = acMessage.Scale;
launchData.bAlwaysHit = acMessage.AlwaysHit;
launchData.bNoDamageOutsideCombat = acMessage.NoDamageOutsideCombat;
launchData.bAutoAim = acMessage.AutoAim;
launchData.bForceConeOfFire = acMessage.ForceConeOfFire;
// always use origin, or it'll recalculate it and it desyncs
launchData.bUseOrigin = true;
launchData.bUnkBool1 = acMessage.UnkBool1;
launchData.bUnkBool2 = acMessage.UnkBool2;
BSPointerHandle<Projectile> result;
Projectile::Launch(&result, launchData);
}
void CombatService::OnHitEvent(const HitEvent& acEvent) const noexcept
{
#if 0
if (!m_transport.IsConnected())
return;
// The targeting system does not apply to players, and only local actors should be considered.
auto* pHittee = Cast<Actor>(TESForm::GetById(acEvent.HitteeId));
if (!pHittee || pHittee->GetExtension()->IsPlayer() || pHittee->GetExtension()->IsRemote())
return;
// NPCs should not affect targeting.
auto* pHitter = Cast<Actor>(TESForm::GetById(acEvent.HitterId));
if (!pHitter || !pHitter->GetExtension()->IsPlayer())
return;
// If the target is not in combat, don't start combat, let the game take care of that first.
if (!pHittee->IsInCombat())
return;
auto view = m_world.view<FormIdComponent, LocalComponent>(entt::exclude<ObjectComponent>);
const auto hitteeIt = std::find_if(std::begin(view), std::end(view), [id = acEvent.HitteeId, view](entt::entity entity) { return view.get<FormIdComponent>(entity).Id == id; });
if (hitteeIt == std::end(view))
{
spdlog::warn(__FUNCTION__ ": hittee form id component not found, form id: {:X}", acEvent.HitterId);
return;
}
if (m_world.any_of<CombatComponent>(*hitteeIt))
return;
m_world.emplace_or_replace<CombatComponent>(*hitteeIt, acEvent.HitterId);
pHittee->SetCombatTargetEx(pHitter);
#endif
}
void CombatService::RunTargetUpdates(const float acDelta) const noexcept
{
#if 0
static std::chrono::steady_clock::time_point lastSendTimePoint;
constexpr auto cDelayBetweenUpdates = 200ms;
const auto now = std::chrono::steady_clock::now();
if (now - lastSendTimePoint < cDelayBetweenUpdates)
return;
lastSendTimePoint = now;
const auto view = m_world.view<FormIdComponent, CombatComponent>();
Vector<entt::entity> toRemove{};
for (const auto entity : view)
{
auto& combatComponent = view.get<CombatComponent>(entity);
combatComponent.Timer = combatComponent.Timer - acDelta;
if (combatComponent.Timer <= 0.f)
{
toRemove.push_back(entity);
continue;
}
auto* pTarget = Cast<Actor>(TESForm::GetById(combatComponent.TargetFormId));
if (!pTarget)
{
spdlog::warn(__FUNCTION__ ": combat target not found, form id {:X}", combatComponent.TargetFormId);
toRemove.push_back(entity);
continue;
}
const auto& formIdComponent = view.get<FormIdComponent>(entity);
auto* pActor = Cast<Actor>(TESForm::GetById(formIdComponent.Id));
if (!pActor)
{
spdlog::error(__FUNCTION__ ": actor not found, form id {:X}", formIdComponent.Id);
toRemove.push_back(entity);
continue;
}
}
for (const auto entity : toRemove)
m_world.remove<CombatComponent>(entity);
#endif
}