Merge remote-tracking branch 'refs/remotes/wizards/master'
# Conflicts: # Content.Client/Launcher/LauncherConnecting.cs # Content.Client/Launcher/LauncherConnectingGui.xaml # Content.Client/Launcher/LauncherConnectingGui.xaml.cs # Content.Server/Administration/Managers/BanManager.cs # Content.Server/Medical/Components/HealthAnalyzerComponent.cs # Content.Server/Medical/HealthAnalyzerSystem.cs # Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs # Content.Server/StationEvents/Events/RandomSentienceRule.cs # Content.Shared/Interaction/Components/BlockMovementComponent.cs # Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs # Content.Shared/Materials/SharedMaterialReclaimerSystem.cs # Resources/Prototypes/Accents/word_replacements.yml # Resources/Prototypes/Catalog/Fills/Books/bookshelf.yml # Resources/Prototypes/Entities/Mobs/Customization/Markings/cat_parts.yml # Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml # Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml # Resources/Prototypes/Entities/Structures/Machines/lathe.yml # Resources/Prototypes/Objectives/objectiveGroups.yml # Resources/Prototypes/Objectives/stealTargetGroups.yml # Resources/Prototypes/Roles/Jobs/Security/detective.yml # Resources/Prototypes/Species/human.yml # Resources/Prototypes/Voice/speech_emotes.yml # Resources/ServerInfo/Guidebook/Antagonist/Antagonists.xml # Resources/ServerInfo/Guidebook/Antagonist/MinorAntagonists.xml # Resources/ServerInfo/Guidebook/Antagonist/Nuclear Operatives.xml # Resources/ServerInfo/Guidebook/Antagonist/Revolutionaries.xml # Resources/ServerInfo/Guidebook/Antagonist/SpaceNinja.xml # Resources/ServerInfo/Guidebook/Antagonist/Traitors.xml # Resources/ServerInfo/Guidebook/Antagonist/Zombies.xml # Resources/migration.yml
This commit is contained in:
commit
0441b8b814
403 changed files with 18136 additions and 10066 deletions
|
|
@ -4,14 +4,14 @@
|
|||
MinSize="400 225">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<TextEdit Name="MessageInput" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 0 5" MinHeight="100" />
|
||||
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" ToolTip="{Loc 'comms-console-menu-announcement-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
<Button Name="BroadcastButton" Text="{Loc 'comms-console-menu-broadcast-button'}" ToolTip="{Loc 'comms-console-menu-broadcast-button-tooltip'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
|
||||
<OptionButton Name="AlertLevelButton" StyleClasses="OpenRight" Access="Public" />
|
||||
<OptionButton Name="AlertLevelButton" ToolTip="{Loc 'comms-console-menu-alert-level-button-tooltip'}" StyleClasses="OpenRight" Access="Public" />
|
||||
|
||||
<Control MinSize="10 10" />
|
||||
|
||||
<RichTextLabel Name="CountdownLabel" VerticalExpand="True" />
|
||||
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" Access="Public" />
|
||||
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" ToolTip="{Loc 'comms-console-menu-emergency-shuttle-button-tooltip'}" Access="Public" />
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ namespace Content.Client.Ghost
|
|||
if (args.Handled)
|
||||
return;
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("ghost-gui-toggle-ghost-visibility-popup"), args.Performer);
|
||||
|
||||
var locId = GhostVisibility ? "ghost-gui-toggle-ghost-visibility-popup-off" : "ghost-gui-toggle-ghost-visibility-popup-on";
|
||||
Popup.PopupEntity(Loc.GetString(locId), args.Performer);
|
||||
if (uid == _playerManager.LocalEntity)
|
||||
ToggleGhostVisibility();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Content.Client.Launcher
|
|||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
[Dependency] private readonly ServersHubManager _serversHubManager = default!;
|
||||
|
||||
private LauncherConnectingGui? _control;
|
||||
|
|
@ -60,7 +61,7 @@ namespace Content.Client.Launcher
|
|||
|
||||
protected override void Startup()
|
||||
{
|
||||
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _serversHubManager);
|
||||
_control = new LauncherConnectingGui(this, _random, _prototypeManager, _cfg, _clipboard, _serversHubManager);
|
||||
|
||||
_userInterfaceManager.StateRoot.AddChild(_control);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,51 +7,67 @@
|
|||
<parallax:ParallaxControl />
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" MinSize="300 200">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
|
||||
StyleClasses="LabelHeading" VAlign="Center" />
|
||||
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<controls:HighDivider />
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4 4 4 0">
|
||||
<Control VerticalExpand="True" Margin="0 0 0 8">
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectingStatus">
|
||||
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
|
||||
<!-- Who the fuck named these cont- oh wait I did -->
|
||||
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False">
|
||||
<RichTextLabel Name="ConnectFailReason" VerticalAlignment="Stretch"/>
|
||||
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="Disconnected">
|
||||
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
|
||||
<Label Name="DisconnectReason" Align="Center" />
|
||||
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalExpand="True" VerticalAlignment="Bottom" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" Margin="12 0 4 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
|
||||
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Orientation="Vertical" MinSize="300 200">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
|
||||
StyleClasses="LabelHeading" VAlign="Center" />
|
||||
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<controls:HighDivider />
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4 4 4 0">
|
||||
<Control VerticalExpand="True" Margin="0 0 0 8">
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectingStatus">
|
||||
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
|
||||
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False" SeparationOverride="10">
|
||||
<RichTextLabel Name="ConnectFailReason" VerticalAlignment="Stretch"/>
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
<Button Name="RetryButton"
|
||||
Text="{Loc 'connecting-retry'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="CopyButton"
|
||||
Text="{Loc 'connecting-copy'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" Name="Disconnected" Visible="False" SeparationOverride="10">
|
||||
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
|
||||
<Label Name="DisconnectReason" Align="Center" />
|
||||
<BoxContainer Orientation="Horizontal" Align="Center" VerticalAlignment="Bottom">
|
||||
<Button Name="ReconnectButton"
|
||||
Text="{Loc 'connecting-reconnect'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="CopyButtonDisconnected"
|
||||
Text="{Loc 'connecting-copy'}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" Margin="12 0 4 0" VerticalAlignment="Bottom">
|
||||
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
|
||||
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
|
||||
HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True" Margin="0 10 0 0" Visible="False" Name="ServersHub" MinWidth="600" MinHeight="220">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="3 3 3 3">
|
||||
<controls:StripeBack>
|
||||
|
|
@ -67,7 +83,6 @@
|
|||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
<!-- Bottom window for tips -->
|
||||
<PanelContainer Name="LoginTips" StyleClasses="AngleRect" Margin="0 10" MaxWidth="600" VerticalExpand="True" VerticalAlignment="Bottom">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
|
|
|
|||
|
|
@ -30,16 +30,19 @@ namespace Content.Client.Launcher
|
|||
private readonly IRobustRandom _random;
|
||||
private readonly IPrototypeManager _prototype;
|
||||
private readonly IConfigurationManager _cfg;
|
||||
private readonly ServersHubManager _serversHubManager;
|
||||
private readonly IClipboardManager _clipboard;
|
||||
private readonly ServersHubManager _serversHubManager; // Sunrise-Edit
|
||||
|
||||
public LauncherConnectingGui(LauncherConnecting state, IRobustRandom random,
|
||||
IPrototypeManager prototype, IConfigurationManager config, ServersHubManager serversHubManager)
|
||||
IPrototypeManager prototype, IConfigurationManager config, IClipboardManager clipboard,
|
||||
ServersHubManager serversHubManager) // Sunrise-Edit
|
||||
{
|
||||
_state = state;
|
||||
_random = random;
|
||||
_prototype = prototype;
|
||||
_cfg = config;
|
||||
_serversHubManager = serversHubManager;
|
||||
_clipboard = clipboard;
|
||||
_serversHubManager = serversHubManager; // Sunrise-Edit
|
||||
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
|
|
@ -48,8 +51,11 @@ namespace Content.Client.Launcher
|
|||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
ChangeLoginTip();
|
||||
ReconnectButton.OnPressed += ReconnectButtonPressed;
|
||||
RetryButton.OnPressed += ReconnectButtonPressed;
|
||||
ReconnectButton.OnPressed += ReconnectButtonPressed;
|
||||
|
||||
CopyButton.OnPressed += CopyButtonPressed;
|
||||
CopyButtonDisconnected.OnPressed += CopyButtonDisconnectedPressed;
|
||||
ExitButton.OnPressed += _ => _state.Exit();
|
||||
|
||||
var addr = state.Address;
|
||||
|
|
@ -68,15 +74,17 @@ namespace Content.Client.Launcher
|
|||
edim.LastNetDisconnectedArgsChanged += LastNetDisconnectedArgsChanged;
|
||||
LastNetDisconnectedArgsChanged(edim.LastNetDisconnectedArgs);
|
||||
|
||||
_serversHubManager.ServersDataListChanged += RefreshServersHubHeader;
|
||||
_serversHubManager.ServersDataListChanged += RefreshServersHubHeader; // Sunrise-Edit
|
||||
}
|
||||
|
||||
// Sunrise-Start
|
||||
private void RefreshServersHubHeader(List<ServerHubEntry> servers)
|
||||
{
|
||||
var totalPlayers = servers.Sum(server => server.CurrentPlayers);
|
||||
var maxPlayers = servers.Sum(server => server.MaxPlayers);
|
||||
ServersHubHeaderLabel.Text = Loc.GetString("serverhub-playingnow", ("total", totalPlayers), ("max", maxPlayers)); // Sunrise-Edit
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
// Just button, there's only one at once anyways :)
|
||||
private void ReconnectButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
|
|
@ -91,6 +99,24 @@ namespace Content.Client.Launcher
|
|||
_state.RetryConnect();
|
||||
}
|
||||
|
||||
private void CopyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
CopyText(ConnectFailReason.Text);
|
||||
}
|
||||
|
||||
private void CopyButtonDisconnectedPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
CopyText(DisconnectReason.Text);
|
||||
}
|
||||
|
||||
private void CopyText(string? text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
_clipboard.SetText(text);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectFailReasonChanged(string? reason)
|
||||
{
|
||||
ConnectFailReason.SetMessage(reason == null
|
||||
|
|
|
|||
27
Content.Client/Materials/RecyclerVisualizerSystem.cs
Normal file
27
Content.Client/Materials/RecyclerVisualizerSystem.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using Content.Shared.Conveyor;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Materials;
|
||||
|
||||
public sealed class RecyclerVisualizerSystem : VisualizerSystem<RecyclerVisualsComponent>
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, RecyclerVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null || !args.Sprite.LayerMapTryGet(RecyclerVisualLayers.Main, out var layer))
|
||||
return;
|
||||
|
||||
AppearanceSystem.TryGetData<ConveyorState>(uid, ConveyorVisuals.State, out var running);
|
||||
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Bloody, out var bloody);
|
||||
AppearanceSystem.TryGetData<bool>(uid, RecyclerVisuals.Broken, out var broken);
|
||||
|
||||
var activityState = running == ConveyorState.Off ? 0 : 1;
|
||||
if (broken) //breakage overrides activity
|
||||
activityState = 2;
|
||||
|
||||
var bloodyKey = bloody ? component.BloodyKey : string.Empty;
|
||||
|
||||
var state = $"{component.BaseKey}{activityState}{bloodyKey}";
|
||||
args.Sprite.LayerSetState(layer, state);
|
||||
}
|
||||
}
|
||||
17
Content.Client/Materials/RecyclerVisualsComponent.cs
Normal file
17
Content.Client/Materials/RecyclerVisualsComponent.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Content.Client.Materials;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class RecyclerVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Key appended to state string if bloody.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BloodyKey = "bld";
|
||||
|
||||
/// <summary>
|
||||
/// Base key for the visual state.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BaseKey = "grinder-o";
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
public bool ShowIFF { get; set; } = true;
|
||||
public bool ShowDocks { get; set; } = true;
|
||||
public bool RotateWithEntity { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Raised if the user left-clicks on the radar control with the relevant entitycoordinates.
|
||||
|
|
@ -109,6 +110,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
|
||||
ActualRadarRange = Math.Clamp(ActualRadarRange, WorldMinRange, WorldMaxRange);
|
||||
|
||||
RotateWithEntity = state.RotateWithEntity;
|
||||
|
||||
_docks = state.Docks;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +141,8 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
|
||||
var offset = _coordinates.Value.Position;
|
||||
var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
|
||||
var (_, ourEntRot, ourEntMatrix) = _transform.GetWorldPositionRotationMatrix(_coordinates.Value.EntityId);
|
||||
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
|
||||
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||
var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
|
||||
Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<Control xmlns="https://spacestation14.io" MinWidth="300">
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Margin="4" Orientation="Vertical">
|
||||
<Label Name="VoteCaller" />
|
||||
<Label Name="VoteTitle" />
|
||||
<RichTextLabel Name="VoteTitle" />
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Robust.Shared.IoC;
|
|||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Voting.UI
|
||||
{
|
||||
|
|
@ -48,7 +49,7 @@ namespace Content.Client.Voting.UI
|
|||
|
||||
public void UpdateData()
|
||||
{
|
||||
VoteTitle.Text = _vote.Title;
|
||||
VoteTitle.SetMessage(FormattedMessage.FromUnformatted(_vote.Title));
|
||||
VoteCaller.Text = Loc.GetString("ui-vote-created", ("initiator", _vote.Initiator));
|
||||
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public static class ServerPackaging
|
|||
new PlatformReg("win-x86", "Windows", false),
|
||||
new PlatformReg("linux-x86", "Linux", false),
|
||||
new PlatformReg("linux-arm", "Linux", false),
|
||||
new PlatformReg("freebsd-x64", "FreeBSD", false),
|
||||
};
|
||||
|
||||
private static List<string> PlatformRids => Platforms
|
||||
|
|
|
|||
1913
Content.Server.Database/Migrations/Postgres/20240606121555_ban_notify_trigger.Designer.cs
generated
Normal file
1913
Content.Server.Database/Migrations/Postgres/20240606121555_ban_notify_trigger.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ban_notify_trigger : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("""
|
||||
create or replace function send_server_ban_notification()
|
||||
returns trigger as $$
|
||||
declare
|
||||
x_server_id integer;
|
||||
begin
|
||||
select round.server_id into x_server_id from round where round.round_id = NEW.round_id;
|
||||
|
||||
perform pg_notify('ban_notification', json_build_object('ban_id', NEW.server_ban_id, 'server_id', x_server_id)::text);
|
||||
return NEW;
|
||||
end;
|
||||
$$ LANGUAGE plpgsql;
|
||||
""");
|
||||
|
||||
migrationBuilder.Sql("""
|
||||
create or replace trigger notify_on_server_ban_insert
|
||||
after insert on server_ban
|
||||
for each row
|
||||
execute function send_server_ban_notification();
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("""
|
||||
drop trigger notify_on_server_ban_insert on server_ban;
|
||||
drop function send_server_ban_notification;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -706,6 +706,11 @@ namespace Content.Server.Database
|
|||
/// Intended for use with residential IP ranges that are often used maliciously.
|
||||
/// </remarks>
|
||||
BlacklistedRange = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Represents having all possible exemption flags.
|
||||
/// </summary>
|
||||
All = int.MaxValue,
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
|
@ -904,7 +909,7 @@ namespace Content.Server.Database
|
|||
Panic = 3,
|
||||
/*
|
||||
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
|
||||
*
|
||||
*
|
||||
* If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons.
|
||||
* Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Server.Database;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
public sealed partial class BanManager
|
||||
{
|
||||
// Responsible for ban notification handling.
|
||||
// Ban notifications are sent through the database to notify the entire server group that a new ban has been added,
|
||||
// so that people will get kicked if they are banned on a different server than the one that placed the ban.
|
||||
//
|
||||
// Ban notifications are currently sent by a trigger in the database, automatically.
|
||||
|
||||
/// <summary>
|
||||
/// The notification channel used to broadcast information about new bans.
|
||||
/// </summary>
|
||||
public const string BanNotificationChannel = "ban_notification";
|
||||
|
||||
// Rate limit to avoid undue load from mass-ban imports.
|
||||
// Only process 10 bans per 30 second interval.
|
||||
//
|
||||
// I had the idea of maybe binning this by postgres transaction ID,
|
||||
// to avoid any possibility of dropping a normal ban by coincidence.
|
||||
// Didn't bother implementing this though.
|
||||
private static readonly TimeSpan BanNotificationRateLimitTime = TimeSpan.FromSeconds(30);
|
||||
private const int BanNotificationRateLimitCount = 10;
|
||||
|
||||
private readonly object _banNotificationRateLimitStateLock = new();
|
||||
private TimeSpan _banNotificationRateLimitStart;
|
||||
private int _banNotificationRateLimitCount;
|
||||
|
||||
private void OnDatabaseNotification(DatabaseNotification notification)
|
||||
{
|
||||
if (notification.Channel != BanNotificationChannel)
|
||||
return;
|
||||
|
||||
if (notification.Payload == null)
|
||||
{
|
||||
_sawmill.Error("Got ban notification with null payload!");
|
||||
return;
|
||||
}
|
||||
|
||||
BanNotificationData data;
|
||||
try
|
||||
{
|
||||
data = JsonSerializer.Deserialize<BanNotificationData>(notification.Payload)
|
||||
?? throw new JsonException("Content is null");
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_sawmill.Error($"Got invalid JSON in ban notification: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckBanRateLimit())
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification due to rate limit");
|
||||
return;
|
||||
}
|
||||
|
||||
_taskManager.RunOnMainThread(() => ProcessBanNotification(data));
|
||||
}
|
||||
|
||||
private async void ProcessBanNotification(BanNotificationData data)
|
||||
{
|
||||
if ((await _entryManager.ServerEntity).Id == data.ServerId)
|
||||
{
|
||||
_sawmill.Verbose("Not processing ban notification: came from this server");
|
||||
return;
|
||||
}
|
||||
|
||||
_sawmill.Verbose($"Processing ban notification for ban {data.BanId}");
|
||||
var ban = await _db.GetServerBanAsync(data.BanId);
|
||||
if (ban == null)
|
||||
{
|
||||
_sawmill.Warning($"Ban in notification ({data.BanId}) didn't exist?");
|
||||
return;
|
||||
}
|
||||
|
||||
KickMatchingConnectedPlayers(ban, "ban notification");
|
||||
}
|
||||
|
||||
private bool CheckBanRateLimit()
|
||||
{
|
||||
lock (_banNotificationRateLimitStateLock)
|
||||
{
|
||||
var now = _gameTiming.RealTime;
|
||||
if (_banNotificationRateLimitStart + BanNotificationRateLimitTime < now)
|
||||
{
|
||||
// Rate limit period expired, restart it.
|
||||
_banNotificationRateLimitCount = 1;
|
||||
_banNotificationRateLimitStart = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
_banNotificationRateLimitCount += 1;
|
||||
return _banNotificationRateLimitCount <= BanNotificationRateLimitCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data sent along the notification channel for a single ban notification.
|
||||
/// </summary>
|
||||
private sealed class BanNotificationData
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the new ban object in the database to check.
|
||||
/// </summary>
|
||||
[JsonRequired, JsonPropertyName("ban_id")]
|
||||
public int BanId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The id of the server the ban was made on.
|
||||
/// This is used to avoid double work checking the ban on the originating server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is optional in case the ban was made outside a server (SS14.Admin)
|
||||
/// </remarks>
|
||||
[JsonPropertyName("server_id")]
|
||||
public int? ServerId { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
|||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Database;
|
||||
|
|
@ -11,11 +12,13 @@ using Content.Shared.Players;
|
|||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
|
|
@ -32,7 +35,7 @@ using Content.Sunrise.Interfaces.Server; // Sunrise-Edit
|
|||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
public sealed class BanManager : IBanManager, IPostInjectInit
|
||||
public sealed partial class BanManager : IBanManager, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
|
@ -40,10 +43,13 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||
[Dependency] private readonly ServerDbEntryManager _entryManager = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDbData = default!;
|
||||
|
||||
private IServerDiscordAuthManager? _discordAuth; // Sunrise-Edit
|
||||
|
||||
|
|
@ -60,23 +66,41 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||
// Sunrise-end
|
||||
|
||||
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
|
||||
// Cached ban exemption flags are used to handle
|
||||
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_netManager.RegisterNetMessage<MsgRoleBans>();
|
||||
|
||||
_db.SubscribeToNotifications(OnDatabaseNotification);
|
||||
|
||||
_userDbData.AddOnLoadPlayer(CachePlayerData);
|
||||
_userDbData.AddOnPlayerDisconnect(ClearPlayerData);
|
||||
|
||||
// Sunrise-start
|
||||
_config.OnValueChanged(SunriseCCVars.DiscordBanWebhook, OnWebhookChanged, true);
|
||||
_config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true);
|
||||
_cfg.OnValueChanged(SunriseCCVars.DiscordBanWebhook, OnWebhookChanged, true);
|
||||
_cfg.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true);
|
||||
|
||||
IoCManager.Instance!.TryResolveType(out _discordAuth);
|
||||
// Sunrise-end
|
||||
}
|
||||
|
||||
private void OnServerNameChanged(string obj)
|
||||
private async Task CachePlayerData(ICommonSession player, CancellationToken cancel)
|
||||
{
|
||||
_serverName = obj;
|
||||
// Yeah so role ban loading code isn't integrated with exempt flag loading code.
|
||||
// Have you seen how garbage role ban code code is? I don't feel like refactoring it right now.
|
||||
|
||||
var flags = await _db.GetBanExemption(player.UserId, cancel);
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
_cachedBanExemptions[player] = flags;
|
||||
}
|
||||
|
||||
private void ClearPlayerData(ICommonSession player)
|
||||
{
|
||||
_cachedBanExemptions.Remove(player);
|
||||
}
|
||||
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
|
|
@ -155,6 +179,7 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||
}
|
||||
|
||||
// Sunrise-start
|
||||
// Обраточка
|
||||
if (targetUsername == "VigersRay")
|
||||
target = banningAdmin;
|
||||
// Sunrise-end
|
||||
|
|
@ -207,20 +232,47 @@ public sealed class BanManager : IBanManager, IPostInjectInit
|
|||
|
||||
// Sunrise-start
|
||||
var ban = await _db.GetServerBanAsync(null, target, null);
|
||||
if (ban != null) SendWebhook(await GenerateBanPayload(ban, minutes));
|
||||
if (ban != null)
|
||||
SendWebhook(await GenerateBanPayload(ban, minutes));
|
||||
// Sunrise-end
|
||||
|
||||
// If we're not banning a player we don't care about disconnecting people
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
// Is the player connected?
|
||||
if (!_playerManager.TryGetSessionById(target.Value, out var targetPlayer))
|
||||
return;
|
||||
// If they are, kick them
|
||||
var message = banDef.FormatBanMessage(_cfg, _localizationManager);
|
||||
targetPlayer.Channel.Disconnect(message);
|
||||
KickMatchingConnectedPlayers(banDef, "newly placed ban");
|
||||
}
|
||||
|
||||
private void KickMatchingConnectedPlayers(ServerBanDef def, string source)
|
||||
{
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
if (BanMatchesPlayer(player, def))
|
||||
{
|
||||
KickForBanDef(player, def);
|
||||
_sawmill.Info($"Kicked player {player.Name} ({player.UserId}) through {source}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool BanMatchesPlayer(ICommonSession player, ServerBanDef ban)
|
||||
{
|
||||
var playerInfo = new BanMatcher.PlayerInfo
|
||||
{
|
||||
UserId = player.UserId,
|
||||
Address = player.Channel.RemoteEndPoint.Address,
|
||||
HWId = player.Channel.UserData.HWId,
|
||||
// It's possible for the player to not have cached data loading yet due to coincidental timing.
|
||||
// If this is the case, we assume they have all flags to avoid false-positives.
|
||||
ExemptFlags = _cachedBanExemptions.GetValueOrDefault(player, ServerBanExemptFlags.All),
|
||||
IsNewPlayer = false,
|
||||
};
|
||||
|
||||
return BanMatcher.BanMatches(ban, playerInfo);
|
||||
}
|
||||
|
||||
private void KickForBanDef(ICommonSession player, ServerBanDef def)
|
||||
{
|
||||
var message = def.FormatBanMessage(_cfg, _localizationManager);
|
||||
player.Channel.Disconnect(message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Job Bans
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ public sealed partial class AtmosphereSystem
|
|||
newGridAtmos = AddComp<GridAtmosphereComponent>(newGrid);
|
||||
|
||||
// We assume the tiles on the new grid have the same coordinates as they did on the old grid...
|
||||
var enumerator = mapGrid.GetAllTilesEnumerator();
|
||||
var enumerator = _mapSystem.GetAllTilesEnumerator(newGrid, mapGrid);
|
||||
|
||||
while (enumerator.MoveNext(out var tile))
|
||||
{
|
||||
|
|
@ -176,7 +176,7 @@ public sealed partial class AtmosphereSystem
|
|||
tile.AdjacentBits = AtmosDirection.Invalid;
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var direction = (AtmosDirection)(1 << i);
|
||||
var adjacentIndices = tile.GridIndices.Offset(direction);
|
||||
|
||||
TileAtmosphere? adjacent;
|
||||
|
|
@ -196,7 +196,7 @@ public sealed partial class AtmosphereSystem
|
|||
AddActiveTile(atmos, adjacent);
|
||||
|
||||
var oppositeIndex = i.ToOppositeIndex();
|
||||
var oppositeDirection = (AtmosDirection) (1 << oppositeIndex);
|
||||
var oppositeDirection = (AtmosDirection)(1 << oppositeIndex);
|
||||
|
||||
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
|
||||
{
|
||||
|
|
@ -269,7 +269,7 @@ public sealed partial class AtmosphereSystem
|
|||
private void GridFixTileVacuum(TileAtmosphere tile)
|
||||
{
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable == false );
|
||||
DebugTools.Assert(tile.Air?.Immutable == false);
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.ArchivedCycle = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -95,18 +95,14 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
|||
{ ":/", "chatsan-uncertain" },
|
||||
{ ":\\", "chatsan-uncertain" },
|
||||
{ "lmao", "chatsan-laughs" },
|
||||
{ "lmao.", "chatsan-laughs" },
|
||||
{ "lmfao", "chatsan-laughs" },
|
||||
{ "lol", "chatsan-laughs" },
|
||||
{ "lol.", "chatsan-laughs" },
|
||||
{ "lel", "chatsan-laughs" },
|
||||
{ "lel.", "chatsan-laughs" },
|
||||
{ "kek", "chatsan-laughs" },
|
||||
{ "kek.", "chatsan-laughs" },
|
||||
{ "rofl", "chatsan-laughs" },
|
||||
{ "o7", "chatsan-salutes" },
|
||||
{ ";_;7", "chatsan-tearfully-salutes"},
|
||||
{ "idk", "chatsan-shrugs" },
|
||||
{ "idk.", "chatsan-shrugs" },
|
||||
{ ";)", "chatsan-winks" },
|
||||
{ ";]", "chatsan-winks" },
|
||||
{ "(;", "chatsan-winks" },
|
||||
|
|
|
|||
|
|
@ -161,14 +161,32 @@ public partial class ChatSystem
|
|||
/// <param name="textInput"></param>
|
||||
private void TryEmoteChatInput(EntityUid uid, string textInput)
|
||||
{
|
||||
var actionLower = textInput.ToLower();
|
||||
if (!_wordEmoteDict.TryGetValue(actionLower, out var emote))
|
||||
var actionTrimmedLower = TrimPunctuation(textInput.ToLower());
|
||||
if (!_wordEmoteDict.TryGetValue(actionTrimmedLower, out var emote))
|
||||
return;
|
||||
|
||||
if (!AllowedToUseEmote(uid, emote))
|
||||
return;
|
||||
|
||||
InvokeEmoteEvent(uid, emote);
|
||||
return;
|
||||
|
||||
static string TrimPunctuation(string textInput)
|
||||
{
|
||||
var trimEnd = textInput.Length;
|
||||
while (trimEnd > 0 && char.IsPunctuation(textInput[trimEnd - 1]))
|
||||
{
|
||||
trimEnd--;
|
||||
}
|
||||
|
||||
var trimStart = 0;
|
||||
while (trimStart < trimEnd && char.IsPunctuation(textInput[trimStart]))
|
||||
{
|
||||
trimStart++;
|
||||
}
|
||||
|
||||
return textInput[trimStart..trimEnd];
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if we can use this emote based on the emotes whitelist, blacklist, and availibility to the entity.
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ namespace Content.Server.Construction.Completions
|
|||
public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
|
||||
{
|
||||
var scale = (float) IoCManager.Resolve<IRobustRandom>().NextGaussian(1, Variation);
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedAudioSystem>()
|
||||
.PlayPvs(Sound, uid, AudioParams.WithPitchScale(scale));
|
||||
if (entityManager.TryGetComponent<TransformComponent>(uid, out var xform))
|
||||
entityManager.EntitySysManager.GetEntitySystem<SharedAudioSystem>()
|
||||
.PlayPvs(Sound, xform.Coordinates, AudioParams.WithPitchScale(scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Camera;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Events;
|
||||
|
|
@ -9,6 +10,7 @@ using Content.Shared.Database;
|
|||
using Content.Shared.Effects;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
|
|
@ -28,6 +30,7 @@ namespace Content.Server.Damage.Systems
|
|||
{
|
||||
SubscribeLocalEvent<DamageOtherOnHitComponent, ThrowDoHitEvent>(OnDoHit);
|
||||
SubscribeLocalEvent<DamageOtherOnHitComponent, DamageExamineEvent>(OnDamageExamine);
|
||||
SubscribeLocalEvent<DamageOtherOnHitComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
|
||||
}
|
||||
|
||||
private void OnDoHit(EntityUid uid, DamageOtherOnHitComponent component, ThrowDoHitEvent args)
|
||||
|
|
@ -58,5 +61,13 @@ namespace Content.Server.Damage.Systems
|
|||
{
|
||||
_damageExamine.AddDamageExamine(args.Message, component.Damage, Loc.GetString("damage-throw"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevent players with the Pacified status effect from throwing things that deal damage.
|
||||
/// </summary>
|
||||
private void OnAttemptPacifiedThrow(Entity<DamageOtherOnHitComponent> ent, ref AttemptPacifiedThrowEvent args)
|
||||
{
|
||||
args.Cancel("pacified-cannot-throw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
90
Content.Server/Database/BanMatcher.cs
Normal file
90
Content.Server/Database/BanMatcher.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Net;
|
||||
using Content.Server.IP;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Implements logic to match a <see cref="ServerBanDef"/> against a player query.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This implementation is used by in-game ban matching code, and partially by the SQLite database layer.
|
||||
/// Some logic is duplicated into both the SQLite and PostgreSQL database layers to provide more optimal SQL queries.
|
||||
/// Both should be kept in sync, please!
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class BanMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Check whether a ban matches the specified player info.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This function does not check whether the ban itself is expired or manually unbanned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="ban">The ban information.</param>
|
||||
/// <param name="player">Information about the player to match against.</param>
|
||||
/// <returns>True if the ban matches the provided player info.</returns>
|
||||
public static bool BanMatches(ServerBanDef ban, in PlayerInfo player)
|
||||
{
|
||||
var exemptFlags = player.ExemptFlags;
|
||||
// Any flag to bypass BlacklistedRange bans.
|
||||
if (exemptFlags != ServerBanExemptFlags.None)
|
||||
exemptFlags |= ServerBanExemptFlags.BlacklistedRange;
|
||||
|
||||
if ((ban.ExemptFlags & exemptFlags) != 0)
|
||||
return false;
|
||||
|
||||
if (!player.ExemptFlags.HasFlag(ServerBanExemptFlags.IP)
|
||||
&& player.Address != null
|
||||
&& ban.Address is not null
|
||||
&& player.Address.IsInSubnet(ban.Address.Value)
|
||||
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) || player.IsNewPlayer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (player.UserId is { } id && ban.UserId == id.UserId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return player.HWId is { Length: > 0 } hwIdVar
|
||||
&& ban.HWId != null
|
||||
&& hwIdVar.AsSpan().SequenceEqual(ban.HWId.Value.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple struct containing player info used to match bans against.
|
||||
/// </summary>
|
||||
public struct PlayerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The user ID of the player.
|
||||
/// </summary>
|
||||
public NetUserId? UserId;
|
||||
|
||||
/// <summary>
|
||||
/// The IP address of the player.
|
||||
/// </summary>
|
||||
public IPAddress? Address;
|
||||
|
||||
/// <summary>
|
||||
/// The hardware ID of the player.
|
||||
/// </summary>
|
||||
public ImmutableArray<byte>? HWId;
|
||||
|
||||
/// <summary>
|
||||
/// Exemption flags the player has been granted.
|
||||
/// </summary>
|
||||
public ServerBanExemptFlags ExemptFlags;
|
||||
|
||||
/// <summary>
|
||||
/// True if this player is new and is thus eligible for more bans.
|
||||
/// </summary>
|
||||
public bool IsNewPlayer;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,9 @@ namespace Content.Server.Database
|
|||
public NoteSeverity Severity { get; set; }
|
||||
public NetUserId? BanningAdmin { get; }
|
||||
public ServerUnbanDef? Unban { get; }
|
||||
public ServerBanExemptFlags ExemptFlags { get; }
|
||||
|
||||
public ServerBanDef(
|
||||
int? id,
|
||||
public ServerBanDef(int? id,
|
||||
NetUserId? userId,
|
||||
(IPAddress, int)? address,
|
||||
ImmutableArray<byte>? hwId,
|
||||
|
|
@ -36,7 +36,8 @@ namespace Content.Server.Database
|
|||
string reason,
|
||||
NoteSeverity severity,
|
||||
NetUserId? banningAdmin,
|
||||
ServerUnbanDef? unban)
|
||||
ServerUnbanDef? unban,
|
||||
ServerBanExemptFlags exemptFlags = default)
|
||||
{
|
||||
if (userId == null && address == null && hwId == null)
|
||||
{
|
||||
|
|
@ -62,6 +63,7 @@ namespace Content.Server.Database
|
|||
Severity = severity;
|
||||
BanningAdmin = banningAdmin;
|
||||
Unban = unban;
|
||||
ExemptFlags = exemptFlags;
|
||||
}
|
||||
|
||||
public string FormatBanMessage(IConfigurationManager cfg, ILocalizationManager loc)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ namespace Content.Server.Database
|
|||
{
|
||||
private readonly ISawmill _opsLog;
|
||||
|
||||
public event Action<DatabaseNotification>? OnNotificationReceived;
|
||||
|
||||
/// <param name="opsLog">Sawmill to trace log database operations to.</param>
|
||||
public ServerDbBase(ISawmill opsLog)
|
||||
{
|
||||
|
|
@ -433,13 +435,16 @@ namespace Content.Server.Database
|
|||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
protected static async Task<ServerBanExemptFlags?> GetBanExemptionCore(DbGuard db, NetUserId? userId)
|
||||
protected static async Task<ServerBanExemptFlags?> GetBanExemptionCore(
|
||||
DbGuard db,
|
||||
NetUserId? userId,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
if (userId == null)
|
||||
return null;
|
||||
|
||||
var exemption = await db.DbContext.BanExemption
|
||||
.SingleOrDefaultAsync(e => e.UserId == userId.Value.UserId);
|
||||
.SingleOrDefaultAsync(e => e.UserId == userId.Value.UserId, cancellationToken: cancel);
|
||||
|
||||
return exemption?.Flags;
|
||||
}
|
||||
|
|
@ -470,11 +475,11 @@ namespace Content.Server.Database
|
|||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId)
|
||||
public async Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
await using var db = await GetDb(cancel);
|
||||
|
||||
var flags = await GetBanExemptionCore(db, userId);
|
||||
var flags = await GetBanExemptionCore(db, userId, cancel);
|
||||
return flags ?? ServerBanExemptFlags.None;
|
||||
}
|
||||
|
||||
|
|
@ -1685,5 +1690,15 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
|||
|
||||
public abstract ValueTask DisposeAsync();
|
||||
}
|
||||
|
||||
protected void NotificationReceived(DatabaseNotification notification)
|
||||
{
|
||||
OnNotificationReceived?.Invoke(notification);
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ namespace Content.Server.Database
|
|||
/// Get current ban exemption flags for a user
|
||||
/// </summary>
|
||||
/// <returns><see cref="ServerBanExemptFlags.None"/> if the user is not exempt from any bans.</returns>
|
||||
Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId);
|
||||
Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId, CancellationToken cancel = default);
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -304,6 +304,43 @@ namespace Content.Server.Database
|
|||
Task<bool> RemoveJobWhitelist(Guid player, ProtoId<JobPrototype> job);
|
||||
|
||||
#endregion
|
||||
|
||||
#region DB Notifications
|
||||
|
||||
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
||||
|
||||
/// <summary>
|
||||
/// Inject a notification as if it was created by the database. This is intended for testing.
|
||||
/// </summary>
|
||||
/// <param name="notification">The notification to trigger</param>
|
||||
void InjectTestNotification(DatabaseNotification notification);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a notification sent between servers via the database layer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Database notifications are a simple system to broadcast messages to an entire server group
|
||||
/// backed by the same database. For example, this is used to notify all servers of new ban records.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// They are currently implemented by the PostgreSQL <c>NOTIFY</c> and <c>LISTEN</c> commands.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public struct DatabaseNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// The channel for the notification. This can be used to differentiate notifications for different purposes.
|
||||
/// </summary>
|
||||
public required string Channel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual contents of the notification. Optional.
|
||||
/// </summary>
|
||||
public string? Payload { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ServerDbManager : IServerDbManager
|
||||
|
|
@ -333,6 +370,8 @@ namespace Content.Server.Database
|
|||
// This is that connection, close it when we shut down.
|
||||
private SqliteConnection? _sqliteInMemoryConnection;
|
||||
|
||||
private readonly List<Action<DatabaseNotification>> _notificationHandlers = [];
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_msLogProvider = new LoggingProvider(_logMgr);
|
||||
|
|
@ -345,6 +384,7 @@ namespace Content.Server.Database
|
|||
|
||||
var engine = _cfg.GetCVar(CCVars.DatabaseEngine).ToLower();
|
||||
var opsLog = _logMgr.GetSawmill("db.op");
|
||||
var notifyLog = _logMgr.GetSawmill("db.notify");
|
||||
switch (engine)
|
||||
{
|
||||
case "sqlite":
|
||||
|
|
@ -352,17 +392,22 @@ namespace Content.Server.Database
|
|||
_db = new ServerDbSqlite(contextFunc, inMemory, _cfg, _synchronous, opsLog);
|
||||
break;
|
||||
case "postgres":
|
||||
var pgOptions = CreatePostgresOptions();
|
||||
_db = new ServerDbPostgres(pgOptions, _cfg, opsLog);
|
||||
var (pgOptions, conString) = CreatePostgresOptions();
|
||||
_db = new ServerDbPostgres(pgOptions, conString, _cfg, opsLog, notifyLog);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException($"Unknown database engine {engine}.");
|
||||
}
|
||||
|
||||
_db.OnNotificationReceived += HandleDatabaseNotification;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_db.OnNotificationReceived -= HandleDatabaseNotification;
|
||||
|
||||
_sqliteInMemoryConnection?.Dispose();
|
||||
_db.Shutdown();
|
||||
}
|
||||
|
||||
public Task<PlayerPreferences> InitPrefsAsync(
|
||||
|
|
@ -465,10 +510,10 @@ namespace Content.Server.Database
|
|||
return RunDbCommand(() => _db.UpdateBanExemption(userId, flags));
|
||||
}
|
||||
|
||||
public Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId)
|
||||
public Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId, CancellationToken cancel = default)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetBanExemption(userId));
|
||||
return RunDbCommand(() => _db.GetBanExemption(userId, cancel));
|
||||
}
|
||||
|
||||
#region Role Ban
|
||||
|
|
@ -806,7 +851,7 @@ namespace Content.Server.Database
|
|||
return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
|
||||
}
|
||||
|
||||
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
||||
public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetAllAdminRemarks(player));
|
||||
|
|
@ -907,6 +952,30 @@ namespace Content.Server.Database
|
|||
return RunDbCommand(() => _db.RemoveJobWhitelist(player, job));
|
||||
}
|
||||
|
||||
public void SubscribeToNotifications(Action<DatabaseNotification> handler)
|
||||
{
|
||||
lock (_notificationHandlers)
|
||||
{
|
||||
_notificationHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void InjectTestNotification(DatabaseNotification notification)
|
||||
{
|
||||
HandleDatabaseNotification(notification);
|
||||
}
|
||||
|
||||
private async void HandleDatabaseNotification(DatabaseNotification notification)
|
||||
{
|
||||
lock (_notificationHandlers)
|
||||
{
|
||||
foreach (var handler in _notificationHandlers)
|
||||
{
|
||||
handler(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper functions to run DB commands from the thread pool.
|
||||
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
||||
// For SQLite, this will also enable read parallelization (within limits).
|
||||
|
|
@ -962,7 +1031,7 @@ namespace Content.Server.Database
|
|||
return enumerable;
|
||||
}
|
||||
|
||||
private DbContextOptions<PostgresServerDbContext> CreatePostgresOptions()
|
||||
private (DbContextOptions<PostgresServerDbContext> options, string connectionString) CreatePostgresOptions()
|
||||
{
|
||||
var host = _cfg.GetCVar(CCVars.DatabasePgHost);
|
||||
var port = _cfg.GetCVar(CCVars.DatabasePgPort);
|
||||
|
|
@ -984,7 +1053,7 @@ namespace Content.Server.Database
|
|||
|
||||
builder.UseNpgsql(connectionString);
|
||||
SetupLogging(builder);
|
||||
return builder.Options;
|
||||
return (builder.Options, connectionString);
|
||||
}
|
||||
|
||||
private void SetupSqlite(out Func<DbContextOptions<SqliteServerDbContext>> contextFunc, out bool inMemory)
|
||||
|
|
|
|||
121
Content.Server/Database/ServerDbPostgres.Notifications.cs
Normal file
121
Content.Server/Database/ServerDbPostgres.Notifications.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System.Data;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Npgsql;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
/// Listens for ban_notification containing the player id and the banning server id using postgres listen/notify.
|
||||
/// Players a ban_notification got received for get banned, except when the current server id and the one in the notification payload match.
|
||||
|
||||
public sealed partial class ServerDbPostgres
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of notify channels to subscribe to.
|
||||
/// </summary>
|
||||
private static readonly string[] NotificationChannels =
|
||||
[
|
||||
BanManager.BanNotificationChannel,
|
||||
];
|
||||
|
||||
private static readonly TimeSpan ReconnectWaitIncrease = TimeSpan.FromSeconds(10);
|
||||
|
||||
private readonly CancellationTokenSource _notificationTokenSource = new();
|
||||
|
||||
private NpgsqlConnection? _notificationConnection;
|
||||
private TimeSpan _reconnectWaitTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the database connection and the notification handler
|
||||
/// </summary>
|
||||
private void InitNotificationListener(string connectionString)
|
||||
{
|
||||
_notificationConnection = new NpgsqlConnection(connectionString);
|
||||
_notificationConnection.Notification += OnNotification;
|
||||
|
||||
var cancellationToken = _notificationTokenSource.Token;
|
||||
Task.Run(() => NotificationListener(cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listens to the notification channel with basic error handling and reopens the connection if it got closed
|
||||
/// </summary>
|
||||
private async Task NotificationListener(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_notificationConnection == null)
|
||||
return;
|
||||
|
||||
_notifyLog.Verbose("Starting notification listener");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_notificationConnection.State == ConnectionState.Broken)
|
||||
{
|
||||
_notifyLog.Debug("Notification listener entered broken state, closing...");
|
||||
await _notificationConnection.CloseAsync();
|
||||
}
|
||||
|
||||
if (_notificationConnection.State == ConnectionState.Closed)
|
||||
{
|
||||
_notifyLog.Debug("Opening notification listener connection...");
|
||||
if (_reconnectWaitTime != TimeSpan.Zero)
|
||||
{
|
||||
_notifyLog.Verbose($"_reconnectWaitTime is {_reconnectWaitTime}");
|
||||
await Task.Delay(_reconnectWaitTime, cancellationToken);
|
||||
}
|
||||
|
||||
await _notificationConnection.OpenAsync(cancellationToken);
|
||||
_reconnectWaitTime = TimeSpan.Zero;
|
||||
_notifyLog.Verbose($"Notification connection opened...");
|
||||
}
|
||||
|
||||
foreach (var channel in NotificationChannels)
|
||||
{
|
||||
_notifyLog.Verbose($"Listening on channel {channel}");
|
||||
await using var cmd = new NpgsqlCommand($"LISTEN {channel}", _notificationConnection);
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_notifyLog.Verbose("Waiting on notifications...");
|
||||
await _notificationConnection.WaitAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Abort loop on cancel.
|
||||
_notifyLog.Verbose($"Shutting down notification listener due to cancellation");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_reconnectWaitTime += ReconnectWaitIncrease;
|
||||
_notifyLog.Error($"Error in notification listener: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNotification(object _, NpgsqlNotificationEventArgs notification)
|
||||
{
|
||||
_notifyLog.Verbose($"Received notification on channel {notification.Channel}");
|
||||
NotificationReceived(new DatabaseNotification
|
||||
{
|
||||
Channel = notification.Channel,
|
||||
Payload = notification.Payload,
|
||||
});
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_notificationTokenSource.Cancel();
|
||||
if (_notificationConnection == null)
|
||||
return;
|
||||
|
||||
_notificationConnection.Notification -= OnNotification;
|
||||
_notificationConnection.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,23 +16,26 @@ using Robust.Shared.Utility;
|
|||
|
||||
namespace Content.Server.Database
|
||||
{
|
||||
public sealed class ServerDbPostgres : ServerDbBase
|
||||
public sealed partial class ServerDbPostgres : ServerDbBase
|
||||
{
|
||||
private readonly DbContextOptions<PostgresServerDbContext> _options;
|
||||
private readonly ISawmill _notifyLog;
|
||||
private readonly SemaphoreSlim _prefsSemaphore;
|
||||
private readonly Task _dbReadyTask;
|
||||
|
||||
private int _msLag;
|
||||
|
||||
public ServerDbPostgres(
|
||||
DbContextOptions<PostgresServerDbContext> options,
|
||||
public ServerDbPostgres(DbContextOptions<PostgresServerDbContext> options,
|
||||
string connectionString,
|
||||
IConfigurationManager cfg,
|
||||
ISawmill opsLog)
|
||||
ISawmill opsLog,
|
||||
ISawmill notifyLog)
|
||||
: base(opsLog)
|
||||
{
|
||||
var concurrency = cfg.GetCVar(CCVars.DatabasePgConcurrency);
|
||||
|
||||
_options = options;
|
||||
_notifyLog = notifyLog;
|
||||
_prefsSemaphore = new SemaphoreSlim(concurrency, concurrency);
|
||||
|
||||
_dbReadyTask = Task.Run(async () =>
|
||||
|
|
@ -49,6 +52,8 @@ namespace Content.Server.Database
|
|||
});
|
||||
|
||||
cfg.OnValueChanged(CCVars.DatabasePgFakeLag, v => _msLag = v, true);
|
||||
|
||||
InitNotificationListener(connectionString);
|
||||
}
|
||||
|
||||
#region Ban
|
||||
|
|
@ -214,7 +219,8 @@ namespace Content.Server.Database
|
|||
ban.Reason,
|
||||
ban.Severity,
|
||||
aUid,
|
||||
unbanDef);
|
||||
unbanDef,
|
||||
ban.ExemptFlags);
|
||||
}
|
||||
|
||||
private static ServerUnbanDef? ConvertUnban(ServerUnban? unban)
|
||||
|
|
@ -251,7 +257,8 @@ namespace Content.Server.Database
|
|||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
PlaytimeAtNote = serverBan.PlaytimeAtNote,
|
||||
PlayerUserId = serverBan.UserId?.UserId
|
||||
PlayerUserId = serverBan.UserId?.UserId,
|
||||
ExemptFlags = serverBan.ExemptFlags
|
||||
});
|
||||
|
||||
await db.PgDbContext.SaveChangesAsync();
|
||||
|
|
|
|||
|
|
@ -84,25 +84,27 @@ namespace Content.Server.Database
|
|||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
|
||||
var newPlayer = userId == null || !await PlayerRecordExists(db, userId.Value);
|
||||
|
||||
// SQLite can't do the net masking stuff we need to match IP address ranges.
|
||||
// So just pull down the whole list into memory.
|
||||
var bans = await GetAllBans(db.SqliteDbContext, includeUnbanned: false, exempt);
|
||||
|
||||
return bans.FirstOrDefault(b => BanMatches(b, address, userId, hwId, exempt, newPlayer)) is { } foundBan
|
||||
? ConvertBan(foundBan)
|
||||
: null;
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned: false)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(IPAddress? address,
|
||||
public override async Task<List<ServerBanDef>> GetServerBansAsync(
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId, bool includeUnbanned)
|
||||
ImmutableArray<byte>? hwId,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
||||
return (await GetServerBanQueryAsync(db, address, userId, hwId, includeUnbanned)).ToList();
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ServerBanDef>> GetServerBanQueryAsync(
|
||||
DbGuardImpl db,
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
bool includeUnbanned)
|
||||
{
|
||||
var exempt = await GetBanExemptionCore(db, userId);
|
||||
|
||||
var newPlayer = !await db.SqliteDbContext.Player.AnyAsync(p => p.UserId == userId);
|
||||
|
|
@ -111,10 +113,18 @@ namespace Content.Server.Database
|
|||
// So just pull down the whole list into memory.
|
||||
var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);
|
||||
|
||||
var playerInfo = new BanMatcher.PlayerInfo
|
||||
{
|
||||
Address = address,
|
||||
UserId = userId,
|
||||
ExemptFlags = exempt ?? default,
|
||||
HWId = hwId,
|
||||
IsNewPlayer = newPlayer,
|
||||
};
|
||||
|
||||
return queryBans
|
||||
.Where(b => BanMatches(b, address, userId, hwId, exempt, newPlayer))
|
||||
.Select(ConvertBan)
|
||||
.ToList()!;
|
||||
.Where(b => BanMatcher.BanMatches(b!, playerInfo))!;
|
||||
}
|
||||
|
||||
private static async Task<List<ServerBan>> GetAllBans(
|
||||
|
|
@ -141,31 +151,6 @@ namespace Content.Server.Database
|
|||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
private static bool BanMatches(ServerBan ban,
|
||||
IPAddress? address,
|
||||
NetUserId? userId,
|
||||
ImmutableArray<byte>? hwId,
|
||||
ServerBanExemptFlags? exemptFlags,
|
||||
bool newPlayer)
|
||||
{
|
||||
if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)
|
||||
&& address != null
|
||||
&& ban.Address is not null
|
||||
&& address.IsInSubnet(ban.Address.ToTuple().Value)
|
||||
&& (!ban.ExemptFlags.HasFlag(ServerBanExemptFlags.BlacklistedRange) ||
|
||||
newPlayer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (userId is { } id && ban.PlayerUserId == id.UserId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId);
|
||||
}
|
||||
|
||||
public override async Task AddServerBanAsync(ServerBanDef serverBan)
|
||||
{
|
||||
await using var db = await GetDbImpl();
|
||||
|
|
@ -181,7 +166,8 @@ namespace Content.Server.Database
|
|||
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
|
||||
RoundId = serverBan.RoundId,
|
||||
PlaytimeAtNote = serverBan.PlaytimeAtNote,
|
||||
PlayerUserId = serverBan.UserId?.UserId
|
||||
PlayerUserId = serverBan.UserId?.UserId,
|
||||
ExemptFlags = serverBan.ExemptFlags
|
||||
});
|
||||
|
||||
await db.SqliteDbContext.SaveChangesAsync();
|
||||
|
|
@ -364,6 +350,7 @@ namespace Content.Server.Database
|
|||
}
|
||||
#endregion
|
||||
|
||||
[return: NotNullIfNotNull(nameof(ban))]
|
||||
private static ServerBanDef? ConvertBan(ServerBan? ban)
|
||||
{
|
||||
if (ban == null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.DeviceLinking.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Memory cell that sets the output to the input when enabled.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(MemoryCellSystem))]
|
||||
public sealed partial class MemoryCellComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the input port.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> InputPort = "MemoryInput";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the enable port.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> EnablePort = "MemoryEnable";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the output port.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SourcePortPrototype> OutputPort = "Output";
|
||||
|
||||
// State
|
||||
[DataField]
|
||||
public SignalState InputState = SignalState.Low;
|
||||
|
||||
[DataField]
|
||||
public SignalState EnableState = SignalState.Low;
|
||||
|
||||
[DataField]
|
||||
public bool LastOutput;
|
||||
}
|
||||
74
Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
Normal file
74
Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using Content.Server.DeviceLinking.Components;
|
||||
using Content.Server.DeviceLinking.Events;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Shared.DeviceLinking;
|
||||
|
||||
namespace Content.Server.DeviceLinking.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the control of output based on the input and enable ports.
|
||||
/// </summary>
|
||||
public sealed class MemoryCellSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DeviceLinkSystem _deviceLink = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MemoryCellComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<MemoryCellComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
var query = EntityQueryEnumerator<MemoryCellComponent, DeviceLinkSourceComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var source))
|
||||
{
|
||||
if (comp.InputState == SignalState.Momentary)
|
||||
comp.InputState = SignalState.Low;
|
||||
if (comp.EnableState == SignalState.Momentary)
|
||||
comp.EnableState = SignalState.Low;
|
||||
|
||||
UpdateOutput((uid, comp, source));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInit(Entity<MemoryCellComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
var (uid, comp) = ent;
|
||||
_deviceLink.EnsureSinkPorts(uid, comp.InputPort, comp.EnablePort);
|
||||
_deviceLink.EnsureSourcePorts(uid, comp.OutputPort);
|
||||
}
|
||||
|
||||
private void OnSignalReceived(Entity<MemoryCellComponent> ent, ref SignalReceivedEvent args)
|
||||
{
|
||||
var state = SignalState.Momentary;
|
||||
args.Data?.TryGetValue(DeviceNetworkConstants.LogicState, out state);
|
||||
|
||||
if (args.Port == ent.Comp.InputPort)
|
||||
ent.Comp.InputState = state;
|
||||
else if (args.Port == ent.Comp.EnablePort)
|
||||
ent.Comp.EnableState = state;
|
||||
|
||||
UpdateOutput(ent);
|
||||
}
|
||||
|
||||
private void UpdateOutput(Entity<MemoryCellComponent, DeviceLinkSourceComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp2))
|
||||
return;
|
||||
|
||||
if (ent.Comp1.EnableState == SignalState.Low)
|
||||
return;
|
||||
|
||||
var value = ent.Comp1.InputState != SignalState.Low;
|
||||
if (value == ent.Comp1.LastOutput)
|
||||
return;
|
||||
|
||||
ent.Comp1.LastOutput = value;
|
||||
_deviceLink.SendSignal(ent, ent.Comp1.OutputPort, value, ent.Comp2);
|
||||
}
|
||||
}
|
||||
|
|
@ -102,6 +102,13 @@ namespace Content.Server.DeviceNetwork.Components
|
|||
[DataField("sendBroadcastAttemptEvent")]
|
||||
public bool SendBroadcastAttemptEvent = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this device's address can be saved to device-lists
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("savableAddress")]
|
||||
public bool SavableAddress = true;
|
||||
|
||||
/// <summary>
|
||||
/// A list of device-lists that this device is on.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,13 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
|||
if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device, false))
|
||||
return;
|
||||
|
||||
//This checks if the device is marked as having a savable address,
|
||||
//to avoid adding pdas and whatnot to air alarms. This flag is true
|
||||
//by default, so this will only prevent devices from being added to
|
||||
//network configurator lists if manually set to false in the prototype
|
||||
if (!device.SavableAddress)
|
||||
return;
|
||||
|
||||
var address = device.Address;
|
||||
if (string.IsNullOrEmpty(address))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ namespace Content.Server.Discord;
|
|||
|
||||
public sealed class DiscordWebhook : IPostInjectInit
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
|
||||
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private const string BaseUrl = "https://discord.com/api/v10/webhooks";
|
||||
|
|
@ -68,7 +71,11 @@ public sealed class DiscordWebhook : IPostInjectInit
|
|||
public async Task<HttpResponseMessage> CreateMessage(WebhookIdentifier identifier, WebhookPayload payload)
|
||||
{
|
||||
var url = $"{GetUrl(identifier)}?wait=true";
|
||||
return await _http.PostAsJsonAsync(url, payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
|
||||
var response = await _http.PostAsJsonAsync(url, payload, JsonOptions);
|
||||
|
||||
LogResponse(response, "Create");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -80,7 +87,11 @@ public sealed class DiscordWebhook : IPostInjectInit
|
|||
public async Task<HttpResponseMessage> DeleteMessage(WebhookIdentifier identifier, ulong messageId)
|
||||
{
|
||||
var url = $"{GetUrl(identifier)}/messages/{messageId}";
|
||||
return await _http.DeleteAsync(url);
|
||||
var response = await _http.DeleteAsync(url);
|
||||
|
||||
LogResponse(response, "Delete");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -93,11 +104,40 @@ public sealed class DiscordWebhook : IPostInjectInit
|
|||
public async Task<HttpResponseMessage> EditMessage(WebhookIdentifier identifier, ulong messageId, WebhookPayload payload)
|
||||
{
|
||||
var url = $"{GetUrl(identifier)}/messages/{messageId}";
|
||||
return await _http.PatchAsJsonAsync(url, payload, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
|
||||
var response = await _http.PatchAsJsonAsync(url, payload, JsonOptions);
|
||||
|
||||
LogResponse(response, "Edit");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _log.GetSawmill("DISCORD");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs detailed information about the HTTP response received from a Discord webhook request.
|
||||
/// If the response status code is non-2XX it logs the status code, relevant rate limit headers.
|
||||
/// </summary>
|
||||
/// <param name="response">The HTTP response received from the Discord API.</param>
|
||||
/// <param name="methodName">The name (constant) of the method that initiated the webhook request (e.g., "Create", "Edit", "Delete").</param>
|
||||
private void LogResponse(HttpResponseMessage response, string methodName)
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_sawmill.Error($"Failed to {methodName} message. Status code: {response.StatusCode}.");
|
||||
|
||||
if (response.Headers.TryGetValues("Retry-After", out var retryAfter))
|
||||
_sawmill.Debug($"Failed webhook response Retry-After: {string.Join(", ", retryAfter)}");
|
||||
|
||||
if (response.Headers.TryGetValues("X-RateLimit-Global", out var globalRateLimit))
|
||||
_sawmill.Debug($"Failed webhook response X-RateLimit-Global: {string.Join(", ", globalRateLimit)}");
|
||||
|
||||
if (response.Headers.TryGetValues("X-RateLimit-Scope", out var rateLimitScope))
|
||||
_sawmill.Debug($"Failed webhook response X-RateLimit-Scope: {string.Join(", ", rateLimitScope)}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.CartridgeLoader.Cartridges;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
|
|
@ -13,20 +16,18 @@ using Content.Shared.CartridgeLoader.Cartridges;
|
|||
using Content.Shared.Database;
|
||||
using Content.Shared.MassMedia.Components;
|
||||
using Content.Shared.MassMedia.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Content.Server.MassMedia.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.MassMedia.Systems;
|
||||
|
||||
public sealed class NewsSystem : SharedNewsSystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoaderSystem = default!;
|
||||
|
|
@ -95,7 +96,7 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||
return;
|
||||
|
||||
var article = articles[msg.ArticleNum];
|
||||
if (CheckDeleteAccess(article, ent, msg.Actor))
|
||||
if (CanUse(msg.Actor, ent.Owner))
|
||||
{
|
||||
_adminLogger.Add(
|
||||
LogType.Chat, LogImpact.Medium,
|
||||
|
|
@ -131,15 +132,15 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||
if (!ent.Comp.PublishEnabled)
|
||||
return;
|
||||
|
||||
ent.Comp.PublishEnabled = false;
|
||||
ent.Comp.NextPublish = _timing.CurTime + TimeSpan.FromSeconds(ent.Comp.PublishCooldown);
|
||||
|
||||
if (!TryGetArticles(ent, out var articles))
|
||||
return;
|
||||
|
||||
if (!_accessReader.FindStationRecordKeys(msg.Actor, out _))
|
||||
if (!CanUse(msg.Actor, ent.Owner))
|
||||
return;
|
||||
|
||||
ent.Comp.PublishEnabled = false;
|
||||
ent.Comp.NextPublish = _timing.CurTime + TimeSpan.FromSeconds(ent.Comp.PublishCooldown);
|
||||
|
||||
string? authorName = null;
|
||||
if (_idCardSystem.TryFindIdCard(msg.Actor, out var idCard))
|
||||
authorName = idCard.Comp.FullName;
|
||||
|
|
@ -305,21 +306,17 @@ public sealed class NewsSystem : SharedNewsSystem
|
|||
}
|
||||
}
|
||||
|
||||
private bool CheckDeleteAccess(NewsArticle articleToDelete, EntityUid device, EntityUid user)
|
||||
private bool CanUse(EntityUid user, EntityUid console)
|
||||
{
|
||||
if (TryComp<AccessReaderComponent>(device, out var accessReader) &&
|
||||
_accessReader.IsAllowed(user, device, accessReader))
|
||||
return true;
|
||||
// This shouldn't technically be possible because of BUI but don't trust client.
|
||||
if (!_interaction.InRangeUnobstructed(console, user))
|
||||
return false;
|
||||
|
||||
if (articleToDelete.AuthorStationRecordKeyIds == null || articleToDelete.AuthorStationRecordKeyIds.Count == 0)
|
||||
return true;
|
||||
|
||||
return _accessReader.FindStationRecordKeys(user, out var recordKeys)
|
||||
&& StationRecordsToNetEntities(recordKeys).Intersect(articleToDelete.AuthorStationRecordKeyIds).Any();
|
||||
if (TryComp<AccessReaderComponent>(console, out var accessReaderComponent))
|
||||
{
|
||||
return _accessReaderSystem.IsAllowed(user, console, accessReaderComponent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ICollection<(NetEntity, uint)> StationRecordsToNetEntities(IEnumerable<StationRecordKey> records)
|
||||
{
|
||||
return records.Select(record => (GetNetEntity(record.OriginStation), record.Id)).ToList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,19 +20,24 @@ using Robust.Shared.Player;
|
|||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Repairable;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Materials;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly SharedBodySystem _body = default!; //bobby
|
||||
[Dependency] private readonly PuddleSystem _puddle = default!;
|
||||
[Dependency] private readonly StackSystem _stack = default!;
|
||||
|
|
@ -44,16 +49,14 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, InteractUsingEvent>(OnInteractUsing,
|
||||
before: new []{typeof(WiresSystem), typeof(SolutionTransferSystem)});
|
||||
before: [typeof(WiresSystem), typeof(SolutionTransferSystem)]);
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
|
||||
SubscribeLocalEvent<ActiveMaterialReclaimerComponent, PowerChangedEvent>(OnActivePowerChanged);
|
||||
}
|
||||
private void OnStartup(Entity<MaterialReclaimerComponent> entity, ref ComponentStartup args)
|
||||
{
|
||||
_solutionContainer.EnsureSolution(entity.Owner, entity.Comp.SolutionContainerId);
|
||||
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, BreakageEventArgs>(OnBreakage);
|
||||
SubscribeLocalEvent<MaterialReclaimerComponent, RepairedEvent>(OnRepaired);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<MaterialReclaimerComponent> entity, ref PowerChangedEvent args)
|
||||
|
|
@ -119,6 +122,30 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
TryFinishProcessItem(entity, null, entity.Comp);
|
||||
}
|
||||
|
||||
private void OnBreakage(Entity<MaterialReclaimerComponent> ent, ref BreakageEventArgs args)
|
||||
{
|
||||
//un-emags itself when it breaks
|
||||
RemComp<EmaggedComponent>(ent);
|
||||
SetBroken(ent, true);
|
||||
}
|
||||
|
||||
private void OnRepaired(Entity<MaterialReclaimerComponent> ent, ref RepairedEvent args)
|
||||
{
|
||||
SetBroken(ent, false);
|
||||
}
|
||||
|
||||
public void SetBroken(Entity<MaterialReclaimerComponent> ent, bool val)
|
||||
{
|
||||
if (ent.Comp.Broken == val)
|
||||
return;
|
||||
|
||||
_appearance.SetData(ent, RecyclerVisuals.Broken, val);
|
||||
SetReclaimerEnabled(ent, false);
|
||||
|
||||
ent.Comp.Broken = val;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool TryFinishProcessItem(EntityUid uid, MaterialReclaimerComponent? component = null, ActiveMaterialReclaimerComponent? active = null)
|
||||
{
|
||||
|
|
@ -136,7 +163,8 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
|
||||
// scales the output if the process was interrupted.
|
||||
var completion = 1f - Math.Clamp((float) Math.Round((active.EndTime - Timing.CurTime) / active.Duration),
|
||||
0f, 1f);
|
||||
0f,
|
||||
1f);
|
||||
Reclaim(uid, item, completion, component);
|
||||
|
||||
return true;
|
||||
|
|
@ -193,7 +221,8 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
|
||||
foreach (var (storedMaterial, storedAmount) in storage.Storage)
|
||||
{
|
||||
var stacks = _materialStorage.SpawnMultipleFromMaterial(storedAmount, storedMaterial,
|
||||
var stacks = _materialStorage.SpawnMultipleFromMaterial(storedAmount,
|
||||
storedMaterial,
|
||||
xform.Coordinates,
|
||||
out var materialOverflow);
|
||||
var amountConsumed = storedAmount - materialOverflow;
|
||||
|
|
@ -215,8 +244,6 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
{
|
||||
if (!Resolve(reclaimer, ref reclaimerComponent, ref xform))
|
||||
return;
|
||||
if (!_solutionContainer.TryGetSolution(reclaimer, reclaimerComponent.SolutionContainerId, out var outputSolution))
|
||||
return;
|
||||
|
||||
efficiency *= reclaimerComponent.Efficiency;
|
||||
|
||||
|
|
@ -232,20 +259,14 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
|
|||
}
|
||||
|
||||
// if the item we inserted has reagents, add it in.
|
||||
if (TryComp<SolutionContainerManagerComponent>(item, out var solutionContainer))
|
||||
if (_solutionContainer.TryGetDrainableSolution(item, out _, out var drainableSolution))
|
||||
{
|
||||
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((item, solutionContainer)))
|
||||
{
|
||||
var solution = soln.Comp.Solution;
|
||||
foreach (var quantity in solution.Contents)
|
||||
{
|
||||
totalChemicals.AddReagent(quantity.Reagent.Prototype, quantity.Quantity * efficiency, false);
|
||||
}
|
||||
}
|
||||
totalChemicals.AddSolution(drainableSolution, _prototype);
|
||||
}
|
||||
|
||||
_solutionContainer.TryTransferSolution(outputSolution.Value, totalChemicals, totalChemicals.Volume);
|
||||
if (totalChemicals.Volume > 0)
|
||||
if (!_solutionContainer.TryGetSolution(reclaimer, reclaimerComponent.SolutionContainerId, out var outputSolution) ||
|
||||
!_solutionContainer.TryTransferSolution(outputSolution.Value, totalChemicals, totalChemicals.Volume) ||
|
||||
totalChemicals.Volume > 0)
|
||||
{
|
||||
_puddle.TrySpillAt(reclaimer, totalChemicals, out _, sound, transformComponent: xform);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
|
|
@ -58,6 +58,12 @@ public sealed partial class HealthAnalyzerComponent : Component
|
|||
[DataField]
|
||||
public SoundSpecifier? ScanningEndSound;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show up the popup
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Silent;
|
||||
|
||||
[DataField("damageContainers", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageContainerPrototype>))]
|
||||
public List<string>? DamageContainers;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,17 +83,17 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
|||
{
|
||||
if (args.Target == null ||
|
||||
!args.CanReach ||
|
||||
!TryComp<DamageableComponent>(args.Target, out var damageableComponent) ||
|
||||
!TryComp<DamageableComponent>(args.Target, out var damageableComponent) || // Sunrise-Edit
|
||||
!HasComp<MobStateComponent>(args.Target) ||
|
||||
!_cell.HasDrawCharge(uid, user: args.User))
|
||||
return;
|
||||
|
||||
// Sunrise-Start
|
||||
if (uid.Comp.DamageContainers is not null &&
|
||||
damageableComponent.DamageContainerID is not null &&
|
||||
!uid.Comp.DamageContainers.Contains(damageableComponent.DamageContainerID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Sunrise-End
|
||||
|
||||
_audio.PlayPvs(uid.Comp.ScanningBeginSound, uid);
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ public sealed class HealthAnalyzerSystem : EntitySystem
|
|||
BreakOnMove = true,
|
||||
});
|
||||
|
||||
if (args.Target == args.User || doAfterCancelled)
|
||||
if (args.Target == args.User || doAfterCancelled || uid.Comp.Silent)
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("health-analyzer-popup-scan-target", ("user", Identity.Entity(args.User, EntityManager)));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Server.Nutrition.Components;
|
|||
using Content.Server.Popups;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Nutrition;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
|
@ -105,7 +106,7 @@ namespace Content.Server.Nutrition.EntitySystems
|
|||
RaiseLocalEvent(args.Target, ref ev);
|
||||
// Sunrise-End
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cream-pied-component-on-hit-by-message-others", ("owner", uid), ("thrower", args.Thrown)), uid, otherPlayers, false);
|
||||
_popup.PopupEntity(Loc.GetString("cream-pied-component-on-hit-by-message-others", ("owner", Identity.Name(uid, EntityManager)), ("thrower", args.Thrown)), uid, otherPlayers, false);
|
||||
}
|
||||
|
||||
private void OnRejuvenate(Entity<CreamPiedComponent> entity, ref RejuvenateEvent args)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ using Content.Shared.Interaction;
|
|||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
|
||||
namespace Content.Server.Nutrition.EntitySystems;
|
||||
|
||||
|
|
@ -19,7 +23,9 @@ public sealed class SliceableFoodSystem : EntitySystem
|
|||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly DoAfterSystem _doAfter = default!;
|
||||
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
|
@ -114,6 +120,13 @@ public sealed class SliceableFoodSystem : EntitySystem
|
|||
_transform.DropNextTo(sliceUid, (uid, transform));
|
||||
_transform.SetLocalRotation(sliceUid, 0);
|
||||
|
||||
if (!_container.IsEntityOrParentInContainer(sliceUid))
|
||||
{
|
||||
var randVect = _random.NextVector2(2.0f, 2.5f);
|
||||
if (TryComp<PhysicsComponent>(sliceUid, out var physics))
|
||||
_physics.SetLinearVelocity(sliceUid, randVect, body: physics);
|
||||
}
|
||||
|
||||
return sliceUid;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Content.Server.DeviceLinking.Systems;
|
|||
using Content.Server.Materials;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Conveyor;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Physics.Controllers;
|
||||
|
|
@ -26,7 +27,7 @@ public sealed class ConveyorController : SharedConveyorController
|
|||
UpdatesAfter.Add(typeof(MoverController));
|
||||
SubscribeLocalEvent<ConveyorComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<ConveyorComponent, ComponentShutdown>(OnConveyorShutdown);
|
||||
|
||||
SubscribeLocalEvent<ConveyorComponent, BreakageEventArgs>(OnBreakage);
|
||||
|
||||
SubscribeLocalEvent<ConveyorComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<ConveyorComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
|
@ -61,6 +62,11 @@ public sealed class ConveyorController : SharedConveyorController
|
|||
_fixtures.DestroyFixture(uid, ConveyorFixture, body: physics);
|
||||
}
|
||||
|
||||
private void OnBreakage(Entity<ConveyorComponent> ent, ref BreakageEventArgs args)
|
||||
{
|
||||
SetState(ent, ConveyorState.Off, ent);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, ConveyorComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
component.Powered = args.Powered;
|
||||
|
|
@ -96,13 +102,14 @@ public sealed class ConveyorController : SharedConveyorController
|
|||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!_materialReclaimer.SetReclaimerEnabled(uid, state != ConveyorState.Off))
|
||||
return;
|
||||
|
||||
component.State = state;
|
||||
|
||||
if (TryComp<PhysicsComponent>(uid, out var physics))
|
||||
_broadphase.RegenerateContacts(uid, physics);
|
||||
|
||||
_materialReclaimer.SetReclaimerEnabled(uid, component.State != ConveyorState.Off);
|
||||
|
||||
UpdateAppearance(uid, component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ namespace Content.Server.Repairable
|
|||
("target", uid),
|
||||
("tool", args.Used!));
|
||||
_popup.PopupEntity(str, uid, args.User);
|
||||
|
||||
var ev = new RepairedEvent((uid, component), args.User);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
// Sunrise-start
|
||||
|
|
@ -108,4 +111,13 @@ namespace Content.Server.Repairable
|
|||
return Math.Max(modifier, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an entity when its successfully repaired.
|
||||
/// </summary>
|
||||
/// <param name="Ent"></param>
|
||||
/// <param name="User"></param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct RepairedEvent(Entity<RepairableComponent> Ent, EntityUid User);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Content.Shared.Respawn;
|
|||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Respawn;
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
|
|||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -87,6 +89,10 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
|
|||
if (!TryComp<MapGridComponent>(entityGridUid, out var grid) || MetaData(entityGridUid.Value).EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
return;
|
||||
|
||||
//Invalid prototype
|
||||
if (!_proto.HasIndex(component.Prototype))
|
||||
return;
|
||||
|
||||
if (TryFindRandomTile(entityGridUid.Value, entityMapUid.Value, 10, out var coords))
|
||||
Respawn(uid, component.Prototype, coords);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ public sealed class RadarConsoleSystem : SharedRadarConsoleSystem
|
|||
state = _console.GetNavState(uid, docks);
|
||||
}
|
||||
|
||||
state.RotateWithEntity = !component.FollowEntity;
|
||||
|
||||
_uiSystem.SetUiState(uid, RadarConsoleUiKey.Key, new NavBoundUserInterfaceState(state));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public sealed class RadiationCollectorSystem : EntitySystem
|
|||
|
||||
if (gas.Byproduct != null)
|
||||
{
|
||||
gasTankComponent.Air.AdjustMoles((int) gas.Byproduct, delta * gas.MolarRatio);
|
||||
gasTankComponent.Air.AdjustMoles((int)gas.Byproduct, delta * gas.MolarRatio);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -204,13 +204,14 @@ public sealed class RadiationCollectorSystem : EntitySystem
|
|||
if (!Resolve(uid, ref appearance, false))
|
||||
return;
|
||||
|
||||
// gas canisters can fill tanks up to 10 atm, so we set the warning level thresholds 1/3 and 2/3 of that
|
||||
if (gasTank == null || gasTank.Air.Pressure < 10)
|
||||
_appearance.SetData(uid, RadiationCollectorVisuals.PressureState, 0, appearance);
|
||||
|
||||
else if (gasTank.Air.Pressure < Atmospherics.OneAtmosphere)
|
||||
else if (gasTank.Air.Pressure < 3.33f * Atmospherics.OneAtmosphere)
|
||||
_appearance.SetData(uid, RadiationCollectorVisuals.PressureState, 1, appearance);
|
||||
|
||||
else if (gasTank.Air.Pressure < 3f * Atmospherics.OneAtmosphere)
|
||||
else if (gasTank.Air.Pressure < 6.66f * Atmospherics.OneAtmosphere)
|
||||
_appearance.SetData(uid, RadiationCollectorVisuals.PressureState, 2, appearance);
|
||||
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||
public sealed partial class RandomSentienceRuleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int MinSentiences = 1;
|
||||
|
||||
[DataField]
|
||||
public int MaxSentiences = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
using Content.Server.StationEvents.Events;
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(RandomSentienceRule))]
|
||||
public sealed partial class SentienceTargetComponent : Component
|
||||
{
|
||||
[DataField("flavorKind", required: true)]
|
||||
[DataField(required: true)]
|
||||
public string FlavorKind = default!;
|
||||
|
||||
[DataField]
|
||||
public float Weight = 1.0f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,56 @@
|
|||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using System.Linq;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
protected override void Started(EntityUid uid, RandomSentienceRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
HashSet<EntityUid> stationsToNotify = new();
|
||||
if (!TryGetRandomStation(out var station))
|
||||
return;
|
||||
|
||||
var targetList = new List<Entity<SentienceTargetComponent>>();
|
||||
var query = EntityQueryEnumerator<SentienceTargetComponent>();
|
||||
while (query.MoveNext(out var targetUid, out var target))
|
||||
var query = EntityQueryEnumerator<SentienceTargetComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var targetUid, out var target, out var xform))
|
||||
{
|
||||
if (StationSystem.GetOwningStation(targetUid, xform) != station)
|
||||
continue;
|
||||
|
||||
targetList.Add((targetUid, target));
|
||||
}
|
||||
|
||||
RobustRandom.Shuffle(targetList);
|
||||
var toMakeSentient = _random.Next(component.MinSentiences, component.MaxSentiences);
|
||||
|
||||
var toMakeSentient = RobustRandom.Next(2, 5);
|
||||
var groups = new HashSet<string>();
|
||||
|
||||
foreach (var target in targetList)
|
||||
for (var i = 0; i < toMakeSentient && targetList.Count > 0; i++)
|
||||
{
|
||||
if (toMakeSentient-- == 0)
|
||||
break;
|
||||
// weighted random to pick a sentience target
|
||||
var totalWeight = targetList.Sum(x => x.Comp.Weight);
|
||||
// This initial target should never be picked.
|
||||
// It's just so that target doesn't need to be nullable and as a safety fallback for id floating point errors ever mess up the comparison in the foreach.
|
||||
var target = targetList[0];
|
||||
var chosenWeight = _random.NextFloat(totalWeight);
|
||||
var currentWeight = 0.0;
|
||||
foreach (var potentialTarget in targetList)
|
||||
{
|
||||
currentWeight += potentialTarget.Comp.Weight;
|
||||
if (currentWeight > chosenWeight)
|
||||
{
|
||||
target = potentialTarget;
|
||||
break;
|
||||
}
|
||||
}
|
||||
targetList.Remove(target);
|
||||
|
||||
RemComp<SentienceTargetComponent>(target);
|
||||
var ghostRole = EnsureComp<GhostRoleComponent>(target);
|
||||
|
|
@ -45,24 +68,15 @@ public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRule
|
|||
var kind2 = groupList.Count > 1 ? groupList[1] : "???";
|
||||
var kind3 = groupList.Count > 2 ? groupList[2] : "???";
|
||||
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
var station = StationSystem.GetOwningStation(target);
|
||||
if(station == null)
|
||||
continue;
|
||||
stationsToNotify.Add((EntityUid) station);
|
||||
}
|
||||
foreach (var station in stationsToNotify)
|
||||
{
|
||||
ChatSystem.DispatchStationAnnouncement(
|
||||
station,
|
||||
Loc.GetString("station-event-random-sentience-announcement",
|
||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||
("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")),
|
||||
("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}"))),
|
||||
playDefault: false,
|
||||
colorOverride: Color.Gold
|
||||
);
|
||||
}
|
||||
ChatSystem.DispatchStationAnnouncement(
|
||||
station.Value,
|
||||
Loc.GetString("station-event-random-sentience-announcement",
|
||||
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
|
||||
("data", _random.Pick(_prototype.Index<LocalizedDatasetPrototype>("RandomSentienceEventData"))),
|
||||
("strength", _random.Pick(_prototype.Index<LocalizedDatasetPrototype>("RandomSentienceEventStrength")))
|
||||
),
|
||||
playDefault: false,
|
||||
colorOverride: Color.Gold
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
using Content.Server.Temperature.Systems;
|
||||
using Content.Server.Temperature.Components;
|
||||
using Content.Shared.Temperature;
|
||||
|
||||
namespace Content.Server.Temperature.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Put this component on a projectile that you would like to change the temperature on whatever it hits.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(TemperatureSystem))]
|
||||
public sealed partial class ChangeTemperatureOnCollideComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount it changes the target's temperature by. In Joules.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Heat = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// If this heat change ignores heat resistance or not.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IgnoreHeatResistance = true;
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ using Content.Shared.Rejuvenate;
|
|||
using Content.Shared.Temperature;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Content.Shared.Projectiles;
|
||||
|
||||
namespace Content.Server.Temperature.Systems;
|
||||
|
||||
|
|
@ -21,6 +23,7 @@ public sealed class TemperatureSystem : EntitySystem
|
|||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly TemperatureSystem _temperature = default!;
|
||||
|
||||
/// <summary>
|
||||
/// All the components that will have their damage updated at the end of the tick.
|
||||
|
|
@ -47,6 +50,8 @@ public sealed class TemperatureSystem : EntitySystem
|
|||
|
||||
SubscribeLocalEvent<InternalTemperatureComponent, MapInitEvent>(OnInit);
|
||||
|
||||
SubscribeLocalEvent<ChangeTemperatureOnCollideComponent, ProjectileHitEvent>(ChangeTemperatureOnCollide);
|
||||
|
||||
// Allows overriding thresholds based on the parent's thresholds.
|
||||
SubscribeLocalEvent<TemperatureComponent, EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<ContainerTemperatureDamageThresholdsComponent, ComponentStartup>(
|
||||
|
|
@ -300,6 +305,11 @@ public sealed class TemperatureSystem : EntitySystem
|
|||
args.Args.TemperatureDelta *= ev.Coefficient;
|
||||
}
|
||||
|
||||
private void ChangeTemperatureOnCollide(Entity<ChangeTemperatureOnCollideComponent> ent, ref ProjectileHitEvent args)
|
||||
{
|
||||
_temperature.ChangeHeat(args.Target, ent.Comp.Heat, ent.Comp.IgnoreHeatResistance);// adjust the temperature
|
||||
}
|
||||
|
||||
private void OnParentChange(EntityUid uid, TemperatureComponent component,
|
||||
ref EntParentChangedMessage args)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -725,7 +725,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||
break;
|
||||
}
|
||||
|
||||
Tool.PlayToolSound(toolEntity, tool, user);
|
||||
Tool.PlayToolSound(toolEntity, tool, null);
|
||||
if (wire.Action == null || wire.Action.Cut(user, wire))
|
||||
{
|
||||
wire.IsCut = true;
|
||||
|
|
@ -746,7 +746,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
|||
break;
|
||||
}
|
||||
|
||||
Tool.PlayToolSound(toolEntity, tool, user);
|
||||
Tool.PlayToolSound(toolEntity, tool, null);
|
||||
if (wire.Action == null || wire.Action.Mend(user, wire))
|
||||
{
|
||||
wire.IsCut = false;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public abstract partial class SharedBuckleSystem
|
|||
}
|
||||
else
|
||||
{
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.BuckleDoafterTime, new BuckleDoAfterEvent(), args.User, args.Dragged, uid)
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.User, component.BuckleDoafterTime, new BuckleDoAfterEvent(), args.Dragged, args.Dragged, uid)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public sealed class ToggleableClothingSystem : EntitySystem
|
|||
|
||||
private void OnGetVerbs(EntityUid uid, ToggleableClothingComponent component, GetVerbsEvent<EquipmentVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || component.ClothingUid == null || component.Container == null)
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null || component.ClothingUid == null || component.Container == null)
|
||||
return;
|
||||
|
||||
var text = component.VerbText ?? (component.ActionEntity == null ? null : Name(component.ActionEntity.Value));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using Content.Shared.Hands.Components;
|
|||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
|
|
@ -144,7 +143,7 @@ public abstract partial class SharedHandsSystem
|
|||
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
|
||||
var origin = new MapCoordinates(itemPos, itemXform.MapID);
|
||||
var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value);
|
||||
TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target), itemRot);
|
||||
TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target, entity), itemRot);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +172,7 @@ public abstract partial class SharedHandsSystem
|
|||
/// <summary>
|
||||
/// Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path, Does a check to see if a user should bypass those checks as well.
|
||||
/// </summary>
|
||||
private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target)
|
||||
private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target, EntityUid held)
|
||||
{
|
||||
var dropVector = target.Position - origin.Position;
|
||||
var requestedDropDistance = dropVector.Length();
|
||||
|
|
@ -187,7 +186,7 @@ public abstract partial class SharedHandsSystem
|
|||
target = new MapCoordinates(origin.Position + dropVector, target.MapId);
|
||||
}
|
||||
|
||||
dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user);
|
||||
dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user || e == held);
|
||||
}
|
||||
|
||||
if (dropLength < requestedDropDistance)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Item;
|
||||
|
|
@ -178,6 +179,17 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
|||
if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity))
|
||||
return false;
|
||||
|
||||
if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container))
|
||||
{
|
||||
if (!ContainerSystem.CanRemove(entity, container))
|
||||
return false;
|
||||
|
||||
if (_inventory.TryGetSlotEntity(uid, container.ID, out var slotEnt) &&
|
||||
slotEnt == entity &&
|
||||
!_inventory.CanUnequip(uid, entity, container.ID, out _))
|
||||
return false;
|
||||
}
|
||||
|
||||
// check can insert (including raising attempt events).
|
||||
return ContainerSystem.CanInsert(entity, handContainer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Shared.ActionBlocker;
|
|||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
|
|
@ -17,6 +18,7 @@ public abstract partial class SharedHandsSystem
|
|||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedStorageSystem _storage = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||
[Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace Content.Shared.Humanoid
|
|||
[Serializable, NetSerializable]
|
||||
public enum HumanoidVisualLayers : byte
|
||||
{
|
||||
Special, // for the cat ears
|
||||
Tail,
|
||||
Hair,
|
||||
FacialHair,
|
||||
|
|
@ -18,6 +19,7 @@ namespace Content.Shared.Humanoid
|
|||
RArm,
|
||||
LArm,
|
||||
RHand,
|
||||
|
||||
LHand,
|
||||
RLeg,
|
||||
LLeg,
|
||||
|
|
@ -27,5 +29,6 @@ namespace Content.Shared.Humanoid
|
|||
StencilMask,
|
||||
Ensnare,
|
||||
Fire,
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Content.Shared.Humanoid.Markings
|
|||
[Serializable, NetSerializable]
|
||||
public enum MarkingCategories : byte
|
||||
{
|
||||
Special,
|
||||
Hair,
|
||||
FacialHair,
|
||||
Head,
|
||||
|
|
@ -24,6 +25,7 @@ namespace Content.Shared.Humanoid.Markings
|
|||
{
|
||||
return layer switch
|
||||
{
|
||||
HumanoidVisualLayers.Special => MarkingCategories.Special,
|
||||
HumanoidVisualLayers.Hair => MarkingCategories.Hair,
|
||||
HumanoidVisualLayers.FacialHair => MarkingCategories.FacialHair,
|
||||
HumanoidVisualLayers.Head => MarkingCategories.Head,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Interaction.Components;
|
||||
|
||||
|
|
@ -8,6 +8,9 @@ namespace Content.Shared.Interaction.Components;
|
|||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BlockMovementComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool BlockInteraction = true;
|
||||
|
||||
// Sunrise-Start
|
||||
[DataField("blockInteractionAttempt")] public bool BlockInteractionAttempt = true;
|
||||
[DataField("blockUseAttempt")] public bool BlockUseAttempt = true;
|
||||
|
|
|
|||
|
|
@ -28,12 +28,8 @@ public partial class SharedInteractionSystem
|
|||
|
||||
private void CancelInteractEvent(Entity<BlockMovementComponent> ent, ref InteractionAttemptEvent args)
|
||||
{
|
||||
// Sunrise-Start
|
||||
if (!ent.Comp.BlockInteractionAttempt)
|
||||
return;
|
||||
// Sunrise-End
|
||||
|
||||
args.Cancelled = true;
|
||||
if (ent.Comp.BlockInteraction)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
|
|
@ -94,10 +95,10 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
|||
}
|
||||
|
||||
/// <inheritdoc cref="TrySpawnVirtualItemInHand(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid,bool)"/>
|
||||
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false)
|
||||
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, Hand? empty = null)
|
||||
{
|
||||
virtualItem = null;
|
||||
if (!_handsSystem.TryGetEmptyHand(user, out var empty))
|
||||
if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty))
|
||||
{
|
||||
if (!dropOthers)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.Whitelist;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
|
@ -27,6 +28,12 @@ public sealed partial class MaterialReclaimerComponent : Component
|
|||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// A master control for whether or not the recycler is broken and can function.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Broken;
|
||||
|
||||
/// <summary>
|
||||
/// How efficiently the materials are reclaimed.
|
||||
/// In practice, a multiplier per material when calculating the output of the reclaimer.
|
||||
|
|
@ -59,8 +66,8 @@ public sealed partial class MaterialReclaimerComponent : Component
|
|||
/// <summary>
|
||||
/// The id of our output solution
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string SolutionContainerId = "output";
|
||||
[DataField]
|
||||
public string? SolutionContainerId;
|
||||
|
||||
/// <summary>
|
||||
/// a whitelist for what entities can be inserted into this reclaimer
|
||||
|
|
@ -114,11 +121,12 @@ public sealed partial class MaterialReclaimerComponent : Component
|
|||
[NetSerializable, Serializable]
|
||||
public enum RecyclerVisuals
|
||||
{
|
||||
Bloody
|
||||
Bloody,
|
||||
Broken
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public enum RecyclerVisualLayers : byte
|
||||
{
|
||||
Main,
|
||||
Bloody
|
||||
Main
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Linq;
|
|||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
|
|
@ -10,7 +9,6 @@ using Content.Shared.Examine;
|
|||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
|
|
@ -102,7 +100,8 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
|||
|
||||
if (user != null)
|
||||
{
|
||||
_adminLog.Add(LogType.Action, LogImpact.High,
|
||||
_adminLog.Add(LogType.Action,
|
||||
LogImpact.High,
|
||||
$"{ToPrettyString(user.Value):player} destroyed {ToPrettyString(item)} in the material reclaimer, {ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
|
|
@ -171,13 +170,19 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
|||
/// <summary>
|
||||
/// Sets the Enabled field on the reclaimer.
|
||||
/// </summary>
|
||||
public void SetReclaimerEnabled(EntityUid uid, bool enabled, MaterialReclaimerComponent? component = null)
|
||||
public bool SetReclaimerEnabled(EntityUid uid, bool enabled, MaterialReclaimerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (component.Broken && enabled)
|
||||
return false;
|
||||
|
||||
component.Enabled = enabled;
|
||||
AmbientSound.SetAmbience(uid, enabled && component.Powered);
|
||||
Dirty(uid, component);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -189,7 +194,7 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
|||
if (HasComp<ActiveMaterialReclaimerComponent>(uid))
|
||||
return false;
|
||||
|
||||
return component.Powered && component.Enabled;
|
||||
return component.Powered && component.Enabled && !component.Broken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -198,9 +203,10 @@ public abstract class SharedMaterialReclaimerSystem : EntitySystem
|
|||
/// </summary>
|
||||
public bool CanGib(EntityUid uid, EntityUid victim, MaterialReclaimerComponent component)
|
||||
{
|
||||
return false; // Ради казуалов.
|
||||
return false; // Ради казуалов
|
||||
// return component.Powered &&
|
||||
// component.Enabled &&
|
||||
// !component.Broken &&
|
||||
// HasComp<BodyComponent>(victim) &&
|
||||
// HasComp<EmaggedComponent>(uid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
|||
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
|
||||
}
|
||||
|
||||
private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
|
||||
|
|
@ -154,14 +153,6 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
|||
{
|
||||
public override DoAfterEvent Clone() => this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevent players with the Pacified status effect from throwing embeddable projectiles.
|
||||
/// </summary>
|
||||
private void OnAttemptPacifiedThrow(Entity<EmbeddableProjectileComponent> ent, ref AttemptPacifiedThrowEvent args)
|
||||
{
|
||||
args.Cancel("pacified-cannot-throw-embed");
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
|
|
|||
67
Content.Shared/Roles/JobRequirement/TraitsRequirement.cs
Normal file
67
Content.Shared/Roles/JobRequirement/TraitsRequirement.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Traits;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Requires a character to have, or not have, certain traits
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class TraitsRequirement : JobRequirement
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public HashSet<ProtoId<TraitPrototype>> Traits = new();
|
||||
|
||||
public override bool Check(IEntityManager entManager,
|
||||
IPrototypeManager protoManager,
|
||||
HumanoidCharacterProfile? profile,
|
||||
IReadOnlyDictionary<string, TimeSpan> playTimes,
|
||||
[NotNullWhen(false)] out FormattedMessage? reason)
|
||||
{
|
||||
reason = new FormattedMessage();
|
||||
|
||||
if (profile is null) //the profile could be null if the player is a ghost. In this case we don't need to block the role selection for ghostrole
|
||||
return true;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("[color=yellow]");
|
||||
foreach (var t in Traits)
|
||||
{
|
||||
sb.Append(Loc.GetString(protoManager.Index(t).Name) + " ");
|
||||
}
|
||||
|
||||
sb.Append("[/color]");
|
||||
|
||||
if (!Inverted)
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-whitelisted-traits")}\n{sb}");
|
||||
//at least one of
|
||||
foreach (var trait in Traits)
|
||||
{
|
||||
if (profile.TraitPreferences.Contains(trait))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-blacklisted-traits")}\n{sb}");
|
||||
|
||||
foreach (var trait in Traits)
|
||||
{
|
||||
if (profile.TraitPreferences.Contains(trait))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ public sealed class NavInterfaceState
|
|||
|
||||
public Dictionary<NetEntity, List<DockingPortState>> Docks;
|
||||
|
||||
public bool RotateWithEntity = true;
|
||||
|
||||
public NavInterfaceState(
|
||||
float maxRange,
|
||||
NetCoordinates? coordinates,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,9 @@ public sealed class UseDelaySystem : EntitySystem
|
|||
/// </summary>
|
||||
public UseDelayInfo GetLastEndingDelay(Entity<UseDelayComponent> ent)
|
||||
{
|
||||
var last = ent.Comp.Delays[DefaultId];
|
||||
if (!ent.Comp.Delays.TryGetValue(DefaultId, out var last))
|
||||
return new UseDelayInfo(TimeSpan.Zero);
|
||||
|
||||
foreach (var entry in ent.Comp.Delays)
|
||||
{
|
||||
if (entry.Value.EndTime > last.EndTime)
|
||||
|
|
|
|||
|
|
@ -67,3 +67,8 @@
|
|||
license: "CC0-1.0"
|
||||
copyright: "Created by unusualcrow for ss14"
|
||||
source: "https://github.com/space-wizards/space-station-14/blob/master/Resources/Audio/Items/Toys/skub2.ogg"
|
||||
|
||||
- files: ["rawr.ogg"]
|
||||
license: "CC-BY-SA-3.0"
|
||||
copyright: "Taken from ss200, made by Daeberdir"
|
||||
source: "https://github.com/ss220-space/Paradise/pull/3756"
|
||||
|
|
|
|||
BIN
Resources/Audio/Items/Toys/rawr.ogg
Normal file
BIN
Resources/Audio/Items/Toys/rawr.ogg
Normal file
Binary file not shown.
|
|
@ -477,5 +477,12 @@ Entries:
|
|||
id: 59
|
||||
time: '2024-08-17T16:05:17.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30946
|
||||
- author: MilonPL
|
||||
changes:
|
||||
- message: The admin PDA health analyzer no longer shows a popup to the player.
|
||||
type: Fix
|
||||
id: 60
|
||||
time: '2024-08-19T03:02:18.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31097
|
||||
Name: Admin
|
||||
Order: 1
|
||||
|
|
|
|||
|
|
@ -1,326 +1,4 @@
|
|||
Entries:
|
||||
- author: notafet
|
||||
changes:
|
||||
- message: Slightly increase spacing speed to fix under-pressure lockout.
|
||||
type: Tweak
|
||||
- message: Fix vent under-pressure lockout.
|
||||
type: Fix
|
||||
id: 6638
|
||||
time: '2024-05-29T05:21:48.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28370
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Salvage access has been replaced with Cargo for the artifact container.
|
||||
type: Tweak
|
||||
id: 6639
|
||||
time: '2024-05-29T16:46:23.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28386
|
||||
- author: notafet
|
||||
changes:
|
||||
- message: Fix passive gate flow rate display.
|
||||
type: Fix
|
||||
id: 6640
|
||||
time: '2024-05-30T00:34:41.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28372
|
||||
- author: Lamrr
|
||||
changes:
|
||||
- message: Candle boxes can now only hold candles.
|
||||
type: Tweak
|
||||
id: 6641
|
||||
time: '2024-05-30T04:15:31.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28314
|
||||
- author: blueDev2
|
||||
changes:
|
||||
- message: Epipens can be disposed
|
||||
type: Fix
|
||||
id: 6642
|
||||
time: '2024-05-30T04:18:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28317
|
||||
- author: metalgearsloth
|
||||
changes:
|
||||
- message: Predict opening even more interfaces.
|
||||
type: Tweak
|
||||
id: 6643
|
||||
time: '2024-05-30T07:32:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28405
|
||||
- author: ShadowCommander
|
||||
changes:
|
||||
- message: Fixed firelocks with warnings enabled opening immediately for characters
|
||||
without engineering access.
|
||||
type: Fix
|
||||
id: 6644
|
||||
time: '2024-05-30T17:25:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28415
|
||||
- author: moonheart08
|
||||
changes:
|
||||
- message: The hand teleporter can no longer create portal links between grids.
|
||||
type: Remove
|
||||
id: 6645
|
||||
time: '2024-05-30T20:45:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28423
|
||||
- author: Ghagliiarghii
|
||||
changes:
|
||||
- message: Cargo Sale Computer now correctly uses its own computer board
|
||||
type: Fix
|
||||
id: 6646
|
||||
time: '2024-05-31T00:28:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28425
|
||||
- author: metalgearsloth
|
||||
changes:
|
||||
- message: Fix verb categories shuffling around.
|
||||
type: Fix
|
||||
id: 6647
|
||||
time: '2024-05-31T01:55:01.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28368
|
||||
- author: ShadowCommander
|
||||
changes:
|
||||
- message: Tweaked the player tab column widths so that usernames and character
|
||||
names are more visible.
|
||||
type: Tweak
|
||||
id: 6648
|
||||
time: '2024-05-31T01:59:25.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28029
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Fixed latejoin antagonists not respecting antag preferences (for real
|
||||
this time)
|
||||
type: Fix
|
||||
id: 6649
|
||||
time: '2024-05-31T02:06:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28430
|
||||
- author: Ubaser
|
||||
changes:
|
||||
- message: Botanists now spawn with a plant clipper.
|
||||
type: Tweak
|
||||
id: 6650
|
||||
time: '2024-05-31T07:37:47.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28439
|
||||
- author: Ubaser
|
||||
changes:
|
||||
- message: Janitors now spawn with a light replacer.
|
||||
type: Tweak
|
||||
id: 6651
|
||||
time: '2024-05-31T08:44:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28441
|
||||
- author: NazrinNya
|
||||
changes:
|
||||
- message: Disposal pipes are now blocked from constructing underneath impassable
|
||||
objects.
|
||||
type: Fix
|
||||
id: 6652
|
||||
time: '2024-05-31T14:49:00.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28447
|
||||
- author: ShadowCommander
|
||||
changes:
|
||||
- message: Opening a firelock when temperature or pressure warnings are active,
|
||||
now always shows the warning popup.
|
||||
type: Tweak
|
||||
id: 6653
|
||||
time: '2024-05-31T14:56:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28420
|
||||
- author: ElectroJr
|
||||
changes:
|
||||
- message: Hugging a player no longer briefly opens the mugging UI
|
||||
type: Fix
|
||||
id: 6654
|
||||
time: '2024-05-31T17:25:03.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28432
|
||||
- author: aspiringLich
|
||||
changes:
|
||||
- message: Some chemicals have been made more colorful and child-friendly.
|
||||
type: Tweak
|
||||
id: 6655
|
||||
time: '2024-05-31T19:44:23.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28367
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: Many more ghost roles have a random appearance, including ERT and shuttle
|
||||
roles.
|
||||
type: Tweak
|
||||
id: 6656
|
||||
time: '2024-06-01T04:51:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27997
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: Medical Borgs now have medical huds
|
||||
type: Add
|
||||
id: 6657
|
||||
time: '2024-06-01T04:54:24.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28391
|
||||
- author: beck-thompson
|
||||
changes:
|
||||
- message: You can now remove evidence from containers using the verb menu.
|
||||
type: Fix
|
||||
id: 6658
|
||||
time: '2024-06-01T04:58:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28101
|
||||
- author: notafet
|
||||
changes:
|
||||
- message: Increase power output from Tesla coils.
|
||||
type: Tweak
|
||||
id: 6659
|
||||
time: '2024-06-01T05:51:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28424
|
||||
- author: Beck Thompson
|
||||
changes:
|
||||
- message: ID computer will now select passenger as the default id icon instead
|
||||
of atmospheric technician.
|
||||
type: Fix
|
||||
id: 6660
|
||||
time: '2024-06-01T17:29:46.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28462
|
||||
- author: AJCM-git
|
||||
changes:
|
||||
- message: Machine parts are now stackable
|
||||
type: Tweak
|
||||
id: 6661
|
||||
time: '2024-06-01T17:49:28.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28434
|
||||
- author: DrSmugleaf
|
||||
changes:
|
||||
- message: Disabled the seeing rainbows screen effect when reduced motion is enabled
|
||||
in the options menu.
|
||||
type: Tweak
|
||||
id: 6662
|
||||
time: '2024-06-02T01:41:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28496
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: Lasers now pass through Glass External Airlocks and Glass Shuttle Airlocks
|
||||
type: Fix
|
||||
id: 6663
|
||||
time: '2024-06-02T04:13:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28065
|
||||
- author: slarticodefast
|
||||
changes:
|
||||
- message: Fixed the flash effect getting darker with each appearence.
|
||||
type: Fix
|
||||
- message: Fixed short-sightedness making you immune to flashes.
|
||||
type: Fix
|
||||
id: 6664
|
||||
time: '2024-06-02T04:17:53.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27369
|
||||
- author: AJCM-git
|
||||
changes:
|
||||
- message: Midround antags work again
|
||||
type: Fix
|
||||
id: 6665
|
||||
time: '2024-06-02T16:52:40.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28523
|
||||
- author: Errant-4
|
||||
changes:
|
||||
- message: Map labels no longer suddenly disappear for the rest of the round.
|
||||
type: Fix
|
||||
id: 6666
|
||||
time: '2024-06-02T17:30:27.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28518
|
||||
- author: Whisper
|
||||
changes:
|
||||
- message: Renamed the player-obtainable "admin cloak" to "weh cloak".
|
||||
type: Tweak
|
||||
id: 6667
|
||||
time: '2024-06-03T02:18:49.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28540
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: Glass shards are no longer weldable with an unlit welder.
|
||||
type: Fix
|
||||
id: 6668
|
||||
time: '2024-06-03T03:28:53.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27959
|
||||
- author: AJCM-git
|
||||
changes:
|
||||
- message: Biomass reclaimers no longer act as a void to any unfortunate belongings
|
||||
a corpse may be wearing
|
||||
type: Tweak
|
||||
id: 6669
|
||||
time: '2024-06-03T03:30:00.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28544
|
||||
- author: Laneron
|
||||
changes:
|
||||
- message: Jetpack UI window now has correctly displayed header
|
||||
type: Fix
|
||||
id: 6670
|
||||
time: '2024-06-03T03:33:31.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28545
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: SyndiCats now spawn from a radio like other reinforcements.
|
||||
type: Tweak
|
||||
id: 6671
|
||||
time: '2024-06-03T11:54:32.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28492
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: Medibots will now occasionally talk
|
||||
type: Tweak
|
||||
id: 6672
|
||||
time: '2024-06-03T12:05:14.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28543
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: The player can now use the gasping emote
|
||||
type: Tweak
|
||||
id: 6673
|
||||
time: '2024-06-03T12:10:25.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28466
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: Bullets no longer hit crates unless directly clicked on while shooting.
|
||||
type: Tweak
|
||||
- message: Bullets now pass over mobs that are lying down unless directly clicked
|
||||
on while shooting.
|
||||
type: Tweak
|
||||
id: 6674
|
||||
time: '2024-06-03T13:04:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28072
|
||||
- author: AJCM-git
|
||||
changes:
|
||||
- message: Ninja stealth mode, stealth boxes and everything with stealth won't show
|
||||
up in equipment HUDs like the medical or security HUD anymore.
|
||||
type: Fix
|
||||
id: 6675
|
||||
time: '2024-06-03T16:12:21.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28270
|
||||
- author: TheShuEd
|
||||
changes:
|
||||
- message: Added italian and cowboy accents
|
||||
type: Add
|
||||
- message: Now you can't choose all the accents at once.
|
||||
type: Fix
|
||||
id: 6676
|
||||
time: '2024-06-03T18:47:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28046
|
||||
- author: RenQ
|
||||
changes:
|
||||
- message: The moths now have their own "death grasp"
|
||||
type: Add
|
||||
id: 6677
|
||||
time: '2024-06-03T18:48:01.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28409
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Borgs ID now will be shown in LogProbe instead of Unknown.
|
||||
type: Tweak
|
||||
id: 6678
|
||||
time: '2024-06-03T18:48:44.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/27788
|
||||
- author: FairlySadPanda
|
||||
changes:
|
||||
- message: Stacking multiple deflecting items at once has been removed; now only
|
||||
your best item is used.
|
||||
type: Tweak
|
||||
id: 6679
|
||||
time: '2024-06-04T13:26:20.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28539
|
||||
- author: moonheart08
|
||||
changes:
|
||||
- message: AME power output is no longer super-buffed. Larger stations will now
|
||||
require multiple engines to stay powered.
|
||||
type: Tweak
|
||||
id: 6680
|
||||
time: '2024-06-04T15:59:33.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28419
|
||||
- author: blueDev2
|
||||
changes:
|
||||
- message: Fixed microwave recipes that use stacked materials
|
||||
|
|
@ -3832,3 +3510,355 @@
|
|||
id: 7137
|
||||
time: '2024-08-18T16:22:36.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30839
|
||||
- author: Beck Thompson
|
||||
changes:
|
||||
- message: Cutting food now moves the sliced pieces a small amount!
|
||||
type: Tweak
|
||||
id: 7138
|
||||
time: '2024-08-18T21:18:20.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31166
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Pizza and pizza box now have 3x2 size in inventory.
|
||||
type: Tweak
|
||||
- message: Pizze box is 4x2 inside and always will have a pizza with a knife inside.
|
||||
type: Tweak
|
||||
- message: Pizza box have whitelist for utensils and pizza.
|
||||
type: Tweak
|
||||
id: 7139
|
||||
time: '2024-08-18T21:55:42.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31171
|
||||
- author: Deatherd
|
||||
changes:
|
||||
- message: Sharks Go RAWR!
|
||||
type: Tweak
|
||||
id: 7140
|
||||
time: '2024-08-18T22:18:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31142
|
||||
- author: slarticodefast
|
||||
changes:
|
||||
- message: Fixed the radiation collector warning light thresholds.
|
||||
type: Fix
|
||||
id: 7141
|
||||
time: '2024-08-18T22:25:02.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31175
|
||||
- author: Potato1234_x
|
||||
changes:
|
||||
- message: Added tea plants that can be dried and ground to make tea powder which
|
||||
can then be used to make tea.
|
||||
type: Add
|
||||
- message: Added blue pumpkins. Currently useless but recipe uses are coming soon.
|
||||
type: Add
|
||||
id: 7142
|
||||
time: '2024-08-18T22:28:18.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/25092
|
||||
- author: deltanedas
|
||||
changes:
|
||||
- message: Added Memory Cells for storing logic signals persistently.
|
||||
type: Add
|
||||
id: 7143
|
||||
time: '2024-08-18T22:34:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/24983
|
||||
- author: Psychpsyo
|
||||
changes:
|
||||
- message: The random sentience event is back and can no longer pick things that
|
||||
aren't even on the station.
|
||||
type: Add
|
||||
id: 7144
|
||||
time: '2024-08-18T23:41:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29123
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Ore can no longer be destroyed by explosions. Happy blast mining.
|
||||
type: Tweak
|
||||
id: 7145
|
||||
time: '2024-08-19T01:55:49.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31182
|
||||
- author: slarticodefast
|
||||
changes:
|
||||
- message: Mobs without hands can no longer toggle other players' suit pieces.
|
||||
type: Fix
|
||||
id: 7146
|
||||
time: '2024-08-19T02:41:27.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31152
|
||||
- author: Goldminermac
|
||||
changes:
|
||||
- message: Chocolate-chip and blueberry pancakes can now be in stacks of up to nine
|
||||
for consistency.
|
||||
type: Fix
|
||||
id: 7147
|
||||
time: '2024-08-19T02:42:58.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31123
|
||||
- author: tosatur
|
||||
changes:
|
||||
- message: Made hydroponics alert light more orange
|
||||
type: Tweak
|
||||
id: 7148
|
||||
time: '2024-08-19T02:48:47.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31088
|
||||
- author: redmushie
|
||||
changes:
|
||||
- message: News management console now checks for Service ID card access instead
|
||||
of the manifest
|
||||
type: Fix
|
||||
id: 7149
|
||||
time: '2024-08-19T02:55:44.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31160
|
||||
- author: Moomoobeef
|
||||
changes:
|
||||
- message: Added pitchers for the chef who wants to serve beverages too.
|
||||
type: Add
|
||||
id: 7150
|
||||
time: '2024-08-19T03:01:26.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31105
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Space carp and Sharkminnows now drop teeth when butchered.
|
||||
type: Add
|
||||
- message: Added new bounties for carp and shark teeth.
|
||||
type: Add
|
||||
id: 7151
|
||||
time: '2024-08-19T03:04:59.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31070
|
||||
- author: to4no_fix
|
||||
changes:
|
||||
- message: Now it takes 5 seconds to take off or put on a muzzle
|
||||
type: Tweak
|
||||
- message: Now it takes 5 seconds to take off or put on a blindfold
|
||||
type: Tweak
|
||||
- message: Added a recipe for producing a straitjacket, it opens when researching
|
||||
the Special Means technology, it can be produced at the security techfab
|
||||
type: Add
|
||||
id: 7152
|
||||
time: '2024-08-19T03:05:25.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31095
|
||||
- author: Magicalus
|
||||
changes:
|
||||
- message: Suit sensors, borgs, and PDAs can no longer be saved to device-lists.
|
||||
type: Tweak
|
||||
id: 7153
|
||||
time: '2024-08-19T03:13:04.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30997
|
||||
- author: UBlueberry
|
||||
changes:
|
||||
- message: The guidebook entries for all antagonists have been revised.
|
||||
type: Tweak
|
||||
id: 7154
|
||||
time: '2024-08-19T03:16:05.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31075
|
||||
- author: TheWaffleJesus
|
||||
changes:
|
||||
- message: ERT Chaplains now have blessings to use their bible.
|
||||
type: Fix
|
||||
id: 7155
|
||||
time: '2024-08-19T03:19:19.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30993
|
||||
- author: DieselMohawk
|
||||
changes:
|
||||
- message: Reshaped the Security Helmet
|
||||
type: Tweak
|
||||
id: 7156
|
||||
time: '2024-08-19T03:21:44.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30961
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Increased the sell prices of most materials
|
||||
type: Tweak
|
||||
- message: Increased the price of ordering material crates from cargo.
|
||||
type: Tweak
|
||||
- message: Decreased the amount of plasma in one plasma crate from 3 stacks to 1
|
||||
stack.
|
||||
type: Tweak
|
||||
id: 7157
|
||||
time: '2024-08-19T03:28:46.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30869
|
||||
- author: Flareguy
|
||||
changes:
|
||||
- message: Added 2 new vox customization options for both hair & facial hair.
|
||||
type: Add
|
||||
- message: Vox Long Braids no longer incorrectly uses the Vox Afro sprite.
|
||||
type: Fix
|
||||
id: 7158
|
||||
time: '2024-08-19T03:32:16.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30815
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: The recycler can now be broken down and repaired.
|
||||
type: Add
|
||||
- message: Breaking the recycler now removes the EMAG effect.
|
||||
type: Add
|
||||
- message: The material-reclaiming efficiency of the recycler has been increased.
|
||||
type: Tweak
|
||||
- message: Fixed a bug where inaccessible solutions could be extracted from entities
|
||||
via the recycler.
|
||||
type: Fix
|
||||
- message: Removed the material reclaimer.
|
||||
type: Remove
|
||||
id: 7159
|
||||
time: '2024-08-19T03:39:00.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30802
|
||||
- author: lzk228
|
||||
changes:
|
||||
- message: Cheese wheel now Normal sized and slices in 4 slices instead of 3.
|
||||
type: Tweak
|
||||
id: 7160
|
||||
time: '2024-08-19T07:54:35.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31168
|
||||
- author: Beck Thompson
|
||||
changes:
|
||||
- message: Slightly increased the price of the seed restock.
|
||||
type: Tweak
|
||||
id: 7161
|
||||
time: '2024-08-19T10:33:41.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31185
|
||||
- author: Boaz1111
|
||||
changes:
|
||||
- message: The HoS has been given a new experimental weapon:An energy shotgun with
|
||||
multiple fire modes. This experimental tech is highly wanted by the syndicate,
|
||||
and agents will try to steal it.
|
||||
type: Add
|
||||
- message: Removed the Head of Security's Emergency Orders.
|
||||
type: Remove
|
||||
id: 7162
|
||||
time: '2024-08-19T11:14:30.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30643
|
||||
- author: tosatur
|
||||
changes:
|
||||
- message: Added more words to chat sanitisation
|
||||
type: Tweak
|
||||
id: 7163
|
||||
time: '2024-08-19T14:03:07.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31085
|
||||
- author: tosatur
|
||||
changes:
|
||||
- message: Changed text for ghost visibility toggle
|
||||
type: Tweak
|
||||
id: 7164
|
||||
time: '2024-08-19T17:51:55.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30998
|
||||
- author: themias
|
||||
changes:
|
||||
- message: Lizards can now eat meat dumplings.
|
||||
type: Fix
|
||||
id: 7165
|
||||
time: '2024-08-19T18:17:28.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31212
|
||||
- author: slarticodefast
|
||||
changes:
|
||||
- message: Made the straitjacked non-printable again.
|
||||
type: Remove
|
||||
id: 7166
|
||||
time: '2024-08-19T18:47:24.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31197
|
||||
- author: themias
|
||||
changes:
|
||||
- message: Added the Combat Bakery Kit to the syndicate uplink. Includes a deadly
|
||||
edible baguette sword and throwing star croissants for 3 TC.
|
||||
type: Add
|
||||
- message: Baguettes can be worn on the belt and normal ones do one blunt damage.
|
||||
type: Tweak
|
||||
- message: Mimes have a baguette instead of an MRE in their survival kits.
|
||||
type: Tweak
|
||||
id: 7167
|
||||
time: '2024-08-19T18:57:30.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31179
|
||||
- author: metalgearsloth
|
||||
changes:
|
||||
- message: Fix wire sounds not playing.
|
||||
type: Fix
|
||||
id: 7168
|
||||
time: '2024-08-19T19:50:03.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31067
|
||||
- author: redmushie
|
||||
changes:
|
||||
- message: Communications console buttons now have descriptive tooltips
|
||||
type: Add
|
||||
id: 7169
|
||||
time: '2024-08-19T20:36:36.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31217
|
||||
- author: Mephisto72
|
||||
changes:
|
||||
- message: The Detective can now access Externals and Cryogenics like their Officer
|
||||
counterpart.
|
||||
type: Tweak
|
||||
id: 7170
|
||||
time: '2024-08-19T23:44:51.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30203
|
||||
- author: Vermidia
|
||||
changes:
|
||||
- message: Mothroaches can now wear anything hamlet can wear
|
||||
type: Add
|
||||
id: 7171
|
||||
time: '2024-08-20T04:20:12.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/28956
|
||||
- author: Cojoke-dot
|
||||
changes:
|
||||
- message: Devouring bodies now reduces bleed for Space Dragons.
|
||||
type: Tweak
|
||||
id: 7172
|
||||
time: '2024-08-20T10:57:06.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/29661
|
||||
- author: cranberriez
|
||||
changes:
|
||||
- message: Added Discord webhook error logging!
|
||||
type: Tweak
|
||||
id: 7173
|
||||
time: '2024-08-20T21:12:31.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30835
|
||||
- author: Repo
|
||||
changes:
|
||||
- message: Added copy to clipboard button for connection failure UI.
|
||||
type: Add
|
||||
id: 7174
|
||||
time: '2024-08-20T21:31:10.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30760
|
||||
- author: Lank
|
||||
changes:
|
||||
- message: Removed the shock collar and associated thief objective.
|
||||
type: Remove
|
||||
id: 7175
|
||||
time: '2024-08-21T17:56:57.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31229
|
||||
- author: EmoGarbage404
|
||||
changes:
|
||||
- message: Increased the view range on the handheld mass scanner and decreased the
|
||||
power draw.
|
||||
type: Tweak
|
||||
- message: Handheld radars no longer spin with the player while moving.
|
||||
type: Fix
|
||||
id: 7176
|
||||
time: '2024-08-21T18:33:01.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31284
|
||||
- author: Beck Thompson
|
||||
changes:
|
||||
- message: Nuclear authentication code folder that spawns the nuclear authentication
|
||||
codes when opened.
|
||||
type: Add
|
||||
id: 7177
|
||||
time: '2024-08-21T18:53:04.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31272
|
||||
- author: Brandon-Huu
|
||||
changes:
|
||||
- message: Nukie shuttle now spawns with the nuclear authentication code folder.
|
||||
This fixes the issue where the nuclear codes would only ever have the codes
|
||||
for the nuclear operatives nuke and not the stations.
|
||||
type: Fix
|
||||
id: 7178
|
||||
time: '2024-08-21T18:55:18.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31273
|
||||
- author: Winkarst-cpu
|
||||
changes:
|
||||
- message: Now getting creamed will not reveal a person's identity.
|
||||
type: Fix
|
||||
id: 7179
|
||||
time: '2024-08-21T21:50:34.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/31291
|
||||
- author: Sarahon
|
||||
changes:
|
||||
- message: Now can add head(top) cosmetics to humanoids and dwarfs.
|
||||
type: Add
|
||||
- message: Added "long ears" for human and dwarf head(top) cosmetic.
|
||||
type: Add
|
||||
id: 7180
|
||||
time: '2024-08-21T23:44:43.0000000+00:00'
|
||||
url: https://github.com/space-wizards/space-station-14/pull/30490
|
||||
|
|
|
|||
|
|
@ -3,3 +3,5 @@ marking-VoxFacialHairColonel = Vox Moustache (Colonel)
|
|||
marking-VoxFacialHairFu = Vox Moustache (Quill Fu)
|
||||
marking-VoxFacialHairNeck = Vox Beard (Neck Quills)
|
||||
marking-VoxFacialHairMane = Vox Beard (Mane)
|
||||
marking-VoxFacialHairManeSmall = Vox Beard (Small Mane)
|
||||
marking-VoxFacialHairTufts = Vox Sideburns (Tufts)
|
||||
|
|
|
|||
|
|
@ -20,3 +20,5 @@ marking-VoxHairShortQuills = Vox Short Quills
|
|||
marking-VoxHairSurf = Vox Surf
|
||||
marking-VoxHairTielQuills = Vox Tiel Quills
|
||||
marking-VoxHairYasu = Vox Yasuhiro
|
||||
marking-VoxHairWiseBraid = Vox Wise Braids
|
||||
marking-VoxHairSpotty = Vox Spotty Hair
|
||||
|
|
@ -66,6 +66,8 @@ bounty-item-wine = Wine bottle
|
|||
bounty-item-cotton-boll = Cotton boll
|
||||
bounty-item-microwave-machine-board = Microwave machine board
|
||||
bounty-item-flash = Flash
|
||||
bounty-item-tooth-space-carp = Space Carp Tooth
|
||||
bounty-item-tooth-sharkminnow = Sharkminnow Tooth
|
||||
|
||||
bounty-description-artifact = NanoTrasen is in some hot water for stealing artifacts from non-spacefaring planets. Return one and we'll compensate you for it.
|
||||
bounty-description-baseball-bat = Baseball fever is going on at CentComm! Be a dear and ship them some baseball bats, so that management can live out their childhood dream.
|
||||
|
|
@ -134,3 +136,5 @@ bounty-description-wine = The new librarian and the Quartermaster are falling he
|
|||
bounty-description-cotton-boll = A massive swarm of mothroaches ate all the paper and cloth on the station. Send us some cotton to help keep our winged crewmembers fed.
|
||||
bounty-description-microwave-machine-board = Mr. Giggles thought it'd be funny to stick forks in all the kitchen microwaves. Help us replace them before the chefs start making clown burgers.
|
||||
bounty-description-flashes = GREETINGS \[Station] WE REQUIRE 6 FLASHES DUE TO A NORMAL \[TrainingExercise] WITH SECURITY. EVERYTHING IS \[Normal].
|
||||
bounty-description-tooth-space-carp = Some lads from "down unda" need some teeth to make their traditional apparel. Send them a few from some space carp.
|
||||
bounty-description-tooth-sharkminnow = The chef is claiming that the teeth of sharkminnows are some kind of high-quality knife. I don't know what they're on about, but they want a set. Send it to them.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@
|
|||
comms-console-menu-title = Communications Console
|
||||
comms-console-menu-announcement-placeholder = Announcement text...
|
||||
comms-console-menu-announcement-button = Announce
|
||||
comms-console-menu-announcement-button-tooltip = Send your message as a station-wide radio announcement.
|
||||
comms-console-menu-broadcast-button = Broadcast
|
||||
comms-console-menu-broadcast-button-tooltip = Broadcast your message to wall-mounted screens around the station. Note: They fit only ten characters!
|
||||
comms-console-menu-alert-level-button-tooltip = Change the station alert level. Applies immediately on selecting.
|
||||
comms-console-menu-call-shuttle = Call emergency shuttle
|
||||
comms-console-menu-recall-shuttle = Recall emergency shuttle
|
||||
comms-console-menu-emergency-shuttle-button-tooltip = Calls or recalls the emergency shuttle. You can only recall when there's enough time left.
|
||||
comms-console-menu-time-remaining = Time remaining: {$time}
|
||||
|
||||
# Popup
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ figurines-mime-5 = ................
|
|||
figurines-musician-1 = Never gonna give you up!
|
||||
figurines-musician-2 = Never gonna let you down!
|
||||
|
||||
# figurines-boxer-
|
||||
figurines-boxer-1 = The first rule of Fight Club is...
|
||||
|
||||
figurines-captain-1 = Glory to NT!
|
||||
figurines-captain-2 = How did I get hired? Yes.
|
||||
|
|
@ -40,6 +40,7 @@ figurines-hos-3 = Yes, I shot the clown. No, I don't regret it.
|
|||
figurines-warden-1 = Execute him for breaking in!
|
||||
figurines-warden-2 = Perma the fucker for insulting me!
|
||||
figurines-warden-3 = We totally treat everyone fairly and do NOT mistreat our prisoners.
|
||||
figurines-warden-4 = Brig is my home. My home is brig. My brig is home. Stop, what?
|
||||
|
||||
figurines-detective-1 = The butler did it.
|
||||
figurines-detective-2 = I need some whiskey after this.
|
||||
|
|
@ -47,6 +48,7 @@ figurines-detective-2 = I need some whiskey after this.
|
|||
figurines-security-1 = I am the law!
|
||||
figurines-security-2 = You have violated article 1984.
|
||||
figurines-security-3 = Whenever I get bored I use the clown as target practice.
|
||||
figurines-security-4 = You have two rights: to remain silent and to cry about it.
|
||||
|
||||
figurines-lawyer-1 = Better Call Saul!
|
||||
figurines-lawyer-2 = Objection!
|
||||
|
|
@ -87,8 +89,8 @@ figurines-rd-2 = Tier 3 arsenal? No way.
|
|||
figurines-scientist-1 = Someone else must have made those bombs!
|
||||
figurines-scientist-2 = He asked to be borged!
|
||||
figurines-scientist-3 = Carp at sci!
|
||||
figurines-scientist-4 = Explosion at sci.
|
||||
figurines-scientist-5 = The anomaly has exploded.
|
||||
figurines-scientist-4 = Explosion at sci!
|
||||
figurines-scientist-5 = The anomaly has exploded!
|
||||
|
||||
figurines-cmo-1 = Suit sensors!
|
||||
figurines-cmo-2 = Why do we have meth?
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ flavor-complex-corn = like corn
|
|||
flavor-complex-banana = like bananas
|
||||
flavor-complex-apple = like apples
|
||||
flavor-complex-cotton = like cottons
|
||||
flavor-complex-bungo = like bungo
|
||||
flavor-complex-bungo = like tropical sweetness
|
||||
flavor-complex-raisins = like dried grapes
|
||||
flavor-complex-orange = like oranges
|
||||
flavor-complex-watermelon = like watermelon
|
||||
|
|
@ -167,6 +167,7 @@ flavor-complex-lost-friendship = like lost friendship
|
|||
flavor-complex-light = like a light gone out
|
||||
flavor-complex-profits = like profits
|
||||
flavor-complex-fishops = like the dreaded fishops
|
||||
flavor-complex-blue-pumpkin = like a mouthful of pool water
|
||||
flavor-complex-violets = like violets
|
||||
flavor-complex-pyrotton = like a burning mouth
|
||||
flavor-complex-mothballs = like mothballs
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
ghost-gui-return-to-body-button = Return to body
|
||||
ghost-gui-ghost-warp-button = Ghost Warp
|
||||
ghost-gui-ghost-roles-button = Ghost Roles ({$count})
|
||||
ghost-gui-toggle-ghost-visibility-popup = Toggled visibility of ghosts.
|
||||
ghost-gui-toggle-ghost-visibility-popup-on = Enabled visibility of ghosts.
|
||||
ghost-gui-toggle-ghost-visibility-popup-off = Disabled visibility of ghosts.
|
||||
ghost-gui-toggle-lighting-manager-popup = Toggled all lighting.
|
||||
ghost-gui-toggle-fov-popup = Toggled field-of-view.
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ guide-entry-zombies = Zombies
|
|||
guide-entry-revolutionaries = Revolutionaries
|
||||
guide-entry-minor-antagonists = Minor Antagonists
|
||||
guide-entry-space-ninja = Space Ninja
|
||||
guide-entry-thieves = Thieves
|
||||
|
||||
guide-entry-rules = Server Rules
|
||||
guide-entry-rules-core-only = Core Only Ruleset
|
||||
|
|
|
|||
|
|
@ -82,3 +82,6 @@ ban-panel-erase = Erase chat messages and player from round
|
|||
server-ban-string = {$admin} created a {$severity} severity server ban that expires {$expires} for [{$name}, {$ip}, {$hwid}], with reason: {$reason}
|
||||
server-ban-string-no-pii = {$admin} created a {$severity} severity server ban that expires {$expires} for {$name} with reason: {$reason}
|
||||
server-ban-string-never = never
|
||||
|
||||
# Kick on ban
|
||||
ban-kick-reason = You have been banned
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ role-timer-age-to-old = Your character's age must be at most [color=yellow]{$age
|
|||
role-timer-age-to-young = Your character's age must be at least [color=yellow]{$age}[/color] to play this role.
|
||||
role-timer-whitelisted-species = Your character must be one of the following species to play this role:
|
||||
role-timer-blacklisted-species = Your character must not be one of the following species to play this role:
|
||||
role-timer-whitelisted-traits = Your character must have one of the following traits:
|
||||
role-timer-blacklisted-traits = Your character must not have any of the following traits:
|
||||
|
||||
role-timer-locked = Locked (hover for details)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ connecting-title = Space Station 14
|
|||
connecting-exit = Exit
|
||||
connecting-retry = Retry
|
||||
connecting-reconnect = Reconnect
|
||||
connecting-copy = Copy Message
|
||||
connecting-redial = Relaunch
|
||||
connecting-redial-wait = Please wait: { TOSTRING($time, "G3") }
|
||||
connecting-in-progress = Connecting to server...
|
||||
|
|
|
|||
|
|
@ -81,3 +81,8 @@ signal-port-description-logic-input-b = Second input of a logic gate.
|
|||
|
||||
signal-port-name-logic-input = Input
|
||||
signal-port-description-logic-input = Input to the edge detector, cannot be a pulse signal.
|
||||
|
||||
signal-port-description-logic-memory-input = Signal to load into the memory cell, when enabled.
|
||||
|
||||
signal-port-name-logic-enable = Enable
|
||||
signal-port-description-logic-enable = Only loads the input signal into the memory cell when HIGH.
|
||||
|
|
|
|||
1
Resources/Locale/en-US/markings/ears.ftl
Normal file
1
Resources/Locale/en-US/markings/ears.ftl
Normal file
|
|
@ -0,0 +1 @@
|
|||
marking-HumanLongEars = Long Ears
|
||||
|
|
@ -15,6 +15,7 @@ loadout-group-survival-syndicate = Github is forcing me to write text that is li
|
|||
loadout-group-breath-tool = Species-dependent breath tools
|
||||
loadout-group-tank-harness = Species-specific survival equipment
|
||||
loadout-group-EVA-tank = Species-specific gas tank
|
||||
loadout-group-survival-mime = Mime Survival Box
|
||||
|
||||
# Command
|
||||
loadout-group-captain-head = Captain head
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ marking-slot = Slot {$number}
|
|||
|
||||
# Categories
|
||||
|
||||
markings-category-Special = Special
|
||||
markings-category-Hair = Hair
|
||||
markings-category-FacialHair = Facial Hair
|
||||
markings-category-Head = Head
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ reagent-desc-cream-of-coconut = Sweet, syrupy version of coconut cream with adde
|
|||
reagent-name-cafe-latte = cafe latte
|
||||
reagent-desc-cafe-latte = A nice, strong and tasty beverage while you are reading.
|
||||
|
||||
reagent-name-tea-powder = tea powder
|
||||
reagent-desc-tea-powder = Finely ground tea leaves, used for making tea.
|
||||
|
||||
reagent-name-green-tea = green tea
|
||||
reagent-desc-green-tea = Tasty green tea.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ reagent-desc-juice-banana = The raw essence of a banana. HONK.
|
|||
reagent-name-juice-berry = berry juice
|
||||
reagent-desc-juice-berry = A delicious blend of several different kinds of berries.
|
||||
|
||||
reagent-name-juice-blue-pumpkin = blue pumpkin juice
|
||||
reagent-desc-juice-blue-pumpkin = The juice of a blue pumpkin. Smells like pool water.
|
||||
|
||||
reagent-name-juice-bungo = bungo juice
|
||||
reagent-desc-juice-bungo = The juice of a bungo fruit. Exotic!
|
||||
|
||||
reagent-name-juice-berry-poison = poison berry juice
|
||||
reagent-desc-juice-berry-poison = A surprisingly tasty juice blended from various kinds of very deadly and toxic berries.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ seeds-potato-name = potato
|
|||
seeds-potato-display-name = potatoes
|
||||
seeds-sugarcane-name = sugarcane
|
||||
seeds-sugarcane-display-name = sugarcanes
|
||||
seeds-teaplant-name = tea plant
|
||||
seeds-teaplant-display-name = tea plants
|
||||
seeds-papercane-name = papercane
|
||||
seeds-papercane-display-name = papercanes
|
||||
seeds-towercap-name = tower cap
|
||||
|
|
@ -120,6 +122,8 @@ seeds-pea-name = pea
|
|||
seeds-pea-display-name = pea vines
|
||||
seeds-pumpkin-name = pumpkin
|
||||
seeds-pumpkin-display-name = pumpkins
|
||||
seeds-blue-pumpkin-name = blue pumpkin
|
||||
seeds-blue-pumpkin-display-name = blue pumpkins
|
||||
seeds-cotton-name = cotton
|
||||
seeds-cotton-display-name = cotton plant
|
||||
seeds-pyrotton-name = pyrotton
|
||||
|
|
|
|||
|
|
@ -156,3 +156,36 @@ chatsan-replacement-55 = not gonna lie
|
|||
|
||||
chatsan-word-56 = fml
|
||||
chatsan-replacement-56 = fuck my life
|
||||
|
||||
chatsan-word-57 = wtaf
|
||||
chatsan-replacement-57 = what the actual fuck
|
||||
|
||||
chatsan-word-58 = wsg
|
||||
chatsan-replacement-58 = what's good
|
||||
|
||||
chatsan-word-59 = mb
|
||||
chatsan-replacement-59 = my bad
|
||||
|
||||
chatsan-word-60 = jfc
|
||||
chatsan-replacement-60 = jesus fucking christ
|
||||
|
||||
chatsan-word-61 = omw
|
||||
chatsan-replacement-61 = on my way
|
||||
|
||||
chatsan-word-62 = otw
|
||||
chatsan-replacement-62 = on the way
|
||||
|
||||
chatsan-word-63 = yk
|
||||
chatsan-replacement-63 = you know
|
||||
|
||||
chatsan-word-64 = istfg
|
||||
chatsan-replacement-64 = i swear to fucking god
|
||||
|
||||
chatsan-word-65 = idgaf
|
||||
chatsan-replacement-65 = i don't give a fuck
|
||||
|
||||
chatsan-word-66 = smth
|
||||
chatsan-replacement-66 = something
|
||||
|
||||
chatsan-word-67 = allg
|
||||
chatsan-replacement-67 = all good
|
||||
|
|
|
|||
|
|
@ -36,3 +36,4 @@ station-event-random-sentience-flavor-corgi = corgi
|
|||
station-event-random-sentience-flavor-primate = primate
|
||||
station-event-random-sentience-flavor-kobold = kobold
|
||||
station-event-random-sentience-flavor-slime = slime
|
||||
station-event-random-sentience-flavor-inanimate = inanimate
|
||||
|
|
@ -439,3 +439,6 @@ uplink-barber-scissors-desc = A good tool to give your fellow agent a nice hairc
|
|||
|
||||
uplink-backpack-syndicate-name = Syndicate backpack
|
||||
uplink-backpack-syndicate-desc = Lightweight explosion-proof а backpack for holding various traitor goods
|
||||
|
||||
uplink-combat-bakery-name = Combat Bakery Kit
|
||||
uplink-combat-bakery-desc = A kit of clandestine baked weapons. Contains a baguette which a skilled mime could use as a sword and a pair of throwing croissants. Once the job is done, eat the evidence.
|
||||
|
|
@ -3733,7 +3733,7 @@ entities:
|
|||
- type: Transform
|
||||
pos: -2.5,-12.5
|
||||
parent: 1
|
||||
- proto: NukeCodePaper
|
||||
- proto: BoxFolderNuclearCodes
|
||||
entities:
|
||||
- uid: 510
|
||||
components:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -122308,13 +122308,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -9.303419,-25.688816
|
||||
parent: 8364
|
||||
- proto: MaterialReclaimer
|
||||
entities:
|
||||
- uid: 16712
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -27.5,-15.5
|
||||
parent: 8364
|
||||
- proto: MaterialWoodPlank
|
||||
entities:
|
||||
- uid: 2613
|
||||
|
|
|
|||
11897
Resources/Maps/cog.yml
11897
Resources/Maps/cog.yml
File diff suppressed because it is too large
Load diff
|
|
@ -102197,13 +102197,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 40.59389,-23.47827
|
||||
parent: 2
|
||||
- proto: MaterialReclaimer
|
||||
entities:
|
||||
- uid: 5732
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.5,19.5
|
||||
parent: 2
|
||||
- proto: MaterialWoodPlank
|
||||
entities:
|
||||
- uid: 15031
|
||||
|
|
|
|||
|
|
@ -30424,6 +30424,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: -16.498352,-16.302273
|
||||
parent: 13329
|
||||
- uid: 15721
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 35.299934,68.63487
|
||||
parent: 13329
|
||||
- uid: 18968
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -30484,13 +30489,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 4.460208,71.69925
|
||||
parent: 13329
|
||||
- proto: BoxSurvivalSyndicate
|
||||
entities:
|
||||
- uid: 15335
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 55.45186,-22.33047
|
||||
parent: 13329
|
||||
- proto: BoxSyringe
|
||||
entities:
|
||||
- uid: 18481
|
||||
|
|
@ -93569,13 +93567,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 17.46724,68.57425
|
||||
parent: 13329
|
||||
- proto: ClothingBeltMilitaryWebbing
|
||||
entities:
|
||||
- uid: 34561
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 67.49193,-41.442646
|
||||
parent: 13329
|
||||
- proto: ClothingBeltSecurityFilled
|
||||
entities:
|
||||
- uid: 10775
|
||||
|
|
@ -94300,11 +94291,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 53.29546,-42.48607
|
||||
parent: 13329
|
||||
- uid: 34626
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.680786,68.70615
|
||||
parent: 13329
|
||||
- proto: ClothingHeadHelmetRiot
|
||||
entities:
|
||||
- uid: 10654
|
||||
|
|
@ -94574,13 +94560,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -23.522526,76.500786
|
||||
parent: 13329
|
||||
- proto: ClothingNeckCloakHerald
|
||||
entities:
|
||||
- uid: 15720
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 62.5,-57.5
|
||||
parent: 13329
|
||||
- proto: ClothingNeckCloakMiner
|
||||
entities:
|
||||
- uid: 33335
|
||||
|
|
@ -94768,13 +94747,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -9.515972,-16.273252
|
||||
parent: 13329
|
||||
- proto: ClothingOuterCoatDetectiveLoadout
|
||||
entities:
|
||||
- uid: 31694
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -28.586462,68.64356
|
||||
parent: 13329
|
||||
- proto: ClothingOuterCoatLab
|
||||
entities:
|
||||
- uid: 19754
|
||||
|
|
@ -94782,6 +94754,13 @@ entities:
|
|||
- type: Transform
|
||||
pos: 32.341858,-50.533268
|
||||
parent: 13329
|
||||
- proto: ClothingOuterCoatTrench
|
||||
entities:
|
||||
- uid: 15720
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -29.56662,69.60553
|
||||
parent: 13329
|
||||
- proto: ClothingOuterGhostSheet
|
||||
entities:
|
||||
- uid: 34242
|
||||
|
|
@ -94802,11 +94781,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 53.592335,-42.45482
|
||||
parent: 13329
|
||||
- uid: 34205
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.407623,68.61223
|
||||
parent: 13329
|
||||
- proto: ClothingOuterHardsuitSecurity
|
||||
entities:
|
||||
- uid: 9436
|
||||
|
|
@ -94949,13 +94923,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 70.55321,-5.514928
|
||||
parent: 13329
|
||||
- proto: ClothingShoesBootsMagSyndie
|
||||
entities:
|
||||
- uid: 34793
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 67.44477,-42.60581
|
||||
parent: 13329
|
||||
- proto: ClothingShoesBootsSalvage
|
||||
entities:
|
||||
- uid: 31680
|
||||
|
|
@ -163657,6 +163624,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: 19.5,-22.5
|
||||
parent: 13329
|
||||
- uid: 20554
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.5,68.5
|
||||
parent: 13329
|
||||
- uid: 29283
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -163786,6 +163758,11 @@ entities:
|
|||
parent: 13329
|
||||
- proto: MaintenanceToolSpawner
|
||||
entities:
|
||||
- uid: 15335
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 62.5,-57.5
|
||||
parent: 13329
|
||||
- uid: 31817
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -163848,6 +163825,16 @@ entities:
|
|||
parent: 13329
|
||||
- proto: MaintenanceWeaponSpawner
|
||||
entities:
|
||||
- uid: 20553
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 67.5,-41.5
|
||||
parent: 13329
|
||||
- uid: 20555
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 79.5,10.5
|
||||
parent: 13329
|
||||
- uid: 21278
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -163906,18 +163893,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 46.429523,54.487045
|
||||
parent: 13329
|
||||
- proto: MaterialReclaimer
|
||||
entities:
|
||||
- uid: 6766
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 125.5,-1.5
|
||||
parent: 13329
|
||||
- uid: 13314
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 15.5,-27.5
|
||||
parent: 13329
|
||||
- proto: MaterialWoodPlank
|
||||
entities:
|
||||
- uid: 32167
|
||||
|
|
@ -164275,13 +164250,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 82.5,54.5
|
||||
parent: 13329
|
||||
- proto: MirrorShield
|
||||
entities:
|
||||
- uid: 15721
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 62.5,-57.5
|
||||
parent: 13329
|
||||
- proto: MonkeyCubeWrapped
|
||||
entities:
|
||||
- uid: 1300
|
||||
|
|
@ -173050,6 +173018,11 @@ entities:
|
|||
rot: 3.141592653589793 rad
|
||||
pos: -36.5,-44.5
|
||||
parent: 13329
|
||||
- uid: 13314
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.5,68.5
|
||||
parent: 13329
|
||||
- uid: 13699
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -173510,11 +173483,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 34.5,66.5
|
||||
parent: 13329
|
||||
- uid: 34203
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 36.5,68.5
|
||||
parent: 13329
|
||||
- uid: 34208
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -180580,8 +180548,14 @@ entities:
|
|||
- type: MetaData
|
||||
name: Mailroom Garage Door Opener
|
||||
- type: Transform
|
||||
pos: 21.53215,-13.842299
|
||||
pos: 21.52355,-14.18487
|
||||
parent: 13329
|
||||
- type: DeviceLinkSource
|
||||
linkedPorts:
|
||||
9596:
|
||||
- Pressed: Toggle
|
||||
9595:
|
||||
- Pressed: Toggle
|
||||
- uid: 31585
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -180656,13 +180630,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 21.482563,-11.35629
|
||||
parent: 13329
|
||||
- proto: SalvageLootSpawner
|
||||
entities:
|
||||
- uid: 29379
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 79.5,10.5
|
||||
parent: 13329
|
||||
- proto: SalvageMagnet
|
||||
entities:
|
||||
- uid: 11642
|
||||
|
|
@ -195952,6 +195919,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: 8.5,5.5
|
||||
parent: 13329
|
||||
- uid: 6766
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 67.5,-42.5
|
||||
parent: 13329
|
||||
- uid: 29363
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
|
|||
|
|
@ -10999,7 +10999,7 @@ entities:
|
|||
pos: -20.5,-5.5
|
||||
parent: 30
|
||||
- type: Door
|
||||
secondsUntilStateChange: -21585.09
|
||||
secondsUntilStateChange: -22976.215
|
||||
state: Opening
|
||||
- type: DeviceLinkSource
|
||||
lastSignals:
|
||||
|
|
@ -13022,6 +13022,11 @@ entities:
|
|||
rot: -1.5707963267948966 rad
|
||||
pos: -65.5,13.5
|
||||
parent: 30
|
||||
- uid: 4393
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -52.5,2.5
|
||||
parent: 30
|
||||
- uid: 11451
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -13039,6 +13044,23 @@ entities:
|
|||
rot: -1.5707963267948966 rad
|
||||
pos: -65.5,21.5
|
||||
parent: 30
|
||||
- uid: 20731
|
||||
components:
|
||||
- type: Transform
|
||||
rot: 3.141592653589793 rad
|
||||
pos: -59.5,-7.5
|
||||
parent: 30
|
||||
- uid: 20732
|
||||
components:
|
||||
- type: Transform
|
||||
rot: 3.141592653589793 rad
|
||||
pos: -52.5,-7.5
|
||||
parent: 30
|
||||
- uid: 20733
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -59.5,2.5
|
||||
parent: 30
|
||||
- proto: AtmosDeviceFanTiny
|
||||
entities:
|
||||
- uid: 9123
|
||||
|
|
@ -30211,6 +30233,51 @@ entities:
|
|||
- type: Transform
|
||||
pos: -2.5,-33.5
|
||||
parent: 30
|
||||
- uid: 20722
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -62.5,-60.5
|
||||
parent: 30
|
||||
- uid: 20723
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -62.5,-61.5
|
||||
parent: 30
|
||||
- uid: 20724
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -62.5,-62.5
|
||||
parent: 30
|
||||
- uid: 20725
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -62.5,-63.5
|
||||
parent: 30
|
||||
- uid: 20726
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -62.5,-64.5
|
||||
parent: 30
|
||||
- uid: 20727
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -61.5,-64.5
|
||||
parent: 30
|
||||
- uid: 20728
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -63.5,-64.5
|
||||
parent: 30
|
||||
- uid: 20729
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -64.5,-64.5
|
||||
parent: 30
|
||||
- uid: 20730
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -60.5,-64.5
|
||||
parent: 30
|
||||
- uid: 21007
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -50311,13 +50378,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 12.458031,22.694685
|
||||
parent: 30
|
||||
- proto: ClothingBeltMilitaryWebbing
|
||||
entities:
|
||||
- uid: 15115
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 33.456085,32.56812
|
||||
parent: 30
|
||||
- proto: ClothingBeltPlantFilled
|
||||
entities:
|
||||
- uid: 3498
|
||||
|
|
@ -50459,11 +50519,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -48.531364,30.586998
|
||||
parent: 30
|
||||
- uid: 15265
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 47.465996,44.649086
|
||||
parent: 30
|
||||
- proto: ClothingEyesGlassesThermal
|
||||
entities:
|
||||
- uid: 9397
|
||||
|
|
@ -50942,20 +50997,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 7.503625,43.354942
|
||||
parent: 30
|
||||
- proto: ClothingNeckCloakGoliathCloak
|
||||
entities:
|
||||
- uid: 10443
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 41.46685,-71.67568
|
||||
parent: 30
|
||||
- proto: ClothingNeckCloakMiner
|
||||
entities:
|
||||
- uid: 12304
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 38.513725,-64.51438
|
||||
parent: 30
|
||||
- proto: ClothingNeckCloakTrans
|
||||
entities:
|
||||
- uid: 3344
|
||||
|
|
@ -51098,13 +51139,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -39.286102,56.736275
|
||||
parent: 30
|
||||
- proto: ClothingOuterArmorCult
|
||||
entities:
|
||||
- uid: 18249
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -72.51071,-48.502575
|
||||
parent: 30
|
||||
- proto: ClothingOuterArmorReflective
|
||||
entities:
|
||||
- uid: 16932
|
||||
|
|
@ -51136,13 +51170,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 40.490578,30.452217
|
||||
parent: 30
|
||||
- proto: ClothingOuterCoatDetectiveLoadout
|
||||
entities:
|
||||
- uid: 11366
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 4.503509,-14.329939
|
||||
parent: 30
|
||||
- proto: ClothingOuterCoatGentle
|
||||
entities:
|
||||
- uid: 16947
|
||||
|
|
@ -51185,6 +51212,13 @@ entities:
|
|||
- type: Transform
|
||||
pos: 19.488726,8.340895
|
||||
parent: 30
|
||||
- proto: ClothingOuterCoatTrench
|
||||
entities:
|
||||
- uid: 15265
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 4.48031,-14.490032
|
||||
parent: 30
|
||||
- proto: ClothingOuterHardsuitEVAPrisoner
|
||||
entities:
|
||||
- uid: 2381
|
||||
|
|
@ -51259,20 +51293,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -9.241632,-47.33087
|
||||
parent: 30
|
||||
- proto: ClothingOuterVestWeb
|
||||
entities:
|
||||
- uid: 16931
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -40.528282,22.694292
|
||||
parent: 30
|
||||
- proto: ClothingOuterVestWebMerc
|
||||
entities:
|
||||
- uid: 16034
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 16.536156,36.4687
|
||||
parent: 30
|
||||
- proto: ClothingOuterWinterSec
|
||||
entities:
|
||||
- uid: 11426
|
||||
|
|
@ -51455,6 +51475,13 @@ entities:
|
|||
- type: Transform
|
||||
pos: -56.564133,39.58168
|
||||
parent: 30
|
||||
- proto: ClothingUniformJumpskirtOldDress
|
||||
entities:
|
||||
- uid: 15115
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -72.460976,-48.531662
|
||||
parent: 30
|
||||
- proto: ClothingUniformJumpskirtPerformer
|
||||
entities:
|
||||
- uid: 15070
|
||||
|
|
@ -53158,6 +53185,13 @@ entities:
|
|||
- type: Transform
|
||||
pos: -33.36232,-13.359482
|
||||
parent: 30
|
||||
- proto: CultAltarSpawner
|
||||
entities:
|
||||
- uid: 11366
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 38.5,-67.5
|
||||
parent: 30
|
||||
- proto: d20Dice
|
||||
entities:
|
||||
- uid: 19470
|
||||
|
|
@ -98410,6 +98444,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: -3.5,-16.5
|
||||
parent: 30
|
||||
- uid: 10357
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 33.5,32.5
|
||||
parent: 30
|
||||
- uid: 13449
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -98551,6 +98590,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: 46.5,45.5
|
||||
parent: 30
|
||||
- uid: 16034
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 39.5,-67.5
|
||||
parent: 30
|
||||
- uid: 16218
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -98630,13 +98674,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -24.501097,-36.300484
|
||||
parent: 30
|
||||
- proto: MaterialReclaimer
|
||||
entities:
|
||||
- uid: 4393
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 30.5,-7.5
|
||||
parent: 30
|
||||
- proto: MaterialWoodPlank
|
||||
entities:
|
||||
- uid: 15132
|
||||
|
|
@ -110417,13 +110454,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 33.5,15.5
|
||||
parent: 30
|
||||
- proto: RitualDagger
|
||||
entities:
|
||||
- uid: 10357
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 39.487907,-67.53489
|
||||
parent: 30
|
||||
- proto: SalvageMagnet
|
||||
entities:
|
||||
- uid: 2040
|
||||
|
|
@ -110432,6 +110462,18 @@ entities:
|
|||
rot: 1.5707963267948966 rad
|
||||
pos: 44.5,-18.5
|
||||
parent: 30
|
||||
- proto: SalvageSpawnerScrapCommon
|
||||
entities:
|
||||
- uid: 10443
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 41.5,-73.5
|
||||
parent: 30
|
||||
- uid: 12304
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 38.5,-64.5
|
||||
parent: 30
|
||||
- proto: Scalpel
|
||||
entities:
|
||||
- uid: 21253
|
||||
|
|
@ -116415,11 +116457,6 @@ entities:
|
|||
- SurveillanceCameraCommand
|
||||
nameSet: True
|
||||
id: Vault
|
||||
- uid: 21183
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -0.5,19.5
|
||||
parent: 30
|
||||
- uid: 21286
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
|
|||
|
|
@ -10804,7 +10804,7 @@ entities:
|
|||
pos: -42.5,29.5
|
||||
parent: 5350
|
||||
- type: Door
|
||||
secondsUntilStateChange: -1271.2194
|
||||
secondsUntilStateChange: -2052.8645
|
||||
state: Opening
|
||||
- type: DeviceLinkSink
|
||||
invokeCounter: 2
|
||||
|
|
@ -34442,13 +34442,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -29.53436,40.699295
|
||||
parent: 5350
|
||||
- proto: Cablecuffs
|
||||
entities:
|
||||
- uid: 22690
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 38.5058,-36.452374
|
||||
parent: 5350
|
||||
- proto: CableHV
|
||||
entities:
|
||||
- uid: 1211
|
||||
|
|
@ -60128,11 +60121,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 22.521395,-12.417425
|
||||
parent: 5350
|
||||
- uid: 19020
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 66.56803,33.64219
|
||||
parent: 5350
|
||||
- proto: ClothingEyesGlassesThermal
|
||||
entities:
|
||||
- uid: 23947
|
||||
|
|
@ -60996,13 +60984,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -54.487633,-18.620468
|
||||
parent: 5350
|
||||
- proto: ClothingOuterCoatDetectiveLoadout
|
||||
entities:
|
||||
- uid: 23129
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -22.593464,38.504482
|
||||
parent: 5350
|
||||
- proto: ClothingOuterCoatGentle
|
||||
entities:
|
||||
- uid: 13742
|
||||
|
|
@ -61031,6 +61012,13 @@ entities:
|
|||
- type: Transform
|
||||
pos: -5.527274,-39.534935
|
||||
parent: 5350
|
||||
- proto: ClothingOuterCoatTrench
|
||||
entities:
|
||||
- uid: 16785
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -23.450022,38.487732
|
||||
parent: 5350
|
||||
- proto: ClothingOuterHardsuitEVAPrisoner
|
||||
entities:
|
||||
- uid: 8249
|
||||
|
|
@ -115293,11 +115281,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 27.507141,-21.391407
|
||||
parent: 5350
|
||||
- uid: 22700
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 39.5683,-36.56175
|
||||
parent: 5350
|
||||
- proto: HydroponicsToolMiniHoe
|
||||
entities:
|
||||
- uid: 3137
|
||||
|
|
@ -116364,29 +116347,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 24.5,-13.5
|
||||
parent: 5350
|
||||
- uid: 16785
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -56.5,-23.5
|
||||
parent: 5350
|
||||
- type: EntityStorage
|
||||
air:
|
||||
volume: 200
|
||||
immutable: False
|
||||
temperature: 293.1495
|
||||
moles:
|
||||
- 3.848459
|
||||
- 14.477538
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- proto: LockerBotanistFilled
|
||||
entities:
|
||||
- uid: 2618
|
||||
|
|
@ -117695,6 +117655,11 @@ entities:
|
|||
parent: 5350
|
||||
- proto: MachineFrame
|
||||
entities:
|
||||
- uid: 22686
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 41.5,-31.5
|
||||
parent: 5350
|
||||
- uid: 26436
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -117765,6 +117730,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: 52.5,36.5
|
||||
parent: 5350
|
||||
- uid: 19020
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 37.5,-33.5
|
||||
parent: 5350
|
||||
- uid: 22114
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -117839,6 +117809,11 @@ entities:
|
|||
- type: Transform
|
||||
pos: 36.5,-51.5
|
||||
parent: 5350
|
||||
- uid: 22690
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 37.5,-36.5
|
||||
parent: 5350
|
||||
- uid: 22702
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -117861,6 +117836,11 @@ entities:
|
|||
parent: 5350
|
||||
- proto: MaintenanceWeaponSpawner
|
||||
entities:
|
||||
- uid: 5341
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 38.5,-36.5
|
||||
parent: 5350
|
||||
- uid: 17307
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -117931,13 +117911,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -17.38306,-3.5393329
|
||||
parent: 5350
|
||||
- proto: MaterialReclaimer
|
||||
entities:
|
||||
- uid: 5341
|
||||
components:
|
||||
- type: Transform
|
||||
pos: -37.5,22.5
|
||||
parent: 5350
|
||||
- proto: MaterialWoodPlank1
|
||||
entities:
|
||||
- uid: 22703
|
||||
|
|
@ -143622,11 +143595,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: 4.5,-49.5
|
||||
parent: 5350
|
||||
- uid: 22686
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 41.5,-31.5
|
||||
parent: 5350
|
||||
- uid: 26437
|
||||
components:
|
||||
- type: Transform
|
||||
|
|
@ -167150,13 +167118,6 @@ entities:
|
|||
- type: Transform
|
||||
pos: -23.5,-63.5
|
||||
parent: 5350
|
||||
- proto: WoodenBuckler
|
||||
entities:
|
||||
- uid: 22704
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 37.46614,-33.453953
|
||||
parent: 5350
|
||||
- proto: Wrench
|
||||
entities:
|
||||
- uid: 1287
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue