diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2b6d556117..4c5dd76940 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,7 +19,7 @@ /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer -/Resources/Prototypes/Maps/ @Emisse +/Resources/Prototypes/Maps/** @Emisse /Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index ffa6dfd29d..68800a2afe 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -130,9 +130,9 @@ public void ReloadHandButtons() OnPlayerHandsAdded?.Invoke(hands); } - public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null) + public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true) { - base.DoDrop(uid, hand, doDropInteraction, hands); + base.DoDrop(uid, hand, doDropInteraction, hands, log); if (TryComp(hand.HeldEntity, out SpriteComponent? sprite)) sprite.RenderOrder = EntityManager.CurrentTick.Value; diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs index 3b54bf82da..1a530d950f 100644 --- a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs +++ b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs @@ -104,7 +104,7 @@ private void PopulatePlaytimeData() { var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime(); - var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime); + var formattedPlaytime = overallPlaytime.ToString(Loc.GetString("ui-playtime-time-format")); OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime)); var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles(); @@ -134,13 +134,4 @@ private void AddRolePlaytimeEntryToTable(string role, string playtimeString) _sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format."); } } - - private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan) - { - var hours = (int) timeSpan.TotalHours; - var minutes = timeSpan.Minutes; - - var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes)); - return formattedTimeLoc; - } } diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 42a79aec95..a4735c89d3 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -53,6 +53,8 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e) { // Reset on disconnect, just in case. _roles.Clear(); + _jobWhitelists.Clear(); + _roleBans.Clear(); } } @@ -60,9 +62,6 @@ private void RxRoleBans(MsgRoleBans message) { _sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries."); - if (_roleBans.Equals(message.Bans)) - return; - _roleBans.Clear(); _roleBans.AddRange(message.Bans); Updated?.Invoke(); diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 4604cd8296..1b4825cc9c 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -107,13 +107,41 @@ public async Task WaitClientCommand(string cmd, int numTicks = 10) /// /// Retrieve all entity prototypes that have some component. /// - public List GetPrototypesWithComponent( + public List<(EntityPrototype, T)> GetPrototypesWithComponent( HashSet? ignored = null, bool ignoreAbstract = true, bool ignoreTestPrototypes = true) where T : IComponent { var id = Server.ResolveDependency().GetComponentName(typeof(T)); + var list = new List<(EntityPrototype, T)>(); + foreach (var proto in Server.ProtoMan.EnumeratePrototypes()) + { + if (ignored != null && ignored.Contains(proto.ID)) + continue; + + if (ignoreAbstract && proto.Abstract) + continue; + + if (ignoreTestPrototypes && IsTestPrototype(proto)) + continue; + + if (proto.Components.TryGetComponent(id, out var cmp)) + list.Add((proto, (T)cmp)); + } + + return list; + } + + /// + /// Retrieve all entity prototypes that have some component. + /// + public List GetPrototypesWithComponent(Type type, + HashSet? ignored = null, + bool ignoreAbstract = true, + bool ignoreTestPrototypes = true) + { + var id = Server.ResolveDependency().GetComponentName(type); var list = new List(); foreach (var proto in Server.ProtoMan.EnumeratePrototypes()) { @@ -127,7 +155,7 @@ public List GetPrototypesWithComponent( continue; if (proto.Components.ContainsKey(id)) - list.Add(proto); + list.Add((proto)); } return list; diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs new file mode 100644 index 0000000000..fcfe1236cf --- /dev/null +++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs @@ -0,0 +1,95 @@ +using System.Linq; +using Content.Server.Roles; +using Content.Shared.Roles; +using Content.Shared.Roles.Jobs; +using Robust.Shared.GameObjects; +using Robust.Shared.Reflection; + +namespace Content.IntegrationTests.Tests.Minds; + +[TestFixture] +public sealed class RoleTests +{ + /// + /// Check that any prototype with a is properly configured + /// + [Test] + public async Task ValidateRolePrototypes() + { + await using var pair = await PoolManager.GetServerClient(); + + var jobComp = pair.Server.ResolveDependency().GetComponentName(typeof(JobRoleComponent)); + + Assert.Multiple(() => + { + foreach (var (proto, comp) in pair.GetPrototypesWithComponent()) + { + Assert.That(comp.AntagPrototype == null || comp.JobPrototype == null, $"Role {proto.ID} has both a job and antag prototype."); + Assert.That(!comp.ExclusiveAntag || comp.Antag, $"Role {proto.ID} is marked as an exclusive antag, despite not being an antag."); + Assert.That(comp.Antag || comp.AntagPrototype == null, $"Role {proto.ID} has an antag prototype, despite not being an antag."); + + if (comp.JobPrototype != null) + Assert.That(proto.Components.ContainsKey(jobComp), $"Role {proto.ID} is a job, despite not having a job prototype."); + + // It is possible that this is meant to be supported? Though I would assume that it would be for + // admin / prototype uploads, and that pre-defined roles should still check this. + Assert.That(!comp.Antag || comp.AntagPrototype != null , $"Role {proto.ID} is an antag, despite not having a antag prototype."); + } + }); + + await pair.CleanReturnAsync(); + } + + /// + /// Check that any prototype with a also has a properly configured + /// + /// + [Test] + public async Task ValidateJobPrototypes() + { + await using var pair = await PoolManager.GetServerClient(); + + var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent)); + + Assert.Multiple(() => + { + foreach (var (proto, comp) in pair.GetPrototypesWithComponent()) + { + if (proto.Components.TryGetComponent(mindCompId, out var mindComp)) + Assert.That(((MindRoleComponent)mindComp).JobPrototype, Is.Not.Null); + } + }); + + await pair.CleanReturnAsync(); + } + + /// + /// Check that any prototype with a component that inherits from also has a + /// + /// + [Test] + public async Task ValidateRolesHaveMindRoleComp() + { + await using var pair = await PoolManager.GetServerClient(); + + var refMan = pair.Server.ResolveDependency(); + var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent)); + + var compTypes = refMan.GetAllChildren(typeof(BaseMindRoleComponent)) + .Append(typeof(RoleBriefingComponent)) + .Where(x => !x.IsAbstract); + + Assert.Multiple(() => + { + foreach (var comp in compTypes) + { + foreach (var proto in pair.GetPrototypesWithComponent(comp)) + { + Assert.That(proto.Components.ContainsKey(mindCompId), $"Role {proto.ID} does not have a {nameof(MindRoleComponent)} despite having a {comp.Name}"); + } + } + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index bf75188f02..da7e1e8e9b 100644 --- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs +++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs @@ -40,7 +40,7 @@ public async Task AllItemsHaveSpritesTest() await pair.Client.WaitPost(() => { - foreach (var proto in pair.GetPrototypesWithComponent(Ignored)) + foreach (var (proto, _) in pair.GetPrototypesWithComponent(Ignored)) { var dummy = pair.Client.EntMan.Spawn(proto.ID); pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy)); diff --git a/Content.IntegrationTests/Tests/StorageTest.cs b/Content.IntegrationTests/Tests/StorageTest.cs index 2d28534347..983ec70936 100644 --- a/Content.IntegrationTests/Tests/StorageTest.cs +++ b/Content.IntegrationTests/Tests/StorageTest.cs @@ -94,14 +94,13 @@ public async Task TestSufficientSpaceForFill() await Assert.MultipleAsync(async () => { - foreach (var proto in pair.GetPrototypesWithComponent()) + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) { if (proto.HasComponent(compFact)) continue; StorageComponent? storage = null; ItemComponent? item = null; - StorageFillComponent fill = default!; var size = 0; await server.WaitAssertion(() => { @@ -112,7 +111,6 @@ await server.WaitAssertion(() => } proto.TryGetComponent("Item", out item); - fill = (StorageFillComponent) proto.Components[id].Component; size = GetFillSize(fill, false, protoMan, itemSys); }); @@ -179,7 +177,7 @@ public async Task TestSufficientSpaceForEntityStorageFill() var itemSys = entMan.System(); - foreach (var proto in pair.GetPrototypesWithComponent()) + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) { if (proto.HasComponent(compFact)) continue; @@ -192,7 +190,6 @@ await server.WaitAssertion(() => if (entStorage == null) return; - var fill = (StorageFillComponent) proto.Components[id].Component; var size = GetFillSize(fill, true, protoMan, itemSys); Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), $"{proto.ID} storage fill is too large."); diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 946770d6aa..1cdfb82224 100644 --- a/Content.Server/Administration/Managers/BanManager.cs +++ b/Content.Server/Administration/Managers/BanManager.cs @@ -14,13 +14,13 @@ using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Asynchronous; +using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.Administration.Managers; @@ -45,14 +45,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit public const string SawmillId = "admin.bans"; public const string JobPrefix = "Job:"; - private readonly Dictionary> _cachedRoleBans = new(); + private readonly Dictionary> _cachedRoleBans = new(); // Cached ban exemption flags are used to handle private readonly Dictionary _cachedBanExemptions = new(); public void Initialize() { - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; - _netManager.RegisterNetMessage(); _db.SubscribeToNotifications(OnDatabaseNotification); @@ -63,12 +61,23 @@ public void Initialize() private async Task CachePlayerData(ICommonSession player, CancellationToken cancel) { - // Yeah so role ban loading code isn't integrated with exempt flag loading code. - // Have you seen how garbage role ban code code is? I don't feel like refactoring it right now. - var flags = await _db.GetBanExemption(player.UserId, cancel); + + var netChannel = player.Channel; + ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId; + var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false); + + var userRoleBans = new List(); + foreach (var ban in roleBans) + { + userRoleBans.Add(ban); + } + cancel.ThrowIfCancellationRequested(); _cachedBanExemptions[player] = flags; + _cachedRoleBans[player] = userRoleBans; + + SendRoleBans(player); } private void ClearPlayerData(ICommonSession player) @@ -76,25 +85,15 @@ private void ClearPlayerData(ICommonSession player) _cachedBanExemptions.Remove(player); } - private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) - { - if (e.NewStatus != SessionStatus.Connected || _cachedRoleBans.ContainsKey(e.Session.UserId)) - return; - - var netChannel = e.Session.Channel; - ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId; - await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, hwId); - - SendRoleBans(e.Session); - } - private async Task AddRoleBan(ServerRoleBanDef banDef) { banDef = await _db.AddServerRoleBanAsync(banDef); - if (banDef.UserId != null) + if (banDef.UserId != null + && _playerManager.TryGetSessionById(banDef.UserId, out var player) + && _cachedRoleBans.TryGetValue(player, out var cachedBans)) { - _cachedRoleBans.GetOrNew(banDef.UserId.Value).Add(banDef); + cachedBans.Add(banDef); } return true; @@ -102,31 +101,21 @@ private async Task AddRoleBan(ServerRoleBanDef banDef) public HashSet? GetRoleBans(NetUserId playerUserId) { - return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) + if (!_playerManager.TryGetSessionById(playerUserId, out var session)) + return null; + + return _cachedRoleBans.TryGetValue(session, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null; } - private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null) - { - var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false); - - var userRoleBans = new HashSet(); - foreach (var ban in roleBans) - { - userRoleBans.Add(ban); - } - - _cachedRoleBans[userId] = userRoleBans; - } - public void Restart() { // Clear out players that have disconnected. - var toRemove = new List(); + var toRemove = new ValueList(); foreach (var player in _cachedRoleBans.Keys) { - if (!_playerManager.TryGetSessionById(player, out _)) + if (player.Status == SessionStatus.Disconnected) toRemove.Add(player); } @@ -138,7 +127,7 @@ public void Restart() // Check for expired bans foreach (var roleBans in _cachedRoleBans.Values) { - roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime); + roleBans.RemoveAll(ban => DateTimeOffset.Now > ban.ExpirationTime); } } @@ -281,9 +270,9 @@ public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUs var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires)); _chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length))); - if (target != null) + if (target != null && _playerManager.TryGetSessionById(target.Value, out var session)) { - SendRoleBans(target.Value); + SendRoleBans(session); } } @@ -311,10 +300,12 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now)); - if (ban.UserId is { } player && _cachedRoleBans.TryGetValue(player, out var roleBans)) + if (ban.UserId is { } player + && _playerManager.TryGetSessionById(player, out var session) + && _cachedRoleBans.TryGetValue(session, out var roleBans)) { - roleBans.RemoveWhere(roleBan => roleBan.Id == ban.Id); - SendRoleBans(player); + roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id); + SendRoleBans(session); } return $"Pardoned ban with id {banId}"; @@ -322,8 +313,12 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da public HashSet>? GetJobBans(NetUserId playerUserId) { - if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans)) + if (!_playerManager.TryGetSessionById(playerUserId, out var session)) + return null; + + if (!_cachedRoleBans.TryGetValue(session, out var roleBans)) return null; + return roleBans .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) .Select(ban => new ProtoId(ban.Role[JobPrefix.Length..])) @@ -331,19 +326,9 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da } #endregion - public void SendRoleBans(NetUserId userId) - { - if (!_playerManager.TryGetSessionById(userId, out var player)) - { - return; - } - - SendRoleBans(player); - } - public void SendRoleBans(ICommonSession pSession) { - var roleBans = _cachedRoleBans.GetValueOrDefault(pSession.UserId) ?? new HashSet(); + var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List(); var bans = new MsgRoleBans() { Bans = roleBans.Select(o => o.Role).ToList() diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs index b60e0a2535..c11e310a82 100644 --- a/Content.Server/Administration/Managers/IBanManager.cs +++ b/Content.Server/Administration/Managers/IBanManager.cs @@ -47,12 +47,6 @@ public interface IBanManager /// The time at which this role ban was pardoned. public Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime); - /// - /// Sends role bans to the target - /// - /// Player's user ID - public void SendRoleBans(NetUserId userId); - /// /// Sends role bans to the target /// diff --git a/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs b/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs index 5123500239..a60d042fb5 100644 --- a/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs +++ b/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs @@ -1,9 +1,11 @@ +using Robust.Shared.Prototypes; + namespace Content.Server.Atmos.Components { /// /// Used by FixGridAtmos. Entities with this may get magically auto-deleted on map initialization in future. /// - [RegisterComponent] + [RegisterComponent, EntityCategory("Mapping")] public sealed partial class AtmosFixMarkerComponent : Component { // See FixGridAtmos for more details diff --git a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs index 9c7bda839e..9621d6945f 100644 --- a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs @@ -26,9 +26,17 @@ public override bool Condition(EntityEffectBaseArgs args) if(!args.EntityManager.HasComponent(roleId)) continue; - if(!args.EntityManager.TryGetComponent(roleId, out var mindRole) - || mindRole.JobPrototype is null) + if (!args.EntityManager.TryGetComponent(roleId, out var mindRole)) + { + Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}"); continue; + } + + if (mindRole.JobPrototype == null) + { + Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}"); + continue; + } if (Job.Contains(mindRole.JobPrototype.Value)) return true; diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index d6dea4fc0c..af3ff61587 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -192,9 +192,6 @@ public int ReadyPlayerCount() if (!_playerManager.TryGetSessionById(userId, out _)) continue; - if (_banManager.GetRoleBans(userId) == null) - continue; - total++; } @@ -238,11 +235,7 @@ public void StartRound(bool force = false) #if DEBUG DebugTools.Assert(_userDb.IsLoadComplete(session), $"Player was readied up but didn't have user DB data loaded yet??"); #endif - if (_banManager.GetRoleBans(userId) == null) - { - Logger.ErrorS("RoleBans", $"Role bans for player {session} {userId} have not been loaded yet."); - continue; - } + readyPlayers.Add(session); HumanoidCharacterProfile profile; if (_prefsManager.TryGetCachedPreferences(userId, out var preferences)) @@ -342,8 +335,23 @@ public void EndRound(string text = "") RunLevel = GameRunLevel.PostRound; - ShowRoundEndScoreboard(text); - SendRoundEndDiscordMessage(); + try + { + ShowRoundEndScoreboard(text); + } + catch (Exception e) + { + Log.Error($"Error while showing round end scoreboard: {e}"); + } + + try + { + SendRoundEndDiscordMessage(); + } + catch (Exception e) + { + Log.Error($"Error while sending round end Discord message: {e}"); + } } public void ShowRoundEndScoreboard(string text = "") diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 939ab87115..a313b78eaf 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -155,8 +155,8 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _)) { - if (_role.MindHasRole(revMindId, out _, out var role)) - role.Value.Comp.ConvertedCount++; + if (_role.MindHasRole(revMindId, out var role)) + role.Value.Comp2.ConvertedCount++; } } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index bb95b827a7..cd01c964ef 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -516,8 +516,8 @@ public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, Entity _roleSystem.MindAddRole(newMind, "MindRoleGhostMarker"); - if(_roleSystem.MindHasRole(newMind, out _, out var markerRole)) - markerRole.Value.Comp.Name = role.RoleName; + if(_roleSystem.MindHasRole(newMind!, out var markerRole)) + markerRole.Value.Comp2.Name = role.RoleName; _mindSystem.SetUserId(newMind, player.UserId); _mindSystem.TransferTo(newMind, mob); diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs index 76575df2fd..223c2d4a37 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Database; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Inventory.VirtualItem; @@ -130,7 +131,7 @@ public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocat TransformSystem.DropNextTo((entity, itemXform), (uid, userXform)); return true; } - + // drop the item with heavy calculations from their hands and place it at the calculated interaction range position // The DoDrop is handle if there's no drop target DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp); @@ -138,7 +139,7 @@ public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocat // if there's no drop location stop here if (targetDropLocation == null) return true; - + // otherwise, also move dropped item and rotate it properly according to grid/map var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity); var origin = new MapCoordinates(itemPos, itemXform.MapID); @@ -197,7 +198,7 @@ private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, M /// /// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly. /// - public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null) + public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null, bool log = true) { if (!Resolve(uid, ref handsComp)) return; @@ -221,6 +222,9 @@ public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = tr if (doDropInteraction) _interactionSystem.DroppedInteraction(uid, entity); + if (log) + _adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}"); + if (hand == handsComp.ActiveHand) RaiseLocalEvent(entity, new HandDeselectedEvent(uid)); } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs index ae22efcd6a..fc5adfaf15 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Interactions.cs @@ -178,8 +178,8 @@ public bool TryMoveHeldEntityToActiveHand(EntityUid uid, string handName, bool c if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp)) return false; - DoDrop(uid, hand, false, handsComp); - DoPickup(uid, handsComp.ActiveHand, entity, handsComp); + DoDrop(uid, hand, false, handsComp, log:false); + DoPickup(uid, handsComp.ActiveHand, entity, handsComp, log: false); return true; } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs index 6d619460f4..7bc0a8025f 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs @@ -220,7 +220,7 @@ public void PickupOrDrop( /// /// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly. /// - public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null) + public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null, bool log = true) { if (!Resolve(uid, ref hands)) return; @@ -235,7 +235,8 @@ public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsCo return; } - _adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}"); + if (log) + _adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}"); Dirty(uid, hands); diff --git a/Content.Shared/Interaction/Events/ContactInteractionEvent.cs b/Content.Shared/Interaction/Events/ContactInteractionEvent.cs index c9d5fba2ed..7be1c01c4a 100644 --- a/Content.Shared/Interaction/Events/ContactInteractionEvent.cs +++ b/Content.Shared/Interaction/Events/ContactInteractionEvent.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Interaction.Events; /// public sealed class ContactInteractionEvent : HandledEntityEventArgs { - public readonly EntityUid Other; + public EntityUid Other; public ContactInteractionEvent(EntityUid other) { diff --git a/Content.Shared/Interaction/Events/InteractionFailureEvent.cs b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs index a820048104..8670164293 100644 --- a/Content.Shared/Interaction/Events/InteractionFailureEvent.cs +++ b/Content.Shared/Interaction/Events/InteractionFailureEvent.cs @@ -3,5 +3,7 @@ namespace Content.Shared.Interaction.Events; /// /// Raised on the target when failing to pet/hug something. /// +// TODO INTERACTION +// Rename this, or move it to another namespace to make it clearer that this is specific to "petting/hugging" (InteractionPopupSystem) [ByRefEvent] public readonly record struct InteractionFailureEvent(EntityUid User); diff --git a/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs index da4f9e43d7..9395ddc910 100644 --- a/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs +++ b/Content.Shared/Interaction/Events/InteractionSuccessEvent.cs @@ -3,5 +3,7 @@ namespace Content.Shared.Interaction.Events; /// /// Raised on the target when successfully petting/hugging something. /// +// TODO INTERACTION +// Rename this, or move it to another namespace to make it clearer that this is specific to "petting/hugging" (InteractionPopupSystem) [ByRefEvent] public readonly record struct InteractionSuccessEvent(EntityUid User); diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 43dd97762c..6f44d3089d 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -456,8 +456,22 @@ public void UserInteraction( inRangeUnobstructed); } + private bool IsDeleted(EntityUid uid) + { + return TerminatingOrDeleted(uid) || EntityManager.IsQueuedForDeletion(uid); + } + + private bool IsDeleted(EntityUid? uid) + { + //optional / null entities can pass this validation check. I.e., is-deleted returns false for null uids + return uid != null && IsDeleted(uid.Value); + } + public void InteractHand(EntityUid user, EntityUid target) { + if (IsDeleted(user) || IsDeleted(target)) + return; + var complexInteractions = _actionBlockerSystem.CanComplexInteract(user); if (!complexInteractions) { @@ -466,7 +480,8 @@ public void InteractHand(EntityUid user, EntityUid target) checkCanInteract: false, checkUseDelay: true, checkAccess: false, - complexInteractions: complexInteractions); + complexInteractions: complexInteractions, + checkDeletion: false); return; } @@ -479,6 +494,7 @@ public void InteractHand(EntityUid user, EntityUid target) return; } + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target)); // all interactions should only happen when in range / unobstructed, so no range check is needed var message = new InteractHandEvent(user, target); RaiseLocalEvent(target, message, true); @@ -487,18 +503,23 @@ public void InteractHand(EntityUid user, EntityUid target) if (message.Handled) return; + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target)); // Else we run Activate. InteractionActivate(user, target, checkCanInteract: false, checkUseDelay: true, checkAccess: false, - complexInteractions: complexInteractions); + complexInteractions: complexInteractions, + checkDeletion: false); } public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool inRangeUnobstructed) { + if (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)) + return; + if (target != null) { _adminLogger.Add( @@ -514,9 +535,10 @@ public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? targe $"{ToPrettyString(user):user} interacted with *nothing* using {ToPrettyString(used):used}"); } - if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed)) + if (RangedInteractDoBefore(user, used, target, clickLocation, inRangeUnobstructed, checkDeletion: false)) return; + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target)); if (target != null) { var rangedMsg = new RangedInteractEvent(user, used, target.Value, clickLocation); @@ -524,12 +546,12 @@ public void InteractUsingRanged(EntityUid user, EntityUid used, EntityUid? targe // We contact the USED entity, but not the target. DoContactInteraction(user, used, rangedMsg); - if (rangedMsg.Handled) return; } - InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed); + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target)); + InteractDoAfter(user, used, target, clickLocation, inRangeUnobstructed, checkDeletion: false); } protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates) @@ -933,11 +955,18 @@ public bool RangedInteractDoBefore( EntityUid used, EntityUid? target, EntityCoordinates clickLocation, - bool canReach) + bool canReach, + bool checkDeletion = true) { + if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target))) + return false; + var ev = new BeforeRangedInteractEvent(user, used, target, clickLocation, canReach); RaiseLocalEvent(used, ev); + if (!ev.Handled) + return false; + // We contact the USED entity, but not the target. DoContactInteraction(user, used, ev); return ev.Handled; @@ -966,6 +995,9 @@ public bool InteractUsing( bool checkCanInteract = true, bool checkCanUse = true) { + if (IsDeleted(user) || IsDeleted(used) || IsDeleted(target)) + return false; + if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, target)) return false; @@ -977,9 +1009,10 @@ public bool InteractUsing( LogImpact.Low, $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target} using {ToPrettyString(used):used}"); - if (RangedInteractDoBefore(user, used, target, clickLocation, true)) + if (RangedInteractDoBefore(user, used, target, clickLocation, canReach: true, checkDeletion: false)) return true; + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target)); // all interactions should only happen when in range / unobstructed, so no range check is needed var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation); RaiseLocalEvent(target, interactUsingEvent, true); @@ -989,8 +1022,10 @@ public bool InteractUsing( if (interactUsingEvent.Handled) return true; - if (InteractDoAfter(user, used, target, clickLocation, canReach: true)) + if (InteractDoAfter(user, used, target, clickLocation, canReach: true, checkDeletion: false)) return true; + + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target)); return false; } @@ -1004,11 +1039,14 @@ public bool InteractUsing( /// Whether the is in range of the . /// /// True if the interaction was handled. Otherwise, false. - public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach) + public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, EntityCoordinates clickLocation, bool canReach, bool checkDeletion = true) { if (target is { Valid: false }) target = null; + if (checkDeletion && (IsDeleted(user) || IsDeleted(used) || IsDeleted(target))) + return false; + var afterInteractEvent = new AfterInteractEvent(user, used, target, clickLocation, canReach); RaiseLocalEvent(used, afterInteractEvent); DoContactInteraction(user, used, afterInteractEvent); @@ -1024,6 +1062,7 @@ public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, E if (target == null) return false; + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used) && !IsDeleted(target)); var afterInteractUsingEvent = new AfterInteractUsingEvent(user, used, target, clickLocation, canReach); RaiseLocalEvent(target.Value, afterInteractUsingEvent); @@ -1034,9 +1073,7 @@ public bool InteractDoAfter(EntityUid user, EntityUid used, EntityUid? target, E // Contact interactions are currently only used for forensics, so we don't raise used -> target } - if (afterInteractUsingEvent.Handled) - return true; - return false; + return afterInteractUsingEvent.Handled; } #region ActivateItemInWorld @@ -1068,8 +1105,13 @@ public bool InteractionActivate( bool checkCanInteract = true, bool checkUseDelay = true, bool checkAccess = true, - bool? complexInteractions = null) + bool? complexInteractions = null, + bool checkDeletion = true) { + if (checkDeletion && (IsDeleted(user) || IsDeleted(used))) + return false; + + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used)); _delayQuery.TryComp(used, out var delayComponent); if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return false; @@ -1085,21 +1127,32 @@ public bool InteractionActivate( if (checkAccess && !IsAccessible(user, used)) return false; - complexInteractions ??= SupportsComplexInteractions(user); + complexInteractions ??= _actionBlockerSystem.CanComplexInteract(user); var activateMsg = new ActivateInWorldEvent(user, used, complexInteractions.Value); RaiseLocalEvent(used, activateMsg, true); + if (activateMsg.Handled) + { + DoContactInteraction(user, used); + if (!activateMsg.WasLogged) + _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); + + if (delayComponent != null) + _useDelay.TryResetDelay(used, component: delayComponent); + return true; + } + + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used)); var userEv = new UserActivateInWorldEvent(user, used, complexInteractions.Value); RaiseLocalEvent(user, userEv, true); - if (!activateMsg.Handled && !userEv.Handled) + if (!userEv.Handled) return false; - DoContactInteraction(user, used, activateMsg); + DoContactInteraction(user, used); // Still need to call this even without checkUseDelay in case this gets relayed from Activate. if (delayComponent != null) _useDelay.TryResetDelay(used, component: delayComponent); - if (!activateMsg.WasLogged) - _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); + _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); return true; } #endregion @@ -1118,6 +1171,9 @@ public bool UseInHandInteraction( bool checkCanInteract = true, bool checkUseDelay = true) { + if (IsDeleted(user) || IsDeleted(used)) + return false; + _delayQuery.TryComp(used, out var delayComponent); if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) return true; // if the item is on cooldown, we consider this handled. @@ -1138,8 +1194,9 @@ public bool UseInHandInteraction( return true; } + DebugTools.Assert(!IsDeleted(user) && !IsDeleted(used)); // else, default to activating the item - return InteractionActivate(user, used, false, false, false); + return InteractionActivate(user, used, false, false, false, checkDeletion: false); } /// @@ -1164,10 +1221,11 @@ public bool AltInteract(EntityUid user, EntityUid target) public void DroppedInteraction(EntityUid user, EntityUid item) { + if (IsDeleted(user) || IsDeleted(item)) + return; + var dropMsg = new DroppedEvent(user); RaiseLocalEvent(item, dropMsg, true); - if (dropMsg.Handled) - _adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}"); // If the dropper is rotated then use their targetrelativerotation as the drop rotation var rotation = Angle.Zero; @@ -1314,15 +1372,20 @@ public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, HandledEntityE if (uidB == null || args?.Handled == false) return; - // Entities may no longer exist (banana was eaten, or human was exploded)? - if (!Exists(uidA) || !Exists(uidB)) - return; + DebugTools.AssertNotEqual(uidA, uidB.Value); - if (Paused(uidA) || Paused(uidB.Value)) + if (!TryComp(uidA, out MetaDataComponent? metaA) || metaA.EntityPaused) return; - RaiseLocalEvent(uidA, new ContactInteractionEvent(uidB.Value)); - RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); + if (!TryComp(uidB, out MetaDataComponent? metaB) || metaB.EntityPaused) + return ; + + // TODO Struct event + var ev = new ContactInteractionEvent(uidB.Value); + RaiseLocalEvent(uidA, ev); + + ev.Other = uidA; + RaiseLocalEvent(uidB.Value, ev); } diff --git a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs index 75a77ae155..30f607adf7 100644 --- a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs @@ -30,7 +30,7 @@ public override bool Check(IEntityManager entManager, if (!Inverted) { - reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-young", + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-too-young", ("age", RequiredAge))); if (profile.Age < RequiredAge) @@ -38,7 +38,7 @@ public override bool Check(IEntityManager entManager, } else { - reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-old", + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-too-old", ("age", RequiredAge))); if (profile.Age > RequiredAge) diff --git a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs index 56b7d8ba81..78c6bd2517 100644 --- a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs @@ -15,7 +15,7 @@ public sealed partial class DepartmentTimeRequirement : JobRequirement /// Which department needs the required amount of time. /// [DataField(required: true)] - public ProtoId Department = default!; + public ProtoId Department; /// /// How long (in seconds) this requirement is. @@ -47,7 +47,9 @@ public override bool Check(IEntityManager entManager, playtime += otherTime; } - var deptDiff = Time.TotalMinutes - playtime.TotalMinutes; + var deptDiffSpan = Time - playtime; + var deptDiff = deptDiffSpan.TotalMinutes; + var formattedDeptDiff = deptDiffSpan.ToString(Loc.GetString("role-timer-time-format")); var nameDepartment = "role-timer-department-unknown"; if (protoManager.TryIndex(Department, out var departmentIndexed)) @@ -62,7 +64,7 @@ public override bool Check(IEntityManager entManager, reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-insufficient", - ("time", Math.Ceiling(deptDiff)), + ("time", formattedDeptDiff), ("department", Loc.GetString(nameDepartment)), ("departmentColor", department.Color.ToHex()))); return false; @@ -72,7 +74,7 @@ public override bool Check(IEntityManager entManager, { reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-too-high", - ("time", -deptDiff), + ("time", formattedDeptDiff), ("department", Loc.GetString(nameDepartment)), ("departmentColor", department.Color.ToHex()))); return false; diff --git a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs index acbb8f2b4d..ed985cadfb 100644 --- a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs @@ -25,7 +25,9 @@ public override bool Check(IEntityManager entManager, reason = new FormattedMessage(); var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall); - var overallDiff = Time.TotalMinutes - overallTime.TotalMinutes; + var overallDiffSpan = Time - overallTime; + var overallDiff = overallDiffSpan.TotalMinutes; + var formattedOverallDiff = overallDiffSpan.ToString(Loc.GetString("role-timer-time-format")); if (!Inverted) { @@ -34,14 +36,14 @@ public override bool Check(IEntityManager entManager, reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-overall-insufficient", - ("time", Math.Ceiling(overallDiff)))); + ("time", formattedOverallDiff))); return false; } if (overallDiff <= 0 || overallTime >= Time) { reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high", - ("time", -overallDiff))); + ("time", formattedOverallDiff))); return false; } diff --git a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs index 658db95ab5..23498ab91a 100644 --- a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs @@ -17,7 +17,7 @@ public sealed partial class RoleTimeRequirement : JobRequirement /// What particular role they need the time requirement with. /// [DataField(required: true)] - public ProtoId Role = default!; + public ProtoId Role; /// [DataField(required: true)] @@ -34,7 +34,9 @@ public override bool Check(IEntityManager entManager, string proto = Role; playTimes.TryGetValue(proto, out var roleTime); - var roleDiff = Time.TotalMinutes - roleTime.TotalMinutes; + var roleDiffSpan = Time - roleTime; + var roleDiff = roleDiffSpan.TotalMinutes; + var formattedRoleDiff = roleDiffSpan.ToString(Loc.GetString("role-timer-time-format")); var departmentColor = Color.Yellow; if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem)) @@ -52,7 +54,7 @@ public override bool Check(IEntityManager entManager, reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-insufficient", - ("time", Math.Ceiling(roleDiff)), + ("time", formattedRoleDiff), ("job", Loc.GetString(proto)), ("departmentColor", departmentColor.ToHex()))); return false; @@ -62,7 +64,7 @@ public override bool Check(IEntityManager entManager, { reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-too-high", - ("time", -roleDiff), + ("time", formattedRoleDiff), ("job", Loc.GetString(proto)), ("departmentColor", departmentColor.ToHex()))); return false; diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 94447a5af4..8a4733c834 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -103,7 +103,6 @@ public bool TryGetPrimaryDepartment(string jobProto, [NotNullWhen(true)] out Dep public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { - MindRoleComponent? comp = null; if (mindId is null) return false; @@ -112,9 +111,7 @@ public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) if (role is null) return false; - comp = role.Value.Comp; - - return (comp.JobPrototype == prototypeId); + return role.Value.Comp1.JobPrototype == prototypeId; } public bool MindTryGetJob( @@ -124,7 +121,7 @@ public bool MindTryGetJob( prototype = null; MindTryGetJobId(mindId, out var protoId); - return (_prototypes.TryIndex(protoId, out prototype) || prototype is not null); + return _prototypes.TryIndex(protoId, out prototype) || prototype is not null; } public bool MindTryGetJobId( @@ -137,9 +134,9 @@ public bool MindTryGetJobId( return false; if (_roles.MindHasRole(mindId.Value, out var role)) - job = role.Value.Comp.JobPrototype; + job = role.Value.Comp1.JobPrototype; - return (job is not null); + return job is not null; } /// diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/MindRoleComponent.cs index 38b83a8b3f..a3dd0b3bc6 100644 --- a/Content.Shared/Roles/MindRoleComponent.cs +++ b/Content.Shared/Roles/MindRoleComponent.cs @@ -42,6 +42,8 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent public ProtoId? JobPrototype { get; set; } } +// Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless? +[EntityCategory("Roles")] public abstract partial class BaseMindRoleComponent : Component { diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 925f61e7c7..00271693ab 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Roles; @@ -92,19 +93,18 @@ public void MindAddJobRole(EntityUid mindId, bool silent = false, string? jobPrototype = null) { + if (!Resolve(mindId, ref mind)) + return; + // Can't have someone get paid for two jobs now, can we - if (MindHasRole(mindId, out var jobRole) - && jobRole.Value.Comp.JobPrototype != jobPrototype) + if (MindHasRole((mindId, mind), out var jobRole) + && jobRole.Value.Comp1.JobPrototype != jobPrototype) { - Resolve(mindId, ref mind); - if (mind is not null) - { - _adminLogger.Add(LogType.Mind, - LogImpact.Low, - $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'"); - } + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'"); - jobRole.Value.Comp.JobPrototype = jobPrototype; + jobRole.Value.Comp1.JobPrototype = jobPrototype; } else MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype); @@ -146,11 +146,12 @@ private void MindAddRoleDo(EntityUid mindId, { mindRoleComp.JobPrototype = jobPrototype; EnsureComp(mindRoleId); + DebugTools.AssertNull(mindRoleComp.AntagPrototype); + DebugTools.Assert(!mindRoleComp.Antag); + DebugTools.Assert(!mindRoleComp.ExclusiveAntag); } - if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag) - antagonist = true; - + antagonist |= mindRoleComp.Antag; mind.MindRoles.Add(mindRoleId); var mindEv = new MindRoleAddedEvent(silent); @@ -182,51 +183,55 @@ private void MindAddRoleDo(EntityUid mindId, /// /// Removes all instances of a specific role from this mind. /// - /// The mind to remove the role from. + /// The mind to remove the role from. /// The type of the role to remove. - /// Thrown if the mind does not exist or does not have this role. - /// Returns False if there was something wrong with the mind or the removal. True if successful> - public bool MindRemoveRole(EntityUid mindId) where T : IComponent + /// Returns false if the role did not exist. True if successful> + public bool MindRemoveRole(Entity mind) where T : IComponent { - if (!TryComp(mindId, out var mind) ) - throw new ArgumentException($"{mindId} does not exist or does not have mind component"); + if (typeof(T) == typeof(MindRoleComponent)) + throw new InvalidOperationException(); + + if (!Resolve(mind.Owner, ref mind.Comp)) + return false; var found = false; var antagonist = false; var delete = new List(); - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { if (!HasComp(role)) continue; - var roleComp = Comp(role); - antagonist = roleComp.Antag; - _entityManager.DeleteEntity(role); + if (!TryComp(role, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}"); + continue; + } + antagonist |= roleComp.Antag | roleComp.ExclusiveAntag; + _entityManager.DeleteEntity(role); delete.Add(role); found = true; - } + if (!found) + return false; + foreach (var role in delete) { - mind.MindRoles.Remove(role); + mind.Comp.MindRoles.Remove(role); } - if (!found) + if (mind.Comp.OwnedEntity != null) { - throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}"); + var message = new RoleRemovedEvent(mind.Owner, mind.Comp, antagonist); + RaiseLocalEvent(mind.Comp.OwnedEntity.Value, message, true); } - 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 {ToPrettyString(mind.OwnedEntity)}"); + $"All roles of type '{typeof(T).Name}' removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}"); + return true; } @@ -238,16 +243,14 @@ public bool MindRemoveRole(EntityUid mindId) where T : IComponent /// True if the role existed and was removed public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent { - if (!MindHasRole(mindId)) - { - Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role "); - return false; - } - if (typeof(T) == typeof(MindRoleComponent)) return false; - return MindRemoveRole(mindId); + if (MindRemoveRole(mindId)) + return true; + + Log.Warning($"Failed to remove role {typeof(T)} from {ToPrettyString(mindId)} : mind does not have role "); + return false; } /// @@ -259,30 +262,29 @@ public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent /// The Mind Role entity component /// The Mind Role's entity component for T /// True if the role is found - public bool MindHasRole(EntityUid mindId, - [NotNullWhen(true)] out Entity? role, - [NotNullWhen(true)] out Entity? roleT) where T : IComponent + public bool MindHasRole(Entity mind, + [NotNullWhen(true)] out Entity? role) where T : IComponent { role = null; - roleT = null; - - if (!TryComp(mindId, out var mind)) + if (!Resolve(mind.Owner, ref mind.Comp)) return false; - var found = false; - - foreach (var roleEnt in mind.MindRoles) + foreach (var roleEnt in mind.Comp.MindRoles) { - if (!HasComp(roleEnt)) + if (!TryComp(roleEnt, out T? tcomp)) continue; - role = (roleEnt,Comp(roleEnt)); - roleT = (roleEnt,Comp(roleEnt)); - found = true; - break; + if (!TryComp(roleEnt, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}"); + continue; + } + + role = (roleEnt, roleComp, tcomp); + return true; } - return found; + return false; } /// @@ -317,7 +319,13 @@ public bool MindHasRole(EntityUid mindId, if (!HasComp(roleEnt, type)) continue; - role = (roleEnt,Comp(roleEnt)); + if (!TryComp(roleEnt, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}"); + continue; + } + + role = (roleEnt, roleComp); found = true; break; } @@ -325,20 +333,6 @@ public bool MindHasRole(EntityUid mindId, return found; } - /// - /// Finds the first mind role of a specific type on a mind entity. - /// Outputs an entity component for the mind role's MindRoleComponent - /// - /// The mind entity - /// The Mind Role entity component - /// The type of the role to find. - /// True if the role is found - public bool MindHasRole(EntityUid mindId, - [NotNullWhen(true)] out Entity? role) where T : IComponent - { - return MindHasRole(mindId, out role, out _); - } - /// /// Finds the first mind role of a specific type on a mind entity. /// @@ -347,7 +341,7 @@ public bool MindHasRole(EntityUid mindId, /// True if the role is found public bool MindHasRole(EntityUid mindId) where T : IComponent { - return MindHasRole(mindId, out _, out _); + return MindHasRole(mindId, out _); } //TODO: Delete this later @@ -374,28 +368,31 @@ public bool MindHasRole(EntityUid mindId) where T : IComponent /// /// Reads all Roles of a mind Entity and returns their data as RoleInfo /// - /// The mind entity + /// The mind entity /// RoleInfo list - public List MindGetAllRoleInfo(EntityUid mindId) + public List MindGetAllRoleInfo(Entity mind) { var roleInfo = new List(); - if (!TryComp(mindId, out var mind)) + if (!Resolve(mind.Owner, ref mind.Comp)) return roleInfo; - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { var valid = false; var name = "game-ticker-unknown-role"; var prototype = ""; - string? playTimeTracker = null; + string? playTimeTracker = null; - var comp = Comp(role); - if (comp.AntagPrototype is not null) + if (!TryComp(role, out MindRoleComponent? comp)) { - prototype = comp.AntagPrototype; + Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}"); + continue; } + if (comp.AntagPrototype is not null) + prototype = comp.AntagPrototype; + if (comp.JobPrototype is not null && comp.AntagPrototype is null) { prototype = comp.JobPrototype; @@ -429,7 +426,7 @@ public List MindGetAllRoleInfo(EntityUid mindId) } if (valid) - roleInfo.Add(new RoleInfo(name, comp.Antag || comp.ExclusiveAntag , playTimeTracker, prototype)); + roleInfo.Add(new RoleInfo(name, comp.Antag, playTimeTracker, prototype)); } return roleInfo; } @@ -442,12 +439,9 @@ public List MindGetAllRoleInfo(EntityUid mindId) public bool MindIsAntagonist(EntityUid? mindId) { if (mindId is null) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; - } - return CheckAntagonistStatus(mindId.Value).Item1; + return CheckAntagonistStatus(mindId.Value).Antag; } /// @@ -458,37 +452,28 @@ public bool MindIsAntagonist(EntityUid? mindId) public bool MindIsExclusiveAntagonist(EntityUid? mindId) { if (mindId is null) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; - } - return CheckAntagonistStatus(mindId.Value).Item2; + return CheckAntagonistStatus(mindId.Value).ExclusiveAntag; } - private (bool, bool) CheckAntagonistStatus(EntityUid mindId) + public (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity mind) { - if (!TryComp(mindId, out var mind)) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found"); + if (!Resolve(mind.Owner, ref mind.Comp)) return (false, false); - } var antagonist = false; var exclusiveAntag = false; - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { if (!TryComp(role, out var roleComp)) { - //If this ever shows up outside of an integration test, then we need to look into this further. - Log.Warning($"Mind Role Entity {role} does not have MindRoleComponent!"); + Log.Error($"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|"); continue; } - if (roleComp.Antag || exclusiveAntag) - antagonist = true; - if (roleComp.ExclusiveAntag) - exclusiveAntag = true; + antagonist |= roleComp.Antag; + exclusiveAntag |= roleComp.ExclusiveAntag; } return (antagonist, exclusiveAntag); @@ -504,6 +489,9 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent _audio.PlayGlobal(sound, mind.Session); } + // TODO ROLES Change to readonly. + // Passing around a reference to a prototype's hashset makes me uncomfortable because it might be accidentally + // mutated. public HashSet? GetJobRequirement(JobPrototype job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) @@ -512,6 +500,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return job.Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetJobRequirement(ProtoId job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req)) @@ -520,6 +509,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return _prototypes.Index(job).Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetAntagRequirement(ProtoId antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req)) @@ -528,6 +518,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return _prototypes.Index(antag).Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetAntagRequirement(AntagPrototype antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req)) diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 275c32bf55..25b7480517 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,47 +1,4 @@ Entries: -- author: themias - changes: - - message: Added envelopes to the PTech and bureaucracy crate - type: Add - id: 7005 - time: '2024-07-30T01:49:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30298 -- author: TheKittehJesus - changes: - - message: The recipe for chow mein, egg-fried rice, and both brownies now use liquid - egg instead of a whole egg. - type: Tweak - - message: Cake batter now also requires 5u of milk - type: Tweak - id: 7006 - time: '2024-07-30T02:14:11.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30262 -- author: Plykiya - changes: - - message: Wearing something that covers your head will prevent your hair from being - cut. - type: Add - - message: You now see a popup when your hair is being altered. - type: Add - - message: The doafter for altering other people's hair now takes seven seconds. - type: Tweak - id: 7007 - time: '2024-07-30T02:17:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30366 -- author: Cojoke-dot - changes: - - message: Hamlet and other ghost rolls can now spin when they enter combat mode - type: Fix - id: 7008 - time: '2024-07-30T02:48:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30478 -- author: themias - changes: - - message: Fixed the ACC wire not appearing in vending machine wire layouts - type: Fix - id: 7009 - time: '2024-07-30T03:04:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30453 - author: Blackern5000 changes: - message: The defibrillator has been recolored slightly @@ -3954,3 +3911,38 @@ id: 7504 time: '2024-10-13T08:22:05.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32779 +- author: SlamBamActionman + changes: + - message: Added a new Safety MothTM poster about being SSD! + type: Add + id: 7505 + time: '2024-10-13T11:25:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32736 +- author: K-Dynamic + changes: + - message: Added rainbow lizard plushies, which can be found in arcade machines. + type: Add + id: 7506 + time: '2024-10-13T22:51:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32564 +- author: PJB3005 + changes: + - message: Fixes the client sometimes not being aware of active role bans. + type: Fix + id: 7507 + time: '2024-10-14T03:30:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32725 +- author: OnyxTheBrave + changes: + - message: Fixed the Industrial Reagent Grinder's visuals + type: Fix + id: 7508 + time: '2024-10-14T03:53:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32758 +- author: irismessage + changes: + - message: Made role requirements display in a more understandable format. + type: Tweak + id: 7509 + time: '2024-10-14T03:54:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32806 diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index b18c4646ed..6fd37c8476 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 12rabbits, 13spacemen, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, Ablankmann, abregado, Absolute-Potato, achookh, Acruid, actioninja, actually-reb, ada-please, adamsong, Adeinitas, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, Afrokada, Agoichi, Ahion, aiden, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexumandxgabriel08x, Alithsko, ALMv1, Alpha-Two, AlphaQwerty, Altoids1, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, Appiah, ar4ill, ArchPigeon, ArchRBX, areitpog, Arendian, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, AruMoon, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, avghdev, Awlod, AzzyIsNotHere, BackeTako, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, bellwetherlogic, benev0, benjamin-burges, BGare, bhenrich, bhespiritu, bibbly, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlitzTheSquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, BombasterDS, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, capnsockless, CaptainSqrBeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, CatTheSystem, Centronias, chairbender, Charlese2, ChaseFlorom, chavonadelal, Cheackraze, cheesePizza2, cheeseplated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, civilCornball, Clement-O, clyf, Clyybber, CMDR-Piboy314, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, corentt, CormosLemming, crazybrain23, creadth, CrigCrag, croilbird, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DakotaGay, DamianX, DangerRevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, DeathCamel58, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinoWattz, DisposableCrewmember42, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DoctorBeard, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, eoineoineoin, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, FirinMaLazors, Fishfish458, FL-OZ, Flareguy, flashgnash, FluffiestFloof, FluidRock, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, Fouin, foxhorn, freeman2651, freeze2222, Froffy025, Fromoriss, froozigiusz, FrostMando, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, Gaxeer, gbasood, Geekyhobo, genderGeometries, GeneralGaws, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GNF54, godisdeadLOL, goet, Goldminermac, Golinth, GoodWheatley, Gorox221, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Henry, HerCoyote23, hitomishirichan, hiucko, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hreno, hubismal, Hugal31, Huxellberger, Hyenh, i-justuser-i, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imrenq, imweax, indeano, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, ItsMeThom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jaskanbe, JasperJRoth, jerryimmouse, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, JoeHammad1844, joelsgp, JohnGinnane, johnku1, Jophire, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, KaiShibaa, kalane15, kalanosh, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, Kimpes, KingFroozy, kira-er, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, koteq, KrasnoshchekovPavel, Krunklehorn, Kukutis96513, Kupie, kxvvv, kyupolaris, kzhanik, lajolico, Lamrr, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonsfriedrich, LeoSantich, LetterN, lettern, Level10Cybermancer, LEVELcat, lever1209, Lgibb18, lgruthes, LightVillet, liltenhead, LinkUyx, LittleBuilderJane, LittleNorthStar, lizelive, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, luringens, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M3739, mac6na6na, MACMAN2003, Macoron, Magicalus, magmodius, MagnusCrowe, malchanceux, MaloTV, ManelNavola, Mangohydra, marboww, Markek1, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, MemeProof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, MilenVolf, MilonPL, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterMecky, Mith-randalf, MjrLandWhale, mkanke-real, MLGTASTICa, moderatelyaware, modern-nm, mokiros, Moneyl, Moomoobeef, moony, Morb0, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, MureixloI, musicmanvr, MWKane, Myakot, Myctai, N3X15, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, NIXC, NkoKirkto, nmajask, noctyrnal, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, och-och, OctoRocket, OldDanceJacket, OliverOtter, onoira, OnyxTheBrave, OrangeMoronage9622, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paigemaeforrest, pali6, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, Phill101, phunnyguy, pigeonpeas, PilgrimViis, Pill-U, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, ProfanedBane, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuceTint, PuroSlavKing, PursuitInAshes, Putnam3145, quatre, QueerNB, QuietlyWhisper, qwerltaz, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, RiceMar1244, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, RobbyTheFish, Rockdtben, Rohesie, rok-povsic, rolfero, RomanNovo, rosieposieeee, Roudenn, router, RumiTiger, S1rFl0, S1ss3l, Saakra, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, SaphireLattice, SapphicOverload, Sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, scrato, Scribbles0, scruq445, scuffedjays, ScumbagDog, Segonist, sephtasm, Serkket, sewerpig, sh18rw, ShadeAware, ShadowCommander, Shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SignalWalker, siigiil, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, Slyfox333, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, Soydium, SpaceManiac, SpaceyLady, spanky-spanky, spartak, SpartanKadence, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, Stealthbomber16, stellar-novas, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, superjj18, Supernorn, SweptWasTaken, Sybil, SYNCHRONIC, Szunti, Tainakov, takemysoult, TaralGit, Taran, taurie, Tayrtahn, tday93, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, TGODiamond, TGRCdev, tgrkzus, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, theashtronaut, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheIntoxicatedCat, thekilk, themias, Theomund, theOperand, TherapyGoth, TheShuEd, thetolbean, thevinter, TheWaffleJesus, Thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, Titian3, tk-a369, tkdrg, tmtmtl30, TokenStyle, Tollhouse, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, Tornado-Technology, tosatur, TotallyLemon, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, TyAshley, Tyler-IN, Tyzemol, UbaserB, ubis1, UBlueberry, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, Unkn0wnGh0st333, unusualcrow, Uriende, UristMcDorf, user424242420, Vaaankas, valentfingerov, Varen, VasilisThePikachu, veliebm, VelonacepsCalyxEggs, veprolet, veritable-calamity, Veritius, Vermidia, vero5123, Verslebas, VigersRay, violet754, Visne, VMSolidus, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, vulppine, wafehling, Warentan, WarMechanic, Watermelon914, waylon531, weaversam8, wertanchik, whateverusername0, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, wrexbe, wtcwr68, xkreksx, xprospero, xRiriq, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, Yousifb26, youtissoum, YuriyKiss, zach-hill, Zadeon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, zerorulez, ZeWaka, zionnBE, ZNixian, ZoldorfTheWizard, Zonespace27, Zumorica, Zylofan, Zymem, zzylex +0x6273, 12rabbits, 13spacemen, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 3nderall, 4310v343k, 4dplanner, 612git, 778b, Ablankmann, abregado, Absolute-Potato, achookh, Acruid, actioninja, actually-reb, ada-please, adamsong, Adeinitas, Admiral-Obvious-001, adrian, Adrian16199, Ady4ik, Aerocrux, Aeshus, Aexolott, Aexxie, africalimedrop, Afrokada, Agoichi, Ahion, aiden, AJCM-git, AjexRose, Alekshhh, alexkar598, AlexMorgan3817, alexumandxgabriel08x, Alithsko, ALMv1, Alpha-Two, AlphaQwerty, Altoids1, amylizzle, ancientpower, Andre19926, AndrewEyeke, AndreyCamper, Anzarot121, Appiah, ar4ill, ArchPigeon, ArchRBX, areitpog, Arendian, arimah, Arkanic, ArkiveDev, armoks, Arteben, ArthurMousatov, ArtisticRoomba, artur, AruMoon, ArZarLordOfMango, as334, AsikKEsel, AsnDen, asperger-sind, aspiringLich, astriloqua, august-sun, AutoOtter, avghdev, Awlod, AzzyIsNotHere, BackeTako, BananaFlambe, Baptr0b0t, BasedUser, beck-thompson, bellwetherlogic, benev0, benjamin-burges, BGare, bhenrich, bhespiritu, bibbly, BIGZi0348, bingojohnson, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlitzTheSquishy, bloodrizer, Bloody2372, blueDev2, Boaz1111, BobdaBiscuit, BobTheSleder, boiled-water-tsar, BombasterDS, botanySupremist, brainfood1183, BramvanZijp, Brandon-Huu, BriBrooo, Bright0, brndd, bryce0110, BubblegumBlue, buletsponge, buntobaggins, bvelliquette, byondfuckery, c0rigin, c4llv07e, CaasGit, Caconym27, Calecute, Callmore, capnsockless, CaptainSqrBeard, Carbonhell, Carolyn3114, Carou02, carteblanche4me, CatTheSystem, Centronias, chairbender, Charlese2, charlie, ChaseFlorom, chavonadelal, Cheackraze, cheesePizza2, cheeseplated, Chief-Engineer, chillyconmor, christhirtle, chromiumboy, Chronophylos, Chubbicous, Chubbygummibear, Ciac32, civilCornball, Clement-O, clyf, Clyybber, CMDR-Piboy314, cohanna, Cohnway, Cojoke-dot, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, CookieMasterT, coolboy911, coolmankid12345, Coolsurf6, corentt, CormosLemming, crazybrain23, creadth, CrigCrag, croilbird, Crotalus, CrudeWax, CrzyPotato, cutemoongod, Cyberboss, d34d10cc, DadeKuma, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, DanSAussieITS, Daracke, Darkenson, DawBla, Daxxi3, dch-GH, de0rix, Deahaka, dean, DEATHB4DEFEAT, DeathCamel58, Deatherd, deathride58, DebugOk, Decappi, Decortex, Deeeeja, deepdarkdepths, degradka, Delete69, deltanedas, DenisShvalov, DerbyX, derek, dersheppard, Deserty0, Detintinto, DevilishMilk, dexlerxd, dffdff2423, DieselMohawk, digitalic, Dimastra, DinoWattz, DisposableCrewmember42, DjfjdfofdjfjD, doc-michael, docnite, Doctor-Cpu, DoctorBeard, DogZeroX, dolgovmi, dontbetank, Doomsdrayk, Doru991, DoubleRiceEddiedd, DoutorWhite, dragonryan06, drakewill-CRL, Drayff, dreamlyjack, DrEnzyme, dribblydrone, DrMelon, drongood12, DrSingh, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, dukevanity, duskyjay, Dutch-VanDerLinde, dvir001, Dynexust, Easypoller, echo, eclips_e, eden077, EEASAS, Efruit, efzapa, Ekkosangen, ElectroSR, elsie, elthundercloud, Elysium206, Emisse, emmafornash, EmoGarbage404, Endecc, eoineoineoin, eris, erohrs2, ERORR404V1, Errant-4, ertanic, esguard, estacaoespacialpirata, eugene, exincore, exp111, f0x-n3rd, FacePluslll, Fahasor, FairlySadPanda, FATFSAAM2, Feluk6174, ficcialfaint, Fiftyllama, Fildrance, FillerVK, FinnishPaladin, FirinMaLazors, Fishfish458, FL-OZ, Flareguy, flashgnash, FluffiestFloof, FluffMe, FluidRock, foboscheshir, FoLoKe, fooberticus, ForestNoises, forgotmyotheraccount, forkeyboards, forthbridge, Fortune117, Fouin, foxhorn, freeman2651, freeze2222, Froffy025, Fromoriss, froozigiusz, FrostMando, FungiFellow, FunTust, Futuristic-OK, GalacticChimp, Gaxeer, gbasood, Geekyhobo, genderGeometries, GeneralGaws, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, githubuser508, gituhabu, GlassEclipse, GNF54, godisdeadLOL, goet, Goldminermac, Golinth, GoodWheatley, Gorox221, graevy, GraniteSidewalk, GreaseMonk, greenrock64, GreyMario, GTRsound, gusxyz, Gyrandola, h3half, hamurlik, Hanzdegloker, HappyRoach, Hardly3D, harikattar, he1acdvv, Hebi, Henry, HerCoyote23, hitomishirichan, hiucko, Hmeister-fake, Hmeister-real, Hobbitmax, hobnob, HoidC, Holinka4ever, holyssss, HoofedEar, Hoolny, hord-brayden, Hreno, hubismal, Hugal31, Huxellberger, Hyenh, i-justuser-i, iacore, IamVelcroboy, Ian321, icekot8, icesickleone, iczero, iglov, IgorAnt028, igorsaux, ike709, illersaver, Illiux, Ilushkins33, Ilya246, IlyaElDunaev, imrenq, imweax, indeano, Injazz, Insineer, IntegerTempest, Interrobang01, IProduceWidgets, irismessage, ItsMeThom, Itzbenz, iztokbajcar, Jackal298, Jackrost, jacksonzck, Jackw2As, jacob, jamessimo, janekvap, Jark255, Jaskanbe, JasperJRoth, JerryImMouse, jerryimmouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JimGamemaster, jimmy12or, JIPDawg, jjtParadox, JoeHammad1844, JohnGinnane, johnku1, Jophire, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, justdie12, justin, justintether, JustinTrotter, justtne, K-Dynamic, k3yw, Kadeo64, Kaga-404, KaiShibaa, kalane15, kalanosh, Kanashi-Panda, katzenminer, kbailey-git, Keelin, Keer-Sar, KEEYNy, keikiru, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, Kimpes, KingFroozy, kira-er, Kirillcas, Kirus59, Kistras, Kit0vras, KittenColony, klaypexx, Kmc2000, Ko4ergaPunk, kognise, kokoc9n, komunre, KonstantinAngelov, kosticia, koteq, KrasnoshchekovPavel, Krunklehorn, Kupie, kxvvv, kyupolaris, kzhanik, lajolico, Lamrr, LankLTE, laok233, lapatison, larryrussian, lawdog4817, Lazzi0706, leander-0, leonardo-dabepis, leonsfriedrich, LeoSantich, lettern, LetterN, Level10Cybermancer, LEVELcat, lever1209, Lgibb18, lgruthes, LightVillet, liltenhead, LinkUyx, LittleBuilderJane, LittleNorthStar, lizelive, localcc, lokachop, Lomcastar, LordCarve, LordEclipse, LucasTheDrgn, luckyshotpictures, LudwigVonChesterfield, luizwritescode, Lukasz825700516, luminight, lunarcomets, luringens, lvvova1, Lyndomen, lyroth001, lzimann, lzk228, M3739, mac6na6na, MACMAN2003, Macoron, Magicalus, magmodius, MagnusCrowe, malchanceux, MaloTV, ManelNavola, Mangohydra, marboww, Markek1, Matz05, max, MaxNox7, maylokana, MehimoNemo, MeltedPixel, MemeProof, MendaxxDev, Menshin, Mephisto72, MerrytheManokit, Mervill, metalgearsloth, MetalSage, MFMessage, mhamsterr, michaelcu, micheel665, MilenVolf, MilonPL, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MissKay1994, MisterMecky, Mith-randalf, MjrLandWhale, mkanke-real, MLGTASTICa, moderatelyaware, modern-nm, mokiros, Moneyl, Moomoobeef, moony, Morb0, mr-bo-jangles, Mr0maks, MrFippik, mrrobdemo, MureixloI, musicmanvr, MWKane, Myakot, Myctai, N3X15, nails-n-tape, Nairodian, Naive817, NakataRin, namespace-Memory, Nannek, NazrinNya, neutrino-laser, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, NIXC, NkoKirkto, nmajask, noctyrnal, nok-ko, NonchalantNoob, NoobyLegion, Nopey, not-gavnaed, notafet, notquitehadouken, NotSoDana, noudoit, noverd, NuclearWinter, nukashimika, nuke-haus, NULL882, nullarmo, nyeogmi, Nylux, Nyranu, och-och, OctoRocket, OldDanceJacket, OliverOtter, onoira, OnyxTheBrave, OrangeMoronage9622, osjarw, Ostaf, othymer, OttoMaticode, Owai-Seek, packmore, paigemaeforrest, pali6, Pangogie, panzer-iv1, partyaddict, patrikturi, PaulRitter, peccneck, Peptide90, peptron1, PeterFuto, PetMudstone, pewter-wiz, Pgriha, Phantom-Lily, Phill101, phunnyguy, PilgrimViis, Pill-U, Pireax, Pissachu, pissdemon, PixeltheAertistContrib, PixelTheKermit, PJB3005, Plasmaguy, plinyvic, Plykiya, poeMota, pofitlo, pointer-to-null, pok27, PolterTzi, PoorMansDreams, PopGamer45, portfiend, potato1234x, PotentiallyTom, ProfanedBane, ProPandaBear, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuceTint, PuroSlavKing, PursuitInAshes, Putnam3145, quatre, QueerNB, QuietlyWhisper, qwerltaz, RadioMull, Radosvik, Radrark, Rainbeon, Rainfey, Raitononai, Ramlik, RamZ, randy10122, Rane, Ranger6012, Rapidgame7, ravage123321, rbertoche, Redfire1331, Redict, RedlineTriad, redmushie, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, Renlou, retequizzle, RiceMar1244, rich-dunne, RieBi, riggleprime, RIKELOLDABOSS, rinary1, Rinkashikachi, riolume, RobbyTheFish, Rockdtben, Rohesie, rok-povsic, rolfero, RomanNovo, rosieposieeee, Roudenn, router, RumiTiger, S1rFl0, S1ss3l, Saakra, Sadie-silly, saga3152, saintmuntzer, Salex08, sam, samgithubaccount, SaphireLattice, SapphicOverload, Sarahon, sativaleanne, SaveliyM360, sBasalto, ScalyChimp, ScarKy0, scrato, Scribbles0, scrivoy, scruq445, scuffedjays, ScumbagDog, Segonist, sephtasm, Serkket, sewerpig, sh18rw, ShadeAware, ShadowCommander, Shadowtheprotogen546, shaeone, shampunj, shariathotpatrol, SignalWalker, siigiil, Simyon264, sirdragooon, Sirionaut, Sk1tch, SkaldetSkaeg, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, Slyfox333, snebl, snicket, sniperchance, Snowni, snowsignal, SolidusSnek, SonicHDC, SoulFN, SoulSloth, Soundwavesghost, southbridge-fur, Soydium, SpaceLizardSky, SpaceManiac, SpaceyLady, spanky-spanky, spartak, SpartanKadence, SpeltIncorrectyl, Spessmann, SphiraI, SplinterGP, spoogemonster, sporekto, ssdaniel24, stalengd, stanberytrask, Stanislav4ix, StanTheCarpenter, Stealthbomber16, stellar-novas, stopbreaking, stopka-html, StrawberryMoses, Stray-Pyramid, strO0pwafel, Strol20, StStevens, Subversionary, sunbear-dev, superjj18, Supernorn, SweptWasTaken, Sybil, SYNCHRONIC, Szunti, Tainakov, takemysoult, TaralGit, Taran, taurie, Tayrtahn, tday93, TekuNut, telyonok, TemporalOroboros, tentekal, terezi4real, Terraspark4941, texcruize, TGODiamond, TGRCdev, tgrkzus, ThatOneGoblin25, thatrandomcanadianguy, TheArturZh, theashtronaut, thecopbennet, TheCze, TheDarkElites, thedraccx, TheEmber, TheIntoxicatedCat, thekilk, themias, Theomund, theOperand, TherapyGoth, TheShuEd, thetolbean, thevinter, TheWaffleJesus, Thinbug0, ThunderBear2006, timothyteakettle, TimrodDX, timurjavid, tin-man-tim, Titian3, tk-a369, tkdrg, tmtmtl30, TokenStyle, Tollhouse, Toly65, tom-leys, tomasalves8, Tomeno, Tonydatguy, topy, Tornado-Technology, tosatur, TotallyLemon, tropicalhibi, truepaintgit, Truoizys, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, TyAshley, Tyler-IN, Tyzemol, UbaserB, ubis1, UBlueberry, UKNOWH, UltimateJester, Unbelievable-Salmon, underscorex5, UnicornOnLSD, Unisol, Unkn0wnGh0st333, unusualcrow, Uriende, UristMcDorf, user424242420, Vaaankas, valentfingerov, Varen, VasilisThePikachu, veliebm, VelonacepsCalyxEggs, veprolet, veritable-calamity, Veritius, Vermidia, vero5123, Verslebas, VigersRay, violet754, Visne, VMSolidus, voidnull000, volotomite, volundr-, Voomra, Vordenburg, vorkathbruh, vulppine, wafehling, Warentan, WarMechanic, Watermelon914, waylon531, weaversam8, wertanchik, whateverusername0, Willhelm53, WilliamECrew, willicassi, Winkarst-cpu, wirdal, wixoaGit, WlarusFromDaSpace, wrexbe, wtcwr68, xkreksx, xprospero, xRiriq, YanehCheck, yathxyz, Ygg01, YotaXP, youarereadingthis, Yousifb26, youtissoum, YuriyKiss, zach-hill, Zadeon, zamp, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zero, ZeroDiamond, zerorulez, ZeWaka, zionnBE, ZNixian, ZoldorfTheWizard, Zonespace27, Zumorica, Zylofan, Zymem, zzylex diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index 6067830b7a..4b6cf87942 100644 --- a/Resources/Locale/en-US/entity-categories.ftl +++ b/Resources/Locale/en-US/entity-categories.ftl @@ -1,3 +1,5 @@ entity-category-name-actions = Actions entity-category-name-game-rules = Game Rules -entity-category-name-objectives = Objectives \ No newline at end of file +entity-category-name-objectives = Objectives +entity-category-name-roles = Mind Roles +entity-category-name-mapping = Mapping diff --git a/Resources/Locale/en-US/info/playtime-stats.ftl b/Resources/Locale/en-US/info/playtime-stats.ftl index 44ba39c25e..85508c1d09 100644 --- a/Resources/Locale/en-US/info/playtime-stats.ftl +++ b/Resources/Locale/en-US/info/playtime-stats.ftl @@ -5,6 +5,6 @@ ui-playtime-overall-base = Overall Playtime: ui-playtime-overall = Overall Playtime: {$time} ui-playtime-first-time = First Time Playing ui-playtime-roles = Playtime per Role -ui-playtime-time-format = {$hours}H {$minutes}M +ui-playtime-time-format = %h\H\ %m\M ui-playtime-header-role-type = Role ui-playtime-header-role-time = Time diff --git a/Resources/Locale/en-US/job/role-requirements.ftl b/Resources/Locale/en-US/job/role-requirements.ftl index 00281fa6cd..79a216fcca 100644 --- a/Resources/Locale/en-US/job/role-requirements.ftl +++ b/Resources/Locale/en-US/job/role-requirements.ftl @@ -1,11 +1,12 @@ -role-timer-department-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of [color={$departmentColor}]{$department}[/color] department playtime to play this role. -role-timer-department-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes in [color={$departmentColor}]{$department}[/color] department to play this role. (Are you trying to play a trainee role?) -role-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of playtime to play this role. -role-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime to play this role. (Are you trying to play a trainee role?) -role-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color] to play this role. -role-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color] to play this role. (Are you trying to play a trainee role?) -role-timer-age-to-old = Your character's age must be at most [color=yellow]{$age}[/color] to play this role. -role-timer-age-to-young = Your character's age must be at least [color=yellow]{$age}[/color] to play this role. +role-timer-department-insufficient = You require [color=yellow]{$time}[/color] more playtime in the [color={$departmentColor}]{$department}[/color] department to play this role. +role-timer-department-too-high = You require [color=yellow]{$time}[/color] less playtime in the [color={$departmentColor}]{$department}[/color] department to play this role. (Are you trying to play a trainee role?) +role-timer-overall-insufficient = You require [color=yellow]{$time}[/color] more overall playtime to play this role. +role-timer-overall-too-high = You require [color=yellow]{$time}[/color] less overall playtime to play this role. (Are you trying to play a trainee role?) +role-timer-role-insufficient = You require [color=yellow]{$time}[/color] more playtime with [color={$departmentColor}]{$job}[/color] to play this role. +role-timer-role-too-high = You require[color=yellow] {$time}[/color] less playtime with [color={$departmentColor}]{$job}[/color] to play this role. (Are you trying to play a trainee role?) +role-timer-time-format = %h\H\ %m\M +role-timer-age-too-old = Your character must be under the age of [color=yellow]{$age}[/color] to play this role. +role-timer-age-too-young = Your character must be over the age of [color=yellow]{$age}[/color] to play this role. role-timer-whitelisted-species = Your character must be one of the following species to play this role: role-timer-blacklisted-species = Your character must not be one of the following species to play this role: role-timer-whitelisted-traits = Your character must have one of the following traits: diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml index 4dcc97ad31..fdfe759e0a 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml @@ -150,4 +150,10 @@ - PosterLegitPeriodicTable - PosterLegitRenault - PosterLegitNTTGC + - PosterLegitSafetyMothDelam + - PosterLegitSafetyMothEpi + - PosterLegitSafetyMothPiping + - PosterLegitSafetyMothMeth + - PosterLegitSafetyMothHardhat + - PosterLegitSafetyMothSSD chance: 1 diff --git a/Resources/Prototypes/Entities/Objects/Decoration/present.yml b/Resources/Prototypes/Entities/Objects/Decoration/present.yml index 1240fa3d8f..861caddd06 100644 --- a/Resources/Prototypes/Entities/Objects/Decoration/present.yml +++ b/Resources/Prototypes/Entities/Objects/Decoration/present.yml @@ -64,6 +64,8 @@ orGroup: GiftPool - id: PlushieLizard #Weh! orGroup: GiftPool + - id: PlushieRainbowLizard + orGroup: GiftPool - id: PlushieNar orGroup: GiftPool - id: PlushieCarp diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index a5105fac5f..4993614964 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -344,6 +344,23 @@ - type: Speech speechVerb: Reptilian # for pais (In the secret stash) +- type: entity + parent: PlushieLizard + id: PlushieRainbowLizard #Weh but gay + description: An adorable stuffed toy that resembles a lizardperson of every color. You just might trip while staring at it... + name: rainbow lizard plushie + components: + - type: PointLight + radius: 1.5 + energy: 2 + - type: RgbLightController + layers: [ 0 ] + - type: Clothing + clothingVisuals: + head: + - state: lizard-equipped-HELMET + shader: unshaded + - type: entity parent: PlushieLizard id: PlushieLizardMirrored diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml index ee330b1f79..38cf0c8ac3 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml @@ -97,6 +97,7 @@ - PlushieLizard - PlushieAtmosian - PlushieSpaceLizard + - PlushieRainbowLizard - PlushieNuke - PlushieCarp - PlushieMagicarp diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 39ab6a3276..72d6b28efa 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml @@ -86,7 +86,7 @@ - type: GenericVisualizer visuals: enum.ConveyorVisuals.State: - enum.RecyclerVisualLayers.Main: + enum.ConveyorState.Off: Forward: { state: grinder-b1 } Reverse: { state: grinder-b1 } Off: { state: grinder-b0 } diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml index 93124b377d..e9b976403c 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml @@ -1060,6 +1060,16 @@ - type: Sprite state: poster51_legit +- type: entity + parent: PosterBase + id: PosterLegitSafetyMothSSD + name: "Safety Moth - Space Sleep Disorder" + description: "This informational poster uses Safety Moth™ to tell the viewer about Space Sleep Disorder (SSD), a condition where the person stops reacting to things. \"Treat SSD crew with care! They might wake up at any time!\"" + components: + - type: Sprite + state: poster52_legit + + #maps - type: entity diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index bc4dd104de..dffc6b6aaf 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -11,4 +11,14 @@ - type: entityCategory id: Objectives name: entity-category-name-objectives - hideSpawnMenu: true \ No newline at end of file + hideSpawnMenu: true + +- type: entityCategory + id: Roles + name: entity-category-name-roles + hideSpawnMenu: true + +# markers, atmos fixing, etc +- type: entityCategory + id: Mapping + name: entity-category-name-mapping diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index eb92fa51ae..926ce512b4 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -46,6 +46,8 @@ description: components: - type: SubvertedSiliconRole + - type: MindRole + antagPrototype: SubvertedSilicon # Dragon - type: entity diff --git a/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json index 5a57615011..15598582c0 100644 --- a/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json @@ -1,7 +1,11 @@ { "version": 1, "license": "CC-BY-SA-3.0", +<<<<<<< HEAD "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/2fea0a59470c476cf3f927833d3918d89cbe6af8", +======= + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, texture edited by TeaMaki (On github TeaMakiNL)", +>>>>>>> wizards/master "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json index 82d315b441..1f4ca721ff 100644 --- a/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json +++ b/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from at commit https://github.com/tgstation/tgstation/commit/f01de25493e2bd2706ef9b0303cb0d7b5e3e471b. poster52_contraband, poster53_contraband and poster54_contraband taken from https://github.com/vgstation-coders/vgstation13/blob/435ed5f2a7926e91cc31abac3a0d47d7e9ad7ed4/icons/obj/posters.dmi. originmap, poster55_contraband, poster56_contraband, poster57_contraband and poster39_legit by discord brainfood#7460, poster63_contraband by discord foboscheshir_. poster1_legit, poster3_contraband, poster3_legit, poster4_contraband, poster4_legit, poster5_contraband, poster5_legit, poster6_contraband, poster6_legit, poster7_contraband, poster7_legit, poster8_contraband, poster8_legit, poster9_contraband, poster9_legit, poster10_legit, poster11_legit, poster12_legit, poster13_legit, poster14_contraband, poster15_legit, poster16_contraband, poster16_legit, poster17_legit, poster19_legit, poster20_contraband, poster20_legit, poster21_contraband, poster21_legit, poster22_contraband, poster22_legit, poster23_contraband, poster23_legit, poster24_legit, poster25_contraband, poster26_legit, poster28_legit, poster29_contraband, poster29_legit, poster30_contraband, poster31_contraband, poster31_legit, poster32_contraband, poster32_legit, poster33_contraband, poster33_legit, poster34_legit, poster35_contraband, poster36_legit, poster37_legit, poster43_contraband, poster44_contraband, poster46_contraband, poster48_contraband, poster50_legit, poster51_legit, poster53_contraband, poster55_contraband, poster56_contraband, poster59_contraband, poster60_contraband edited and localised by github:lapatison", + "copyright": "Taken from at commit https://github.com/tgstation/tgstation/commit/f01de25493e2bd2706ef9b0303cb0d7b5e3e471b. poster52_contraband, poster53_contraband and poster54_contraband taken from https://github.com/vgstation-coders/vgstation13/blob/435ed5f2a7926e91cc31abac3a0d47d7e9ad7ed4/icons/obj/posters.dmi. originmap, poster55_contraband, poster56_contraband, poster57_contraband and poster39_legit by discord brainfood#7460, poster63_contraband by discord foboscheshir_, poster52_legit by SlamBamActionman, poster1_legit, poster3_contraband, poster3_legit, poster4_contraband, poster4_legit, poster5_contraband, poster5_legit, poster6_contraband, poster6_legit, poster7_contraband, poster7_legit, poster8_contraband, poster8_legit, poster9_contraband, poster9_legit, poster10_legit, poster11_legit, poster12_legit, poster13_legit, poster14_contraband, poster15_legit, poster16_contraband, poster16_legit, poster17_legit, poster19_legit, poster20_contraband, poster20_legit, poster21_contraband, poster21_legit, poster22_contraband, poster22_legit, poster23_contraband, poster23_legit, poster24_legit, poster25_contraband, poster26_legit, poster28_legit, poster29_contraband, poster29_legit, poster30_contraband, poster31_contraband, poster31_legit, poster32_contraband, poster32_legit, poster33_contraband, poster33_legit, poster34_legit, poster35_contraband, poster36_legit, poster37_legit, poster43_contraband, poster44_contraband, poster46_contraband, poster48_contraband, poster50_legit, poster51_legit, poster53_contraband, poster55_contraband, poster56_contraband, poster59_contraband, poster60_contraband edited and localised by github:lapatison.", "size": { "x": 32, "y": 32 @@ -381,6 +381,9 @@ { "name": "poster51_legit" }, + { + "name": "poster52_legit" + }, { "name": "random_legit" }, diff --git a/Resources/Textures/Structures/Wallmounts/posters.rsi/poster52_legit.png b/Resources/Textures/Structures/Wallmounts/posters.rsi/poster52_legit.png new file mode 100644 index 0000000000..91954d0edc Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/posters.rsi/poster52_legit.png differ