using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.Ghost.Roles; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Roles; public abstract class SharedRoleSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; // TODO please lord make role entities private readonly HashSet _antagTypes = new(); private JobRequirementOverridePrototype? _requirementOverride; public override void Initialize() { // TODO make roles entities SubscribeLocalEvent(OnJobGetAllRoles); Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); } private void SetRequirementOverride(string value) { if (string.IsNullOrEmpty(value)) { _requirementOverride = null; return; } if (!_prototypes.TryIndex(value, out _requirementOverride )) Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) { var name = "game-ticker-unknown-role"; var prototype = ""; string? playTimeTracker = null; if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job)) { name = job.Name; prototype = job.ID; playTimeTracker = job.PlayTimeTracker; } name = Loc.GetString(name); args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype)); } protected void SubscribeAntagEvents() where T : AntagonistRoleComponent { SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) => { var name = "game-ticker-unknown-role"; var prototype = ""; if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag)) { name = antag.Name; prototype = antag.ID; } name = Loc.GetString(name); args.Roles.Add(new RoleInfo(component, name, true, null, prototype)); }); SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute(out _); }); _antagTypes.Add(typeof(T)); } public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false) { if (!Resolve(mindId, ref mind)) return; EntityManager.AddComponents(mindId, components); var antagonist = false; foreach (var compReg in components.Values) { var compType = compReg.Component.GetType(); var comp = EntityManager.ComponentFactory.GetComponent(compType); if (IsAntagonistRole(comp.GetType())) { antagonist = true; break; } } var mindEv = new MindRoleAddedEvent(silent); RaiseLocalEvent(mindId, ref mindEv); var message = new RoleAddedEvent(mindId, mind, antagonist, silent); if (mind.OwnedEntity != null) { RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } _adminLogger.Add(LogType.Mind, LogImpact.Low, $"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}"); } public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false) { if (!Resolve(mindId, ref mind)) return; if (HasComp(mindId, component.GetType())) { throw new ArgumentException($"We already have this role: {component}"); } EntityManager.AddComponent(mindId, component); var antagonist = IsAntagonistRole(component.GetType()); var mindEv = new MindRoleAddedEvent(silent); RaiseLocalEvent(mindId, ref mindEv); var message = new RoleAddedEvent(mindId, mind, antagonist, silent); if (mind.OwnedEntity != null) { RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); } /// /// Gives this mind a new role. /// /// The mind to add the role to. /// The role instance to add. /// The role type to add. /// Whether or not the role should be added silently /// The instance of the role. /// /// Thrown if we already have a role with this type. /// public void MindAddRole(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : IComponent, new() { if (!Resolve(mindId, ref mind)) return; if (HasComp(mindId)) { throw new ArgumentException($"We already have this role: {typeof(T)}"); } AddComp(mindId, component); var antagonist = IsAntagonistRole(); var mindEv = new MindRoleAddedEvent(silent); RaiseLocalEvent(mindId, ref mindEv); var message = new RoleAddedEvent(mindId, mind, antagonist, silent); if (mind.OwnedEntity != null) { RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'Role {typeof(T).Name}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); } /// /// Removes a role from this mind. /// /// The mind to remove the role from. /// The type of the role to remove. /// /// Thrown if we do not have this role. /// public void MindRemoveRole(EntityUid mindId) where T : IComponent { if (!RemComp(mindId)) { throw new ArgumentException($"We do not have this role: {typeof(T)}"); } var mind = Comp(mindId); var antagonist = IsAntagonistRole(); var message = new RoleRemovedEvent(mindId, mind, antagonist); if (mind.OwnedEntity != null) { RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'Role {typeof(T).Name}' removed from mind of {_minds.MindOwnerLoggingString(mind)}"); } public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent { if (!MindHasRole(mindId)) return false; MindRemoveRole(mindId); return true; } public bool MindHasRole(EntityUid mindId) where T : IComponent { DebugTools.Assert(HasComp(mindId)); return HasComp(mindId); } public List MindGetAllRoles(EntityUid mindId) { DebugTools.Assert(HasComp(mindId)); var ev = new MindGetAllRolesEvent(new List()); RaiseLocalEvent(mindId, ref ev); return ev.Roles; } public bool MindIsAntagonist(EntityUid? mindId) { if (mindId == null) return false; DebugTools.Assert(HasComp(mindId)); var ev = new MindIsAntagonistEvent(); RaiseLocalEvent(mindId.Value, ref ev); return ev.IsAntagonist; } /// /// Does this mind possess an exclusive antagonist role /// /// The mind entity /// True if the mind possesses an exclusive antag role public bool MindIsExclusiveAntagonist(EntityUid? mindId) { if (mindId == null) return false; var ev = new MindIsAntagonistEvent(); RaiseLocalEvent(mindId.Value, ref ev); return ev.IsExclusiveAntagonist; } public bool IsAntagonistRole() { return _antagTypes.Contains(typeof(T)); } public bool IsAntagonistRole(Type component) { return _antagTypes.Contains(component); } /// /// Play a sound for the mind, if it has a session attached. /// Use this for role greeting sounds. /// public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent? mind = null) { if (Resolve(mindId, ref mind) && mind.Session != null) _audio.PlayGlobal(sound, mind.Session); } public HashSet? GetJobRequirement(JobPrototype job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) return req; return job.Requirements; } public HashSet? GetJobRequirement(ProtoId job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req)) return req; return _prototypes.Index(job).Requirements; } public HashSet? GetAntagRequirement(ProtoId antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req)) return req; return _prototypes.Index(antag).Requirements; } public HashSet? GetAntagRequirement(AntagPrototype antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req)) return req; return antag.Requirements; } }