using System.Diagnostics.CodeAnalysis; using Content.Shared.Mind; using Content.Shared.Objectives.Components; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Objectives.Systems; /// /// Provides API for creating and interacting with objectives. /// public abstract class SharedObjectivesSystem : EntitySystem { [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!; private EntityQuery _metaQuery; public override void Initialize() { base.Initialize(); _metaQuery = GetEntityQuery(); } /// /// Checks requirements and duplicate objectives to see if an objective can be assigned. /// public bool CanBeAssigned(EntityUid uid, EntityUid mindId, MindComponent mind, ObjectiveComponent? comp = null) { if (!Resolve(uid, ref comp)) return false; var ev = new RequirementCheckEvent(mindId, mind); RaiseLocalEvent(uid, ref ev); if (ev.Cancelled) return false; // only check for duplicate prototypes if it's unique if (comp.Unique) { var proto = _metaQuery.GetComponent(uid).EntityPrototype?.ID; foreach (var objective in mind.Objectives) { if (_metaQuery.GetComponent(objective).EntityPrototype?.ID == proto) return false; } } return true; } /// /// Spawns and assigns an objective for a mind. /// The objective is not added to the mind's objectives, mind system does that in TryAddObjective. /// If the objective could not be assigned the objective is deleted and null is returned. /// public EntityUid? TryCreateObjective(EntityUid mindId, MindComponent mind, string proto) { if (!_protoMan.HasIndex(proto)) return null; var uid = Spawn(proto); if (!TryComp(uid, out var comp)) { Del(uid); Log.Error($"Invalid objective prototype {proto}, missing ObjectiveComponent"); return null; } if (!CanBeAssigned(uid, mindId, mind, comp)) { Log.Warning($"Objective {proto} did not match the requirements for {_mind.MindOwnerLoggingString(mind)}, deleted it"); return null; } var ev = new ObjectiveAssignedEvent(mindId, mind); RaiseLocalEvent(uid, ref ev); if (ev.Cancelled) { Del(uid); Log.Warning($"Could not assign objective {proto}, deleted it"); return null; } // let the title description and icon be set by systems var afterEv = new ObjectiveAfterAssignEvent(mindId, mind, comp, MetaData(uid)); RaiseLocalEvent(uid, ref afterEv); Log.Debug($"Created objective {ToPrettyString(uid):objective}"); return uid; } /// /// Spawns and assigns an objective for a mind. /// The objective is not added to the mind's objectives, mind system does that in TryAddObjective. /// If the objective could not be assigned the objective is deleted and false is returned. /// public bool TryCreateObjective(Entity mind, EntProtoId proto, [NotNullWhen(true)] out EntityUid? objective) { objective = TryCreateObjective(mind.Owner, mind.Comp, proto); return objective != null; } /// /// Get the title, description, icon and progress of an objective using . /// If any of them are null it is logged and null is returned. /// /// ID of the condition entity /// ID of the player's mind entity /// Mind component of the player's mind public ObjectiveInfo? GetInfo(EntityUid uid, EntityUid mindId, MindComponent? mind = null) { if (!Resolve(mindId, ref mind)) return null; if (GetProgress(uid, (mindId, mind)) is not {} progress) return null; var comp = Comp(uid); var meta = MetaData(uid); var title = meta.EntityName; var description = meta.EntityDescription; if (comp.Icon == null) { Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing an icon!"); return null; } return new ObjectiveInfo(title, description, comp.Icon, progress); } /// /// Gets the progress of an objective using . /// Returning null is a programmer error. /// public float? GetProgress(EntityUid uid, Entity mind) { var ev = new ObjectiveGetProgressEvent(mind, mind.Comp); RaiseLocalEvent(uid, ref ev); if (ev.Progress != null) return ev.Progress; Log.Error($"Objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} didn't set a progress value!"); return null; } /// /// Returns true if an objective is completed. /// public bool IsCompleted(EntityUid uid, Entity mind) { return (GetProgress(uid, mind) ?? 0f) >= 0.999f; } /// /// Sets the objective's icon to the one specified. /// Intended for handlers to set an icon. /// public void SetIcon(EntityUid uid, SpriteSpecifier icon, ObjectiveComponent? comp = null) { if (!Resolve(uid, ref comp)) return; comp.Icon = icon; } }