using System.Diagnostics.CodeAnalysis; using Content.Server.NPC.Components; using Content.Server.NPC.HTN; using Content.Shared.CCVar; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.NPC; using Content.Shared.NPC.Systems; using Prometheus; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Player; namespace Content.Server.NPC.Systems { /// /// Handles NPCs running every tick. /// public sealed partial class NPCSystem : EntitySystem { private static readonly Gauge ActiveGauge = Metrics.CreateGauge( "npc_active_count", "Amount of NPCs that are actively processing"); [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly HTNSystem _htn = default!; [Dependency] private readonly MobStateSystem _mobState = default!; /// /// Whether any NPCs are allowed to run at all. /// public bool Enabled { get; set; } = true; private int _maxUpdates; private int _count; /// public override void Initialize() { base.Initialize(); Subs.CVar(_configurationManager, CCVars.NPCEnabled, value => Enabled = value, true); Subs.CVar(_configurationManager, CCVars.NPCMaxUpdates, obj => _maxUpdates = obj, true); } public void OnPlayerNPCAttach(EntityUid uid, HTNComponent component, PlayerAttachedEvent args) { SleepNPC(uid, component); } public void OnPlayerNPCDetach(EntityUid uid, HTNComponent component, PlayerDetachedEvent args) { if (_mobState.IsIncapacitated(uid) || TerminatingOrDeleted(uid)) return; // This NPC has an attached mind, so it should not wake up. if (TryComp(uid, out var mindContainer) && mindContainer.HasMind) return; WakeNPC(uid, component); } public void OnNPCStartup(EntityUid uid, HTNComponent component, ComponentStartup args) { component.Blackboard.SetValue(NPCBlackboard.Owner, uid); } public void OnNPCMapInit(EntityUid uid, HTNComponent component, MapInitEvent args) { WakeNPC(uid, component); } public void OnNPCShutdown(EntityUid uid, HTNComponent component, ComponentShutdown args) { SleepNPC(uid, component); } /// /// Is the NPC awake and updating? /// public bool IsAwake(EntityUid uid, HTNComponent component, ActiveNPCComponent? active = null) { return Resolve(uid, ref active, false); } public bool TryGetNpc(EntityUid uid, [NotNullWhen(true)] out NPCComponent? component) { // If you add your own NPC components then add them here. if (TryComp(uid, out var htn)) { component = htn; return true; } component = null; return false; } /// /// Allows the NPC to actively be updated. /// public void WakeNPC(EntityUid uid, HTNComponent? component = null) { if (!Resolve(uid, ref component, false)) { return; } Log.Debug($"Waking {ToPrettyString(uid)}"); EnsureComp(uid); } public void SleepNPC(EntityUid uid, HTNComponent? component = null) { if (!Resolve(uid, ref component, false)) { return; } // Don't bother with an event if (TryComp(uid, out var htn)) { if (htn.Plan != null) { var currentOperator = htn.Plan.CurrentOperator; _htn.ShutdownTask(currentOperator, htn.Blackboard, HTNOperatorStatus.Failed); _htn.ShutdownPlan(htn); htn.Plan = null; } } Log.Debug($"Sleeping {ToPrettyString(uid)}"); RemComp(uid); } /// public override void Update(float frameTime) { base.Update(frameTime); if (!Enabled) return; // Add your system here. _htn.UpdateNPC(ref _count, _maxUpdates, frameTime); ActiveGauge.Set(Count()); } public void OnMobStateChange(EntityUid uid, HTNComponent component, MobStateChangedEvent args) { if (HasComp(uid)) return; switch (args.NewMobState) { case MobState.Alive: WakeNPC(uid, component); break; case MobState.Critical: case MobState.Dead: SleepNPC(uid, component); break; } } } }