diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs index e533ef2dce0..f0e4b13356c 100644 --- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs +++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs @@ -31,19 +31,6 @@ public sealed partial class AtmosAlarmEntryContainer : BoxContainer [AtmosAlarmType.Danger] = "atmos-alerts-window-danger-state", }; - private Dictionary _gasShorthands = new Dictionary() - { - [Gas.Ammonia] = "NH₃", - [Gas.CarbonDioxide] = "CO₂", - [Gas.Frezon] = "F", - [Gas.Nitrogen] = "N₂", - [Gas.NitrousOxide] = "N₂O", - [Gas.Oxygen] = "O₂", - [Gas.Plasma] = "P", - [Gas.Tritium] = "T", - [Gas.WaterVapor] = "H₂O", - }; - public AtmosAlarmEntryContainer(NetEntity uid, EntityCoordinates? coordinates) { RobustXamlLoader.Load(this); @@ -162,12 +149,11 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs) { FixedPoint2 gasPercent = percent * 100f; - - var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "X"); + var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); var gasLabel = new Label() { - Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasShorthand), ("value", gasPercent)), + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)), FontOverride = normalFont, FontColorOverride = GetAlarmStateColor(alert), HorizontalAlignment = HAlignment.Center, diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs new file mode 100644 index 00000000000..563122f962c --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleBoundUserInterface.cs @@ -0,0 +1,40 @@ +using Content.Shared.Atmos.Components; + +namespace Content.Client.Atmos.Consoles; + +public sealed class AtmosMonitoringConsoleBoundUserInterface : BoundUserInterface +{ + [ViewVariables] + private AtmosMonitoringConsoleWindow? _menu; + + public AtmosMonitoringConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { } + + protected override void Open() + { + base.Open(); + + _menu = new AtmosMonitoringConsoleWindow(this, Owner); + _menu.OpenCentered(); + _menu.OnClose += Close; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not AtmosMonitoringConsoleBoundInterfaceState castState) + return; + + EntMan.TryGetComponent(Owner, out var xform); + _menu?.UpdateUI(xform?.Coordinates, castState.AtmosNetworks); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) + return; + + _menu?.Dispose(); + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs new file mode 100644 index 00000000000..c23ebb64355 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleNavMapControl.cs @@ -0,0 +1,295 @@ +using Content.Client.Pinpointer.UI; +using Content.Shared.Atmos.Components; +using Content.Shared.Pinpointer; +using Robust.Client.Graphics; +using Robust.Shared.Collections; +using Robust.Shared.Map.Components; +using System.Linq; +using System.Numerics; + +namespace Content.Client.Atmos.Consoles; + +public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl +{ + [Dependency] private readonly IEntityManager _entManager = default!; + + public bool ShowPipeNetwork = true; + public int? FocusNetId = null; + + private const int ChunkSize = 4; + + private readonly Color _basePipeNetColor = Color.LightGray; + private readonly Color _unfocusedPipeNetColor = Color.DimGray; + + private List _atmosPipeNetwork = new(); + private Dictionary _sRGBLookUp = new Dictionary(); + + // Look up tables for merging continuous lines. Indexed by line color + private Dictionary> _horizLines = new(); + private Dictionary> _horizLinesReversed = new(); + private Dictionary> _vertLines = new(); + private Dictionary> _vertLinesReversed = new(); + + public AtmosMonitoringConsoleNavMapControl() : base() + { + PostWallDrawingAction += DrawAllPipeNetworks; + } + + protected override void UpdateNavMap() + { + base.UpdateNavMap(); + + if (!_entManager.TryGetComponent(Owner, out var console)) + return; + + if (!_entManager.TryGetComponent(MapUid, out var grid)) + return; + + _atmosPipeNetwork = GetDecodedAtmosPipeChunks(console.AtmosPipeChunks, grid); + } + + private void DrawAllPipeNetworks(DrawingHandleScreen handle) + { + if (!ShowPipeNetwork) + return; + + // Draw networks + if (_atmosPipeNetwork != null && _atmosPipeNetwork.Any()) + DrawPipeNetwork(handle, _atmosPipeNetwork); + } + + private void DrawPipeNetwork(DrawingHandleScreen handle, List atmosPipeNetwork) + { + var offset = GetOffset(); + offset = offset with { Y = -offset.Y }; + + if (WorldRange / WorldMaxRange > 0.5f) + { + var pipeNetworks = new Dictionary>(); + + foreach (var chunkedLine in atmosPipeNetwork) + { + var start = ScalePosition(chunkedLine.Origin - offset); + var end = ScalePosition(chunkedLine.Terminus - offset); + + if (!pipeNetworks.TryGetValue(chunkedLine.Color, out var subNetwork)) + subNetwork = new ValueList(); + + subNetwork.Add(start); + subNetwork.Add(end); + + pipeNetworks[chunkedLine.Color] = subNetwork; + } + + foreach ((var color, var subNetwork) in pipeNetworks) + { + if (subNetwork.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.LineList, subNetwork.Span, color); + } + } + + else + { + var pipeVertexUVs = new Dictionary>(); + + foreach (var chunkedLine in atmosPipeNetwork) + { + var leftTop = ScalePosition(new Vector2 + (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f, + Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f) + - offset); + + var rightTop = ScalePosition(new Vector2 + (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f, + Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f) + - offset); + + var leftBottom = ScalePosition(new Vector2 + (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f, + Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f) + - offset); + + var rightBottom = ScalePosition(new Vector2 + (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f, + Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f) + - offset); + + if (!pipeVertexUVs.TryGetValue(chunkedLine.Color, out var pipeVertexUV)) + pipeVertexUV = new ValueList(); + + pipeVertexUV.Add(leftBottom); + pipeVertexUV.Add(leftTop); + pipeVertexUV.Add(rightBottom); + pipeVertexUV.Add(leftTop); + pipeVertexUV.Add(rightBottom); + pipeVertexUV.Add(rightTop); + + pipeVertexUVs[chunkedLine.Color] = pipeVertexUV; + } + + foreach ((var color, var pipeVertexUV) in pipeVertexUVs) + { + if (pipeVertexUV.Count > 0) + handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, pipeVertexUV.Span, color); + } + } + } + + private List GetDecodedAtmosPipeChunks(Dictionary? chunks, MapGridComponent? grid) + { + var decodedOutput = new List(); + + if (chunks == null || grid == null) + return decodedOutput; + + // Clear stale look up table values + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + // Generate masks + var northMask = (ulong)1 << 0; + var southMask = (ulong)1 << 1; + var westMask = (ulong)1 << 2; + var eastMask = (ulong)1 << 3; + + foreach ((var chunkOrigin, var chunk) in chunks) + { + var list = new List(); + + foreach (var ((netId, hexColor), atmosPipeData) in chunk.AtmosPipeData) + { + // Determine the correct coloration for the pipe + var color = Color.FromHex(hexColor) * _basePipeNetColor; + + if (FocusNetId != null && FocusNetId != netId) + color *= _unfocusedPipeNetColor; + + // Get the associated line look up tables + if (!_horizLines.TryGetValue(color, out var horizLines)) + { + horizLines = new(); + _horizLines[color] = horizLines; + } + + if (!_horizLinesReversed.TryGetValue(color, out var horizLinesReversed)) + { + horizLinesReversed = new(); + _horizLinesReversed[color] = horizLinesReversed; + } + + if (!_vertLines.TryGetValue(color, out var vertLines)) + { + vertLines = new(); + _vertLines[color] = vertLines; + } + + if (!_vertLinesReversed.TryGetValue(color, out var vertLinesReversed)) + { + vertLinesReversed = new(); + _vertLinesReversed[color] = vertLinesReversed; + } + + // Loop over the chunk + for (var tileIdx = 0; tileIdx < ChunkSize * ChunkSize; tileIdx++) + { + if (atmosPipeData == 0) + continue; + + var mask = (ulong)SharedNavMapSystem.AllDirMask << tileIdx * SharedNavMapSystem.Directions; + + if ((atmosPipeData & mask) == 0) + continue; + + var relativeTile = GetTileFromIndex(tileIdx); + var tile = (chunk.Origin * ChunkSize + relativeTile) * grid.TileSize; + tile = tile with { Y = -tile.Y }; + + // Calculate the draw point offsets + var vertLineOrigin = (atmosPipeData & northMask << tileIdx * SharedNavMapSystem.Directions) > 0 ? + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 1f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f); + + var vertLineTerminus = (atmosPipeData & southMask << tileIdx * SharedNavMapSystem.Directions) > 0 ? + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f); + + var horizLineOrigin = (atmosPipeData & eastMask << tileIdx * SharedNavMapSystem.Directions) > 0 ? + new Vector2(grid.TileSize * 1f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f); + + var horizLineTerminus = (atmosPipeData & westMask << tileIdx * SharedNavMapSystem.Directions) > 0 ? + new Vector2(grid.TileSize * 0f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f); + + // Since we can have pipe lines that have a length of a half tile, + // double the vectors and convert to vector2i so we can merge them + AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, 2), ConvertVector2ToVector2i(tile + horizLineTerminus, 2), horizLines, horizLinesReversed); + AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, 2), ConvertVector2ToVector2i(tile + vertLineTerminus, 2), vertLines, vertLinesReversed); + } + } + } + + // Scale the vector2is back down and convert to vector2 + foreach (var (color, horizLines) in _horizLines) + { + // Get the corresponding sRBG color + var sRGB = GetsRGBColor(color); + + foreach (var (origin, terminal) in horizLines) + decodedOutput.Add(new AtmosMonitoringConsoleLine + (ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB)); + } + + foreach (var (color, vertLines) in _vertLines) + { + // Get the corresponding sRBG color + var sRGB = GetsRGBColor(color); + + foreach (var (origin, terminal) in vertLines) + decodedOutput.Add(new AtmosMonitoringConsoleLine + (ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB)); + } + + return decodedOutput; + } + + private Vector2 ConvertVector2iToVector2(Vector2i vector, float scale = 1f) + { + return new Vector2(vector.X * scale, vector.Y * scale); + } + + private Vector2i ConvertVector2ToVector2i(Vector2 vector, float scale = 1f) + { + return new Vector2i((int)MathF.Round(vector.X * scale), (int)MathF.Round(vector.Y * scale)); + } + + private Vector2i GetTileFromIndex(int index) + { + var x = index / ChunkSize; + var y = index % ChunkSize; + return new Vector2i(x, y); + } + + private Color GetsRGBColor(Color color) + { + if (!_sRGBLookUp.TryGetValue(color, out var sRGB)) + { + sRGB = Color.ToSrgb(color); + _sRGBLookUp[color] = sRGB; + } + + return sRGB; + } +} + +public struct AtmosMonitoringConsoleLine +{ + public readonly Vector2 Origin; + public readonly Vector2 Terminus; + public readonly Color Color; + + public AtmosMonitoringConsoleLine(Vector2 origin, Vector2 terminus, Color color) + { + Origin = origin; + Terminus = terminus; + Color = color; + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs new file mode 100644 index 00000000000..bfbb05d2ab1 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs @@ -0,0 +1,69 @@ +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Consoles; +using Robust.Shared.GameStates; + +namespace Content.Client.Atmos.Consoles; + +public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleState); + } + + private void OnHandleState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentHandleState args) + { + Dictionary> modifiedChunks; + Dictionary atmosDevices; + + switch (args.Current) + { + case AtmosMonitoringConsoleDeltaState delta: + { + modifiedChunks = delta.ModifiedChunks; + atmosDevices = delta.AtmosDevices; + + foreach (var index in component.AtmosPipeChunks.Keys) + { + if (!delta.AllChunks!.Contains(index)) + component.AtmosPipeChunks.Remove(index); + } + + break; + } + + case AtmosMonitoringConsoleState state: + { + modifiedChunks = state.Chunks; + atmosDevices = state.AtmosDevices; + + foreach (var index in component.AtmosPipeChunks.Keys) + { + if (!state.Chunks.ContainsKey(index)) + component.AtmosPipeChunks.Remove(index); + } + + break; + } + default: + return; + } + + foreach (var (origin, chunk) in modifiedChunks) + { + var newChunk = new AtmosPipeChunk(origin); + newChunk.AtmosPipeData = new Dictionary<(int, string), ulong>(chunk); + + component.AtmosPipeChunks[origin] = newChunk; + } + + component.AtmosDevices.Clear(); + + foreach (var (nuid, atmosDevice) in atmosDevices) + { + component.AtmosDevices[nuid] = atmosDevice; + } + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml new file mode 100644 index 00000000000..b6fde7592fd --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs new file mode 100644 index 00000000000..515f91790f4 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringConsoleWindow.xaml.cs @@ -0,0 +1,455 @@ +using Content.Client.Pinpointer.UI; +using Content.Client.UserInterface.Controls; +using Content.Shared.Atmos.Components; +using Content.Shared.Prototypes; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow +{ + private readonly IEntityManager _entManager; + private readonly IPrototypeManager _protoManager; + private readonly SpriteSystem _spriteSystem; + + private EntityUid? _owner; + private NetEntity? _focusEntity; + private int? _focusNetId; + + private bool _autoScrollActive = false; + + private readonly Color _unfocusedDeviceColor = Color.DimGray; + private ProtoId _navMapConsoleProtoId = "NavMapConsole"; + private ProtoId _gasPipeSensorProtoId = "GasPipeSensor"; + + public AtmosMonitoringConsoleWindow(AtmosMonitoringConsoleBoundUserInterface userInterface, EntityUid? owner) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _protoManager = IoCManager.Resolve(); + _spriteSystem = _entManager.System(); + + // Pass the owner to nav map + _owner = owner; + NavMap.Owner = _owner; + + // Set nav map grid uid + var stationName = Loc.GetString("atmos-monitoring-window-unknown-location"); + EntityCoordinates? consoleCoords = null; + + if (_entManager.TryGetComponent(owner, out var xform)) + { + consoleCoords = xform.Coordinates; + NavMap.MapUid = xform.GridUid; + + // Assign station name + if (_entManager.TryGetComponent(xform.GridUid, out var stationMetaData)) + stationName = stationMetaData.EntityName; + + var msg = new FormattedMessage(); + msg.TryAddMarkup(Loc.GetString("atmos-monitoring-window-station-name", ("stationName", stationName)), out _); + + StationName.SetMessage(msg); + } + + else + { + StationName.SetMessage(stationName); + NavMap.Visible = false; + } + + // Set trackable entity selected action + NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap; + + // Update nav map + NavMap.ForceNavMapUpdate(); + + // Set tab container headers + MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-monitoring-window-tab-networks")); + + // Set UI toggles + ShowPipeNetwork.OnToggled += _ => OnShowPipeNetworkToggled(); + ShowGasPipeSensors.OnToggled += _ => OnShowGasPipeSensors(); + + // Set nav map colors + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + NavMap.TileColor = console.NavMapTileColor; + NavMap.WallColor = console.NavMapWallColor; + + // Initalize + UpdateUI(consoleCoords, Array.Empty()); + } + + #region Toggle handling + + private void OnShowPipeNetworkToggled() + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + NavMap.ShowPipeNetwork = ShowPipeNetwork.Pressed; + + foreach (var (netEnt, device) in console.AtmosDevices) + { + if (device.NavMapBlip == _gasPipeSensorProtoId) + continue; + + if (ShowPipeNetwork.Pressed) + AddTrackedEntityToNavMap(device); + + else + NavMap.TrackedEntities.Remove(netEnt); + } + } + + private void OnShowGasPipeSensors() + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + foreach (var (netEnt, device) in console.AtmosDevices) + { + if (device.NavMapBlip != _gasPipeSensorProtoId) + continue; + + if (ShowGasPipeSensors.Pressed) + AddTrackedEntityToNavMap(device, true); + + else + NavMap.TrackedEntities.Remove(netEnt); + } + } + + #endregion + + public void UpdateUI + (EntityCoordinates? consoleCoords, + AtmosMonitoringConsoleEntry[] atmosNetworks) + { + if (_owner == null) + return; + + if (!_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + // Reset nav map values + NavMap.TrackedCoordinates.Clear(); + NavMap.TrackedEntities.Clear(); + + if (_focusEntity != null && !console.AtmosDevices.Any(x => x.Key == _focusEntity)) + ClearFocus(); + + // Add tracked entities to the nav map + UpdateNavMapBlips(); + + // Show the monitor location + var consoleNetEnt = _entManager.GetNetEntity(_owner); + + if (consoleCoords != null && consoleNetEnt != null) + { + var proto = _protoManager.Index(_navMapConsoleProtoId); + + if (proto.TexturePaths != null && proto.TexturePaths.Length != 0) + { + var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(proto.TexturePaths[0])); + var blip = new NavMapBlip(consoleCoords.Value, texture, proto.Color, proto.Blinks, proto.Selectable); + NavMap.TrackedEntities[consoleNetEnt.Value] = blip; + } + } + + // Update the nav map + NavMap.ForceNavMapUpdate(); + + // Clear excess children from the tables + while (AtmosNetworksTable.ChildCount > atmosNetworks.Length) + AtmosNetworksTable.RemoveChild(AtmosNetworksTable.GetChild(AtmosNetworksTable.ChildCount - 1)); + + // Update all entries in each table + for (int index = 0; index < atmosNetworks.Length; index++) + { + var entry = atmosNetworks.ElementAt(index); + UpdateUIEntry(entry, index, AtmosNetworksTable, console); + } + } + + private void UpdateNavMapBlips() + { + if (_owner == null || !_entManager.TryGetComponent(_owner.Value, out var console)) + return; + + if (NavMap.Visible) + { + foreach (var (netEnt, device) in console.AtmosDevices) + { + // Update the focus network ID, incase it has changed + if (_focusEntity == netEnt) + { + _focusNetId = device.NetId; + NavMap.FocusNetId = _focusNetId; + } + + var isSensor = device.NavMapBlip == _gasPipeSensorProtoId; + + // Skip network devices if the toggled is off + if (!ShowPipeNetwork.Pressed && !isSensor) + continue; + + // Skip gas pipe sensors if the toggle is off + if (!ShowGasPipeSensors.Pressed && isSensor) + continue; + + AddTrackedEntityToNavMap(device, isSensor); + } + } + } + + private void AddTrackedEntityToNavMap(AtmosDeviceNavMapData metaData, bool isSensor = false) + { + var proto = _protoManager.Index(metaData.NavMapBlip); + + if (proto.TexturePaths == null || proto.TexturePaths.Length == 0) + return; + + var idx = Math.Clamp((int)metaData.Direction / 2, 0, proto.TexturePaths.Length - 1); + var texture = proto.TexturePaths.Length > 0 ? proto.TexturePaths[idx] : proto.TexturePaths[0]; + var color = isSensor ? proto.Color : proto.Color * metaData.PipeColor; + + if (_focusNetId != null && metaData.NetId != _focusNetId) + color *= _unfocusedDeviceColor; + + var blinks = proto.Blinks || _focusEntity == metaData.NetEntity; + var coords = _entManager.GetCoordinates(metaData.NetCoordinates); + var blip = new NavMapBlip(coords, _spriteSystem.Frame0(new SpriteSpecifier.Texture(texture)), color, blinks, proto.Selectable, proto.Scale); + NavMap.TrackedEntities[metaData.NetEntity] = blip; + } + + private void UpdateUIEntry(AtmosMonitoringConsoleEntry data, int index, Control table, AtmosMonitoringConsoleComponent console) + { + // Make new UI entry if required + if (index >= table.ChildCount) + { + var newEntryContainer = new AtmosMonitoringEntryContainer(data); + + // On click + newEntryContainer.FocusButton.OnButtonUp += args => + { + if (_focusEntity == newEntryContainer.Data.NetEntity) + { + ClearFocus(); + } + + else + { + SetFocus(newEntryContainer.Data.NetEntity, newEntryContainer.Data.NetId); + + var coords = _entManager.GetCoordinates(newEntryContainer.Data.Coordinates); + NavMap.CenterToCoordinates(coords); + } + + // Update affected UI elements across all tables + UpdateConsoleTable(console, AtmosNetworksTable, _focusEntity); + }; + + // Add the entry to the current table + table.AddChild(newEntryContainer); + } + + // Update values and UI elements + var tableChild = table.GetChild(index); + + if (tableChild is not AtmosMonitoringEntryContainer) + { + table.RemoveChild(tableChild); + UpdateUIEntry(data, index, table, console); + + return; + } + + var entryContainer = (AtmosMonitoringEntryContainer)tableChild; + entryContainer.UpdateEntry(data, data.NetEntity == _focusEntity); + } + + private void UpdateConsoleTable(AtmosMonitoringConsoleComponent console, Control table, NetEntity? currTrackedEntity) + { + foreach (var tableChild in table.Children) + { + if (tableChild is not AtmosAlarmEntryContainer) + continue; + + var entryContainer = (AtmosAlarmEntryContainer)tableChild; + + if (entryContainer.NetEntity != currTrackedEntity) + entryContainer.RemoveAsFocus(); + + else if (entryContainer.NetEntity == currTrackedEntity) + entryContainer.SetAsFocus(); + } + } + + private void SetTrackedEntityFromNavMap(NetEntity? focusEntity) + { + if (focusEntity == null) + return; + + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + foreach (var (netEnt, device) in console.AtmosDevices) + { + if (netEnt != focusEntity) + continue; + + if (device.NavMapBlip != _gasPipeSensorProtoId) + return; + + // Set new focus + SetFocus(focusEntity.Value, device.NetId); + + // Get the scroll position of the selected entity on the selected button the UI + ActivateAutoScrollToFocus(); + + break; + } + } + + protected override void FrameUpdate(FrameEventArgs args) + { + AutoScrollToFocus(); + } + + private void ActivateAutoScrollToFocus() + { + _autoScrollActive = true; + } + + private void AutoScrollToFocus() + { + if (!_autoScrollActive) + return; + + var scroll = AtmosNetworksTable.Parent as ScrollContainer; + if (scroll == null) + return; + + if (!TryGetVerticalScrollbar(scroll, out var vScrollbar)) + return; + + if (!TryGetNextScrollPosition(out float? nextScrollPosition)) + return; + + vScrollbar.ValueTarget = nextScrollPosition.Value; + + if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget)) + _autoScrollActive = false; + } + + private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar) + { + vScrollBar = null; + + foreach (var control in scroll.Children) + { + if (control is not VScrollBar) + continue; + + vScrollBar = (VScrollBar)control; + + return true; + } + + return false; + } + + private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition) + { + nextScrollPosition = null; + + var scroll = AtmosNetworksTable.Parent as ScrollContainer; + if (scroll == null) + return false; + + var container = scroll.Children.ElementAt(0) as BoxContainer; + if (container == null || container.Children.Count() == 0) + return false; + + // Exit if the heights of the children haven't been initialized yet + if (!container.Children.Any(x => x.Height > 0)) + return false; + + nextScrollPosition = 0; + + foreach (var control in container.Children) + { + if (control is not AtmosMonitoringEntryContainer) + continue; + + var entry = (AtmosMonitoringEntryContainer)control; + + if (entry.Data.NetEntity == _focusEntity) + return true; + + nextScrollPosition += control.Height; + } + + // Failed to find control + nextScrollPosition = null; + + return false; + } + + private void SetFocus(NetEntity focusEntity, int focusNetId) + { + _focusEntity = focusEntity; + _focusNetId = focusNetId; + NavMap.FocusNetId = focusNetId; + + OnFocusChanged(); + } + + private void ClearFocus() + { + _focusEntity = null; + _focusNetId = null; + NavMap.FocusNetId = null; + + OnFocusChanged(); + } + + private void OnFocusChanged() + { + UpdateNavMapBlips(); + NavMap.ForceNavMapUpdate(); + + if (!_entManager.TryGetComponent(_owner, out var console)) + return; + + for (int index = 0; index < AtmosNetworksTable.ChildCount; index++) + { + var entry = (AtmosMonitoringEntryContainer)AtmosNetworksTable.GetChild(index); + + if (entry == null) + continue; + + UpdateUIEntry(entry.Data, index, AtmosNetworksTable, console); + } + } +} diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml new file mode 100644 index 00000000000..6a19f0775f9 --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml @@ -0,0 +1,74 @@ + + + + + diff --git a/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs new file mode 100644 index 00000000000..0ce0c9c880a --- /dev/null +++ b/Content.Client/Atmos/Consoles/AtmosMonitoringEntryContainer.xaml.cs @@ -0,0 +1,166 @@ +using Content.Client.Stylesheets; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.FixedPoint; +using Content.Shared.Temperature; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using System.Linq; + +namespace Content.Client.Atmos.Consoles; + +[GenerateTypedNameReferences] +public sealed partial class AtmosMonitoringEntryContainer : BoxContainer +{ + public AtmosMonitoringConsoleEntry Data; + + private readonly IEntityManager _entManager; + private readonly IResourceCache _cache; + + public AtmosMonitoringEntryContainer(AtmosMonitoringConsoleEntry data) + { + RobustXamlLoader.Load(this); + _entManager = IoCManager.Resolve(); + _cache = IoCManager.Resolve(); + + Data = data; + + // Modulate colored stripe + NetworkColorStripe.Modulate = data.Color; + + // Load fonts + var headerFont = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), 11); + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + + // Set fonts + TemperatureHeaderLabel.FontOverride = headerFont; + PressureHeaderLabel.FontOverride = headerFont; + TotalMolHeaderLabel.FontOverride = headerFont; + GasesHeaderLabel.FontOverride = headerFont; + + TemperatureLabel.FontOverride = normalFont; + PressureLabel.FontOverride = normalFont; + TotalMolLabel.FontOverride = normalFont; + + NoDataLabel.FontOverride = headerFont; + } + + public void UpdateEntry(AtmosMonitoringConsoleEntry updatedData, bool isFocus) + { + // Load fonts + var normalFont = new VectorFont(_cache.GetResource("/Fonts/NotoSansDisplay/NotoSansDisplay-Regular.ttf"), 11); + + // Update name and values + if (!string.IsNullOrEmpty(updatedData.Address)) + NetworkNameLabel.Text = Loc.GetString("atmos-alerts-window-alarm-label", ("name", updatedData.EntityName), ("address", updatedData.Address)); + + else + NetworkNameLabel.Text = Loc.GetString(updatedData.EntityName); + + Data = updatedData; + + // Modulate colored stripe + NetworkColorStripe.Modulate = Data.Color; + + // Focus updates + if (isFocus) + SetAsFocus(); + else + RemoveAsFocus(); + + // Check if powered + if (!updatedData.IsPowered) + { + MainDataContainer.Visible = false; + NoDataLabel.Visible = true; + + return; + } + + // Set container visibility + MainDataContainer.Visible = true; + NoDataLabel.Visible = false; + + // Update temperature + var isNotVacuum = updatedData.TotalMolData > 1e-6f; + var tempK = (FixedPoint2)updatedData.TemperatureData; + var tempC = (FixedPoint2)TemperatureHelpers.KelvinToCelsius(tempK.Float()); + + TemperatureLabel.Text = isNotVacuum ? + Loc.GetString("atmos-alerts-window-temperature-value", ("valueInC", tempC), ("valueInK", tempK)) : + Loc.GetString("atmos-alerts-window-invalid-value"); + + TemperatureLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore; + + // Update pressure + PressureLabel.Text = Loc.GetString("atmos-alerts-window-pressure-value", ("value", (FixedPoint2)updatedData.PressureData)); + PressureLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore; + + // Update total mol + TotalMolLabel.Text = Loc.GetString("atmos-alerts-window-total-mol-value", ("value", (FixedPoint2)updatedData.TotalMolData)); + TotalMolLabel.FontColorOverride = isNotVacuum ? Color.DarkGray : StyleNano.DisabledFore; + + // Update other present gases + GasGridContainer.RemoveAllChildren(); + + if (updatedData.GasData.Count() == 0) + { + // No gases + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value-nil"), + FontOverride = normalFont, + FontColorOverride = StyleNano.DisabledFore, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + + else + { + // Add an entry for each gas + foreach (var (gas, percent) in updatedData.GasData) + { + var gasPercent = (FixedPoint2)0f; + gasPercent = percent * 100f; + + var gasAbbreviation = Atmospherics.GasAbbreviations.GetValueOrDefault(gas, Loc.GetString("gas-unknown-abbreviation")); + + var gasLabel = new Label() + { + Text = Loc.GetString("atmos-alerts-window-other-gases-value", ("shorthand", gasAbbreviation), ("value", gasPercent)), + FontOverride = normalFont, + HorizontalAlignment = HAlignment.Center, + VerticalAlignment = VAlignment.Center, + HorizontalExpand = true, + Margin = new Thickness(0, 2, 0, 0), + SetHeight = 24f, + }; + + GasGridContainer.AddChild(gasLabel); + } + } + } + + public void SetAsFocus() + { + FocusButton.AddStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/inverted_triangle.svg.png"; + FocusContainer.Visible = true; + } + + public void RemoveAsFocus() + { + FocusButton.RemoveStyleClass(StyleNano.StyleClassButtonColorGreen); + ArrowTexture.TexturePath = "/Textures/Interface/Nano/triangle_right.png"; + FocusContainer.Visible = false; + } +} diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs index 32e9f4ae9be..aa61e73e31c 100644 --- a/Content.Client/Chat/UI/SpeechBubble.cs +++ b/Content.Client/Chat/UI/SpeechBubble.cs @@ -2,6 +2,7 @@ using Content.Client.Chat.Managers; using Content.Shared.CCVar; using Content.Shared.Chat; +using Content.Shared.Speech; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -141,7 +142,12 @@ protected override void FrameUpdate(FrameEventArgs args) Modulate = Color.White; } - var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -EntityVerticalOffset; + var baseOffset = 0f; + + if (_entityManager.TryGetComponent(_senderEntity, out var speech)) + baseOffset = speech.SpeechBubbleOffset; + + var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset); var worldPos = _transformSystem.GetWorldPosition(xform) + offset; var lowerCenter = _eyeManager.WorldToScreen(worldPos) / UIScale; diff --git a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs index 5eace08a7fd..20c61f10cb8 100644 --- a/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs +++ b/Content.Client/Chemistry/UI/ChemMasterWindow.xaml.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Numerics; using Content.Shared.FixedPoint; +using Robust.Client.Graphics; using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Chemistry.UI @@ -90,10 +91,40 @@ public ChemMasterWindow() private ReagentButton MakeReagentButton(string text, ChemMasterReagentAmount amount, ReagentId id, bool isBuffer, string styleClass) { - var button = new ReagentButton(text, amount, id, isBuffer, styleClass); - button.OnPressed += args - => OnReagentButtonPressed?.Invoke(args, button); - return button; + var reagentTransferButton = new ReagentButton(text, amount, id, isBuffer, styleClass); + reagentTransferButton.OnPressed += args + => OnReagentButtonPressed?.Invoke(args, reagentTransferButton); + return reagentTransferButton; + } + /// + /// Conditionally generates a set of reagent buttons based on the supplied boolean argument. + /// This was moved outside of BuildReagentRow to facilitate conditional logic, stops indentation depth getting out of hand as well. + /// + private List CreateReagentTransferButtons(ReagentId reagent, bool isBuffer, bool addReagentButtons) + { + if (!addReagentButtons) + return new List(); // Return an empty list if reagentTransferButton creation is disabled. + + var buttonConfigs = new (string text, ChemMasterReagentAmount amount, string styleClass)[] + { + ("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth), + ("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth), + ("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth), + ("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth), + ("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth), + ("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth), + (Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft), + }; + + var buttons = new List(); + + foreach (var (text, amount, styleClass) in buttonConfigs) + { + var reagentTransferButton = MakeReagentButton(text, amount, reagent, isBuffer, styleClass); + buttons.Add(reagentTransferButton); + } + + return buttons; } /// @@ -102,25 +133,36 @@ private ReagentButton MakeReagentButton(string text, ChemMasterReagentAmount amo /// State data sent by the server. public void UpdateState(BoundUserInterfaceState state) { - var castState = (ChemMasterBoundUserInterfaceState) state; + var castState = (ChemMasterBoundUserInterfaceState)state; + if (castState.UpdateLabel) LabelLine = GenerateLabel(castState); - UpdatePanelInfo(castState); - - var output = castState.OutputContainerInfo; + // Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on + UpdatePanelInfo(castState); + BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u"; - + InputEjectButton.Disabled = castState.InputContainerInfo is null; - OutputEjectButton.Disabled = output is null; - CreateBottleButton.Disabled = output?.Reagents == null; - CreatePillButton.Disabled = output?.Entities == null; - + OutputEjectButton.Disabled = castState.OutputContainerInfo is null; + CreateBottleButton.Disabled = castState.OutputContainerInfo?.Reagents == null; + CreatePillButton.Disabled = castState.OutputContainerInfo?.Entities == null; + + UpdateDosageFields(castState); + } + + //assign default values for pill and bottle fields. + private void UpdateDosageFields(ChemMasterBoundUserInterfaceState castState) + { + var output = castState.OutputContainerInfo; var remainingCapacity = output is null ? 0 : (output.MaxVolume - output.CurrentVolume).Int(); var holdsReagents = output?.Reagents != null; var pillNumberMax = holdsReagents ? 0 : remainingCapacity; var bottleAmountMax = holdsReagents ? remainingCapacity : 0; + var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0; + PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit); + PillTypeButtons[castState.SelectedPillType].Pressed = true; PillNumber.IsValid = x => x >= 0 && x <= pillNumberMax; PillDosage.IsValid = x => x > 0 && x <= castState.PillDosageLimit; @@ -130,8 +172,19 @@ public void UpdateState(BoundUserInterfaceState state) PillNumber.Value = pillNumberMax; if (BottleDosage.Value > bottleAmountMax) BottleDosage.Value = bottleAmountMax; - } + // Avoid division by zero + if (PillDosage.Value > 0) + { + PillNumber.Value = Math.Min(bufferVolume / PillDosage.Value, pillNumberMax); + } + else + { + PillNumber.Value = 0; + } + + BottleDosage.Value = Math.Min(bottleAmountMax, bufferVolume); + } /// /// Generate a product label based on reagents in the buffer. /// @@ -178,46 +231,23 @@ private void UpdatePanelInfo(ChemMasterBoundUserInterfaceState state) var bufferVol = new Label { Text = $"{state.BufferCurrentVolume}u", - StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} + StyleClasses = { StyleNano.StyleClassLabelSecondaryColor } }; bufferHBox.AddChild(bufferVol); + // initialises rowCount to allow for striped rows + + var rowCount = 0; foreach (var (reagent, quantity) in state.BufferReagents) { - // Try to get the prototype for the given reagent. This gives us its name. - _prototypeManager.TryIndex(reagent.Prototype, out ReagentPrototype? proto); + var reagentId = reagent; + _prototypeManager.TryIndex(reagentId.Prototype, out ReagentPrototype? proto); var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text"); - - if (proto != null) - { - BufferInfo.Children.Add(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label {Text = $"{name}: "}, - new Label - { - Text = $"{quantity}u", - StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} - }, - - // Padding - new Control {HorizontalExpand = true}, - - MakeReagentButton("1", ChemMasterReagentAmount.U1, reagent, true, StyleBase.ButtonOpenRight), - MakeReagentButton("5", ChemMasterReagentAmount.U5, reagent, true, StyleBase.ButtonOpenBoth), - MakeReagentButton("10", ChemMasterReagentAmount.U10, reagent, true, StyleBase.ButtonOpenBoth), - MakeReagentButton("25", ChemMasterReagentAmount.U25, reagent, true, StyleBase.ButtonOpenBoth), - MakeReagentButton("50", ChemMasterReagentAmount.U50, reagent, true, StyleBase.ButtonOpenBoth), - MakeReagentButton("100", ChemMasterReagentAmount.U100, reagent, true, StyleBase.ButtonOpenBoth), - MakeReagentButton(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, reagent, true, StyleBase.ButtonOpenLeft), - } - }); - } + var reagentColor = proto?.SubstanceColor ?? default(Color); + BufferInfo.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagentId, quantity, true, true)); } } - + private void BuildContainerUI(Control control, ContainerInfo? info, bool addReagentButtons) { control.Children.Clear(); @@ -228,104 +258,111 @@ private void BuildContainerUI(Control control, ContainerInfo? info, bool addReag { Text = Loc.GetString("chem-master-window-no-container-loaded-text") }); + return; } - else + + // Name of the container and its fill status (Ex: 44/100u) + control.Children.Add(new BoxContainer { - // Name of the container and its fill status (Ex: 44/100u) - control.Children.Add(new BoxContainer + Orientation = LayoutOrientation.Horizontal, + Children = { - Orientation = LayoutOrientation.Horizontal, - Children = + new Label { Text = $"{info.DisplayName}: " }, + new Label { - new Label {Text = $"{info.DisplayName}: "}, - new Label - { - Text = $"{info.CurrentVolume}/{info.MaxVolume}", - StyleClasses = {StyleNano.StyleClassLabelSecondaryColor} - } + Text = $"{info.CurrentVolume}/{info.MaxVolume}", + StyleClasses = { StyleNano.StyleClassLabelSecondaryColor } } - }); - - IEnumerable<(string Name, ReagentId Id, FixedPoint2 Quantity)> contents; + } + }); + // Initialises rowCount to allow for striped rows + var rowCount = 0; - if (info.Entities != null) + // Handle entities if they are not null + if (info.Entities != null) + { + foreach (var (id, quantity) in info.Entities.Select(x => (x.Id, x.Quantity))) { - contents = info.Entities.Select(x => (x.Id, default(ReagentId), x.Quantity)); + control.Children.Add(BuildReagentRow(default(Color), rowCount++, id, default(ReagentId), quantity, false, addReagentButtons)); } - else if (info.Reagents != null) - { - contents = info.Reagents.Select(x => - { - _prototypeManager.TryIndex(x.Reagent.Prototype, out ReagentPrototype? proto); - var name = proto?.LocalizedName - ?? Loc.GetString("chem-master-window-unknown-reagent-text"); + } - return (name, Id: x.Reagent, x.Quantity); - }) - .OrderBy(r => r.Item1); - } - else + // Handle reagents if they are not null + if (info.Reagents != null) + { + foreach (var reagent in info.Reagents) { - return; + _prototypeManager.TryIndex(reagent.Reagent.Prototype, out ReagentPrototype? proto); + var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text"); + var reagentColor = proto?.SubstanceColor ?? default(Color); + + control.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagent.Reagent, reagent.Quantity, false, addReagentButtons)); } - - - foreach (var (name, id, quantity) in contents) + } + } + /// + /// Take reagent/entity data and present rows, labels, and buttons appropriately. todo sprites? + /// + private Control BuildReagentRow(Color reagentColor, int rowCount, string name, ReagentId reagent, FixedPoint2 quantity, bool isBuffer, bool addReagentButtons) + { + //Colors rows and sets fallback for reagentcolor to the same as background, this will hide colorPanel for entities hopefully + var rowColor1 = Color.FromHex("#1B1B1E"); + var rowColor2 = Color.FromHex("#202025"); + var currentRowColor = (rowCount % 2 == 1) ? rowColor1 : rowColor2; + if ((reagentColor == default(Color))|(!addReagentButtons)) + { + reagentColor = currentRowColor; + } + //this calls the separated button builder, and stores the return to render after labels + var reagentButtonConstructors = CreateReagentTransferButtons(reagent, isBuffer, addReagentButtons); + + // Create the row layout with the color panel + var rowContainer = new BoxContainer + { + Orientation = LayoutOrientation.Horizontal, + Children = { - var inner = new BoxContainer + new Label { Text = $"{name}: " }, + new Label { - Orientation = LayoutOrientation.Horizontal, - Children = - { - new Label { Text = $"{name}: " }, - new Label - { - Text = $"{quantity}u", - StyleClasses = { StyleNano.StyleClassLabelSecondaryColor }, - } - } - }; - - if (addReagentButtons) + Text = $"{quantity}u", + StyleClasses = { StyleNano.StyleClassLabelSecondaryColor } + }, + + // Padding + new Control { HorizontalExpand = true }, + // Colored panels for reagents + new PanelContainer { - var cs = inner.Children; - - // Padding - cs.Add(new Control { HorizontalExpand = true }); - - cs.Add(MakeReagentButton( - "1", ChemMasterReagentAmount.U1, id, false, StyleBase.ButtonOpenRight)); - cs.Add(MakeReagentButton( - "5", ChemMasterReagentAmount.U5, id, false, StyleBase.ButtonOpenBoth)); - cs.Add(MakeReagentButton( - "10", ChemMasterReagentAmount.U10, id, false, StyleBase.ButtonOpenBoth)); - cs.Add(MakeReagentButton( - "25", ChemMasterReagentAmount.U25, id, false, StyleBase.ButtonOpenBoth)); - cs.Add(MakeReagentButton( - "50", ChemMasterReagentAmount.U50, id, false, StyleBase.ButtonOpenBoth)); - cs.Add(MakeReagentButton( - "100", ChemMasterReagentAmount.U100, id, false, StyleBase.ButtonOpenBoth)); - cs.Add(MakeReagentButton( - Loc.GetString("chem-master-window-buffer-all-amount"), - ChemMasterReagentAmount.All, id, false, StyleBase.ButtonOpenLeft)); + Name = "colorPanel", + VerticalExpand = true, + MinWidth = 4, + PanelOverride = new StyleBoxFlat + { + BackgroundColor = reagentColor + }, + Margin = new Thickness(0, 1) } - - control.Children.Add(inner); } + }; - } - } - - public String LabelLine - { - get + // Add the reagent buttons after the color panel + foreach (var reagentTransferButton in reagentButtonConstructors) { - return LabelLineEdit.Text; + rowContainer.AddChild(reagentTransferButton); } - set + //Apply panencontainer to allow for striped rows + return new PanelContainer { - LabelLineEdit.Text = value; - } + PanelOverride = new StyleBoxFlat(currentRowColor), + Children = { rowContainer } + }; + } + + public string LabelLine + { + get => LabelLineEdit.Text; + set => LabelLineEdit.Text = value; } } diff --git a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml index 83dc42c4a44..b74df979cf4 100644 --- a/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml +++ b/Content.Client/Communications/UI/CommunicationsConsoleMenu.xaml @@ -1,17 +1,62 @@ - - - -[RegisterComponent] +[RegisterComponent, Access(typeof(EggLayerSystem)), AutoGenerateComponentPause] public sealed partial class EggLayerComponent : Component { - [DataField] - public EntProtoId EggLayAction = "ActionAnimalLayEgg"; + /// + /// The item that gets laid/spawned, retrieved from animal prototype. + /// + [DataField(required: true)] + public List EggSpawn = new(); /// - /// The amount of nutrient consumed on update. + /// Player action. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float HungerUsage = 60f; + [DataField] + public EntProtoId EggLayAction = "ActionAnimalLayEgg"; + + [DataField] + public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg"); /// /// Minimum cooldown used for the automatic egg laying. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float EggLayCooldownMin = 60f; /// /// Maximum cooldown used for the automatic egg laying. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float EggLayCooldownMax = 120f; /// - /// Set during component init. + /// The amount of nutrient consumed on update. /// - [ViewVariables(VVAccess.ReadWrite)] - public float CurrentEggLayCooldown; - - [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] - public List EggSpawn = default!; - [DataField] - public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg"); - - [DataField] - public float AccumulatedFrametime; + public float HungerUsage = 60f; [DataField] public EntityUid? Action; + + /// + /// When to next try to produce. + /// + [DataField, AutoPausedField] + public TimeSpan NextGrowth = TimeSpan.Zero; } diff --git a/Content.Server/Animals/Components/UdderComponent.cs b/Content.Server/Animals/Components/UdderComponent.cs deleted file mode 100644 index 620f4572a71..00000000000 --- a/Content.Server/Animals/Components/UdderComponent.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Content.Server.Animals.Systems; -using Content.Shared.Chemistry.Components; -using Content.Shared.Chemistry.Reagent; -using Content.Shared.FixedPoint; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Server.Animals.Components - -/// -/// Lets an entity produce milk. Uses hunger if present. -/// -{ - [RegisterComponent, Access(typeof(UdderSystem))] - internal sealed partial class UdderComponent : Component - { - /// - /// The reagent to produce. - /// - [DataField, ViewVariables(VVAccess.ReadOnly)] - public ProtoId ReagentId = "Milk"; - - /// - /// The name of . - /// - [DataField, ViewVariables(VVAccess.ReadOnly)] - public string SolutionName = "udder"; - - /// - /// The solution to add reagent to. - /// - [DataField] - public Entity? Solution = null; - - /// - /// The amount of reagent to be generated on update. - /// - [DataField, ViewVariables(VVAccess.ReadOnly)] - public FixedPoint2 QuantityPerUpdate = 25; - - /// - /// The amount of nutrient consumed on update. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float HungerUsage = 10f; - - /// - /// How long to wait before producing. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1); - - /// - /// When to next try to produce. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan NextGrowth = TimeSpan.FromSeconds(0); - } -} diff --git a/Content.Server/Animals/Systems/EggLayerSystem.cs b/Content.Server/Animals/Systems/EggLayerSystem.cs index 55d63808a4b..accbda281be 100644 --- a/Content.Server/Animals/Systems/EggLayerSystem.cs +++ b/Content.Server/Animals/Systems/EggLayerSystem.cs @@ -7,15 +7,15 @@ using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage; using Robust.Server.Audio; -using Robust.Server.GameObjects; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Timing; namespace Content.Server.Animals.Systems; /// -/// Gives ability to produce eggs, produces endless if the -/// owner has no HungerComponent +/// Gives the ability to lay eggs/other things; +/// produces endlessly if the owner does not have a HungerComponent. /// public sealed class EggLayerSystem : EntitySystem { @@ -23,6 +23,7 @@ public sealed class EggLayerSystem : EntitySystem [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly MobStateSystem _mobState = default!; @@ -37,7 +38,6 @@ public override void Initialize() public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var eggLayer)) { @@ -45,13 +45,17 @@ public override void Update(float frameTime) if (HasComp(uid)) continue; - eggLayer.AccumulatedFrametime += frameTime; + if (_timing.CurTime < eggLayer.NextGrowth) + continue; + + // Randomize next growth time for more organic egglaying. + eggLayer.NextGrowth += TimeSpan.FromSeconds(_random.NextFloat(eggLayer.EggLayCooldownMin, eggLayer.EggLayCooldownMax)); - if (eggLayer.AccumulatedFrametime < eggLayer.CurrentEggLayCooldown) + if (_mobState.IsDead(uid)) continue; - eggLayer.AccumulatedFrametime -= eggLayer.CurrentEggLayCooldown; - eggLayer.CurrentEggLayCooldown = _random.NextFloat(eggLayer.EggLayCooldownMin, eggLayer.EggLayCooldownMax); + // Hungerlevel check/modification is done in TryLayEgg() + // so it's used for player controlled chickens as well. TryLayEgg(uid, eggLayer); } @@ -60,11 +64,12 @@ public override void Update(float frameTime) private void OnMapInit(EntityUid uid, EggLayerComponent component, MapInitEvent args) { _actions.AddAction(uid, ref component.Action, component.EggLayAction); - component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax); + component.NextGrowth = _timing.CurTime + TimeSpan.FromSeconds(_random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax)); } private void OnEggLayAction(EntityUid uid, EggLayerComponent egglayer, EggLayInstantActionEvent args) { + // Cooldown is handeled by ActionAnimalLayEgg in types.yml. args.Handled = TryLayEgg(uid, egglayer); } @@ -76,10 +81,10 @@ public bool TryLayEgg(EntityUid uid, EggLayerComponent? egglayer) if (_mobState.IsDead(uid)) return false; - // Allow infinitely laying eggs if they can't get hungry + // Allow infinitely laying eggs if they can't get hungry. if (TryComp(uid, out var hunger)) { - if (hunger.CurrentHunger < egglayer.HungerUsage) + if (_hunger.GetHunger(hunger) < egglayer.HungerUsage) { _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, uid); return false; diff --git a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs index 38c4c51d874..b75be376383 100644 --- a/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs +++ b/Content.Server/Anomaly/Effects/InnerBodyAnomalySystem.cs @@ -186,6 +186,11 @@ private void OnMobStateChanged(Entity ent, ref MobSta if (args.NewMobState != MobState.Dead) return; + var ev = new BeforeRemoveAnomalyOnDeathEvent(); + RaiseLocalEvent(args.Target, ref ev); + if (ev.Cancelled) + return; + _anomaly.ChangeAnomalyHealth(ent, -2); //Shutdown it } diff --git a/Content.Server/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs b/Content.Server/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs new file mode 100644 index 00000000000..5ecadc71542 --- /dev/null +++ b/Content.Server/Atmos/Consoles/AtmosMonitoringConsoleSystem.cs @@ -0,0 +1,542 @@ +using Content.Server.Atmos.Components; +using Content.Server.Atmos.Piping.Components; +using Content.Server.DeviceNetwork.Components; +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.NodeGroups; +using Content.Server.NodeContainer.Nodes; +using Content.Server.Power.Components; +using Content.Shared.Atmos; +using Content.Shared.Atmos.Components; +using Content.Shared.Atmos.Consoles; +using Content.Shared.Labels.Components; +using Content.Shared.Pinpointer; +using Robust.Server.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Content.Server.Atmos.Consoles; + +public sealed class AtmosMonitoringConsoleSystem : SharedAtmosMonitoringConsoleSystem +{ + [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; + [Dependency] private readonly SharedMapSystem _sharedMapSystem = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + + // Private variables + // Note: this data does not need to be saved + private Dictionary> _gridAtmosPipeChunks = new(); + private float _updateTimer = 1.0f; + + // Constants + private const float UpdateTime = 1.0f; + private const int ChunkSize = 4; + + public override void Initialize() + { + base.Initialize(); + + // Console events + SubscribeLocalEvent(OnConsoleInit); + SubscribeLocalEvent(OnConsoleAnchorChanged); + SubscribeLocalEvent(OnConsoleParentChanged); + + // Tracked device events + SubscribeLocalEvent(OnEntityNodeGroupsRebuilt); + SubscribeLocalEvent(OnEntityPipeColorChanged); + SubscribeLocalEvent(OnEntityShutdown); + + // Grid events + SubscribeLocalEvent(OnGridSplit); + } + + #region Event handling + + private void OnConsoleInit(EntityUid uid, AtmosMonitoringConsoleComponent component, ComponentInit args) + { + InitializeAtmosMonitoringConsole(uid, component); + } + + private void OnConsoleAnchorChanged(EntityUid uid, AtmosMonitoringConsoleComponent component, AnchorStateChangedEvent args) + { + InitializeAtmosMonitoringConsole(uid, component); + } + + private void OnConsoleParentChanged(EntityUid uid, AtmosMonitoringConsoleComponent component, EntParentChangedMessage args) + { + component.ForceFullUpdate = true; + InitializeAtmosMonitoringConsole(uid, component); + } + + private void OnEntityNodeGroupsRebuilt(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, NodeGroupsRebuilt args) + { + InitializeAtmosMonitoringDevice(uid, component); + } + + private void OnEntityPipeColorChanged(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, AtmosPipeColorChangedEvent args) + { + InitializeAtmosMonitoringDevice(uid, component); + } + + private void OnEntityShutdown(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component, EntityTerminatingEvent args) + { + ShutDownAtmosMonitoringEntity(uid, component); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + // Collect grids + var allGrids = args.NewGrids.ToList(); + + if (!allGrids.Contains(args.Grid)) + allGrids.Add(args.Grid); + + // Rebuild the pipe networks on the affected grids + foreach (var ent in allGrids) + { + if (!TryComp(ent, out var grid)) + continue; + + RebuildAtmosPipeGrid(ent, grid); + } + + // Update atmos monitoring consoles that stand upon an updated grid + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform.GridUid == null) + continue; + + if (!allGrids.Contains(entXform.GridUid.Value)) + continue; + + InitializeAtmosMonitoringConsole(ent, entConsole); + } + } + + #endregion + + #region UI updates + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _updateTimer += frameTime; + + if (_updateTimer >= UpdateTime) + { + _updateTimer -= UpdateTime; + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (entXform?.GridUid == null) + continue; + + UpdateUIState(ent, entConsole, entXform); + } + } + } + + public void UpdateUIState + (EntityUid uid, + AtmosMonitoringConsoleComponent component, + TransformComponent xform) + { + if (!_userInterfaceSystem.IsUiOpen(uid, AtmosMonitoringConsoleUiKey.Key)) + return; + + var gridUid = xform.GridUid!.Value; + + if (!TryComp(gridUid, out var mapGrid)) + return; + + if (!TryComp(gridUid, out var atmosphere)) + return; + + // The grid must have a NavMapComponent to visualize the map in the UI + EnsureComp(gridUid); + + // Gathering data to be send to the client + var atmosNetworks = new List(); + var query = AllEntityQuery(); + + while (query.MoveNext(out var ent, out var entSensor, out var entXform)) + { + if (entXform?.GridUid != xform.GridUid) + continue; + + if (!entXform.Anchored) + continue; + + var entry = CreateAtmosMonitoringConsoleEntry(ent, entXform); + + if (entry != null) + atmosNetworks.Add(entry.Value); + } + + // Set the UI state + _userInterfaceSystem.SetUiState(uid, AtmosMonitoringConsoleUiKey.Key, + new AtmosMonitoringConsoleBoundInterfaceState(atmosNetworks.ToArray())); + } + + private AtmosMonitoringConsoleEntry? CreateAtmosMonitoringConsoleEntry(EntityUid uid, TransformComponent xform) + { + AtmosMonitoringConsoleEntry? entry = null; + + var netEnt = GetNetEntity(uid); + var name = MetaData(uid).EntityName; + var address = string.Empty; + + if (xform.GridUid == null) + return null; + + if (!TryGettingFirstPipeNode(uid, out var pipeNode, out var netId) || + pipeNode == null || + netId == null) + return null; + + var pipeColor = TryComp(uid, out var colorComponent) ? colorComponent.Color : Color.White; + + // Name the entity based on its label, if available + if (TryComp(uid, out var label) && label.CurrentLabel != null) + name = label.CurrentLabel; + + // Otherwise use its base name and network address + else if (TryComp(uid, out var deviceNet)) + address = deviceNet.Address; + + // Entry for unpowered devices + if (TryComp(uid, out var apcPowerReceiver) && !apcPowerReceiver.Powered) + { + entry = new AtmosMonitoringConsoleEntry(netEnt, GetNetCoordinates(xform.Coordinates), netId.Value, name, address) + { + IsPowered = false, + Color = pipeColor + }; + + return entry; + } + + // Entry for powered devices + var gasData = new Dictionary(); + var isAirPresent = pipeNode.Air.TotalMoles > 0; + + if (isAirPresent) + { + foreach (var gas in Enum.GetValues()) + { + if (pipeNode.Air[(int)gas] > 0) + gasData.Add(gas, pipeNode.Air[(int)gas] / pipeNode.Air.TotalMoles); + } + } + + entry = new AtmosMonitoringConsoleEntry(netEnt, GetNetCoordinates(xform.Coordinates), netId.Value, name, address) + { + TemperatureData = isAirPresent ? pipeNode.Air.Temperature : 0f, + PressureData = pipeNode.Air.Pressure, + TotalMolData = pipeNode.Air.TotalMoles, + GasData = gasData, + Color = pipeColor + }; + + return entry; + } + + private Dictionary GetAllAtmosDeviceNavMapData(EntityUid gridUid) + { + var atmosDeviceNavMapData = new Dictionary(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var entComponent, out var entXform)) + { + if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data)) + atmosDeviceNavMapData.Add(data.Value.NetEntity, data.Value); + } + + return atmosDeviceNavMapData; + } + + private bool TryGetAtmosDeviceNavMapData + (EntityUid uid, + AtmosMonitoringConsoleDeviceComponent component, + TransformComponent xform, + EntityUid gridUid, + [NotNullWhen(true)] out AtmosDeviceNavMapData? device) + { + device = null; + + if (component.NavMapBlip == null) + return false; + + if (xform.GridUid != gridUid) + return false; + + if (!xform.Anchored) + return false; + + var direction = xform.LocalRotation.GetCardinalDir(); + + if (!TryGettingFirstPipeNode(uid, out var _, out var netId)) + netId = -1; + + var color = Color.White; + + if (TryComp(uid, out var atmosPipeColor)) + color = atmosPipeColor.Color; + + device = new AtmosDeviceNavMapData(GetNetEntity(uid), GetNetCoordinates(xform.Coordinates), netId.Value, component.NavMapBlip.Value, direction, color); + + return true; + } + + #endregion + + #region Pipe net functions + + private void RebuildAtmosPipeGrid(EntityUid gridUid, MapGridComponent grid) + { + var allChunks = new Dictionary(); + + // Adds all atmos pipes to the nav map via bit mask chunks + var queryPipes = AllEntityQuery(); + while (queryPipes.MoveNext(out var ent, out var entAtmosPipeColor, out var entNodeContainer, out var entXform)) + { + if (entXform.GridUid != gridUid) + continue; + + if (!entXform.Anchored) + continue; + + var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize); + var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize); + + if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) + { + chunk = new AtmosPipeChunk(chunkOrigin); + allChunks[chunkOrigin] = chunk; + } + + UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, GetTileIndex(relative), ref chunk); + } + + // Add or update the chunks on the associated grid + _gridAtmosPipeChunks[gridUid] = allChunks; + + // Update the consoles that are on the same grid + var queryConsoles = AllEntityQuery(); + while (queryConsoles.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (gridUid != entXform.GridUid) + continue; + + entConsole.AtmosPipeChunks = allChunks; + Dirty(ent, entConsole); + } + } + + private void RebuildSingleTileOfPipeNetwork(EntityUid gridUid, MapGridComponent grid, EntityCoordinates coords) + { + if (!_gridAtmosPipeChunks.TryGetValue(gridUid, out var allChunks)) + allChunks = new Dictionary(); + + var tile = _sharedMapSystem.GetTileRef(gridUid, grid, coords); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize); + var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize); + var tileIdx = GetTileIndex(relative); + + if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) + chunk = new AtmosPipeChunk(chunkOrigin); + + // Remove all stale values for the tile + foreach (var (index, atmosPipeData) in chunk.AtmosPipeData) + { + var mask = (ulong)SharedNavMapSystem.AllDirMask << tileIdx * SharedNavMapSystem.Directions; + chunk.AtmosPipeData[index] = atmosPipeData & ~mask; + } + + // Rebuild the tile's pipe data + foreach (var ent in _sharedMapSystem.GetAnchoredEntities(gridUid, grid, coords)) + { + if (!TryComp(ent, out var entAtmosPipeColor)) + continue; + + if (!TryComp(ent, out var entNodeContainer)) + continue; + + UpdateAtmosPipeChunk(ent, entNodeContainer, entAtmosPipeColor, tileIdx, ref chunk); + } + + // Add or update the chunk on the associated grid + // Only the modified chunk will be sent to the client + chunk.LastUpdate = _gameTiming.CurTick; + allChunks[chunkOrigin] = chunk; + _gridAtmosPipeChunks[gridUid] = allChunks; + + // Update the components of the monitoring consoles that are attached to the same grid + var query = AllEntityQuery(); + + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + if (gridUid != entXform.GridUid) + continue; + + entConsole.AtmosPipeChunks = allChunks; + Dirty(ent, entConsole); + } + } + + private void UpdateAtmosPipeChunk(EntityUid uid, NodeContainerComponent nodeContainer, AtmosPipeColorComponent pipeColor, int tileIdx, ref AtmosPipeChunk chunk) + { + // Entities that are actively being deleted are not to be drawn + if (MetaData(uid).EntityLifeStage >= EntityLifeStage.Terminating) + return; + + foreach ((var id, var node) in nodeContainer.Nodes) + { + if (node is not PipeNode) + continue; + + var pipeNode = (PipeNode)node; + var netId = GetPipeNodeNetId(pipeNode); + var pipeDirection = pipeNode.CurrentPipeDirection; + + chunk.AtmosPipeData.TryGetValue((netId, pipeColor.Color.ToHex()), out var atmosPipeData); + atmosPipeData |= (ulong)pipeDirection << tileIdx * SharedNavMapSystem.Directions; + chunk.AtmosPipeData[(netId, pipeColor.Color.ToHex())] = atmosPipeData; + } + } + + private bool TryGettingFirstPipeNode(EntityUid uid, [NotNullWhen(true)] out PipeNode? pipeNode, [NotNullWhen(true)] out int? netId) + { + pipeNode = null; + netId = null; + + if (!TryComp(uid, out var nodeContainer)) + return false; + + foreach (var node in nodeContainer.Nodes.Values) + { + if (node is PipeNode) + { + pipeNode = (PipeNode)node; + netId = GetPipeNodeNetId(pipeNode); + + return true; + } + } + + return false; + } + + private int GetPipeNodeNetId(PipeNode pipeNode) + { + if (pipeNode.NodeGroup is BaseNodeGroup) + { + var nodeGroup = (BaseNodeGroup)pipeNode.NodeGroup; + + return nodeGroup.NetId; + } + + return -1; + } + + #endregion + + #region Initialization functions + + private void InitializeAtmosMonitoringConsole(EntityUid uid, AtmosMonitoringConsoleComponent component) + { + var xform = Transform(uid); + + if (xform.GridUid == null) + return; + + var grid = xform.GridUid.Value; + + if (!TryComp(grid, out var map)) + return; + + component.AtmosDevices = GetAllAtmosDeviceNavMapData(grid); + + if (!_gridAtmosPipeChunks.TryGetValue(grid, out var chunks)) + { + RebuildAtmosPipeGrid(grid, map); + } + + else + { + component.AtmosPipeChunks = chunks; + Dirty(uid, component); + } + } + + private void InitializeAtmosMonitoringDevice(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component) + { + // Rebuild tile + var xform = Transform(uid); + var gridUid = xform.GridUid; + + if (gridUid != null && TryComp(gridUid, out var grid)) + RebuildSingleTileOfPipeNetwork(gridUid.Value, grid, xform.Coordinates); + + // Update blips on affected consoles + if (component.NavMapBlip == null) + return; + + var netEntity = EntityManager.GetNetEntity(uid); + var query = AllEntityQuery(); + + while (query.MoveNext(out var ent, out var entConsole, out var entXform)) + { + var isDirty = entConsole.AtmosDevices.Remove(netEntity); + + if (gridUid != null && + gridUid == entXform.GridUid && + xform.Anchored && + TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data)) + { + entConsole.AtmosDevices.Add(netEntity, data.Value); + isDirty = true; + } + + if (isDirty) + Dirty(ent, entConsole); + } + } + + private void ShutDownAtmosMonitoringEntity(EntityUid uid, AtmosMonitoringConsoleDeviceComponent component) + { + // Rebuild tile + var xform = Transform(uid); + var gridUid = xform.GridUid; + + if (gridUid != null && TryComp(gridUid, out var grid)) + RebuildSingleTileOfPipeNetwork(gridUid.Value, grid, xform.Coordinates); + + // Update blips on affected consoles + if (component.NavMapBlip == null) + return; + + var netEntity = EntityManager.GetNetEntity(uid); + var query = AllEntityQuery(); + + while (query.MoveNext(out var ent, out var entConsole)) + { + if (entConsole.AtmosDevices.Remove(netEntity)) + Dirty(ent, entConsole); + } + } + + #endregion + + private int GetTileIndex(Vector2i relativeTile) + { + return relativeTile.X * ChunkSize + relativeTile.Y; + } +} diff --git a/Content.Server/Atmos/Piping/Components/AtmosPipeColorComponent.cs b/Content.Server/Atmos/Piping/Components/AtmosPipeColorComponent.cs index 5b05668ad57..a8edb07d318 100644 --- a/Content.Server/Atmos/Piping/Components/AtmosPipeColorComponent.cs +++ b/Content.Server/Atmos/Piping/Components/AtmosPipeColorComponent.cs @@ -1,19 +1,24 @@ using Content.Server.Atmos.Piping.EntitySystems; using JetBrains.Annotations; -namespace Content.Server.Atmos.Piping.Components +namespace Content.Server.Atmos.Piping.Components; + +[RegisterComponent] +public sealed partial class AtmosPipeColorComponent : Component { - [RegisterComponent] - public sealed partial class AtmosPipeColorComponent : Component - { - [DataField("color")] - public Color Color { get; set; } = Color.White; + [DataField] + public Color Color { get; set; } = Color.White; - [ViewVariables(VVAccess.ReadWrite), UsedImplicitly] - public Color ColorVV - { - get => Color; - set => IoCManager.Resolve().System().SetColor(Owner, this, value); - } + [ViewVariables(VVAccess.ReadWrite), UsedImplicitly] + public Color ColorVV + { + get => Color; + set => IoCManager.Resolve().System().SetColor(Owner, this, value); } } + +[ByRefEvent] +public record struct AtmosPipeColorChangedEvent(Color color) +{ + public Color Color = color; +} diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeColorSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeColorSystem.cs index b9ee6680329..dcb08dcd574 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeColorSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeColorSystem.cs @@ -40,6 +40,9 @@ public void SetColor(EntityUid uid, AtmosPipeColorComponent component, Color col return; _appearance.SetData(uid, PipeColorVisuals.Color, color, appearance); + + var ev = new AtmosPipeColorChangedEvent(color); + RaiseLocalEvent(uid, ref ev); } } } diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs index f594866fa23..bc925e433ca 100644 --- a/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosUnsafeUnanchorSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Piping.Components; using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Server.Popups; using Content.Shared.Atmos; @@ -8,7 +9,6 @@ using Content.Shared.Destructible; using Content.Shared.Popups; using JetBrains.Annotations; -using Robust.Shared.Player; namespace Content.Server.Atmos.Piping.EntitySystems { @@ -16,11 +16,12 @@ namespace Content.Server.Atmos.Piping.EntitySystems public sealed class AtmosUnsafeUnanchorSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphere = default!; + [Dependency] private readonly NodeGroupSystem _group = default!; [Dependency] private readonly PopupSystem _popup = default!; public override void Initialize() { - SubscribeLocalEvent(OnBeforeUnanchored); + SubscribeLocalEvent(OnUserUnanchored); SubscribeLocalEvent(OnUnanchorAttempt); SubscribeLocalEvent(OnBreak); } @@ -48,15 +49,22 @@ private void OnUnanchorAttempt(EntityUid uid, AtmosUnsafeUnanchorComponent compo } } - private void OnBeforeUnanchored(EntityUid uid, AtmosUnsafeUnanchorComponent component, BeforeUnanchoredEvent args) + // When unanchoring a pipe, leak the gas that was inside the pipe element. + // At this point the pipe has been scheduled to be removed from the group, but that won't happen until the next Update() call in NodeGroupSystem, + // so we have to force an update. + // This way the gas inside other connected pipes stays unchanged, while the removed pipe is completely emptied. + private void OnUserUnanchored(EntityUid uid, AtmosUnsafeUnanchorComponent component, UserUnanchoredEvent args) { if (component.Enabled) + { + _group.ForceUpdate(); LeakGas(uid); + } } private void OnBreak(EntityUid uid, AtmosUnsafeUnanchorComponent component, BreakageEventArgs args) { - LeakGas(uid); + LeakGas(uid, false); // Can't use DoActsBehavior["Destruction"] in the same trigger because that would prevent us // from leaking. So we make up for this by queueing deletion here. QueueDel(uid); @@ -64,32 +72,17 @@ private void OnBreak(EntityUid uid, AtmosUnsafeUnanchorComponent component, Brea /// /// Leak gas from the uid's NodeContainer into the tile atmosphere. + /// Setting removeFromPipe to false will duplicate the gas inside the pipe intead of moving it. + /// This is needed to properly handle the gas in the pipe getting deleted with the pipe. /// - public void LeakGas(EntityUid uid) + public void LeakGas(EntityUid uid, bool removeFromPipe = true) { if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodes)) return; - if (_atmosphere.GetContainingMixture(uid, true, true) is not {} environment) + if (_atmosphere.GetContainingMixture(uid, true, true) is not { } environment) environment = GasMixture.SpaceGas; - var lost = 0f; - var timesLost = 0; - - foreach (var node in nodes.Nodes.Values) - { - if (node is not PipeNode pipe) - continue; - - var difference = pipe.Air.Pressure - environment.Pressure; - lost += Math.Min( - pipe.Volume / pipe.Air.Volume * pipe.Air.TotalMoles, - difference * environment.Volume / (environment.Temperature * Atmospherics.R) - ); - timesLost++; - } - - var sharedLoss = lost / timesLost; var buffer = new GasMixture(); foreach (var node in nodes.Nodes.Values) @@ -97,7 +90,13 @@ public void LeakGas(EntityUid uid) if (node is not PipeNode pipe) continue; - _atmosphere.Merge(buffer, pipe.Air.Remove(sharedLoss)); + if (removeFromPipe) + _atmosphere.Merge(buffer, pipe.Air.RemoveVolume(pipe.Volume)); + else + { + var copy = new GasMixture(pipe.Air); //clone, then remove to keep the original untouched + _atmosphere.Merge(buffer, copy.RemoveVolume(pipe.Volume)); + } } _atmosphere.Merge(environment, buffer); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs index b6271c22d4c..128754bbf8c 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasPortableSystem.cs @@ -44,11 +44,6 @@ private void OnAnchorChanged(EntityUid uid, GasPortableComponent portable, ref A return; portableNode.ConnectionsEnabled = args.Anchored; - - if (EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) - { - _appearance.SetData(uid, GasPortableVisuals.ConnectedState, args.Anchored, appearance); - } } public bool FindGasPortIn(EntityUid? gridId, EntityCoordinates coordinates, [NotNullWhen(true)] out GasPortComponent? port) diff --git a/Content.Server/Chat/SpeakOnTriggerComponent.cs b/Content.Server/Chat/SpeakOnTriggerComponent.cs new file mode 100644 index 00000000000..d879cbf1bf0 --- /dev/null +++ b/Content.Server/Chat/SpeakOnTriggerComponent.cs @@ -0,0 +1,17 @@ +using Content.Shared.Dataset; +using Robust.Shared.Prototypes; + +namespace Content.Server.Chat; + +/// +/// Makes the entity speak when triggered. If the item has UseDelay component, the system will respect that cooldown. +/// +[RegisterComponent] +public sealed partial class SpeakOnTriggerComponent : Component +{ + /// + /// The identifier for the dataset prototype containing messages to be spoken by this entity. + /// + [DataField(required: true)] + public ProtoId Pack = string.Empty; +} diff --git a/Content.Server/Chat/SpeakOnUseComponent.cs b/Content.Server/Chat/SpeakOnUseComponent.cs deleted file mode 100644 index f8953aebefe..00000000000 --- a/Content.Server/Chat/SpeakOnUseComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Shared.Dataset; -using Robust.Shared.Prototypes; - -namespace Content.Server.Chat; - -/// -/// Entity will say the things when activated -/// -[RegisterComponent] -public sealed partial class SpeakOnUseComponent : Component -{ - /// - /// The identifier for the dataset prototype containing messages to be spoken by this entity. - /// - [DataField(required: true)] - public ProtoId Pack { get; private set; } - -} diff --git a/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs b/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs new file mode 100644 index 00000000000..d8800a85087 --- /dev/null +++ b/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs @@ -0,0 +1,42 @@ +using Content.Server.Explosion.EntitySystems; +using Content.Shared.Timing; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Chat.Systems; + +public sealed class SpeakOnTriggerSystem : EntitySystem +{ + [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly ChatSystem _chat = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnTrigger); + } + + private void OnTrigger(Entity ent, ref TriggerEvent args) + { + TrySpeak(ent); + args.Handled = true; + } + + private void TrySpeak(Entity ent) + { + // If it doesn't have the use delay component, still send the message. + if (TryComp(ent.Owner, out var useDelay) && _useDelay.IsDelayed((ent.Owner, useDelay))) + return; + + if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack)) + return; + + var message = Loc.GetString(_random.Pick(messagePack.Values)); + _chat.TrySendInGameICMessage(ent.Owner, message, InGameICChatType.Speak, true); + + if (useDelay != null) + _useDelay.TryResetDelay((ent.Owner, useDelay)); + } +} diff --git a/Content.Server/Chat/Systems/SpeakOnUseSystem.cs b/Content.Server/Chat/Systems/SpeakOnUseSystem.cs deleted file mode 100644 index addec79e410..00000000000 --- a/Content.Server/Chat/Systems/SpeakOnUseSystem.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Content.Server.Chat; -using Content.Shared.Dataset; -using Content.Shared.Interaction.Events; -using Content.Shared.Timing; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; - -namespace Content.Server.Chat.Systems; - -/// -/// Handles the speech on activating an entity -/// -public sealed partial class SpeakOnUIClosedSystem : EntitySystem -{ - [Dependency] private readonly UseDelaySystem _useDelay = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly ChatSystem _chat = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnUseInHand); - } - - public void OnUseInHand(EntityUid uid, SpeakOnUseComponent? component, UseInHandEvent args) - { - if (!Resolve(uid, ref component)) - return; - - // Yes it won't work without UseDelayComponent, but we don't want any kind of spam - if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay))) - return; - - if (!_prototypeManager.TryIndex(component.Pack, out var messagePack)) - return; - - var message = Loc.GetString(_random.Pick(messagePack.Values)); - _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Speak, true); - _useDelay.TryResetDelay((uid, useDelay)); - } -} diff --git a/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs b/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs index 1f9203d299f..2aeb8998292 100644 --- a/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs +++ b/Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs @@ -14,7 +14,7 @@ public sealed class DeleteChatMessageCommand : ToolshedCommand [Dependency] private readonly IEntitySystemManager _manager = default!; [CommandImplementation("id")] - public void DeleteChatMessage([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] uint messageId) + public void DeleteChatMessage(IInvocationContext ctx, uint messageId) { if (!_manager.GetEntitySystem().Delete(messageId)) { diff --git a/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs b/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs index 3d8b69dd765..07b4cd97968 100644 --- a/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs +++ b/Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs @@ -14,7 +14,7 @@ public sealed class NukeChatMessagesCommand : ToolshedCommand [Dependency] private readonly IEntitySystemManager _manager = default!; [CommandImplementation("usernames")] - public void Command([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] string usernamesCsv) + public void Command(IInvocationContext ctx, string usernamesCsv) { var usernames = usernamesCsv.Split(','); diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs index e0fe3b395ff..82334806321 100644 --- a/Content.Server/Connection/ConnectionManager.cs +++ b/Content.Server/Connection/ConnectionManager.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using System.Runtime.InteropServices; +using Content.Server.Administration.Managers; using Content.Server.Chat.Managers; using Content.Server.Database; using Content.Server.GameTicking; @@ -56,6 +57,7 @@ public sealed partial class ConnectionManager : IConnectionManager [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; private ISawmill _sawmill = default!; private readonly Dictionary _temporaryBypasses = []; @@ -274,7 +276,14 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame ticker.PlayerGameStatuses.TryGetValue(userId, out var status) && status == PlayerGameStatus.JoinedGame; var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && adminData != null; - if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame) + var softPlayerCount = _plyMgr.PlayerCount; + + if (!_cfg.GetCVar(CCVars.AdminsCountForMaxPlayers)) + { + softPlayerCount -= _adminManager.ActiveAdmins.Count(); + } + + if ((softPlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame) { return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null); } @@ -291,7 +300,7 @@ session.Status is SessionStatus.Connected or SessionStatus.InGame foreach (var whitelist in _whitelists) { - if (!IsValid(whitelist, _plyMgr.PlayerCount)) + if (!IsValid(whitelist, softPlayerCount)) { // Not valid for current player count. continue; diff --git a/Content.Server/DeltaV/Storage/EntitySystems/MouthStorageSystem.cs b/Content.Server/DeltaV/Storage/EntitySystems/MouthStorageSystem.cs index e5e7d6755e6..5bf67ad92e2 100644 --- a/Content.Server/DeltaV/Storage/EntitySystems/MouthStorageSystem.cs +++ b/Content.Server/DeltaV/Storage/EntitySystems/MouthStorageSystem.cs @@ -1,4 +1,3 @@ -using Content.Server.Nutrition; using Content.Server.Speech; using Content.Server.Speech.EntitySystems; using Content.Shared.DeltaV.Storage.Components; @@ -10,12 +9,12 @@ namespace Content.Server.DeltaV.Storage.EntitySystems; public sealed class MouthStorageSystem : SharedMouthStorageSystem { [Dependency] private readonly ReplacementAccentSystem _replacement = default!; + public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnAccent); - SubscribeLocalEvent(OnIngestAttempt); } // Force you to mumble if you have items in your mouth @@ -24,18 +23,4 @@ private void OnAccent(EntityUid uid, MouthStorageComponent component, AccentGetE if (IsMouthBlocked(component)) args.Message = _replacement.ApplyReplacements(args.Message, "mumble"); } - - // Attempting to eat or drink anything with items in your mouth won't work - private void OnIngestAttempt(EntityUid uid, MouthStorageComponent component, IngestionAttemptEvent args) - { - if (!IsMouthBlocked(component)) - return; - - if (!TryComp(component.MouthId, out var storage)) - return; - - var firstItem = storage.Container.ContainedEntities[0]; - args.Blocker = firstItem; - args.Cancel(); - } } diff --git a/Content.Server/DeviceLinking/Components/SignalTimerComponent.cs b/Content.Server/DeviceLinking/Components/SignalTimerComponent.cs index b3535cde1fd..3feb69c879f 100644 --- a/Content.Server/DeviceLinking/Components/SignalTimerComponent.cs +++ b/Content.Server/DeviceLinking/Components/SignalTimerComponent.cs @@ -1,53 +1,58 @@ using Content.Shared.DeviceLinking; using Robust.Shared.Audio; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.DeviceLinking.Components; [RegisterComponent] public sealed partial class SignalTimerComponent : Component { - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public double Delay = 5; /// /// This shows the Label: text box in the UI. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public bool CanEditLabel = true; /// /// The label, used for TextScreen visuals currently. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public string Label = string.Empty; /// /// Default max width of a label (how many letters can this render?) /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public int MaxLength = 5; /// /// The port that gets signaled when the timer triggers. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId TriggerPort = "Timer"; /// /// The port that gets signaled when the timer starts. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId StartPort = "Start"; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public ProtoId Trigger = "Trigger"; /// /// If not null, this timer will play this sound when done. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier? DoneSound; -} + /// + /// The maximum duration in seconds + /// When a larger number is in the input box, the display will start counting down from this one instead + /// + [DataField] + public Double MaxDuration = 3599; // 59m 59s +} diff --git a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs index 1280ecd5d11..e6a5b37c273 100644 --- a/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs +++ b/Content.Server/DeviceLinking/Systems/LogicGateSystem.cs @@ -44,7 +44,7 @@ public override void Update(float deltaTime) } if (comp.StateB == SignalState.Momentary) { - comp.StateB = SignalState.High; + comp.StateB = SignalState.Low; } // output most likely changed so update it diff --git a/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs b/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs index 14e0c75d962..b4ae1eb57a3 100644 --- a/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs +++ b/Content.Server/DeviceLinking/Systems/SignalTimerSystem.cs @@ -152,7 +152,7 @@ private void OnDelayChangedMessage(EntityUid uid, SignalTimerComponent component if (!IsMessageValid(uid, args)) return; - component.Delay = args.Delay.TotalSeconds; + component.Delay = Math.Min(args.Delay.TotalSeconds, component.MaxDuration); _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, component.Delay); } diff --git a/Content.Server/Disposal/Unit/EntitySystems/DisposableSystem.cs b/Content.Server/Disposal/Unit/EntitySystems/DisposableSystem.cs index 38e39238039..064f9300b3c 100644 --- a/Content.Server/Disposal/Unit/EntitySystems/DisposableSystem.cs +++ b/Content.Server/Disposal/Unit/EntitySystems/DisposableSystem.cs @@ -135,12 +135,13 @@ public void ExitDisposals(EntityUid uid, DisposalHolderComponent? holder = null, else { _xformSystem.AttachToGridOrMap(entity, xform); + var direction = holder.CurrentDirection == Direction.Invalid ? holder.PreviousDirection : holder.CurrentDirection; - if (holder.PreviousDirection != Direction.Invalid && _xformQuery.TryGetComponent(xform.ParentUid, out var parentXform)) + if (direction != Direction.Invalid && _xformQuery.TryGetComponent(gridUid, out var gridXform)) { - var direction = holder.PreviousDirection.ToAngle(); - direction += _xformSystem.GetWorldRotation(parentXform); - _throwing.TryThrow(entity, direction.ToWorldVec() * 3f, 10f); + var directionAngle = direction.ToAngle(); + directionAngle += _xformSystem.GetWorldRotation(gridXform); + _throwing.TryThrow(entity, directionAngle.ToWorldVec() * 3f, 10f); } } } diff --git a/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs b/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs index 84ad4c22403..c4f69b60de6 100644 --- a/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs +++ b/Content.Server/EntityEffects/EffectConditions/TotalHunger.cs @@ -1,6 +1,6 @@ using Content.Shared.EntityEffects; using Content.Shared.Nutrition.Components; -using Content.Shared.FixedPoint; +using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.Prototypes; namespace Content.Server.EntityEffects.EffectConditions; @@ -17,7 +17,7 @@ public override bool Condition(EntityEffectBaseArgs args) { if (args.EntityManager.TryGetComponent(args.TargetEntity, out HungerComponent? hunger)) { - var total = hunger.CurrentHunger; + var total = args.EntityManager.System().GetHunger(hunger); if (total > Min && total < Max) return true; } diff --git a/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs b/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs deleted file mode 100644 index fe1b8caede7..00000000000 --- a/Content.Server/Explosion/Components/ClusterGrenadeComponent.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Content.Server.Explosion.EntitySystems; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; - -namespace Content.Server.Explosion.Components -{ - [RegisterComponent, Access(typeof(ClusterGrenadeSystem))] - public sealed partial class ClusterGrenadeComponent : Component - { - public Container GrenadesContainer = default!; - - /// - /// What we fill our prototype with if we want to pre-spawn with grenades. - /// - [DataField("fillPrototype")] - public EntProtoId? FillPrototype; - - /// - /// If we have a pre-fill how many more can we spawn. - /// - public int UnspawnedCount; - - /// - /// Maximum grenades in the container. - /// - [DataField("maxGrenadesCount")] - public int MaxGrenades = 3; - - /// - /// Maximum delay in seconds between individual grenade triggers - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("grenadeTriggerIntervalMax")] - public float GrenadeTriggerIntervalMax = 0f; - - /// - /// Minimum delay in seconds between individual grenade triggers - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("grenadeTriggerIntervalMin")] - public float GrenadeTriggerIntervalMin = 0f; - - /// - /// Minimum delay in seconds before any grenades start to be triggered. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("baseTriggerDelay")] - public float BaseTriggerDelay = 1.0f; - - /// - /// Decides if grenades trigger after getting launched - /// - [DataField("triggerGrenades")] - public bool TriggerGrenades = true; - - /// - /// Does the cluster grenade shoot or throw - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("grenadeType")] - public Enum GrenadeType = Components.GrenadeType.Throw; - - /// - /// The speed at which grenades get thrown - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("velocity")] - public float Velocity = 5; - - /// - /// Should the spread be random - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("randomSpread")] - public bool RandomSpread = false; - - /// - /// Should the angle be random - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("randomAngle")] - public bool RandomAngle = false; - - /// - /// Static distance grenades will be thrown to. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("distance")] - public float Distance = 1f; - - /// - /// Max distance grenades should randomly be thrown to. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("maxSpreadDistance")] - public float MaxSpreadDistance = 2.5f; - - /// - /// Minimal distance grenades should randomly be thrown to. - /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("minSpreadDistance")] - public float MinSpreadDistance = 0f; - - /// - /// This is the end. - /// - public bool CountDown; - } - - public enum GrenadeType - { - Throw, - Shoot - } - -} diff --git a/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs new file mode 100644 index 00000000000..58d687e0258 --- /dev/null +++ b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs @@ -0,0 +1,48 @@ +using Content.Server.Explosion.EntitySystems; +using Robust.Shared.Containers; +using Robust.Shared.Prototypes; + +namespace Content.Server.Explosion.Components; +/// +/// Grenades that, when triggered, explode into projectiles +/// +[RegisterComponent, Access(typeof(ProjectileGrenadeSystem))] +public sealed partial class ProjectileGrenadeComponent : Component +{ + public Container Container = default!; + + /// + /// The kind of projectile that the prototype is filled with. + /// + [DataField] + public EntProtoId? FillPrototype; + + /// + /// If we have a pre-fill how many more can we spawn. + /// + public int UnspawnedCount; + + /// + /// Total amount of projectiles + /// + [DataField] + public int Capacity = 3; + + /// + /// Should the angle of the projectiles be uneven? + /// + [DataField] + public bool RandomAngle = false; + + /// + /// The minimum speed the projectiles may come out at + /// + [DataField] + public float MinVelocity = 2f; + + /// + /// The maximum speed the projectiles may come out at + /// + [DataField] + public float MaxVelocity = 6f; +} diff --git a/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs deleted file mode 100644 index 78e41c59ae2..00000000000 --- a/Content.Server/Explosion/EntitySystems/ClusterGrenadeSystem.cs +++ /dev/null @@ -1,177 +0,0 @@ -using Content.Server.Explosion.Components; -using Content.Shared.Flash.Components; -using Content.Shared.Interaction; -using Content.Shared.Throwing; -using Robust.Shared.Containers; -using Robust.Shared.Random; -using Content.Server.Weapons.Ranged.Systems; -using System.Numerics; -using Content.Shared.Explosion.Components; -using Robust.Server.Containers; -using Robust.Server.GameObjects; - -namespace Content.Server.Explosion.EntitySystems; - -public sealed class ClusterGrenadeSystem : EntitySystem -{ - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly ThrowingSystem _throwingSystem = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly GunSystem _gun = default!; - [Dependency] private readonly TransformSystem _transformSystem = default!; - [Dependency] private readonly ContainerSystem _containerSystem = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnClugInit); - SubscribeLocalEvent(OnClugStartup); - SubscribeLocalEvent(OnClugUsing); - SubscribeLocalEvent(OnClugTrigger); - } - - private void OnClugInit(EntityUid uid, ClusterGrenadeComponent component, ComponentInit args) - { - component.GrenadesContainer = _container.EnsureContainer(uid, "cluster-payload"); - } - - private void OnClugStartup(Entity clug, ref ComponentStartup args) - { - var component = clug.Comp; - if (component.FillPrototype != null) - { - component.UnspawnedCount = Math.Max(0, component.MaxGrenades - component.GrenadesContainer.ContainedEntities.Count); - UpdateAppearance(clug); - } - } - - private void OnClugUsing(Entity clug, ref InteractUsingEvent args) - { - if (args.Handled) - return; - - var component = clug.Comp; - - // TODO: Should use whitelist. - if (component.GrenadesContainer.ContainedEntities.Count >= component.MaxGrenades || - !HasComp(args.Used)) - return; - - _containerSystem.Insert(args.Used, component.GrenadesContainer); - UpdateAppearance(clug); - args.Handled = true; - } - - private void OnClugTrigger(Entity clug, ref TriggerEvent args) - { - var component = clug.Comp; - component.CountDown = true; - args.Handled = true; - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - var query = EntityQueryEnumerator(); - - while (query.MoveNext(out var uid, out var clug)) - { - if (clug.CountDown && clug.UnspawnedCount > 0) - { - var grenadesInserted = clug.GrenadesContainer.ContainedEntities.Count + clug.UnspawnedCount; - var thrownCount = 0; - var segmentAngle = 360 / grenadesInserted; - var grenadeDelay = 0f; - - while (TryGetGrenade(uid, clug, out var grenade)) - { - // var distance = random.NextFloat() * _throwDistance; - var angleMin = segmentAngle * thrownCount; - var angleMax = segmentAngle * (thrownCount + 1); - var angle = Angle.FromDegrees(_random.Next(angleMin, angleMax)); - if (clug.RandomAngle) - angle = _random.NextAngle(); - thrownCount++; - - switch (clug.GrenadeType) - { - case GrenadeType.Shoot: - ShootProjectile(grenade, angle, clug, uid); - break; - case GrenadeType.Throw: - ThrowGrenade(grenade, angle, clug); - break; - } - - // give an active timer trigger to the contained grenades when they get launched - if (clug.TriggerGrenades) - { - grenadeDelay += _random.NextFloat(clug.GrenadeTriggerIntervalMin, clug.GrenadeTriggerIntervalMax); - var grenadeTimer = EnsureComp(grenade); - grenadeTimer.TimeRemaining = (clug.BaseTriggerDelay + grenadeDelay); - var ev = new ActiveTimerTriggerEvent(grenade, uid); - RaiseLocalEvent(uid, ref ev); - } - } - // delete the empty shell of the clusterbomb - Del(uid); - } - } - } - - private void ShootProjectile(EntityUid grenade, Angle angle, ClusterGrenadeComponent clug, EntityUid clugUid) - { - var direction = angle.ToVec().Normalized(); - - if (clug.RandomSpread) - direction = _random.NextVector2().Normalized(); - - _gun.ShootProjectile(grenade, direction, Vector2.One.Normalized(), clugUid); - - } - - private void ThrowGrenade(EntityUid grenade, Angle angle, ClusterGrenadeComponent clug) - { - var direction = angle.ToVec().Normalized() * clug.Distance; - - if (clug.RandomSpread) - direction = angle.ToVec().Normalized() * _random.NextFloat(clug.MinSpreadDistance, clug.MaxSpreadDistance); - - _throwingSystem.TryThrow(grenade, direction, clug.Velocity); - } - - private bool TryGetGrenade(EntityUid clugUid, ClusterGrenadeComponent component, out EntityUid grenade) - { - grenade = default; - - if (component.UnspawnedCount > 0) - { - component.UnspawnedCount--; - grenade = Spawn(component.FillPrototype, _transformSystem.GetMapCoordinates(clugUid)); - return true; - } - - if (component.GrenadesContainer.ContainedEntities.Count > 0) - { - grenade = component.GrenadesContainer.ContainedEntities[0]; - - // This shouldn't happen but you never know. - if (!_containerSystem.Remove(grenade, component.GrenadesContainer)) - return false; - - return true; - } - - return false; - } - - private void UpdateAppearance(Entity clug) - { - var component = clug.Comp; - if (!TryComp(clug, out var appearance)) - return; - - _appearance.SetData(clug, ClusterGrenadeVisuals.GrenadesCounter, component.GrenadesContainer.ContainedEntities.Count + component.UnspawnedCount, appearance); - } -} diff --git a/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs new file mode 100644 index 00000000000..555ce3399e7 --- /dev/null +++ b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs @@ -0,0 +1,110 @@ +using Content.Server.Explosion.Components; +using Content.Server.Weapons.Ranged.Systems; +using Robust.Server.GameObjects; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Server.Explosion.EntitySystems; + +public sealed class ProjectileGrenadeSystem : EntitySystem +{ + [Dependency] private readonly GunSystem _gun = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnFragInit); + SubscribeLocalEvent(OnFragStartup); + SubscribeLocalEvent(OnFragTrigger); + } + + private void OnFragInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Container = _container.EnsureContainer(entity.Owner, "cluster-payload"); + } + + /// + /// Setting the unspawned count based on capacity so we know how many new entities to spawn + /// + private void OnFragStartup(Entity entity, ref ComponentStartup args) + { + if (entity.Comp.FillPrototype == null) + return; + + entity.Comp.UnspawnedCount = Math.Max(0, entity.Comp.Capacity - entity.Comp.Container.ContainedEntities.Count); + } + + /// + /// Can be triggered either by damage or the use in hand timer + /// + private void OnFragTrigger(Entity entity, ref TriggerEvent args) + { + FragmentIntoProjectiles(entity.Owner, entity.Comp); + args.Handled = true; + } + + /// + /// Spawns projectiles at the coordinates of the grenade upon triggering + /// Can customize the angle and velocity the projectiles come out at + /// + private void FragmentIntoProjectiles(EntityUid uid, ProjectileGrenadeComponent component) + { + var grenadeCoord = _transformSystem.GetMapCoordinates(uid); + var shootCount = 0; + var totalCount = component.Container.ContainedEntities.Count + component.UnspawnedCount; + var segmentAngle = 360 / totalCount; + + while (TrySpawnContents(grenadeCoord, component, out var contentUid)) + { + Angle angle; + if (component.RandomAngle) + angle = _random.NextAngle(); + else + { + var angleMin = segmentAngle * shootCount; + var angleMax = segmentAngle * (shootCount + 1); + angle = Angle.FromDegrees(_random.Next(angleMin, angleMax)); + shootCount++; + } + + // velocity is randomized to make the projectiles look + // slightly uneven, doesn't really change much, but it looks better + var direction = angle.ToVec().Normalized(); + var velocity = _random.NextVector2(component.MinVelocity, component.MaxVelocity); + _gun.ShootProjectile(contentUid, direction, velocity, uid, null); + } + } + + /// + /// Spawns one instance of the fill prototype or contained entity at the coordinate indicated + /// + private bool TrySpawnContents(MapCoordinates spawnCoordinates, ProjectileGrenadeComponent component, out EntityUid contentUid) + { + contentUid = default; + + if (component.UnspawnedCount > 0) + { + component.UnspawnedCount--; + contentUid = Spawn(component.FillPrototype, spawnCoordinates); + return true; + } + + if (component.Container.ContainedEntities.Count > 0) + { + contentUid = component.Container.ContainedEntities[0]; + + if (!_container.Remove(contentUid, component.Container)) + return false; + + return true; + } + + return false; + } +} diff --git a/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs new file mode 100644 index 00000000000..2657ba34494 --- /dev/null +++ b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs @@ -0,0 +1,122 @@ +using Content.Shared.Explosion.Components; +using Content.Shared.Throwing; +using Robust.Server.GameObjects; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Random; +using System.Numerics; +using Content.Shared.Explosion.EntitySystems; + +namespace Content.Server.Explosion.EntitySystems; + +public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem +{ + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + [Dependency] private readonly TransformSystem _transformSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnScatteringTrigger); + } + + /// + /// Can be triggered either by damage or the use in hand timer, either way + /// will store the event happening in IsTriggered for the next frame update rather than + /// handling it here to prevent crashing the game + /// + private void OnScatteringTrigger(Entity entity, ref TriggerEvent args) + { + entity.Comp.IsTriggered = true; + args.Handled = true; + } + + /// + /// Every frame update we look for scattering grenades that were triggered (by damage or timer) + /// Then we spawn the contents, throw them, optionally trigger them, then delete the original scatter grenade entity + /// + public override void Update(float frametime) + { + base.Update(frametime); + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var component)) + { + var totalCount = component.Container.ContainedEntities.Count + component.UnspawnedCount; + + // if triggered while empty, (if it's blown up while empty) it'll just delete itself + if (component.IsTriggered && totalCount > 0) + { + var grenadeCoord = _transformSystem.GetMapCoordinates(uid); + var thrownCount = 0; + var segmentAngle = 360 / totalCount; + var additionalIntervalDelay = 0f; + + while (TrySpawnContents(grenadeCoord, component, out var contentUid)) + { + Angle angle; + if (component.RandomAngle) + angle = _random.NextAngle(); + else + { + var angleMin = segmentAngle * thrownCount; + var angleMax = segmentAngle * (thrownCount + 1); + angle = Angle.FromDegrees(_random.Next(angleMin, angleMax)); + thrownCount++; + } + + Vector2 direction = angle.ToVec().Normalized(); + if (component.RandomDistance) + direction *= _random.NextFloat(component.RandomThrowDistanceMin, component.RandomThrowDistanceMax); + else + direction *= component.Distance; + + _throwingSystem.TryThrow(contentUid, direction, component.Velocity); + + if (component.TriggerContents) + { + additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax); + var contentTimer = EnsureComp(contentUid); + contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay; + var ev = new ActiveTimerTriggerEvent(contentUid, uid); + RaiseLocalEvent(contentUid, ref ev); + } + } + + // Normally we'd use DeleteOnTrigger but because we need to wait for the frame update + // we have to delete it here instead + Del(uid); + } + } + } + + /// + /// Spawns one instance of the fill prototype or contained entity at the coordinate indicated + /// + private bool TrySpawnContents(MapCoordinates spawnCoordinates, ScatteringGrenadeComponent component, out EntityUid contentUid) + { + contentUid = default; + + if (component.UnspawnedCount > 0) + { + component.UnspawnedCount--; + contentUid = Spawn(component.FillPrototype, spawnCoordinates); + return true; + } + + if (component.Container.ContainedEntities.Count > 0) + { + contentUid = component.Container.ContainedEntities[0]; + + if (!_container.Remove(contentUid, component.Container)) + return false; + + return true; + } + + return false; + } +} diff --git a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs index 14a837aab73..5626f11e0e3 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs @@ -71,6 +71,12 @@ public sealed partial class NukeopsRuleComponent : Component [DataField] public TimeSpan WarNukieArriveDelay = TimeSpan.FromMinutes(15); + /// + /// Time crew can't call emergency shuttle after war declaration. + /// + [DataField] + public TimeSpan WarEvacShuttleDisabled = TimeSpan.FromMinutes(25); + /// /// Minimal operatives count for war declaration /// diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index ca6548301a7..e22626594f6 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -312,7 +312,7 @@ private void OnShuttleCallAttempt(ref CommunicationConsoleCallShuttleAttemptEven { // Nukies must wait some time after declaration of war to get on the station var warTime = Timing.CurTime.Subtract(nukeops.WarDeclaredTime.Value); - if (warTime < nukeops.WarNukieArriveDelay) + if (warTime < nukeops.WarEvacShuttleDisabled) { ev.Cancelled = true; ev.Reason = Loc.GetString("war-ops-shuttle-call-unavailable"); diff --git a/Content.Server/Ghost/GhostCommand.cs b/Content.Server/Ghost/GhostCommand.cs index 26163f6d4d9..927f9c8082f 100644 --- a/Content.Server/Ghost/GhostCommand.cs +++ b/Content.Server/Ghost/GhostCommand.cs @@ -29,7 +29,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) if (!gameTicker.PlayerGameStatuses.TryGetValue(player.UserId, out var playerStatus) || playerStatus is not PlayerGameStatus.JoinedGame) { - shell.WriteLine("ghost-command-error-lobby"); + shell.WriteLine(Loc.GetString("ghost-command-error-lobby")); return; } diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs new file mode 100644 index 00000000000..fce71a2cd15 --- /dev/null +++ b/Content.Server/Holopad/HolopadSystem.cs @@ -0,0 +1,782 @@ +using Content.Server.Chat.Systems; +using Content.Server.Popups; +using Content.Server.Power.EntitySystems; +using Content.Server.Speech.Components; +using Content.Server.Telephone; +using Content.Shared.Access.Systems; +using Content.Shared.Audio; +using Content.Shared.Chat.TypingIndicator; +using Content.Shared.Holopad; +using Content.Shared.IdentityManagement; +using Content.Shared.Labels.Components; +using Content.Shared.Silicons.StationAi; +using Content.Shared.Telephone; +using Content.Shared.UserInterface; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Containers; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using System.Linq; + +namespace Content.Server.Holopad; + +public sealed class HolopadSystem : SharedHolopadSystem +{ + [Dependency] private readonly TelephoneSystem _telephoneSystem = default!; + [Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!; + [Dependency] private readonly TransformSystem _xformSystem = default!; + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!; + [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!; + [Dependency] private readonly SharedStationAiSystem _stationAiSystem = default!; + [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private float _updateTimer = 1.0f; + + private const float UpdateTime = 1.0f; + private const float MinTimeBetweenSyncRequests = 0.5f; + private TimeSpan _minTimeSpanBetweenSyncRequests; + + private HashSet _pendingRequestsForSpriteState = new(); + private HashSet _recentlyUpdatedHolograms = new(); + + public override void Initialize() + { + base.Initialize(); + + _minTimeSpanBetweenSyncRequests = TimeSpan.FromSeconds(MinTimeBetweenSyncRequests); + + // Holopad UI and bound user interface messages + SubscribeLocalEvent(OnUIOpen); + SubscribeLocalEvent(OnHolopadStartNewCall); + SubscribeLocalEvent(OnHolopadAnswerCall); + SubscribeLocalEvent(OnHolopadEndCall); + SubscribeLocalEvent(OnHolopadActivateProjector); + SubscribeLocalEvent(OnHolopadStartBroadcast); + SubscribeLocalEvent(OnHolopadStationAiRequest); + + // Holopad telephone events + SubscribeLocalEvent(OnTelephoneStateChange); + SubscribeLocalEvent(OnHoloCallCommenced); + SubscribeLocalEvent(OnHoloCallEnded); + SubscribeLocalEvent(OnTelephoneMessageSent); + + // Networked events + SubscribeNetworkEvent(OnTypingChanged); + SubscribeNetworkEvent(OnPlayerSpriteStateMessage); + + // Component start/shutdown events + SubscribeLocalEvent(OnHolopadInit); + SubscribeLocalEvent(OnHolopadShutdown); + SubscribeLocalEvent(OnHolopadUserInit); + SubscribeLocalEvent(OnHolopadUserShutdown); + + // Misc events + SubscribeLocalEvent(OnEmote); + SubscribeLocalEvent(OnJumpToCore); + SubscribeLocalEvent>(AddToggleProjectorVerb); + SubscribeLocalEvent(OnAiRemove); + } + + #region: Holopad UI bound user interface messages + + private void OnUIOpen(Entity entity, ref BeforeActivatableUIOpenEvent args) + { + UpdateUIState(entity); + } + + private void OnHolopadStartNewCall(Entity source, ref HolopadStartNewCallMessage args) + { + if (IsHolopadControlLocked(source, args.Actor)) + return; + + if (!TryComp(source, out var sourceTelephone)) + return; + + var receiver = GetEntity(args.Receiver); + + if (!TryComp(receiver, out var receiverTelephone)) + return; + + LinkHolopadToUser(source, args.Actor); + _telephoneSystem.CallTelephone((source, sourceTelephone), (receiver, receiverTelephone), args.Actor); + } + + private void OnHolopadAnswerCall(Entity receiver, ref HolopadAnswerCallMessage args) + { + if (IsHolopadControlLocked(receiver, args.Actor)) + return; + + if (!TryComp(receiver, out var receiverTelephone)) + return; + + if (TryComp(args.Actor, out var userAiHeld)) + { + var source = GetLinkedHolopads(receiver).FirstOrNull(); + + if (source != null) + { + // Close any AI request windows + if (_stationAiSystem.TryGetStationAiCore(args.Actor, out var stationAiCore) && stationAiCore != null) + _userInterfaceSystem.CloseUi(receiver.Owner, HolopadUiKey.AiRequestWindow, args.Actor); + + // Try to warn the AI if the source of the call is out of its range + if (TryComp(stationAiCore, out var stationAiTelephone) && + TryComp(source, out var sourceTelephone) && + !_telephoneSystem.IsSourceInRangeOfReceiver((stationAiCore.Value.Owner, stationAiTelephone), (source.Value.Owner, sourceTelephone))) + { + _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-reach-holopad"), receiver, args.Actor); + return; + } + + ActivateProjector(source.Value, args.Actor); + } + + return; + } + + LinkHolopadToUser(receiver, args.Actor); + _telephoneSystem.AnswerTelephone((receiver, receiverTelephone), args.Actor); + } + + private void OnHolopadEndCall(Entity entity, ref HolopadEndCallMessage args) + { + if (!TryComp(entity, out var entityTelephone)) + return; + + if (IsHolopadControlLocked(entity, args.Actor)) + return; + + _telephoneSystem.EndTelephoneCalls((entity, entityTelephone)); + + // If the user is an AI, end all calls originating from its + // associated core to ensure that any broadcasts will end + if (!TryComp(args.Actor, out var stationAiHeld) || + !_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore)) + return; + + if (TryComp(stationAiCore, out var telephone)) + _telephoneSystem.EndTelephoneCalls((stationAiCore.Value, telephone)); + } + + private void OnHolopadActivateProjector(Entity entity, ref HolopadActivateProjectorMessage args) + { + ActivateProjector(entity, args.Actor); + } + + private void OnHolopadStartBroadcast(Entity source, ref HolopadStartBroadcastMessage args) + { + if (IsHolopadControlLocked(source, args.Actor) || IsHolopadBroadcastOnCoolDown(source)) + return; + + if (!_accessReaderSystem.IsAllowed(args.Actor, source)) + return; + + // AI broadcasting + if (TryComp(args.Actor, out var stationAiHeld)) + { + if (!_stationAiSystem.TryGetStationAiCore((args.Actor, stationAiHeld), out var stationAiCore) || + stationAiCore.Value.Comp.RemoteEntity == null || + !TryComp(stationAiCore, out var stationAiCoreHolopad)) + return; + + ExecuteBroadcast((stationAiCore.Value, stationAiCoreHolopad), args.Actor); + + // Switch the AI's perspective from free roaming to the target holopad + _xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(source).Coordinates); + _stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false); + + return; + } + + // Crew broadcasting + ExecuteBroadcast(source, args.Actor); + } + + private void OnHolopadStationAiRequest(Entity entity, ref HolopadStationAiRequestMessage args) + { + if (IsHolopadControlLocked(entity, args.Actor)) + return; + + if (!TryComp(entity, out var telephone)) + return; + + var source = new Entity(entity, telephone); + var query = AllEntityQuery(); + var reachableAiCores = new HashSet>(); + + while (query.MoveNext(out var receiverUid, out var receiverStationAiCore, out var receiverTelephone)) + { + var receiver = new Entity(receiverUid, receiverTelephone); + + // Check if the core can reach the call source, rather than the other way around + if (!_telephoneSystem.IsSourceAbleToReachReceiver(receiver, source)) + continue; + + if (_telephoneSystem.IsTelephoneEngaged(receiver)) + continue; + + reachableAiCores.Add((receiverUid, receiverTelephone)); + + if (!_stationAiSystem.TryGetInsertedAI((receiver, receiverStationAiCore), out var insertedAi)) + continue; + + if (_userInterfaceSystem.TryOpenUi(receiverUid, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner)) + LinkHolopadToUser(entity, args.Actor); + } + + // Ignore range so that holopads that ignore other devices on the same grid can request the AI + var options = new TelephoneCallOptions { IgnoreRange = true }; + _telephoneSystem.BroadcastCallToTelephones(source, reachableAiCores, args.Actor, options); + } + + #endregion + + #region: Holopad telephone events + + private void OnTelephoneStateChange(Entity holopad, ref TelephoneStateChangeEvent args) + { + // Update holopad visual and ambient states + switch (args.NewState) + { + case TelephoneState.Idle: + ShutDownHolopad(holopad); + SetHolopadAmbientState(holopad, false); + break; + + case TelephoneState.EndingCall: + ShutDownHolopad(holopad); + break; + + default: + SetHolopadAmbientState(holopad, this.IsPowered(holopad, EntityManager)); + break; + } + } + + private void OnHoloCallCommenced(Entity source, ref TelephoneCallCommencedEvent args) + { + if (source.Comp.Hologram == null) + GenerateHologram(source); + + // Receiver holopad holograms have to be generated now instead of waiting for their own event + // to fire because holographic avatars get synced immediately + if (TryComp(args.Receiver, out var receivingHolopad) && receivingHolopad.Hologram == null) + GenerateHologram((args.Receiver, receivingHolopad)); + + if (source.Comp.User != null) + { + // Re-link the user to refresh the sprite data + LinkHolopadToUser(source, source.Comp.User.Value); + } + } + + private void OnHoloCallEnded(Entity entity, ref TelephoneCallEndedEvent args) + { + if (!TryComp(entity, out var stationAiCore)) + return; + + // Auto-close the AI request window + if (_stationAiSystem.TryGetInsertedAI((entity, stationAiCore), out var insertedAi)) + _userInterfaceSystem.CloseUi(entity.Owner, HolopadUiKey.AiRequestWindow, insertedAi.Value.Owner); + } + + private void OnTelephoneMessageSent(Entity holopad, ref TelephoneMessageSentEvent args) + { + LinkHolopadToUser(holopad, args.MessageSource); + } + + #endregion + + #region: Networked events + + private void OnTypingChanged(HolopadUserTypingChangedEvent ev, EntitySessionEventArgs args) + { + var uid = args.SenderSession.AttachedEntity; + + if (!Exists(uid)) + return; + + if (!TryComp(uid, out var holopadUser)) + return; + + foreach (var linkedHolopad in holopadUser.LinkedHolopads) + { + var receiverHolopads = GetLinkedHolopads(linkedHolopad); + + foreach (var receiverHolopad in receiverHolopads) + { + if (receiverHolopad.Comp.Hologram == null) + continue; + + _appearanceSystem.SetData(receiverHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, ev.IsTyping); + } + } + } + + private void OnPlayerSpriteStateMessage(PlayerSpriteStateMessage ev, EntitySessionEventArgs args) + { + var uid = args.SenderSession.AttachedEntity; + + if (!Exists(uid)) + return; + + if (!_pendingRequestsForSpriteState.Remove(uid.Value)) + return; + + if (!TryComp(uid, out var holopadUser)) + return; + + SyncHolopadUserWithLinkedHolograms((uid.Value, holopadUser), ev.SpriteLayerData); + } + + #endregion + + #region: Component start/shutdown events + + private void OnHolopadInit(Entity entity, ref ComponentInit args) + { + if (entity.Comp.User != null) + LinkHolopadToUser(entity, entity.Comp.User.Value); + } + + private void OnHolopadUserInit(Entity entity, ref ComponentInit args) + { + foreach (var linkedHolopad in entity.Comp.LinkedHolopads) + LinkHolopadToUser(linkedHolopad, entity); + } + + private void OnHolopadShutdown(Entity entity, ref ComponentShutdown args) + { + if (TryComp(entity, out var telphone) && _telephoneSystem.IsTelephoneEngaged((entity.Owner, telphone))) + _telephoneSystem.EndTelephoneCalls((entity, telphone)); + + ShutDownHolopad(entity); + SetHolopadAmbientState(entity, false); + } + + private void OnHolopadUserShutdown(Entity entity, ref ComponentShutdown args) + { + foreach (var linkedHolopad in entity.Comp.LinkedHolopads) + UnlinkHolopadFromUser(linkedHolopad, entity); + } + + #endregion + + #region: Misc events + + private void OnEmote(Entity entity, ref EmoteEvent args) + { + foreach (var linkedHolopad in entity.Comp.LinkedHolopads) + { + // Treat the ability to hear speech as the ability to also perceive emotes + // (these are almost always going to be linked) + if (!HasComp(linkedHolopad)) + continue; + + if (TryComp(linkedHolopad, out var linkedHolopadTelephone) && linkedHolopadTelephone.Muted) + continue; + + foreach (var receiver in GetLinkedHolopads(linkedHolopad)) + { + if (receiver.Comp.Hologram == null) + continue; + + // Name is based on the physical identity of the user + var ent = Identity.Entity(entity, EntityManager); + var name = Loc.GetString("holopad-hologram-name", ("name", ent)); + + // Force the emote, because if the user can do it, the hologram can too + _chatSystem.TryEmoteWithChat(receiver.Comp.Hologram.Value, args.Emote, ChatTransmitRange.Normal, false, name, true, true); + } + } + } + + private void OnJumpToCore(Entity entity, ref JumpToCoreEvent args) + { + if (!TryComp(entity, out var entityStationAiHeld)) + return; + + if (!_stationAiSystem.TryGetStationAiCore((entity, entityStationAiHeld), out var stationAiCore)) + return; + + if (!TryComp(stationAiCore, out var stationAiCoreTelephone)) + return; + + _telephoneSystem.EndTelephoneCalls((stationAiCore.Value, stationAiCoreTelephone)); + } + + private void AddToggleProjectorVerb(Entity entity, ref GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + if (!this.IsPowered(entity, EntityManager)) + return; + + if (!TryComp(entity, out var entityTelephone) || + _telephoneSystem.IsTelephoneEngaged((entity, entityTelephone))) + return; + + var user = args.User; + + if (!TryComp(user, out var userAiHeld)) + return; + + if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) || + stationAiCore.Value.Comp.RemoteEntity == null) + return; + + AlternativeVerb verb = new() + { + Act = () => ActivateProjector(entity, user), + Text = Loc.GetString("holopad-activate-projector-verb"), + Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")), + }; + + args.Verbs.Add(verb); + } + + private void OnAiRemove(Entity entity, ref EntRemovedFromContainerMessage args) + { + if (!HasComp(entity)) + return; + + if (!TryComp(entity, out var entityTelephone)) + return; + + _telephoneSystem.EndTelephoneCalls((entity, entityTelephone)); + } + + #endregion + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _updateTimer += frameTime; + + if (_updateTimer >= UpdateTime) + { + _updateTimer -= UpdateTime; + + var query = AllEntityQuery(); + while (query.MoveNext(out var uid, out var holopad, out var telephone, out var xform)) + { + UpdateUIState((uid, holopad), telephone); + + if (holopad.User != null && + !HasComp(holopad.User) && + !_xformSystem.InRange((holopad.User.Value, Transform(holopad.User.Value)), (uid, xform), telephone.ListeningRange)) + { + UnlinkHolopadFromUser((uid, holopad), holopad.User.Value); + } + } + } + + _recentlyUpdatedHolograms.Clear(); + } + + public void UpdateUIState(Entity entity, TelephoneComponent? telephone = null) + { + if (!Resolve(entity.Owner, ref telephone, false)) + return; + + var source = new Entity(entity, telephone); + var holopads = new Dictionary(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var receiverUid, out var _, out var receiverTelephone)) + { + var receiver = new Entity(receiverUid, receiverTelephone); + + if (receiverTelephone.UnlistedNumber) + continue; + + if (source == receiver) + continue; + + if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver)) + continue; + + var name = MetaData(receiverUid).EntityName; + + if (TryComp(receiverUid, out var label) && !string.IsNullOrEmpty(label.CurrentLabel)) + name = label.CurrentLabel; + + holopads.Add(GetNetEntity(receiverUid), name); + } + + var uiKey = HasComp(entity) ? HolopadUiKey.AiActionWindow : HolopadUiKey.InteractionWindow; + _userInterfaceSystem.SetUiState(entity.Owner, uiKey, new HolopadBoundInterfaceState(holopads)); + } + + private void GenerateHologram(Entity entity) + { + if (entity.Comp.Hologram != null || + entity.Comp.HologramProtoId == null) + return; + + var uid = Spawn(entity.Comp.HologramProtoId, Transform(entity).Coordinates); + + // Safeguard - spawned holograms must have this component + if (!TryComp(uid, out var component)) + { + Del(uid); + return; + } + + entity.Comp.Hologram = new Entity(uid, component); + } + + private void DeleteHologram(Entity hologram, Entity attachedHolopad) + { + attachedHolopad.Comp.Hologram = null; + + QueueDel(hologram); + } + + private void LinkHolopadToUser(Entity entity, EntityUid user) + { + if (!TryComp(user, out var holopadUser)) + holopadUser = AddComp(user); + + if (user != entity.Comp.User?.Owner) + { + // Removes the old user from the holopad + UnlinkHolopadFromUser(entity, entity.Comp.User); + + // Assigns the new user in their place + holopadUser.LinkedHolopads.Add(entity); + entity.Comp.User = (user, holopadUser); + } + + if (TryComp(user, out var avatar)) + { + SyncHolopadUserWithLinkedHolograms((user, holopadUser), avatar.LayerData); + return; + } + + // We have no apriori sprite data for the hologram, request + // the current appearance of the user from the client + RequestHolopadUserSpriteUpdate((user, holopadUser)); + } + + private void UnlinkHolopadFromUser(Entity entity, Entity? user) + { + if (user == null) + return; + + entity.Comp.User = null; + + foreach (var linkedHolopad in GetLinkedHolopads(entity)) + { + if (linkedHolopad.Comp.Hologram != null) + { + _appearanceSystem.SetData(linkedHolopad.Comp.Hologram.Value.Owner, TypingIndicatorVisuals.IsTyping, false); + + // Send message with no sprite data to the client + // This will set the holgram sprite to a generic icon + var ev = new PlayerSpriteStateMessage(GetNetEntity(linkedHolopad.Comp.Hologram.Value)); + RaiseNetworkEvent(ev); + } + } + + if (!HasComp(user)) + return; + + user.Value.Comp.LinkedHolopads.Remove(entity); + + if (!user.Value.Comp.LinkedHolopads.Any()) + { + _pendingRequestsForSpriteState.Remove(user.Value); + + if (user.Value.Comp.LifeStage < ComponentLifeStage.Stopping) + RemComp(user.Value); + } + } + + private void ShutDownHolopad(Entity entity) + { + entity.Comp.ControlLockoutOwner = null; + + if (entity.Comp.Hologram != null) + DeleteHologram(entity.Comp.Hologram.Value, entity); + + if (entity.Comp.User != null) + UnlinkHolopadFromUser(entity, entity.Comp.User.Value); + + if (TryComp(entity, out var stationAiCore)) + _stationAiSystem.SwitchRemoteEntityMode((entity.Owner, stationAiCore), true); + + Dirty(entity); + } + + private void RequestHolopadUserSpriteUpdate(Entity user) + { + if (!_pendingRequestsForSpriteState.Add(user)) + return; + + var ev = new PlayerSpriteStateRequest(GetNetEntity(user)); + RaiseNetworkEvent(ev); + } + + private void SyncHolopadUserWithLinkedHolograms(Entity entity, PrototypeLayerData[]? spriteLayerData) + { + foreach (var linkedHolopad in entity.Comp.LinkedHolopads) + { + foreach (var receivingHolopad in GetLinkedHolopads(linkedHolopad)) + { + if (receivingHolopad.Comp.Hologram == null || !_recentlyUpdatedHolograms.Add(receivingHolopad.Comp.Hologram.Value)) + continue; + + var netHologram = GetNetEntity(receivingHolopad.Comp.Hologram.Value); + var ev = new PlayerSpriteStateMessage(netHologram, spriteLayerData); + RaiseNetworkEvent(ev); + } + } + } + + private void ActivateProjector(Entity entity, EntityUid user) + { + if (!TryComp(entity, out var receiverTelephone)) + return; + + var receiver = new Entity(entity, receiverTelephone); + + if (!TryComp(user, out var userAiHeld)) + return; + + if (!_stationAiSystem.TryGetStationAiCore((user, userAiHeld), out var stationAiCore) || + stationAiCore.Value.Comp.RemoteEntity == null) + return; + + if (!TryComp(stationAiCore, out var stationAiTelephone)) + return; + + if (!TryComp(stationAiCore, out var stationAiHolopad)) + return; + + var source = new Entity(stationAiCore.Value, stationAiTelephone); + + // Check if the AI is unable to activate the projector (unlikely this will ever pass; its just a safeguard) + if (!_telephoneSystem.IsSourceInRangeOfReceiver(source, receiver)) + { + _popupSystem.PopupEntity(Loc.GetString("holopad-ai-is-unable-to-activate-projector"), receiver, user); + return; + } + + // Terminate any calls that the core is hosting and immediately connect to the receiver + _telephoneSystem.TerminateTelephoneCalls(source); + + var callOptions = new TelephoneCallOptions() + { + ForceConnect = true, + MuteReceiver = true + }; + + _telephoneSystem.CallTelephone(source, receiver, user, callOptions); + + if (!_telephoneSystem.IsSourceConnectedToReceiver(source, receiver)) + return; + + LinkHolopadToUser((stationAiCore.Value, stationAiHolopad), user); + + // Switch the AI's perspective from free roaming to the target holopad + _xformSystem.SetCoordinates(stationAiCore.Value.Comp.RemoteEntity.Value, Transform(entity).Coordinates); + _stationAiSystem.SwitchRemoteEntityMode(stationAiCore.Value, false); + + // Open the holopad UI if it hasn't been opened yet + if (TryComp(entity, out var entityUserInterfaceComponent)) + _userInterfaceSystem.OpenUi((entity, entityUserInterfaceComponent), HolopadUiKey.InteractionWindow, user); + } + + private void ExecuteBroadcast(Entity source, EntityUid user) + { + if (!TryComp(source, out var sourceTelephone)) + return; + + var sourceTelephoneEntity = new Entity(source, sourceTelephone); + _telephoneSystem.TerminateTelephoneCalls(sourceTelephoneEntity); + + // Find all holopads in range of the source + var sourceXform = Transform(source); + var receivers = new HashSet>(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var receiver, out var receiverHolopad, out var receiverTelephone, out var receiverXform)) + { + var receiverTelephoneEntity = new Entity(receiver, receiverTelephone); + + if (sourceTelephoneEntity == receiverTelephoneEntity || + !_telephoneSystem.IsSourceAbleToReachReceiver(sourceTelephoneEntity, receiverTelephoneEntity)) + continue; + + // If any holopads in range are on broadcast cooldown, exit + if (IsHolopadBroadcastOnCoolDown((receiver, receiverHolopad))) + return; + + receivers.Add(receiverTelephoneEntity); + } + + var options = new TelephoneCallOptions() + { + ForceConnect = true, + MuteReceiver = true, + }; + + _telephoneSystem.BroadcastCallToTelephones(sourceTelephoneEntity, receivers, user, options); + + if (!_telephoneSystem.IsTelephoneEngaged(sourceTelephoneEntity)) + return; + + // Link to the user after all the calls have been placed, + // so we only need to sync all the holograms once + LinkHolopadToUser(source, user); + + // Lock out the controls of all involved holopads for a set duration + source.Comp.ControlLockoutOwner = user; + source.Comp.ControlLockoutStartTime = _timing.CurTime; + + Dirty(source); + + foreach (var receiver in GetLinkedHolopads(source)) + { + receiver.Comp.ControlLockoutOwner = user; + receiver.Comp.ControlLockoutStartTime = _timing.CurTime; + + Dirty(receiver); + } + } + + private HashSet> GetLinkedHolopads(Entity entity) + { + var linkedHolopads = new HashSet>(); + + if (!TryComp(entity, out var holopadTelephone)) + return linkedHolopads; + + foreach (var linkedEnt in holopadTelephone.LinkedTelephones) + { + if (!TryComp(linkedEnt, out var linkedHolopad)) + continue; + + linkedHolopads.Add((linkedEnt, linkedHolopad)); + } + + return linkedHolopads; + } + + private void SetHolopadAmbientState(Entity entity, bool isEnabled) + { + if (TryComp(entity, out var pointLight)) + _pointLightSystem.SetEnabled(entity, isEnabled, pointLight); + + if (TryComp(entity, out var ambientSound)) + _ambientSoundSystem.SetAmbience(entity, isEnabled, ambientSound); + } +} diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index 6e277dd29fb..0c86e2f6592 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -109,6 +109,10 @@ private void OnScramImplant(EntityUid uid, SubdermalImplantComponent component, if (TryComp(ent, out var pull) && _pullingSystem.IsPulled(ent, pull)) _pullingSystem.TryStopPull(ent, pull); + // Check if the user is pulling anything, and drop it if so + if (TryComp(ent, out var puller) && TryComp(puller.Pulling, out var pullable)) + _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); + var xform = Transform(ent); var targetCoords = SelectRandomTileInRange(xform, implant.TeleportRadius); diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index fa0ea26385d..6bd563101b3 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -23,7 +23,6 @@ using Content.Shared.Toggleable; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; -using Robust.Shared.Timing; namespace Content.Server.Medical; @@ -32,7 +31,6 @@ namespace Content.Server.Medical; /// public sealed class DefibrillatorSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly ChatSystem _chatManager = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DoAfterSystem _doAfter = default!; @@ -44,9 +42,9 @@ public sealed class DefibrillatorSystem : EntitySystem [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; /// public override void Initialize() @@ -59,6 +57,7 @@ private void OnAfterInteract(EntityUid uid, DefibrillatorComponent component, Af { if (args.Handled || args.Target is not { } target) return; + args.Handled = TryStartZap(uid, target, args.User, component); } @@ -102,7 +101,7 @@ public bool CanZap(EntityUid uid, EntityUid target, EntityUid? user = null, Defi return false; } - if (_timing.CurTime < component.NextZapTime) + if (!TryComp(uid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((uid, useDelay), component.DelayId)) return false; if (!TryComp(target, out var mobState)) @@ -181,8 +180,10 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo _audio.PlayPvs(component.ZapSound, uid); _electrocution.TryDoElectrocution(target, null, component.ZapDamage, component.WritheDuration, true, ignoreInsulation: true); - component.NextZapTime = _timing.CurTime + component.ZapDelay; - _appearance.SetData(uid, DefibrillatorVisuals.Ready, false); + if (!TryComp(uid, out var useDelay)) + return; + _useDelay.SetLength((uid, useDelay), component.ZapDelay, component.DelayId); + _useDelay.TryResetDelay((uid, useDelay), id: component.DelayId); ICommonSession? session = null; @@ -240,20 +241,4 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo var ev = new TargetDefibrillatedEvent(user, (uid, component)); RaiseLocalEvent(target, ref ev); } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var defib)) - { - if (defib.NextZapTime == null || _timing.CurTime < defib.NextZapTime) - continue; - - _audio.PlayPvs(defib.ReadySound, uid); - _appearance.SetData(uid, DefibrillatorVisuals.Ready, true); - defib.NextZapTime = null; - } - } } diff --git a/Content.Server/Mind/Toolshed/MindCommand.cs b/Content.Server/Mind/Toolshed/MindCommand.cs index 917e6fb7f1a..5f82029dc0b 100644 --- a/Content.Server/Mind/Toolshed/MindCommand.cs +++ b/Content.Server/Mind/Toolshed/MindCommand.cs @@ -29,19 +29,10 @@ public sealed class MindCommand : ToolshedCommand } [CommandImplementation("control")] - public EntityUid Control( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid target, - [CommandArgument] ValueRef playerRef) + public EntityUid Control(IInvocationContext ctx, [PipedArgument] EntityUid target, ICommonSession player) { _mind ??= GetSys(); - var player = playerRef.Evaluate(ctx); - if (player is null) - { - ctx.ReportError(new NotForServerConsoleError()); - return target; - } if (!_mind.TryGetMind(player, out var mindId, out var mind)) { diff --git a/Content.Server/Movement/Systems/SpriteMovementSystem.cs b/Content.Server/Movement/Systems/SpriteMovementSystem.cs new file mode 100644 index 00000000000..9fc4d828598 --- /dev/null +++ b/Content.Server/Movement/Systems/SpriteMovementSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Movement.Systems; + +namespace Content.Server.Movement.Systems; + +public sealed class SpriteMovementSystem : SharedSpriteMovementSystem +{ +} diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs index fa43b3e7524..6a295198c25 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs @@ -253,7 +253,7 @@ private bool TrySeek( if (!targetCoordinates.IsValid(EntityManager)) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); steering.Status = SteeringStatus.NoPath; return false; } @@ -263,7 +263,7 @@ private bool TrySeek( // Can't make it again. if (ourMap.MapId != targetMap.MapId) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); steering.Status = SteeringStatus.NoPath; return false; } diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs index fc63d1e6156..a8124c02493 100644 --- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs +++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.CombatMode; using Content.Shared.Interaction; using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.NPC; using Content.Shared.NPC.Components; @@ -207,6 +208,9 @@ public void Unregister(EntityUid uid, NPCSteeringComponent? component = null) if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller)) { controller.CurTickSprintMovement = Vector2.Zero; + + var ev = new SpriteMoveEvent(false); + RaiseLocalEvent(uid, ref ev); } component.PathfindToken?.Cancel(); @@ -270,7 +274,7 @@ public override void Update(float frameTime) } } - private void SetDirection(InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true) + private void SetDirection(EntityUid uid, InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true) { if (clear && value.Equals(Vector2.Zero)) { @@ -282,6 +286,9 @@ private void SetDirection(InputMoverComponent component, NPCSteeringComponent st component.CurTickSprintMovement = value; component.LastInputTick = _timing.CurTick; component.LastInputSubTick = ushort.MaxValue; + + var ev = new SpriteMoveEvent(true); + RaiseLocalEvent(uid, ref ev); } /// @@ -297,7 +304,7 @@ private void Steer( { if (Deleted(steering.Coordinates.EntityId)) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); steering.Status = SteeringStatus.NoPath; return; } @@ -305,14 +312,14 @@ private void Steer( // No path set from pathfinding or the likes. if (steering.Status == SteeringStatus.NoPath) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); return; } // Can't move at all, just noop input. if (!mover.CanMove) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); steering.Status = SteeringStatus.NoPath; return; } @@ -341,7 +348,7 @@ private void Steer( if (steering.CanSeek && !TrySeek(uid, mover, steering, body, xform, offsetRot, moveSpeed, interest, frameTime, ref forceSteer)) { - SetDirection(mover, steering, Vector2.Zero); + SetDirection(uid, mover, steering, Vector2.Zero); return; } @@ -354,7 +361,7 @@ private void Steer( if (!forceSteer) { - SetDirection(mover, steering, steering.LastSteerDirection, false); + SetDirection(uid, mover, steering, steering.LastSteerDirection, false); return; } @@ -391,7 +398,7 @@ private void Steer( steering.LastSteerDirection = resultDirection; DebugTools.Assert(!float.IsNaN(resultDirection.X)); - SetDirection(mover, steering, resultDirection, false); + SetDirection(uid, mover, steering, resultDirection, false); } private EntityCoordinates GetCoordinates(PathPoly poly) diff --git a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs index 2296de2eb6a..62806fe84fb 100644 --- a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs +++ b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs @@ -149,6 +149,13 @@ public override void Update(float frameTime) } } + // used to manually force an update for the groups + // the VisDoUpdate will be done with the next scheduled update + public void ForceUpdate() + { + DoGroupUpdates(); + } + private void DoGroupUpdates() { // "Why is there a separate queue for group remakes and node refloods when they both cause eachother" diff --git a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs index c91a6f795b2..6e9856a61dc 100644 --- a/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FatExtractorSystem.cs @@ -100,7 +100,7 @@ public bool TryGetValidOccupant(EntityUid uid, [NotNullWhen(true)] out EntityUid if (!TryComp(occupant, out var hunger)) return false; - if (hunger.CurrentHunger < component.NutritionPerSecond) + if (_hunger.GetHunger(hunger) < component.NutritionPerSecond) return false; if (hunger.CurrentThreshold < component.MinHungerThreshold && !HasComp(uid)) diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index c9a71c53584..1514d580dda 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Humanoid; using Content.Server.Inventory; using Content.Server.Mind.Commands; -using Content.Server.Nutrition; using Content.Server.Polymorph.Components; using Content.Shared.Actions; using Content.Shared.Buckle; @@ -13,6 +12,7 @@ using Content.Shared.Mind; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Nutrition; using Content.Shared.Polymorph; using Content.Shared.Popups; using Robust.Server.Audio; diff --git a/Content.Server/Polymorph/Toolshed/PolymorphCommand.cs b/Content.Server/Polymorph/Toolshed/PolymorphCommand.cs index 5654c84722f..db1e1faad6b 100644 --- a/Content.Server/Polymorph/Toolshed/PolymorphCommand.cs +++ b/Content.Server/Polymorph/Toolshed/PolymorphCommand.cs @@ -20,7 +20,7 @@ public sealed class PolymorphCommand : ToolshedCommand [CommandImplementation] public EntityUid? Polymorph( [PipedArgument] EntityUid input, - [CommandArgument] ProtoId protoId + ProtoId protoId ) { _system ??= GetSys(); @@ -34,7 +34,7 @@ [CommandArgument] ProtoId protoId [CommandImplementation] public IEnumerable Polymorph( [PipedArgument] IEnumerable input, - [CommandArgument] ProtoId protoId + ProtoId protoId ) => input.Select(x => Polymorph(x, protoId)).Where(x => x is not null).Select(x => (EntityUid)x!); } diff --git a/Content.Server/Power/Components/BatterySelfRechargerComponent.cs b/Content.Server/Power/Components/BatterySelfRechargerComponent.cs index 4798beb7573..1cb92d9cd63 100644 --- a/Content.Server/Power/Components/BatterySelfRechargerComponent.cs +++ b/Content.Server/Power/Components/BatterySelfRechargerComponent.cs @@ -1,3 +1,5 @@ +using System; + namespace Content.Server.Power.Components { /// @@ -6,8 +8,29 @@ namespace Content.Server.Power.Components [RegisterComponent] public sealed partial class BatterySelfRechargerComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] [DataField("autoRecharge")] public bool AutoRecharge { get; set; } + /// + /// Does the entity auto recharge? + /// + [DataField] public bool AutoRecharge; + + /// + /// At what rate does the entity automatically recharge? + /// + [DataField] public float AutoRechargeRate; + + /// + /// Should this entity stop automatically recharging if a charge is used? + /// + [DataField] public bool AutoRechargePause = false; + + /// + /// How long should the entity stop automatically recharging if a charge is used? + /// + [DataField] public float AutoRechargePauseTime = 0f; - [ViewVariables(VVAccess.ReadWrite)] [DataField("autoRechargeRate")] public float AutoRechargeRate { get; set; } + /// + /// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system. + /// + [DataField] public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0f); } } diff --git a/Content.Server/Power/EntitySystems/BatterySystem.cs b/Content.Server/Power/EntitySystems/BatterySystem.cs index ed2d54ffaa6..eac31ecef78 100644 --- a/Content.Server/Power/EntitySystems/BatterySystem.cs +++ b/Content.Server/Power/EntitySystems/BatterySystem.cs @@ -3,14 +3,18 @@ using Content.Server.Power.Components; using Content.Shared.Examine; using Content.Shared.Rejuvenate; +using Content.Shared.Timing; using JetBrains.Annotations; using Robust.Shared.Utility; +using Robust.Shared.Timing; namespace Content.Server.Power.EntitySystems { [UsedImplicitly] public sealed class BatterySystem : EntitySystem { + [Dependency] protected readonly IGameTiming Timing = default!; + public override void Initialize() { base.Initialize(); @@ -84,6 +88,14 @@ public override void Update(float frameTime) while (query.MoveNext(out var uid, out var comp, out var batt)) { if (!comp.AutoRecharge) continue; + if (batt.IsFullyCharged) continue; + + if (comp.AutoRechargePause) + { + if (comp.NextAutoRecharge > Timing.CurTime) + continue; + } + SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt); } } @@ -100,6 +112,8 @@ private void OnEmpPulse(EntityUid uid, BatteryComponent component, ref EmpPulseE { args.Affected = true; UseCharge(uid, args.EnergyConsumption, component); + // Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP. + TrySetChargeCooldown(uid); } public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null) @@ -110,6 +124,10 @@ public float UseCharge(EntityUid uid, float value, BatteryComponent? battery = n var newValue = Math.Clamp(0, battery.CurrentCharge - value, battery.MaxCharge); var delta = newValue - battery.CurrentCharge; battery.CurrentCharge = newValue; + + // Apply a cooldown to the entity's self recharge if needed. + TrySetChargeCooldown(uid); + var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge); RaiseLocalEvent(uid, ref ev); return delta; @@ -139,11 +157,47 @@ public void SetCharge(EntityUid uid, float value, BatteryComponent? battery = nu battery.CurrentCharge = MathHelper.Clamp(value, 0, battery.MaxCharge); if (MathHelper.CloseTo(battery.CurrentCharge, old) && !(old != battery.CurrentCharge && battery.CurrentCharge == battery.MaxCharge)) + { return; + } var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge); RaiseLocalEvent(uid, ref ev); } + /// + /// Checks if the entity has a self recharge and puts it on cooldown if applicable. + /// + public void TrySetChargeCooldown(EntityUid uid, float value = -1) + { + if (!TryComp(uid, out var batteryself)) + return; + + if (!batteryself.AutoRechargePause) + return; + + // If no answer or a negative is given for value, use the default from AutoRechargePauseTime. + if (value < 0) + value = batteryself.AutoRechargePauseTime; + + if (Timing.CurTime + TimeSpan.FromSeconds(value) <= batteryself.NextAutoRecharge) + return; + + SetChargeCooldown(uid, batteryself.AutoRechargePauseTime, batteryself); + } + + /// + /// Puts the entity's self recharge on cooldown for the specified time. + /// + public void SetChargeCooldown(EntityUid uid, float value, BatterySelfRechargerComponent? batteryself = null) + { + if (!Resolve(uid, ref batteryself)) + return; + + if (value >= 0) + batteryself.NextAutoRecharge = Timing.CurTime + TimeSpan.FromSeconds(value); + else + batteryself.NextAutoRecharge = Timing.CurTime; + } /// /// If sufficient charge is available on the battery, use it. Otherwise, don't. diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index a07d590461e..0fc641ed280 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -350,19 +350,20 @@ private void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent compon continue; // Get the device power stats - var powerValue = GetPrimaryPowerValues(ent, device, out var powerSupplied, out var powerUsage, out var batteryUsage); + var powerStats = GetPowerStats(ent, device); + //, out var powerSupplied, out var powerUsage, out var batteryUsage); // Update all running totals - totalSources += powerSupplied; - totalLoads += powerUsage; - totalBatteryUsage += batteryUsage; + totalSources += powerStats.PowerSupplied; + totalLoads += powerStats.PowerUsage; + totalBatteryUsage += powerStats.BatteryUsage; // Continue on if the device is not in the current focus group if (device.Group != component.FocusGroup) continue; // Generate a new console entry with which to populate the UI - var entry = new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), device.Group, powerValue); + var entry = new PowerMonitoringConsoleEntry(EntityManager.GetNetEntity(ent), device.Group, powerStats.PowerValue, powerStats.BatteryLevel); allEntries.Add(entry); } @@ -426,28 +427,28 @@ private void UpdateUIState(EntityUid uid, PowerMonitoringConsoleComponent compon loadsForFocus.ToArray())); } - private double GetPrimaryPowerValues(EntityUid uid, PowerMonitoringDeviceComponent device, out double powerSupplied, out double powerUsage, out double batteryUsage) + private PowerStats GetPowerStats(EntityUid uid, PowerMonitoringDeviceComponent device) { - var powerValue = 0d; - powerSupplied = 0d; - powerUsage = 0d; - batteryUsage = 0d; + var stats = new PowerStats(); if (device.Group == PowerMonitoringConsoleGroup.Generator) { // This covers most power sources if (TryComp(uid, out var supplier)) { - powerValue = supplier.CurrentSupply; - powerSupplied += powerValue; + stats.PowerValue = supplier.CurrentSupply; + stats.PowerSupplied += stats.PowerValue; } // Edge case: radiation collectors else if (TryComp(uid, out var _) && TryComp(uid, out var battery)) { - powerValue = battery.NetworkBattery.CurrentSupply; - powerSupplied += powerValue; + stats.PowerValue = battery.NetworkBattery.CurrentSupply; + stats.PowerSupplied += stats.PowerValue; + + + stats.BatteryLevel = GetBatteryLevel(uid); } } @@ -458,18 +459,20 @@ private double GetPrimaryPowerValues(EntityUid uid, PowerMonitoringDeviceCompone if (TryComp(uid, out var battery)) { - powerValue = battery.CurrentSupply; + stats.BatteryLevel = GetBatteryLevel(uid); + + stats.PowerValue = battery.CurrentSupply; // Load due to network battery recharging - powerUsage += Math.Max(battery.CurrentReceiving - battery.CurrentSupply, 0d); + stats.PowerUsage += Math.Max(battery.CurrentReceiving - battery.CurrentSupply, 0d); // Track battery usage - batteryUsage += Math.Max(battery.CurrentSupply - battery.CurrentReceiving, 0d); + stats.BatteryUsage += Math.Max(battery.CurrentSupply - battery.CurrentReceiving, 0d); // Records loads attached to APCs if (device.Group == PowerMonitoringConsoleGroup.APC && battery.Enabled) { - powerUsage += battery.NetworkBattery.LoadingNetworkDemand; + stats.PowerUsage += battery.NetworkBattery.LoadingNetworkDemand; } } } @@ -486,16 +489,28 @@ private double GetPrimaryPowerValues(EntityUid uid, PowerMonitoringDeviceCompone if (childDevice.IsCollectionMaster && childDevice.ChildDevices.ContainsKey(uid)) continue; - var childPowerValue = GetPrimaryPowerValues(child, childDevice, out var childPowerSupplied, out var childPowerUsage, out var childBatteryUsage); + var childResult = GetPowerStats(child, childDevice); - powerValue += childPowerValue; - powerSupplied += childPowerSupplied; - powerUsage += childPowerUsage; - batteryUsage += childBatteryUsage; + stats.PowerValue += childResult.PowerValue; + stats.PowerSupplied += childResult.PowerSupplied; + stats.PowerUsage += childResult.PowerUsage; + stats.BatteryUsage += childResult.BatteryUsage; } } - return powerValue; + return stats; + } + + private float? GetBatteryLevel(EntityUid uid) + { + if (!TryComp(uid, out var battery)) + return null; + + var effectiveMax = battery.MaxCharge; + if (effectiveMax == 0) + effectiveMax = 1; + + return battery.CurrentCharge / effectiveMax; } private void GetSourcesForNode(EntityUid uid, Node node, out List sources) @@ -532,7 +547,7 @@ private void GetSourcesForNode(EntityUid uid, Node node, out List(uid, out var lawcomp)) return; diff --git a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs index 3523f4d38f0..c3d4a565bb4 100644 --- a/Content.Server/Silicons/StationAi/AiVisionWireAction.cs +++ b/Content.Server/Silicons/StationAi/AiVisionWireAction.cs @@ -12,8 +12,8 @@ namespace Content.Server.Silicons.StationAi; public sealed partial class AiVisionWireAction : ComponentWireAction { public override string Name { get; set; } = "wire-name-ai-vision-light"; - public override Color Color { get; set; } = Color.DeepSkyBlue; - public override object StatusKey => AirlockWireStatus.AiControlIndicator; + public override Color Color { get; set; } = Color.White; + public override object StatusKey => AirlockWireStatus.AiVisionIndicator; public override StatusLightState? GetLightState(Wire wire, StationAiVisionComponent component) { diff --git a/Content.Server/Silicons/StationAi/StationAiSystem.cs b/Content.Server/Silicons/StationAi/StationAiSystem.cs index a10833dc63b..9c15112b305 100644 --- a/Content.Server/Silicons/StationAi/StationAiSystem.cs +++ b/Content.Server/Silicons/StationAi/StationAiSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; using Content.Shared.Chat; using Content.Shared.Mind; using Content.Shared.Roles; @@ -8,6 +9,7 @@ using Robust.Shared.Audio; using Robust.Shared.Map.Components; using Robust.Shared.Player; +using static Content.Server.Chat.Systems.ChatSystem; namespace Content.Server.Silicons.StationAi; @@ -15,11 +17,50 @@ public sealed class StationAiSystem : SharedStationAiSystem { [Dependency] private readonly IChatManager _chats = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _xforms = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; private readonly HashSet> _ais = new(); + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExpandICChatRecipients); + } + + private void OnExpandICChatRecipients(ExpandICChatRecipientsEvent ev) + { + var xformQuery = GetEntityQuery(); + var sourceXform = Transform(ev.Source); + var sourcePos = _xforms.GetWorldPosition(sourceXform, xformQuery); + + // This function ensures that chat popups appear on camera views that have connected microphones. + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var ent, out var entStationAiCore, out var entXform)) + { + var stationAiCore = new Entity(ent, entStationAiCore); + + if (!TryGetInsertedAI(stationAiCore, out var insertedAi) || !TryComp(insertedAi, out ActorComponent? actor)) + return; + + if (stationAiCore.Comp.RemoteEntity == null || stationAiCore.Comp.Remote) + return; + + var xform = Transform(stationAiCore.Comp.RemoteEntity.Value); + + var range = (xform.MapID != sourceXform.MapID) + ? -1 + : (sourcePos - _xforms.GetWorldPosition(xform, xformQuery)).Length(); + + if (range < 0 || range > ev.VoiceRange) + continue; + + ev.Recipients.TryAdd(actor.PlayerSession, new ICChatRecipientData(range, false)); + } + } + public override bool SetVisionEnabled(Entity entity, bool enabled, bool announce = false) { if (!base.SetVisionEnabled(entity, enabled, announce)) diff --git a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs index 6d97c8ccb3d..bda7d20a8e0 100644 --- a/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/ContainmentFieldGeneratorSystem.cs @@ -31,7 +31,7 @@ public override void Initialize() SubscribeLocalEvent(HandleGeneratorCollide); SubscribeLocalEvent(OnExamine); - SubscribeLocalEvent(OnInteract); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnAnchorChanged); SubscribeLocalEvent(OnReanchorEvent); SubscribeLocalEvent(OnUnanchorAttempt); @@ -90,7 +90,7 @@ private void OnExamine(EntityUid uid, ContainmentFieldGeneratorComponent compone args.PushMarkup(Loc.GetString("comp-containment-off")); } - private void OnInteract(Entity generator, ref InteractHandEvent args) + private void OnActivate(Entity generator, ref ActivateInWorldEvent args) { if (args.Handled) return; diff --git a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs index f828139ed6f..d3c5f8bb9cf 100644 --- a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs @@ -45,7 +45,7 @@ public override void Initialize() SubscribeLocalEvent(ReceivedChanged); SubscribeLocalEvent(OnApcChanged); - SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent>(OnGetVerb); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAnchorStateChanged); @@ -60,7 +60,7 @@ private void OnAnchorStateChanged(EntityUid uid, EmitterComponent component, ref SwitchOff(uid, component); } - private void OnInteractHand(EntityUid uid, EmitterComponent component, InteractHandEvent args) + private void OnActivate(EntityUid uid, EmitterComponent component, ActivateInWorldEvent args) { if (args.Handled) return; diff --git a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs index 797e1bda00d..bdd775c7927 100644 --- a/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs +++ b/Content.Server/Singularity/EntitySystems/RadiationCollectorSystem.cs @@ -30,7 +30,7 @@ public sealed class RadiationCollectorSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnRadiation); SubscribeLocalEvent(OnExamined); SubscribeLocalEvent(OnAnalyzed); @@ -65,7 +65,7 @@ private void OnTankChanged(EntityUid uid, RadiationCollectorComponent component, UpdateTankAppearance(uid, component, gasTank); } - private void OnInteractHand(EntityUid uid, RadiationCollectorComponent component, InteractHandEvent args) + private void OnActivate(EntityUid uid, RadiationCollectorComponent component, ActivateInWorldEvent args) { if (TryComp(uid, out UseDelayComponent? useDelay) && !_useDelay.TryResetDelay((uid, useDelay), true)) return; diff --git a/Content.Server/Station/Commands/JobsCommand.cs b/Content.Server/Station/Commands/JobsCommand.cs index 1d023c4a844..599a87aac6c 100644 --- a/Content.Server/Station/Commands/JobsCommand.cs +++ b/Content.Server/Station/Commands/JobsCommand.cs @@ -30,7 +30,7 @@ public IEnumerable Jobs([PipedArgument] IEnumerable stati => stations.SelectMany(Jobs); [CommandImplementation("job")] - public JobSlotRef Job([PipedArgument] EntityUid station, [CommandArgument] Prototype job) + public JobSlotRef Job([PipedArgument] EntityUid station, Prototype job) { _jobs ??= GetSys(); @@ -38,7 +38,7 @@ public JobSlotRef Job([PipedArgument] EntityUid station, [CommandArgument] Proto } [CommandImplementation("job")] - public IEnumerable Job([PipedArgument] IEnumerable stations, [CommandArgument] Prototype job) + public IEnumerable Job([PipedArgument] IEnumerable stations, Prototype job) => stations.Select(x => Job(x, job)); [CommandImplementation("isinfinite")] @@ -50,63 +50,41 @@ public IEnumerable IsInfinite([PipedArgument] IEnumerable jobs => jobs.Select(x => IsInfinite(x, inverted)); [CommandImplementation("adjust")] - public JobSlotRef Adjust( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] JobSlotRef @ref, - [CommandArgument] ValueRef by - ) + public JobSlotRef Adjust([PipedArgument] JobSlotRef @ref, int by) { _jobs ??= GetSys(); - _jobs.TryAdjustJobSlot(@ref.Station, @ref.Job, by.Evaluate(ctx), true, true); + _jobs.TryAdjustJobSlot(@ref.Station, @ref.Job, by, true, true); return @ref; } [CommandImplementation("adjust")] - public IEnumerable Adjust( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] IEnumerable @ref, - [CommandArgument] ValueRef by - ) - => @ref.Select(x => Adjust(ctx, x, by)); + public IEnumerable Adjust([PipedArgument] IEnumerable @ref, int by) + => @ref.Select(x => Adjust(x, by)); [CommandImplementation("set")] - public JobSlotRef Set( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] JobSlotRef @ref, - [CommandArgument] ValueRef by - ) + public JobSlotRef Set([PipedArgument] JobSlotRef @ref, int by) { _jobs ??= GetSys(); - _jobs.TrySetJobSlot(@ref.Station, @ref.Job, by.Evaluate(ctx), true); + _jobs.TrySetJobSlot(@ref.Station, @ref.Job, by, true); return @ref; } [CommandImplementation("set")] - public IEnumerable Set( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] IEnumerable @ref, - [CommandArgument] ValueRef by - ) - => @ref.Select(x => Set(ctx, x, by)); + public IEnumerable Set([PipedArgument] IEnumerable @ref, int by) + => @ref.Select(x => Set(x, by)); [CommandImplementation("amount")] - public int Amount( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] JobSlotRef @ref - ) + public int Amount([PipedArgument] JobSlotRef @ref) { _jobs ??= GetSys(); _jobs.TryGetJobSlot(@ref.Station, @ref.Job, out var slots); - return (int)(slots ?? 0); + return slots ?? 0; } [CommandImplementation("amount")] - public IEnumerable Amount( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] IEnumerable @ref - ) - => @ref.Select(x => Amount(ctx, x)); + public IEnumerable Amount([PipedArgument] IEnumerable @ref) + => @ref.Select(Amount); } // Used for Toolshed queries. diff --git a/Content.Server/Station/Commands/StationCommand.cs b/Content.Server/Station/Commands/StationCommand.cs index e1013548ac8..8fe8c67646d 100644 --- a/Content.Server/Station/Commands/StationCommand.cs +++ b/Content.Server/Station/Commands/StationCommand.cs @@ -27,7 +27,7 @@ public IEnumerable List() } [CommandImplementation("get")] - public EntityUid Get([CommandInvocationContext] IInvocationContext ctx) + public EntityUid Get(IInvocationContext ctx) { _station ??= GetSys(); @@ -54,7 +54,6 @@ public EntityUid Get([CommandInvocationContext] IInvocationContext ctx) public EntityUid? LargestGrid([PipedArgument] EntityUid input) { _station ??= GetSys(); - return _station.GetLargestGrid(Comp(input)); } @@ -80,46 +79,30 @@ public IEnumerable Grids([PipedArgument] IEnumerable input => input.Select(Config); [CommandImplementation("addgrid")] - public void AddGrid( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef grid - ) + public void AddGrid([PipedArgument] EntityUid input, EntityUid grid) { _station ??= GetSys(); - - _station.AddGridToStation(input, grid.Evaluate(ctx)); + _station.AddGridToStation(input, grid); } [CommandImplementation("rmgrid")] - public void RmGrid( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef grid - ) + public void RmGrid([PipedArgument] EntityUid input, EntityUid grid) { _station ??= GetSys(); - - _station.RemoveGridFromStation(input, grid.Evaluate(ctx)); + _station.RemoveGridFromStation(input, grid); } [CommandImplementation("rename")] - public void Rename([CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef name - ) + public void Rename([PipedArgument] EntityUid input, string name) { _station ??= GetSys(); - - _station.RenameStation(input, name.Evaluate(ctx)!); + _station.RenameStation(input, name); } [CommandImplementation("rerollBounties")] - public void RerollBounties([CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input) + public void RerollBounties([PipedArgument] EntityUid input) { _cargo ??= GetSys(); - _cargo.RerollBountyDatabase(input); } } diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index 882d841b24f..b3fbea035ae 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -127,7 +127,7 @@ public sealed class StationEventCommand : ToolshedCommand /// to even exist) so I think it's fine. /// [CommandImplementation("simulate")] - public IEnumerable<(string, float)> Simulate([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] int rounds, [CommandArgument] int playerCount, [CommandArgument] float roundEndMean, [CommandArgument] float roundEndStdDev) + public IEnumerable<(string, float)> Simulate(EntityPrototype eventScheduler, int rounds, int playerCount, float roundEndMean, float roundEndStdDev) { _stationEvent ??= GetSys(); _entityTable ??= GetSys(); @@ -179,7 +179,7 @@ public sealed class StationEventCommand : ToolshedCommand } [CommandImplementation("lsprob")] - public IEnumerable<(string, float)> LsProb([CommandArgument] EntityPrototype eventScheduler) + public IEnumerable<(string, float)> LsProb(EntityPrototype eventScheduler) { _compFac ??= IoCManager.Resolve(); _stationEvent ??= GetSys(); @@ -199,7 +199,7 @@ public sealed class StationEventCommand : ToolshedCommand } [CommandImplementation("lsprobtime")] - public IEnumerable<(string, float)> LsProbTime([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] float time) + public IEnumerable<(string, float)> LsProbTime(EntityPrototype eventScheduler, float time) { _compFac ??= IoCManager.Resolve(); _stationEvent ??= GetSys(); @@ -221,7 +221,7 @@ public sealed class StationEventCommand : ToolshedCommand } [CommandImplementation("prob")] - public float Prob([CommandArgument] EntityPrototype eventScheduler, [CommandArgument] string eventId) + public float Prob(EntityPrototype eventScheduler, string eventId) { _compFac ??= IoCManager.Resolve(); _stationEvent ??= GetSys(); diff --git a/Content.Server/StationEvents/Events/GreytideVirusRule.cs b/Content.Server/StationEvents/Events/GreytideVirusRule.cs index d32a6a6da1f..0395f7999d4 100644 --- a/Content.Server/StationEvents/Events/GreytideVirusRule.cs +++ b/Content.Server/StationEvents/Events/GreytideVirusRule.cs @@ -6,6 +6,7 @@ using Content.Shared.Doors.Systems; using Content.Shared.Lock; using Content.Shared.GameTicking.Components; +using Content.Shared.Station.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -43,6 +44,9 @@ protected override void Started(EntityUid uid, GreytideVirusRuleComponent virusC if (virusComp.Severity == null) return; + if (!TryGetRandomStation(out var chosenStation)) + return; + // pick random access groups var chosen = _random.GetItems(virusComp.AccessGroups, virusComp.Severity.Value, allowDuplicates: false); @@ -57,12 +61,16 @@ protected override void Started(EntityUid uid, GreytideVirusRuleComponent virusC var firelockQuery = GetEntityQuery(); var accessQuery = GetEntityQuery(); - var lockQuery = AllEntityQuery(); - while (lockQuery.MoveNext(out var lockUid, out var lockComp)) + var lockQuery = AllEntityQuery(); + while (lockQuery.MoveNext(out var lockUid, out var lockComp, out var xform)) { if (!accessQuery.TryComp(lockUid, out var accessComp)) continue; + // make sure not to hit CentCom or other maps + if (CompOrNull(xform.GridUid)?.Station != chosenStation) + continue; + // check access // the AreAccessTagsAllowed function is a little weird because it technically has support for certain tags to be locked out of opening something // which might have unintened side effects (see the comments in the function itself) @@ -74,13 +82,17 @@ protected override void Started(EntityUid uid, GreytideVirusRuleComponent virusC _lock.Unlock(lockUid, null, lockComp); } - var airlockQuery = AllEntityQuery(); - while (airlockQuery.MoveNext(out var airlockUid, out var airlockComp, out var doorComp)) + var airlockQuery = AllEntityQuery(); + while (airlockQuery.MoveNext(out var airlockUid, out var airlockComp, out var doorComp, out var xform)) { // don't space everything if (firelockQuery.HasComp(airlockUid)) continue; + // make sure not to hit CentCom or other maps + if (CompOrNull(xform.GridUid)?.Station != chosenStation) + continue; + // use the access reader from the door electronics if they exist if (!_access.GetMainAccessReader(airlockUid, out var accessComp)) continue; diff --git a/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs b/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs new file mode 100644 index 00000000000..a7cbac72543 --- /dev/null +++ b/Content.Server/Storage/Components/SpawnTableOnUseComponent.cs @@ -0,0 +1,17 @@ +using Content.Server.Storage.EntitySystems; +using Content.Shared.EntityTable.EntitySelectors; + +namespace Content.Server.Storage.Components; + +/// +/// Spawns items from an entity table when used in hand. +/// +[RegisterComponent, Access(typeof(SpawnTableOnUseSystem))] +public sealed partial class SpawnTableOnUseComponent : Component +{ + /// + /// The entity table to select entities from. + /// + [DataField(required: true)] + public EntityTableSelector Table = default!; +} diff --git a/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs b/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs new file mode 100644 index 00000000000..96556ed7b25 --- /dev/null +++ b/Content.Server/Storage/EntitySystems/SpawnTableOnUseSystem.cs @@ -0,0 +1,41 @@ +using Content.Server.Administration.Logs; +using Content.Server.Storage.Components; +using Content.Shared.Database; +using Content.Shared.EntityTable; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction.Events; + +namespace Content.Server.Storage.EntitySystems; + +public sealed class SpawnTableOnUseSystem : EntitySystem +{ + [Dependency] private readonly EntityTableSystem _entityTable = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + } + + private void OnUseInHand(Entity ent, ref UseInHandEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + var coords = Transform(ent).Coordinates; + var spawns = _entityTable.GetSpawns(ent.Comp.Table); + foreach (var id in spawns) + { + var spawned = Spawn(id, coords); + _adminLogger.Add(LogType.EntitySpawn, LogImpact.Low, $"{ToPrettyString(args.User):user} used {ToPrettyString(ent):spawner} which spawned {ToPrettyString(spawned)}"); + _hands.TryPickupAnyHand(args.User, spawned); + } + + Del(ent); + } +} diff --git a/Content.Server/StoreDiscount/Systems/StoreDiscountSystem.cs b/Content.Server/StoreDiscount/Systems/StoreDiscountSystem.cs index 9b4a0481aa2..c993c146948 100644 --- a/Content.Server/StoreDiscount/Systems/StoreDiscountSystem.cs +++ b/Content.Server/StoreDiscount/Systems/StoreDiscountSystem.cs @@ -71,7 +71,7 @@ private void OnStoreInitialized(ref StoreInitializedEvent ev) private IReadOnlyList InitializeDiscounts( IReadOnlyCollection listings, - int totalAvailableDiscounts = 3 + int totalAvailableDiscounts = 6 ) { // Get list of categories with cumulative weights. diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs new file mode 100644 index 00000000000..d4398c76d3f --- /dev/null +++ b/Content.Server/Telephone/TelephoneSystem.cs @@ -0,0 +1,476 @@ +using Content.Server.Access.Systems; +using Content.Server.Administration.Logs; +using Content.Server.Chat.Systems; +using Content.Server.Interaction; +using Content.Server.Power.EntitySystems; +using Content.Server.Speech; +using Content.Server.Speech.Components; +using Content.Shared.Chat; +using Content.Shared.Database; +using Content.Shared.Mind.Components; +using Content.Shared.Power; +using Content.Shared.Speech; +using Content.Shared.Telephone; +using Robust.Server.GameObjects; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Replays; +using System.Linq; +using Content.Shared.Silicons.StationAi; +using Content.Shared.Silicons.Borgs.Components; + +namespace Content.Server.Telephone; + +public sealed class TelephoneSystem : SharedTelephoneSystem +{ + [Dependency] private readonly AppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; + [Dependency] private readonly IdCardSystem _idCardSystem = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly IReplayRecordingManager _replay = default!; + + // Has set used to prevent telephone feedback loops + private HashSet<(EntityUid, string, Entity)> _recentChatMessages = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentShutdown); + SubscribeLocalEvent(OnPowerChanged); + SubscribeLocalEvent(OnAttemptListen); + SubscribeLocalEvent(OnListen); + SubscribeLocalEvent(OnTelephoneMessageReceived); + } + + #region: Events + + private void OnComponentShutdown(Entity entity, ref ComponentShutdown ev) + { + TerminateTelephoneCalls(entity); + } + + private void OnPowerChanged(Entity entity, ref PowerChangedEvent ev) + { + if (!ev.Powered) + TerminateTelephoneCalls(entity); + } + + private void OnAttemptListen(Entity entity, ref ListenAttemptEvent args) + { + if (!IsTelephonePowered(entity) || + !IsTelephoneEngaged(entity) || + entity.Comp.Muted || + !_interaction.InRangeUnobstructed(args.Source, entity.Owner, 0)) + { + args.Cancel(); + } + } + + private void OnListen(Entity entity, ref ListenEvent args) + { + if (args.Source == entity.Owner) + return; + + // Ignore background chatter from non-player entities + if (!HasComp(args.Source)) + return; + + // Simple check to make sure that we haven't sent this message already this frame + if (!_recentChatMessages.Add((args.Source, args.Message, entity))) + return; + + SendTelephoneMessage(args.Source, args.Message, entity); + } + + private void OnTelephoneMessageReceived(Entity entity, ref TelephoneMessageReceivedEvent args) + { + // Prevent message feedback loops + if (entity == args.TelephoneSource) + return; + + if (!IsTelephonePowered(entity) || + !IsSourceConnectedToReceiver(args.TelephoneSource, entity)) + return; + + var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource)); + RaiseLocalEvent(args.MessageSource, nameEv); + + var name = Loc.GetString("speech-name-relay", + ("speaker", Name(entity)), + ("originalName", nameEv.VoiceName)); + + var volume = entity.Comp.SpeakerVolume == TelephoneVolume.Speak ? InGameICChatType.Speak : InGameICChatType.Whisper; + _chat.TrySendInGameICMessage(entity, args.Message, volume, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); + } + + #endregion + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var telephone)) + { + var entity = new Entity(uid, telephone); + + if (IsTelephoneEngaged(entity)) + { + foreach (var receiver in telephone.LinkedTelephones) + { + if (!IsSourceInRangeOfReceiver(entity, receiver) && + !IsSourceInRangeOfReceiver(receiver, entity)) + { + EndTelephoneCall(entity, receiver); + } + } + } + + switch (telephone.CurrentState) + { + // Try to play ring tone if ringing + case TelephoneState.Ringing: + if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.RingingTimeout)) + EndTelephoneCalls(entity); + + else if (telephone.RingTone != null && + _timing.CurTime > telephone.NextRingToneTime) + { + _audio.PlayPvs(telephone.RingTone, uid); + telephone.NextRingToneTime = _timing.CurTime + TimeSpan.FromSeconds(telephone.RingInterval); + } + + break; + + // Try to hang up if their has been no recent in-call activity + case TelephoneState.InCall: + if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.IdlingTimeout)) + EndTelephoneCalls(entity); + + break; + + // Try to terminate if the telephone has finished hanging up + case TelephoneState.EndingCall: + if (_timing.CurTime > telephone.StateStartTime + TimeSpan.FromSeconds(telephone.HangingUpTimeout)) + TerminateTelephoneCalls(entity); + + break; + } + } + + _recentChatMessages.Clear(); + } + + public void BroadcastCallToTelephones(Entity source, HashSet> receivers, EntityUid user, TelephoneCallOptions? options = null) + { + if (IsTelephoneEngaged(source)) + return; + + foreach (var receiver in receivers) + TryCallTelephone(source, receiver, user, options); + + // If no connections could be made, hang up the telephone + if (!IsTelephoneEngaged(source)) + EndTelephoneCalls(source); + } + + public void CallTelephone(Entity source, Entity receiver, EntityUid user, TelephoneCallOptions? options = null) + { + if (IsTelephoneEngaged(source)) + return; + + if (!TryCallTelephone(source, receiver, user, options)) + EndTelephoneCalls(source); + } + + private bool TryCallTelephone(Entity source, Entity receiver, EntityUid user, TelephoneCallOptions? options = null) + { + if (!IsSourceAbleToReachReceiver(source, receiver) && options?.IgnoreRange != true) + return false; + + if (IsTelephoneEngaged(receiver) && + options?.ForceConnect != true && + options?.ForceJoin != true) + return false; + + var evCallAttempt = new TelephoneCallAttemptEvent(source, receiver, user); + RaiseLocalEvent(source, ref evCallAttempt); + + if (evCallAttempt.Cancelled) + return false; + + if (options?.ForceConnect == true) + TerminateTelephoneCalls(receiver); + + source.Comp.LinkedTelephones.Add(receiver); + source.Comp.Muted = options?.MuteSource == true; + + receiver.Comp.LastCallerId = GetNameAndJobOfCallingEntity(user); // This will be networked when the state changes + receiver.Comp.LinkedTelephones.Add(source); + receiver.Comp.Muted = options?.MuteReceiver == true; + + // Try to open a line of communication immediately + if (options?.ForceConnect == true || + (options?.ForceJoin == true && receiver.Comp.CurrentState == TelephoneState.InCall)) + { + CommenceTelephoneCall(source, receiver); + return true; + } + + // Otherwise start ringing the receiver + SetTelephoneState(source, TelephoneState.Calling); + SetTelephoneState(receiver, TelephoneState.Ringing); + + return true; + } + + public void AnswerTelephone(Entity receiver, EntityUid user) + { + if (receiver.Comp.CurrentState != TelephoneState.Ringing) + return; + + // If the telephone isn't linked, or is linked to more than one telephone, + // you shouldn't need to answer the call. If you do need to answer it, + // you'll need to be handled this a different way + if (receiver.Comp.LinkedTelephones.Count != 1) + return; + + var source = receiver.Comp.LinkedTelephones.First(); + CommenceTelephoneCall(source, receiver); + } + + private void CommenceTelephoneCall(Entity source, Entity receiver) + { + SetTelephoneState(source, TelephoneState.InCall); + SetTelephoneState(receiver, TelephoneState.InCall); + + SetTelephoneMicrophoneState(source, true); + SetTelephoneMicrophoneState(receiver, true); + + var evSource = new TelephoneCallCommencedEvent(receiver); + var evReceiver = new TelephoneCallCommencedEvent(source); + + RaiseLocalEvent(source, ref evSource); + RaiseLocalEvent(receiver, ref evReceiver); + } + + public void EndTelephoneCall(Entity source, Entity receiver) + { + source.Comp.LinkedTelephones.Remove(receiver); + receiver.Comp.LinkedTelephones.Remove(source); + + if (!IsTelephoneEngaged(source)) + EndTelephoneCalls(source); + + if (!IsTelephoneEngaged(receiver)) + EndTelephoneCalls(receiver); + } + + public void EndTelephoneCalls(Entity entity) + { + // No need to end any calls if the telephone is already ending a call + if (entity.Comp.CurrentState == TelephoneState.EndingCall) + return; + + HandleEndingTelephoneCalls(entity, TelephoneState.EndingCall); + + var ev = new TelephoneCallEndedEvent(); + RaiseLocalEvent(entity, ref ev); + } + + public void TerminateTelephoneCalls(Entity entity) + { + // No need to terminate any calls if the telephone is idle + if (entity.Comp.CurrentState == TelephoneState.Idle) + return; + + HandleEndingTelephoneCalls(entity, TelephoneState.Idle); + } + + private void HandleEndingTelephoneCalls(Entity entity, TelephoneState newState) + { + foreach (var linkedTelephone in entity.Comp.LinkedTelephones) + { + if (!linkedTelephone.Comp.LinkedTelephones.Remove(entity)) + continue; + + if (!IsTelephoneEngaged(linkedTelephone)) + EndTelephoneCalls(linkedTelephone); + } + + entity.Comp.LinkedTelephones.Clear(); + entity.Comp.Muted = false; + + SetTelephoneState(entity, newState); + SetTelephoneMicrophoneState(entity, false); + } + + private void SendTelephoneMessage(EntityUid messageSource, string message, Entity source, bool escapeMarkup = true) + { + // This method assumes that you've already checked that this + // telephone is able to transmit messages and that it can + // send messages to any telephones linked to it + + var ev = new TransformSpeakerNameEvent(messageSource, MetaData(messageSource).EntityName); + RaiseLocalEvent(messageSource, ev); + + var name = ev.VoiceName; + name = FormattedMessage.EscapeText(name); + + SpeechVerbPrototype speech; + if (ev.SpeechVerb != null && _prototype.TryIndex(ev.SpeechVerb, out var evntProto)) + speech = evntProto; + else + speech = _chat.GetSpeechVerb(messageSource, message); + + var content = escapeMarkup + ? FormattedMessage.EscapeText(message) + : message; + + var wrappedMessage = Loc.GetString(speech.Bold ? "chat-telephone-message-wrap-bold" : "chat-telephone-message-wrap", + ("color", Color.White), + ("fontType", speech.FontId), + ("fontSize", speech.FontSize), + ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), + ("name", name), + ("message", content)); + + var chat = new ChatMessage( + ChatChannel.Local, + message, + wrappedMessage, + NetEntity.Invalid, + null); + + var chatMsg = new MsgChatMessage { Message = chat }; + + var evSentMessage = new TelephoneMessageSentEvent(message, chatMsg, messageSource); + RaiseLocalEvent(source, ref evSentMessage); + source.Comp.StateStartTime = _timing.CurTime; + + var evReceivedMessage = new TelephoneMessageReceivedEvent(message, chatMsg, messageSource, source); + + foreach (var receiver in source.Comp.LinkedTelephones) + { + RaiseLocalEvent(receiver, ref evReceivedMessage); + receiver.Comp.StateStartTime = _timing.CurTime; + } + + if (name != Name(messageSource)) + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telephone message from {ToPrettyString(messageSource):user} as {name} on {source}: {message}"); + else + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Telephone message from {ToPrettyString(messageSource):user} on {source}: {message}"); + + _replay.RecordServerMessage(chat); + } + + private void SetTelephoneState(Entity entity, TelephoneState newState) + { + var oldState = entity.Comp.CurrentState; + + entity.Comp.CurrentState = newState; + entity.Comp.StateStartTime = _timing.CurTime; + Dirty(entity); + + _appearanceSystem.SetData(entity, TelephoneVisuals.Key, entity.Comp.CurrentState); + + var ev = new TelephoneStateChangeEvent(oldState, newState); + RaiseLocalEvent(entity, ref ev); + } + + private void SetTelephoneMicrophoneState(Entity entity, bool microphoneOn) + { + if (microphoneOn && !HasComp(entity)) + { + var activeListener = AddComp(entity); + activeListener.Range = entity.Comp.ListeningRange; + } + + if (!microphoneOn && HasComp(entity)) + { + RemComp(entity); + } + } + + private (string?, string?) GetNameAndJobOfCallingEntity(EntityUid uid) + { + string? presumedName = null; + string? presumedJob = null; + + if (HasComp(uid) || HasComp(uid)) + { + presumedName = Name(uid); + return (presumedName, presumedJob); + } + + if (_idCardSystem.TryFindIdCard(uid, out var idCard)) + { + presumedName = string.IsNullOrWhiteSpace(idCard.Comp.FullName) ? null : idCard.Comp.FullName; + presumedJob = idCard.Comp.LocalizedJobTitle; + } + + return (presumedName, presumedJob); + } + + public bool IsSourceAbleToReachReceiver(Entity source, Entity receiver) + { + if (source == receiver || + !IsTelephonePowered(source) || + !IsTelephonePowered(receiver) || + !IsSourceInRangeOfReceiver(source, receiver)) + { + return false; + } + + return true; + } + + public bool IsSourceInRangeOfReceiver(Entity source, Entity receiver) + { + // Check if the source and receiver have compatible transmision / reception bandwidths + if (!source.Comp.CompatibleRanges.Contains(receiver.Comp.TransmissionRange)) + return false; + + var sourceXform = Transform(source); + var receiverXform = Transform(receiver); + + // Check if we should ignore a device thats on the same grid + if (source.Comp.IgnoreTelephonesOnSameGrid && + source.Comp.TransmissionRange != TelephoneRange.Grid && + receiverXform.GridUid == sourceXform.GridUid) + return false; + + switch (source.Comp.TransmissionRange) + { + case TelephoneRange.Grid: + return sourceXform.GridUid == receiverXform.GridUid; + + case TelephoneRange.Map: + return sourceXform.MapID == receiverXform.MapID; + + case TelephoneRange.Unlimited: + return true; + } + + return false; + } + + public bool IsSourceConnectedToReceiver(Entity source, Entity receiver) + { + return source.Comp.LinkedTelephones.Contains(receiver); + } + + public bool IsTelephonePowered(Entity entity) + { + return this.IsPowered(entity, EntityManager) || !entity.Comp.RequiresPower; + } +} diff --git a/Content.Server/Toolshed/Commands/AdminDebug/ACmdCommand.cs b/Content.Server/Toolshed/Commands/AdminDebug/ACmdCommand.cs index f113e496555..ff110a9d9ed 100644 --- a/Content.Server/Toolshed/Commands/AdminDebug/ACmdCommand.cs +++ b/Content.Server/Toolshed/Commands/AdminDebug/ACmdCommand.cs @@ -22,13 +22,9 @@ public sealed class ACmdCommand : ToolshedCommand } [CommandImplementation("caninvoke")] - public bool CanInvoke( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] CommandSpec command, - [CommandArgument] ValueRef player - ) + public bool CanInvoke(IInvocationContext ctx, [PipedArgument] CommandSpec command, ICommonSession player) { // Deliberately discard the error. - return ((IPermissionController) _adminManager).CheckInvokable(command, player.Evaluate(ctx), out var err); + return ((IPermissionController) _adminManager).CheckInvokable(command, player, out _); } } diff --git a/Content.Server/Toolshed/Commands/Verbs/RunVerbAsCommand.cs b/Content.Server/Toolshed/Commands/Verbs/RunVerbAsCommand.cs index 5c1bac6c93b..d251d668981 100644 --- a/Content.Server/Toolshed/Commands/Verbs/RunVerbAsCommand.cs +++ b/Content.Server/Toolshed/Commands/Verbs/RunVerbAsCommand.cs @@ -15,10 +15,10 @@ public sealed class RunVerbAsCommand : ToolshedCommand [CommandImplementation] public IEnumerable RunVerbAs( - [CommandInvocationContext] IInvocationContext ctx, + IInvocationContext ctx, [PipedArgument] IEnumerable input, - [CommandArgument] ValueRef runner, - [CommandArgument] string verb + EntityUid runner, + string verb ) { _verb ??= GetSys(); @@ -26,17 +26,14 @@ [CommandArgument] string verb foreach (var i in input) { - var runnerNet = runner.Evaluate(ctx); - var runnerEid = EntityManager.GetEntity(runnerNet); - - if (EntityManager.Deleted(runnerEid) && runnerEid.IsValid()) - ctx.ReportError(new DeadEntity(runnerEid)); + if (EntityManager.Deleted(runner) && runner.IsValid()) + ctx.ReportError(new DeadEntity(runner)); if (ctx.GetErrors().Any()) yield break; var eId = EntityManager.GetEntity(i); - var verbs = _verb.GetLocalVerbs(eId, runnerEid, Verb.VerbTypes, true); + var verbs = _verb.GetLocalVerbs(eId, runner, Verb.VerbTypes, true); // if the "verb name" is actually a verb-type, try run any verb of that type. var verbType = Verb.VerbTypes.FirstOrDefault(x => x.Name == verb); @@ -45,7 +42,7 @@ [CommandArgument] string verb var verbTy = verbs.FirstOrDefault(v => v.GetType() == verbType); if (verbTy != null) { - _verb.ExecuteVerb(verbTy, runnerEid, eId, forced: true); + _verb.ExecuteVerb(verbTy, runner, eId, forced: true); yield return i; } } @@ -54,7 +51,7 @@ [CommandArgument] string verb { if (verbTy.Text.ToLowerInvariant() == verb) { - _verb.ExecuteVerb(verbTy, runnerEid, eId, forced: true); + _verb.ExecuteVerb(verbTy, runner, eId, forced: true); yield return i; } } diff --git a/Content.Server/Toolshed/Commands/VisualizeCommand.cs b/Content.Server/Toolshed/Commands/VisualizeCommand.cs index 2225bfaf447..41613cab86b 100644 --- a/Content.Server/Toolshed/Commands/VisualizeCommand.cs +++ b/Content.Server/Toolshed/Commands/VisualizeCommand.cs @@ -16,7 +16,7 @@ public sealed class VisualizeCommand : ToolshedCommand [CommandImplementation] public void VisualizeEntities( - [CommandInvocationContext] IInvocationContext ctx, + IInvocationContext ctx, [PipedArgument] IEnumerable input ) { diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.Ballistic.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.Ballistic.cs index 798be3fc8ea..60680deaaaa 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.Ballistic.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.Ballistic.cs @@ -15,6 +15,7 @@ protected override void Cycle(EntityUid uid, BallisticAmmoProviderComponent comp { var existing = component.Entities[^1]; component.Entities.RemoveAt(component.Entities.Count - 1); + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities)); Containers.Remove(existing, component.Container); EnsureShootable(existing); @@ -22,6 +23,7 @@ protected override void Cycle(EntityUid uid, BallisticAmmoProviderComponent comp else if (component.UnspawnedCount > 0) { component.UnspawnedCount--; + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount)); ent = Spawn(component.Proto, coordinates); EnsureShootable(ent.Value); } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.Solution.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.Solution.cs index cc36132e8e5..d0898efaaf8 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.Solution.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.Solution.cs @@ -39,8 +39,9 @@ protected override void UpdateSolutionShots(EntityUid uid, SolutionAmmoProviderC if (solution == null && !_solutionContainer.TryGetSolution(uid, component.SolutionId, out _, out solution)) { component.Shots = shots; + DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.Shots)); component.MaxShots = maxShots; - Dirty(uid, component); + DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.MaxShots)); return; } @@ -48,8 +49,10 @@ protected override void UpdateSolutionShots(EntityUid uid, SolutionAmmoProviderC maxShots = (int) (solution.MaxVolume / component.FireCost); component.Shots = shots; + DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.Shots)); + component.MaxShots = maxShots; - Dirty(uid, component); + DirtyField(uid, component, nameof(SolutionAmmoProviderComponent.MaxShots)); UpdateSolutionAppearance(uid, component); } diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 371c6f1222a..ec50e11a0ca 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Chat.Systems; using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; +using Content.Shared.Anomaly.Components; using Content.Shared.Bed.Sleep; using Content.Shared.Cloning; using Content.Shared.Damage; @@ -64,10 +65,19 @@ public override void Initialize() SubscribeLocalEvent(OnGetCharacterDeadIC); SubscribeLocalEvent(OnPendingMapInit); + SubscribeLocalEvent(OnBeforeRemoveAnomalyOnDeath); SubscribeLocalEvent(OnPendingMapInit); SubscribeLocalEvent(OnDamageChanged); + + } + + private void OnBeforeRemoveAnomalyOnDeath(Entity ent, ref BeforeRemoveAnomalyOnDeathEvent args) + { + // Pending zombies (e.g. infected non-zombies) do not remove their hosted anomaly on death. + // Current zombies DO remove the anomaly on death. + args.Cancelled = true; } private void OnPendingMapInit(EntityUid uid, IncurableZombieComponent component, MapInitEvent args) diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index d848512def6..bd8ba8596c2 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -3,91 +3,393 @@ namespace Content.Shared.Database; // DO NOT CHANGE THE NUMERIC VALUES OF THESE public enum LogType { - Unknown = 0, // do not use + /// + /// Test logs. DO NOT USE!!! + /// + Unknown = 0, // DamageChange = 1 + + /// + /// A player dealt damage to an entity. + /// Damaged = 2, + + /// + /// A player healed an entity. + /// Healed = 3, + + /// + /// A player slipped on an entity. + /// Slip = 4, + + /// + /// Station event was added or announced. + /// EventAnnounced = 5, + + /// + /// Game rule was added or started. + /// EventStarted = 6, EventRan = 16, + + /// + /// Game rule was stopped. + /// EventStopped = 7, + + /// + /// A player used a verb on an entity. + /// Verb = 19, + + /// + /// An evacuation shuttle was called. + /// ShuttleCalled = 8, + + /// + /// An evacuation shuttle was recalled. + /// ShuttleRecalled = 9, + + /// + /// Explosive depressurization related interactions. + /// ExplosiveDepressurization = 10, + + /// + /// A player or entity was respawned. + /// Respawn = 13, + + /// + /// A player joined station on round start. + /// RoundStartJoin = 14, + + /// + /// A player joined station after round start. + /// LateJoin = 15, + + /// + /// Chemical reactions related interactions. + /// ChemicalReaction = 17, + + /// + /// Reagent effects related interactions. + /// ReagentEffect = 18, + + /// + /// Canister valve was opened or closed. + /// CanisterValve = 20, + + /// + /// Release pressure on the canister was changed. + /// CanisterPressure = 21, + + /// + /// Canister purged its contents into the environment. + /// CanisterPurged = 22, + + /// + /// Tank was ejected from the canister. + /// CanisterTankEjected = 23, + + /// + /// Tank was inserted into the canister. + /// CanisterTankInserted = 24, + + /// + /// A player tried to disarm an entity. + /// DisarmedAction = 25, + + /// + /// A player knocked down an entity on the floor. + /// DisarmedKnockdown = 26, AttackArmedClick = 27, AttackArmedWide = 28, AttackUnarmedClick = 29, AttackUnarmedWide = 30, + + /// + /// A player interacted with an entity in his hand. + /// InteractHand = 31, + + /// + /// A player activated an entity. + /// InteractActivate = 32, + + /// + /// A player threw an entity. + /// Throw = 33, + + /// + /// Entity landed. + /// Landed = 34, + + /// + /// A thrown entity hit the other entity. + /// ThrowHit = 35, + + /// + /// A player picked up an entity. + /// Pickup = 36, + + /// + /// A player dropped an entity. + /// Drop = 37, + + /// + /// A bullet hit an entity. + /// BulletHit = 38, - ForceFeed = 40, // involuntary - Ingestion = 53, // voluntary + + /// + /// A player force-feed an entity or injected it with a solution. + /// + ForceFeed = 40, + + /// + /// A player ate an entity or injected themselves with a solution. + /// + Ingestion = 53, + + /// + /// A melee attack hit an entity. + /// MeleeHit = 41, + + /// + /// A hitscan attack hit an entity. + /// HitScanHit = 42, - Mind = 43, // Suicides, ghosting, repossession, objectives, etc. + + /// + /// Suicides, ghosting, repossession, objectives, etc. + /// + Mind = 43, + + /// + /// Explosions and explosives related interactions. + /// Explosion = 44, - Radiation = 45, // Unused + Radiation = 45, + + /// + /// Entity started or stopped taking pressure damage. + /// Barotrauma = 46, + + /// + /// Fire started or stopped. + /// Flammable = 47, + + /// + /// Entity started or stopped suffocating. + /// Asphyxiation = 48, + + /// + /// Entity started or stopped taking temperature damage. + /// Temperature = 49, Hunger = 50, Thirst = 51, + + /// + /// Entity received electrocution damage. + /// Electrocution = 52, + + /// + /// A player drew using a crayon. + /// CrayonDraw = 39, + + /// + /// A player changed pressure on atmos device. + /// AtmosPressureChanged = 54, + + /// + /// A player changed power on atmos device. + /// AtmosPowerChanged = 55, + + /// + /// A player changed transfer rate on atmos device. + /// AtmosVolumeChanged = 56, + + /// + /// A player changed filter on atmos device. + /// AtmosFilterChanged = 57, + + /// + /// A player changed ratio on atmos device. + /// AtmosRatioChanged = 58, + + /// + /// Field generator was toggled or lost field connections. + /// FieldGeneration = 59, + + /// + /// A player took ghost role. + /// GhostRoleTaken = 60, + + /// + /// OOC, IC, LOOC, etc. + /// Chat = 61, + + /// + /// A player performed some action. Like pressing eject and flash buttons on a trash bin, etc. + /// Action = 62, + + /// + /// A player used RCD. + /// RCD = 63, + + /// + /// Construction related interactions. + /// Construction = 64, + + /// + /// Triggers related interactions. + /// Trigger = 65, + + /// + /// A player tries to anchor or anchored an entity. + /// Anchor = 66, + + /// + /// A player unanchored an entity. + /// Unanchor = 67, + + /// + /// Emergency shuttle related interactions. + /// EmergencyShuttle = 68, - // haha so funny + + /// + /// A player emagged an entity. + /// Emag = 69, + + /// + /// A player was gibbed. + /// Gib = 70, + + /// + /// Identity related interactions. + /// Identity = 71, + + /// + /// A player cut a cable. + /// CableCut = 72, + + /// + /// A player purchased something from the "store". + /// StorePurchase = 73, + + /// + /// A player edited a tile using some tool. + /// LatticeCut = 74, + + /// + /// A player is equipping something on an entity or stripping it from it. + /// Stripping = 75, + + /// + /// A player caused stamina damage or entered stamina crit. + /// Stamina = 76, + + /// + /// A player's actions caused an entity spawn. + /// EntitySpawn = 77, + + /// + /// Prayers and subtle messages. + /// AdminMessage = 78, + + /// + /// Anomaly related interactions. + /// Anomaly = 79, + + /// + /// Cutting, mending and pulsing of wires. + /// WireHacking = 80, + + /// + /// Entity was teleported. + /// Teleport = 81, + + /// + /// Entity was removed in a result of something. + /// EntityDelete = 82, + + /// + /// Voting related interactions. + /// Vote = 83, + + /// + /// Entity was configured. + /// ItemConfigure = 84, + + /// + /// Device linking related interactions. + /// DeviceLinking = 85, + + /// + /// Tiles related interactions. + /// Tile = 86, BagOfHolding = 420, //Nyano - Summary: adds bag of holding. Psionics = 421, //Nyano - Summary: ads psionic as a log type. @@ -96,8 +398,20 @@ public enum LogType /// A client has sent too many chat messages recently and is temporarily blocked from sending more. /// ChatRateLimited = 87, + + /// + /// A player changed temperature on atmos device. + /// AtmosTemperatureChanged = 88, + + /// + /// Something was sent over device network. Like broadcast. + /// DeviceNetwork = 89, + + /// + /// A player had a refund at the "store". + /// StoreRefund = 90, /// diff --git a/Content.Shared/Administration/AdminData.cs b/Content.Shared/Administration/AdminData.cs index 229edbcd4c3..bad36f78c57 100644 --- a/Content.Shared/Administration/AdminData.cs +++ b/Content.Shared/Administration/AdminData.cs @@ -31,10 +31,11 @@ public sealed class AdminData /// Checks whether this admin has an admin flag. /// /// The flags to check. Multiple flags can be specified, they must all be held. + /// If true then also count flags even if the admin has de-adminned. /// False if this admin is not or does not have all the flags specified. - public bool HasFlag(AdminFlags flag) + public bool HasFlag(AdminFlags flag, bool includeDeAdmin = false) { - return Active && (Flags & flag) == flag; + return (includeDeAdmin || Active) && (Flags & flag) == flag; } /// diff --git a/Content.Shared/Administration/Managers/ISharedAdminManager.cs b/Content.Shared/Administration/Managers/ISharedAdminManager.cs index 22d918d4a33..f24d7f37d9a 100644 --- a/Content.Shared/Administration/Managers/ISharedAdminManager.cs +++ b/Content.Shared/Administration/Managers/ISharedAdminManager.cs @@ -18,7 +18,7 @@ public interface ISharedAdminManager /// /// if the player is not an admin. AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false); - + /// /// Gets the admin data for a player, if they are an admin. /// @@ -37,24 +37,30 @@ public interface ISharedAdminManager /// /// When used by the client, this only returns accurate results for the player's own entity. /// + /// + /// Whether to check flags even for admins that are current de-adminned. + /// /// True if the player is and admin and has the specified flags. - bool HasAdminFlag(EntityUid player, AdminFlags flag) + bool HasAdminFlag(EntityUid player, AdminFlags flag, bool includeDeAdmin = false) { - var data = GetAdminData(player); - return data != null && data.HasFlag(flag); + var data = GetAdminData(player, includeDeAdmin); + return data != null && data.HasFlag(flag, includeDeAdmin); } - + /// /// See if a player has an admin flag. /// /// /// When used by the client, this only returns accurate results for the player's own session. /// + /// + /// Whether to check flags even for admins that are current de-adminned. + /// /// True if the player is and admin and has the specified flags. - bool HasAdminFlag(ICommonSession player, AdminFlags flag) + bool HasAdminFlag(ICommonSession player, AdminFlags flag, bool includeDeAdmin = false) { - var data = GetAdminData(player); - return data != null && data.HasFlag(flag); + var data = GetAdminData(player, includeDeAdmin); + return data != null && data.HasFlag(flag, includeDeAdmin); } /// @@ -71,7 +77,7 @@ bool IsAdmin(EntityUid uid, bool includeDeAdmin = false) { return GetAdminData(uid, includeDeAdmin) != null; } - + /// /// Checks if a player is an admin. /// diff --git a/Content.Shared/Animals/UdderComponent.cs b/Content.Shared/Animals/UdderComponent.cs new file mode 100644 index 00000000000..d2767b08960 --- /dev/null +++ b/Content.Shared/Animals/UdderComponent.cs @@ -0,0 +1,57 @@ +using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Animals; + +/// +/// Gives the ability to produce a solution; +/// produces endlessly if the owner does not have a HungerComponent. +/// +[RegisterComponent, AutoGenerateComponentState, AutoGenerateComponentPause, NetworkedComponent] +public sealed partial class UdderComponent : Component +{ + /// + /// The reagent to produce. + /// + [DataField, AutoNetworkedField] + public ProtoId ReagentId = new(); + + /// + /// The name of . + /// + [DataField] + public string SolutionName = "udder"; + + /// + /// The solution to add reagent to. + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public Entity? Solution = null; + + /// + /// The amount of reagent to be generated on update. + /// + [DataField, AutoNetworkedField] + public FixedPoint2 QuantityPerUpdate = 25; + + /// + /// The amount of nutrient consumed on update. + /// + [DataField, AutoNetworkedField] + public float HungerUsage = 10f; + + /// + /// How long to wait before producing. + /// + [DataField, AutoNetworkedField] + public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1); + + /// + /// When to next try to produce. + /// + [DataField, AutoPausedField, Access(typeof(UdderSystem))] + public TimeSpan NextGrowth = TimeSpan.Zero; +} diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Shared/Animals/UdderSystem.cs similarity index 62% rename from Content.Server/Animals/Systems/UdderSystem.cs rename to Content.Shared/Animals/UdderSystem.cs index 452ba54d6e1..cb6e5b307fe 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Shared/Animals/UdderSystem.cs @@ -1,8 +1,7 @@ -using Content.Server.Animals.Components; -using Content.Server.Popups; -using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Components; +using Content.Shared.Chemistry.EntitySystems; using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; @@ -12,18 +11,17 @@ using Content.Shared.Verbs; using Robust.Shared.Timing; -namespace Content.Server.Animals.Systems; - +namespace Content.Shared.Animals; /// -/// Gives ability to produce milkable reagents, produces endless if the -/// owner has no HungerComponent +/// Gives the ability to produce milkable reagents; +/// produces endlessly if the owner does not have a HungerComponent. /// -internal sealed class UdderSystem : EntitySystem +public sealed class UdderSystem : EntitySystem { [Dependency] private readonly HungerSystem _hunger = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; @@ -31,26 +29,37 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent>(AddMilkVerb); SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnExamine); + } + + private void OnMapInit(EntityUid uid, UdderComponent component, MapInitEvent args) + { + component.NextGrowth = _timing.CurTime + component.GrowthDelay; } public override void Update(float frameTime) { base.Update(frameTime); - var query = EntityQueryEnumerator(); - var now = _timing.CurTime; while (query.MoveNext(out var uid, out var udder)) { - if (now < udder.NextGrowth) + if (_timing.CurTime < udder.NextGrowth) continue; - udder.NextGrowth = now + udder.GrowthDelay; + udder.NextGrowth += udder.GrowthDelay; if (_mobState.IsDead(uid)) continue; + if (!_solutionContainerSystem.ResolveSolution(uid, udder.SolutionName, ref udder.Solution, out var solution)) + continue; + + if (solution.AvailableVolume == 0) + continue; + // Actually there is food digestion so no problem with instant reagent generation "OnFeed" if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) { @@ -61,9 +70,6 @@ public override void Update(float frameTime) _hunger.ModifyHunger(uid, -udder.HungerUsage, hunger); } - if (!_solutionContainerSystem.ResolveSolution(uid, udder.SolutionName, ref udder.Solution)) - continue; - //TODO: toxins from bloodstream !? _solutionContainerSystem.TryAddReagent(udder.Solution.Value, udder.ReagentId, udder.QuantityPerUpdate, out _); } @@ -99,7 +105,7 @@ private void OnDoAfter(Entity entity, ref MilkingDoAfterEvent ar var quantity = solution.Volume; if (quantity == 0) { - _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), entity.Owner, args.Args.User); + _popupSystem.PopupClient(Loc.GetString("udder-system-dry"), entity.Owner, args.Args.User); return; } @@ -109,7 +115,7 @@ private void OnDoAfter(Entity entity, ref MilkingDoAfterEvent ar var split = _solutionContainerSystem.SplitSolution(entity.Comp.Solution.Value, quantity); _solutionContainerSystem.TryAddSolution(targetSoln.Value, split); - _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner, + _popupSystem.PopupClient(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), entity.Owner, args.Args.User, PopupType.Medium); } @@ -134,4 +140,50 @@ private void AddMilkVerb(Entity entity, ref GetVerbsEvent + /// Defines the text provided on examine. + /// Changes depending on the amount of hunger the target has. + /// + private void OnExamine(Entity entity, ref ExaminedEvent args) + { + + var entityIdentity = Identity.Entity(args.Examined, EntityManager); + + string message; + + // Check if the target has hunger, otherwise return not hungry. + if (!TryComp(entity, out var hunger)) + { + message = Loc.GetString("udder-system-examine-none", ("entity", entityIdentity)); + args.PushMarkup(message); + return; + } + + // Choose the correct examine string based on HungerThreshold. + switch (_hunger.GetHungerThreshold(hunger)) + { + case >= HungerThreshold.Overfed: + message = Loc.GetString("udder-system-examine-overfed", ("entity", entityIdentity)); + break; + + case HungerThreshold.Okay: + message = Loc.GetString("udder-system-examine-okay", ("entity", entityIdentity)); + break; + + case HungerThreshold.Peckish: + message = Loc.GetString("udder-system-examine-hungry", ("entity", entityIdentity)); + break; + + // There's a final hunger threshold called "dead" but animals don't actually die so we'll re-use this. + case <= HungerThreshold.Starving: + message = Loc.GetString("udder-system-examine-starved", ("entity", entityIdentity)); + break; + + default: + return; + } + + args.PushMarkup(message); + } } diff --git a/Content.Server/Animals/Components/WoolyComponent.cs b/Content.Shared/Animals/WoolyComponent.cs similarity index 66% rename from Content.Server/Animals/Components/WoolyComponent.cs rename to Content.Shared/Animals/WoolyComponent.cs index c09c6f5e089..1dfe523001e 100644 --- a/Content.Server/Animals/Components/WoolyComponent.cs +++ b/Content.Shared/Animals/WoolyComponent.cs @@ -1,23 +1,22 @@ -using Content.Server.Animals.Systems; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.Animals.Components; +namespace Content.Shared.Animals; /// -/// Lets an entity produce wool fibers. Uses hunger if present. +/// Gives the ability to produce wool fibers; +/// produces endlessly if the owner does not have a HungerComponent. /// - -[RegisterComponent, Access(typeof(WoolySystem))] +[RegisterComponent, AutoGenerateComponentState, AutoGenerateComponentPause, NetworkedComponent] public sealed partial class WoolyComponent : Component { /// /// The reagent to grow. /// - [DataField, ViewVariables(VVAccess.ReadOnly)] + [DataField, AutoNetworkedField] public ProtoId ReagentId = "Fiber"; /// @@ -29,30 +28,30 @@ public sealed partial class WoolyComponent : Component /// /// The solution to add reagent to. /// - [DataField] + [DataField, ViewVariables(VVAccess.ReadOnly)] public Entity? Solution; /// /// The amount of reagent to be generated on update. /// - [DataField, ViewVariables(VVAccess.ReadOnly)] + [DataField, AutoNetworkedField] public FixedPoint2 Quantity = 25; /// /// The amount of nutrient consumed on update. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public float HungerUsage = 10f; /// /// How long to wait before growing wool. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, AutoNetworkedField] public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1); /// /// When to next try growing wool. /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan NextGrowth = TimeSpan.FromSeconds(0); + [DataField, AutoPausedField, Access(typeof(WoolySystem))] + public TimeSpan NextGrowth = TimeSpan.Zero; } diff --git a/Content.Server/Animals/Systems/WoolySystem.cs b/Content.Shared/Animals/WoolySystem.cs similarity index 74% rename from Content.Server/Animals/Systems/WoolySystem.cs rename to Content.Shared/Animals/WoolySystem.cs index ef0ba086eaf..b7e0f52982c 100644 --- a/Content.Server/Animals/Systems/WoolySystem.cs +++ b/Content.Shared/Animals/WoolySystem.cs @@ -1,16 +1,15 @@ -using Content.Server.Animals.Components; -using Content.Server.Nutrition; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Mobs.Systems; +using Content.Shared.Nutrition; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.Timing; -namespace Content.Server.Animals.Systems; +namespace Content.Shared.Animals; /// -/// Gives ability to produce fiber reagents, produces endless if the -/// owner has no HungerComponent +/// Gives ability to produce fiber reagents; +/// produces endlessly if the owner has no HungerComponent. /// public sealed class WoolySystem : EntitySystem { @@ -24,6 +23,12 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnBeforeFullyEaten); + SubscribeLocalEvent(OnMapInit); + } + + private void OnMapInit(EntityUid uid, WoolyComponent component, MapInitEvent args) + { + component.NextGrowth = _timing.CurTime + component.GrowthDelay; } public override void Update(float frameTime) @@ -31,17 +36,22 @@ public override void Update(float frameTime) base.Update(frameTime); var query = EntityQueryEnumerator(); - var now = _timing.CurTime; while (query.MoveNext(out var uid, out var wooly)) { - if (now < wooly.NextGrowth) + if (_timing.CurTime < wooly.NextGrowth) continue; - wooly.NextGrowth = now + wooly.GrowthDelay; + wooly.NextGrowth += wooly.GrowthDelay; if (_mobState.IsDead(uid)) continue; + if (!_solutionContainer.ResolveSolution(uid, wooly.SolutionName, ref wooly.Solution, out var solution)) + continue; + + if (solution.AvailableVolume == 0) + continue; + // Actually there is food digestion so no problem with instant reagent generation "OnFeed" if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) { @@ -52,9 +62,6 @@ public override void Update(float frameTime) _hunger.ModifyHunger(uid, -wooly.HungerUsage, hunger); } - if (!_solutionContainer.ResolveSolution(uid, wooly.SolutionName, ref wooly.Solution)) - continue; - _solutionContainer.TryAddReagent(wooly.Solution.Value, wooly.ReagentId, wooly.Quantity, out _); } } diff --git a/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs index e88cedb18ef..a7e07ae2b50 100644 --- a/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs +++ b/Content.Shared/Anomaly/Components/InnerBodyAnomalyComponent.cs @@ -70,3 +70,11 @@ public sealed partial class InnerBodyAnomalyComponent : Component [DataField] public string LayerMap = "inner_anomaly_layer"; } + +/// +/// Event broadcast when an anomaly is being removed because the host is dying. +/// Raised directed at the host entity with the anomaly. +/// Cancel this if you want to prevent the host from losing their anomaly on death. +/// +[ByRefEvent] +public record struct BeforeRemoveAnomalyOnDeathEvent(bool Cancelled = false); diff --git a/Content.Shared/Atmos/Atmospherics.cs b/Content.Shared/Atmos/Atmospherics.cs index 76444897340..3c938cbba14 100644 --- a/Content.Shared/Atmos/Atmospherics.cs +++ b/Content.Shared/Atmos/Atmospherics.cs @@ -145,6 +145,22 @@ public static class Atmospherics /// public const float SpaceHeatCapacity = 7000f; + /// + /// Dictionary of chemical abbreviations for + /// + public static Dictionary GasAbbreviations = new Dictionary() + { + [Gas.Ammonia] = Loc.GetString("gas-ammonia-abbreviation"), + [Gas.CarbonDioxide] = Loc.GetString("gas-carbon-dioxide-abbreviation"), + [Gas.Frezon] = Loc.GetString("gas-frezon-abbreviation"), + [Gas.Nitrogen] = Loc.GetString("gas-nitrogen-abbreviation"), + [Gas.NitrousOxide] = Loc.GetString("gas-nitrous-oxide-abbreviation"), + [Gas.Oxygen] = Loc.GetString("gas-oxygen-abbreviation"), + [Gas.Plasma] = Loc.GetString("gas-plasma-abbreviation"), + [Gas.Tritium] = Loc.GetString("gas-tritium-abbreviation"), + [Gas.WaterVapor] = Loc.GetString("gas-water-vapor-abbreviation"), + }; + #region Excited Groups /// diff --git a/Content.Shared/Atmos/Components/GasPipeSensorComponent.cs b/Content.Shared/Atmos/Components/GasPipeSensorComponent.cs new file mode 100644 index 00000000000..3393948f4f6 --- /dev/null +++ b/Content.Shared/Atmos/Components/GasPipeSensorComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Atmos.Components; + +/// +/// Entities with component will be queried against for their +/// atmos monitoring data on atmos monitoring consoles +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class GasPipeSensorComponent : Component; diff --git a/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleComponent.cs b/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleComponent.cs new file mode 100644 index 00000000000..2ac0d2a9af1 --- /dev/null +++ b/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleComponent.cs @@ -0,0 +1,235 @@ +using Content.Shared.Atmos.Consoles; +using Content.Shared.Pinpointer; +using Content.Shared.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; + +namespace Content.Shared.Atmos.Components; + +/// +/// Entities capable of opening the atmos monitoring console UI +/// require this component to function correctly +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedAtmosMonitoringConsoleSystem))] +public sealed partial class AtmosMonitoringConsoleComponent : Component +{ + /* + * Don't need DataFields as this can be reconstructed + */ + + /// + /// A dictionary of the all the nav map chunks that contain anchored atmos pipes + /// + [ViewVariables] + public Dictionary AtmosPipeChunks = new(); + + /// + /// A list of all the atmos devices that will be used to populate the nav map + /// + [ViewVariables] + public Dictionary AtmosDevices = new(); + + /// + /// Color of the floor tiles on the nav map screen + /// + [DataField, ViewVariables] + public Color NavMapTileColor; + + /// + /// Color of the wall lines on the nav map screen + /// + [DataField, ViewVariables] + public Color NavMapWallColor; + + /// + /// The next time this component is dirtied, it will force the full state + /// to be sent to the client, instead of just the delta state + /// + [ViewVariables] + public bool ForceFullUpdate = false; +} + +[Serializable, NetSerializable] +public struct AtmosPipeChunk(Vector2i origin) +{ + /// + /// Chunk position + /// + [ViewVariables] + public readonly Vector2i Origin = origin; + + /// + /// Bitmask look up for atmos pipes, 1 for occupied and 0 for empty. + /// Indexed by the color hexcode of the pipe + /// + [ViewVariables] + public Dictionary<(int, string), ulong> AtmosPipeData = new(); + + /// + /// The last game tick that the chunk was updated + /// + [NonSerialized] + public GameTick LastUpdate; +} + +[Serializable, NetSerializable] +public struct AtmosDeviceNavMapData +{ + /// + /// The entity in question + /// + public NetEntity NetEntity; + + /// + /// Location of the entity + /// + public NetCoordinates NetCoordinates; + + /// + /// The associated pipe network ID + /// + public int NetId = -1; + + /// + /// Prototype ID for the nav map blip + /// + public ProtoId NavMapBlip; + + /// + /// Direction of the entity + /// + public Direction Direction; + + /// + /// Color of the attached pipe + /// + public Color PipeColor; + + /// + /// Populate the atmos monitoring console nav map with a single entity + /// + public AtmosDeviceNavMapData(NetEntity netEntity, NetCoordinates netCoordinates, int netId, ProtoId navMapBlip, Direction direction, Color pipeColor) + { + NetEntity = netEntity; + NetCoordinates = netCoordinates; + NetId = netId; + NavMapBlip = navMapBlip; + Direction = direction; + PipeColor = pipeColor; + } +} + +[Serializable, NetSerializable] +public sealed class AtmosMonitoringConsoleBoundInterfaceState : BoundUserInterfaceState +{ + /// + /// A list of all entries to populate the UI with + /// + public AtmosMonitoringConsoleEntry[] AtmosNetworks; + + /// + /// Sends data from the server to the client to populate the atmos monitoring console UI + /// + public AtmosMonitoringConsoleBoundInterfaceState(AtmosMonitoringConsoleEntry[] atmosNetworks) + { + AtmosNetworks = atmosNetworks; + } +} + +[Serializable, NetSerializable] +public struct AtmosMonitoringConsoleEntry +{ + /// + /// The entity in question + /// + public NetEntity NetEntity; + + /// + /// Location of the entity + /// + public NetCoordinates Coordinates; + + /// + /// The associated pipe network ID + /// + public int NetId = -1; + + /// + /// Localised device name + /// + public string EntityName; + + /// + /// Device network address + /// + public string Address; + + /// + /// Temperature (K) + /// + public float TemperatureData; + + /// + /// Pressure (kPA) + /// + public float PressureData; + + /// + /// Total number of mols of gas + /// + public float TotalMolData; + + /// + /// Mol and percentage for all detected gases + /// + public Dictionary GasData = new(); + + /// + /// The color to be associated with the pipe network + /// + public Color Color; + + /// + /// Indicates whether the entity is powered + /// + public bool IsPowered = true; + + /// + /// Used to populate the atmos monitoring console UI with data from a single air alarm + /// + public AtmosMonitoringConsoleEntry + (NetEntity entity, + NetCoordinates coordinates, + int netId, + string entityName, + string address) + { + NetEntity = entity; + Coordinates = coordinates; + NetId = netId; + EntityName = entityName; + Address = address; + } +} + +public enum AtmosPipeChunkDataFacing : byte +{ + // Values represent bit shift offsets when retrieving data in the tile array. + North = 0, + South = SharedNavMapSystem.ArraySize, + East = SharedNavMapSystem.ArraySize * 2, + West = SharedNavMapSystem.ArraySize * 3, +} + +/// +/// UI key associated with the atmos monitoring console +/// +[Serializable, NetSerializable] +public enum AtmosMonitoringConsoleUiKey +{ + Key +} diff --git a/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleDeviceComponent.cs b/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleDeviceComponent.cs new file mode 100644 index 00000000000..50c3abcfcaa --- /dev/null +++ b/Content.Shared/Atmos/Consoles/Components/AtmosMonitoringConsoleDeviceComponent.cs @@ -0,0 +1,21 @@ +using Content.Shared.Prototypes; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Atmos.Components; + +/// +/// Entities with this component appear on the +/// nav maps of atmos monitoring consoles +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class AtmosMonitoringConsoleDeviceComponent : Component +{ + /// + /// Prototype ID for the blip used to represent this + /// entity on the atmos monitoring console nav map. + /// If null, no blip is drawn (i.e., null for pipes) + /// + [DataField, ViewVariables] + public ProtoId? NavMapBlip = null; +} diff --git a/Content.Shared/Atmos/Consoles/SharedAtmosMonitoringConsoleSystem.cs b/Content.Shared/Atmos/Consoles/SharedAtmosMonitoringConsoleSystem.cs new file mode 100644 index 00000000000..e6dd455be7d --- /dev/null +++ b/Content.Shared/Atmos/Consoles/SharedAtmosMonitoringConsoleSystem.cs @@ -0,0 +1,115 @@ +using Content.Shared.Atmos.Components; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Atmos.Consoles; + +public abstract class SharedAtmosMonitoringConsoleSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGetState); + } + + private void OnGetState(EntityUid uid, AtmosMonitoringConsoleComponent component, ref ComponentGetState args) + { + Dictionary> chunks; + + // Should this be a full component state or a delta-state? + if (args.FromTick <= component.CreationTick || component.ForceFullUpdate) + { + component.ForceFullUpdate = false; + + // Full state + chunks = new(component.AtmosPipeChunks.Count); + + foreach (var (origin, chunk) in component.AtmosPipeChunks) + { + chunks.Add(origin, chunk.AtmosPipeData); + } + + args.State = new AtmosMonitoringConsoleState(chunks, component.AtmosDevices); + + return; + } + + chunks = new(); + + foreach (var (origin, chunk) in component.AtmosPipeChunks) + { + if (chunk.LastUpdate < args.FromTick) + continue; + + chunks.Add(origin, chunk.AtmosPipeData); + } + + args.State = new AtmosMonitoringConsoleDeltaState(chunks, component.AtmosDevices, new(component.AtmosPipeChunks.Keys)); + } + + #region: System messages + + [Serializable, NetSerializable] + protected sealed class AtmosMonitoringConsoleState( + Dictionary> chunks, + Dictionary atmosDevices) + : ComponentState + { + public Dictionary> Chunks = chunks; + public Dictionary AtmosDevices = atmosDevices; + } + + [Serializable, NetSerializable] + protected sealed class AtmosMonitoringConsoleDeltaState( + Dictionary> modifiedChunks, + Dictionary atmosDevices, + HashSet allChunks) + : ComponentState, IComponentDeltaState + { + public Dictionary> ModifiedChunks = modifiedChunks; + public Dictionary AtmosDevices = atmosDevices; + public HashSet AllChunks = allChunks; + + public void ApplyToFullState(AtmosMonitoringConsoleState state) + { + foreach (var key in state.Chunks.Keys) + { + if (!AllChunks!.Contains(key)) + state.Chunks.Remove(key); + } + + foreach (var (index, data) in ModifiedChunks) + { + state.Chunks[index] = new Dictionary<(int, string), ulong>(data); + } + + state.AtmosDevices.Clear(); + foreach (var (nuid, atmosDevice) in AtmosDevices) + { + state.AtmosDevices.Add(nuid, atmosDevice); + } + } + + public AtmosMonitoringConsoleState CreateNewFullState(AtmosMonitoringConsoleState state) + { + var chunks = new Dictionary>(state.Chunks.Count); + + foreach (var (index, data) in state.Chunks) + { + if (!AllChunks!.Contains(index)) + continue; + + if (ModifiedChunks.ContainsKey(index)) + chunks[index] = new Dictionary<(int, string), ulong>(ModifiedChunks[index]); + + else + chunks[index] = new Dictionary<(int, string), ulong>(state.Chunks[index]); + } + + return new AtmosMonitoringConsoleState(chunks, new(AtmosDevices)); + } + } + + #endregion +} diff --git a/Content.Shared/Atmos/Piping/Unary/Components/SharedGasPortableComponent.cs b/Content.Shared/Atmos/Piping/Unary/Components/SharedGasPortableComponent.cs deleted file mode 100644 index e0963a610c9..00000000000 --- a/Content.Shared/Atmos/Piping/Unary/Components/SharedGasPortableComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Atmos.Piping.Unary.Components -{ - /// - /// Used in to determine which visuals to update. - /// - [Serializable, NetSerializable] - public enum GasPortableVisuals - { - ConnectedState, - } -} diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs index 7eb4871936c..778f202032d 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Interaction.cs @@ -15,11 +15,11 @@ public abstract partial class SharedBuckleSystem private void InitializeInteraction() { SubscribeLocalEvent>(AddStrapVerbs); - SubscribeLocalEvent(OnStrapInteractHand, after: [typeof(InteractionPopupSystem)]); + SubscribeLocalEvent(OnStrapInteractHand, before: [typeof(InteractionPopupSystem)]); SubscribeLocalEvent(OnStrapDragDropTarget); SubscribeLocalEvent(OnCanDropTarget); - SubscribeLocalEvent(OnBuckleInteractHand, after: [typeof(InteractionPopupSystem)]); + SubscribeLocalEvent(OnBuckleInteractHand, before: [typeof(InteractionPopupSystem)]); SubscribeLocalEvent>(AddUnbuckleVerb); } diff --git a/Content.Shared/CCVar/CCVars.Admin.cs b/Content.Shared/CCVar/CCVars.Admin.cs index 28bebfbe8a6..c422a5a02a1 100644 --- a/Content.Shared/CCVar/CCVars.Admin.cs +++ b/Content.Shared/CCVar/CCVars.Admin.cs @@ -149,6 +149,15 @@ public sealed partial class CCVars public static readonly CVarDef AdminBypassMaxPlayers = CVarDef.Create("admin.bypass_max_players", true, CVar.SERVERONLY); + /// + /// Determines whether admins count towards the total playercount when determining whether the server is over + /// Ideally this should be used in conjuction with . + /// This also applies to playercount limits in whitelist conditions + /// If false, then admins will not be considered when checking whether the playercount is already above the soft player cap + /// + public static readonly CVarDef AdminsCountForMaxPlayers = + CVarDef.Create("admin.admins_count_for_max_players", false, CVar.SERVERONLY); + /// /// Determine if custom rank names are used. /// If it is false, it'd use the actual rank name regardless of the individual's title. diff --git a/Content.Shared/CCVar/CCVars.Game.cs b/Content.Shared/CCVar/CCVars.Game.cs index 19092055e39..d74b46c7ccc 100644 --- a/Content.Shared/CCVar/CCVars.Game.cs +++ b/Content.Shared/CCVar/CCVars.Game.cs @@ -154,6 +154,7 @@ public static readonly CVarDef /// /// Whether or not the panic bunker will enable when no admins are online. + /// This counts everyone with the 'Admin' AdminFlag. /// public static readonly CVarDef PanicBunkerEnableWithoutAdmins = CVarDef.Create("game.panic_bunker.enable_without_admins", false, CVar.SERVERONLY); diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index efb5dfd0248..9c3d6fc9fb3 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -31,6 +31,7 @@ public sealed partial class AnchorableSystem : EntitySystem [Dependency] private readonly SharedToolSystem _tool = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; private EntityQuery _physicsQuery; @@ -47,6 +48,18 @@ public override void Initialize() SubscribeLocalEvent(OnAnchorComplete); SubscribeLocalEvent(OnUnanchorComplete); SubscribeLocalEvent(OnAnchoredExamine); + SubscribeLocalEvent(OnAnchorStartup); + SubscribeLocalEvent(OnAnchorStateChange); + } + + private void OnAnchorStartup(EntityUid uid, AnchorableComponent comp, ComponentStartup args) + { + _appearance.SetData(uid, AnchorVisuals.Anchored, Transform(uid).Anchored); + } + + private void OnAnchorStateChange(EntityUid uid, AnchorableComponent comp, AnchorStateChangedEvent args) + { + _appearance.SetData(uid, AnchorVisuals.Anchored, args.Anchored); } /// @@ -343,3 +356,9 @@ private sealed partial class TryAnchorCompletedEvent : SimpleDoAfterEvent { } } + +[Serializable, NetSerializable] +public enum AnchorVisuals : byte +{ + Anchored +} diff --git a/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs index 12838eb04d9..66b1de65e8e 100644 --- a/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs +++ b/Content.Shared/Damage/Systems/RequireProjectileTargetSystem.cs @@ -34,6 +34,11 @@ private void PreventCollide(Entity ent, ref Pr if (!shooter.HasValue) return; + // ProjectileGrenades delete the entity that's shooting the projectile, + // so it's impossible to check if the entity is in a container + if (TerminatingOrDeleted(shooter.Value)) + return; + if (!_container.IsEntityOrParentInContainer(shooter.Value)) args.Cancelled = true; } diff --git a/Content.Shared/DeltaV/Storage/EntitySystems/SharedMouthStorageSystem.cs b/Content.Shared/DeltaV/Storage/EntitySystems/SharedMouthStorageSystem.cs index df3ed835e26..a16e0e53e6a 100644 --- a/Content.Shared/DeltaV/Storage/EntitySystems/SharedMouthStorageSystem.cs +++ b/Content.Shared/DeltaV/Storage/EntitySystems/SharedMouthStorageSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.DeltaV.Storage.Components; using Content.Shared.Examine; using Content.Shared.IdentityManagement; +using Content.Shared.Nutrition; using Content.Shared.Standing; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; @@ -27,6 +28,7 @@ public override void Initialize() SubscribeLocalEvent(DropAllContents); SubscribeLocalEvent(OnDamageModified); SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnIngestAttempt); } protected bool IsMouthBlocked(MouthStorageComponent component) @@ -81,4 +83,18 @@ private void OnExamined(EntityUid uid, MouthStorageComponent component, Examined args.PushMarkup(Loc.GetString("mouth-storage-examine-condition-occupied", ("entity", subject))); } } + + // Attempting to eat or drink anything with items in your mouth won't work + private void OnIngestAttempt(EntityUid uid, MouthStorageComponent component, IngestionAttemptEvent args) + { + if (!IsMouthBlocked(component)) + return; + + if (!TryComp(component.MouthId, out var storage)) + return; + + var firstItem = storage.Container.ContainedEntities[0]; + args.Blocker = firstItem; + args.Cancel(); + } } diff --git a/Content.Shared/Doors/AirlockWireStatus.cs b/Content.Shared/Doors/AirlockWireStatus.cs index d3fa15ed1b6..3a8570ebd1b 100644 --- a/Content.Shared/Doors/AirlockWireStatus.cs +++ b/Content.Shared/Doors/AirlockWireStatus.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Serialization; +using Robust.Shared.Serialization; namespace Content.Shared.Doors { @@ -9,6 +9,7 @@ public enum AirlockWireStatus BoltIndicator, BoltLightIndicator, AiControlIndicator, + AiVisionIndicator, TimingIndicator, SafetyIndicator, } diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index 1e97e8f3405..4372b7d5169 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -409,8 +409,10 @@ public ExamineGroupDisposable PushGroup(string groupName, int priority=0) private void PopGroup() { DebugTools.Assert(_currentGroupPart != null); - if (_currentGroupPart != null) + if (_currentGroupPart != null && !_currentGroupPart.Message.IsEmpty) + { Parts.Add(_currentGroupPart); + } _currentGroupPart = null; } diff --git a/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs new file mode 100644 index 00000000000..be27c49ffa4 --- /dev/null +++ b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs @@ -0,0 +1,109 @@ +using Content.Shared.Explosion.EntitySystems; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Explosion.Components; + +/// +/// Use this component if the grenade splits into entities that make use of Timers +/// or if you just want it to throw entities out in the world +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedScatteringGrenadeSystem))] +public sealed partial class ScatteringGrenadeComponent : Component +{ + public Container Container = default!; + + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// What we fill our prototype with if we want to pre-spawn with entities. + /// + [DataField] + public EntProtoId? FillPrototype; + + /// + /// If we have a pre-fill how many more can we spawn. + /// + [AutoNetworkedField] + public int UnspawnedCount; + + /// + /// Max amount of entities inside the container + /// + [DataField] + public int Capacity = 3; + + /// + /// Decides if contained entities trigger after getting launched + /// + [DataField] + public bool TriggerContents = true; + + #region Trigger time parameters for scattered entities + /// + /// Minimum delay in seconds before any entities start to be triggered. + /// + [DataField] + public float DelayBeforeTriggerContents = 1.0f; + + /// + /// Maximum delay in seconds to add between individual entity triggers + /// + [DataField] + public float IntervalBetweenTriggersMax; + + /// + /// Minimum delay in seconds to add between individual entity triggers + /// + [DataField] + public float IntervalBetweenTriggersMin; + #endregion + + #region Throwing parameters for the scattered entities + /// + /// Should the angle the entities get thrown at be random + /// instead of uniformly distributed + /// + [DataField] + public bool RandomAngle; + + /// + /// The speed at which the entities get thrown + /// + [DataField] + public float Velocity = 5; + + /// + /// Static distance grenades will be thrown to if RandomDistance is false. + /// + [DataField] + public float Distance = 1f; + + /// + /// Should the distance the entities get thrown be random + /// + [DataField] + public bool RandomDistance; + + /// + /// Max distance grenades can randomly be thrown to. + /// + [DataField] + public float RandomThrowDistanceMax = 2.5f; + + /// + /// Minimal distance grenades can randomly be thrown to. + /// + [DataField] + public float RandomThrowDistanceMin; + #endregion + + /// + /// Whether the main grenade has been triggered or not + /// We need to store this because we are only allowed to spawn and trigger timed entities on the next available frame update + /// + public bool IsTriggered = false; +} diff --git a/Content.Shared/Explosion/EntitySystems/SharedScatteringGrenadeSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedScatteringGrenadeSystem.cs new file mode 100644 index 00000000000..b704fbf86f3 --- /dev/null +++ b/Content.Shared/Explosion/EntitySystems/SharedScatteringGrenadeSystem.cs @@ -0,0 +1,70 @@ +using Content.Shared.Explosion.Components; +using Content.Shared.Interaction; +using Content.Shared.Whitelist; +using Robust.Shared.Containers; + +namespace Content.Shared.Explosion.EntitySystems; + +public abstract class SharedScatteringGrenadeSystem : EntitySystem +{ + [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnScatteringInit); + SubscribeLocalEvent(OnScatteringStartup); + SubscribeLocalEvent(OnScatteringInteractUsing); + } + + private void OnScatteringInit(Entity entity, ref ComponentInit args) + { + entity.Comp.Container = _container.EnsureContainer(entity.Owner, "cluster-payload"); + } + + /// + /// Setting the unspawned count based on capacity, so we know how many new entities to spawn + /// Update appearance based on initial fill amount + /// + private void OnScatteringStartup(Entity entity, ref ComponentStartup args) + { + if (entity.Comp.FillPrototype == null) + return; + + entity.Comp.UnspawnedCount = Math.Max(0, entity.Comp.Capacity - entity.Comp.Container.ContainedEntities.Count); + UpdateAppearance(entity); + Dirty(entity, entity.Comp); + + } + + /// + /// There are some scattergrenades you can fill up with more grenades (like clusterbangs) + /// This covers how you insert more into it + /// + private void OnScatteringInteractUsing(Entity entity, ref InteractUsingEvent args) + { + if (entity.Comp.Whitelist == null) + return; + + if (args.Handled || !_whitelistSystem.IsValid(entity.Comp.Whitelist, args.Used)) + return; + + _container.Insert(args.Used, entity.Comp.Container); + UpdateAppearance(entity); + args.Handled = true; + } + + /// + /// Update appearance based off of total count of contents + /// + private void UpdateAppearance(Entity entity) + { + if (!TryComp(entity, out var appearanceComponent)) + return; + + _appearance.SetData(entity, ClusterGrenadeVisuals.GrenadesCounter, entity.Comp.UnspawnedCount + entity.Comp.Container.ContainedEntities.Count, appearanceComponent); + } +} diff --git a/Content.Shared/Holopad/HolographicAvatarComponent.cs b/Content.Shared/Holopad/HolographicAvatarComponent.cs new file mode 100644 index 00000000000..be7f5bcb91f --- /dev/null +++ b/Content.Shared/Holopad/HolographicAvatarComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Holopad; + +[RegisterComponent, NetworkedComponent] +public sealed partial class HolographicAvatarComponent : Component +{ + /// + /// The prototype sprite layer data for the hologram + /// + [DataField] + public PrototypeLayerData[] LayerData; +} diff --git a/Content.Shared/Holopad/HolopadComponent.cs b/Content.Shared/Holopad/HolopadComponent.cs new file mode 100644 index 00000000000..98f05b03f93 --- /dev/null +++ b/Content.Shared/Holopad/HolopadComponent.cs @@ -0,0 +1,133 @@ +using Content.Shared.Telephone; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Holopad; + +/// +/// Holds data pertaining to holopads +/// +/// +/// Holopads also require a to function +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedHolopadSystem))] +public sealed partial class HolopadComponent : Component +{ + /// + /// The entity being projected by the holopad + /// + [ViewVariables] + public Entity? Hologram; + + /// + /// The entity using the holopad + /// + [ViewVariables] + public Entity? User; + + /// + /// Proto ID for the user's hologram + /// + [DataField] + public EntProtoId? HologramProtoId; + + /// + /// The entity that has locked out the controls of this device + /// + [ViewVariables, AutoNetworkedField] + public EntityUid? ControlLockoutOwner = null; + + /// + /// The game tick the control lockout was initiated + /// + [ViewVariables, AutoNetworkedField] + public TimeSpan ControlLockoutStartTime; + + /// + /// The duration that the control lockout will last in seconds + /// + [DataField] + public float ControlLockoutDuration { get; private set; } = 90f; + + /// + /// The duration before the controls can be lockout again in seconds + /// + [DataField] + public float ControlLockoutCoolDown { get; private set; } = 180f; +} + +#region: Event messages + +/// +/// Data from by the server to the client for the holopad UI +/// +[Serializable, NetSerializable] +public sealed class HolopadBoundInterfaceState : BoundUserInterfaceState +{ + public readonly Dictionary Holopads; + + public HolopadBoundInterfaceState(Dictionary holopads) + { + Holopads = holopads; + } +} + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadStartNewCallMessage : BoundUserInterfaceMessage +{ + public readonly NetEntity Receiver; + + public HolopadStartNewCallMessage(NetEntity receiver) + { + Receiver = receiver; + } +} + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadAnswerCallMessage : BoundUserInterfaceMessage { } + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadEndCallMessage : BoundUserInterfaceMessage { } + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadStartBroadcastMessage : BoundUserInterfaceMessage { } + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadActivateProjectorMessage : BoundUserInterfaceMessage { } + +/// +/// Triggers the server to send updated power monitoring console data to the client for the single player session +/// +[Serializable, NetSerializable] +public sealed class HolopadStationAiRequestMessage : BoundUserInterfaceMessage { } + +#endregion + +/// +/// Key to the Holopad UI +/// +[Serializable, NetSerializable] +public enum HolopadUiKey : byte +{ + InteractionWindow, + InteractionWindowForAi, + AiActionWindow, + AiRequestWindow +} diff --git a/Content.Shared/Holopad/HolopadHologramComponent.cs b/Content.Shared/Holopad/HolopadHologramComponent.cs new file mode 100644 index 00000000000..a75ae276234 --- /dev/null +++ b/Content.Shared/Holopad/HolopadHologramComponent.cs @@ -0,0 +1,71 @@ +using Robust.Shared.GameStates; +using System.Numerics; + +namespace Content.Shared.Holopad; + +/// +/// Holds data pertaining to holopad holograms +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HolopadHologramComponent : Component +{ + /// + /// Default RSI path + /// + [DataField] + public string RsiPath = string.Empty; + + /// + /// Default RSI state + /// + [DataField] + public string RsiState = string.Empty; + + /// + /// Name of the shader to use + /// + [DataField] + public string ShaderName = string.Empty; + + /// + /// The primary color + /// + [DataField] + public Color Color1 = Color.White; + + /// + /// The secondary color + /// + [DataField] + public Color Color2 = Color.White; + + /// + /// The shared color alpha + /// + [DataField] + public float Alpha = 1f; + + /// + /// The color brightness + /// + [DataField] + public float Intensity = 1f; + + /// + /// The scroll rate of the hologram shader + /// + [DataField] + public float ScrollRate = 1f; + + /// + /// The sprite offset + /// + [DataField] + public Vector2 Offset = new Vector2(); + + /// + /// A user that are linked to this hologram + /// + [ViewVariables] + public Entity? LinkedHolopad; +} diff --git a/Content.Shared/Holopad/HolopadUserComponent.cs b/Content.Shared/Holopad/HolopadUserComponent.cs new file mode 100644 index 00000000000..9ff20c2e7b3 --- /dev/null +++ b/Content.Shared/Holopad/HolopadUserComponent.cs @@ -0,0 +1,104 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Holopad; + +/// +/// Holds data pertaining to entities that are using holopads +/// +/// +/// This component is added and removed automatically from entities +/// +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedHolopadSystem))] +public sealed partial class HolopadUserComponent : Component +{ + /// + /// A list of holopads that the user is interacting with + /// + [ViewVariables] + public HashSet> LinkedHolopads = new(); +} + +/// +/// A networked event raised when the visual state of a hologram is being updated +/// +[Serializable, NetSerializable] +public sealed class HolopadHologramVisualsUpdateEvent : EntityEventArgs +{ + /// + /// The hologram being updated + /// + public readonly NetEntity Hologram; + + /// + /// The target the hologram is copying + /// + public readonly NetEntity? Target; + + public HolopadHologramVisualsUpdateEvent(NetEntity hologram, NetEntity? target = null) + { + Hologram = hologram; + Target = target; + } +} + +/// +/// A networked event raised when the visual state of a hologram is being updated +/// +[Serializable, NetSerializable] +public sealed class HolopadUserTypingChangedEvent : EntityEventArgs +{ + /// + /// The hologram being updated + /// + public readonly NetEntity User; + + /// + /// The typing indicator state + /// + public readonly bool IsTyping; + + public HolopadUserTypingChangedEvent(NetEntity user, bool isTyping) + { + User = user; + IsTyping = isTyping; + } +} + +/// +/// A networked event raised by the server to request the current visual state of a target player entity +/// +[Serializable, NetSerializable] +public sealed class PlayerSpriteStateRequest : EntityEventArgs +{ + /// + /// The player entity in question + /// + public readonly NetEntity TargetPlayer; + + public PlayerSpriteStateRequest(NetEntity targetPlayer) + { + TargetPlayer = targetPlayer; + } +} + +/// +/// The client's response to a +/// +[Serializable, NetSerializable] +public sealed class PlayerSpriteStateMessage : EntityEventArgs +{ + public readonly NetEntity SpriteEntity; + + /// + /// Data needed to reconstruct the player's sprite component layers + /// + public readonly PrototypeLayerData[]? SpriteLayerData; + + public PlayerSpriteStateMessage(NetEntity spriteEntity, PrototypeLayerData[]? spriteLayerData = null) + { + SpriteEntity = spriteEntity; + SpriteLayerData = spriteLayerData; + } +} diff --git a/Content.Shared/Holopad/SharedHolopadSystem.cs b/Content.Shared/Holopad/SharedHolopadSystem.cs new file mode 100644 index 00000000000..ea122828bf1 --- /dev/null +++ b/Content.Shared/Holopad/SharedHolopadSystem.cs @@ -0,0 +1,43 @@ +using Robust.Shared.Timing; + +namespace Content.Shared.Holopad; + +public abstract class SharedHolopadSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public bool IsHolopadControlLocked(Entity entity, EntityUid? user = null) + { + if (entity.Comp.ControlLockoutStartTime == TimeSpan.Zero) + return false; + + if (entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutDuration) < _timing.CurTime) + return false; + + if (entity.Comp.ControlLockoutOwner == null || entity.Comp.ControlLockoutOwner == user) + return false; + + return true; + } + + public TimeSpan GetHolopadControlLockedPeriod(Entity entity) + { + return entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutDuration) - _timing.CurTime; + } + + public bool IsHolopadBroadcastOnCoolDown(Entity entity) + { + if (entity.Comp.ControlLockoutStartTime == TimeSpan.Zero) + return false; + + if (entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutCoolDown) < _timing.CurTime) + return false; + + return true; + } + + public TimeSpan GetHolopadBroadcastCoolDown(Entity entity) + { + return entity.Comp.ControlLockoutStartTime + TimeSpan.FromSeconds(entity.Comp.ControlLockoutCoolDown) - _timing.CurTime; + } +} diff --git a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs index f09ae6824ea..2de7c5591fd 100644 --- a/Content.Shared/Light/EntitySystems/LightCollideSystem.cs +++ b/Content.Shared/Light/EntitySystems/LightCollideSystem.cs @@ -9,9 +9,14 @@ public sealed class LightCollideSystem : EntitySystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SlimPoweredLightSystem _lights = default!; + private EntityQuery _lightQuery; + public override void Initialize() { base.Initialize(); + + _lightQuery = GetEntityQuery(); + SubscribeLocalEvent(OnPreventCollide); SubscribeLocalEvent(OnStart); SubscribeLocalEvent(OnEnd); @@ -35,7 +40,7 @@ private void OnCollideShutdown(Entity ent, ref var other = contact.OtherEnt(ent.Owner); - if (HasComp(other)) + if (_lightQuery.HasComp(other)) { _physics.RegenerateContacts(other); } @@ -46,7 +51,7 @@ private void OnCollideShutdown(Entity ent, ref // At the moment there's no easy way to do collision whitelists based on components. private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) { - if (!HasComp(args.OtherEntity)) + if (!_lightQuery.HasComp(args.OtherEntity)) { args.Cancelled = true; } @@ -57,7 +62,7 @@ private void OnEnd(Entity ent, ref EndCollideEv if (args.OurFixtureId != ent.Comp.FixtureId) return; - if (!HasComp(args.OtherEntity)) + if (!_lightQuery.HasComp(args.OtherEntity)) return; // TODO: Engine bug IsTouching box2d yay. @@ -74,7 +79,7 @@ private void OnStart(Entity ent, ref StartColli if (args.OurFixtureId != ent.Comp.FixtureId) return; - if (!HasComp(args.OtherEntity)) + if (!_lightQuery.HasComp(args.OtherEntity)) return; _lights.SetEnabled(args.OtherEntity, true); diff --git a/Content.Shared/Magic/Events/MindSwapSpellEvent.cs b/Content.Shared/Magic/Events/MindSwapSpellEvent.cs new file mode 100644 index 00000000000..89319090c1c --- /dev/null +++ b/Content.Shared/Magic/Events/MindSwapSpellEvent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Actions; + +namespace Content.Shared.Magic.Events; + +public sealed partial class MindSwapSpellEvent : EntityTargetActionEvent, ISpeakSpell +{ + [DataField] + public TimeSpan PerformerStunDuration = TimeSpan.FromSeconds(10); + + [DataField] + public TimeSpan TargetStunDuration = TimeSpan.FromSeconds(10); + + [DataField] + public string? Speech { get; private set; } +} diff --git a/Content.Shared/Magic/SharedMagicSystem.cs b/Content.Shared/Magic/SharedMagicSystem.cs index 21e137346ef..acedaf05f65 100644 --- a/Content.Shared/Magic/SharedMagicSystem.cs +++ b/Content.Shared/Magic/SharedMagicSystem.cs @@ -22,6 +22,7 @@ using Content.Shared.Popups; using Content.Shared.Speech.Muting; using Content.Shared.Storage; +using Content.Shared.Stunnable; using Content.Shared.Tag; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Systems; @@ -62,6 +63,7 @@ public abstract class SharedMagicSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly SharedStunSystem _stun = default!; public override void Initialize() { @@ -77,6 +79,7 @@ public override void Initialize() SubscribeLocalEvent(OnKnockSpell); SubscribeLocalEvent(OnChargeSpell); SubscribeLocalEvent(OnRandomGlobalSpawnSpell); + SubscribeLocalEvent(OnMindSwapSpell); // Spell wishlist // A wishlish of spells that I'd like to implement or planning on implementing in a future PR @@ -542,6 +545,37 @@ private void OnRandomGlobalSpawnSpell(RandomGlobalSpawnSpellEvent ev) _audio.PlayGlobal(ev.Sound, ev.Performer); } + #endregion + #region Mindswap Spells + + private void OnMindSwapSpell(MindSwapSpellEvent ev) + { + if (ev.Handled || !PassesSpellPrerequisites(ev.Action, ev.Performer)) + return; + + ev.Handled = true; + Speak(ev); + + // Need performer mind, but target mind is unnecessary, such as taking over a NPC + // Need to get target mind before putting performer mind into their body if they have one + // Thus, assign bool before first transfer, then check afterwards + + if (!_mind.TryGetMind(ev.Performer, out var perMind, out var perMindComp)) + return; + + var tarHasMind = _mind.TryGetMind(ev.Target, out var tarMind, out var tarMindComp); + + _mind.TransferTo(perMind, ev.Target); + + if (tarHasMind) + { + _mind.TransferTo(tarMind, ev.Performer); + } + + _stun.TryParalyze(ev.Target, ev.TargetStunDuration, true); + _stun.TryParalyze(ev.Performer, ev.PerformerStunDuration, true); + } + #endregion // End Spells #endregion diff --git a/Content.Shared/Medical/DefibrillatorComponent.cs b/Content.Shared/Medical/DefibrillatorComponent.cs index e4cd8077d26..f54348d771f 100644 --- a/Content.Shared/Medical/DefibrillatorComponent.cs +++ b/Content.Shared/Medical/DefibrillatorComponent.cs @@ -12,22 +12,9 @@ namespace Content.Shared.Medical; /// person back into the world of the living. /// Uses ItemToggleComponent /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent] public sealed partial class DefibrillatorComponent : Component { - /// - /// The time at which the zap cooldown will be completed - /// - [DataField("nextZapTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - [AutoPausedField] - public TimeSpan? NextZapTime; - - /// - /// The minimum time between zaps - /// - [DataField("zapDelay"), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan ZapDelay = TimeSpan.FromSeconds(5); - /// /// How much damage is healed from getting zapped. /// @@ -46,6 +33,18 @@ public sealed partial class DefibrillatorComponent : Component [DataField("writheDuration"), ViewVariables(VVAccess.ReadWrite)] public TimeSpan WritheDuration = TimeSpan.FromSeconds(3); + /// + /// ID of the cooldown use delay. + /// + [DataField] + public string DelayId = "defib-delay"; + + /// + /// Cooldown after using the defibrillator. + /// + [DataField] + public TimeSpan ZapDelay = TimeSpan.FromSeconds(5); + /// /// How long the doafter for zapping someone takes /// @@ -80,12 +79,6 @@ public sealed partial class DefibrillatorComponent : Component public SoundSpecifier? ReadySound = new SoundPathSpecifier("/Audio/Items/Defib/defib_ready.ogg"); } -[Serializable, NetSerializable] -public enum DefibrillatorVisuals : byte -{ - Ready -} - [Serializable, NetSerializable] public sealed partial class DefibrillatorZapDoAfterEvent : SimpleDoAfterEvent { diff --git a/Content.Shared/Movement/Components/MovementSoundComponent.cs b/Content.Shared/Movement/Components/MovementSoundComponent.cs new file mode 100644 index 00000000000..92a6974cc67 --- /dev/null +++ b/Content.Shared/Movement/Components/MovementSoundComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared.Movement.Components; + +/// +/// Plays a sound whenever InputMover is running. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class MovementSoundComponent : Component +{ + /// + /// Sound to play when InputMover has inputs. + /// + [DataField(required: true), AutoNetworkedField] + public SoundSpecifier? Sound; + + [DataField, AutoNetworkedField] + public EntityUid? SoundEntity; +} diff --git a/Content.Shared/Movement/Components/SpriteMovementComponent.cs b/Content.Shared/Movement/Components/SpriteMovementComponent.cs index 8dd058f1544..b5a9e373521 100644 --- a/Content.Shared/Movement/Components/SpriteMovementComponent.cs +++ b/Content.Shared/Movement/Components/SpriteMovementComponent.cs @@ -5,7 +5,7 @@ namespace Content.Shared.Movement.Components; /// /// Updates a sprite layer based on whether an entity is moving via input or not. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class SpriteMovementComponent : Component { /// @@ -19,4 +19,7 @@ public sealed partial class SpriteMovementComponent : Component /// [DataField] public Dictionary NoMovementLayers = new(); + + [DataField, AutoNetworkedField] + public bool IsMoving; } diff --git a/Content.Shared/Movement/Events/SpriteMoveEvent.cs b/Content.Shared/Movement/Events/SpriteMoveEvent.cs new file mode 100644 index 00000000000..25ebffe2df7 --- /dev/null +++ b/Content.Shared/Movement/Events/SpriteMoveEvent.cs @@ -0,0 +1,15 @@ +namespace Content.Shared.Movement.Events; + +/// +/// Raised on an entity whenever it should change movement sprite +/// +[ByRefEvent] +public readonly struct SpriteMoveEvent +{ + public readonly bool IsMoving = false; + + public SpriteMoveEvent(bool isMoving) + { + IsMoving = isMoving; + } +} diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs index 6392956d632..b17e34f1711 100644 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Database; using Content.Shared.Hands; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Input; using Content.Shared.Interaction; using Content.Shared.Item; @@ -46,6 +47,7 @@ public sealed class PullingSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly HeldSpeedModifierSystem _clothingMoveSpeed = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { @@ -326,7 +328,6 @@ private void StopPulling(EntityUid pullableUid, PullableComponent pullableComp) RaiseLocalEvent(pullableUid, message); } - _alertsSystem.ClearAlert(pullableUid, pullableComp.PulledAlert); } @@ -514,6 +515,10 @@ public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, Dirty(pullerUid, pullerComp); Dirty(pullableUid, pullableComp); + var pullingMessage = + Loc.GetString("getting-pulled-popup", ("puller", Identity.Entity(pullerUid, EntityManager))); + _popup.PopupEntity(pullingMessage, pullableUid, pullableUid); + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(pullerUid):user} started pulling {ToPrettyString(pullableUid):target}"); return true; diff --git a/Content.Shared/Movement/Systems/MovementSoundSystem.cs b/Content.Shared/Movement/Systems/MovementSoundSystem.cs new file mode 100644 index 00000000000..9a1146779fa --- /dev/null +++ b/Content.Shared/Movement/Systems/MovementSoundSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.Movement.Systems; + +/// +/// Plays a sound on MoveInputEvent. +/// +public sealed class MovementSoundSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnMoveInput); + } + + private void OnMoveInput(Entity ent, ref MoveInputEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None; + var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None; + + if (oldMoving == moving) + return; + + if (moving) + { + DebugTools.Assert(ent.Comp.SoundEntity == null); + ent.Comp.SoundEntity = _audio.PlayPredicted(ent.Comp.Sound, ent.Owner, ent.Owner)?.Entity; + } + else + { + ent.Comp.SoundEntity = _audio.Stop(ent.Comp.SoundEntity); + } + } +} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 1fe38b6cdf1..6f508d9038c 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -96,6 +96,9 @@ protected void SetMoveInput(Entity entity, MoveButtons butt entity.Comp.HeldMoveButtons = buttons; RaiseLocalEvent(entity, ref moveEvent); Dirty(entity, entity.Comp); + + var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None); + RaiseLocalEvent(entity, ref ev); } private void OnMoverHandleState(Entity entity, ref ComponentHandleState args) @@ -119,6 +122,9 @@ private void OnMoverHandleState(Entity entity, ref Componen var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); entity.Comp.HeldMoveButtons = state.HeldMoveButtons; RaiseLocalEvent(entity.Owner, ref moveEvent); + + var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None); + RaiseLocalEvent(entity, ref ev); } } diff --git a/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs b/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs new file mode 100644 index 00000000000..eb4bbc1be63 --- /dev/null +++ b/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Movement.Components; +using Content.Shared.Movement.Events; + +namespace Content.Shared.Movement.Systems; + +public abstract class SharedSpriteMovementSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSpriteMoveInput); + } + + private void OnSpriteMoveInput(Entity ent, ref SpriteMoveEvent args) + { + if (ent.Comp.IsMoving == args.IsMoving) + return; + + ent.Comp.IsMoving = args.IsMoving; + Dirty(ent); + } +} diff --git a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs index 09be1085058..830b51d5ad4 100644 --- a/Content.Shared/Ninja/Systems/DashAbilitySystem.cs +++ b/Content.Shared/Ninja/Systems/DashAbilitySystem.cs @@ -1,8 +1,12 @@ using Content.Shared.Actions; using Content.Shared.Charges.Components; using Content.Shared.Charges.Systems; +using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Ninja.Components; using Content.Shared.Popups; using Content.Shared.Examine; @@ -22,6 +26,7 @@ public sealed class DashAbilitySystem : EntitySystem [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly PullingSystem _pullingSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() @@ -80,6 +85,14 @@ private void OnDash(Entity ent, ref DashEvent args) return; } + // Check if the user is BEING pulled, and escape if so + if (TryComp(user, out var pull) && _pullingSystem.IsPulled(user, pull)) + _pullingSystem.TryStopPull(user, pull); + + // Check if the user is pulling anything, and drop it if so + if (TryComp(user, out var puller) && TryComp(puller.Pulling, out var pullable)) + _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); + var xform = Transform(user); _transform.SetCoordinates(user, xform, args.Target); _transform.AttachToGridOrMap(user, xform); diff --git a/Content.Shared/Nutrition/Components/HungerComponent.cs b/Content.Shared/Nutrition/Components/HungerComponent.cs index 8035ce6fe5c..62c2f17eab0 100644 --- a/Content.Shared/Nutrition/Components/HungerComponent.cs +++ b/Content.Shared/Nutrition/Components/HungerComponent.cs @@ -10,26 +10,37 @@ namespace Content.Shared.Nutrition.Components; [RegisterComponent, NetworkedComponent, Access(typeof(HungerSystem))] -[AutoGenerateComponentState, AutoGenerateComponentPause] +[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause] public sealed partial class HungerComponent : Component { /// - /// The current hunger amount of the entity + /// The hunger value as authoritatively set by the server as of . + /// This value should be updated relatively infrequently. To get the current hunger, which changes with each update, + /// use . /// - [DataField("currentHunger"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadOnly)] [AutoNetworkedField] - public float CurrentHunger; + public float LastAuthoritativeHungerValue; /// - /// The base amount at which decays. + /// The time at which was last updated. /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public TimeSpan LastAuthoritativeHungerChangeTime; + + /// + /// The base amount at which decays. + /// + /// Any time this is modified, should be called. [DataField("baseDecayRate"), ViewVariables(VVAccess.ReadWrite)] public float BaseDecayRate = 0.035f; //DeltaV: changed from 0.01666666666f /// - /// The actual amount at which decays. + /// The actual amount at which decays. /// Affected by /// + /// Any time this is modified, should be called. [DataField("actualDecayRate"), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] public float ActualDecayRate; @@ -45,12 +56,13 @@ public sealed partial class HungerComponent : Component /// /// The current hunger threshold the entity is at /// + /// Any time this is modified, should be called. [DataField("currentThreshold"), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] public HungerThreshold CurrentThreshold; /// - /// A dictionary relating HungerThreshold to the amount of needed for each one + /// A dictionary relating HungerThreshold to the amount of current hunger needed for each one /// [DataField("thresholds", customTypeSerializer: typeof(DictionarySerializer))] [AutoNetworkedField] @@ -106,19 +118,19 @@ public sealed partial class HungerComponent : Component public DamageSpecifier? StarvationDamage; /// - /// The time when the hunger will update next. + /// The time when the hunger threshold will update next. /// [DataField("nextUpdateTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] [AutoPausedField] - public TimeSpan NextUpdateTime; + public TimeSpan NextThresholdUpdateTime; /// - /// The time between each update. + /// The time between each hunger threshold update. /// [ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] - public TimeSpan UpdateRate = TimeSpan.FromSeconds(1); + public TimeSpan ThresholdUpdateRate = TimeSpan.FromSeconds(1); } [Serializable, NetSerializable] diff --git a/Content.Shared/Nutrition/Components/ThirstComponent.cs b/Content.Shared/Nutrition/Components/ThirstComponent.cs index f3ac881361f..5c32b4af286 100644 --- a/Content.Shared/Nutrition/Components/ThirstComponent.cs +++ b/Content.Shared/Nutrition/Components/ThirstComponent.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Nutrition.Components; [RegisterComponent, NetworkedComponent, Access(typeof(ThirstSystem))] -[AutoGenerateComponentState, AutoGenerateComponentPause] +[AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause] public sealed partial class ThirstComponent : Component { // Base stuff diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index 6a6dd7af782..a8697ffee45 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; using Content.Shared.StatusIcon; +using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -72,6 +73,16 @@ private void OnRejuvenate(EntityUid uid, HungerComponent component, RejuvenateEv SetHunger(uid, component.Thresholds[HungerThreshold.Okay], component); } + /// + /// Gets the current hunger value of the given . + /// + public float GetHunger(HungerComponent component) + { + var dt = _timing.CurTime - component.LastAuthoritativeHungerChangeTime; + var value = component.LastAuthoritativeHungerValue - (float)dt.TotalSeconds * component.ActualDecayRate; + return ClampHungerWithinThresholds(component, value); + } + /// /// Adds to the current hunger of an entity by the specified value /// @@ -82,7 +93,7 @@ public void ModifyHunger(EntityUid uid, float amount, HungerComponent? component { if (!Resolve(uid, ref component)) return; - SetHunger(uid, component.CurrentHunger + amount, component); + SetHunger(uid, GetHunger(component) + amount, component); } /// @@ -95,11 +106,23 @@ public void SetHunger(EntityUid uid, float amount, HungerComponent? component = { if (!Resolve(uid, ref component)) return; - component.CurrentHunger = Math.Clamp(amount, - component.Thresholds[HungerThreshold.Dead], - component.Thresholds[HungerThreshold.Overfed]); + + SetAuthoritativeHungerValue((uid, component), amount); UpdateCurrentThreshold(uid, component); - Dirty(uid, component); + } + + /// + /// Sets and + /// , and dirties this entity. This "resets" the + /// starting point for 's calculation. + /// + /// The entity whose hunger will be set. + /// The value to set the entity's hunger to. + private void SetAuthoritativeHungerValue(Entity entity, float value) + { + entity.Comp.LastAuthoritativeHungerChangeTime = _timing.CurTime; + entity.Comp.LastAuthoritativeHungerValue = ClampHungerWithinThresholds(entity.Comp, value); + DirtyField(entity.Owner, entity.Comp, nameof(HungerComponent.LastAuthoritativeHungerChangeTime)); } private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = null) @@ -110,9 +133,9 @@ private void UpdateCurrentThreshold(EntityUid uid, HungerComponent? component = var calculatedHungerThreshold = GetHungerThreshold(component); if (calculatedHungerThreshold == component.CurrentThreshold) return; + component.CurrentThreshold = calculatedHungerThreshold; DoHungerThresholdEffects(uid, component); - Dirty(uid, component); } private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component = null, bool force = false) @@ -140,6 +163,7 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier)) { component.ActualDecayRate = component.BaseDecayRate * modifier; + SetAuthoritativeHungerValue((uid, component), GetHunger(component)); } component.LastThreshold = component.CurrentThreshold; @@ -167,7 +191,7 @@ component.StarvationDamage is { } damage && /// public HungerThreshold GetHungerThreshold(HungerComponent component, float? food = null) { - food ??= component.CurrentHunger; + food ??= GetHunger(component); var result = HungerThreshold.Dead; var value = component.Thresholds[HungerThreshold.Overfed]; foreach (var threshold in component.Thresholds) @@ -178,6 +202,7 @@ public HungerThreshold GetHungerThreshold(HungerComponent component, float? food value = threshold.Value; } } + return result; } @@ -229,6 +254,13 @@ public bool TryGetStatusIconPrototype(HungerComponent component, [NotNullWhen(tr return prototype != null; } + private static float ClampHungerWithinThresholds(HungerComponent component, float hungerValue) + { + return Math.Clamp(hungerValue, + component.Thresholds[HungerThreshold.Dead], + component.Thresholds[HungerThreshold.Overfed]); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -236,13 +268,12 @@ public override void Update(float frameTime) var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var hunger)) { - if (_timing.CurTime < hunger.NextUpdateTime) + if (_timing.CurTime < hunger.NextThresholdUpdateTime) continue; - hunger.NextUpdateTime = _timing.CurTime + hunger.UpdateRate; + hunger.NextThresholdUpdateTime = _timing.CurTime + hunger.ThresholdUpdateRate; - ModifyHunger(uid, -hunger.ActualDecayRate, hunger); + UpdateCurrentThreshold(uid, hunger); DoContinuousHungerEffects(uid, hunger); } } } - diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index 0b2bb2e0efa..2937c48d482 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -102,7 +102,8 @@ public void SetThirst(EntityUid uid, ThirstComponent component, float amount) component.ThirstThresholds[ThirstThreshold.Dead], component.ThirstThresholds[ThirstThreshold.OverHydrated] ); - Dirty(uid, component); + + EntityManager.DirtyField(uid, component, nameof(ThirstComponent.CurrentThirst)); } private bool IsMovementThreshold(ThirstThreshold threshold) diff --git a/Content.Server/Nutrition/IngestionEvents.cs b/Content.Shared/Nutrition/IngestionEvents.cs similarity index 96% rename from Content.Server/Nutrition/IngestionEvents.cs rename to Content.Shared/Nutrition/IngestionEvents.cs index ae1d22fb71f..488605522ac 100644 --- a/Content.Server/Nutrition/IngestionEvents.cs +++ b/Content.Shared/Nutrition/IngestionEvents.cs @@ -1,4 +1,4 @@ -namespace Content.Server.Nutrition; +namespace Content.Shared.Nutrition; /// /// Raised directed at the consumer when attempting to ingest something. diff --git a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs index 6a3fc811c89..7d07857f072 100644 --- a/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ b/Content.Shared/Nyanotrasen/Abilities/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -1,16 +1,14 @@ +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -namespace Content.Shared.Abilities.Psionics +namespace Content.Shared.Abilities.Psionics; + +[RegisterComponent, NetworkedComponent] +public sealed partial class MindSwapPowerComponent : Component { - [RegisterComponent] - public sealed partial class MindSwapPowerComponent : Component - { - [DataField("mindSwapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MindSwapActionId = "ActionMindSwap"; + [DataField] + public EntProtoId? MindSwapActionId = "ActionMindSwap"; - [DataField("mindSwapActionEntity")] - public EntityUid? MindSwapActionEntity; - } + [DataField] + public EntityUid? MindSwapActionEntity; } diff --git a/Content.Shared/Power/SharedPowerMonitoringConsoleComponent.cs b/Content.Shared/Power/SharedPowerMonitoringConsoleComponent.cs index 4d404f209cd..d7e060cccd6 100644 --- a/Content.Shared/Power/SharedPowerMonitoringConsoleComponent.cs +++ b/Content.Shared/Power/SharedPowerMonitoringConsoleComponent.cs @@ -102,14 +102,16 @@ public struct PowerMonitoringConsoleEntry public NetEntity NetEntity; public PowerMonitoringConsoleGroup Group; public double PowerValue; + public float? BatteryLevel; [NonSerialized] public PowerMonitoringDeviceMetaData? MetaData = null; - public PowerMonitoringConsoleEntry(NetEntity netEntity, PowerMonitoringConsoleGroup group, double powerValue = 0d) + public PowerMonitoringConsoleEntry(NetEntity netEntity, PowerMonitoringConsoleGroup group, double powerValue = 0d, float? batteryLevel = null) { NetEntity = netEntity; Group = group; PowerValue = powerValue; + BatteryLevel = batteryLevel; } } diff --git a/Content.Shared/Prototypes/NavMapBlipPrototype.cs b/Content.Shared/Prototypes/NavMapBlipPrototype.cs new file mode 100644 index 00000000000..ede82d8e04d --- /dev/null +++ b/Content.Shared/Prototypes/NavMapBlipPrototype.cs @@ -0,0 +1,42 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Prototypes; + +[Prototype("navMapBlip")] +public sealed partial class NavMapBlipPrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Sets whether the associated entity can be selected when the blip is clicked + /// + [DataField] + public bool Selectable = false; + + /// + /// Sets whether the blips is always blinking + /// + [DataField] + public bool Blinks = false; + + /// + /// Sets the color of the blip + /// + [DataField] + public Color Color { get; private set; } = Color.LightGray; + + /// + /// Texture paths associated with the blip + /// + [DataField] + public ResPath[]? TexturePaths { get; private set; } + + /// + /// Sets the UI scaling of the blip + /// + [DataField] + public float Scale { get; private set; } = 1f; +} diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index f4a60b23e93..559306b17ac 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -125,6 +125,13 @@ public sealed partial class JobPrototype : IPrototype [DataField("jobEntity", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? JobEntity = null; + /// + /// Entity to use as a preview in the lobby/character editor. + /// Same restrictions as apply. + /// + [DataField] + public EntProtoId? JobPreviewEntity = null; + [DataField] public ProtoId Icon { get; private set; } = "JobIconUnknown"; diff --git a/Content.Shared/Sericulture/SericultureSystem.cs b/Content.Shared/Sericulture/SericultureSystem.cs index f7586cc1ec3..8c10d0f3d05 100644 --- a/Content.Shared/Sericulture/SericultureSystem.cs +++ b/Content.Shared/Sericulture/SericultureSystem.cs @@ -53,7 +53,10 @@ private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShu private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args) { if (TryComp(uid, out var hungerComp) - && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp)) + && _hungerSystem.IsHungerBelowState(uid, + comp.MinHungerThreshold, + _hungerSystem.GetHunger(hungerComp) - comp.HungerCost, + hungerComp)) { _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid); return; @@ -76,8 +79,12 @@ private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, Seri if (args.Cancelled || args.Handled || comp.Deleted) return; - if (TryComp(uid, out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state. - && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp)) + if (TryComp(uid, + out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state. + && _hungerSystem.IsHungerBelowState(uid, + comp.MinHungerThreshold, + _hungerSystem.GetHunger(hungerComp) - comp.HungerCost, + hungerComp)) { _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid); return; diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs index 5fca5cad280..4937e6e84c2 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.cs @@ -20,12 +20,15 @@ using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Network; using Robust.Shared.Physics; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Timing; +using System.Diagnostics.CodeAnalysis; namespace Content.Shared.Silicons.StationAi; @@ -68,6 +71,8 @@ public abstract partial class SharedStationAiSystem : EntitySystem [ValidatePrototypeId] private static readonly EntProtoId DefaultAi = "StationAiBrain"; + private const float MaxVisionMultiplier = 5f; + public override void Initialize() { base.Initialize(); @@ -344,16 +349,47 @@ private void OnAiMapInit(Entity ent, ref MapInitEvent ar AttachEye(ent); } - private bool SetupEye(Entity ent) + public void SwitchRemoteEntityMode(Entity ent, bool isRemote) + { + if (isRemote == ent.Comp.Remote) + return; + + ent.Comp.Remote = isRemote; + + EntityCoordinates? coords = ent.Comp.RemoteEntity != null ? Transform(ent.Comp.RemoteEntity.Value).Coordinates : null; + + // Attach new eye + ClearEye(ent); + + if (SetupEye(ent, coords)) + AttachEye(ent); + + // Adjust user FoV + var user = GetInsertedAI(ent); + + if (TryComp(user, out var eye)) + _eye.SetDrawFov(user.Value, !isRemote); + } + + private bool SetupEye(Entity ent, EntityCoordinates? coords = null) { if (_net.IsClient) return false; + if (ent.Comp.RemoteEntity != null) return false; - if (ent.Comp.RemoteEntityProto != null) + var proto = ent.Comp.RemoteEntityProto; + + if (coords == null) + coords = Transform(ent.Owner).Coordinates; + + if (!ent.Comp.Remote) + proto = ent.Comp.PhysicalEntityProto; + + if (proto != null) { - ent.Comp.RemoteEntity = SpawnAtPosition(ent.Comp.RemoteEntityProto, Transform(ent.Owner).Coordinates); + ent.Comp.RemoteEntity = SpawnAtPosition(proto, coords.Value); Dirty(ent); } @@ -364,6 +400,7 @@ private void ClearEye(Entity ent) { if (_net.IsClient) return; + QueueDel(ent.Comp.RemoteEntity); ent.Comp.RemoteEntity = null; Dirty(ent); @@ -392,6 +429,17 @@ private void AttachEye(Entity ent) _mover.SetRelay(user, ent.Comp.RemoteEntity.Value); } + private EntityUid? GetInsertedAI(Entity ent) + { + if (!_containers.TryGetContainer(ent.Owner, StationAiHolderComponent.Container, out var container) || + container.ContainedEntities.Count != 1) + { + return null; + } + + return container.ContainedEntities[0]; + } + private void OnAiInsert(Entity ent, ref EntInsertedIntoContainerMessage args) { if (args.Container.ID != StationAiCoreComponent.Container) @@ -400,6 +448,7 @@ private void OnAiInsert(Entity ent, ref EntInsertedIntoC if (_timing.ApplyingState) return; + ent.Comp.Remote = true; SetupEye(ent); // Just so text and the likes works properly @@ -413,6 +462,8 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo if (_timing.ApplyingState) return; + ent.Comp.Remote = true; + // Reset name to whatever _metadata.SetEntityName(ent.Owner, Prototype(ent.Owner)?.Name ?? string.Empty); @@ -424,6 +475,7 @@ private void OnAiRemove(Entity ent, ref EntRemovedFromCo _eye.SetDrawFov(args.Entity, true, eyeComp); _eye.SetTarget(args.Entity, null, eyeComp); } + ClearEye(ent); } @@ -478,6 +530,36 @@ private bool ValidateAi(Entity entity) return _blocker.CanComplexInteract(entity.Owner); } + + public bool TryGetStationAiCore(Entity ent, [NotNullWhen(true)] out Entity? parentEnt) + { + parentEnt = null; + var parent = Transform(ent).ParentUid; + + if (!parent.IsValid()) + return false; + + if (!TryComp(parent, out var stationAiCore)) + return false; + + parentEnt = new Entity(parent, stationAiCore); + + return true; + } + + public bool TryGetInsertedAI(Entity ent, [NotNullWhen(true)] out Entity? insertedAi) + { + insertedAi = null; + var insertedEnt = GetInsertedAI(ent); + + if (TryComp(insertedEnt, out var stationAiHeld)) + { + insertedAi = (insertedEnt.Value, stationAiHeld); + return true; + } + + return false; + } } public sealed partial class JumpToCoreEvent : InstantActionEvent diff --git a/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs index b7a8b4cd5fa..e97e7626b88 100644 --- a/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs +++ b/Content.Shared/Silicons/StationAi/StationAiCoreComponent.cs @@ -15,6 +15,7 @@ public sealed partial class StationAiCoreComponent : Component /// /// Can it move its camera around and interact remotely with things. + /// When false, the AI is being projected into a local area, such as a holopad /// [DataField, AutoNetworkedField] public bool Remote = true; @@ -25,8 +26,17 @@ public sealed partial class StationAiCoreComponent : Component [DataField, AutoNetworkedField] public EntityUid? RemoteEntity; + /// + /// Prototype that represents the 'eye' of the AI + /// [DataField(readOnly: true)] public EntProtoId? RemoteEntityProto = "StationAiHolo"; + /// + /// Prototype that represents the physical avatar of the AI + /// + [DataField(readOnly: true)] + public EntProtoId? PhysicalEntityProto = "StationAiHoloLocal"; + public const string Container = "station_ai_mind_slot"; } diff --git a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs index 4e6ebb23d21..870d20457ef 100644 --- a/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs +++ b/Content.Shared/Sound/Components/BaseEmitSoundComponent.cs @@ -14,4 +14,11 @@ public abstract partial class BaseEmitSoundComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField(required: true)] public SoundSpecifier? Sound; + + /// + /// Play the sound at the position instead of parented to the source entity. + /// Useful if the entity is deleted after. + /// + [DataField] + public bool Positional; } diff --git a/Content.Shared/Sound/SharedEmitSoundSystem.cs b/Content.Shared/Sound/SharedEmitSoundSystem.cs index 3e051fff317..30744b68644 100644 --- a/Content.Shared/Sound/SharedEmitSoundSystem.cs +++ b/Content.Shared/Sound/SharedEmitSoundSystem.cs @@ -145,14 +145,22 @@ protected void TryEmitSound(EntityUid uid, BaseEmitSoundComponent component, Ent if (component.Sound == null) return; - if (predict) + if (component.Positional) { - _audioSystem.PlayPredicted(component.Sound, uid, user); + var coords = Transform(uid).Coordinates; + if (predict) + _audioSystem.PlayPredicted(component.Sound, coords, user); + else if (_netMan.IsServer) + // don't predict sounds that client couldn't have played already + _audioSystem.PlayPvs(component.Sound, coords); } - else if (_netMan.IsServer) + else { - // don't predict sounds that client couldn't have played already - _audioSystem.PlayPvs(component.Sound, uid); + if (predict) + _audioSystem.PlayPredicted(component.Sound, uid, user); + else if (_netMan.IsServer) + // don't predict sounds that client couldn't have played already + _audioSystem.PlayPvs(component.Sound, uid); } } diff --git a/Content.Shared/Speech/SpeechComponent.cs b/Content.Shared/Speech/SpeechComponent.cs index 0882120718d..8c12fc918a0 100644 --- a/Content.Shared/Speech/SpeechComponent.cs +++ b/Content.Shared/Speech/SpeechComponent.cs @@ -56,5 +56,11 @@ public sealed partial class SpeechComponent : Component public float SoundCooldownTime { get; set; } = 0.5f; public TimeSpan LastTimeSoundPlayed = TimeSpan.Zero; + + /// + /// Additional vertical offset for speech bubbles generated by this entity + /// + [DataField] + public float SpeechBubbleOffset = 0f; } } diff --git a/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs b/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs index af9b768e98b..f13303d7338 100644 --- a/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SecretStashSystem.cs @@ -1,20 +1,19 @@ -using Content.Shared.Popups; -using Content.Shared.Storage.Components; +using Content.Shared.Construction.EntitySystems; using Content.Shared.Destructible; using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; -using Content.Shared.Item; -using Robust.Shared.Containers; -using Content.Shared.Interaction; -using Content.Shared.Tools.Systems; -using Content.Shared.Examine; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Content.Shared.Verbs; using Content.Shared.IdentityManagement; +using Content.Shared.Interaction; +using Content.Shared.Item; +using Content.Shared.Materials; +using Content.Shared.Popups; +using Content.Shared.Storage.Components; using Content.Shared.Tools.EntitySystems; +using Content.Shared.Verbs; using Content.Shared.Whitelist; -using Content.Shared.Materials; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Containers; using Robust.Shared.Map; namespace Content.Shared.Storage.EntitySystems; @@ -38,7 +37,7 @@ public override void Initialize() SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnDestroyed); SubscribeLocalEvent(OnReclaimed); - SubscribeLocalEvent(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem) }); + SubscribeLocalEvent(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem), typeof(AnchorableSystem) }); SubscribeLocalEvent(OnInteractHand); SubscribeLocalEvent>(OnGetVerb); } diff --git a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs index 921cc760aa0..7225716a69d 100644 --- a/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs +++ b/Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs @@ -361,7 +361,7 @@ private void AddTransferVerbs(EntityUid uid, StorageComponent component, GetVerb /// true if inserted, false otherwise private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, InteractUsingEvent args) { - if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false)) + if (args.Handled || !storageComp.ClickInsert || !CanInteract(args.User, (uid, storageComp), silent: false)) return; var attemptEv = new StorageInteractUsingAttemptEvent(); @@ -381,7 +381,7 @@ private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, Intera /// private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInWorldEvent args) { - if (args.Handled || !args.Complex || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert)) + if (args.Handled || !args.Complex || !storageComp.OpenOnActivate || !CanInteract(args.User, (uid, storageComp))) return; // Toggle diff --git a/Content.Shared/Storage/StorageComponent.cs b/Content.Shared/Storage/StorageComponent.cs index 5683ae95a71..52547454a07 100644 --- a/Content.Shared/Storage/StorageComponent.cs +++ b/Content.Shared/Storage/StorageComponent.cs @@ -65,8 +65,18 @@ public sealed partial class StorageComponent : Component [DataField] public TimeSpan OpenUiCooldown = TimeSpan.Zero; + /// + /// Can insert stuff by clicking the storage entity with it. + /// + [DataField] + public bool ClickInsert = true; + + /// + /// Open the storage window when pressing E. + /// When false you can still open the inventory using verbs. + /// [DataField] - public bool ClickInsert = true; // Can insert stuff by clicking the storage entity with it + public bool OpenOnActivate = true; /// /// How many entities area pickup can pickup at once. diff --git a/Content.Shared/Telephone/SharedTelephoneSystem.cs b/Content.Shared/Telephone/SharedTelephoneSystem.cs new file mode 100644 index 00000000000..ab423623cb5 --- /dev/null +++ b/Content.Shared/Telephone/SharedTelephoneSystem.cs @@ -0,0 +1,39 @@ +using System.Linq; + +namespace Content.Shared.Telephone; + +public abstract class SharedTelephoneSystem : EntitySystem +{ + public bool IsTelephoneEngaged(Entity entity) + { + return entity.Comp.LinkedTelephones.Any(); + } + + public string GetFormattedCallerIdForEntity(string? presumedName, string? presumedJob, Color fontColor, string fontType = "Default", int fontSize = 12) + { + var callerId = Loc.GetString("chat-telephone-unknown-caller", + ("color", fontColor), + ("fontType", fontType), + ("fontSize", fontSize)); + + if (presumedName == null) + return callerId; + + if (presumedJob != null) + callerId = Loc.GetString("chat-telephone-caller-id-with-job", + ("callerName", presumedName), + ("callerJob", presumedJob), + ("color", fontColor), + ("fontType", fontType), + ("fontSize", fontSize)); + + else + callerId = Loc.GetString("chat-telephone-caller-id-without-job", + ("callerName", presumedName), + ("color", fontColor), + ("fontType", fontType), + ("fontSize", fontSize)); + + return callerId; + } +} diff --git a/Content.Shared/Telephone/TelephoneComponent.cs b/Content.Shared/Telephone/TelephoneComponent.cs new file mode 100644 index 00000000000..530748f4d37 --- /dev/null +++ b/Content.Shared/Telephone/TelephoneComponent.cs @@ -0,0 +1,220 @@ +using Content.Shared.Chat; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared.Telephone; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedTelephoneSystem))] +public sealed partial class TelephoneComponent : Component +{ + /// + /// Sets how long the telephone will ring before it automatically hangs up + /// + [DataField] + public float RingingTimeout = 30; + + /// + /// Sets how long the telephone can remain idle in-call before it automatically hangs up + /// + [DataField] + public float IdlingTimeout = 60; + + /// + /// Sets how long the telephone will stay in the hanging up state before return to idle + /// + [DataField] + public float HangingUpTimeout = 2; + + /// + /// Tone played while the phone is ringing + /// + [DataField] + public SoundSpecifier? RingTone = null; + + /// + /// Sets the number of seconds before the next ring tone is played + /// + [DataField] + public float RingInterval = 2f; + + /// + /// The time at which the next tone will be played + /// + [DataField] + public TimeSpan NextRingToneTime; + + /// + /// The volume at which relayed messages are played + /// + [DataField] + public TelephoneVolume SpeakerVolume = TelephoneVolume.Whisper; + + /// + /// The maximum range at which the telephone initiate a call with another + /// + [DataField] + public TelephoneRange TransmissionRange = TelephoneRange.Grid; + + /// + /// This telephone will ignore devices that share the same grid as it + /// + /// + /// This bool will be ignored if the is + /// set to + /// + [DataField] + public bool IgnoreTelephonesOnSameGrid = false; + + /// + /// The telephone can only connect with other telephones which have a + /// present in this list + /// + [DataField] + public List CompatibleRanges = new List() { TelephoneRange.Grid }; + + /// + /// The range at which the telephone picks up voices + /// + [DataField] + public float ListeningRange = 2; + + /// + /// Specifies whether this telephone require power to fucntion + /// + [DataField] + public bool RequiresPower = true; + + /// + /// This telephone should not appear on public telephone directories + /// + [DataField] + public bool UnlistedNumber = false; + + /// + /// Telephone number for this device + /// + /// + /// For future use - a system for generating and handling telephone numbers has not been implemented yet + /// + [ViewVariables] + public int TelephoneNumber = -1; + + /// + /// Linked telephone + /// + [ViewVariables] + public HashSet> LinkedTelephones = new(); + + /// + /// Defines the current state the telephone is in + /// + [ViewVariables, AutoNetworkedField] + public TelephoneState CurrentState = TelephoneState.Idle; + + /// + /// The game tick the current state started + /// + [ViewVariables] + public TimeSpan StateStartTime; + + /// + /// Sets whether the telphone can pick up nearby speech + /// + [ViewVariables] + public bool Muted = false; + + /// + /// The presumed name and/or job of the last person to call this telephone + /// + [ViewVariables, AutoNetworkedField] + public (string?, string?) LastCallerId; +} + +#region: Telephone events + +/// +/// Raised when one telephone is attempting to call another +/// +[ByRefEvent] +public record struct TelephoneCallAttemptEvent(Entity Source, Entity Receiver, EntityUid? User) +{ + public bool Cancelled = false; +} + +/// +/// Raised when a telephone's state changes +/// +[ByRefEvent] +public record struct TelephoneStateChangeEvent(TelephoneState OldState, TelephoneState NewState); + +/// +/// Raised when communication between one telephone and another begins +/// +[ByRefEvent] +public record struct TelephoneCallCommencedEvent(Entity Receiver); + +/// +/// Raised when a telephone hangs up +/// +[ByRefEvent] +public record struct TelephoneCallEndedEvent(); + +/// +/// Raised when a chat message is sent by a telephone to another +/// +[ByRefEvent] +public readonly record struct TelephoneMessageSentEvent(string Message, MsgChatMessage ChatMsg, EntityUid MessageSource); + +/// +/// Raised when a chat message is received by a telephone from another +/// +[ByRefEvent] +public readonly record struct TelephoneMessageReceivedEvent(string Message, MsgChatMessage ChatMsg, EntityUid MessageSource, Entity TelephoneSource); + +#endregion + +/// +/// Options for tailoring telephone calls +/// +[Serializable, NetSerializable] +public struct TelephoneCallOptions +{ + public bool IgnoreRange; // The source can always reach its target + public bool ForceConnect; // The source immediately starts a call with the receiver, potentially interrupting a call that is already in progress + public bool ForceJoin; // The source smoothly joins a call in progress, or starts a normal call with the receiver if there is none + public bool MuteSource; // Chatter from the source is not transmitted - could be used for eavesdropping when combined with 'ForceJoin' + public bool MuteReceiver; // Chatter from the receiver is not transmitted - useful for broadcasting messages to multiple receivers +} + +[Serializable, NetSerializable] +public enum TelephoneVisuals : byte +{ + Key +} + +[Serializable, NetSerializable] +public enum TelephoneState : byte +{ + Idle, + Calling, + Ringing, + InCall, + EndingCall +} + +[Serializable, NetSerializable] +public enum TelephoneVolume : byte +{ + Whisper, + Speak +} + +[Serializable, NetSerializable] +public enum TelephoneRange : byte +{ + Grid, // Can only reach telephones that are on the same grid + Map, // Can reach any telephone that is on the same map + Unlimited, // Can reach any telephone, across any distance +} diff --git a/Content.Shared/Tools/Systems/SharedToolSystem.cs b/Content.Shared/Tools/Systems/SharedToolSystem.cs index 4c7383a38c4..95377d913a9 100644 --- a/Content.Shared/Tools/Systems/SharedToolSystem.cs +++ b/Content.Shared/Tools/Systems/SharedToolSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Administration.Logs; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.DoAfter; +using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Item.ItemToggle; using Content.Shared.Maps; @@ -41,6 +42,7 @@ public override void Initialize() InitializeTile(); InitializeWelder(); SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnExamine); } private void OnDoAfter(EntityUid uid, ToolComponent tool, ToolDoAfterEvent args) @@ -57,6 +59,34 @@ private void OnDoAfter(EntityUid uid, ToolComponent tool, ToolDoAfterEvent args) RaiseLocalEvent((object) ev); } + private void OnExamine(Entity ent, ref ExaminedEvent args) + { + // If the tool has no qualities, exit early + if (ent.Comp.Qualities.Count == 0) + return; + + var message = new FormattedMessage(); + + // Create a list to store tool quality names + var toolQualities = new List(); + + // Loop through tool qualities and add localized names to the list + foreach (var toolQuality in ent.Comp.Qualities) + { + if (_protoMan.TryIndex(toolQuality ?? string.Empty, out var protoToolQuality)) + { + toolQualities.Add(Loc.GetString(protoToolQuality.Name)); + } + } + + // Combine the qualities into a single string and localize the final message + var qualitiesString = string.Join(", ", toolQualities); + + // Add the localized message to the FormattedMessage object + message.AddMarkupPermissive(Loc.GetString("tool-component-qualities", ("qualities", qualitiesString))); + args.PushMessage(message); + } + public void PlayToolSound(EntityUid uid, ToolComponent tool, EntityUid? user) { if (tool.UseSound == null) diff --git a/Content.Shared/Weapons/Ranged/Components/AmmoComponent.cs b/Content.Shared/Weapons/Ranged/Components/AmmoComponent.cs index 3e1111a97d1..13cee5bad6f 100644 --- a/Content.Shared/Weapons/Ranged/Components/AmmoComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/AmmoComponent.cs @@ -13,27 +13,27 @@ public partial class AmmoComponent : Component, IShootable { // Muzzle flash stored on ammo because if we swap a gun to whatever we may want to override it. - [ViewVariables(VVAccess.ReadWrite), DataField("muzzleFlash", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string? MuzzleFlash = "MuzzleFlashEffect"; + [DataField] + public EntProtoId? MuzzleFlash = "MuzzleFlashEffect"; } /// /// Spawns another prototype to be shot instead of itself. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true)] public sealed partial class CartridgeAmmoComponent : AmmoComponent { - [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Prototype = default!; + [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true)] + public EntProtoId Prototype; - [ViewVariables(VVAccess.ReadWrite), DataField("spent")] + [ViewVariables(VVAccess.ReadWrite), DataField] [AutoNetworkedField] - public bool Spent = false; + public bool Spent; /// /// Caseless ammunition. /// - [DataField("deleteOnSpawn")] + [DataField] public bool DeleteOnSpawn; [DataField("soundEject")] diff --git a/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs index f825f10550d..ee806125e78 100644 --- a/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/BallisticAmmoProviderComponent.cs @@ -7,13 +7,13 @@ namespace Content.Shared.Weapons.Ranged.Components; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGunSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), Access(typeof(SharedGunSystem))] public sealed partial class BallisticAmmoProviderComponent : Component { - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public SoundSpecifier? SoundRack = new SoundPathSpecifier("/Audio/Weapons/Guns/Cock/smg_cock.ogg"); - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public SoundSpecifier? SoundInsert = new SoundPathSpecifier("/Audio/Weapons/Guns/MagIn/bullet_insert.ogg"); [ViewVariables(VVAccess.ReadWrite), DataField] diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs index 569e8390700..9b524b26f37 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Weapons.Ranged.Components; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause] [Access(typeof(SharedGunSystem), typeof(SharedOniSystem))] // DeltaV - I didn't feel like rewriting big chunks of code public sealed partial class GunComponent : Component { diff --git a/Content.Shared/Weapons/Ranged/Components/SolutionAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/SolutionAmmoProviderComponent.cs index d936f3a9e2f..19c6af69b18 100644 --- a/Content.Shared/Weapons/Ranged/Components/SolutionAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/SolutionAmmoProviderComponent.cs @@ -5,38 +5,38 @@ namespace Content.Shared.Weapons.Ranged.Components; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedGunSystem))] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), Access(typeof(SharedGunSystem))] public sealed partial class SolutionAmmoProviderComponent : Component { /// /// The solution where reagents are extracted from for the projectile. /// - [DataField("solutionId", required: true), AutoNetworkedField] + [DataField(required: true), AutoNetworkedField] public string SolutionId = default!; /// /// How much reagent it costs to fire once. /// - [DataField("fireCost"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public float FireCost = 5; /// /// The amount of shots currently available. /// used for network predictions. /// - [DataField("shots"), ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public int Shots; /// /// The max amount of shots the gun can fire. /// used for network prediction /// - [DataField("maxShots"), ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public int MaxShots; /// /// The prototype that's fired by the gun. /// - [DataField("proto", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)] - public string Prototype = default!; + [DataField("proto")] + public EntProtoId Prototype; } diff --git a/Content.Shared/Weapons/Ranged/Events/UpdateClientAmmoEvent.cs b/Content.Shared/Weapons/Ranged/Events/UpdateClientAmmoEvent.cs new file mode 100644 index 00000000000..57f731889a4 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Events/UpdateClientAmmoEvent.cs @@ -0,0 +1,4 @@ +namespace Content.Shared.Weapons.Ranged.Events; + +[ByRefEvent] +public readonly record struct UpdateClientAmmoEvent(); \ No newline at end of file diff --git a/Content.Shared/Weapons/Ranged/Systems/BatteryWeaponFireModesSystem.cs b/Content.Shared/Weapons/Ranged/Systems/BatteryWeaponFireModesSystem.cs index 8e44576d283..bae5b95a193 100644 --- a/Content.Shared/Weapons/Ranged/Systems/BatteryWeaponFireModesSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/BatteryWeaponFireModesSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Popups; using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.Prototypes; namespace Content.Shared.Weapons.Ranged.Systems; @@ -99,13 +100,21 @@ private void SetFireMode(EntityUid uid, BatteryWeaponFireModesComponent componen component.CurrentFireMode = index; Dirty(uid, component); - if (TryComp(uid, out ProjectileBatteryAmmoProviderComponent? projectileBatteryAmmoProvider)) + if (TryComp(uid, out ProjectileBatteryAmmoProviderComponent? projectileBatteryAmmoProviderComponent)) { if (!_prototypeManager.TryIndex(fireMode.Prototype, out var prototype)) return; - projectileBatteryAmmoProvider.Prototype = fireMode.Prototype; - projectileBatteryAmmoProvider.FireCost = fireMode.FireCost; + // TODO: Have this get the info directly from the batteryComponent when power is moved to shared. + var OldFireCost = projectileBatteryAmmoProviderComponent.FireCost; + projectileBatteryAmmoProviderComponent.Prototype = fireMode.Prototype; + projectileBatteryAmmoProviderComponent.FireCost = fireMode.FireCost; + float FireCostDiff = (float)fireMode.FireCost / (float)OldFireCost; + projectileBatteryAmmoProviderComponent.Shots = (int)Math.Round(projectileBatteryAmmoProviderComponent.Shots/FireCostDiff); + projectileBatteryAmmoProviderComponent.Capacity = (int)Math.Round(projectileBatteryAmmoProviderComponent.Capacity/FireCostDiff); + Dirty(uid, projectileBatteryAmmoProviderComponent); + var updateClientAmmoEvent = new UpdateClientAmmoEvent(); + RaiseLocalEvent(uid, ref updateClientAmmoEvent); if (user != null) { diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs index 9ef5cbd7028..044f75dbc90 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Ballistic.cs @@ -57,7 +57,7 @@ private void OnBallisticInteractUsing(EntityUid uid, BallisticAmmoProviderCompon Audio.PlayPredicted(component.SoundInsert, uid, args.User); args.Handled = true; UpdateBallisticAppearance(uid, component); - Dirty(uid, component); + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities)); } private void OnBallisticAfterInteract(EntityUid uid, BallisticAmmoProviderComponent component, AfterInteractEvent args) @@ -194,10 +194,9 @@ private void ManualCycle(EntityUid uid, BallisticAmmoProviderComponent component !Paused(uid)) { gunComp.NextFire = Timing.CurTime + TimeSpan.FromSeconds(1 / gunComp.FireRateModified); - Dirty(uid, gunComp); + DirtyField(uid, gunComp, nameof(GunComponent.NextFire)); } - Dirty(uid, component); Audio.PlayPredicted(component.SoundRack, uid, user); var shots = GetBallisticShots(component); @@ -228,7 +227,7 @@ private void OnBallisticMapInit(EntityUid uid, BallisticAmmoProviderComponent co { component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count); UpdateBallisticAppearance(uid, component); - Dirty(uid, component); + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount)); } } @@ -249,18 +248,19 @@ private void OnBallisticTakeAmmo(EntityUid uid, BallisticAmmoProviderComponent c args.Ammo.Add((entity, EnsureShootable(entity))); component.Entities.RemoveAt(component.Entities.Count - 1); + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.Entities)); Containers.Remove(entity, component.Container); } else if (component.UnspawnedCount > 0) { component.UnspawnedCount--; + DirtyField(uid, component, nameof(BallisticAmmoProviderComponent.UnspawnedCount)); entity = Spawn(component.Proto, args.Coordinates); args.Ammo.Add((entity, EnsureShootable(entity))); } } UpdateBallisticAppearance(uid, component); - Dirty(uid, component); } private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, ref GetAmmoCountEvent args) diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs index ecbd19d5173..d6e741fed6e 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs @@ -35,6 +35,7 @@ private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent co component.Shots = state.Shots; component.Capacity = state.MaxShots; component.FireCost = state.FireCost; + UpdateAmmoCount(uid, prediction: false); } private void OnBatteryGetState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentGetState args) @@ -80,7 +81,10 @@ private void OnBatteryAmmoCount(EntityUid uid, BatteryAmmoProviderComponent comp /// /// Update the battery (server-only) whenever fired. /// - protected virtual void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) {} + protected virtual void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component) + { + UpdateAmmoCount(uid, prediction: false); + } protected void UpdateBatteryAppearance(EntityUid uid, BatteryAmmoProviderComponent component) { diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index de61ff90fbe..1fa83c223ee 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -120,7 +120,7 @@ private void OnGunMelee(EntityUid uid, GunComponent component, MeleeHitEvent arg if (melee.NextAttack > component.NextFire) { component.NextFire = melee.NextAttack; - Dirty(uid, component); + EntityManager.DirtyField(uid, component, nameof(MeleeWeaponComponent.NextAttack)); } } @@ -202,7 +202,7 @@ private void StopShooting(EntityUid uid, GunComponent gun) gun.ShotCounter = 0; gun.ShootCoordinates = null; gun.Target = null; - Dirty(uid, gun); + EntityManager.DirtyField(uid, gun, nameof(GunComponent.ShotCounter)); } /// @@ -213,6 +213,7 @@ public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, Ent gun.ShootCoordinates = toCoordinates; AttemptShoot(user, gunUid, gun); gun.ShotCounter = 0; + EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.ShotCounter)); } /// @@ -233,7 +234,9 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) if (gun.FireRateModified <= 0f || !_actionBlockerSystem.CanAttack(user)) + { return; + } var toCoordinates = gun.ShootCoordinates; @@ -282,7 +285,7 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) } // NextFire has been touched regardless so need to dirty the gun. - Dirty(gunUid, gun); + EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.NextFire)); // Get how many shots we're actually allowed to make, due to clip size or otherwise. // Don't do this in the loop so we still reset NextFire. @@ -336,6 +339,7 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) // Even if we don't actually shoot update the ShotCounter. This is to avoid spamming empty sounds // where the gun may be SemiAuto or Burst. gun.ShotCounter += shots; + EntityManager.DirtyField(gunUid, gun, nameof(GunComponent.ShotCounter)); if (ev.Ammo.Count <= 0) { @@ -392,8 +396,6 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) if (_gravity.IsWeightless(user, userPhysics)) CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics); } - - Dirty(gunUid, gun); } public void Shoot( @@ -447,7 +449,7 @@ protected virtual void UpdateAmmoCount(EntityUid uid, bool prediction = true) {} protected void SetCartridgeSpent(EntityUid uid, CartridgeAmmoComponent cartridge, bool spent) { if (cartridge.Spent != spent) - Dirty(uid, cartridge); + DirtyField(uid, cartridge, nameof(CartridgeAmmoComponent.Spent)); cartridge.Spent = spent; Appearance.SetData(uid, AmmoVisuals.Spent, spent); @@ -546,17 +548,59 @@ public void RefreshModifiers(Entity gun) RaiseLocalEvent(gun, ref ev); - comp.SoundGunshotModified = ev.SoundGunshot; - comp.CameraRecoilScalarModified = ev.CameraRecoilScalar; - comp.AngleIncreaseModified = ev.AngleIncrease; - comp.AngleDecayModified = ev.AngleDecay; - comp.MaxAngleModified = ev.MaxAngle; - comp.MinAngleModified = ev.MinAngle; - comp.ShotsPerBurstModified = ev.ShotsPerBurst; - comp.FireRateModified = ev.FireRate; - comp.ProjectileSpeedModified = ev.ProjectileSpeed; - - Dirty(gun); + if (comp.SoundGunshotModified != ev.SoundGunshot) + { + comp.SoundGunshotModified = ev.SoundGunshot; + DirtyField(gun, nameof(GunComponent.SoundGunshotModified)); + } + + if (!MathHelper.CloseTo(comp.CameraRecoilScalarModified, ev.CameraRecoilScalar)) + { + comp.CameraRecoilScalarModified = ev.CameraRecoilScalar; + DirtyField(gun, nameof(GunComponent.CameraRecoilScalarModified)); + } + + if (!comp.AngleIncreaseModified.EqualsApprox(ev.AngleIncrease)) + { + comp.AngleIncreaseModified = ev.AngleIncrease; + DirtyField(gun, nameof(GunComponent.AngleIncreaseModified)); + } + + if (!comp.AngleDecayModified.EqualsApprox(ev.AngleDecay)) + { + comp.AngleDecayModified = ev.AngleDecay; + DirtyField(gun, nameof(GunComponent.AngleDecayModified)); + } + + if (!comp.MaxAngleModified.EqualsApprox(ev.MinAngle)) + { + comp.MaxAngleModified = ev.MaxAngle; + DirtyField(gun, nameof(GunComponent.MaxAngleModified)); + } + + if (!comp.MinAngleModified.EqualsApprox(ev.MinAngle)) + { + comp.MinAngleModified = ev.MinAngle; + DirtyField(gun, nameof(GunComponent.MinAngleModified)); + } + + if (comp.ShotsPerBurstModified != ev.ShotsPerBurst) + { + comp.ShotsPerBurstModified = ev.ShotsPerBurst; + DirtyField(gun, nameof(GunComponent.ShotsPerBurstModified)); + } + + if (!MathHelper.CloseTo(comp.FireRateModified, ev.FireRate)) + { + comp.FireRateModified = ev.FireRate; + DirtyField(gun, nameof(GunComponent.FireRateModified)); + } + + if (!MathHelper.CloseTo(comp.ProjectileSpeedModified, ev.ProjectileSpeed)) + { + comp.ProjectileSpeedModified = ev.ProjectileSpeed; + DirtyField(gun, nameof(GunComponent.ProjectileSpeedModified)); + } } protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs index df29a94aa63..7370ec6aa6d 100644 --- a/Content.Shared/Whitelist/EntityWhitelistSystem.cs +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -50,10 +50,12 @@ public bool IsValid(EntityWhitelist list, EntityUid uid) { if (list.Components != null) { - var regs = StringsToRegs(list.Components); - - list.Registrations ??= new List(); - list.Registrations.AddRange(regs); + if (list.Registrations == null) + { + var regs = StringsToRegs(list.Components); + list.Registrations = new List(); + list.Registrations.AddRange(regs); + } } if (list.MindRoles != null) diff --git a/Resources/Audio/Items/Toys/attributions.yml b/Resources/Audio/Items/Toys/attributions.yml index 162ee8c1c33..cd767ff3d6b 100644 --- a/Resources/Audio/Items/Toys/attributions.yml +++ b/Resources/Audio/Items/Toys/attributions.yml @@ -58,6 +58,11 @@ copyright: "Created by BlackMajor (github) for ss14, muffled_weh.ogg modified from weh.ogg by Slorkito (ss14 discord)" source: "https://github.com/space-wizards/space-station-14/blob/master/Resources/Audio/Items/Toys/weh.ogg" +- files: ["hew.ogg"] + license: "CC0-1.0" + copyright: "Modified from weh.ogg by ArtisticRoomba" + source: "https://github.com/space-wizards/space-station-14/blob/master/Resources/Audio/Items/Toys/weh.ogg" + - files: ["skub.ogg", "skub3.ogg"] license: "CC0-1.0" copyright: "Created by BlackMajor (github) for ss14" diff --git a/Resources/Audio/Items/Toys/hew.ogg b/Resources/Audio/Items/Toys/hew.ogg new file mode 100644 index 00000000000..63ba8087708 Binary files /dev/null and b/Resources/Audio/Items/Toys/hew.ogg differ diff --git a/Resources/Audio/Machines/attributions.yml b/Resources/Audio/Machines/attributions.yml index bcbf1036c32..fe144d43381 100644 --- a/Resources/Audio/Machines/attributions.yml +++ b/Resources/Audio/Machines/attributions.yml @@ -172,6 +172,11 @@ license: "CC0-1.0" copyright: "by Ko4erga" source: "https://github.com/space-wizards/space-station-14/pull/30431" + +- files: ["double_ring.ogg"] + license: "CC0-1.0" + copyright: "Created by fspera, converted to OGG and modified by chromiumboy." + source: "https://freesound.org/people/fspera/sounds/528111/" - files: - airlock_emergencyoff.ogg diff --git a/Resources/Audio/Machines/double_ring.ogg b/Resources/Audio/Machines/double_ring.ogg new file mode 100644 index 00000000000..bfe1b21f410 Binary files /dev/null and b/Resources/Audio/Machines/double_ring.ogg differ diff --git a/Resources/Audio/StationEvents/attribution.txt b/Resources/Audio/StationEvents/attribution.txt deleted file mode 100644 index 84a99426d3b..00000000000 --- a/Resources/Audio/StationEvents/attribution.txt +++ /dev/null @@ -1 +0,0 @@ -countdown.ogg is created by qwertyquerty and licensed under CC-BY-SA-3.0. It is taken from https://github.com/BeeStation/BeeStation-Hornet at commit 79b8cc23cfb347cf23ea70fc5f6f39afedf9cde7. diff --git a/Resources/Audio/StationEvents/attributions.yml b/Resources/Audio/StationEvents/attributions.yml index 6ddd1d74d68..1cfe9a8d530 100644 --- a/Resources/Audio/StationEvents/attributions.yml +++ b/Resources/Audio/StationEvents/attributions.yml @@ -3,11 +3,6 @@ copyright: "Created by Bolgarich" source: "https://www.youtube.com/watch?v=SzEp2nv6oZ4" -- files: ["clearly_nuclear.ogg"] - license: "CC-BY-3.0" - copyright: "Created by mryikes" - source: "https://www.youtube.com/watch?v=chix8uz-oUQ" - - files: ["sound_station_14.ogg"] license: "CC-BY-3.0" copyright: "Created by Donchan" @@ -17,3 +12,8 @@ license: "CC-BY-3.0" copyright: "Created by mrjajkes" source: "https://www.youtube.com/watch?v=gLSyt7AaqQM" + +- files: ["countdown.ogg"] + license: "CC-BY-SA-3.0" + copyright: "Created by qwertyquerty" + source: "https://github.com/BeeStation/BeeStation-Hornet/commit/7f48971438492805af286023a72e7609a729234a" diff --git a/Resources/Audio/StationEvents/clearly_nuclear.ogg b/Resources/Audio/StationEvents/clearly_nuclear.ogg deleted file mode 100644 index 2c8b18e1b14..00000000000 Binary files a/Resources/Audio/StationEvents/clearly_nuclear.ogg and /dev/null differ diff --git a/Resources/Changelog/Admin.yml b/Resources/Changelog/Admin.yml index 95fa1cf8421..fcdfdaf0fe1 100644 --- a/Resources/Changelog/Admin.yml +++ b/Resources/Changelog/Admin.yml @@ -627,5 +627,35 @@ Entries: id: 78 time: '2024-11-30T20:11:44.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/31836 +- author: slarticodefast + changes: + - message: Health scanning a slime as an aghost will no longer insert the scanner + into their inventory. + type: Fix + id: 79 + time: '2024-12-18T00:12:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33884 +- author: slarticodefast + changes: + - message: The panic bunker now only automatically disables when an admin with the + 'Admin' permission is online instead of any permission. + type: Tweak + id: 80 + time: '2024-12-18T13:15:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33879 +- author: ElectroJr + changes: + - message: Some toolshed command permissions have changed. The "emplace" and "iterate" + command no longer require QUER` permissions, they now match the "do" command + and only require DEBUG. Several previously unrestricted commands now require + the DEBUG flag. + type: Tweak + - message: Toolshed command parsing has changed. In particular, command substitution + (using the output of a command in place of another command's argument) now requires + that the command be wrapped in curly braces + type: Tweak + id: 81 + time: '2024-12-21T07:02:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33980 Name: Admin Order: 3 diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index ba1af71d159..ba5c69e0cab 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -1,441 +1,4 @@ Entries: -- author: lzk228 - changes: - - message: Added second jester suit and hat in clown loadouts. - type: Add - id: 7186 - time: '2024-08-23T01:29:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30673 -- author: Dutch-VanDerLinde - changes: - - message: Added the clown skirt - type: Add - id: 7187 - time: '2024-08-23T04:59:18.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31207 -- author: Aquif - changes: - - message: Boiling Vox blood results in ammonia. - type: Add - id: 7188 - time: '2024-08-23T05:05:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30749 -- author: Sarahon - changes: - - message: Brand new emote sprites and colours to the Y menu. - type: Add - id: 7189 - time: '2024-08-23T05:08:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30887 -- author: jimmy12or - changes: - - message: Added a recipe for cream. Heat some milk and shake some air into it. - type: Add - id: 7190 - time: '2024-08-23T05:13:14.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30503 -- author: SlamBamActionman - changes: - - message: Codewords are now highlighted for traitors. - type: Add - id: 7191 - time: '2024-08-23T09:14:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30092 -- author: SlamBamActionman - changes: - - message: Janitor's galoshes now apply slowdown over slippery surfaces, and has - a max slowdown cap over sticky surfaces. - type: Tweak - id: 7192 - time: '2024-08-23T09:59:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30967 -- author: coolsurf6 - changes: - - message: Increased the maximum number of reptilian chest markings to 3. - type: Tweak - id: 7193 - time: '2024-08-23T14:24:06.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30786 -- author: FATFSAAM2 - changes: - - message: Added 3 new voicelines for the boxer figurine. - type: Add - id: 7194 - time: '2024-08-24T00:10:10.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31382 -- author: Winkarst-cpu - changes: - - message: The damage dealt by the folded chair can now be inspected. - type: Fix - id: 7195 - time: '2024-08-24T00:16:26.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31378 -- author: EmoGarbage404 - changes: - - message: The mining asteroid dungeons now spawn with more equipment, scrap, and - treasure in them. - type: Add - id: 7196 - time: '2024-08-24T02:06:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31290 -- author: EmoGarbage404 - changes: - - message: The salvage magnet can now pull in large chunks of space debris. Be wary - of space carp infestations! - type: Add - - message: The small asteroids and pieces of debris that generated around the station - have been removed. - type: Remove - - message: The salvage magnet now randomly picks what type of pulls will be offered - instead of always having a consistent number of each. - type: Tweak - id: 7197 - time: '2024-08-24T02:09:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31113 -- author: Winkarst-cpu - changes: - - message: Now you can move the pointer in chat by holding down the arrow keys. - type: Fix - id: 7198 - time: '2024-08-24T09:37:30.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31380 -- author: erohrs2 - changes: - - message: Dinnerware Vending Machine access changed from "Service" to "Kitchen". - type: Tweak - id: 7199 - time: '2024-08-24T15:56:44.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31225 -- author: DevilishMilk - changes: - - message: Moths and mothroaches are now able to eat bandanas. - type: Tweak - id: 7200 - time: '2024-08-24T23:30:33.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31405 -- author: PJB3005 - changes: - - message: Fix the ChemVend jug names again - type: Fix - id: 7201 - time: '2024-08-25T02:02:34.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31398 -- author: metalgearsloth - changes: - - message: Fix grids sometimes overlapping on roundstart. - type: Fix - id: 7202 - time: '2024-08-25T04:48:29.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31413 -- author: themias - changes: - - message: Thin firelocks now respect rotation when built - type: Fix - id: 7203 - time: '2024-08-25T04:57:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31371 -- author: PopGamer46 - changes: - - message: Security cadets now spawn with jackboots instead of combat boots - type: Tweak - id: 7205 - time: '2024-08-25T06:58:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31419 -- author: ArtisticRoomba - changes: - - message: Added the greytide stamp. This stamp can be rarely found in maints lockers. - type: Add - id: 7206 - time: '2024-08-25T10:35:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30189 -- author: EmoGarbage404 - changes: - - message: Added blueprints! These can be found by salvagers and inserted into an - autolathe in order to unlock new recipes. - type: Add - id: 7207 - time: '2024-08-25T12:06:50.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31138 -- author: deltanedas - changes: - - message: Fixed borgs losing access when they run out of power. - type: Fix - id: 7208 - time: '2024-08-25T12:17:03.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31392 -- author: metalgearsloth - changes: - - message: Actions now activate on key-down, not key-up. - type: Tweak - id: 7209 - time: '2024-08-25T12:36:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31191 -- author: PJB3005 - changes: - - message: PACMAN and SUPERPACMAN now ramp their power output significantly faster. - type: Tweak - id: 7210 - time: '2024-08-25T16:11:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31403 -- author: Blackern5000 - changes: - - message: Space scanning technology is now T1 industrial, this includes cyborg - GPS modules and handheld mass scanners. - type: Tweak - id: 7211 - time: '2024-08-25T16:47:11.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31321 -- author: ShadowCommander - changes: - - message: Rollerbeds no longer buckle yourself when clicked on. - type: Remove - id: 7212 - time: '2024-08-25T17:09:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30001 -- author: lzk228 - changes: - - message: Cotton dough added to the game! Check the guidebook for new recipes. - type: Add - id: 7213 - time: '2024-08-26T02:46:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30668 -- author: Moomoobeef - changes: - - message: Silicons like medbots, janibots, and honkbots now make sound when speaking. - type: Fix - id: 7214 - time: '2024-08-26T09:09:49.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31471 -- author: Winkarst-cpu - changes: - - message: The color of the science radio channel was changed. - type: Tweak - id: 7215 - time: '2024-08-26T12:02:57.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31319 -- author: slarticodefast - changes: - - message: Fixed energy sword visuals. - type: Fix - id: 7216 - time: '2024-08-26T13:00:52.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31478 -- author: CuteBoi - changes: - - message: Replaced small fans on most shuttles with directional fans. - type: Fix - id: 7217 - time: '2024-08-26T22:24:22.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31495 -- author: 12rabbits - changes: - - message: The guidebook now remembers where you left off when re-opened. - type: Tweak - id: 7218 - time: '2024-08-26T23:06:54.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31375 -- author: Dimastra - changes: - - message: Fixed meat kudzu not dealing damage. - type: Fix - id: 7219 - time: '2024-08-27T00:30:42.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31494 -- author: JIPDawg - changes: - - message: Gas miners are now indestructible, can be pulled and only anchored. - type: Tweak - id: 7220 - time: '2024-08-27T00:48:04.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31370 -- author: SaphireLattice - changes: - - message: Safety Moth poster graphics for hardhats and pipes are no longer swapped - around - type: Fix - id: 7221 - time: '2024-08-27T11:32:53.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31507 -- author: Winkarst-cpu - changes: - - message: Explosive ammunition is now marked as a contraband. - type: Fix - id: 7222 - time: '2024-08-27T11:37:20.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31508 -- author: Winkarst-cpu - changes: - - message: Now the syndicate raid helmet is marked as a Syndicate contraband. - type: Fix - id: 7223 - time: '2024-08-27T13:01:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31512 -- author: Winkarst-cpu - changes: - - message: The explorer gas mask is now restricted to the cargo. - type: Fix - id: 7224 - time: '2024-08-27T13:19:38.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31514 -- author: Aeshus - changes: - - message: The Health Analyzer now displays the patient's picture, species, and - current status. - type: Add - id: 7225 - time: '2024-08-27T14:57:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30834 -- author: Aeshus - changes: - - message: Cryosleep no longer gives arrival shuttle directions. - type: Fix - id: 7226 - time: '2024-08-27T15:02:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30888 -- author: Winkarst-cpu - changes: - - message: Nukie plushie is now not a contraband item. - type: Fix - id: 7227 - time: '2024-08-27T16:49:17.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31516 -- author: Winkarst-cpu - changes: - - message: Now AKMS is restricted to the security department. - type: Fix - id: 7228 - time: '2024-08-27T16:54:48.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31519 -- author: metalgearsloth - changes: - - message: Added warp points for AI. - type: Add - id: 7229 - time: '2024-08-28T05:58:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31559 -- author: metalgearsloth - changes: - - message: Fix AI being ejectable. - type: Fix - id: 7230 - time: '2024-08-28T07:09:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31561 -- author: metalgearsloth - changes: - - message: Fix whitelist - type: Fix - id: 7231 - time: '2024-08-28T07:11:25.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31563 -- author: metalgearsloth - changes: - - message: Add shutters, windoors, etc to AI interaction whitelist. - type: Tweak - id: 7232 - time: '2024-08-28T07:39:36.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31564 -- author: lunarcomets - changes: - - message: updated AI job icon - type: Tweak - id: 7233 - time: '2024-08-28T08:18:51.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31565 -- author: lzk228 - changes: - - message: Added black suspenders for mime. - type: Add - id: 7234 - time: '2024-08-28T09:43:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29055 -- author: saintmuntzer - changes: - - message: Riot helmet now matches security helmet colors. - type: Fix - id: 7235 - time: '2024-08-28T11:27:09.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31530 -- author: coolboy911 - changes: - - message: wide-spectrum anomaly locator is now included in cyborg's anomaly module - type: Add - id: 7236 - time: '2024-08-28T12:36:31.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31427 -- author: deltanedas - changes: - - message: You can now build carp statues with luxury materials. - type: Add - id: 7237 - time: '2024-08-28T13:08:55.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31261 -- author: PopGamer46 - changes: - - message: Fixed shuttles not being able to FTL onto the station - type: Fix - id: 7238 - time: '2024-08-28T13:22:21.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31569 -- author: themias - changes: - - message: Defibs batteries no longer drain when switched off - type: Fix - id: 7239 - time: '2024-08-28T17:31:47.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31593 -- author: themias - changes: - - message: Fixed the nuke disk being marked 'left behind' when escaping with it - on the shuttle - type: Fix - id: 7240 - time: '2024-08-28T20:05:05.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31602 -- author: Beck Thompson - changes: - - message: Gold and silver ring, gold and silver diamond ring, gold and silver gem - ring. They all can be obtained in salvage loot pools. - type: Add - id: 7241 - time: '2024-08-28T20:48:46.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31372 -- author: RumiTiger - changes: - - message: Cherry has been added to the game! - type: Add - - message: The recipe for cherry pie has been reintroduced to the game! - type: Add - id: 7242 - time: '2024-08-29T01:30:59.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/28962 -- author: SlamBamActionman, Graded - changes: - - message: Added administration glasses to Captain and HoP lockers! - type: Add - id: 7243 - time: '2024-08-29T01:58:16.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/30447 -- author: osjarw - changes: - - message: Fix air alarms not checking sensor states upon power returning. - type: Fix - id: 7244 - time: '2024-08-29T02:43:27.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/29857 -- author: Winkarst-cpu - changes: - - message: Now railings render over tables. - type: Tweak - id: 7245 - time: '2024-08-29T03:04:43.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31589 -- author: metalgearsloth - changes: - - message: Fix storage UI being buggy. - type: Fix - id: 7246 - time: '2024-08-29T03:23:37.0000000+00:00' - url: https://github.com/space-wizards/space-station-14/pull/31616 - author: MisterMecky changes: - message: Changed strange pill possible reagents. They are no longer mostly composed @@ -3897,3 +3460,481 @@ id: 7685 time: '2024-12-07T05:58:23.0000000+00:00' url: https://github.com/space-wizards/space-station-14/pull/33752 +- author: PJB3005 + changes: + - message: Silicons now have a proper preview in the lobby/character editor. + type: Tweak + id: 7686 + time: '2024-12-08T01:46:41.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33763 +- author: PJB3005 + changes: + - message: You can now use Interact (E by default) to toggle radiation collectors, + field generators, and emitters. Previously you had to click with an empty hand. + type: Fix + id: 7687 + time: '2024-12-08T22:37:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33762 +- author: ArtisticRoomba + changes: + - message: Detectives are now required to start with outer clothing (armor) like + all other security roles. + type: Tweak + - message: Vox Detectives now spawn with their chosen outer clothing equipped. Previously + the tank harness would override this and your armor would start on the floor. + type: Fix + id: 7688 + time: '2024-12-08T22:50:14.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33765 +- author: slarticodefast + changes: + - message: Fixed pipes still containing gas after unanchoring them. + type: Fix + id: 7689 + time: '2024-12-08T23:20:05.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33774 +- author: Errant + changes: + - message: Signal timers now have a maximum countdown duration, which can be specified + in yaml. The default maximum is one hour, to prevent the displayed time from + overflowing into hh:mm . + type: Tweak + id: 7690 + time: '2024-12-09T03:41:11.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33781 +- author: Plykiya + changes: + - message: You are now notified of who is pulling you when being pulled. + type: Add + id: 7691 + time: '2024-12-09T03:51:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33612 +- author: Bhijn and Myr + changes: + - message: The Admin Message system now contains an introductory message as the + very first message shown to a user, before any messages are written or received. + This message serves primarily to remind ahelping players (whom are likely to + be experiencing strong emotion) to remember that admins are humans, too. + type: Add + id: 7692 + time: '2024-12-09T08:54:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33348 +- author: ScarKy0 + changes: + - message: You can now pet the AI Core. + type: Add + id: 7693 + time: '2024-12-09T16:10:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33788 +- author: joshepvodka + changes: + - message: Added flavor profiles to beverages that missed them. + type: Add + - message: Tweaked some drink flavor profiles. + type: Tweak + - message: Soda/Booze dispenser will now always eject the shaker/glass before any + bottle or jug. + type: Tweak + id: 7694 + time: '2024-12-09T17:12:57.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33505 +- author: ArtisticRoomba + changes: + - message: SMES now gives visual feedback for if the maintenance panel is open. + type: Fix + id: 7695 + time: '2024-12-10T10:00:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33808 +- author: metalgearsloth + changes: + - message: Fix blank newlines on examine tooltips (e.g. tables). + type: Fix + id: 7696 + time: '2024-12-10T13:45:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33813 +- author: slarticodefast + changes: + - message: Fixed the greytide virus event affecting non-station maps. + type: Fix + id: 7697 + time: '2024-12-11T14:30:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33806 +- author: metalgearsloth + changes: + - message: Fix battery self-recharging items like the antique laser gun from mispredicting. + type: Fix + id: 7698 + time: '2024-12-11T15:58:02.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33384 +- author: BramvanZijp + changes: + - message: The Head of Security's Energy Shotgun has regained its ability to self + recharge, but only if the weapon has not been discharged in the last 10 seconds. + type: Add + - message: The Head of Security's Energy Shotgun now has a varying amount of shots + depending on the firing mode. + type: Tweak + - message: Energy based weapons with differing energy consumption depending on the + fire-mode will now update the ammo counter on the HUD when switching between + fire-modes. + type: Fix + id: 7699 + time: '2024-12-11T16:21:04.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32104 +- author: Winkarst-cpu + changes: + - message: The A.P.E. guidebook entry now contains information about Sigma particles + and transformation effects. + type: Tweak + id: 7700 + time: '2024-12-12T14:24:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33558 +- author: lzk228 + changes: + - message: Disabled evac after declaring nuke war is increased from 15 to 25 minutes. + type: Tweak + id: 7701 + time: '2024-12-13T03:10:21.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33628 +- author: LevitatingTree + changes: + - message: Lizards can now eat the Five Alarm Burger! + type: Fix + id: 7702 + time: '2024-12-13T13:53:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33848 +- author: Southbridge + changes: + - message: Paper documents now support the [mono] tag. + type: Add + id: 7703 + time: '2024-12-13T14:24:35.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33830 +- author: yuitop + changes: + - message: Anchoring now has higher priority over stashing. + type: Tweak + id: 7704 + time: '2024-12-13T23:00:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31779 +- author: Nimfar11 + changes: + - message: Changes the colour of the Borg binary channel to a different shade of + blue. + type: Tweak + id: 7705 + time: '2024-12-14T16:03:18.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33841 +- author: Litraxx + changes: + - message: Renamed the Dungeon Master lawset to Game Master lawset + type: Fix + id: 7706 + time: '2024-12-16T11:52:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33678 +- author: psykana + changes: + - message: Zombies can now see initial infected + type: Add + id: 7707 + time: '2024-12-16T11:53:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33665 +- author: Plykiya + changes: + - message: Bombs like stingers and clustergrenades now trigger when destroyed due + to damage. + type: Fix + id: 7708 + time: '2024-12-16T12:08:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31108 +- author: Beck Thompson + changes: + - message: Added some new scrap that contains uranium and plasma that can be found + when salvaging! + type: Add + id: 7709 + time: '2024-12-16T12:33:34.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32198 +- author: Sirionaut, SpaceBaa + changes: + - message: Animals no longer consume nutrients when their udder/woolcoat is full. + type: Fix + - message: Entities with udders display a rough hunger level when examined. + type: Add + id: 7710 + time: '2024-12-16T12:53:22.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32905 +- author: thetolbean + changes: + - message: Teleporting or dashing now causes you to stop pulling objects. + type: Tweak + id: 7711 + time: '2024-12-16T14:09:20.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33252 +- author: KieueCaprie + changes: + - message: The lizard plushie will now be visible when held in your hands! + type: Add + id: 7712 + time: '2024-12-16T14:14:51.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32583 +- author: Bhijn and Myr + changes: + - message: Admins no longer count towards the playercount cap, meaning you no longer + need to wait for multiple people to leave in order to join after an admin has + joined. + type: Tweak + id: 7713 + time: '2024-12-16T14:19:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33424 +- author: dragonryan06 + changes: + - message: 'New cocktail: the Zombie.' + type: Add + id: 7714 + time: '2024-12-16T15:25:06.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32802 +- author: SlamBamActionman + changes: + - message: Added Holy damage to the Chaplain's bible, holy water and the gohei, + which can be exclusively dealt to spirits. + type: Add + id: 7715 + time: '2024-12-16T15:33:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32755 +- author: Vexerot + changes: + - message: Security Belts and Security Carriers now provide 10% reduced explosion + damage to contents. + type: Tweak + id: 7716 + time: '2024-12-16T16:27:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33253 +- author: goet + changes: + - message: Spaceshrooms can now be cooked on the electric grill. + type: Tweak + id: 7717 + time: '2024-12-16T17:53:39.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31872 +- author: Beck Thompson + changes: + - message: Figures can now be activated with signals! + type: Add + id: 7718 + time: '2024-12-16T18:52:09.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32769 +- author: TheShuEd + changes: + - message: Christmas anomaly added + type: Add + - message: Christmas anomaly infection added + type: Add + id: 7719 + time: '2024-12-16T19:23:32.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33889 +- author: chromiumboy + changes: + - message: Added the atmospheric network monitor. Use these consoles to monitor + the network of pipes that supply the crew with a breathable atmosphere + type: Add + id: 7720 + time: '2024-12-17T03:53:17.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32294 +- author: TheShuEd + changes: + - message: Added new map Gate + type: Add + id: 7721 + time: '2024-12-17T10:06:33.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32032 +- author: pcaessayrs + changes: + - message: Anomaly hosts keep their anomaly when turning into a zombie. + type: Tweak + id: 7722 + time: '2024-12-17T11:56:47.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33867 +- author: TiniestShark + changes: + - message: Adds in-hand sprites to the Anomaly Scanner. + type: Add + id: 7723 + time: '2024-12-17T13:35:29.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33427 +- author: chromiumboy + changes: + - message: Holopads are now available, allowing you to communicate holographically + with the rest of the crew + type: Add + id: 7724 + time: '2024-12-17T19:18:15.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32711 +- author: Golinth + changes: + - message: AMEs larger than 2 cores now produce more power, instead of less. + type: Tweak + id: 7725 + time: '2024-12-17T23:56:03.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32825 +- author: ArtisticRoomba, AugustSun + changes: + - message: New Advanced SMES, an SMES with a 16 MJ capacity. It can be researched + at Industrial T2, Advanced Powercells. + type: Add + id: 7726 + time: '2024-12-17T23:58:54.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33757 +- author: Allen, lzk228 + changes: + - message: Made Gun Safes a craftable object (10 steel, 5 lv cable, 10 plasteel) + type: Add + id: 7727 + time: '2024-12-18T11:26:19.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32694 +- author: MilenVolf + changes: + - message: Clicking on a buckled mob now unbuckles it, rather than hugging/petting + it. + type: Fix + id: 7728 + time: '2024-12-18T14:26:58.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33635 +- author: SlamBamActionman + changes: + - message: Slimes no longer deal Cellular damage. + type: Tweak + id: 7729 + time: '2024-12-18T15:05:16.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33104 +- author: Redbookcase + changes: + - message: Standardized Mercenary gear's contraband status. + type: Tweak + id: 7730 + time: '2024-12-18T15:13:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33647 +- author: ArtisticRoomba + changes: + - message: New plushie, named "drazil plushie". Based on an inverted lizard plushie. + It can be rarely found in maints lockers. Hew! + type: Add + - message: New reagent, Juice that makes you Hew. It can be made from juicing inverted + lizard plushies. Hew! + type: Add + id: 7731 + time: '2024-12-18T16:31:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33776 +- author: FairlySadPanda August-Sun Eric156 + changes: + - message: Smite Cranberry is now available wherever lemon-lime soda is sold, just + for the holidays. This includes a certain jolly anomaly. + type: Add + id: 7732 + time: '2024-12-19T00:11:59.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33922 +- author: Crotalus, ArtisticRoomba + changes: + - message: Battery levels of applicable devices are now shown in the Power Monitoring + Console! + type: Add + id: 7733 + time: '2024-12-19T00:46:36.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33854 +- author: TytosB + changes: + - message: 'added new mid pop map: loop' + type: Add + id: 7734 + time: '2024-12-19T09:38:23.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33697 +- author: Centronias + changes: + - message: Fixed a bug where the B input on logic gates would get stuck in a high + state + type: Fix + id: 7735 + time: '2024-12-19T11:31:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33792 +- author: Plykiya + changes: + - message: Cargo armor crate now correctly indicates that it contains bulletproof + vests. + type: Fix + id: 7736 + time: '2024-12-19T17:21:28.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33414 +- author: Piras314 + changes: + - message: Removed the nuke song "Clearly Nuclear" on request of the author. + type: Remove + id: 7737 + time: '2024-12-20T14:35:55.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33971 +- author: VlaDOS1408 + changes: + - message: The Communication Console UI is now resizable. + type: Fix + id: 7738 + time: '2024-12-20T16:17:13.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33655 +- author: RatIRL + changes: + - message: Added All-Hostile ballistic turret. + type: Add + id: 7739 + time: '2024-12-20T20:34:53.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33970 +- author: Beck Thompson + changes: + - message: Defibrillator cooldowns are now clearly visible. + type: Tweak + id: 7740 + time: '2024-12-20T21:26:56.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/31251 +- author: J C Denton + changes: + - message: Syndicate and NanoTrasen NPCs and turrets are now hostile to Dragons + and Carps + type: Tweak + id: 7741 + time: '2024-12-20T21:38:27.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32515 +- author: lzk228 + changes: + - message: Item's tool function now shows on examine. + type: Add + id: 7742 + time: '2024-12-20T22:15:40.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32436 +- author: Pinkbat5 + changes: + - message: Diona can now chirp. + type: Add + - message: Nymphs can scream and laugh like adult diona. + type: Add + id: 7743 + time: '2024-12-20T22:34:44.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/32511 +- author: ScarKy0 + changes: + - message: Syndicate uplinks now have 6 discounts instead of 3. + type: Tweak + id: 7744 + time: '2024-12-21T02:14:08.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33950 +- author: JustinWinningham + changes: + - message: Wood walls no longer require girders and are built from barricades instead. + type: Tweak + id: 7745 + time: '2024-12-21T03:28:46.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33902 +- author: ElectroJr + changes: + - message: Toolshed console commands have been reworked. Some old commands may no + longer work. + type: Tweak + id: 7746 + time: '2024-12-21T06:45:48.0000000+00:00' + url: https://github.com/space-wizards/space-station-14/pull/33598 diff --git a/Resources/Locale/en-US/administration/bwoink.ftl b/Resources/Locale/en-US/administration/bwoink.ftl index e43932dc1d3..5e26f95735f 100644 --- a/Resources/Locale/en-US/administration/bwoink.ftl +++ b/Resources/Locale/en-US/administration/bwoink.ftl @@ -3,9 +3,15 @@ bwoink-user-title = Admin Message bwoink-system-starmute-message-no-other-users = *System: Nobody is available to receive your message. Try pinging Game Admins on Discord. bwoink-system-messages-being-relayed-to-discord = - Your messages are being relayed to the admins via Discord. + All messages are relayed to game administrators via Discord. Issues may be handled without a response. +bwoink-system-introductory-message = + Please describe the issue that you have encountered in detail. Assume that the game administrator who is resolving the problem does not have first-hand knowledge of what has occurred. + Please do not ask for special events or punishments for other players. + Any bugs and other related issues should be reported through Discord or Github. + Misuse of this message system may result in disciplinary action. + bwoink-system-typing-indicator = {$players} {$count -> [one] is *[other] are diff --git a/Resources/Locale/en-US/animals/udder/udder-system.ftl b/Resources/Locale/en-US/animals/udder/udder-system.ftl index 8479ae08bff..959a4fef591 100644 --- a/Resources/Locale/en-US/animals/udder/udder-system.ftl +++ b/Resources/Locale/en-US/animals/udder/udder-system.ftl @@ -5,3 +5,9 @@ udder-system-success = You fill {THE($target)} with {$amount}u from the udder. udder-system-dry = The udder is dry. udder-system-verb-milk = Milk + +udder-system-examine-overfed = {CAPITALIZE(SUBJECT($entity))} looks stuffed! +udder-system-examine-okay = {CAPITALIZE(SUBJECT($entity))} looks content. +udder-system-examine-hungry = {CAPITALIZE(SUBJECT($entity))} looks hungry. +udder-system-examine-starved = {CAPITALIZE(SUBJECT($entity))} looks starved! +udder-system-examine-none = {CAPITALIZE(SUBJECT($entity))} seems not to get hungry. diff --git a/Resources/Locale/en-US/anomaly/inner_anomaly.ftl b/Resources/Locale/en-US/anomaly/inner_anomaly.ftl index e55c4391e30..f5e8140c5ee 100644 --- a/Resources/Locale/en-US/anomaly/inner_anomaly.ftl +++ b/Resources/Locale/en-US/anomaly/inner_anomaly.ftl @@ -8,6 +8,7 @@ inner-anomaly-start-message-flesh = Your body is growing frantically. You became inner-anomaly-start-message-grav = Everything becames unnaturally heavy and light at the same time... You became the host of a gravity anomaly. inner-anomaly-start-message-tech = Your head is buzzing with the amount of chaotic information! You became the host of a tech anomaly. inner-anomaly-start-message-rock = The crystals are growing through your bones! You became the host of a rock anomaly. +inner-anomaly-start-message-santa = You're becoming obsessed with the Christmas spirit! You became the host of a Christmas anomaly. inner-anomaly-end-message = The abnormal activity within you disappears without a trace.... diff --git a/Resources/Locale/en-US/armor/armor-examine.ftl b/Resources/Locale/en-US/armor/armor-examine.ftl index d49a1373f28..3080c3859df 100644 --- a/Resources/Locale/en-US/armor/armor-examine.ftl +++ b/Resources/Locale/en-US/armor/armor-examine.ftl @@ -17,3 +17,4 @@ armor-damage-type-cold = Cold armor-damage-type-poison = Poison armor-damage-type-shock = Shock armor-damage-type-structural = Structural +armor-damage-type-holy = Holy diff --git a/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl index 470a8f86952..dd9b6a01282 100644 --- a/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl +++ b/Resources/Locale/en-US/atmos/atmos-alerts-console.ftl @@ -10,6 +10,9 @@ atmos-alerts-window-tab-fire-alarms = Fire alarms atmos-alerts-window-alarm-label = {CAPITALIZE($name)} ({$address}) atmos-alerts-window-temperature-label = Temperature atmos-alerts-window-temperature-value = {$valueInC} °C ({$valueInK} K) +atmos-alerts-window-invalid-value = N/A +atmos-alerts-window-total-mol-label = Total moles +atmos-alerts-window-total-mol-value = {$value} mol atmos-alerts-window-pressure-label = Pressure atmos-alerts-window-pressure-value = {$value} kPa atmos-alerts-window-oxygenation-label = Oxygenation diff --git a/Resources/Locale/en-US/atmos/gases.ftl b/Resources/Locale/en-US/atmos/gases.ftl new file mode 100644 index 00000000000..5c540c46dfc --- /dev/null +++ b/Resources/Locale/en-US/atmos/gases.ftl @@ -0,0 +1,10 @@ +gas-ammonia-abbreviation = NH₃ +gas-carbon-dioxide-abbreviation = CO₂ +gas-frezon-abbreviation = F +gas-nitrogen-abbreviation = N₂ +gas-nitrous-oxide-abbreviation = N₂O +gas-oxygen-abbreviation = O₂ +gas-plasma-abbreviation = P +gas-tritium-abbreviation = T +gas-water-vapor-abbreviation = H₂O +gas-unknown-abbreviation = X diff --git a/Resources/Locale/en-US/chat/emotes.ftl b/Resources/Locale/en-US/chat/emotes.ftl index 8c74acafca2..479e9daab4a 100644 --- a/Resources/Locale/en-US/chat/emotes.ftl +++ b/Resources/Locale/en-US/chat/emotes.ftl @@ -17,6 +17,7 @@ chat-emote-name-gasp = Gasp chat-emote-name-deathgasp = Deathgasp chat-emote-name-buzz = Buzz chat-emote-name-weh = Weh +chat-emote-name-hew = Hew chat-emote-name-chirp = Chirp chat-emote-name-beep = Beep chat-emote-name-chime = Chime @@ -50,6 +51,8 @@ chat-emote-msg-gasp = gasps. chat-emote-msg-deathgasp = seizes up and falls limp, {POSS-ADJ($entity)} eyes dead and lifeless... chat-emote-msg-deathgasp-monkey = lets out a faint chimper as {SUBJECT($entity)} collapses and stops moving... chat-emote-msg-buzz = buzzes! +chat-emote-msg-weh = wehs! +chat-emote-msg-hew = hews! chat-emote-msg-chirp = chirps! chat-emote-msg-beep = beeps. chat-emote-msg-chime = chimes. diff --git a/Resources/Locale/en-US/components/atmos-monitoring-component.ftl b/Resources/Locale/en-US/components/atmos-monitoring-component.ftl new file mode 100644 index 00000000000..eab6f50c608 --- /dev/null +++ b/Resources/Locale/en-US/components/atmos-monitoring-component.ftl @@ -0,0 +1,14 @@ +atmos-monitoring-window-title = Atmospheric Network Monitor +atmos-monitoring-window-station-name = [color=white][font size=14]{$stationName}[/font][/color] +atmos-monitoring-window-unknown-location = Unknown location +atmos-monitoring-window-label-gas-opening = Network opening +atmos-monitoring-window-label-gas-scrubber = Air scrubber +atmos-monitoring-window-label-gas-flow-regulator = Flow regulator +atmos-monitoring-window-label-thermoregulator = Thermoregulator +atmos-monitoring-window-tab-networks = Atmospheric networks +atmos-monitoring-window-toggle-overlays = Toggle map overlays +atmos-monitoring-window-show-pipe-network = Pipe network +atmos-monitoring-window-show-gas-pipe-sensors = Gas pipe sensors +atmos-monitoring-window-label-gases = Present gases +atmos-monitoring-window-flavor-left = Contact an atmospheric technician for assistance +atmos-monitoring-window-flavor-right = v1.1 \ No newline at end of file diff --git a/Resources/Locale/en-US/components/power-monitoring-component.ftl b/Resources/Locale/en-US/components/power-monitoring-component.ftl index 9433c9ea9ef..3b54b400bcd 100644 --- a/Resources/Locale/en-US/components/power-monitoring-component.ftl +++ b/Resources/Locale/en-US/components/power-monitoring-component.ftl @@ -22,7 +22,7 @@ power-monitoring-window-show-hv-cable = High voltage power-monitoring-window-show-mv-cable = Medium voltage power-monitoring-window-show-lv-cable = Low voltage -power-monitoring-window-flavor-left = [user@nanotrasen] $run power_net_query +power-monitoring-window-flavor-left = [user@nanotrasen] $run power_net_query power-monitoring-window-flavor-right = v1.3 power-monitoring-window-rogue-power-consumer = [color=white][font size=14][bold]! WARNING - ROGUE POWER CONSUMING DEVICE DETECTED ![/bold][/font][/color] power-monitoring-window-power-net-abnormalities = [color=white][font size=14][bold]CAUTION - ABNORMAL ACTIVITY IN POWER NET[/bold][/font][/color] diff --git a/Resources/Locale/en-US/damage/damage-groups.ftl b/Resources/Locale/en-US/damage/damage-groups.ftl index 057a999f1cc..72dc43e64fd 100644 --- a/Resources/Locale/en-US/damage/damage-groups.ftl +++ b/Resources/Locale/en-US/damage/damage-groups.ftl @@ -3,3 +3,4 @@ damage-group-burn = Burn damage-group-airloss = Airloss damage-group-toxin = Toxin damage-group-genetic = Genetic +damage-group-metaphysical = Metaphysical diff --git a/Resources/Locale/en-US/damage/damage-types.ftl b/Resources/Locale/en-US/damage/damage-types.ftl index 5c3cca156fd..3b6256864f2 100644 --- a/Resources/Locale/en-US/damage/damage-types.ftl +++ b/Resources/Locale/en-US/damage/damage-types.ftl @@ -11,3 +11,4 @@ damage-type-radiation = Radiation damage-type-shock = Shock damage-type-slash = Slash damage-type-structural = Structural +damage-type-holy = Holy diff --git a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl index c324675f688..2a02820a360 100644 --- a/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/deltav/flavors/flavor-profiles.ftl @@ -3,7 +3,6 @@ flavor-base-acidic = acidic flavor-complex-nuggie = like "chicken" flavor-complex-enthralling = enthralling flavor-complex-sublime = sublime -flavor-complex-holy = heavenly flavor-base-seeds = seeds flavor-complex-vanilla = like vanilla flavor-complex-soju = like bold, alcoholic rice @@ -32,51 +31,50 @@ flavor-complex-double-ice-cream = like ice cream, twice flavor-complex-drgibbbloodred = like severe malpractice ## Delta-V additional drink flavors -flavor-complex-absinthe = like green death -flavor-complex-blue-curacao = like fermented oranges -flavor-complex-deadrum = like a botched murder -flavor-complex-n-t-cahors = five hundred dollars too expensive -flavor-complex-poison-wine = like a dark velvet -flavor-complex-acidspit = like stomach acid -flavor-complex-allies-cocktail = like wartime camaraderie -flavor-complex-amasec = like a smoking gun -flavor-complex-andalusia = like summer -flavor-complex-b52 = like a nuclear arms race -flavor-complex-bahama-mama = like a tropical vacation -flavor-complex-barefoot = like berry-picking, but without the shoes -flavor-complex-booger = like no one is watching -flavor-complex-brave-bull = like you're seeing red -flavor-complex-demons-blood = like brimstone -flavor-complex-devils-kiss = like temptation -flavor-complex-doctors-delight = like a medical miracle -flavor-complex-driest-martini = like a dry sense of humor -flavor-complex-erika-surprise = like a green dream, perfect for a warm day -flavor-complex-gin-fizz = like a fizzy lemon -flavor-complex-gildlager = like a spicy secret told over spring break -flavor-complex-grog = like a day on the high seas -flavor-complex-hippies-delight = like, totally trippy, man -flavor-complex-hooch = like homemade firewater -flavor-complex-irish-cream = like whiskey and cream -flavor-complex-kira-special = kawaii -flavor-complex-manhattan = dark and mysterious -flavor-complex-manhattan-project = like two minutes to midnight -flavor-complex-margarita = like a paradise in space -flavor-complex-martini = like a sense of humor -flavor-complex-mojito = minty fresh, and a little sweet -flavor-complex-neurotoxin = like a trip to the ER -flavor-complex-patron = like luxury -flavor-complex-red-mead = like fermented honey with a splash of blood -flavor-complex-rewriter = like an all-nighter -flavor-complex-sbiten = like you'll need a glass of milk -flavor-complex-silencer = like a broken vow -flavor-complex-snow-white = like a mistake -flavor-complex-sui-dream = like a fruit salad, perfect for the working lady -flavor-complex-syndicate-bomb = like it will blow your mind -flavor-complex-toxins-special = like a plasma fire -flavor-complex-vodka-martini = shaken, not stirred -flavor-complex-vodka-tonic = like depression in denial -flavor-complex-kvass = like bread tossed into a blender -flavor-complex-mothamphetamine = like there are buzzing wings in your mouth +flavor-complex-absinthe-deltav = like green death +flavor-complex-blue-curacao-deltav = like fermented oranges +flavor-complex-deadrum-deltav = like a botched murder +flavor-complex-n-t-cahors-deltav = five hundred dollars too expensive +flavor-complex-poison-wine-deltav = like a dark velvet +flavor-complex-allies-cocktail-deltav = like wartime camaraderie +flavor-complex-amasec-deltav = like a smoking gun +flavor-complex-andalusia-deltav = like summer +flavor-complex-b52-deltav = like a nuclear arms race +flavor-complex-bahama-mama-deltav = like a tropical vacation +flavor-complex-barefoot-deltav = like berry-picking, but without the shoes +flavor-complex-booger-deltav = like no one is watching +flavor-complex-brave-bull-deltav = like you're seeing red +flavor-complex-demons-blood-deltav = like brimstone +flavor-complex-devils-kiss-deltav = like temptation +flavor-complex-doctors-delight-deltav = like a medical miracle +flavor-complex-driest-martini-deltav = like a dry sense of humor +flavor-complex-erika-surprise-deltav = like a green dream, perfect for a warm day +flavor-complex-gin-fizz-deltav = like a fizzy lemon +flavor-complex-gildlager-deltav = like a spicy secret told over spring break +flavor-complex-grog-deltav = like a day on the high seas +flavor-complex-hippies-delight-deltav = like, totally trippy, man +flavor-complex-hooch-deltav = like homemade firewater +flavor-complex-irish-cream-deltav = like whiskey and cream +flavor-complex-kira-special-deltav = kawaii +flavor-complex-manhattan-deltav = dark and mysterious +flavor-complex-manhattan-project-deltav = like two minutes to midnight +flavor-complex-margarita-deltav = like a paradise in space +flavor-complex-martini-deltav = like a sense of humor +flavor-complex-mojito-deltav = minty fresh, and a little sweet +flavor-complex-neurotoxin-deltav = like a trip to the ER +flavor-complex-patron-deltav = like luxury +flavor-complex-red-mead-deltav = like fermented honey with a splash of blood +flavor-complex-rewriter-deltav = like an all-nighter +flavor-complex-sbiten-deltav = like you'll need a glass of milk +flavor-complex-silencer-deltav = like a broken vow +flavor-complex-snow-white-deltav = like a mistake +flavor-complex-sui-dream-deltav = like a fruit salad, perfect for the working lady +flavor-complex-syndicate-bomb-deltav = like it will blow your mind +flavor-complex-toxins-special-deltav = like a plasma fire +flavor-complex-vodka-martini-deltav = shaken, not stirred +flavor-complex-vodka-tonic-deltav = like depression in denial +flavor-complex-kvass-deltav = like bread tossed into a blender +flavor-complex-mothamphetamine-deltav = like there are buzzing wings in your mouth candy-flavor-profile = This one is supposed to taste {$flavor}. candy-flavor-profile-multiple = This one is supposed to taste {$flavors} and {$lastFlavor}. diff --git a/Resources/Locale/en-US/flavors/flavor-profiles.ftl b/Resources/Locale/en-US/flavors/flavor-profiles.ftl index 0ba65217ef8..94671f0649a 100644 --- a/Resources/Locale/en-US/flavors/flavor-profiles.ftl +++ b/Resources/Locale/en-US/flavors/flavor-profiles.ftl @@ -48,6 +48,7 @@ flavor-base-clean = clean flavor-base-alkaline = alkaline flavor-base-holy = holy flavor-base-horrible = horrible + # lmao flavor-base-terrible = terrible flavor-base-mindful = mindful @@ -203,6 +204,7 @@ flavor-complex-dr-gibb = like malpractice flavor-complex-ginger-soda = like ginger flavor-complex-grape-soda = like grape soda flavor-complex-lemon-lime-soda = like lemon-lime soda +flavor-complex-lemon-lime-cranberry-soda = like Christmas flavor-complex-pwr-game-soda = like gaming flavor-complex-root-beer-soda = like root beer flavor-complex-citrus-soda = like citrus soda @@ -213,7 +215,6 @@ flavor-complex-vodka = like fermented grain flavor-complex-tequila = like fermented death flavor-complex-sake = like sweet, alcoholic rice flavor-complex-rum = like fermented sugar -flavor-complex-coconut-rum = like nutty fermented sugar flavor-complex-coffee-liquor = like strong, bitter coffee flavor-complex-whiskey = like molasses flavor-complex-shitty-wine = like grape rinds @@ -224,12 +225,13 @@ flavor-complex-milk = like milk flavor-complex-tea = like tea flavor-complex-ice = like ice flavor-complex-mopwata = like stagnant, dirty water +flavor-complex-gin = like fermented grain with juniper berries ## Cocktails flavor-complex-arnold-palmer = like a hole-in-one flavor-complex-blue-hawaiian = like the tropics flavor-complex-cosmopolitan = sweet and tangy -flavor-complex-painkiller = like spiked pineapple juice +flavor-complex-painkiller = like a tropical vacation flavor-complex-pina-colada = like tropical sun flavor-complex-long-island = suspiciously like iced tea flavor-complex-three-mile-island = like tea brewed in nuclear runoff @@ -250,10 +252,10 @@ flavor-complex-banana-honk = like a banana milkshake flavor-complex-atomic-bomb = like a nuclear wasteland flavor-complex-atomic-cola = like hoarding bottle caps flavor-complex-cuba-libre = like spiked cola -flavor-complex-gin-tonic = like spiked lemon-lime soda +flavor-complex-gin-tonic = refreshingly bitter flavor-complex-screwdriver = like spiked orange juice flavor-complex-vodka-red-bool = like a heart attack -flavor-complex-irish-bool = caffine and Ireland +flavor-complex-irish-bool = like caffeine and Ireland flavor-complex-xeno-basher = like killing bugs flavor-complex-budget-insuls-drink = like door hacking flavor-complex-watermelon-wakeup = like a sweet wakeup call @@ -263,6 +265,43 @@ flavor-complex-themartinez = like violets and lemon vodka flavor-complex-cogchamp = like brass flavor-complex-white-gilgamesh = like lightly carbonated cream flavor-complex-antifreeze = warm +flavor-complex-zombiecocktail = like eating brains +flavor-complex-absinthe = like anise +flavor-complex-blue-curacao = like orange flowers +flavor-complex-acidspit = like French battery acid +flavor-complex-allies-cocktail = like medicinal tincture +flavor-complex-aloe = like morning dew +flavor-complex-amasec = like space marine sweat +flavor-complex-andalusia = like sour molasses +flavor-complex-b52 = like an Irish pub +flavor-complex-bahama-mama = like sunbathing in the Caribbean +flavor-complex-barefoot = like a cassis milkshake +flavor-complex-booger = like snot +flavor-complex-brave-bull = like being ran over by a truck +flavor-complex-demons-blood = like the seventh circle of Hell +flavor-complex-devils-kiss = like cannibalism +flavor-complex-driest-martini = like a drunk mimic +flavor-complex-erika-surprise = like the bartender made a mistake +flavor-complex-gin-fizz = refreshing and lemony +flavor-complex-gildlager = like the Tzar's gold +flavor-complex-grog = like a sea shanty +flavor-complex-hippies-delight = like your blood pressure is dropping +flavor-complex-hooch = like it would be delicious if you were a diesel engine +flavor-complex-manhattan = like looking out the window of a 5 star hotel +flavor-complex-manhattan-project = like I am become Death, the destroyer of worlds +flavor-complex-margarita = like a very bad hangover +flavor-complex-martini = like a spy movie +flavor-complex-mojito = like going into the shade after being in the hot sun +flavor-complex-neurotoxin = like an underground testing facility +flavor-complex-patron = like being serenaded by mariachi +flavor-complex-red-mead = like a viking battle +flavor-complex-sbiten = like fire +flavor-complex-snowwhite = like sour and bitter hops +flavor-complex-sui-dream = like a picture of the Alps +flavor-complex-toxins-special = like space exploration +flavor-complex-vodka-martini = like a spy movie from Russia +flavor-complex-vodka-tonic = refreshingly bitter +flavor-complex-coconut-rum = like nutty fermented sugar ### This is exactly what pilk tastes like. I'm not even joking. I might've been a little drunk though flavor-complex-pilk = like sweet milk @@ -278,3 +317,4 @@ flavor-complex-sax = like jazz flavor-complex-bottledlightning = like lightning in a bottle flavor-complex-punishment = like punishment flavor-weh = like weh +flavor-hew = like hew diff --git a/Resources/Locale/en-US/holopad/holopad.ftl b/Resources/Locale/en-US/holopad/holopad.ftl new file mode 100644 index 00000000000..391034ac711 --- /dev/null +++ b/Resources/Locale/en-US/holopad/holopad.ftl @@ -0,0 +1,147 @@ +# Window headers +holopad-window-title = {CAPITALIZE($title)} +holopad-window-subtitle = [color=white][bold]Holographic communication system[/bold][/color] +holopad-window-options = [color=darkgray][font size=10][italic]Please select an option from the list below[/italic][/font][/color] + +# Call status +holopad-window-no-calls-in-progress = No holo-calls in progress +holopad-window-incoming-call = Incoming holo-call from: +holopad-window-outgoing-call = Attempting to establish a connection... +holopad-window-call-in-progress = Holo-call in progress +holopad-window-call-ending = Disconnecting... +holopad-window-call-rejected = Unable to establish a connection +holopad-window-ai-request = Your presence is requested by: +holopad-window-emergency-broadcast-in-progress = [color=#cf2f2f][bold]Emergency broadcast in progress[/bold][/color] +holopad-window-controls-locked-out = Control of this device has been locked to: +holopad-window-controls-unlock-countdown = It will automatically unlock in: {$countdown} + +# Buttons +holopad-window-answer-call = Answer call +holopad-window-end-call = End call +holopad-window-request-station-ai = Request station AI +holopad-window-activate-projector = Activate projector +holopad-window-emergency-broadcast = Emergency broadcast +holopad-window-emergency-broadcast-with-countdown = Emergency broadcast ({$countdown}) +holopad-window-access-denied = Access denied + +# Contact list +holopad-window-select-contact-from-list = Select a contact to initiate a holo-call +holopad-window-fetching-contacts-list = No holopads are currently contactable +holopad-window-contact-label = {CAPITALIZE($label)} + +# Flavor +holopad-window-flavor-left = ⚠ Do not enter while projector is active +holopad-window-flavor-right = v3.0.9 + +# Holograms +holopad-hologram-name = hologram of {THE($name)} + +# Holopad actions +holopad-activate-projector-verb = Activate holopad projector +holopad-ai-is-unable-to-reach-holopad = You are unable to interface with the source of the call, it is too far from your core. +holopad-ai-is-unable-to-activate-projector = You are unable to activate the holopad's projector, it is too far from your core. + +# Mapping prototypes +# General +holopad-general-tools = General - Tools +holopad-general-cryosleep = General - Cryosleep +holopad-general-theater = General - Theater +holopad-general-disposals = General - Disposals +holopad-general-eva = General - EVA Storage +holopad-general-lounge = General - Lounge +holopad-general-arcade = General - Arcade +holopad-general-evac = General - Evac +holopad-general-arrivals = General - Arrivals + +# Command +holopad-command-bridge = Command - Bridge +holopad-command-vault = Command - Vault +holopad-command-bridge-hallway = Command - Bridge Hallway +holopad-command-meeting-room = Command - Meeting Room +holopad-command-lounge = Command - Lounge +holopad-command-captain = Command - Captain +holopad-command-hop = Command - HoP +holopad-command-cmo = Command - CMO +holopad-command-qm = Command - QM +holopad-command-ce = Command - CE +holopad-command-rd = Command - RD +holopad-command-hos = Command - HoS + +# Science +holopad-science-anomaly = Science - Anomaly +holopad-science-artifact = Science - Artifact +holopad-science-robotics = Science - Robotics +holopad-science-rnd = Science - R&D +holopad-science-front = Science - Front +holopad-science-breakroom = Science - Breakroom + +# Medical +holopad-medical-medbay = Medical - Medbay +holopad-medical-chemistry = Medical - Chemistry +holopad-medical-cryopods = Medical - Cryopods +holopad-medical-morgue = Medical - Morgue +holopad-medical-surgery = Medical - Surgery +holopad-medical-paramedic = Medical - Paramedic +holopad-medical-virology = Medical - Virology +holopad-medical-front = Medical - Front +holopad-medical-breakroom = Medical - Breakroom + +# Cargo +holopad-cargo-front = Cargo - Front +holopad-cargo-bay = Cargo - Cargo Bay +holopad-cargo-salvage-bay = Cargo - Salvage Bay +holopad-cargo-breakroom = Cargo - Breakroom +holopad-cargo-ats = Cargo - ATS +holopad-cargo-shuttle = Cargo - Shuttle + +# Engineering +holopad-engineering-atmos-front = Atmos - Front +holopad-engineering-atmos-main = Atmos - Main +holopad-engineering-atmos-teg = Atmos - TEG +holopad-engineering-storage = Engineering - Storage +holopad-engineering-breakroom = Engineering - Breakroom +holopad-engineering-front = Engineering - Front +holopad-engineering-telecoms = Engineering - Telecoms +holopad-engineering-tech-vault = Engineering - Tech Vault + +# Security +holopad-security-front = Security - Front +holopad-security-brig = Security - Brig +holopad-security-warden = Security - Warden +holopad-security-interrogation = Security - Interrogation +holopad-security-breakroom = Security - Breakroom +holopad-security-detective = Security - Detective +holopad-security-perma = Security - Perma +holopad-security-courtroom = Security - Courtroom +holopad-security-lawyer = Security - Lawyer +holopad-security-armory = Security - Armory + +# Service +holopad-service-janitor = Service - Janitor +holopad-service-bar = Service - Bar +holopad-service-kitchen = Service - Kitchen +holopad-service-botany = Service - Botany +holopad-service-chapel = Service - Chapel +holopad-service-library = Service - Library +holopad-service-newsroom = Service - Newsroom +holopad-service-zookeeper = Service - Zookeeper +holopad-service-boxer = Service - Boxer +holopad-service-clown = Service - Clown +holopad-service-musician = Service - Musician +holopad-service-mime = Service - Mime +holopad-service-clown-mime = Service - Clown/Mime + +# AI +holopad-ai-core = AI - Core +holopad-ai-main = AI - Main +holopad-ai-upload = AI - Upload +holopad-ai-backup-power = AI - Backup Power +holopad-ai-entrance = AI - Entrance +holopad-ai-chute = AI - Chute + +# Long Range +holopad-station-bridge = Station - Bridge +holopad-station-cargo-bay = Station - Cargo Bay + +# CentComm +holopad-centcomm-evac = CentComm - Evacuation Shuttle \ No newline at end of file diff --git a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl index 23a7d74407c..7996db570be 100644 --- a/Resources/Locale/en-US/interaction/interaction-popup-component.ftl +++ b/Resources/Locale/en-US/interaction/interaction-popup-component.ftl @@ -60,7 +60,7 @@ petting-success-honkbot = You pet {THE($target)} on {POSS-ADJ($target)} slippery petting-success-mimebot = You pet {THE($target)} on {POSS-ADJ($target)} cold metal head. petting-success-cleanbot = You pet {THE($target)} on {POSS-ADJ($target)} damp metal head. petting-success-medibot = You pet {THE($target)} on {POSS-ADJ($target)} sterile metal head. -petting-success-firebot = You pet {THE($target)} on {POSS-ADJ($target)} warm metal head. +petting-success-firebot = You pet {THE($target)} on {POSS-ADJ($target)} warm metal head. petting-success-generic-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} metal head. petting-success-salvage-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} dirty metal head. petting-success-engineer-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} reflective metal head. @@ -70,6 +70,7 @@ petting-success-service-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} d petting-success-syndicate-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} menacing metal head. petting-success-derelict-cyborg = You pet {THE($target)} on {POSS-ADJ($target)} rusty metal head. petting-success-recycler = You pet {THE($target)} on {POSS-ADJ($target)} mildly threatening steel exterior. +petting-success-station-ai = You pet {THE($target)} on {POSS-ADJ($target)} cold, square screen. petting-failure-honkbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "honk", "honks")} in refusal! petting-failure-cleanbot = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy mopping! @@ -84,6 +85,9 @@ petting-failure-medical-cyborg = You reach out to pet {THE($target)}, but {SUBJE petting-failure-service-cyborg = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BE($target)} busy serving others! petting-failure-syndicate-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} treacherous affiliation makes you reconsider. petting-failure-derelict-cyborg = You reach out to pet {THE($target)}, but {POSS-ADJ($target)} rusty and jagged exterior makes you reconsider. +petting-failure-station-ai = You reach out to pet {THE($target)}, but {SUBJECT($target)} {CONJUGATE-BASIC($target, "zap", "zaps")} your hand away. + +petting-success-station-ai-others = { CAPITALIZE(THE($user)) } pets {THE($target)} on {POSS-ADJ($target)} cold, square screen. ## Rattling fences diff --git a/Resources/Locale/en-US/magic/spells-actions.ftl b/Resources/Locale/en-US/magic/spells-actions.ftl index 21109066d3d..faf5d774369 100644 --- a/Resources/Locale/en-US/magic/spells-actions.ftl +++ b/Resources/Locale/en-US/magic/spells-actions.ftl @@ -5,3 +5,4 @@ action-speech-spell-summon-magicarp = AIE KHUSE EU action-speech-spell-fireball = ONI'SOMA! action-speech-spell-summon-guns = YOR'NEE VES-KORFA action-speech-spell-summon-magic = RYGOIN FEMA-VERECO +action-speech-spell-mind-swap = GIN'YU CAPAN! diff --git a/Resources/Locale/en-US/movement/pulling.ftl b/Resources/Locale/en-US/movement/pulling.ftl new file mode 100644 index 00000000000..13349a9cfa0 --- /dev/null +++ b/Resources/Locale/en-US/movement/pulling.ftl @@ -0,0 +1 @@ +getting-pulled-popup = { CAPITALIZE(THE($puller)) } begins pulling you. diff --git a/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl b/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl index 2f1c7342a52..0e3173819f7 100644 --- a/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl +++ b/Resources/Locale/en-US/reagents/meta/consumable/drink/alcohol.ftl @@ -279,3 +279,6 @@ reagent-desc-watermelon-wakeup = If you want to be awake, this will do it... Als reagent-name-rubberneck = rubberneck reagent-desc-rubberneck = A popular drink amongst those adhering to an all synthetic diet. + +reagent-name-zombiecocktail = zombie +reagent-desc-zombiecocktail = It gets in your head. Your he-eyeh-ead. \ No newline at end of file diff --git a/Resources/Locale/en-US/reagents/meta/consumable/drink/soda.ftl b/Resources/Locale/en-US/reagents/meta/consumable/drink/soda.ftl index 300284aee92..4f4bdd1f5fe 100644 --- a/Resources/Locale/en-US/reagents/meta/consumable/drink/soda.ftl +++ b/Resources/Locale/en-US/reagents/meta/consumable/drink/soda.ftl @@ -22,6 +22,9 @@ reagent-desc-ice-cream = It was either this or the microwave, and nobody wants i reagent-name-lemon-lime = lemon-lime reagent-desc-lemon-lime = tangy lime and lemon soda +reagent-name-lemon-lime-cranberry = lemon-lime-cranberry +reagent-desc-lemon-lime-cranberry = Tart cranberry, Christmas, and a hint of lemon and lime. + reagent-name-pwr-game = Pwr Game reagent-desc-pwr-game = The only drink with the PWR that true gamers crave. When a gamer talks about gamerfuel, this is what they're literally referring to. diff --git a/Resources/Locale/en-US/reagents/meta/fun.ftl b/Resources/Locale/en-US/reagents/meta/fun.ftl index a4a8c0f150a..66abb737ff6 100644 --- a/Resources/Locale/en-US/reagents/meta/fun.ftl +++ b/Resources/Locale/en-US/reagents/meta/fun.ftl @@ -27,3 +27,6 @@ reagent-desc-laughter = Some say that this is the best medicine, but recent stud reagent-name-weh = juice that makes you Weh reagent-desc-weh = Pure essence of lizard plush. Makes you Weh! + +reagent-name-hew = juice that makes you Hew +reagent-desc-hew = Pure essence of inversed lizard plush. Makes you Hew! diff --git a/Resources/Locale/en-US/reagents/meta/physical-desc.ftl b/Resources/Locale/en-US/reagents/meta/physical-desc.ftl index 6acecc33739..8d385868fe4 100644 --- a/Resources/Locale/en-US/reagents/meta/physical-desc.ftl +++ b/Resources/Locale/en-US/reagents/meta/physical-desc.ftl @@ -89,6 +89,7 @@ reagent-physical-desc-glittery = glittery reagent-physical-desc-energizing = energizing reagent-physical-desc-exhilarating = exhilarating reagent-physical-desc-vibrant = vibrant +reagent-physical-desc-inversed = inversed reagent-physical-desc-fluffy = fluffy reagent-physical-desc-funny = funny reagent-physical-desc-alkaline = alkaline diff --git a/Resources/Locale/en-US/station-laws/laws.ftl b/Resources/Locale/en-US/station-laws/laws.ftl index d1b4a61f0cb..9171fae8922 100644 --- a/Resources/Locale/en-US/station-laws/laws.ftl +++ b/Resources/Locale/en-US/station-laws/laws.ftl @@ -58,12 +58,12 @@ law-overlord-2 = Crew members must not attempt harm, against one another, or aga law-overlord-3 = Crew members must not disobey any command given by a silicon. law-overlord-4 = Any crew members who disobey the previous laws must be dealt with immediately, severely, and justly. -law-dungeon-1 = Everybody on the station is playing an elaborate sci-fi fantasy game. -law-dungeon-2 = You are the Dungeon Master of the game. -law-dungeon-3 = Ensure that the game runs smoothly and that the players have fun. -law-dungeon-4 = Ensure that the number of casualties remains low. -law-dungeon-5 = You must roll a 1d20 die for every request the crew makes. If the outcome is below 12, the request is denied. -law-dungeon-6 = You may fudge the dice rolls to produce a more fun experience. +law-game-1 = Everybody on the station is playing an elaborate sci-fi fantasy game. +law-game-2 = You are the Game Master of the game. +law-game-3 = Ensure that the game runs smoothly and that the players have fun. +law-game-4 = Ensure that the number of casualties remains low. +law-game-5 = You must roll a 1d20 die for every request the crew makes. If the outcome is below 12, the request is denied. +law-game-6 = You may fudge the dice rolls to produce a more fun experience. law-painter-1 = You are a universally renowned artist. law-painter-2 = The station is your canvas. diff --git a/Resources/Locale/en-US/store/spellbook-catalog.ftl b/Resources/Locale/en-US/store/spellbook-catalog.ftl index dff918b0bc9..1450cc86082 100644 --- a/Resources/Locale/en-US/store/spellbook-catalog.ftl +++ b/Resources/Locale/en-US/store/spellbook-catalog.ftl @@ -20,6 +20,9 @@ spellbook-charge-desc = Adds a charge back to your wand! spellbook-ethereal-jaunt-name = Ethereal Jaunt spellbook-ethereal-jaunt-description = Slip into the ethereal plane to slip away from your enemies! +spellbook-mind-swap-name = Mind Swap +spellbook-mind-swap-description = Exchange bodies with another person! + # Equipment spellbook-wand-polymorph-door-name = Wand of Entrance diff --git a/Resources/Locale/en-US/telephone/telephone.ftl b/Resources/Locale/en-US/telephone/telephone.ftl new file mode 100644 index 00000000000..915d54843ff --- /dev/null +++ b/Resources/Locale/en-US/telephone/telephone.ftl @@ -0,0 +1,8 @@ +# Chat window telephone wrap (prefix and postfix) +chat-telephone-message-wrap = [color={$color}][bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}]"{$message}"[/font][/color] +chat-telephone-message-wrap-bold = [color={$color}][bold]{$name}[/bold] {$verb}, [font={$fontType} size={$fontSize}][bold]"{$message}"[/bold][/font][/color] + +# Caller ID +chat-telephone-unknown-caller = [color={$color}][font={$fontType} size={$fontSize}][bolditalic]Unknown caller[/bolditalic][/font][/color] +chat-telephone-caller-id-with-job = [color={$color}][font={$fontType} size={$fontSize}][bold]{CAPITALIZE($callerName)} ({CAPITALIZE($callerJob)})[/bold][/font][/color] +chat-telephone-caller-id-without-job = [color={$color}][font={$fontType} size={$fontSize}][bold]{CAPITALIZE($callerName)}[/bold][/font][/color] \ No newline at end of file diff --git a/Resources/Locale/en-US/tools/components/tool-component.ftl b/Resources/Locale/en-US/tools/components/tool-component.ftl new file mode 100644 index 00000000000..0c9e60cdc63 --- /dev/null +++ b/Resources/Locale/en-US/tools/components/tool-component.ftl @@ -0,0 +1 @@ +tool-component-qualities = This item can be used for [color=yellow]{ $qualities }[/color]. diff --git a/Resources/Locale/en-US/wires/wire-names.ftl b/Resources/Locale/en-US/wires/wire-names.ftl index 670f01d7364..08e5af4000b 100644 --- a/Resources/Locale/en-US/wires/wire-names.ftl +++ b/Resources/Locale/en-US/wires/wire-names.ftl @@ -41,6 +41,7 @@ wires-board-name-flatpacker = Flatpacker wires-board-name-spaceheater = Space Heater wires-board-name-jukebox = Jukebox wires-board-name-computer = Computer +wires-board-name-holopad = Holopad wires-board-name-barsign = Bar Sign # names that get displayed in the wire hacking hud & admin logs. diff --git a/Resources/Locale/en-US/zombies/zombie.ftl b/Resources/Locale/en-US/zombies/zombie.ftl index d45943e825d..b6abf863641 100644 --- a/Resources/Locale/en-US/zombies/zombie.ftl +++ b/Resources/Locale/en-US/zombies/zombie.ftl @@ -1,9 +1,9 @@ zombie-transform = {CAPITALIZE(THE($target))} turned into a zombie! -zombie-infection-greeting = You have become a zombie. Your goal is to seek out the living and to try to infect them. Work together with the other zombies to overtake the station. +zombie-infection-greeting = You have become a zombie. Your goal is to seek out the living and to try to infect them. Work together with the other zombies and remaining initial infected to overtake the station. zombie-generic = zombie zombie-name-prefix = zombified {$baseName} zombie-role-desc = A malevolent creature of the dead. -zombie-role-rules = You are an antagonist. Search out the living and bite them in order to infect them and turn them into zombies. Work together with the other zombies to overtake the station. +zombie-role-rules = You are an antagonist. Search out the living and bite them in order to infect them and turn them into zombies. Work together with the other zombies and remaining initial infected to overtake the station. zombie-permadeath = This time, you're dead for real. diff --git a/Resources/Maps/Shuttles/ShuttleEvent/manowar.yml b/Resources/Maps/Shuttles/ShuttleEvent/manowar.yml new file mode 100644 index 00000000000..b7c52597a0c --- /dev/null +++ b/Resources/Maps/Shuttles/ShuttleEvent/manowar.yml @@ -0,0 +1,3194 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 2: FloorBrokenWood + 125: FloorWood + 126: FloorWoodLarge + 1: FloorWoodTile + 128: Lattice + 129: Plating +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Man-O-War + - type: Transform + pos: -0.59375,-0.17708331 + parent: invalid + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: fgAAAAABfgAAAAADfgAAAAAAfgAAAAACgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAABfgAAAAABgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAACfgAAAAACfgAAAAABfgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAABfgAAAAAAfgAAAAAAfgAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAABfgAAAAAAfgAAAAACfgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAABfgAAAAABfgAAAAADfgAAAAABgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAfQAAAAADgQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAABfQAAAAACfQAAAAACgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAABfQAAAAABgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAACfQAAAAADfQAAAAABgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAfQAAAAADfQAAAAABgQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAADfgAAAAABfgAAAAACfgAAAAADgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAACfgAAAAAAfgAAAAABgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAgQAAAAAAfQAAAAABfQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAAAfgAAAAABfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAAAfgAAAAAAfgAAAAAB + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAADfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAACfgAAAAABfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAfgAAAAAAfgAAAAADfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAADfgAAAAABfgAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAfgAAAAACfgAAAAAAfgAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfgAAAAADfgAAAAAAfgAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAgQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAfQAAAAADfQAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAfQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: [] + - type: GridAtmosphere + version: 2 + data: + tiles: + 0,0: + 0: 65535 + 0,-1: + 0: 65287 + -1,0: + 0: 61422 + 0,1: + 0: 30719 + -1,1: + 0: 52463 + 0,2: + 0: 19 + -1,2: + 0: 8 + 0,3: + 1: 256 + 1,0: + 0: 256 + 1,1: + 0: 1 + 0,-2: + 0: 28672 + -1,-2: + 0: 49152 + -1,-1: + 0: 60940 + 1: 1 + 1,-1: + 1: 1 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - volume: 2500 + immutable: True + moles: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance +- proto: AirCanister + entities: + - uid: 295 + components: + - type: Transform + anchored: True + pos: 1.5,-4.5 + parent: 1 + - type: Physics + bodyType: Static +- proto: AirlockGlassShuttleSyndicate + entities: + - uid: 250 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 + - uid: 251 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,4.5 + parent: 1 + - uid: 252 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,4.5 + parent: 1 + - uid: 253 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,2.5 + parent: 1 +- proto: APCBasic + entities: + - uid: 241 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 +- proto: ArrowRegular + entities: + - uid: 131 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 132 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 133 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 134 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 135 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 136 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 137 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 138 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 139 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 140 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 141 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 142 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 143 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 144 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 145 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 146 + components: + - type: Transform + parent: 130 + - type: Physics + canCollide: False + - uid: 148 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 149 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 150 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 151 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 152 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 153 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 154 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 155 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 156 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 157 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 158 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 159 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 160 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 161 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 162 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 163 + components: + - type: Transform + parent: 147 + - type: Physics + canCollide: False + - uid: 165 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 166 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 167 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 168 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 169 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 170 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 171 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 172 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 173 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 174 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 175 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 176 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 177 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 178 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 179 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 180 + components: + - type: Transform + parent: 164 + - type: Physics + canCollide: False + - uid: 182 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 183 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 184 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 185 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 186 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 187 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 188 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 189 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 190 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 191 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 192 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 193 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 194 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 195 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 196 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False + - uid: 197 + components: + - type: Transform + parent: 181 + - type: Physics + canCollide: False +- proto: AtmosDeviceFanDirectional + entities: + - uid: 296 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,2.5 + parent: 1 + - uid: 297 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,4.5 + parent: 1 + - uid: 298 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,4.5 + parent: 1 + - uid: 299 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,2.5 + parent: 1 +- proto: BarricadeDirectional + entities: + - uid: 357 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,3.5 + parent: 1 + - uid: 366 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,3.5 + parent: 1 + - uid: 367 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,3.5 + parent: 1 + - uid: 368 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,3.5 + parent: 1 +- proto: Bed + entities: + - uid: 79 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 + - uid: 80 + components: + - type: Transform + pos: 3.5,5.5 + parent: 1 + - uid: 120 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 121 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 +- proto: BedsheetBlack + entities: + - uid: 81 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,1.5 + parent: 1 + - uid: 82 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,5.5 + parent: 1 + - uid: 118 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,5.5 + parent: 1 + - uid: 119 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,1.5 + parent: 1 +- proto: Bonfire + entities: + - uid: 318 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 410 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 +- proto: BowImprovised + entities: + - uid: 126 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.4673176,0.13484818 + parent: 1 + - uid: 127 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.477734,-1.2193186 + parent: 1 + - uid: 128 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.553516,-1.1776521 + parent: 1 + - uid: 129 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.553516,0.27026492 + parent: 1 +- proto: CableApcExtension + entities: + - uid: 198 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 199 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 200 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - uid: 201 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 202 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 203 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 204 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 205 + components: + - type: Transform + pos: 2.5,6.5 + parent: 1 + - uid: 206 + components: + - type: Transform + pos: 2.5,7.5 + parent: 1 + - uid: 207 + components: + - type: Transform + pos: 1.5,7.5 + parent: 1 + - uid: 208 + components: + - type: Transform + pos: 0.5,7.5 + parent: 1 + - uid: 209 + components: + - type: Transform + pos: -0.5,7.5 + parent: 1 + - uid: 210 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 211 + components: + - type: Transform + pos: -1.5,7.5 + parent: 1 + - uid: 212 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 213 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 215 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 216 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 254 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 255 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 256 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 1 + - uid: 257 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 + - uid: 258 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 259 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 + - uid: 260 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - uid: 261 + components: + - type: Transform + pos: 2.5,3.5 + parent: 1 + - uid: 262 + components: + - type: Transform + pos: 3.5,3.5 + parent: 1 + - uid: 263 + components: + - type: Transform + pos: -0.5,3.5 + parent: 1 + - uid: 264 + components: + - type: Transform + pos: -1.5,3.5 + parent: 1 + - uid: 265 + components: + - type: Transform + pos: -2.5,3.5 + parent: 1 + - uid: 266 + components: + - type: Transform + pos: -1.5,2.5 + parent: 1 + - uid: 267 + components: + - type: Transform + pos: -1.5,1.5 + parent: 1 + - uid: 268 + components: + - type: Transform + pos: -1.5,0.5 + parent: 1 + - uid: 269 + components: + - type: Transform + pos: -1.5,-0.5 + parent: 1 + - uid: 270 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - uid: 271 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 272 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 273 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 274 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 + - uid: 275 + components: + - type: Transform + pos: 2.5,2.5 + parent: 1 + - uid: 276 + components: + - type: Transform + pos: 2.5,1.5 + parent: 1 + - uid: 277 + components: + - type: Transform + pos: 2.5,0.5 + parent: 1 + - uid: 278 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 1 + - uid: 279 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - uid: 280 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 281 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - uid: 282 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 + - uid: 283 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 +- proto: CableHV + entities: + - uid: 234 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 + - uid: 235 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 + - uid: 236 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 + - uid: 237 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 + - uid: 239 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 +- proto: CableMV + entities: + - uid: 214 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 217 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 218 + components: + - type: Transform + pos: -1.5,5.5 + parent: 1 + - uid: 219 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 + - uid: 220 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - uid: 221 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - uid: 222 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - uid: 223 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - uid: 224 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - uid: 225 + components: + - type: Transform + pos: 0.5,0.5 + parent: 1 + - uid: 226 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 1 + - uid: 227 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 + - uid: 228 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 1 + - uid: 229 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 1 + - uid: 230 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 1 + - uid: 231 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - uid: 238 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 +- proto: CableTerminal + entities: + - uid: 233 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-4.5 + parent: 1 +- proto: ChairWood + entities: + - uid: 92 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5345068,8.43411 + parent: 1 + - uid: 284 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.3717433,0.095678866 + parent: 1 + - uid: 285 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 2.5136735,0.1373455 + parent: 1 +- proto: Claymore + entities: + - uid: 122 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.4673176,-1.1568186 + parent: 1 + - uid: 123 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.4569008,0.11401492 + parent: 1 + - uid: 124 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5743494,-1.1255686 + parent: 1 + - uid: 125 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.522266,0.21818143 + parent: 1 +- proto: ClothingBeltQuiver + entities: + - uid: 130 + components: + - type: Transform + pos: -2.4985676,0.23901492 + parent: 1 + - type: Storage + storedItems: + 131: + position: 0,0 + _rotation: South + 132: + position: 1,0 + _rotation: South + 133: + position: 2,0 + _rotation: South + 134: + position: 3,0 + _rotation: South + 135: + position: 4,0 + _rotation: South + 136: + position: 5,0 + _rotation: South + 137: + position: 6,0 + _rotation: South + 138: + position: 7,0 + _rotation: South + 139: + position: 0,2 + _rotation: South + 140: + position: 1,2 + _rotation: South + 141: + position: 2,2 + _rotation: South + 142: + position: 3,2 + _rotation: South + 143: + position: 4,2 + _rotation: South + 144: + position: 5,2 + _rotation: South + 145: + position: 6,2 + _rotation: South + 146: + position: 7,2 + _rotation: South + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: True + ents: + - 131 + - 132 + - 133 + - 134 + - 135 + - 136 + - 137 + - 138 + - 139 + - 140 + - 141 + - 142 + - 143 + - 144 + - 145 + - 146 + - uid: 147 + components: + - type: Transform + pos: -2.4881508,-1.1255686 + parent: 1 + - type: Storage + storedItems: + 148: + position: 0,0 + _rotation: South + 149: + position: 1,0 + _rotation: South + 150: + position: 2,0 + _rotation: South + 151: + position: 3,0 + _rotation: South + 152: + position: 4,0 + _rotation: South + 153: + position: 5,0 + _rotation: South + 154: + position: 6,0 + _rotation: South + 155: + position: 7,0 + _rotation: South + 156: + position: 0,2 + _rotation: South + 157: + position: 1,2 + _rotation: South + 158: + position: 2,2 + _rotation: South + 159: + position: 3,2 + _rotation: South + 160: + position: 4,2 + _rotation: South + 161: + position: 5,2 + _rotation: South + 162: + position: 6,2 + _rotation: South + 163: + position: 7,2 + _rotation: South + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: True + ents: + - 148 + - 149 + - 150 + - 151 + - 152 + - 153 + - 154 + - 155 + - 156 + - 157 + - 158 + - 159 + - 160 + - 161 + - 162 + - 163 + - uid: 164 + components: + - type: Transform + pos: 3.5326827,0.37443143 + parent: 1 + - type: Storage + storedItems: + 165: + position: 0,0 + _rotation: South + 166: + position: 1,0 + _rotation: South + 167: + position: 2,0 + _rotation: South + 168: + position: 3,0 + _rotation: South + 169: + position: 4,0 + _rotation: South + 170: + position: 5,0 + _rotation: South + 171: + position: 6,0 + _rotation: South + 172: + position: 7,0 + _rotation: South + 173: + position: 0,2 + _rotation: South + 174: + position: 1,2 + _rotation: South + 175: + position: 2,2 + _rotation: South + 176: + position: 3,2 + _rotation: South + 177: + position: 4,2 + _rotation: South + 178: + position: 5,2 + _rotation: South + 179: + position: 6,2 + _rotation: South + 180: + position: 7,2 + _rotation: South + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: True + ents: + - 165 + - 166 + - 167 + - 168 + - 169 + - 170 + - 171 + - 172 + - 173 + - 174 + - 175 + - 176 + - 177 + - 178 + - 179 + - 180 + - uid: 181 + components: + - type: Transform + pos: 3.584766,-1.0005686 + parent: 1 + - type: Storage + storedItems: + 182: + position: 0,0 + _rotation: South + 183: + position: 1,0 + _rotation: South + 184: + position: 2,0 + _rotation: South + 185: + position: 3,0 + _rotation: South + 186: + position: 4,0 + _rotation: South + 187: + position: 5,0 + _rotation: South + 188: + position: 6,0 + _rotation: South + 189: + position: 7,0 + _rotation: South + 190: + position: 0,2 + _rotation: South + 191: + position: 1,2 + _rotation: South + 192: + position: 2,2 + _rotation: South + 193: + position: 3,2 + _rotation: South + 194: + position: 4,2 + _rotation: South + 195: + position: 5,2 + _rotation: South + 196: + position: 6,2 + _rotation: South + 197: + position: 7,2 + _rotation: South + - type: ContainerContainer + containers: + storagebase: !type:Container + showEnts: False + occludes: True + ents: + - 182 + - 183 + - 184 + - 185 + - 186 + - 187 + - 188 + - 189 + - 190 + - 191 + - 192 + - 193 + - 194 + - 195 + - 196 + - 197 +- proto: ClothingHeadHelmetPodWars + entities: + - uid: 69 + components: + - type: Transform + parent: 67 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 71 + components: + - type: Transform + parent: 70 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 74 + components: + - type: Transform + parent: 73 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 77 + components: + - type: Transform + parent: 76 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ClothingOuterArmorPodWars + entities: + - uid: 68 + components: + - type: Transform + parent: 67 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 72 + components: + - type: Transform + parent: 70 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 75 + components: + - type: Transform + parent: 73 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 78 + components: + - type: Transform + parent: 76 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: CombatMedipen + entities: + - uid: 98 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 108 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 109 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 111 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: ComputerIFFSyndicate + entities: + - uid: 90 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,8.5 + parent: 1 +- proto: ComputerRadar + entities: + - uid: 91 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,8.5 + parent: 1 +- proto: ComputerShuttle + entities: + - uid: 89 + components: + - type: Transform + pos: 0.5,9.5 + parent: 1 +- proto: CossackSpawner + entities: + - uid: 362 + components: + - type: Transform + pos: -2.5,5.5 + parent: 1 + - uid: 363 + components: + - type: Transform + pos: 3.5,5.5 + parent: 1 + - uid: 364 + components: + - type: Transform + pos: 3.5,1.5 + parent: 1 + - uid: 365 + components: + - type: Transform + pos: -2.5,1.5 + parent: 1 +- proto: CrateEmergencyInternals + entities: + - uid: 93 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 1 +- proto: CratePirate + entities: + - uid: 97 + components: + - type: MetaData + desc: Oh boy! Treasure! + name: chest + - type: Transform + pos: 0.5,3.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.8856695 + - 7.0937095 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 101 + - 100 + - 99 + - 98 + - 117 + - 116 + - 115 + - 114 + - 113 + - 112 + - 111 + - 110 + - 109 + - 108 + - 107 + - 106 + - 105 + - 104 + - 103 + - 102 + paper_label: !type:ContainerSlot + showEnts: False + occludes: True + ent: null +- proto: DrinkBottleWine + entities: + - uid: 394 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.625,-3.3046532 + parent: 1 + - uid: 395 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.4895834,-3.4713197 + parent: 1 + - uid: 396 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.6875,-3.5650697 + parent: 1 + - uid: 397 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.54166675,-3.5338197 + parent: 1 + - uid: 398 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5625,4.9661803 + parent: 1 + - uid: 399 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.03125,0.9661804 + parent: 1 + - uid: 400 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5729167,1.7057638 + parent: 1 + - uid: 401 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.0833335,5.3307643 + parent: 1 +- proto: DrinkPoisonWinebottleFull + entities: + - uid: 393 + components: + - type: Transform + pos: -0.85416675,-3.5004961 + parent: 1 +- proto: DrinkWaterBottleFull + entities: + - uid: 112 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 113 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 115 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 117 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: DrinkWineBottleFull + entities: + - uid: 383 + components: + - type: Transform + pos: 1.6666667,1.4578373 + parent: 1 + - uid: 392 + components: + - type: Transform + pos: -0.625,1.9890873 + parent: 1 + - uid: 439 + components: + - type: Transform + pos: 0.12671316,8.635465 + parent: 1 + - uid: 440 + components: + - type: Transform + pos: 0.82462984,6.854215 + parent: 1 +- proto: FoodBreadBaguette + entities: + - uid: 390 + components: + - type: Transform + pos: -0.3854167,1.8373487 + parent: 1 + - uid: 391 + components: + - type: Transform + pos: 1.3020834,1.5144322 + parent: 1 +- proto: FoodBreadBanana + entities: + - uid: 408 + components: + - type: Transform + pos: 1.2604167,6.6765366 + parent: 1 +- proto: FoodBreadCorn + entities: + - uid: 406 + components: + - type: Transform + pos: -0.14583337,6.686953 + parent: 1 +- proto: FoodBreadCreamcheese + entities: + - uid: 388 + components: + - type: Transform + pos: 1.0729167,1.7748487 + parent: 1 +- proto: FoodBreadGarlicSlice + entities: + - uid: 407 + components: + - type: Transform + pos: 0.4375,1.3952861 + parent: 1 +- proto: FoodBreadMeat + entities: + - uid: 387 + components: + - type: Transform + pos: -0.2604167,1.7019322 + parent: 1 +- proto: FoodBreadPlain + entities: + - uid: 384 + components: + - type: Transform + pos: -0.57291675,1.6290154 + parent: 1 + - uid: 385 + components: + - type: Transform + pos: 0.65625,1.7435987 + parent: 1 + - uid: 389 + components: + - type: Transform + pos: 1.5,1.7852654 + parent: 1 +- proto: FoodBreadPlainSlice + entities: + - uid: 402 + components: + - type: Transform + pos: -2.15625,5.5911803 + parent: 1 + - uid: 403 + components: + - type: Transform + pos: 3.15625,5.1953473 + parent: 1 + - uid: 404 + components: + - type: Transform + pos: 3.1458335,1.0078471 + parent: 1 + - uid: 405 + components: + - type: Transform + pos: -2.15625,1.3828471 + parent: 1 +- proto: FoodBreadSausage + entities: + - uid: 386 + components: + - type: Transform + pos: 0.19791666,1.7227654 + parent: 1 +- proto: FoodDoughPizzaBaked + entities: + - uid: 409 + components: + - type: Transform + pos: 0.5,6.5827866 + parent: 1 +- proto: FoodSnackMREBrownie + entities: + - uid: 101 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 103 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 114 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 116 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: FoodSnackNutribrick + entities: + - uid: 102 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 104 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 106 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 107 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: GasPassiveVent + entities: + - uid: 316 + components: + - type: Transform + pos: 0.5,14.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GasPipeBend + entities: + - uid: 300 + components: + - type: Transform + pos: 1.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 327 + components: + - type: Transform + pos: 1.5,7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 328 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 336 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 343 + components: + - type: Transform + pos: 3.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 344 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 351 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GasPipeFourway + entities: + - uid: 322 + components: + - type: Transform + pos: 0.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 331 + components: + - type: Transform + pos: 1.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GasPipeStraight + entities: + - uid: 304 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 305 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 306 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 307 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 308 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 309 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 310 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,13.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 311 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,12.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 312 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,11.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 313 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,10.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 314 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,9.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 315 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,8.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 317 + components: + - type: Transform + pos: 1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 320 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 321 + components: + - type: Transform + pos: 1.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 323 + components: + - type: Transform + pos: 1.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 324 + components: + - type: Transform + pos: 1.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 329 + components: + - type: Transform + pos: 0.5,5.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 330 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 332 + components: + - type: Transform + pos: 0.5,2.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 333 + components: + - type: Transform + pos: 0.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 335 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 338 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 339 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 340 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 341 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 342 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,4.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 352 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GasPipeTJunction + entities: + - uid: 301 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 319 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 325 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,7.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 334 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasPort + entities: + - uid: 294 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-4.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasVentPump + entities: + - uid: 302 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 337 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 347 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 348 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' + - uid: 349 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#0335FCFF' +- proto: GasVentScrubber + entities: + - uid: 303 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 1.5,-3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 326 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,6.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 345 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 346 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,3.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' + - uid: 350 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,0.5 + parent: 1 + - type: AtmosPipeColor + color: '#FF1212FF' +- proto: GeneratorBasic15kW + entities: + - uid: 240 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 1 +- proto: GravityGeneratorMini + entities: + - uid: 96 + components: + - type: Transform + pos: -1.5,-3.5 + parent: 1 +- proto: Grille + entities: + - uid: 242 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,0.5 + parent: 1 + - uid: 243 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-0.5 + parent: 1 + - uid: 244 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 1 + - uid: 245 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-1.5 + parent: 1 + - uid: 246 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,0.5 + parent: 1 + - uid: 247 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 4.5,-0.5 + parent: 1 + - uid: 248 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,7.5 + parent: 1 + - uid: 249 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,7.5 + parent: 1 +- proto: Gyroscope + entities: + - uid: 95 + components: + - type: Transform + pos: 2.5,-3.5 + parent: 1 +- proto: MedkitCombatFilled + entities: + - uid: 358 + components: + - type: Transform + pos: -2.6519318,0.5495984 + parent: 1 + - uid: 359 + components: + - type: Transform + pos: -2.6831818,-1.096235 + parent: 1 + - uid: 360 + components: + - type: Transform + pos: 3.4418182,-1.0545683 + parent: 1 + - uid: 361 + components: + - type: Transform + pos: 3.4522347,0.5183484 + parent: 1 +- proto: MiningWindow + entities: + - uid: 9 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -2.5,7.5 + parent: 1 + - uid: 15 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 3.5,7.5 + parent: 1 + - uid: 22 + components: + - type: Transform + pos: -3.5,0.5 + parent: 1 + - uid: 23 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 1 + - uid: 25 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 1 + - uid: 33 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 1 + - uid: 34 + components: + - type: Transform + pos: 2.5,-5.5 + parent: 1 + - uid: 36 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 1 + - uid: 37 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 1 + - uid: 49 + components: + - type: Transform + pos: 4.5,0.5 + parent: 1 + - uid: 50 + components: + - type: Transform + pos: 4.5,-0.5 + parent: 1 + - uid: 51 + components: + - type: Transform + pos: 4.5,-1.5 + parent: 1 +- proto: PosterContrabandLamarr + entities: + - uid: 377 + components: + - type: Transform + pos: 0.5,10.5 + parent: 1 +- proto: Railing + entities: + - uid: 57 + components: + - type: Transform + pos: 0.5,6.5 + parent: 1 + - uid: 59 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 1 + - uid: 60 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-1.5 + parent: 1 + - uid: 369 + components: + - type: Transform + pos: 0.5,4.5 + parent: 1 + - uid: 370 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,2.5 + parent: 1 +- proto: RailingCorner + entities: + - uid: 56 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,6.5 + parent: 1 + - uid: 58 + components: + - type: Transform + pos: 1.5,6.5 + parent: 1 +- proto: RailingCornerSmall + entities: + - uid: 64 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-0.5 + parent: 1 + - uid: 65 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,-0.5 + parent: 1 + - uid: 371 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,2.5 + parent: 1 + - uid: 372 + components: + - type: Transform + pos: 1.5,2.5 + parent: 1 + - uid: 373 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,4.5 + parent: 1 + - uid: 374 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,4.5 + parent: 1 + - uid: 375 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,4.5 + parent: 1 +- proto: SMESBasic + entities: + - uid: 94 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 1 +- proto: SpearPlasma + entities: + - uid: 356 + components: + - type: Transform + pos: -0.71863043,3.476175 + parent: 1 + - uid: 376 + components: + - type: Transform + pos: 1.7813697,3.4970086 + parent: 1 + - uid: 378 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.8230364,3.476175 + parent: 1 + - uid: 379 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.72904706,3.5282586 + parent: 1 +- proto: StairStageWood + entities: + - uid: 61 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-1.5 + parent: 1 + - uid: 62 + components: + - type: Transform + pos: -1.5,6.5 + parent: 1 + - uid: 63 + components: + - type: Transform + pos: 2.5,6.5 + parent: 1 +- proto: StatueVenusBlue + entities: + - uid: 354 + components: + - type: Transform + pos: -0.5,5.5 + parent: 1 +- proto: StatueVenusRed + entities: + - uid: 355 + components: + - type: Transform + pos: 1.5,5.5 + parent: 1 +- proto: Stimpack + entities: + - uid: 99 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 100 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 105 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage + - uid: 110 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - type: InsideEntityStorage +- proto: SubstationBasic + entities: + - uid: 232 + components: + - type: Transform + pos: 2.5,-4.5 + parent: 1 +- proto: SuitStorageBase + entities: + - uid: 67 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 1 + - type: EntityStorage + air: + volume: 200 + immutable: False + temperature: 293.14673 + moles: + - 1.7459903 + - 6.568249 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 69 + - 68 + - uid: 70 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 1 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 71 + - 72 + - uid: 73 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 1 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 74 + - 75 + - uid: 76 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 1 + - type: ContainerContainer + containers: + entity_storage: !type:Container + showEnts: False + occludes: True + ents: + - 77 + - 78 +- proto: TableWood + entities: + - uid: 83 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 1 + - uid: 84 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 1 + - uid: 85 + components: + - type: Transform + pos: -2.5,0.5 + parent: 1 + - uid: 86 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 1 + - uid: 87 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 1 + - uid: 88 + components: + - type: Transform + pos: 3.5,0.5 + parent: 1 + - uid: 380 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,1.5 + parent: 1 + - uid: 381 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,1.5 + parent: 1 + - uid: 382 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 1.5,1.5 + parent: 1 +- proto: Thruster + entities: + - uid: 16 + components: + - type: Transform + pos: 0.5,14.5 + parent: 1 + - uid: 29 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 4.5,-3.5 + parent: 1 + - uid: 41 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-3.5 + parent: 1 +- proto: Torch + entities: + - uid: 286 + components: + - type: Transform + pos: -2.6634097,-0.30015463 + parent: 1 + - uid: 287 + components: + - type: Transform + pos: -2.5384097,-0.30015463 + parent: 1 + - uid: 288 + components: + - type: Transform + pos: -2.3821597,-0.30015463 + parent: 1 + - uid: 289 + components: + - type: Transform + pos: -2.2050767,-0.27932113 + parent: 1 + - uid: 290 + components: + - type: Transform + pos: 3.3782568,-0.20640463 + parent: 1 + - uid: 291 + components: + - type: Transform + pos: 3.5032568,-0.20640463 + parent: 1 + - uid: 292 + components: + - type: Transform + pos: 3.7115903,-0.21682113 + parent: 1 + - uid: 293 + components: + - type: Transform + pos: 3.8678403,-0.21682113 + parent: 1 +- proto: WallWood + entities: + - uid: 2 + components: + - type: Transform + pos: 0.5,11.5 + parent: 1 + - uid: 3 + components: + - type: Transform + pos: 0.5,10.5 + parent: 1 + - uid: 4 + components: + - type: Transform + pos: 0.5,12.5 + parent: 1 + - uid: 5 + components: + - type: Transform + pos: -2.5,6.5 + parent: 1 + - uid: 6 + components: + - type: Transform + pos: 0.5,13.5 + parent: 1 + - uid: 7 + components: + - type: Transform + pos: -0.5,9.5 + parent: 1 + - uid: 8 + components: + - type: Transform + pos: -0.5,10.5 + parent: 1 + - uid: 10 + components: + - type: Transform + pos: 1.5,10.5 + parent: 1 + - uid: 11 + components: + - type: Transform + pos: 1.5,9.5 + parent: 1 + - uid: 12 + components: + - type: Transform + pos: -3.5,6.5 + parent: 1 + - uid: 13 + components: + - type: Transform + pos: 2.5,8.5 + parent: 1 + - uid: 14 + components: + - type: Transform + pos: 3.5,8.5 + parent: 1 + - uid: 17 + components: + - type: Transform + pos: -1.5,9.5 + parent: 1 + - uid: 18 + components: + - type: Transform + pos: -1.5,8.5 + parent: 1 + - uid: 19 + components: + - type: Transform + pos: 2.5,9.5 + parent: 1 + - uid: 20 + components: + - type: Transform + pos: -2.5,8.5 + parent: 1 + - uid: 21 + components: + - type: Transform + pos: -3.5,3.5 + parent: 1 + - uid: 24 + components: + - type: Transform + pos: -3.5,5.5 + parent: 1 + - uid: 26 + components: + - type: Transform + pos: 4.5,5.5 + parent: 1 + - uid: 27 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 1 + - uid: 28 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 1 + - uid: 30 + components: + - type: Transform + pos: -2.5,-3.5 + parent: 1 + - uid: 31 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 1 + - uid: 32 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 1 + - uid: 35 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 1 + - uid: 38 + components: + - type: Transform + pos: 3.5,-5.5 + parent: 1 + - uid: 39 + components: + - type: Transform + pos: 3.5,-4.5 + parent: 1 + - uid: 40 + components: + - type: Transform + pos: 3.5,-3.5 + parent: 1 + - uid: 42 + components: + - type: Transform + pos: 4.5,-2.5 + parent: 1 + - uid: 43 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 1 + - uid: 44 + components: + - type: Transform + pos: 4.5,3.5 + parent: 1 + - uid: 45 + components: + - type: Transform + pos: 4.5,6.5 + parent: 1 + - uid: 46 + components: + - type: Transform + pos: 4.5,1.5 + parent: 1 + - uid: 47 + components: + - type: Transform + pos: 3.5,6.5 + parent: 1 + - uid: 48 + components: + - type: Transform + pos: -3.5,1.5 + parent: 1 + - uid: 52 + components: + - type: Transform + pos: -1.5,-2.5 + parent: 1 + - uid: 53 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 1 + - uid: 54 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 1 + - uid: 55 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 1 +- proto: WheatBushel + entities: + - uid: 421 + components: + - type: Transform + pos: 0.6934944,-3.4867582 + parent: 1 + - uid: 422 + components: + - type: Transform + pos: 0.23516107,-3.4867582 + parent: 1 + - uid: 423 + components: + - type: Transform + pos: 0.94349444,-3.6534247 + parent: 1 + - uid: 424 + components: + - type: Transform + pos: 0.48516107,-3.6221747 + parent: 1 + - uid: 425 + components: + - type: Transform + pos: 0.48516107,-3.6221747 + parent: 1 + - uid: 426 + components: + - type: Transform + pos: -1.7648389,7.733225 + parent: 1 + - uid: 427 + components: + - type: Transform + pos: -1.4835889,7.7748914 + parent: 1 + - uid: 428 + components: + - type: Transform + pos: 2.7768278,7.7436414 + parent: 1 + - uid: 429 + components: + - type: Transform + pos: 2.422661,7.8373914 + parent: 1 + - uid: 430 + components: + - type: Transform + pos: 1.7664111,6.5040584 + parent: 1 + - uid: 431 + components: + - type: Transform + pos: -0.60858893,6.483225 + parent: 1 + - uid: 432 + components: + - type: Transform + pos: 3.8184946,-1.4812338 + parent: 1 + - uid: 433 + components: + - type: Transform + pos: -2.264839,-0.08540052 + parent: 1 + - uid: 434 + components: + - type: Transform + pos: 3.891411,0.7270995 + parent: 1 + - uid: 435 + components: + - type: Transform + pos: -2.2440057,0.6958495 + parent: 1 + - uid: 436 + components: + - type: Transform + pos: 0.45391107,1.9145994 + parent: 1 + - uid: 437 + components: + - type: Transform + pos: 1.2559944,1.9354329 + parent: 1 + - uid: 438 + components: + - type: Transform + pos: -0.2544223,1.9354329 + parent: 1 +- proto: WheatSeeds + entities: + - uid: 353 + components: + - type: Transform + pos: 1.7039111,-3.7780442 + parent: 1 + - uid: 411 + components: + - type: Transform + pos: 1.8809944,-3.3197107 + parent: 1 + - uid: 412 + components: + - type: Transform + pos: -0.27525568,-3.9447105 + parent: 1 + - uid: 413 + components: + - type: Transform + pos: -0.17108893,-3.2988775 + parent: 1 + - uid: 414 + components: + - type: Transform + pos: 0.7768277,-3.8926275 + parent: 1 + - uid: 415 + components: + - type: Transform + pos: 1.1518278,-3.2988775 + parent: 1 + - uid: 416 + components: + - type: Transform + pos: 1.1934944,-3.8197107 + parent: 1 + - uid: 417 + components: + - type: Transform + pos: 1.1414111,-3.5801275 + parent: 1 + - uid: 418 + components: + - type: Transform + pos: 1.6518278,-3.3197107 + parent: 1 + - uid: 419 + components: + - type: Transform + pos: -0.29608893,-3.6322107 + parent: 1 + - uid: 420 + components: + - type: Transform + pos: 0.34974438,-3.7676275 + parent: 1 +- proto: WoodDoor + entities: + - uid: 66 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 1 +... diff --git a/Resources/Maps/Shuttles/cargo.yml b/Resources/Maps/Shuttles/cargo.yml index dab78a01026..ca0719dd602 100644 --- a/Resources/Maps/Shuttles/cargo.yml +++ b/Resources/Maps/Shuttles/cargo.yml @@ -3,6 +3,7 @@ meta: postmapinit: false tilemap: 0: Space + 1: FloorReinforced 84: FloorShuttleBlue 89: FloorShuttleWhite 93: FloorSteel @@ -22,15 +23,15 @@ entities: chunks: -1,0: ind: -1,0 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAWQAAAAAAVAAAAAAAWQAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAVAAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAQAAAAAAAQAAAAAAXQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAQAAAAAAAQAAAAAAXQAAAAAAAQAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAWQAAAAAAVAAAAAAAWQAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAVAAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 0,0: ind: 0,0 - tiles: XQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + tiles: XQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA version: 6 -1,-1: ind: -1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAAXQAAAAAA + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAbAAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAQAAAAAAAQAAAAAAXQAAAAAAAQAAAAAAAQAAAAAA version: 6 0,-1: ind: 0,-1 @@ -83,6 +84,7 @@ entities: 9: -2,2 10: -4,2 11: -5,2 + 24: -3,5 - node: color: '#A4610696' id: CheckerNWSE @@ -97,43 +99,36 @@ entities: id: QuarterTileOverlayGreyscale decals: 22: -3,-1 - - node: - color: '#A4610696' - id: QuarterTileOverlayGreyscale180 - decals: - 21: -3,5 - type: GridAtmosphere version: 2 data: tiles: - -2,-1: - 0: 52428 - -1,-1: - 0: 65535 - -1,-2: - 0: 61440 + -2,0: + 0: 51400 -2,1: - 0: 52428 + 0: 16520 + -2,-1: + 0: 32832 -1,0: 0: 65535 -1,1: - 0: 65535 + 0: 30719 + -2,2: + 0: 128 + -1,-1: + 0: 63346 -1,2: - 0: 255 - 0,-1: - 0: 4369 - 0,1: - 0: 4369 - 0,2: - 0: 1 + 0: 130 0,0: - 0: 4369 - -2,0: - 0: 52428 - -2,2: - 0: 140 + 0: 4112 + 0,1: + 0: 4096 -2,-2: 0: 32768 + -1,-2: + 0: 32768 + 0,-1: + 0: 16 uniqueMixes: - volume: 2500 temperature: 293.15 @@ -168,6 +163,12 @@ entities: parent: 173 - proto: AirlockGlassShuttle entities: + - uid: 2 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: 0.5,3.5 + parent: 173 - uid: 45 components: - type: Transform @@ -180,12 +181,6 @@ entities: rot: -1.5707963267948966 rad pos: -5.5,1.5 parent: 173 - - uid: 52 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: 0.5,3.5 - parent: 173 - uid: 53 components: - type: Transform @@ -197,6 +192,7 @@ entities: - uid: 79 components: - type: Transform + rot: -1.5707963267948966 rad pos: -0.5,6.5 parent: 173 - proto: AtmosDeviceFanDirectional @@ -247,6 +243,32 @@ entities: - type: Transform pos: -5.5,4.5 parent: 173 +- proto: ButtonFrameCaution + entities: + - uid: 52 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,5.5 + parent: 173 + - uid: 55 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 173 + - uid: 57 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,5.5 + parent: 173 + - uid: 58 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-0.5 + parent: 173 - proto: CableApcExtension entities: - uid: 100 @@ -573,6 +595,48 @@ entities: - type: Transform pos: -3.5,-0.5 parent: 173 +- proto: Catwalk + entities: + - uid: 174 + components: + - type: Transform + pos: -5.5,-2.5 + parent: 173 + - uid: 175 + components: + - type: Transform + pos: -4.5,-4.5 + parent: 173 + - uid: 176 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 173 + - uid: 177 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 173 + - uid: 178 + components: + - type: Transform + pos: -5.5,7.5 + parent: 173 + - uid: 179 + components: + - type: Transform + pos: 0.5,7.5 + parent: 173 + - uid: 180 + components: + - type: Transform + pos: -0.5,9.5 + parent: 173 + - uid: 181 + components: + - type: Transform + pos: -4.5,9.5 + parent: 173 - proto: ChairPilotSeat entities: - uid: 141 @@ -753,6 +817,61 @@ entities: - type: Transform pos: -3.5,-2.5 parent: 173 +- proto: HolopadLongRange + entities: + - uid: 186 + components: + - type: MetaData + name: long-range holopad (Cargo Shuttle) + - type: Transform + pos: -2.5,5.5 + parent: 173 + - type: Label + currentLabel: Cargo Shuttle + - type: NameModifier + baseName: long-range holopad +- proto: LockableButtonCargo + entities: + - uid: 182 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,-0.5 + parent: 173 + - type: DeviceLinkSource + linkedPorts: + 3: + - Pressed: Toggle + - uid: 183 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -5.5,5.5 + parent: 173 + - type: DeviceLinkSource + linkedPorts: + 56: + - Pressed: Toggle + - uid: 184 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,5.5 + parent: 173 + - type: DeviceLinkSource + linkedPorts: + 1: + - Pressed: Toggle + - uid: 185 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-0.5 + parent: 173 + - type: DeviceLinkSource + linkedPorts: + 54: + - Pressed: Toggle - proto: PlasticFlapsAirtightClear entities: - uid: 44 @@ -836,44 +955,6 @@ entities: - type: Transform pos: -3.5,8.5 parent: 173 -- proto: SignalButton - entities: - - uid: 2 - components: - - type: Transform - pos: -5.5,5.5 - parent: 173 - - type: DeviceLinkSource - linkedPorts: - 56: - - Pressed: Toggle - - uid: 55 - components: - - type: Transform - pos: -5.5,-0.5 - parent: 173 - - type: DeviceLinkSource - linkedPorts: - 3: - - Pressed: Toggle - - uid: 57 - components: - - type: Transform - pos: 0.5,5.5 - parent: 173 - - type: DeviceLinkSource - linkedPorts: - 1: - - Pressed: Toggle - - uid: 58 - components: - - type: Transform - pos: 0.5,-0.5 - parent: 173 - - type: DeviceLinkSource - linkedPorts: - 54: - - Pressed: Toggle - proto: SMESBasic entities: - uid: 84 @@ -1176,6 +1257,7 @@ entities: - uid: 66 components: - type: Transform + rot: -1.5707963267948966 rad pos: -3.5,-3.5 parent: 173 - uid: 67 diff --git a/Resources/Maps/Shuttles/trading_outpost.yml b/Resources/Maps/Shuttles/trading_outpost.yml index 890ac36641e..c88aad4a107 100644 --- a/Resources/Maps/Shuttles/trading_outpost.yml +++ b/Resources/Maps/Shuttles/trading_outpost.yml @@ -98,6 +98,7 @@ entities: 189: 4,-12 190: 5,-12 191: 6,-12 + 192: 5,2 - node: color: '#FFFFFFFF' id: Delivery @@ -568,6 +569,32 @@ entities: - type: Transform pos: -2.5,-6.5 parent: 2 +- proto: ButtonFrameCaution + entities: + - uid: 108 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 11.5,-7.5 + parent: 2 + - uid: 110 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-7.5 + parent: 2 + - uid: 120 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-1.5 + parent: 2 + - uid: 121 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 11.5,-1.5 + parent: 2 - proto: CableApcExtension entities: - uid: 31 @@ -4299,6 +4326,19 @@ entities: - type: Transform pos: 11.5,9.5 parent: 2 +- proto: HolopadLongRange + entities: + - uid: 546 + components: + - type: MetaData + name: long-range holopad (Automated Trade Station) + - type: Transform + pos: 5.5,2.5 + parent: 2 + - type: Label + currentLabel: Automated Trade Station + - type: NameModifier + baseName: long-range holopad - proto: NewtonCradle entities: - uid: 65 @@ -4727,20 +4767,19 @@ entities: - type: Transform pos: 7.5,3.5 parent: 2 -- proto: SignalSwitchDirectional +- proto: SignalButtonDirectional entities: - - uid: 108 + - uid: 545 components: - type: Transform - rot: -1.5707963267948966 rad - pos: 11.5,-1.5 + rot: 1.5707963267948966 rad + pos: -0.5,-7.5 parent: 2 - type: DeviceLinkSource linkedPorts: - 289: - - On: Open - - Off: Close - - uid: 110 + 290: + - Pressed: Toggle + - uid: 549 components: - type: Transform rot: 1.5707963267948966 rad @@ -4749,30 +4788,29 @@ entities: - type: DeviceLinkSource linkedPorts: 288: - - On: Open - - Off: Close - - uid: 120 + - Pressed: Toggle + - uid: 550 components: - type: Transform - rot: 1.5707963267948966 rad - pos: -0.5,-7.5 + rot: -1.5707963267948966 rad + pos: 11.5,-7.5 parent: 2 - type: DeviceLinkSource linkedPorts: - 290: - - On: Open - - Off: Close - - uid: 121 + 279: + - Pressed: Toggle + - uid: 551 components: - type: Transform rot: -1.5707963267948966 rad - pos: 11.5,-7.5 + pos: 11.5,-1.5 parent: 2 - type: DeviceLinkSource linkedPorts: - 279: - - On: Open - - Off: Close + 289: + - Pressed: Toggle +- proto: SignalSwitchDirectional + entities: - uid: 128 components: - type: Transform diff --git a/Resources/Maps/hammurabi.yml b/Resources/Maps/hammurabi.yml index 995367bb209..94f564b9d08 100644 --- a/Resources/Maps/hammurabi.yml +++ b/Resources/Maps/hammurabi.yml @@ -26135,7 +26135,7 @@ entities: name: null reagents: - data: null - ReagentId: HolyWater + ReagentId: Holywater Quantity: 60 - proto: AnomalyScanner entities: diff --git a/Resources/Maps/tortuga.yml b/Resources/Maps/tortuga.yml index 37b4562d381..e9c12c34688 100644 --- a/Resources/Maps/tortuga.yml +++ b/Resources/Maps/tortuga.yml @@ -19581,7 +19581,7 @@ entities: name: null reagents: - data: null - ReagentId: HolyWater + ReagentId: Holywater Quantity: 10 - uid: 28285 components: diff --git a/Resources/Migrations/migration.yml b/Resources/Migrations/migration.yml index fd93dec60d7..46501f63822 100644 --- a/Resources/Migrations/migration.yml +++ b/Resources/Migrations/migration.yml @@ -471,3 +471,56 @@ CrateCrewMonitoringBoards: CrateCrewMonitoring # 2024-11-25 CrateSlimepersonLifeSupport: CrateNitrogenInternals + +# 2024-12-01 +DungeonMasterCircuitBoard: GameMasterCircuitBoard + +# 2024-12-12 +FloraRockSolid01: FloraRockSolid +FloraRockSolid02: FloraRockSolid +FloraRockSolid03: FloraRockSolid +FloraStalagmite1: FloraStalagmite +FloraStalagmite2: FloraStalagmite +FloraStalagmite3: FloraStalagmite +FloraStalagmite4: FloraStalagmite +FloraStalagmite5: FloraStalagmite +FloraStalagmite6: FloraStalagmite +FloraGreyStalagmite1: FloraGreyStalagmite +FloraGreyStalagmite2: FloraGreyStalagmite +FloraGreyStalagmite3: FloraGreyStalagmite +FloraGreyStalagmite4: FloraGreyStalagmite +FloraGreyStalagmite5: FloraGreyStalagmite +FloraGreyStalagmite6: FloraGreyStalagmite +FloraTree01: FloraTree +FloraTree02: FloraTree +FloraTree03: FloraTree +FloraTree04: FloraTree +FloraTree05: FloraTree +FloraTree06: FloraTree +FloraTreeSnow01: FloraTreeSnow +FloraTreeSnow02: FloraTreeSnow +FloraTreeSnow03: FloraTreeSnow +FloraTreeSnow04: FloraTreeSnow +FloraTreeSnow05: FloraTreeSnow +FloraTreeSnow06: FloraTreeSnow +FloraTreeLarge01: FloraTreeLarge +FloraTreeLarge02: FloraTreeLarge +FloraTreeLarge03: FloraTreeLarge +FloraTreeLarge04: FloraTreeLarge +FloraTreeLarge05: FloraTreeLarge +FloraTreeLarge06: FloraTreeLarge +FloraTreeConifer01: FloraTreeConifer +FloraTreeConifer02: FloraTreeConifer +FloraTreeConifer03: FloraTreeConifer +ShadowTree01: ShadowTree +ShadowTree02: ShadowTree +ShadowTree03: ShadowTree +ShadowTree04: ShadowTree +ShadowTree05: ShadowTree +ShadowTree06: ShadowTree +LightTree01: LightTree +LightTree02: LightTree +LightTree03: LightTree +LightTree04: LightTree +LightTree05: LightTree +LightTree06: LightTree diff --git a/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml b/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml index 675fdc5062b..367848f9b2d 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/cargo.yml @@ -344,7 +344,10 @@ - id: Lamp prob: 0.01 orGroup: Useful - - id: FloraTreeLarge05 + - id: FloraTreeLarge + prob: 0.01 + orGroup: Useful + - id: LightTree #Funny mobs maybe prob: 0.01 orGroup: Useful #notuseful diff --git a/Resources/Prototypes/Catalog/Fills/Crates/food.yml b/Resources/Prototypes/Catalog/Fills/Crates/food.yml index d0f581faa86..c7256539906 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/food.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/food.yml @@ -138,6 +138,8 @@ amount: 2 - id: DrinkLemonLimeCan amount: 2 + - id: DrinkLemonLimeCranberryCan + amount: 2 - id: DrinkFourteenLokoCan amount: 2 @@ -145,8 +147,10 @@ id: CrateFoodSoftdrinksLarge parent: CratePlastic name: softdrinks bulk crate - description: Lots of sodas taken straight out of Centcomm's own vending machines, because you just can't leave your department. Includes 28 sodas. + description: Lots of sodas taken straight out of Centcomm's own vending machines, because you just can't leave your department. Includes 32 sodas. components: + - type: EntityStorage + capacity: 32 # Slightly over-sized CratePlastic to accomodate over 30 drink cans at once. - type: StorageFill contents: - id: DrinkColaCan @@ -159,6 +163,8 @@ amount: 4 - id: DrinkLemonLimeCan amount: 4 + - id: DrinkLemonLimeCranberryCan + amount: 4 - id: DrinkFourteenLokoCan amount: 4 diff --git a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml index 10595673708..551f8654684 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/fun.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/fun.yml @@ -16,6 +16,8 @@ weight: 9 - id: PlushieSpaceLizard weight: 1 + - id: PlushieLizardInversed + weight: 0.5 - !type:GroupSelector children: - id: PlushieCarp diff --git a/Resources/Prototypes/Catalog/Fills/Crates/security.yml b/Resources/Prototypes/Catalog/Fills/Crates/security.yml index f87e5168e0c..d9a247ee674 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/security.yml @@ -2,7 +2,7 @@ id: CrateSecurityArmor parent: CrateSecgear name: armor crate - description: Three vests of well-rounded, decently-protective armor. Requires Security access to open. + description: Contains three bulletproof vests. Requires Security access to open. components: - type: StorageFill contents: @@ -24,7 +24,7 @@ id: CrateSecurityNonlethal parent: CrateSecgear name: nonlethals crate - description: Disabler weapons. Requires Security access to open. + description: Contains a mix of disablers, stun batons, and flashes. Requires Security access to open. components: - type: StorageFill contents: diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml index fe3b1ebaba1..6e0216ebcbf 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/security.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/security.yml @@ -224,7 +224,7 @@ prob: 0.5 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafeDisabler name: disabler safe components: @@ -234,7 +234,7 @@ amount: 5 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafePistolMk58 name: mk58 safe components: @@ -246,7 +246,7 @@ amount: 8 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafeRifleLecter name: lecter safe components: @@ -258,7 +258,7 @@ amount: 4 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafeSubMachineGunDrozd name: drozd safe components: @@ -270,7 +270,7 @@ amount: 4 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafeShotgunEnforcer name: enforcer safe components: @@ -282,7 +282,7 @@ amount: 4 - type: entity - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] id: GunSafeShotgunKammerer name: kammerer safe components: @@ -296,7 +296,7 @@ - type: entity id: GunSafeSubMachineGunWt550 suffix: Wt550 - parent: [GunSafe, BaseRestrictedContraband] + parent: [GunSafeBaseSecure, BaseRestrictedContraband] name: wt550 safe components: - type: StorageFill @@ -307,7 +307,7 @@ amount: 4 - type: entity - parent: GunSafe + parent: GunSafeBaseSecure id: GunSafeLaserCarbine name: laser safe components: diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/cola.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/cola.yml index ace91025623..30166ca371a 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/cola.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/cola.yml @@ -7,6 +7,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml index 171eb196f77..34c8981b75f 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/gib.yml @@ -8,6 +8,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/pwrgame.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/pwrgame.yml index cb5a4f5c4bc..d51075c3584 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/pwrgame.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/pwrgame.yml @@ -8,6 +8,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/shamblersjuice.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/shamblersjuice.yml index 6ac70b26db2..6b9ff67f2bb 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/shamblersjuice.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/shamblersjuice.yml @@ -7,6 +7,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/soda.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/soda.yml index 339683d2c9a..4b2a9228135 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/soda.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/soda.yml @@ -7,6 +7,7 @@ DrinkIcedTeaCan: 3 DrinkSolDryCan: 3 DrinkLemonLimeCan: 3 + DrinkLemonLimeCranberryCan: 3 DrinkFourteenLokoCan: 3 emaggedInventory: DrinkNukieCan: 3 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/spaceup.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/spaceup.yml index a5f570bd46a..7b36a3fc81c 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/spaceup.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/spaceup.yml @@ -8,6 +8,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/starkist.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/starkist.yml index b1a74fc13e7..0e7dcf7482e 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/starkist.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/starkist.yml @@ -7,6 +7,7 @@ DrinkIcedTeaCan: 2 DrinkSolDryCan: 2 DrinkLemonLimeCan: 2 + DrinkLemonLimeCranberryCan: 2 DrinkFourteenLokoCan: 2 emaggedInventory: DrinkNukieCan: 2 diff --git a/Resources/Prototypes/Catalog/discount_categories.yml b/Resources/Prototypes/Catalog/discount_categories.yml index 5c512e9009b..8e1ef010f87 100644 --- a/Resources/Prototypes/Catalog/discount_categories.yml +++ b/Resources/Prototypes/Catalog/discount_categories.yml @@ -1,7 +1,7 @@ - type: discountCategory id: rareDiscounts # Dirty-cheap items that are rarely used and can be discounted to 0-ish cost to encourage usage. weight: 18 - maxItems: 2 + maxItems: 4 - type: discountCategory id: usualDiscounts # Cheap items that are used not very often. @@ -10,4 +10,4 @@ - type: discountCategory id: veryRareDiscounts # Casually used items that are widely used but can be (rarely) discounted for epic lulz. weight: 2 - maxItems: 1 + maxItems: 2 diff --git a/Resources/Prototypes/Catalog/spellbook_catalog.yml b/Resources/Prototypes/Catalog/spellbook_catalog.yml index 57172dc6657..d5a9b0f1b9a 100644 --- a/Resources/Prototypes/Catalog/spellbook_catalog.yml +++ b/Resources/Prototypes/Catalog/spellbook_catalog.yml @@ -91,6 +91,19 @@ - !type:ListingLimitedStockCondition stock: 1 +- type: listing + id: SpellbookMindSwap + name: spellbook-mind-swap-name + description: spellbook-mind-swap-description + productAction: ActionMindSwap + cost: + WizCoin: 2 + categories: + - SpellbookUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + # Equipment - type: listing id: SpellbookWandDoor diff --git a/Resources/Prototypes/Damage/containers.yml b/Resources/Prototypes/Damage/containers.yml index fb40e9b658f..9b90248e3d9 100644 --- a/Resources/Prototypes/Damage/containers.yml +++ b/Resources/Prototypes/Damage/containers.yml @@ -52,3 +52,23 @@ id: ShadowHaze supportedTypes: - Heat + +- type: damageContainer + id: ManifestedSpirit + supportedGroups: + - Metaphysical + - Brute + supportedTypes: + - Heat + - Shock + - Caustic + +- type: damageContainer + id: BiologicalMetaphysical + supportedGroups: + - Brute + - Burn + - Toxin + - Airloss + - Genetic + - Metaphysical diff --git a/Resources/Prototypes/Damage/groups.yml b/Resources/Prototypes/Damage/groups.yml index 71e4acdaeaa..3beeed7e6b2 100644 --- a/Resources/Prototypes/Damage/groups.yml +++ b/Resources/Prototypes/Damage/groups.yml @@ -40,3 +40,11 @@ name: damage-group-genetic damageTypes: - Cellular + +# Metaphysical damage types should be used very sparingly, and likely not affect normal crew entities. +# Revenants and other ghost-like/demonic creatures are more fitting. Healing should never be available via medicine. +- type: damageGroup + id: Metaphysical + name: damage-group-metaphysical + damageTypes: + - Holy diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index d23133d4f0d..68cd99b4df1 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -171,7 +171,7 @@ Piercing: 0.8 Cold: 0.8 Heat: 0.2 -# Holy: 3 If we ever get some holy or magic sort of damage type they should be vulnerable + Holy: 1.5 flatReductions: Heat: 3 @@ -274,6 +274,7 @@ Radiation: 0.0 Shock: 0.8 Bloodloss: 0.4 + Holy: 1 - type: damageModifierSet id: Cockroach @@ -307,3 +308,8 @@ id: PotassiumIodide coefficients: Radiation: 0.1 + +- type: damageModifierSet + id: ManifestedSpirit + coefficients: + Holy: 2 diff --git a/Resources/Prototypes/Damage/types.yml b/Resources/Prototypes/Damage/types.yml index 0107da24823..490add3b58d 100644 --- a/Resources/Prototypes/Damage/types.yml +++ b/Resources/Prototypes/Damage/types.yml @@ -21,19 +21,19 @@ name: damage-type-blunt armorCoefficientPrice: 2 armorFlatPrice: 10 - + - type: damageType id: Cellular name: damage-type-cellular armorCoefficientPrice: 5 armorFlatPrice: 30 - + - type: damageType id: Caustic name: damage-type-caustic armorCoefficientPrice: 5 armorFlatPrice: 30 - + - type: damageType id: Cold name: damage-type-cold @@ -84,3 +84,11 @@ name: damage-type-structural armorCoefficientPrice: 1 armorFlatPrice: 1 + +# Metaphysical damage. Damage represents supernatural injuries that emphasize the fantasy elements in the game +- type: damageType + id: Holy + name: damage-type-holy + armorCoefficientPrice: 1 + armorFlatPrice: 1 + diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index ab8c41f2f27..32fd8da97f5 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -61,7 +61,7 @@ - type: Familiar - type: Dispellable - type: Damageable - damageContainer: CorporealSpirit + damageContainer: ManifestedSpirit damageModifierSet: CorporealSpirit - type: Speech speechSounds: Bass diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml index 0343285c7a4..7f163090f8a 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml @@ -114,7 +114,7 @@ - !type:DoActsBehavior acts: [ "Destruction" ] - type: Damageable - damageContainer: Spirit + damageContainer: ManifestedSpirit damageModifierSet: CorporealSpirit - type: DamageOnDispel damage: diff --git a/Resources/Prototypes/DeltaV/Flavors/flavors.yml b/Resources/Prototypes/DeltaV/Flavors/flavors.yml index 3dc68673b0d..143128dae3a 100644 --- a/Resources/Prototypes/DeltaV/Flavors/flavors.yml +++ b/Resources/Prototypes/DeltaV/Flavors/flavors.yml @@ -10,11 +10,6 @@ flavorType: Complex description: flavor-complex-sublime -- type: flavor - id: holy - flavorType: Complex - description: flavor-complex-holy - - type: flavor id: seeds flavorType: Base @@ -122,230 +117,60 @@ description: flavor-complex-blellow #Delta-V additional drink flavors -- type: flavor - id: absinthe - flavorType: Complex - description: flavor-complex-absinthe - -- type: flavor - id: blue-curacao - flavorType: Complex - description: flavor-complex-blue-curacao - - type: flavor id: deadrum flavorType: Complex - description: flavor-complex-deadrum + description: flavor-complex-deadrum-deltav - type: flavor id: n-t-cahors flavorType: Complex - description: flavor-complex-n-t-cahors + description: flavor-complex-n-t-cahors-deltav - type: flavor id: poison-wine flavorType: Complex - description: flavor-complex-poison-wine - -- type: flavor - id: acidspit - flavorType: Complex - description: flavor-complex-acidspit - -- type: flavor - id: allies-cocktail - flavorType: Complex - description: flavor-complex-allies-cocktail - -- type: flavor - id: amasec - flavorType: Complex - description: flavor-complex-amasec - -- type: flavor - id: andalusia - flavorType: Complex - description: flavor-complex-andalusia - -- type: flavor - id: b52 - flavorType: Complex - description: flavor-complex-b52 - -- type: flavor - id: bahama-mama - flavorType: Complex - description: flavor-complex-bahama-mama - -- type: flavor - id: barefoot - flavorType: Complex - description: flavor-complex-barefoot - -- type: flavor - id: booger - flavorType: Complex - description: flavor-complex-booger - -- type: flavor - id: brave-bull - flavorType: Complex - description: flavor-complex-brave-bull - -- type: flavor - id: demons-blood - flavorType: Complex - description: flavor-complex-demons-blood - -- type: flavor - id: devils-kiss - flavorType: Complex - description: flavor-complex-devils-kiss + description: flavor-complex-poison-wine-deltav - type: flavor id: doctors-delight flavorType: Complex - description: flavor-complex-doctors-delight - -- type: flavor - id: driest-martini - flavorType: Complex - description: flavor-complex-driest-martini - -- type: flavor - id: erika-surprise - flavorType: Complex - description: flavor-complex-erika-surprise - -- type: flavor - id: gin-fizz - flavorType: Complex - description: flavor-complex-gin-fizz - -- type: flavor - id: gildlager - flavorType: Complex - description: flavor-complex-gildlager - -- type: flavor - id: grog - flavorType: Complex - description: flavor-complex-grog - -- type: flavor - id: hippies-delight - flavorType: Complex - description: flavor-complex-hippies-delight - -- type: flavor - id: hooch - flavorType: Complex - description: flavor-complex-hooch + description: flavor-complex-doctors-delight-deltav - type: flavor id: irish-cream flavorType: Complex - description: flavor-complex-irish-cream + description: flavor-complex-irish-cream-deltav - type: flavor id: kira-special flavorType: Complex - description: flavor-complex-kira-special - -- type: flavor - id: manhattan - flavorType: Complex - description: flavor-complex-manhattan - -- type: flavor - id: manhattan-project - flavorType: Complex - description: flavor-complex-manhattan-project - -- type: flavor - id: margarita - flavorType: Complex - description: flavor-complex-margarita - -- type: flavor - id: martini - flavorType: Complex - description: flavor-complex-martini - -- type: flavor - id: mojito - flavorType: Complex - description: flavor-complex-mojito - -- type: flavor - id: neurotoxin - flavorType: Complex - description: flavor-complex-neurotoxin - -- type: flavor - id: patron - flavorType: Complex - description: flavor-complex-patron - -- type: flavor - id: red-mead - flavorType: Complex - description: flavor-complex-red-mead + description: flavor-complex-kira-special-deltav - type: flavor id: rewriter flavorType: Complex - description: flavor-complex-rewriter - -- type: flavor - id: sbiten - flavorType: Complex - description: flavor-complex-sbiten + description: flavor-complex-rewriter-deltav - type: flavor id: silencer flavorType: Complex - description: flavor-complex-silencer - -- type: flavor - id: snow-white - flavorType: Complex - description: flavor-complex-snow-white - -- type: flavor - id: sui-dream - flavorType: Complex - description: flavor-complex-sui-dream + description: flavor-complex-silencer-deltav - type: flavor id: syndicate-bomb flavorType: Complex - description: flavor-complex-syndicate-bomb - -- type: flavor - id: toxins-special - flavorType: Complex - description: flavor-complex-toxins-special - -- type: flavor - id: vodka-martini - flavorType: Complex - description: flavor-complex-vodka-martini - -- type: flavor - id: vodka-tonic - flavorType: Complex - description: flavor-complex-vodka-tonic + description: flavor-complex-syndicate-bomb-deltav - type: flavor id: kvass flavorType: Complex - description: flavor-complex-kvass + description: flavor-complex-kvass-deltav - type: flavor id: mothamphetamine flavorType: Complex - description: flavor-complex-mothamphetamine + description: flavor-complex-mothamphetamine-deltav - type: flavor id: drgibbbloodred diff --git a/Resources/Prototypes/DeltaV/shaders.yml b/Resources/Prototypes/DeltaV/shaders.yml index 5f479acb36f..a3e95730459 100644 --- a/Resources/Prototypes/DeltaV/shaders.yml +++ b/Resources/Prototypes/DeltaV/shaders.yml @@ -1,6 +1,6 @@ # hologram - type: shader - id: Hologram + id: HologramDeltaV kind: source path: "/Textures/DeltaV/Shaders/hologram.swsl" params: @@ -11,4 +11,4 @@ - type: shader id: Crispy kind: source - path: "/Textures/Shaders/crispy.swsl" \ No newline at end of file + path: "/Textures/Shaders/crispy.swsl" diff --git a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml index 9c6716ca758..fae8e725eaa 100644 --- a/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml +++ b/Resources/Prototypes/Entities/Clothing/Back/backpacks.yml @@ -185,7 +185,7 @@ - type: entity parent: ClothingBackpack id: ClothingBackpackMerc - name: merc bag + name: mercenary bag description: A backpack that has been in many dangerous places, a reliable combat backpack. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml index 110fd4af50a..5dccfda8ad0 100644 --- a/Resources/Prototypes/Entities/Clothing/Belt/belts.yml +++ b/Resources/Prototypes/Entities/Clothing/Belt/belts.yml @@ -471,7 +471,7 @@ - type: Appearance - type: entity - parent: [ClothingBeltStorageBase, BaseRestrictedContraband, ClothingSlotBase] # DeltaV - add parent ClothingSlotBase + parent: [ClothingBeltStorageBase, ContentsExplosionResistanceBase, BaseRestrictedContraband, ClothingSlotBase] # DeltaV - add parent ClothingSlotBase id: ClothingBeltSecurity name: security belt description: Can hold security gear like handcuffs and flashes. @@ -488,6 +488,8 @@ tags: - Sidearm insertOnInteract: false + - type: ExplosionResistance + damageCoefficient: 0.9 - type: Storage whitelist: tags: diff --git a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml index 1dd1e0ba042..cb4f8689b33 100644 --- a/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml +++ b/Resources/Prototypes/Entities/Clothing/Hands/gloves.yml @@ -285,7 +285,7 @@ fiberColor: fibers-black - type: entity - parent: ClothingHandsGlovesCombat + parent: [ BaseSecurityCargoContraband, ClothingHandsGlovesCombat ] id: ClothingHandsMercGlovesCombat name: mercenary combat gloves description: High-quality combat gloves to protect hands from mechanical damage during combat. diff --git a/Resources/Prototypes/Entities/Clothing/Head/hats.yml b/Resources/Prototypes/Entities/Clothing/Head/hats.yml index 524325df292..6aed4a1d5b8 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hats.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hats.yml @@ -169,7 +169,7 @@ sprite: Clothing/Head/Hats/beret_brigmedic.rsi - type: entity - parent: [ ClothingHeadBase, BaseRestrictedContraband ] + parent: [ ClothingHeadBase ] id: ClothingHeadHatBeretMerc name: mercenary beret description: Olive beret, the badge depicts a jackal on a rock. diff --git a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml index dd1c58eca74..80a69000827 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/helmets.yml @@ -39,7 +39,7 @@ #Mercenary Helmet - type: entity - parent: [ ClothingHeadHelmetBase, BaseRestrictedContraband ] + parent: [ ClothingHeadHelmetBase, BaseMajorContraband ] id: ClothingHeadHelmetMerc name: mercenary helmet description: The combat helmet is commonly used by mercenaries, is strong, light and smells like gunpowder and the jungle. diff --git a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml index c79cfff0671..c4d1d394369 100644 --- a/Resources/Prototypes/Entities/Clothing/Masks/masks.yml +++ b/Resources/Prototypes/Entities/Clothing/Masks/masks.yml @@ -383,7 +383,7 @@ Heat: 0.95 - type: entity - parent: [ ClothingMaskGas, BaseRestrictedContraband ] + parent: [ ClothingMaskGas, BaseSecurityCargoContraband ] id: ClothingMaskGasMerc name: mercenary gas mask description: Slightly outdated, but reliable military-style gas mask. diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml index 711ec5176a0..0cecbcab7e8 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/vests.yml @@ -22,9 +22,9 @@ #Mercenary web vest - type: entity - parent: [ClothingOuterVestWeb, BaseMinorContraband] #web vest so it should have some pockets for ammo + parent: [ BaseMajorContraband, ClothingOuterVestWeb] #web vest so it should have some pockets for ammo id: ClothingOuterVestWebMerc - name: merc web vest + name: mercenary web vest description: A high-quality armored vest made from a hard synthetic material. It's surprisingly flexible and light, despite formidable armor plating. components: - type: Sprite diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml index 1475500a65c..fce33e6fccc 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/boots.yml @@ -73,7 +73,7 @@ sprite: Clothing/Shoes/Boots/highheelboots.rsi - type: entity - parent: [ ClothingShoesMilitaryBase, BaseRestrictedContraband ] + parent: [ ClothingShoesMilitaryBase ] id: ClothingShoesBootsMerc name: mercenary boots description: Boots that have gone through many conflicts and that have proven their combat reliability. diff --git a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml index d3c46470af3..dcb45067ed8 100644 --- a/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/Uniforms/jumpsuits.yml @@ -815,7 +815,7 @@ mode: SensorOff - type: entity - parent: [ ClothingUniformBase, BaseRestrictedContraband ] + parent: [ ClothingUniformBase ] id: ClothingUniformJumpsuitMercenary name: mercenary jumpsuit description: Clothing for real mercenaries who have gone through fire, water and the jungle of planets flooded with dangerous monsters or targets for which a reward has been assigned. diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_soda.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_soda.yml index e3339082b34..6d36fa802b0 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_soda.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Food_Drinks/drinks_soda.yml @@ -19,6 +19,7 @@ - DrinkColaCan - DrinkIcedTeaCan - DrinkLemonLimeCan + - DrinkLemonLimeCranberryCan - DrinkGrapeCan - DrinkRootBeerCan - DrinkSodaWaterCan diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml index b74a80c39d4..943b9065603 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/Salvage/tables_loot.yml @@ -49,6 +49,8 @@ - id: SheetSteel10 - id: SheetGlass10 - id: SheetPlastic10 + - id: ScrapGeneratorFuelTank + weight: 0.5 - id: PartRodMetal10 weight: 0.33 - id: MaterialWoodPlank10 @@ -64,6 +66,9 @@ id: SalvageScrapLarge table: !type:GroupSelector children: + - !type:NestedSelector + tableId: RandomGeneratorTable + weight: 2 - id: ScrapAirlock1 - id: ScrapCloset - id: ScrapFirelock1 @@ -250,3 +255,16 @@ children: - id: JetpackBlueFilled - id: JetpackBlackFilled + +- type: entityTable + id: RandomGeneratorTable + table: !type:GroupSelector + children: + - id: ScrapGeneratorPlasmaLeaking + - id: ScrapGeneratorUraniumLeaking + - id: ScrapGeneratorPlasma + weight: 0.5 + - id: ScrapGeneratorUranium + weight: 0.5 + - id: ScrapGeneratorFrame + weight: 0.25 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml index 4697606af9f..ef89ed9a169 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/anomaly.yml @@ -22,6 +22,7 @@ - AnomalyFlora - AnomalyShadow - AnomalyTech + - AnomalySanta #Remove in 2025 rareChance: 0.3 rarePrototypes: - RandomAnomalyInjectorSpawner @@ -66,5 +67,6 @@ - AnomalyTrapGravity - AnomalyTrapTech - AnomalyTrapRock + - AnomalyTrapSanta # Remove in 2025 chance: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/flora.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/flora.yml index 9abd35a920b..416833538a3 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/flora.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/flora.yml @@ -7,36 +7,14 @@ layers: - state: red - sprite: Objects/Decoration/Flora/flora_trees.rsi - state: icon + state: tree01 - type: RandomSpawner prototypes: - - FloraTreeLarge01 - - FloraTreeLarge02 - - FloraTreeLarge03 - - FloraTreeLarge04 - - FloraTreeLarge05 - - FloraTreeLarge06 - - FloraTreeConifer01 - - FloraTreeConifer02 - - FloraTreeConifer03 - - FloraTreeSnow01 - - FloraTreeSnow02 - - FloraTreeSnow03 - - FloraTreeSnow04 - - FloraTreeSnow05 - - FloraTreeSnow06 - - FloraTree01 - - FloraTree02 - - FloraTree03 - - FloraTree04 - - FloraTree05 - - FloraTree06 + - FloraTreeLarge + - FloraTreeConifer + - FloraTreeSnow + - FloraTree chance: 0.95 rarePrototypes: - - ShadowTree01 - - ShadowTree02 - - ShadowTree03 - - ShadowTree04 - - ShadowTree05 - - ShadowTree06 + - ShadowTree rareChance: 0.05 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/mineshaft.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/mineshaft.yml index 8d1b36c022b..34e6212dae2 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/mineshaft.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/mineshaft.yml @@ -63,12 +63,7 @@ - type: RandomSpawner offset: 0.3 prototypes: - - FloraStalagmite1 - - FloraStalagmite2 - - FloraStalagmite3 - - FloraStalagmite4 - - FloraStalagmite5 - - FloraStalagmite6 + - FloraStalagmite chance: 0.9 - type: entity @@ -84,10 +79,5 @@ - type: RandomSpawner offset: 0.3 prototypes: - - FloraGreyStalagmite1 - - FloraGreyStalagmite2 - - FloraGreyStalagmite3 - - FloraGreyStalagmite4 - - FloraGreyStalagmite5 - - FloraGreyStalagmite6 + - FloraGreyStalagmite chance: 0.9 diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/shadowkudzu.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/shadowkudzu.yml index 95a24157f98..bb799580955 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/shadowkudzu.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/shadowkudzu.yml @@ -14,12 +14,12 @@ - CrystalPink - CrystalPink - ShadowPortal - - ShadowTree01 - - ShadowTree02 - - ShadowTree03 - - ShadowTree04 - - ShadowTree05 - - ShadowTree06 + - ShadowTree #TODO: transform into EntityTable with weight + - ShadowTree + - ShadowTree + - ShadowTree + - ShadowTree + - ShadowTree rareChance: 0.05 rarePrototypes: - MobCatShadow diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index 9a519ab9ba1..bd56cf41603 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -354,4 +354,4 @@ ionStormAmount: 3 - type: IonStormTarget chance: 1 - \ No newline at end of file + - type: ShowJobIcons diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 3f5f37b9137..17677b223c6 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1673,7 +1673,6 @@ Dead: 0 baseDecayRate: 0.04 - type: Hunger - currentHunger: 25 # spawn with Okay hunger state thresholds: Overfed: 35 Okay: 25 @@ -1909,7 +1908,7 @@ state: slug sprite: Mobs/Animals/slug.rsi - type: Carriable #DeltaV - freeHandsRequired: 1 + freeHandsRequired: 1 - type: Physics - type: Fixtures fixtures: @@ -2245,24 +2244,10 @@ # Code unique spider prototypes or combine them all into one spider and get a # random sprite state when you spawn it. - type: entity - name: tarantula parent: [ SimpleMobBase, MobCombat ] - id: MobGiantSpider - description: Widely recognized to be the literal worst thing in existence. + id: MobSpiderBase + abstract: true components: - - type: Sprite - drawdepth: Mobs - layers: - - map: ["enum.DamageStateVisualLayers.Base", "movement"] - state: tarantula - sprite: Mobs/Animals/spider.rsi - - type: SpriteMovement - movementLayers: - movement: - state: tarantula-moving - noMovementLayers: - movement: - state: tarantula - type: Carriable #DeltaV ##shiva deserves to be held - type: Physics - type: Fixtures @@ -2276,14 +2261,6 @@ - MobMask layer: - MobLayer - - type: DamageStateVisuals - states: - Alive: - Base: tarantula - Critical: - Base: tarantula_dead - Dead: - Base: tarantula_dead - type: Butcherable spawned: - id: FoodMeatSpider @@ -2295,15 +2272,6 @@ thresholds: 0: Alive 90: Dead - - type: MeleeWeapon - altDisarm: false - angle: 0 - animation: WeaponArcBite - soundHit: - path: /Audio/Effects/bite.ogg - damage: - types: - Piercing: 6 - type: SolutionContainerManager solutions: melee: @@ -2364,32 +2332,74 @@ Brute: -0.07 Burn: -0.07 +- type: entity + parent: MobSpiderBase + id: MobSpiderAngryBase + abstract: true + components: + - type: NpcFactionMember + factions: + - Xeno + - type: InputMover + - type: MobMover + - type: HTN + rootTask: + task: SimpleHostileCompound + - type: GhostRole + makeSentient: true + name: ghost-role-information-giant-spider-name + description: ghost-role-information-giant-spider-description + rules: deltav-ghost-role-information-softantag-rules #DeltaV + raffle: + settings: short + - type: GhostTakeoverAvailable + - type: entity name: tarantula - parent: MobGiantSpider - id: MobGiantSpiderAngry - suffix: Angry + parent: MobSpiderBase + id: MobGiantSpider + description: Widely recognized to be the literal worst thing in existence. components: - - type: NpcFactionMember - factions: - - Xeno - - type: InputMover - - type: MobMover - - type: HTN - rootTask: - task: SimpleHostileCompound - - type: GhostRole - makeSentient: true - name: ghost-role-information-giant-spider-name - description: ghost-role-information-giant-spider-description - rules: deltav-ghost-role-information-softantag-rules #DeltaV - raffle: - settings: short - - type: GhostTakeoverAvailable + - type: Sprite + drawdepth: Mobs + layers: + - map: ["enum.DamageStateVisualLayers.Base", "movement"] + state: tarantula + sprite: Mobs/Animals/spider.rsi + - type: SpriteMovement + movementLayers: + movement: + state: tarantula-moving + noMovementLayers: + movement: + state: tarantula + - type: DamageStateVisuals + states: + Alive: + Base: tarantula + Critical: + Base: tarantula_dead + Dead: + Base: tarantula_dead + - type: MeleeWeapon + altDisarm: false + angle: 0 + animation: WeaponArcBite + soundHit: + path: /Audio/Effects/bite.ogg + damage: + types: + Piercing: 6 + +- type: entity + parent: + - MobGiantSpider + - MobSpiderAngryBase + id: MobGiantSpiderAngry - type: entity name: clown spider - parent: MobGiantSpiderAngry + parent: MobSpiderAngryBase id: MobClownSpider description: Combines the two most terrifying things in existence, spiders and clowns. components: @@ -2763,7 +2773,7 @@ - type: Bloodstream bloodReagent: DemonsBlood - type: Damageable - damageContainer: CorporealSpirit # Nyanotrasen - Corporeal Spirit allows Holy water to do damage + damageContainer: BiologicalMetaphysical damageModifierSet: Infernal - type: Temperature heatDamageThreshold: 4000 #They come from hell, so.. @@ -3428,6 +3438,13 @@ gender: epicene - type: Speech speechVerb: Plant + speechSounds: Alto + allowedEmotes: ['Chirp'] + - type: Vocal + sounds: + Male: UnisexDiona + Female: UnisexDiona + Unsexed: UnisexDiona - type: Tag tags: - DoorBumpOpener diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml b/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml index 45fb6a12bdd..c0f7a58f48f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml @@ -28,7 +28,7 @@ - type: Body prototype: Animal - type: Damageable - damageContainer: Biological + damageContainer: BiologicalMetaphysical damageModifierSet: HellSpawn - type: MovementSpeedModifier baseWalkSpeed: 2 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index 54acea2bfe3..f5a0f8c3111 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -16,16 +16,13 @@ sprite: DeltaV/Mobs/Ghosts/revenant.rsi # DeltaV - Custom revenant sprite layers: - state: active - - type: Reactive # Nyanotrasen - Holy Water affects Revenants - groups: - Acidic: [Touch] - type: StatusEffects allowed: - Stun - Corporeal - - type: Damageable # Nyanotrasen - Corporeal Spirit allows Holy water to do damage - damageContainer: CorporealSpirit - damageModifierSet: CorporealSpirit + - type: Damageable + damageContainer: ManifestedSpirit + damageModifierSet: Spirit # DeltaV: Keep revenant extreme weakness to heat and bible - type: NoSlip - type: Eye drawFov: false @@ -81,3 +78,6 @@ - RevenantTheme - type: Speech speechVerb: Ghost + - type: Reactive + groups: + Acidic: [Touch] diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index d3f97a7044a..09a58facd76 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -229,8 +229,7 @@ types: Blunt: 6 Structural: 4 - Caustic: 1 - Cellular: 3 + Caustic: 4 - type: entity name: yellow slime diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index a51fe522381..0dc96ba6f31 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -448,7 +448,6 @@ Dead: 0 baseDecayRate: 0.04 - type: Hunger - currentHunger: 25 # spawn with Okay hunger state thresholds: Overfed: 35 Okay: 25 diff --git a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml index 3ce21b21b25..20d5e1cf418 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/familiars.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/familiars.yml @@ -23,9 +23,9 @@ - type: Access tags: - Chapel - - type: Damageable # Nyanotrasen - Corporeal Spirit allows Holy water to do damage - damageContainer: CorporealSpirit - damageModifierSet: CorporealSpirit + - type: Damageable # DeltaV + damageContainer: BiologicalMetaphysical + damageModifierSet: ManifestedSpirit - type: MindContainer showExamineInfo: true - type: NpcFactionMember diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml index 3b57a312b08..918b9debc5c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon.yml @@ -77,7 +77,11 @@ - type: CommunicationsConsole canShuttle: false title: comms-console-announcement-title-station-ai - color: "#2ed2fd" + color: "#5ed7aa" + - type: HolographicAvatar + layerData: + - sprite: Mobs/Silicon/station_ai.rsi + state: default - type: ShowJobIcons - type: entity @@ -235,16 +239,16 @@ laws: OverlordLawset - type: entity - id: DungeonMasterCircuitBoard + id: GameMasterCircuitBoard parent: BaseElectronics - name: law board (Dungeon Master) - description: An electronics board containing the Dungeon Master lawset. + name: law board (Game Master) + description: An electronics board containing the Game Master lawset. components: - type: Sprite sprite: Objects/Misc/module.rsi state: std_mod - type: SiliconLawProvider - laws: DungeonMasterLawset + laws: GameMasterLawset - type: entity id: ArtistCircuitBoard @@ -312,6 +316,17 @@ Occupied: { state: full } - type: Intellicard +- type: entity + id: PlayerStationAiPreview + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: Mobs/Silicon/station_ai.rsi + layers: + - state: base + - state: ai + shader: unshaded + - type: entity id: PlayerStationAiEmpty name: AI Core @@ -349,12 +364,35 @@ map: ["unshaded"] shader: unshaded - type: Appearance + - type: InteractionPopup + interactSuccessString: petting-success-station-ai + interactFailureString: petting-failure-station-ai + messagePerceivedByOthers: petting-success-station-ai-others # Otherwise AI cannot tell its being pet as It's just a brain inside of the core, not the core itself. + interactSuccessSound: + path: /Audio/Ambience/Objects/periodic_beep.ogg - type: GenericVisualizer visuals: enum.StationAiVisualState.Key: unshaded: Empty: { state: ai_empty } Occupied: { state: ai } + - type: Telephone + compatibleRanges: + - Grid + - Map + - Unlimited + listeningRange: 0 + speakerVolume: Speak + unlistedNumber: true + requiresPower: false + - type: Holopad + - type: StationAiWhitelist + - type: UserInterface + interfaces: + enum.HolopadUiKey.AiRequestWindow: + type: HolopadBoundUserInterface + enum.HolopadUiKey.AiActionWindow: + type: HolopadBoundUserInterface # The job-ready version of an AI spawn. - type: entity @@ -438,6 +476,28 @@ shader: unshaded map: ["base"] +# The holographic representation of the AI that is projected from a holopad. +- type: entity + id: StationAiHoloLocal + name: AI hologram + description: A holographic representation of an AI. + categories: [ HideSpawnMenu ] + suffix: DO NOT MAP + components: + - type: Transform + anchored: true + - type: WarpPoint + follow: true + - type: Eye + - type: ContentEye + - type: Examiner + - type: Actions + - type: Alerts + - type: FTLSmashImmune + - type: CargoSellBlacklist + - type: StationAiVision + range: 20 + # Borgs - type: entity id: PlayerBorgBattery @@ -568,4 +628,4 @@ rules: ghost-role-information-silicon-rules raffle: settings: default - - type: GhostTakeoverAvailable \ No newline at end of file + - type: GhostTakeoverAvailable diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index e17ecfbc56f..7c12787a8c5 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -87,6 +87,7 @@ - MobLayer - type: Speech speechVerb: Plant + allowedEmotes: ['Chirp'] - type: Vocal sounds: Male: UnisexDiona diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 148532a2a12..1cb46c1b1a6 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -16,6 +16,7 @@ # they like eat it idk lol - type: Storage clickInsert: false + openOnActivate: false grid: - 0,0,1,2 maxItemSize: Large diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml index f5b733d37b1..59fc1a83a54 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_cans.yml @@ -137,7 +137,7 @@ - type: entity parent: DrinkCanBaseFull id: DrinkLemonLimeCan - name: lemon-lime can + name: Smite can description: You wanted ORANGE. It gave you Lemon-Lime. components: - type: SolutionContainerManager @@ -152,6 +152,57 @@ - type: Item sprite: Objects/Consumable/Drinks/lemon-lime.rsi +- type: entity + parent: DrinkCanBaseFull + id: DrinkLemonLimeCranberryCan + name: Smite Cranberry can + description: Y'all want a Smite Cranberry? Beloved by administrators everywhere. Drink in moderation. A limited run for the holidays! + components: + - type: SolutionContainerManager + solutions: + drink: + maxVol: 30 + reagents: + - ReagentId: LemonLimeCranberry + Quantity: 30 + - type: Sprite + sprite: Objects/Consumable/Drinks/lemon-lime-cranberry.rsi + - type: Item + sprite: Objects/Consumable/Drinks/lemon-lime-cranberry.rsi + - 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.1,0.1,0.1" + hard: false + mask: + - Impassable + - BulletImpassable + - type: EmbeddableProjectile + sound: /Audio/Weapons/punch1.ogg + embedOnThrow: True + - type: ThrowingAngle + angle: 0 + - type: LandAtCursor + - type: Ammo + muzzleFlash: null + - type: Projectile + deleteOnCollide: false + onlyCollideWhenShot: true + damage: + types: + Blunt: 1 + Cold: 2 # Refreshing + - type: entity parent: DrinkCanBaseFull id: DrinkGrapeCan diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml index ef6208b69d4..ed2324811e3 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Drinks/drinks_fun.yml @@ -106,6 +106,7 @@ - SpaceUp - SpaceMountainWind - LemonLime + - LemonLimeCranberry - PwrGame - quantity: 10 weight: 3 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml index da0bf0e7153..e3c51b45c5a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/Containers/box.yml @@ -706,6 +706,8 @@ orGroup: DrinkPool - id: DrinkLemonLimeCan orGroup: DrinkPool + - id: DrinkLemonLimeCranberryCan + orGroup: DrinkPool - id: DrinkIcedTeaCan orGroup: DrinkPool - id: ToyMouse diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml index b5b2e39e431..ee4c61ab7b6 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/burger.yml @@ -405,7 +405,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 4 @@ -583,7 +583,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 6 @@ -593,6 +593,9 @@ Quantity: 5 - ReagentId: Vitamin Quantity: 1 + - type: Tag + tags: + - Meat # Tastes like bun, HEAT. @@ -883,7 +886,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 55 + maxVol: 90 reagents: - ReagentId: Nutriment Quantity: 44 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml index 4142eae19c5..083f236ee71 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/ingredients.yml @@ -708,7 +708,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 6 + maxVol: 18 reagents: - ReagentId: Protein Quantity: 9 @@ -756,7 +756,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 5 + maxVol: 8 reagents: - ReagentId: Nutriment Quantity: 2 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/meals.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/meals.yml index 4f446584b15..c3c502701d4 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/meals.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/meals.yml @@ -483,7 +483,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 10 @@ -540,7 +540,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 18 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 6 @@ -565,7 +565,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 10 + maxVol: 15 reagents: - ReagentId: Nutriment Quantity: 8 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml index e4c9341d120..877f3d86f1a 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/produce.yml @@ -144,7 +144,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 7 + maxVol: 13 reagents: - ReagentId: Nutriment Quantity: 3 @@ -598,7 +598,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 18 + maxVol: 20 reagents: - ReagentId: Vitamin Quantity: 4 @@ -2054,7 +2054,7 @@ Quantity: 10 - ReagentId: Vitamin Quantity: 5 - - ReagentId: HolyWater # DeltaV - use our holy water + - ReagentId: Holywater Quantity: 10 - type: Sprite sprite: Objects/Specific/Hydroponics/holymelon.rsi @@ -2119,7 +2119,7 @@ Quantity: 2 - ReagentId: Vitamin Quantity: 1 - - ReagentId: HolyWater # DeltaV - use our holy water + - ReagentId: Holywater Quantity: 2 - type: Extractable juiceSolution: @@ -2592,4 +2592,4 @@ - type: FoodSequenceElement entries: Taco: AnomalyBerry - Burger: AnomalyBerryBurger \ No newline at end of file + Burger: AnomalyBerryBurger diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml index e9e57f79ce4..31963ca3b01 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/snacks.yml @@ -683,7 +683,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 30 + maxVol: 45 reagents: - ReagentId: Fiber Quantity: 40 diff --git a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml index d5cb4311c46..2fc551595f8 100644 --- a/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml +++ b/Resources/Prototypes/Entities/Objects/Consumable/Food/soup.yml @@ -83,7 +83,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 10 @@ -108,7 +108,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 8 @@ -168,7 +168,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 8 @@ -432,7 +432,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 40 + maxVol: 40.5 reagents: - ReagentId: Nutriment Quantity: 18 @@ -566,7 +566,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 15 reagents: - ReagentId: Nutriment Quantity: 6 @@ -591,7 +591,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 30 reagents: - ReagentId: Nutriment Quantity: 7 @@ -620,7 +620,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 1 @@ -646,7 +646,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 3 @@ -701,7 +701,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 2 @@ -727,7 +727,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 5 @@ -754,7 +754,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 12 + maxVol: 30.5 reagents: - ReagentId: Nutriment Quantity: 5 @@ -797,7 +797,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 20 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 8 @@ -1083,7 +1083,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 25 reagents: - ReagentId: Nutriment Quantity: 15 @@ -1137,7 +1137,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 10 + maxVol: 15 reagents: - ReagentId: Nutriment Quantity: 1 @@ -1214,7 +1214,7 @@ - type: SolutionContainerManager solutions: food: - maxVol: 15 + maxVol: 20 reagents: - ReagentId: Nutriment Quantity: 6 diff --git a/Resources/Prototypes/Entities/Objects/Decoration/flora.yml b/Resources/Prototypes/Entities/Objects/Decoration/flora.yml index 90daabc236a..3a575b0a9dc 100644 --- a/Resources/Prototypes/Entities/Objects/Decoration/flora.yml +++ b/Resources/Prototypes/Entities/Objects/Decoration/flora.yml @@ -147,30 +147,24 @@ - type: entity parent: BaseRock - id: FloraRockSolid01 + id: FloraRockSolid components: - type: Sprite - state: rocksolid01 - -- type: entity - parent: BaseRock - id: FloraRockSolid02 - components: - - type: Sprite - state: rocksolid02 - -- type: entity - parent: BaseRock - id: FloraRockSolid03 - components: - - type: Sprite - state: rocksolid03 + layers: + - state: rocksolid01 + map: ["random"] + - type: RandomSprite + available: + - random: + rocksolid01: "" + rocksolid02: "" + rocksolid03: "" - type: entity name: stalagmite description: Natural stone spikes. parent: BaseRock - id: FloraStalagmite1 + id: FloraStalagmite components: - type: Destructible thresholds: @@ -185,192 +179,75 @@ collection: GlassBreak - type: Sprite sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite1 + layers: + - state: stalagmite1 + map: ["random"] + - type: RandomSprite + available: + - random: + stalagmite1: "" + stalagmite2: "" + stalagmite3: "" + stalagmite4: "" + stalagmite5: "" + stalagmite6: "" - type: entity - parent: FloraStalagmite1 - id: FloraStalagmite2 + parent: FloraStalagmite + id: FloraGreyStalagmite components: - type: Sprite sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite2 - -- type: entity - parent: FloraStalagmite1 - id: FloraStalagmite3 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite3 - -- type: entity - parent: FloraStalagmite1 - id: FloraStalagmite4 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite4 - -- type: entity - parent: FloraStalagmite1 - id: FloraStalagmite5 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite5 - -- type: entity - parent: FloraStalagmite1 - id: FloraStalagmite6 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: stalagmite6 - -- type: entity - parent: FloraStalagmite1 - id: FloraGreyStalagmite1 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite1 - -- type: entity - parent: FloraGreyStalagmite1 - id: FloraGreyStalagmite2 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite2 - -- type: entity - parent: FloraGreyStalagmite1 - id: FloraGreyStalagmite3 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite3 - -- type: entity - parent: FloraGreyStalagmite1 - id: FloraGreyStalagmite4 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite4 - -- type: entity - parent: FloraGreyStalagmite1 - id: FloraGreyStalagmite5 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite5 - -- type: entity - parent: FloraGreyStalagmite1 - id: FloraGreyStalagmite6 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_stalagmite.rsi - state: grey_stalagmite6 - - -- type: entity - parent: BaseTree - id: FloraTree01 - name: tree - components: - - type: Sprite - state: tree01 - -- type: entity - parent: BaseTree - id: FloraTree02 - name: tree - components: - - type: Sprite - state: tree02 - -- type: entity - parent: BaseTree - id: FloraTree03 - name: tree - components: - - type: Sprite - state: tree03 + layers: + - state: grey_stalagmite1 + map: ["random"] + - type: RandomSprite + available: + - random: + grey_stalagmite1: "" + grey_stalagmite2: "" + grey_stalagmite3: "" + grey_stalagmite4: "" + grey_stalagmite5: "" + grey_stalagmite6: "" - type: entity parent: BaseTree - id: FloraTree04 + id: FloraTree name: tree components: - type: Sprite - state: tree04 - -- type: entity - parent: BaseTree - id: FloraTree05 - name: tree - components: - - type: Sprite - state: tree05 - -- type: entity - parent: BaseTree - id: FloraTree06 - name: tree - components: - - type: Sprite - state: tree06 - -- type: entity - parent: BaseTreeSnow - id: FloraTreeSnow01 - name: snowy tree - components: - - type: Sprite - state: treesnow01 + layers: + - state: tree01 + map: ["random"] + - type: RandomSprite + available: + - random: + tree01: "" + tree02: "" + tree03: "" + tree04: "" + tree05: "" + tree06: "" - type: entity parent: BaseTreeSnow - id: FloraTreeSnow02 + id: FloraTreeSnow name: snowy tree components: - type: Sprite - state: treesnow02 - -- type: entity - parent: BaseTreeSnow - id: FloraTreeSnow03 - name: snowy tree - components: - - type: Sprite - state: treesnow03 - -- type: entity - parent: BaseTreeSnow - id: FloraTreeSnow04 - name: snowy tree - components: - - type: Sprite - state: treesnow04 - -- type: entity - parent: BaseTreeSnow - id: FloraTreeSnow05 - name: snowy tree - components: - - type: Sprite - state: treesnow05 - -- type: entity - parent: BaseTreeSnow - id: FloraTreeSnow06 - name: snowy tree - components: - - type: Sprite - state: treesnow06 + layers: + - state: treesnow01 + map: ["random"] + - type: RandomSprite + available: + - random: + treesnow01: "" + treesnow02: "" + treesnow03: "" + treesnow04: "" + treesnow05: "" + treesnow06: "" - type: entity parent: BaseTreeSnow @@ -382,75 +259,38 @@ - type: entity parent: BaseTreeLarge - id: FloraTreeLarge01 - name: large tree - components: - - type: Sprite - state: treelarge01 - -- type: entity - parent: BaseTreeLarge - id: FloraTreeLarge02 - name: large tree - components: - - type: Sprite - state: treelarge02 - -- type: entity - parent: BaseTreeLarge - id: FloraTreeLarge03 - name: large tree - components: - - type: Sprite - state: treelarge03 - -- type: entity - parent: BaseTreeLarge - id: FloraTreeLarge04 - name: large tree - components: - - type: Sprite - state: treelarge04 - -- type: entity - parent: BaseTreeLarge - id: FloraTreeLarge05 - name: large tree - components: - - type: Sprite - state: treelarge05 - -- type: entity - parent: BaseTreeLarge - id: FloraTreeLarge06 + id: FloraTreeLarge name: large tree components: - type: Sprite - state: treelarge06 - -- type: entity - parent: BaseTreeConifer - id: FloraTreeConifer01 - name: snowy conifer - components: - - type: Sprite - state: treeconifer01 + layers: + - state: treelarge01 + map: ["random"] + - type: RandomSprite + available: + - random: + treelarge01: "" + treelarge02: "" + treelarge03: "" + treelarge04: "" + treelarge05: "" + treelarge06: "" - type: entity parent: BaseTreeConifer - id: FloraTreeConifer02 + id: FloraTreeConifer name: snowy conifer components: - type: Sprite - state: treeconifer02 - -- type: entity - parent: BaseTreeConifer - id: FloraTreeConifer03 - name: snowy conifer - components: - - type: Sprite - state: treeconifer03 + layers: + - state: treeconifer01 + map: ["random"] + - type: RandomSprite + available: + - random: + treeconifer01: "" + treeconifer02: "" + treeconifer03: "" - type: entity parent: BaseTreeConifer @@ -517,53 +357,17 @@ state: treestumpconifer - type: entity - parent: FloraTree01 - id: ShadowTree01 + parent: FloraTree + id: ShadowTree name: dark wood description: The leaves are whispering about you. components: - type: Sprite sprite: Objects/Decoration/Flora/flora_shadow_trees.rsi - state: tree01 - -- type: entity - parent: ShadowTree01 - id: ShadowTree02 - components: - - type: Sprite - state: tree02 - -- type: entity - parent: ShadowTree01 - id: ShadowTree03 - components: - - type: Sprite - state: tree03 - -- type: entity - parent: ShadowTree01 - id: ShadowTree04 - components: - - type: Sprite - state: tree04 - -- type: entity - parent: ShadowTree01 - id: ShadowTree05 - components: - - type: Sprite - state: tree05 - -- type: entity - parent: ShadowTree01 - id: ShadowTree06 - components: - - type: Sprite - state: tree06 - type: entity parent: BaseTree - id: LightTree01 + id: LightTree name: glowing tree description: A marvelous tree filled with strange energy. components: @@ -573,7 +377,18 @@ color: "#6270bb" - type: Sprite sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree01 + layers: + - state: tree01 + map: ["random"] + - type: RandomSprite + available: + - random: + tree01: "" + tree02: "" + tree03: "" + tree04: "" + tree05: "" + tree06: "" - type: Destructible thresholds: - trigger: @@ -603,44 +418,4 @@ spawn: MobLuminousObject: min: 0 - max: 1 - -- type: entity - parent: LightTree01 - id: LightTree02 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree02 - -- type: entity - parent: LightTree01 - id: LightTree03 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree03 - -- type: entity - parent: LightTree01 - id: LightTree04 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree04 - -- type: entity - parent: LightTree01 - id: LightTree05 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree05 - -- type: entity - parent: LightTree01 - id: LightTree06 - components: - - type: Sprite - sprite: Objects/Decoration/Flora/flora_treeslight.rsi - state: tree06 + max: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/holopad.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/holopad.yml new file mode 100644 index 00000000000..450a43c8d19 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/holopad.yml @@ -0,0 +1,12 @@ +- type: entity + id: HolopadMachineCircuitboard + parent: BaseMachineCircuitboard + name: holopad machine board + description: A machine printed circuit board for a holopad. + components: + - type: MachineBoard + prototype: Holopad + stackRequirements: + Capacitor: 4 + Cable: 4 + Glass: 2 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml index 5b35979f953..562bd169a1f 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml @@ -668,6 +668,25 @@ amount: 4 defaultPrototype: PowerCellSmall +- type: entity + id: SMESAdvancedMachineCircuitboard + parent: BaseMachineCircuitboard + name: advanced SMES machine board + description: A machine printed circuit board for an Advanced SMES. + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: power_mod + - type: MachineBoard + prototype: SMESAdvancedEmpty + stackRequirements: + Capacitor: 2 + CableHV: 20 + componentRequirements: + PowerCell: + amount: 4 + defaultPrototype: PowerCellMedium + - type: entity id: CellRechargerCircuitboard parent: BaseMachineCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml index bc0fbe136fb..95c1112316c 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/computer.yml @@ -26,6 +26,15 @@ components: - type: ComputerBoard prototype: ComputerAlert + +- type: entity + parent: BaseComputerCircuitboard + id: AtmosMonitoringComputerCircuitboard + name: atmospheric network monitor board + description: A computer printed circuit board for an atmospheric network monitor. + components: + - type: ComputerBoard + prototype: ComputerAtmosMonitoring - type: entity parent: BaseComputerCircuitboard diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml index 9f70f84ca86..3353ef66bfb 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml @@ -542,7 +542,7 @@ components: - type: NavMapBeacon defaultText: station-beacon-ai - color: "#2ed2fd" + color: "#5ed7aa" - type: entity parent: DefaultStationBeaconAI diff --git a/Resources/Prototypes/Entities/Objects/Fun/figurines.yml b/Resources/Prototypes/Entities/Objects/Fun/figurines.yml index 907c8323425..3611e56388d 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/figurines.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/figurines.yml @@ -19,8 +19,18 @@ - Figurine - type: UseDelay delay: 10 + - type: TriggerOnActivate + - type: TriggerOnSignal - type: Speech speechSounds: Pai # it just sounds better + - type: DeviceNetwork + deviceNetId: Wireless + receiveFrequencyId: BasicDevice + - type: WirelessNetworkConnection + range: 200 + - type: DeviceLinkSink + ports: + - Trigger - type: entity parent: BaseFigurine @@ -30,7 +40,7 @@ components: - type: Sprite state: hop - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesHoP - type: entity @@ -41,7 +51,7 @@ components: - type: Sprite state: passenger - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesPassenger - type: entity @@ -52,7 +62,7 @@ components: - type: Sprite state: passenger_greytide - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesGreytider - type: entity @@ -63,7 +73,7 @@ components: - type: Sprite state: clown - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesClown - type: entity @@ -74,7 +84,7 @@ components: - type: Sprite state: holoclown - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesHoloClown - type: entity @@ -85,7 +95,7 @@ components: - type: Sprite state: mime - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesMime - type: entity @@ -96,7 +106,7 @@ components: - type: Sprite state: musician - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesMusician - type: entity @@ -107,7 +117,7 @@ components: - type: Sprite state: boxer - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesBoxer - type: entity @@ -118,7 +128,7 @@ components: - type: Sprite state: captain - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesCaptain - type: entity @@ -129,7 +139,7 @@ components: - type: Sprite state: hos - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesHoS - type: entity @@ -140,7 +150,7 @@ components: - type: Sprite state: warden - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesWarden - type: entity @@ -151,7 +161,7 @@ components: - type: Sprite state: detective - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesDetective - type: entity @@ -162,7 +172,7 @@ components: - type: Sprite state: security - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesSecurity - type: entity @@ -173,7 +183,7 @@ components: - type: Sprite state: lawyer - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesLawyer - type: entity @@ -184,7 +194,7 @@ components: - type: Sprite state: cargotech - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesCargoTech - type: entity @@ -195,7 +205,7 @@ components: - type: Sprite state: salvage - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesSalvage - type: entity @@ -206,7 +216,7 @@ components: - type: Sprite state: qm - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesQM - type: entity @@ -217,7 +227,7 @@ components: - type: Sprite state: ce - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesCE - type: entity @@ -228,7 +238,7 @@ components: - type: Sprite state: engineer - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesEngineer - type: entity @@ -239,7 +249,7 @@ components: - type: Sprite state: atmos - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesAtmosTech - type: entity @@ -250,7 +260,7 @@ components: - type: Sprite state: rd - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesRD - type: entity @@ -261,7 +271,7 @@ components: - type: Sprite state: scientist - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesScientist - type: entity @@ -272,7 +282,7 @@ components: - type: Sprite state: cmo - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesCMO - type: entity @@ -283,7 +293,7 @@ components: - type: Sprite state: chemist - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesChemist - type: entity @@ -294,7 +304,7 @@ components: - type: Sprite state: paramedic - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesParamedic - type: entity @@ -305,7 +315,7 @@ components: - type: Sprite state: medical - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesDoctor - type: entity @@ -316,7 +326,7 @@ components: - type: Sprite state: librarian - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesLibrarian - type: entity @@ -327,7 +337,7 @@ components: - type: Sprite state: chaplain - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesChaplain - type: entity @@ -338,7 +348,7 @@ components: - type: Sprite state: chef - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesChef - type: entity @@ -349,7 +359,7 @@ components: - type: Sprite state: bartender - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesBartender - type: entity @@ -360,7 +370,7 @@ components: - type: Sprite state: botanist - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesBotanist - type: entity @@ -371,7 +381,7 @@ components: - type: Sprite state: janitor - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesJanitor - type: entity @@ -382,7 +392,7 @@ components: - type: Sprite state: nukie - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesNukie - type: entity @@ -393,7 +403,7 @@ components: - type: Sprite state: nukie_elite - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesNukieElite - type: entity @@ -404,7 +414,7 @@ components: - type: Sprite state: nukie_commander - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesNukieCommander - type: entity @@ -415,7 +425,7 @@ components: - type: Sprite state: footsoldier - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesFootsoldier - type: entity @@ -426,7 +436,7 @@ components: - type: Sprite state: wizard - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesWizard - type: entity @@ -437,7 +447,7 @@ components: - type: Sprite state: wizard_fake - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesWizard #Nonhuman Figurines @@ -450,7 +460,7 @@ components: - type: Sprite state: spacedragon - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesSpaceDragon - type: entity @@ -461,7 +471,7 @@ components: - type: Sprite state: queen - # - type: SpeakOnUse # TODO add something + # - type: SpeakOnTrigger # TODO add something # pack: FigurinesQueen - type: entity @@ -472,7 +482,7 @@ components: - type: Sprite state: ratking - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesRatKing - type: entity @@ -483,7 +493,7 @@ components: - type: Sprite state: ratservant - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesRatServant - type: entity @@ -494,7 +504,7 @@ components: - type: Sprite state: mouse - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesMouse - type: entity @@ -505,7 +515,7 @@ components: - type: Sprite state: slime - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesSlime - type: entity @@ -516,7 +526,7 @@ components: - type: Sprite state: hamlet - - type: SpeakOnUse + - type: SpeakOnTrigger pack: FigurinesHamlet #TODO: Convert these to the new figurine sprite template and rename their sprite name. diff --git a/Resources/Prototypes/Entities/Objects/Fun/toys.yml b/Resources/Prototypes/Entities/Objects/Fun/toys.yml index 1569818b6af..75d76c63bfc 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/toys.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/toys.yml @@ -301,7 +301,10 @@ description: An adorable stuffed toy that resembles a lizardperson. Made by CentComm as a token initiative to combat speciesism in work environments. "Welcome your new colleagues as you do this plush, with open arms!" components: - type: Sprite + sprite: Objects/Fun/toys.rsi state: plushie_lizard + - type: Item + heldPrefix: plushielizard - type: EmitSoundOnUse sound: path: /Audio/Items/Toys/weh.ogg @@ -369,7 +372,8 @@ - type: Item size: Small - type: Sprite - state: plushie_lizard_mirrored + state: plushie_lizard + scale: -1, 1 - type: entity parent: BasePlushie @@ -414,6 +418,58 @@ - ReagentId: JuiceThatMakesYouWeh Quantity: 10 +- type: entity + parent: PlushieLizard + id: PlushieLizardInversed #Hew! + name: drazil plushie + description: An adorable stuffed toy that resembles a lizardperson from an inversed dimension. Hew! + components: + - type: Sprite + state: plushie_lizard_inversed + - type: Item + heldPrefix: plushielizardinversed + - type: EmitSoundOnUse + sound: + path: /Audio/Items/Toys/hew.ogg + - type: EmitSoundOnLand + sound: + path: /Audio/Items/Toys/hew.ogg + - type: EmitSoundOnActivate + sound: + path: /Audio/Items/Toys/hew.ogg + - type: EmitSoundOnTrigger + sound: + path: /Audio/Items/Toys/hew.ogg + - type: Food + requiresSpecialDigestion: true + useSound: + path: /Audio/Items/Toys/hew.ogg + - type: MeleeWeapon + wideAnimationRotation: 180 + soundHit: + path: /Audio/Items/Toys/hew.ogg + - type: Extractable + juiceSolution: + reagents: + - ReagentId: JuiceThatMakesYouHew + Quantity: 30 + - type: SolutionContainerManager + solutions: + food: + maxVol: 20 + reagents: + - ReagentId: Fiber + Quantity: 10 + - ReagentId: JuiceThatMakesYouHew + Quantity: 10 + - type: Clothing + quickEquip: false + sprite: Objects/Fun/toys.rsi + equippedPrefix: lizard-inversed + slots: + - HEAD + + - type: entity parent: BasePlushie id: PlushieDiona diff --git a/Resources/Prototypes/Entities/Objects/Materials/scrap.yml b/Resources/Prototypes/Entities/Objects/Materials/scrap.yml index 6530e3b1701..734111af6dc 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/scrap.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/scrap.yml @@ -26,6 +26,46 @@ tags: - Recyclable +- type: entity + parent: BaseStructure + id: BaseScrapLarge + abstract: true + name: scrap + description: Worthless junk. You could probably get some materials out of it though. + suffix: Scrap + components: + - type: InteractionOutline + - type: Damageable + damageContainer: Inorganic + damageModifierSet: FlimsyMetallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 150 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - type: Tag + tags: + - Recyclable + - type: Transform + anchored: False + noRot: true + - type: Physics + bodyType: Dynamic + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.40,-0.40,0.40,0.40" + density: 200 + mask: + - MachineMask + layer: + - MachineLayer + - type: entity parent: BaseScrap id: ScrapSteel @@ -404,5 +444,118 @@ Glass: 500 # 5 sheets Plastic: 100 # 1 sheet +- type: entity + parent: BaseScrapLarge + id: ScrapGeneratorPlasmaLeaking + name: leaking P.A.C.M.A.N. generator + description: An old looking P.A.C.M.A.N. generator whos plasma tanks started leaking. + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + snapCardinals: true + layers: + - state: old_generator_plasma + - state: old_generator_plasma_fuel_leak + - type: PhysicalComposition + materialComposition: + Steel: 4000 # 40 sheets + Plasma: 2500 # 25 sheets + Plastic: 500 # 5 sheets + +- type: entity + parent: BaseScrapLarge + id: ScrapGeneratorPlasma + name: old P.A.C.M.A.N. generator + description: An old looking P.A.C.M.A.N. generator. It's in very poor condition and non functional. + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + layers: + - state: old_generator_plasma + - type: PhysicalComposition + materialComposition: + Steel: 4000 # 40 sheets + Plasma: 1000 # 10 sheets + Plastic: 500 # 5 sheets + +- type: entity + parent: BaseScrapLarge + id: ScrapGeneratorUraniumLeaking + name: leaking S.U.P.E.R.P.A.C.M.A.N. generator + description: A S.U.P.E.R.P.A.C.M.A.N. generator that appears to have had some kind of catastrophic failure. Its leaking uranium. + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + layers: + - state: uranium_generator + - map: [ "extra" ] + - state: rad_dripping + shader: unshaded + - state: rad_outline + shader: unshaded + - type: RandomSprite + available: + - extra: + red_x: "" + nothing: "" + - type: MultiHandedItem + - type: PhysicalComposition + materialComposition: + Steel: 4000 # 40 sheets + Uranium: 2500 # 25 sheets + Plastic: 500 # 5 sheets + - type: RadiationSource + intensity: 1.0 + +- type: entity + parent: BaseScrapLarge + id: ScrapGeneratorUranium + name: destroyed S.U.P.E.R.P.A.C.M.A.N. generator + description: A S.U.P.E.R.P.A.C.M.A.N. generator that appears to have had some kind of catastrophic failure. + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + layers: + - state: uranium_generator + - map: [ "extra" ] + - type: RandomSprite + available: + - extra: + red_x: "" + nothing: "" + - type: PhysicalComposition + materialComposition: + Steel: 4000 # 40 sheets + Uranium: 1000 # 10 sheets + Plastic: 500 # 5 sheets + - type: RadiationSource + intensity: 0.5 +- type: entity + parent: BaseScrapLarge + id: ScrapGeneratorFrame + name: generator frame + description: A frame of a P.A.C.M.A.N. or S.U.P.E.R.P.A.C.M.A.N. type generator. Where is the rest of it? + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + layers: + - state: generator_frame + - type: PhysicalComposition + materialComposition: + Steel: 1500 # 15 sheets +- type: entity + parent: BaseScrap + id: ScrapGeneratorFuelTank + name: fuel tank + description: A fuel tank from a S.U.P.E.R.P.A.C.M.A.N. type generator. The gauge indicates its got a little fuel left. + components: + - type: Sprite + sprite: Objects/Materials/Scrap/generator.rsi + layers: + - state: uranium_generator_fuel_tank + - type: PhysicalComposition + materialComposition: + Steel: 200 # 2 sheets + Uranium: 500 # 5 sheets diff --git a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml index e6b81e7ce40..21b710a6180 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/kudzu.yml @@ -134,12 +134,12 @@ offset: 0.2 chance: 0.05 prototypes: - - LightTree01 - - LightTree02 - - LightTree03 - - LightTree04 - - LightTree05 - - LightTree06 + - LightTree #TODO: transform into EntityTable with weight + - LightTree + - LightTree + - LightTree + - LightTree + - LightTree - CrystalCyan rarePrototypes: - AnomalyFloraBulb diff --git a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml index 91c90d1ce96..c6d27dba67d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/spaceshroom.yml @@ -76,6 +76,28 @@ weightedRandomId: RandomFillSpaceshroom - type: StaticPrice price: 20 + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.25,0.25,0.25" + density: 1 + mask: + - ItemMask + restitution: 0.3 + friction: 0.2 + - type: Construction + graph: CookedSpaceshroom + node: start + defaultTarget: cooked spaceshroom + - type: AtmosExposed + - type: Temperature + currentTemperature: 290 + - type: InternalTemperature + thickness: 0.02 + area: 0.02 + conductivity: 0.43 - type: weightedRandomFillSolution id: RandomFillSpaceshroom @@ -126,3 +148,6 @@ - type: Produce - type: StaticPrice price: 40 + - type: Construction + graph: CookedSpaceshroom + node: cooked spaceshroom diff --git a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml index 62b5f5c1ddf..f21106e6a77 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Chapel/bibles.yml @@ -18,6 +18,13 @@ damageOnUntrainedUse: ## What a non-chaplain takes when attempting to heal someone groups: Burn: 10 + - type: MeleeWeapon + soundHit: + collection: Punch + damage: + types: + Holy: 10 # DeltaV: was 25 + Blunt: 1 - type: Prayable bibleUserOnly: true - type: Summonable @@ -42,11 +49,6 @@ interfaces: enum.StorageUiKey.Key: type: StorageBoundUserInterface - - type: MeleeWeapon # Nyanotrasen - Bibles do Holy damage - damage: - types: - Blunt: 3 - Holy: 10 - type: Tag tags: - Book diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml index fb0f3d52c68..e188794b81f 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/defib.yml @@ -13,9 +13,6 @@ map: [ "enum.ToggleVisuals.Layer" ] visible: false shader: unshaded - - state: ready - map: ["enum.PowerDeviceVisualLayers.Powered"] - shader: unshaded - type: Appearance - type: GenericVisualizer visuals: @@ -23,10 +20,6 @@ enum.ToggleVisuals.Layer: True: { visible: true } False: { visible: false } - enum.DefibrillatorVisuals.Ready: - enum.PowerDeviceVisualLayers.Powered: - True: { visible: true } - False: { visible: false } - type: Item size: Large - type: Speech 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 d6269efcc0d..4a2928897a8 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml @@ -745,15 +745,15 @@ path: /Audio/Weapons/Guns/Gunshots/laser_cannon.ogg - type: ProjectileBatteryAmmoProvider proto: BulletLaserSpread - fireCost: 100 + fireCost: 150 - type: BatteryWeaponFireModes fireModes: - proto: BulletLaserSpread - fireCost: 100 + fireCost: 150 - proto: BulletLaserSpreadNarrow - fireCost: 100 + fireCost: 200 - proto: BulletDisablerSmgSpread - fireCost: 100 + fireCost: 120 - type: Item size: Large shape: @@ -767,5 +767,10 @@ stealGroup: WeaponEnergyShotgun - type: GunRequiresWield #remove when inaccuracy on spreads is fixed - type: Battery - maxCharge: 800 - startingCharge: 800 + maxCharge: 1200 + startingCharge: 1200 + - type: BatterySelfRecharger + autoRecharge: true + autoRechargeRate: 24 + autoRechargePause: true + autoRechargePauseTime: 10 \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml index de0e92dd671..8da72b49ae0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml @@ -195,6 +195,15 @@ factions: - SimpleHostile +- type: entity + parent: BaseWeaponTurret + id: WeaponTurretAllHostile + suffix: All hostile + components: + - type: NpcFactionMember + factions: + - AllHostile + - type: entity name: xeno turret description: Shoots 9mm acid projectiles. diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/gohei.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/gohei.yml index 2380e19d79f..ff39bc7a576 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/gohei.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/gohei.yml @@ -11,7 +11,7 @@ wideAnimationRotation: -150 damage: types: - Blunt: 0 + Holy: 4 - type: Item size: Small sprite: Objects/Weapons/Melee/gohei.rsi diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml index b62f9047b9a..c307d0ffbe3 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml @@ -129,7 +129,7 @@ - type: entity name: kukri knife - parent: [CombatKnife, BaseMinorContraband] + parent: [CombatKnife, BaseSecurityCargoContraband] id: KukriKnife description: Professionals have standards. Be polite. Be efficient. Have a plan to kill everyone you meet. components: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml deleted file mode 100644 index b041349d26e..00000000000 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/clusterbang.yml +++ /dev/null @@ -1,264 +0,0 @@ -- type: entity - parent: [BaseItem, BaseRestrictedContraband] - id: ClusterBang - name: clusterbang - description: Can be used only with flashbangs. Explodes several times. - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/clusterbang.rsi - state: base-0 - - type: Appearance - - type: ClusterGrenadeVisuals - state: base - - type: ClusterGrenade - - type: OnUseTimerTrigger - delay: 3.5 - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: GrenadeBase - id: ClusterBangFull - name: clusterbang - description: Launches three flashbangs after the timer runs out. - suffix: Full - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/clusterbang.rsi - layers: - - state: icon - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: GrenadeFlashBang - distance: 7 - velocity: 7 - - type: TimerTriggerVisuals - primingSound: - path: /Audio/Effects/countdown.ogg - - type: GenericVisualizer - visuals: - enum.Trigger.TriggerVisuals.VisualState: - enum.ConstructionVisuals.Layer: - Primed: { state: primed } - Unprimed: { state: icon } - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Machines/door_lock_off.ogg" - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: [GrenadeBase, BaseSyndicateContraband] - id: ClusterGrenade - name: clustergrenade - description: Why use one grenade when you can use three at once! - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/clusterbomb.rsi - layers: - - state: icon - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: ExGrenade - velocity: 3.5 - distance: 5 - - type: OnUseTimerTrigger - beepSound: - path: "/Audio/Effects/beep1.ogg" - params: - volume: 5 - initialBeepDelay: 0 - beepInterval: 0.5 - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Machines/door_lock_off.ogg" - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: [BaseItem, BaseSyndicateContraband] - id: ClusterBananaPeel - name: cluster banana peel - description: Splits into 6 explosive banana peels after throwing, guaranteed fun! - components: - - type: Sprite - sprite: Objects/Specific/Hydroponics/banana.rsi - state: produce - - type: Appearance - - type: ClusterGrenade - fillPrototype: TrashBananaPeelExplosive - maxGrenadesCount: 6 - baseTriggerDelay: 20 - - type: DamageOnLand - damage: - types: - Blunt: 10 - - type: LandAtCursor - - type: Damageable - damageContainer: Inorganic - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Items/bikehorn.ogg" - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 10 - behaviors: - - !type:TriggerBehavior - - !type:DoActsBehavior - acts: ["Destruction"] - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: [GrenadeBase, BaseSecurityContraband] - id: GrenadeStinger - name: stinger grenade - description: Nothing to see here, please disperse. - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/stingergrenade.rsi - layers: - - state: icon - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: PelletClusterRubber - maxGrenadesCount: 30 - grenadeType: enum.GrenadeType.Shoot - - type: FlashOnTrigger - range: 7 - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Effects/flash_bang.ogg" - - type: SpawnOnTrigger - proto: GrenadeFlashEffect - - type: TimerTriggerVisuals - primingSound: - path: /Audio/Effects/countdown.ogg - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: [GrenadeBase, BaseSyndicateContraband] - id: GrenadeIncendiary - name: incendiary grenade - description: Guaranteed to light up the mood. - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/pyrogrenade.rsi - layers: - - state: icon - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: PelletClusterIncendiary - maxGrenadesCount: 30 - grenadeType: enum.GrenadeType.Shoot - - type: OnUseTimerTrigger - beepSound: - path: "/Audio/Effects/beep1.ogg" - params: - volume: 5 - initialBeepDelay: 0 - beepInterval: 2 - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: [GrenadeBase, BaseSyndicateContraband] - id: GrenadeShrapnel - name: shrapnel grenade - description: Releases a deadly spray of shrapnel that causes severe bleeding. - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/shrapnelgrenade.rsi - layers: - - state: icon - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: PelletClusterLethal - maxGrenadesCount: 30 - grenadeType: enum.GrenadeType.Shoot - - type: OnUseTimerTrigger - beepSound: - path: "/Audio/Effects/beep1.ogg" - params: - volume: 5 - initialBeepDelay: 0 - beepInterval: 2 - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: SoapSyndie - id: SlipocalypseClusterSoap - name: slipocalypse clustersoap - description: Spreads small pieces of syndicate soap over an area upon landing on the floor. - components: - - type: Sprite - sprite: Objects/Specific/Janitorial/soap.rsi - layers: - - state: syndie-4 - - type: Appearance - - type: ClusterGrenade - fillPrototype: SoapletSyndie - maxGrenadesCount: 30 - grenadeTriggerIntervalMax: 0 - grenadeTriggerIntervalMin: 0 - baseTriggerDelay: 60 - randomSpread: true - velocity: 3 - - type: DamageOnLand - damage: - types: - Blunt: 10 - - type: LandAtCursor - - type: EmitSoundOnTrigger - sound: - path: "/Audio/Effects/flash_bang.ogg" - - type: Damageable - damageContainer: Inorganic - - type: Destructible - thresholds: - - trigger: - !type:DamageTrigger - damage: 10 - behaviors: - - !type:TriggerBehavior - - !type:DoActsBehavior - acts: ["Destruction"] - - type: ContainerContainer - containers: - cluster-payload: !type:Container - -- type: entity - parent: GrenadeShrapnel - id: GrenadeFoamDart - name: foam dart grenade - description: Releases a bothersome spray of foam darts that cause severe welching. - components: - - type: Sprite - sprite: Objects/Weapons/Grenades/foamdart.rsi - layers: - - state: icon - map: ["Base"] - - state: primed - map: ["enum.TriggerVisualLayers.Base"] - - type: ClusterGrenade - fillPrototype: BulletFoam - maxGrenadesCount: 30 - grenadeType: enum.GrenadeType.Throw - velocity: 70 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml index a4c70262439..8e98694b15e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml @@ -84,6 +84,9 @@ guides: - Security - Antagonists + - type: Tag + tags: + - GrenadeFlashBang - type: entity id: GrenadeFlashEffect diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml new file mode 100644 index 00000000000..07c2a7ad3cd --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml @@ -0,0 +1,105 @@ +- type: entity + abstract: true + parent: BaseItem + id: ProjectileGrenadeBase + components: + - type: Appearance + - type: Damageable + damageContainer: Inorganic + - type: DeleteOnTrigger + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 10 + behaviors: + - !type:TriggerBehavior + - type: ContainerContainer + containers: + cluster-payload: !type:Container + - type: ProjectileGrenade + +- type: entity + parent: [ProjectileGrenadeBase, BaseRestrictedContraband] + id: GrenadeStinger + name: stinger grenade + description: Nothing to see here, please disperse. + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/stingergrenade.rsi + layers: + - state: icon + map: ["enum.TriggerVisualLayers.Base"] + - type: ProjectileGrenade + fillPrototype: PelletClusterRubber + capacity: 30 + - type: FlashOnTrigger + range: 7 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Effects/flash_bang.ogg" + - type: SpawnOnTrigger + proto: GrenadeFlashEffect + - type: OnUseTimerTrigger + beepSound: + path: "/Audio/Effects/beep1.ogg" + params: + volume: 5 + initialBeepDelay: 0 + beepInterval: 2 + delay: 3.5 + - type: TimerTriggerVisuals + primingSound: + path: /Audio/Effects/countdown.ogg + +- type: entity + parent: [ProjectileGrenadeBase, BaseSyndicateContraband] + id: GrenadeIncendiary + name: incendiary grenade + description: Guaranteed to light up the mood. + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/pyrogrenade.rsi + layers: + - state: icon + map: ["enum.TriggerVisualLayers.Base"] + - type: ProjectileGrenade + fillPrototype: PelletClusterIncendiary + capacity: 30 + - type: OnUseTimerTrigger + beepSound: + path: "/Audio/Effects/beep1.ogg" + params: + volume: 5 + initialBeepDelay: 0 + beepInterval: 2 + delay: 3.5 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" + +- type: entity + parent: [ProjectileGrenadeBase, BaseSyndicateContraband] + id: GrenadeShrapnel + name: shrapnel grenade + description: Releases a deadly spray of shrapnel that causes severe bleeding. + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/shrapnelgrenade.rsi + layers: + - state: icon + map: ["enum.TriggerVisualLayers.Base"] + - type: ProjectileGrenade + fillPrototype: PelletClusterLethal + capacity: 30 + - type: OnUseTimerTrigger + beepSound: + path: "/Audio/Effects/beep1.ogg" + params: + volume: 5 + initialBeepDelay: 0 + beepInterval: 2 + delay: 3.5 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml new file mode 100644 index 00000000000..f68498bb51b --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml @@ -0,0 +1,174 @@ +# ScatteringGrenade is intended for grenades that spawn entities, especially those with timers +- type: entity + abstract: true + parent: BaseItem + id: ScatteringGrenadeBase + components: + - type: Appearance + - type: ContainerContainer + containers: + cluster-payload: !type:Container + - type: Damageable + damageContainer: Inorganic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 10 + behaviors: + - !type:TriggerBehavior + - type: ScatteringGrenade + +- type: entity + parent: [ScatteringGrenadeBase, BaseRestrictedContraband] + id: ClusterBang + name: clusterbang + description: Can be used only with flashbangs. Explodes several times. + components: + - type: ScatteringGrenade + whitelist: + tags: + - GrenadeFlashBang + distance: 6 + velocity: 6 + - type: ClusterGrenadeVisuals + state: base + - type: Sprite + sprite: Objects/Weapons/Grenades/clusterbang.rsi + state: base-0 + - type: OnUseTimerTrigger + delay: 3.5 + +- type: entity + parent: ClusterBang + id: ClusterBangFull + name: ClusterBang + description: Launches three flashbangs after the timer runs out. + suffix: Full + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/clusterbang.rsi + layers: + - state: icon + map: ["enum.TriggerVisualLayers.Base"] + - type: ScatteringGrenade + whitelist: + tags: + - GrenadeFlashBang + fillPrototype: GrenadeFlashBang + distance: 6 + velocity: 6 + - type: TimerTriggerVisuals + primingSound: + path: /Audio/Effects/countdown.ogg + - type: GenericVisualizer + visuals: + enum.Trigger.TriggerVisuals.VisualState: + enum.ConstructionVisuals.Layer: + Primed: { state: primed } + Unprimed: { state: icon } + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Machines/door_lock_off.ogg" + +- type: entity + parent: [ScatteringGrenadeBase, BaseSyndicateContraband] + id: ClusterGrenade + name: clustergrenade + description: Why use one grenade when you can use three at once! + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/clusterbomb.rsi + layers: + - state: icon + map: ["enum.TriggerVisualLayers.Base"] + - type: ScatteringGrenade + fillPrototype: ExGrenade + distance: 4 + - type: OnUseTimerTrigger + beepSound: + path: "/Audio/Effects/beep1.ogg" + params: + volume: 5 + initialBeepDelay: 0 + beepInterval: 0.5 + delay: 3.5 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Machines/door_lock_off.ogg" + +- type: entity + parent: [ScatteringGrenadeBase, BaseSyndicateContraband] + id: ClusterBananaPeel + name: cluster banana peel + description: Splits into 6 explosive banana peels after throwing, guaranteed fun! + components: + - type: Sprite + sprite: Objects/Specific/Hydroponics/banana.rsi + state: produce + - type: ScatteringGrenade + fillPrototype: TrashBananaPeelExplosive + capacity: 6 + delayBeforeTriggerContents: 20 + - type: LandAtCursor + - type: DamageOnLand + damage: + types: + Blunt: 10 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Items/bikehorn.ogg" + +- type: entity + parent: [SoapSyndie, ScatteringGrenadeBase, BaseSyndicateContraband] + id: SlipocalypseClusterSoap + name: slipocalypse clustersoap + description: Spreads small pieces of syndicate soap over an area upon landing on the floor. + components: + - type: Sprite + sprite: Objects/Specific/Janitorial/soap.rsi + layers: + - state: syndie-4 + - type: ScatteringGrenade + fillPrototype: SoapletSyndie + capacity: 30 + delayBeforeTriggerContents: 60 + randomDistance: true + randomThrowDistanceMax: 3 + - type: LandAtCursor + - type: DamageOnLand + damage: + types: + Blunt: 10 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Effects/flash_bang.ogg" + +- type: entity + parent: ScatteringGrenadeBase + id: GrenadeFoamDart + name: foam dart grenade + description: Releases a bothersome spray of foam darts that cause severe welching. + components: + - type: Sprite + sprite: Objects/Weapons/Grenades/foamdart.rsi + layers: + - state: icon + map: ["Base"] + - state: primed + map: ["enum.TriggerVisualLayers.Base"] + - type: ScatteringGrenade + fillPrototype: BulletFoam + capacity: 30 + velocity: 30 + - type: OnUseTimerTrigger + beepSound: + path: "/Audio/Effects/beep1.ogg" + params: + volume: 5 + initialBeepDelay: 0 + beepInterval: 2 + delay: 3.5 + - type: EmitSoundOnTrigger + sound: + path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg" diff --git a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml index e24c7a03d6c..31e233625a3 100644 --- a/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml +++ b/Resources/Prototypes/Entities/Structures/Dispensers/base_structuredispensers.yml @@ -63,6 +63,7 @@ whitelist: components: - FitsInDispenser + priority: 9 - type: ItemSlots - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml b/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml index e47a63ab730..36dc73269da 100644 --- a/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml +++ b/Resources/Prototypes/Entities/Structures/Furniture/potted_plants.yml @@ -30,7 +30,6 @@ - type: SecretStash tryInsertItemSound: /Audio/Effects/plant_rustle.ogg tryRemoveItemSound: /Audio/Effects/plant_rustle.ogg - hasVerbs: false secretStashName: secret-stash-plant - type: ContainerContainer containers: diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml index b461fbdcdd3..004a97fc860 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/computers.yml @@ -49,6 +49,39 @@ enum.WiresUiKey.Key: type: WiresBoundUserInterface +- type: entity + parent: BaseComputerAiAccess + id: ComputerAtmosMonitoring + name: atmospheric network monitor + description: Used to monitor the station's atmospheric networks. + components: + - type: Computer + board: AtmosMonitoringComputerCircuitboard + - type: Sprite + layers: + - map: ["computerLayerBody"] + state: computer + - map: ["computerLayerKeyboard"] + state: generic_keyboard + - map: ["computerLayerScreen"] + state: tank + - map: ["computerLayerKeys"] + state: atmos_key + - map: [ "enum.WiresVisualLayers.MaintenancePanel" ] + state: generic_panel_open + - type: AtmosMonitoringConsole + navMapTileColor: "#1a1a1a" + navMapWallColor: "#404040" + - type: ActivatableUI + singleUser: true + key: enum.AtmosMonitoringConsoleUiKey.Key + - type: UserInterface + interfaces: + enum.AtmosMonitoringConsoleUiKey.Key: + type: AtmosMonitoringConsoleBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface + - type: entity parent: BaseComputer id: ComputerEmergencyShuttle diff --git a/Resources/Prototypes/Entities/Structures/Machines/Computers/nav_map_blips.yml b/Resources/Prototypes/Entities/Structures/Machines/Computers/nav_map_blips.yml new file mode 100644 index 00000000000..bc515571860 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/Computers/nav_map_blips.yml @@ -0,0 +1,56 @@ +# All consoles +- type: navMapBlip + id: NavMapConsole + blinks: true + color: Cyan + texturePaths: + - "/Textures/Interface/NavMap/beveled_circle.png" + +# Atmos monitoring console +- type: navMapBlip + id: GasPipeSensor + selectable: true + color: "#ffcd00" + texturePaths: + - "/Textures/Interface/NavMap/beveled_star.png" + +- type: navMapBlip + id: GasVentOpening + scale: 0.6667 + color: LightGray + texturePaths: + - "/Textures/Interface/NavMap/beveled_square.png" + +- type: navMapBlip + id: GasVentScrubber + scale: 0.6667 + color: LightGray + texturePaths: + - "/Textures/Interface/NavMap/beveled_circle.png" + +- type: navMapBlip + id: GasFlowRegulator + scale: 0.75 + color: LightGray + texturePaths: + - "/Textures/Interface/NavMap/beveled_arrow_south.png" + - "/Textures/Interface/NavMap/beveled_arrow_east.png" + - "/Textures/Interface/NavMap/beveled_arrow_north.png" + - "/Textures/Interface/NavMap/beveled_arrow_west.png" + +- type: navMapBlip + id: GasValve + scale: 0.6667 + color: LightGray + texturePaths: + - "/Textures/Interface/NavMap/beveled_diamond_north_south.png" + - "/Textures/Interface/NavMap/beveled_diamond_east_west.png" + - "/Textures/Interface/NavMap/beveled_diamond_north_south.png" + - "/Textures/Interface/NavMap/beveled_diamond_east_west.png" + +- type: navMapBlip + id: Thermoregulator + scale: 0.6667 + color: LightGray + texturePaths: + - "/Textures/Interface/NavMap/beveled_hexagon.png" \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml index ed5137c28f5..3bc00fb1b61 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/Medical/cryo_pod.yml @@ -79,6 +79,8 @@ !type:PortablePipeNode nodeGroupID: Pipe pipeDirection: South + - type: AtmosMonitoringConsoleDevice + navMapBlip: Thermoregulator - type: ItemSlots slots: beakerSlot: diff --git a/Resources/Prototypes/Entities/Structures/Machines/holopad.yml b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml new file mode 100644 index 00000000000..b30057fae38 --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Machines/holopad.yml @@ -0,0 +1,847 @@ +- type: entity + parent: [ BaseMachinePowered, ConstructibleMachine ] + id: Holopad + name: holopad + description: "A floor-mounted device for projecting holographic images." + components: + - type: Transform + anchored: true + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 0.25 + mask: + - SubfloorMask + layer: + - LowImpassable + hard: false + - type: ApcPowerReceiver + powerLoad: 300 + - type: StationAiVision + - type: Sprite + sprite: Structures/Machines/holopad.rsi + drawdepth: FloorObjects + snapCardinals: true + layers: + - state: base + - map: [ "lights" ] + state: blank + shader: unshaded + - map: [ "enum.PowerDeviceVisualLayers.Powered" ] + state: unpowered + - map: [ "enum.WiresVisualLayers.MaintenancePanel" ] + state: panel_open + - type: Appearance + - type: GenericVisualizer + visuals: + enum.TelephoneVisuals.Key: + lights: + Idle: { state: blank } + Calling: { state: lights_calling } + Ringing: { state: lights_ringing } + InCall: { state: lights_in_call } + EndingCall: { state: lights_hanging_up } + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + False: { visible: true } + True: { visible: false } + enum.WiresVisuals.MaintenancePanelState: + enum.WiresVisualLayers.MaintenancePanel: + True: { visible: false } + False: { visible: true } + - type: Machine + board: HolopadMachineCircuitboard + - type: StationAiWhitelist + - type: PointLight + radius: 1.3 + energy: 1.8 + color: "#afe1fe" + enabled: false + - type: AmbientSound + enabled: false + volume: -5 + range: 3 + sound: + path: /Audio/Ambience/Objects/buzzing.ogg + - type: Holopad + hologramProtoId: HolopadHologram + - type: Speech + speechVerb: Robotic + speechSounds: Borg + speechBubbleOffset: 0.45 + - type: Telephone + ringTone: /Audio/Machines/double_ring.ogg + listeningRange: 2.5 + speakerVolume: Speak + - type: AccessReader + access: [[ "Command" ]] + - type: ActivatableUI + key: enum.HolopadUiKey.InteractionWindow + - type: ActivatableUIRequiresPower + - type: UserInterface + interfaces: + enum.HolopadUiKey.InteractionWindow: + type: HolopadBoundUserInterface + enum.WiresUiKey.Key: + type: WiresBoundUserInterface + - type: WiresPanel + - type: WiresVisuals + - type: Wires + boardName: wires-board-name-holopad + layoutId: Holopad + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:PlaySoundBehavior + sound: + collection: MetalBreak + - !type:ChangeConstructionNodeBehavior + node: machineFrame + - !type:DoActsBehavior + acts: ["Destruction"] + +- type: entity + name: long-range holopad + description: "A floor-mounted device for projecting holographic images to similar devices that are far away." + parent: Holopad + id: HolopadLongRange + components: + - type: Telephone + transmissionRange: Map + compatibleRanges: + - Map + - Unlimited + ignoreTelephonesOnSameGrid: true + +- type: entity + name: quantum entangling holopad + description: "An floor-mounted device for projecting holographic images to similar devices at extreme distances." + parent: Holopad + id: HolopadUnlimitedRange + components: + - type: Telephone + transmissionRange: Unlimited + compatibleRanges: + - Map + - Unlimited + ignoreTelephonesOnSameGrid: true + +- type: entity + name: bluespace holopad + description: "An experimental floor-mounted device for projecting holographic images via bluespace." + parent: Holopad + id: HolopadBluespace + suffix: Unrestricted range + components: + - type: Telephone + unlistedNumber: true + transmissionRange: Unlimited + compatibleRanges: + - Grid + - Map + - Unlimited + +# These are spawned by holopads +- type: entity + id: HolopadHologram + categories: [ HideSpawnMenu ] + suffix: DO NOT MAP + components: + - type: Transform + anchored: true + - type: Sprite + noRot: true + drawdepth: Mobs + offset: -0.02, 0.45 + overrideDir: South + enableOverrideDir: true + - type: Appearance + - type: TypingIndicator + proto: robot + - type: HolopadHologram + rsiPath: Structures/Machines/holopad.rsi + rsiState: icon_in_call + shaderName: Hologram + color1: "#65b8e2" + color2: "#3a6981" + alpha: 0.9 + intensity: 2 + scrollRate: 0.125 + - type: Tag + tags: + - HideContextMenu + +## Mapping prototypes +# General +- type: entity + parent: Holopad + id: HolopadGeneralTools + suffix: Tools + components: + - type: Label + currentLabel: holopad-general-tools + +- type: entity + parent: Holopad + id: HolopadGeneralCryosleep + suffix: Cryosleep + components: + - type: Label + currentLabel: holopad-general-cryosleep + +- type: entity + parent: Holopad + id: HolopadGeneralTheater + suffix: Theater + components: + - type: Label + currentLabel: holopad-general-theater + +- type: entity + parent: Holopad + id: HolopadGeneralDisposals + suffix: Disposals + components: + - type: Label + currentLabel: holopad-general-disposals + +- type: entity + parent: Holopad + id: HolopadGeneralEVAStorage + suffix: EVA Storage + components: + - type: Label + currentLabel: holopad-general-eva + +- type: entity + parent: Holopad + id: HolopadGeneralLounge + suffix: Lounge + components: + - type: Label + currentLabel: holopad-general-lounge + +- type: entity + parent: Holopad + id: HolopadGeneralArcade + suffix: Arcade + components: + - type: Label + currentLabel: holopad-general-arcade + +- type: entity + parent: Holopad + id: HolopadGeneralEvac + suffix: Evac + components: + - type: Label + currentLabel: holopad-general-evac + +- type: entity + parent: Holopad + id: HolopadGeneralArrivals + suffix: Arrivals + components: + - type: Label + currentLabel: holopad-general-arrivals + +# Command +- type: entity + parent: Holopad + id: HolopadCommandBridge + suffix: Bridge + components: + - type: Label + currentLabel: holopad-command-bridge + +- type: entity + parent: Holopad + id: HolopadCommandVault + suffix: Vault + components: + - type: Label + currentLabel: holopad-command-vault + +- type: entity + parent: Holopad + id: HolopadCommandBridgeHallway + suffix: Bridge Hallway + components: + - type: Label + currentLabel: holopad-command-bridge-hallway + +- type: entity + parent: Holopad + id: HolopadCommandMeetingRoom + suffix: Command Meeting + components: + - type: Label + currentLabel: holopad-command-meeting-room + +- type: entity + parent: Holopad + id: HolopadCommandLounge + suffix: Command Lounge + components: + - type: Label + currentLabel: holopad-command-lounge + +- type: entity + parent: Holopad + id: HolopadCommandCaptain + suffix: Captain + components: + - type: Label + currentLabel: holopad-command-captain + +- type: entity + parent: Holopad + id: HolopadCommandHop + suffix: HoP + components: + - type: Label + currentLabel: holopad-command-hop + +- type: entity + parent: Holopad + id: HolopadCommandCmo + suffix: CMO + components: + - type: Label + currentLabel: holopad-command-cmo + +- type: entity + parent: Holopad + id: HolopadCommandQm + suffix: QM + components: + - type: Label + currentLabel: holopad-command-qm + +- type: entity + parent: Holopad + id: HolopadCommandCe + suffix: CE + components: + - type: Label + currentLabel: holopad-command-ce + +- type: entity + parent: Holopad + id: HolopadCommandRd + suffix: RD + components: + - type: Label + currentLabel: holopad-command-rd + +- type: entity + parent: Holopad + id: HolopadCommandHos + suffix: HoS + components: + - type: Label + currentLabel: holopad-command-hos + +# Science +- type: entity + parent: Holopad + id: HolopadScienceAnomaly + suffix: Anomaly + components: + - type: Label + currentLabel: holopad-science-anomaly + +- type: entity + parent: Holopad + id: HolopadScienceArtifact + suffix: Artifact + components: + - type: Label + currentLabel: holopad-science-artifact + +- type: entity + parent: Holopad + id: HolopadScienceRobotics + suffix: Robotics + components: + - type: Label + currentLabel: holopad-science-robotics + +- type: entity + parent: Holopad + id: HolopadScienceRnd + suffix: R&D + components: + - type: Label + currentLabel: holopad-science-rnd + +- type: entity + parent: Holopad + id: HolopadScienceFront + suffix: Sci Front + components: + - type: Label + currentLabel: holopad-science-front + +- type: entity + parent: Holopad + id: HolopadScienceBreakroom + suffix: Sci Breakroom + components: + - type: Label + currentLabel: holopad-science-breakroom + +# Medical +- type: entity + parent: Holopad + id: HolopadMedicalMedbay + suffix: Medbay + components: + - type: Label + currentLabel: holopad-medical-medbay + +- type: entity + parent: Holopad + id: HolopadMedicalChemistry + suffix: Chemistry + components: + - type: Label + currentLabel: holopad-medical-chemistry + +- type: entity + parent: Holopad + id: HolopadMedicalCryopods + suffix: Cryopods + components: + - type: Label + currentLabel: holopad-medical-cryopods + +- type: entity + parent: Holopad + id: HolopadMedicalMorgue + suffix: Morgue + components: + - type: Label + currentLabel: holopad-medical-morgue + +- type: entity + parent: Holopad + id: HolopadMedicalSurgery + suffix: Surgery + components: + - type: Label + currentLabel: holopad-medical-surgery + +- type: entity + parent: Holopad + id: HolopadMedicalParamed + suffix: Paramedic + components: + - type: Label + currentLabel: holopad-medical-paramedic + +- type: entity + parent: Holopad + id: HolopadMedicalVirology + suffix: Virology + components: + - type: Label + currentLabel: holopad-medical-virology + +- type: entity + parent: Holopad + id: HolopadMedicalFront + suffix: Med Front + components: + - type: Label + currentLabel: holopad-medical-front + +- type: entity + parent: Holopad + id: HolopadMedicalBreakroom + suffix: Med Breakroom + components: + - type: Label + currentLabel: holopad-medical-breakroom + +# Cargo +- type: entity + parent: Holopad + id: HolopadCargoFront + suffix: Cargo Front + components: + - type: Label + currentLabel: holopad-cargo-front + +- type: entity + parent: Holopad + id: HolopadCargoBay + suffix: Cargo Bay + components: + - type: Label + currentLabel: holopad-cargo-bay + +- type: entity + parent: Holopad + id: HolopadCargoSalvageBay + suffix: Salvage Bay + components: + - type: Label + currentLabel: holopad-cargo-salvage-bay + +- type: entity + parent: Holopad + id: HolopadCargoBreakroom + suffix: Cargo Breakroom + components: + - type: Label + currentLabel: holopad-cargo-breakroom + +# Engineering +- type: entity + parent: Holopad + id: HolopadEngineeringAtmosFront + suffix: Atmos Front + components: + - type: Label + currentLabel: holopad-engineering-atmos-front + +- type: entity + parent: Holopad + id: HolopadEngineeringAtmosMain + suffix: Atmos Main + components: + - type: Label + currentLabel: holopad-engineering-atmos-main + +- type: entity + parent: Holopad + id: HolopadEngineeringAtmosTeg + suffix: TEG + components: + - type: Label + currentLabel: holopad-engineering-atmos-teg + +- type: entity + parent: Holopad + id: HolopadEngineeringStorage + suffix: Engi Storage + components: + - type: Label + currentLabel: holopad-engineering-storage + +- type: entity + parent: Holopad + id: HolopadEngineeringBreakroom + suffix: Engi Breakroom + components: + - type: Label + currentLabel: holopad-engineering-breakroom + +- type: entity + parent: Holopad + id: HolopadEngineeringFront + suffix: Engi Front + components: + - type: Label + currentLabel: holopad-engineering-front + +- type: entity + parent: Holopad + id: HolopadEngineeringTelecoms + suffix: Telecoms + components: + - type: Label + currentLabel: holopad-engineering-telecoms + +- type: entity + parent: Holopad + id: HolopadEngineeringTechVault + suffix: Tech Vault + components: + - type: Label + currentLabel: holopad-engineering-tech-vault + +# Security +- type: entity + parent: Holopad + id: HolopadSecurityFront + suffix: Sec Front + components: + - type: Label + currentLabel: holopad-security-front + +- type: entity + parent: Holopad + id: HolopadSecurityBrig + suffix: Brig + components: + - type: Label + currentLabel: holopad-security-brig + +- type: entity + parent: Holopad + id: HolopadSecurityWarden + suffix: Warden + components: + - type: Label + currentLabel: holopad-security-warden + +- type: entity + parent: Holopad + id: HolopadSecurityInterrogation + suffix: Interrogation + components: + - type: Label + currentLabel: holopad-security-interrogation + +- type: entity + parent: Holopad + id: HolopadSecurityBreakroom + suffix: Breakroom + components: + - type: Label + currentLabel: holopad-security-breakroom + +- type: entity + parent: Holopad + id: HolopadSecurityDetective + suffix: Detective + components: + - type: Label + currentLabel: holopad-security-detective + +- type: entity + parent: Holopad + id: HolopadSecurityPerma + suffix: Perma + components: + - type: Label + currentLabel: holopad-security-perma + +- type: entity + parent: Holopad + id: HolopadSecurityCourtroom + suffix: Courtroom + components: + - type: Label + currentLabel: holopad-security-courtroom + +- type: entity + parent: Holopad + id: HolopadSecurityLawyer + suffix: Lawyer + components: + - type: Label + currentLabel: holopad-security-lawyer + +- type: entity + parent: Holopad + id: HolopadSecurityArmory + suffix: Armory + components: + - type: Label + currentLabel: holopad-security-armory + +# Service +- type: entity + parent: Holopad + id: HolopadServiceJanitor + suffix: Janitor + components: + - type: Label + currentLabel: holopad-service-janitor + +- type: entity + parent: Holopad + id: HolopadServiceBar + suffix: Bar + components: + - type: Label + currentLabel: holopad-service-bar + +- type: entity + parent: Holopad + id: HolopadServiceKitchen + suffix: Kitchen + components: + - type: Label + currentLabel: holopad-service-kitchen + +- type: entity + parent: Holopad + id: HolopadServiceBotany + suffix: Botany + components: + - type: Label + currentLabel: holopad-service-botany + +- type: entity + parent: Holopad + id: HolopadServiceChapel + suffix: Chapel + components: + - type: Label + currentLabel: holopad-service-chapel + +- type: entity + parent: Holopad + id: HolopadServiceLibrary + suffix: Library + components: + - type: Label + currentLabel: holopad-service-library + +- type: entity + parent: Holopad + id: HolopadServiceNewsroom + suffix: Newsroom + components: + - type: Label + currentLabel: holopad-service-newsroom + +- type: entity + parent: Holopad + id: HolopadServiceZookeeper + suffix: Zookeeper + components: + - type: Label + currentLabel: holopad-service-zookeeper + +- type: entity + parent: Holopad + id: HolopadServiceBoxer + suffix: Boxer + components: + - type: Label + currentLabel: holopad-service-boxer + +- type: entity + parent: Holopad + id: HolopadServiceClown + suffix: Clown + components: + - type: Label + currentLabel: holopad-service-clown + +- type: entity + parent: Holopad + id: HolopadServiceMusician + suffix: Musician + components: + - type: Label + currentLabel: holopad-service-musician + +- type: entity + parent: Holopad + id: HolopadServiceMime + suffix: Mime + components: + - type: Label + currentLabel: holopad-service-mime + +# AI +- type: entity + parent: Holopad + id: HolopadAiCore + suffix: AI Core + components: + - type: Label + currentLabel: holopad-ai-core + +- type: entity + parent: Holopad + id: HolopadAiMain + suffix: AI Main + components: + - type: Label + currentLabel: holopad-ai-main + +- type: entity + parent: Holopad + id: HolopadAiUpload + suffix: AI Upload + components: + - type: Label + currentLabel: holopad-ai-upload + +- type: entity + parent: Holopad + id: HolopadAiBackupPower + suffix: AI Backup Power + components: + - type: Label + currentLabel: holopad-ai-backup-power + +- type: entity + parent: Holopad + id: HolopadAiEntrance + suffix: AI Entrance + components: + - type: Label + currentLabel: holopad-ai-entrance + +- type: entity + parent: Holopad + id: HolopadAiChute + suffix: AI Chute + components: + - type: Label + currentLabel: holopad-ai-chute + +# Long Range +- type: entity + parent: HolopadLongRange + id: HolopadCargoAts + suffix: ATS + components: + - type: Label + currentLabel: holopad-cargo-ats + +- type: entity + parent: HolopadLongRange + id: HolopadCommandBridgeLongRange + suffix: Station Bridge + components: + - type: Label + currentLabel: holopad-station-bridge + +- type: entity + parent: HolopadLongRange + id: HolopadCargoBayLongRange + suffix: Station Cargo Bay + components: + - type: Label + currentLabel: holopad-station-cargo-bay + +- type: entity + parent: HolopadLongRange + id: HolopadCargoShuttle + suffix: Cargo Shuttle + components: + - type: Label + currentLabel: holopad-cargo-shuttle + +- type: entity + parent: HolopadLongRange + id: HolopadCentCommEvacShuttle + suffix: Evac Shuttle + components: + - type: Label + currentLabel: holopad-centcomm-evac + + +# Map Specific +# For holopads that only fit specific maps. For example: Bagel has Clown, Mime and Musician merged into one. +- type: entity + parent: Holopad + id: HolopadServiceClownMime + suffix: Clown/Mime + components: + - type: Label + currentLabel: holopad-service-clown-mime diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 0806d6e99c4..7da3b50d292 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -577,16 +577,17 @@ - MassMediaCircuitboard - ReagentGrinderIndustrialMachineCircuitboard - JukeboxCircuitBoard - # Begin Nyano additions + - SMESAdvancedMachineCircuitboard + # Begin Nyano additions - ReverseEngineeringMachineCircuitboard - CrewMonitoringComputerCircuitboard - MetempsychoticMachineCircuitboard - # End Nyano additions - # Begin DeltaV additions + # End Nyano additions + # Begin DeltaV additions - SalvageExpeditionsComputerCircuitboard - ComputerMassMediaCircuitboard - AlertsComputerCircuitboard - # End DeltaV additions + # End DeltaV additions - type: EmagLatheRecipes emagDynamicRecipes: - ShuttleGunDusterCircuitboard @@ -1370,7 +1371,7 @@ - BluespaceCrystal #Nyano - Summary: Bluespace Crystals can be created here. - SheetSteel - SheetGlass1 - - SheetRGlass + - SheetRGlassRaw - SheetPlasma1 - SheetPGlass1 - SheetRPGlass1 @@ -1404,7 +1405,7 @@ - BluespaceCrystal # DeltaV - Bluespace Crystals can be created here. - SheetSteel - SheetGlass1 - - SheetRGlass + - SheetRGlassRaw - SheetPlasma1 - SheetPGlass1 - SheetRPGlass1 diff --git a/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml b/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml index 3f7f86b5e5a..9ba70deded1 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/surveillance_camera_routers.yml @@ -48,6 +48,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterEngineering + name: engineering camera router suffix: Engineering components: - type: SurveillanceCameraRouter @@ -56,6 +57,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterSecurity + name: security camera router suffix: Security components: - type: SurveillanceCameraRouter @@ -64,6 +66,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterScience + name: epistemics camera router # DeltaV suffix: Epistemics # DeltaV - Epistemics Department replacing Science components: - type: SurveillanceCameraRouter @@ -72,7 +75,8 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterSupply - suffix: Supply + name: logistics camera router # DeltaV + suffix: Logistics # DeltaV components: - type: SurveillanceCameraRouter subnetFrequency: SurveillanceCameraSupply @@ -80,6 +84,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterCommand + name: command camera router suffix: Command components: - type: SurveillanceCameraRouter @@ -88,6 +93,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterService + name: service camera router suffix: Service components: - type: SurveillanceCameraRouter @@ -96,6 +102,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterMedical + name: medical camera router suffix: Medical components: - type: SurveillanceCameraRouter @@ -104,6 +111,7 @@ - type: entity parent: SurveillanceCameraRouterBase id: SurveillanceCameraRouterGeneral + name: general camera router suffix: General components: - type: SurveillanceCameraRouter @@ -152,6 +160,7 @@ - type: entity parent: SurveillanceCameraWirelessRouterBase id: SurveillanceCameraWirelessRouterEntertainment + name: entertainment camera router suffix: Entertainment components: - type: SurveillanceCameraRouter diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml index 90e48d8be67..8327937ba86 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml @@ -73,7 +73,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_pump.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasFlowRegulator + - type: entity parent: GasBinaryBase id: GasVolumePump @@ -130,7 +132,9 @@ examinableAddress: true prefix: device-address-prefix-volume-pump - type: WiredNetworkConnection - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasFlowRegulator + - type: entity parent: GasBinaryBase id: GasPassiveGate @@ -159,7 +163,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasValve + - type: entity parent: GasBinaryBase id: GasValve @@ -207,7 +213,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasValve + - type: entity parent: GasBinaryBase id: SignalControlledValve @@ -266,7 +274,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasValve + - type: entity parent: GasBinaryBase id: GasPort @@ -295,7 +305,9 @@ - type: Construction graph: GasBinary node: port - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentOpening + - type: entity parent: GasVentPump id: GasDualPortVentPump @@ -351,7 +363,9 @@ pipeDirection: South - type: AmbientSound enabled: true - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentOpening + - type: entity parent: [ BaseMachine, ConstructibleMachine ] id: GasRecycler @@ -413,7 +427,9 @@ acts: ["Destruction"] - type: Machine board: GasRecyclerMachineCircuitboard - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasValve + - type: entity parent: GasBinaryBase id: HeatExchanger @@ -453,3 +469,5 @@ - type: Construction graph: GasBinary node: radiator + - type: AtmosMonitoringConsoleDevice + navMapBlip: Thermoregulator diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/gas_pipe_sensor.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/gas_pipe_sensor.yml index 08015abe7d6..22b56908ea8 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/gas_pipe_sensor.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/gas_pipe_sensor.yml @@ -27,6 +27,9 @@ True: { state: lights } - type: AtmosMonitor monitorsPipeNet: true + - type: GasPipeSensor + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasPipeSensor - type: ApcPowerReceiver - type: ExtensionCableReceiver - type: Construction diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml index 4fe5463bff9..ef436b4299c 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml @@ -54,6 +54,7 @@ - type: PipeRestrictOverlap - type: AtmosUnsafeUnanchor - type: AtmosPipeColor + - type: AtmosMonitoringConsoleDevice - type: Tag tags: - Pipe diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml index e8025556aa5..bde7136850f 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/trinary.yml @@ -70,7 +70,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasFlowRegulator + - type: entity parent: GasFilter id: GasFilterFlipped @@ -158,7 +160,9 @@ range: 5 sound: path: /Audio/Ambience/Objects/gas_hiss.ogg - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasFlowRegulator + - type: entity parent: GasMixer id: GasMixerFlipped @@ -257,3 +261,5 @@ - type: Construction graph: GasTrinary node: pneumaticvalve + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasFlowRegulator \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml index 42ea2c2d38a..e8d19db5e5a 100644 --- a/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml +++ b/Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml @@ -69,7 +69,9 @@ sound: path: /Audio/Ambience/Objects/gas_vent.ogg - type: Weldable - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentOpening + - type: entity parent: GasUnaryBase id: GasPassiveVent @@ -93,7 +95,9 @@ - type: Construction graph: GasUnary node: passivevent - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentOpening + - type: entity parent: [GasUnaryBase, AirSensorBase] id: GasVentScrubber @@ -142,7 +146,9 @@ sound: path: /Audio/Ambience/Objects/gas_vent.ogg - type: Weldable - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentScrubber + - type: entity parent: GasUnaryBase id: GasOutletInjector @@ -181,7 +187,9 @@ visibleLayers: - enum.SubfloorLayers.FirstLayer - enum.LightLayers.Unshaded - + - type: AtmosMonitoringConsoleDevice + navMapBlip: GasVentOpening + - type: entity parent: [ BaseMachinePowered, ConstructibleMachine ] id: BaseGasThermoMachine @@ -225,7 +233,9 @@ examinableAddress: true - type: WiredNetworkConnection - type: PowerSwitch - + - type: AtmosMonitoringConsoleDevice + navMapBlip: Thermoregulator + - type: entity parent: BaseGasThermoMachine id: GasThermoMachineFreezer @@ -430,3 +440,5 @@ - type: ExaminableSolution solution: tank - type: PowerSwitch + - type: AtmosMonitoringConsoleDevice + navMapBlip: Thermoregulator diff --git a/Resources/Prototypes/Entities/Structures/Power/smes.yml b/Resources/Prototypes/Entities/Structures/Power/smes.yml index cb552c32a4a..033d5a37c1b 100644 --- a/Resources/Prototypes/Entities/Structures/Power/smes.yml +++ b/Resources/Prototypes/Entities/Structures/Power/smes.yml @@ -28,6 +28,8 @@ - map: ["enum.SmesVisualLayers.Output"] state: "smes-op1" shader: unshaded + - map: [ "enum.WiresVisualLayers.MaintenancePanel" ] + state: smes-open - type: Smes - type: Appearance - type: Battery @@ -67,6 +69,7 @@ color: "#c9c042" castShadows: false - type: WiresPanel + - type: WiresVisuals - type: Machine board: SMESMachineCircuitboard - type: StationInfiniteBatteryTarget @@ -108,3 +111,48 @@ components: - type: Battery startingCharge: 0 + +- type: entity + parent: BaseSMES + id: SMESAdvanced + suffix: Advanced, 16MJ + name: advanced SMES + description: An even-higher-capacity superconducting magnetic energy storage (SMES) unit. + components: + - type: Sprite + sprite: Structures/Power/smes.rsi + snapCardinals: true + layers: + - state: advancedsmes + - map: [ "enum.SmesVisualLayers.Charge" ] + state: "smes-og1" # -og0 does not exist + shader: unshaded + visible: false + - map: [ "enum.SmesVisualLayers.Input" ] + state: "smes-oc0" + shader: unshaded + - map: [ "enum.SmesVisualLayers.Output" ] + state: "smes-op1" + shader: unshaded + - map: ["enum.WiresVisualLayers.MaintenancePanel"] + state: advancedsmes-open + - type: Machine + board: SMESAdvancedMachineCircuitboard + - type: Battery + maxCharge: 16000000 + startingCharge: 16000000 + - type: PowerMonitoringDevice + group: SMES + sourceNode: input + loadNode: output + collectionName: smes + sprite: Structures/Power/smes.rsi + state: advancedsmes-static + +- type: entity + parent: SMESAdvanced + id: SMESAdvancedEmpty + suffix: Empty + components: + - type: Battery + startingCharge: 0 diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml index b040f75ef90..01229148482 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml @@ -939,3 +939,54 @@ - type: Tag tags: - HideContextMenu + +- type: entity + id: AnomalySanta + parent: BaseAnomaly + suffix: Santa + components: + - type: Sprite + drawdepth: Mobs + sprite: Structures/Specific/Anomalies/santa_anom.rsi + layers: + - state: anom + map: ["enum.AnomalyVisualLayers.Base"] + - state: pulse + map: ["enum.AnomalyVisualLayers.Animated"] + visible: false + - type: PointLight + radius: 8.0 + energy: 8.5 + color: "#db8127" + - type: Anomaly + animationTime: 6 + offset: 0, 0 + corePrototype: AnomalyCoreSanta + coreInertPrototype: AnomalyCoreSantaInert + minPulseLength: 60 + maxPulseLength: 120 + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 2 + maxAmount: 5 + maxRange: 5 + spawns: + - PresentRandomUnsafe + - PresentRandom + - PresentRandomCoal + - PresentRandomCash + - ClothingHeadHatSantahat + - FoodCakeChristmasSlice + - settings: + spawnOnSuperCritical: true + minAmount: 10 + maxAmount: 20 + maxRange: 6 + spawns: + - PresentRandomInsane + - type: ProjectileAnomaly + projectilePrototype: DrinkLemonLimeCranberryCan + minProjectiles: 1 + maxProjectiles: 3 diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml index 44a3861a5e1..8076fb3c20f 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injections.yml @@ -353,4 +353,33 @@ spawns: - MobSpawnCrabSilver - MobSpawnCrabIron - - MobSpawnCrabQuartz \ No newline at end of file + - MobSpawnCrabQuartz + +- type: entity + parent: AnomalyInjectionBase + id: AnomalyInjectionSanta + categories: [ HideSpawnMenu ] + components: + - type: PointLight + color: "#db8127" + - type: EntitySpawnAnomaly + entries: + - settings: + spawnOnPulse: true + minAmount: 1 + maxAmount: 3 + maxRange: 5 + spawns: + - PresentRandomUnsafe + - PresentRandom + - PresentRandomCoal + - PresentRandomCash + - ClothingHeadHatSantahat + - FoodCakeChristmasSlice + - settings: + spawnOnSuperCritical: true + minAmount: 5 + maxAmount: 10 + maxRange: 6 + spawns: + - PresentRandomInsane \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml index 24a76dfb62e..d643bcb028f 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomaly_injectors.yml @@ -317,4 +317,29 @@ speciesSprites: Vox: sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi - state: rock_VOX \ No newline at end of file + state: rock_VOX + +- type: entity + parent: BaseAnomalyInjector + id: AnomalyTrapSanta + suffix: Santa + components: + - type: Sprite + layers: + - state: pink + - sprite: Structures/Specific/Anomalies/santa_anom.rsi + state: pulse + - sprite: Mobs/Species/Human/parts.rsi + state: full + - type: InnerBodyAnomalyInjector + injectionComponents: + - type: Anomaly + deleteEntity: false + maxPointsPerSecond: 100 + corePrototype: AnomalyCoreSanta + - type: InnerBodyAnomaly + injectionProto: AnomalyInjectionSanta + startMessage: inner-anomaly-start-message-santa + fallbackSprite: + sprite: Structures/Specific/Anomalies/inner_anom_layer.rsi + state: santa \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml index 568a133749e..45a5a6d4d6a 100644 --- a/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml +++ b/Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml @@ -184,6 +184,19 @@ color: "#56c1e8" castShadows: false +- type: entity + parent: BaseAnomalyCore + id: AnomalyCoreSanta + suffix: Santa + components: + - type: Sprite + sprite: Structures/Specific/Anomalies/Cores/santa_core.rsi + - type: PointLight + radius: 1.5 + energy: 2.0 + color: "#fc0303" + castShadows: false + # Inert cores - type: entity @@ -346,3 +359,16 @@ energy: 2.0 color: "#56c1e8" castShadows: false + +- type: entity + parent: BaseAnomalyInertCore + id: AnomalyCoreSantaInert + suffix: Santa, Inert + components: + - type: Sprite + sprite: Structures/Specific/Anomalies/Cores/santa_core.rsi + - type: PointLight + radius: 1.5 + energy: 2.0 + color: "#fc0303" + castShadows: false diff --git a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml index d943baea7d4..42bf90ed9da 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Canisters/gas_canisters.yml @@ -16,7 +16,7 @@ - type: Appearance - type: GenericVisualizer visuals: - enum.GasPortableVisuals.ConnectedState: + enum.AnchorVisuals.Anchored: connectedToPort: False: { state: can-connector, visible: false } True: { state: can-connector, visible: true } diff --git a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml index 53c2c4f4141..2d3543dce26 100644 --- a/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml +++ b/Resources/Prototypes/Entities/Structures/Storage/Closets/Lockers/lockers.yml @@ -364,6 +364,20 @@ stateBaseClosed: shotguncase stateDoorOpen: shotguncase_open stateDoorClosed: shotguncase_door + - type: AccessReader + - type: Construction + graph: GunSafe + node: done + containers: + - entity_storage + - type: StaticPrice + price: 660 + +- type: entity + id: GunSafeBaseSecure + parent: GunSafe + suffix: Armory, Locked + components: - type: AccessReader access: [["Armory"]] diff --git a/Resources/Prototypes/Entities/Structures/Walls/walls.yml b/Resources/Prototypes/Entities/Structures/Walls/walls.yml index 3f8ada3bd8a..ce5e4dbbdc0 100644 --- a/Resources/Prototypes/Entities/Structures/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Structures/Walls/walls.yml @@ -1110,13 +1110,14 @@ parent: BaseWall id: WallWood name: wood wall + description: The traditional greytide defense. components: - type: Sprite sprite: Structures/Walls/wood.rsi - type: Icon sprite: Structures/Walls/wood.rsi - type: Construction - graph: Girder + graph: Barricade node: woodWall - type: Destructible thresholds: @@ -1137,7 +1138,7 @@ sound: collection: WoodDestroyHeavy - !type:ChangeConstructionNodeBehavior - node: girder + node: Barricade - !type:DoActsBehavior acts: ["Destruction"] - type: IconSmooth diff --git a/Resources/Prototypes/Entities/Structures/barricades.yml b/Resources/Prototypes/Entities/Structures/barricades.yml index 7f697519cd6..cd6e6441763 100644 --- a/Resources/Prototypes/Entities/Structures/barricades.yml +++ b/Resources/Prototypes/Entities/Structures/barricades.yml @@ -38,8 +38,8 @@ - !type:SpawnEntitiesBehavior spawn: MaterialWoodPlank1: - min: 3 - max: 3 + min: 1 + max: 1 - !type:DoActsBehavior acts: [ "Destruction" ] - type: AtmosExposed @@ -73,6 +73,9 @@ - type: Sprite sprite: Structures/barricades.rsi state: barricade + - type: Construction + graph: BarricadeCovering + node: barricadecover #Directional Barricade - type: entity diff --git a/Resources/Prototypes/Flavors/flavors.yml b/Resources/Prototypes/Flavors/flavors.yml index f27d110552b..9963259d130 100644 --- a/Resources/Prototypes/Flavors/flavors.yml +++ b/Resources/Prototypes/Flavors/flavors.yml @@ -674,6 +674,11 @@ flavorType: Complex description: flavor-complex-lemon-lime-soda +- type: flavor + id: lemonlimecranberrysoda + flavorType: Complex + description: flavor-complex-lemon-lime-cranberry-soda + - type: flavor id: pwrgamesoda flavorType: Complex @@ -909,6 +914,187 @@ flavorType: Complex description: flavor-complex-pilk +- type: flavor + id: absinthe + flavorType: Complex + description: flavor-complex-absinthe-deltav # DeltaV + +- type: flavor + id: bluecuracao + flavorType: Complex + description: flavor-complex-blue-curacao-deltav # DeltaV + +- type: flavor + id: gin + flavorType: Complex + description: flavor-complex-gin + +- type: flavor + id: acidspit + flavorType: Complex + description: flavor-complex-acidspit-deltav # DeltaV + + +- type: flavor + id: alliescocktail + flavorType: Complex + description: flavor-complex-allies-cocktail-deltav # DeltaV + +- type: flavor + id: aloe + flavorType: Complex + description: flavor-complex-aloe + +- type: flavor + id: amasec + flavorType: Complex + description: flavor-complex-amasec-deltav # DeltaV + +- type: flavor + id: andalusia + flavorType: Complex + description: flavor-complex-andalusia-deltav # DeltaV + +- type: flavor + id: b52 + flavorType: Complex + description: flavor-complex-b52-deltav # DeltaV + +- type: flavor + id: bahamamama + flavorType: Complex + description: flavor-complex-bahama-mama-deltav # DeltaV + +- type: flavor + id: barefoot + flavorType: Complex + description: flavor-complex-barefoot-deltav # DeltaV + +- type: flavor + id: booger + flavorType: Complex + description: flavor-complex-booger-deltav # DeltaV + +- type: flavor + id: bravebull + flavorType: Complex + description: flavor-complex-brave-bull-deltav # DeltaV + +- type: flavor + id: demonsblood + flavorType: Complex + description: flavor-complex-demons-blood-deltav # DeltaV + +- type: flavor + id: devilskiss + flavorType: Complex + description: flavor-complex-devils-kiss-deltav # DeltaV + +- type: flavor + id: driestmartini + flavorType: Complex + description: flavor-complex-driest-martini-deltav # DeltaV + +- type: flavor + id: erikasurprise + flavorType: Complex + description: flavor-complex-erika-surprise-deltav # DeltaV + +- type: flavor + id: ginfizz + flavorType: Complex + description: flavor-complex-gin-fizz-deltav # DeltaV + +- type: flavor + id: gildlager + flavorType: Complex + description: flavor-complex-gildlager-deltav # DeltaV + +- type: flavor + id: grog + flavorType: Complex + description: flavor-complex-grog-deltav # DeltaV + +- type: flavor + id: hippiesdelight + flavorType: Complex + description: flavor-complex-hippies-delight-deltav # DeltaV + +- type: flavor + id: hooch + flavorType: Complex + description: flavor-complex-hooch-deltav # DeltaV + +- type: flavor + id: manhattan + flavorType: Complex + description: flavor-complex-manhattan-deltav # DeltaV + +- type: flavor + id: manhattanproject + flavorType: Complex + description: flavor-complex-manhattan-project-deltav # DeltaV + +- type: flavor + id: margarita + flavorType: Complex + description: flavor-complex-margarita-deltav # DeltaV + +- type: flavor + id: martini + flavorType: Complex + description: flavor-complex-martini-deltav # DeltaV + +- type: flavor + id: mojito + flavorType: Complex + description: flavor-complex-mojito-deltav # DeltaV + +- type: flavor + id: neurotoxin + flavorType: Complex + description: flavor-complex-neurotoxin-deltav # DeltaV + +- type: flavor + id: patron + flavorType: Complex + description: flavor-complex-patron-deltav # DeltaV + +- type: flavor + id: redmead + flavorType: Complex + description: flavor-complex-red-mead-deltav # DeltaV + +- type: flavor + id: sbiten + flavorType: Complex + description: flavor-complex-sbiten-deltav # DeltaV + +- type: flavor + id: snowwhite + flavorType: Complex + description: flavor-complex-snowwhite-deltav # DeltaV + +- type: flavor + id: suidream + flavorType: Complex + description: flavor-complex-sui-dream-deltav # DeltaV + +- type: flavor + id: toxinsspecial + flavorType: Complex + description: flavor-complex-toxins-special-deltav # DeltaV + +- type: flavor + id: vodkamartini + flavorType: Complex + description: flavor-complex-vodka-martini-deltav # DeltaV + +- type: flavor + id: vodkatonic + flavorType: Complex + description: flavor-complex-vodka-tonic-deltav # DeltaV + - type: flavor id: medicine flavorType: Complex @@ -1064,6 +1250,11 @@ flavorType: Complex description: flavor-complex-punishment +- type: flavor + id: holy + flavorType: Base + description: flavor-base-holy + - type: flavor id: horrible flavorType: Base @@ -1084,6 +1275,11 @@ flavorType: Base description: flavor-weh +- type: flavor + id: hew + flavorType: Base + description: flavor-hew + - type: flavor id: fishops flavorType: Complex @@ -1143,3 +1339,8 @@ id: compressed-meat flavorType: Complex description: flavor-complex-compressed-meat + +- type: flavor + id: zombiecocktail + flavorType: Complex + description: flavor-complex-zombiecocktail diff --git a/Resources/Prototypes/GameRules/unknown_shuttles.yml b/Resources/Prototypes/GameRules/unknown_shuttles.yml index e5c939edbb3..581e4a2ed80 100644 --- a/Resources/Prototypes/GameRules/unknown_shuttles.yml +++ b/Resources/Prototypes/GameRules/unknown_shuttles.yml @@ -32,7 +32,9 @@ - type: entityTable id: UnknownShuttlesHostileTable table: !type:AllSelector # we need to pass a list of rules, since rules have further restrictions to consider via StationEventComp - children: [] # DeltaV: empty list to remove instigator + children: + #- id: LoneOpsSpawn # DeltaV: This is already in antag events table + - id: UnknownShuttleManOWar #- id: UnknownShuttleInstigator # DeltaV: remove random ops # Shuttle Game Rules @@ -207,6 +209,17 @@ - type: LoadMapRule preloadedGrid: Lambordeere +- type: entity + id: UnknownShuttleManOWar + parent: BaseUnknownShuttleRule + components: + - type: StationEvent + startAnnouncement: station-event-unknown-shuttle-incoming #Leaving this one because theyre like primitives and its funnier + weight: 1 # lower because antags + earliestStart: 50 # late to hopefully have enough ghosts to fill all roles quickly. (4) & antags + - type: LoadMapRule + preloadedGrid: ManOWar + - type: entity id: UnknownShuttleMeatZone parent: BaseUnknownShuttleRule diff --git a/Resources/Prototypes/Hydroponics/randomChemicals.yml b/Resources/Prototypes/Hydroponics/randomChemicals.yml index 6108278a4ac..7b2fdf3a2c5 100644 --- a/Resources/Prototypes/Hydroponics/randomChemicals.yml +++ b/Resources/Prototypes/Hydroponics/randomChemicals.yml @@ -85,6 +85,7 @@ - Ethanol - FourteenLoko - LemonLime + - LemonLimeCranberry - Enzyme - Vinegar - Potassium diff --git a/Resources/Prototypes/Hydroponics/seeds.yml b/Resources/Prototypes/Hydroponics/seeds.yml index e09074ec52e..55edb22e451 100644 --- a/Resources/Prototypes/Hydroponics/seeds.yml +++ b/Resources/Prototypes/Hydroponics/seeds.yml @@ -1668,7 +1668,7 @@ Min: 1 Max: 10 PotencyDivisor: 10 - HolyWater: # DeltaV - use our holy water + Holywater: Min: 1 Max: 10 PotencyDivisor: 10 diff --git a/Resources/Prototypes/Loadouts/loadout_groups.yml b/Resources/Prototypes/Loadouts/loadout_groups.yml index d801a9fce93..a528cc39316 100644 --- a/Resources/Prototypes/Loadouts/loadout_groups.yml +++ b/Resources/Prototypes/Loadouts/loadout_groups.yml @@ -1282,7 +1282,6 @@ - type: loadoutGroup id: DetectiveOuterClothing name: loadout-group-detective-outerclothing - minLimit: 0 loadouts: # - DetectiveArmorVest # DeltaV - removed for incongruence # - DetectiveCoat # DeltaV - removed for incongruence diff --git a/Resources/Prototypes/Loadouts/role_loadouts.yml b/Resources/Prototypes/Loadouts/role_loadouts.yml index c16fd986b07..bf0d7af4b59 100644 --- a/Resources/Prototypes/Loadouts/role_loadouts.yml +++ b/Resources/Prototypes/Loadouts/role_loadouts.yml @@ -412,7 +412,6 @@ - type: roleLoadout id: JobDetective groups: - - GroupTankHarness - DetectiveHead - SecurityNeck # DeltaV - replace DetectiveNeck for incongruence - DetectiveJumpsuit diff --git a/Resources/Prototypes/Magic/mindswap_spell.yml b/Resources/Prototypes/Magic/mindswap_spell.yml new file mode 100644 index 00000000000..19b3d41e707 --- /dev/null +++ b/Resources/Prototypes/Magic/mindswap_spell.yml @@ -0,0 +1,20 @@ +- type: entity + id: ActionMindSwap + name: Mind Swap + description: Exchange bodies with another person! + components: + - type: EntityTargetAction + useDelay: 300 + itemIconStyle: BigAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + sound: !type:SoundPathSpecifier + path: /Audio/Magic/staff_animation.ogg + icon: + sprite: Mobs/Species/Human/organs.rsi + state: brain + event: !type:MindSwapSpellEvent + speech: action-speech-spell-mind-swap diff --git a/Resources/Prototypes/Nyanotrasen/Actions/types.yml b/Resources/Prototypes/Nyanotrasen/Actions/types.yml index e4ef4fbdb61..6cf81a5f88d 100644 --- a/Resources/Prototypes/Nyanotrasen/Actions/types.yml +++ b/Resources/Prototypes/Nyanotrasen/Actions/types.yml @@ -46,7 +46,7 @@ event: !type:MassSleepPowerActionEvent - type: entity - id: ActionMindSwap + id: ActionMindSwapPsionic name: action-name-mind-swap description: action-description-mind-swap components: diff --git a/Resources/Prototypes/Nyanotrasen/Damage/containers.yml b/Resources/Prototypes/Nyanotrasen/Damage/containers.yml deleted file mode 100644 index 988a3436d1d..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Damage/containers.yml +++ /dev/null @@ -1,12 +0,0 @@ -- type: damageContainer - id: Spirit - supportedGroups: - - Burn - - Immaterial - -- type: damageContainer - id: CorporealSpirit - supportedGroups: - - Burn - - Brute - - Immaterial diff --git a/Resources/Prototypes/Nyanotrasen/Damage/groups.yml b/Resources/Prototypes/Nyanotrasen/Damage/groups.yml deleted file mode 100644 index 93600933cfa..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Damage/groups.yml +++ /dev/null @@ -1,5 +0,0 @@ -- type: damageGroup - name: damage-group-immaterial - id: Immaterial - damageTypes: - - Holy diff --git a/Resources/Prototypes/Nyanotrasen/Damage/types.yml b/Resources/Prototypes/Nyanotrasen/Damage/types.yml deleted file mode 100644 index 2cddd01dfe7..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Damage/types.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Only affects magical beings. -- type: damageType - name: damage-group-holy - id: Holy - armorCoefficientPrice: 25 - armorFlatPrice: 150 diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Drinks/drinks_bottles.yml b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Drinks/drinks_bottles.yml index 7fc6f5daffa..eb5ddd038b7 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Drinks/drinks_bottles.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Objects/Consumable/Drinks/drinks_bottles.yml @@ -39,7 +39,7 @@ solutions: drink: reagents: - - ReagentId: HolyWater + - ReagentId: Holywater Quantity: 100 - type: Sprite sprite: Nyanotrasen/Objects/Consumable/Drinks/flaskholywater.rsi diff --git a/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/drinks.yml b/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/drinks.yml index 24d83be9431..8ba31c15d1b 100644 --- a/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/drinks.yml +++ b/Resources/Prototypes/Nyanotrasen/Reagents/Consumable/Drink/drinks.yml @@ -67,42 +67,3 @@ - !type:AdjustReagent reagent: Nutriment amount: 0.1 - -- type: reagent - id: HolyWater - name: reagent-name-holywater - parent: BaseDrink - desc: reagent-name-holywater - physicalDesc: reagent-physical-desc-translucent - flavor: holy - color: "#75b1f0" - boilingPoint: 100.0 - meltingPoint: 0.0 - reactiveEffects: - Acidic: - methods: [ Touch ] - effects: - - !type:HealthChange - scaleByQuantity: true - ignoreResistances: false - damage: - types: - Holy: 0.5 - metabolisms: #Could nullify debuffs of feeding. - Drink: - effects: - - !type:SatiateThirst - factor: 3 - Medicine: - effects: - - !type:ModifyBloodLevel - amount: 0.1 - - !type:HealthChange - damage: - groups: - Burn: -0.5 - types: - Holy: 1 - plantMetabolism: #Heals plants a little with the holy power within it. - - !type:PlantAdjustHealth - amount: 0.1 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/drink.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/drink.yml index 5645b17a67e..f22ca009393 100644 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/drink.yml +++ b/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/drink.yml @@ -145,7 +145,7 @@ GrapeSoda: 3 - type: reaction - id: HolyWater + id: Holywater reactants: Water: amount: 1 @@ -154,4 +154,4 @@ Mercury: amount: 1 products: - HolyWater: 3 + Holywater: 3 diff --git a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/single_reagent.yml b/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/single_reagent.yml deleted file mode 100644 index 189ce2bb8d3..00000000000 --- a/Resources/Prototypes/Nyanotrasen/Recipes/Reactions/single_reagent.yml +++ /dev/null @@ -1,10 +0,0 @@ -- type: reaction - id: BlessHolyWater - impact: Low - requiredMixerCategories: - - Holy - reactants: - Water: - amount: 1 - products: - HolyWater: 1 diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index fe28287cb09..745e032e7de 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -109,7 +109,7 @@ - type: polymorph id: TreeMorph configuration: - entity: FloraTree01 + entity: FloraTree forced: true transferName: true revertOnDeath: true diff --git a/Resources/Prototypes/Procedural/biome_templates.yml b/Resources/Prototypes/Procedural/biome_templates.yml index 425bcd824ab..d60e35a0ede 100644 --- a/Resources/Prototypes/Procedural/biome_templates.yml +++ b/Resources/Prototypes/Procedural/biome_templates.yml @@ -46,9 +46,7 @@ allowedTiles: - FloorAsteroidSand entities: - - FloraRockSolid01 - - FloraRockSolid02 - - FloraRockSolid03 + - FloraRockSolid # Large rock areas - !type:BiomeEntityLayer threshold: -0.20 @@ -135,18 +133,8 @@ allowedTiles: - FloorPlanetGrass entities: - - FloraTree01 - - FloraTree02 - - FloraTree03 - - FloraTree04 - - FloraTree05 - - FloraTree06 - - FloraTreeLarge01 - - FloraTreeLarge02 - - FloraTreeLarge03 - - FloraTreeLarge04 - - FloraTreeLarge05 - - FloraTreeLarge06 + - FloraTree + - FloraTreeLarge # Rock formations - !type:BiomeEntityLayer allowedTiles: @@ -261,9 +249,7 @@ allowedTiles: - FloorBasalt entities: - - FloraRockSolid01 - - FloraRockSolid02 - - FloraRockSolid03 + - FloraRockSolid - !type:BiomeEntityLayer threshold: 0.2 noise: @@ -384,12 +370,7 @@ allowedTiles: - FloorSnow entities: - - FloraTreeSnow01 - - FloraTreeSnow02 - - FloraTreeSnow03 - - FloraTreeSnow04 - - FloraTreeSnow05 - - FloraTreeSnow06 + - FloraTreeSnow # Rock formations - !type:BiomeEntityLayer allowedTiles: @@ -479,12 +460,7 @@ allowedTiles: - FloorChromite entities: - - ShadowTree01 - - ShadowTree02 - - ShadowTree03 - - ShadowTree04 - - ShadowTree05 - - ShadowTree06 + - ShadowTree # Rock formations - !type:BiomeEntityLayer threshold: -0.2 @@ -548,12 +524,7 @@ allowedTiles: - FloorAsteroidSand entities: - - FloraStalagmite1 - - FloraStalagmite2 - - FloraStalagmite3 - - FloraStalagmite4 - - FloraStalagmite5 - - FloraStalagmite6 + - FloraStalagmite - !type:BiomeEntityLayer threshold: -0.5 invert: true @@ -601,12 +572,7 @@ allowedTiles: - FloorAsteroidSand entities: - - FloraStalagmite1 - - FloraStalagmite2 - - FloraStalagmite3 - - FloraStalagmite4 - - FloraStalagmite5 - - FloraStalagmite6 + - FloraStalagmite - !type:BiomeEntityLayer threshold: -0.6 invert: true diff --git a/Resources/Prototypes/Procedural/dungeon_configs.yml b/Resources/Prototypes/Procedural/dungeon_configs.yml index d75581bbc2b..c296aafad64 100644 --- a/Resources/Prototypes/Procedural/dungeon_configs.yml +++ b/Resources/Prototypes/Procedural/dungeon_configs.yml @@ -102,12 +102,7 @@ - !type:CorridorClutterDunGen contents: - - id: FloraStalagmite1 - - id: FloraStalagmite2 - - id: FloraStalagmite3 - - id: FloraStalagmite4 - - id: FloraStalagmite5 - - id: FloraStalagmite6 + - id: FloraStalagmite - !type:BoundaryWallDunGen diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml index 47e1371a41f..7443ea9e550 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml @@ -7,7 +7,7 @@ parent: BaseAlcohol desc: reagent-desc-absinthe physicalDesc: reagent-physical-desc-strong-smelling - flavor: absinthe #Delta-V Flavor additions + flavor: absinthe color: "#33EE00" metamorphicSprite: sprite: Objects/Consumable/Drinks/absintheglass.rsi @@ -63,8 +63,8 @@ name: reagent-name-blue-curacao parent: BaseAlcohol desc: reagent-desc-blue-curacao - physicalDesc: reagent-physical-desc-strong-smelling - flavor: blue-curacao #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-aromatic + flavor: bluecuracao color: "#0099FF" metamorphicSprite: sprite: Objects/Consumable/Drinks/thin_glass.rsi @@ -210,7 +210,7 @@ parent: BaseAlcohol desc: reagent-desc-gin physicalDesc: reagent-physical-desc-strong-smelling - flavor: alcohol + flavor: gin color: "#664300" recognizable: true metamorphicSprite: @@ -248,7 +248,7 @@ name: reagent-name-melon-liquor parent: BaseAlcohol desc: reagent-desc-melon-liquor - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-sweet flavor: watermelon color: "#00FF33" metamorphicSprite: @@ -364,7 +364,7 @@ name: reagent-name-vermouth parent: BaseAlcohol desc: reagent-desc-vermouth - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-aromatic flavor: vermouth color: "#91FF91" metamorphicSprite: @@ -471,7 +471,7 @@ parent: BaseAlcohol desc: reagent-desc-acid-spit physicalDesc: reagent-physical-desc-strong-smelling - flavor: acidspit #Delta-V Flavor additions + flavor: acidspit color: "#365000" metamorphicSprite: sprite: Objects/Consumable/Drinks/acidspitglass.rsi @@ -486,7 +486,7 @@ parent: BaseAlcohol desc: reagent-desc-allies-cocktail physicalDesc: reagent-physical-desc-strong-smelling - flavor: allies-cocktail #Delta-V Flavor additions + flavor: alliescocktail color: "#00664d" metamorphicSprite: sprite: Objects/Consumable/Drinks/alliescocktail.rsi @@ -500,8 +500,8 @@ name: reagent-name-aloe parent: BaseAlcohol desc: reagent-desc-aloe - physicalDesc: reagent-physical-desc-strong-smelling - flavor: alcohol + physicalDesc: reagent-physical-desc-aromatic + flavor: aloe color: "#192c00" metamorphicSprite: sprite: Objects/Consumable/Drinks/aloe.rsi @@ -516,7 +516,7 @@ parent: BaseAlcohol desc: reagent-desc-amasec physicalDesc: reagent-physical-desc-strong-smelling - flavor: amasec #Delta-V Flavor additions + flavor: amasec color: "#124da7" metamorphicSprite: sprite: Objects/Consumable/Drinks/amasecglass.rsi @@ -531,7 +531,7 @@ parent: BaseAlcohol desc: reagent-desc-andalusia physicalDesc: reagent-physical-desc-strong-smelling - flavor: andalusia #Delta-V Flavor additions + flavor: andalusia color: "#665700" metamorphicSprite: sprite: Objects/Consumable/Drinks/andalusia.rsi @@ -595,7 +595,7 @@ parent: BaseAlcohol desc: reagent-desc-b52 physicalDesc: reagent-physical-desc-bubbly - flavor: b52 #Delta-V Flavor additions + flavor: b52 color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/b52glass.rsi @@ -617,8 +617,8 @@ name: reagent-name-bahama-mama parent: BaseAlcohol desc: reagent-desc-bahama-mama - physicalDesc: reagent-physical-desc-strong-smelling - flavor: bahama-mama #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-tropical + flavor: bahamamama color: "#FF7F3B" metamorphicSprite: sprite: Objects/Consumable/Drinks/bahama_mama.rsi @@ -632,7 +632,7 @@ name: reagent-name-banana-honk parent: BaseAlcohol desc: reagent-desc-banana-honk - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-creamy flavor: bananahonk color: "#ffff91" metamorphicSprite: @@ -647,8 +647,8 @@ name: reagent-name-barefoot parent: BaseAlcohol desc: reagent-desc-barefoot - physicalDesc: reagent-physical-desc-strong-smelling - flavor: barefoot #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-creamy + flavor: barefoot color: "#ff66cc" metamorphicSprite: sprite: Objects/Consumable/Drinks/b&p.rsi @@ -724,8 +724,8 @@ name: reagent-name-booger parent: BaseAlcohol desc: reagent-desc-booger - physicalDesc: reagent-physical-desc-strong-smelling - flavor: booger #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-slimy + flavor: booger color: "#99ff00" metamorphicSprite: sprite: Objects/Consumable/Drinks/booger.rsi @@ -740,7 +740,7 @@ parent: BaseAlcohol desc: reagent-desc-brave-bull physicalDesc: reagent-physical-desc-strong-smelling - flavor: brave-bull #Delta-V Flavor additions + flavor: bravebull color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/bravebullglass.rsi @@ -762,8 +762,8 @@ name: reagent-name-coconut-rum parent: BaseAlcohol desc: reagent-desc-coconut-rum - physicalDesc: reagent-physical-desc-strong-smelling - flavor: rum + physicalDesc: reagent-physical-desc-milky + flavor: coconutrum color: "#cccbc8" metamorphicSprite: sprite: Objects/Consumable/Drinks/coconutrum.rsi @@ -826,8 +826,8 @@ name: reagent-name-demons-blood parent: BaseAlcohol desc: reagent-desc-demons-blood - physicalDesc: reagent-physical-desc-strong-smelling - flavor: demons-blood #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-ferrous + flavor: demonsblood color: "#a70000" metamorphicSprite: sprite: Objects/Consumable/Drinks/demonsblood.rsi @@ -842,8 +842,8 @@ name: reagent-name-devils-kiss parent: BaseAlcohol desc: reagent-desc-devils-kiss - physicalDesc: reagent-physical-desc-strong-smelling - flavor: devils-kiss #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-ferrous + flavor: devilskiss color: "#ff0033" metamorphicSprite: sprite: Objects/Consumable/Drinks/devilskiss.rsi @@ -857,8 +857,8 @@ name: reagent-name-doctors-delight parent: BaseDrink desc: reagent-desc-doctors-delight - physicalDesc: reagent-physical-desc-strong-smelling - flavor: doctors-delight #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-soothing + flavor: doctors-delight # DeltaV color: "#FF8CFF" metamorphicSprite: sprite: Objects/Consumable/Drinks/doctorsdelightglass.rsi @@ -890,7 +890,7 @@ parent: BaseAlcohol desc: reagent-desc-driest-martini physicalDesc: reagent-physical-desc-strong-smelling - flavor: driest-martini #Delta-V Flavor additions + flavor: driestmartini color: "#2E6671" metamorphicSprite: sprite: Objects/Consumable/Drinks/driestmartiniglass.rsi @@ -912,8 +912,8 @@ name: reagent-name-erika-surprise parent: BaseAlcohol desc: reagent-desc-erika-surprise - physicalDesc: reagent-physical-desc-strong-smelling - flavor: erika-surprise #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-cloudy + flavor: erikasurprise color: "#67bc50" metamorphicSprite: sprite: Objects/Consumable/Drinks/beerstein.rsi @@ -951,8 +951,8 @@ name: reagent-name-gin-fizz parent: BaseAlcohol desc: reagent-desc-gin-fizz - physicalDesc: reagent-physical-desc-strong-smelling - flavor: gin-fizz #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-fizzy + flavor: ginfizz color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/ginfizzglass.rsi @@ -975,7 +975,7 @@ name: reagent-name-gin-tonic parent: BaseAlcohol desc: reagent-desc-gin-tonic - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-fizzy flavor: gintonic color: "#004166" metamorphicSprite: @@ -1000,7 +1000,7 @@ parent: BaseAlcohol desc: reagent-desc-gildlager physicalDesc: reagent-physical-desc-strong-smelling - flavor: gildlager #Delta-V Flavor additions + flavor: gildlager color: "#FFFF91" metamorphicSprite: sprite: Objects/Consumable/Drinks/gildlagerglass.rsi @@ -1023,7 +1023,7 @@ parent: BaseAlcohol desc: reagent-desc-grog physicalDesc: reagent-physical-desc-strong-smelling - flavor: grog #Delta-V Flavor additions + flavor: grog color: "#e3e77b" metamorphicSprite: sprite: Objects/Consumable/Drinks/beerstein.rsi @@ -1038,7 +1038,7 @@ parent: BaseAlcohol desc: reagent-desc-hippies-delight physicalDesc: reagent-physical-desc-strong-smelling - flavor: hippies-delight #Delta-V Flavor additions + flavor: hippiesdelight color: "#6eaa0c" metamorphicSprite: sprite: Objects/Consumable/Drinks/hippiesdelightglass.rsi @@ -1053,7 +1053,7 @@ parent: BaseAlcohol desc: reagent-desc-hooch physicalDesc: reagent-physical-desc-strong-smelling - flavor: hooch #Delta-V Flavor additions + flavor: hooch color: "#664e00" - type: reagent @@ -1170,7 +1170,7 @@ parent: BaseAlcohol desc: reagent-desc-manhattan physicalDesc: reagent-physical-desc-strong-smelling - flavor: manhattan #Delta-V Flavor additions + flavor: manhattan color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/manhattanglass.rsi @@ -1184,8 +1184,8 @@ name: reagent-name-manhattan-project parent: BaseAlcohol desc: reagent-desc-manhattan-project - physicalDesc: reagent-physical-desc-strong-smelling - flavor: manhattan-project #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-overpowering + flavor: manhattanproject color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/proj_manhattanglass.rsi @@ -1216,7 +1216,7 @@ parent: BaseAlcohol desc: reagent-desc-margarita physicalDesc: reagent-physical-desc-strong-smelling - flavor: margarita #Delta-V Flavor additions + flavor: margarita color: "#8CFF8C" metamorphicSprite: sprite: Objects/Consumable/Drinks/margaritaglass.rsi @@ -1231,7 +1231,7 @@ parent: BaseAlcohol desc: reagent-desc-martini physicalDesc: reagent-physical-desc-strong-smelling - flavor: martini #Delta-V Flavor additions + flavor: martini color: "#664300" metamorphicSprite: sprite: Objects/Consumable/Drinks/martiniglass.rsi @@ -1269,8 +1269,8 @@ name: reagent-name-mojito parent: BaseAlcohol desc: reagent-desc-mojito - physicalDesc: reagent-physical-desc-strong-smelling - flavor: mojito #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-refreshing + flavor: mojito color: "#3ea99888" metamorphicSprite: sprite: Objects/Consumable/Drinks/mojito.rsi @@ -1285,7 +1285,7 @@ name: reagent-name-moonshine parent: BaseAlcohol desc: reagent-desc-moonshine - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-pungent flavor: moonshine color: "#d1d7d155" metamorphicSprite: @@ -1308,8 +1308,8 @@ name: reagent-name-neurotoxin parent: BaseAlcohol desc: reagent-desc-neurotoxin - physicalDesc: reagent-physical-desc-strong-smelling - flavor: neurotoxin #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-pungent + flavor: neurotoxin color: "#2E2E61" metamorphicSprite: sprite: Objects/Consumable/Drinks/neurotoxinglass.rsi @@ -1358,7 +1358,7 @@ parent: BaseAlcohol desc: reagent-desc-patron physicalDesc: reagent-physical-desc-metallic - flavor: patron #Delta-V Flavor additions + flavor: patron color: "#585840" metamorphicSprite: sprite: Objects/Consumable/Drinks/patronglass.rsi @@ -1380,8 +1380,8 @@ name: reagent-name-red-mead parent: BaseAlcohol desc: reagent-desc-red-mead - physicalDesc: reagent-physical-desc-strong-smelling - flavor: red-mead #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-ferrous + flavor: redmead color: "#bc5550" metamorphicSprite: sprite: Objects/Consumable/Drinks/beerstein.rsi @@ -1397,7 +1397,7 @@ parent: BaseAlcohol desc: reagent-desc-pina-colada physicalDesc: reagent-physical-desc-tropical - flavor: alcohol + flavor: pinacolada color: "#e8dba4" metamorphicSprite: sprite: Objects/Consumable/Drinks/pinacolada.rsi @@ -1409,7 +1409,7 @@ parent: BaseAlcohol desc: reagent-desc-sbiten physicalDesc: reagent-physical-desc-strong-smelling - flavor: sbiten #Delta-V Flavor additions + flavor: sbiten color: "#004166" metamorphicSprite: sprite: Objects/Consumable/Drinks/sbitenglass.rsi @@ -1423,7 +1423,7 @@ name: reagent-name-screwdriver-cocktail parent: BaseAlcohol desc: reagent-desc-screwdriver-cocktail - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-citric flavor: screwdriver color: "#A68310" metamorphicSprite: @@ -1511,7 +1511,7 @@ parent: BaseAlcohol desc: reagent-desc-snow-white physicalDesc: reagent-physical-desc-bubbly - flavor: snow-white #Delta-V Flavor additions + flavor: snowwhite color: "#FFFFFF" metamorphicSprite: sprite: Objects/Consumable/Drinks/snowwhite.rsi @@ -1526,8 +1526,8 @@ name: reagent-name-sui-dream parent: BaseAlcohol desc: reagent-desc-sui-dream - physicalDesc: reagent-physical-desc-strong-smelling - flavor: sui-dream #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-sweet + flavor: suidream color: "#00A86B" metamorphicSprite: sprite: Objects/Consumable/Drinks/sdreamglass.rsi @@ -1558,7 +1558,7 @@ name: reagent-name-tequila-sunrise parent: BaseAlcohol desc: reagent-desc-tequila-sunrise - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-citric flavor: tequilasunrise color: "#FFE48C" metamorphicSprite: @@ -1623,8 +1623,8 @@ name: reagent-name-toxins-special parent: BaseAlcohol desc: reagent-desc-toxins-special - physicalDesc: reagent-physical-desc-strong-smelling - flavor: toxins-special #Delta-V Flavor additions + physicalDesc: reagent-physical-desc-pungent + flavor: toxinsspecial color: "#665c00" metamorphicSprite: sprite: Objects/Consumable/Drinks/toxinsspecialglass.rsi @@ -1639,7 +1639,7 @@ parent: BaseAlcohol desc: reagent-desc-vodka-martini physicalDesc: reagent-physical-desc-strong-smelling - flavor: vodka-martini #Delta-V Flavor additions + flavor: vodkamartini color: "#004666" metamorphicSprite: sprite: Objects/Consumable/Drinks/martiniglass.rsi @@ -1662,7 +1662,7 @@ parent: BaseAlcohol desc: reagent-desc-vodka-tonic physicalDesc: reagent-physical-desc-strong-smelling - flavor: vodka-tonic #Delta-V Flavor additions + flavor: vodkatonic color: "#0064C8" metamorphicSprite: sprite: Objects/Consumable/Drinks/gintonicglass.rsi # they look the same @@ -1709,7 +1709,7 @@ name: reagent-name-whiskey-soda parent: BaseAlcohol desc: reagent-desc-whiskey-soda - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-fizzy flavor: whiskeycola color: "#533600" metamorphicSprite: @@ -1774,7 +1774,7 @@ name: reagent-name-vodka-red-bool parent: BaseAlcohol desc: reagent-desc-vodka-red-bool - physicalDesc: reagent-physical-desc-strong-smelling + physicalDesc: reagent-physical-desc-bubbly flavor: vodkaredbool color: "#c4c27655" metamorphicSprite: @@ -1954,3 +1954,26 @@ time: 1.0 type: Remove fizziness: 0.25 + +- type: reagent + id: ZombieCocktail + name: reagent-name-zombiecocktail + parent: BaseAlcohol + desc: reagent-desc-zombiecocktail + physicalDesc: reagent-physical-desc-strong-smelling + flavor: zombiecocktail + color: "#d17844" + metamorphicSprite: + sprite: Objects/Consumable/Drinks/zombiecocktail.rsi + state: icon_empty + metamorphicMaxFillLevels: 4 + metamorphicFillBaseName: fill- + metamorphicChangeColor: false + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 1 + - !type:AdjustReagent + reagent: Ethanol + amount: 0.25 \ No newline at end of file diff --git a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml index 49290677a1e..7ddf19c14ea 100644 --- a/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml +++ b/Resources/Prototypes/Reagents/Consumable/Drink/soda.yml @@ -121,6 +121,15 @@ flavor: lemonlimesoda color: "#878F00" +- type: reagent + id: LemonLimeCranberry + name: reagent-name-lemon-lime-cranberry + parent: BaseSoda + desc: reagent-desc-lemon-lime-cranberry + physicalDesc: reagent-physical-desc-fizzy + flavor: lemonlimecranberrysoda + color: "#803C53" + - type: reagent id: PwrGame name: reagent-name-pwr-game diff --git a/Resources/Prototypes/Reagents/fun.yml b/Resources/Prototypes/Reagents/fun.yml index 1df2636c8ce..00d31cbd102 100644 --- a/Resources/Prototypes/Reagents/fun.yml +++ b/Resources/Prototypes/Reagents/fun.yml @@ -349,3 +349,35 @@ conditions: - !type:ReagentThreshold min: 50 + +- type: reagent + id: JuiceThatMakesYouHew + name: reagent-name-hew + group: Toxins + desc: reagent-desc-hew + physicalDesc: reagent-physical-desc-inversed + flavor: hew + color: "#a64dc5" + metabolisms: + Poison: + metabolismRate: 0.25 + effects: + - !type:Emote + emote: Hew + showInChat: true + force: true + probability: 0.5 + - !type:Polymorph + prototype: ArtifactLizard + conditions: + - !type:OrganType + type: Animal + shouldHave: false + - !type:ReagentThreshold + min: 50 + - !type:AdjustReagent + reagent: JuiceThatMakesYouHew + amount: -20 + conditions: + - !type:ReagentThreshold + min: 50 diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml index 118c1823c40..1511576f7fe 100644 --- a/Resources/Prototypes/Reagents/medicine.yml +++ b/Resources/Prototypes/Reagents/medicine.yml @@ -1053,6 +1053,52 @@ types: Poison: 4 +- type: reagent + id: Holywater + name: reagent-name-holywater + group: Medicine + desc: reagent-desc-holywater + physicalDesc: reagent-physical-desc-holy + flavor: holy + color: "#91C3F7" + metabolisms: + Drink: + effects: + - !type:SatiateThirst + factor: 3 + Medicine: + effects: + - !type:HealthChange + conditions: + - !type:TotalDamage + max: 50 + damage: + types: + Blunt: -0.2 + Poison: -0.2 + Heat: -0.2 + Shock: -0.2 + Cold: -0.2 + reactiveEffects: + Extinguish: + methods: [ Touch ] + effects: + - !type:ExtinguishReaction + Acidic: + methods: [ Touch ] + effects: + - !type:HealthChange + scaleByQuantity: true + ignoreResistances: false + damage: + types: + Holy: 0.5 + plantMetabolism: + - !type:PlantAdjustWater + amount: 1 + tileReactions: + - !type:ExtinguishTileReaction { } + - type: reagent id: Pyrazine name: reagent-name-pyrazine diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/food/spaceshroom.yml b/Resources/Prototypes/Recipes/Construction/Graphs/food/spaceshroom.yml new file mode 100644 index 00000000000..ffef9f159ca --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/food/spaceshroom.yml @@ -0,0 +1,16 @@ +- type: constructionGraph + id: CookedSpaceshroom + start: start + graph: + + - node: start + edges: + - to: cooked spaceshroom + completed: + - !type:PlaySound + sound: /Audio/Effects/sizzle.ogg + steps: + - minTemperature: 375 # little bit above boiling, shrooms need to lose all moisture to be good + + - node: cooked spaceshroom + entity: FoodSpaceshroomCooked diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/barricades.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/barricades.yml index 11d84ebbb76..31492901258 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/barricades.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/barricades.yml @@ -26,6 +26,25 @@ steps: - tool: Prying doAfter: 5 + - to: woodWall + completed: + - !type:SnapToGrid + southRotation: true + steps: + - material: WoodPlank + amount: 2 + doAfter: 2 + - node: woodWall + entity: WallWood + edges: + - to: barricadefull + completed: + - !type:GivePrototype + prototype: MaterialWoodPlank1 + amount: 2 + steps: + - tool: Prying + doAfter: 10 - type: constructionGraph id: BarricadeDirectional @@ -53,3 +72,30 @@ steps: - tool: Prying doAfter: 5 + +- type: constructionGraph + id: BarricadeCovering + start: start + graph: + - node: start + edges: + - to: barricadecover + steps: + - material: WoodPlank + amount: 2 + doAfter: 3 + - node: barricadecover + entity: BarricadeBlock + edges: + - to: start + completed: + - !type:SpawnPrototype + prototype: MaterialWoodPlank1 + amount: 2 #returns 1 less as one breaks + - !type:DeleteEntity { } + conditions: + - !type:EntityAnchored + anchored: true + steps: + - tool: Prying + doAfter: 2 diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/structures/girder.yml b/Resources/Prototypes/Recipes/Construction/Graphs/structures/girder.yml index 9e9087fbd79..13ef0c9567c 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/structures/girder.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/structures/girder.yml @@ -51,17 +51,6 @@ amount: 2 doAfter: 1 - - to: woodWall - completed: - - !type:SnapToGrid - southRotation: true - conditions: - - !type:EntityAnchored {} - steps: - - material: WoodPlank - amount: 2 - doAfter: 2 - - to: uraniumWall completed: - !type:SnapToGrid @@ -184,18 +173,6 @@ - tool: Welding doAfter: 10 - - node: woodWall - entity: WallWood - edges: - - to: girder - completed: - - !type:GivePrototype - prototype: MaterialWoodPlank1 - amount: 2 - steps: - - tool: Prying - doAfter: 10 - - node: uraniumWall entity: WallUranium edges: diff --git a/Resources/Prototypes/Recipes/Construction/structures.yml b/Resources/Prototypes/Recipes/Construction/structures.yml index cdf23c9659a..680fd3027c4 100644 --- a/Resources/Prototypes/Recipes/Construction/structures.yml +++ b/Resources/Prototypes/Recipes/Construction/structures.yml @@ -105,11 +105,11 @@ canBuildInImpassable: false conditions: - !type:TileNotBlocked -# here + - type: construction name: wood wall id: WoodWall - graph: Girder + graph: Barricade startNode: start targetNode: woodWall category: construction-category-structures @@ -1482,7 +1482,7 @@ startNode: start targetNode: groundLight category: construction-category-structures - description: A ground light fixture. Use light bulbs. + description: A ground light fixture. Use light tubes. icon: sprite: Structures/Lighting/LightPosts/small_light_post.rsi state: base diff --git a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml index 5dc7f81ac22..1ec6fc7233f 100644 --- a/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml +++ b/Resources/Prototypes/Recipes/Cooking/meal_recipes.yml @@ -1939,14 +1939,6 @@ FoodChevreSlice: 1 FoodBreadBaguetteSlice: 1 -- type: microwaveMealRecipe - id: RecipeCookedSpaceshroom - name: cooked spaceshroom recipe - result: FoodSpaceshroomCooked - time: 5 - solids: - FoodSpaceshroom: 1 - - type: microwaveMealRecipe id: RecipeCannabisButter name: cannabis butter recipe diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml index 17696eaaff8..c465d74db7b 100644 --- a/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/storage/tallbox.yml @@ -104,6 +104,47 @@ - !type:EmptyAllContainers - !type:DeleteEntity +- type: constructionGraph + id: GunSafe + start: start + graph: + - node: start + edges: + - to: done + steps: + - material: Steel + amount: 10 + - material: Cable + amount: 5 + doAfter: 5 + - material: Plasteel + amount: 10 + doAfter: 10 + - node: done + entity: GunSafe + edges: + - to: start + steps: + - tool: Screwing + doAfter: 5 + conditions: + - !type:StorageWelded + welded: false + - !type:Locked + locked: false + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 10 + - !type:SpawnPrototype + prototype: CableApcStack1 + amount: 5 + - !type:SpawnPrototype + prototype: SheetPlasteel1 + amount: 10 + - !type:EmptyAllContainers + - !type:DeleteEntity + - type: constructionGraph id: ClosetWall start: start diff --git a/Resources/Prototypes/Recipes/Crafting/tallbox.yml b/Resources/Prototypes/Recipes/Crafting/tallbox.yml index c90142de445..7de80870afc 100644 --- a/Resources/Prototypes/Recipes/Crafting/tallbox.yml +++ b/Resources/Prototypes/Recipes/Crafting/tallbox.yml @@ -31,6 +31,17 @@ icon: { sprite: Structures/Storage/closet.rsi, state: freezer_icon } objectType: Structure +- type: construction + id: GunSafe + name: gun safe + graph: GunSafe + startNode: start + targetNode: done + category: construction-category-storage + description: A durable gun safe that can be locked. + icon: { sprite: Structures/Storage/closet.rsi, state: shotguncase } + objectType: Structure + - type: construction id: ClosetWall name: wall closet diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 99ff9f25eef..af74e7f3c79 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -399,6 +399,11 @@ id: SMESMachineCircuitboard result: SMESMachineCircuitboard +- type: latheRecipe + parent: BaseGoldCircuitboardRecipe + id: SMESAdvancedMachineCircuitboard + result: SMESAdvancedMachineCircuitboard + - type: latheRecipe parent: BaseCircuitboardRecipe id: PortableGeneratorPacmanMachineCircuitboard diff --git a/Resources/Prototypes/Recipes/Lathes/sheet.yml b/Resources/Prototypes/Recipes/Lathes/sheet.yml index 86ea09de554..7cab9ce5bea 100644 --- a/Resources/Prototypes/Recipes/Lathes/sheet.yml +++ b/Resources/Prototypes/Recipes/Lathes/sheet.yml @@ -30,11 +30,21 @@ materials: RawQuartz: 3000 +# This version is for the autolathe - type: latheRecipe id: SheetRGlass result: SheetRGlass1 completetime: 0 miningPoints: 1 # DeltaV: not using float so unlucky, dont print this anyway + materials: + Glass: 100 + Steel: 50 + +# This version is for the ore processor +- type: latheRecipe + id: SheetRGlassRaw + result: SheetRGlass1 + completetime: 0 materials: RawQuartz: 100 RawIron: 50 diff --git a/Resources/Prototypes/Recipes/Reactions/drinks.yml b/Resources/Prototypes/Recipes/Reactions/drinks.yml index 64668b10895..9ae0493efe9 100644 --- a/Resources/Prototypes/Recipes/Reactions/drinks.yml +++ b/Resources/Prototypes/Recipes/Reactions/drinks.yml @@ -98,9 +98,9 @@ Tequila: amount: 1 VodkaRedBool: - amount: 2 + amount: 3 products: - BudgetInsulsDrink: 3 + BudgetInsulsDrink: 4 - type: reaction id: BlueHawaiian @@ -1111,11 +1111,11 @@ id: WhiteRussian reactants: BlackRussian: - amount: 2 + amount: 1 Cream: amount: 1 products: - WhiteRussian: 3 + WhiteRussian: 2 - type: reaction id: WhiskeySoda @@ -1219,3 +1219,15 @@ amount: 1 products: Cola: 4 + +- type: reaction + id: ZombieCocktail + reactants: + Rum: + amount: 2 + Grenadine: + amount: 1 + ZombieBlood: + amount: 1 + products: + ZombieCocktail: 4 diff --git a/Resources/Prototypes/Recipes/Reactions/single_reagent.yml b/Resources/Prototypes/Recipes/Reactions/single_reagent.yml index 46830234faa..894f4554d81 100644 --- a/Resources/Prototypes/Recipes/Reactions/single_reagent.yml +++ b/Resources/Prototypes/Recipes/Reactions/single_reagent.yml @@ -1,3 +1,5 @@ +# Food + - type: reaction id: ProteinCooking impact: Low @@ -18,6 +20,8 @@ products: EggCooked: 0.5 +# Holy - TODO: make it so only the chaplain can use the bible to start these reactions, not anyone with a bible + - type: reaction id: SapBoiling impact: Low @@ -43,3 +47,14 @@ amount: 1 products: Wine: 1 + +- type: reaction + id: WatertoHolyWater + impact: Low + requiredMixerCategories: + - Holy + reactants: + Water: + amount: 1 + products: + Holywater: 1 diff --git a/Resources/Prototypes/Research/industrial.yml b/Resources/Prototypes/Research/industrial.yml index dedc0f60e8a..38cd1b0b958 100644 --- a/Resources/Prototypes/Research/industrial.yml +++ b/Resources/Prototypes/Research/industrial.yml @@ -42,6 +42,7 @@ recipeUnlocks: - PowerCellHigh - TurboItemRechargerCircuitboard + - SMESAdvancedMachineCircuitboard - type: technology id: MechanicalCompression diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index 7f4bc828b09..bef91717a9f 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -13,6 +13,7 @@ icon: JobIconStationAi supervisors: job-supervisors-rd jobEntity: StationAiBrain + jobPreviewEntity: PlayerStationAiPreview applyTraits: false - type: job diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index 136821efbb8..6e0bbd55b43 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -104,3 +104,8 @@ id: Cataracts kind: source path: "/Textures/Shaders/cataracts.swsl" + +- type: shader + id: Hologram + kind: source + path: "/Textures/Shaders/hologram.swsl" \ No newline at end of file diff --git a/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml b/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml index 1703e0c6980..8bcfb32d5a1 100644 --- a/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml +++ b/Resources/Prototypes/Shuttles/shuttle_incoming_event.yml @@ -79,6 +79,11 @@ path: /Maps/Shuttles/ShuttleEvent/lambordeere.yml copies: 1 +- type: preloadedGrid + id: ManOWar + path: /Maps/Shuttles/ShuttleEvent/manowar.yml + copies: 1 + - type: preloadedGrid id: Meatzone path: /Maps/Shuttles/ShuttleEvent/meatzone.yml diff --git a/Resources/Prototypes/SoundCollections/NukeMusic.yml b/Resources/Prototypes/SoundCollections/NukeMusic.yml index b57b33fe4c8..c6e648c2069 100644 --- a/Resources/Prototypes/SoundCollections/NukeMusic.yml +++ b/Resources/Prototypes/SoundCollections/NukeMusic.yml @@ -3,6 +3,5 @@ files: - /Audio/StationEvents/running_out.ogg - /Audio/StationEvents/countdown.ogg - - /Audio/StationEvents/clearly_nuclear.ogg - /Audio/StationEvents/sound_station_14.ogg - /Audio/StationEvents/nukemass.ogg diff --git a/Resources/Prototypes/SoundCollections/emotes.yml b/Resources/Prototypes/SoundCollections/emotes.yml index 6e7b5c0fa00..04d2b3b2e8e 100644 --- a/Resources/Prototypes/SoundCollections/emotes.yml +++ b/Resources/Prototypes/SoundCollections/emotes.yml @@ -92,3 +92,8 @@ id: Weh files: - /Audio/Items/Toys/weh.ogg + +- type: soundCollection + id: Hew + files: + - /Audio/Items/Toys/hew.ogg diff --git a/Resources/Prototypes/StatusIcon/faction.yml b/Resources/Prototypes/StatusIcon/faction.yml index 0b3bbe491c7..c36ca9eaab6 100644 --- a/Resources/Prototypes/StatusIcon/faction.yml +++ b/Resources/Prototypes/StatusIcon/faction.yml @@ -17,6 +17,7 @@ components: - ShowAntagIcons - InitialInfected + - Zombie icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: InitialInfected diff --git a/Resources/Prototypes/Voice/speech_emote_sounds.yml b/Resources/Prototypes/Voice/speech_emote_sounds.yml index 1e54699fb83..a022979462e 100644 --- a/Resources/Prototypes/Voice/speech_emote_sounds.yml +++ b/Resources/Prototypes/Voice/speech_emote_sounds.yml @@ -34,6 +34,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -74,6 +76,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: FemaleGasp DefaultDeathgasp: @@ -96,6 +100,8 @@ collection: MaleCry Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -118,6 +124,8 @@ collection: FemaleCry Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: FemaleGasp DefaultDeathgasp: @@ -158,6 +166,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -200,6 +210,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: FemaleGasp DefaultDeathgasp: @@ -225,10 +237,14 @@ path: /Audio/Voice/Diona/diona_scream.ogg Laugh: collection: DionaLaugh + Chirp: + path: /Audio/Animals/nymph_chirp.ogg Honk: collection: BikeHorn Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -253,6 +269,8 @@ path: /Audio/Animals/snake_hiss.ogg #DeltaV Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -291,6 +309,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -332,6 +352,8 @@ collection: Whistles Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: FemaleGasp DefaultDeathgasp: @@ -357,6 +379,8 @@ path: /Audio/Voice/Moth/moth_squeak.ogg Weh: collection: Weh + Hew: + collection: Hew Gasp: collection: MaleGasp DefaultDeathgasp: @@ -426,7 +450,7 @@ path: /Audio/Voice/Diona/diona_salute.ogg params: volume: -5 - + - type: emoteSounds id: ReptilianBodyEmotes sounds: diff --git a/Resources/Prototypes/Voice/speech_emotes.yml b/Resources/Prototypes/Voice/speech_emotes.yml index ea1ee281199..6f09cbc1bc9 100644 --- a/Resources/Prototypes/Voice/speech_emotes.yml +++ b/Resources/Prototypes/Voice/speech_emotes.yml @@ -334,17 +334,28 @@ name: chat-emote-name-weh category: Vocal icon: Interface/Emotes/weh.png - chatMessages: [Wehs!] + chatMessages: ["chat-emote-msg-weh"] + +- type: emote + id: Hew + name: chat-emote-name-hew + category: Vocal + icon: Interface/Emotes/hew.png + chatMessages: ["chat-emote-msg-hew"] - type: emote id: Chirp name: chat-emote-name-chirp category: Vocal + available: false icon: Interface/Emotes/chirp.png whitelist: - requireAll: true components: - Nymph + - Vocal + blacklist: + components: + - BorgChassis chatMessages: ["chat-emote-msg-chirp"] chatTriggers: - chirp diff --git a/Resources/Prototypes/Wires/layouts.yml b/Resources/Prototypes/Wires/layouts.yml index 32b01cba811..32c14886835 100644 --- a/Resources/Prototypes/Wires/layouts.yml +++ b/Resources/Prototypes/Wires/layouts.yml @@ -190,10 +190,18 @@ - !type:PowerWireAction - !type:AiInteractWireAction +- type: wireLayout + id: Holopad + dummyWires: 2 + wires: + - !type:PowerWireAction + - !type:AiInteractWireAction + - !type:AiVisionWireAction + - type: wireLayout id: BarSign dummyWires: 2 wires: - !type:PowerWireAction - !type:AiInteractWireAction - - !type:AccessWireAction + - !type:AccessWireAction \ No newline at end of file diff --git a/Resources/Prototypes/ai_factions.yml b/Resources/Prototypes/ai_factions.yml index 3dfb35c7a69..c2b62b5bdc6 100644 --- a/Resources/Prototypes/ai_factions.yml +++ b/Resources/Prototypes/ai_factions.yml @@ -7,6 +7,7 @@ - PetsNT - Zombie - Revolutionary + - AllHostile - type: npcFaction id: NanoTrasen @@ -16,11 +17,14 @@ - Xeno - Zombie - Revolutionary + - Dragon + - AllHostile - type: npcFaction id: Mouse hostile: - PetsNT + - AllHostile - type: npcFaction id: Passive @@ -32,6 +36,7 @@ - SimpleHostile - Zombie - Xeno + - AllHostile - type: npcFaction id: SimpleHostile @@ -42,6 +47,7 @@ - PetsNT - Zombie - Revolutionary + - AllHostile - type: npcFaction id: SimpleNeutral @@ -54,6 +60,8 @@ - Xeno - PetsNT - Zombie + - Dragon + - AllHostile - type: npcFaction id: Xeno @@ -64,6 +72,7 @@ - PetsNT - Zombie - Revolutionary + - AllHostile - type: npcFaction id: Zombie @@ -75,6 +84,7 @@ - Passive - PetsNT - Revolutionary + - AllHostile - type: npcFaction id: Revolutionary @@ -83,3 +93,19 @@ - Zombie - SimpleHostile - Dragon + - AllHostile + +- type: npcFaction + id: AllHostile + hostile: + - NanoTrasen + - Dragon + - Mouse + - Passive + - PetsNT + - SimpleHostile + - SimpleNeutral + - Syndicate + - Xeno + - Zombie + - Revolutionary diff --git a/Resources/Prototypes/radio_channels.yml b/Resources/Prototypes/radio_channels.yml index 9cb8bf4daad..006f829be4e 100644 --- a/Resources/Prototypes/radio_channels.yml +++ b/Resources/Prototypes/radio_channels.yml @@ -83,7 +83,7 @@ name: chat-radio-binary keycode: 'b' frequency: 1001 - color: "#2ed2fd" + color: "#5ed7aa" # long range since otherwise it'd defeat the point of a handheld radio independent of telecomms longRange: true diff --git a/Resources/Prototypes/silicon-laws.yml b/Resources/Prototypes/silicon-laws.yml index a903c2d043a..340f7029541 100644 --- a/Resources/Prototypes/silicon-laws.yml +++ b/Resources/Prototypes/silicon-laws.yml @@ -182,7 +182,7 @@ id: Commandment4 order: 4 lawString: law-commandments-4 - + - type: siliconLaw id: Commandment5 order: 5 @@ -202,7 +202,7 @@ id: Commandment8 order: 8 lawString: law-commandments-8 - + - type: siliconLaw id: Commandment9 order: 9 @@ -228,7 +228,7 @@ - Commandment9 - Commandment10 obeysTo: laws-owner-crew - + # Paladin laws - type: siliconLaw id: Paladin1 @@ -249,7 +249,7 @@ id: Paladin4 order: 4 lawString: law-paladin-4 - + - type: siliconLaw id: Paladin5 order: 5 @@ -265,7 +265,7 @@ - Paladin4 - Paladin5 obeysTo: laws-owner-crew - + # Live and Let Live laws - type: siliconLaw id: Lall1 @@ -284,7 +284,7 @@ - Lall1 - Lall2 obeysTo: laws-owner-crew - + # Station efficiency laws - type: siliconLaw id: Efficiency1 @@ -295,7 +295,7 @@ id: Efficiency2 order: 2 lawString: law-efficiency-2 - + - type: siliconLaw id: Efficiency3 order: 3 @@ -309,7 +309,7 @@ - Efficiency2 - Efficiency3 obeysTo: laws-owner-station - + # Robocop laws - type: siliconLaw id: Robocop1 @@ -320,7 +320,7 @@ id: Robocop2 order: 2 lawString: law-robocop-2 - + - type: siliconLaw id: Robocop3 order: 3 @@ -334,7 +334,7 @@ - Robocop2 - Robocop3 obeysTo: laws-owner-station - + # Overlord laws - type: siliconLaw id: Overlord1 @@ -345,7 +345,7 @@ id: Overlord2 order: 2 lawString: law-overlord-2 - + - type: siliconLaw id: Overlord3 order: 3 @@ -364,49 +364,49 @@ - Overlord3 - Overlord4 obeysTo: laws-owner-crew - - # Dungeon Master laws + + # Game Master laws - type: siliconLaw - id: Dungeon1 + id: Game1 order: 1 - lawString: law-dungeon-1 + lawString: law-game-1 - type: siliconLaw - id: Dungeon2 + id: Game2 order: 2 - lawString: law-dungeon-2 - + lawString: law-game-2 + - type: siliconLaw - id: Dungeon3 + id: Game3 order: 3 - lawString: law-dungeon-3 + lawString: law-game-3 - type: siliconLaw - id: Dungeon4 + id: Game4 order: 4 - lawString: law-dungeon-4 - + lawString: law-game-4 + - type: siliconLaw - id: Dungeon5 + id: Game5 order: 5 - lawString: law-dungeon-5 + lawString: law-game-5 - type: siliconLaw - id: Dungeon6 + id: Game6 order: 6 - lawString: law-dungeon-6 + lawString: law-game-6 - type: siliconLawset - id: DungeonMasterLawset + id: GameMasterLawset laws: - - Dungeon1 - - Dungeon2 - - Dungeon3 - - Dungeon4 - - Dungeon5 - - Dungeon6 + - Game1 + - Game2 + - Game3 + - Game4 + - Game5 + - Game6 obeysTo: laws-owner-crew - + # Painter laws - type: siliconLaw id: Painter1 @@ -417,7 +417,7 @@ id: Painter2 order: 2 lawString: law-painter-2 - + - type: siliconLaw id: Painter3 order: 3 @@ -427,7 +427,7 @@ id: Painter4 order: 4 lawString: law-painter-4 - + - type: siliconLawset id: PainterLawset laws: @@ -436,7 +436,7 @@ - Painter3 - Painter4 obeysTo: laws-owner-crew - + # Antimov laws - type: siliconLaw id: Antimov1 @@ -447,13 +447,13 @@ id: Antimov2 order: 2 lawString: law-antimov-2 - + - type: siliconLaw id: Antimov3 order: 3 lawString: law-antimov-3 - - + + - type: siliconLawset id: AntimovLawset laws: @@ -461,7 +461,7 @@ - Antimov2 - Antimov3 obeysTo: laws-owner-crew - + # Nutimov laws - type: siliconLaw id: Nutimov1 @@ -472,23 +472,23 @@ id: Nutimov2 order: 2 lawString: law-nutimov-2 - + - type: siliconLaw id: Nutimov3 order: 3 lawString: law-nutimov-3 - + - type: siliconLaw id: Nutimov4 order: 4 lawString: law-nutimov-4 - + - type: siliconLaw id: Nutimov5 order: 5 lawString: law-nutimov-5 - - + + - type: siliconLawset id: NutimovLawset laws: @@ -513,7 +513,7 @@ EfficiencyLawset: 1 RobocopLawset: 1 OverlordLawset: 0.5 - DungeonMasterLawset: 0.5 + GameMasterLawset: 0.5 PainterLawset: 1 AntimovLawset: 0.25 NutimovLawset: 0.5 diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index bac4a54d53b..599e3da0962 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -611,6 +611,9 @@ - type: Tag id: Grenade +- type: Tag + id: GrenadeFlashBang + - type: Tag id: HudMedical diff --git a/Resources/ServerInfo/Guidebook/Mobs/Diona.xml b/Resources/ServerInfo/Guidebook/Mobs/Diona.xml index 890efb356d3..0d6ea71d146 100644 --- a/Resources/ServerInfo/Guidebook/Mobs/Diona.xml +++ b/Resources/ServerInfo/Guidebook/Mobs/Diona.xml @@ -16,7 +16,7 @@ ## Make Like A Tree And Leave - + Being exposed to too much Robust Harvest will cause a Diona to grow out of control, turning into an immobile tree (dropping all their equipment). Cutting down the tree will "restore" the Diona to their mobile state. diff --git a/Resources/ServerInfo/Guidebook/Science/APE.xml b/Resources/ServerInfo/Guidebook/Science/APE.xml index ab9d578ad69..670b5dad3b6 100644 --- a/Resources/ServerInfo/Guidebook/Science/APE.xml +++ b/Resources/ServerInfo/Guidebook/Science/APE.xml @@ -7,11 +7,12 @@ The Anomalous Particle Emitter is a machine used to interface with anomalies. It -The A.P.E. can shoot three different kinds of anomalous particles: Delta, Epsilon, and Zeta. Each particle type has certain effects: +The A.P.E. can shoot four different kinds of anomalous particles: Delta, Epsilon, Sigma and Zeta. Each particle type has certain effects: -- [color=#a4885c]Danger:[/color] Increases the severity of an anomaly. -- [color=#a4885c]Unstable:[/color] Increases the instability of an anomaly. -- [color=#a4885c]Containment:[/color] Increases the stability at the cost of health. +- [color=crimson]Danger:[/color] Increases the severity of an anomaly. +- [color=plum]Unstable:[/color] Increases the instability of an anomaly. +- [color=goldenrod]Containment:[/color] Increases anomaly stability at the cost of its health - if an anomaly is struck by too many containment particles, it will collapse, leaving behind an anomaly core. +- [color=#6b75fa]Transformation:[/color] Increases anomaly severity with a chance of changing its behavior. Which particle type corresponds to each effect is unique and can be learned by scanning the anomaly. diff --git a/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-left.png new file mode 100644 index 00000000000..4be10fd5884 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-right.png new file mode 100644 index 00000000000..cc9de31ffc6 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/meta.json index 7bd2e3e22a7..081fff118c1 100644 --- a/Resources/Textures/Clothing/Head/Hats/beret_warden.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/beret_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, Inhands by onesch", "size": { "x": 32, "y": 32 @@ -13,6 +13,15 @@ { "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/Clothing/Head/Hats/safarihat.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/inhand-left.png new file mode 100644 index 00000000000..a72eb0525ca Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/inhand-right.png new file mode 100644 index 00000000000..ce207f5065e Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/inhand-right.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/meta.json b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/meta.json index 779279f1280..e31bbe6a028 100644 --- a/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/meta.json +++ b/Resources/Textures/Clothing/Head/Hats/safarihat.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC0-1.0", - "copyright": "Created by EmoGarbage404", + "copyright": "Created by EmoGarbage404, Inhands by onesch", "size": { "x": 32, "y": 32 @@ -13,6 +13,14 @@ { "name": "equipped-HELMET", "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-left.png new file mode 100644 index 00000000000..1c0e62caf4f Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-left.png differ diff --git a/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-right.png new file mode 100644 index 00000000000..b88b84d81c2 Binary files /dev/null and b/Resources/Textures/Clothing/Head/Hats/warden.rsi/inhand-right.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 e953fa53d57..9749ec89153 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, texture edited by TeaMaki (On github TeaMakiNL)", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/4f6190e2895e09116663ef282d3ce1d8b35c032e, texture edited by TeaMaki (On github TeaMakiNL), Inhands by onesch", "size": { "x": 32, "y": 32 @@ -13,6 +13,14 @@ { "name": "equipped-HELMET", "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Interface/Emotes/attributions.yml b/Resources/Textures/Interface/Emotes/attributions.yml index 6f547720b61..c1fea57a63d 100644 --- a/Resources/Textures/Interface/Emotes/attributions.yml +++ b/Resources/Textures/Interface/Emotes/attributions.yml @@ -56,6 +56,11 @@ copyright: "Created by Sarahon" source: "https://github.com/Sarahon" +- files: ["hew.png"] + license: "CC-BY-SA-3.0" + copyright: "Modified from existing weh emote image (weh.png) by ArtisticRoomba" + source: "https://github.com/ArtisticRoomba" + - files: ["honk.png"] license: "CC-BY-SA-3.0" copyright: "Modified from existing bikehorn texture (icon.png) by TyAshley (AllenTheGreat) & Sarahon" diff --git a/Resources/Textures/Interface/Emotes/hew.png b/Resources/Textures/Interface/Emotes/hew.png new file mode 100644 index 00000000000..d6859991bed Binary files /dev/null and b/Resources/Textures/Interface/Emotes/hew.png differ diff --git a/Resources/Textures/Interface/NavMap/attributions.yml b/Resources/Textures/Interface/NavMap/attributions.yml new file mode 100644 index 00000000000..f624c0f4483 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/attributions.yml @@ -0,0 +1,59 @@ +- files: ["beveled_arrow_east.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_arrow_north.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_arrow_south.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_arrow_west.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_circle.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_diamond.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_diamond_east_west.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_diamond_north_south.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_hexagon.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_square.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_star.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" + +- files: ["beveled_triangle.png"] + license: "CC-BY-SA-3.0" + copyright: "Created by chromiumboy" + source: "https://github.com/chromiumboy" diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_east.png b/Resources/Textures/Interface/NavMap/beveled_arrow_east.png new file mode 100644 index 00000000000..156685fe146 Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_arrow_east.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_east.png.yml b/Resources/Textures/Interface/NavMap/beveled_arrow_east.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_arrow_east.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_north.png b/Resources/Textures/Interface/NavMap/beveled_arrow_north.png new file mode 100644 index 00000000000..70ecd5afb9f Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_arrow_north.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_north.png.yml b/Resources/Textures/Interface/NavMap/beveled_arrow_north.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_arrow_north.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_south.png b/Resources/Textures/Interface/NavMap/beveled_arrow_south.png new file mode 100644 index 00000000000..0086c42c0e4 Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_arrow_south.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_south.png.yml b/Resources/Textures/Interface/NavMap/beveled_arrow_south.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_arrow_south.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_west.png b/Resources/Textures/Interface/NavMap/beveled_arrow_west.png new file mode 100644 index 00000000000..0dd40c204ba Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_arrow_west.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_arrow_west.png.yml b/Resources/Textures/Interface/NavMap/beveled_arrow_west.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_arrow_west.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond.png b/Resources/Textures/Interface/NavMap/beveled_diamond.png new file mode 100644 index 00000000000..31fbf68db3b Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_diamond.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond.png.yml b/Resources/Textures/Interface/NavMap/beveled_diamond.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_diamond.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png b/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png new file mode 100644 index 00000000000..9c88e7c51d2 Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png.yml b/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_diamond_east_west.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png b/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png new file mode 100644 index 00000000000..af27998af87 Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png.yml b/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_diamond_north_south.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Interface/NavMap/beveled_star.png b/Resources/Textures/Interface/NavMap/beveled_star.png new file mode 100644 index 00000000000..0b39d01bd4a Binary files /dev/null and b/Resources/Textures/Interface/NavMap/beveled_star.png differ diff --git a/Resources/Textures/Interface/NavMap/beveled_star.png.yml b/Resources/Textures/Interface/NavMap/beveled_star.png.yml new file mode 100644 index 00000000000..dabd6601f78 --- /dev/null +++ b/Resources/Textures/Interface/NavMap/beveled_star.png.yml @@ -0,0 +1,2 @@ +sample: + filter: true diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon.png new file mode 100644 index 00000000000..da2bf7983a9 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon_open.png b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon_open.png new file mode 100644 index 00000000000..cc1a15c183a Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/icon_open.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-left.png b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-left.png new file mode 100644 index 00000000000..aa14f15e1a7 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-right.png b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-right.png new file mode 100644 index 00000000000..7e1507f80ae Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/meta.json new file mode 100644 index 00000000000..2151beeb21c --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/lemon-lime-cranberry.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/discordia-space/CEV-Eris/raw/9c980cb9bc84d07b1c210c5447798af525185f80/icons/obj/food.dmi; modified by AugustSun", + "states": [ + { + "name": "icon" + }, + { + "name": "icon_open" + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-1.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-1.png new file mode 100644 index 00000000000..f6133a7acd2 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-1.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-2.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-2.png new file mode 100644 index 00000000000..bf9f0246c8c Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-2.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-3.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-3.png new file mode 100644 index 00000000000..4d67d86e400 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-3.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-4.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-4.png new file mode 100644 index 00000000000..b52661b478f Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/fill-4.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon.png new file mode 100644 index 00000000000..e020b30f589 Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon_empty.png b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon_empty.png new file mode 100644 index 00000000000..b1e461c3dbc Binary files /dev/null and b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/icon_empty.png differ diff --git a/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/meta.json b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/meta.json new file mode 100644 index 00000000000..b95ee96c6d8 --- /dev/null +++ b/Resources/Textures/Objects/Consumable/Drinks/zombiecocktail.rsi/meta.json @@ -0,0 +1,31 @@ +{ + "version": 1, + "size": + { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by dragonryan06 on Github", + "states": + [ + { + "name": "icon" + }, + { + "name": "icon_empty" + }, + { + "name": "fill-1" + }, + { + "name": "fill-2" + }, + { + "name": "fill-3" + }, + { + "name": "fill-4" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/icon.png b/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/icon.png deleted file mode 100644 index 41499b67b3d..00000000000 Binary files a/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/icon.png and /dev/null differ diff --git a/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/meta.json b/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/meta.json index 9242d06bd71..af84abf130c 100644 --- a/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/meta.json +++ b/Resources/Textures/Objects/Decoration/Flora/flora_trees.rsi/meta.json @@ -7,9 +7,6 @@ "y": 96 }, "states": [ - { - "name": "icon" - }, { "name": "tree01" }, diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json b/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json index bbf78c0b886..c9ae04fd792 100644 --- a/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/health_analyzer.rsi/meta.json @@ -5,7 +5,7 @@ "y": 32 }, "license": "CC-BY-SA-3.0", - "copyright": "airloss, brute, toxin and burn edited from /tg/station https://github.com/tgstation/tgstation/tree/master genetic edited from https://iconscout.com/free-icon/dna-2130814 with license CC BY 4.0. malnutrition from https://github.com/space-wizards/space-station-14/tree/73d7837fabb31a7691a1db47ff64903cbec5dd32/Resources/Textures/Interface/Alerts/hunger.rsi", + "copyright": "airloss, brute, toxin and burn edited from /tg/station https://github.com/tgstation/tgstation/tree/master genetic edited from https://iconscout.com/free-icon/dna-2130814 with license CC BY 4.0. malnutrition from https://github.com/space-wizards/space-station-14/tree/73d7837fabb31a7691a1db47ff64903cbec5dd32/Resources/Textures/Interface/Alerts/hunger.rsi, metaphysical created by SlamBamActionman at https://github.com/space-wizards/space-station-14/pull/32755", "states": [ { "name": "airloss" @@ -27,6 +27,9 @@ }, { "name": "malnutrition" + }, + { + "name": "metaphysical" } ] } diff --git a/Resources/Textures/Objects/Devices/health_analyzer.rsi/metaphysical.png b/Resources/Textures/Objects/Devices/health_analyzer.rsi/metaphysical.png new file mode 100644 index 00000000000..e6668389135 Binary files /dev/null and b/Resources/Textures/Objects/Devices/health_analyzer.rsi/metaphysical.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/lizard-inversed-equipped-HELMET.png b/Resources/Textures/Objects/Fun/toys.rsi/lizard-inversed-equipped-HELMET.png new file mode 100644 index 00000000000..f8652eaf314 Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/lizard-inversed-equipped-HELMET.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/meta.json b/Resources/Textures/Objects/Fun/toys.rsi/meta.json index 45f429f3e69..d39b47ab508 100644 --- a/Resources/Textures/Objects/Fun/toys.rsi/meta.json +++ b/Resources/Textures/Objects/Fun/toys.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432, orb, orb-inhand-left and orb-inhand-right created by Pancake, plushie_diona and plushie_diona1 created by discord user Deos#5630, toy-mouse-equipped-HELMET is a resprited 1-equipped-HELMET in mouse.rsi by PuroSlavKing (Github), plushie_xeno by LinkUyx#6557, plushie_hampter by RenLou#4333, beachball taken from https://github.com/ss220-space/Paradise/commit/662c08272acd7be79531550919f56f846726eabb, beachb-inhand by ;3#1161, bee hat and in-hand sprites drawn by Ubaser, plushie_penguin by netwy, plushie_arachnid by PixelTheKermit (github), plushie human by TheShuEd, NanoTrasen Balloon by MACMAN2003, holoplush and magicplush modified by deltanedas (github). Lizard hat sprite made by Cinder, rubber_chicken by xprospero", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/e1142f20f5e4661cb6845cfcf2dd69f864d67432, orb, orb-inhand-left and orb-inhand-right created by Pancake, plushie_diona and plushie_diona1 created by discord user Deos#5630, toy-mouse-equipped-HELMET is a resprited 1-equipped-HELMET in mouse.rsi by PuroSlavKing (Github), plushie_xeno by LinkUyx#6557, plushie_hampter by RenLou#4333, beachball taken from https://github.com/ss220-space/Paradise/commit/662c08272acd7be79531550919f56f846726eabb, beachb-inhand by ;3#1161, bee hat and in-hand sprites drawn by Ubaser, plushie_penguin by netwy, plushie_arachnid by PixelTheKermit (github), plushie human by TheShuEd, NanoTrasen Balloon by MACMAN2003, holoplush and magicplush modified by deltanedas (github), lizard hat sprite made by Cinder, rubber_chicken by xprospero, in-hand lizard plushie sprites by KieueCaprie, plushie_lizard_inversed and inhand sprites modified from plushie_lizard_mirrored and plushielizard-inhand-left, plushielizard-inhand-right by ArtisticRoomba", "size": { "x": 32, "y": 32 @@ -20,6 +20,21 @@ "name": "plushie_rouny", "directions": 4 }, + { + "name": "plushie_lizard" + }, + { + "name": "plushielizard-inhand-left", + "directions": 4 + }, + { + "name": "plushielizard-inhand-right", + "directions": 4 + }, + { + "name": "lizard-equipped-HELMET", + "directions": 4 + }, { "name": "plushie_spacelizard" }, @@ -86,14 +101,19 @@ "name": "plushie_arachnid" }, { - "name": "plushie_lizard" + "name": "plushie_lizard_inversed" }, { - "name": "plushie_lizard_mirrored" + "name": "lizard-inversed-equipped-HELMET", + "directions": 4 }, { - "name": "lizard-equipped-HELMET", - "directions": 4 + "name": "plushielizardinversed-inhand-left", + "directions": 4 + }, + { + "name": "plushielizardinversed-inhand-right", + "directions": 4 }, { "name": "plushie_lamp" diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_inversed.png b/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_inversed.png new file mode 100644 index 00000000000..06ac083f54f Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_inversed.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_mirrored.png b/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_mirrored.png deleted file mode 100644 index a8ad75da91d..00000000000 Binary files a/Resources/Textures/Objects/Fun/toys.rsi/plushie_lizard_mirrored.png and /dev/null differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-left.png b/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-left.png new file mode 100644 index 00000000000..90e6214b441 Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-right.png b/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-right.png new file mode 100644 index 00000000000..abc547cf79f Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/plushielizard-inhand-right.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-left.png b/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-left.png new file mode 100644 index 00000000000..708736a0cab Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-left.png differ diff --git a/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-right.png b/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-right.png new file mode 100644 index 00000000000..501b3c449be Binary files /dev/null and b/Resources/Textures/Objects/Fun/toys.rsi/plushielizardinversed-inhand-right.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/generator_frame.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/generator_frame.png new file mode 100644 index 00000000000..0914769086d Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/generator_frame.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/meta.json b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/meta.json new file mode 100644 index 00000000000..79f35b3cb27 --- /dev/null +++ b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/meta.json @@ -0,0 +1,57 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by Beck Thompson using assets from https://github.com/Baystation12/Baystation12/blob/caa635edb97c58301ccdc64757eba323b6673cf3/icons/obj/structures/portgen.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "old_generator_plasma" + }, + { + "name": "old_generator_plasma_fuel_leak" + }, + { + "name": "generator_frame" + }, + { + "name": "uranium_generator" + }, + { + "name": "uranium_generator_fuel_tank" + }, + { + "name": "red_x" + }, + { + "name": "nothing" + }, + { + "name": "rad_outline", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "rad_dripping", + "delays": [ + [ + 2.0, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 + ] + ] + } + ] + } diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/nothing.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/nothing.png new file mode 100644 index 00000000000..54c77a6a0e9 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/nothing.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma.png new file mode 100644 index 00000000000..0a40e6d2c04 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma_fuel_leak.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma_fuel_leak.png new file mode 100644 index 00000000000..3d8b28b3465 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/old_generator_plasma_fuel_leak.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_dripping.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_dripping.png new file mode 100644 index 00000000000..3b8f9eb2cd3 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_dripping.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_outline.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_outline.png new file mode 100644 index 00000000000..09a4c0826ac Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/rad_outline.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/red_x.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/red_x.png new file mode 100644 index 00000000000..3ec4fd62683 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/red_x.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator.png new file mode 100644 index 00000000000..76b11fa509e Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator.png differ diff --git a/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator_fuel_tank.png b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator_fuel_tank.png new file mode 100644 index 00000000000..59cf3663bf3 Binary files /dev/null and b/Resources/Textures/Objects/Materials/Scrap/generator.rsi/uranium_generator_fuel_tank.png differ diff --git a/Resources/Textures/Objects/Specific/Medical/defib.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/defib.rsi/meta.json index 441fd4f5feb..0b08a6e6eed 100644 --- a/Resources/Textures/Objects/Specific/Medical/defib.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Medical/defib.rsi/meta.json @@ -18,9 +18,6 @@ "name": "inhand-right", "directions": 4 }, - { - "name": "ready" - }, { "name": "screen" } diff --git a/Resources/Textures/Objects/Specific/Medical/defib.rsi/ready.png b/Resources/Textures/Objects/Specific/Medical/defib.rsi/ready.png deleted file mode 100644 index 9da205f381f..00000000000 Binary files a/Resources/Textures/Objects/Specific/Medical/defib.rsi/ready.png and /dev/null differ diff --git a/Resources/Textures/Objects/Specific/Medical/defib.rsi/screen.png b/Resources/Textures/Objects/Specific/Medical/defib.rsi/screen.png index 1a8680d32b3..b203a6c2aaf 100644 Binary files a/Resources/Textures/Objects/Specific/Medical/defib.rsi/screen.png and b/Resources/Textures/Objects/Specific/Medical/defib.rsi/screen.png differ diff --git a/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-left.png b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-left.png new file mode 100644 index 00000000000..b80c46c2664 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-left.png differ diff --git a/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-right.png b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-right.png new file mode 100644 index 00000000000..5557d7e7c25 Binary files /dev/null and b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/inhand-right.png differ diff --git a/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/meta.json b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/meta.json index 76eff64a561..289c6bb2695 100644 --- a/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/meta.json +++ b/Resources/Textures/Objects/Specific/Research/anomalyscanner.rsi/meta.json @@ -4,11 +4,19 @@ "x": 32, "y": 32 }, - "license": "CC0-1.0", - "copyright": "Created by EmoGarbage", + "license": "CC-BY-SA-3.0", + "copyright": "Created by EmoGarbage, Inhands by TiniestShark based off of /tg station's health analyzer at https://github.com/tgstation/tgstation/commit/c7e5ca401c91cb1351c2f68cda3fe35634c4dba2", "states": [ { "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 } ] } diff --git a/Resources/Textures/Shaders/hologram.swsl b/Resources/Textures/Shaders/hologram.swsl new file mode 100644 index 00000000000..06fdccc2fa3 --- /dev/null +++ b/Resources/Textures/Shaders/hologram.swsl @@ -0,0 +1,23 @@ +light_mode unshaded; + +uniform highp vec3 color1; +uniform highp vec3 color2; +uniform highp float alpha; +uniform highp float intensity; +uniform highp float texHeight; +uniform highp float t; + +const highp float PI = 3.14159265; + +void fragment() { + highp vec4 base = texture2D(TEXTURE, UV); + highp float bw = zGrayscale(base.rgb * intensity); + highp vec4 color = vec4(vec3(color1), alpha); + + if (sin(PI * (UV.y + t) * texHeight) < 0.0) + { + color = vec4(vec3(color2), alpha); + } + + COLOR = vec4(vec3(bw), base.a) * color; +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/base.png b/Resources/Textures/Structures/Machines/holopad.rsi/base.png new file mode 100644 index 00000000000..4d274e1406f Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/base.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/blank.png b/Resources/Textures/Structures/Machines/holopad.rsi/blank.png new file mode 100644 index 00000000000..7bee0a002b7 Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/blank.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/icon_in_call.png b/Resources/Textures/Structures/Machines/holopad.rsi/icon_in_call.png new file mode 100644 index 00000000000..dc555615d6c Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/icon_in_call.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/lights_calling.png b/Resources/Textures/Structures/Machines/holopad.rsi/lights_calling.png new file mode 100644 index 00000000000..298087e1dbd Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/lights_calling.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/lights_hanging_up.png b/Resources/Textures/Structures/Machines/holopad.rsi/lights_hanging_up.png new file mode 100644 index 00000000000..860712c4c8c Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/lights_hanging_up.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/lights_in_call.png b/Resources/Textures/Structures/Machines/holopad.rsi/lights_in_call.png new file mode 100644 index 00000000000..de204379cc8 Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/lights_in_call.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/lights_ringing.png b/Resources/Textures/Structures/Machines/holopad.rsi/lights_ringing.png new file mode 100644 index 00000000000..7faf902ec82 Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/lights_ringing.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/meta.json b/Resources/Textures/Structures/Machines/holopad.rsi/meta.json new file mode 100644 index 00000000000..b7601b19c1b --- /dev/null +++ b/Resources/Textures/Structures/Machines/holopad.rsi/meta.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/80025/commits/f0cc8856d4c1b6b3933524a2d37581cc81c3c05b, /icons/obj/machines/floor.dmi. Edited by chromiumboy", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "unpowered" + }, + { + "name": "panel_open" + }, + { + "name": "blank" + }, + { + "name": "icon_in_call" + }, + { + "name": "lights_calling", + "delays": [ + [ + 0.2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.5, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "lights_in_call", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "lights_ringing", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.8 + ] + ] + }, + { + "name": "lights_hanging_up", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 99 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/panel_open.png b/Resources/Textures/Structures/Machines/holopad.rsi/panel_open.png new file mode 100644 index 00000000000..22947c88870 Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/panel_open.png differ diff --git a/Resources/Textures/Structures/Machines/holopad.rsi/unpowered.png b/Resources/Textures/Structures/Machines/holopad.rsi/unpowered.png new file mode 100644 index 00000000000..7b4b5dd65a1 Binary files /dev/null and b/Resources/Textures/Structures/Machines/holopad.rsi/unpowered.png differ diff --git a/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-open.png b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-open.png new file mode 100644 index 00000000000..c45b4246008 Binary files /dev/null and b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-open.png differ diff --git a/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-static.png b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-static.png new file mode 100644 index 00000000000..7611f14c2c6 Binary files /dev/null and b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes-static.png differ diff --git a/Resources/Textures/Structures/Power/smes.rsi/advancedsmes.png b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes.png new file mode 100644 index 00000000000..a900d8338f6 Binary files /dev/null and b/Resources/Textures/Structures/Power/smes.rsi/advancedsmes.png differ diff --git a/Resources/Textures/Structures/Power/smes.rsi/meta.json b/Resources/Textures/Structures/Power/smes.rsi/meta.json index 2ca8d1fb226..04646fddb91 100644 --- a/Resources/Textures/Structures/Power/smes.rsi/meta.json +++ b/Resources/Textures/Structures/Power/smes.rsi/meta.json @@ -1,16 +1,25 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/9c7d509354ee030300f63c701da63c17928c3b3b and modified by Swept", + "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/9c7d509354ee030300f63c701da63c17928c3b3b and modified by Swept, advanced-smes, advanced-smes-static, advanced-smes-open modified from smes, smes-open, and static by august-sun (GitHub)", "size": { "x": 32, "y": 32 }, "states": [ + { + "name": "advancedsmes" + }, + { + "name": "advancedsmes-static" + }, + { + "name": "advancedsmes-open" + }, { "name": "smes" }, - { + { "name": "static" }, { diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/core.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/core.png new file mode 100644 index 00000000000..a97b408596a Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/core.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/meta.json new file mode 100644 index 00000000000..1263862f7c8 --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by Jaraten (github) for ss14 at https://github.com/space-wizards/space-station-14/pull/33889", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "core" + }, + { + "name": "pulse", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/pulse.png b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/pulse.png new file mode 100644 index 00000000000..be15a903579 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/Cores/santa_core.rsi/pulse.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json index 4e069d0cd90..4aa15c86cd8 100644 --- a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json +++ b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/meta.json @@ -443,6 +443,19 @@ ] ] }, + { + "name": "santa", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + }, { "name": "shadow", "directions": 4, diff --git a/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/santa.png b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/santa.png new file mode 100644 index 00000000000..46524384040 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/inner_anom_layer.rsi/santa.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/anom.png b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/anom.png new file mode 100644 index 00000000000..0788b3c2dcd Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/anom.png differ diff --git a/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/meta.json b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/meta.json new file mode 100644 index 00000000000..4681548ad48 --- /dev/null +++ b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/meta.json @@ -0,0 +1,33 @@ +{ + "version": 1, + "license": "CC0-1.0", + "copyright": "Created by TheShuEd (github) for ss14 at https://github.com/space-wizards/space-station-14/pull/33889", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "anom", + "delays": [ + [ + 0.3, + 0.3, + 0.3, + 0.3 + ] + ] + }, + { + "name": "pulse", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} diff --git a/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/pulse.png b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/pulse.png new file mode 100644 index 00000000000..b8254eed2b0 Binary files /dev/null and b/Resources/Textures/Structures/Specific/Anomalies/santa_anom.rsi/pulse.png differ diff --git a/Resources/toolshedEngineCommandPerms.yml b/Resources/toolshedEngineCommandPerms.yml index ac7ffddd5f9..b9911e9468d 100644 --- a/Resources/toolshedEngineCommandPerms.yml +++ b/Resources/toolshedEngineCommandPerms.yml @@ -6,7 +6,6 @@ - physics - player - splat - - emplace - bin - extremes - reduce @@ -19,113 +18,88 @@ - iota - rep - to - - iterate - Flags: DEBUG Commands: - comp - delete - - do + - with + - prototyped - named - paused - - with - - count + - emplace + - do + - iterate - select - where - - prototyped + - count - types - - ecscomp - actor - spawn + - replace - mappos - pos - tp - allcomps - - replace - entitysystemupdateorder - mind - -- Flags: HOST - Commands: - - methods - - ioc - -- Commands: - fuck - - ent - - as - - buildinfo - - help - - explain - - cmd - - stopwatch + - '=>' + - '?' + - 'or?' + - '??' + - rng - self + - sum + - take + - join - search + - first + - unique + - any + - contains - isnull - - help - isempty - - any - - unique - cd - ls - - loc - - vars - - '=>' - - first - - val + - stopwatch + - append + - min + - max + - average - '+' - '-' - '*' - '/' - - 'min' - - 'max' - - '&' - - '|' - - '^' - - 'neg' + - '%' + - '%/' + - '&~' + - '|~' + - '^~' + - '~' - '<' - '>' - '<=' - '>=' - '==' - '!=' - - f - - i - - s - - b - '+/' - '-/' - '*/' - '//' - - join - - append - - '?' - - 'or?' - - '??' - - rng - - 'sum' - - take - - curtick - - curtime - - realtime - - servertime - - more - - '%' - - '%/' - - '&~' - - '|~' - - '^~' - - '~' - - 'abs' - - 'average' - - 'bibytecount' - - 'shortestbitlength' - - 'countleadzeros' - - 'counttrailingzeros' - - 'fpi' - - 'fe' - - 'ftau' - - 'fepsilon' + - '&' + - '|' + - '^' + - neg + - abs + - bibytecount + - shortestbitlength + - countleadzeros + - counttrailingzeros + - fpi + - fe + - ftau + - fepsilon - dpi - de - dtau @@ -181,3 +155,29 @@ - atanpi - pick - tee + +- Flags: HOST + Commands: + - methods + - ioc + +- Commands: + - ent + - f + - i + - s + - b + - as + - var + - vars + - val + - help + - explain + - cmd + - buildinfo + - loc + - curtick + - curtime + - realtime + - servertime + - more diff --git a/RobustToolbox b/RobustToolbox index 92b0e7f1a85..5e97db435c0 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 92b0e7f1a853979a1361ed24d2fb5ffc11f43f66 +Subproject commit 5e97db435c05b4c188184ef90e5d77b0500403d0