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