diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs index f0b7ffbe119..5416f73e709 100644 --- a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml.cs @@ -23,6 +23,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow { private readonly IEntityManager _entManager; private readonly SpriteSystem _spriteSystem; + private readonly SharedNavMapSystem _navMapSystem; private EntityUid? _owner; private NetEntity? _trackedEntity; @@ -47,6 +48,7 @@ public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInter RobustXamlLoader.Load(this); _entManager = IoCManager.Resolve(); _spriteSystem = _entManager.System(); + _navMapSystem = _entManager.System(); // Pass the owner to nav map _owner = owner; @@ -179,6 +181,9 @@ public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[ // Add tracked entities to the nav map foreach (var device in console.AtmosDevices) { + if (!device.NetEntity.Valid) + continue; + if (!NavMap.Visible) continue; @@ -270,6 +275,34 @@ public void UpdateUI(EntityCoordinates? consoleCoords, AtmosAlertsComputerEntry[ else MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount))); + // Update sensor regions + NavMap.RegionOverlays.Clear(); + var prioritizedRegionOverlays = new Dictionary(); + + if (_owner != null && + _entManager.TryGetComponent(_owner, out var xform) && + _entManager.TryGetComponent(xform.GridUid, out var navMap)) + { + var regionOverlays = _navMapSystem.GetNavMapRegionOverlays(_owner.Value, navMap, AtmosAlertsComputerUiKey.Key); + + foreach (var (regionOwner, regionOverlay) in regionOverlays) + { + var alarmState = GetAlarmState(regionOwner); + + if (!TryGetSensorRegionColor(regionOwner, alarmState, out var regionColor)) + continue; + + regionOverlay.Color = regionColor.Value; + + var priority = (_trackedEntity == regionOwner) ? 999 : (int)alarmState; + prioritizedRegionOverlays.Add(regionOverlay, priority); + } + + // Sort overlays according to their priority + var sortedOverlays = prioritizedRegionOverlays.OrderBy(x => x.Value).Select(x => x.Key).ToList(); + NavMap.RegionOverlays = sortedOverlays; + } + // Auto-scroll re-enable if (_autoScrollAwaitsUpdate) { @@ -298,6 +331,27 @@ private void AddTrackedEntityToNavMap(AtmosAlertsDeviceNavMapData metaData, Atmo NavMap.TrackedEntities[metaData.NetEntity] = blip; } + private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, [NotNullWhen(true)] out Color? color) + { + color = null; + + var blip = GetBlipTexture(alarmState); + + if (blip == null) + return false; + + // DeltaV: fix client until upstream does + // Color the region based on alarm state and entity tracking + var output = blip.Value.Item2 * new Color(154, 154, 154); + + if (_trackedEntity != null && _trackedEntity != regionOwner) + output *= Color.DimGray; + + color = output; + + return true; + } + private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null) { // Make new UI entry if required diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index eaea6b01eca..602c13149b1 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -4,6 +4,7 @@ using Content.Client.DebugMon; using Content.Client.Eui; using Content.Client.Fullscreen; +using Content.Client.GameTicking.Managers; using Content.Client.GhostKick; using Content.Client.Guidebook; using Content.Client.Input; @@ -70,6 +71,7 @@ public sealed class EntryPoint : GameClient [Dependency] private readonly IReplayLoadManager _replayLoad = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!; + [Dependency] private readonly TitleWindowManager _titleWindowManager = default!; public override void Init() { @@ -140,6 +142,12 @@ public override void Init() _configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f); } + public override void Shutdown() + { + base.Shutdown(); + _titleWindowManager.Shutdown(); + } + public override void PostInit() { base.PostInit(); @@ -160,6 +168,7 @@ public override void PostInit() _userInterfaceManager.SetDefaultTheme("SS14DefaultTheme"); _userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme)); _documentParsingManager.Initialize(); + _titleWindowManager.Initialize(); _baseClient.RunLevelChanged += (_, args) => { diff --git a/Content.Client/GameTicking/Managers/TitleWindowManager.cs b/Content.Client/GameTicking/Managers/TitleWindowManager.cs new file mode 100644 index 00000000000..18ce16f634c --- /dev/null +++ b/Content.Client/GameTicking/Managers/TitleWindowManager.cs @@ -0,0 +1,62 @@ +using Content.Shared.CCVar; +using Robust.Client; +using Robust.Client.Graphics; +using Robust.Shared; +using Robust.Shared.Configuration; + +namespace Content.Client.GameTicking.Managers; + +public sealed class TitleWindowManager +{ + [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameController _gameController = default!; + + public void Initialize() + { + _cfg.OnValueChanged(CVars.GameHostName, OnHostnameChange, true); + _cfg.OnValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange, true); + + _client.RunLevelChanged += OnRunLevelChangedChange; + } + + public void Shutdown() + { + _cfg.UnsubValueChanged(CVars.GameHostName, OnHostnameChange); + _cfg.UnsubValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange); + } + + private void OnHostnameChange(string hostname) + { + var defaultWindowTitle = _gameController.GameTitle(); + + // Since the game assumes the server name is MyServer and that GameHostnameInTitlebar CCVar is true by default + // Lets just... not show anything. This also is used to revert back to just the game title on disconnect. + if (_client.RunLevel == ClientRunLevel.Initialize) + { + _clyde.SetWindowTitle(defaultWindowTitle); + return; + } + + if (_cfg.GetCVar(CCVars.GameHostnameInTitlebar)) + // If you really dislike the dash I guess change it here + _clyde.SetWindowTitle(hostname + " - " + defaultWindowTitle); + else + _clyde.SetWindowTitle(defaultWindowTitle); + } + + // Clients by default assume game.hostname_in_titlebar is true + // but we need to clear it as soon as we join and actually receive the servers preference on this. + // This will ensure we rerun OnHostnameChange and set the correct title bar name. + private void OnHostnameTitleChange(bool colonthree) + { + OnHostnameChange(_cfg.GetCVar(CVars.GameHostName)); + } + + // This is just used we can rerun the hostname change function when we disconnect to revert back to just the games title. + private void OnRunLevelChangedChange(object? sender, RunLevelChangedEventArgs runLevelChangedEventArgs) + { + OnHostnameChange(_cfg.GetCVar(CVars.GameHostName)); + } +} diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index ffa6dfd29d6..68800a2afe5 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/PlaytimeStatsEntry.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs index aff01800f94..632ad8de4ac 100644 --- a/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs +++ b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs @@ -1,3 +1,4 @@ +using Content.Shared.Localizations; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; @@ -16,19 +17,10 @@ public PlaytimeStatsEntry(string role, TimeSpan playtime, StyleBox styleBox) RoleLabel.Text = role; Playtime = playtime; // store the TimeSpan value directly - PlaytimeLabel.Text = ConvertTimeSpanToHoursMinutes(playtime); // convert to string for display + PlaytimeLabel.Text = ContentLocalizationManager.FormatPlaytime(playtime); // convert to string for display BackgroundColorPanel.PanelOverride = styleBox; } - 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; - } - public void UpdateShading(StyleBoxFlat styleBox) { BackgroundColorPanel.PanelOverride = styleBox; diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs index 3b54bf82daf..98241b2ccab 100644 --- a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs +++ b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs @@ -104,8 +104,7 @@ private void PopulatePlaytimeData() { var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime(); - var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime); - OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime)); + OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", overallPlaytime)); var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles(); @@ -134,13 +133,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/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index e643552f70b..370188e3c61 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -5,6 +5,7 @@ using Content.Client.DebugMon; using Content.Client.Eui; using Content.Client.Fullscreen; +using Content.Client.GameTicking.Managers; using Content.Client.GhostKick; using Content.Client.Guidebook; using Content.Client.Launcher; @@ -57,6 +58,7 @@ public static void Register() collection.Register(); collection.Register(); collection.Register(); + collection.Register(); } } } diff --git a/Content.Client/Pinpointer/NavMapSystem.Regions.cs b/Content.Client/Pinpointer/NavMapSystem.Regions.cs new file mode 100644 index 00000000000..4cc775418ec --- /dev/null +++ b/Content.Client/Pinpointer/NavMapSystem.Regions.cs @@ -0,0 +1,303 @@ +using Content.Shared.Atmos; +using Content.Shared.Pinpointer; +using System.Linq; + +namespace Content.Client.Pinpointer; + +public sealed partial class NavMapSystem +{ + private (AtmosDirection, Vector2i, AtmosDirection)[] _regionPropagationTable = + { + (AtmosDirection.East, new Vector2i(1, 0), AtmosDirection.West), + (AtmosDirection.West, new Vector2i(-1, 0), AtmosDirection.East), + (AtmosDirection.North, new Vector2i(0, 1), AtmosDirection.South), + (AtmosDirection.South, new Vector2i(0, -1), AtmosDirection.North), + }; + + public override void Update(float frameTime) + { + // To prevent compute spikes, only one region is flood filled per frame + var query = AllEntityQuery(); + + while (query.MoveNext(out var ent, out var entNavMapRegions)) + FloodFillNextEnqueuedRegion(ent, entNavMapRegions); + } + + private void FloodFillNextEnqueuedRegion(EntityUid uid, NavMapComponent component) + { + if (!component.QueuedRegionsToFlood.Any()) + return; + + var regionOwner = component.QueuedRegionsToFlood.Dequeue(); + + // If the region is no longer valid, flood the next one in the queue + if (!component.RegionProperties.TryGetValue(regionOwner, out var regionProperties) || + !regionProperties.Seeds.Any()) + { + FloodFillNextEnqueuedRegion(uid, component); + return; + } + + // Flood fill the region, using the region seeds as starting points + var (floodedTiles, floodedChunks) = FloodFillRegion(uid, component, regionProperties); + + // Combine the flooded tiles into larger rectangles + var gridCoords = GetMergedRegionTiles(floodedTiles); + + // Create and assign the new region overlay + var regionOverlay = new NavMapRegionOverlay(regionProperties.UiKey, gridCoords) + { + Color = regionProperties.Color + }; + + component.RegionOverlays[regionOwner] = regionOverlay; + + // To reduce unnecessary future flood fills, we will track which chunks have been flooded by a region owner + + // First remove an old assignments + if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var oldChunks)) + { + foreach (var chunk in oldChunks) + { + if (component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var oldOwners)) + { + oldOwners.Remove(regionOwner); + component.ChunkToRegionOwnerTable[chunk] = oldOwners; + } + } + } + + // Now update with the new assignments + component.RegionOwnerToChunkTable[regionOwner] = floodedChunks; + + foreach (var chunk in floodedChunks) + { + if (!component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var owners)) + owners = new(); + + owners.Add(regionOwner); + component.ChunkToRegionOwnerTable[chunk] = owners; + } + } + + private (HashSet, HashSet) FloodFillRegion(EntityUid uid, NavMapComponent component, NavMapRegionProperties regionProperties) + { + if (!regionProperties.Seeds.Any()) + return (new(), new()); + + var visitedChunks = new HashSet(); + var visitedTiles = new HashSet(); + var tilesToVisit = new Stack(); + + foreach (var regionSeed in regionProperties.Seeds) + { + tilesToVisit.Push(regionSeed); + + while (tilesToVisit.Count > 0) + { + // If the max region area is hit, exit + if (visitedTiles.Count > regionProperties.MaxArea) + return (new(), new()); + + // Pop the top tile from the stack + var current = tilesToVisit.Pop(); + + // If the current tile position has already been visited, + // or is too far away from the seed, continue + if ((regionSeed - current).Length > regionProperties.MaxRadius) + continue; + + if (visitedTiles.Contains(current)) + continue; + + // Determine the tile's chunk index + var chunkOrigin = SharedMapSystem.GetChunkIndices(current, ChunkSize); + var relative = SharedMapSystem.GetChunkRelative(current, ChunkSize); + var idx = GetTileIndex(relative); + + // Extract the tile data + if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk)) + continue; + + var flag = chunk.TileData[idx]; + + // If the current tile is entirely occupied, continue + if ((FloorMask & flag) == 0) + continue; + + if ((WallMask & flag) == WallMask) + continue; + + if ((AirlockMask & flag) == AirlockMask) + continue; + + // Otherwise the tile can be added to this region + visitedTiles.Add(current); + visitedChunks.Add(chunkOrigin); + + // Determine if we can propagate the region into its cardinally adjacent neighbors + // To propagate to a neighbor, movement into the neighbors closest edge must not be + // blocked, and vice versa + + foreach (var (direction, tileOffset, reverseDirection) in _regionPropagationTable) + { + if (!RegionCanPropagateInDirection(chunk, current, direction)) + continue; + + var neighbor = current + tileOffset; + var neighborOrigin = SharedMapSystem.GetChunkIndices(neighbor, ChunkSize); + + if (!component.Chunks.TryGetValue(neighborOrigin, out var neighborChunk)) + continue; + + visitedChunks.Add(neighborOrigin); + + if (!RegionCanPropagateInDirection(neighborChunk, neighbor, reverseDirection)) + continue; + + tilesToVisit.Push(neighbor); + } + } + } + + return (visitedTiles, visitedChunks); + } + + private bool RegionCanPropagateInDirection(NavMapChunk chunk, Vector2i tile, AtmosDirection direction) + { + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + var idx = GetTileIndex(relative); + var flag = chunk.TileData[idx]; + + if ((FloorMask & flag) == 0) + return false; + + var directionMask = 1 << (int)direction; + var wallMask = (int)direction << (int)NavMapChunkType.Wall; + var airlockMask = (int)direction << (int)NavMapChunkType.Airlock; + + if ((wallMask & flag) > 0) + return false; + + if ((airlockMask & flag) > 0) + return false; + + return true; + } + + private List<(Vector2i, Vector2i)> GetMergedRegionTiles(HashSet tiles) + { + if (!tiles.Any()) + return new(); + + var x = tiles.Select(t => t.X); + var minX = x.Min(); + var maxX = x.Max(); + + var y = tiles.Select(t => t.Y); + var minY = y.Min(); + var maxY = y.Max(); + + var matrix = new int[maxX - minX + 1, maxY - minY + 1]; + + foreach (var tile in tiles) + { + var a = tile.X - minX; + var b = tile.Y - minY; + + matrix[a, b] = 1; + } + + return GetMergedRegionTiles(matrix, new Vector2i(minX, minY)); + } + + private List<(Vector2i, Vector2i)> GetMergedRegionTiles(int[,] matrix, Vector2i offset) + { + var output = new List<(Vector2i, Vector2i)>(); + + var rows = matrix.GetLength(0); + var cols = matrix.GetLength(1); + + var dp = new int[rows, cols]; + var coords = (new Vector2i(), new Vector2i()); + var maxArea = 0; + + var count = 0; + + while (!IsArrayEmpty(matrix)) + { + count++; + + if (count > rows * cols) + break; + + // Clear old values + dp = new int[rows, cols]; + coords = (new Vector2i(), new Vector2i()); + maxArea = 0; + + // Initialize the first row of dp + for (int j = 0; j < cols; j++) + { + dp[0, j] = matrix[0, j]; + } + + // Calculate dp values for remaining rows + for (int i = 1; i < rows; i++) + { + for (int j = 0; j < cols; j++) + dp[i, j] = matrix[i, j] == 1 ? dp[i - 1, j] + 1 : 0; + } + + // Find the largest rectangular area seeded for each position in the matrix + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + int minWidth = dp[i, j]; + + for (int k = j; k >= 0; k--) + { + if (dp[i, k] <= 0) + break; + + minWidth = Math.Min(minWidth, dp[i, k]); + var currArea = Math.Max(maxArea, minWidth * (j - k + 1)); + + if (currArea > maxArea) + { + maxArea = currArea; + coords = (new Vector2i(i - minWidth + 1, k), new Vector2i(i, j)); + } + } + } + } + + // Save the recorded rectangle vertices + output.Add((coords.Item1 + offset, coords.Item2 + offset)); + + // Removed the tiles covered by the rectangle from matrix + for (int i = coords.Item1.X; i <= coords.Item2.X; i++) + { + for (int j = coords.Item1.Y; j <= coords.Item2.Y; j++) + matrix[i, j] = 0; + } + } + + return output; + } + + private bool IsArrayEmpty(int[,] matrix) + { + for (int i = 0; i < matrix.GetLength(0); i++) + { + for (int j = 0; j < matrix.GetLength(1); j++) + { + if (matrix[i, j] == 1) + return false; + } + } + + return true; + } +} diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index 9aeb792a429..47469d4ea79 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Pinpointer; using Robust.Shared.GameStates; @@ -16,6 +17,7 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone { Dictionary modifiedChunks; Dictionary beacons; + Dictionary regions; switch (args.Current) { @@ -23,6 +25,8 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone { modifiedChunks = delta.ModifiedChunks; beacons = delta.Beacons; + regions = delta.Regions; + foreach (var index in component.Chunks.Keys) { if (!delta.AllChunks!.Contains(index)) @@ -35,6 +39,8 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone { modifiedChunks = state.Chunks; beacons = state.Beacons; + regions = state.Regions; + foreach (var index in component.Chunks.Keys) { if (!state.Chunks.ContainsKey(index)) @@ -47,13 +53,54 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone return; } + // Update region data and queue new regions for flooding + var prevRegionOwners = component.RegionProperties.Keys.ToList(); + var validRegionOwners = new List(); + + component.RegionProperties.Clear(); + + foreach (var (regionOwner, regionData) in regions) + { + if (!regionData.Seeds.Any()) + continue; + + component.RegionProperties[regionOwner] = regionData; + validRegionOwners.Add(regionOwner); + + if (component.RegionOverlays.ContainsKey(regionOwner)) + continue; + + if (component.QueuedRegionsToFlood.Contains(regionOwner)) + continue; + + component.QueuedRegionsToFlood.Enqueue(regionOwner); + } + + // Remove stale region owners + var regionOwnersToRemove = prevRegionOwners.Except(validRegionOwners); + + foreach (var regionOwnerRemoved in regionOwnersToRemove) + RemoveNavMapRegion(uid, component, regionOwnerRemoved); + + // Modify chunks foreach (var (origin, chunk) in modifiedChunks) { var newChunk = new NavMapChunk(origin); Array.Copy(chunk, newChunk.TileData, chunk.Length); component.Chunks[origin] = newChunk; + + // If the affected chunk intersects one or more regions, re-flood them + if (!component.ChunkToRegionOwnerTable.TryGetValue(origin, out var affectedOwners)) + continue; + + foreach (var affectedOwner in affectedOwners) + { + if (!component.QueuedRegionsToFlood.Contains(affectedOwner)) + component.QueuedRegionsToFlood.Enqueue(affectedOwner); + } } + // Refresh beacons component.Beacons.Clear(); foreach (var (nuid, beacon) in beacons) { diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index 413b41c36a6..90c2680c4a7 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -48,6 +48,7 @@ public partial class NavMapControl : MapGridControl public List<(Vector2, Vector2)> TileLines = new(); public List<(Vector2, Vector2)> TileRects = new(); public List<(Vector2[], Color)> TilePolygons = new(); + public List RegionOverlays = new(); // Default colors public Color WallColor = new(102, 217, 102); @@ -228,7 +229,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { if (!blip.Selectable) continue; - + var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length(); if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance) @@ -319,6 +320,22 @@ protected override void Draw(DrawingHandleScreen handle) } } + // Draw region overlays + if (_grid != null) + { + foreach (var regionOverlay in RegionOverlays) + { + foreach (var gridCoords in regionOverlay.GridCoords) + { + var positionTopLeft = ScalePosition(new Vector2(gridCoords.Item1.X, -gridCoords.Item1.Y) - new Vector2(offset.X, -offset.Y)); + var positionBottomRight = ScalePosition(new Vector2(gridCoords.Item2.X + _grid.TileSize, -gridCoords.Item2.Y - _grid.TileSize) - new Vector2(offset.X, -offset.Y)); + + var box = new UIBox2(positionTopLeft, positionBottomRight); + handle.DrawRect(box, regionOverlay.Color); + } + } + } + // Draw map lines if (TileLines.Any()) { diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 7056a15e0e4..faf8aa28d57 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -52,6 +52,8 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e) { // Reset on disconnect, just in case. _roles.Clear(); + _jobWhitelists.Clear(); + _roleBans.Clear(); } } @@ -59,9 +61,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.Client/Power/APC/ApcBoundUserInterface.cs b/Content.Client/Power/APC/ApcBoundUserInterface.cs index a790c5d984a..5c4036a9159 100644 --- a/Content.Client/Power/APC/ApcBoundUserInterface.cs +++ b/Content.Client/Power/APC/ApcBoundUserInterface.cs @@ -1,8 +1,9 @@ -using Content.Client.Power.APC.UI; +using Content.Client.Power.APC.UI; +using Content.Shared.Access.Systems; using Content.Shared.APC; using JetBrains.Annotations; -using Robust.Client.GameObjects; using Robust.Client.UserInterface; +using Robust.Shared.Player; namespace Content.Client.Power.APC { @@ -22,6 +23,14 @@ protected override void Open() _menu = this.CreateWindow(); _menu.SetEntity(Owner); _menu.OnBreaker += BreakerPressed; + + var hasAccess = false; + if (PlayerManager.LocalEntity != null) + { + var accessReader = EntMan.System(); + hasAccess = accessReader.IsAllowed((EntityUid)PlayerManager.LocalEntity, Owner); + } + _menu?.SetAccessEnabled(hasAccess); } protected override void UpdateState(BoundUserInterfaceState state) diff --git a/Content.Client/Power/APC/UI/ApcMenu.xaml.cs b/Content.Client/Power/APC/UI/ApcMenu.xaml.cs index 2f61ea63a86..25e885b3c7a 100644 --- a/Content.Client/Power/APC/UI/ApcMenu.xaml.cs +++ b/Content.Client/Power/APC/UI/ApcMenu.xaml.cs @@ -1,4 +1,4 @@ -using Robust.Client.AutoGenerated; +using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.XAML; using Robust.Client.GameObjects; using Robust.Shared.IoC; @@ -36,19 +36,9 @@ public void UpdateState(BoundUserInterfaceState state) { var castState = (ApcBoundInterfaceState) state; - if (BreakerButton != null) + if (!BreakerButton.Disabled) { - if(castState.HasAccess == false) - { - BreakerButton.Disabled = true; - BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access"); - } - else - { - BreakerButton.Disabled = false; - BreakerButton.ToolTip = null; - BreakerButton.Pressed = castState.MainBreaker; - } + BreakerButton.Pressed = castState.MainBreaker; } if (PowerLabel != null) @@ -86,6 +76,20 @@ public void UpdateState(BoundUserInterfaceState state) } } + public void SetAccessEnabled(bool hasAccess) + { + if(hasAccess) + { + BreakerButton.Disabled = false; + BreakerButton.ToolTip = null; + } + else + { + BreakerButton.Disabled = true; + BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access"); + } + } + private void UpdateChargeBarColor(float charge) { if (ChargeBar == null) diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml index 913b07a8f65..44b1ff95e7f 100644 --- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml +++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml @@ -2,7 +2,8 @@ xmlns="https://spacestation14.io" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" - xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls" + MinHeight="210"> diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 7604d5f8808..26ec75f9957 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -28,6 +28,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem [Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + [Dependency] private readonly MapSystem _map = default!; private EntityQuery _xformQuery; @@ -109,11 +110,11 @@ public override void Update(float frameTime) if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) { - coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager); + coordinates = TransformSystem.ToCoordinates(gridUid, mousePos); } else { - coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager); + coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos); } // Heavy attack. diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 4604cd82966..1b4825cc9c7 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/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index a4563aa37e6..039c0c7b184 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.Body.Components; using Content.Server.GameTicking; @@ -120,8 +121,8 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(roleSys.MindHasRole(mind)); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); - var roles = roleSys.MindGetAllRoles(mind); - var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); + var roles = roleSys.MindGetAllRoleInfo(mind); + var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander"); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); // The second dummy player should be a medic @@ -131,8 +132,8 @@ public async Task TryStopNukeOpsFromConstantlyFailing() Assert.That(roleSys.MindHasRole(dummyMind)); Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); - roles = roleSys.MindGetAllRoles(dummyMind); - cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); + roles = roleSys.MindGetAllRoleInfo(dummyMind); + cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic"); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); // The other two players should have just spawned in as normal. @@ -141,13 +142,14 @@ public async Task TryStopNukeOpsFromConstantlyFailing() void CheckDummy(int i) { var ent = dummyEnts[i]; - var mind = mindSys.GetMind(ent)!.Value; + var mindCrew = mindSys.GetMind(ent)!.Value; Assert.That(entMan.HasComponent(ent), Is.False); - Assert.That(roleSys.MindIsAntagonist(mind), Is.False); - Assert.That(roleSys.MindHasRole(mind), Is.False); + Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False); + Assert.That(roleSys.MindHasRole(mindCrew), Is.False); Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); - Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False); + var nukeroles = new List() { "Nukeops", "NukeopsMedic", "NukeopsCommander" }; + Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False); } // The game rule exists, and all the stations/shuttles/maps are properly initialized @@ -238,7 +240,8 @@ await server.WaitAssertion(() => for (var i = 0; i < nukies.Length - 1; i++) { entMan.DeleteEntity(nukies[i]); - Assert.That(roundEndSys.IsRoundEndRequested, Is.False, + Assert.That(roundEndSys.IsRoundEndRequested, + Is.False, $"The round ended, but {nukies.Length - i - 1} nukies are still alive!"); } // Delete the last nukie and make sure the round ends. diff --git a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs index dbd612c7101..d1535368736 100644 --- a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs +++ b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs @@ -2,7 +2,6 @@ using Content.Server.Body.Systems; using Content.Server.Station.Systems; using Content.Shared.Preferences; -using Content.Shared.Roles.Jobs; namespace Content.IntegrationTests.Tests.Internals; @@ -25,10 +24,7 @@ public async Task TestInternalsAutoActivateInSpaceForStationSpawn() await server.WaitAssertion(() => { var profile = new HumanoidCharacterProfile(); - var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent() - { - Prototype = "TestInternalsDummy" - }, profile, station: null); + var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null); Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!"); Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!"); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 3b7ecf0135f..d4d551f4e0a 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -1,10 +1,8 @@ #nullable enable using System.Linq; -using Content.Server.Ghost; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind.Commands; -using Content.Server.Players; using Content.Server.Roles; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -18,7 +16,6 @@ using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -287,27 +284,27 @@ await server.WaitAssertion(() => Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); - var traitorRole = new TraitorRoleComponent(); + var traitorRole = "MindRoleTraitor"; roleSystem.MindAddRole(mindId, traitorRole); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId)); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); - var jobRole = new JobComponent(); + var jobRole = ""; - roleSystem.MindAddRole(mindId, jobRole); + roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId)); - Assert.That(roleSystem.MindHasRole(mindId)); + Assert.That(roleSystem.MindHasRole(mindId)); }); roleSystem.MindRemoveRole(mindId); @@ -315,15 +312,15 @@ await server.WaitAssertion(() => Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId)); + Assert.That(roleSystem.MindHasRole(mindId)); }); - roleSystem.MindRemoveRole(mindId); + roleSystem.MindRemoveRole(mindId); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); }); diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs new file mode 100644 index 00000000000..fcfe1236cfc --- /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/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs index 6746d6d5a94..267b3637e0a 100644 --- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs @@ -3,7 +3,6 @@ using Content.Shared.Inventory; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Roles.Jobs; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -68,10 +67,7 @@ await server.WaitAssertion(() => profile.SetLoadout(new RoleLoadout("LoadoutTester")); - var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent() - { - Prototype = "LoadoutTester" - }, profile, station: null); + var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null); var slotQuery = inventorySystem.GetSlotEnumerator(tester); var checkedCount = 0; diff --git a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs index f8060edb2b4..3b2935258a7 100644 --- a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs +++ b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs @@ -35,15 +35,16 @@ await server.WaitAssertion(() => { foreach (var gearProto in protos) { - var backpackProto = ((IEquipmentLoadout) gearProto).GetGear("back"); - if (backpackProto == string.Empty) - continue; - - var bag = server.EntMan.SpawnEntity(backpackProto, coords); var ents = new ValueList(); foreach (var (slot, entProtos) in gearProto.Storage) { + ents.Clear(); + var storageProto = ((IEquipmentLoadout)gearProto).GetGear(slot); + if (storageProto == string.Empty) + continue; + + var bag = server.EntMan.SpawnEntity(storageProto, coords); if (entProtos.Count == 0) continue; @@ -59,9 +60,8 @@ await server.WaitAssertion(() => server.EntMan.DeleteEntity(ent); } + server.EntMan.DeleteEntity(bag); } - - server.EntMan.DeleteEntity(bag); } mapManager.DeleteMap(testMap.MapId); diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index bf75188f029..da7e1e8e9b0 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 2d28534347d..983ec709362 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/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs index 4de908bc301..a38aefce935 100644 --- a/Content.Server/Access/Systems/AgentIDCardSystem.cs +++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs @@ -67,7 +67,7 @@ private void AfterUIOpen(EntityUid uid, AgentIDCardComponent component, AfterAct if (!TryComp(uid, out var idCard)) return; - var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon); + var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon); _uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state); } diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs index e02664f2bbd..a9e5d9a6d3e 100644 --- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs +++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs @@ -96,7 +96,7 @@ private void UpdateUserInterface(EntityUid uid, IdCardConsoleComponent component PrivilegedIdIsAuthorized(uid, component), true, targetIdComponent.FullName, - targetIdComponent.JobTitle, + targetIdComponent.LocalizedJobTitle, targetAccessComponent.Tags.ToList(), possibleAccess, jobProto, diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs index 946770d6aaf..1cdfb822242 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 b60e0a25351..c11e310a825 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/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index d108c29975a..78362390811 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -98,9 +98,10 @@ private void AddSmiteVerbs(GetVerbsEvent args) if (HasComp(args.Target) || HasComp(args.Target)) return; + var explodeName = Loc.GetString("admin-smite-explode-name").ToLowerInvariant(); Verb explode = new() { - Text = "admin-smite-explode-name", + Text = explodeName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")), Act = () => @@ -114,13 +115,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _bodySystem.GibBody(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-explode-description") + Message = string.Join(": ", explodeName, Loc.GetString("admin-smite-explode-description")) // we do this so the description tells admins the Text to run it via console. }; args.Verbs.Add(explode); + var chessName = Loc.GetString("admin-smite-chess-dimension-name").ToLowerInvariant(); Verb chess = new() { - Text = "admin-smite-chess-dimension-name", + Text = chessName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Tabletop/chessboard.rsi"), "chessboard"), Act = () => @@ -140,12 +142,13 @@ private void AddSmiteVerbs(GetVerbsEvent args) xform.WorldRotation = Angle.Zero; }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-chess-dimension-description") + Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description")) }; args.Verbs.Add(chess); if (TryComp(args.Target, out var flammable)) { + var flamesName = Loc.GetString("admin-smite-set-alight-name").ToLowerInvariant(); Verb flames = new() { Text = "admin-smite-set-alight-name", @@ -163,14 +166,15 @@ private void AddSmiteVerbs(GetVerbsEvent args) Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-set-alight-description") + Message = string.Join(": ", flamesName, Loc.GetString("admin-smite-set-alight-description")) }; args.Verbs.Add(flames); } + var monkeyName = Loc.GetString("admin-smite-monkeyify-name").ToLowerInvariant(); Verb monkey = new() { - Text = "admin-smite-monkeyify-name", + Text = monkeyName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Animals/monkey.rsi"), "monkey"), Act = () => @@ -178,13 +182,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminMonkeySmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-monkeyify-description") + Message = string.Join(": ", monkeyName, Loc.GetString("admin-smite-monkeyify-description")) }; args.Verbs.Add(monkey); + var disposalBinName = Loc.GetString("admin-smite-garbage-can-name").ToLowerInvariant(); Verb disposalBin = new() { - Text = "admin-smite-electrocute-name", + Text = disposalBinName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Piping/disposal.rsi"), "disposal"), Act = () => @@ -192,16 +197,17 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-garbage-can-description") + Message = string.Join(": ", disposalBinName, Loc.GetString("admin-smite-garbage-can-description")) }; args.Verbs.Add(disposalBin); if (TryComp(args.Target, out var damageable) && HasComp(args.Target)) { + var hardElectrocuteName = Loc.GetString("admin-smite-electrocute-name").ToLowerInvariant(); Verb hardElectrocute = new() { - Text = "admin-smite-creampie-name", + Text = hardElectrocuteName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Hands/Gloves/Color/yellow.rsi"), "icon"), Act = () => @@ -237,16 +243,17 @@ private void AddSmiteVerbs(GetVerbsEvent args) TimeSpan.FromSeconds(30), refresh: true, ignoreInsulation: true); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-electrocute-description") + Message = string.Join(": ", hardElectrocuteName, Loc.GetString("admin-smite-electrocute-description")) }; args.Verbs.Add(hardElectrocute); } if (TryComp(args.Target, out var creamPied)) { + var creamPieName = Loc.GetString("admin-smite-creampie-name").ToLowerInvariant(); Verb creamPie = new() { - Text = "admin-smite-remove-blood-name", + Text = creamPieName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"), Act = () => @@ -254,16 +261,17 @@ private void AddSmiteVerbs(GetVerbsEvent args) _creamPieSystem.SetCreamPied(args.Target, creamPied, true); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-creampie-description") + Message = string.Join(": ", creamPieName, Loc.GetString("admin-smite-creampie-description")) }; args.Verbs.Add(creamPie); } if (TryComp(args.Target, out var bloodstream)) { + var bloodRemovalName = Loc.GetString("admin-smite-remove-blood-name").ToLowerInvariant(); Verb bloodRemoval = new() { - Text = "admin-smite-vomit-organs-name", + Text = bloodRemovalName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Fluids/tomato_splat.rsi"), "puddle-1"), Act = () => @@ -276,7 +284,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-remove-blood-description") + Message = string.Join(": ", bloodRemovalName, Loc.GetString("admin-smite-remove-blood-description")) }; args.Verbs.Add(bloodRemoval); } @@ -284,9 +292,10 @@ private void AddSmiteVerbs(GetVerbsEvent args) // bobby... if (TryComp(args.Target, out var body)) { + var vomitOrgansName = Loc.GetString("admin-smite-vomit-organs-name").ToLowerInvariant(); Verb vomitOrgans = new() { - Text = "admin-smite-remove-hands-name", + Text = vomitOrgansName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new("/Textures/Fluids/vomit_toxin.rsi"), "vomit_toxin-1"), Act = () => @@ -308,13 +317,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-vomit-organs-description") + Message = string.Join(": ", vomitOrgansName, Loc.GetString("admin-smite-vomit-organs-description")) }; args.Verbs.Add(vomitOrgans); + var handsRemovalName = Loc.GetString("admin-smite-remove-hands-name").ToLowerInvariant(); Verb handsRemoval = new() { - Text = "admin-smite-remove-hand-name", + Text = handsRemovalName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hands.png")), Act = () => @@ -330,13 +340,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) Filter.PvsExcept(args.Target), true, PopupType.Medium); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-remove-hands-description") + Message = string.Join(": ", handsRemovalName, Loc.GetString("admin-smite-remove-hands-description")) }; args.Verbs.Add(handsRemoval); + var handRemovalName = Loc.GetString("admin-smite-remove-hand-name").ToLowerInvariant(); Verb handRemoval = new() { - Text = "admin-smite-pinball-name", + Text = handRemovalName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hand.png")), Act = () => @@ -353,13 +364,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) Filter.PvsExcept(args.Target), true, PopupType.Medium); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-remove-hand-description") + Message = string.Join(": ", handRemovalName, Loc.GetString("admin-smite-remove-hand-description")) }; args.Verbs.Add(handRemoval); + var stomachRemovalName = Loc.GetString("admin-smite-stomach-removal-name").ToLowerInvariant(); Verb stomachRemoval = new() { - Text = "admin-smite-yeet-name", + Text = stomachRemovalName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"), Act = () => @@ -373,13 +385,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) args.Target, PopupType.LargeCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-stomach-removal-description"), + Message = string.Join(": ", stomachRemovalName, Loc.GetString("admin-smite-stomach-removal-description")) }; args.Verbs.Add(stomachRemoval); + var lungRemovalName = Loc.GetString("admin-smite-lung-removal-name").ToLowerInvariant(); Verb lungRemoval = new() { - Text = "admin-smite-become-bread-name", + Text = lungRemovalName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"), Act = () => @@ -393,16 +406,17 @@ private void AddSmiteVerbs(GetVerbsEvent args) args.Target, PopupType.LargeCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-lung-removal-description"), + Message = string.Join(": ", lungRemovalName, Loc.GetString("admin-smite-lung-removal-description")) }; args.Verbs.Add(lungRemoval); } if (TryComp(args.Target, out var physics)) { + var pinballName = Loc.GetString("admin-smite-pinball-name").ToLowerInvariant(); Verb pinball = new() { - Text = "admin-smite-ghostkick-name", + Text = pinballName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"), Act = () => @@ -430,13 +444,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetAngularDamping(args.Target, physics, 0f); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-pinball-description") + Message = string.Join(": ", pinballName, Loc.GetString("admin-smite-pinball-description")) }; args.Verbs.Add(pinball); + var yeetName = Loc.GetString("admin-smite-yeet-name").ToLowerInvariant(); Verb yeet = new() { - Text = "admin-smite-nyanify-name", + Text = yeetName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")), Act = () => @@ -460,11 +475,12 @@ private void AddSmiteVerbs(GetVerbsEvent args) _physics.SetAngularDamping(args.Target, physics, 0f); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-yeet-description") + Message = string.Join(": ", yeetName, Loc.GetString("admin-smite-yeet-description")) }; args.Verbs.Add(yeet); } + var breadName = Loc.GetString("admin-smite-become-bread-name").ToLowerInvariant(); // Will I get cancelled for breadName-ing you? Verb bread = new() { Text = "admin-smite-kill-sign-name", @@ -475,10 +491,11 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminBreadSmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-become-bread-description") + Message = string.Join(": ", breadName, Loc.GetString("admin-smite-become-bread-description")) }; args.Verbs.Add(bread); + var mouseName = Loc.GetString("admin-smite-become-mouse-name").ToLowerInvariant(); Verb mouse = new() { Text = "admin-smite-cluwne-name", @@ -489,15 +506,16 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminMouseSmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-become-mouse-description") + Message = string.Join(": ", mouseName, Loc.GetString("admin-smite-become-mouse-description")) }; args.Verbs.Add(mouse); if (TryComp(args.Target, out var actorComponent)) { + var ghostKickName = Loc.GetString("admin-smite-ghostkick-name").ToLowerInvariant(); Verb ghostKick = new() { - Text = "admin-smite-anger-pointing-arrows-name", + Text = ghostKickName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/gavel.svg.192dpi.png")), Act = () => @@ -505,15 +523,18 @@ private void AddSmiteVerbs(GetVerbsEvent args) _ghostKickManager.DoDisconnect(actorComponent.PlayerSession.Channel, "Smitten."); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-ghostkick-description") + Message = string.Join(": ", ghostKickName, Loc.GetString("admin-smite-ghostkick-description")) + }; args.Verbs.Add(ghostKick); } - if (TryComp(args.Target, out var inventory)) { + if (TryComp(args.Target, out var inventory)) + { + var nyanifyName = Loc.GetString("admin-smite-nyanify-name").ToLowerInvariant(); Verb nyanify = new() { - Text = "admin-smite-dust-name", + Text = nyanifyName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Head/Hats/catears.rsi"), "icon"), Act = () => @@ -524,13 +545,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-nyanify-description") + Message = string.Join(": ", nyanifyName, Loc.GetString("admin-smite-nyanify-description")) }; args.Verbs.Add(nyanify); + var killSignName = Loc.GetString("admin-smite-kill-sign-name").ToLowerInvariant(); Verb killSign = new() { - Text = "admin-smite-buffering-name", + Text = killSignName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Misc/killsign.rsi"), "icon"), Act = () => @@ -538,13 +560,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-kill-sign-description") + Message = string.Join(": ", killSignName, Loc.GetString("admin-smite-kill-sign-description")) }; args.Verbs.Add(killSign); + var cluwneName = Loc.GetString("admin-smite-cluwne-name").ToLowerInvariant(); Verb cluwne = new() { - Text = "admin-smite-become-instrument-name", + Text = cluwneName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Mask/cluwne.rsi"), "icon"), @@ -554,13 +577,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-cluwne-description") + Message = string.Join(": ", cluwneName, Loc.GetString("admin-smite-cluwne-description")) }; args.Verbs.Add(cluwne); + var maidenName = Loc.GetString("admin-smite-maid-name").ToLowerInvariant(); Verb maiden = new() { - Text = "admin-smite-remove-gravity-name", + Text = maidenName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi"), "icon"), Act = () => @@ -573,14 +597,15 @@ private void AddSmiteVerbs(GetVerbsEvent args) }); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-maid-description") + Message = string.Join(": ", maidenName, Loc.GetString("admin-smite-maid-description")) }; args.Verbs.Add(maiden); } + var angerPointingArrowsName = Loc.GetString("admin-smite-anger-pointing-arrows-name").ToLowerInvariant(); Verb angerPointingArrows = new() { - Text = "admin-smite-reptilian-species-swap-name", + Text = angerPointingArrowsName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/pointing.rsi"), "pointing"), Act = () => @@ -588,13 +613,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-anger-pointing-arrows-description") + Message = string.Join(": ", angerPointingArrowsName, Loc.GetString("admin-smite-anger-pointing-arrows-description")) }; args.Verbs.Add(angerPointingArrows); + var dustName = Loc.GetString("admin-smite-dust-name").ToLowerInvariant(); Verb dust = new() { - Text = "admin-smite-locker-stuff-name", + Text = dustName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Materials/materials.rsi"), "ash"), Act = () => @@ -604,13 +630,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _popupSystem.PopupEntity(Loc.GetString("admin-smite-turned-ash-other", ("name", args.Target)), args.Target, PopupType.LargeCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-dust-description"), + Message = string.Join(": ", dustName, Loc.GetString("admin-smite-dust-description")) }; args.Verbs.Add(dust); + var youtubeVideoSimulationName = Loc.GetString("admin-smite-buffering-name").ToLowerInvariant(); Verb youtubeVideoSimulation = new() { - Text = "admin-smite-headstand-name", + Text = youtubeVideoSimulationName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Misc/buffering_smite_icon.png")), Act = () => @@ -618,10 +645,11 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-buffering-description"), + Message = string.Join(": ", youtubeVideoSimulationName, Loc.GetString("admin-smite-buffering-description")) }; args.Verbs.Add(youtubeVideoSimulation); + var instrumentationName = Loc.GetString("admin-smite-become-instrument-name").ToLowerInvariant(); Verb instrumentation = new() { Text = "admin-smite-become-mouse-name", @@ -632,13 +660,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-become-instrument-description"), + Message = string.Join(": ", instrumentationName, Loc.GetString("admin-smite-become-instrument-description")) }; args.Verbs.Add(instrumentation); + var noGravityName = Loc.GetString("admin-smite-remove-gravity-name").ToLowerInvariant(); Verb noGravity = new() { - Text = "admin-smite-maid-name", + Text = noGravityName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Machines/gravity_generator.rsi"), "off"), Act = () => @@ -649,13 +678,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) Dirty(args.Target, grav); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-remove-gravity-description"), + Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description")) }; args.Verbs.Add(noGravity); + var reptilianName = Loc.GetString("admin-smite-reptilian-species-swap-name").ToLowerInvariant(); Verb reptilian = new() { - Text = "admin-smite-zoom-in-name", + Text = reptilianName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"), Act = () => @@ -663,13 +693,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite"); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-reptilian-species-swap-description"), + Message = string.Join(": ", reptilianName, Loc.GetString("admin-smite-reptilian-species-swap-description")) }; args.Verbs.Add(reptilian); + var lockerName = Loc.GetString("admin-smite-locker-stuff-name").ToLowerInvariant(); Verb locker = new() { - Text = "admin-smite-flip-eye-name", + Text = lockerName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Storage/closet.rsi"), "generic"), Act = () => @@ -685,10 +716,11 @@ private void AddSmiteVerbs(GetVerbsEvent args) _weldableSystem.SetWeldedState(locker, true); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-locker-stuff-description"), + Message = string.Join(": ", lockerName, Loc.GetString("admin-smite-locker-stuff-description")) }; args.Verbs.Add(locker); + var headstandName = Loc.GetString("admin-smite-headstand-name").ToLowerInvariant(); Verb headstand = new() { Text = "admin-smite-run-walk-swap-name", @@ -699,13 +731,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-headstand-description"), + Message = string.Join(": ", headstandName, Loc.GetString("admin-smite-headstand-description")) }; args.Verbs.Add(headstand); + var zoomInName = Loc.GetString("admin-smite-zoom-in-name").ToLowerInvariant(); Verb zoomIn = new() { - Text = "admin-smite-super-speed-name", + Text = zoomInName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/zoom.png")), Act = () => @@ -714,13 +747,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _eyeSystem.SetZoom(args.Target, eye.TargetZoom * 0.2f, ignoreLimits: true); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-zoom-in-description"), + Message = string.Join(": ", zoomInName, Loc.GetString("admin-smite-zoom-in-description")) }; args.Verbs.Add(zoomIn); + var flipEyeName = Loc.GetString("admin-smite-flip-eye-name").ToLowerInvariant(); Verb flipEye = new() { - Text = "admin-smite-stomach-removal-name", + Text = flipEyeName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/flip.png")), Act = () => @@ -729,13 +763,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) _eyeSystem.SetZoom(args.Target, eye.TargetZoom * -1, ignoreLimits: true); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-flip-eye-description"), + Message = string.Join(": ", flipEyeName, Loc.GetString("admin-smite-flip-eye-description")) }; args.Verbs.Add(flipEye); + var runWalkSwapName = Loc.GetString("admin-smite-run-walk-swap-name").ToLowerInvariant(); Verb runWalkSwap = new() { - Text = "admin-smite-speak-backwards-name", + Text = runWalkSwapName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/run-walk-swap.png")), Act = () => @@ -749,13 +784,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) args.Target, PopupType.LargeCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-run-walk-swap-description"), + Message = string.Join(": ", runWalkSwapName, Loc.GetString("admin-smite-run-walk-swap-description")) }; args.Verbs.Add(runWalkSwap); + var backwardsAccentName = Loc.GetString("admin-smite-speak-backwards-name").ToLowerInvariant(); Verb backwardsAccent = new() { - Text = "admin-smite-lung-removal-name", + Text = backwardsAccentName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/help-backwards.png")), Act = () => @@ -763,13 +799,14 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-speak-backwards-description"), + Message = string.Join(": ", backwardsAccentName, Loc.GetString("admin-smite-speak-backwards-description")) }; args.Verbs.Add(backwardsAccent); + var disarmProneName = Loc.GetString("admin-smite-disarm-prone-name").ToLowerInvariant(); Verb disarmProne = new() { - Text = "admin-smite-disarm-prone-name", + Text = disarmProneName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/disarm.png")), Act = () => @@ -777,10 +814,11 @@ private void AddSmiteVerbs(GetVerbsEvent args) EnsureComp(args.Target); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-disarm-prone-description"), + Message = string.Join(": ", disarmProneName, Loc.GetString("admin-smite-disarm-prone-description")) }; args.Verbs.Add(disarmProne); + var superSpeedName = Loc.GetString("admin-smite-super-speed-name").ToLowerInvariant(); Verb superSpeed = new() { Text = "admin-smite-garbage-can-name", @@ -795,41 +833,45 @@ private void AddSmiteVerbs(GetVerbsEvent args) args.Target, PopupType.LargeCaution); }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-super-speed-description"), + Message = string.Join(": ", superSpeedName, Loc.GetString("admin-smite-super-speed-description")) }; args.Verbs.Add(superSpeed); //Bonk + var superBonkLiteName = Loc.GetString("admin-smite-super-bonk-lite-name").ToLowerInvariant(); Verb superBonkLite = new() { - Text = "admin-smite-super-bonk-name", + Text = superBonkLiteName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/glass.rsi"), "full"), Act = () => { _superBonkSystem.StartSuperBonk(args.Target, stopWhenDead: true); }, - Message = Loc.GetString("admin-smite-super-bonk-lite-description"), Impact = LogImpact.Extreme, + Message = string.Join(": ", superBonkLiteName, Loc.GetString("admin-smite-super-bonk-lite-description")) }; args.Verbs.Add(superBonkLite); + + var superBonkName = Loc.GetString("admin-smite-super-bonk-name").ToLowerInvariant(); Verb superBonk= new() { - Text = "admin-smite-super-bonk-lite-name", + Text = superBonkName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/generic.rsi"), "full"), Act = () => { _superBonkSystem.StartSuperBonk(args.Target); }, - Message = Loc.GetString("admin-smite-super-bonk-description"), Impact = LogImpact.Extreme, + Message = string.Join(": ", superBonkName, Loc.GetString("admin-smite-super-bonk-description")) }; args.Verbs.Add(superBonk); + var superslipName = Loc.GetString("admin-smite-super-slip-name").ToLowerInvariant(); Verb superslip = new() { - Text = "admin-smite-super-slip-name", + Text = superslipName, Category = VerbCategory.Smite, Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Janitorial/soap.rsi"), "omega-4"), Act = () => @@ -849,7 +891,7 @@ private void AddSmiteVerbs(GetVerbsEvent args) } }, Impact = LogImpact.Extreme, - Message = Loc.GetString("admin-smite-super-slip-description") + Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description")) }; args.Verbs.Add(superslip); } diff --git a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs index 3e4d101f4fd..9f81c64dbc1 100644 --- a/Content.Server/Anomaly/Effects/TechAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/TechAnomalySystem.cs @@ -22,11 +22,17 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnTechMapInit); SubscribeLocalEvent(OnPulse); SubscribeLocalEvent(OnSupercritical); SubscribeLocalEvent(OnStabilityChanged); } + private void OnTechMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextTimer = _timing.CurTime; + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 3f33d01116d..224629ff2e5 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -11,7 +11,6 @@ using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Shuttles.Components; -using Content.Server.Station.Systems; using Content.Shared.Antag; using Content.Shared.Clothing; using Content.Shared.GameTicking; @@ -20,7 +19,6 @@ using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Players; -using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Content.Shared.Whitelist; using Robust.Server.Audio; @@ -37,14 +35,14 @@ namespace Content.Server.Antag; public sealed partial class AntagSelectionSystem : GameRuleSystem { - [Dependency] private readonly IChatManager _chat = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IServerPreferencesManager _pref = default!; [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly JobSystem _jobs = default!; [Dependency] private readonly LoadoutSystem _loadout = default!; [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerPreferencesManager _pref = default!; [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; @@ -193,6 +191,9 @@ protected override void Started(EntityUid uid, AntagSelectionComponent component /// /// Chooses antagonists from the given selection of players /// + /// The antagonist rule entity + /// The players to choose from + /// Disable picking players for pre-spawn antags in the middle of a round public void ChooseAntags(Entity ent, IList pool, bool midround = false) { if (ent.Comp.SelectionsComplete) @@ -209,8 +210,14 @@ public void ChooseAntags(Entity ent, IList /// Chooses antagonists from the given selection of players for the given antag definition. /// + /// The antagonist rule entity + /// The players to choose from + /// The antagonist selection parameters and criteria /// Disable picking players for pre-spawn antags in the middle of a round - public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def, bool midround = false) + public void ChooseAntags(Entity ent, + IList pool, + AntagSelectionDefinition def, + bool midround = false) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); @@ -331,7 +338,7 @@ public void MakeAntag(Entity ent, ICommonSession? sessi EntityManager.AddComponents(player, def.Components); // Equip the entity's RoleLoadout and LoadoutGroup - List>? gear = new(); + List> gear = new(); if (def.StartingGear is not null) gear.Add(def.StartingGear.Value); @@ -340,8 +347,8 @@ public void MakeAntag(Entity ent, ICommonSession? sessi if (session != null) { var curMind = session.GetMind(); - - if (curMind == null || + + if (curMind == null || !TryComp(curMind.Value, out var mindComp) || mindComp.OwnedEntity != antagEnt) { @@ -350,7 +357,7 @@ public void MakeAntag(Entity ent, ICommonSession? sessi } _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); - _role.MindAddRoles(curMind.Value, def.MindComponents, null, true); + _role.MindAddRoles(curMind.Value, def.MindRoles, null, true); ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); SendBriefing(session, def.Briefing); } diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs index 0e5a0afc9bc..502fb8eda2c 100644 --- a/Content.Server/Antag/Components/AntagSelectionComponent.cs +++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs @@ -3,7 +3,6 @@ using Content.Shared.Destructible.Thresholds; using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; -using Content.Shared.Storage; using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Player; @@ -145,10 +144,17 @@ public partial struct AntagSelectionDefinition() /// /// Components added to the player's mind. + /// Do NOT use this to add role-type components. Add those as MindRoles instead /// [DataField] public ComponentRegistry MindComponents = new(); + /// + /// List of Mind Role Prototypes to be added to the player's mind. + /// + [DataField] + public List>? MindRoles; + /// /// A set of starting gear that's equipped to the player. /// diff --git a/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs b/Content.Server/Atmos/Components/AtmosFixMarkerComponent.cs index 5123500239e..a60d042fb57 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/Atmos/Consoles/AtmosAlertsComputerSystem.cs b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs index d9a475dbfb7..2d35ae59731 100644 --- a/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs +++ b/Content.Server/Atmos/Consoles/AtmosAlertsComputerSystem.cs @@ -1,15 +1,19 @@ using Content.Server.Atmos.Monitor.Components; using Content.Server.DeviceNetwork.Components; +using Content.Server.DeviceNetwork.Systems; +using Content.Server.Pinpointer; using Content.Server.Power.Components; using Content.Shared.Atmos; using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Consoles; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor.Components; +using Content.Shared.DeviceNetwork.Components; using Content.Shared.Pinpointer; +using Content.Shared.Tag; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; -using Robust.Shared.Player; +using Robust.Shared.Timing; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -21,6 +25,12 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem [Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!; [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly MapSystem _mapSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + [Dependency] private readonly NavMapSystem _navMapSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly DeviceListSystem _deviceListSystem = default!; private const float UpdateTime = 1.0f; @@ -38,6 +48,9 @@ public override void Initialize() // Grid events SubscribeLocalEvent(OnGridSplit); + + // Alarm events + SubscribeLocalEvent(OnDeviceTerminatingEvent); SubscribeLocalEvent(OnDeviceAnchorChanged); } @@ -81,6 +94,16 @@ private void OnGridSplit(ref GridSplitEvent args) } private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args) + { + OnDeviceAdditionOrRemoval(uid, component, args.Anchored); + } + + private void OnDeviceTerminatingEvent(EntityUid uid, AtmosAlertsDeviceComponent component, ref EntityTerminatingEvent args) + { + OnDeviceAdditionOrRemoval(uid, component, false); + } + + private void OnDeviceAdditionOrRemoval(EntityUid uid, AtmosAlertsDeviceComponent component, bool isAdding) { var xform = Transform(uid); var gridUid = xform.GridUid; @@ -88,10 +111,13 @@ private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent com if (gridUid == null) return; - if (!TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data)) + if (!TryComp(xform.GridUid, out var navMap)) return; - var netEntity = EntityManager.GetNetEntity(uid); + if (!TryGetAtmosDeviceNavMapData(uid, component, xform, out var data)) + return; + + var netEntity = GetNetEntity(uid); var query = AllEntityQuery(); while (query.MoveNext(out var ent, out var entConsole, out var entXform)) @@ -99,11 +125,18 @@ private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent com if (gridUid != entXform.GridUid) continue; - if (args.Anchored) + if (isAdding) + { entConsole.AtmosDevices.Add(data.Value); + } - else if (!args.Anchored) + else + { entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity); + _navMapSystem.RemoveNavMapRegion(gridUid.Value, navMap, netEntity); + } + + Dirty(ent, entConsole); } } @@ -209,6 +242,12 @@ private List GetAlarmStateData(EntityUid gridUid, Atmo if (entDevice.Group != group) continue; + if (!TryComp(entXform.GridUid, out var mapGrid)) + continue; + + if (!TryComp(entXform.GridUid, out var navMap)) + continue; + // If emagged, change the alarm type to normal var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState; @@ -216,14 +255,45 @@ private List GetAlarmStateData(EntityUid gridUid, Atmo if (TryComp(ent, out var entAPCPower) && !entAPCPower.Powered) alarmState = AtmosAlarmType.Invalid; + // Create entry + var netEnt = GetNetEntity(ent); + var entry = new AtmosAlertsComputerEntry - (GetNetEntity(ent), + (netEnt, GetNetCoordinates(entXform.Coordinates), entDevice.Group, alarmState, MetaData(ent).EntityName, entDeviceNetwork.Address); + // Get the list of sensors attached to the alarm + var sensorList = TryComp(ent, out var entDeviceList) ? _deviceListSystem.GetDeviceList(ent, entDeviceList) : null; + + if (sensorList?.Any() == true) + { + var alarmRegionSeeds = new HashSet(); + + // If valid and anchored, use the position of sensors as seeds for the region + foreach (var (address, sensorEnt) in sensorList) + { + if (!sensorEnt.IsValid() || !HasComp(sensorEnt)) + continue; + + var sensorXform = Transform(sensorEnt); + + if (sensorXform.Anchored && sensorXform.GridUid == entXform.GridUid) + alarmRegionSeeds.Add(_mapSystem.CoordinatesToTile(entXform.GridUid.Value, mapGrid, _transformSystem.GetMapCoordinates(sensorEnt, sensorXform))); + } + + var regionProperties = new SharedNavMapSystem.NavMapRegionProperties(netEnt, AtmosAlertsComputerUiKey.Key, alarmRegionSeeds); + _navMapSystem.AddOrUpdateNavMapRegion(gridUid, navMap, netEnt, regionProperties); + } + + else + { + _navMapSystem.RemoveNavMapRegion(entXform.GridUid.Value, navMap, netEnt); + } + alarmStateData.Add(entry); } @@ -306,7 +376,10 @@ private HashSet GetAllAtmosDeviceNavMapData(EntityU var query = AllEntityQuery(); while (query.MoveNext(out var ent, out var entComponent, out var entXform)) { - if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data)) + if (entXform.GridUid != gridUid) + continue; + + if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, out var data)) atmosDeviceNavMapData.Add(data.Value); } @@ -317,14 +390,10 @@ private bool TryGetAtmosDeviceNavMapData (EntityUid uid, AtmosAlertsDeviceComponent component, TransformComponent xform, - EntityUid gridUid, [NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output) { output = null; - if (xform.GridUid != gridUid) - return false; - if (!xform.Anchored) return false; diff --git a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs index cd4aa4a0981..345f51783ca 100644 --- a/Content.Server/Bed/Cryostorage/CryostorageSystem.cs +++ b/Content.Server/Bed/Cryostorage/CryostorageSystem.cs @@ -239,7 +239,7 @@ public void HandleEnterCryostorage(Entity ent, Ne Loc.GetString( "earlyleave-cryo-announcement", ("character", name), - ("entity", ent.Owner), + ("entity", ent.Owner), // gender things for supporting downstreams with other languages ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName)) ), Loc.GetString("earlyleave-cryo-sender"), playDefaultSound: false diff --git a/Content.Server/Body/Systems/BloodstreamSystem.cs b/Content.Server/Body/Systems/BloodstreamSystem.cs index 18790e7326b..198123cc5fd 100644 --- a/Content.Server/Body/Systems/BloodstreamSystem.cs +++ b/Content.Server/Body/Systems/BloodstreamSystem.cs @@ -472,7 +472,7 @@ public void ChangeBloodReagent(EntityUid uid, string reagent, BloodstreamCompone return; } - var currentVolume = bloodSolution.RemoveReagent(component.BloodReagent, bloodSolution.Volume); + var currentVolume = bloodSolution.RemoveReagent(component.BloodReagent, bloodSolution.Volume, ignoreReagentData: true); component.BloodReagent = reagent; diff --git a/Content.Server/Botany/Components/PlantHolderComponent.cs b/Content.Server/Botany/Components/PlantHolderComponent.cs index 8218bead72c..f0661e4a301 100644 --- a/Content.Server/Botany/Components/PlantHolderComponent.cs +++ b/Content.Server/Botany/Components/PlantHolderComponent.cs @@ -1,5 +1,6 @@ using Content.Shared.Chemistry.Components; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +using Robust.Shared.Audio; namespace Content.Server.Botany.Components; @@ -23,6 +24,9 @@ public sealed partial class PlantHolderComponent : Component [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] public TimeSpan LastCycle = TimeSpan.Zero; + [DataField] + public SoundSpecifier? WateringSound; + [DataField] public bool UpdateSpriteAfterUpdate; diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index 0fdca029b79..34d6a75bf25 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; -using Content.Server.Fluids.Components; using Content.Server.Kitchen.Components; using Content.Server.Popups; using Content.Shared.Chemistry.EntitySystems; @@ -18,7 +17,6 @@ using Content.Shared.Random; using Content.Shared.Tag; using Robust.Server.GameObjects; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -37,7 +35,6 @@ public sealed class PlantHolderSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedPointLightSystem _pointLight = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly RandomHelperSystem _randomHelper = default!; @@ -53,6 +50,7 @@ public override void Initialize() SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnSolutionTransferred); } public override void Update(float frameTime) @@ -158,6 +156,7 @@ private void OnInteractUsing(Entity entity, ref InteractUs if (!_botany.TryGetSeed(seeds, out var seed)) return; + args.Handled = true; var name = Loc.GetString(seed.Name); var noun = Loc.GetString(seed.Noun); _popup.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", @@ -185,6 +184,7 @@ private void OnInteractUsing(Entity entity, ref InteractUs return; } + args.Handled = true; _popup.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", ("name", Comp(uid).EntityName)), args.User, PopupType.Medium); return; @@ -192,6 +192,7 @@ private void OnInteractUsing(Entity entity, ref InteractUs if (_tagSystem.HasTag(args.Used, "Hoe")) { + args.Handled = true; if (component.WeedLevel > 0) { _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message", @@ -211,6 +212,7 @@ private void OnInteractUsing(Entity entity, ref InteractUs if (HasComp(args.Used)) { + args.Handled = true; if (component.Seed != null) { _popup.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message", @@ -228,39 +230,9 @@ private void OnInteractUsing(Entity entity, ref InteractUs return; } - if (_solutionContainerSystem.TryGetDrainableSolution(args.Used, out var solution, out _) - && _solutionContainerSystem.ResolveSolution(uid, component.SoilSolutionName, ref component.SoilSolution) - && TryComp(args.Used, out SprayComponent? spray)) - { - var amount = FixedPoint2.New(1); - - var targetEntity = uid; - var solutionEntity = args.Used; - - _audio.PlayPvs(spray.SpraySound, args.Used, AudioParams.Default.WithVariation(0.125f)); - - var split = _solutionContainerSystem.Drain(solutionEntity, solution.Value, amount); - - if (split.Volume == 0) - { - _popup.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", - ("owner", args.Used)), args.User); - return; - } - - _popup.PopupCursor(Loc.GetString("plant-holder-component-spray-message", - ("owner", uid), - ("amount", split.Volume)), args.User, PopupType.Medium); - - _solutionContainerSystem.TryAddSolution(component.SoilSolution.Value, split); - - ForceUpdateByExternalCause(uid, component); - - return; - } - if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) { + args.Handled = true; if (component.Seed == null) { _popup.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), args.User); @@ -316,10 +288,15 @@ private void OnInteractUsing(Entity entity, ref InteractUs } if (HasComp(args.Used)) + { + args.Handled = true; DoHarvest(uid, args.User, component); + return; + } if (TryComp(args.Used, out var produce)) { + args.Handled = true; _popup.PopupCursor(Loc.GetString("plant-holder-component-compost-message", ("owner", uid), ("usingItem", args.Used)), args.User, PopupType.Medium); @@ -351,6 +328,10 @@ private void OnInteractUsing(Entity entity, ref InteractUs } } + private void OnSolutionTransferred(Entity ent, ref SolutionTransferredEvent args) + { + _audio.PlayPvs(ent.Comp.WateringSound, ent.Owner); + } private void OnInteractHand(Entity entity, ref InteractHandEvent args) { DoHarvest(entity, args.User, entity.Comp); diff --git a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs index 37d7015fd9f..373e8e243ba 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Bounty.cs @@ -420,6 +420,13 @@ public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCarg return false; _nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal); + var newBounty = new CargoBountyData(bounty, randomVal); + // This bounty id already exists! Probably because NameIdentifierSystem ran out of ids. + if (component.Bounties.Any(b => b.Id == newBounty.Id)) + { + Log.Error("Failed to add bounty {ID} because another one with the same ID already existed!", newBounty.Id); + return false; + } component.Bounties.Add(new CargoBountyData(bounty, randomVal)); _adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}"); component.TotalBounties++; diff --git a/Content.Server/Chat/Managers/ChatManager.RateLimit.cs b/Content.Server/Chat/Managers/ChatManager.RateLimit.cs index ccb38166a6d..86bd3619e5e 100644 --- a/Content.Server/Chat/Managers/ChatManager.RateLimit.cs +++ b/Content.Server/Chat/Managers/ChatManager.RateLimit.cs @@ -28,7 +28,7 @@ private void RateLimitPlayerLimited(ICommonSession player) private void RateLimitAlertAdmins(ICommonSession player) { - SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name))); + // SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name))); // DeltaV - I DON'T CARE } public RateLimitStatus HandleRateLimit(ICommonSession player) diff --git a/Content.Server/Chat/Managers/ChatSanitizationManager.cs b/Content.Server/Chat/Managers/ChatSanitizationManager.cs index 634d8cdefab..0c78e45f86e 100644 --- a/Content.Server/Chat/Managers/ChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/ChatSanitizationManager.cs @@ -1,17 +1,19 @@ using System.Diagnostics.CodeAnalysis; -using System.Globalization; +using System.Text.RegularExpressions; using Content.Shared.CCVar; using Robust.Shared.Configuration; namespace Content.Server.Chat.Managers; +/// +/// Sanitizes messages! +/// It currently ony removes the shorthands for emotes (like "lol" or "^-^") from a chat message and returns the last +/// emote in their message +/// public sealed class ChatSanitizationManager : IChatSanitizationManager { - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - private static readonly Dictionary SmileyToEmote = new() + private static readonly Dictionary ShorthandToEmote = new() { - // I could've done this with regex, but felt it wasn't the right idea. { ":)", "chatsan-smiles" }, { ":]", "chatsan-smiles" }, { "=)", "chatsan-smiles" }, @@ -33,7 +35,7 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { ":D", "chatsan-smiles-widely" }, { "D:", "chatsan-frowns-deeply" }, { ":O", "chatsan-surprised" }, - { ":3", "chatsan-smiles" }, //nope + { ":3", "chatsan-smiles" }, { ":S", "chatsan-uncertain" }, { ":>", "chatsan-grins" }, { ":<", "chatsan-pouts" }, @@ -75,7 +77,7 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { "kek", "chatsan-laughs" }, { "rofl", "chatsan-laughs" }, { "o7", "chatsan-salutes" }, - { ";_;7", "chatsan-tearfully-salutes"}, + { ";_;7", "chatsan-tearfully-salutes" }, { "idk", "chatsan-shrugs" }, { ";)", "chatsan-winks" }, { ";]", "chatsan-winks" }, @@ -88,9 +90,12 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager { "(':", "chatsan-tearfully-smiles" }, { "[':", "chatsan-tearfully-smiles" }, { "('=", "chatsan-tearfully-smiles" }, - { "['=", "chatsan-tearfully-smiles" }, + { "['=", "chatsan-tearfully-smiles" } }; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly ILocalizationManager _loc = default!; + private bool _doSanitize; public void Initialize() @@ -98,29 +103,60 @@ public void Initialize() _configurationManager.OnValueChanged(CCVars.ChatSanitizerEnabled, x => _doSanitize = x, true); } - public bool TrySanitizeOutSmilies(string input, EntityUid speaker, out string sanitized, [NotNullWhen(true)] out string? emote) + /// + /// Remove the shorthands from the message, returning the last one found as the emote + /// + /// The pre-sanitized message + /// The speaker + /// The sanitized message with shorthands removed + /// The localized emote + /// True if emote has been sanitized out + public bool TrySanitizeEmoteShorthands(string message, + EntityUid speaker, + out string sanitized, + [NotNullWhen(true)] out string? emote) { + emote = null; + sanitized = message; + if (!_doSanitize) - { - sanitized = input; - emote = null; return false; - } - input = input.TrimEnd(); + // -1 is just a canary for nothing found yet + var lastEmoteIndex = -1; - foreach (var (smiley, replacement) in SmileyToEmote) + foreach (var (shorthand, emoteKey) in ShorthandToEmote) { - if (input.EndsWith(smiley, true, CultureInfo.InvariantCulture)) + // We have to escape it because shorthands like ":)" or "-_-" would break the regex otherwise. + var escaped = Regex.Escape(shorthand); + + // So there are 2 cases: + // - If there is whitespace before it and after it is either punctuation, whitespace, or the end of the line + // Delete the word and the whitespace before + // - If it is at the start of the string and is followed by punctuation, whitespace, or the end of the line + // Delete the word and the punctuation if it exists. + var pattern = + $@"\s{escaped}(?=\p{{P}}|\s|$)|^{escaped}(?:\p{{P}}|(?=\s|$))"; + + var r = new Regex(pattern, RegexOptions.RightToLeft | RegexOptions.IgnoreCase); + + // We're using sanitized as the original message until the end so that we can make sure the indices of + // the emotes are accurate. + var lastMatch = r.Match(sanitized); + + if (!lastMatch.Success) + continue; + + if (lastMatch.Index > lastEmoteIndex) { - sanitized = input.Remove(input.Length - smiley.Length).TrimEnd(); - emote = Loc.GetString(replacement, ("ent", speaker)); - return true; + lastEmoteIndex = lastMatch.Index; + emote = _loc.GetString(emoteKey, ("ent", speaker)); } + + message = r.Replace(message, string.Empty); } - sanitized = input; - emote = null; - return false; + sanitized = message.Trim(); + return emote is not null; } } diff --git a/Content.Server/Chat/Managers/IChatSanitizationManager.cs b/Content.Server/Chat/Managers/IChatSanitizationManager.cs index c067cf02ee7..ac85d4b4a7a 100644 --- a/Content.Server/Chat/Managers/IChatSanitizationManager.cs +++ b/Content.Server/Chat/Managers/IChatSanitizationManager.cs @@ -6,5 +6,8 @@ public interface IChatSanitizationManager { public void Initialize(); - public bool TrySanitizeOutSmilies(string input, EntityUid speaker, out string sanitized, [NotNullWhen(true)] out string? emote); + public bool TrySanitizeEmoteShorthands(string input, + EntityUid speaker, + out string sanitized, + [NotNullWhen(true)] out string? emote); } diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b173dbbdf97..4c88e8cc5a4 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -756,11 +756,12 @@ private bool CanSendInGame(string message, IConsoleShell? shell = null, ICommonS // ReSharper disable once InconsistentNaming private string SanitizeInGameICMessage(EntityUid source, string message, out string? emoteStr, bool capitalize = true, bool punctuate = false, bool capitalizeTheWordI = true) { - var newMessage = message.Trim(); - newMessage = SanitizeMessageReplaceWords(newMessage); + var newMessage = SanitizeMessageReplaceWords(message.Trim()); + + GetRadioKeycodePrefix(source, newMessage, out newMessage, out var prefix); - // DeltaV: sanitize first - _sanitizer.TrySanitizeOutSmilies(newMessage, source, out newMessage, out emoteStr); + // Sanitize it first as it might change the word order + _sanitizer.TrySanitizeEmoteShorthands(newMessage, source, out newMessage, out emoteStr); if (capitalize) newMessage = SanitizeMessageCapital(newMessage); @@ -769,7 +770,7 @@ private string SanitizeInGameICMessage(EntityUid source, string message, out str if (punctuate) newMessage = SanitizeMessagePeriod(newMessage); - return newMessage; + return prefix + newMessage; } private string SanitizeInGameOOCMessage(string message) diff --git a/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs b/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs new file mode 100644 index 00000000000..0f10e2a4492 --- /dev/null +++ b/Content.Server/Chemistry/Components/SolutionInjectWhileEmbeddedComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + + +namespace Content.Server.Chemistry.Components; + +/// +/// Used for embeddable entities that should try to inject a +/// contained solution into a target over time while they are embbeded into. +/// +[RegisterComponent, AutoGenerateComponentPause] +public sealed partial class SolutionInjectWhileEmbeddedComponent : BaseSolutionInjectOnEventComponent { + /// + ///The time at which the injection will happen. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan NextUpdate; + + /// + ///The delay between each injection in seconds. + /// + [DataField] + public TimeSpan UpdateInterval = TimeSpan.FromSeconds(3); +} + diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs index d56fded024a..f15edcf0672 100644 --- a/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectOnEventSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Body.Systems; using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Events; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Projectiles; @@ -29,6 +30,7 @@ public override void Initialize() SubscribeLocalEvent(HandleProjectileHit); SubscribeLocalEvent(HandleEmbed); SubscribeLocalEvent(HandleMeleeHit); + SubscribeLocalEvent(OnInjectOverTime); } private void HandleProjectileHit(Entity entity, ref ProjectileHitEvent args) @@ -49,6 +51,11 @@ private void HandleMeleeHit(Entity entity, ref M TryInjectTargets((entity.Owner, entity.Comp), args.HitEntities, args.User); } + private void OnInjectOverTime(Entity entity, ref InjectOverTimeEvent args) + { + DoInjection((entity.Owner, entity.Comp), args.EmbeddedIntoUid); + } + private void DoInjection(Entity injectorEntity, EntityUid target, EntityUid? source = null) { TryInjectTargets(injectorEntity, [target], source); diff --git a/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs b/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs new file mode 100644 index 00000000000..2baeba9da15 --- /dev/null +++ b/Content.Server/Chemistry/EntitySystems/SolutionInjectWhileEmbeddedSystem.cs @@ -0,0 +1,60 @@ +using Content.Server.Body.Components; +using Content.Server.Body.Systems; +using Content.Server.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Chemistry.Events; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Content.Shared.Projectiles; +using Content.Shared.Tag; +using Content.Shared.Weapons.Melee.Events; +using Robust.Shared.Collections; +using Robust.Shared.Timing; + +namespace Content.Server.Chemistry.EntitySystems; + +/// +/// System for handling injecting into an entity while a projectile is embedded. +/// +public sealed class SolutionInjectWhileEmbeddedSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly BloodstreamSystem _bloodstream = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly TagSystem _tag = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var injectComponent, out var projectileComponent)) + { + if (_gameTiming.CurTime < injectComponent.NextUpdate) + continue; + + injectComponent.NextUpdate += injectComponent.UpdateInterval; + + if(projectileComponent.EmbeddedIntoUid == null) + continue; + + var ev = new InjectOverTimeEvent(projectileComponent.EmbeddedIntoUid.Value); + RaiseLocalEvent(uid, ref ev); + + } + } +} diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index ac33250474a..0eafad35862 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -271,7 +271,7 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity logs) { + const int maxRetryAttempts = 5; + var initialRetryDelay = TimeSpan.FromSeconds(5); + DebugTools.Assert(logs.All(x => x.RoundId > 0), "Adding logs with invalid round ids."); - await using var db = await GetDb(); - db.DbContext.AdminLog.AddRange(logs); - await db.DbContext.SaveChangesAsync(); + + var attempt = 0; + var retryDelay = initialRetryDelay; + + while (attempt < maxRetryAttempts) + { + try + { + await using var db = await GetDb(); + db.DbContext.AdminLog.AddRange(logs); + await db.DbContext.SaveChangesAsync(); + _opsLog.Debug($"Successfully saved {logs.Count} admin logs."); + break; + } + catch (Exception ex) + { + attempt += 1; + _opsLog.Error($"Attempt {attempt} failed to save logs: {ex}"); + + if (attempt >= maxRetryAttempts) + { + _opsLog.Error($"Max retry attempts reached. Failed to save {logs.Count} admin logs."); + return; + } + + _opsLog.Warning($"Retrying in {retryDelay.TotalSeconds} seconds..."); + await Task.Delay(retryDelay); + + retryDelay *= 2; + } + } } protected abstract IQueryable StartAdminLogsQuery(ServerDbContext db, LogFilter? filter = null); diff --git a/Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs b/Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs index ced4ec802d8..e13050add48 100644 --- a/Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs +++ b/Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs @@ -14,7 +14,6 @@ public sealed partial class GhostRoleSystem { [Dependency] private readonly IServerPreferencesManager _prefs = default!; [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private void OnSpawnerTakeCharacter( EntityUid uid, GhostRoleCharacterSpawnerComponent component, ref TakeGhostRoleEvent args) @@ -33,7 +32,7 @@ private void OnSpawnerTakeCharacter( EntityUid uid, GhostRoleCharacterSpawnerCom _transform.AttachToGridOrMap(mob); string? outfit = null; - if (_prototypeManager.TryIndex(component.OutfitPrototype, out var outfitProto)) + if (_prototype.TryIndex(component.OutfitPrototype, out var outfitProto)) outfit = outfitProto.ID; var spawnedEvent = new GhostRoleSpawnerUsedEvent(uid, mob); diff --git a/Content.Server/DeltaV/Objectives/Systems/NotJobsRequirementSystem.cs b/Content.Server/DeltaV/Objectives/Systems/NotJobsRequirementSystem.cs index 4ba15008371..e6667f3b159 100644 --- a/Content.Server/DeltaV/Objectives/Systems/NotJobsRequirementSystem.cs +++ b/Content.Server/DeltaV/Objectives/Systems/NotJobsRequirementSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Objectives.Components; using Content.Shared.Objectives.Components; -using Content.Shared.Roles.Jobs; +using Content.Shared.Roles; namespace Content.Server.Objectives.Systems; @@ -9,23 +9,29 @@ namespace Content.Server.Objectives.Systems; /// public sealed class NotJobsRequirementSystem : EntitySystem { + private EntityQuery _query; + public override void Initialize() { base.Initialize(); + _query = GetEntityQuery(); + SubscribeLocalEvent(OnCheck); } - private void OnCheck(EntityUid uid, NotJobsRequirementComponent comp, ref RequirementCheckEvent args) + private void OnCheck(Entity ent, ref RequirementCheckEvent args) { if (args.Cancelled) return; - // if player has no job then don't care - if (!TryComp(args.MindId, out var job)) - return; - foreach (string forbidJob in comp.Jobs) - if (job.Prototype == forbidJob) - args.Cancelled = true; + foreach (var forbidJob in ent.Comp.Jobs) + { + foreach (var roleId in args.Mind.MindRoles) + { + if (_query.CompOrNull(roleId)?.JobPrototype == forbidJob) + args.Cancelled = true; + } + } } } diff --git a/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs b/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs index 102563ddcb2..27129e49e9a 100644 --- a/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/ParadoxClonerRule.cs @@ -59,16 +59,17 @@ private bool TrySpawnParadoxAnomaly(EntityUid spawner, [NotNullWhen(true)] out E clone = null; // Get a list of potential candidates - var candidates = new List<(EntityUid, Entity, HumanoidCharacterProfile)>(); + var candidates = new List<(EntityUid, EntityUid, ProtoId, HumanoidCharacterProfile)>(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var mindContainer, out var humanoid)) { if (humanoid.LastProfileLoaded is {} profile && - _mind.GetMind(uid, mindContainer) is {} mindId && - TryComp(mindId, out var job) && - !_role.MindIsAntagonist(mindId)) + mindContainer.Mind is {} mindId && + !_role.MindIsAntagonist(mindId) && + _role.MindHasRole(mindId, out var role) && + role?.Comp1.JobPrototype is {} job) { - candidates.Add((uid, (mindId, job), profile)); + candidates.Add((uid, mindId, job, profile)); } } @@ -82,10 +83,10 @@ private bool TrySpawnParadoxAnomaly(EntityUid spawner, [NotNullWhen(true)] out E return true; } - private EntityUid SpawnParadoxAnomaly(EntityUid spawner, List<(EntityUid, Entity, HumanoidCharacterProfile)> candidates) + private EntityUid SpawnParadoxAnomaly(EntityUid spawner, List<(EntityUid, EntityUid, ProtoId, HumanoidCharacterProfile)> candidates) { // Select a candidate. - var (uid, (mindId, job), profile) = _random.Pick(candidates); + var (uid, mindId, job, profile) = _random.Pick(candidates); // Spawn the clone. var coords = Transform(spawner).Coordinates; diff --git a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs index 20d67d6de0c..9621d6945f6 100644 --- a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs @@ -1,49 +1,53 @@ using System.Linq; using Content.Shared.EntityEffects; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Localizations; -using Robust.Shared.Prototypes; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; -using Content.Shared.Station; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.IoC; +using Robust.Shared.Prototypes; namespace Content.Server.EntityEffects.EffectConditions; public sealed partial class JobCondition : EntityEffectCondition { [DataField(required: true)] public List> Job; - + public override bool Condition(EntityEffectBaseArgs args) - { + { args.EntityManager.TryGetComponent(args.TargetEntity, out var mindContainer); - if (mindContainer != null && mindContainer.Mind != null) + + if ( mindContainer is null + || !args.EntityManager.TryGetComponent(mindContainer.Mind, out var mind)) + return false; + + foreach (var roleId in mind.MindRoles) { - var prototypeManager = IoCManager.Resolve(); - if (args.EntityManager.TryGetComponent(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype)) + if(!args.EntityManager.HasComponent(roleId)) + continue; + + if (!args.EntityManager.TryGetComponent(roleId, out var mindRole)) { - foreach (var jobId in Job) - { - if (prototype.ID == jobId) - { - return true; - } - } + 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; } - + return false; } - + public override string GuidebookExplanation(IPrototypeManager prototype) { var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList(); return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames))); } } - - diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 0c7668d354d..a7dd5d6ab62 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -4,6 +4,7 @@ using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Maps; +using Content.Server.Roles; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; @@ -26,6 +27,7 @@ namespace Content.Server.GameTicking public sealed partial class GameTicker { [Dependency] private readonly DiscordWebhook _discord = default!; + [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly ITaskManager _taskManager = default!; private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( @@ -190,9 +192,6 @@ public int ReadyPlayerCount() if (!_playerManager.TryGetSessionById(userId, out _)) continue; - if (_banManager.GetRoleBans(userId) == null) - continue; - total++; } @@ -236,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)) @@ -339,8 +334,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 = "") @@ -373,7 +383,7 @@ public void ShowRoundEndScoreboard(string text = "") var userId = mind.UserId ?? mind.OriginalOwnerUserId; var connected = false; - var observer = HasComp(mindId); + var observer = _role.MindHasRole(mindId); // Continuing if (userId != null && _playerManager.ValidSessionId(userId.Value)) { @@ -400,7 +410,7 @@ public void ShowRoundEndScoreboard(string text = "") _pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true); } - var roles = _roles.MindGetAllRoles(mindId); + var roles = _roles.MindGetAllRoleInfo(mindId); var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo() { diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index c20c18af129..8bec89747d9 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -3,8 +3,6 @@ using System.Numerics; using Content.Server.Administration.Managers; using Content.Server.GameTicking.Events; -using Content.Server.Ghost; -using Content.Server.Shuttles.Components; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; @@ -224,8 +222,7 @@ private void SpawnPlayer(ICommonSession player, _mind.SetUserId(newMind, data.UserId); var jobPrototype = _prototypeManager.Index(jobId); - var job = new JobComponent {Prototype = jobId}; - _roles.MindAddRole(newMind, job, silent: silent); + _roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId); var jobName = _jobs.MindTryGetJobName(newMind); _playTimeTrackings.PlayerRolesChanged(player); @@ -238,7 +235,7 @@ private void SpawnPlayer(ICommonSession player, spawnPointType = SpawnPointType.Job; } - var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character, spawnPointType: spawnPointType); // DeltaV: pass in spawn point type + var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character, spawnPointType: spawnPointType); // DeltaV: pass in spawn point type DebugTools.AssertNotNull(mobMaybe); var mob = mobMaybe!.Value; @@ -277,13 +274,17 @@ private void SpawnPlayer(ICommonSession player, _stationJobs.TryAssignJob(station, jobPrototype, player.UserId); if (lateJoin) + { _adminLogger.Add(LogType.LateJoin, LogImpact.Medium, $"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + } else + { _adminLogger.Add(LogType.RoundStartJoin, LogImpact.Medium, $"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + } // Make sure they're aware of extended access. if (Comp(station).ExtendedAccess @@ -369,7 +370,7 @@ public void SpawnObserver(ICommonSession player) var (mindId, mindComp) = _mind.CreateMind(player.UserId, name); mind = (mindId, mindComp); _mind.SetUserId(mind.Value, player.UserId); - _roles.MindAddRole(mind.Value, new ObserverRoleComponent()); + _roles.MindAddRole(mind.Value, "MindRoleObserver"); } var ghost = _ghost.SpawnGhost(mind.Value); diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs index 62f92963aa7..6f82aa042f0 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Dataset; +using Content.Shared.FixedPoint; using Content.Shared.NPC.Prototypes; using Content.Shared.Random; using Content.Shared.Roles; @@ -31,6 +32,24 @@ public sealed partial class TraitorRuleComponent : Component [DataField] public ProtoId ObjectiveIssuers = "TraitorCorporations"; + /// + /// Give this traitor an Uplink on spawn. + /// + [DataField] + public bool GiveUplink = true; + + /// + /// Give this traitor the codewords. + /// + [DataField] + public bool GiveCodewords = true; + + /// + /// Give this traitor a briefing in chat. + /// + [DataField] + public bool GiveBriefing = true; + public int TotalTraitors => TraitorMinds.Count; public string[] Codewords = new string[3]; @@ -68,5 +87,5 @@ public enum SelectionState /// The amount of TC traitors start with. /// [DataField] - public int StartingBalance = 20; + public FixedPoint2 StartingBalance = 20; } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 57239ee8c15..ca6548301a7 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -9,7 +9,6 @@ using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; -using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs; @@ -31,9 +30,9 @@ namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; [Dependency] private readonly StoreSystem _store = default!; @@ -57,6 +56,8 @@ public override void Initialize() SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnOperativeZombified); + SubscribeLocalEvent(OnGetBriefing); + SubscribeLocalEvent(OnShuttleFTLAttempt); SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); @@ -65,7 +66,9 @@ public override void Initialize() SubscribeLocalEvent(OnRuleLoadedGrids); } - protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + protected override void Started(EntityUid uid, + NukeopsRuleComponent component, + GameRuleComponent gameRule, GameRuleStartedEvent args) { var eligible = new List>(); @@ -85,7 +88,9 @@ protected override void Started(EntityUid uid, NukeopsRuleComponent component, G } #region Event Handlers - protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + protected override void AppendRoundEndText(EntityUid uid, + NukeopsRuleComponent component, + GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); @@ -227,7 +232,8 @@ private void OnRoundEnd(Entity ent) // If the disk is currently at Central Command, the crew wins - just slightly. // This also implies that some nuclear operatives have died. - SetWinType(ent, diskAtCentCom + SetWinType(ent, + diskAtCentCom ? WinType.CrewMinor : WinType.OpsMinor); ent.Comp.WinConditions.Add(diskAtCentCom @@ -456,8 +462,11 @@ private void CheckRoundShouldEnd(Entity ent) : WinCondition.AllNukiesDead); SetWinType(ent, WinType.CrewMajor, false); - _roundEndSystem.DoRoundEndBehavior( - nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); + _roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior, + nukeops.EvacShuttleTime, + nukeops.RoundEndTextSender, + nukeops.RoundEndTextShuttleCall, + nukeops.RoundEndTextAnnouncement); // prevent it called multiple times nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; @@ -465,16 +474,22 @@ private void CheckRoundShouldEnd(Entity ent) private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - if (ent.Comp.TargetStation is not { } station) - return; + var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target"; - _antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", - ("station", station), + _antag.SendBriefing(args.Session, + Loc.GetString("nukeops-welcome", + ("station", target), ("name", Name(ent))), Color.Red, ent.Comp.GreetSoundNotification); } + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) + { + // TODO Different character screen briefing for the 3 nukie types + args.Append(Loc.GetString("nukeops-briefing")); + } + /// /// Is this method the shitty glue holding together the last of my sanity? yes. /// Do i have a better solution? not presently. diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index c5f88ab6cf1..a313b78eaf1 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -15,7 +15,6 @@ using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; -using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mindshield.Components; using Content.Shared.Mobs; @@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules; public sealed class RevolutionaryRuleSystem : GameRuleSystem { [Dependency] private readonly IAdminLogManager _adminLogManager = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; [Dependency] private readonly EuiManager _euiMan = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly MobStateSystem _mobState = default!; @@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -59,9 +58,12 @@ public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnCommandMobStateChanged); + + SubscribeLocalEvent(OnPostFlash); SubscribeLocalEvent(OnHeadRevMobStateChanged); + SubscribeLocalEvent(OnGetBriefing); - SubscribeLocalEvent(OnPostFlash); + } protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) @@ -85,7 +87,9 @@ protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent com } } - protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, + protected override void AppendRoundEndText(EntityUid uid, + RevolutionaryRuleComponent component, + GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { base.AppendRoundEndText(uid, component, gameRule, ref args); @@ -101,7 +105,9 @@ protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleCompo args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count))); foreach (var (mind, data, name) in sessionData) { - var count = CompOrNull(mind)?.ConvertedCount ?? 0; + _role.MindHasRole(mind, out var role); + var count = CompOrNull(role)?.ConvertedCount ?? 0; + args.AddLine(Loc.GetString("rev-headrev-name-user", ("name", name), ("username", data.UserName), @@ -113,10 +119,8 @@ protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleCompo private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args) { - if (!TryComp(uid, out var mind) || mind.OwnedEntity == null) - return; - - var head = HasComp(mind.OwnedEntity); + var ent = args.Mind.Comp.OwnedEntity; + var head = HasComp(ent); args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing")); } @@ -145,15 +149,20 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft if (ev.User != null) { - _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary"); + _adminLogManager.Add(LogType.Mind, + LogImpact.Medium, + $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary"); - if (_mind.TryGetRole(ev.User.Value, out var headrev)) - headrev.ConvertedCount++; + if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _)) + { + if (_role.MindHasRole(revMindId, out var role)) + role.Value.Comp2.ConvertedCount++; + } } if (mindId == default || !_role.MindHasRole(mindId)) { - _role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId }); + _role.MindAddRole(mindId, "MindRoleRevolutionary"); } if (mind?.Session != null) diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 074b4c0df7a..b00ed386363 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -1,18 +1,12 @@ using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Mind; -using Content.Server.Objectives; using Content.Server.Roles; using Content.Shared.Humanoid; -using Content.Shared.Mind; -using Content.Shared.Objectives.Components; -using Robust.Shared.Random; namespace Content.Server.GameTicking.Rules; public sealed class ThiefRuleSystem : GameRuleSystem { - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; public override void Initialize() @@ -24,32 +18,33 @@ public override void Initialize() SubscribeLocalEvent(OnGetBriefing); } - private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + // Greeting upon thief activation + private void AfterAntagSelected(Entity mindId, ref AfterAntagEntitySelectedEvent args) { - if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) - return; - - //Generate objectives - _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); + var ent = args.EntityUid; + _antag.SendBriefing(ent, MakeBriefing(ent), null, null); } - //Add mind briefing - private void OnGetBriefing(Entity thief, ref GetBriefingEvent args) + // Character screen briefing + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(thief.Owner, out var mind) || mind.OwnedEntity == null) - return; + var ent = args.Mind.Comp.OwnedEntity; - args.Append(MakeBriefing(mind.OwnedEntity.Value)); + if (ent is null) + return; + args.Append(MakeBriefing(ent.Value)); } - private string MakeBriefing(EntityUid thief) + private string MakeBriefing(EntityUid ent) { - var isHuman = HasComp(thief); + var isHuman = HasComp(ent); var briefing = isHuman ? Loc.GetString("thief-role-greeting-human") : Loc.GetString("thief-role-greeting-animal"); - briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; + if (isHuman) + briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; + return briefing; } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 4e4191a51bf..1987613763b 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Administration.Logs; using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; @@ -5,12 +6,12 @@ using Content.Server.PDA.Ringer; using Content.Server.Roles; using Content.Server.Traitor.Uplink; +using Content.Shared.Database; +using Content.Shared.FixedPoint; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.NPC.Systems; -using Content.Shared.Objectives.Components; using Content.Shared.PDA; -using Content.Shared.Radio; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; using Content.Shared.Roles.RoleCodeword; @@ -25,29 +26,29 @@ public sealed class TraitorRuleSystem : GameRuleSystem { private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b"); - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; - [Dependency] private readonly UplinkSystem _uplink = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly UplinkSystem _uplink = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(AfterEntitySelected); - SubscribeLocalEvent(OnObjectivesTextPrepend); } protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { base.Added(uid, component, gameRule, args); - SetCodewords(component); + SetCodewords(component, args.RuleEntity); } private void AfterEntitySelected(Entity ent, ref AfterAntagEntitySelectedEvent args) @@ -55,9 +56,10 @@ private void AfterEntitySelected(Entity ent, ref AfterAnta MakeTraitor(args.EntityUid, ent); } - private void SetCodewords(TraitorRuleComponent component) + private void SetCodewords(TraitorRuleComponent component, EntityUid ruleEntity) { component.Codewords = GenerateTraitorCodewords(component); + _adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for game rule {ToPrettyString(ruleEntity)}: {string.Join(", ", component.Codewords)}"); } public string[] GenerateTraitorCodewords(TraitorRuleComponent component) @@ -74,47 +76,59 @@ public string[] GenerateTraitorCodewords(TraitorRuleComponent component) return codewords; } - public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) + public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component) { - //Grab the mind if it wasnt provided + //Grab the mind if it wasn't provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) return false; - var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); + var briefing = ""; + + if (component.GiveCodewords) + briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); + var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers).Values); + // Uplink code will go here if applicable, but we still need the variable if there aren't any Note[]? code = null; - if (giveUplink) + + if (component.GiveUplink) { // Calculate the amount of currency on the uplink. var startingBalance = component.StartingBalance; - if (_jobs.MindTryGetJob(mindId, out _, out var prototype)) - startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0); - - // creadth: we need to create uplink for the antag. - // PDA should be in place already - var pda = _uplink.FindUplinkTarget(traitor); - if (pda == null || !_uplink.AddUplink(traitor, startingBalance, giveDiscounts: true)) - return false; - - // Give traitors their codewords and uplink code to keep in their character info menu - code = EnsureComp(pda.Value).Code; + if (_jobs.MindTryGetJob(mindId, out var prototype)) + { + if (startingBalance < prototype.AntagAdvantage) // Can't use Math functions on FixedPoint2 + startingBalance = 0; + else + startingBalance = startingBalance - prototype.AntagAdvantage; + } - // If giveUplink is false the uplink code part is omitted - briefing = string.Format("{0}\n{1}", briefing, - Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); + // Choose and generate an Uplink, and return the uplink code if applicable + var uplinkParams = RequestUplink(traitor, startingBalance, briefing); + code = uplinkParams.Item1; + briefing = uplinkParams.Item2; } - _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification); + string[]? codewords = null; + if (component.GiveCodewords) + codewords = component.Codewords; + if (component.GiveBriefing) + _antag.SendBriefing(traitor, GenerateBriefing(codewords, code, issuer), null, component.GreetSoundNotification); component.TraitorMinds.Add(mindId); // Assign briefing - _roleSystem.MindAddRole(mindId, new RoleBriefingComponent + //Since this provides neither an antag/job prototype, nor antag status/roletype, + //and is intrinsically related to the traitor role + //it does not need to be a separate Mind Role Entity + _roleSystem.MindHasRole(mindId, out var traitorRole); + if (traitorRole is not null) { - Briefing = briefing - }, mind, true); + AddComp(traitorRole.Value.Owner); + Comp(traitorRole.Value.Owner).Briefing = briefing; + } // Send codewords to only the traitor client var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found @@ -129,6 +143,32 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool return true; } + private (Note[]?, string) RequestUplink(EntityUid traitor, FixedPoint2 startingBalance, string briefing) + { + var pda = _uplink.FindUplinkTarget(traitor); + Note[]? code = null; + + var uplinked = _uplink.AddUplink(traitor, startingBalance, pda, true); + + if (pda is not null && uplinked) + { + // Codes are only generated if the uplink is a PDA + code = EnsureComp(pda.Value).Code; + + // If giveUplink is false the uplink code part is omitted + briefing = string.Format("{0}\n{1}", + briefing, + Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); + return (code, briefing); + } + else if (pda is null && uplinked) + { + briefing += "\n" + Loc.GetString("traitor-role-uplink-implant-short"); + } + + return (null, briefing); + } + // TODO: AntagCodewordsComponent private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args) { @@ -136,13 +176,17 @@ private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, r } // TODO: figure out how to handle this? add priority to briefing event? - private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null) + private string GenerateBriefing(string[]? codewords, Note[]? uplinkCode, string? objectiveIssuer = null) { var sb = new StringBuilder(); sb.AppendLine(Loc.GetString("traitor-role-greeting", ("corporation", objectiveIssuer ?? Loc.GetString("objective-issuer-unknown")))); - sb.AppendLine(Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", codewords)))); + if (codewords != null) + sb.AppendLine(Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", codewords)))); if (uplinkCode != null) sb.AppendLine(Loc.GetString("traitor-role-uplink-code", ("code", string.Join("-", uplinkCode).Replace("sharp", "#")))); + else + sb.AppendLine(Loc.GetString("traitor-role-uplink-implant")); + return sb.ToString(); } diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index e91931300e2..bb76721340d 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Roles; using Content.Shared.Zombies; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -22,15 +23,16 @@ namespace Content.Server.GameTicking.Rules; public sealed class ZombieRuleSystem : GameRuleSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly RoundEndSystem _roundEnd = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ZombieSystem _zombie = default!; public override void Initialize() { @@ -41,23 +43,20 @@ public override void Initialize() SubscribeLocalEvent(OnZombifySelf); } - private void OnGetBriefing(EntityUid uid, InitialInfectedRoleComponent component, ref GetBriefingEvent args) + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(uid, out var mind) || mind.OwnedEntity == null) - return; - if (HasComp(uid)) // don't show both briefings - return; - args.Append(Loc.GetString("zombie-patientzero-role-greeting")); + if (!_roles.MindHasRole(args.Mind.Owner)) + args.Append(Loc.GetString("zombie-patientzero-role-greeting")); } - private void OnGetBriefing(EntityUid uid, ZombieRoleComponent component, ref GetBriefingEvent args) + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(uid, out var mind) || mind.OwnedEntity == null) - return; args.Append(Loc.GetString("zombie-infection-greeting")); } - protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, + protected override void AppendRoundEndText(EntityUid uid, + ZombieRuleComponent component, + GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { base.AppendRoundEndText(uid, component, gameRule, ref args); diff --git a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs index bd276e6df70..3dfa37a6b13 100644 --- a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs +++ b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs @@ -1,11 +1,13 @@ -namespace Content.Server.Ghost.Roles; +using Content.Shared.Roles; + +namespace Content.Server.Ghost.Roles; /// /// This is used for round end display of ghost roles. /// It may also be used to ensure some ghost roles count as antagonists in future. /// [RegisterComponent] -public sealed partial class GhostRoleMarkerRoleComponent : Component +public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent { [DataField("name")] public string? Name; } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 1b7f25122d5..998116994e8 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -71,18 +71,22 @@ public override void Initialize() SubscribeLocalEvent(Reset); SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnMindRemoved); SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnTakeoverTakeRole); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnRoleStartup); SubscribeLocalEvent(OnRoleShutdown); SubscribeLocalEvent(OnPaused); SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnRaffleInit); SubscribeLocalEvent(OnRaffleShutdown); + SubscribeLocalEvent(OnSpawnerTakeRole); - SubscribeLocalEvent(OnTakeoverTakeRole); SubscribeLocalEvent(OnSpawnerTakeCharacter); // DeltaV - Character ghost roles, see Content.Server/DeltaV/Ghost/Roles/GhostRoleSystem.Character.cs SubscribeLocalEvent>(OnVerb); SubscribeLocalEvent(OnGhostRoleRadioMessage); @@ -510,7 +514,11 @@ public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, Entity var newMind = _mindSystem.CreateMind(player.UserId, EntityManager.GetComponent(mob).EntityName); - _roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName }); + + _roleSystem.MindAddRole(newMind, "MindRoleGhostMarker"); + + if(_roleSystem.MindHasRole(newMind!, out var markerRole)) + markerRole.Value.Comp2.Name = role.RoleName; _mindSystem.SetUserId(newMind, player.UserId); _mindSystem.TransferTo(newMind, mob); @@ -603,10 +611,7 @@ private void OnMindAdded(EntityUid uid, GhostTakeoverAvailableComponent componen if (ghostRole.JobProto != null) { - if (HasComp(args.Mind)) - _roleSystem.MindRemoveRole(args.Mind); - - _roleSystem.MindAddRole(args.Mind, new JobComponent { Prototype = ghostRole.JobProto }); + _roleSystem.MindAddJobRole(args.Mind, args.Mind, silent:false,ghostRole.JobProto); } ghostRole.Taken = true; diff --git a/Content.Server/Holosign/HolosignSystem.cs b/Content.Server/Holosign/HolosignSystem.cs index a36603b01dd..b63a5459898 100644 --- a/Content.Server/Holosign/HolosignSystem.cs +++ b/Content.Server/Holosign/HolosignSystem.cs @@ -45,7 +45,7 @@ private void OnBeforeInteract(EntityUid uid, HolosignProjectorComponent componen if (args.Handled || !args.CanReach // prevent placing out of range || HasComp(args.Target) // if it's a storage component like a bag, we ignore usage so it can be stored - || !_powerCell.TryUseCharge(uid, component.ChargeUse) // if no battery or no charge, doesn't work + || !_powerCell.TryUseCharge(uid, component.ChargeUse, user: args.User) // if no battery or no charge, doesn't work ) return; diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index e110a424834..ff6901f5a92 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -166,7 +166,7 @@ private IdentityRepresentation GetIdentityRepresentation(EntityUid target, if (_idCard.TryFindIdCard(target, out var id)) { presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName; - presumedJob = id.Comp.JobTitle?.ToLowerInvariant(); + presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant(); } // If it didn't find a job, that's fine. diff --git a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs index 12c5a30a4b2..de848b34c2c 100644 --- a/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs +++ b/Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Components; using Content.Shared.Containers.ItemSlots; +using Content.Shared.Destructible; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Kitchen; @@ -122,6 +123,9 @@ public override void Update(float frameTime) if (solution.Volume > containerSolution.AvailableVolume) continue; + var dev = new DestructionEventArgs(); + RaiseLocalEvent(item, dev); + QueueDel(item); } diff --git a/Content.Server/Materials/MaterialStorageSystem.cs b/Content.Server/Materials/MaterialStorageSystem.cs index 25e409fd010..9f43a220493 100644 --- a/Content.Server/Materials/MaterialStorageSystem.cs +++ b/Content.Server/Materials/MaterialStorageSystem.cs @@ -175,6 +175,10 @@ public List SpawnMultipleFromMaterial(int amount, MaterialPrototype m var materialPerStack = composition.MaterialComposition[materialProto.ID]; var amountToSpawn = amount / materialPerStack; overflowMaterial = amount - amountToSpawn * materialPerStack; + + if (amountToSpawn == 0) + return new List(); + return _stackSystem.SpawnMultiple(materialProto.StackEntity, amountToSpawn, coordinates); } diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index b1b87ae981d..f56b16432e1 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -363,8 +363,8 @@ public void SetSensor(Entity sensors, SuitSensorMode mode, { if (card.Comp.FullName != null) userName = card.Comp.FullName; - if (card.Comp.JobTitle != null) - userJob = card.Comp.JobTitle; + if (card.Comp.LocalizedJobTitle != null) + userJob = card.Comp.LocalizedJobTitle; userJobIcon = card.Comp.JobIcon; foreach (var department in card.Comp.JobDepartments) diff --git a/Content.Server/Mind/Commands/MindInfoCommand.cs b/Content.Server/Mind/Commands/MindInfoCommand.cs index d4961b82dbb..c365702a314 100644 --- a/Content.Server/Mind/Commands/MindInfoCommand.cs +++ b/Content.Server/Mind/Commands/MindInfoCommand.cs @@ -43,7 +43,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity); var roles = _entities.System(); - foreach (var role in roles.MindGetAllRoles(mindId)) + foreach (var role in roles.MindGetAllRoleInfo(mindId)) { builder.AppendFormat("{0} ", role.Name); } diff --git a/Content.Server/NameIdentifier/NameIdentifierSystem.cs b/Content.Server/NameIdentifier/NameIdentifierSystem.cs index eefd4357cb3..6db6e0ff565 100644 --- a/Content.Server/NameIdentifier/NameIdentifierSystem.cs +++ b/Content.Server/NameIdentifier/NameIdentifierSystem.cs @@ -14,6 +14,7 @@ public sealed class NameIdentifierSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly ILogManager _logManager = default!; /// /// Free IDs available per . @@ -118,23 +119,39 @@ private void OnMapInit(EntityUid uid, NameIdentifierComponent component, MapInit private void InitialSetupPrototypes() { - foreach (var proto in _prototypeManager.EnumeratePrototypes()) - { - AddGroup(proto); - } + EnsureIds(); } - private void AddGroup(NameIdentifierGroupPrototype proto) + private void FillGroup(NameIdentifierGroupPrototype proto, List values) { - var values = new List(proto.MaxValue - proto.MinValue); - + values.Clear(); for (var i = proto.MinValue; i < proto.MaxValue; i++) { values.Add(i); } _robustRandom.Shuffle(values); - CurrentIds.Add(proto.ID, values); + } + + private List GetOrCreateIdList(NameIdentifierGroupPrototype proto) + { + if (!CurrentIds.TryGetValue(proto.ID, out var ids)) + { + ids = new List(proto.MaxValue - proto.MinValue); + CurrentIds.Add(proto.ID, ids); + } + + return ids; + } + + private void EnsureIds() + { + foreach (var proto in _prototypeManager.EnumeratePrototypes()) + { + var ids = GetOrCreateIdList(proto); + + FillGroup(proto, ids); + } } private void OnReloadPrototypes(PrototypesReloadedEventArgs ev) @@ -159,19 +176,20 @@ private void OnReloadPrototypes(PrototypesReloadedEventArgs ev) foreach (var proto in set.Modified.Values) { + var name_proto = (NameIdentifierGroupPrototype) proto; + // Only bother adding new ones. if (CurrentIds.ContainsKey(proto.ID)) continue; - AddGroup((NameIdentifierGroupPrototype) proto); + var ids = GetOrCreateIdList(name_proto); + FillGroup(name_proto, ids); } } + private void CleanupIds(RoundRestartCleanupEvent ev) { - foreach (var values in CurrentIds.Values) - { - _robustRandom.Shuffle(values); - } + EnsureIds(); } } diff --git a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs index 244b7adf036..20674dda879 100644 --- a/Content.Server/Ninja/Systems/NinjaSuitSystem.cs +++ b/Content.Server/Ninja/Systems/NinjaSuitSystem.cs @@ -22,6 +22,9 @@ public sealed class NinjaSuitSystem : SharedNinjaSuitSystem [Dependency] private readonly PowerCellSystem _powerCell = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + // How much the cell score should be increased per 1 AutoRechargeRate. + private const int AutoRechargeValue = 100; + public override void Initialize() { base.Initialize(); @@ -59,15 +62,26 @@ private void OnSuitInsertAttempt(EntityUid uid, NinjaSuitComponent comp, Contain return; // no power cell for some reason??? allow it - if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery)) + if (!_powerCell.TryGetBatteryFromSlot(uid, out var batteryUid, out var battery)) + return; + + if (!TryComp(args.EntityUid, out var inserting)) + { + args.Cancel(); return; + } + + var user = Transform(uid).ParentUid; // can only upgrade power cell, not swap to recharge instantly otherwise ninja could just swap batteries with flashlights in maints for easy power - if (!TryComp(args.EntityUid, out var inserting) || inserting.MaxCharge <= battery.MaxCharge) + if (GetCellScore(inserting.Owner, inserting) <= GetCellScore(battery.Owner, battery)) + { args.Cancel(); + Popup.PopupEntity(Loc.GetString("ninja-cell-downgrade"), user, user); + return; + } // tell ninja abilities that use battery to update it so they don't use charge from the old one - var user = Transform(uid).ParentUid; if (!_ninja.IsNinja(user)) return; @@ -76,6 +90,16 @@ private void OnSuitInsertAttempt(EntityUid uid, NinjaSuitComponent comp, Contain RaiseLocalEvent(user, ref ev); } + // this function assigns a score to a power cell depending on the capacity, to be used when comparing which cell is better. + private float GetCellScore(EntityUid uid, BatteryComponent battcomp) + { + // if a cell is able to automatically recharge, boost the score drastically depending on the recharge rate, + // this is to ensure a ninja can still upgrade to a micro reactor cell even if they already have a medium or high. + if (TryComp(uid, out var selfcomp) && selfcomp.AutoRecharge) + return battcomp.MaxCharge + (selfcomp.AutoRechargeRate*AutoRechargeValue); + return battcomp.MaxCharge; + } + private void OnEmpAttempt(EntityUid uid, NinjaSuitComponent comp, EmpAttemptEvent args) { // ninja suit (battery) is immune to emp diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs index c916d568d5f..6594d7883bc 100644 --- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs +++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs @@ -1,14 +1,12 @@ using Content.Server.Explosion.EntitySystems; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; using Content.Server.Objectives.Components; using Content.Server.Popups; using Content.Server.Roles; -using Content.Shared.Interaction; using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Systems; +using Content.Shared.Roles; using Content.Shared.Sticky; -using Robust.Shared.GameObjects; namespace Content.Server.Ninja.Systems; @@ -19,6 +17,7 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem { [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SpaceNinjaSystem _ninja = default!; @@ -41,7 +40,10 @@ private void OnAttemptStick(EntityUid uid, SpiderChargeComponent comp, ref Attem var user = args.User; - if (!_mind.TryGetRole(user, out var _)) + if (!_mind.TryGetMind(args.User, out var mind, out _)) + return; + + if (!_role.MindHasRole(mind)) { _popup.PopupEntity(Loc.GetString("spider-charge-not-ninja"), user, user); args.Cancelled = true; diff --git a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs index f04d79b47da..90a925e39f1 100644 --- a/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/DrinkSystem.cs @@ -317,7 +317,7 @@ private void OnDoAfter(Entity entity, ref ConsumeDoAfterEvent ar _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} drank {ToPrettyString(entity.Owner):drink}"); } - _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-2f)); + _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-2f).WithVariation(0.25f)); _reaction.DoEntityReaction(args.Target.Value, solution, ReactionMethod.Ingestion); _stomach.TryTransferSolution(firstStomach.Value.Owner, drained, firstStomach.Value.Comp1); diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 3a7c249c2b3..158c7f4955c 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -33,6 +33,7 @@ using Content.Shared.Containers.ItemSlots; using Robust.Server.GameObjects; using Content.Shared.Whitelist; +using Content.Shared.Destructible; namespace Content.Server.Nutrition.EntitySystems; @@ -295,7 +296,7 @@ private void OnDoAfter(Entity entity, ref ConsumeDoAfterEvent arg _adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(args.User):target} ate {ToPrettyString(entity.Owner):food}"); } - _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-1f)); + _audio.PlayPvs(entity.Comp.UseSound, args.Target.Value, AudioParams.Default.WithVolume(-1f).WithVariation(0.20f)); // Try to break all used utensils foreach (var utensil in utensils) @@ -335,6 +336,9 @@ public void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityU if (ev.Cancelled) return; + var dev = new DestructionEventArgs(); + RaiseLocalEvent(food, dev); + if (component.Trash.Count == 0) { QueueDel(food); diff --git a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs index b775117b716..63a06e9fdc8 100644 --- a/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Nyanotrasen/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -1,25 +1,16 @@ using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; -using Content.Shared.StatusEffect; using Content.Shared.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class MetapsionicPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - public override void Initialize() { diff --git a/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs b/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs index 03ebd044a2f..698a54c0aec 100644 --- a/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs +++ b/Content.Server/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveSystem.cs @@ -400,7 +400,7 @@ public override void Update(float frameTime) /// has the exact /// values corresponding to tiers. /// - public class GlimmerTierChangedEvent : EntityEventArgs + public sealed class GlimmerTierChangedEvent : EntityEventArgs { /// /// What was the last glimmer tier before this event fired? diff --git a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs index 82f2fddbc59..bee14cc2778 100644 --- a/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Nyanotrasen/Research/Oracle/OracleSystem.cs @@ -107,7 +107,7 @@ private void OnInteractUsing(EntityUid uid, OracleComponent component, InteractU if (HasComp(args.Used)) return; - if (!TryComp(args.Used, out var meta)) + if (!TryComp(args.Used, out MetaDataComponent? meta)) return; if (HasComp(args.User)) @@ -139,13 +139,13 @@ private void OnInteractUsing(EntityUid uid, OracleComponent component, InteractU return; } - EntityManager.QueueDeleteEntity(args.Used); + QueueDel(args.Used); _adminLog.Add(LogType.InteractHand, LogImpact.Medium, $"{ToPrettyString(args.User):player} sold {ToPrettyString(args.Used)} to {ToPrettyString(uid)}."); - EntityManager.SpawnEntity("ResearchDisk5000", Transform(args.User).Coordinates); + Spawn("ResearchDisk5000", Transform(args.User).Coordinates); DispenseLiquidReward(uid, component); @@ -153,7 +153,7 @@ private void OnInteractUsing(EntityUid uid, OracleComponent component, InteractU while (i != 0) { - EntityManager.SpawnEntity("MaterialBluespace1", Transform(args.User).Coordinates); + Spawn("MaterialBluespace1", Transform(args.User).Coordinates); i--; } diff --git a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs index c1caa819e44..8dcbf191b36 100644 --- a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs +++ b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs @@ -1,9 +1,9 @@ using Content.Server.Objectives.Components; +using Content.Server.Revolutionary.Components; using Content.Server.Shuttles.Systems; using Content.Shared.CCVar; using Content.Shared.Mind; using Content.Shared.Objectives.Components; -using Content.Shared.Roles.Jobs; using Robust.Shared.Configuration; using Robust.Shared.Random; @@ -17,7 +17,6 @@ public sealed class KillPersonConditionSystem : EntitySystem [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; [Dependency] private readonly IConfigurationManager _config = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedJobSystem _job = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly TargetObjectiveSystem _target = default!; @@ -86,11 +85,10 @@ private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref Obj } var allHeads = new List(); - foreach (var mind in allHumans) + foreach (var person in allHumans) { - // RequireAdminNotify used as a cheap way to check for command department - if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify) - allHeads.Add(mind); + if (TryComp(person, out var mind) && mind.OwnedEntity is { } ent && HasComp(ent)) + allHeads.Add(person); } if (allHeads.Count == 0) diff --git a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs index 47c54b937a0..ee99658f66e 100644 --- a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs +++ b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs @@ -1,9 +1,10 @@ using Content.Server.Objectives.Components; +using Content.Server.Roles; using Content.Server.Warps; using Content.Shared.Objectives.Components; using Content.Shared.Ninja.Components; +using Content.Shared.Roles; using Robust.Shared.Random; -using Content.Server.Roles; namespace Content.Server.Objectives.Systems; @@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly NumberObjectiveSystem _number = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; public override void Initialize() { @@ -46,10 +48,8 @@ private float DoorjackProgress(DoorjackConditionComponent comp, int target) // spider charge private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args) { - if (args.Cancelled || !HasComp(args.MindId)) - { + if (args.Cancelled || !_roles.MindHasRole(args.MindId)) return; - } // choose spider charge detonation point var warps = new List(); diff --git a/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs b/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs index e63492bb5ed..0808dc5bcfd 100644 --- a/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs @@ -1,13 +1,11 @@ using Content.Server.Objectives.Components; +using Content.Server.Revolutionary.Components; using Content.Shared.Objectives.Components; -using Content.Shared.Roles.Jobs; namespace Content.Server.Objectives.Systems; public sealed class NotCommandRequirementSystem : EntitySystem { - [Dependency] private readonly SharedJobSystem _job = default!; - public override void Initialize() { base.Initialize(); @@ -20,8 +18,7 @@ private void OnCheck(EntityUid uid, NotCommandRequirementComponent comp, ref Req if (args.Cancelled) return; - // cheap equivalent to checking that job department is command, since all command members require admin notification when leaving - if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify) + if (args.Mind.OwnedEntity is { } ent && HasComp(ent)) args.Cancelled = true; } } diff --git a/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs b/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs index 5c2b0404634..ac7e579c380 100644 --- a/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs @@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems; /// public sealed class NotJobRequirementSystem : EntitySystem { + [Dependency] private readonly SharedJobSystem _jobs = default!; + public override void Initialize() { base.Initialize(); @@ -20,11 +22,10 @@ private void OnCheck(EntityUid uid, NotJobRequirementComponent comp, ref Require if (args.Cancelled) return; - // if player has no job then don't care - if (!TryComp(args.MindId, out var job)) - return; + _jobs.MindTryGetJob(args.MindId, out var proto); - if (job.Prototype == comp.Job) + // if player has no job then don't care + if (proto is not null && proto.ID == comp.Job) args.Cancelled = true; } } diff --git a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs index 8421987bae9..83d4c2ea4c5 100644 --- a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs @@ -22,8 +22,6 @@ private void OnCheck(EntityUid uid, RoleRequirementComponent comp, ref Requireme if (args.Cancelled) return; - // this whitelist trick only works because roles are components on the mind and not entities - // if that gets reworked then this will need changing if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId)) args.Cancelled = true; } diff --git a/Content.Server/PDA/PdaSystem.cs b/Content.Server/PDA/PdaSystem.cs index cdcdbc02e5f..7f17b97d0ad 100644 --- a/Content.Server/PDA/PdaSystem.cs +++ b/Content.Server/PDA/PdaSystem.cs @@ -192,7 +192,7 @@ public void UpdatePdaUi(EntityUid uid, PdaComponent? pda = null) { ActualOwnerName = pda.OwnerName, IdOwner = id?.FullName, - JobTitle = id?.JobTitle, + JobTitle = id?.LocalizedJobTitle, StationAlertLevel = pda.StationAlertLevel, StationAlertColor = pda.StationAlertColor }, diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index e9bd5140ae1..c897020d17f 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -30,14 +30,15 @@ namespace Content.Server.Players.PlayTimeTracking; /// public sealed class PlayTimeTrackingSystem : EntitySystem { + [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IAfkManager _afk = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly MindSystem _minds = default!; - [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; - [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; public override void Initialize() { @@ -101,10 +102,7 @@ private bool IsPlayerAlive(ICommonSession session) public IEnumerable GetTimedRoles(EntityUid mindId) { - var ev = new MindGetAllRolesEvent(new List()); - RaiseLocalEvent(mindId, ref ev); - - foreach (var role in ev.Roles) + foreach (var role in _roles.MindGetAllRoleInfo(mindId)) { if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId)) continue; diff --git a/Content.Server/Power/Components/ApcComponent.cs b/Content.Server/Power/Components/ApcComponent.cs index bee8defc43e..0bf9bc18721 100644 --- a/Content.Server/Power/Components/ApcComponent.cs +++ b/Content.Server/Power/Components/ApcComponent.cs @@ -25,9 +25,6 @@ public sealed partial class ApcComponent : BaseApcNetComponent [DataField("enabled")] public bool MainBreakerEnabled = true; - // TODO: remove this since it probably breaks when 2 people use it - [DataField("hasAccess")] - public bool HasAccess = false; /// /// APC state needs to always be updated after first processing tick. diff --git a/Content.Server/Power/EntitySystems/ApcSystem.cs b/Content.Server/Power/EntitySystems/ApcSystem.cs index 52c19c302ce..14dddbb43e3 100644 --- a/Content.Server/Power/EntitySystems/ApcSystem.cs +++ b/Content.Server/Power/EntitySystems/ApcSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Popups; using Content.Server.Power.Components; using Content.Server.Power.Pow3r; -using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.APC; using Content.Shared.Emag.Components; @@ -71,11 +70,8 @@ private static void OnApcStartup(EntityUid uid, ApcComponent component, Componen component.NeedStateUpdate = true; } - //Update the HasAccess var for UI to read private void OnBoundUiOpen(EntityUid uid, ApcComponent component, BoundUIOpenedEvent args) { - // TODO: this should be per-player not stored on the apc - component.HasAccess = _accessReader.IsAllowed(args.Actor, uid); UpdateApcState(uid, component); } @@ -165,7 +161,7 @@ public void UpdateUIState(EntityUid uid, // TODO: Fix ContentHelpers or make a new one coz this is cooked. var charge = ContentHelpers.RoundToNearestLevels(battery.CurrentStorage / battery.Capacity, 1.0, 100 / ChargeAccuracy) / 100f * ChargeAccuracy; - var state = new ApcBoundInterfaceState(apc.MainBreakerEnabled, apc.HasAccess, + var state = new ApcBoundInterfaceState(apc.MainBreakerEnabled, (int) MathF.Ceiling(battery.CurrentSupply), apc.LastExternalState, charge); diff --git a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs index 8e42f41cb3d..79349b25da7 100644 --- a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs +++ b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs @@ -1,12 +1,12 @@ -using Content.Server.GameTicking.Rules; - namespace Content.Server.Revolutionary.Components; /// -/// Given to heads at round start for Revs. Used for tracking if heads died or not. +/// Given to heads at round start. Used for assigning traitors to kill heads and for revs to check if the heads died or not. /// -[RegisterComponent, Access(typeof(RevolutionaryRuleSystem))] +[RegisterComponent] public sealed partial class CommandStaffComponent : Component { } + +//TODO this should probably be on a mind role, not the mob diff --git a/Content.Server/Roles/DragonRoleComponent.cs b/Content.Server/Roles/DragonRoleComponent.cs index b85fd53eb0c..c47455d8f6f 100644 --- a/Content.Server/Roles/DragonRoleComponent.cs +++ b/Content.Server/Roles/DragonRoleComponent.cs @@ -4,9 +4,9 @@ namespace Content.Server.Roles; /// -/// Role used to keep track of space dragons for antag purposes. +/// Added to mind role entities to tag that they are a space dragon. /// -[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist] -public sealed partial class DragonRoleComponent : AntagonistRoleComponent +[RegisterComponent, Access(typeof(DragonSystem))] +public sealed partial class DragonRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/FugitiveRoleComponent.cs b/Content.Server/Roles/FugitiveRoleComponent.cs deleted file mode 100644 index e68984ccf7a..00000000000 --- a/Content.Server/Roles/FugitiveRoleComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.Roles; - -namespace Content.Server.Roles; - -/// -/// DeltaV - fugitive antag role -/// -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class FugitiveRoleComponent : AntagonistRoleComponent; diff --git a/Content.Server/Roles/InitialInfectedRoleComponent.cs b/Content.Server/Roles/InitialInfectedRoleComponent.cs index 52d3db41643..475cd3ba603 100644 --- a/Content.Server/Roles/InitialInfectedRoleComponent.cs +++ b/Content.Server/Roles/InitialInfectedRoleComponent.cs @@ -2,8 +2,11 @@ namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are an initial infected. +/// +[RegisterComponent] +public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/Jobs/JobSystem.cs b/Content.Server/Roles/Jobs/JobSystem.cs index 9f0dd7ae32b..7e69d9d92bc 100644 --- a/Content.Server/Roles/Jobs/JobSystem.cs +++ b/Content.Server/Roles/Jobs/JobSystem.cs @@ -30,7 +30,7 @@ private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref Min if (!_mind.TryGetSession(mindId, out var session)) return; - if (!MindTryGetJob(mindId, out _, out var prototype)) + if (!MindTryGetJob(mindId, out var prototype)) return; _chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", @@ -47,6 +47,6 @@ public void MindAddJob(EntityUid mindId, string jobPrototypeId) if (MindHasJobWithId(mindId, jobPrototypeId)) return; - _roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId }); + _roles.MindAddJobRole(mindId, null, false, jobPrototypeId); } } diff --git a/Content.Server/Roles/NinjaRoleComponent.cs b/Content.Server/Roles/NinjaRoleComponent.cs index cb60e5bdf03..7bdffe67a31 100644 --- a/Content.Server/Roles/NinjaRoleComponent.cs +++ b/Content.Server/Roles/NinjaRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class NinjaRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a space ninja. +/// +[RegisterComponent] +public sealed partial class NinjaRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/NukeopsRoleComponent.cs b/Content.Server/Roles/NukeopsRoleComponent.cs index a6ff0b71b06..41561088ea8 100644 --- a/Content.Server/Roles/NukeopsRoleComponent.cs +++ b/Content.Server/Roles/NukeopsRoleComponent.cs @@ -3,9 +3,9 @@ namespace Content.Server.Roles; /// -/// Added to mind entities to tag that they are a nuke operative. +/// Added to mind role entities to tag that they are a nuke operative. /// -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent +[RegisterComponent] +public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/RemoveRoleCommand.cs b/Content.Server/Roles/RemoveRoleCommand.cs index feba63a253f..fd4bb09317a 100644 --- a/Content.Server/Roles/RemoveRoleCommand.cs +++ b/Content.Server/Roles/RemoveRoleCommand.cs @@ -45,7 +45,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) var roles = _entityManager.System(); var jobs = _entityManager.System(); if (jobs.MindHasJobWithId(mind, args[1])) - roles.MindRemoveRole(mind.Value); + roles.MindTryRemoveRole(mind.Value); } } } diff --git a/Content.Server/Roles/RevolutionaryRoleComponent.cs b/Content.Server/Roles/RevolutionaryRoleComponent.cs index bf56b960084..dcdb131b9d0 100644 --- a/Content.Server/Roles/RevolutionaryRoleComponent.cs +++ b/Content.Server/Roles/RevolutionaryRoleComponent.cs @@ -3,10 +3,10 @@ namespace Content.Server.Roles; /// -/// Added to mind entities to tag that they are a Revolutionary. +/// Added to mind role entities to tag that they are a Revolutionary. /// -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent +[RegisterComponent] +public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent { /// /// For headrevs, how many people you have converted. diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs index c53fa1cf9eb..333f4f9b606 100644 --- a/Content.Server/Roles/RoleSystem.cs +++ b/Content.Server/Roles/RoleSystem.cs @@ -1,32 +1,40 @@ +using Content.Shared.Mind; using Content.Shared.Roles; namespace Content.Server.Roles; public sealed class RoleSystem : SharedRoleSystem { - public override void Initialize() - { - // TODO make roles entities - base.Initialize(); - - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - } - public string? MindGetBriefing(EntityUid? mindId) { if (mindId == null) + { + Log.Error($"MingGetBriefing failed for mind {mindId}"); + return null; + } + + TryComp(mindId.Value, out var mindComp); + + if (mindComp is null) + { + Log.Error($"MingGetBriefing failed for mind {mindId}"); return null; + } var ev = new GetBriefingEvent(); - RaiseLocalEvent(mindId.Value, ref ev); + + // This is on the event because while this Entity is also present on every Mind Role Entity's MindRoleComp + // getting to there from a GetBriefing event subscription can be somewhat boilerplate + // and this needs to be looked up for the event anyway so why calculate it again later + ev.Mind = (mindId.Value, mindComp); + + // Briefing is no longer raised on the mind entity itself + // because all the components that briefings subscribe to should be on Mind Role Entities + foreach(var role in mindComp.MindRoles) + { + RaiseLocalEvent(role, ref ev); + } + return ev.Briefing; } } @@ -38,8 +46,16 @@ public override void Initialize() [ByRefEvent] public sealed class GetBriefingEvent { + /// + /// The text that will be shown on the Character Screen + /// public string? Briefing; + /// + /// The Mind to whose Mind Role Entities the briefing is sent to + /// + public Entity Mind; + public GetBriefingEvent(string? briefing = null) { Briefing = briefing; diff --git a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs index 70056fbec9e..55727573b9d 100644 --- a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs +++ b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; +/// +/// Added to mind role entities to tag that they are a hacked borg. +/// [RegisterComponent] -public sealed partial class SubvertedSiliconRoleComponent : AntagonistRoleComponent +public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/ThiefRoleComponent.cs b/Content.Server/Roles/ThiefRoleComponent.cs index 82e350ef630..c0ddee71a49 100644 --- a/Content.Server/Roles/ThiefRoleComponent.cs +++ b/Content.Server/Roles/ThiefRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; +/// +/// Added to mind role entities to tag that they are a thief. +/// [RegisterComponent] -public sealed partial class ThiefRoleComponent : AntagonistRoleComponent +public sealed partial class ThiefRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/TraitorRoleComponent.cs b/Content.Server/Roles/TraitorRoleComponent.cs index 96bfe8dd801..a8a11a8f1bd 100644 --- a/Content.Server/Roles/TraitorRoleComponent.cs +++ b/Content.Server/Roles/TraitorRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class TraitorRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a syndicate traitor. +/// +[RegisterComponent] +public sealed partial class TraitorRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/ZombieRoleComponent.cs b/Content.Server/Roles/ZombieRoleComponent.cs index 2f9948022b3..cff25e53e80 100644 --- a/Content.Server/Roles/ZombieRoleComponent.cs +++ b/Content.Server/Roles/ZombieRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class ZombieRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a zombie. +/// +[RegisterComponent] +public sealed partial class ZombieRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/ServerUpdates/ServerUpdateManager.cs b/Content.Server/ServerUpdates/ServerUpdateManager.cs index f4e54984e9b..bf18428e25b 100644 --- a/Content.Server/ServerUpdates/ServerUpdateManager.cs +++ b/Content.Server/ServerUpdates/ServerUpdateManager.cs @@ -12,9 +12,13 @@ namespace Content.Server.ServerUpdates; /// -/// Responsible for restarting the server for update, when not disruptive. +/// Responsible for restarting the server periodically or for update, when not disruptive. /// -public sealed class ServerUpdateManager +/// +/// This was originally only designed for restarting on *update*, +/// but now also handles periodic restarting to keep server uptime via . +/// +public sealed class ServerUpdateManager : IPostInjectInit { [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IWatchdogApi _watchdog = default!; @@ -22,23 +26,43 @@ public sealed class ServerUpdateManager [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IBaseServer _server = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ILogManager _logManager = default!; + + private ISawmill _sawmill = default!; [ViewVariables] private bool _updateOnRoundEnd; private TimeSpan? _restartTime; + private TimeSpan _uptimeRestart; + public void Initialize() { _watchdog.UpdateReceived += WatchdogOnUpdateReceived; _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; + + _cfg.OnValueChanged( + CCVars.ServerUptimeRestartMinutes, + minutes => _uptimeRestart = TimeSpan.FromMinutes(minutes), + true); } public void Update() { - if (_restartTime != null && _restartTime < _gameTiming.RealTime) + if (_restartTime != null) { - DoShutdown(); + if (_restartTime < _gameTiming.RealTime) + { + DoShutdown(); + } + } + else + { + if (ShouldShutdownDueToUptime()) + { + ServerEmptyUpdateRestartCheck("uptime"); + } } } @@ -48,7 +72,7 @@ public void Update() /// True if the server is going to restart. public bool RoundEnded() { - if (_updateOnRoundEnd) + if (_updateOnRoundEnd || ShouldShutdownDueToUptime()) { DoShutdown(); return true; @@ -61,11 +85,14 @@ private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEve { switch (e.NewStatus) { - case SessionStatus.Connecting: + case SessionStatus.Connected: + if (_restartTime != null) + _sawmill.Debug("Aborting server restart timer due to player connection"); + _restartTime = null; break; case SessionStatus.Disconnected: - ServerEmptyUpdateRestartCheck(); + ServerEmptyUpdateRestartCheck("last player disconnect"); break; } } @@ -74,20 +101,20 @@ private void WatchdogOnUpdateReceived() { _chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received")); _updateOnRoundEnd = true; - ServerEmptyUpdateRestartCheck(); + ServerEmptyUpdateRestartCheck("update notification"); } /// /// Checks whether there are still players on the server, /// and if not starts a timer to automatically reboot the server if an update is available. /// - private void ServerEmptyUpdateRestartCheck() + private void ServerEmptyUpdateRestartCheck(string reason) { // Can't simple check the current connected player count since that doesn't update // before PlayerStatusChanged gets fired. // So in the disconnect handler we'd still see a single player otherwise. var playersOnline = _playerManager.Sessions.Any(p => p.Status != SessionStatus.Disconnected); - if (playersOnline || !_updateOnRoundEnd) + if (playersOnline || !(_updateOnRoundEnd || ShouldShutdownDueToUptime())) { // Still somebody online. return; @@ -95,16 +122,30 @@ private void ServerEmptyUpdateRestartCheck() if (_restartTime != null) { - // Do nothing because I guess we already have a timer running..? + // Do nothing because we already have a timer running. return; } var restartDelay = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.UpdateRestartDelay)); _restartTime = restartDelay + _gameTiming.RealTime; + + _sawmill.Debug("Started server-empty restart timer due to {Reason}", reason); } private void DoShutdown() { - _server.Shutdown(Loc.GetString("server-updates-shutdown")); + _sawmill.Debug($"Shutting down via {nameof(ServerUpdateManager)}!"); + var reason = _updateOnRoundEnd ? "server-updates-shutdown" : "server-updates-shutdown-uptime"; + _server.Shutdown(Loc.GetString(reason)); + } + + private bool ShouldShutdownDueToUptime() + { + return _uptimeRestart != TimeSpan.Zero && _gameTiming.RealTime > _uptimeRestart; + } + + void IPostInjectInit.PostInject() + { + _sawmill = _logManager.GetSawmill("restart"); } } diff --git a/Content.Server/Shuttles/Commands/FTLDiskCommand.cs b/Content.Server/Shuttles/Commands/FTLDiskCommand.cs new file mode 100644 index 00000000000..b17c7c11a71 --- /dev/null +++ b/Content.Server/Shuttles/Commands/FTLDiskCommand.cs @@ -0,0 +1,183 @@ +using Content.Server.Administration; +using Content.Server.Labels; +using Content.Shared.Administration; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Shuttles.Components; +using Content.Shared.Storage; +using Content.Shared.Storage.EntitySystems; +using Robust.Shared.Console; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.Shuttles.Commands; + +/// +/// Creates FTL disks, to maps, grids, or entities. +/// +[AdminCommand(AdminFlags.Fun)] + +public sealed class FTLDiskCommand : LocalizedCommands +{ + [Dependency] private readonly IEntityManager _entManager = default!; + [Dependency] private readonly IEntitySystemManager _entSystemManager = default!; + + public override string Command => "ftldisk"; + + [ValidatePrototypeId] + public const string CoordinatesDisk = "CoordinatesDisk"; + + [ValidatePrototypeId] + public const string DiskCase = "DiskCase"; + public override void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length == 0) + { + shell.WriteError(Loc.GetString("shell-need-minimum-one-argument")); + return; + } + + var player = shell.Player; + + if (player == null) + { + shell.WriteLine(Loc.GetString("shell-only-players-can-run-this-command")); + return; + } + + if (player.AttachedEntity == null) + { + shell.WriteLine(Loc.GetString("shell-must-be-attached-to-entity")); + return; + } + + EntityUid entity = player.AttachedEntity.Value; + var coords = _entManager.GetComponent(entity).Coordinates; + + var handsSystem = _entSystemManager.GetEntitySystem(); + var labelSystem = _entSystemManager.GetEntitySystem(); + var mapSystem = _entSystemManager.GetEntitySystem(); + var storageSystem = _entSystemManager.GetEntitySystem(); + + foreach (var destinations in args) + { + DebugTools.AssertNotNull(destinations); + + // make sure destination is an id. + EntityUid dest; + + if (_entManager.TryParseNetEntity(destinations, out var nullableDest)) + { + DebugTools.AssertNotNull(nullableDest); + + dest = (EntityUid) nullableDest; + + // we need to go to a map, so check if the EntID is something else then try for its map + if (!_entManager.HasComponent(dest)) + { + if (!_entManager.TryGetComponent(dest, out var entTransform)) + { + shell.WriteLine(Loc.GetString("cmd-ftldisk-no-transform", ("destination", destinations))); + continue; + } + + if (!mapSystem.TryGetMap(entTransform.MapID, out var mapDest)) + { + shell.WriteLine(Loc.GetString("cmd-ftldisk-no-map", ("destination", destinations))); + continue; + } + + DebugTools.AssertNotNull(mapDest); + dest = mapDest!.Value; // explicit cast here should be fine since the previous if should catch it. + } + + // find and verify the map is not somehow unusable. + if (!_entManager.TryGetComponent(dest, out var mapComp)) // We have to check for a MapComponent here and above since we could have changed our dest entity. + { + shell.WriteLine(Loc.GetString("cmd-ftldisk-no-map-comp", ("destination", destinations), ("map", dest))); + continue; + } + if (mapComp.MapInitialized == false) + { + shell.WriteLine(Loc.GetString("cmd-ftldisk-map-not-init", ("destination", destinations), ("map", dest))); + continue; + } + if (mapComp.MapPaused == true) + { + shell.WriteLine(Loc.GetString("cmd-ftldisk-map-paused", ("destination", destinations), ("map", dest))); + continue; + } + + // check if our destination works already, if not, make it. + if (!_entManager.TryGetComponent(dest, out var ftlDestComp)) + { + FTLDestinationComponent ftlDest = _entManager.AddComponent(dest); + ftlDest.RequireCoordinateDisk = true; + + if (_entManager.HasComponent(dest)) + { + ftlDest.BeaconsOnly = true; + + shell.WriteLine(Loc.GetString("cmd-ftldisk-planet", ("destination", destinations), ("map", dest))); + } + } + else + { + // we don't do these automatically, since it isn't clear what the correct resolution is. Instead we provide feedback to the user and carry on like they know what theyre doing. + if (ftlDestComp.Enabled == false) + shell.WriteLine(Loc.GetString("cmd-ftldisk-already-dest-not-enabled", ("destination", destinations), ("map", dest))); + + if (ftlDestComp.BeaconsOnly == true) + shell.WriteLine(Loc.GetString("cmd-ftldisk-requires-ftl-point", ("destination", destinations), ("map", dest))); + } + + // create the FTL disk + EntityUid cdUid = _entManager.SpawnEntity(CoordinatesDisk, coords); + var cd = _entManager.EnsureComponent(cdUid); + cd.Destination = dest; + _entManager.Dirty(cdUid, cd); + + // create disk case + EntityUid cdCaseUid = _entManager.SpawnEntity(DiskCase, coords); + + // apply labels + if (_entManager.TryGetComponent(dest, out var meta) && meta != null && meta.EntityName != null) + { + labelSystem.Label(cdUid, meta.EntityName); + labelSystem.Label(cdCaseUid, meta.EntityName); + } + + // if the case has a storage, try to place the disk in there and then the case inhand + + if (_entManager.TryGetComponent(cdCaseUid, out var storage) && storageSystem.Insert(cdCaseUid, cdUid, out _, storageComp: storage, playSound: false)) + { + if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + { + handsSystem.TryPickup(entity, cdCaseUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent); + } + } + else // the case was messed up, put disk inhand + { + _entManager.DeleteEntity(cdCaseUid); // something went wrong so just yeet the chaf + + if (_entManager.TryGetComponent(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent)) + { + handsSystem.TryPickup(entity, cdUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent); + } + } + } + else + { + shell.WriteLine(Loc.GetString("shell-invalid-entity-uid", ("uid", destinations))); + } + } + } + + public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) + { + if (args.Length >= 1) + return CompletionResult.FromHintOptions(CompletionHelper.MapUids(_entManager), Loc.GetString("cmd-ftldisk-hint")); + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs index f289752b7cf..d5a429db030 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.Modules.cs @@ -60,6 +60,10 @@ private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent if (_actions.AddAction(chassis, ref component.ModuleSwapActionEntity, out var action, component.ModuleSwapActionId, uid)) { + if(TryComp(uid, out var moduleIconComp)) + { + action.Icon = moduleIconComp.Icon; + }; action.EntityIcon = uid; Dirty(component.ModuleSwapActionEntity.Value, action); } diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 6b7df52a6eb..0070beb6ef9 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -28,12 +28,12 @@ namespace Content.Server.Silicons.Laws; public sealed class SiliconLawSystem : SharedSiliconLawSystem { [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly UserInterfaceSystem _userInterface = default!; /// public override void Initialize() @@ -178,10 +178,8 @@ private void EnsureEmaggedRole(EntityUid uid, EmagSiliconLawComponent component) if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) return; - if (_roles.MindHasRole(mindId)) - return; - - _roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole }); + if (!_roles.MindHasRole(mindId)) + _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon"); } public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) @@ -295,6 +293,8 @@ protected override void OnUpdaterInsert(Entity ent, while (query.MoveNext(out var update)) { SetLaws(lawset, update); + if (provider.LawUploadSound != null && _mind.TryGetMind(update, out var mindId, out _)) + _roles.MindPlaySound(mindId, provider.LawUploadSound); } } } diff --git a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs index 91314570f5b..bf694d229a1 100644 --- a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs @@ -12,10 +12,10 @@ namespace Content.Server.Spawners.EntitySystems; public sealed class ContainerSpawnPointSystem : EntitySystem { + [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly GameTicker _gameTicker = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; @@ -36,7 +36,7 @@ public void HandlePlayerSpawning(PlayerSpawningEvent args) // If it's just a spawn pref check if it's for cryo (silly). if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep && - (!_proto.TryIndex(args.Job?.Prototype, out var jobProto) || jobProto.JobEntity == null)) + (!_proto.TryIndex(args.Job, out var jobProto) || jobProto.JobEntity == null)) { return; } @@ -52,7 +52,7 @@ public void HandlePlayerSpawning(PlayerSpawningEvent args) // DeltaV - Custom override for override spawnpoints, only used for prisoners currently. This shouldn't run for any other jobs if (args.DesiredSpawnPointType == SpawnPointType.Job) { - if (spawnPoint.SpawnType != SpawnPointType.Job || spawnPoint.Job != args.Job?.Prototype) + if (spawnPoint.SpawnType != SpawnPointType.Job || spawnPoint.Job != args.Job) continue; possibleContainers.Add((uid, spawnPoint, container, xform)); @@ -63,7 +63,7 @@ public void HandlePlayerSpawning(PlayerSpawningEvent args) if (spawnPoint.SpawnType == SpawnPointType.Unset) { // make sure we also check the job here for various reasons. - if (spawnPoint.Job == null || spawnPoint.Job == args.Job?.Prototype) + if (spawnPoint.Job == null || spawnPoint.Job == args.Job) possibleContainers.Add((uid, spawnPoint, container, xform)); continue; } @@ -75,7 +75,7 @@ public void HandlePlayerSpawning(PlayerSpawningEvent args) if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job)) { possibleContainers.Add((uid, spawnPoint, container, xform)); } diff --git a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index ca41f5ce4e9..2a29b833d88 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs @@ -36,7 +36,7 @@ private void OnPlayerSpawning(PlayerSpawningEvent args) if (args.DesiredSpawnPointType != SpawnPointType.Unset) { var isMatchingJob = spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job?.Id == args.Job.Prototype); + (args.Job == null || spawnPoint.Job?.Id == args.Job); switch (args.DesiredSpawnPointType) { @@ -57,7 +57,7 @@ private void OnPlayerSpawning(PlayerSpawningEvent args) if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job)) { possiblePositions.Add(xform.Coordinates); } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 47340d9ff1e..4a0225c2302 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Server.Access.Systems; using Content.Server.DetailExaminable; using Content.Server.Humanoid; @@ -18,13 +17,10 @@ using Content.Shared.PDA; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Random; using Content.Shared.Random.Helpers; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; using Content.Shared.Station; -using Content.Shared.StatusIcon; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Map; @@ -42,16 +38,18 @@ namespace Content.Server.Station.Systems; [PublicAPI] public sealed class StationSpawningSystem : SharedStationSpawningSystem { - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly ActorSystem _actors = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly PdaSystem _pdaSystem = default!; - [Dependency] private readonly SharedAccessSystem _accessSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; private bool _randomizeCharacters; @@ -75,7 +73,7 @@ public override void Initialize() /// /// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable. /// - public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, JobComponent? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null, SpawnPointType spawnPointType = SpawnPointType.Unset) + public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, ProtoId? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null, SpawnPointType spawnPointType = SpawnPointType.Unset) { if (station != null && !Resolve(station.Value, ref stationSpawning)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); @@ -104,12 +102,12 @@ public override void Initialize() /// The spawned entity public EntityUid SpawnPlayerMob( EntityCoordinates coordinates, - JobComponent? job, + ProtoId? job, HumanoidCharacterProfile? profile, EntityUid? station, EntityUid? entity = null) { - _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype); + _prototypeManager.TryIndex(job ?? string.Empty, out var prototype); RoleLoadout? loadout = null; // Need to get the loadout up-front to handle names if we use an entity spawn override. @@ -187,8 +185,8 @@ public EntityUid SpawnPlayerMob( if (profile != null) { - if (job != null) - SetPdaAndIdCardData(entity.Value, profile.Name, job, station); // DeltaV #1425 - Inherit job data from a VirtualJob if one exists + if (prototype != null) + SetPdaAndIdCardData(entity.Value, profile.Name, prototype, station); _humanoidSystem.LoadProfile(entity.Value, profile); _metaSystem.SetEntityName(entity.Value, profile.Name); @@ -203,9 +201,9 @@ public EntityUid SpawnPlayerMob( return entity.Value; } - private void DoJobSpecials(JobComponent? job, EntityUid entity) + private void DoJobSpecials(ProtoId? job, EntityUid entity) { - if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype)) + if (!_prototypeManager.TryIndex(job ?? string.Empty, out JobPrototype? prototype)) return; foreach (var jobSpecial in prototype.Special) @@ -221,11 +219,8 @@ private void DoJobSpecials(JobComponent? job, EntityUid entity) /// Character name to use for the ID. /// Job prototype to use for the PDA and ID. /// The station this player is being spawned on. - public void SetPdaAndIdCardData(EntityUid entity, string characterName, JobComponent job, EntityUid? station) // DeltaV #1425 - Use Job instead of JobId to pass VirtualJobLocalizedName/Icon + public void SetPdaAndIdCardData(EntityUid entity, string characterName, JobPrototype jobPrototype, EntityUid? station) { - if (!_prototypeManager.TryIndex(job.Prototype, out var jobPrototype)) // DeltaV #1425 - Get jobPrototype separately as a result - return; - if (!InventorySystem.TryGetSlotEntity(entity, "id", out var idUid)) return; @@ -277,7 +272,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs /// /// The job to use, if any. /// - public readonly JobComponent? Job; + public readonly ProtoId? Job; /// /// The profile to use, if any. /// @@ -291,7 +286,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs /// public readonly SpawnPointType DesiredSpawnPointType; - public PlayerSpawningEvent(JobComponent? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station, SpawnPointType spawnPointType = SpawnPointType.Unset) + public PlayerSpawningEvent(ProtoId? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station, SpawnPointType spawnPointType = SpawnPointType.Unset) { Job = job; HumanoidCharacterProfile = humanoidCharacterProfile; diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs index 80c88917c02..e416effbdd8 100644 --- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs +++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs @@ -159,7 +159,7 @@ public void CreateGeneralRecord( { Name = name, Age = age, - JobTitle = card?.Comp.JobTitle ?? jobPrototype.LocalizedName, // DeltaV + JobTitle = card?.Comp.LocalizedJobTitle ?? jobPrototype.LocalizedName, // DeltaV JobIcon = card?.Comp.JobIcon ?? jobPrototype.Icon, // DeltaV JobPrototype = jobId, Species = species, diff --git a/Content.Server/Store/Conditions/BuyerAntagCondition.cs b/Content.Server/Store/Conditions/BuyerAntagCondition.cs index 1edc4a33657..4b1b6013eb8 100644 --- a/Content.Server/Store/Conditions/BuyerAntagCondition.cs +++ b/Content.Server/Store/Conditions/BuyerAntagCondition.cs @@ -33,16 +33,16 @@ public override bool Condition(ListingConditionArgs args) return true; var roleSystem = ent.System(); - var roles = roleSystem.MindGetAllRoles(mindId); + var roles = roleSystem.MindGetAllRoleInfo(mindId); if (Blacklist != null) { foreach (var role in roles) { - if (role.Component is not AntagonistRoleComponent blacklistantag) + if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype)) continue; - if (blacklistantag.PrototypeId != null && Blacklist.Contains(blacklistantag.PrototypeId)) + if (Blacklist.Contains(role.Prototype)) return false; } } @@ -52,10 +52,11 @@ public override bool Condition(ListingConditionArgs args) var found = false; foreach (var role in roles) { - if (role.Component is not AntagonistRoleComponent antag) + + if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype)) continue; - if (antag.PrototypeId != null && Whitelist.Contains(antag.PrototypeId)) + if (Whitelist.Contains(role.Prototype)) found = true; } if (!found) diff --git a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs index ea8de4a9ccd..43c06efad3c 100644 --- a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs +++ b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs @@ -37,13 +37,13 @@ public override bool Condition(ListingConditionArgs args) return true; var jobs = ent.System(); - jobs.MindTryGetJob(mindId, out var job, out _); + jobs.MindTryGetJob(mindId, out var job); - if (Blacklist != null && job?.Prototype != null) + if (Blacklist != null && job != null) { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) + if (department.Roles.Contains(job.ID) && Blacklist.Contains(department.ID)) return false; } } @@ -52,11 +52,11 @@ public override bool Condition(ListingConditionArgs args) { var found = false; - if (job?.Prototype != null) + if (job != null) { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) + if (department.Roles.Contains(job.ID) && Whitelist.Contains(department.ID)) { found = true; break; diff --git a/Content.Server/Store/Conditions/BuyerJobCondition.cs b/Content.Server/Store/Conditions/BuyerJobCondition.cs index 6a53af188c2..1ff4a97c33c 100644 --- a/Content.Server/Store/Conditions/BuyerJobCondition.cs +++ b/Content.Server/Store/Conditions/BuyerJobCondition.cs @@ -34,17 +34,17 @@ public override bool Condition(ListingConditionArgs args) return true; var jobs = ent.System(); - jobs.MindTryGetJob(mindId, out var job, out _); + jobs.MindTryGetJob(mindId, out var job); if (Blacklist != null) { - if (job?.Prototype != null && Blacklist.Contains(job.Prototype)) + if (job is not null && Blacklist.Contains(job.ID)) return false; } if (Whitelist != null) { - if (job?.Prototype == null || !Whitelist.Contains(job.Prototype)) + if (job == null || !Whitelist.Contains(job.ID)) return false; } diff --git a/Content.Server/Temperature/Systems/TemperatureSystem.cs b/Content.Server/Temperature/Systems/TemperatureSystem.cs index ccd981bbbc3..e46b18a2659 100644 --- a/Content.Server/Temperature/Systems/TemperatureSystem.cs +++ b/Content.Server/Temperature/Systems/TemperatureSystem.cs @@ -130,7 +130,7 @@ public void ForceChangeTemperature(EntityUid uid, float temp, TemperatureCompone public void ChangeHeat(EntityUid uid, float heatAmount, bool ignoreHeatResistance = false, TemperatureComponent? temperature = null) { - if (!Resolve(uid, ref temperature)) + if (!Resolve(uid, ref temperature, false)) return; if (!ignoreHeatResistance) @@ -311,7 +311,7 @@ private void OnTemperatureChangeAttempt(EntityUid uid, TemperatureProtectionComp private void ChangeTemperatureOnCollide(Entity ent, ref ProjectileHitEvent args) { - _temperature.ChangeHeat(args.Target, ent.Comp.Heat, ent.Comp.IgnoreHeatResistance);// adjust the temperature + _temperature.ChangeHeat(args.Target, ent.Comp.Heat, ent.Comp.IgnoreHeatResistance);// adjust the temperature } private void OnParentChange(EntityUid uid, TemperatureComponent component, diff --git a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs index 80471b64279..de1c3d2e6d1 100644 --- a/Content.Server/Thief/Systems/ThiefBeaconSystem.cs +++ b/Content.Server/Thief/Systems/ThiefBeaconSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Foldable; using Content.Shared.Popups; using Content.Shared.Verbs; +using Content.Shared.Roles; using Robust.Shared.Audio.Systems; namespace Content.Server.Thief.Systems; @@ -18,6 +19,7 @@ public sealed class ThiefBeaconSystem : EntitySystem [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; public override void Initialize() { @@ -37,7 +39,7 @@ private void OnGetInteractionVerbs(Entity beacon, ref GetV return; var mind = _mind.GetMind(args.User); - if (!HasComp(mind)) + if (mind == null || !_roles.MindHasRole(mind.Value)) return; var user = args.User; diff --git a/Content.Server/Traitor/Components/AutoTraitorComponent.cs b/Content.Server/Traitor/Components/AutoTraitorComponent.cs index ab4bee2f267..a4710afd8eb 100644 --- a/Content.Server/Traitor/Components/AutoTraitorComponent.cs +++ b/Content.Server/Traitor/Components/AutoTraitorComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Traitor.Systems; +using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Components; @@ -9,14 +10,8 @@ namespace Content.Server.Traitor.Components; public sealed partial class AutoTraitorComponent : Component { /// - /// Whether to give the traitor an uplink or not. + /// The traitor profile to use /// - [DataField("giveUplink"), ViewVariables(VVAccess.ReadWrite)] - public bool GiveUplink = true; - - /// - /// Whether to give the traitor objectives or not. - /// - [DataField("giveObjectives"), ViewVariables(VVAccess.ReadWrite)] - public bool GiveObjectives = true; + [DataField] + public EntProtoId Profile = "Traitor"; } diff --git a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs index e9307effbc6..d5a4db591a7 100644 --- a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs +++ b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs @@ -12,9 +12,6 @@ public sealed class AutoTraitorSystem : EntitySystem { [Dependency] private readonly AntagSelectionSystem _antag = default!; - [ValidatePrototypeId] - private const string DefaultTraitorRule = "Traitor"; - public override void Initialize() { base.Initialize(); @@ -24,6 +21,6 @@ public override void Initialize() private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args) { - _antag.ForceMakeAntag(args.Mind.Comp.Session, DefaultTraitorRule); + _antag.ForceMakeAntag(args.Mind.Comp.Session, comp.Profile); } } diff --git a/Content.Server/Traitor/Uplink/UplinkSystem.cs b/Content.Server/Traitor/Uplink/UplinkSystem.cs index ae809dc4d77..4c0a990b148 100644 --- a/Content.Server/Traitor/Uplink/UplinkSystem.cs +++ b/Content.Server/Traitor/Uplink/UplinkSystem.cs @@ -1,97 +1,136 @@ using System.Linq; using Content.Server.Store.Systems; using Content.Server.StoreDiscount.Systems; +using Content.Shared.FixedPoint; using Content.Shared.Hands.EntitySystems; +using Content.Shared.Implants; using Content.Shared.Inventory; using Content.Shared.PDA; -using Content.Shared.FixedPoint; using Content.Shared.Store; using Content.Shared.Store.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server.Traitor.Uplink; -namespace Content.Server.Traitor.Uplink +public sealed class UplinkSystem : EntitySystem { - public sealed class UplinkSystem : EntitySystem + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly StoreSystem _store = default!; + [Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!; + + [ValidatePrototypeId] + public const string TelecrystalCurrencyPrototype = "Telecrystal"; + private const string FallbackUplinkImplant = "UplinkImplant"; + private const string FallbackUplinkCatalog = "UplinkUplinkImplanter"; + + /// + /// Adds an uplink to the target + /// + /// The person who is getting the uplink + /// The amount of currency on the uplink. If null, will just use the amount specified in the preset. + /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. + /// Marker that enables discounts for uplink items. + /// Whether or not the uplink was added successfully + public bool AddUplink( + EntityUid user, + FixedPoint2 balance, + EntityUid? uplinkEntity = null, + bool giveDiscounts = false) { - [Dependency] private readonly InventorySystem _inventorySystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly StoreSystem _store = default!; - - [ValidatePrototypeId] - public const string TelecrystalCurrencyPrototype = "Telecrystal"; - - /// - /// Adds an uplink to the target - /// - /// The person who is getting the uplink - /// The amount of currency on the uplink. If null, will just use the amount specified in the preset. - /// The entity that will actually have the uplink functionality. Defaults to the PDA if null. - /// Marker that enables discounts for uplink items. - /// Whether or not the uplink was added successfully - public bool AddUplink( - EntityUid user, - FixedPoint2? balance, - EntityUid? uplinkEntity = null, - bool giveDiscounts = false - ) - { - // Try to find target item if none passed - uplinkEntity ??= FindUplinkTarget(user); - if (uplinkEntity == null) - { - return false; - } + // Try to find target item if none passed - EnsureComp(uplinkEntity.Value); - var store = EnsureComp(uplinkEntity.Value); + uplinkEntity ??= FindUplinkTarget(user); - store.AccountOwner = user; - store.Balance.Clear(); - if (balance != null) - { - store.Balance.Clear(); - _store.TryAddCurrency(new Dictionary { { TelecrystalCurrencyPrototype, balance.Value } }, uplinkEntity.Value, store); - } + if (uplinkEntity == null) + return ImplantUplink(user, balance, giveDiscounts); - var uplinkInitializedEvent = new StoreInitializedEvent( - TargetUser: user, - Store: uplinkEntity.Value, - UseDiscounts: giveDiscounts, - Listings: _store.GetAvailableListings(user, uplinkEntity.Value, store) - .ToArray() - ); - RaiseLocalEvent(ref uplinkInitializedEvent); - // TODO add BUI. Currently can't be done outside of yaml -_- - - return true; - } + EnsureComp(uplinkEntity.Value); + + SetUplink(user, uplinkEntity.Value, balance, giveDiscounts); + + // TODO add BUI. Currently can't be done outside of yaml -_- + // ^ What does this even mean? + + return true; + } + + /// + /// Configure TC for the uplink + /// + private void SetUplink(EntityUid user, EntityUid uplink, FixedPoint2 balance, bool giveDiscounts) + { + var store = EnsureComp(uplink); + store.AccountOwner = user; + + store.Balance.Clear(); + _store.TryAddCurrency(new Dictionary { { TelecrystalCurrencyPrototype, balance } }, + uplink, + store); - /// - /// Finds the entity that can hold an uplink for a user. - /// Usually this is a pda in their pda slot, but can also be in their hands. (but not pockets or inside bag, etc.) - /// - public EntityUid? FindUplinkTarget(EntityUid user) + var uplinkInitializedEvent = new StoreInitializedEvent( + TargetUser: user, + Store: uplink, + UseDiscounts: giveDiscounts, + Listings: _store.GetAvailableListings(user, uplink, store) + .ToArray()); + RaiseLocalEvent(ref uplinkInitializedEvent); + } + + /// + /// Implant an uplink as a fallback measure if the traitor had no PDA + /// + private bool ImplantUplink(EntityUid user, FixedPoint2 balance, bool giveDiscounts) + { + var implantProto = new string(FallbackUplinkImplant); + + if (!_proto.TryIndex(FallbackUplinkCatalog, out var catalog)) + return false; + + if (!catalog.Cost.TryGetValue(TelecrystalCurrencyPrototype, out var cost)) + return false; + + if (balance < cost) // Can't use Math functions on FixedPoint2 + balance = 0; + else + balance = balance - cost; + + var implant = _subdermalImplant.AddImplant(user, implantProto); + + if (!HasComp(implant)) + return false; + + SetUplink(user, implant.Value, balance, giveDiscounts); + return true; + } + + /// + /// Finds the entity that can hold an uplink for a user. + /// Usually this is a pda in their pda slot, but can also be in their hands. (but not pockets or inside bag, etc.) + /// + public EntityUid? FindUplinkTarget(EntityUid user) + { + // Try to find PDA in inventory + if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator)) { - // Try to find PDA in inventory - if (_inventorySystem.TryGetContainerSlotEnumerator(user, out var containerSlotEnumerator)) + while (containerSlotEnumerator.MoveNext(out var pdaUid)) { - while (containerSlotEnumerator.MoveNext(out var pdaUid)) - { - if (!pdaUid.ContainedEntity.HasValue) - continue; - - if (HasComp(pdaUid.ContainedEntity.Value) || HasComp(pdaUid.ContainedEntity.Value)) - return pdaUid.ContainedEntity.Value; - } - } + if (!pdaUid.ContainedEntity.HasValue) + continue; - // Also check hands - foreach (var item in _handsSystem.EnumerateHeld(user)) - { - if (HasComp(item) || HasComp(item)) - return item; + if (HasComp(pdaUid.ContainedEntity.Value) || HasComp(pdaUid.ContainedEntity.Value)) + return pdaUid.ContainedEntity.Value; } + } - return null; + // Also check hands + foreach (var item in _handsSystem.EnumerateHeld(user)) + { + if (HasComp(item) || HasComp(item)) + return item; } + + return null; } } diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 190a2d0263e..ec462ae23e8 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -56,8 +56,14 @@ private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component, _damageExamine.AddDamageExamine(args.Message, damageSpec, Loc.GetString("damage-melee")); } - protected override bool ArcRaySuccessful(EntityUid targetUid, Vector2 position, Angle angle, Angle arcWidth, float range, MapId mapId, - EntityUid ignore, ICommonSession? session) + protected override bool ArcRaySuccessful(EntityUid targetUid, + Vector2 position, + Angle angle, + Angle arcWidth, + float range, + MapId mapId, + EntityUid ignore, + ICommonSession? session) { // Originally the client didn't predict damage effects so you'd intuit some level of how far // in the future you'd need to predict, but then there was a lot of complaining like "why would you add artifical delay" as if ping is a choice. diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs index e44ee6baa12..b1a08fb5056 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Effects/Systems/PortalArtifactSystem.cs @@ -2,6 +2,8 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Events; using Content.Shared.Mind.Components; using Content.Shared.Teleportation.Systems; +using Robust.Shared.Collections; +using Robust.Shared.Containers; using Robust.Shared.Random; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems; @@ -11,6 +13,7 @@ public sealed class PortalArtifactSystem : EntitySystem [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly LinkedEntitySystem _link = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; public override void Initialize() { @@ -21,21 +24,28 @@ public override void Initialize() private void OnActivate(Entity artifact, ref ArtifactActivatedEvent args) { var map = Transform(artifact).MapID; - var firstPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(artifact)); - - var minds = new List(); - var mindQuery = EntityQueryEnumerator(); - while (mindQuery.MoveNext(out var uid, out var mc, out var xform)) + var validMinds = new ValueList(); + var mindQuery = EntityQueryEnumerator(); + while (mindQuery.MoveNext(out var uid, out var mc, out var xform, out var meta)) { - if (mc.HasMind && xform.MapID == map) - minds.Add(uid); + // check if the MindContainer has a Mind and if the entity is not in a container (this also auto excludes AI) and if they are on the same map + if (mc.HasMind && !_container.IsEntityOrParentInContainer(uid, meta: meta, xform: xform) && xform.MapID == map) + { + validMinds.Add(uid); + } } + //this would only be 0 if there were a station full of AIs and no one else, in that case just stop this function + if (validMinds.Count == 0) + return; + + var firstPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(artifact)); + + var target = _random.Pick(validMinds); - var target = _random.Pick(minds); var secondPortal = Spawn(artifact.Comp.PortalProto, _transform.GetMapCoordinates(target)); //Manual position swapping, because the portal that opens doesn't trigger a collision, and doesn't teleport targets the first time. - _transform.SwapPositions(target, secondPortal); + _transform.SwapPositions(target, artifact.Owner); _link.TryLink(firstPortal, secondPortal, true); } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 95d63ab938f..852e3182c3c 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -12,7 +12,6 @@ using Content.Server.NPC; using Content.Server.NPC.HTN; using Content.Server.NPC.Systems; -using Content.Server.Roles; using Content.Server.Speech.Components; using Content.Server.Temperature.Components; using Content.Shared.Abilities.Psionics; @@ -49,18 +48,18 @@ namespace Content.Server.Zombies; /// public sealed partial class ZombieSystem { - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly ServerInventorySystem _inventory = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly SharedCombatModeSystem _combat = default!; [Dependency] private readonly NpcFactionSystem _faction = default!; - [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; [Dependency] private readonly IdentitySystem _identity = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; - [Dependency] private readonly SharedCombatModeSystem _combat = default!; - [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; /// /// Handles an entity turning into a zombie when they die or go into crit @@ -249,7 +248,7 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) if (hasMind && _mind.TryGetSession(mindId, out var session)) { //Zombie role for player manifest - _roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId }); + _roles.MindAddRole(mindId, "MindRoleZombie", mind: null, silent: true); //Greeting message for new bebe zombers _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); diff --git a/Content.Shared/APC/SharedApc.cs b/Content.Shared/APC/SharedApc.cs index bf9fdc9444b..802c06a6ab6 100644 --- a/Content.Shared/APC/SharedApc.cs +++ b/Content.Shared/APC/SharedApc.cs @@ -178,15 +178,13 @@ public enum ApcChargeState : sbyte public sealed class ApcBoundInterfaceState : BoundUserInterfaceState, IEquatable { public readonly bool MainBreaker; - public readonly bool HasAccess; public readonly int Power; public readonly ApcExternalPowerState ApcExternalPower; public readonly float Charge; - public ApcBoundInterfaceState(bool mainBreaker, bool hasAccess, int power, ApcExternalPowerState apcExternalPower, float charge) + public ApcBoundInterfaceState(bool mainBreaker, int power, ApcExternalPowerState apcExternalPower, float charge) { MainBreaker = mainBreaker; - HasAccess = hasAccess; Power = power; ApcExternalPower = apcExternalPower; Charge = charge; @@ -197,7 +195,6 @@ public bool Equals(ApcBoundInterfaceState? other) if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return MainBreaker == other.MainBreaker && - HasAccess == other.HasAccess && Power == other.Power && ApcExternalPower == other.ApcExternalPower && MathHelper.CloseTo(Charge, other.Charge); @@ -210,7 +207,7 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine(MainBreaker, HasAccess, Power, (int) ApcExternalPower, Charge); + return HashCode.Combine(MainBreaker, Power, (int) ApcExternalPower, Charge); } } diff --git a/Content.Shared/Access/Components/IdCardComponent.cs b/Content.Shared/Access/Components/IdCardComponent.cs index ccd4cccbe7b..e80ced64643 100644 --- a/Content.Shared/Access/Components/IdCardComponent.cs +++ b/Content.Shared/Access/Components/IdCardComponent.cs @@ -20,7 +20,12 @@ public sealed partial class IdCardComponent : Component [DataField] [AutoNetworkedField] [Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWrite)] - public string? JobTitle; + public LocId? JobTitle; + + private string? _jobTitle; + + [Access(typeof(SharedIdCardSystem), typeof(SharedPdaSystem), typeof(SharedAgentIdCardSystem), Other = AccessPermissions.ReadWriteExecute)] + public string? LocalizedJobTitle { set => _jobTitle = value; get => _jobTitle ?? Loc.GetString(JobTitle ?? string.Empty); } /// /// The state of the job icon rsi. diff --git a/Content.Shared/Access/Systems/IdExaminableSystem.cs b/Content.Shared/Access/Systems/IdExaminableSystem.cs index 13359adcba2..807ccc6616d 100644 --- a/Content.Shared/Access/Systems/IdExaminableSystem.cs +++ b/Content.Shared/Access/Systems/IdExaminableSystem.cs @@ -67,7 +67,7 @@ public string GetMessage(EntityUid uid) private string GetNameAndJob(IdCardComponent id) { - var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})"; + var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})"; var val = string.IsNullOrWhiteSpace(id.FullName) ? Loc.GetString(id.NameLocId, diff --git a/Content.Shared/Access/Systems/SharedIdCardSystem.cs b/Content.Shared/Access/Systems/SharedIdCardSystem.cs index 8bdc548e353..a5a37eecbd0 100644 --- a/Content.Shared/Access/Systems/SharedIdCardSystem.cs +++ b/Content.Shared/Access/Systems/SharedIdCardSystem.cs @@ -116,6 +116,7 @@ public bool TryGetIdCard(EntityUid uid, out Entity idCard) /// /// /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs. + /// Actually works with the LocalizedJobTitle DataField and not with JobTitle. /// public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null) { @@ -134,9 +135,9 @@ public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? jobTitle = null; } - if (id.JobTitle == jobTitle) + if (id.LocalizedJobTitle == jobTitle) return true; - id.JobTitle = jobTitle; + id.LocalizedJobTitle = jobTitle; Dirty(uid, id); UpdateEntityName(uid, id); @@ -238,7 +239,7 @@ private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null) if (!Resolve(uid, ref id)) return; - var jobSuffix = string.IsNullOrWhiteSpace(id.JobTitle) ? string.Empty : $" ({id.JobTitle})"; + var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})"; var val = string.IsNullOrWhiteSpace(id.FullName) ? Loc.GetString(id.NameLocId, @@ -251,7 +252,7 @@ private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null) private static string ExtractFullTitle(IdCardComponent idCardComponent) { - return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.JobTitle ?? string.Empty)})" + return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})" .Trim(); } } diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs index 0e29fcd98ae..6a04bfe42df 100644 --- a/Content.Shared/Bed/Sleep/SleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Buckle.Components; using Content.Shared.Damage; using Content.Shared.Damage.ForceSay; +using Content.Shared.Emoting; using Content.Shared.Examine; using Content.Shared.Eye.Blinding.Systems; using Content.Shared.IdentityManagement; @@ -61,6 +62,7 @@ public override void Initialize() SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnUnbuckleAttempt); + SubscribeLocalEvent(OnEmoteAttempt); } private void OnUnbuckleAttempt(Entity ent, ref UnbuckleAttemptEvent args) @@ -310,6 +312,14 @@ public bool TryWaking(Entity ent, bool force = false, Entity Wake((ent, ent.Comp)); return true; } + + /// + /// Prevents the use of emote actions while sleeping + /// + public void OnEmoteAttempt(Entity ent, ref EmoteAttemptEvent args) + { + args.Cancel(); + } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 66c3bc6cd0e..a8965aa93c4 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -32,6 +32,21 @@ public sealed class CCVars : CVars public static readonly CVarDef DefaultGuide = CVarDef.Create("server.default_guide", "NewPlayer", CVar.REPLICATED | CVar.SERVER); + /// + /// If greater than 0, automatically restart the server after this many minutes of uptime. + /// + /// + /// + /// This is intended to work around various bugs and performance issues caused by long continuous server uptime. + /// + /// + /// This uses the same non-disruptive logic as update restarts, + /// i.e. the game will only restart at round end or when there is nobody connected. + /// + /// + public static readonly CVarDef ServerUptimeRestartMinutes = + CVarDef.Create("server.uptime_restart_minutes", 0, CVar.SERVERONLY); + /* * Ambience */ @@ -436,6 +451,12 @@ public static readonly CVarDef public static readonly CVarDef GameEntityMenuLookup = CVarDef.Create("game.entity_menu_lookup", 0.25f, CVar.CLIENTONLY | CVar.ARCHIVE); + /// + /// Should the clients window show the server hostname in the title? + /// + public static readonly CVarDef GameHostnameInTitlebar = + CVarDef.Create("game.hostname_in_titlebar", true, CVar.SERVER | CVar.REPLICATED); + /* * Discord */ diff --git a/Content.Shared/ChangeNameInContainer/ChangeNameInContainerComponent.cs b/Content.Shared/ChangeNameInContainer/ChangeNameInContainerComponent.cs new file mode 100644 index 00000000000..dca8f5b29b4 --- /dev/null +++ b/Content.Shared/ChangeNameInContainer/ChangeNameInContainerComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.ChangeNameInContainer; + +/// +/// An entity with this component will get its name and verb chaned to the container it's inside of. E.g, if your a +/// pAI that has this component and are inside a lizard plushie, your name when talking will be "lizard plushie". +/// +[RegisterComponent, NetworkedComponent, Access(typeof(ChangeNameInContainerSystem))] +public sealed partial class ChangeVoiceInContainerComponent : Component +{ + /// + /// A whitelist of containers that will change the name. + /// + [DataField] + public EntityWhitelist? Whitelist; +} diff --git a/Content.Shared/ChangeNameInContainer/ChangeNameInContainerSystem.cs b/Content.Shared/ChangeNameInContainer/ChangeNameInContainerSystem.cs new file mode 100644 index 00000000000..f9abda3ec23 --- /dev/null +++ b/Content.Shared/ChangeNameInContainer/ChangeNameInContainerSystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Chat; +using Robust.Shared.Containers; +using Content.Shared.Whitelist; +using Content.Shared.Speech; + +namespace Content.Shared.ChangeNameInContainer; + +public sealed partial class ChangeNameInContainerSystem : EntitySystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnTransformSpeakerName); + } + + private void OnTransformSpeakerName(Entity ent, ref TransformSpeakerNameEvent args) + { + if (!_container.TryGetContainingContainer((ent, null, null), out var container) + || _whitelist.IsWhitelistFail(ent.Comp.Whitelist, container.Owner)) + return; + + args.VoiceName = Name(container.Owner); + if (TryComp(container.Owner, out var speechComp)) + args.SpeechVerb = speechComp.SpeechVerb; + } + +} diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index e5944ec30ae..414c26d6c7a 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -85,6 +85,35 @@ public SpeechVerbPrototype GetSpeechVerb(EntityUid source, string message, Speec return current ?? _prototypeManager.Index(speech.SpeechVerb); } + /// + /// Splits the input message into a radio prefix part and the rest to preserve it during sanitization. + /// + /// + /// This is primarily for the chat emote sanitizer, which can match against ":b" as an emote, which is a valid radio keycode. + /// + public void GetRadioKeycodePrefix(EntityUid source, + string input, + out string output, + out string prefix) + { + prefix = string.Empty; + output = input; + + // If the string is less than 2, then it's probably supposed to be an emote. + // No one is sending empty radio messages! + if (input.Length <= 2) + return; + + if (!(input.StartsWith(RadioChannelPrefix) || input.StartsWith(RadioChannelAltPrefix))) + return; + + if (!_keyCodes.TryGetValue(input[1], out _)) + return; + + prefix = input[..2]; + output = input[2..]; + } + /// /// Attempts to resolve radio prefixes in chat messages (e.g., remove a leading ":e" and resolve the requested /// channel. Returns true if a radio message was attempted, even if the channel is invalid. diff --git a/Content.Shared/Chemistry/InjectOverTimeEvent.cs b/Content.Shared/Chemistry/InjectOverTimeEvent.cs new file mode 100644 index 00000000000..ca5ab4213ff --- /dev/null +++ b/Content.Shared/Chemistry/InjectOverTimeEvent.cs @@ -0,0 +1,13 @@ +namespace Content.Shared.Chemistry.Events; + +/// +/// Raised directed on an entity when it embeds in another entity. +/// +[ByRefEvent] +public readonly record struct InjectOverTimeEvent(EntityUid embeddedIntoUid) +{ + /// + /// Entity that is embedded in. + /// + public readonly EntityUid EmbeddedIntoUid = embeddedIntoUid; +} diff --git a/Content.Shared/DeltaV/Recruiter/RecruiterPenComponent.cs b/Content.Shared/DeltaV/Recruiter/RecruiterPenComponent.cs index 654e987320e..b753ffdd9ef 100644 --- a/Content.Shared/DeltaV/Recruiter/RecruiterPenComponent.cs +++ b/Content.Shared/DeltaV/Recruiter/RecruiterPenComponent.cs @@ -1,7 +1,7 @@ -using Content.Shared.FixedPoint; +using Content.Shared.NPC.Prototypes; using Content.Shared.Whitelist; using Robust.Shared.GameStates; -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; namespace Content.Shared.DeltaV.Recruiter; @@ -45,8 +45,8 @@ public sealed partial class RecruiterPenComponent : Component public EntityWhitelist? Blacklist; /// - /// If the user's mind matches this blacklist they can't use this pen. + /// If the user is in any of these factions they can't use this pen. /// [DataField] - public EntityWhitelist? MindBlacklist; + public List> FactionBlacklist = new(); } diff --git a/Content.Shared/DeltaV/Recruiter/SharedRecruiterPenSystem.cs b/Content.Shared/DeltaV/Recruiter/SharedRecruiterPenSystem.cs index cdd8a1ddd4f..9ac22df2920 100644 --- a/Content.Shared/DeltaV/Recruiter/SharedRecruiterPenSystem.cs +++ b/Content.Shared/DeltaV/Recruiter/SharedRecruiterPenSystem.cs @@ -6,8 +6,11 @@ using Content.Shared.Interaction.Events; using Content.Shared.Mind; using Content.Shared.Mindshield.Components; +using Content.Shared.NPC.Systems; using Content.Shared.Popups; +using Content.Shared.Roles; using Content.Shared.Whitelist; +using Robust.Shared.Timing; namespace Content.Shared.DeltaV.Recruiter; @@ -17,8 +20,11 @@ namespace Content.Shared.DeltaV.Recruiter; public abstract class SharedRecruiterPenSystem : EntitySystem { [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly NpcFactionSystem _faction = default!; [Dependency] protected readonly SharedMindSystem Mind = default!; [Dependency] protected readonly SharedPopupSystem Popup = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; private EntityQuery _shieldQuery; @@ -45,7 +51,7 @@ private void OnHandSelected(Entity ent, ref HandSelectedE if (!Mind.TryGetMind(user, out var mind, out _)) return; - if (!HasComp(mind)) + if (!_role.MindHasRole(mind)) return; Popup.PopupEntity(Loc.GetString("recruiter-pen-bound", ("pen", uid)), user, user); @@ -77,7 +83,7 @@ private void OnPrick(Entity ent, ref UseInHandEvent args) private void OnSignAttempt(Entity ent, ref SignAttemptEvent args) { var (uid, comp) = ent; - if (args.Cancelled) + if (args.Cancelled || !_timing.IsFirstTimePredicted) return; args.Cancelled = true; @@ -88,7 +94,7 @@ private void OnSignAttempt(Entity ent, ref SignAttemptEve var user = args.User; if (!comp.Bound) { - Popup.PopupEntity(Loc.GetString("recruiter-pen-locked", ("pen", uid)), user, user); + Popup.PopupClient(Loc.GetString("recruiter-pen-locked", ("pen", uid)), user, user); return; } @@ -97,7 +103,7 @@ private void OnSignAttempt(Entity ent, ref SignAttemptEve if (blood.Value.Comp.Solution.AvailableVolume > 0) { - Popup.PopupEntity(Loc.GetString("recruiter-pen-empty", ("pen", uid)), user, user); + Popup.PopupClient(Loc.GetString("recruiter-pen-empty", ("pen", uid)), user, user); return; } @@ -110,11 +116,8 @@ private void OnSignAttempt(Entity ent, ref SignAttemptEve private bool CheckBlacklist(Entity ent, EntityUid user, string action) { - if (!Mind.TryGetMind(user, out var mind, out _)) - return false; // mindless nt drone... - var (uid, comp) = ent; - if (_whitelist.IsBlacklistPass(comp.Blacklist, user) || _whitelist.IsBlacklistPass(comp.MindBlacklist, mind)) + if (_whitelist.IsBlacklistPass(comp.Blacklist, user) || _faction.IsMemberOfAny(user, ent.Comp.FactionBlacklist)) { Popup.PopupPredicted(Loc.GetString($"recruiter-pen-{action}-forbidden", ("pen", uid)), user, user); return true; diff --git a/Content.Shared/DeltaV/Roles/FugitiveRoleComponent.cs b/Content.Shared/DeltaV/Roles/FugitiveRoleComponent.cs new file mode 100644 index 00000000000..6e081a72b93 --- /dev/null +++ b/Content.Shared/DeltaV/Roles/FugitiveRoleComponent.cs @@ -0,0 +1,6 @@ +using Content.Shared.Roles; + +namespace Content.Shared.DeltaV.Roles; + +[RegisterComponent] +public sealed partial class FugitiveRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/DeltaV/Roles/ListeningPostRoleComponent.cs b/Content.Shared/DeltaV/Roles/ListeningPostRoleComponent.cs index f244e0176bb..4d94a7c1ed6 100644 --- a/Content.Shared/DeltaV/Roles/ListeningPostRoleComponent.cs +++ b/Content.Shared/DeltaV/Roles/ListeningPostRoleComponent.cs @@ -2,5 +2,5 @@ namespace Content.Shared.DeltaV.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class ListeningPostRoleComponent : AntagonistRoleComponent; +[RegisterComponent] +public sealed partial class ListeningPostRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/DeltaV/Roles/ParadoxAnomalyRole.cs b/Content.Shared/DeltaV/Roles/ParadoxAnomalyRole.cs index dfafb7341f7..9f2e4902d3c 100644 --- a/Content.Shared/DeltaV/Roles/ParadoxAnomalyRole.cs +++ b/Content.Shared/DeltaV/Roles/ParadoxAnomalyRole.cs @@ -2,5 +2,5 @@ namespace Content.Shared.DeltaV.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class ParadoxAnomalyRoleComponent : AntagonistRoleComponent; +[RegisterComponent] +public sealed partial class ParadoxAnomalyRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/DeltaV/Roles/RecruiterRole.cs b/Content.Shared/DeltaV/Roles/RecruiterRole.cs index 62603885023..d85ff85fa12 100644 --- a/Content.Shared/DeltaV/Roles/RecruiterRole.cs +++ b/Content.Shared/DeltaV/Roles/RecruiterRole.cs @@ -2,5 +2,5 @@ namespace Content.Shared.DeltaV.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class RecruiterRoleComponent : AntagonistRoleComponent; +[RegisterComponent] +public sealed partial class RecruiterRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/DeltaV/Roles/SharedRoleSystem.DeltaV.cs b/Content.Shared/DeltaV/Roles/SharedRoleSystem.DeltaV.cs deleted file mode 100644 index c4de921543f..00000000000 --- a/Content.Shared/DeltaV/Roles/SharedRoleSystem.DeltaV.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Content.Shared.DeltaV.Roles; - -namespace Content.Shared.Roles; - -public abstract partial class SharedRoleSystem -{ - private void InitializeDeltaV() - { - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - } -} diff --git a/Content.Shared/DeltaV/Roles/SynthesisRole.cs b/Content.Shared/DeltaV/Roles/SynthesisRole.cs index 16a680a3497..b850ac6e44c 100644 --- a/Content.Shared/DeltaV/Roles/SynthesisRole.cs +++ b/Content.Shared/DeltaV/Roles/SynthesisRole.cs @@ -2,5 +2,5 @@ namespace Content.Shared.DeltaV.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class SynthesisRoleComponent : AntagonistRoleComponent; +[RegisterComponent] +public sealed partial class SynthesisRoleComponent : BaseMindRoleComponent; diff --git a/Content.Shared/Ghost/SpectralComponent.cs b/Content.Shared/Ghost/SpectralComponent.cs new file mode 100644 index 00000000000..3799951152e --- /dev/null +++ b/Content.Shared/Ghost/SpectralComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Ghost; + +/// +/// Marker component to identify "ghostly" entities. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class SpectralComponent : Component { } diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Drop.cs index 76575df2fd8..223c2d4a378 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 ae22efcd6a5..fc5adfaf15a 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 6d619460f4f..7bc0a8025fe 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/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs index 29d74071e88..97deff49260 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs @@ -97,20 +97,36 @@ private void OnRemove(EntityUid uid, SubdermalImplantComponent component, EntGot /// public void AddImplants(EntityUid uid, IEnumerable implants) { - var coords = Transform(uid).Coordinates; foreach (var id in implants) { - var ent = Spawn(id, coords); - if (TryComp(ent, out var implant)) - { - ForceImplant(uid, ent, implant); - } - else - { - Log.Warning($"Found invalid starting implant '{id}' on {uid} {ToPrettyString(uid):implanted}"); - Del(ent); - } + AddImplant(uid, id); + } + } + + /// + /// Adds a single implant to a person, and returns the implant. + /// Logs any implant ids that don't have . + /// + /// + /// The implant, if it was successfully created. Otherwise, null. + /// > + public EntityUid? AddImplant(EntityUid uid, String implantId) + { + var coords = Transform(uid).Coordinates; + var ent = Spawn(implantId, coords); + + if (TryComp(ent, out var implant)) + { + ForceImplant(uid, ent, implant); + } + else + { + Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}"); + Del(ent); + return null; } + + return ent; } /// diff --git a/Content.Shared/Interaction/Events/ContactInteractionEvent.cs b/Content.Shared/Interaction/Events/ContactInteractionEvent.cs index c9d5fba2ed0..7be1c01c4ad 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 a820048104d..86701642939 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 da4f9e43d7d..9395ddc910c 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 43dd97762c5..7f2ecb50f88 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,21 @@ 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)) + if (uidA == uidB.Value) return; - 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/Localizations/ContentLocalizationManager.cs b/Content.Shared/Localizations/ContentLocalizationManager.cs index ad8890ae0fd..e60ca74a37f 100644 --- a/Content.Shared/Localizations/ContentLocalizationManager.cs +++ b/Content.Shared/Localizations/ContentLocalizationManager.cs @@ -36,6 +36,7 @@ public void Initialize() _loc.AddFunction(culture, "LOC", FormatLoc); _loc.AddFunction(culture, "NATURALFIXED", FormatNaturalFixed); _loc.AddFunction(culture, "NATURALPERCENT", FormatNaturalPercent); + _loc.AddFunction(culture, "PLAYTIME", FormatPlaytime); /* @@ -141,6 +142,16 @@ public static string FormatDirection(Direction dir) return Loc.GetString($"zzzz-fmt-direction-{dir.ToString()}"); } + /// + /// Formats playtime as hours and minutes. + /// + public static string FormatPlaytime(TimeSpan time) + { + var hours = (int)time.TotalHours; + var minutes = time.Minutes; + return Loc.GetString($"zzzz-fmt-playtime", ("hours", hours), ("minutes", minutes)); + } + private static ILocValue FormatLoc(LocArgs args) { var id = ((LocValueString) args.Args[0]).Value; @@ -229,5 +240,15 @@ private static ILocValue FormatUnits(LocArgs args) return new LocValueString(res); } + + private static ILocValue FormatPlaytime(LocArgs args) + { + var time = TimeSpan.Zero; + if (args.Args is { Count: > 0 } && args.Args[0].Value is TimeSpan timeArg) + { + time = timeArg; + } + return new LocValueString(FormatPlaytime(time)); + } } } diff --git a/Content.Shared/Mind/Components/MindContainerComponent.cs b/Content.Shared/Mind/Components/MindContainerComponent.cs index 380d30ca143..760f5026fad 100644 --- a/Content.Shared/Mind/Components/MindContainerComponent.cs +++ b/Content.Shared/Mind/Components/MindContainerComponent.cs @@ -14,7 +14,6 @@ public sealed partial class MindContainerComponent : Component /// The mind controlling this mob. Can be null. /// [DataField, AutoNetworkedField] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends public EntityUid? Mind { get; set; } /// @@ -35,7 +34,6 @@ public sealed partial class MindContainerComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("ghostOnShutdown")] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends public bool GhostOnShutdown { get; set; } = true; } diff --git a/Content.Shared/Mind/MindComponent.cs b/Content.Shared/Mind/MindComponent.cs index d603102682b..a0812be8f74 100644 --- a/Content.Shared/Mind/MindComponent.cs +++ b/Content.Shared/Mind/MindComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Actions; using Content.Shared.GameTicking; using Content.Shared.Mind.Components; using Robust.Shared.GameStates; @@ -87,17 +86,21 @@ public sealed partial class MindComponent : Component /// /// Prevents user from ghosting out /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventGhosting")] + [DataField] public bool PreventGhosting { get; set; } /// /// Prevents user from suiciding /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventSuicide")] + [DataField] public bool PreventSuicide { get; set; } + /// + /// Mind Role Entities belonging to this Mind + /// + [DataField, AutoNetworkedField] + public List MindRoles = new List(); + /// /// The session of the player owning this mind. /// Can be null, in which case the player is currently not logged in. diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 162bca495ca..bf0b5f650ad 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -483,19 +483,6 @@ public bool TryGetMind( return false; } - /// - /// Gets a role component from a player's mind. - /// - /// Whether a role was found - public bool TryGetRole(EntityUid user, [NotNullWhen(true)] out T? role) where T : IComponent - { - role = default; - if (!TryComp(user, out var mindContainer) || mindContainer.Mind == null) - return false; - - return TryComp(mindContainer.Mind, out role); - } - /// /// Sets the Mind's UserId, Session, and updates the player's PlayerData. This should have no direct effect on the /// entity that any mind is connected to, except as a side effect of the fact that it may change a player's diff --git a/Content.Shared/NPC/Systems/NpcFactionSystem.cs b/Content.Shared/NPC/Systems/NpcFactionSystem.cs index 5e9dbbbf6d4..0d684de80b9 100644 --- a/Content.Shared/NPC/Systems/NpcFactionSystem.cs +++ b/Content.Shared/NPC/Systems/NpcFactionSystem.cs @@ -8,7 +8,6 @@ namespace Content.Shared.NPC.Systems; /// /// Outlines faction relationships with each other. -/// part of psionics rework was making this a partial class. Should've already been handled upstream, based on the linter. /// public sealed partial class NpcFactionSystem : EntitySystem { @@ -82,6 +81,24 @@ public bool IsMember(Entity ent, string faction) return ent.Comp.Factions.Contains(faction); } + /// + /// Returns whether an entity is a member of any listed faction. + /// If the list is empty this returns false. + /// + public bool IsMemberOfAny(Entity ent, IEnumerable> factions) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + foreach (var faction in factions) + { + if (ent.Comp.Factions.Contains(faction)) + return true; + } + + return false; + } + /// /// Adds this entity to the particular faction. /// diff --git a/Content.Shared/Pinpointer/NavMapComponent.cs b/Content.Shared/Pinpointer/NavMapComponent.cs index d77169d32ed..b876cb20fe2 100644 --- a/Content.Shared/Pinpointer/NavMapComponent.cs +++ b/Content.Shared/Pinpointer/NavMapComponent.cs @@ -27,6 +27,50 @@ public sealed partial class NavMapComponent : Component /// [ViewVariables] public Dictionary Beacons = new(); + + /// + /// Describes the properties of a region on the station. + /// It is indexed by the entity assigned as the region owner. + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary RegionProperties = new(); + + /// + /// All flood filled regions, ready for display on a NavMapControl. + /// It is indexed by the entity assigned as the region owner. + /// + /// + /// For client use only + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary RegionOverlays = new(); + + /// + /// A queue of all region owners that are waiting their associated regions to be floodfilled. + /// + /// + /// For client use only + /// + [ViewVariables(VVAccess.ReadOnly)] + public Queue QueuedRegionsToFlood = new(); + + /// + /// A look up table to get a list of region owners associated with a flood filled chunk. + /// + /// + /// For client use only + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary> ChunkToRegionOwnerTable = new(); + + /// + /// A look up table to find flood filled chunks associated with a given region owner. + /// + /// + /// For client use only + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary> RegionOwnerToChunkTable = new(); } [Serializable, NetSerializable] @@ -51,10 +95,30 @@ public sealed class NavMapChunk(Vector2i origin) public GameTick LastUpdate; } +[Serializable, NetSerializable] +public sealed class NavMapRegionOverlay(Enum uiKey, List<(Vector2i, Vector2i)> gridCoords) +{ + /// + /// The key to the UI that will be displaying this region on its navmap + /// + public Enum UiKey = uiKey; + + /// + /// The local grid coordinates of the rectangles that make up the region + /// Item1 is the top left corner, Item2 is the bottom right corner + /// + public List<(Vector2i, Vector2i)> GridCoords = gridCoords; + + /// + /// Color of the region + /// + public Color Color = Color.White; +} + public enum NavMapChunkType : byte { // Values represent bit shift offsets when retrieving data in the tile array. - Invalid = byte.MaxValue, + Invalid = byte.MaxValue, Floor = 0, // I believe floors have directional information for diagonal tiles? Wall = SharedNavMapSystem.Directions, Airlock = 2 * SharedNavMapSystem.Directions, diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 3ced5f3c9ed..37d60dec28a 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -3,10 +3,9 @@ using System.Runtime.CompilerServices; using Content.Shared.Tag; using Robust.Shared.GameStates; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Pinpointer; @@ -16,7 +15,7 @@ public abstract class SharedNavMapSystem : EntitySystem public const int Directions = 4; // Not directly tied to number of atmos directions public const int ChunkSize = 8; - public const int ArraySize = ChunkSize* ChunkSize; + public const int ArraySize = ChunkSize * ChunkSize; public const int AllDirMask = (1 << Directions) - 1; public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock; @@ -24,6 +23,7 @@ public abstract class SharedNavMapSystem : EntitySystem public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor; [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!; + [Robust.Shared.IoC.Dependency] private readonly INetManager _net = default!; private static readonly ProtoId[] WallTags = {"Wall", "Window"}; private EntityQuery _doorQuery; @@ -57,7 +57,7 @@ public static Vector2i GetTileFromIndex(int index) public NavMapChunkType GetEntityType(EntityUid uid) { if (_doorQuery.HasComp(uid)) - return NavMapChunkType.Airlock; + return NavMapChunkType.Airlock; if (_tagSystem.HasAnyTag(uid, WallTags)) return NavMapChunkType.Wall; @@ -81,6 +81,57 @@ protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent co return true; } + public void AddOrUpdateNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner, NavMapRegionProperties regionProperties) + { + // Check if a new region has been added or an existing one has been altered + var isDirty = !component.RegionProperties.TryGetValue(regionOwner, out var oldProperties) || oldProperties != regionProperties; + + if (isDirty) + { + component.RegionProperties[regionOwner] = regionProperties; + + if (_net.IsServer) + Dirty(uid, component); + } + } + + public void RemoveNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner) + { + bool regionOwnerRemoved = component.RegionProperties.Remove(regionOwner) | component.RegionOverlays.Remove(regionOwner); + + if (regionOwnerRemoved) + { + if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var affectedChunks)) + { + foreach (var affectedChunk in affectedChunks) + { + if (component.ChunkToRegionOwnerTable.TryGetValue(affectedChunk, out var regionOwners)) + regionOwners.Remove(regionOwner); + } + + component.RegionOwnerToChunkTable.Remove(regionOwner); + } + + if (_net.IsServer) + Dirty(uid, component); + } + } + + public Dictionary GetNavMapRegionOverlays(EntityUid uid, NavMapComponent component, Enum uiKey) + { + var regionOverlays = new Dictionary(); + + foreach (var (regionOwner, regionOverlay) in component.RegionOverlays) + { + if (!regionOverlay.UiKey.Equals(uiKey)) + continue; + + regionOverlays.Add(regionOwner, regionOverlay); + } + + return regionOverlays; + } + #region: Event handling private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) @@ -97,7 +148,7 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG chunks.Add(origin, chunk.TileData); } - args.State = new NavMapState(chunks, component.Beacons); + args.State = new NavMapState(chunks, component.Beacons, component.RegionProperties); return; } @@ -110,7 +161,7 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG chunks.Add(origin, chunk.TileData); } - args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); + args.State = new NavMapDeltaState(chunks, component.Beacons, component.RegionProperties, new(component.Chunks.Keys)); } #endregion @@ -120,22 +171,26 @@ private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentG [Serializable, NetSerializable] protected sealed class NavMapState( Dictionary chunks, - Dictionary beacons) + Dictionary beacons, + Dictionary regions) : ComponentState { public Dictionary Chunks = chunks; public Dictionary Beacons = beacons; + public Dictionary Regions = regions; } [Serializable, NetSerializable] protected sealed class NavMapDeltaState( Dictionary modifiedChunks, Dictionary beacons, + Dictionary regions, HashSet allChunks) : ComponentState, IComponentDeltaState { public Dictionary ModifiedChunks = modifiedChunks; public Dictionary Beacons = beacons; + public Dictionary Regions = regions; public HashSet AllChunks = allChunks; public void ApplyToFullState(NavMapState state) @@ -159,11 +214,18 @@ public void ApplyToFullState(NavMapState state) { state.Beacons.Add(nuid, beacon); } + + state.Regions.Clear(); + foreach (var (nuid, region) in Regions) + { + state.Regions.Add(nuid, region); + } } public NavMapState CreateNewFullState(NavMapState state) { var chunks = new Dictionary(state.Chunks.Count); + foreach (var (index, data) in state.Chunks) { if (!AllChunks!.Contains(index)) @@ -177,12 +239,25 @@ public NavMapState CreateNewFullState(NavMapState state) Array.Copy(newData, data, ArraySize); } - return new NavMapState(chunks, new(Beacons)); + return new NavMapState(chunks, new(Beacons), new(Regions)); } } [Serializable, NetSerializable] public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position); + [Serializable, NetSerializable] + public record struct NavMapRegionProperties(NetEntity Owner, Enum UiKey, HashSet Seeds) + { + // Server defined color for the region + public Color Color = Color.White; + + // The maximum number of tiles that can be assigned to this region + public int MaxArea = 625; + + // The maximum distance this region can propagate from its seeds + public int MaxRadius = 25; + } + #endregion } diff --git a/Content.Shared/Power/Generator/ActiveGeneratorRevvingComponent.cs b/Content.Shared/Power/Generator/ActiveGeneratorRevvingComponent.cs index 25f97dc15a0..fde0319a37d 100644 --- a/Content.Shared/Power/Generator/ActiveGeneratorRevvingComponent.cs +++ b/Content.Shared/Power/Generator/ActiveGeneratorRevvingComponent.cs @@ -3,7 +3,7 @@ namespace Content.Shared.Power.Generator; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class ActiveGeneratorRevvingComponent: Component +public sealed partial class ActiveGeneratorRevvingComponent : Component { [DataField, ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] public TimeSpan CurrentTime = TimeSpan.Zero; diff --git a/Content.Shared/Power/Generator/ActiveGeneratorRevvingSystem.cs b/Content.Shared/Power/Generator/ActiveGeneratorRevvingSystem.cs index 9cd11ae6045..459b2fd78de 100644 --- a/Content.Shared/Power/Generator/ActiveGeneratorRevvingSystem.cs +++ b/Content.Shared/Power/Generator/ActiveGeneratorRevvingSystem.cs @@ -1,6 +1,6 @@ namespace Content.Shared.Power.Generator; -public sealed class ActiveGeneratorRevvingSystem: EntitySystem +public sealed class ActiveGeneratorRevvingSystem : EntitySystem { public override void Initialize() { @@ -25,7 +25,7 @@ private void OnAnchorStateChanged(EntityUid uid, ActiveGeneratorRevvingComponent /// ActiveGeneratorRevvingComponent of the generator entity. public void StartAutoRevving(EntityUid uid, ActiveGeneratorRevvingComponent? component = null) { - if (Resolve(uid, ref component)) + if (Resolve(uid, ref component, false)) { // reset the revving component.CurrentTime = TimeSpan.FromSeconds(0); diff --git a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs index 008b7c2ced4..e4125945d26 100644 --- a/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs +++ b/Content.Shared/Projectiles/EmbeddableProjectileComponent.cs @@ -13,37 +13,43 @@ public sealed partial class EmbeddableProjectileComponent : Component /// /// Minimum speed of the projectile to embed. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float MinimumSpeed = 5f; /// /// Delete the entity on embedded removal? /// Does nothing if there's no RemovalTime. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool DeleteOnRemove; /// /// How long it takes to remove the embedded object. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public float? RemovalTime = 3f; /// /// Whether this entity will embed when thrown, or only when shot as a projectile. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public bool EmbedOnThrow = true; /// /// How far into the entity should we offset (0 is wherever we collided). /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public Vector2 Offset = Vector2.Zero; /// /// Sound to play after embedding into a hit target. /// - [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField] + [DataField, AutoNetworkedField] public SoundSpecifier? Sound; + + /// + /// Uid of the entity the projectile is embed into. + /// + [DataField, AutoNetworkedField] + public EntityUid? EmbeddedIntoUid; } diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 1b7d6d6f991..85e75d6d291 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -71,6 +71,8 @@ private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent componen TryComp(uid, out var physics); _physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform); _transform.AttachToGridOrMap(uid, xform); + component.EmbeddedIntoUid = null; + Dirty(uid, component); // Reset whether the projectile has damaged anything if it successfully was removed if (TryComp(uid, out var projectile)) @@ -127,8 +129,10 @@ private void Embed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableP } _audio.PlayPredicted(component.Sound, uid, null); + component.EmbeddedIntoUid = target; var ev = new EmbedEvent(user, target); RaiseLocalEvent(uid, ref ev); + Dirty(uid, component); } private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args) diff --git a/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs b/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs index 8c5baf93f5d..67af4cc900a 100644 --- a/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs +++ b/Content.Shared/Radio/EntitySystems/SharedJammerSystem.cs @@ -42,10 +42,12 @@ private void OnGetVerb(Entity entity, ref GetVerbsEvent))] - public string? PrototypeId; -} - -/// -/// Mark the antagonist role component as being exclusive -/// IE by default other antagonists should refuse to select the same entity for a different antag role -/// -[AttributeUsage(AttributeTargets.Class, Inherited = false)] -[BaseTypeRequired(typeof(AntagonistRoleComponent))] -public sealed partial class ExclusiveAntagonistAttribute : Attribute -{ -} diff --git a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs index 3ef36c89791..bbc377de048 100644 --- a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs @@ -31,7 +31,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) @@ -39,7 +39,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 6d045d80344..86a64d10989 100644 --- a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Localizations; using Content.Shared.Preferences; using JetBrains.Annotations; using Robust.Shared.Prototypes; @@ -15,7 +16,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. @@ -48,7 +49,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 = ContentLocalizationManager.FormatPlaytime(deptDiffSpan); var nameDepartment = "role-timer-department-unknown"; if (protoManager.TryIndex(Department, out var departmentIndexed)) @@ -63,7 +66,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; @@ -73,7 +76,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 62b3ecd1244..d08c3176547 100644 --- a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Localizations; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Preferences; using JetBrains.Annotations; @@ -26,7 +27,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 = ContentLocalizationManager.FormatPlaytime(overallDiffSpan); if (!Inverted) { @@ -35,14 +38,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 4529f93bee8..6fd4c0b564b 100644 --- a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared.Localizations; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Preferences; using Content.Shared.Roles.Jobs; @@ -17,7 +18,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)] @@ -35,7 +36,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 = ContentLocalizationManager.FormatPlaytime(roleDiffSpan); var departmentColor = Color.Yellow; if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem)) @@ -53,7 +56,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; @@ -63,7 +66,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/JobComponent.cs b/Content.Shared/Roles/Jobs/JobComponent.cs deleted file mode 100644 index b7df34cb9f5..00000000000 --- a/Content.Shared/Roles/Jobs/JobComponent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Content.Shared.Chat.V2.Repository; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Roles.Jobs; - -/// -/// Added to mind entities to hold the data for the player's current job. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class JobComponent : Component -{ - [DataField(required: true), AutoNetworkedField] - public ProtoId? Prototype; -} diff --git a/Content.Shared/Roles/Jobs/JobRoleComponent.cs b/Content.Shared/Roles/Jobs/JobRoleComponent.cs new file mode 100644 index 00000000000..dbaf12beec4 --- /dev/null +++ b/Content.Shared/Roles/Jobs/JobRoleComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Jobs; + +/// +/// Added to mind role entities to mark them as a job role entity. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class JobRoleComponent : BaseMindRoleComponent +{ + +} diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 939a57baadf..8a4733c8340 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -13,8 +13,10 @@ namespace Content.Shared.Roles.Jobs; /// public abstract class SharedJobSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedPlayerSystem _playerSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + private readonly Dictionary _inverseTrackerLookup = new(); public override void Initialize() @@ -100,32 +102,41 @@ public bool TryGetPrimaryDepartment(string jobProto, [NotNullWhen(true)] out Dep public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { - return CompOrNull(mindId)?.Prototype == prototypeId; + + if (mindId is null) + return false; + + _roles.MindHasRole(mindId.Value, out var role); + + if (role is null) + return false; + + return role.Value.Comp1.JobPrototype == prototypeId; } public bool MindTryGetJob( [NotNullWhen(true)] EntityUid? mindId, - [NotNullWhen(true)] out JobComponent? comp, [NotNullWhen(true)] out JobPrototype? prototype) { - comp = null; prototype = null; + MindTryGetJobId(mindId, out var protoId); - return TryComp(mindId, out comp) && - comp.Prototype != null && - _prototypes.TryIndex(comp.Prototype, out prototype); + return _prototypes.TryIndex(protoId, out prototype) || prototype is not null; } - public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId? job) + public bool MindTryGetJobId( + [NotNullWhen(true)] EntityUid? mindId, + out ProtoId? job) { - if (!TryComp(mindId, out JobComponent? comp)) - { - job = null; + job = null; + + if (mindId is null) return false; - } - job = comp.Prototype; - return true; + if (_roles.MindHasRole(mindId.Value, out var role)) + job = role.Value.Comp1.JobPrototype; + + return job is not null; } /// @@ -134,7 +145,7 @@ public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name) { - if (MindTryGetJob(mindId, out _, out var prototype)) + if (MindTryGetJob(mindId, out var prototype)) { name = prototype.LocalizedName; return true; @@ -161,7 +172,7 @@ public bool CanBeAntag(ICommonSession player) if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) return true; - if (!MindTryGetJob(mindId, out _, out var prototype)) + if (!MindTryGetJob(mindId, out var prototype)) return true; return prototype.CanBeAntag; diff --git a/Content.Shared/Roles/MindGetAllRolesEvent.cs b/Content.Shared/Roles/MindGetAllRoleInfoEvent.cs similarity index 79% rename from Content.Shared/Roles/MindGetAllRolesEvent.cs rename to Content.Shared/Roles/MindGetAllRoleInfoEvent.cs index 69878739084..a2f2820b5c3 100644 --- a/Content.Shared/Roles/MindGetAllRolesEvent.cs +++ b/Content.Shared/Roles/MindGetAllRoleInfoEvent.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Roles; /// /// The list of roles on the player. [ByRefEvent] -public readonly record struct MindGetAllRolesEvent(List Roles); +public readonly record struct MindGetAllRoleInfoEvent(List Roles); /// /// Returned by to give some information about a player's role. @@ -17,4 +17,4 @@ namespace Content.Shared.Roles; /// Whether or not this role makes this player an antagonist. /// The id associated with the role. /// The prototype ID of the role -public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype); +public readonly record struct RoleInfo(string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype); diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/MindRoleComponent.cs new file mode 100644 index 00000000000..a3dd0b3bc6d --- /dev/null +++ b/Content.Shared/Roles/MindRoleComponent.cs @@ -0,0 +1,50 @@ +using Content.Shared.Mind; +using JetBrains.Annotations; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Roles; + +/// +/// This holds data for, and indicates, a Mind Role entity +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class MindRoleComponent : BaseMindRoleComponent +{ + /// + /// Marks this Mind Role as Antagonist + /// A single antag Mind Role is enough to make the owner mind count as Antagonist. + /// + [DataField] + public bool Antag { get; set; } = false; + + /// + /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. + /// + [DataField] + public bool ExclusiveAntag { get; set; } = false; + + /// + /// The Mind that this role belongs to + /// + public Entity Mind { get; set; } + + /// + /// The Antagonist prototype of this role + /// + [DataField] + public ProtoId? AntagPrototype { get; set; } + + /// + /// The Job prototype of this role + /// + [DataField] + 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 90e2025cb97..00271693abe 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,36 +1,33 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; -using Content.Shared.Ghost.Roles; +using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; +using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Roles; -public abstract partial class SharedRoleSystem : EntitySystem // DeltaV - make it partial +public abstract class SharedRoleSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - - // TODO please lord make role entities - private readonly HashSet _antagTypes = new(); + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly SharedGameTicker _gameTicker = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; private JobRequirementOverridePrototype? _requirementOverride; public override void Initialize() { - // TODO make roles entities - SubscribeLocalEvent(OnJobGetAllRoles); Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); - InitializeDeltaV(); // DeltaV } private void SetRequirementOverride(string value) @@ -45,62 +42,118 @@ private void SetRequirementOverride(string value) Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } - private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) + /// + /// Adds multiple mind roles to a mind + /// + /// The mind entity to add the role to + /// The list of mind roles to add + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + public void MindAddRoles(EntityUid mindId, + List>? roles, + MindComponent? mind = null, + bool silent = false) { - var name = "game-ticker-unknown-role"; - var prototype = ""; - string? playTimeTracker = null; - if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job)) + if (roles is null || roles.Count == 0) + return; + + foreach (var proto in roles) { - name = job.Name; - prototype = job.ID; - playTimeTracker = job.PlayTimeTracker; + MindAddRole(mindId, proto, mind, silent); } + } - name = Loc.GetString(name); - - args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype)); + /// + /// Adds a mind role to a mind + /// + /// The mind entity to add the role to + /// The mind role to add + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + public void MindAddRole(EntityUid mindId, + ProtoId protoId, + MindComponent? mind = null, + bool silent = false) + { + if (protoId == "MindRoleJob") + MindAddJobRole(mindId, mind, silent, ""); + else + MindAddRoleDo(mindId, protoId, mind, silent); } - protected void SubscribeAntagEvents() where T : AntagonistRoleComponent + /// + /// Adds a Job mind role with the specified job prototype + /// + /// /// The mind entity to add the job role to + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + /// The Job prototype for the new role + public void MindAddJobRole(EntityUid mindId, + MindComponent? mind = null, + bool silent = false, + string? jobPrototype = null) { - SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) => - { - var name = "game-ticker-unknown-role"; - var prototype = ""; - if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag)) - { - name = antag.Name; - prototype = antag.ID; - } - name = Loc.GetString(name); + if (!Resolve(mindId, ref mind)) + return; - args.Roles.Add(new RoleInfo(component, name, true, null, prototype)); - }); + // Can't have someone get paid for two jobs now, can we + if (MindHasRole((mindId, mind), out var jobRole) + && jobRole.Value.Comp1.JobPrototype != jobPrototype) + { + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'"); - SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute(out _); }); - _antagTypes.Add(typeof(T)); + jobRole.Value.Comp1.JobPrototype = jobPrototype; + } + else + MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype); } - public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false) + /// + /// Creates a Mind Role + /// + private void MindAddRoleDo(EntityUid mindId, + ProtoId protoId, + MindComponent? mind = null, + bool silent = false, + string? jobPrototype = null) { if (!Resolve(mindId, ref mind)) + { + Log.Error($"Failed to add role {protoId} to mind {mindId} : Mind does not match provided mind component"); return; + } - EntityManager.AddComponents(mindId, components); var antagonist = false; - foreach (var compReg in components.Values) + + if (!_prototypes.TryIndex(protoId, out var protoEnt)) { - var compType = compReg.Component.GetType(); + Log.Error($"Failed to add role {protoId} to mind {mindId} : Role prototype does not exist"); + return; + } - var comp = EntityManager.ComponentFactory.GetComponent(compType); - if (IsAntagonistRole(comp.GetType())) - { - antagonist = true; - break; - } + //TODO don't let a prototype being added a second time + //If that was somehow to occur, a second mindrole for that comp would be created + //Meaning any mind role checks could return wrong results, since they just return the first match they find + + var mindRoleId = Spawn(protoId, MapCoordinates.Nullspace); + EnsureComp(mindRoleId); + var mindRoleComp = Comp(mindRoleId); + + mindRoleComp.Mind = (mindId,mind); + if (jobPrototype is not null) + { + mindRoleComp.JobPrototype = jobPrototype; + EnsureComp(mindRoleId); + DebugTools.AssertNull(mindRoleComp.AntagPrototype); + DebugTools.Assert(!mindRoleComp.Antag); + DebugTools.Assert(!mindRoleComp.ExclusiveAntag); } + antagonist |= mindRoleComp.Antag; + mind.MindRoles.Add(mindRoleId); + var mindEv = new MindRoleAddedEvent(silent); RaiseLocalEvent(mindId, ref mindEv); @@ -110,157 +163,320 @@ public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindCom RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}"); + var name = Loc.GetString(protoEnt.Name); + if (mind.OwnedEntity is not null) + { + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"{name} added to mind of {ToPrettyString(mind.OwnedEntity)}"); + } + else + { + //TODO: This is not tied to the player on the Admin Log filters. + //Probably only happens when Job Role is added on initial spawn, before the mind entity is put in a mob + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"{name} added to {ToPrettyString(mindId)}"); + } } - public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false) + /// + /// Removes all instances of a specific role from this mind. + /// + /// The mind to remove the role from. + /// The type of the role to remove. + /// Returns false if the role did not exist. True if successful> + public bool MindRemoveRole(Entity mind) where T : IComponent { - if (!Resolve(mindId, ref mind)) - return; + if (typeof(T) == typeof(MindRoleComponent)) + throw new InvalidOperationException(); + + if (!Resolve(mind.Owner, ref mind.Comp)) + return false; - if (HasComp(mindId, component.GetType())) + var found = false; + var antagonist = false; + var delete = new List(); + foreach (var role in mind.Comp.MindRoles) { - throw new ArgumentException($"We already have this role: {component}"); + if (!HasComp(role)) + continue; + + 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; } - EntityManager.AddComponent(mindId, component); - var antagonist = IsAntagonistRole(component.GetType()); + if (!found) + return false; - var mindEv = new MindRoleAddedEvent(silent); - RaiseLocalEvent(mindId, ref mindEv); + foreach (var role in delete) + { + mind.Comp.MindRoles.Remove(role); + } - var message = new RoleAddedEvent(mindId, mind, antagonist, silent); - if (mind.OwnedEntity != null) + if (mind.Comp.OwnedEntity != null) { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + var message = new RoleRemovedEvent(mind.Owner, mind.Comp, antagonist); + RaiseLocalEvent(mind.Comp.OwnedEntity.Value, message, true); } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"All roles of type '{typeof(T).Name}' removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}"); + + return true; } /// - /// Gives this mind a new role. + /// Finds and removes all mind roles of a specific type /// - /// The mind to add the role to. - /// The role instance to add. - /// The role type to add. - /// Whether or not the role should be added silently - /// The instance of the role. - /// - /// Thrown if we already have a role with this type. - /// - public void MindAddRole(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : IComponent, new() + /// The mind entity + /// The type of the role to remove. + /// True if the role existed and was removed + public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent { - if (!Resolve(mindId, ref mind)) - return; + if (typeof(T) == typeof(MindRoleComponent)) + return false; - if (HasComp(mindId)) - { - throw new ArgumentException($"We already have this role: {typeof(T)}"); - } + if (MindRemoveRole(mindId)) + return true; - AddComp(mindId, component); - var antagonist = IsAntagonistRole(); + Log.Warning($"Failed to remove role {typeof(T)} from {ToPrettyString(mindId)} : mind does not have role "); + return false; + } - var mindEv = new MindRoleAddedEvent(silent); - RaiseLocalEvent(mindId, ref mindEv); + /// + /// Finds the first mind role of a specific T type on a mind entity. + /// Outputs entity components for the mind role's MindRoleComponent and for T + /// + /// The mind entity + /// The type of the role to find. + /// The Mind Role entity component + /// The Mind Role's entity component for T + /// True if the role is found + public bool MindHasRole(Entity mind, + [NotNullWhen(true)] out Entity? role) where T : IComponent + { + role = null; + if (!Resolve(mind.Owner, ref mind.Comp)) + return false; - var message = new RoleAddedEvent(mindId, mind, antagonist, silent); - if (mind.OwnedEntity != null) + foreach (var roleEnt in mind.Comp.MindRoles) { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + if (!TryComp(roleEnt, out T? tcomp)) + continue; + + 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; } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {typeof(T).Name}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + return false; } /// - /// Removes a role from this mind. + /// 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 to remove the role from. - /// The type of the role to remove. - /// - /// Thrown if we do not have this role. - /// - public void MindRemoveRole(EntityUid mindId) where T : IComponent + /// The mind entity + /// The Type to look for + /// The output role + /// True if the role is found + public bool MindHasRole(EntityUid mindId, + Type type, + [NotNullWhen(true)] out Entity? role) { - if (!RemComp(mindId)) + role = null; + // All MindRoles have this component, it would just return the first one. + // Order might not be what is expected. + // Better to report null + if (type == Type.GetType("MindRoleComponent")) { - throw new ArgumentException($"We do not have this role: {typeof(T)}"); + Log.Error($"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role."); + return false; } - var mind = Comp(mindId); - var antagonist = IsAntagonistRole(); - var message = new RoleRemovedEvent(mindId, mind, antagonist); + if (!TryComp(mindId, out var mind)) + return false; - if (mind.OwnedEntity != null) + var found = false; + + foreach (var roleEnt in mind.MindRoles) { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + if (!HasComp(roleEnt, type)) + continue; + + 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; } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {typeof(T).Name}' removed from mind of {_minds.MindOwnerLoggingString(mind)}"); + + return found; } - public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent + /// + /// Finds the first mind role of a specific type on a mind entity. + /// + /// The mind entity + /// The type of the role to find. + /// True if the role is found + public bool MindHasRole(EntityUid mindId) where T : IComponent { - if (!MindHasRole(mindId)) - return false; - - MindRemoveRole(mindId); - return true; + return MindHasRole(mindId, out _); } - public bool MindHasRole(EntityUid mindId) where T : IComponent + //TODO: Delete this later + /// + /// Returns the first mind role of a specific type + /// + /// The mind entity + /// Entity Component of the mind role + [Obsolete("Use MindHasRole's output value")] + public Entity? MindGetRole(EntityUid mindId) where T : IComponent { - DebugTools.Assert(HasComp(mindId)); - return HasComp(mindId); + Entity? result = null; + + var mind = Comp(mindId); + + foreach (var uid in mind.MindRoles) + { + if (HasComp(uid) && TryComp(uid, out var comp)) + result = (uid,comp); + } + return result; } - public List MindGetAllRoles(EntityUid mindId) + /// + /// Reads all Roles of a mind Entity and returns their data as RoleInfo + /// + /// The mind entity + /// RoleInfo list + public List MindGetAllRoleInfo(Entity mind) { - DebugTools.Assert(HasComp(mindId)); - var ev = new MindGetAllRolesEvent(new List()); - RaiseLocalEvent(mindId, ref ev); - return ev.Roles; + var roleInfo = new List(); + + if (!Resolve(mind.Owner, ref mind.Comp)) + return roleInfo; + + foreach (var role in mind.Comp.MindRoles) + { + var valid = false; + var name = "game-ticker-unknown-role"; + var prototype = ""; + string? playTimeTracker = null; + + if (!TryComp(role, out MindRoleComponent? comp)) + { + 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; + if (_prototypes.TryIndex(comp.JobPrototype, out var job)) + { + playTimeTracker = job.PlayTimeTracker; + name = job.Name; + valid = true; + } + else + { + Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Job prototype: '{comp.JobPrototype}'"); + } + } + else if (comp.AntagPrototype is not null && comp.JobPrototype is null) + { + prototype = comp.AntagPrototype; + if (_prototypes.TryIndex(comp.AntagPrototype, out var antag)) + { + name = antag.Name; + valid = true; + } + else + { + Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Antagonist prototype: '{comp.AntagPrototype}'"); + } + } + else if (comp.JobPrototype is not null && comp.AntagPrototype is not null) + { + Log.Error($" Mind Role Prototype '{role.Id}' contains both Job and Antagonist prototypes"); + } + + if (valid) + roleInfo.Add(new RoleInfo(name, comp.Antag, playTimeTracker, prototype)); + } + return roleInfo; } + /// + /// Does this mind possess an antagonist role + /// + /// The mind entity + /// True if the mind possesses any antag roles public bool MindIsAntagonist(EntityUid? mindId) { - if (mindId == null) + if (mindId is null) return false; - DebugTools.Assert(HasComp(mindId)); - var ev = new MindIsAntagonistEvent(); - RaiseLocalEvent(mindId.Value, ref ev); - return ev.IsAntagonist; + return CheckAntagonistStatus(mindId.Value).Antag; } /// /// Does this mind possess an exclusive antagonist role /// /// The mind entity - /// True if the mind possesses an exclusive antag role + /// True if the mind possesses any exclusive antag roles public bool MindIsExclusiveAntagonist(EntityUid? mindId) { - if (mindId == null) + if (mindId is null) return false; - var ev = new MindIsAntagonistEvent(); - RaiseLocalEvent(mindId.Value, ref ev); - return ev.IsExclusiveAntagonist; + return CheckAntagonistStatus(mindId.Value).ExclusiveAntag; } - public bool IsAntagonistRole() - { - return _antagTypes.Contains(typeof(T)); - } + public (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity mind) + { + if (!Resolve(mind.Owner, ref mind.Comp)) + return (false, false); - public bool IsAntagonistRole(Type component) - { - return _antagTypes.Contains(component); + var antagonist = false; + var exclusiveAntag = false; + foreach (var role in mind.Comp.MindRoles) + { + if (!TryComp(role, out var roleComp)) + { + Log.Error($"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|"); + continue; + } + + antagonist |= roleComp.Antag; + exclusiveAntag |= roleComp.ExclusiveAntag; + } + + return (antagonist, exclusiveAntag); } /// @@ -273,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)) @@ -281,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)) @@ -289,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)) @@ -297,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/Content.Shared/Silicons/Borgs/Components/BorgModuleIconComponent.cs b/Content.Shared/Silicons/Borgs/Components/BorgModuleIconComponent.cs new file mode 100644 index 00000000000..ff38a40f487 --- /dev/null +++ b/Content.Shared/Silicons/Borgs/Components/BorgModuleIconComponent.cs @@ -0,0 +1,20 @@ +//using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Utility; + +namespace Content.Shared.Silicons.Borgs.Components; + +/// +/// This is used to override the action icon for cyborg actions. +/// Without this component the no-action state will be used. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class BorgModuleIconComponent : Component +{ + /// + /// The action icon for this module + /// + [DataField] + public SpriteSpecifier.Rsi Icon = default!; + +} \ No newline at end of file diff --git a/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs b/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs index 4800aa0c59d..1c54938b8a4 100644 --- a/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs +++ b/Content.Shared/Silicons/Laws/Components/SiliconLawProviderComponent.cs @@ -1,4 +1,5 @@ using Robust.Shared.Prototypes; +using Robust.Shared.Audio; namespace Content.Shared.Silicons.Laws.Components; @@ -20,4 +21,12 @@ public sealed partial class SiliconLawProviderComponent : Component /// [DataField, ViewVariables(VVAccess.ReadWrite)] public SiliconLawset? Lawset; + + /// + /// The sound that plays for the Silicon player + /// when the particular lawboard has been inserted. + /// + [DataField] + public SoundSpecifier? LawUploadSound = new SoundPathSpecifier("/Audio/Misc/cryo_warning.ogg"); + } diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index baef62c3da9..7eef20cebd8 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -285,6 +285,8 @@ private void OnAiMapInit(Entity ent, ref MapInitEvent ar private bool SetupEye(Entity ent) { + if (_net.IsClient) + return false; if (ent.Comp.RemoteEntity != null) return false; @@ -299,8 +301,11 @@ private bool SetupEye(Entity ent) private void ClearEye(Entity ent) { + if (_net.IsClient) + return; QueueDel(ent.Comp.RemoteEntity); ent.Comp.RemoteEntity = null; + Dirty(ent); } private void AttachEye(Entity ent) @@ -330,6 +335,8 @@ private void OnAiInsert(Entity ent, ref EntInsertedIntoC if (_timing.ApplyingState) return; + SetupEye(ent); + // Just so text and the likes works properly _metadata.SetEntityName(ent.Owner, MetaData(args.Entity).EntityName); @@ -351,6 +358,7 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo { _eye.SetTarget(args.Entity, null, eyeComp); } + ClearEye(ent); } private void UpdateAppearance(Entity entity) diff --git a/Content.Shared/Sound/Components/EmitSoundOnUIOpenComponent.cs b/Content.Shared/Sound/Components/EmitSoundOnUIOpenComponent.cs index a979a6ec50e..65848cb5e57 100644 --- a/Content.Shared/Sound/Components/EmitSoundOnUIOpenComponent.cs +++ b/Content.Shared/Sound/Components/EmitSoundOnUIOpenComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Whitelist; using Robust.Shared.GameStates; namespace Content.Shared.Sound.Components; @@ -8,4 +9,9 @@ namespace Content.Shared.Sound.Components; [RegisterComponent, NetworkedComponent] public sealed partial class EmitSoundOnUIOpenComponent : BaseEmitSoundComponent { + /// + /// Blacklist for making the sound not play if certain entities open the UI + /// + [DataField] + public EntityWhitelist Blacklist = new(); } diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 8040910dc3c..3e051fff317 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -58,7 +58,10 @@ public override void Initialize() private void HandleEmitSoundOnUIOpen(EntityUid uid, EmitSoundOnUIOpenComponent component, AfterActivatableUIOpenEvent args) { - TryEmitSound(uid, component, args.User); + if (_whitelistSystem.IsBlacklistFail(component.Blacklist, args.User)) + { + TryEmitSound(uid, component, args.User); + } } private void OnMobState(Entity entity, ref MobStateChangedEvent args) diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 102c967a223..03bfd3e3663 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -150,6 +150,7 @@ public void EquipStartingGear(EntityUid entity, IEquipmentLoadout? startingGear, foreach (var (slot, entProtos) in startingGear.Storage) { + ents.Clear(); if (entProtos.Count == 0) continue; diff --git a/Content.Shared/Storage/Components/SecretStashComponent.cs b/Content.Shared/Storage/Components/SecretStashComponent.cs index 3bf8e2e871b..f8fff4c1949 100644 --- a/Content.Shared/Storage/Components/SecretStashComponent.cs +++ b/Content.Shared/Storage/Components/SecretStashComponent.cs @@ -8,6 +8,7 @@ using Content.Shared.DoAfter; using Robust.Shared.Serialization; using Robust.Shared.Audio; +using Content.Shared.Whitelist; namespace Content.Shared.Storage.Components { @@ -26,6 +27,12 @@ public sealed partial class SecretStashComponent : Component [DataField("maxItemSize")] public ProtoId MaxItemSize = "Small"; + /// + /// Entity blacklist for secret stashes. + /// + [DataField] + public EntityWhitelist? Blacklist; + /// /// This sound will be played when you try to insert an item in the stash. /// The sound will be played whether or not the item is actually inserted. diff --git a/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs b/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs index 901d744df5f..af9b768e98b 100644 --- a/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs @@ -13,6 +13,9 @@ using Content.Shared.Verbs; using Content.Shared.IdentityManagement; using Content.Shared.Tools.EntitySystems; +using Content.Shared.Whitelist; +using Content.Shared.Materials; +using Robust.Shared.Map; namespace Content.Shared.Storage.EntitySystems; @@ -27,13 +30,14 @@ public sealed class SecretStashSystem : EntitySystem [Dependency] private readonly SharedItemSystem _item = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly ToolOpenableSystem _toolOpenableSystem = default!; - + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnDestroyed); + SubscribeLocalEvent(OnReclaimed); SubscribeLocalEvent(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) }); SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent>(OnGetVerb); @@ -46,12 +50,12 @@ private void OnInit(Entity entity, ref ComponentInit args) private void OnDestroyed(Entity entity, ref DestructionEventArgs args) { - var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer); - if (storedInside != null && storedInside.Count >= 1) - { - var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity))); - _popupSystem.PopupEntity(popup, storedInside[0], PopupType.MediumCaution); - } + DropContentsAndAlert(entity); + } + + private void OnReclaimed(Entity entity, ref GotReclaimedEvent args) + { + DropContentsAndAlert(entity, args.ReclaimerCoordinates); } private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) @@ -90,8 +94,9 @@ private bool TryStashItem(Entity entity, EntityUid userUid return false; } - // check if item is too big to fit into secret stash - if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize)) + // check if item is too big to fit into secret stash or is in the blacklist + if (_item.GetSizePrototype(itemComp.Size) > _item.GetSizePrototype(entity.Comp.MaxItemSize) || + _whitelistSystem.IsBlacklistPass(entity.Comp.Blacklist, itemToHideUid)) { var msg = Loc.GetString("comp-secret-stash-action-hide-item-too-big", ("item", itemToHideUid), ("stashname", GetStashName(entity))); @@ -209,5 +214,18 @@ private bool HasItemInside(Entity entity) return entity.Comp.ItemContainer.ContainedEntity != null; } + /// + /// Drop the item stored in the stash and alert all nearby players with a popup. + /// + private void DropContentsAndAlert(Entity entity, EntityCoordinates? cords = null) + { + var storedInside = _containerSystem.EmptyContainer(entity.Comp.ItemContainer, true, cords); + if (storedInside != null && storedInside.Count >= 1) + { + var popup = Loc.GetString("comp-secret-stash-on-destroyed-popup", ("stashname", GetStashName(entity))); + _popupSystem.PopupPredicted(popup, storedInside[0], null, PopupType.MediumCaution); + } + } + #endregion } diff --git a/Content.Shared/Strip/SharedStrippableSystem.cs b/Content.Shared/Strip/SharedStrippableSystem.cs index e1c3d8ef0d8..7afe503275a 100644 --- a/Content.Shared/Strip/SharedStrippableSystem.cs +++ b/Content.Shared/Strip/SharedStrippableSystem.cs @@ -103,7 +103,7 @@ private void OnStripButtonPressed(Entity strippable, ref St if (userHands.ActiveHandEntity != null && !hasEnt) StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot); - else if (userHands.ActiveHandEntity == null && hasEnt) + else if (hasEnt) StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot); } @@ -135,7 +135,7 @@ private void StripHand( if (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null) StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable); - else if (user.Comp.ActiveHandEntity == null && handSlot.HeldEntity != null) + else if (handSlot.HeldEntity != null) StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable); } diff --git a/Content.Shared/UserInterface/IntrinsicUISystem.cs b/Content.Shared/UserInterface/IntrinsicUISystem.cs index 2d8c5d14801..b16492b8355 100644 --- a/Content.Shared/UserInterface/IntrinsicUISystem.cs +++ b/Content.Shared/UserInterface/IntrinsicUISystem.cs @@ -10,6 +10,7 @@ public sealed class IntrinsicUISystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(InitActions); + SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnActionToggle); } @@ -21,6 +22,15 @@ private void OnActionToggle(EntityUid uid, IntrinsicUIComponent component, Toggl args.Handled = InteractUI(uid, args.Key, component); } + private void OnShutdown(EntityUid uid, IntrinsicUIComponent component, ref ComponentShutdown args) + { + foreach (var actionEntry in component.UIs.Values) + { + var actionId = actionEntry.ToggleActionEntity; + _actionsSystem.RemoveAction(uid, actionId); + } + } + private void InitActions(EntityUid uid, IntrinsicUIComponent component, MapInitEvent args) { foreach (var entry in component.UIs.Values) diff --git a/Content.Shared/VendingMachines/VendingMachineComponent.cs b/Content.Shared/VendingMachines/VendingMachineComponent.cs index 50023a023ab..f3fe3a1ecdb 100644 --- a/Content.Shared/VendingMachines/VendingMachineComponent.cs +++ b/Content.Shared/VendingMachines/VendingMachineComponent.cs @@ -87,12 +87,13 @@ public sealed partial class VendingMachineComponent : Component /// Sound that plays when ejecting an item /// [DataField("soundVend")] - // Grabbed from: https://github.com/discordia-space/CEV-Eris/blob/f702afa271136d093ddeb415423240a2ceb212f0/sound/machines/vending_drop.ogg + // Grabbed from: https://github.com/tgstation/tgstation/blob/d34047a5ae911735e35cd44a210953c9563caa22/sound/machines/machine_vend.ogg public SoundSpecifier SoundVend = new SoundPathSpecifier("/Audio/Machines/machine_vend.ogg") { Params = new AudioParams { - Volume = -2f + Volume = -4f, + Variation = 0.15f } }; diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index bc19235cd39..767b5c4ef62 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -435,7 +435,7 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo throw new NotImplementedException(); } - DoLungeAnimation(user, weaponUid, weapon.Angle, GetCoordinates(attack.Coordinates).ToMap(EntityManager, TransformSystem), weapon.Range, animation); + DoLungeAnimation(user, weaponUid, weapon.Angle, TransformSystem.ToMapCoordinates(GetCoordinates(attack.Coordinates)), weapon.Range, animation); } var attackEv = new MeleeAttackEvent(weaponUid); @@ -467,12 +467,14 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity // TODO: This needs fixing if (meleeUid == user) { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Low, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Low, $"{ToPrettyString(user):actor} melee attacked (light) using their hands and missed"); } else { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Low, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Low, $"{ToPrettyString(user):actor} melee attacked (light) using {ToPrettyString(meleeUid):tool} and missed"); } var missEvent = new MeleeHitEvent(new List(), user, meleeUid, damage, null); @@ -521,12 +523,14 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity if (meleeUid == user) { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Medium, $"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using their hands and dealt {damageResult.GetTotal():damage} damage"); } else { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Medium, $"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.GetTotal():damage} damage"); } @@ -548,7 +552,7 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU if (!TryComp(user, out TransformComponent? userXform)) return false; - var targetMap = GetCoordinates(ev.Coordinates).ToMap(EntityManager, TransformSystem); + var targetMap = TransformSystem.ToMapCoordinates(GetCoordinates(ev.Coordinates)); if (targetMap.MapId != userXform.MapID) return false; @@ -564,12 +568,14 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU { if (meleeUid == user) { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Low, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Low, $"{ToPrettyString(user):actor} melee attacked (heavy) using their hands and missed"); } else { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Low, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Low, $"{ToPrettyString(user):actor} melee attacked (heavy) using {ToPrettyString(meleeUid):tool} and missed"); } var missEvent = new MeleeHitEvent(new List(), user, meleeUid, damage, direction); @@ -590,8 +596,14 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU // Validate client for (var i = entities.Count - 1; i >= 0; i--) { - if (ArcRaySuccessful(entities[i], userPos, direction.ToWorldAngle(), component.Angle, distance, - userXform.MapID, user, session)) + if (ArcRaySuccessful(entities[i], + userPos, + direction.ToWorldAngle(), + component.Angle, + distance, + userXform.MapID, + user, + session)) { continue; } @@ -658,16 +670,24 @@ private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeU if (damageResult != null && damageResult.GetTotal() > FixedPoint2.Zero) { + // If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor + if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage)) + { + _stamina.TakeStaminaDamage(entity, (bluntDamage * component.BluntStaminaDamageFactor).Float(), visual: false, source: user, with: meleeUid == user ? null : meleeUid); + } + appliedDamage += damageResult; if (meleeUid == user) { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Medium, $"{ToPrettyString(user):actor} melee attacked (heavy) {ToPrettyString(entity):subject} using their hands and dealt {damageResult.GetTotal():damage} damage"); } else { - AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium, + AdminLogger.Add(LogType.MeleeHit, + LogImpact.Medium, $"{ToPrettyString(user):actor} melee attacked (heavy) {ToPrettyString(entity):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.GetTotal():damage} damage"); } } @@ -701,8 +721,13 @@ protected HashSet ArcRayCast(Vector2 position, Angle angle, Angle arc { var castAngle = new Angle(baseAngle + increment * i); var res = _physics.IntersectRay(mapId, - new CollisionRay(position, castAngle.ToWorldVec(), - AttackMask), range, ignore, false).ToList(); + new CollisionRay(position, + castAngle.ToWorldVec(), + AttackMask), + range, + ignore, + false) + .ToList(); if (res.Count != 0) { @@ -713,8 +738,14 @@ protected HashSet ArcRayCast(Vector2 position, Angle angle, Angle arc return resSet; } - protected virtual bool ArcRaySuccessful(EntityUid targetUid, Vector2 position, Angle angle, Angle arcWidth, float range, - MapId mapId, EntityUid ignore, ICommonSession? session) + protected virtual bool ArcRaySuccessful(EntityUid targetUid, + Vector2 position, + Angle angle, + Angle arcWidth, + float range, + MapId mapId, + EntityUid ignore, + ICommonSession? session) { // Only matters for server. return true; diff --git a/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs b/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs index d2f0333b833..e790973538f 100644 --- a/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedGrapplingGunSystem.cs @@ -114,19 +114,14 @@ private void OnWeightlessMove(ref CanWeightlessMoveEvent ev) private void OnGunActivate(EntityUid uid, GrapplingGunComponent component, ActivateInWorldEvent args) { - if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex) - return; - - if (Deleted(component.Projectile)) + if (!Timing.IsFirstTimePredicted || args.Handled || !args.Complex || component.Projectile is not {} projectile) return; _audio.PlayPredicted(component.CycleSound, uid, args.User); _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, true); if (_netManager.IsServer) - { - QueueDel(component.Projectile.Value); - } + QueueDel(projectile); component.Projectile = null; SetReeling(uid, component, false, args.User); diff --git a/Content.Shared/Whitelist/EntityWhitelist.cs b/Content.Shared/Whitelist/EntityWhitelist.cs index 3e4e2fecb2e..cbe4633360f 100644 --- a/Content.Shared/Whitelist/EntityWhitelist.cs +++ b/Content.Shared/Whitelist/EntityWhitelist.cs @@ -32,6 +32,12 @@ public sealed partial class EntityWhitelist [DataField] public string[]? Components; // TODO yaml validation + /// + /// Mind Role Prototype names that are allowed in the whitelist. + /// + [DataField] public string[]? MindRoles; + // TODO yaml validation + /// /// Item sizes that are allowed in the whitelist. /// diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs index 57fdb523dd4..7c78b410a18 100644 --- a/Content.Shared/Whitelist/EntityWhitelistSystem.cs +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Item; +using Content.Shared.Roles; using Content.Shared.Tag; namespace Content.Shared.Whitelist; @@ -7,6 +8,7 @@ namespace Content.Shared.Whitelist; public sealed class EntityWhitelistSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly TagSystem _tag = default!; private EntityQuery _itemQuery; @@ -46,9 +48,30 @@ public bool CheckBoth([NotNullWhen(true)] EntityUid? uid, EntityWhitelist? black public bool IsValid(EntityWhitelist list, EntityUid uid) { if (list.Components != null) - EnsureRegistrations(list); + { + var regs = StringsToRegs(list.Components); + + list.Registrations ??= new List(); + list.Registrations.AddRange(regs); + } + + if (list.MindRoles != null) + { + var regs = StringsToRegs(list.MindRoles); + + foreach (var role in regs) + { + if ( _roles.MindHasRole(uid, role.Type, out _)) + { + if (!list.RequireAll) + return true; + } + else if (list.RequireAll) + return false; + } + } - if (list.Registrations != null) + if (list.Registrations != null && list.Registrations.Count > 0) { foreach (var reg in list.Registrations) { @@ -153,7 +176,7 @@ public bool IsBlacklistPassOrNull(EntityWhitelist? blacklist, EntityUid uid) return IsWhitelistPassOrNull(blacklist, uid); } - /// + /// /// Helper function to determine if Blacklist is either null or the entity is not on the list /// Duplicate of equivalent Whitelist function /// @@ -162,24 +185,27 @@ public bool IsBlacklistFailOrNull(EntityWhitelist? blacklist, EntityUid uid) return IsWhitelistFailOrNull(blacklist, uid); } - private void EnsureRegistrations(EntityWhitelist list) + private List StringsToRegs(string[]? input) { - if (list.Components == null) - return; + var list = new List(); - list.Registrations = new List(); - foreach (var name in list.Components) + if (input == null || input.Length == 0) + return list; + + foreach (var name in input) { var availability = _factory.GetComponentAvailability(name); if (_factory.TryGetRegistration(name, out var registration) && availability == ComponentAvailability.Available) { - list.Registrations.Add(registration); + list.Add(registration); } else if (availability == ComponentAvailability.Unknown) { - Log.Warning($"Unknown component name {name} passed to EntityWhitelist!"); + Log.Error($"StringsToRegs failed: Unknown component name {name} passed to EntityWhitelist!"); } } + + return list; } } diff --git a/Resources/Audio/Ambience/Antag/attributions.yml b/Resources/Audio/Ambience/Antag/attributions.yml index 25917a5da2b..0d3d278a627 100644 --- a/Resources/Audio/Ambience/Antag/attributions.yml +++ b/Resources/Audio/Ambience/Antag/attributions.yml @@ -18,3 +18,7 @@ license: "CC-BY-SA-3.0" copyright: "Made by @ps3moira on github" source: https://www.youtube.com/watch?v=4-R-_DiqiLo +- files: ["silicon_lawboard_antimov.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Made by @ps3moira on Discord for SS14" + source: "https://www.youtube.com/watch?v=jf1sYGYVLsw" diff --git a/Resources/Audio/Ambience/Antag/silicon_lawboard_antimov.ogg b/Resources/Audio/Ambience/Antag/silicon_lawboard_antimov.ogg new file mode 100644 index 00000000000..911a34532d6 Binary files /dev/null and b/Resources/Audio/Ambience/Antag/silicon_lawboard_antimov.ogg differ diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index 1b4ea747416..7675162a04d 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -163,6 +163,7 @@ - chime.ogg - buzz-sigh.ogg - buzztwo.ogg + - machine_vend.gg license: "CC-BY-SA-3.0" copyright: "Taken from TG station." source: "https://github.com/tgstation/tgstation/tree/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0" diff --git a/Resources/Audio/Machines/machine_vend.ogg b/Resources/Audio/Machines/machine_vend.ogg index 8f7c187d0c3..92867a1f3d3 100644 Binary files a/Resources/Audio/Machines/machine_vend.ogg and b/Resources/Audio/Machines/machine_vend.ogg differ diff --git a/Resources/Audio/Misc/attributions.yml b/Resources/Audio/Misc/attributions.yml index 9b3cb64ae73..a7349a445be 100644 --- a/Resources/Audio/Misc/attributions.yml +++ b/Resources/Audio/Misc/attributions.yml @@ -28,6 +28,11 @@ copyright: "Taken from TG station." source: "https://github.com/tgstation/tgstation/blob/2f63c779cb43543cfde76fa7ddaeacfde185fded/sound/effects/ratvar_reveal.ogg" +- files: ["cryo_warning.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from TG station." + source: "https://github.com/tgstation/tgstation/blob/00cf2da5f785c110b9930077b3202f1a4638e1fd/sound/machines/cryo_warning.ogg" + - files: ["epsilon.ogg"] license: "CC-BY-SA-3.0" copyright: "Made by dj-34 (https://github.com/dj-34)" diff --git a/Resources/Audio/Misc/cryo_warning.ogg b/Resources/Audio/Misc/cryo_warning.ogg new file mode 100644 index 00000000000..06cdebc9447 Binary files /dev/null and b/Resources/Audio/Misc/cryo_warning.ogg differ diff --git a/Resources/Audio/Voice/Reptilian/attritbutions.yml b/Resources/Audio/Voice/Reptilian/attritbutions.yml index 7e8b2a0ce39..7fa86b2ebfc 100644 --- a/Resources/Audio/Voice/Reptilian/attritbutions.yml +++ b/Resources/Audio/Voice/Reptilian/attritbutions.yml @@ -2,3 +2,8 @@ copyright: '"scream_lizard.ogg" by Skyrat-SS13' license: source: https://github.com/Skyrat-SS13/Skyrat-tg/pull/892 + +- files: [reptilian_tailthump.ogg] + copyright: "Taken from https://freesound.org/" + license: "CC0-1.0" + source: https://freesound.org/people/TylerAM/sounds/389665/ \ No newline at end of file diff --git a/Resources/Audio/Voice/Reptilian/reptilian_tailthump.ogg b/Resources/Audio/Voice/Reptilian/reptilian_tailthump.ogg new file mode 100644 index 00000000000..e4bf25f7b8d Binary files /dev/null and b/Resources/Audio/Voice/Reptilian/reptilian_tailthump.ogg differ diff --git a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml index 7c46974818a..89db045c96d 100644 --- a/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml +++ b/Resources/Audio/Weapons/Guns/Gunshots/attributions.yml @@ -26,4 +26,9 @@ - files: ["ship_duster.ogg", "ship_friendship.ogg", "ship_svalinn.ogg", "ship_perforator.ogg"] license: "CC0-1.0" copyright: "Created by MIXnikita for Space Station 14" - source: "https://github.com/MIXnikita" \ No newline at end of file + source: "https://github.com/MIXnikita" + +- files: ["syringe_gun.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from vgstation" + source: "https://github.com/vgstation-coders/vgstation13/commit/23303188abe6fe31b114a218a1950d7325a23730" \ No newline at end of file diff --git a/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg b/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg new file mode 100644 index 00000000000..2132808ecdc Binary files /dev/null and b/Resources/Audio/Weapons/Guns/Gunshots/syringe_gun.ogg differ diff --git a/Resources/Audio/_RMC14/Voice/Xeno/alien_talk1.ogg b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk1.ogg new file mode 100644 index 00000000000..a6cd1d901b6 Binary files /dev/null and b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk1.ogg differ diff --git a/Resources/Audio/_RMC14/Voice/Xeno/alien_talk2.ogg b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk2.ogg new file mode 100644 index 00000000000..8766ef4c887 Binary files /dev/null and b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk2.ogg differ diff --git a/Resources/Audio/_RMC14/Voice/Xeno/alien_talk3.ogg b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk3.ogg new file mode 100644 index 00000000000..d2b4a14ce7d Binary files /dev/null and b/Resources/Audio/_RMC14/Voice/Xeno/alien_talk3.ogg differ diff --git a/Resources/Audio/_RMC14/Voice/Xeno/attributions.yml b/Resources/Audio/_RMC14/Voice/Xeno/attributions.yml new file mode 100644 index 00000000000..b98d9d09eb5 --- /dev/null +++ b/Resources/Audio/_RMC14/Voice/Xeno/attributions.yml @@ -0,0 +1,14 @@ +- files: ["alien_talk1.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/master/sound/voice/alien_talk.ogg" + +- files: ["alien_talk2.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/master/sound/voice/alien_talk2.ogg" + +- files: ["alien_talk3.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Taken from cmss13" + source: "https://github.com/cmss13-devs/cmss13/blob/master/sound/voice/alien_talk3.ogg" diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 0137c7b3403..f78eec801ea 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -552,5 +552,20 @@ Entries: id: 68 time: '2024-09-23T07:28:42.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32395 +- author: SlamBamActionman + changes: + - message: Added admin logging for generated codewords. + type: Add + id: 69 + time: '2024-10-09T11:55:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32531 +- author: IProduceWidgets + changes: + - message: invokeverb should work with smite names now. Find the names in the smite + tab on mouse hover. + type: Fix + id: 70 + time: '2024-10-16T22:24:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32844 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index d51092849de..c4edb358ab6 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,455 +1,4 @@ Entries: -- author: metalgearsloth - changes: - - message: Moved VGRoid from 1,000m away to ~500m. - type: Tweak - id: 6993 - time: '2024-07-28T03:14:18.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29943 -- author: lzk228 - changes: - - message: Fixed pancakes stacks. Before it, splitting not default pancakes stacks - would give you default pancakes. - type: Fix - id: 6994 - time: '2024-07-28T03:49:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30270 -- author: Plykiya - changes: - - message: Fixed the client mispredicting people slipping with their magboots turned - on - type: Fix - id: 6995 - time: '2024-07-28T06:17:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30425 -- author: Katzenminer - changes: - - message: Pun and similar pets are no longer firemune - type: Fix - id: 6996 - time: '2024-07-28T08:32:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30424 -- author: lzk228 - changes: - - message: Fixed permanent absence of the approver string in cargo invoice. - type: Fix - id: 6997 - time: '2024-07-29T06:19:43.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29690 -- author: JIPDawg - changes: - - message: F9 is correctly bound to the Round End Summary window by default now. - type: Fix - id: 6998 - time: '2024-07-29T06:49:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30438 -- author: githubuser508 - changes: - - message: Candles crate and the ability for Cargo to order it. - type: Add - id: 6999 - time: '2024-07-29T08:29:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29736 -- author: Blackern5000 - changes: - - message: Emergency oxygen and fire lockers now generally contain more supplies - type: Tweak - id: 7000 - time: '2024-07-29T09:57:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29230 -- author: Moomoobeef - changes: - - message: Added the ability to wear lizard plushies on your head! - type: Add - id: 7001 - time: '2024-07-29T12:52:40.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30400 -- author: TurboTrackerss14 - changes: - - message: Reduced Kobold ghostrole chance to mirror Monkey - type: Tweak - id: 7002 - time: '2024-07-29T15:16:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30450 -- author: Ian321 - changes: - - message: The Courser now comes with a defibrillator. - type: Tweak - id: 7003 - time: '2024-07-30T01:05:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30471 -- author: slarticodefast - changes: - - message: Fixed puppy Ian not counting as a thief steal target. - type: Fix - id: 7004 - time: '2024-07-30T01:22:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30474 -- 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 - type: Tweak - id: 7010 - time: '2024-07-30T04:41:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29964 -- author: themias - changes: - - message: Fixed victim's fingerprints transferring onto an attacker's weapon - type: Fix - id: 7011 - time: '2024-07-30T08:35:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30257 -- author: to4no_fix - changes: - - message: Now engineering access is needed to interact with the particle accelerator - type: Tweak - id: 7012 - time: '2024-07-30T11:29:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30394 -- author: Cojoke-dot - changes: - - message: You can no longer get out of a disposal chute or container while knocked - over by trying to walk - type: Fix - id: 7013 - time: '2024-07-30T13:53:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30391 -- author: Cojoke-dot - changes: - - message: QSI now swaps the top most valid container instead of QSI when placed - in an anchored container - type: Fix - id: 7014 - time: '2024-07-30T14:07:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30241 -- author: TheShuEd - changes: - - message: industrial ore processor can now process diamonds - type: Fix - id: 7015 - time: '2024-07-30T14:41:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30499 -- author: PJB3005 - changes: - - message: CLF3 is now called "chlorine trifluoride" - type: Tweak - id: 7016 - time: '2024-07-31T00:14:23.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30510 -- author: slarticodefast - changes: - - message: Fixed the mouse position when it is over a singularity distortion effect - while zoomed in or out. - type: Fix - id: 7017 - time: '2024-07-31T00:14:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30509 -- author: metalgearsloth - changes: - - message: Add a button to the lobby so you can export a .png of your characters - type: Add - id: 7018 - time: '2024-07-31T15:14:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29874 -- author: slarticodefast - changes: - - message: Skeletons no longer have fingerprints. - type: Tweak - id: 7019 - time: '2024-07-31T16:08:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30530 -- author: themias - changes: - - message: Pens can be clicked cathartically - type: Tweak - id: 7020 - time: '2024-07-31T17:57:41.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30531 -- author: Plykiya - changes: - - message: Meteors now leave behind asteroid rocks on impact. - type: Add - id: 7021 - time: '2024-08-01T02:55:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30419 -- author: PixelTheAertist - changes: - - message: The Social Anxiety trait is now renamed to "Stutter" - type: Tweak - id: 7022 - time: '2024-08-01T02:58:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29898 -- author: Plykiya - changes: - - message: Adds hand labelers to the PTech, ChemDrobe, and LawDrobe. - type: Add - id: 7023 - time: '2024-08-01T02:59:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29958 -- author: Ko4erga - changes: - - message: Added a cutter machine for crafting patterned steel tiles, concrete and - wooden tiles. - type: Add - - message: After rip off patterned tiles you get current pattern, not just steel - tile. - type: Tweak - id: 7024 - time: '2024-08-01T10:26:32.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30431 -- author: NakataRin - changes: - - message: Added paramedic to the train station. - type: Add - id: 7025 - time: '2024-08-01T19:59:43.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30556 -- author: marbow - changes: - - message: Rejoice, detectives! Hand labeler has been added to your closet! - type: Add - id: 7026 - time: '2024-08-01T20:01:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30501 -- author: metalgearsloth - changes: - - message: Fix some popups playing twice. - type: Fix - id: 7027 - time: '2024-08-02T01:33:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30452 -- author: WarMechanic - changes: - - message: Adjusted meteors to have less lethal blast fragments. - type: Tweak - id: 7028 - time: '2024-08-02T05:43:41.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29199 -- author: slarticodefast - changes: - - message: Fixed borgs not being able to state laws or open other UIs without an - active module. - type: Fix - id: 7029 - time: '2024-08-02T05:44:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30299 -- author: TropicalHibi - changes: - - message: Now fs (for sure) and wru (where are you) are changed to their full version - in text - type: Add - id: 7030 - time: '2024-08-02T05:57:50.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30508 -- author: Plykiya - changes: - - message: Rechargers now show the percent charged of the item it is charging. - type: Add - id: 7031 - time: '2024-08-02T06:05:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28500 -- author: ShadowCommander - changes: - - message: Rollerbeds now deploy when holding them in hand and clicking on the ground. - type: Add - id: 7032 - time: '2024-08-02T07:05:12.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30000 -- author: slarticodefast - changes: - - message: The digital audio workstation can now be rotated. - type: Tweak - id: 7033 - time: '2024-08-02T10:02:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30571 -- author: slarticodefast - changes: - - message: Added potassium iodide. It gives you short term radiation protection - and can be found in radiation treatment kits. - type: Add - - message: Added haloperidol. It removes most stimulating/hallucinogenic drugs from - the body and makes you drowsy. - type: Add - - message: Added the drowsiness status effect. It blurs your vision and makes you - randomly fall asleep. Drink some coffee or cola to remove it. - type: Add - - message: Chloral hydrate now makes you drowsy instead of forcing you to sleep - instantly. - type: Tweak - id: 7034 - time: '2024-08-02T17:12:08.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/27454 -- author: Blackern5000 - changes: - - message: Winter boots no longer have ugly outlines - type: Tweak - id: 7035 - time: '2024-08-02T23:05:19.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30350 -- author: lzk228 - changes: - - message: Figurines will say phrases on activation. - type: Add - id: 7036 - time: '2024-08-03T14:06:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30455 -- author: JoelZimmerman - changes: - - message: Dank pizza is now cooked with cannabis leaves instead of ambrosia vulgaris. - type: Tweak - id: 7037 - time: '2024-08-03T14:07:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30430 -- author: Ian321 - changes: - - message: Help menus now include the linked guides. - type: Fix - id: 7038 - time: '2024-08-04T03:32:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30462 -- author: DrSmugleaf - changes: - - message: The Toggle Internals verb no longer shows up in the context menu if no - breathing tool is equipped and internals are off. - type: Tweak - id: 7039 - time: '2024-08-04T03:34:56.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30622 -- author: Mervill - changes: - - message: Rotten Meat can be collected in trash bags and deleted by the recycler - type: Tweak - id: 7040 - time: '2024-08-04T10:13:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30594 -- author: DrSmugleaf - changes: - - message: Fixed the client sometimes falsely showing the red color flash effect - when attacking something that they can't, like allied xenonids. - type: Fix - id: 7041 - time: '2024-08-05T03:14:01.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30661 -- author: Killerqu00 - changes: - - message: Sleeper agents event no longer occurs when evacuation is called. - type: Tweak - id: 7042 - time: '2024-08-05T03:17:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30646 -- author: Cojoke-dot - changes: - - message: Mice can now use combat mode with a 0 damage attack - type: Tweak - id: 7043 - time: '2024-08-05T03:26:28.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30487 -- author: TheShuEd - changes: - - message: Nanotrasen has introduced recruitment rules. Characters under the age - of 20 are no longer allowed to take on the role of head. - type: Tweak - id: 7044 - time: '2024-08-05T04:25:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30347 -- author: foboscheshir - changes: - - message: added missing mime mask sprites for Vox - scared and sad. - type: Add - id: 7045 - time: '2024-08-05T06:09:35.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30607 -- author: SlamBamActionman, PolarTundra - changes: - - message: Thief's Syndie Kit now comes with a Radio Jammer, a lighter and a Syndicate - codeword. - type: Add - - message: Thief's Agent ID has been moved from the Syndie Kit to the Chameleon - Kit. - type: Tweak - - message: The Syndicate pAI has been removed from the Thief's Syndie Kit. - type: Remove - id: 7046 - time: '2024-08-05T08:03:24.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30446 -- author: EmoGarbage404 - changes: - - message: Being cold now slows down your character. - type: Add - id: 7047 - time: '2024-08-05T08:07:02.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29692 -- author: EmoGarbage404 - changes: - - message: The biogenerator has been recolored and renamed to the "biocube fabricator." - type: Tweak - id: 7048 - time: '2024-08-06T10:51:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30696 -- author: Errant - changes: - - message: Vox can be nukies and ninjas again. - type: Tweak - id: 7049 - time: '2024-08-06T10:58:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29783 -- author: slarticodefast - changes: - - message: Fixed borgs, animals and aghosts being able to enter cryosleep. - type: Fix - id: 7050 - time: '2024-08-06T11:00:15.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30574 -- author: Flareguy - changes: - - message: Most hats are now automatically displaced on vox to look more fitting. - type: Tweak - id: 7051 - time: '2024-08-06T13:12:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30699 - author: Errant changes: - message: Medical Mask sprite now works on vox. @@ -3911,3 +3460,470 @@ id: 7492 time: '2024-10-07T22:42:43.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/32429 +- author: shampunj + changes: + - message: When curtains are destroyed, only 1 cloth drops out + type: Tweak + id: 7493 + time: '2024-10-08T09:51:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32685 +- author: Plykiya + changes: + - message: Librarians now start with a D20. + type: Tweak + id: 7494 + time: '2024-10-08T09:53:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32648 +- author: K-Dynamic + changes: + - message: CHIMP and APE particles should be almost as fast as before + type: Tweak + id: 7495 + time: '2024-10-08T18:29:37.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32690 +- author: ss14nekow + changes: + - message: Added pumpkin pie! Can be made in the microwave with 1 pie tin, 1 pie + dough, and 1 pumpkin. + type: Add + id: 7496 + time: '2024-10-08T23:29:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32623 +- author: Beck Thompson + changes: + - message: The radio jammers price has been decreased from 4 -> 3 TC. + type: Tweak + id: 7497 + time: '2024-10-09T12:44:38.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32472 +- author: beck-thompson, Moomoobeef + changes: + - message: Plushies now can have small items stuffed inside of them! To open, click + on the plush with a sharp item. You can then insert a small item into the plush. + To close, just click on it again with any sharp item! + type: Add + - message: When pAIs are inserted into plushies, their names will be updated to + the plushies name. E.g when inside a lizard plushie the pAI's name will appear + as "lizard plushie" when speaking. + type: Add + id: 7498 + time: '2024-10-09T18:01:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/30805 +- author: Errant + changes: + - message: Nukies now have role briefing on their character screen. + type: Tweak + - message: Animals who are made Thief no longer get told in their briefing about + the thief gear that they don't actually have and couldn't possibly use. + type: Fix + id: 7499 + time: '2024-10-10T08:48:57.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31318 +- author: nikthechampiongr + changes: + - message: It is no longer impossible to do bounties other than the first one if + the server has been up for a long period of time. + type: Fix + id: 7500 + time: '2024-10-10T14:35:36.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32700 +- author: pheenty + changes: + - message: Quartermaster now has external access + type: Add + id: 7501 + time: '2024-10-13T04:55:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32631 +- author: Golinth + changes: + - message: Firebots can no longer become sentient via the sentience event. + type: Remove + id: 7502 + time: '2024-10-13T04:55:51.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32629 +- author: BramvanZijp + changes: + - message: The playtime requirements for station AI have been increased. + type: Tweak + - message: The playtime requirements to play borg have been slightly decreased. + type: Tweak + id: 7503 + time: '2024-10-13T06:38:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32007 +- author: SkaldetSkaeg + changes: + - message: Sleepers can no longer use emotions + type: Fix + 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 +- author: Gamer3107 + changes: + - message: Portal artifacts no longer target the AI or people within containers + type: Fix + id: 7510 + time: '2024-10-14T05:55:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32677 +- author: ScarKy0 + changes: + - message: Cyborg modules now have icons instead of using the module sprite. + type: Add + id: 7511 + time: '2024-10-14T07:05:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32505 +- author: metalgearsloth + changes: + - message: Fix tech anomalies firing every tick. + type: Fix + id: 7512 + time: '2024-10-14T07:06:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32805 +- author: ada-please + changes: + - message: Added more prizes to the Space Villain arcade machine + type: Add + id: 7513 + time: '2024-10-14T21:59:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32309 +- author: K-Dynamic + changes: + - message: Added nitrogen tanks to engineering tank dispensers. + type: Add + id: 7514 + time: '2024-10-15T08:28:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32565 +- author: slarticodefast + changes: + - message: Fixed inconsistent solution transfer amounts from spray bottles to plant + holders. + type: Fix + id: 7515 + time: '2024-10-16T03:57:30.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32813 +- author: Minemoder, ArtisticRoomba, Sarahon + changes: + - message: Reptiles and Kobolds can now thump their tail. + type: Add + id: 7516 + time: '2024-10-16T10:04:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32064 +- author: deltanedas + changes: + - message: Fixed grappling hooks sometimes getting bricked. + type: Fix + id: 7517 + time: '2024-10-16T22:32:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32738 +- author: SlamBamActionman + changes: + - message: The Microphone instrument has a new vocal style "Kweh". + type: Add + id: 7518 + time: '2024-10-17T01:57:43.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32848 +- author: deltanedas + changes: + - message: You can now eject biomass from a biogenerator without having to deconstruct + it. + type: Tweak + id: 7519 + time: '2024-10-17T03:12:30.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32854 +- author: lzk228 + changes: + - message: Fixed holosign projectors power cell popups. + type: Fix + id: 7520 + time: '2024-10-17T03:21:04.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32808 +- author: Kickguy223 + changes: + - message: As an AI, Having a Lawboard inserted into your AI Upload Computer will + now Play a sound cue + type: Add + - message: As an AI, Having the Antimov Lawboard uploaded will play an appropriate + sound cue + type: Add + id: 7521 + time: '2024-10-17T03:41:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32625 +- author: Callmore + changes: + - message: Prefered item quick store locations can be created again. + type: Fix + id: 7522 + time: '2024-10-17T04:00:52.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32480 +- author: Myra + changes: + - message: The game's title bar window will display the name of the server you have + joined (unless disabled). + type: Add + id: 7523 + time: '2024-10-17T11:06:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32547 +- author: Aeshus + changes: + - message: Emote shorthands (like "lol" or ":)") sent in chat are detected throughout + the whole message. Note that only the last shorthand in the message will be + emoted. + type: Tweak + id: 7524 + time: '2024-10-17T14:01:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/28645 +- author: slarticodefast + changes: + - message: Fixed the playtime stats window not showing entries correctly. + type: Fix + id: 7525 + time: '2024-10-17T21:32:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32856 +- author: Thatonestomf + changes: + - message: Smile no longer has a ghost role raffle attached to her + type: Fix + id: 7526 + time: '2024-10-18T02:42:50.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32837 +- author: Catofquestionableethics + changes: + - message: Spacemans cake now contains Polypyrylium Oligomers instead of Omnizine. + type: Tweak + - message: Slime, Brain and Christmas cakes can now be eaten by Lizards. + type: Fix + id: 7527 + time: '2024-10-18T03:08:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32830 +- author: Plykiya + changes: + - message: You can no longer print flares or shotgun flares. + type: Remove + id: 7528 + time: '2024-10-18T03:28:30.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32563 +- author: pheenty + changes: + - message: QM is now considered an important job. + type: Tweak + - message: QM is now correctly considered head for traitor's kill head objective, + while warden now isn't. + type: Fix + id: 7529 + time: '2024-10-18T09:43:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32721 +- author: JIPDawg + changes: + - message: On Salamander the round will now restart after 5 minutes from when the + Evac shuttle arrives at CentCom. + type: Tweak + id: 7530 + time: '2024-10-18T10:03:12.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32776 +- author: Errant + changes: + - message: Players becoming Traitor without a PDA now correctly get the text and + audio notifications, and the code words. They are also given an Uplink Implant, + with the price taken from their starting TC. + type: Fix + id: 7531 + time: '2024-10-18T12:55:43.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/30359 +- author: Beck Thompson + changes: + - message: Plushies will now eject their contents when recycled in the recycler! + type: Fix + id: 7532 + time: '2024-10-18T12:58:07.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32838 +- author: Ilya246 + changes: + - message: Spectral locator, rare maintenance loot that lets you know if any ghosts + are nearby. + type: Add + id: 7533 + time: '2024-10-18T13:42:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32323 +- author: PJB3005 + changes: + - message: You can now start removing items via stripping while holding something. + type: Tweak + id: 7534 + time: '2024-10-18T19:20:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32750 +- author: Beck Thompson + changes: + - message: Scalpels and shivs now work as knives! + type: Add + id: 7535 + time: '2024-10-19T02:40:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32858 +- author: PJB3005 + changes: + - message: Fix more performance issues on long-running game servers, hopefully. + type: Fix + id: 7536 + time: '2024-10-19T14:51:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32897 +- author: insoPL + changes: + - message: Zombies once again have zombie blood. + type: Fix + id: 7537 + time: '2024-10-19T15:31:45.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32532 +- author: Beck Thompson + changes: + - message: When MMIs and positronic brains are inserted into plushies, their names + will be updated to the plushies name (Exactly the same way pAIs do). + type: Add + id: 7538 + time: '2024-10-20T02:46:31.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32914 +- author: Calecute + changes: + - message: wide attacks that deal blunt damage will now deal stamina damage. + type: Fix + id: 7539 + time: '2024-10-20T03:41:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32422 +- author: Thatonestomf + changes: + - message: Mute toxin now mutes even after is metabolized, similar to glue + type: Tweak + - message: Mute toxin is now even more powerful at muting people + type: Tweak + id: 7540 + time: '2024-10-21T03:43:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32915 +- author: MendaxxDev + changes: + - message: Fixed typing sound playing when AI or admin ghosts interacted with consoles. + type: Fix + id: 7541 + time: '2024-10-21T03:50:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32906 +- author: NoElkaTheGod + changes: + - message: Station AI can now interact with long range fax machines. + type: Tweak + id: 7542 + time: '2024-10-21T12:44:43.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32929 +- author: Southbridge + changes: + - message: Box Station's window between the containment room and TEG has been upgraded + to plasma. + type: Fix + - message: Box Station's Engineering storage room air alarm has been properly connected + to nearby devices. + type: Fix + - message: Box Station's Janitorial disposal chute has been replaced with a working + one. + type: Fix + id: 7543 + time: '2024-10-22T05:39:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32950 +- author: Moomoobeef + changes: + - message: Ammo-boxes no longer appear empty when only one bullet is removed. + type: Fix + id: 7544 + time: '2024-10-22T09:00:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32930 +- author: ScarKy0 + changes: + - message: Added the syringe gun and it's respective ammo. Currently Admeme only. + type: Add + id: 7545 + time: '2024-10-22T13:03:42.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32112 +- author: ScarKy0, Fildrance + changes: + - message: Using an intellicard on the AI core now swaps the AI between the core + and intellicard. (You can evac with the AI!) + type: Add + - message: Added an intellicard to the RD's locker. + type: Add + id: 7546 + time: '2024-10-22T13:49:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32347 +- author: Southbridge + changes: + - message: Nuclear Cola can now be broken down in a centrifuge. + type: Add + id: 7547 + time: '2024-10-22T17:01:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32441 +- author: BramvanZijp + changes: + - message: The Space Ninja Suit will now give an error popup if you are trying to + install a cell that is not better compared to the current cell. + type: Add + - message: When comparing which power cell is better when trying to swap them, the + Space Ninja's Suit will now also consider if the power cells have self-recharge + capability. + type: Tweak + - message: You can no longer fit weapons-grade power cages into the space ninja + suit. + type: Fix + id: 7548 + time: '2024-10-22T23:36:51.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32902 +- author: UBlueberry + changes: + - message: The appraisal tool now has in-hand sprites! + type: Fix + id: 7549 + time: '2024-10-22T23:51:49.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32849 +- author: chromiumboy + changes: + - message: The atmospheric alerts computer has been upgraded to visually indicate + the rooms of the station that are being monitored by its air and fire alarm + systems + type: Tweak + id: 7550 + time: '2024-10-23T12:49:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31910 +- author: hyphenationc + changes: + - message: Snake meat is now properly considered Meat, so Lizards can eat it. + type: Fix + id: 7551 + time: '2024-10-24T03:41:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32965 diff --git a/Resources/Changelog/DeltaVChangelog.yml b/Resources/Changelog/DeltaVChangelog.yml index 436d1247939..4b051af5d1c 100644 --- a/Resources/Changelog/DeltaVChangelog.yml +++ b/Resources/Changelog/DeltaVChangelog.yml @@ -1,49 +1,4 @@ Entries: -- author: leonardo-dabepis - changes: - - message: Switched logos around - type: Tweak - id: 116 - time: '2023-11-01T22:45:55.0000000+00:00' -- author: TJohnson - changes: - - message: Security has received new models of hardsuit! - type: Tweak - id: 117 - time: '2023-11-02T00:31:53.0000000+00:00' -- author: ps3moira - changes: - - message: Added Two New Round-End Sounds - type: Add - - message: Lobby Round-End Sounds - type: Remove - id: 118 - time: '2023-11-02T01:00:16.0000000+00:00' -- author: leonardo-dabepis - changes: - - message: Technical Assistant now has a 4 hour playtime requirement. - type: Tweak - id: 119 - time: '2023-11-02T16:15:06.0000000+00:00' -- author: Colin-Tel - changes: - - message: Updated Asterisk Station with various fixes, a slot for an atmos technician, - and a familiar parallax... - type: Tweak - id: 120 - time: '2023-11-02T20:38:51.0000000+00:00' -- author: FluffiestFloof - changes: - - message: Removed starvation! - type: Remove - id: 121 - time: '2023-11-05T19:24:51.0000000+00:00' -- author: FluffiestFloof - changes: - - message: Cat Ears have been moved from the Syndicate Uplink into the AutoDrobe. - type: Tweak - id: 122 - time: '2023-11-06T14:39:49.0000000+00:00' - author: FluffiestFloof changes: - message: Removed the carp suit bundle from the syndicate uplink. @@ -3679,3 +3634,56 @@ id: 615 time: '2024-10-20T16:02:51.0000000+00:00' url: https://github.com/DeltaV-Station/Delta-v/pull/2026 +- author: deltanedas + changes: + - message: Removed salt ore in favour of coal. Give the chemists carbon! + type: Remove + id: 616 + time: '2024-10-20T17:30:04.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2027 +- author: PPooch + changes: + - message: Clerks now have brig timer and evidence locker access. Don't steal the + Prosecutor's job from them! + type: Tweak + id: 617 + time: '2024-10-23T07:04:54.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2034 +- author: deltanedas + changes: + - message: Merged the past few weeks changes from upstream, report any bugs related + to your character menu or objectives! + type: Add + id: 618 + time: '2024-10-24T08:41:03.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2036 +- author: deltanedas + changes: + - message: Fixed the crew manifest. + type: Fix + id: 619 + time: '2024-10-24T12:54:35.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2037 +- author: deltanedas + changes: + - message: Martyr cyborg modules can now be disarmed with a crowbar to yield its + explosive payload. + type: Tweak + id: 620 + time: '2024-10-24T15:19:46.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/1960 +- author: Solaris7518 + changes: + - message: Fixed almost every midi being completely ruined by a recent change from + upstream. You're welcome, my fellow musicians. + type: Fix + id: 621 + time: '2024-10-24T23:39:16.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2041 +- author: MilonPL + changes: + - message: Fixed playtime stats and requirements not showing up correctly. + type: Fix + id: 622 + time: '2024-10-25T00:15:01.0000000+00:00' + url: https://github.com/DeltaV-Station/Delta-v/pull/2038 diff --git a/Resources/ConfigPresets/DeltaV/deltav.toml b/Resources/ConfigPresets/DeltaV/deltav.toml index 9f4705092b1..8b98868b7d7 100644 --- a/Resources/ConfigPresets/DeltaV/deltav.toml +++ b/Resources/ConfigPresets/DeltaV/deltav.toml @@ -33,6 +33,7 @@ alert.min_players_sharing_connection = 2 [server] id = "deltav" rules_file = "DeltaVRuleset" +uptime_restart_minutes = 2880 # 48h [discord] rich_main_icon_id = "deltav" diff --git a/Resources/Locale/en-US/_lib.ftl b/Resources/Locale/en-US/_lib.ftl index c901d0f461e..5c6f73af66f 100644 --- a/Resources/Locale/en-US/_lib.ftl +++ b/Resources/Locale/en-US/_lib.ftl @@ -31,3 +31,6 @@ zzzz-fmt-power-joules = { TOSTRING($divided, "F1") } { $places -> [4] TJ *[5] ??? } + +# Used internally by the PLAYTIME() function. +zzzz-fmt-playtime = {$hours}H {$minutes}M \ No newline at end of file diff --git a/Resources/Locale/en-US/administration/smites.ftl b/Resources/Locale/en-US/administration/smites.ftl index 467a1577e7a..de02129917e 100644 --- a/Resources/Locale/en-US/administration/smites.ftl +++ b/Resources/Locale/en-US/administration/smites.ftl @@ -21,42 +21,42 @@ admin-smite-explode-name = Explode admin-smite-chess-dimension-name = Chess Dimension admin-smite-set-alight-name = Set Alight admin-smite-monkeyify-name = Monkeyify -admin-smite-electrocute-name = Garbage Can -admin-smite-creampie-name = Electrocute -admin-smite-remove-blood-name = Creampie -admin-smite-vomit-organs-name = Remove blood -admin-smite-remove-hands-name = Vomit organs -admin-smite-remove-hand-name = Remove hands -admin-smite-pinball-name = Remove hand -admin-smite-yeet-name = Stomach Removal -admin-smite-become-bread-name = Lungs Removal -admin-smite-ghostkick-name = Pinball -admin-smite-nyanify-name = Yeet -admin-smite-kill-sign-name = Become Bread -admin-smite-cluwne-name = Become Mouse -admin-smite-anger-pointing-arrows-name = Ghostkick -admin-smite-dust-name = Nyanify -admin-smite-buffering-name = Kill sign -admin-smite-become-instrument-name = Cluwne -admin-smite-remove-gravity-name = Maid -admin-smite-reptilian-species-swap-name = Anger Pointing Arrows -admin-smite-locker-stuff-name = Dust -admin-smite-headstand-name = Buffering -admin-smite-become-mouse-name = Become Instrument -admin-smite-maid-name = Remove gravity -admin-smite-zoom-in-name = Reptilian Species Swap -admin-smite-flip-eye-name = Locker stuff -admin-smite-run-walk-swap-name = Headstand -admin-smite-super-speed-name = Zoom in -admin-smite-stomach-removal-name = Flip eye -admin-smite-speak-backwards-name = Run Walk Swap -admin-smite-lung-removal-name = Speak Backwards +admin-smite-garbage-can-name = Garbage Can +admin-smite-electrocute-name = Electrocute +admin-smite-remove-blood-name = Remove blood +admin-smite-remove-hands-name = Remove hands +admin-smite-remove-hand-name = Remove hand +admin-smite-pinball-name = Pinball +admin-smite-yeet-name = Yeet +admin-smite-become-bread-name = Become Bread +admin-smite-cluwne-name = Cluwne +admin-smite-anger-pointing-arrows-name = Anger Pointing Arrows +admin-smite-dust-name = Dust +admin-smite-buffering-name = Buffering +admin-smite-become-instrument-name = Become Instrument +admin-smite-remove-gravity-name = Remove Gravity +admin-smite-reptilian-species-swap-name = Become Reptilian +admin-smite-locker-stuff-name = Locker Stuff +admin-smite-headstand-name = Headstand +admin-smite-become-mouse-name = Become Mouse +admin-smite-maid-name = Cat Maid +admin-smite-zoom-in-name = Zoom In +admin-smite-flip-eye-name = Flip Eye +admin-smite-run-walk-swap-name = Run Walk Swap +admin-smite-super-speed-name = Run Up +admin-smite-stomach-removal-name = Stomach Removal +admin-smite-speak-backwards-name = Speak Backwards +admin-smite-lung-removal-name = Lungs Removal admin-smite-disarm-prone-name = Disarm Prone -admin-smite-garbage-can-name = Super speed -admin-smite-super-bonk-name = Super Bonk Lite -admin-smite-super-bonk-lite-name = Super Bonk +admin-smite-super-bonk-name = Super Bonk +admin-smite-super-bonk-lite-name = Super Bonk Lite admin-smite-terminate-name = Terminate admin-smite-super-slip-name = Super Slip +admin-smite-creampie-name = Cream +admin-smite-vomit-organs-name = Vomit Organs +admin-smite-ghostkick-name = Ghost Kick +admin-smite-nyanify-name = Cat Ears +admin-smite-kill-sign-name = Kill Sign ## Smite descriptions diff --git a/Resources/Locale/en-US/advertisements/other/firebot.ftl b/Resources/Locale/en-US/advertisements/other/firebot.ftl new file mode 100644 index 00000000000..c614d5ecd0f --- /dev/null +++ b/Resources/Locale/en-US/advertisements/other/firebot.ftl @@ -0,0 +1,4 @@ +advertisement-firebot-1 = No fires detected. +advertisement-firebot-2 = Only you can prevent station fires. +advertisement-firebot-3 = Temperature nominal. +advertisement-firebot-4 = Keep it cool. \ No newline at end of file diff --git a/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl index a1640c5e9d5..470a8f86952 100644 --- a/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl +++ b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl @@ -25,7 +25,7 @@ atmos-alerts-window-warning-state = Warning atmos-alerts-window-danger-state = Danger! atmos-alerts-window-invalid-state = Inactive -atmos-alerts-window-no-active-alerts = [font size=16][color=white]No active alerts -[/color] [color={$color}]situation normal[/color][/font] +atmos-alerts-window-no-active-alerts = [font size=16][color=white]No active alerts -[/color] [color={$color}]Situation normal[/color][/font] atmos-alerts-window-no-data-available = No data available atmos-alerts-window-alerts-being-silenced = Silencing alerts... diff --git a/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl b/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl index 7d1c0794435..f966f30e2d4 100644 --- a/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl +++ b/Resources/Locale/en-US/bed/cryostorage/cryogenic-storage.ftl @@ -2,5 +2,6 @@ ### Announcement earlyleave-cryo-job-unknown = Unknown -earlyleave-cryo-announcement = {$character} ({$job}) { CONJUGATE-HAVE($entity) } entered cryogenic storage! +# {$entity} available for GENDER function purposes +earlyleave-cryo-announcement = {$character} ({$job}) has entered cryogenic storage! earlyleave-cryo-sender = Station diff --git a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl index 0e8c4137f4e..ca20c277f53 100644 --- a/Resources/Locale/en-US/botany/components/plant-holder-component.ftl +++ b/Resources/Locale/en-US/botany/components/plant-holder-component.ftl @@ -8,8 +8,6 @@ plant-holder-component-no-weeds-message = This plot is devoid of weeds! It doesn plant-holder-component-remove-plant-message = You remove the plant from the {$name}. plant-holder-component-remove-plant-others-message = {$name} removes the plant. plant-holder-component-no-plant-message = There is no plant to remove. -plant-holder-component-empty-message = {$owner} is empty! -plant-holder-component-spray-message = You spray {$owner}. plant-holder-component-transfer-message = You transfer {$amount}u to {$owner}. plant-holder-component-nothing-to-sample-message = There is nothing to take a sample of! plant-holder-component-already-sampled-message = This plant has already been sampled. diff --git a/Resources/Locale/en-US/chat/emotes.ftl b/Resources/Locale/en-US/chat/emotes.ftl index cccb33a1f17..8c74acafca2 100644 --- a/Resources/Locale/en-US/chat/emotes.ftl +++ b/Resources/Locale/en-US/chat/emotes.ftl @@ -8,6 +8,7 @@ chat-emote-name-crying = Crying chat-emote-name-squish = Squish chat-emote-name-chitter = Chitter chat-emote-name-squeak = Squeak +chat-emote-name-thump = Thump Tail chat-emote-name-click = Click chat-emote-name-clap = Clap chat-emote-name-snap = Snap @@ -40,6 +41,7 @@ chat-emote-msg-crying = cries. chat-emote-msg-squish = squishes. chat-emote-msg-chitter = chitters. chat-emote-msg-squeak = squeaks. +chat-emote-msg-thump = thumps {POSS-ADJ($entity)} tail. chat-emote-msg-click = clicks. chat-emote-msg-clap = claps! chat-emote-msg-snap = snaps {POSS-ADJ($entity)} fingers. diff --git a/Resources/Locale/en-US/deltav/job/job-names.ftl b/Resources/Locale/en-US/deltav/job/job-names.ftl index 48521a7e9b7..c5c4c9da684 100644 --- a/Resources/Locale/en-US/deltav/job/job-names.ftl +++ b/Resources/Locale/en-US/deltav/job/job-names.ftl @@ -12,6 +12,47 @@ job-name-senior-engineer = Senior Engineer job-name-senior-officer = Senior Officer job-name-qm = Logistics Officer +# Alternate titles +job-alt-title-tourist = Tourist +job-alt-title-off-duty-crew = Off-Duty Crew +job-alt-title-student = Student + +job-alt-title-mixologist = Mixologist + +job-alt-title-baker = Baker +job-alt-title-butcher = Butcher +job-alt-title-pizzaiolo = Pizzaiolo + +job-alt-title-practical-nurse = Practical Nurse +job-alt-title-resident = Resident + +job-alt-title-senior-physician = Senior Physician +job-alt-title-clinician = Clinician + +job-alt-title-life-support = Life Support Technician +job-alt-title-plasma-scientist = Plasma Scientist + +job-alt-title-senior-engineer = Senior Engineer +job-alt-title-electrician = Electrician +job-alt-title-mechanic = Mechanic + +job-alt-title-deck-worker = Deck Worker +job-alt-title-inventory-associate = Inventory Associate + +job-alt-title-mail-carrier = Mail Carrier + +job-alt-title-prospector = Prospector +job-alt-title-excavator = Excavator + +job-alt-title-senior-researcher = Senior Researcher +job-alt-title-lab-technician = Lab Technician +job-alt-title-xenoarch = Xenoarchaeologist + +job-alt-title-senior-officer = Senior Officer + +job-alt-title-jester = Jester +job-alt-title-fool = Fool + # Role timers JobMedicalBorg = Medical Cyborg JobCourier = Courier diff --git a/Resources/Locale/en-US/deltav/job/job-supervisors.ftl b/Resources/Locale/en-US/deltav/job/job-supervisors.ftl index 3cf44813098..c5765d9d199 100644 --- a/Resources/Locale/en-US/deltav/job/job-supervisors.ftl +++ b/Resources/Locale/en-US/deltav/job/job-supervisors.ftl @@ -1 +1 @@ -job-supervisors-cj = the chief justice +job-supervisors-cj = the Chief Justice diff --git a/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl b/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl index 5509125fb47..29a259faf05 100644 --- a/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl +++ b/Resources/Locale/en-US/deltav/preferences/loadout-groups.ftl @@ -67,7 +67,6 @@ loadout-group-scarfs = Scarf # Epistemics loadout-group-mantis-head = Mantis head loadout-group-mantis-jumpsuit = Mantis jumpsuit -loadout-group-mantis-backpack = Mantis backpack loadout-group-mantis-outerclothing = Mantis outer clothing loadout-group-mantis-shoes = Mantis shoes loadout-group-mantis-gloves = Mantis gloves diff --git a/Resources/Locale/en-US/deltav/prototypes/access/accesses.ftl b/Resources/Locale/en-US/deltav/prototypes/access/accesses.ftl index 07a6659b05b..abf10217cb0 100644 --- a/Resources/Locale/en-US/deltav/prototypes/access/accesses.ftl +++ b/Resources/Locale/en-US/deltav/prototypes/access/accesses.ftl @@ -5,3 +5,4 @@ id-card-access-level-prosecutor = Prosecutor id-card-access-level-clerk = Clerk id-card-access-level-justice = Justice id-card-access-level-corpsman = Corpsman +id-card-access-level-robotics = Robotics diff --git a/Resources/Locale/en-US/deltav/recruiter/recruiter.ftl b/Resources/Locale/en-US/deltav/recruiter/recruiter.ftl index 64e4136c1f1..6a4cb4a7090 100644 --- a/Resources/Locale/en-US/deltav/recruiter/recruiter.ftl +++ b/Resources/Locale/en-US/deltav/recruiter/recruiter.ftl @@ -1 +1,3 @@ recruiter-round-end-agent-name = Syndicate Recruiter + +recruiter-role-briefing = Find candidates, conduct interviews and seal the deal by having them sign with your special recruiter's pen. diff --git a/Resources/Locale/en-US/deltav/synthesis/synthesis.ftl b/Resources/Locale/en-US/deltav/synthesis/synthesis.ftl index 4214d8ff3fb..43e92f8343a 100644 --- a/Resources/Locale/en-US/deltav/synthesis/synthesis.ftl +++ b/Resources/Locale/en-US/deltav/synthesis/synthesis.ftl @@ -1 +1,3 @@ synthesis-round-end-agent-name = Synthesis Specialist + +synthesis-role-briefing = You are Interdyne's Synthesis Specialist! Prescribe deadly medications, barter your goods, and make a killing. diff --git a/Resources/Locale/en-US/entity-categories.ftl b/Resources/Locale/en-US/entity-categories.ftl index f17f65fde21..4b6cf87942f 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-objectives = Objectives entity-category-name-game-rules = Game Rules +entity-category-name-objectives = Objectives +entity-category-name-roles = Mind Roles +entity-category-name-mapping = Mapping diff --git a/Resources/Locale/en-US/forensics/fibers.ftl b/Resources/Locale/en-US/forensics/fibers.ftl index 53cfe5e7c12..72eae55e380 100644 --- a/Resources/Locale/en-US/forensics/fibers.ftl +++ b/Resources/Locale/en-US/forensics/fibers.ftl @@ -25,3 +25,7 @@ fibers-white = white fibers-yellow = yellow fibers-regal-blue = regal blue fibers-olive = olive +fibers-silver = silver +fibers-gold = gold +fibers-maroon = maroon +fibers-pink = pink diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl index 1a4fcafbf86..06f8aeb565e 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl @@ -4,6 +4,7 @@ nukeops-description = Nuclear operatives have targeted the station. Try to keep nukeops-welcome = You are a nuclear operative. Your goal is to blow up {$station}, and ensure that it is nothing but a pile of rubble. Your bosses, the Syndicate, have provided you with the tools you'll need for the task. Operation {$name} is a go ! Death to Nanotrasen! +nukeops-briefing = Your objectives are simple. Deliver the payload and make sure it detonates. Begin mission. nukeops-opsmajor = [color=crimson]Syndicate major victory![/color] nukeops-opsminor = [color=crimson]Syndicate minor victory![/color] diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl index fd3e6b82aa7..cf2f2b11308 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-traitor.ftl @@ -26,7 +26,7 @@ traitor-death-match-end-round-description-entry = {$originalName}'s PDA, with {$ traitor-role-greeting = You are an agent sent by {$corporation} on behalf of [color = darkred]The Syndicate.[/color] Your objectives and codewords are listed in the character menu. - Use the uplink loaded into your PDA to buy the tools you'll need for this mission. + Use your uplink to buy the tools you'll need for this mission. Death to Nanotrasen! traitor-role-codewords = The codewords are: [color = lightgray] @@ -36,9 +36,13 @@ traitor-role-codewords = traitor-role-uplink-code = Set your ringtone to the notes [color = lightgray]{$code}[/color] to lock or unlock your uplink. Remember to lock it after, or the stations crew will easily open it too! +traitor-role-uplink-implant = + Your uplink implant has been activated, access it from your hotbar. + The uplink is secure unless someone removes it from your body. # don't need all the flavour text for character menu traitor-role-codewords-short = The codewords are: {$codewords}. traitor-role-uplink-code-short = Your uplink code is {$code}. Set it as your PDA ringtone to access uplink. +traitor-role-uplink-implant-short = Your uplink was implanted. Access it from your hotbar. diff --git a/Resources/Locale/en-US/info/playtime-stats.ftl b/Resources/Locale/en-US/info/playtime-stats.ftl index 44ba39c25e9..b4925176a76 100644 --- a/Resources/Locale/en-US/info/playtime-stats.ftl +++ b/Resources/Locale/en-US/info/playtime-stats.ftl @@ -2,9 +2,8 @@ ui-playtime-stats-title = User Playtime Stats ui-playtime-overall-base = Overall Playtime: -ui-playtime-overall = Overall Playtime: {$time} +ui-playtime-overall = Overall Playtime: {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-header-role-type = Role ui-playtime-header-role-time = Time diff --git a/Resources/Locale/en-US/job/job-names.ftl b/Resources/Locale/en-US/job/job-names.ftl index db6c9e46808..62c109545c0 100644 --- a/Resources/Locale/en-US/job/job-names.ftl +++ b/Resources/Locale/en-US/job/job-names.ftl @@ -64,6 +64,11 @@ job-name-unknown = Unknown job-name-virologist = Virologist job-name-zombie = Zombie +# Job titles +job-title-visitor = Visitor +job-title-cluwne = Cluwne +job-title-universal = Universal + # Role timers - Make these alphabetical or I cut you JobAtmosphericTechnician = Atmospheric Technician JobBartender = Bartender diff --git a/Resources/Locale/en-US/job/job-supervisors.ftl b/Resources/Locale/en-US/job/job-supervisors.ftl index 46321f40dd0..47eb3152be5 100644 --- a/Resources/Locale/en-US/job/job-supervisors.ftl +++ b/Resources/Locale/en-US/job/job-supervisors.ftl @@ -1,15 +1,15 @@ job-supervisors-centcom = Central Command -job-supervisors-captain = the captain -job-supervisors-hop = the head of personnel -job-supervisors-hos = the head of security -job-supervisors-ce = the chief engineer -job-supervisors-cmo = the chief medical officer -job-supervisors-rd = the mystagogue -job-supervisors-qm = the logistics officer -job-supervisors-service = chefs, botanists, the bartender, and the head of personnel -job-supervisors-engineering = station engineers, atmospheric technicians, and the chief engineer -job-supervisors-medicine = medical doctors, chemists, and the chief medical officer -job-supervisors-security = security officers, the warden, and the head of security -job-supervisors-science = scientists, and the mystagogue +job-supervisors-captain = the Captain +job-supervisors-hop = the Head of Personnel +job-supervisors-hos = the Head of Security +job-supervisors-ce = the Chief Engineer +job-supervisors-cmo = the Chief Medical Officer +job-supervisors-rd = the Mystagogue +job-supervisors-qm = the Logistics Officer +job-supervisors-service = Chefs, Botanists, the Bartender, and the Head of Personnel +job-supervisors-engineering = Station Engineers, Atmospheric Technicians, and the Chief Engineer +job-supervisors-medicine = Medical Doctors, Paramedics, Chemists, and the Chief Medical Officer +job-supervisors-security = Security Officers, the Warden, and the Head of Security +job-supervisors-science = Scientists and the Mystagogue job-supervisors-hire = whoever hires you -job-supervisors-everyone = absolutely everyone +job-supervisors-everyone = absolutely everyone \ No newline at end of file diff --git a/Resources/Locale/en-US/job/role-requirements.ftl b/Resources/Locale/en-US/job/role-requirements.ftl index f0fff98c09f..2b6af7b1d2e 100644 --- a/Resources/Locale/en-US/job/role-requirements.ftl +++ b/Resources/Locale/en-US/job/role-requirements.ftl @@ -1,11 +1,11 @@ -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-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: diff --git a/Resources/Locale/en-US/ninja/ninja-actions.ftl b/Resources/Locale/en-US/ninja/ninja-actions.ftl index f01f02a60e5..b3e295b7a29 100644 --- a/Resources/Locale/en-US/ninja/ninja-actions.ftl +++ b/Resources/Locale/en-US/ninja/ninja-actions.ftl @@ -1,6 +1,8 @@ ninja-no-power = Not enough charge in suit battery! ninja-revealed = You have been revealed! ninja-suit-cooldown = The suit needs time to recuperate from the last attack. +ninja-cell-downgrade = The suit will only accept a new power cell that is better than the current one! +ninja-cell-too-large = This power source does not fit in the ninja suit! ninja-research-steal-fail = No new research nodes were stolen... ninja-research-steal-success = Stole {$count} new nodes from {THE($server)}. diff --git a/Resources/Locale/en-US/reagents/meta/elements.ftl b/Resources/Locale/en-US/reagents/meta/elements.ftl index 6d6439565ba..b5ef028bed9 100644 --- a/Resources/Locale/en-US/reagents/meta/elements.ftl +++ b/Resources/Locale/en-US/reagents/meta/elements.ftl @@ -61,8 +61,5 @@ reagent-desc-sodium = A silvery-white alkali metal. Highly reactive in its pure reagent-name-uranium = uranium reagent-desc-uranium = A grey metallic chemical element in the actinide series, weakly radioactive. -reagent-name-bananium = bananium -reagent-desc-bananium = A yellow radioactive organic solid. - reagent-name-zinc = zinc -reagent-desc-zinc = A silvery, brittle metal, often used in batteries to carry charge. \ No newline at end of file +reagent-desc-zinc = A silvery, brittle metal, often used in batteries to carry charge. diff --git a/Resources/Locale/en-US/server-updates/server-updates.ftl b/Resources/Locale/en-US/server-updates/server-updates.ftl index 72047432bb5..ae775c99314 100644 --- a/Resources/Locale/en-US/server-updates/server-updates.ftl +++ b/Resources/Locale/en-US/server-updates/server-updates.ftl @@ -1,2 +1,3 @@ server-updates-received = Update has been received, server will automatically restart for update at the end of this round. server-updates-shutdown = Server is shutting down for update and will automatically restart. +server-updates-shutdown-uptime = Server is shutting down for periodic cleanup and will automatically restart. diff --git a/Resources/Locale/en-US/shuttles/commands.ftl b/Resources/Locale/en-US/shuttles/commands.ftl new file mode 100644 index 00000000000..37583568e7c --- /dev/null +++ b/Resources/Locale/en-US/shuttles/commands.ftl @@ -0,0 +1,14 @@ +# FTLdiskburner +cmd-ftldisk-desc = Creates an FTL coordinates disk to sail to the map the given EntityID is/on +cmd-ftldisk-help = ftldisk [EntityID] + +cmd-ftldisk-no-transform = Entity {$destination} has no Transform Component! +cmd-ftldisk-no-map = Entity {$destination} has no map! +cmd-ftldisk-no-map-comp = Entity {$destination} is somehow on map {$map} with no map component. +cmd-ftldisk-map-not-init = Entity {$destination} is on map {$map} which is not initialized! Check it's safe to initialize, then initialize the map first or the players will be stuck in place! +cmd-ftldisk-map-paused = Entity {$desintation} is on map {$map} which is paused! Please unpause the map first or the players will be stuck in place. +cmd-ftldisk-planet = Entity {$desintation} is on planet map {$map} and will require an FTL point. It may already exist. +cmd-ftldisk-already-dest-not-enabled = Entity {$destination} is on map {$map} that already has an FTLDestinationComponent, but it is not Enabled! Set this manually for safety. +cmd-ftldisk-requires-ftl-point = Entity {$destination} is on map {$map} that requires a FTL point to travel to! It may already exist. + +cmd-ftldisk-hint = Map netID diff --git a/Resources/Locale/en-US/storage/components/secret-stash-component.ftl b/Resources/Locale/en-US/storage/components/secret-stash-component.ftl index 36892428070..16e575c0f13 100644 --- a/Resources/Locale/en-US/storage/components/secret-stash-component.ftl +++ b/Resources/Locale/en-US/storage/components/secret-stash-component.ftl @@ -22,3 +22,4 @@ comp-secret-stash-verb-open = Open ### Stash names secret-stash-plant = plant secret-stash-toilet = toilet cistern +secret-stash-plushie = plushie diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 06ab5564c9e..df0222e4651 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -450,7 +450,7 @@ uplink-cameraBug-desc = A portable device that allows you to view the station's uplink-combat-bakery-name = Combat Bakery Kit uplink-combat-bakery-desc = A kit of clandestine baked weapons. Contains a baguette sword, a pair of throwing croissants, and a syndicate microwave board for making more. Once the job is done, eat the evidence. -uplink-business-card-name = Syndicate business card. +uplink-business-card-name = Syndicate Business Card uplink-business-card-desc = A business card that you can give to someone to demonstrate your involvement in the syndicate or leave at the crime scene in order to make fun of the detective. You can buy no more than three of them. # Objectives # DeltaV, reverted upstream diff --git a/Resources/Maps/DeltaV/centcomm.yml b/Resources/Maps/DeltaV/centcomm.yml index bb0b3b505f8..6b6fead7565 100644 --- a/Resources/Maps/DeltaV/centcomm.yml +++ b/Resources/Maps/DeltaV/centcomm.yml @@ -19117,6 +19117,8 @@ entities: linkedPorts: 722: - CloningPodSender: CloningPodReceiver + 723: + - MedicalScannerSender: MedicalScannerReceiver - proto: ComputerComms entities: - uid: 652 diff --git a/Resources/Prototypes/Access/misc.yml b/Resources/Prototypes/Access/misc.yml index 417d2cdb028..4617797bb51 100644 --- a/Resources/Prototypes/Access/misc.yml +++ b/Resources/Prototypes/Access/misc.yml @@ -49,3 +49,4 @@ - Prosecutor # DeltaV - Add Prosecutor access - Clerk # DeltaV - Add Clerk access - Corpsman # DeltaV - Add Corpsman access + - Robotics # DeltaV: Robotics access diff --git a/Resources/Prototypes/Access/research.yml b/Resources/Prototypes/Access/research.yml index f0de2c93db4..70785ce89a2 100644 --- a/Resources/Prototypes/Access/research.yml +++ b/Resources/Prototypes/Access/research.yml @@ -12,3 +12,4 @@ - ResearchDirector - Research - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml + - Robotics # DeltaV: Robotics access diff --git a/Resources/Prototypes/Body/Organs/Animal/animal.yml b/Resources/Prototypes/Body/Organs/Animal/animal.yml index 8384e006df7..e59aad9da3f 100644 --- a/Resources/Prototypes/Body/Organs/Animal/animal.yml +++ b/Resources/Prototypes/Body/Organs/Animal/animal.yml @@ -60,6 +60,9 @@ reagents: - ReagentId: UncookedAnimalProteins Quantity: 5 + - type: Item + size: Small + heldPrefix: lungs - type: entity id: OrganAnimalStomach @@ -86,6 +89,9 @@ groups: - id: Food - id: Drink + - type: Item + size: Small + heldPrefix: stomach - type: entity id: OrganMouseStomach @@ -97,6 +103,9 @@ solutions: stomach: maxVol: 30 + - type: Item + size: Small + heldPrefix: stomach - type: entity id: OrganAnimalLiver @@ -113,6 +122,9 @@ groups: - id: Alcohol rateModifier: 0.1 + - type: Item + size: Small + heldPrefix: liver - type: entity id: OrganAnimalHeart @@ -130,6 +142,9 @@ - id: Medicine - id: Poison - id: Narcotic + - type: Item + size: Small + heldPrefix: heart - type: entity id: OrganAnimalKidneys @@ -146,3 +161,6 @@ maxReagents: 5 metabolizerTypes: [ Animal ] removeEmpty: true + - type: Item + size: Small + heldPrefix: kidneys diff --git a/Resources/Prototypes/Body/Organs/arachnid.yml b/Resources/Prototypes/Body/Organs/arachnid.yml index 29ca393d137..c7542ae1118 100644 --- a/Resources/Prototypes/Body/Organs/arachnid.yml +++ b/Resources/Prototypes/Body/Organs/arachnid.yml @@ -34,6 +34,9 @@ - type: Sprite sprite: Mobs/Species/Arachnid/organs.rsi state: stomach + - type: Item + size: Small + heldPrefix: stomach - type: Stomach updateInterval: 1.5 - type: SolutionContainerManager @@ -91,6 +94,9 @@ components: - type: Sprite state: heart-on + - type: Item + size: Small + heldPrefix: heart - type: Metabolizer updateInterval: 1.5 maxReagents: 2 @@ -107,6 +113,9 @@ description: "Pairing suggestion: chianti and fava beans." categories: [ HideSpawnMenu ] components: + - type: Item + size: Small + heldPrefix: liver - type: Sprite state: liver - type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol. @@ -129,6 +138,9 @@ - state: kidney-l - state: kidney-r # The kidneys just remove anything that doesn't currently have any metabolisms, as a stopgap. + - type: Item + size: Small + heldPrefix: kidneys - type: Metabolizer updateInterval: 1.5 maxReagents: 5 @@ -145,6 +157,9 @@ layers: - state: eyeball-l - state: eyeball-r + - type: Item + size: Small + heldPrefix: eyeballs - type: entity id: OrganArachnidTongue diff --git a/Resources/Prototypes/Body/Organs/diona.yml b/Resources/Prototypes/Body/Organs/diona.yml index e248355df2c..bf865a07fd9 100644 --- a/Resources/Prototypes/Body/Organs/diona.yml +++ b/Resources/Prototypes/Body/Organs/diona.yml @@ -29,8 +29,11 @@ id: OrganDionaBrain parent: [BaseDionaOrgan, OrganHumanBrain] name: brain - description: "The source of incredible, unending intelligence. Honk." + description: "The central hub of a diona's pseudo-neurological activity, its root-like tendrils search for its former body." components: + - type: Item + size: Small + heldPrefix: brain - type: Sprite state: brain - type: SolutionContainerManager @@ -64,7 +67,7 @@ id: OrganDionaStomach parent: BaseDionaOrgan name: stomach - description: "Gross. This is hard to stomach." + description: "The diona's equivalent of a stomach, it reeks of asparagus and vinegar." components: - type: Sprite state: stomach @@ -90,18 +93,21 @@ - id: Narcotic - id: Alcohol rateModifier: 0.1 + - type: Item + size: Small + heldPrefix: stomach - type: entity id: OrganDionaLungs parent: BaseDionaOrgan name: lungs - description: "Filters oxygen from an atmosphere, which is then sent into the bloodstream to be used as an electron carrier." + description: "A spongy mess of slimy, leaf-like structures. Capable of breathing both carbon dioxide and oxygen." components: - type: Sprite - sprite: Mobs/Species/Human/organs.rsi - layers: - - state: lung-l - - state: lung-r + state: lungs + - type: Item + size: Small + heldPrefix: lungs - type: Lung - type: Metabolizer removeEmpty: true diff --git a/Resources/Prototypes/Body/Organs/human.yml b/Resources/Prototypes/Body/Organs/human.yml index c67f4f6cd16..cb1492b8a6a 100644 --- a/Resources/Prototypes/Body/Organs/human.yml +++ b/Resources/Prototypes/Body/Organs/human.yml @@ -71,6 +71,9 @@ entries: Burger: Brain Taco: Brain + - type: Item + size: Small + heldPrefix: brain - type: entity id: OrganHumanEyes @@ -82,6 +85,9 @@ layers: - state: eyeball-l - state: eyeball-r + - type: Item + size: Small + heldPrefix: eyeballs - type: entity id: OrganHumanTongue @@ -122,6 +128,9 @@ layers: - state: lung-l - state: lung-r + - type: Item + size: Small + heldPrefix: lungs - type: Lung - type: Metabolizer removeEmpty: true @@ -164,6 +173,9 @@ - id: Medicine - id: Poison - id: Narcotic + - type: Item + size: Small + heldPrefix: heart - type: entity id: OrganHumanStomach @@ -173,6 +185,9 @@ components: - type: Sprite state: stomach + - type: Item + size: Small + heldPrefix: stomach - type: SolutionContainerManager solutions: stomach: @@ -202,6 +217,9 @@ components: - type: Sprite state: liver + - type: Item + size: Small + heldPrefix: liver - type: Metabolizer # The liver metabolizes certain chemicals only, like alcohol. maxReagents: 1 metabolizerTypes: [Human] @@ -219,6 +237,9 @@ layers: - state: kidney-l - state: kidney-r + - type: Item + size: Small + heldPrefix: kidneys # The kidneys just remove anything that doesn't currently have any metabolisms, as a stopgap. - type: Metabolizer maxReagents: 5 diff --git a/Resources/Prototypes/Body/Organs/slime.yml b/Resources/Prototypes/Body/Organs/slime.yml index 3da76c5d4aa..ca22d25423c 100644 --- a/Resources/Prototypes/Body/Organs/slime.yml +++ b/Resources/Prototypes/Body/Organs/slime.yml @@ -33,6 +33,9 @@ reagents: - ReagentId: Slime Quantity: 10 + - type: Item + size: Small + heldPrefix: brain - type: entity @@ -70,3 +73,6 @@ reagents: - ReagentId: UncookedAnimalProteins Quantity: 5 + - type: Item + size: Small + heldPrefix: lungs diff --git a/Resources/Prototypes/Body/Organs/vox.yml b/Resources/Prototypes/Body/Organs/vox.yml index 1b4d12116f8..70e07832712 100644 --- a/Resources/Prototypes/Body/Organs/vox.yml +++ b/Resources/Prototypes/Body/Organs/vox.yml @@ -1,9 +1,15 @@ - type: entity id: OrganVoxLungs parent: OrganHumanLungs + description: "The blue, anaerobic lungs of a vox, they intake nitrogen to breathe. Any form of gaseous oxygen is lethally toxic if breathed in." suffix: "vox" components: + - type: Sprite + sprite: Mobs/Species/Vox/organs.rsi - type: Metabolizer metabolizerTypes: [ Vox ] - type: Lung alert: LowNitrogen + - type: Item + size: Small + heldPrefix: lungs diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index e53579afccb..5329c957b5a 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -273,6 +273,7 @@ children: - !type:NestedSelector # DeltaV tableId: LockerFillResearchDirectorDeltaV + #- id: Intellicard # DeltaV: temporarily removed until AI is enabled - id: BoxEncryptionKeyScience - id: CircuitImprinterMachineCircuitboard - id: ClothingBeltUtilityFilled diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/tankdispenser.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/tankdispenser.yml index fce18024a7e..188aaf7641a 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/tankdispenser.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/tankdispenser.yml @@ -8,4 +8,5 @@ id: TankDispenserEngineeringInventory startingInventory: PlasmaTankFilled: 10 + NitrogenTankFilled: 10 OxygenTankFilled: 10 diff --git a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml index 9314de97914..46edd7bafef 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/advertisements.yml @@ -130,6 +130,12 @@ prefix: fat-extractor-fact- count: 6 +- type: localizedDataset + id: FirebotAd + values: + prefix: advertisement-firebot- + count: 4 + - type: localizedDataset id: GoodCleanFunAds values: diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index 5dced91b93f..93c6998469d 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -885,7 +885,7 @@ discountDownTo: Telecrystal: 2 cost: - Telecrystal: 4 + Telecrystal: 3 categories: - UplinkDisruption diff --git a/Resources/Prototypes/DeltaV/Access/epistemics.yml b/Resources/Prototypes/DeltaV/Access/epistemics.yml index 8ccee35e709..2803ccdbaa3 100644 --- a/Resources/Prototypes/DeltaV/Access/epistemics.yml +++ b/Resources/Prototypes/DeltaV/Access/epistemics.yml @@ -1,3 +1,7 @@ - type: accessLevel id: Mantis name: id-card-access-level-mantis # Custom access level for the Mantis so they can have their own locker and maybe doors + +- type: accessLevel + id: Robotics + name: id-card-access-level-robotics diff --git a/Resources/Prototypes/DeltaV/Access/misc.yml b/Resources/Prototypes/DeltaV/Access/misc.yml index 495e4cb456d..364b1ab4c2d 100644 --- a/Resources/Prototypes/DeltaV/Access/misc.yml +++ b/Resources/Prototypes/DeltaV/Access/misc.yml @@ -24,6 +24,7 @@ - Salvage - Cargo - Research + - Robotics # would be silly if they couldn't go to robo - Service - Maintenance - External diff --git a/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/jobs.yml b/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/jobs.yml index 4f671e14b28..50f08716086 100644 --- a/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/jobs.yml +++ b/Resources/Prototypes/DeltaV/Entities/Markers/Spawners/jobs.yml @@ -79,3 +79,16 @@ state: security - sprite: DeltaV/Mobs/Silicon/chassis.rsi state: security_e + +- type: entity + parent: SpawnPointJobBase + id: SpawnPointRoboticist + name: roboticist + components: + - type: SpawnPoint + job_id: Roboticist + - type: Sprite + layers: + - state: green + - sprite: DeltaV/Markers/jobs.rsi + state: roboticist diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml deleted file mode 100644 index ce4ed2aa7e0..00000000000 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Consumable/Food/Baked/pie.yml +++ /dev/null @@ -1,38 +0,0 @@ -- type: entity - name: pumpkin pie - parent: FoodPieBase - id: FoodPiePumpkin - description: A seasonal favorite, consisting mostly of pumpkin and a handful of spooky spices. - components: - - type: FlavorProfile - flavors: - - sweet - - pumpkin - - type: Sprite - layers: - - state: tin - - sprite: DeltaV/Objects/Consumable/Food/Baked/pie.rsi - state: pumpkin - - type: SliceableFood - slice: FoodPiePumpkinSlice - - type: Tag - tags: - - Fruit - - Pie - -- type: entity - name: slice of pumpkin pie - parent: FoodPieSliceBase - id: FoodPiePumpkinSlice - components: - - type: FlavorProfile - flavors: - - sweet - - pumpkin - - type: Sprite - sprite: DeltaV/Objects/Consumable/Food/Baked/pie.rsi - state: pumpkin-slice - - type: Tag - tags: - - Fruit -# Tastes like pie, pumpkin. diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/Electronics/door_access.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/Electronics/door_access.yml index ce4e63e30fb..6f7450cc8f5 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Devices/Electronics/door_access.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Devices/Electronics/door_access.yml @@ -125,3 +125,11 @@ components: - type: AccessReader access: [["Clerk"]] + +- type: entity + parent: DoorElectronics + id: DoorElectronicsRobotics + suffix: Robotics, Locked + components: + - type: AccessReader + access: [["Research"]] # Uses research access until the job is added diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/identification_cards.yml index e472a3ebf04..2a8b0661773 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Misc/identification_cards.yml @@ -60,7 +60,7 @@ name: visitor ID card components: - type: IdCard - jobTitle: Visitor + jobTitle: job-title-visitor - type: entity parent: PassengerIDCard @@ -68,7 +68,7 @@ name: tourist ID card components: - type: IdCard - jobTitle: Tourist + jobTitle: job-alt-title-tourist - type: entity parent: PassengerIDCard @@ -76,7 +76,7 @@ name: off-duty crew ID card components: - type: IdCard - jobTitle: Off-Duty Crew + jobTitle: job-alt-title-off-duty-crew - type: entity parent: PassengerIDCard @@ -84,7 +84,7 @@ name: student ID card components: - type: IdCard - jobTitle: Student + jobTitle: job-alt-title-student # Bartender @@ -94,7 +94,7 @@ name: mixologist ID card components: - type: IdCard - jobTitle: Mixologist + jobTitle: job-alt-title-mixologist # Chef @@ -104,7 +104,7 @@ name: baker ID card components: - type: IdCard - jobTitle: Baker + jobTitle: job-alt-title-baker - type: entity parent: ChefIDCard @@ -112,7 +112,7 @@ name: butcher ID card components: - type: IdCard - jobTitle: Butcher + jobTitle: job-alt-title-butcher - type: entity parent: ChefIDCard @@ -120,7 +120,7 @@ name: pizzaiolo ID card components: - type: IdCard - jobTitle: Pizzaiolo + jobTitle: job-alt-title-pizzaiolo # Medical Intern @@ -130,7 +130,7 @@ name: practical nurse ID card components: - type: IdCard - jobTitle: Practical Nurse + jobTitle: job-alt-title-practical-nurse - type: entity parent: MedicalInternIDCard @@ -138,7 +138,7 @@ name: resident ID card components: - type: IdCard - jobTitle: Resident + jobTitle: job-alt-title-resident # Medical Doctor @@ -148,7 +148,7 @@ name: clinician ID card components: - type: IdCard - jobTitle: Clinician + jobTitle: job-alt-title-clinician # Atmospheric Technician @@ -158,7 +158,7 @@ name: life support technician ID card components: - type: IdCard - jobTitle: Life Support Technician + jobTitle: job-alt-title-life-support - type: entity parent: AtmosIDCard @@ -166,7 +166,7 @@ name: plasma scientist ID card components: - type: IdCard - jobTitle: Plasma Scientist + jobTitle: job-alt-title-plasma-scientist # Station Engineer @@ -176,7 +176,7 @@ name: electrician ID card components: - type: IdCard - jobTitle: Electrician + jobTitle: job-alt-title-electrician - type: entity parent: EngineeringIDCard @@ -184,7 +184,7 @@ name: mechanic ID card components: - type: IdCard - jobTitle: Mechanic + jobTitle: job-alt-title-mechanic # Cargo Technician @@ -194,7 +194,7 @@ name: deck worker ID card components: - type: IdCard - jobTitle: Deck Worker + jobTitle: job-alt-title-deck-worker - type: entity parent: CargoIDCard @@ -202,7 +202,7 @@ name: inventory associate ID card components: - type: IdCard - jobTitle: Inventory Associate + jobTitle: job-alt-title-inventory-associate # Salvage Specialist @@ -212,7 +212,7 @@ name: prospector ID card components: - type: IdCard - jobTitle: Prospector + jobTitle: job-alt-title-prospector - type: entity parent: SalvageIDCard @@ -220,7 +220,7 @@ name: excavator ID card components: - type: IdCard - jobTitle: Excavator + jobTitle: job-alt-title-excavator # Scientist @@ -230,7 +230,7 @@ name: lab technician ID card components: - type: IdCard - jobTitle: Lab Technician + jobTitle: job-alt-title-lab-technician - type: entity parent: ResearchIDCard @@ -238,7 +238,7 @@ name: xenoarchaeologist ID card components: - type: IdCard - jobTitle: Xenoarchaeologist + jobTitle: job-alt-title-xenoarch - type: entity parent: ResearchIDCard @@ -260,7 +260,7 @@ name: jester ID card components: - type: IdCard - jobTitle: Jester + jobTitle: job-alt-title-jester - type: entity parent: ClownIDCard @@ -268,4 +268,4 @@ name: fool ID card components: - type: IdCard - jobTitle: Fool + jobTitle: job-alt-title-fool diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Robotics/borg_modules.yml index 9491cc6f329..ce70c9ac4c8 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -94,3 +94,17 @@ - type: ItemBorgModule items: - WeaponEnergyGunMiniRecharging + +# Syndicate modules +- type: entity + parent: BorgModuleMartyr + id: BorgModuleMartyrDud + name: unfinished martyr cyborg module + description: This unfinished module has a large space for an explosive payload, with "boom" helpfully scribbled under it. + components: + - type: ItemBorgModule + items: + - SelfDestructSeqDud + - type: Construction + node: start + defaultTarget: live diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/recruiter.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/recruiter.yml index eba50f59c03..d17abefed8c 100644 --- a/Resources/Prototypes/DeltaV/Entities/Objects/Specific/recruiter.yml +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Specific/recruiter.yml @@ -16,9 +16,8 @@ blacklist: components: - MindShield - mindBlacklist: - components: - - RecruiterRole # no silly goose + factionBlacklist: + - Syndicate # no silly goose - type: SolutionContainerManager solutions: blood: diff --git a/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Throwable/grenades.yml new file mode 100644 index 00000000000..418b1437082 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Objects/Weapons/Throwable/grenades.yml @@ -0,0 +1,9 @@ +# if you try to use an unfinished martyr module it does next to no damage, but still has a good scare +- type: entity + parent: SelfDestructSeq + id: SelfDestructSeqDud + components: + - type: Explosive + totalIntensity: 1 + canCreateVacuum: false + deleteAfterExplosion: false # prevent borg having an empty hand diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml index 753f7cec457..d07778081e9 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/access.yml @@ -154,6 +154,15 @@ - type: GridFill path: /Maps/Shuttles/DeltaV/sub_escape_pod.yml +- type: entity + parent: AirlockRobotics + id: AirlockRoboticsLocked + suffix: Robotics, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsRobotics ] + #Add airlocks from upstream roles - type: entity parent: AirlockServiceLocked @@ -360,6 +369,15 @@ containers: board: [ DoorElectronicsPsychologist ] +- type: entity + parent: AirlockRoboticsGlass + id: AirlockRoboticsGlassLocked + suffix: Robotics, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsRobotics ] + # Maintenance Hatches - type: entity parent: AirlockMaintRnDLocked @@ -471,6 +489,15 @@ containers: board: [ DoorElectronicsSecurityLawyer ] +- type: entity + parent: AirlockMaintRnDLocked + id: AirlockMaintRoboticsLocked + suffix: Robotics, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsRobotics ] + # Command-locked External airlocks. These don't exist upstream for some reason. - type: entity parent: AirlockExternal diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/airlocks.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/airlocks.yml index 93a8cec851a..12ec20ec951 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/airlocks.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Airlocks/airlocks.yml @@ -8,6 +8,12 @@ - type: PaintableAirlock department: Justice +- type: entity + parent: AirlockScience + id: AirlockRobotics + suffix: Robotics + # TODO: make a sprite for it + # Glass - type: entity @@ -20,3 +26,8 @@ - type: PaintableAirlock department: Justice +- type: entity + parent: AirlockScienceGlass + id: AirlockRoboticsGlass + suffix: Robotics + # TODO: make a sprite for it diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Shutter/blast_door.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Shutter/blast_door.yml index 30f718cf42b..c6046ec68ac 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Shutter/blast_door.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Shutter/blast_door.yml @@ -109,3 +109,19 @@ components: - type: AccessReader access: [["Command"], ["Armory"]] + +- type: entity + parent: BlastDoor + id: BlastDoorUnlinkedRobotics + suffix: Robotics + components: + - type: AccessReader + access: [["Robotics"]] + +- type: entity + parent: BlastDoorOpen + id: BlastDoorUnlinkedRoboticsOpen + suffix: Open, Robotics + components: + - type: AccessReader + access: [["Robotics"]] diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Windoors/windoor.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Windoors/windoor.yml index d770d620c68..3a144cdc202 100644 --- a/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Windoors/windoor.yml +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Doors/Windoors/windoor.yml @@ -79,6 +79,15 @@ containers: board: [ DoorElectronicsMantis ] +- type: entity + parent: WindoorSecure + id: WindoorSecureRoboticsLocked + suffix: Robotics, Locked + components: + - type: ContainerFill + containers: + board: [ DoorElectronicsRobotics ] + #Add windoors from upstream roles - type: entity parent: WindoorSecure diff --git a/Resources/Prototypes/DeltaV/Entities/Structures/Wallmounts/switch.yml b/Resources/Prototypes/DeltaV/Entities/Structures/Wallmounts/switch.yml new file mode 100644 index 00000000000..adb5f1f5b71 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Entities/Structures/Wallmounts/switch.yml @@ -0,0 +1,7 @@ +- type: entity + parent: LockableButton + suffix: Robotics + id: LockableButtonRobotics + components: + - type: AccessReader + access: [["Robotics"]] diff --git a/Resources/Prototypes/DeltaV/GameRules/events.yml b/Resources/Prototypes/DeltaV/GameRules/events.yml index 1a434e85063..ddf95808f88 100644 --- a/Resources/Prototypes/DeltaV/GameRules/events.yml +++ b/Resources/Prototypes/DeltaV/GameRules/events.yml @@ -116,9 +116,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: ListeningPostRole - prototype: ListeningPost + mindRoles: + - MindRoleListeningPost # Mid round antag spawns - type: entity @@ -160,11 +159,8 @@ min: 1 max: 1 pickPlayer: false - mindComponents: - - type: RoleBriefing - briefing: paradox-anomaly-role-briefing - - type: ParadoxAnomalyRole - prototype: ParadoxAnomaly + mindRoles: + - MindRoleParadoxAnomaly - type: entity parent: BaseMidRoundAntag @@ -195,6 +191,5 @@ - fake_human_last - type: EmitSoundOnSpawn # fell out of the ceiling sound: /Audio/Effects/clang.ogg - mindComponents: - - type: FugitiveRole - prototype: Fugitive + mindRoles: + - MindRoleFugitive diff --git a/Resources/Prototypes/DeltaV/GameRules/unknown_shuttles.yml b/Resources/Prototypes/DeltaV/GameRules/unknown_shuttles.yml index 3cf5087c948..2959bebcc0b 100644 --- a/Resources/Prototypes/DeltaV/GameRules/unknown_shuttles.yml +++ b/Resources/Prototypes/DeltaV/GameRules/unknown_shuttles.yml @@ -44,11 +44,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: RecruiterRole - prototype: Recruiter - - type: RoleBriefing - briefing: Find candidates, conduct interviews and seal the deal by having them sign with your special recruiter's pen. + mindRoles: + - MindRoleRecruiter - type: entity parent: BaseUnknownShuttleRule @@ -88,8 +85,5 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: SynthesisRole - prototype: SynthesisSpecialist - - type: RoleBriefing - briefing: You are Interdyne's Synthesis Specialist! Prescribe deadly medications, barter your goods, and make a killing. + mindRoles: + - MindRoleSynthesis diff --git a/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/glasses.yml b/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/glasses.yml new file mode 100644 index 00000000000..479b21d3af9 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/glasses.yml @@ -0,0 +1,17 @@ +- type: loadoutEffectGroup + id: CheapSunglassesTimer + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobMusician + time: 7200 # 2 hours of being a rockstar + +# Cheap Sunglasses +- type: loadout + id: GlassesCheapSunglasses + effects: + - !type:GroupLoadoutEffect + proto: CheapSunglassesTimer + equipment: + eyes: ClothingEyesGlassesCheapSunglasses diff --git a/Resources/Prototypes/DeltaV/Objectives/fugitive.yml b/Resources/Prototypes/DeltaV/Objectives/fugitive.yml index 7361e44f56d..d2c1ce56729 100644 --- a/Resources/Prototypes/DeltaV/Objectives/fugitive.yml +++ b/Resources/Prototypes/DeltaV/Objectives/fugitive.yml @@ -8,7 +8,7 @@ difficulty: 1 - type: RoleRequirement roles: - components: + mindRoles: - FugitiveRole - type: entity diff --git a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml index da193ea3b58..b929d5cfda0 100644 --- a/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml +++ b/Resources/Prototypes/DeltaV/Objectives/paradox_anomaly.yml @@ -8,7 +8,7 @@ issuer: objective-issuer-self - type: RoleRequirement roles: - components: + mindRoles: - ParadoxAnomalyRole # not using base kill/keep alive objectives since these intentionally conflict with eachother diff --git a/Resources/Prototypes/DeltaV/Objectives/recruiter.yml b/Resources/Prototypes/DeltaV/Objectives/recruiter.yml index d8760388c71..4a41862356a 100644 --- a/Resources/Prototypes/DeltaV/Objectives/recruiter.yml +++ b/Resources/Prototypes/DeltaV/Objectives/recruiter.yml @@ -7,7 +7,7 @@ difficulty: 0 # difficulty is unused - type: RoleRequirement roles: - components: + mindRoles: - RecruiterRole - type: entity diff --git a/Resources/Prototypes/DeltaV/Objectives/synthesis_specialist.yml b/Resources/Prototypes/DeltaV/Objectives/synthesis_specialist.yml index 033d268680a..cd00866e343 100644 --- a/Resources/Prototypes/DeltaV/Objectives/synthesis_specialist.yml +++ b/Resources/Prototypes/DeltaV/Objectives/synthesis_specialist.yml @@ -7,7 +7,7 @@ difficulty: 0 # difficulty is unused - type: RoleRequirement roles: - components: + mindRoles: - SynthesisRole - type: entity diff --git a/Resources/Prototypes/DeltaV/Recipes/Construction/Graphs/utilities/borg_modules.yml b/Resources/Prototypes/DeltaV/Recipes/Construction/Graphs/utilities/borg_modules.yml new file mode 100644 index 00000000000..deb3007a9e7 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Recipes/Construction/Graphs/utilities/borg_modules.yml @@ -0,0 +1,23 @@ +- type: constructionGraph + id: BorgModuleMartyr + start: start + graph: + - node: start + entity: BorgModuleMartyrDud + edges: + - to: live + steps: + - tag: ExplosivePayload + name: explosive payload + - tool: Screwing + doAfter: 2 + - node: live + entity: BorgModuleMartyr + edges: + - to: start + completed: + - !type:GivePrototype + prototype: ExplosivePayload + steps: + - tool: Prying + doAfter: 2 diff --git a/Resources/Prototypes/DeltaV/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/DeltaV/Recipes/Cooking/meal_recipes.yml index 01287c1016d..22d19ea2f95 100644 --- a/Resources/Prototypes/DeltaV/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/DeltaV/Recipes/Cooking/meal_recipes.yml @@ -20,16 +20,6 @@ FoodEgg: 3 FoodButter: 1 -- type: microwaveMealRecipe - id: RecipePumpkinPie - name: pumpkin pie - result: FoodPiePumpkin - time: 15 - solids: - FoodDoughPie: 1 - FoodPumpkin: 1 - FoodPlateTin: 1 - - type: microwaveMealRecipe id: RecipeBlueTomatoSoup name: blue tomato soup recipe diff --git a/Resources/Prototypes/DeltaV/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/DeltaV/Roles/MindRoles/mind_roles.yml new file mode 100644 index 00000000000..78a30855d08 --- /dev/null +++ b/Resources/Prototypes/DeltaV/Roles/MindRoles/mind_roles.yml @@ -0,0 +1,55 @@ +- type: entity + parent: BaseMindRoleAntag + id: MindRoleListeningPost + name: Listening Post Role + components: + - type: MindRole + antagPrototype: ListeningPost + exclusiveAntag: true + - type: ListeningPostRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleParadoxAnomaly + name: Paradox Anomaly Role + components: + - type: MindRole + antagPrototype: ParadoxAnomaly + exclusiveAntag: true + - type: ParadoxAnomalyRole + - type: RoleBriefing + briefing: paradox-anomaly-role-briefing + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleFugitive + name: Fugitive Role + components: + - type: MindRole + antagPrototype: Fugitive + exclusiveAntag: true + - type: FugitiveRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleRecruiter + name: Recruiter Role + components: + - type: MindRole + antagPrototype: Recruiter + exclusiveAntag: true + - type: RecruiterRole + - type: RoleBriefing + briefing: recruiter-role-briefing + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleSynthesis + name: Synthesis Specialist Role + components: + - type: MindRole + antagPrototype: SynthesisSpecialist + exclusiveAntag: true + - type: SynthesisRole + - type: RoleBriefing + briefing: synthesis-role-briefing diff --git a/Resources/Prototypes/DeltaV/tags.yml b/Resources/Prototypes/DeltaV/tags.yml index 90d59dcd26a..212ce68c672 100644 --- a/Resources/Prototypes/DeltaV/tags.yml +++ b/Resources/Prototypes/DeltaV/tags.yml @@ -48,6 +48,9 @@ - type: Tag id: DockShipyard +- type: Tag + id: ExplosivePayload + - type: Tag id: ForensicBeltEquip diff --git a/Resources/Prototypes/Entities/Clothing/Multiple/towel.yml b/Resources/Prototypes/Entities/Clothing/Multiple/towel.yml new file mode 100644 index 00000000000..d3f7dc480ca --- /dev/null +++ b/Resources/Prototypes/Entities/Clothing/Multiple/towel.yml @@ -0,0 +1,764 @@ +- type: entity + id: BaseTowel + name: base towel + abstract: true + description: If you want to survive out here, you gotta know where your towel is. + parent: [ UnsensoredClothingUniformBase, ClothingHeadBase, ClothingBeltBase ] + components: + - type: Sprite + sprite: Clothing/Multiple/towel.rsi + - type: Clothing + sprite: Clothing/Multiple/towel.rsi + slots: + - BELT + - INNERCLOTHING + - HEAD + femaleMask: UniformTop + equipSound: + unequipSound: + - type: Spillable + solution: absorbed + - type: Absorbent + pickupAmount: 15 + - type: SolutionContainerManager + solutions: + food: + maxVol: 30 + reagents: + - ReagentId: Fiber + Quantity: 30 + absorbed: + maxVol: 30 + - type: Fiber + fiberColor: fibers-white + - type: DnaSubstanceTrace + - type: Item + size: Small + +- type: entity + id: TowelColorWhite + name: white towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#EAE8E8" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#EAE8E8" + right: + - state: inhand-right + color: "#EAE8E8" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#EAE8E8" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#EAE8E8" + belt: + - state: equipped-BELT + color: "#EAE8E8" + - type: Fiber + fiberColor: fibers-white + +- type: entity + id: TowelColorPurple + name: purple towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#9C0DE1" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#9C0DE1" + right: + - state: inhand-right + color: "#9C0DE1" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#9C0DE1" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#9C0DE1" + belt: + - state: equipped-BELT + color: "#9C0DE1" + - type: Fiber + fiberColor: fibers-purple + +- type: entity + id: TowelColorRed + name: red towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#940000" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#940000" + right: + - state: inhand-right + color: "#940000" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#940000" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#940000" + belt: + - state: equipped-BELT + color: "#940000" + - type: Fiber + fiberColor: fibers-red + +- type: entity + id: TowelColorBlue + name: blue towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#0089EF" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#0089EF" + right: + - state: inhand-right + color: "#0089EF" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#0089EF" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#0089EF" + belt: + - state: equipped-BELT + color: "#0089EF" + - type: Fiber + fiberColor: fibers-blue + +- type: entity + id: TowelColorDarkBlue + name: dark blue towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#3285ba" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#3285ba" + right: + - state: inhand-right + color: "#3285ba" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#3285ba" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#3285ba" + belt: + - state: equipped-BELT + color: "#3285ba" + - type: Fiber + fiberColor: fibers-blue + +- type: entity + id: TowelColorLightBlue + name: light blue towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#58abcc" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#58abcc" + right: + - state: inhand-right + color: "#58abcc" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#58abcc" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#58abcc" + belt: + - state: equipped-BELT + color: "#58abcc" + - type: Fiber + fiberColor: fibers-blue + +- type: entity + id: TowelColorTeal + name: teal towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#3CB57C" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#3CB57C" + right: + - state: inhand-right + color: "#3CB57C" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#3CB57C" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#3CB57C" + belt: + - state: equipped-BELT + color: "#3CB57C" + - type: Fiber + fiberColor: fibers-teal + +- type: entity + id: TowelColorBrown + name: brown towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#723A02" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#723A02" + right: + - state: inhand-right + color: "#723A02" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#723A02" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#723A02" + belt: + - state: equipped-BELT + color: "#723A02" + - type: Fiber + fiberColor: fibers-brown + +- type: entity + id: TowelColorLightBrown + name: light brown towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#c59431" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#c59431" + right: + - state: inhand-right + color: "#c59431" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#c59431" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#c59431" + belt: + - state: equipped-BELT + color: "#c59431" + - type: Fiber + fiberColor: fibers-brown + +- type: entity + id: TowelColorGray + name: gray towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#999999" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#999999" + right: + - state: inhand-right + color: "#999999" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#999999" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#999999" + belt: + - state: equipped-BELT + color: "#999999" + - type: Fiber + fiberColor: fibers-grey + +- type: entity + id: TowelColorGreen + name: green towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#5ABF2F" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#5ABF2F" + right: + - state: inhand-right + color: "#5ABF2F" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#5ABF2F" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#5ABF2F" + belt: + - state: equipped-BELT + color: "#5ABF2F" + - type: Fiber + fiberColor: fibers-green + +- type: entity + id: TowelColorDarkGreen + name: dark green towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#79CC26" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#79CC26" + right: + - state: inhand-right + color: "#79CC26" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#79CC26" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#79CC26" + belt: + - state: equipped-BELT + color: "#79CC26" + - type: Fiber + fiberColor: fibers-green + +- type: entity + id: TowelColorGold + name: gold towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#F7C430" + - state: iconstripe + color: "#535353" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#F7C430" + right: + - state: inhand-right + color: "#F7C430" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#F7C430" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#F7C430" + belt: + - state: equipped-BELT + color: "#F7C430" + - type: Fiber + fiberColor: fibers-gold + +- type: entity + id: TowelColorOrange + name: orange towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#EF8100" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#EF8100" + right: + - state: inhand-right + color: "#EF8100" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#EF8100" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#EF8100" + belt: + - state: equipped-BELT + color: "#EF8100" + - type: Fiber + fiberColor: fibers-orange + +- type: entity + id: TowelColorBlack + name: black towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#535353" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#535353" + right: + - state: inhand-right + color: "#535353" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#535353" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#535353" + belt: + - state: equipped-BELT + color: "#535353" + - type: Fiber + fiberColor: fibers-black + +- type: entity + id: TowelColorPink + name: pink towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#ffa69b" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#ffa69b" + right: + - state: inhand-right + color: "#ffa69b" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#ffa69b" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#ffa69b" + belt: + - state: equipped-BELT + color: "#ffa69b" + - type: Fiber + fiberColor: fibers-pink + +- type: entity + id: TowelColorYellow + name: yellow towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#ffe14d" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#ffe14d" + right: + - state: inhand-right + color: "#ffe14d" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#ffe14d" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#ffe14d" + belt: + - state: equipped-BELT + color: "#ffe14d" + - type: Fiber + fiberColor: fibers-yellow + +- type: entity + id: TowelColorMaroon + name: maroon towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#cc295f" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#cc295f" + right: + - state: inhand-right + color: "#cc295f" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#cc295f" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#cc295f" + belt: + - state: equipped-BELT + color: "#cc295f" + - type: Fiber + fiberColor: fibers-maroon + +- type: entity + id: TowelColorSilver + name: silver towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#d0d0d0" + - state: iconstripe + color: "#F7C430" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#d0d0d0" + right: + - state: inhand-right + color: "#d0d0d0" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#d0d0d0" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#d0d0d0" + belt: + - state: equipped-BELT + color: "#d0d0d0" + - type: Fiber + fiberColor: fibers-silver + +- type: entity + id: TowelColorMime + name: silent towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#EAE8E8" + - state: iconstripe + color: "#535353" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#EAE8E8" + right: + - state: inhand-right + color: "#EAE8E8" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#EAE8E8" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#EAE8E8" + belt: + - state: equipped-BELT + color: "#EAE8E8" + - type: Fiber + fiberColor: fibers-white + +- type: entity + id: TowelColorNT + name: NanoTrasen brand towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#004787" + - state: iconstripe + color: "#EAE8E8" + - state: NTmono + color: "#EAE8E8" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#004787" + right: + - state: inhand-right + color: "#004787" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#004787" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#004787" + belt: + - state: equipped-BELT + color: "#004787" + - type: Fiber + fiberColor: fibers-regal-blue + +- type: entity + id: TowelColorCentcom + name: centcom towel + parent: BaseTowel + components: + - type: Sprite + layers: + - state: icon + color: "#29722e" + - state: iconstripe + color: "#F7C430" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#29722e" + right: + - state: inhand-right + color: "#29722e" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#29722e" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#29722e" + belt: + - state: equipped-BELT + color: "#29722e" + - type: Fiber + fiberColor: fibers-green + +- type: entity + id: TowelColorSyndicate + name: syndicate towel + parent: [ BaseTowel, BaseSyndicateContraband ] + components: + - type: Sprite + layers: + - state: icon + color: "#535353" + - state: iconstripe + color: "#940000" + - type: Item + inhandVisuals: + left: + - state: inhand-left + color: "#535353" + right: + - state: inhand-right + color: "#535353" + - type: Clothing + clothingVisuals: + head: + - state: equipped-HELMET + color: "#535353" + jumpsuit: + - state: equipped-INNERCLOTHING + color: "#535353" + belt: + - state: equipped-BELT + color: "#535353" + - type: Fiber + fiberColor: fibers-black + \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml index 2c4d165cbe4..4417fae13d2 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml @@ -197,6 +197,9 @@ name: power-cell-slot-component-slot-name-default startingItem: PowerCellSmall disableEject: true + whitelist: + tags: + - PowerCell - type: entity parent: ClothingOuterBase diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml index cee5b039130..d31b3cba176 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml @@ -204,6 +204,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepSpurs + params: + variation: 0.09 - type: entity parent: ClothingShoesBootsCowboyBrown diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml index 0838ecd914e..c61cab1294f 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/specific.yml @@ -41,6 +41,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepClown + params: + variation: 0.17 # for H.O.N.K. construction - type: Tag tags: @@ -60,6 +62,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepSlip + params: + variation: 0.10 - type: Construction graph: BananaClownShoes node: shoes @@ -80,6 +84,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepClown + params: + variation: 0.17 - type: PointLight enabled: true radius: 3 @@ -216,6 +222,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepJester + params: + variation: 0.07 - type: entity parent: ClothingShoesClown @@ -275,3 +283,5 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepSkates + params: + variation: 0.08 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index e3a0a96417c..2ef16c6ec18 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -290,6 +290,7 @@ - id: ResearchDisk5000 - id: PetCarrier - id: DrinkMopwataBottleRandom + #- id: SpectralLocator # DeltaV: no ghost locator - id: LidSalami weight: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml index f6e27c3d18b..a9852a404b5 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/posters.yml @@ -154,6 +154,12 @@ - PosterLegitPeriodicTable - PosterLegitRenault - PosterLegitNTTGC + - PosterLegitSafetyMothDelam + - PosterLegitSafetyMothEpi + - PosterLegitSafetyMothPiping + - PosterLegitSafetyMothMeth + - PosterLegitSafetyMothHardhat + - PosterLegitSafetyMothSSD - PosterLegitDejaVu # Nyanotrasen Poster, see Resources/Prototypes/Nyanotrasen/Entities/Structures/Wallmount/Signs/posters.yml - PosterLegitDontPanic # Nyanotrasen Poster, see Resources/Prototypes/Nyanotrasen/Entities/Structures/Wallmount/Signs/posters.yml - PosterLegitBarDrinks # Nyanotrasen Poster, see Resources/Prototypes/Nyanotrasen/Entities/Structures/Wallmount/Signs/posters.yml diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 3ba8462b185..6808e440419 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1394,8 +1394,7 @@ components: # make the player a traitor once its taken - type: AutoTraitor - giveUplink: false - giveObjectives: false + profile: TraitorReinforcement - type: entity id: MobMonkeySyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink @@ -1419,11 +1418,14 @@ - type: Speech speechSounds: Lizard speechVerb: Reptilian + allowedEmotes: ['Thump'] - type: Vocal sounds: Male: MaleReptilian Female: FemaleReptilian Unsexed: MaleReptilian + - type: BodyEmotes + soundsId: ReptilianBodyEmotes - type: TypingIndicator proto: lizard - type: InteractionPopup @@ -1553,8 +1555,7 @@ components: # make the player a traitor once its taken - type: AutoTraitor - giveUplink: false - giveObjectives: false + profile: TraitorReinforcement - type: entity id: MobKoboldSyndicateAgentNukeops # Reinforcement exclusive to nukeops uplink diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 7733f0a4be6..3308baf4d74 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -791,6 +791,7 @@ name: ghost-role-information-smile-name description: ghost-role-information-smile-description rules: ghost-role-information-nonantagonist-rules + raffle: null - type: Grammar attributes: proper: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index f39f2e8df95..54acea2bfe3 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -8,6 +8,7 @@ components: - type: Input context: "ghost" + - type: Spectral - type: MovementSpeedModifier baseWalkSpeed: 6 baseSprintSpeed: 6 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index e13ee1c3cbc..12a84a178c5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -120,8 +120,6 @@ - type: Construction graph: FireBot node: bot - - type: SentienceTarget - flavorKind: station-event-random-sentience-flavor-mechanical - type: HTN rootTask: task: FirebotCompound @@ -148,12 +146,14 @@ vaporSpread: 90 sprayVelocity: 3.0 - type: UseDelay - delay: 4 + delay: 2 - type: InteractionPopup interactSuccessString: petting-success-firebot interactFailureString: petting-failure-firebot interactSuccessSound: path: /Audio/Ambience/Objects/periodic_beep.ogg + - type: Advertise + pack: FirebotAd - type: entity parent: MobSiliconBase @@ -166,6 +166,8 @@ maxInterval: 12 sound: collection: BikeHorn + params: + variation: 0.125 - type: Sprite sprite: Mobs/Silicon/Bots/honkbot.rsi state: honkbot @@ -210,6 +212,8 @@ interactFailureString: petting-failure-honkbot interactSuccessSound: path: /Audio/Items/bikehorn.ogg + params: + variation: 0.125 - type: entity parent: MobHonkBot @@ -220,6 +224,8 @@ - type: SpamEmitSound sound: collection: CluwneHorn + params: + variation: 0.125 - type: Sprite state: jonkbot - type: Construction @@ -235,6 +241,8 @@ - type: InteractionPopup interactSuccessSound: path: /Audio/Items/brokenbikehorn.ogg + params: + variation: 0.125 - type: Vocal sounds: Unsexed: Cluwne diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 503089a0c24..dc2011b804b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -119,6 +119,7 @@ molsPerSecondPerUnitMass: 0.0005 - type: Speech speechVerb: LargeMob + speechSounds: Xenonid # DeltaV: Use RMC's speech sounds for sentient xenos - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. chance: -2 - type: Psionic #Nyano - Summary: makes psionic by default. diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index b3e8fa97c9a..fc4f9efdd52 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -1,5 +1,5 @@ - type: entity - parent: [MobObserver, InventoryBase] + parent: [MobObserverBase, InventoryBase] id: AdminObserver name: admin observer categories: [ HideSpawnMenu ] @@ -18,6 +18,7 @@ - CanPilot - BypassInteractionRangeChecks - BypassDropChecks + - NoConsoleSound - type: Input context: "aghost" - type: Ghost @@ -102,7 +103,7 @@ - type: entity id: ActionAGhostShowSolar name: Solar Control Interface - description: View a solar control interface. + description: View a Solar Control Interface. components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -114,7 +115,7 @@ - type: entity id: ActionAGhostShowCommunications name: Communications Interface - description: View a communications interface. + description: View a Communications Interface. components: - type: InstantAction icon: { sprite: Interface/Actions/actions_ai.rsi, state: comms_console } @@ -126,7 +127,7 @@ - type: entity id: ActionAGhostShowRadar name: Mass Scanner Interface - description: View a mass scanner interface. + description: View a Mass Scanner Interface. components: - type: InstantAction icon: { sprite: Interface/Actions/actions_ai.rsi, state: mass_scanner } @@ -138,7 +139,7 @@ - type: entity id: ActionAGhostShowCargo name: Cargo Ordering Interface - description: View a cargo ordering interface. + description: View a Cargo Ordering Interface. components: - type: InstantAction icon: { sprite: Structures/Machines/parts.rsi, state: box_0 } @@ -150,7 +151,7 @@ - type: entity id: ActionAGhostShowCrewMonitoring name: Crew Monitoring Interface - description: View a crew monitoring interface. + description: View a Crew Monitoring Interface. components: - type: InstantAction icon: { sprite: Interface/Actions/actions_ai.rsi, state: crew_monitor } @@ -162,7 +163,7 @@ - type: entity id: ActionAGhostShowStationRecords name: Station Records Interface - description: View a station records Interface. + description: View a Station Records Interface. components: - type: InstantAction icon: { sprite: Interface/Actions/actions_ai.rsi, state: station_records } diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 35cb54b0b1b..c29be592687 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -31,8 +31,7 @@ components: # make the player a traitor once its taken - type: AutoTraitor - giveUplink: false - giveObjectives: false + profile: TraitorReinforcement - type: entity parent: MobHumanSyndicateAgent diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 16d4101afc4..9208d4c8a98 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -28,11 +28,13 @@ layer: - GhostImpassable +# shared parent between aghosts, replay spectators and normal observers - type: entity parent: - Incorporeal - BaseMob - id: MobObserver + id: MobObserverBase + abstract: true name: observer description: Boo! categories: [ HideSpawnMenu ] @@ -66,6 +68,13 @@ tags: - BypassInteractionRangeChecks +# proto for player ghosts specifically +- type: entity + parent: MobObserverBase + id: MobObserver + components: + - type: Spectral + - type: entity id: ActionGhostBoo name: Boo! diff --git a/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml b/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml index ffbc46e94c3..8a01de40809 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/replay_observer.yml @@ -1,5 +1,5 @@ - type: entity - parent: MobObserver + parent: MobObserverBase id: ReplayObserver categories: [ HideSpawnMenu ] save: false diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 15878a4017d..9f7e206c249 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -71,6 +71,26 @@ title: comms-console-announcement-title-station-ai color: "#2ed2fd" +- type: entity + id: AiHeldIntellicard + description: Components added / removed from an entity that gets inserted into an Intellicard. + categories: [ HideSpawnMenu ] + components: + - type: IntrinsicRadioReceiver + - type: IntrinsicRadioTransmitter + channels: + - Binary + - type: ActiveRadio + channels: + - Binary + - Common + - type: ActionGrant + actions: + - ActionAIViewLaws + - type: UserInterface + interfaces: + enum.SiliconLawsUiKey.Key: + type: SiliconLawBoundUserInterface # Ai - type: entity @@ -82,7 +102,8 @@ - type: StationAiHolder slot: name: station-ai-mind-slot - locked: true + locked: false + disableEject: true whitelist: tags: - StationAi @@ -239,6 +260,7 @@ state: std_mod - type: SiliconLawProvider laws: AntimovLawset + lawUploadSound: /Audio/Ambience/Antag/silicon_lawboard_antimov.ogg - type: entity id: NutimovCircuitBoard @@ -255,13 +277,16 @@ # Items - type: entity id: Intellicard - name: Intellicard + name: intellicard description: A storage device for AIs. parent: - BaseItem - AiHolder suffix: Empty components: + - type: ContainerComp + proto: AiHeldIntellicard + container: station_ai_mind_slot - type: Sprite sprite: Objects/Devices/ai_card.rsi layers: @@ -360,7 +385,6 @@ enum.SiliconLawsUiKey.Key: type: SiliconLawBoundUserInterface - type: ComplexInteraction - - type: DoorRemote - type: Actions - type: Access groups: @@ -373,6 +397,7 @@ tags: - HideContextMenu - StationAi + - NoConsoleSound # Hologram projection that the AI's eye tracks. - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index d406f0bf2e2..0c928aeecf1 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -31,6 +31,7 @@ - type: Speech speechSounds: Lizard speechVerb: Reptilian + allowedEmotes: ['Thump'] - type: TypingIndicator proto: lizard - type: Vocal @@ -38,6 +39,8 @@ Male: MaleReptilian Female: FemaleReptilian Unsexed: MaleReptilian + - type: BodyEmotes + soundsId: ReptilianBodyEmotes - type: Damageable damageContainer: Biological damageModifierSet: Scale diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml index 448ef0868d6..0b574cdd720 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/cake.yml @@ -135,6 +135,10 @@ Quantity: 5 - ReagentId: Vitamin Quantity: 5 + - type: Tag + tags: + - Cake + - Vegetable - type: entity name: slice of carrot cake @@ -155,6 +159,11 @@ Quantity: 1 - ReagentId: Vitamin Quantity: 1 + - type: Tag + tags: + - Cake + - Vegetable + - Slice # Tastes like sweetness, cake, carrot. @@ -168,7 +177,10 @@ state: brain - type: SliceableFood slice: FoodCakeBrainSlice - + - type: Tag + tags: + - Cake + - Meat - type: entity name: slice of brain cake @@ -178,6 +190,11 @@ components: - type: Sprite state: brain-slice + - type: Tag + tags: + - Cake + - Meat + - Slice # Tastes like sweetness, cake, brains. - type: entity @@ -429,6 +446,10 @@ state: slime - type: SliceableFood slice: FoodCakeSlimeSlice + - type: Tag + tags: + - Cake + - Meat - type: entity name: slice of slime cake @@ -438,6 +459,11 @@ components: - type: Sprite state: slime-slice + - type: Tag + tags: + - Cake + - Meat + - Slice # Tastes like sweetness, cake, slime. - type: entity @@ -498,6 +524,10 @@ state: christmas - type: SliceableFood slice: FoodCakeChristmasSlice + - type: Tag + tags: + - Cake + - Fruit - type: entity name: slice of christmas cake @@ -506,6 +536,11 @@ components: - type: Sprite state: christmas-slice + - type: Tag + tags: + - Cake + - Fruit + - Slice # Tastes like sweetness, cake, christmas. - type: entity @@ -634,7 +669,7 @@ Quantity: 20 - ReagentId: Vitamin Quantity: 5 - - ReagentId: Omnizine #This is a really rare cake with healing stuff and we don't have any of its chems yet + - ReagentId: PolypyryliumOligomers Quantity: 15 - type: entity @@ -654,7 +689,7 @@ Quantity: 4 - ReagentId: Vitamin Quantity: 1 - - ReagentId: Omnizine + - ReagentId: PolypyryliumOligomers Quantity: 3 # Tastes like sweetness, cake, jam. diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml index c9ac4833431..b86a4201e8b 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml @@ -23,7 +23,7 @@ - ReagentId: Vitamin Quantity: 5 - type: Food #All pies here made with a pie tin; unless you're some kind of savage, you're probably not destroying this when you eat or slice the pie! - trash: + trash: - FoodPlateTin - type: SliceableFood count: 4 @@ -317,6 +317,43 @@ - Slice # Tastes like pie, meat. +- type: entity + name: pumpkin pie + parent: FoodPieBase + id: FoodPiePumpkin + description: Someone should turn this into a latte! + components: + - type: FlavorProfile + flavors: + - sweet + - pumpkin + - type: Sprite + layers: + - state: tin + - state: pumpkin + - type: SliceableFood + slice: FoodPiePumpkinSlice + - type: Tag + tags: + - Pie + +- type: entity + name: slice of pumpkin pie + parent: FoodPieSliceBase + id: FoodPiePumpkinSlice + components: + - type: FlavorProfile + flavors: + - sweet + - pumpkin + - type: Sprite + layers: + - state: pumpkin-slice + - type: Tag + tags: + - Pie + - Slice + - type: entity name: xeno pie parent: FoodPieBase diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml index 000f21db3f4..612f7da0472 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meat.yml @@ -525,6 +525,7 @@ - type: Tag tags: - Raw + - Meat - type: Sprite state: snake - type: SolutionContainerManager diff --git a/Resources/Prototypes/Entities/Objects/Decoration/present.yml b/Resources/Prototypes/Entities/Objects/Decoration/present.yml index 8dfc79c6279..d68b04e0a1e 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/Devices/payload.yml b/Resources/Prototypes/Entities/Objects/Devices/payload.yml index 160f0c58e79..44a816a18fa 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/payload.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/payload.yml @@ -48,6 +48,10 @@ behaviors: - !type:DoActsBehavior acts: [ "Destruction" ] + - type: Tag # DeltaV: add ExplosivePayload tag to it + tags: + - ExplosivePayload + - Payload - type: entity name: chemical payload diff --git a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_percussion.yml b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_percussion.yml index a6eaa128d60..ec0219377c8 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_percussion.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/Instruments/instruments_percussion.yml @@ -54,6 +54,7 @@ instrumentList: "Aah": {52: 0} "Ooh": {53: 0} + # "Kweh": {4: 1} #Delta-V change, this stupid instrument fucks with literally every single midi known to mankind. Uncomment when whoever added this makes this not fuck with every midi. - Solaris - type: Sprite sprite: Objects/Fun/Instruments/microphone.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index ff662c2186a..b4af7f010e0 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -69,6 +69,10 @@ Searching: { state: pai-searching-overlay } On: { state: pai-on-overlay } - type: StationMap + - type: ChangeVoiceInContainer + whitelist: + components: + - SecretStash - type: entity parent: [ PersonalAI, BaseSyndicateContraband] diff --git a/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml b/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml new file mode 100644 index 00000000000..930b9c4926d --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Fun/spectral_locator.yml @@ -0,0 +1,58 @@ +- type: entity + id: SpectralLocatorUnpowered + parent: BaseItem + name: spectral locator + description: Appears to be a modified anomaly locator. Seems very old. + suffix: Unpowered + components: + - type: Sprite + sprite: Objects/Fun/spectrallocator.rsi + layers: + - state: icon + - state: screen + shader: unshaded + visible: false + map: ["enum.ToggleVisuals.Layer"] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ToggleVisuals.Toggled: + enum.ToggleVisuals.Layer: + True: { visible: true } + False: { visible: false } + - type: ItemToggle + - type: ProximityBeeper + - type: ProximityDetector + range: 12 + criteria: + components: + - Spectral # reacts to AI eye, intentional + - type: Beeper + isMuted: true + minBeepInterval: 0.25 + maxBeepInterval: 0.5 + beepSound: + path: "/Audio/Items/locator_beep.ogg" + params: + maxDistance: 1 + volume: -8 + +- type: entity + id: SpectralLocator + parent: [ SpectralLocatorUnpowered, PowerCellSlotSmallItem ] + suffix: Powered + components: + - type: PowerCellDraw + drawRate: 1 + useRate: 0 + - type: ToggleCellDraw + +- type: entity + id: SpectralLocatorEmpty + parent: SpectralLocator + suffix: Empty + components: + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 65ba53fcccf..73678920339 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -52,6 +52,24 @@ reagents: - ReagentId: Fiber Quantity: 10 + - type: ContainerContainer + containers: + stash: !type:ContainerSlot {} + - type: Speech + speechVerb: Default # for pais (In the secret stash) + - type: SecretStash + secretStashName: secret-stash-plushie + blacklist: + components: + - SecretStash # Prevents being able to insert plushies inside each other (infinite plush)! + - NukeDisk # Could confuse the nukies if they don't know that plushies have a stash. + tags: + - QuantumSpinInverter # It will cause issues with the grinder... + - FakeNukeDisk # So you can't tell if the nuke disk is real or fake depending on if it can be inserted or not. + - type: ToolOpenable + openToolQualityNeeded: Slicing + closeToolQualityNeeded: Slicing # Should probably be stitching or something if that gets added + name: secret-stash-plushie - type: entity parent: BasePlushie @@ -323,6 +341,25 @@ equippedPrefix: lizard slots: - HEAD + - 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 diff --git a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml index 90e975b93ef..e1fc3fa5b6f 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/dat_fukken_disk.yml @@ -35,3 +35,6 @@ state: icon - type: StaticPrice price: 1 # it's worth even less than normal items. Perfection. + - type: Tag + tags: + - FakeNukeDisk diff --git a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml index b306da64421..b0c586fc753 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/fire_extinguisher.yml @@ -97,7 +97,7 @@ name: pocket fire extinguisher parent: FireExtinguisher id: FireExtinguisherMini - description: A light and compact fibreglass-framed model fire extinguisher. It holds less water then its bigger brother. + description: A light and compact fibreglass-framed model fire extinguisher. It holds less water than its bigger brother. components: - type: Sprite sprite: Objects/Misc/fire_extinguisher_mini.rsi diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index 73693a9e068..802b5c79b58 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -445,7 +445,7 @@ - state: default - state: idvisitor - type: IdCard - jobTitle: Visitor + jobTitle: job-title-visitor jobIcon: JobIconVisitor - type: PresetIdCard job: Visitor @@ -745,7 +745,7 @@ - state: default - state: idcluwne - type: IdCard - jobTitle: Cluwne + jobTitle: job-title-cluwne - type: Unremoveable - type: entity @@ -758,7 +758,7 @@ - state: default - state: idseniorengineer - type: IdCard # DeltaV - Change senior job titles - jobTitle: Senior Engineer + jobTitle: job-alt-title-senior-engineer jobIcon: JobIconSeniorEngineer - type: entity @@ -771,7 +771,7 @@ - state: default - state: idseniorresearcher - type: IdCard # DeltaV - Change senior job titles - jobTitle: Senior Researcher + jobTitle: job-alt-title-senior-researcher jobIcon: JobIconSeniorResearcher - type: entity @@ -784,7 +784,7 @@ - state: default - state: idseniorphysician - type: IdCard # DeltaV - Change senior job titles - jobTitle: Senior Physician + jobTitle: job-alt-title-senior-physician jobIcon: JobIconSeniorPhysician - type: entity @@ -797,7 +797,7 @@ - state: default - state: idseniorofficer - type: IdCard # DeltaV - Change senior job titles - jobTitle: Senior Officer + jobTitle: job-alt-title-senior-officer jobIcon: JobIconSeniorOfficer - type: entity @@ -817,7 +817,7 @@ - type: Item heldPrefix: green - type: IdCard - jobTitle: Universal + jobTitle: job-title-universal jobIcon: JobIconAdmin - type: Access groups: diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index 1fbde27e71b..4c4e44c28cd 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -167,6 +167,8 @@ - type: FootstepModifier footstepSoundCollection: collection: FootstepClown + params: + variation: 0.17 - type: Mech baseState: honker openState: honker-open diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml index 9b6da25eb7d..5b0c97dc837 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/surgery.yml @@ -68,6 +68,10 @@ components: - type: Sharp butcherDelayModifier: 1.5 # Butchering with a scalpel, regardless of the type, will take 50% longer + - type: Tool + qualities: + - Slicing + speedModifier: 0.66 # pretend the sixes go on forever :) - type: Utensil types: - Knife diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml index 9eb75d04f96..2846410943d 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml @@ -10,6 +10,8 @@ - type: Sprite sprite: Objects/Specific/Robotics/borgmodule.rsi - type: BorgModule + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: no-action } - type: StaticPrice price: 100 - type: Tag @@ -35,7 +37,7 @@ description: Select this module, enabling you to use the tools it provides. components: - type: InstantAction - itemIconStyle: BigItem + itemIconStyle: BigAction useDelay: 0.5 event: !type:BorgModuleActionSelectedEvent @@ -119,6 +121,8 @@ - CableHVStackLingering10 - Wirecutter - trayScanner + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: wire-module } - type: entity id: BorgModuleFireExtinguisher @@ -132,6 +136,8 @@ - type: ItemBorgModule items: - FireExtinguisher + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: extinguisher-module } - type: entity id: BorgModuleGPS @@ -147,6 +153,8 @@ - HandheldGPSBasic - HandHeldMassScannerBorg - HandheldStationMapUnpowered + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: gps-module } - type: entity id: BorgModuleRadiationDetection @@ -160,6 +168,8 @@ - type: ItemBorgModule items: - GeigerCounter + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: geiger-module } - type: entity id: BorgModuleTool @@ -178,6 +188,8 @@ - Wirecutter - Multitool - WelderIndustrial + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: tool-module } # cargo modules - type: entity @@ -192,6 +204,8 @@ - type: ItemBorgModule items: - AppraisalTool + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: appraisal-module } - type: entity id: BorgModuleMining @@ -210,6 +224,8 @@ - OreBag - Crowbar - RadioHandheld + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: mining-module } - type: entity id: BorgModuleGrapplingGun @@ -224,6 +240,8 @@ items: - WeaponGrapplingGun - HandheldGPSBasic + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: grappling-module } # engineering modules - type: entity @@ -244,6 +262,8 @@ - RemoteSignaller - GasAnalyzer - GeigerCounter + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-tools-module } - type: entity id: BorgModuleConstruction @@ -260,6 +280,8 @@ - SheetGlassLingering0 - PartRodMetalLingering0 - FloorTileItemSteelLingering0 + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: construction-module } - type: entity id: BorgModuleRCD @@ -273,6 +295,8 @@ - type: ItemBorgModule items: - RCDRecharging + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: rcd-module } # janitorial modules (this gets its own unique things because janis are epic) - type: entity @@ -289,6 +313,8 @@ - LightReplacer - Crowbar - Screwdriver + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: light-replacer-module } - type: entity id: BorgModuleCleaning @@ -304,6 +330,8 @@ - MopItem - Bucket - TrashBag + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: cleaning-module } - type: entity id: BorgModuleAdvancedCleaning @@ -321,6 +349,8 @@ - SprayBottleSpaceCleaner - Dropper - TrashBag + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-cleaning-module } # medical modules - type: entity @@ -336,6 +366,8 @@ items: - HandheldHealthAnalyzerUnpowered - ClothingNeckStethoscope + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: diagnosis-module } - type: entity id: BorgModuleTreatment @@ -354,6 +386,8 @@ - Gauze10Lingering - Bloodpack10Lingering - Syringe + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: treatment-module } - type: entity id: BorgModuleDefibrillator @@ -367,6 +401,8 @@ - type: ItemBorgModule items: - DefibrillatorOneHandedUnpowered + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: defib-module } - type: entity id: BorgModuleAdvancedTreatment @@ -384,6 +420,8 @@ - Beaker - BorgDropper - BorgHypo + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: adv-diagnosis-module } # science modules # todo: if science ever gets their own custom robot, add more "sci" modules. @@ -399,6 +437,8 @@ - type: ItemBorgModule items: - NodeScanner + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: node-scanner-module } - type: entity id: BorgModuleAnomaly @@ -416,6 +456,8 @@ - AnomalyLocatorWideUnpowered - RemoteSignaller - Multitool + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: anomaly-module } # service modules - type: entity @@ -435,6 +477,8 @@ - Lighter - DrinkShaker - BorgDropper + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: service-module } - type: entity id: BorgModuleMusique @@ -450,6 +494,8 @@ - SynthesizerInstrument - ElectricGuitarInstrument - SaxophoneInstrument + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: musical-module } - type: entity id: BorgModuleGardening @@ -466,6 +512,8 @@ - HydroponicsToolSpade - HydroponicsToolClippers - Bucket + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: gardening-module } - type: entity id: BorgModuleHarvesting @@ -481,6 +529,8 @@ - HydroponicsToolScythe - HydroponicsToolHatchet - PlantBag + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: harvesting-module } - type: entity id: BorgModuleClowning @@ -496,6 +546,8 @@ - BikeHorn - ClownRecorder - BikeHornInstrument + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: clowning-module } #syndicate modules - type: entity @@ -511,6 +563,8 @@ items: - WeaponPistolEchis - EnergyDaggerLoud + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-weapon-module } - type: entity id: BorgModuleOperative @@ -528,6 +582,8 @@ - Emag - Doorjack #Delta-V add doorjack, emag no longer opens doors. - PinpointerSyndicateNuclear + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-operative-module } - type: entity id: BorgModuleEsword @@ -543,6 +599,8 @@ items: - CyborgEnergySwordDouble - PinpointerSyndicateNuclear + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-esword-module } - type: entity id: BorgModuleL6C @@ -558,6 +616,8 @@ items: - WeaponLightMachineGunL6C - PinpointerSyndicateNuclear + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-l6c-module } - type: entity id: BorgModuleMartyr @@ -572,3 +632,8 @@ - type: ItemBorgModule items: - SelfDestructSeq + - type: BorgModuleIcon + icon: { sprite: Interface/Actions/actions_borg.rsi, state: syndicate-martyr-module } + - type: Construction # DeltaV: construction for adding explosive payload to the dud version + graph: BorgModuleMartyr + node: live diff --git a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml index 5155e70cca4..33eabbb60b5 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Robotics/mmi.yml @@ -49,6 +49,10 @@ guides: - Cyborgs - Robotics + - type: ChangeVoiceInContainer + whitelist: + components: + - SecretStash - type: entity parent: MMI @@ -127,3 +131,7 @@ guides: - Cyborgs - Robotics + - type: ChangeVoiceInContainer + whitelist: + components: + - SecretStash diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index c7fb68c9d7a..4c31d700905 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -391,6 +391,66 @@ - Syringe - Trash +- type: entity + name: mini syringe + parent: Syringe + description: A regular syringe, reshaped to fit inside of a gun. + id: MiniSyringe + components: + - type: Sprite + sprite: Objects/Specific/Chemistry/syringe.rsi + layers: + - state: minisyringe1 + map: ["enum.SolutionContainerLayers.Fill"] + visible: false + - state: syringeproj + - type: SolutionContainerVisuals + maxFillLevels: 3 + fillBaseName: minisyringe + inHandsMaxFillLevels: 3 + inHandsFillBaseName: -fill- + - type: EmbeddableProjectile + offset: "-0.1,0" + minimumSpeed: 3 + removalTime: 0.25 + embedOnThrow: false + - type: SolutionInjectWhileEmbedded + transferAmount: 1 + solution: injector + updateInterval: 2 + - type: SolutionInjectOnEmbed + transferAmount: 2 + solution: injector + - type: Fixtures + fixtures: + fix1: + shape: !type:PhysShapeCircle + radius: 0.2 + density: 5 + mask: + - ItemMask + restitution: 0.3 + friction: 0.2 + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.1,-0.3,0.1,0.3" + hard: false + mask: + - Impassable + - BulletImpassable + - type: Projectile + deleteOnCollide: false + onlyCollideWhenShot: true + damage: + types: + Piercing: 5 + - type: Tag + tags: + - Syringe + - Trash + - SyringeGunAmmo + - type: entity parent: BaseSyringe id: PrefilledSyringe diff --git a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml index 7455bc81b7b..8a06868ee03 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml @@ -55,6 +55,7 @@ - Reporter #Delta V: Add Reporter Access - Research - ResearchDirector + - Robotics # DeltaV: Robotics access - Salvage - Security - Service diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/antimateriel.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/antimateriel.yml index 28157ef3450..8fe03f3c6e1 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/antimateriel.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/antimateriel.yml @@ -20,7 +20,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 4 zeroVisible: false - type: Appearance @@ -41,7 +41,7 @@ map: ["enum.GunVisualLayers.Mag"] - type: MagazineVisuals magState: magb - steps: 2 + steps: 4 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.yml index 98ad35b70d2..3af2b7affbb 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.yml @@ -20,7 +20,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 4 zeroVisible: false - type: Appearance @@ -41,7 +41,7 @@ map: ["enum.GunVisualLayers.Mag"] - type: MagazineVisuals magState: mag10 - steps: 2 + steps: 4 zeroVisible: false - type: Appearance @@ -61,7 +61,7 @@ map: ["enum.GunVisualLayers.Mag"] - type: MagazineVisuals magState: magb - steps: 2 + steps: 4 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml index d5fb4360a82..bcd56c1d64a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.yml @@ -20,7 +20,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 4 zeroVisible: false - type: Appearance @@ -41,7 +41,7 @@ map: ["enum.GunVisualLayers.Mag"] - type: MagazineVisuals magState: magb - steps: 2 + steps: 4 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml index 018d812e3f5..6ccf0a1e2a2 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/magnum.yml @@ -19,7 +19,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 4 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/pistol.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/pistol.yml index fbd20446906..e081d574d7a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/pistol.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/pistol.yml @@ -20,7 +20,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 3 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml index 7a5f5d27ca6..98e063e297f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Boxes/rifle.yml @@ -19,7 +19,7 @@ sprite: Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi - type: MagazineVisuals magState: mag - steps: 2 + steps: 4 zeroVisible: false - type: Appearance @@ -40,7 +40,7 @@ map: ["enum.GunVisualLayers.Mag"] - type: MagazineVisuals magState: magb - steps: 2 + steps: 4 zeroVisible: false - type: Appearance diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml index 62eaf20f783..d6269efcc0d 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -652,7 +652,7 @@ - type: Clothing sprite: Objects/Weapons/Guns/Revolvers/chimp.rsi - type: Gun - projectileSpeed: 4 + projectileSpeed: 10 fireRate: 1.5 soundGunshot: path: /Audio/Weapons/Guns/Gunshots/taser2.ogg diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index 1d18c2b0500..63a7acd2576 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -103,6 +103,43 @@ containers: storagebase: !type:Container ents: [] + +- type: entity + name: syringe gun + parent: BaseStorageItem + id: LauncherSyringe + description: Load full of poisoned syringes for optimal fun. + components: + - type: Sprite + sprite: Objects/Weapons/Guns/Cannons/syringe_gun.rsi + layers: + - state: syringe_gun + - type: Storage + maxItemSize: Normal + grid: + - 0,0,2,0 + whitelist: + tags: + - SyringeGunAmmo + - type: Gun + fireRate: 1 + selectedMode: SemiAuto + availableModes: + - SemiAuto + - FullAuto + soundGunshot: + path: /Audio/Weapons/Guns/Gunshots/syringe_gun.ogg + soundEmpty: + path: /Audio/Weapons/Guns/Empty/empty.ogg + clumsyProof: true + - type: ContainerAmmoProvider + container: storagebase + - type: Item + size: Normal + - type: ContainerContainer + containers: + storagebase: !type:Container + ents: [] # shoots bullets instead of throwing them, no other changes - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml b/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml index 1d5069c7da8..fc7fe23bb87 100644 --- a/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml +++ b/Resources/Prototypes/Entities/Structures/Decoration/curtains.yml @@ -49,7 +49,7 @@ spawn: MaterialCloth1: min: 1 - max: 2 + max: 1 - type: WallMount arc: 360 diff --git a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml index bbff3cb43ec..a4cb78e67b7 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml @@ -115,6 +115,7 @@ color: Red enabled: false castShadows: false + - type: NavMapDoor - type: entity id: Firelock diff --git a/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml b/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml index 61edb22d220..e47a63ab730 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml @@ -25,6 +25,8 @@ offset: "0.0,0.3" sprite: Structures/Furniture/potted_plants.rsi noRot: true + - type: Speech + speechVerb: Plant # for pais (In the secret stash) - type: SecretStash tryInsertItemSound: /Audio/Effects/plant_rustle.ogg tryRemoveItemSound: /Audio/Effects/plant_rustle.ogg diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml index 7c22e70ef95..dda0b40fff7 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/arcades.yml @@ -72,6 +72,59 @@ rewardMinAmount: 0 rewardMaxAmount: 0 possibleRewards: + - Basketball + - BalloonNT + - BalloonCorgi + - BoxDonkSoftBox + - BoxCartridgeCap + - BeachBall + - CandyBucket + - CrayonBox + - ClothingHeadHatCowboyRed + - FoamCrossbow + - FoamBlade + - FoamCutlass + - Football + - GlowstickBase #green + - GlowstickBlue + - GlowstickYellow + - GlowstickPurple + - GlowstickRed + - HarmonicaInstrument + - OcarinaInstrument + - RecorderInstrument + - GunpetInstrument + - BirdToyInstrument + - MysteryFigureBox + - PlushieHampter + - PlushieLizard + - PlushieRainbowLizard + - PlushieAtmosian + - PlushieSpaceLizard + - PlushieNuke + - PlushieCarp + - PlushieMagicarp + - PlushieHolocarp + - PlushieRainbowCarp + - PlushieRatvar + - PlushieNar + - PlushieSnake + - PlushieArachind + - PlushieMoth + - PlushieHampter + - PlushiePenguin + - PlushieHuman + - PlushieRouny + - PlushieBee + - PlushieSlime + - PlushieGhost + - PlushieDiona + - PlushieSharkBlue + - PlushieVox + - PlushieXeno + - PlasticBanana + - RevolverCapGun + - SnapPopBox - ToyMouse - ToyAi - ToyNuke @@ -91,46 +144,14 @@ - ToySeraph - ToyDurand - ToySkeleton - - FoamCrossbow - - RevolverCapGun - - PlushieHampter - - PlushieLizard - - PlushieAtmosian - - PlushieSpaceLizard - - PlushieNuke - - PlushieCarp - - PlushieMagicarp - - PlushieHolocarp - - PlushieRainbowCarp - - PlushieRatvar - - PlushieNar - - PlushieSnake - - PlushieArachind - - Basketball - - Football - - PlushieRouny - - PlushieBee - - PlushieSlime - - BalloonNT - - BalloonCorgi - ToySword - - CrayonBox - - BoxDonkSoftBox - - BoxCartridgeCap - - HarmonicaInstrument - - OcarinaInstrument - - RecorderInstrument - - GunpetInstrument - - BirdToyInstrument - - PlushieXeno - - BeachBall - - PlushieMoth - - PlushieHampter + - ToyAmongPequeno + - ToyRubberDuck + - ToyHammer - ToyRenault # DeltaV Toy, see Resources/Prototypes/DeltaV/Entities/Objects/Fun/toys.yml - ToySiobhan # DeltaV Toy, see Resources/Prototypes/DeltaV/Entities/Objects/Fun/toys.yml - - PlushiePenguin - - PlushieHuman - - ClothingHeadHatCowboyRed + - WeaponWaterPistol + - WhoopieCushion - Whistle - type: WiresPanel - type: Wires diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml index 9baca8b4b6b..d79348bfa61 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/base_structurecomputers.yml @@ -60,6 +60,11 @@ collection: Keyboard params: volume: -1 + variation: 0.10 + pitch: 1.10 # low pitch keyboard sounds feel kinda weird + blacklist: + tags: + - NoConsoleSound - type: ContainerContainer containers: board: !type:Container diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml index 4d4eff1f702..1761f176da7 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml @@ -184,7 +184,7 @@ guides: - APE - type: Gun - projectileSpeed: 4 + projectileSpeed: 10 fireRate: 10 #just has to be fast enough to keep up with upgrades showExamineText: false selectedMode: SemiAuto diff --git a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml index 9bbf278e72d..db1ca9e7d5e 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/fax_machine.yml @@ -34,6 +34,7 @@ interfaces: enum.FaxUiKey.Key: type: FaxBoundUi + - type: StationAiWhitelist - type: ApcPowerReceiver powerLoad: 250 - type: Faxecute diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 395f52ad5bd..4edf038e2eb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -199,7 +199,6 @@ - WetFloorSign - ClothingHeadHatCone - FreezerElectronics - - Flare # Begin Delta-V additions - GoldRing - SilverRing @@ -209,7 +208,6 @@ - type: EmagLatheRecipes emagStaticRecipes: - BoxLethalshot - - BoxShotgunFlare - BoxShotgunSlug - CombatKnife - MagazineBoxLightRifle @@ -812,7 +810,6 @@ runningState: icon staticRecipes: - BoxLethalshot - - BoxShotgunFlare - BoxShotgunPractice - BoxShotgunSlug - ClothingEyesHudSecurity @@ -977,7 +974,6 @@ - MagazineBoxSpecial # End of modified code - BoxLethalshot - - BoxShotgunFlare - BoxShotgunSlug - BoxShellTranquilizer - MagazineBoxLightRifle @@ -1324,8 +1320,6 @@ - type: Machine board: BiogeneratorMachineCircuitboard - type: MaterialStorage - insertOnInteract: false - canEjectStoredMaterials: false - type: ProduceMaterialExtractor - type: ItemSlots slots: diff --git a/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml b/Resources/Prototypes/Entities/Structures/Machines/reagent_grinder.yml index 39ab6a3276b..72d6b28efa0 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/Power/apc.yml b/Resources/Prototypes/Entities/Structures/Power/apc.yml index 937908c8ef8..0ab427ef11a 100644 --- a/Resources/Prototypes/Entities/Structures/Power/apc.yml +++ b/Resources/Prototypes/Entities/Structures/Power/apc.yml @@ -91,7 +91,6 @@ type: ApcBoundUserInterface - type: ActivatableUI inHandsOnly: false - singleUser: true key: enum.ApcUiKey.Key - type: Construction graph: APC diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index 47bfcceedb0..53c2c4f4141 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -384,7 +384,7 @@ description: To store bags of bullet casings and detainee belongings. components: - type: AccessReader - access: [["Security"], ["Prosecutor"]] # DeltaV - allow Pros access to Evidence + access: [["Security"], ["Prosecutor"], ["Clerk"]] # DeltaV - allow Pros and Clerk access to Evidence # Syndicate - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/Signs/posters.yml index 95ee429cdce..31d5a9d6c18 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/Structures/Wallmounts/timer.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml index dabd93a339b..cd81687ec1e 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/timer.yml @@ -89,7 +89,7 @@ description: It's a timer for brig cells. components: - type: AccessReader - access: [["Security"], ["Prosecutor"]] # DeltaV - Added justice dept + access: [["Security"], ["Prosecutor"], ["Clerk"]] # DeltaV - Added justice dept - type: Construction graph: Timer node: brig diff --git a/Resources/Prototypes/Entities/Structures/hydro_tray.yml b/Resources/Prototypes/Entities/Structures/hydro_tray.yml index 7224c154f9a..b1cbcc8b863 100644 --- a/Resources/Prototypes/Entities/Structures/hydro_tray.yml +++ b/Resources/Prototypes/Entities/Structures/hydro_tray.yml @@ -67,6 +67,11 @@ False: { visible: false } - type: PlantHolder drawWarnings: true + wateringSound: + path: /Audio/Effects/Fluids/slosh.ogg + params: + volume: -6 + variation: 0.20 - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/categories.yml b/Resources/Prototypes/Entities/categories.yml index 0815c693eb6..dffc6b6aaf9 100644 --- a/Resources/Prototypes/Entities/categories.yml +++ b/Resources/Prototypes/Entities/categories.yml @@ -3,12 +3,22 @@ name: entity-category-name-actions hideSpawnMenu: true +- type: entityCategory + id: GameRules + name: entity-category-name-game-rules + hideSpawnMenu: true + - type: entityCategory id: Objectives name: entity-category-name-objectives hideSpawnMenu: true - type: entityCategory - id: GameRules - name: entity-category-name-game-rules + 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/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 7b4094cd91a..cd60ecd70e0 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -180,9 +180,8 @@ min: 1 max: 1 pickPlayer: false - mindComponents: - - type: DragonRole - prototype: Dragon + mindRoles: + - MindRoleDragon - type: entity parent: BaseGameRule @@ -230,9 +229,8 @@ nameSegments: - names_ninja_title - names_ninja - mindComponents: - - type: NinjaRole - prototype: SpaceNinja + mindRoles: + - MindRoleNinja - type: entity parent: BaseGameRule @@ -444,9 +442,8 @@ - type: ZombifyOnDeath - type: IncurableZombie - type: InitialInfected - mindComponents: - - type: InitialInfectedRole - prototype: InitialInfected + mindRoles: + - MindRoleInitialInfected - type: entity parent: BaseNukeopsRule @@ -472,7 +469,6 @@ startingGear: SyndicateLoneOperativeGearFull roleLoadout: - RoleSurvivalNukie - components: - type: NukeOperative - type: RandomMetadata @@ -482,9 +478,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: Nukeops + mindRoles: + - MindRoleNukeops - type: entity parent: BaseTraitorRule @@ -511,9 +506,8 @@ blacklist: components: - AntagImmune - mindComponents: - - type: TraitorRole - prototype: TraitorSleeper + mindRoles: + - MindRoleTraitorSleeper - type: entity id: MassHallucinations diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 7446995f262..6cc53a3d100 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -26,8 +26,7 @@ startingGear: ThiefGear components: - type: Pacified - mindComponents: - - type: ThiefRole - prototype: Thief + mindRoles: + - MindRoleThief briefing: sound: "/Audio/Misc/thief_greeting.ogg" diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index aa484de19f1..8b72770b647 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -114,9 +114,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: NukeopsCommander + mindRoles: + - MindRoleNukeopsCommander - prefRoles: [ NukeopsMedic ] fallbackRoles: [ Nukeops, NukeopsCommander ] spawnerPrototype: SpawnPointNukeopsMedic @@ -132,9 +131,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: NukeopsMedic + mindRoles: + - MindRoleNukeopsMedic - prefRoles: [ Nukeops ] fallbackRoles: [ NukeopsCommander, NukeopsMedic ] spawnerPrototype: SpawnPointNukeopsOperative @@ -152,9 +150,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: Nukeops + mindRoles: + - MindRoleNukeops - type: entity abstract: true @@ -189,9 +186,17 @@ components: - AntagImmune lateJoinAdditional: true - mindComponents: - - type: TraitorRole - prototype: Traitor + mindRoles: + - MindRoleTraitor + +- type: entity + id: TraitorReinforcement + parent: Traitor + components: + - type: TraitorRule + giveUplink: false + giveCodewords: false # It would actually give them a different set of codewords than the regular traitors, anyway + giveBriefing: false - type: entity id: Revolutionary @@ -213,9 +218,8 @@ components: - type: Revolutionary - type: HeadRevolutionary - mindComponents: - - type: RevolutionaryRole - prototype: HeadRev + mindRoles: + - MindRoleHeadRevolutionary - type: entity id: Sandbox @@ -257,9 +261,8 @@ - type: ZombifyOnDeath - type: IncurableZombie - type: InitialInfected - mindComponents: - - type: InitialInfectedRole - prototype: InitialInfected + mindRoles: + - MindRoleInitialInfected # event schedulers diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml index 41cad93a21f..06283d91816 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml @@ -8,15 +8,6 @@ role: JobLibrarian time: 3600 # 1 hour of being the biggest nerd on the station -- type: loadoutEffectGroup # DeltaV - id: CheapSunglassesTimer - effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobMusician - time: 7200 # 2 hours of being a rockstar - - type: loadoutEffectGroup id: JensenTimer effects: @@ -51,12 +42,3 @@ proto: JensenTimer equipment: eyes: ClothingEyesGlassesJensen - -# Cheap Sunglasses - DeltaV -- type: loadout # DeltaV - id: GlassesCheapSunglasses - effects: - - !type:GroupLoadoutEffect - proto: CheapSunglassesTimer - equipment: - eyes: ClothingEyesGlassesCheapSunglasses diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml index 14c1174a7d0..fc4e689084f 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml @@ -155,3 +155,193 @@ storage: back: - ClothingNeckGoldAutismPin + +# Towels +- type: loadout + id: TowelColorWhite + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:OverallPlaytimeRequirement + time: 36000 # 10hr + storage: + back: + - TowelColorWhite + +- type: loadout + id: TowelColorSilver + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:OverallPlaytimeRequirement + time: 1800000 # 500hr + storage: + back: + - TowelColorSilver + +- type: loadout + id: TowelColorGold + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:OverallPlaytimeRequirement + time: 3600000 # 1000hr + storage: + back: + - TowelColorGold + +- type: loadout + id: TowelColorLightBrown + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Logistics # DeltaV: Logistics replaces Cargo + time: 360000 # 100hr + storage: + back: + - TowelColorLightBrown + +- type: loadout + id: TowelColorGreen + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Civilian + time: 360000 # 100hr + storage: + back: + - TowelColorGreen + +- type: loadout + id: TowelColorDarkBlue + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Command + time: 360000 # 100hr + storage: + back: + - TowelColorDarkBlue + +- type: loadout + id: TowelColorOrange + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Engineering + time: 360000 # 100hr + storage: + back: + - TowelColorOrange + +- type: loadout + id: TowelColorLightBlue + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Medical + time: 360000 # 100hr + storage: + back: + - TowelColorLightBlue + +- type: loadout + id: TowelColorPurple + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Epistemics # DeltaV: Epistemics replaces Science + time: 360000 # 100hr + storage: + back: + - TowelColorPurple + +- type: loadout + id: TowelColorRed + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:DepartmentTimeRequirement + department: Security + time: 360000 # 100hr + storage: + back: + - TowelColorRed + +- type: loadout + id: TowelColorGray + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobPassenger + time: 360000 # 100hr + storage: + back: + - TowelColorGray + +- type: loadout + id: TowelColorBlack + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobChaplain + time: 360000 # 100hr + storage: + back: + - TowelColorBlack + +- type: loadout + id: TowelColorDarkGreen + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobLibrarian + time: 360000 # 100hr + storage: + back: + - TowelColorDarkGreen + +- type: loadout + id: TowelColorMaroon + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobLawyer + time: 360000 # 100hr + storage: + back: + - TowelColorMaroon + +- type: loadout + id: TowelColorYellow + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobClown + time: 360000 # 100hr + storage: + back: + - TowelColorYellow + +- type: loadout + id: TowelColorMime + effects: + - !type:JobRequirementLoadoutEffect + requirement: + !type:RoleTimeRequirement + role: JobMime + time: 360000 # 100hr + storage: + back: + - TowelColorMime diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index dc30a4f16cf..f5783b89ca3 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -28,6 +28,23 @@ - ClothingNeckTransPin - ClothingNeckAutismPin - ClothingNeckGoldAutismPin + - TowelColorBlack + - TowelColorDarkBlue + - TowelColorDarkGreen + - TowelColorGold + - TowelColorGray + - TowelColorGreen + - TowelColorLightBlue + - TowelColorLightBrown + - TowelColorMaroon + - TowelColorMime + - TowelColorOrange + - TowelColorPurple + - TowelColorRed + - TowelColorSilver + - TowelColorLightBlue + - TowelColorWhite + - TowelColorYellow - AACTablet # DeltaV - GoldRing # DeltaV - SilverRing # DeltaV diff --git a/Resources/Prototypes/NPCs/firebot.yml b/Resources/Prototypes/NPCs/firebot.yml index acea6498bdb..2da9da50d25 100644 --- a/Resources/Prototypes/NPCs/firebot.yml +++ b/Resources/Prototypes/NPCs/firebot.yml @@ -29,6 +29,18 @@ pathfindKey: TargetPathfind rangeKey: InteractRange + - !type:HTNPrimitiveTask + operator: !type:SetFloatOperator + targetKey: WaitTime + amount: 1 + + - !type:HTNPrimitiveTask + operator: !type:WaitOperator + key: WaitTime + preconditions: + - !type:KeyExistsPrecondition + key: WaitTime + - !type:HTNPrimitiveTask preconditions: - !type:TargetInRangePrecondition diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/backpack.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/backpack.yml deleted file mode 100644 index 66b1854dafb..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/backpack.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: entity - categories: [ HideSpawnMenu ] - parent: ClothingBackpackScience - id: ClothingBackpackMantisFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - # - id: BoxForensicPad # DeltaV - Mantis is no longer a Detective - - id: HandLabeler - - id: PillMindbreakerToxin - - id: BoxFolderGrey - - id: RubberStampMantis diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml deleted file mode 100644 index 49ca0460aa5..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/duffelbag.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: entity - categories: [ HideSpawnMenu ] - parent: ClothingBackpackDuffelScience - id: ClothingBackpackDuffelMantisFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - # - id: BoxForensicPad # DeltaV - Mantis is no longer a Detective - - id: HandLabeler - - id: PillMindbreakerToxin - - id: BoxFolderGrey - - id: RubberStampMantis diff --git a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/satchel.yml b/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/satchel.yml deleted file mode 100644 index 9a33ad34685..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Catalog/Fills/Backpacks/StarterGear/satchel.yml +++ /dev/null @@ -1,13 +0,0 @@ -- type: entity - categories: [ HideSpawnMenu ] - parent: ClothingBackpackSatchelScience - id: ClothingBackpackSatchelMantisFilled - components: - - type: StorageFill - contents: - - id: BoxSurvival - # - id: BoxForensicPad # DeltaV - Mantis is no longer a Detective - - id: HandLabeler - - id: PillMindbreakerToxin - - id: BoxFolderGrey - - id: RubberStampMantis diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/Misc/identification_cards.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/Misc/identification_cards.yml index d44f7c12083..39dcca3b7ac 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/Misc/identification_cards.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Devices/Misc/identification_cards.yml @@ -48,7 +48,7 @@ - sprite: DeltaV/Objects/Misc/id_cards.rsi state: nyanomailcarrier - type: IdCard - jobTitle: Mail Carrier + jobTitle: job-alt-title-mail-carrier - type: entity parent: IDCardStandard diff --git a/Resources/Prototypes/Nyanotrasen/Loadouts/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Loadouts/Jobs/Epistemics/forensicmantis.yml index c6df3e71ca3..33224a158a2 100644 --- a/Resources/Prototypes/Nyanotrasen/Loadouts/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Loadouts/Jobs/Epistemics/forensicmantis.yml @@ -15,22 +15,6 @@ equipment: jumpsuit: ClothingUniformSkirtMantis -# Back -- type: loadout - id: MantisBackpack - equipment: - back: ClothingBackpackMantisFilled - -- type: loadout - id: MantisSatchel - equipment: - back: ClothingBackpackSatchelMantisFilled - -- type: loadout - id: MantisDuffel - equipment: - back: ClothingBackpackDuffelMantisFilled - # OuterClothing - type: loadout id: MantisCoat diff --git a/Resources/Prototypes/Nyanotrasen/Loadouts/loadout_groups.yml b/Resources/Prototypes/Nyanotrasen/Loadouts/loadout_groups.yml index 2ffcbd25368..740d034967f 100644 --- a/Resources/Prototypes/Nyanotrasen/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Nyanotrasen/Loadouts/loadout_groups.yml @@ -37,14 +37,6 @@ - MantisJumpsuit - MantisJumpskirt -- type: loadoutGroup - id: MantisBackpack - name: loadout-group-mantis-backpack - loadouts: - - MantisBackpack - - MantisSatchel - - MantisDuffel - - type: loadoutGroup id: MantisOuterClothing name: loadout-group-mantis-outerclothing diff --git a/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml b/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml index 0ef279f75cc..5a7f6576a80 100644 --- a/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Nyanotrasen/Loadouts/role_loadouts.yml @@ -18,7 +18,7 @@ - GroupTankHarness - MantisHead - MantisJumpsuit - - MantisBackpack + - ScientistBackpack - MantisOuterClothing - MantisShoes - MantisGloves diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index f4d511f6a87..b31b6ec1b0b 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -32,3 +32,9 @@ eyes: ClothingEyesGlassesSunglasses ears: ClothingHeadsetScience # DeltaV - Mantis is part of Epistemics belt: ClothingBeltMantis + storage: + back: + - HandLabeler + - PillMindbreakerToxin + - BoxFolderGrey + - RubberStampMantis diff --git a/Resources/Prototypes/Objectives/dragon.yml b/Resources/Prototypes/Objectives/dragon.yml index bbdac8faa1a..9d81c62ab3f 100644 --- a/Resources/Prototypes/Objectives/dragon.yml +++ b/Resources/Prototypes/Objectives/dragon.yml @@ -9,7 +9,7 @@ issuer: objective-issuer-dragon - type: RoleRequirement roles: - components: + mindRoles: - DragonRole - type: entity diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index 00bf9d705b8..5118e89e26f 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -9,7 +9,7 @@ issuer: objective-issuer-spiderclan - type: RoleRequirement roles: - components: + mindRoles: - NinjaRole - type: entity diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index f4b2a27fa77..7d0ebbaef0c 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -7,7 +7,7 @@ issuer: objective-issuer-thief - type: RoleRequirement roles: - components: + mindRoles: - ThiefRole - type: entity diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index fc97e1797c7..3acfb04d244 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -7,7 +7,7 @@ issuer: objective-issuer-syndicate - type: RoleRequirement roles: - components: + mindRoles: - TraitorRole - type: entity diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml index 661350f6de6..de70e2d5ecd 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid_ore_gens.yml @@ -3,8 +3,8 @@ weights: OreIron: 1.0 OreQuartz: 1.0 - OreCoal: 0.33 - OreSalt: 0.25 + OreCoal: 0.58 # DeltaV: was 0.33, added 0.25 from salt + #OreSalt: 0.25 # DeltaV: removed salt, added weight to coal OreGold: 0.25 OreSilver: 0.25 OrePlasma: 0.20 diff --git a/Resources/Prototypes/Procedural/salvage_loot.yml b/Resources/Prototypes/Procedural/salvage_loot.yml index 68bfcd89573..c29cc2ef158 100644 --- a/Resources/Prototypes/Procedural/salvage_loot.yml +++ b/Resources/Prototypes/Procedural/salvage_loot.yml @@ -139,7 +139,7 @@ guaranteed: true loots: - !type:BiomeMarkerLoot - proto: OreSalt + proto: OreCoal # DeltaV: replace salt with coal # - Medium value - type: salvageLoot diff --git a/Resources/Prototypes/Reagents/narcotics.yml b/Resources/Prototypes/Reagents/narcotics.yml index ca4c613dae4..beeefcb8797 100644 --- a/Resources/Prototypes/Reagents/narcotics.yml +++ b/Resources/Prototypes/Reagents/narcotics.yml @@ -366,6 +366,9 @@ - !type:GenericStatusEffect key: Muted component: Muted + type: Add + time: 10 + refresh: false - type: reagent id: NorepinephricAcid diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index 8dc25c1c725..fa2b6573915 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -1062,6 +1062,16 @@ FoodMeat: 3 FoodPlateTin: 1 +- type: microwaveMealRecipe + id: RecipePumpkinPie + name: pumpkin pie recipe + result: FoodPiePumpkin + time: 15 + solids: + FoodDoughPie: 1 + FoodPumpkin: 1 + FoodPlateTin: 1 + #- type: microwaveMealRecipe # id: RecipePlumpPie # name: plump pie recipe @@ -1978,4 +1988,4 @@ solids: FoodCroissantRaw: 1 FoodButterSlice: 1 - ShardGlass: 1 \ No newline at end of file + ShardGlass: 1 diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index 9bbd07848a8..2353099df56 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -768,6 +768,20 @@ products: NuclearCola: 5 +- type: reaction + id: NuclearColaBreakdown + source: true + requiredMixerCategories: + - Centrifuge + reactants: + NuclearCola: + amount: 10 # assuming we loose all o2 released as gas in the centrifugal process + products: + Ipecac: 2 + Water: 2 + Sugar: 2 + Uranium: 1 + - type: reaction id: Patron requiredMixerCategories: diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index 3fd9e173e50..02682ed863b 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -22,6 +22,7 @@ weight: 10 startingGear: QuartermasterGear icon: "JobIconQuarterMaster" + requireAdminNotify: true supervisors: job-supervisors-captain canBeAntag: false access: @@ -30,9 +31,9 @@ - Mail # Nyanotrasen - MailCarrier, see Resources/Prototypes/Nyanotrasen/Roles/Jobs/Cargo/mail-carrier.yml - Quartermaster - Maintenance + - External - Command - Orders # DeltaV - Orders, see Resources/Prototypes/DeltaV/Access/cargo.yml - - External # DeltaV - for promoting salvage specialists - Cryogenics special: - !type:AddImplantSpecial diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml index a0d73320faf..4d6d8e019e6 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/librarian.yml @@ -20,7 +20,7 @@ shoes: ClothingShoesBootsLaceup id: LibrarianPDA ears: ClothingHeadsetService - pocket1: d10Dice + pocket1: d20Dice pocket2: HandLabeler # for making named bestsellers storage: back: diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index a06731e2409..441583c0ca4 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -7,7 +7,7 @@ requirements: - !type:RoleTimeRequirement role: JobBorg - time: 18000 # 5 hrs + time: 54000 # 15 hrs canBeAntag: false setPreference: false # DeltaV - disable AI until its fleshed out whitelisted: true # DeltaV - ai must be whitelisted @@ -23,7 +23,7 @@ playTimeTracker: JobBorg requirements: - !type:OverallPlaytimeRequirement - time: 216000 # 60 hrs + time: 144000 # 40 hrs canBeAntag: false icon: JobIconBorg supervisors: job-supervisors-rd diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index 9cbd6a3fde1..691778e65bf 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -23,6 +23,7 @@ - ResearchDirector - Mantis # DeltaV - Psionic Mantis, see Resources/Prototypes/DeltaV/Access/epistemics.yml - Chapel # DeltaV - Chaplain is in Epistemics + - Robotics # DeltaV - Robotics access - External # DeltaV - AI satellite access - Cryogenics special: # Nyanotrasen - Mystagogue can use the Bible diff --git a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml index 0f1ec0ec7a3..193f59bd571 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml @@ -14,6 +14,8 @@ access: - Research - Maintenance + extendedAccess: # DeltaV: Scientists get robotics access on lowpop + - Robotics - type: startingGear id: ScientistGear diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml new file mode 100644 index 00000000000..926ce512b41 --- /dev/null +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -0,0 +1,182 @@ +- type: entity + id: BaseMindRole + name: Mind Role + description: Mind Role entity + abstract: true + components: + - type: MindRole + +- type: entity + parent: BaseMindRole + id: BaseMindRoleAntag + abstract: true + components: + - type: MindRole + antag: true + +#Observer +- type: entity + parent: BaseMindRole + id: MindRoleObserver + name: Observer Role + components: + - type: ObserverRole + +#Ghostrole Marker +- type: entity + parent: BaseMindRole + id: MindRoleGhostMarker + name: Ghost Role + components: + - type: GhostRoleMarkerRole + +# The Job MindRole holds the mob's Job prototype +- type: entity + parent: BaseMindRole + id: MindRoleJob + name: Job Role +# description: + # MindRoleComponent.JobPrototype is filled by SharedJobSystem + +# Subverted Silicon +- type: entity + parent: BaseMindRoleAntag + id: MindRoleSubvertedSilicon + name: Subverted Silicon Role + description: + components: + - type: SubvertedSiliconRole + - type: MindRole + antagPrototype: SubvertedSilicon + +# Dragon +- type: entity + parent: BaseMindRoleAntag + id: MindRoleDragon + name: Dragon Role +# description: + components: + - type: MindRole + antagPrototype: Dragon + exclusiveAntag: true + - type: DragonRole + - type: RoleBriefing + briefing: dragon-role-briefing + +# Ninja +- type: entity + parent: BaseMindRoleAntag + id: MindRoleNinja + name: Space Ninja Role +# description: mind-role-ninja-description + components: + - type: MindRole + antagPrototype: SpaceNinja + exclusiveAntag: true + - type: NinjaRole + +# Nukies +- type: entity + parent: BaseMindRoleAntag + id: MindRoleNukeops + name: Nukeops Operative Role +# description: mind-role-nukeops-description + components: + - type: MindRole + exclusiveAntag: true + antagPrototype: Nukeops + - type: NukeopsRole + +- type: entity + parent: MindRoleNukeops + id: MindRoleNukeopsMedic + name: Nukeops Medic Role +# description: mind-role-nukeops-medic-description + components: + - type: MindRole + antagPrototype: NukeopsMedic + +- type: entity + parent: MindRoleNukeops + id: MindRoleNukeopsCommander + name: Nukeops Commander Role +# description: mind-role-nukeops-commander-description + components: + - type: MindRole + antagPrototype: NukeopsCommander + +# Revolutionaries +- type: entity + parent: BaseMindRoleAntag + id: MindRoleHeadRevolutionary + name: Head Revolutionary Role +# description: mind-role-head-revolutionary-description + components: + - type: MindRole + antagPrototype: HeadRev + exclusiveAntag: true + - type: RevolutionaryRole + +- type: entity + parent: MindRoleHeadRevolutionary + id: MindRoleRevolutionary + name: Revolutionary Role +# description: mind-role-revolutionary-description + components: + - type: MindRole + antagPrototype: Rev + +# Thief +- type: entity + parent: BaseMindRoleAntag + id: MindRoleThief + name: Thief Role +# description: mind-role-thief-description + components: + - type: MindRole + antagPrototype: Thief + - type: ThiefRole + +# Traitors +- type: entity + parent: BaseMindRoleAntag + id: MindRoleTraitor + name: Traitor Role +# description: mind-role-traitor-description + components: + - type: MindRole + antagPrototype: Traitor + exclusiveAntag: true + - type: TraitorRole + +- type: entity + parent: MindRoleTraitor + id: MindRoleTraitorSleeper + name: Sleeper Agent Role +# description: mind-role-traitor-sleeper-description + components: + - type: MindRole + antagPrototype: TraitorSleeper + +# Zombie Squad +- type: entity + parent: BaseMindRoleAntag + id: MindRoleInitialInfected + name: Initial Infected Role +# description: mind-role-initial-infected-description + components: + - type: MindRole + antagPrototype: InitialInfected + exclusiveAntag: true + - type: InitialInfectedRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleZombie + name: Zombie Role +# description: mind-role-zombie-description + components: + - type: MindRole + antagPrototype: Zombie + exclusiveAntag: true + - type: ZombieRole diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml index 90b89996a4b..1e54699fb83 100644 --- a/Resources/Prototypes/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -426,6 +426,22 @@ path: /Audio/Voice/Diona/diona_salute.ogg params: volume: -5 + +- type: emoteSounds + id: ReptilianBodyEmotes + sounds: + Thump: + path: /Audio/Voice/Reptilian/reptilian_tailthump.ogg + params: + variation: 0.125 + Clap: + collection: Claps + Snap: + collection: Snaps + params: + volume: -6 + Salute: + collection: Salutes # mobs - type: emoteSounds diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index 580357d4769..ea1ee281199 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -242,6 +242,31 @@ - snapping fingers - snapped fingers +- type: emote + id: Thump + name: chat-emote-name-thump + category: Hands + available: false + icon: Interface/Emotes/tailslap.png + whitelist: + components: + - Hands + blacklist: + components: + - BorgChassis + chatMessages: ["chat-emote-msg-thump"] + chatTriggers: + - thump + - thumps + - thumping + - thumped + - thump tail + - thumps tail + - thumps their tail + - thumps her tail + - thumps his tail + - thumps its tail + - type: emote id: Salute name: chat-emote-name-salute diff --git a/Resources/Prototypes/_RMC14/SoundCollections/xeno_speech.yml b/Resources/Prototypes/_RMC14/SoundCollections/xeno_speech.yml new file mode 100644 index 00000000000..82e03453ce8 --- /dev/null +++ b/Resources/Prototypes/_RMC14/SoundCollections/xeno_speech.yml @@ -0,0 +1,6 @@ +- type: soundCollection + id: XenonidSpeech + files: + - /Audio/_RMC14/Voice/Xeno/alien_talk1.ogg + - /Audio/_RMC14/Voice/Xeno/alien_talk2.ogg + - /Audio/_RMC14/Voice/Xeno/alien_talk3.ogg diff --git a/Resources/Prototypes/_RMC14/Voice/speech_sounds.yml b/Resources/Prototypes/_RMC14/Voice/speech_sounds.yml new file mode 100644 index 00000000000..98c4f643e8c --- /dev/null +++ b/Resources/Prototypes/_RMC14/Voice/speech_sounds.yml @@ -0,0 +1,8 @@ +- type: speechSounds + id: Xenonid + saySound: + collection: XenonidSpeech + askSound: + collection: XenonidSpeech + exclaimSound: + collection: XenonidSpeech diff --git a/Resources/Prototypes/ore.yml b/Resources/Prototypes/ore.yml index 5beda7a2098..3fa2507392c 100644 --- a/Resources/Prototypes/ore.yml +++ b/Resources/Prototypes/ore.yml @@ -90,7 +90,7 @@ id: RandomOreDistributionStandard weights: OreSteel: 10 - OreCoal: 10 + OreCoal: 12 # DeltaV: increased slightly from 10 so there is a surplus to give to chem OreSpaceQuartz: 8 OreGold: 2 OrePlasma: 4 @@ -98,7 +98,6 @@ OreUranium: 1 OreBananium: 0.5 OreArtifactFragment: 0.5 - OreSalt: 5 #Delta V - Adds so our rocks can spawn salt - type: weightedRandomOre id: OreCrab diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index de10389797d..1c02d7f3d03 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -987,12 +987,18 @@ - type: Tag id: NoBlockAnchoring +- type: Tag + id: NoConsoleSound + - type: Tag id: NozzleBackTank - type: Tag id: Nugget # for chicken nuggets +- type: Tag + id: FakeNukeDisk + - type: Tag id: NukeOpsUplink @@ -1180,7 +1186,7 @@ - type: Tag id: SecureWindoor -- type: Tag +- type: Tag id: SecurityHelmet - type: Tag @@ -1297,6 +1303,9 @@ - type: Tag id: Syringe +- type: Tag + id: SyringeGunAmmo + - type: Tag id: Spellbook diff --git a/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml b/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml index eadef874031..34484fe0463 100644 --- a/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml +++ b/Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml @@ -18,12 +18,17 @@ By pressing [color=yellow][bold][keybind="OpenCharacterMenu"][/bold][/color], you'll see your personal uplink code. [bold]Setting your PDA's ringtone as this code will open the uplink.[/bold] Pressing [color=yellow][bold][keybind="OpenCharacterMenu"][/bold][/color] also lets you view your objectives and the codewords. + If you do not have a PDA when you are activated, an [color=cyan]uplink implant[/color] is provided [bold]for the full [color=red]TC[/color] price of the implant.[/bold] + It can be accessed from your hotbar. + - [bold]Make sure to close your uplink to prevent anyone else from seeing it.[/bold] You don't want [color=#cb0000]Security[/color] to get their hands on this premium selection of contraband! + [bold]Make sure to close your PDA uplink to prevent anyone else from seeing it.[/bold] You don't want [color=#cb0000]Security[/color] to get their hands on this premium selection of contraband! + + Implanted uplinks are not normally accessible to other people, so they do not have any security measures. They can, however, be removed from you with an empty implanter. diff --git a/Resources/Textures/Clothing/Head/Hats/warden.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Head/Hats/warden.rsi/equipped-HELMET.png index 10dab5101f7..70d2f5adc40 100644 Binary files a/Resources/Textures/Clothing/Head/Hats/warden.rsi/equipped-HELMET.png and b/Resources/Textures/Clothing/Head/Hats/warden.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json index 7bd2e3e22a7..e953fa53d57 100644 --- a/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/warden.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, texture edited by TeaMaki (On github TeaMakiNL)", "size": { "x": 32, "y": 32 diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/NTmono.png b/Resources/Textures/Clothing/Multiple/towel.rsi/NTmono.png new file mode 100644 index 00000000000..e293a0e7e9e Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/NTmono.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-BELT.png b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-BELT.png new file mode 100644 index 00000000000..6ccb1f26ae0 Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-HELMET.png b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-HELMET.png new file mode 100644 index 00000000000..769f67b9a0c Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-HELMET.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-INNERCLOTHING.png b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-INNERCLOTHING.png new file mode 100644 index 00000000000..6105c8aba12 Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/equipped-INNERCLOTHING.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/icon.png b/Resources/Textures/Clothing/Multiple/towel.rsi/icon.png new file mode 100644 index 00000000000..c5c73e1060d Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/iconstripe.png b/Resources/Textures/Clothing/Multiple/towel.rsi/iconstripe.png new file mode 100644 index 00000000000..334d3f3531e Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/iconstripe.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-left.png b/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-left.png new file mode 100644 index 00000000000..4c8b4428ae6 Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-right.png b/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-right.png new file mode 100644 index 00000000000..7ef955ba5f8 Binary files /dev/null and b/Resources/Textures/Clothing/Multiple/towel.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Multiple/towel.rsi/meta.json b/Resources/Textures/Clothing/Multiple/towel.rsi/meta.json new file mode 100644 index 00000000000..95acda0af39 --- /dev/null +++ b/Resources/Textures/Clothing/Multiple/towel.rsi/meta.json @@ -0,0 +1,40 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from Baystation12 at commit https://github.com/Baystation12/Baystation12/commit/c5dc6953e6e1fde87c2ded60038144f1d21fbd48", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "iconstripe" + }, + { + "name": "NTmono" + }, + { + "name": "equipped-INNERCLOTHING", + "directions": 4 + }, + { + "name": "equipped-BELT", + "directions": 4 + }, + { + "name": "equipped-HELMET", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/DeltaV/Markers/jobs.rsi/meta.json b/Resources/Textures/DeltaV/Markers/jobs.rsi/meta.json index a7534b9ee5b..73c7764df0a 100644 --- a/Resources/Textures/DeltaV/Markers/jobs.rsi/meta.json +++ b/Resources/Textures/DeltaV/Markers/jobs.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "made by Floofers", + "copyright": "made by Floofers. roboticist.png created by deltanedas (github) for DeltaV.", "size": { "x": 32, "y": 32 @@ -45,6 +45,9 @@ }, { "name": "mobster" + }, + { + "name": "roboticist" } ] } diff --git a/Resources/Textures/DeltaV/Markers/jobs.rsi/roboticist.png b/Resources/Textures/DeltaV/Markers/jobs.rsi/roboticist.png new file mode 100644 index 00000000000..4199496274f Binary files /dev/null and b/Resources/Textures/DeltaV/Markers/jobs.rsi/roboticist.png differ diff --git a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-left.png b/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-left.png deleted file mode 100644 index fa40fb33491..00000000000 Binary files a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-left.png and /dev/null differ diff --git a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-right.png b/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-right.png deleted file mode 100644 index e08270f864a..00000000000 Binary files a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/inhand-right.png and /dev/null differ diff --git a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png b/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png deleted file mode 100644 index a07ece057b8..00000000000 Binary files a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png and /dev/null differ diff --git a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png b/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png deleted file mode 100644 index ebad31e29d8..00000000000 Binary files a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png and /dev/null differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-cleaning-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-cleaning-module.png new file mode 100644 index 00000000000..f608aaa0bc8 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-cleaning-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-diagnosis-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-diagnosis-module.png new file mode 100644 index 00000000000..df4d53633be Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-diagnosis-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-tools-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-tools-module.png new file mode 100644 index 00000000000..47d0a45033a Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/adv-tools-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/anomaly-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/anomaly-module.png new file mode 100644 index 00000000000..2270e2e86f1 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/anomaly-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/appraisal-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/appraisal-module.png new file mode 100644 index 00000000000..9f327af0630 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/appraisal-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/cleaning-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/cleaning-module.png new file mode 100644 index 00000000000..8caf592553c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/cleaning-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/clowning-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/clowning-module.png new file mode 100644 index 00000000000..bb6c4412062 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/clowning-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/construction-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/construction-module.png new file mode 100644 index 00000000000..c77c02f207d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/construction-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/defib-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/defib-module.png new file mode 100644 index 00000000000..4d22bb55d79 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/defib-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/diagnosis-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/diagnosis-module.png new file mode 100644 index 00000000000..80686c3ce4d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/diagnosis-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/extinguisher-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/extinguisher-module.png new file mode 100644 index 00000000000..b74cd09fda5 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/extinguisher-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/gardening-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/gardening-module.png new file mode 100644 index 00000000000..d400ba2b999 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/gardening-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/geiger-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/geiger-module.png new file mode 100644 index 00000000000..d962befeace Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/geiger-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/gps-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/gps-module.png new file mode 100644 index 00000000000..67af612968a Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/gps-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/grappling-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/grappling-module.png new file mode 100644 index 00000000000..68209e0adaf Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/grappling-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/harvesting-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/harvesting-module.png new file mode 100644 index 00000000000..24e1c57f5ef Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/harvesting-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/light-replacer-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/light-replacer-module.png new file mode 100644 index 00000000000..7f70d15f248 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/light-replacer-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json index f63bacd07a9..dc8a6fcf9c6 100644 --- a/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json +++ b/Resources/Textures/Interface/Actions/actions_borg.rsi/meta.json @@ -9,6 +9,99 @@ "states": [ { "name": "state-laws" + }, + { + "name": "no-action" + }, + { + "name":"tool-module" + }, + { + "name":"wire-module" + }, + { + "name":"gps-module" + }, + { + "name":"extinguisher-module" + }, + { + "name":"geiger-module" + }, + { + "name":"rcd-module" + }, + { + "name":"adv-tools-module" + }, + { + "name":"construction-module" + }, + { + "name":"appraisal-module" + }, + { + "name":"grappling-module" + }, + { + "name":"mining-module" + }, + { + "name":"light-replacer-module" + }, + { + "name":"cleaning-module" + }, + { + "name":"adv-cleaning-module" + }, + { + "name":"diagnosis-module" + }, + { + "name":"treatment-module" + }, + { + "name":"adv-diagnosis-module" + }, + { + "name":"defib-module" + }, + { + "name":"node-scanner-module" + }, + { + "name":"anomaly-module" + }, + { + "name":"service-module" + }, + { + "name":"musical-module" + }, + { + "name":"gardening-module" + }, + { + "name":"harvesting-module" + }, + { + "name":"clowning-module" + }, + { + "name":"syndicate-weapon-module" + }, + { + "name":"syndicate-operative-module" + }, + { + "name":"syndicate-esword-module" + }, + { + "name":"syndicate-l6c-module" + }, + { + "name":"syndicate-martyr-module" } ] } diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/mining-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/mining-module.png new file mode 100644 index 00000000000..bfef5bfd04d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/mining-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/musical-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/musical-module.png new file mode 100644 index 00000000000..c611269ddfe Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/musical-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/no-action.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/no-action.png new file mode 100644 index 00000000000..4196b8b9f4e Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/no-action.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/node-scanner-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/node-scanner-module.png new file mode 100644 index 00000000000..4fa2554eadf Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/node-scanner-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/rcd-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/rcd-module.png new file mode 100644 index 00000000000..388c0c6e8fb Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/rcd-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/service-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/service-module.png new file mode 100644 index 00000000000..1c114ef314e Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/service-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-esword-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-esword-module.png new file mode 100644 index 00000000000..201149cf18e Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-esword-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-l6c-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-l6c-module.png new file mode 100644 index 00000000000..c06ce963580 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-l6c-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-martyr-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-martyr-module.png new file mode 100644 index 00000000000..771bb75f10d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-martyr-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-operative-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-operative-module.png new file mode 100644 index 00000000000..19642942f9d Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-operative-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-weapon-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-weapon-module.png new file mode 100644 index 00000000000..cd1f115e8bb Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/syndicate-weapon-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/tool-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/tool-module.png new file mode 100644 index 00000000000..c4658049760 Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/tool-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/treatment-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/treatment-module.png new file mode 100644 index 00000000000..988b3de761c Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/treatment-module.png differ diff --git a/Resources/Textures/Interface/Actions/actions_borg.rsi/wire-module.png b/Resources/Textures/Interface/Actions/actions_borg.rsi/wire-module.png new file mode 100644 index 00000000000..00361aa00fa Binary files /dev/null and b/Resources/Textures/Interface/Actions/actions_borg.rsi/wire-module.png differ diff --git a/Resources/Textures/Interface/Emotes/attributions.yml b/Resources/Textures/Interface/Emotes/attributions.yml index c65eeb25da0..6f547720b61 100644 --- a/Resources/Textures/Interface/Emotes/attributions.yml +++ b/Resources/Textures/Interface/Emotes/attributions.yml @@ -115,3 +115,8 @@ license: "CC-BY-SA-3.0" copyright: "Created by Sarahon" source: "https://github.com/Sarahon" + +- files: ["tailslap.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by Sarahon" + source: "https://github.com/Sarahon" diff --git a/Resources/Textures/Interface/Emotes/tailslap.png b/Resources/Textures/Interface/Emotes/tailslap.png new file mode 100644 index 00000000000..75405a67bab Binary files /dev/null and b/Resources/Textures/Interface/Emotes/tailslap.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-left.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-left.png new file mode 100644 index 00000000000..a623e75a285 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-right.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-right.png new file mode 100644 index 00000000000..45649b92618 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/eyeballs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-left.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-left.png new file mode 100644 index 00000000000..56698f87e29 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-right.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-right.png new file mode 100644 index 00000000000..c7fb3993998 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/heart-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/meta.json index 6141fd5de86..62d1f04bf0c 100644 --- a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Made by PixelTheKermit (github) for SS14", + "copyright": "Made by PixelTheKermit (github) for SS14, inhands by mubururu_ (github)", "size": { "x": 32, "y": 32 @@ -14,7 +14,23 @@ "name": "eyeball-r" }, { - "name": "tongue" + "name": "eyeballs-inhand-left", + "directions": 4 + }, + { + "name": "eyeballs-inhand-right", + "directions": 4 + }, + { + "name": "tongue" + }, + { + "name": "heart-inhand-left", + "directions": 4 + }, + { + "name": "heart-inhand-right", + "directions": 4 }, { "name": "heart-on", @@ -42,6 +58,14 @@ { "name": "stomach" }, + { + "name": "stomach-inhand-left", + "directions": 4 + }, + { + "name": "stomach-inhand-right", + "directions": 4 + }, { "name": "web-gland" } diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-left.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-left.png new file mode 100644 index 00000000000..a346078b4a8 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-right.png b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-right.png new file mode 100644 index 00000000000..001203bdc38 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Arachnid/organs.rsi/stomach-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-left.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-left.png new file mode 100644 index 00000000000..43e0cd40340 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-right.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-right.png new file mode 100644 index 00000000000..df6dded7441 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/brain-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-left.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-left.png new file mode 100644 index 00000000000..5d31e96a37a Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-right.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-right.png new file mode 100644 index 00000000000..4830b1e0a68 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs.png new file mode 100644 index 00000000000..77b61c344f7 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/lungs.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Diona/organs.rsi/meta.json index 1c1697efc71..de4f8f8dc39 100644 --- a/Resources/Textures/Mobs/Species/Diona/organs.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Diona/organs.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/commit/38051f45d3b26bd31f6e292239f43adb36ff01fe", + "copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/commit/38051f45d3b26bd31f6e292239f43adb36ff01fe lungs and inhands by mubururu_ (github)", "size": { "x": 32, "y": 32 @@ -10,6 +10,14 @@ { "name": "brain" }, + { + "name": "brain-inhand-left", + "directions": 4 + }, + { + "name": "brain-inhand-right", + "directions": 4 + }, { "name": "ears" }, @@ -19,8 +27,27 @@ { "name": "eyeball-r" }, + { + "name": "lungs" + }, + { + "name": "lungs-inhand-left", + "directions": 4 + }, + { + "name": "lungs-inhand-right", + "directions": 4 + }, { "name": "stomach" + }, + { + "name": "stomach-inhand-left", + "directions": 4 + }, + { + "name": "stomach-inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-left.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-left.png new file mode 100644 index 00000000000..e8d5a7c86c4 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-right.png b/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-right.png new file mode 100644 index 00000000000..257c0af83a3 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Diona/organs.rsi/stomach-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-left.png index cf6e4684e9c..b4d4d86dd56 100644 Binary files a/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-left.png and b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-right.png index 978d4f3c5f1..2360c07898b 100644 Binary files a/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-right.png and b/Resources/Textures/Mobs/Species/Human/organs.rsi/brain-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-left.png new file mode 100644 index 00000000000..d0187579a30 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-right.png new file mode 100644 index 00000000000..4dfafd118d0 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/eyeballs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-left.png new file mode 100644 index 00000000000..1d73edaf033 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-right.png new file mode 100644 index 00000000000..3f7ace9b485 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/heart-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-left.png new file mode 100644 index 00000000000..f8d889b5def Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-right.png new file mode 100644 index 00000000000..9b2e3131504 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/kidneys-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-left.png new file mode 100644 index 00000000000..d63f9e1ef43 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-right.png new file mode 100644 index 00000000000..69ce8ffd77e Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/liver-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-left.png new file mode 100644 index 00000000000..62b30a5ad5c Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-right.png new file mode 100644 index 00000000000..1a5e3a70582 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/lungs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json index 354132ea110..a9112535f82 100644 --- a/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Human/organs.rsi/meta.json @@ -1,80 +1,128 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation and cev-eris at https://github.com/tgstation/tgstation/commit/c4b7f3c41b6742aca260fe60cc358a778ba9b8c8 and https://github.com/discordia-space/CEV-Eris/commit/476e374cea95ff5e8b1603c48342bf700e2cd7af", + "copyright": "Taken from tgstation and cev-eris at https://github.com/tgstation/tgstation/commit/c4b7f3c41b6742aca260fe60cc358a778ba9b8c8 and https://github.com/discordia-space/CEV-Eris/commit/476e374cea95ff5e8b1603c48342bf700e2cd7af, inhands by mubururu_ (github)", "size": { "x": 32, "y": 32 }, - "states": [ - { - "name": "appendix" - }, - { - "name": "appendix-inflamed" - }, - { - "name": "brain" - }, - { - "name": "brain-inhand-left", - "directions": 4 - }, - { - "name": "brain-inhand-right", - "directions": 4 - }, - { - "name": "ears" - }, - { - "name": "eyeball-l" - }, - { - "name": "eyeball-r" - }, - { - "name": "heart-off" - }, - { - "name": "heart-on", - "delays": [ - [ - 0.6, - 0.1, - 0.1 - ] - ] - }, - { - "name": "kidney-l" - }, - { - "name": "kidney-r" - }, - { - "name": "liver" - }, - { - "name": "lung-l" - }, - { - "name": "lung-r" - }, - { - "name": "stomach" - }, - { - "name": "tongue" - }, - { - "name": "muscle" - }, - { - "name": "nerve" - }, - { - "name": "vessel" - } - ] + "states": [ + { + "name": "appendix" + }, + { + "name": "appendix-inflamed" + }, + { + "name": "brain" + }, + { + "name": "brain-inhand-left", + "directions": 4 + }, + { + "name": "brain-inhand-right", + "directions": 4 + }, + { + "name": "ears" + }, + { + "name": "eyeballs-inhand-left", + "directions": 4 + }, + { + "name": "eyeballs-inhand-right", + "directions": 4 + }, + { + "name": "eyeball-l" + }, + { + "name": "eyeball-r" + }, + { + "name": "heart-inhand-left", + "directions": 4 + }, + { + "name": "heart-inhand-right", + "directions": 4 + }, + { + "name": "heart-off" + }, + { + "name": "heart-on", + "delays": [ + [ + 0.6, + 0.1, + 0.1 + ] + ] + }, + { + "name": "kidneys-inhand-left", + "directions": 4 + }, + { + "name": "kidneys-inhand-right", + "directions": 4 + }, + { + "name": "kidney-l" + }, + { + "name": "kidney-r" + }, + { + "name": "liver" + }, + { + "name": "liver-inhand-left", + "directions": 4 + }, + { + "name": "liver-inhand-right", + "directions": 4 + }, + { + "name": "lungs-inhand-left", + "directions": 4 + }, + { + "name": "lungs-inhand-right", + "directions": 4 + }, + { + "name": "lung-l" + }, + { + "name": "lung-r" + }, + { + "name": "stomach" + }, + { + "name": "stomach-inhand-left", + "directions": 4 + }, + { + "name": "stomach-inhand-right", + "directions": 4 + }, + { + "name": "tongue" + }, + { + "name": "muscle" + }, + { + "name": "nerve" + }, + { + "name": "vessel" + } + ] } diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-left.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-left.png new file mode 100644 index 00000000000..a346078b4a8 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-right.png b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-right.png new file mode 100644 index 00000000000..001203bdc38 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Human/organs.rsi/stomach-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-left.png b/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-left.png index a536818eee9..c0c260afced 100644 Binary files a/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-left.png and b/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-right.png b/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-right.png index c7e0adca9a1..50f22d7a3f2 100644 Binary files a/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-right.png and b/Resources/Textures/Mobs/Species/Slime/organs.rsi/brain-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-left.png b/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-left.png new file mode 100644 index 00000000000..0354025ce91 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-right.png b/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-right.png new file mode 100644 index 00000000000..a3c6ba485ed Binary files /dev/null and b/Resources/Textures/Mobs/Species/Slime/organs.rsi/lungs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Slime/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Slime/organs.rsi/meta.json index cacfcdc40bb..451ab138dfa 100644 --- a/Resources/Textures/Mobs/Species/Slime/organs.rsi/meta.json +++ b/Resources/Textures/Mobs/Species/Slime/organs.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Sprited by Nimfar11 (Github) for Space Station 14", + "copyright": "Sprited by Nimfar11 (Github) for Space Station 14, inhands by mubururu_ (github)", "size": { "x": 32, "y": 32 @@ -15,8 +15,16 @@ "directions": 4 }, { - "name": "brain-inhand-right", - "directions": 4 + "name": "brain-inhand-right", + "directions": 4 + }, + { + "name": "lungs-inhand-left", + "directions": 4 + }, + { + "name": "lungs-inhand-right", + "directions": 4 }, { "name": "lung-l-slime" diff --git a/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-l.png b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-l.png new file mode 100644 index 00000000000..5ae28e95945 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-l.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-r.png b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-r.png new file mode 100644 index 00000000000..8110a03b473 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lung-r.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-left.png b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-left.png new file mode 100644 index 00000000000..4b9489e8384 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-left.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-right.png b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-right.png new file mode 100644 index 00000000000..b1fd54694e9 Binary files /dev/null and b/Resources/Textures/Mobs/Species/Vox/organs.rsi/lungs-inhand-right.png differ diff --git a/Resources/Textures/Mobs/Species/Vox/organs.rsi/meta.json b/Resources/Textures/Mobs/Species/Vox/organs.rsi/meta.json new file mode 100644 index 00000000000..1d7b44d6edb --- /dev/null +++ b/Resources/Textures/Mobs/Species/Vox/organs.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "made by mubururu_ (github)", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "lungs-inhand-left", + "directions": 4 + }, + { + "name": "lungs-inhand-right", + "directions": 4 + }, + { + "name": "lung-l" + }, + { + "name": "lung-r" + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/meta.json b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/meta.json index ced8d583736..644424690db 100644 --- a/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/meta.json +++ b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24, mime tart slice and banana cream pie slice from rosysyntax under under CC BY-SA 4.0", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/c6e3401f2e7e1e55c57060cdf956a98ef1fefc24, the pumpkin and pumpkin slice are made by ss14nekow (Mute Maid), the mime tart slice and banana cream pie slice are made by rosysyntax.", "size": { "x": 32, "y": 32 @@ -95,6 +95,12 @@ { "name": "plump" }, + { + "name": "pumpkin" + }, + { + "name": "pumpkin-slice" + }, { "name": "tin" }, diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png new file mode 100644 index 00000000000..e462de6b654 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin-slice.png differ diff --git a/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png new file mode 100644 index 00000000000..f2f18879448 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Food/Baked/pie.rsi/pumpkin.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/base.png b/Resources/Textures/Objects/Devices/ai_card.rsi/base.png index 244183c078c..535f5a48e99 100644 Binary files a/Resources/Textures/Objects/Devices/ai_card.rsi/base.png and b/Resources/Textures/Objects/Devices/ai_card.rsi/base.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png b/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png index 7e61f368de2..a62b9263d52 100644 Binary files a/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png and b/Resources/Textures/Objects/Devices/ai_card.rsi/empty.png differ diff --git a/Resources/Textures/Objects/Devices/ai_card.rsi/full.png b/Resources/Textures/Objects/Devices/ai_card.rsi/full.png index 59131c8c0aa..69a1825d91d 100644 Binary files a/Resources/Textures/Objects/Devices/ai_card.rsi/full.png and b/Resources/Textures/Objects/Devices/ai_card.rsi/full.png differ diff --git a/Resources/Textures/Objects/Fun/spectrallocator.rsi/icon.png b/Resources/Textures/Objects/Fun/spectrallocator.rsi/icon.png new file mode 100644 index 00000000000..b18dfd1c294 Binary files /dev/null and b/Resources/Textures/Objects/Fun/spectrallocator.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-left.png b/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-left.png new file mode 100644 index 00000000000..cb25c6e3cf2 Binary files /dev/null and b/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-right.png b/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-right.png new file mode 100644 index 00000000000..7c34512cb66 Binary files /dev/null and b/Resources/Textures/Objects/Fun/spectrallocator.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/spectrallocator.rsi/meta.json b/Resources/Textures/Objects/Fun/spectrallocator.rsi/meta.json new file mode 100644 index 00000000000..1556cd4eb9f --- /dev/null +++ b/Resources/Textures/Objects/Fun/spectrallocator.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Originally created by EmoGarbage404 (github) for Space Station 14, modified by Ilya246 (github) for Space Station 14.", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "screen", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Fun/spectrallocator.rsi/screen.png b/Resources/Textures/Objects/Fun/spectrallocator.rsi/screen.png new file mode 100644 index 00000000000..eb4ab6bbbec Binary files /dev/null and b/Resources/Textures/Objects/Fun/spectrallocator.rsi/screen.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json index 1495eccd7a6..0c29f3e3fb1 100644 --- a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/meta.json @@ -57,6 +57,15 @@ { "name": "syringe2" }, + { + "name": "minisyringe1" + }, + { + "name": "minisyringe2" + }, + { + "name": "minisyringe3" + }, { "name": "inhand-left", "directions": 4 diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png new file mode 100644 index 00000000000..66c49a744cd Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe1.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png new file mode 100644 index 00000000000..6f5d5663546 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe2.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png new file mode 100644 index 00000000000..71d3bad044a Binary files /dev/null and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/minisyringe3.png differ diff --git a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png index 7819a48c661..5faf746ae3a 100644 Binary files a/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png and b/Resources/Textures/Objects/Specific/Chemistry/syringe.rsi/syringeproj.png differ diff --git a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/equipped-BELT.png b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/equipped-BELT.png index 877a76785e8..e80c4fa942b 100644 Binary files a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/equipped-BELT.png and b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/equipped-BELT.png differ diff --git a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-left.png b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-left.png new file mode 100644 index 00000000000..19d43a58d25 Binary files /dev/null and b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-right.png b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-right.png new file mode 100644 index 00000000000..99d7bc37309 Binary files /dev/null and b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/meta.json b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/meta.json index 07cec4822e9..3a6e8e9d8e9 100644 --- a/Resources/Textures/Objects/Tools/appraisal-tool.rsi/meta.json +++ b/Resources/Textures/Objects/Tools/appraisal-tool.rsi/meta.json @@ -1,18 +1,26 @@ { - "copyright" : "Taken from https://github.com/tgstation/tgstation/blob/master/icons/obj/device.dmi state export_scanner at commit 7544e865d0771d9bd917461e9da16d301fe40fc3.", + "copyright" : "Taken from https://github.com/tgstation/tgstation/blob/master/icons/obj/device.dmi state export_scanner at commit 7544e865d0771d9bd917461e9da16d301fe40fc3. In-hand sprites made by obscenelytinyshark for use in SS14.", "license" : "CC-BY-SA-3.0", "size" : { "x" : 32, "y" : 32 }, - "states" : [ - { - "name" : "icon" - }, - { - "name": "equipped-BELT", - "directions": 4 - } - ], + "states": [ + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "equipped-BELT", + "directions": 4 + } + ], "version" : 1 } diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-1.png index 1784e7f6236..6a141b7efb1 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-2.png new file mode 100644 index 00000000000..2414fe500b9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-3.png new file mode 100644 index 00000000000..1784e7f6236 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-1.png index 3ff1180051b..d68f85845e5 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-2.png new file mode 100644 index 00000000000..50ea7c47ff3 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-3.png new file mode 100644 index 00000000000..94a2272c800 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/magb-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/meta.json index a72e24594e3..b2728ab02bb 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/anti_materiel.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/983ad377d25729357b7ff8025f8014bd2f6ae9f7/icons/obj/ammo.dmi , base and mag-1 by Alekshhh", + "copyright": "Taken from cev-eris at https://github.com/discordia-space/CEV-Eris/raw/983ad377d25729357b7ff8025f8014bd2f6ae9f7/icons/obj/ammo.dmi, base and mag-1 by Alekshhh", "size": { "x": 32, "y": 32 @@ -16,8 +16,20 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, { "name": "magb-1" + }, + { + "name": "magb-2" + }, + { + "name": "magb-3" } ] } diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-1.png index edcbdbd3851..c9f029f32a3 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-2.png new file mode 100644 index 00000000000..84725cfd321 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-3.png new file mode 100644 index 00000000000..532baa546b3 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-1.png index da73a5f853b..8b49392b77d 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-2.png new file mode 100644 index 00000000000..80e504f560e Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-3.png new file mode 100644 index 00000000000..0c582b01d57 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/mag10-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-1.png index 6a77161ab26..39e05c72114 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-2.png new file mode 100644 index 00000000000..8fd938dce0b Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-3.png new file mode 100644 index 00000000000..6a77161ab26 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/magb-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/meta.json index 878138f73fa..958aeb6f48e 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/caseless_rifle.rsi/meta.json @@ -19,12 +19,30 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, { "name": "magb-1" }, + { + "name": "magb-2" + }, + { + "name": "magb-3" + }, { "name": "mag10-1" }, + { + "name": "mag10-2" + }, + { + "name": "mag10-3" + }, { "name": "practice" }, diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-1.png index a6feb95c36c..36c872a4268 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-2.png new file mode 100644 index 00000000000..025bec650e0 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-3.png new file mode 100644 index 00000000000..31c1c6b5c76 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-1.png index 7fbc8f50acf..39e05c72114 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-2.png new file mode 100644 index 00000000000..8fd938dce0b Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-3.png new file mode 100644 index 00000000000..6a77161ab26 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/magb-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/meta.json index 22eef0aaf3a..ff9704e4fc7 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/light_rifle.rsi/meta.json @@ -16,9 +16,21 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, { "name": "magb-1" }, + { + "name": "magb-2" + }, + { + "name": "magb-3" + }, { "name": "incendiary" }, diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-1.png index a36a9226170..8b7d23dc6c4 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-2.png new file mode 100644 index 00000000000..6d2b4034e1f Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-3.png new file mode 100644 index 00000000000..7d2bcd29ec2 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/meta.json index 67af0dcd27b..bb09b47a524 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/magnum.rsi/meta.json @@ -16,6 +16,12 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, { "name": "cap" }, diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-1.png index f32f6862662..43d1f0f5f71 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-2.png new file mode 100644 index 00000000000..5d6d61f5dbf Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/meta.json index 6237fcbe6fe..dd1e88cffcb 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/pistol.rsi/meta.json @@ -13,6 +13,9 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, { "name": "incendiarydisplay" }, diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-1.png index 0c642312083..4d7eb62cbf2 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-2.png new file mode 100644 index 00000000000..24cf3a37fb9 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-3.png new file mode 100644 index 00000000000..68bb5da7bed Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/mag-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-1.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-1.png index a3d15b76e69..39e05c72114 100644 Binary files a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-1.png and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-1.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-2.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-2.png new file mode 100644 index 00000000000..8fd938dce0b Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-2.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-3.png b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-3.png new file mode 100644 index 00000000000..6a77161ab26 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/magb-3.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/meta.json index 18e89881dd9..5c5b4eaef38 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Ammunition/Boxes/rifle.rsi/meta.json @@ -16,9 +16,21 @@ { "name": "mag-1" }, + { + "name": "mag-2" + }, + { + "name": "mag-3" + }, { "name": "magb-1" }, + { + "name": "magb-2" + }, + { + "name": "magb-3" + }, { "name": "incendiary" }, diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png new file mode 100644 index 00000000000..b59a4d52682 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png new file mode 100644 index 00000000000..6a8268d27e3 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/inhand-right.png differ diff --git a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json similarity index 51% rename from Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/meta.json rename to Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json index 0bcf8b915a5..dae584eb812 100644 --- a/Resources/Textures/DeltaV/Objects/Consumable/Food/Baked/pie.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/meta.json @@ -1,25 +1,22 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "pumpkin-slice taken from https://github.com/fulpstation/fulpstation/commit/64d85bf269fd4e5f3be314bcedc3463cf1d57676 | pumpkin made by rosysyntax", + "copyright": "Taken from vgstation13 at https://github.com/vgstation-coders/vgstation13 at f91dfe2e0dba1b7a8b9a0fa18251340920979a62, held sprite by ScarKy0", "size": { "x": 32, "y": 32 }, "states": [ - { - "name": "pumpkin" - }, - { - "name": "pumpkin-slice" - }, { "name": "inhand-right", "directions": 4 }, + { + "name": "syringe_gun" + }, { "name": "inhand-left", "directions": 4 } ] -} +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png new file mode 100644 index 00000000000..972714d1c71 Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Guns/Cannons/syringe_gun.rsi/syringe_gun.png differ diff --git a/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/posters.rsi/meta.json index 751413ad0c0..a33df81531d 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_ poster29_legit modified by TJohnson.", + "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 | poster29_legit modified by TJohnson.", "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 00000000000..91954d0edc1 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/posters.rsi/poster52_legit.png differ