#nullable enable using System; using System.Collections.Generic; using Content.Server.AI.Utility.AiLogic; using Content.Server.Administration; using Content.Server.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Mobs.State; using Content.Shared; using Content.Shared.Administration; using Content.Shared.GameObjects.Components.Movement; using JetBrains.Annotations; using Robust.Server.AI; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; namespace Content.Server.GameObjects.EntitySystems.AI { [UsedImplicitly] internal class AiSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IDynamicTypeFactory _typeFactory = default!; [Dependency] private readonly IReflectionManager _reflectionManager = default!; private readonly Dictionary _processorTypes = new(); /// /// To avoid iterating over dead AI continuously they can wake and sleep themselves when necessary. /// private readonly HashSet _awakeAi = new(); // To avoid modifying awakeAi while iterating over it. private readonly List _queuedSleepMessages = new(); private readonly List _queuedMobStateMessages = new(); public bool IsAwake(AiLogicProcessor processor) => _awakeAi.Contains(processor); /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(HandleAiSleep); SubscribeLocalEvent(MobStateChanged); var processors = _reflectionManager.GetAllChildren(); foreach (var processor in processors) { var att = (AiLogicProcessorAttribute) Attribute.GetCustomAttribute(processor, typeof(AiLogicProcessorAttribute))!; // Tests should pick this up DebugTools.AssertNotNull(att); _processorTypes.Add(att.SerializeName, processor); } } /// public override void Update(float frameTime) { var cvarMaxUpdates = _configurationManager.GetCVar(CCVars.AIMaxUpdates); if (cvarMaxUpdates <= 0) return; foreach (var message in _queuedMobStateMessages) { if (message.Entity.Deleted || !message.Entity.TryGetComponent(out AiControllerComponent? controller)) { continue; } controller.Processor?.MobStateChanged(message); } _queuedMobStateMessages.Clear(); foreach (var message in _queuedSleepMessages) { switch (message.Sleep) { case true: if (_awakeAi.Count == cvarMaxUpdates && _awakeAi.Contains(message.Processor)) { Logger.Warning($"Under AI limit again: {_awakeAi.Count - 1} / {cvarMaxUpdates}"); } _awakeAi.Remove(message.Processor); break; case false: _awakeAi.Add(message.Processor); if (_awakeAi.Count > cvarMaxUpdates) { Logger.Warning($"AI limit exceeded: {_awakeAi.Count} / {cvarMaxUpdates}"); } break; } } _queuedSleepMessages.Clear(); var toRemove = new List(); var maxUpdates = Math.Min(_awakeAi.Count, cvarMaxUpdates); var count = 0; foreach (var processor in _awakeAi) { if (processor.SelfEntity.Deleted || !processor.SelfEntity.HasComponent()) { toRemove.Add(processor); continue; } if (count >= maxUpdates) { break; } processor.Update(frameTime); count++; } foreach (var processor in toRemove) { _awakeAi.Remove(processor); } } private void HandleAiSleep(SleepAiMessage message) { _queuedSleepMessages.Add(message); } private void MobStateChanged(MobStateChangedMessage message) { if (!message.Entity.HasComponent()) { return; } _queuedMobStateMessages.Add(message); } /// /// Will start up the controller's processor if not already done so. /// Also add them to the awakeAi for updates. /// /// public void ProcessorInitialize(AiControllerComponent controller) { if (controller.Processor != null || controller.LogicName == null) return; controller.Processor = CreateProcessor(controller.LogicName); controller.Processor.SelfEntity = controller.Owner; controller.Processor.Setup(); _awakeAi.Add(controller.Processor); } private UtilityAi CreateProcessor(string name) { if (_processorTypes.TryGetValue(name, out var type)) { return (UtilityAi)_typeFactory.CreateInstance(type); } // processor needs to inherit AiLogicProcessor, and needs an AiLogicProcessorAttribute to define the YAML name throw new ArgumentException($"Processor type {name} could not be found.", nameof(name)); } public bool ProcessorTypeExists(string name) => _processorTypes.ContainsKey(name); } }