Merge remote-tracking branch 'space-wizards/master'

# Conflicts:
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml.cs
#	Content.Server/Administration/Systems/AdminSystem.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
#	Content.Server/Ghost/GhostSystem.cs
#	Resources/Locale/en-US/_strings/administration/antag.ftl
#	Resources/Locale/en-US/_strings/administration/commands/adminnotes.ftl
#	Resources/Locale/en-US/_strings/construction/components/block-anchor-component.ftl
#	Resources/Locale/en-US/_strings/power/commands.ftl
#	Resources/Prototypes/Catalog/VendingMachines/Inventories/clothesmate.yml
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Objects/Specific/Hydroponics/aloe.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/apple.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/chili.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/corn.rsi/meta.json
#	Resources/Textures/Objects/Specific/Hydroponics/corn.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/eggplant.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/eggy.rsi/meta.json
#	Resources/Textures/Objects/Specific/Hydroponics/lemon.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/onion_red.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/potato.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/sugarcane.rsi/produce.png
#	Resources/Textures/Objects/Specific/Hydroponics/tomato.rsi/produce.png
This commit is contained in:
Vigers Ray 2025-03-27 20:37:49 +03:00
commit 84c607c84a
422 changed files with 2318 additions and 385 deletions

View file

@ -1,7 +1,10 @@
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Systems;
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@ -14,32 +17,54 @@ namespace Content.Client.Administration;
internal sealed class AdminNameOverlay : Overlay
{
[Dependency] private readonly IConfigurationManager _config = default!;
private readonly AdminSystem _system;
private readonly IEntityManager _entityManager;
private readonly IEyeManager _eyeManager;
private readonly EntityLookupSystem _entityLookup;
private readonly IUserInterfaceManager _userInterfaceManager;
private readonly Font _font;
private readonly Font _fontBold;
private bool _overlayClassic;
private bool _overlaySymbols;
private bool _overlayPlaytime;
private bool _overlayStartingJob;
private float _ghostFadeDistance;
private float _ghostHideDistance;
private int _overlayStackMax;
private float _overlayMergeDistance;
//TODO make this adjustable via GUI
private readonly ProtoId<RoleTypePrototype>[] _filter =
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
private readonly Color _antagColorClassic = Color.OrangeRed;
public AdminNameOverlay(AdminSystem system, IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache, EntityLookupSystem entityLookup, IUserInterfaceManager userInterfaceManager)
public AdminNameOverlay(
AdminSystem system,
IEntityManager entityManager,
IEyeManager eyeManager,
IResourceCache resourceCache,
EntityLookupSystem entityLookup,
IUserInterfaceManager userInterfaceManager,
IConfigurationManager config)
{
IoCManager.InjectDependencies(this);
_system = system;
_entityManager = entityManager;
_eyeManager = eyeManager;
_entityLookup = entityLookup;
_userInterfaceManager = userInterfaceManager;
ZIndex = 200;
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
// Setting these to a specific ttf would break the antag symbols
_font = resourceCache.NotoStack();
_fontBold = resourceCache.NotoStack(variation: "Bold");
config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true);
config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true);
config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true);
config.OnValueChanged(CCVars.AdminOverlayGhostFadeDistance, (f) => { _ghostFadeDistance = f; }, true);
config.OnValueChanged(CCVars.AdminOverlayStackMax, (i) => { _overlayStackMax = i; }, true);
config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true);
}
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
@ -47,75 +72,147 @@ internal sealed class AdminNameOverlay : Overlay
protected override void Draw(in OverlayDrawArgs args)
{
var viewport = args.WorldAABB;
var colorDisconnected = Color.White;
var uiScale = _userInterfaceManager.RootControl.UIScale;
var lineoffset = new Vector2(0f, 14f) * uiScale;
var drawnOverlays = new List<(Vector2,Vector2)>() ; // A saved list of the overlays already drawn
//TODO make this adjustable via GUI
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
var playTime = _config.GetCVar(CCVars.AdminOverlayPlaytime);
var startingJob = _config.GetCVar(CCVars.AdminOverlayStartingJob);
foreach (var playerInfo in _system.PlayerList)
// Get all player positions before drawing overlays, so they can be sorted before iteration
var sortable = new List<(PlayerInfo, Box2, EntityUid, Vector2)>();
foreach (var info in _system.PlayerList)
{
var entity = _entityManager.GetEntity(playerInfo.NetEntity);
var entity = _entityManager.GetEntity(info.NetEntity);
// Otherwise the entity can not exist yet
if (entity == null || !_entityManager.EntityExists(entity))
{
// If entity does not exist or is on a different map, skip
if (entity == null
|| !_entityManager.EntityExists(entity)
|| _entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
continue;
}
// if not on the same map, continue
if (_entityManager.GetComponent<TransformComponent>(entity.Value).MapID != args.MapId)
{
continue;
}
var aabb = _entityLookup.GetWorldAABB(entity.Value);
// if not on screen, continue
// if not on screen, skip
if (!aabb.Intersects(in viewport))
{
continue;
}
var uiScale = _userInterfaceManager.RootControl.UIScale;
var lineoffset = new Vector2(0f, 14f) * uiScale;
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
// Get on-screen coordinates of player
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center).Rounded();
sortable.Add((info, aabb, entity.Value, screenCoordinates));
}
// Draw overlays for visible players, starting from the top of the screen
foreach (var info in sortable.OrderBy(s => s.Item4.Y).ToList())
{
var playerInfo = info.Item1;
var aabb = info.Item2;
var entity = info.Item3;
var screenCoordinatesCenter = info.Item4;
//the center position is kept separately, for simpler position comparison later
var centerOffset = new Vector2(28f, -18f) * uiScale;
var screenCoordinates = screenCoordinatesCenter + centerOffset;
var alpha = 1f;
//TODO make a smarter system where the starting offset can be modified by the predicted position and size of already-drawn overlays/stacks?
var currentOffset = Vector2.Zero;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
// Ghosts near the cursor are made transparent/invisible
// TODO would be "cheaper" if playerinfo already contained a ghost bool, this gets called every frame for every onscreen player!
if (_entityManager.HasComponent<GhostComponent>(entity))
{
// We want the map positions here, so we don't have to worry about resolution and such shenanigans
var mobPosition = aabb.Center;
var mousePosition = _eyeManager
.ScreenToMap(_userInterfaceManager.MousePositionScaled.Position * uiScale)
.Position;
var dist = Vector2.Distance(mobPosition, mousePosition);
if (dist < _ghostHideDistance)
continue;
alpha = Math.Clamp((dist - _ghostHideDistance) / (_ghostFadeDistance - _ghostHideDistance), 0f, 1f);
colorDisconnected.A = alpha;
}
// If the new overlay text block is within merge distance of any previous ones
// merge them into a stack so they don't hide each other
var stack = drawnOverlays.FindAll(x =>
Vector2.Distance(_eyeManager.ScreenToMap(x.Item1).Position, aabb.Center) <= _overlayMergeDistance);
if (stack.Count > 0)
{
screenCoordinates = stack.First().Item1 + centerOffset;
// Replacing this overlay's coordinates for the later save with the stack root's coordinates
// so that other overlays don't try to stack to these coordinates
screenCoordinatesCenter = stack.First().Item1;
var i = 1;
foreach (var s in stack)
{
// additional entries after maximum stack size is reached will be drawn over the last entry
if (i <= _overlayStackMax - 1)
currentOffset = lineoffset + s.Item2 ;
i++;
}
}
// Character name
var color = Color.Aquamarine;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
// Username
color = Color.Yellow;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
// Playtime
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && _overlayPlaytime)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? Color.Orange : Color.White);
color = Color.Orange;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && startingJob)
// Job
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && _overlayStartingJob)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? Color.GreenYellow : Color.White);
color = Color.GreenYellow;
color.A = alpha;
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? color : colorDisconnected);
currentOffset += lineoffset;
}
if (classic && playerInfo.Antag)
// Classic Antag Label
if (_overlayClassic && playerInfo.Antag)
{
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, _antagLabelClassic, uiScale, Color.OrangeRed);
var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
var label = _overlaySymbols
? Loc.GetString("player-tab-character-name-antag-symbol",
("symbol", symbol),
("name", _antagLabelClassic))
: _antagLabelClassic;
color = Color.OrangeRed;
color.A = alpha;
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
currentOffset += lineoffset;
}
else if (!classic && _filter.Contains(playerInfo.RoleProto))
// Role Type
else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto))
{
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
var color = playerInfo.RoleProto.Color;
var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty;
var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
var label = _overlaySymbols
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role))
: role;
color = playerInfo.RoleProto.Color;
color.A = alpha;
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
currentOffset += lineoffset;
}
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, label, uiScale, color);
currentOffset += lineoffset;
}
//Save the coordinates and size of the text block, for stack merge check
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
}
}
}

View file

@ -14,6 +14,7 @@ namespace Content.Client.Administration.Systems
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
private AdminNameOverlay _adminNameOverlay = default!;
@ -22,7 +23,14 @@ namespace Content.Client.Administration.Systems
private void InitializeOverlay()
{
_adminNameOverlay = new AdminNameOverlay(this, EntityManager, _eyeManager, _resourceCache, _entityLookup, _userInterfaceManager);
_adminNameOverlay = new AdminNameOverlay(
this,
EntityManager,
_eyeManager,
_resourceCache,
_entityLookup,
_userInterfaceManager,
_configurationManager);
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
}

View file

@ -4,11 +4,13 @@ using Content.Client.Administration.Systems;
using Content.Client.Administration.UI.AntagObjectives;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader;
using static Robust.Client.UserInterface.Controls.BaseButton;
@ -18,6 +20,7 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab;
public sealed partial class PlayerTab : Control
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
[Dependency] private readonly IPlayerManager _playerMan = default!;
private const string ArrowUp = "↑";
@ -45,6 +48,10 @@ public sealed partial class PlayerTab : Control
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
_config.OnValueChanged(CCVars.AdminPlayerlistSeparateSymbols, PlayerListSettingsChanged);
_config.OnValueChanged(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerListSettingsChanged);
_config.OnValueChanged(CCVars.AdminPlayerlistRoleTypeColor, PlayerListSettingsChanged);
OverlayButton.OnPressed += OverlayButtonPressed;
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
@ -111,6 +118,11 @@ public sealed partial class PlayerTab : Control
#region ListContainer
private void PlayerListSettingsChanged(bool _)
{
RefreshPlayerList(_adminSystem.PlayerList);
}
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
_players = players;
@ -233,8 +245,7 @@ public sealed partial class PlayerTab : Control
Header.Character => Compare(x.CharacterName, y.CharacterName),
Header.Job => Compare(x.StartingJob, y.StartingJob),
Header.Sponsor => string.Compare(x.SponsorTitle!, y.SponsorTitle, StringComparison.Ordinal), // Sunrise-Sponsors
Header.Antagonist => x.Antag.CompareTo(y.Antag),
Header.RoleType => Compare(x.RoleProto.Name , y.RoleProto.Name),
Header.RoleType => y.SortWeight - x.SortWeight,
Header.Playtime => TimeSpan.Compare(x.OverallPlaytime ?? default, y.OverallPlaytime ?? default),
_ => 1
};

View file

@ -26,11 +26,6 @@
ClipText="True"/>
<customControls:VSeparator/>
<!-- Sunrise-Sponsors-End -->
<Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="RoleTypeLabel"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"

View file

@ -1,8 +1,10 @@
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
@ -16,18 +18,26 @@ public sealed partial class PlayerTabEntry : PanelContainer
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
{
RobustXamlLoader.Load(this);
var config = IoCManager.Resolve<IConfigurationManager>();
UsernameLabel.Text = player.Username;
if (!player.Connected)
UsernameLabel.StyleClasses.Add("Disabled");
JobLabel.Text = player.StartingJob;
CharacterLabel.Text = player.CharacterName;
var separateAntagSymbols = config.GetCVar(CCVars.AdminPlayerlistSeparateSymbols);
var genericAntagSymbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
var roleSymbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
var symbol = separateAntagSymbols ? roleSymbol : genericAntagSymbol;
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor))
CharacterLabel.FontColorOverride = player.RoleProto.Color;
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
SponsorLabel.Text = player.IsSponsor ? player.SponsorTitle : ""; // Sunrise-Sponsors
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor))
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
PlayerEntity = player.NetEntity;

View file

@ -34,13 +34,6 @@
MouseFilter="Pass"/>
<cc:VSeparator/>
<!-- Sunrise-Sponsors-End -->
<Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1"
HorizontalExpand="True"
ClipText="True"
Text="{Loc player-tab-antagonist}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="RoleTypeLabel"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"

View file

@ -19,7 +19,6 @@ public sealed partial class PlayerTabHeader : Control
CharacterLabel.OnKeyBindDown += CharacterClicked;
JobLabel.OnKeyBindDown += JobClicked;
SponsorLabel.OnKeyBindDown += SponsorClicked; // Sunrise-Sponsors
AntagonistLabel.OnKeyBindDown += AntagonistClicked;
RoleTypeLabel.OnKeyBindDown += RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown += PlaytimeClicked;
}
@ -32,7 +31,6 @@ public sealed partial class PlayerTabHeader : Control
Header.Character => CharacterLabel,
Header.Job => JobLabel,
Header.Sponsor => SponsorLabel, // Sunrise-Sponsors
Header.Antagonist => AntagonistLabel,
Header.RoleType => RoleTypeLabel,
Header.Playtime => PlaytimeLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
@ -45,7 +43,6 @@ public sealed partial class PlayerTabHeader : Control
CharacterLabel.Text = Loc.GetString("player-tab-character");
JobLabel.Text = Loc.GetString("player-tab-job");
SponsorLabel.Text = Loc.GetString("player-tab-sponsor"); // Sunrise-Sponsors
AntagonistLabel.Text = Loc.GetString("player-tab-antagonist");
RoleTypeLabel.Text = Loc.GetString("player-tab-roletype");
PlaytimeLabel.Text = Loc.GetString("player-tab-playtime");
}
@ -76,11 +73,6 @@ public sealed partial class PlayerTabHeader : Control
HeaderClicked(args, Header.Job);
}
private void AntagonistClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.Antagonist);
}
private void RoleTypeClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.RoleType);
@ -107,7 +99,6 @@ public sealed partial class PlayerTabHeader : Control
UsernameLabel.OnKeyBindDown -= UsernameClicked;
CharacterLabel.OnKeyBindDown -= CharacterClicked;
JobLabel.OnKeyBindDown -= JobClicked;
AntagonistLabel.OnKeyBindDown -= AntagonistClicked;
RoleTypeLabel.OnKeyBindDown -= RoleTypeClicked;
PlaytimeLabel.OnKeyBindDown -= PlaytimeClicked;
}
@ -119,7 +110,6 @@ public sealed partial class PlayerTabHeader : Control
Character,
Job,
Sponsor, // Sunrise-Sponsors
Antagonist,
RoleType,
Playtime
}

View file

@ -4,7 +4,20 @@
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Margin="8">
<Label Text="{Loc 'ui-options-admin-player-panel'}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="PlayerlistSeparateSymbolsCheckBox" Text="{Loc 'ui-options-admin-playerlist-separate-symbols'}" />
<CheckBox Name="PlayerlistCharacterColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-character-color'}" />
<CheckBox Name="PlayerlistRoleTypeColorCheckBox" Text="{Loc 'ui-options-admin-playerlist-roletype-color'}" />
<Label Text="{Loc 'ui-options-admin-overlay-title'}"
StyleClasses="LabelKeyText"/>
<CheckBox Name="EnableClassicOverlayCheckBox" Text="{Loc 'ui-options-enable-classic-overlay'}" />
<CheckBox Name="EnableOverlaySymbolsCheckBox" Text="{Loc 'ui-options-enable-overlay-symbols'}" />
<CheckBox Name="EnableOverlayPlaytimeCheckBox" Text="{Loc 'ui-options-enable-overlay-playtime'}" />
<CheckBox Name="EnableOverlayStartingJobCheckBox" Text="{Loc 'ui-options-enable-overlay-starting-job'}" />
<ui:OptionSlider Name="OverlayMergeDistanceSlider" Title="{Loc 'ui-options-overlay-merge-distance'}"/>
<ui:OptionSlider Name="OverlayGhostFadeSlider" Title="{Loc 'ui-options-overlay-ghost-fade-distance'}"/>
<ui:OptionSlider Name="OverlayGhostHideSlider" Title="{Loc 'ui-options-overlay-ghost-hide-distance'}"/>
</BoxContainer>
</ScrollContainer>
<ui:OptionsTabControlRow Name="Control" Access="Public" />

View file

@ -8,13 +8,45 @@ namespace Content.Client.Options.UI.Tabs;
[GenerateTypedNameReferences]
public sealed partial class AdminOptionsTab : Control
{
private const float OverlayMergeMin = 0.05f;
private const float OverlayMergeMax = 0.95f;
private const int OverlayGhostFadeMin = 0;
private const int OverlayGhostFadeMax = 10;
private const int OverlayGhostHideMin = 0;
private const int OverlayGhostHideMax = 5;
public AdminOptionsTab()
{
RobustXamlLoader.Load(this);
Control.AddOptionCheckBox(CCVars.AdminPlayerlistSeparateSymbols, PlayerlistSeparateSymbolsCheckBox);
Control.AddOptionCheckBox(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerlistCharacterColorCheckBox);
Control.AddOptionCheckBox(CCVars.AdminPlayerlistRoleTypeColor, PlayerlistRoleTypeColorCheckBox);
Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox);
Control.AddOptionCheckBox(CCVars.AdminOverlaySymbols, EnableOverlaySymbolsCheckBox);
Control.AddOptionCheckBox(CCVars.AdminOverlayPlaytime, EnableOverlayPlaytimeCheckBox);
Control.AddOptionCheckBox(CCVars.AdminOverlayStartingJob, EnableOverlayStartingJobCheckBox);
Control.Initialize();
Control.AddOptionPercentSlider(
CCVars.AdminOverlayMergeDistance,
OverlayMergeDistanceSlider,
OverlayMergeMin,
OverlayMergeMax);
Control.AddOptionSlider(
CCVars.AdminOverlayGhostFadeDistance,
OverlayGhostFadeSlider,
OverlayGhostFadeMin,
OverlayGhostFadeMax);
Control.AddOptionSlider(
CCVars.AdminOverlayGhostHideDistance,
OverlayGhostHideSlider,
OverlayGhostHideMin,
OverlayGhostHideMax);
}
}

View file

@ -11,13 +11,21 @@ public sealed class PaperVisualizerSystem : VisualizerSystem<PaperVisualsCompone
if (args.Sprite == null)
return;
if (AppearanceSystem.TryGetData<PaperStatus>(uid, PaperVisuals.Status , out var writingStatus, args.Component))
if (AppearanceSystem.TryGetData<PaperStatus>(uid, PaperVisuals.Status, out var writingStatus, args.Component))
args.Sprite.LayerSetVisible(PaperVisualLayers.Writing, writingStatus == PaperStatus.Written);
if (AppearanceSystem.TryGetData<string>(uid, PaperVisuals.Stamp, out var stampState, args.Component))
{
args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState);
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true);
if (stampState != string.Empty)
{
args.Sprite.LayerSetState(PaperVisualLayers.Stamp, stampState);
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, true);
}
else
{
args.Sprite.LayerSetVisible(PaperVisualLayers.Stamp, false);
}
}
}
}

View file

@ -244,6 +244,12 @@ namespace Content.Client.Popups
PopupEntity(message, uid, recipient.Value, type);
}
public override void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, Filter filter, bool recordReplay, PopupType type = PopupType.Small)
{
if (recipient != null && _timing.IsFirstTimePredicted)
PopupEntity(message, uid, recipient.Value, type);
}
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
{
if (recipient != null && _timing.IsFirstTimePredicted)

View file

@ -1,19 +1,19 @@
using Content.Server.Administration.Notes;
using System.Linq;
using Content.Server.Administration.Notes;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.ViewNotes)]
public sealed class OpenAdminNotesCommand : IConsoleCommand
public sealed class OpenAdminNotesCommand : LocalizedCommands
{
public const string CommandName = "adminnotes";
public string Command => CommandName;
public string Description => "Opens the admin notes panel.";
public string Help => $"Usage: {Command} <notedPlayerUserId OR notedPlayerUsername>";
public override string Command => CommandName;
public async void Execute(IConsoleShell shell, string argStr, string[] args)
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not { } player)
{
@ -33,17 +33,27 @@ public sealed class OpenAdminNotesCommand : IConsoleCommand
if (dbGuid == null)
{
shell.WriteError($"Unable to find {args[0]} netuserid");
shell.WriteError(Loc.GetString("cmd-adminnotes-wrong-target", ("user", args[0])));
return;
}
notedPlayer = dbGuid.UserId;
break;
default:
shell.WriteError($"Invalid arguments.\n{Help}");
shell.WriteError(Loc.GetString("cmd-adminnotes-args-error"));
return;
}
await IoCManager.Resolve<IAdminNotesManager>().OpenEui(player, notedPlayer);
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length != 1)
return CompletionResult.Empty;
var playerMgr = IoCManager.Resolve<IPlayerManager>();
var options = playerMgr.Sessions.Select(c => c.Name).OrderBy(c => c).ToArray();
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-adminnotes-hint"));
}
}

View file

@ -48,7 +48,7 @@ namespace Content.Server.Administration.Commands
}
else
{
if (player.Status != SessionStatus.InGame || player.AttachedEntity is not {Valid: true} playerEntity)
if (player.Status != SessionStatus.InGame || player.AttachedEntity is not { Valid: true } playerEntity)
{
shell.WriteLine("You are not in-game!");
return;
@ -57,14 +57,16 @@ namespace Content.Server.Administration.Commands
var currentMap = _entManager.GetComponent<TransformComponent>(playerEntity).MapID;
var currentGrid = _entManager.GetComponent<TransformComponent>(playerEntity).GridUid;
var xformSystem = _entManager.System<SharedTransformSystem>();
var found = GetWarpPointByName(location)
.OrderBy(p => p.Item1, Comparer<EntityCoordinates>.Create((a, b) =>
{
// Sort so that warp points on the same grid/map are first.
// So if you have two maps loaded with the same warp points,
// it will prefer the warp points on the map you're currently on.
var aGrid = a.GetGridUid(_entManager);
var bGrid = b.GetGridUid(_entManager);
var aGrid = xformSystem.GetGrid(a);
var bGrid = xformSystem.GetGrid(b);
if (aGrid == bGrid)
{
@ -81,8 +83,8 @@ namespace Content.Server.Administration.Commands
return 1;
}
var mapA = a.GetMapId(_entManager);
var mapB = a.GetMapId(_entManager);
var mapA = xformSystem.GetMapId(a);
var mapB = xformSystem.GetMapId(b);
if (mapA == mapB)
{
@ -117,10 +119,8 @@ namespace Content.Server.Administration.Commands
return;
}
var xform = _entManager.GetComponent<TransformComponent>(playerEntity);
var xformSystem = _entManager.System<SharedTransformSystem>();
xform.Coordinates = coords;
xformSystem.AttachToGridOrMap(playerEntity, xform);
xformSystem.SetCoordinates(playerEntity, coords);
xformSystem.AttachToGridOrMap(playerEntity);
if (_entManager.TryGetComponent(playerEntity, out PhysicsComponent? physics))
{
_entManager.System<SharedPhysicsSystem>().SetLinearVelocity(playerEntity, Vector2.Zero, body: physics);

View file

@ -226,6 +226,7 @@ public sealed class AdminSystem : EntitySystem
var name = data.UserName;
var entityName = string.Empty;
var identityName = string.Empty;
var sortWeight = 0;
// Visible (identity) name can be different from real name
if (session?.AttachedEntity != null)
@ -239,8 +240,10 @@ public sealed class AdminSystem : EntitySystem
// Starting role, antagonist status and role type
RoleTypePrototype roleType = new();
var startingRole = string.Empty;
if (_minds.TryGetMind(session, out var mindId, out var mindComp))
if (_minds.TryGetMind(session, out var mindId, out var mindComp) && mindComp is not null)
{
sortWeight = _role.GetRoleCompByTime(mindComp)?.Comp.SortWeight ?? 0;
if (_proto.TryIndex(mindComp.RoleType, out var role))
roleType = role;
else
@ -274,8 +277,21 @@ public sealed class AdminSystem : EntitySystem
}
// Sunrise-Sponsors-End
return new PlayerInfo(name, entityName, identityName, startingRole, antag, roleType, GetNetEntity(session?.AttachedEntity), data.UserId,
connected, _roundActivePlayers.Contains(data.UserId), overallPlaytime, isSponsor, sponsorTitle); // Sunrise-Sponsors
return new PlayerInfo(
name,
entityName,
identityName,
startingRole,
antag,
roleType,
sortWeight,
GetNetEntity(session?.AttachedEntity),
data.UserId,
connected,
_roundActivePlayers.Contains(data.UserId),
overallPlaytime,
isSponsor,
sponsorTitle);
}
private void OnPanicBunkerChanged(bool enabled)

View file

@ -2,10 +2,12 @@ using Content.Server._Sunrise.AssaultOps;
using Content.Server._Sunrise.FleshCult.GameRule;
using Content.Server.Administration.Commands;
using Content.Server.Antag;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Zombies;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.Roles;
using Content.Shared.Verbs;
@ -19,6 +21,7 @@ public sealed partial class AdminVerbSystem
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[ValidatePrototypeId<EntityPrototype>]
private const string DefaultTraitorRule = "Traitor";
@ -50,6 +53,8 @@ public sealed partial class AdminVerbSystem
[ValidatePrototypeId<StartingGearPrototype>]
private const string PirateGearId = "PirateGear";
private readonly EntProtoId _paradoxCloneRuleId = "ParadoxCloneSpawn";
// All antag verbs have names so invokeverb works.
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
{
@ -172,6 +177,30 @@ public sealed partial class AdminVerbSystem
};
args.Verbs.Add(thief);
var paradoxCloneName = Loc.GetString("admin-verb-text-make-paradox-clone");
Verb paradox = new()
{
Text = paradoxCloneName,
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "ParadoxClone"),
Act = () =>
{
var ruleEnt = _gameTicker.AddGameRule(_paradoxCloneRuleId);
if (!TryComp<ParadoxCloneRuleComponent>(ruleEnt, out var paradoxCloneRuleComp))
return;
paradoxCloneRuleComp.OriginalBody = args.Target; // override the target player
_gameTicker.StartGameRule(ruleEnt);
},
Impact = LogImpact.High,
Message = string.Join(": ", paradoxCloneName, Loc.GetString("admin-verb-make-paradox-clone")),
};
if (HasComp<HumanoidAppearanceComponent>(args.Target)) // only humanoids can be cloned
args.Verbs.Add(paradox);
Verb ling = new()
{
Text = Loc.GetString("admin-verb-text-make-changeling"),

View file

@ -153,7 +153,7 @@ public sealed partial class AdminVerbSystem
PopupType.MediumCaution);
var board = Spawn("ChessBoard", xform.Coordinates);
var session = _tabletopSystem.EnsureSession(Comp<TabletopGameComponent>(board));
xform.Coordinates = _transformSystem.ToCoordinates(session.Position);
_transformSystem.SetMapCoordinates(args.Target, session.Position);
_transformSystem.SetWorldRotationNoLerp((args.Target, xform), Angle.Zero);
},
Impact = LogImpact.Extreme,
@ -481,7 +481,7 @@ public sealed partial class AdminVerbSystem
{
var xform = Transform(args.Target);
var fixtures = Comp<FixturesComponent>(args.Target);
xform.Anchored = false; // Just in case.
_transformSystem.Unanchor(args.Target); // Just in case.
_physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics);
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
_physics.WakeBody(args.Target, manager: fixtures, body: physics);
@ -519,7 +519,7 @@ public sealed partial class AdminVerbSystem
{
var xform = Transform(args.Target);
var fixtures = Comp<FixturesComponent>(args.Target);
xform.Anchored = false; // Just in case.
_transformSystem.Unanchor(args.Target); // Just in case.
_physics.SetBodyType(args.Target, BodyType.Dynamic, body: physics);
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);

View file

@ -637,7 +637,7 @@ public sealed partial class AdminVerbSystem
{
if (_adminManager.HasAdminFlag(player, AdminFlags.Mapping))
{
if (_mapManager.IsMapPaused(map.MapId))
if (_map.IsPaused(map.MapId))
{
Verb unpauseMap = new()
{
@ -646,7 +646,7 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/play.png")),
Act = () =>
{
_mapManager.SetMapPaused(map.MapId, false);
_map.SetPaused(map.MapId, false);
},
Impact = LogImpact.Extreme,
Message = Loc.GetString("admin-trick-unpause-map-description"),
@ -663,7 +663,7 @@ public sealed partial class AdminVerbSystem
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/pause.png")),
Act = () =>
{
_mapManager.SetMapPaused(map.MapId, true);
_map.SetPaused(map.MapId, true);
},
Impact = LogImpact.Extreme,
Message = Loc.GetString("admin-trick-pause-map-description"),

View file

@ -53,7 +53,7 @@ namespace Content.Server.Administration.Systems
[Dependency] private readonly IConsoleHost _console = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly AdminSystem _adminSystem = default!;
[Dependency] private readonly DisposalTubeSystem _disposalTubes = default!;
@ -153,12 +153,10 @@ namespace Content.Server.Administration.Systems
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
var targetMind = _mindSystem.GetMind(args.Target);
if (targetMind != null)
{
_mindSystem.TransferTo(targetMind.Value, mobUid, true);
}
if (_mindSystem.TryGetMind(args.Target, out var mindId, out var mindComp))
_mindSystem.TransferTo(mindId, mobUid, true, mind: mindComp);
},
ConfirmationPopup = true,
Impact = LogImpact.High,

View file

@ -0,0 +1,83 @@
using Content.Server.Forensics;
using Content.Shared.Cloning.Events;
using Content.Shared.Clothing.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Labels.Components;
using Content.Shared.Labels.EntitySystems;
using Content.Shared.Paper;
using Content.Shared.Stacks;
using Content.Shared.Store;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Cloning;
/// <summary>
/// The part of item cloning responsible for copying over important components.
/// This is used for <see cref="CopyItem"/>.
/// Anything not copied over here gets reverted to the values the item had in its prototype.
/// </summary>
/// <remarks>
/// This method of copying items is of course not perfect as we cannot clone every single component, which would be pretty much impossible with our ECS.
/// We only consider the most important components so the paradox clone gets similar equipment.
/// This method of using subscriptions was chosen to make it easy for forks to add their own custom components that need to be copied.
/// </remarks>
public sealed partial class CloningSystem : EntitySystem
{
[Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] private readonly SharedLabelSystem _label = default!;
[Dependency] private readonly ForensicsSystem _forensics = default!;
[Dependency] private readonly PaperSystem _paper = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StackComponent, CloningItemEvent>(OnCloneStack);
SubscribeLocalEvent<LabelComponent, CloningItemEvent>(OnCloneLabel);
SubscribeLocalEvent<PaperComponent, CloningItemEvent>(OnClonePaper);
SubscribeLocalEvent<ForensicsComponent, CloningItemEvent>(OnCloneForensics);
SubscribeLocalEvent<StoreComponent, CloningItemEvent>(OnCloneStore);
}
private void OnCloneStack(Entity<StackComponent> ent, ref CloningItemEvent args)
{
// if the clone is a stack as well, adjust the count of the copy
if (TryComp<StackComponent>(args.CloneUid, out var cloneStackComp))
_stack.SetCount(args.CloneUid, ent.Comp.Count, cloneStackComp);
}
private void OnCloneLabel(Entity<LabelComponent> ent, ref CloningItemEvent args)
{
// copy the label
_label.Label(args.CloneUid, ent.Comp.CurrentLabel);
}
private void OnClonePaper(Entity<PaperComponent> ent, ref CloningItemEvent args)
{
// copy the text and any stamps
if (TryComp<PaperComponent>(args.CloneUid, out var clonePaperComp))
{
_paper.SetContent((args.CloneUid, clonePaperComp), ent.Comp.Content);
_paper.CopyStamps(ent.AsNullable(), (args.CloneUid, clonePaperComp));
}
}
private void OnCloneForensics(Entity<ForensicsComponent> ent, ref CloningItemEvent args)
{
// copy any forensics to the cloned item
_forensics.CopyForensicsFrom(ent.Comp, args.CloneUid);
}
private void OnCloneStore(Entity<StoreComponent> ent, ref CloningItemEvent args)
{
// copy the current amount of currency in the store
// at the moment this takes care of uplink implants and the portable nukie uplinks
// turning a copied pda into an uplink will need some refactoring first
if (TryComp<StoreComponent>(args.CloneUid, out var cloneStoreComp))
{
cloneStoreComp.Balance = new Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2>(ent.Comp.Balance);
}
}
}

View file

@ -9,7 +9,6 @@ using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Content.Shared.NameModifier.Components;
using Content.Shared.StatusEffect;
using Content.Shared.Stacks;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Whitelist;
@ -25,7 +24,7 @@ namespace Content.Server.Cloning;
/// System responsible for making a copy of a humanoid's body.
/// For the cloning machines themselves look at CloningPodSystem, CloningConsoleSystem and MedicalScannerSystem instead.
/// </summary>
public sealed class CloningSystem : EntitySystem
public sealed partial class CloningSystem : EntitySystem
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
@ -36,7 +35,6 @@ public sealed class CloningSystem : EntitySystem
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly SharedStackSystem _stack = default!;
[Dependency] private readonly SharedSubdermalImplantSystem _subdermalImplant = default!;
/// <summary>
@ -157,9 +155,9 @@ public sealed class CloningSystem : EntitySystem
var spawned = EntityManager.SpawnAtPosition(prototype, coords);
// if the original is a stack, adjust the count of the copy
if (TryComp<StackComponent>(original, out var originalStack) && TryComp<StackComponent>(spawned, out var spawnedStack))
_stack.SetCount(spawned, originalStack.Count, spawnedStack);
// copy over important component data
var ev = new CloningItemEvent(spawned);
RaiseLocalEvent(original, ref ev);
// if the original has items inside its storage, copy those as well
if (TryComp<StorageComponent>(original, out var originalStorage) && TryComp<StorageComponent>(spawned, out var spawnedStorage))
@ -232,7 +230,14 @@ public sealed class CloningSystem : EntitySystem
var targetImplant = _subdermalImplant.AddImplant(target, implantId);
if (copyStorage && targetImplant != null)
if (targetImplant == null)
continue;
// copy over important component data
var ev = new CloningItemEvent(targetImplant.Value);
RaiseLocalEvent(originalImplant, ref ev);
if (copyStorage)
CopyStorage(originalImplant, targetImplant.Value, whitelist, blacklist); // only needed for storage implants
}

View file

@ -138,6 +138,11 @@ namespace Content.Server.Forensics
{
dest.Fingerprints.Add(print);
}
foreach (var residue in src.Residues)
{
dest.Residues.Add(residue);
}
}
public List<string> GetSolutionsDNA(EntityUid uid)

View file

@ -14,7 +14,7 @@ public sealed partial class ParadoxCloneRuleComponent : Component
/// Cloning settings to be used.
/// </summary>
[DataField]
public ProtoId<CloningSettingsPrototype> Settings = "BaseClone";
public ProtoId<CloningSettingsPrototype> Settings = "Antag";
/// <summary>
/// Visual effect spawned when gibbing at round end.
@ -22,12 +22,19 @@ public sealed partial class ParadoxCloneRuleComponent : Component
[DataField]
public EntProtoId GibProto = "MobParadoxTimed";
/// <summary>
/// Entity of the original player.
/// Gets randomly chosen from all alive players if not specified.
/// </summary>
[DataField]
public EntityUid? OriginalBody;
/// <summary>
/// Mind entity of the original player.
/// Gets assigned when cloning.
/// </summary>
[DataField]
public EntityUid? Original;
public EntityUid? OriginalMind;
/// <summary>
/// Whitelist for Objectives to be copied to the clone.

View file

@ -47,28 +47,42 @@ public sealed class ParadoxCloneRuleSystem : GameRuleSystem<ParadoxCloneRuleComp
if (args.Session?.AttachedEntity is not { } spawner)
return;
// get possible targets
var allHumans = _mind.GetAliveHumans();
// we already checked when starting the gamerule, but someone might have died since then.
if (allHumans.Count == 0)
if (ent.Comp.OriginalBody != null) // target was overridden, for example by admin antag control
{
Log.Warning("Could not find any alive players to create a paradox clone from!");
return;
if (Deleted(ent.Comp.OriginalBody.Value) || !_mind.TryGetMind(ent.Comp.OriginalBody.Value, out var originalMindId, out var _))
{
Log.Warning("Could not find mind of target player to paradox clone!");
return;
}
ent.Comp.OriginalMind = originalMindId;
}
else
{
// get possible targets
var allAliveHumanoids = _mind.GetAliveHumans();
// we already checked when starting the gamerule, but someone might have died since then.
if (allAliveHumanoids.Count == 0)
{
Log.Warning("Could not find any alive players to create a paradox clone from!");
return;
}
// pick a random player
var randomHumanoidMind = _random.Pick(allAliveHumanoids);
ent.Comp.OriginalMind = randomHumanoidMind;
ent.Comp.OriginalBody = randomHumanoidMind.Comp.OwnedEntity;
}
// pick a random player
var playerToClone = _random.Pick(allHumans);
var bodyToClone = playerToClone.Comp.OwnedEntity;
if (bodyToClone == null || !_cloning.TryCloning(bodyToClone.Value, _transform.GetMapCoordinates(spawner), ent.Comp.Settings, out var clone))
if (ent.Comp.OriginalBody == null || !_cloning.TryCloning(ent.Comp.OriginalBody.Value, _transform.GetMapCoordinates(spawner), ent.Comp.Settings, out var clone))
{
Log.Error($"Unable to make a paradox clone of entity {ToPrettyString(bodyToClone)}");
Log.Error($"Unable to make a paradox clone of entity {ToPrettyString(ent.Comp.OriginalBody)}");
return;
}
var targetComp = EnsureComp<TargetOverrideComponent>(clone.Value);
targetComp.Target = playerToClone.Owner; // set the kill target
targetComp.Target = ent.Comp.OriginalMind; // set the kill target
var gibComp = EnsureComp<GibOnRoundEndComponent>(clone.Value);
gibComp.SpawnProto = ent.Comp.GibProto;
@ -78,17 +92,16 @@ public sealed class ParadoxCloneRuleSystem : GameRuleSystem<ParadoxCloneRuleComp
_sensor.SetAllSensors(clone.Value, SuitSensorMode.SensorOff);
args.Entity = clone;
ent.Comp.Original = playerToClone.Owner;
}
private void AfterAntagEntitySelected(Entity<ParadoxCloneRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
if (ent.Comp.Original == null)
if (ent.Comp.OriginalMind == null)
return;
if (!_mind.TryGetMind(args.EntityUid, out var cloneMindId, out var cloneMindComp))
return;
_mind.CopyObjectives(ent.Comp.Original.Value, (cloneMindId, cloneMindComp), ent.Comp.ObjectiveWhitelist, ent.Comp.ObjectiveBlacklist);
_mind.CopyObjectives(ent.Comp.OriginalMind.Value, (cloneMindId, cloneMindComp), ent.Comp.ObjectiveWhitelist, ent.Comp.ObjectiveBlacklist);
}
}

View file

@ -27,6 +27,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.Tag;
@ -69,6 +70,7 @@ namespace Content.Server.Ghost
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly NameModifierSystem _nameMod = default!;
[Dependency] private readonly NewLifeSystem _newLifeSystem = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
@ -532,6 +534,10 @@ namespace Content.Server.Ghost
else
_minds.TransferTo(mind.Owner, ghost, mind: mind.Comp);
Log.Debug($"Spawned ghost \"{ToPrettyString(ghost)}\" for {mind.Comp.CharacterName}.");
// we changed the entity name above
// we have to call this after the mind has been transferred since some mind roles modify the ghost's name
_nameMod.RefreshNameModifiers(ghost);
return ghost;
}

View file

@ -188,8 +188,8 @@ namespace Content.Server.NPC.Pathfinding
/// </summary>
public bool TryCreatePortal(EntityCoordinates coordsA, EntityCoordinates coordsB, out int handle)
{
var mapUidA = coordsA.GetMapUid(EntityManager);
var mapUidB = coordsB.GetMapUid(EntityManager);
var mapUidA = _transform.GetMap(coordsA);
var mapUidB = _transform.GetMap(coordsB);
handle = -1;
if (mapUidA != mapUidB || mapUidA == null)
@ -197,8 +197,8 @@ namespace Content.Server.NPC.Pathfinding
return false;
}
var gridUidA = coordsA.GetGridUid(EntityManager);
var gridUidB = coordsB.GetGridUid(EntityManager);
var gridUidA = _transform.GetGrid(coordsA);
var gridUidB = _transform.GetGrid(coordsB);
if (!TryComp<GridPathfindingComponent>(gridUidA, out var gridA) ||
!TryComp<GridPathfindingComponent>(gridUidB, out var gridB))
@ -236,8 +236,8 @@ namespace Content.Server.NPC.Pathfinding
_portals.Remove(handle);
var gridUidA = portal.CoordinatesA.GetGridUid(EntityManager);
var gridUidB = portal.CoordinatesB.GetGridUid(EntityManager);
var gridUidA = _transform.GetGrid(portal.CoordinatesA);
var gridUidB = _transform.GetGrid(portal.CoordinatesB);
if (!TryComp<GridPathfindingComponent>(gridUidA, out var gridA) ||
!TryComp<GridPathfindingComponent>(gridUidB, out var gridB))
@ -397,7 +397,7 @@ namespace Content.Server.NPC.Pathfinding
/// </summary>
public PathPoly? GetPoly(EntityCoordinates coordinates)
{
var gridUid = coordinates.GetGridUid(EntityManager);
var gridUid = _transform.GetGrid(coordinates);
if (!TryComp<GridPathfindingComponent>(gridUid, out var comp) ||
!TryComp(gridUid, out TransformComponent? xform))
@ -405,14 +405,14 @@ namespace Content.Server.NPC.Pathfinding
return null;
}
var localPos = Vector2.Transform(coordinates.ToMapPos(EntityManager, _transform), _transform.GetInvWorldMatrix(xform));
var localPos = Vector2.Transform(_transform.ToMapCoordinates(coordinates).Position, _transform.GetInvWorldMatrix(xform));
var origin = GetOrigin(localPos);
if (!TryGetChunk(origin, comp, out var chunk))
return null;
var chunkPos = new Vector2(MathHelper.Mod(localPos.X, ChunkSize), MathHelper.Mod(localPos.Y, ChunkSize));
var polys = chunk.Polygons[(int) chunkPos.X * ChunkSize + (int) chunkPos.Y];
var polys = chunk.Polygons[(int)chunkPos.X * ChunkSize + (int)chunkPos.Y];
foreach (var poly in polys)
{

View file

@ -1,4 +1,3 @@
using System.Linq;
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Parallax.Biomes;
@ -29,9 +28,9 @@ public sealed partial class BiomeSystem
int.TryParse(args[0], out var mapInt);
var mapId = new MapId(mapInt);
var mapUid = _mapManager.GetMapEntityId(mapId);
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
if (_mapManager.MapExists(mapId) ||
if (_mapSystem.MapExists(mapId) ||
!TryComp<BiomeComponent>(mapUid, out var biome))
{
return;
@ -64,9 +63,9 @@ public sealed partial class BiomeSystem
}
var mapId = new MapId(mapInt);
var mapUid = _mapManager.GetMapEntityId(mapId);
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
if (!_mapManager.MapExists(mapId) || !TryComp<BiomeComponent>(mapUid, out var biome))
if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(mapUid, out var biome))
{
return;
}
@ -105,7 +104,7 @@ public sealed partial class BiomeSystem
{
var mapId = new MapId(mapInt);
if (TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
if (TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
{
var results = new List<string>();
@ -145,7 +144,7 @@ public sealed partial class BiomeSystem
var mapId = new MapId(mapInt);
if (!_mapManager.MapExists(mapId) || !TryComp<BiomeComponent>(_mapManager.GetMapEntityId(mapId), out var biome))
if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
{
return;
}

View file

@ -149,6 +149,20 @@ namespace Content.Server.Popups
}
}
public override void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, Filter filter, bool recordReplay, PopupType type = PopupType.Small)
{
if (message == null)
return;
if (recipient != null)
{
// Don't send to recipient, since they predicted it locally
filter = filter.RemovePlayerByAttachedEntity(recipient.Value);
}
RaiseNetworkEvent(new PopupEntityEvent(message, type, GetNetEntity(uid)), filter, recordReplay);
}
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
{
PopupPredicted(othersMessage, uid, recipient, type);

View file

@ -0,0 +1,29 @@
using Content.Server.Administration;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Power.Commands;
[AdminCommand(AdminFlags.Debug)]
public sealed class PowerValidateCommand : LocalizedEntityCommands
{
[Dependency] private readonly PowerNetSystem _powerNet = null!;
public override string Command => "power_validate";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
try
{
_powerNet.Validate();
}
catch (Exception e)
{
shell.WriteLine(LocalizationManager.GetString("cmd-power_validate-error", ("err", e.ToString())));
return;
}
shell.WriteLine(LocalizationManager.GetString("cmd-power_validate-success"));
}
}

View file

@ -519,6 +519,14 @@ namespace Content.Server.Power.EntitySystems
supplier.NetworkSupply.LinkedNetwork = netNode.Id;
}
}
/// <summary>
/// Validate integrity of the power state data. Throws if an error is found.
/// </summary>
public void Validate()
{
_solver.Validate(_powerState);
}
}
/// <summary>

View file

@ -1,6 +1,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Utility;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Threading;
using static Content.Server.Power.Pow3r.PowerState;
@ -40,7 +42,9 @@ namespace Content.Server.Power.Pow3r
DebugTools.Assert(state.GroupedNets.Select(x => x.Count).Sum() == state.Networks.Count);
_networkJob.State = state;
_networkJob.FrameTime = frameTime;
#if DEBUG
ValidateNetworkGroups(state, state.GroupedNets);
#endif
// Each network height layer can be run in parallel without issues.
foreach (var group in state.GroupedNets)
@ -328,16 +332,22 @@ namespace Content.Server.Power.Pow3r
RecursivelyEstimateNetworkDepth(state, network, groupedNetworks);
}
ValidateNetworkGroups(state, groupedNetworks);
return groupedNetworks;
}
public void Validate(PowerState state)
{
if (state.GroupedNets == null)
throw new InvalidOperationException("We don't have grouped networks cached??");
ValidateNetworkGroups(state, state.GroupedNets);
}
/// <summary>
/// Validate that network grouping is up to date. I.e., that it is safe to solve each networking in a given
/// group in parallel. This assumes that batteries are the only device that connects to multiple networks, and
/// is thus the only obstacle to solving everything in parallel.
/// </summary>
[Conditional("DEBUG")]
private void ValidateNetworkGroups(PowerState state, List<List<Network>> groupedNetworks)
{
HashSet<Network> nets = new();
@ -362,9 +372,9 @@ namespace Content.Server.Power.Pow3r
continue;
}
DebugTools.Assert(!nets.Contains(subNet));
DebugTools.Assert(!netIds.Contains(subNet.Id));
DebugTools.Assert(subNet.Height < net.Height);
Check(!nets.Contains(subNet), $"Net {net.Id}, battery {batteryId}");
Check(!netIds.Contains(subNet.Id), $"Net {net.Id}, battery {batteryId}");
Check(subNet.Height < net.Height, $"Net {net.Id}, battery {batteryId}");
}
foreach (var batteryId in net.BatterySupplies)
@ -380,15 +390,32 @@ namespace Content.Server.Power.Pow3r
continue;
}
DebugTools.Assert(!nets.Contains(parentNet));
DebugTools.Assert(!netIds.Contains(parentNet.Id));
DebugTools.Assert(parentNet.Height > net.Height);
Check(!nets.Contains(parentNet), $"Net {net.Id}, battery {batteryId}");
Check(!netIds.Contains(parentNet.Id), $"Net {net.Id}, battery {batteryId}");
Check(parentNet.Height > net.Height, $"Net {net.Id}, battery {batteryId}");
}
DebugTools.Assert(nets.Add(net));
DebugTools.Assert(netIds.Add(net.Id));
Check(nets.Add(net), $"Net {net.Id}");
Check(netIds.Add(net.Id), $"Net {net.Id}");
}
}
return;
// Most readable C# function def.
[AssertionMethod]
static void Check(
[AssertionCondition(AssertionConditionType.IS_TRUE)]
[DoesNotReturnIf(false)]
bool condition,
[InterpolatedStringHandlerArgument("condition")]
ref DebugTools.AssertInterpolatedStringHandler handler,
[CallerArgumentExpression(nameof(condition))]
string check = "")
{
if (!condition)
throw new DebugAssertException($"{handler.ToStringAndClear()}: failed check: {check}");
}
}
private static void RecursivelyEstimateNetworkDepth(PowerState state, Network network, List<List<Network>> groupedNetworks)

View file

@ -5,5 +5,6 @@ namespace Content.Server.Power.Pow3r
public interface IPowerSolver
{
void Tick(float frameTime, PowerState state, IParallelManager parallel);
void Validate(PowerState state);
}
}

View file

@ -8,5 +8,10 @@ namespace Content.Server.Power.Pow3r
{
// Literally nothing.
}
public void Validate(PowerState state)
{
// Literally nothing.
}
}
}

View file

@ -111,7 +111,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
if (tile.IsSpace(_tileDefinitionManager)
|| _turf.IsTileBlocked(tile, CollisionGroup.MobMask)
|| !_atmosphere.IsTileMixtureProbablySafe(entityGridUid, entityMapUid.Value,
grid.TileIndicesFor(mapPos)))
_map.TileIndicesFor((entityGridUid.Value, grid), mapPos)))
{
continue;
}
@ -136,7 +136,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
private void Respawn(EntityUid oldEntity, string prototype, EntityCoordinates coords)
{
var entity = Spawn(prototype, coords);
_adminLog.Add(LogType.Respawn, LogImpact.Extreme, $"{ToPrettyString(oldEntity)} was deleted and was respawned at {coords.ToMap(EntityManager, _transform)} as {ToPrettyString(entity)}");
_adminLog.Add(LogType.Respawn, LogImpact.Extreme, $"{ToPrettyString(oldEntity)} was deleted and was respawned at {_transform.ToMapCoordinates(coords)} as {ToPrettyString(entity)}");
_chat.SendAdminAlert($"{MetaData(oldEntity).EntityName} was deleted and was respawned as {ToPrettyString(entity)}");
}
@ -157,7 +157,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
var xform = Transform(targetGrid);
if (!grid.TryGetTileRef(xform.Coordinates, out var tileRef))
if (!_map.TryGetTileRef(targetGrid, grid, xform.Coordinates, out var tileRef))
return false;
var tile = tileRef.GridIndices;
@ -169,12 +169,12 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
//Obviously don't put anything ridiculous in here
for (var i = 0; i < maxAttempts; i++)
{
var randomX = _random.Next((int) gridBounds.Left, (int) gridBounds.Right);
var randomY = _random.Next((int) gridBounds.Bottom, (int) gridBounds.Top);
var randomX = _random.Next((int)gridBounds.Left, (int)gridBounds.Right);
var randomY = _random.Next((int)gridBounds.Bottom, (int)gridBounds.Top);
tile = new Vector2i(randomX - (int) gridPos.X, randomY - (int) gridPos.Y);
var mapPos = grid.GridTileToWorldPos(tile);
var mapTarget = grid.WorldToTile(mapPos);
tile = new Vector2i(randomX - (int)gridPos.X, randomY - (int)gridPos.Y);
var mapPos = _map.GridTileToWorldPos(targetGrid, grid, tile);
var mapTarget = _map.WorldToTile(targetGrid, grid, mapPos);
var circle = new Circle(mapPos, 2);
foreach (var newTileRef in _map.GetTilesIntersecting(targetGrid, grid, circle))
@ -183,7 +183,7 @@ public sealed class SpecialRespawnSystem : SharedSpecialRespawnSystem
continue;
found = true;
targetCoords = grid.GridTileToLocal(tile);
targetCoords = _map.GridTileToLocal(targetGrid, grid, tile);
break;
}

View file

@ -6,4 +6,12 @@ namespace Content.Server.Roles;
/// Added to mind role entities to tag that they are a paradox clone.
/// </summary>
[RegisterComponent]
public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent;
public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent
{
/// <summary>
/// Name modifer applied to the player when they turn into a ghost.
/// Needed to be able to keep the original and the clone apart in dead chat.
/// </summary>
[DataField]
public LocId? NameModifier = "paradox-clone-ghost-name-modifier";
}

View file

@ -0,0 +1,32 @@
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Roles;
namespace Content.Server.Roles;
/// <summary>
/// System responsible for giving a ghost of a paradox clone a name modifier.
/// </summary>
public sealed class ParadoxCloneRoleSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ParadoxCloneRoleComponent, MindRelayedEvent<RefreshNameModifiersEvent>>(OnRefreshNameModifiers);
}
private void OnRefreshNameModifiers(Entity<ParadoxCloneRoleComponent> ent, ref MindRelayedEvent<RefreshNameModifiersEvent> args)
{
if (!TryComp<MindRoleComponent>(ent.Owner, out var roleComp))
return;
// only show for ghosts
if (!HasComp<GhostComponent>(roleComp.Mind.Comp.OwnedEntity))
return;
if (ent.Comp.NameModifier != null)
args.Args.AddModifier(ent.Comp.NameModifier.Value, 50);
}
}

View file

@ -45,12 +45,12 @@ namespace Content.Server.Tabletop
/// </summary>
private void EnsureTabletopMap()
{
if (TabletopMap != MapId.Nullspace && _mapManager.MapExists(TabletopMap))
if (TabletopMap != MapId.Nullspace && _map.MapExists(TabletopMap))
return;
TabletopMap = _mapManager.CreateMap();
var mapUid = _map.CreateMap(out var mapId);
TabletopMap = mapId;
_tabletops = 0;
var mapUid = _mapManager.GetMapEntityId(TabletopMap);
var mapComp = EntityManager.GetComponent<MapComponent>(mapUid);
@ -89,11 +89,11 @@ namespace Content.Server.Tabletop
private void OnRoundRestart(RoundRestartCleanupEvent _)
{
if (TabletopMap == MapId.Nullspace || !_mapManager.MapExists(TabletopMap))
if (TabletopMap == MapId.Nullspace || !_map.MapExists(TabletopMap))
return;
// This will usually *not* be the case, but better make sure.
_mapManager.DeleteMap(TabletopMap);
_map.DeleteMap(TabletopMap);
// Reset tabletop count.
_tabletops = 0;

View file

@ -12,7 +12,6 @@ using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Utility;
@ -21,7 +20,7 @@ namespace Content.Server.Tabletop
[UsedImplicitly]
public sealed partial class TabletopSystem : SharedTabletopSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly EyeSystem _eye = default!;
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;

View file

@ -12,6 +12,7 @@ public sealed record PlayerInfo(
string StartingJob,
bool Antag,
RoleTypePrototype RoleProto,
int SortWeight,
NetEntity? NetEntity,
NetUserId SessionId,
bool Connected,

View file

@ -49,11 +49,60 @@ public sealed partial class CCVars
/// If true, the admin overlay will display the total time of the players
/// </summary>
public static readonly CVarDef<bool> AdminOverlayPlaytime =
CVarDef.Create("ui.admin_overlay_playtime", false, CVar.CLIENTONLY | CVar.ARCHIVE);
CVarDef.Create("ui.admin_overlay_playtime", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// If true, the admin overlay will display the players starting position.
/// </summary>
public static readonly CVarDef<bool> AdminOverlayStartingJob =
CVarDef.Create("ui.admin_overlay_starting_job", false, CVar.CLIENTONLY | CVar.ARCHIVE);
CVarDef.Create("ui.admin_overlay_starting_job", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// If true, the admin window player tab will show different antag symbols for each role type
/// </summary>
public static readonly CVarDef<bool> AdminPlayerlistSeparateSymbols =
CVarDef.Create("ui.admin_playerlist_separate_symbols", false, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// If true, characters with antag role types will have their names colored by their role type
/// </summary>
public static readonly CVarDef<bool> AdminPlayerlistHighlightedCharacterColor =
CVarDef.Create("ui.admin_playerlist_highlighted_character_color", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// If true, the Role Types column will be colored
/// </summary>
public static readonly CVarDef<bool> AdminPlayerlistRoleTypeColor =
CVarDef.Create("ui.admin_playerlist_role_type_color", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// If true, the admin overlay will show antag symbols
/// </summary>
public static readonly CVarDef<bool> AdminOverlaySymbols =
CVarDef.Create("ui.admin_overlay_symbols", true, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// The range (in tiles) around the cursor within which the admin overlays of ghosts start to fade out
/// </summary>
public static readonly CVarDef<int> AdminOverlayGhostFadeDistance =
CVarDef.Create("ui.admin_overlay_ghost_fade_distance", 6, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// The range (in tiles) around the cursor within which the admin overlays of ghosts disappear
/// </summary>
public static readonly CVarDef<int> AdminOverlayGhostHideDistance =
CVarDef.Create("ui.admin_overlay_ghost_hide_distance", 2, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// The maximum range (in tiles) at which admin overlay entries still merge to form a stack
/// Recommended to keep under 1, otherwise the overlays of people sitting next to each other will stack
/// </summary>
public static readonly CVarDef<float> AdminOverlayMergeDistance =
CVarDef.Create("ui.admin_overlay_merge_distance", 0.33f, CVar.CLIENTONLY | CVar.ARCHIVE);
/// <summary>
/// The maximum size that an overlay stack can reach. Additional overlays will be superimposed over the last one.
/// </summary>
public static readonly CVarDef<int> AdminOverlayStackMax =
CVarDef.Create("ui.admin_overlay_stack_max", 3, CVar.CLIENTONLY | CVar.ARCHIVE);
}

View file

@ -2,12 +2,21 @@ namespace Content.Shared.Cloning.Events;
/// <summary>
/// Raised before a mob is cloned. Cancel to prevent cloning.
/// This is raised on the original mob.
/// </summary>
[ByRefEvent]
public record struct CloningAttemptEvent(CloningSettingsPrototype Settings, bool Cancelled = false);
/// <summary>
/// Raised after a new mob got spawned when cloning a humanoid.
/// Raised after a new mob was spawned when cloning a humanoid.
/// This is raised on the original mob.
/// </summary>
[ByRefEvent]
public record struct CloningEvent(CloningSettingsPrototype Settings, EntityUid CloneUid);
/// <summary>
/// Raised after a new item was spawned when cloning an item.
/// This is raised on the original item.
/// </summary>
[ByRefEvent]
public record struct CloningItemEvent(EntityUid CloneUid);

View file

@ -0,0 +1,24 @@
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Construction.Components;
/// <summary>
/// Will not allow anchoring if there is an anchored item in the same tile that fails the <see cref="EntityWhitelist"/>.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(BlockAnchorOnSystem))]
public sealed partial class BlockAnchorOnComponent : Component
{
/// <summary>
/// If not null, entities that match this whitelist are allowed.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
/// <summary>
/// If not null, entities that match this blacklist are not allowed.
/// </summary>
[DataField]
public EntityWhitelist? Blacklist;
}

View file

@ -0,0 +1,82 @@
using Content.Shared.Construction.Components;
using Content.Shared.Popups;
using Content.Shared.Whitelist;
using Robust.Shared.Map.Components;
namespace Content.Shared.Construction.EntitySystems;
/// <summary>
/// Prevents anchoring an item in the same tile as an item matching the <see cref="EntityWhitelist"/>.
/// <seealso cref="BlockAnchorOnComponent"/>
/// </summary>
public sealed class BlockAnchorOnSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _xform = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BlockAnchorOnComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
SubscribeLocalEvent<BlockAnchorOnComponent, AnchorAttemptEvent>(OnAnchorAttempt);
}
/// <summary>
/// Handles the <see cref="AnchorStateChangedEvent"/>.
/// </summary>
private void OnAnchorStateChanged(Entity<BlockAnchorOnComponent> ent, ref AnchorStateChangedEvent args)
{
if (!args.Anchored)
return;
if (!HasOverlap((ent, ent.Comp, Transform(ent))))
return;
_popup.PopupPredicted(Loc.GetString("anchored-already-present"), ent, null);
_xform.Unanchor(ent, Transform(ent));
}
/// <summary>
/// Handles the <see cref="AnchorAttemptEvent"/>.
/// </summary>
private void OnAnchorAttempt(Entity<BlockAnchorOnComponent> ent, ref AnchorAttemptEvent args)
{
if (args.Cancelled)
return;
if (!HasOverlap((ent, ent.Comp, Transform(ent))))
return;
_popup.PopupPredicted(Loc.GetString("anchored-already-present"), ent, args.User);
args.Cancel();
}
/// <summary>
/// Check if there is any anchored overlap with non whitelisted or blacklisted entities.
/// </summary>
/// <returns>True if there is, false if there isn't</returns>
private bool HasOverlap(Entity<BlockAnchorOnComponent, TransformComponent> ent)
{
if (ent.Comp2.GridUid is not { } grid || !TryComp<MapGridComponent>(grid, out var gridComp))
return false;
var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates);
var enumerator = _map.GetAnchoredEntitiesEnumerator(grid, gridComp, indices);
while (enumerator.MoveNext(out var otherEnt))
{
// Don't match yourself.
if (otherEnt == ent)
continue;
if (!_whitelist.CheckBoth(otherEnt, ent.Comp1.Blacklist, ent.Comp1.Whitelist))
return true;
}
return false;
}
}

View file

@ -22,4 +22,10 @@ public sealed partial class RoleTypePrototype : IPrototype
/// </summary>
[DataField]
public Color Color { get; private set; } = Color.FromHex("#eeeeee");
/// <summary>
/// A symbol used to represent the role type.
/// </summary>
[DataField]
public string Symbol = string.Empty;
}

View file

@ -0,0 +1,48 @@
using Content.Shared.NameModifier.EntitySystems;
using Content.Shared.Mind.Components;
namespace Content.Shared.Mind;
/// <summary>
/// Relays events raised on a mobs body to its mind and mind role entities.
/// Useful for events that should be raised both on the body and the mind.
/// </summary>
public abstract partial class SharedMindSystem : EntitySystem
{
public void InitializeRelay()
{
// for name modifiers that depend on certain mind roles
SubscribeLocalEvent<MindContainerComponent, RefreshNameModifiersEvent>(RelayRefToMind);
}
protected void RelayToMind<T>(EntityUid uid, MindContainerComponent component, T args) where T : class
{
var ev = new MindRelayedEvent<T>(args);
if (TryGetMind(uid, out var mindId, out var mindComp, component))
{
RaiseLocalEvent(mindId, ref ev);
foreach (var role in mindComp.MindRoles)
RaiseLocalEvent(role, ref ev);
}
}
protected void RelayRefToMind<T>(EntityUid uid, MindContainerComponent component, ref T args) where T : class
{
var ev = new MindRelayedEvent<T>(args);
if (TryGetMind(uid, out var mindId, out var mindComp, component))
{
RaiseLocalEvent(mindId, ref ev);
foreach (var role in mindComp.MindRoles)
RaiseLocalEvent(role, ref ev);
}
args = ev.Args;
}
}
[ByRefEvent]
public record struct MindRelayedEvent<TEvent>(TEvent Args);

View file

@ -19,7 +19,7 @@ using Robust.Shared.Utility;
namespace Content.Shared.Mind;
public abstract class SharedMindSystem : EntitySystem
public abstract partial class SharedMindSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly INetManager _net = default!;
@ -42,6 +42,8 @@ public abstract class SharedMindSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnReset);
SubscribeLocalEvent<MindComponent, ComponentStartup>(OnMindStartup);
SubscribeLocalEvent<MindComponent, EntityRenamedEvent>(OnRenamed);
InitializeRelay();
}
public override void Shutdown()

View file

@ -224,6 +224,26 @@ public sealed class PaperSystem : EntitySystem
return true;
}
/// <summary>
/// Copy any stamp information from one piece of paper to another.
/// </summary>
public void CopyStamps(Entity<PaperComponent?> source, Entity<PaperComponent?> target)
{
if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp))
return;
target.Comp.StampedBy = new List<StampDisplayInfo>(source.Comp.StampedBy);
target.Comp.StampState = source.Comp.StampState;
Dirty(target);
if (TryComp<AppearanceComponent>(target, out var appearance))
{
// delete any stamps if the stamp state is null
_appearance.SetData(target, PaperVisuals.Stamp, target.Comp.StampState ?? "", appearance);
}
}
public void SetContent(Entity<PaperComponent> entity, string content)
{
entity.Comp.Content = content;

View file

@ -114,6 +114,19 @@ namespace Content.Shared.Popups
/// </summary>
public abstract void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small);
/// <summary>
/// Variant of <see cref="PopupEntity(string, EntityUid, Filter, bool, PopupType)"/> for use with prediction.
/// The local client will show the popup to the recipient, and the server will show it to players in the filter.
/// If recipient is null, the local client will do nothing and the server will show the message to players in the filter.
/// </summary>
/// <param name="message">The message to display.</param>
/// <param name="uid">The entity to display the popup above.</param>
/// <param name="recipient">The client that will see this popup locally during prediction.</param>
/// <param name="filter">Filter for players that will see the popup from the server.</param>
/// <param name="recordReplay">If true, this pop-up will be considered as a globally visible pop-up that gets shown during replays.</param>
/// <param name="type">Used to customize how this popup should appear visually. See: <see cref="PopupType"/>.</param>
public abstract void PopupPredicted(string? message, EntityUid uid, EntityUid? recipient, Filter filter, bool recordReplay, PopupType type = PopupType.Small);
/// <summary>
/// Variant of <see cref="PopupPredicted(string?, EntityUid, EntityUid?, PopupType)"/> that displays <paramref name="recipientMessage"/>
/// to the recipient and <paramref name="othersMessage"/> to everyone else in PVS range.

View file

@ -45,6 +45,12 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent
/// </summary>
[DataField]
public ProtoId<JobPrototype>? JobPrototype { get; set; }
/// <summary>
/// Used to order the characters on by role/antag status. Highest numbers are shown first.
/// </summary>
[DataField]
public int SortWeight;
}
// Why does this base component actually exist? It does make auto-categorization easy, but before that it was useless?

View file

@ -211,20 +211,30 @@ public abstract class SharedRoleSystem : EntitySystem
return true;
}
/// <summary>
/// Return the most recently specified role type, or Neutral
/// </summary>
private ProtoId<RoleTypePrototype> GetRoleTypeByTime(MindComponent mind)
{
// If any Mind Roles specify a Role Type, return the most recent. Otherwise return Neutral
var role = GetRoleCompByTime(mind);
return role?.Comp?.RoleType ?? "Neutral";
}
var roles = new List<ProtoId<RoleTypePrototype>>();
/// <summary>
/// Return the most recently specified role type's mind role entity, or null
/// </summary>
public Entity<MindRoleComponent>? GetRoleCompByTime(MindComponent mind)
{
var roles = new List<Entity<MindRoleComponent>>();
foreach (var role in mind.MindRoles)
{
var comp = Comp<MindRoleComponent>(role);
if (comp.RoleType is not null)
roles.Add(comp.RoleType.Value);
roles.Add((role, comp));
}
ProtoId<RoleTypePrototype> result = (roles.Count > 0) ? roles.LastOrDefault() : "Neutral";
Entity<MindRoleComponent>? result = roles.Count > 0 ? roles.LastOrDefault() : null;
return (result);
}

View file

@ -0,0 +1,28 @@
using Content.Shared.Storage.EntitySystems;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Storage.Components;
/// <summary>
/// Entities with this component will eject all items that match the whitelist / blacklist when anchored.
/// It also doesn't allow any items to be inserted that fit the whitelist / blacklist while anchored.
/// </summary>
/// <example>
/// If you have a smuggler stash that has a player inside of it, you want to eject the player before its anchored so they don't get stuck
/// </example>
[RegisterComponent, NetworkedComponent, Access(typeof(AnchoredStorageFilterSystem))]
public sealed partial class AnchoredStorageFilterComponent : Component
{
/// <summary>
/// If not null, entities that do not match this whitelist will be ejected.
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
/// <summary>
/// If not null, entities that match this blacklist will be ejected..
/// </summary>
[DataField]
public EntityWhitelist? Blacklist;
}

View file

@ -0,0 +1,54 @@
using Content.Shared.Storage.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
namespace Content.Shared.Storage.EntitySystems;
/// <summary>
/// Ejects items that do not match a <see cref="EntityWhitelist"/> from a storage when it is anchored.
/// <seealso cref="AnchoredStorageFilterComponent"/>
/// </summary>
public sealed class AnchoredStorageFilterSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AnchoredStorageFilterComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
SubscribeLocalEvent<AnchoredStorageFilterComponent, ContainerIsInsertingAttemptEvent>(OnInsertAttempt);
}
/// <summary>
/// Handles the <see cref="AnchorStateChangedEvent"/>.
/// </summary>
private void OnAnchorStateChanged(Entity<AnchoredStorageFilterComponent> ent, ref AnchorStateChangedEvent args)
{
if (!args.Anchored)
return;
if (!TryComp<StorageComponent>(ent, out var storage))
return;
foreach (var item in storage.StoredItems.Keys)
{
if (!_whitelist.CheckBoth(item, ent.Comp.Blacklist, ent.Comp.Whitelist))
_container.RemoveEntity(ent, item);
}
}
/// <summary>
/// Handles the <see cref="ContainerIsInsertingAttemptEvent"/>.
/// </summary>
private void OnInsertAttempt(Entity<AnchoredStorageFilterComponent> ent, ref ContainerIsInsertingAttemptEvent args)
{
if (args.Cancelled)
return;
if (Transform(ent).Anchored && !_whitelist.CheckBoth(args.EntityUid, ent.Comp.Blacklist, ent.Comp.Whitelist))
args.Cancel();
}
}

View file

@ -918,5 +918,56 @@ Entries:
id: 113
time: '2025-03-21T23:21:32.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35994
- author: Errant
changes:
- message: The Antag column on the player list has been removed, and it's functionality
folded into the Character and Role Type columns.
type: Remove
- message: The Role Type column now sorts entries in a revised order. All antagonist
types are grouped at the end.
type: Tweak
- message: On the player list, the names of antagonist players are now prefixed
with an antag symbol, and (optionally) shown in the color of their current role
type. The default setting is to show the "standard antag dagger" symbol for
any type of antag, same as the ahelp relay. Optionally a distinct icon can be
shown for each role type for easier identification.
type: Tweak
- message: More playerlist/overlay settings can now be changed in Admin Options.
type: Tweak
id: 114
time: '2025-03-25T16:03:59.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35538
- author: ScarKy0
changes:
- message: The "Spawn here" verb now reliably transfers mind of the target to their
new body.
type: Fix
id: 115
time: '2025-03-25T18:40:54.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36080
- author: Errant
changes:
- message: Admin overlays of mobs/players in very close proximity no longer overlap
each other. They now form stacks of up to 3. If more than 3 overlay entries
are stacked, any additional entries will be drawn over the 3rd element of the
stack. These names are ordered by vertical position, but when the coordinates
match exactly (such as someone carrying a mothroach/pAI, or ghosts orbiting),
the top member of the stack is NOT guaranteed to be the most visible/interesting
element in the stack.
type: Add
- message: Ghosts near the mouse cursor no longer show up on the admin overlay,
including any ghosts in stacks near the mouse cursor.
type: Add
id: 116
time: '2025-03-25T20:15:22.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35622
- author: slarticodefast
changes:
- message: Added an antag control verb to create a paradox clone ghost role of a
selected player.
type: Add
id: 117
time: '2025-03-27T17:04:25.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36105
Name: Admin
Order: 1

View file

@ -1,58 +1,4 @@
Entries:
- author: keronshb
changes:
- message: Added Ethereal Jaunt! A Wizard Spell that turns you invisible and into
a ghost-like creature for escaping!
type: Add
id: 7607
time: '2024-11-13T23:36:37.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/33201
- author: CheddaCheez
changes:
- message: Fixed mimes being unable to break their vow more than once. Despicable!
type: Fix
id: 7608
time: '2024-11-14T16:56:22.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/33303
- author: PJB3005
changes:
- message: Borgs can now select their chassis type upon creation (construction or
job spawn), immediately giving borgs access to all chassis types.
type: Add
- message: Borg chassis types now come with built-in modules depending on the type,
so you can immediately do your job without help from science. Some upgrade modules
must still be installed later however.
type: Add
- message: Specialized chassis types have been removed from construction, as they
are no longer necessary.
type: Remove
- message: Borg unlock access is no longer determined by their chassis, it's always
science or command. This means the janitor can't unlock jani borgs anymore.
type: Remove
id: 7609
time: '2024-11-14T17:08:35.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32586
- author: thetolbean
changes:
- message: The Quartermaster's requisition digi-board can no longer be recycled.
type: Fix
id: 7610
time: '2024-11-15T06:54:53.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/33315
- author: RedBookcase
changes:
- message: Mixing up a Snow White no longer creates extra liquid out of thin air.
type: Fix
id: 7611
time: '2024-11-15T20:52:19.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/33331
- author: lzk228
changes:
- message: Added 10 seconds delay to Succumb action
type: Add
id: 7612
time: '2024-11-15T21:21:08.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/32985
- author: Beck Thompson
changes:
- message: Minor tweaks to clumsiness. Some of the timings and or noises have been
@ -3880,3 +3826,52 @@
id: 8106
time: '2025-03-24T23:55:17.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36062
- author: Velcroboy, ErhardSteinhauer, whatston3, MilonPL, Beck, ArtisticRoomba, ScarKy0
changes:
- message: Smuggler stashes! Will get spawned as a round start event and can also
be found in a hacked ClothesMate. Will have loot from a large random pool.
type: Add
- message: Smuggler stashes can now be bought from the syndicate uplink for 2TC.
type: Add
id: 8107
time: '2025-03-26T15:20:15.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/19460
- author: slarticodefast
changes:
- message: Ghosts of paradox clones now have a name modifier so they can be distinguished
by other ghosts.
type: Tweak
id: 8108
time: '2025-03-26T15:30:14.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35940
- author: slarticodefast
changes:
- message: Paradox clones of nuclear operatives and head revolutionaries now have
the corresponding faction icon and need to be killed for a crew major victory.
type: Fix
id: 8109
time: '2025-03-26T15:34:19.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35910
- author: slarticodefast
changes:
- message: Further improved item copying for paradox clones.
type: Tweak
id: 8110
time: '2025-03-26T16:13:03.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/35993
- author: Centronias
changes:
- message: Raw meatballs, like steaks, can now be cooked in the microwave or on
a grill.
type: Add
id: 8111
time: '2025-03-27T06:26:13.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36003
- author: SlamBamActionman
changes:
- message: Liltenhead! Thank you for over 2 years of update videos, and congrats
on the 100th video!
type: Add
id: 8112
time: '2025-03-27T17:19:36.0000000+00:00'
url: https://github.com/space-wizards/space-station-14/pull/36104

View file

@ -14,3 +14,6 @@ force_client_hud_version_watermark = true
[chat]
motd = "\n########################################################\n\n[font size=17]This is a test server. You can play with the newest changes to the game, but these [color=red]changes may not be final or stable[/color], and may be reverted. Please report bugs via our GitHub, forum, or community Discord.[/font]\n\n########################################################\n"
[debug]
pow3r_disable_parallel = false

View file

@ -6,6 +6,7 @@ admin-verb-make-nuclear-operative = Make target into a lone Nuclear Operative.
admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule.
admin-verb-make-head-rev = Make the target into a Head Revolutionary.
admin-verb-make-thief = Make the target into a thief.
admin-verb-make-paradox-clone = Create a Paradox Clone ghost role of the target.
admin-verb-make-changeling = Make the target into a changeling.
admin-verb-text-make-traitor = Make Traitor
@ -15,6 +16,7 @@ admin-verb-text-make-nuclear-operative = Make Nuclear Operative
admin-verb-text-make-pirate = Make Pirate
admin-verb-text-make-head-rev = Make Head Rev
admin-verb-text-make-thief = Make Thief
admin-verb-text-make-paradox-clone = Create Paradox Clone
admin-verb-text-make-changeling = Make Changeling

View file

@ -0,0 +1,8 @@
cmd-adminnotes-desc = Opens the admin notes panel of target player.
cmd-adminnotes-help = Usage: adminnotes <UserId OR Username>
cmd-adminnotes-wrong-target = Unable to find user '{$user}'.
cmd-adminnotes-args-error = Invalid arguments.
Usage: adminnotes <UserId OR Username>
cmd-adminnotes-hint = UserId OR Username

View file

@ -2,7 +2,6 @@
player-tab-username = Username
player-tab-character = Character
player-tab-job = Job
player-tab-antagonist = Antagonist
player-tab-roletype = Role Type
player-tab-playtime = Playtime
player-tab-show-disconnected = Show Disconnected
@ -11,3 +10,7 @@ player-tab-entry-tooltip = Playtime is displayed in days:hours:minutes.
player-tab-filter-line-edit-placeholder = Filter
player-tab-is-antag-yes = YES
player-tab-is-antag-no = NO
player-tab-character-name-antag-symbol = {$symbol} {$name}
player-tab-antag-prefix = 🗡

View file

@ -0,0 +1 @@
anchored-already-present = There's already something anchored here!

View file

@ -1,66 +1,118 @@
figurines-hop-1 = Papers, please.
figurines-hop-2 = You are fired.
figurines-hop-3 = BRB.
figurines-hop-4 = You can get AA if you fill out the form.
figurines-hop-5 = I was gone for two seconds...
figurines-passenger-1 = Insuls please.
figurines-passenger-2 = Call evac.
figurines-passenger-3 = HELP MAINTS!!
figurines-passenger-4 = I'm no tider.
figurines-passenger-5 = How much for a toolbelt?
figurines-greytider-1 = Man, this party stinks. I fucking hate these people.
figurines-greytider-2 = Uh-oh, who's lost their stunbaton?
figurines-greytider-3 = Robust.
figurines-greytider-4 = I'm not me without a toolbox.
figurines-greytider-5 = Grey tide station wide!
figurines-greytider-6 = Viva la revolution.
figurines-clown-1 = Honk!
figurines-clown-2 = Banana!
figurines-clown-3 = Soap!
figurines-clown-4 = HoP has one clown, HoS has the whole department.
figurines-clown-5 = Do I annoy you?
figurines-clown-6 = Can I have AA? Please?
figurines-clown-7 = I'm a clown, but you're the whole circus!
figurines-holoclown-1 = I'm helping my older brother.
figurines-holoclown-2 = Hello, officer!
figurines-holoclown-3 = Who are you calling blue?
figurines-holoclown-4 = Bleeding on the ground is a good look for you.
figurines-holoclown-5 = Pathetic.
figurines-holoclown-6 = It's not them you need to worry about; it's me.
figurines-holoclown-7 = What's so funny?
figurines-mime-1 = ...
figurines-mime-2 = ...
figurines-mime-3 = ....
figurines-mime-4 = .......
figurines-mime-5 = ................
figurines-mime-6 = ...........?
figurines-mime-7 = !!!
figurines-mime-8 = ....!
figurines-mime-9 = ???
figurines-musician-1 = Never gonna give you up!
figurines-musician-2 = Never gonna let you down!
figurines-musician-3 = Music is an art.
figurines-musician-4 = Thank you, I'll be here all night.
figurines-musician-5 = I'm a one man band.
figurines-boxer-1 = The first rule of Fight Club is...
figurines-boxer-2 = We settle this in the ring, alright?
figurines-boxer-3 = I. AM. THE. CHAMPION!!
figurines-boxer-4 = Don't look at me, he was shot, not punched.
figurines-boxer-4 = Don't look at me; he was shot, not punched.
figurines-boxer-5 = 1v1 me, captain.
figurines-captain-1 = Glory to NT!
figurines-captain-2 = How did I get hired? Yes.
figurines-captain-3 = The nuclear disk is secure. Where? Somewhere.
figurines-captain-4 = Where did my ID go?
figurines-captain-5 = Everything is under control.
figurines-captain-6 = The disk was in my bag last I checked.
figurines-captain-7 = The chain of command starts and ends with me.
figurines-captain-8 = It's hard being at the top.
figurines-hos-1 = Space law? What?
figurines-hos-2 = Shoot the clown.
figurines-hos-3 = Yes, I shot the clown. No, I don't regret it.
figurines-hos-4 = Clown is now KOS.
figurines-hos-5 = Armory is now open to the public!
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-warden-5 = Soap is now contraband.
figurines-warden-6 = You're going away for a long time, buddy.
figurines-detective-1 = The butler did it.
figurines-detective-2 = I need some whiskey after this.
figurines-detective-3 = Chameleon fibers? How did a chameleon get in here?
figurines-detective-4 = Go go gadget!
figurines-detective-5 = Of course I checked the door logs!
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-security-5 = Harmbaton? It sure as hell harms!
figurines-security-6 = Space law? Never heard of it.
figurines-security-7 = Random search! Hand it over.
figurines-security-8 = I love donuts.
figurines-security-9 = Greytide this, motherfucker.
figurines-security-10 = Do not resist.
figurines-lawyer-1 = Better Call Saul!
figurines-lawyer-2 = Objection!
figurines-lawyer-3 = Did you know that you have rights?
figurines-lawyer-4 = Space law says!
figurines-lawyer-5 = Sign the contract first.
figurines-cargotech-1 = DRAGON ON ATS!
figurines-cargotech-2 = I sold the station!
figurines-cargotech-3 = Brain bounty? I don't have a brain.
figurines-cargotech-4 = You're worth 3000 spesos. Congrats.
figurines-cargotech-5 = Vegetable bounty? Nobody eats those anyways.
figurines-cargotech-6 = WE ARE SECEDING!! ALL HAIL CARGONIA!!
figurines-salvage-1 = Megafauna? It was mega easy.
figurines-salvage-2 = We're lost. Anyone bring a GPS?
figurines-salvage-3 = Anyone have oxygen?
figurines-salvage-4 = I found a blood-red and e-sword!
figurines-salvage-5 = There's bears in space?
figurines-salvage-6 = Crusher? I barely know her!
figurines-qm-1 = Who stole the shuttle?
figurines-qm-2 = I won't approve the guns.
@ -69,91 +121,179 @@ figurines-qm-4 = One toys crate for ma fellow clown!
figurines-qm-5 = Time to spent all money on gambling.
figurines-qm-6 = Viva La Cargonia!
figurines-qm-7 = Fill the form.
figurines-qm-8 = Where'd all our money go?
figurines-qm-9 = 99% of gamblers quit right before they hit it big!
figurines-ce-1 = Everyone to the briefing!
figurines-ce-2 = Wire the solars!
figurines-ce-3 = How to setup the TEG?
figurines-ce-4 = SINGULOOSE!
figurines-ce-5 = TESLOOSE!
figurines-ce-6 = Power's out again.
figurines-engineer-1 = SINGULOOSE!
figurines-engineer-2 = TESLOOSE!
figurines-engineer-3 = What is AME?
figurines-engineer-4 = Free insuls at engineering
figurines-engineer-5 = Where'd the power go?
figurines-engineer-6 = Someone bombed medbay... again...
figurines-engineer-7 = Well, why don't you come and fix it?
figurines-atmostech-1 = I put plasma in distro.
figurines-atmostech-2 = I will burn you in a burn chamber.
figurines-atmostech-3 = Frezon...
figurines-atmostech-4 = Tritium...
figurines-atmostech-5 = Glory to Atmosia!
figurines-atmostech-6 = Distro? That's short for disposal.
figurines-atmostech-7 = TEG: Thermal Energy? Gone!
figurines-rd-1 = Blowing up all of the borgs!
figurines-rd-2 = Tier 3 arsenal? No way.
figurines-rd-3 = Now where did I leave my hardsuit...?
figurines-rd-4 = Now you're thinking with portals!
figurines-rd-5 = The cake is a lie!
figurines-rd-6 = The trait I look for in a scientist is expendability.
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-5 = Anyone seen an anomaly?
figurines-scientist-6 = The anomaly exploded!
figurines-cmo-1 = Suit sensors!
figurines-cmo-2 = Why do we have meth?
figurines-cmo-3 = Who drank all the chems?
figurines-cmo-4 = Desoxyephedrine? Sounds healthy.
figurines-cmo-5 = No, you're not getting my hypospray.
figurines-chemist-1 = Get your pills!
figurines-chemist-2 = We need to cook.
figurines-chemist-3 = I am the one who knocks!
figurines-chemist-4 = Say my name.
figurines-chemist-5 = 99.8% purity.
figurines-chemist-6 = Epinephrine? Didn't you say methamphetamine?
figurines-paramedic-1 = Insuls and tools!
figurines-paramedic-2 = I need AA for saving people!
figurines-paramedic-3 = SUIT SENSORS!!
figurines-paramedic-4 = I need the hypospray for saving people!
figurines-paramedic-5 = 14 dead in the clown's room.
figurines-doctor-1 = The patient is already dead!
figurines-doctor-2 = CLEAR!
figurines-doctor-3 = Saw makes BRRR.
figurines-doctor-4 = Just a week away...
figurines-doctor-5 = I knew it...
figurines-librarian-1 = One day while...
figurines-librarian-2 = Silence!
figurines-librarian-1 = Silence!
figurines-librarian-2 = One day while...
figurines-librarian-3 = Once upon a time...
figurines-librarian-4 = In a world where...
figurines-librarian-5 = It was a dark and stormy night...
figurines-librarian-6 = Long, long ago...
figurines-librarian-7 = As the story goes...
figurines-librarian-8 = Imagine, if you will...
figurines-librarian-9 = Long before time had a name...
figurines-librarian-10 = In a galaxy far, far away...
figurines-librarian-11 = As the old saying goes...
figurines-librarian-12 = Gather round...
figurines-librarian-13 = ...It's a tale as old as time...
figurines-librarian-14 = ...That's all she wrote.
figurines-chaplain-1 = Would you like to join my cul- I mean religion.
figurines-chaplain-2 = Gods make me a killing machine please!
figurines-chaplain-3 = God exists!
figurines-chaplain-4 = Those aren't blood runes, I drew them in crayon.
figurines-chaplain-5 = Anyone want to be sacrificed?
figurines-chaplain-6 = Vampires aren't real.
figurines-chef-1 = I swear it's not human meat.
figurines-chef-2 = More banana cream pies?
figurines-chef-3 = How does rotary sushi sound?
figurines-chef-4 = That'll be 1000 spesos
figurines-chef-5 = For here or to go?
figurines-chef-6 = Where'd Pun Pun go? No idea...
figurines-bartender-1 = Where's my monkey?
figurines-bartender-2 = Sec won't drink.
figurines-bartender-3 = I mixed a little something in there...
figurines-bartender-4 = The recipe? Plasma and vomit. Why?
figurines-bartender-5 = I need those toxins for my drinks, officer!
figurines-bartender-6 = Read the room.
figurines-bartender-7 = I've got a shotgun.
figurines-botanist-1 = I don't have any weed, officer!
figurines-botanist-2 = Dude, I see colors...
figurines-botanist-3 = Is it just me, or is that weed glowing?
figurines-botanist-4 = 50 more units of mutagen. That should be enough.
figurines-botanist-5 = More bananas for my favorite clown!
figurines-janitor-1 = Clown stole my soap. Again.
figurines-janitor-2 = Look at the signs, you idiot.
figurines-janitor-3 = I've never seen this much lube in my life.
figurines-janitor-4 = Another day, another spill.
figurines-janitor-5 = I'm not even paid for this.
figurines-janitor-6 = This blood wasn't evidence, right?
figurines-janitor-7 = My only friend is my mop.
figurines-janitor-8 = That better not be what I think it is...
figurines-janitor-9 = Another day, another body.
figurines-nukie-1 = I got the disk!
figurines-nukie-2 = Whiskey, Echo, Whiskey.
figurines-nukie-3 = The nuke makes boom.
figurines-nukie-4 = What's the code?
figurines-nukie-5 = Commander...? ...That's a balloon...
figurines-nukie-elite-1 = Not a word in nanotrasen.
figurines-nukie-elite-2 = THAT'S A KEG!
figurines-nukie-elite-3 = Guys are you alive?
figurines-nukie-elite-4 = Breach and clear!
figurines-nukie-elite-5 = Leave no survivors.
figurines-nukie-elite-6 = Good work, team.
figurines-nukie-commander-1 = GET DAT FUKKEN DISK!
figurines-nukie-commander-2 = Fuckin' flukies.
figurines-nukie-commander-3 = The syndicate sends its regards.
figurines-nukie-commander-4 = Failure is not an option.
figurines-nukie-commander-5 = Whoops.
figurines-footsoldier-1 = I'm an evil boy. Less boy every day, more evil every day.
figurines-footsoldier-2 = Who will you choose? Them or us? Us or them?
figurines-footsoldier-3 = Glory to the syndicate!
figurines-footsoldier-4 = Down with Nanotrasen!
figurines-footsoldier-5 = I'd rather die than join Nanotrasen.
figurines-wizard-1 = Ei Nath!
figurines-wizard-1 = Ei Nath!!
figurines-wizard-2 = Wehgardium Leviosa!
figurines-wizard-3 = Skidaddle skadoodle!
figurines-wizard-4 = FIREBALL!
figurines-space-dragon-1 = Fish will consume the station.
figurines-space-dragon-2 = Dragon de- Actually, nevermind.
figurines-space-dragon-3 = Crew is delicious.
figurines-space-dragon-4 = Don't you dare make sushi.
figurines-space-dragon-5 = This station ain't big enough for the two of us.
# figurines-queen
figurines-queen-1 = Our domain must grow.
figurines-queen-2 = The hive hungers.
figurines-queen-3 = We consume all.
figurines-queen-4 = We are the apex.
figurines-queen-5 = You're just biomass.
figurines-queen-6 = We must evolve.
figurines-rat-king-1 = Gimme some food, capiche?
figurines-rat-king-2 = Fugeddaboutit.
figurines-rat-king-3 = Whack 'em!
figurines-rat-king-4 = Let me give you an offer you can't refuse, capiche?
figurines-rat-king-5 = Nothing personal, capiche?
figurines-rat-king-6 = I run this station now, see? Nyeh!
figurines-rat-servant-1 = Capiche!
figurines-rat-servant-2 = Boss says!
figurines-rat-servant-3 = The boss wants a word with youse.
figurines-rat-servant-4 = Ay, I'm walkin' here!
figurines-rat-servant-5 = You get the chedda', then we talk.
figurines-mouse-1 = Piep!
figurines-mouse-2 = Squeak!
@ -166,6 +306,7 @@ figurines-mouse-7 = Heep!
figurines-slime-1 = Blyump.
figurines-slime-2 = Blimpuf?
figurines-slime-3 = Blump!
figurines-slime-4 = Squish!
figurines-hamlet-1 = Piep!
figurines-hamlet-2 = Squeak!

View file

@ -337,4 +337,17 @@ ui-options-censor-nudity = Censor character nudity
## Admin menu
ui-options-enable-classic-overlay = Revert antag overlay to classic mode
ui-options-admin-player-panel = Admin Menu Players List
ui-options-admin-playerlist-separate-symbols = Show separate symbols for each antag role type
ui-options-admin-playerlist-character-color = Color names of antagonist characters
ui-options-admin-playerlist-roletype-color = Color role types
ui-options-admin-overlay-title = Admin Overlay
ui-options-enable-classic-overlay = Revert overlay to classic mode
ui-options-enable-overlay-symbols = Add antag symbol to text
ui-options-enable-overlay-playtime = Show playtime
ui-options-enable-overlay-starting-job = Show starting job
ui-options-overlay-merge-distance = Stack merge distance
ui-options-overlay-ghost-fade-distance = Ghost overlay fade range from mouse
ui-options-overlay-ghost-hide-distance = Ghost overlay hide range from mouse

View file

@ -0,0 +1,4 @@
cmd-power_validate-desc = Validate power network state integrity
cmd-power_validate-help = Usage: power_validate
cmd-power_validate-error = Error while validating: { $err }
cmd-power_validate-success = Validation succeeded without error

View file

@ -466,3 +466,6 @@ uplink-business-card-desc = A business card that you can give to someone to demo
uplink-fake-mindshield-name = Fake Mindshield
uplink-fake-mindshield-desc = A togglable implant capable of mimicking the same transmissions a real mindshield puts out when on, tricking capable Heads-up displays into thinking you have a mindshield (Nanotrasen brand implanter not provided.)
uplink-smuggler-satchel-name = Smuggler's Satchel
uplink-smuggler-satchel-desc = A handy, suspicious looking satchel. Just flat enough to fit underneath floor tiles.

View file

@ -3,3 +3,5 @@ paradox-clone-round-end-agent-name = paradox clone
objective-issuer-paradox = [color=lightblue]Paradox[/color]
paradox-clone-role-greeting = A freak space-time anomaly has teleported you into another reality! Now you have to find your counterpart and kill and replace them. Only one of you two can survive.
paradox-clone-ghost-name-modifier = {$baseName} (clone)

View file

@ -119,4 +119,5 @@
ClothingUniformJumpskirtTacticool: 1
ToyFigurinePassenger: 1
ToyFigurineGreytider: 1
# DO NOT ADD MORE, USE UNIFORM DYING
ClothingBackpackSatchelSmugglerUnanchored: 1
# DO NOT ADD MORE, USE UNIFORM DYING

View file

@ -2331,3 +2331,16 @@
whitelist:
- Chef
- Mime
- type: listing
id: UplinkSmugglerSatchel
name: uplink-smuggler-satchel-name
description: uplink-smuggler-satchel-desc
productEntity: ClothingBackpackSatchelSmugglerUnanchored
discountCategory: usualDiscounts
discountDownTo:
Telecrystal: 1
cost:
Telecrystal: 2
categories:
- UplinkDeception

View file

@ -2,211 +2,211 @@
id: FigurinesHoP
values:
prefix: figurines-hop-
count: 2
count: 5
- type: localizedDataset
id: FigurinesPassenger
values:
prefix: figurines-passenger-
count: 2
count: 5
- type: localizedDataset
id: FigurinesGreytider
values:
prefix: figurines-greytider-
count: 5
count: 6
- type: localizedDataset
id: FigurinesClown
values:
prefix: figurines-clown-
count: 5
count: 7
- type: localizedDataset
id: FigurinesHoloClown
values:
prefix: figurines-holoclown-
count: 1
count: 7
- type: localizedDataset
id: FigurinesMime
values:
prefix: figurines-mime-
count: 5
count: 9
- type: localizedDataset
id: FigurinesMusician
values:
prefix: figurines-musician-
count: 2
count: 5
- type: localizedDataset
id: FigurinesBoxer
values:
prefix: figurines-boxer-
count: 4
count: 5
- type: localizedDataset
id: FigurinesCaptain
values:
prefix: figurines-captain-
count: 3
count: 8
- type: localizedDataset
id: FigurinesHoS
values:
prefix: figurines-hos-
count: 3
count: 5
- type: localizedDataset
id: FigurinesWarden
values:
prefix: figurines-warden-
count: 4
count: 6
- type: localizedDataset
id: FigurinesDetective
values:
prefix: figurines-detective-
count: 2
count: 5
- type: localizedDataset
id: FigurinesSecurity
values:
prefix: figurines-security-
count: 4
count: 10
- type: localizedDataset
id: FigurinesLawyer
values:
prefix: figurines-lawyer-
count: 2
count: 5
- type: localizedDataset
id: FigurinesCargoTech
values:
prefix: figurines-cargotech-
count: 3
count: 6
- type: localizedDataset
id: FigurinesSalvage
values:
prefix: figurines-salvage-
count: 1
count: 6
- type: localizedDataset
id: FigurinesQM
values:
prefix: figurines-qm-
count: 7
count: 9
- type: localizedDataset
id: FigurinesCE
values:
prefix: figurines-ce-
count: 5
count: 6
- type: localizedDataset
id: FigurinesEngineer
values:
prefix: figurines-engineer-
count: 3
count: 7
- type: localizedDataset
id: FigurinesAtmosTech
values:
prefix: figurines-atmostech-
count: 5
count: 7
- type: localizedDataset
id: FigurinesRD
values:
prefix: figurines-rd-
count: 2
count: 6
- type: localizedDataset
id: FigurinesScientist
values:
prefix: figurines-scientist-
count: 5
count: 6
- type: localizedDataset
id: FigurinesCMO
values:
prefix: figurines-cmo-
count: 2
count: 5
- type: localizedDataset
id: FigurinesChemist
values:
prefix: figurines-chemist-
count: 1
count: 6
- type: localizedDataset
id: FigurinesParamedic
values:
prefix: figurines-paramedic-
count: 2
count: 5
- type: localizedDataset
id: FigurinesDoctor
values:
prefix: figurines-doctor-
count: 3
count: 5
- type: localizedDataset
id: FigurinesLibrarian
values:
prefix: figurines-librarian-
count: 2
count: 14
- type: localizedDataset
id: FigurinesChaplain
values:
prefix: figurines-chaplain-
count: 3
count: 6
- type: localizedDataset
id: FigurinesChef
values:
prefix: figurines-chef-
count: 1
count: 6
- type: localizedDataset
id: FigurinesBartender
values:
prefix: figurines-bartender-
count: 2
count: 7
- type: localizedDataset
id: FigurinesBotanist
values:
prefix: figurines-botanist-
count: 2
count: 5
- type: localizedDataset
id: FigurinesJanitor
values:
prefix: figurines-janitor-
count: 2
count: 9
- type: localizedDataset
id: FigurinesNukie
values:
prefix: figurines-nukie-
count: 4
count: 5
- type: localizedDataset
id: FigurinesNukieElite
values:
prefix: figurines-nukie-elite-
count: 3
count: 6
- type: localizedDataset
id: FigurinesNukieCommander
values:
prefix: figurines-nukie-commander-
count: 1
count: 5
- type: localizedDataset
id: FigurinesFootsoldier
@ -218,31 +218,31 @@
id: FigurinesWizard
values:
prefix: figurines-wizard-
count: 1
count: 4
- type: localizedDataset
id: FigurinesSpaceDragon
values:
prefix: figurines-space-dragon-
count: 1
count: 5
# - type: localizedDataset # TODO add something
# id: FigurinesQueen
# values:
# prefix: figurines-queen-
# count: 0
- type: localizedDataset
id: FigurinesQueen
values:
prefix: figurines-queen-
count: 6
- type: localizedDataset
id: FigurinesRatKing
values:
prefix: figurines-rat-king-
count: 3
count: 6
- type: localizedDataset
id: FigurinesRatServant
values:
prefix: figurines-rat-servant-
count: 2
count: 5
- type: localizedDataset
id: FigurinesMouse
@ -254,7 +254,7 @@
id: FigurinesSlime
values:
prefix: figurines-slime-
count: 3
count: 4
- type: localizedDataset
id: FigurinesHamlet

View file

@ -0,0 +1,120 @@
- type: entity
abstract: true
id: BaseSubfloorAnchorStorage
components:
- type: Appearance
- type: SubFloorHide
- type: Anchorable
- type: CollideOnAnchor
- type: Transform
anchored: false
- type: AnchoredStorageFilter
blacklist:
components:
- HumanoidAppearance # for forks with felines
- type: BlockAnchorOn
blacklist:
components:
- AnchoredStorageFilter
- type: Visibility
layer: 1
- type: entity
abstract: true
parent: BaseSubfloorAnchorStorage
id: BaseSubfloorAnchorStorageAnchored
placement:
mode: SnapgridCenter
components:
- type: Transform
anchored: true
- type: Physics
canCollide: false
bodyType: Static
- type: entity
parent: [ BaseSubfloorAnchorStorageAnchored, ClothingBackpackSatchel, BaseMinorContraband ]
id: ClothingBackpackSatchelSmuggler
name: smuggler's satchel
suffix: Empty
description: A dingy, suspicious looking satchel.
components:
- type: Sprite
sprite: Clothing/Back/Satchels/smuggler.rsi
state: icon
- type: entity
parent: [ BaseSubfloorAnchorStorage, ClothingBackpackSatchel, BaseMinorContraband ]
id: ClothingBackpackSatchelSmugglerUnanchored
name: smuggler's satchel
suffix: Empty, Unanchored
description: A dingy, suspicious looking satchel.
components:
- type: Sprite
sprite: Clothing/Back/Satchels/smuggler.rsi
state: icon
- type: entity
parent: [ BaseSubfloorAnchorStorageAnchored, BriefcaseSyndie, BaseMinorContraband ]
id: BriefcaseSmugglerCash
name: smuggler's briefcase
suffix: Smuggler, Do Not Map
components:
- type: EntityTableContainerFill
containers:
storagebase: !type:AllSelector
children:
- id: SpaceCash5000
amount: !type:RangeNumberSelector
range: 1, 11
- type: entity
parent: [ BaseSubfloorAnchorStorageAnchored, ClothingBackpackDuffelClown, BaseMinorContraband ]
id: ClothingBackpackDuffelClownSmuggler
name: smuggler's clown duffel bag
suffix: Smuggler, Do Not Map
components:
- type: EntityTableContainerFill
containers:
storagebase: !type:AllSelector
children:
- id: SpeedLoaderCap
amount: !type:RangeNumberSelector
range: 4, 8
- !type:GroupSelector
children:
- id: RevolverCapGun
amount: !type:RangeNumberSelector
range: 4, 8
weight: 95
- id: RevolverCapGunFake
amount: !type:RangeNumberSelector
range: 1, 8
weight: 5
- type: entity
parent: ClothingBackpackSatchelSmuggler
id: ClothingBackpackSatchelSmugglerFilled
suffix: Smuggler, Do Not Map
components:
- type: Sprite
sprite: Clothing/Back/Satchels/smuggler.rsi
state: icon
- type: EntityTableContainerFill
containers:
storagebase: !type:NestedSelector
tableId: FillSmugglerBackpack
- type: entity
parent: MarkerBase
id: RandomSatchelSpawner
name: random smuggler's satchel spawner
suffix: Do Not Map
components:
- type: Sprite
layers:
- sprite: Clothing/Back/Satchels/smuggler.rsi
state: icon
- type: EntityTableSpawner
table: !type:NestedSelector
tableId: RandomSatchelTable

View file

@ -0,0 +1,671 @@
#Table
- type: entityTable
id: RandomSatchelTable
table: !type:GroupSelector
children:
- id: ClothingBackpackSatchelSmugglerFilled
weight: 85
- id: BriefcaseSmugglerCash
weight: 10
- id: ClothingBackpackDuffelClownSmuggler
weight: 5
#Table
- type: entityTable
id: FillSmugglerBackpack
table: !type:GroupSelector
children:
- !type:NestedSelector
tableId: RandomSatchelTable1
- !type:NestedSelector
tableId: RandomSatchelTable2
- !type:NestedSelector
tableId: RandomSatchelTable3
- !type:NestedSelector
tableId: RandomSatchelTable4
- !type:NestedSelector
tableId: RandomSatchelTable5
- !type:NestedSelector
tableId: RandomSatchelTable6
- type: entityTable
id: RandomSatchelTable1
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelGenericTable
- !type:NestedSelector
tableId: RandomSatchelFunnyTable
- !type:NestedSelector
tableId: RandomSatchelClothingTable
- !type:NestedSelector
tableId: RandomSatchelCannabisTable
- !type:NestedSelector
tableId: RandomSatchelGizmosTable
- !type:NestedSelector
tableId: RandomSatchelChemsTable
- type: entityTable
id: RandomSatchelFunnyTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 20
- id: WhoopieCushion
- id: RubberChicken
- id: PlasticBanana
amount: !type:RangeNumberSelector
range: 1, 5
- id: PillSpaceDrugs
amount: !type:RangeNumberSelector
range: 1, 2
- id: StrangePill
amount: !type:RangeNumberSelector
range: 1, 5
- type: entityTable
id: RandomSatchelCannabisTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 4
- id: Joint
amount: !type:RangeNumberSelector
range: 1, 5
- id: Blunt
amount: !type:RangeNumberSelector
range: 1, 5
- id: SmokingPipeFilledCannabis
amount: !type:RangeNumberSelector
range: 1, 5
- id: GroundCannabis
amount: !type:RangeNumberSelector
range: 1, 15
- type: entityTable
id: RandomSatchelGizmosTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 40
- id: TimerTrigger
amount: !type:RangeNumberSelector
range: 1, 2
- id: SignalTrigger
amount: !type:RangeNumberSelector
range: 1, 2
- id: VoiceTrigger
amount: !type:RangeNumberSelector
range: 1, 2
- id: ProximitySensor
- type: entityTable
id: RandomSatchelChemsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 20
- id: ChemistryBottleUnstableMutagen
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleLeft4Zed
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleEZNutrient
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleRobustHarvest
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleEpinephrine
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleEphedrine
amount: !type:RangeNumberSelector
range: 1, 5
- id: ChemistryBottleOmnizine
- id: ChemistryBottleCognizine
- id: ChemistryBottleToxin
- id: ChemistryBottleNocturine
- id: VestineChemistryVial
- type: entityTable
id: RandomSatchelTable2
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelTobaccoTable
- !type:NestedSelector
tableId: RandomSatchelPartyTable
- !type:NestedSelector
tableId: RandomSatchelClothingTable
- !type:NestedSelector
tableId: RandomSatchelPayloadTable
- !type:NestedSelector
tableId: RandomSatchelCircuitboardsTable
- type: entityTable
id: RandomSatchelTobaccoTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 8
- id: CigPackSyndicate
weight: 0.5
- id: CigCartonGreen
- id: CigCartonRed
- id: CigCartonGreen
- id: CigCartonBlack
- id: CigarCase
amount: !type:RangeNumberSelector
range: 1, 2
- id: CigarGoldCase
weight: 0.25
amount: !type:RangeNumberSelector
range: 1, 2
- type: entityTable
id: RandomSatchelPartyTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 2
- id: GlowstickBase
amount: !type:RangeNumberSelector
range: 1, 5
- id: GlowstickRed
amount: !type:RangeNumberSelector
range: 1, 5
- id: GlowstickPurple
amount: !type:RangeNumberSelector
range: 1, 5
- id: GlowstickYellow
amount: !type:RangeNumberSelector
range: 1, 5
- id: GlowstickBlue
amount: !type:RangeNumberSelector
range: 1, 5
- type: entityTable
id: RandomSatchelClothingTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 3
- id: ClothingEyesGlassesOutlawGlasses
- id: ClothingEyesEyepatch
- id: ClothingHandsGlovesNitrile
- id: ClothingHeadHatOutlawHat
- id: ClothingMaskItalianMoustache
- id: ClothingHandsGlovesCombat
- id: ClothingNeckScarfStripedSyndieRed
- type: entityTable
id: RandomSatchelPayloadTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 45
- id: FlashPayload
amount: !type:RangeNumberSelector
range: 1, 2
- id: ChemicalPayload
amount: !type:RangeNumberSelector
range: 1, 2
- id: ExplosivePayload
amount: !type:RangeNumberSelector
range: 1, 2
- type: entityTable
id: RandomSatchelCircuitboardsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 15
- id: ChemDispenserMachineCircuitboard
- id: SyndicateMicrowaveMachineCircuitboard
- id: HydroponicsTrayMachineCircuitboard
- id: DawInstrumentMachineCircuitboard
- id: PortableGeneratorPacmanMachineCircuitboard
- id: PortableGeneratorSuperPacmanMachineCircuitboard
- id: HellfireFreezerMachineCircuitBoard
- id: HellfireHeaterMachineCircuitBoard
- id: ReagentGrinderMachineCircuitboard
- id: ReagentGrinderIndustrialMachineCircuitboard
- id: BoozeDispenserMachineCircuitboard
- id: MiniGravityGeneratorCircuitboard
- id: AmmoTechFabCircuitboard
- id: CryoPodMachineCircuitboard
- id: PowerCageRechargerCircuitboard
- id: ShuttleConsoleCircuitboard
- id: TurboItemRechargerCircuitboard
- type: entityTable
id: RandomSatchelTable3
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelPresentsOrToysTable
- !type:NestedSelector
tableId: RandomSatchelCashTable
- !type:NestedSelector
tableId: RandomSatchelWeaponTable
- !type:NestedSelector
tableId: RandomSatchelBurgerTable
- !type:NestedSelector
tableId: RandomSatchelGenericTable
- !type:NestedSelector
tableId: RandomSatchelKeysTable
- type: entityTable
id: RandomSatchelPresentsOrToysTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 5
- id: PresentRandom
amount: !type:RangeNumberSelector
range: 1, 3
- id: ToyFigurineHamlet
- id: ToyFigurineSpaceDragon
- id: ToyFigurineQueen
- id: ToyFigurineRatKing
- type: entityTable
id: RandomSatchelCashTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 2
amount: !type:RangeNumberSelector
range: 1, 4
- id: SpaceCash1000
weight: 2
amount: !type:RangeNumberSelector
range: 1, 4
- id: SpaceCash2500
amount: !type:RangeNumberSelector
range: 1, 4
- id: SpaceCash5000
weight: 0.25
amount: !type:RangeNumberSelector
range: 1, 4
- id: SpaceCash10000
weight: 0.005
amount: !type:RangeNumberSelector
range: 1, 4
- id: SpaceCash1000000
prob: 0.0001
- id: SpaceCash
weight: 0.01
amount: !type:RangeNumberSelector
range: 1, 10
- type: entityTable
id: RandomSatchelWeaponTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 100
- id: Katana
- id: ThrowingStar
amount: !type:RangeNumberSelector
range: 1, 5
- type: entityTable
id: RandomSatchelBurgerTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 10
- id: FoodBurgerAppendix
amount: !type:RangeNumberSelector
range: 1, 5
- id: FoodBurgerEmpowered
amount: !type:RangeNumberSelector
range: 1, 5
- id: FoodBurgerClown
amount: !type:RangeNumberSelector
range: 1, 5
- id: FoodBurgerGhost
amount: !type:RangeNumberSelector
range: 1, 5
- type: entityTable
id: RandomSatchelGenericTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 15
- id: RemoteSignaller
- id: PersonalAI
- id: WeaponFlareGun
- id: ModularReceiver
- id: RifleStock
- id: DrinkSpaceGlue
amount: !type:RangeNumberSelector
range: 1, 2
- id: DrinkSpaceLube
amount: !type:RangeNumberSelector
range: 1, 2
- id: CrazyGlue
amount: !type:RangeNumberSelector
range: 1, 2
- type: entityTable
id: RandomSatchelKeysTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 50
- id: EncryptionKeyCommon
- id: EncryptionKeyCargo
- id: EncryptionKeyService
- id: EncryptionKeyRobo
- id: EncryptionKeyScience
- id: EncryptionKeyMedical
- id: EncryptionKeyEngineering
- id: EncryptionKeySecurity
weight: 0.5
- id: EncryptionKeyCommand
weight: 0.25
- id: EncryptionKeyStationMaster
weight: 0.01
- type: entityTable
id: RandomSatchelTable4
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelMaterialsTable
- !type:NestedSelector
tableId: RandomSatchelImplantersTable
- !type:NestedSelector
tableId: RandomSatchelCellsTable
- !type:NestedSelector
tableId: RandomSatchelSyndicateTable
- !type:NestedSelector
tableId: RandomSatchelToolsTable
- type: entityTable
id: RandomSatchelMaterialsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 6
- !type:GroupSelector
children:
- id: MaterialDiamond1
- !type:GroupSelector
children:
- id: MaterialBananium1
weight: 2
- id: MaterialBananium
- !type:GroupSelector
children:
- id: IngotGold1
weight: 2
- id: IngotGold
- !type:GroupSelector
children:
- id: IngotSilver1
weight: 2
- id: IngotSilver
- !type:GroupSelector
children:
- id: SheetPlasma1
weight: 2
- id: SheetPlasma10
- id: SheetPlasma
weight: 0.50
- !type:GroupSelector
children:
- id: SheetUranium1
weight: 2
- id: SheetUranium
- type: entityTable
id: RandomSatchelImplantersTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 3
- id: LightImplanter
- id: BikeHornImplanter
- id: SadTromboneImplanter
- type: entityTable
id: RandomSatchelCellsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 15
- id: PowerCellHyper
- id: PowerCellMicroreactor
- id: PowerCellHigh
- type: entityTable
id: RandomSatchelSyndicateTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 50
- id: Telecrystal1
amount: !type:RangeNumberSelector
range: 1, 3
- id: GatfruitSeeds
- id: ToySword
- id: NukeDiskFake
- id: RadioJammer
- id: SoapSyndie
- id: SingularityToy
- type: entityTable
id: RandomSatchelToolsTable
table: !type:GroupSelector
children:
- id: Crowbar
- id: Multitool
- id: ClothingHandsGlovesColorYellow
- id: Screwdriver
- id: ClothingHeadHatWeldingMaskFlame
- id: WelderExperimental
weight: 0.50
- type: entityTable
id: RandomSatchelTable5
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelAlcoholTable
- !type:NestedSelector
tableId: RandomSatchelInstrumentTable
- !type:NestedSelector
tableId: RandomSatchelMedsTable
- !type:NestedSelector
tableId: RandomSatchelMysteriesTable
- type: entityTable
id: RandomSatchelAlcoholTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 5
- id: DrinkCognacBottleFull
amount: !type:RangeNumberSelector
range: 1, 4
- id: DrinkGildlagerBottleFull
amount: !type:RangeNumberSelector
range: 1, 4
- id: DrinkPatronBottleFull
amount: !type:RangeNumberSelector
range: 1, 4
- id: DrinkRumBottleFull
amount: !type:RangeNumberSelector
range: 1, 4
- type: entityTable
id: RandomSatchelInstrumentTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 15
- id: SeashellInstrument
- id: MusicalLungInstrument
- id: HelicopterInstrument
- id: GunpetInstrument
- id: RockGuitarInstrument
- id: BassGuitarInstrument
- type: entityTable
id: RandomSatchelMedsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 10
- id: SyringeAmbuzol
- id: SyringeHyronalin
- id: SyringeDermaline
- id: SyringeBicaridine
- id: SyringeTranexamicAcid
- id: SyringeInaprovaline
- id: SyringeEphedrine
- id: Gauze
- id: Bloodpack
- id: RegenerativeMesh
- id: MedicatedSuture
- id: EmergencyMedipen
amount: !type:RangeNumberSelector
range: 1, 5
- id: AntiPoisonMedipen
amount: !type:RangeNumberSelector
range: 1, 5
- id: BruteAutoInjector
amount: !type:RangeNumberSelector
range: 1, 5
- id: BurnAutoInjector
amount: !type:RangeNumberSelector
range: 1, 5
- id: SpaceMedipen
amount: !type:RangeNumberSelector
range: 1, 5
- id: Stimpack
- id: CombatMedipen
- type: entityTable
id: RandomSatchelMysteriesTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 15
- id: EggSpider
weight: 5
- id: ArtifactFragment1
amount: !type:RangeNumberSelector
range: 1, 2
weight: 10
- id: AnomalyCorePyroclastic
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreGravity
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreIce
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreFlesh
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreLiquid
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreBluespace
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreElectricity
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreFlora
amount: !type:RangeNumberSelector
range: 1, 3
- id: AnomalyCoreShadow
amount: !type:RangeNumberSelector
range: 1, 3
- type: entityTable
id: RandomSatchelTable6
table: !type:AllSelector
children:
- !type:NestedSelector
tableId: RandomSatchelGearTable
- !type:NestedSelector
tableId: RandomSatchelGadgetsTable
- !type:NestedSelector
tableId: CubeTable
- type: entityTable
id: RandomSatchelGearTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 8
- id: JetpackMiniFilled
- id: HandheldGPSBasic
- id: WelderIndustrialAdvanced
- id: HandheldStationMap
- id: PinpointerStation
- type: entityTable
id: RandomSatchelGadgetsTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 30
- id: HolofanProjector
- id: HoloprojectorField
- id: HoloprojectorSecurity
- id: DeviceQuantumSpinInverter
amount: !type:ConstantNumberSelector
value: 2
- id: SpectralLocator
- id: ArabianLamp
weight: 0.50
- id: ChameleonProjector
weight: 0.25
- type: entityTable
id: CubeTable
table: !type:GroupSelector
children:
- id: SpaceCash100
weight: 8
- id: MonkeyCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: KoboldCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: CowCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: GoatCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: MothroachCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: MouseCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: CockroachCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: SpaceCarpCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: SpaceTickCube
amount: !type:RangeNumberSelector
range: 1, 10
- id: AbominationCube
amount: !type:RangeNumberSelector
range: 1, 10

View file

@ -160,4 +160,5 @@
- PosterLegitSafetyMothHardhat
- PosterLegitSafetyMothSSD
- PosterLegitOppenhopper
- PosterLegitTyrone
chance: 1

View file

@ -4,6 +4,8 @@
# The datafields of the components are only shallow copied using CopyComp.
# Subscribe to CloningEvent instead if that is not enough.
# for basic traits etc.
# used by the random clone spawner
- type: cloningSettings
id: BaseClone
components:
@ -59,15 +61,19 @@
components:
- AttachedClothing # helmets, which are part of the suit
- HumanoidAppearance # will cause problems for downstream felinids getting cloned as Urists
- Implanter # they will spawn full again, but you already get the implant. And we can't do item slot copying yet
- VirtualItem
# all antagonist roles
- type: cloningSettings
id: Antag
parent: BaseClone
components:
- HeadRevolutionary
- Revolutionary
- NukeOperative
# for cloning pods
- type: cloningSettings
id: CloningPod
parent: Antag

View file

@ -664,6 +664,9 @@
- Meat
- type: Sprite
state: meatball
- type: Construction
graph: MeatMeatballCooked
node: start
- type: entity
name: slimeball
@ -1178,6 +1181,9 @@
entries:
Burger: MeatBall
Taco: MeatBall
- type: Construction
graph: MeatMeatballCooked
node: meatball cooked
- type: entity
name: boiled snail

View file

@ -18,7 +18,7 @@
tags:
- Figurine
- type: UseDelay
delay: 10
delay: 5
- type: TriggerOnActivate
- type: TriggerOnSignal
- type: Speech

View file

@ -1078,6 +1078,14 @@
- type: Sprite
state: poster53_legit
- type: entity
parent: PosterBase
id: PosterLegitTyrone
name: "Tyrone's Guide to Space"
description: "A poster advertising online schooling about space. The classes listed seem to cover things from the basic usage of station equipment to complicated subjects like creating pipebombs or covering entire hallways in spacelube. A disclaimer reads \"It's never THAT bad, and at the end you might even get a tortilla.\""
components:
- type: Sprite
state: poster54_legit
#maps

View file

@ -34,8 +34,6 @@
- type: Damageable
damageContainer: StructuralInorganic
damageModifierSet: PerforatedMetallic
- type: PowerConsumer
showInMonitor: false
- type: Electrified
requirePower: true
noWindowInTile: true

View file

@ -655,6 +655,19 @@
blacklist:
- External # don't space everything
- type: entity
parent: BaseGameRule
id: SmugglerStashVariationPass
components:
- type: StationEvent
earliestStart: 0
duration: 1
minimumPlayers: 1
maxOccurrences: 2
weight: 10
- type: RandomSpawnRule
prototype: RandomSatchelSpawner
- type: entity
parent: BaseGameRule
id: DerelictCyborgSpawn

View file

@ -478,3 +478,5 @@
- id: BloodbathPuddleMessVariationPass
prob: 0.01
orGroup: puddleMess
- id: SmugglerStashVariationPass
prob: 0.90

View file

@ -667,6 +667,7 @@
production: 6
yield: 2
potency: 20
growthStages: 3
idealLight: 9
idealHeat: 298
chemicals:

View file

@ -539,6 +539,9 @@
metamorphicFillBaseName: fill-
metamorphicChangeColor: false
#Antifreeze as a drink is a bright blue color while it is an orangish red otherwise.
#As a result this sprite doesn't need changing.
- type: reagent
id: Antifreeze
name: reagent-name-antifreeze

View file

@ -0,0 +1,16 @@
- type: constructionGraph
id: MeatMeatballCooked
start: start
graph:
- node: start
edges:
- to: meatball cooked
completed:
- !type:PlaySound
sound: /Audio/Effects/sizzle.ogg
steps:
- minTemperature: 335
- node: meatball cooked
entity: FoodMeatMeatballCooked

View file

@ -15,6 +15,7 @@
antag: true
antagPrototype: GenericAntagonist
roleType: SoloAntagonist
sortWeight: 50
#Observer
- type: entity
@ -23,6 +24,8 @@
name: Observer Role
components:
- type: ObserverRole
- type: MindRole
sortWeight: -10
#Ghost Roles
- type: entity
@ -48,6 +51,7 @@
- type: MindRole
roleType: FreeAgent
antagPrototype: GenericFreeAgent
sortWeight: 30
- type: entity
parent: MindRoleGhostRoleNeutral
@ -56,6 +60,7 @@
components:
- type: MindRole
roleType: FreeAgent
sortWeight: 0 # Maybe 10?
- type: entity
parent: MindRoleGhostRoleNeutral
@ -73,6 +78,7 @@
- type: MindRole
roleType: SiliconAntagonist
antagPrototype: GenericSiliconAntagonist
sortWeight: 30
- type: entity
parent: [ BaseMindRoleAntag, MindRoleGhostRoleNeutral ]
@ -94,6 +100,9 @@
parent: MindRoleGhostRoleTeamAntagonist
id: MindRoleGhostRoleTeamAntagonistFlock
name: Ghost Role (Team Antagonist)
components:
- type: MindRole
sortWeight: 40
# The Job MindRole holds the mob's Job prototype
- type: entity

View file

@ -5,34 +5,40 @@
id: Neutral
name: role-type-crew-aligned-name
color: '#eeeeee'
symbol: "🗡" # Should never be antag, but just in case.
- type: roleType
id: SoloAntagonist
name: role-type-solo-antagonist-name
color: '#d82000'
symbol: "🗡"
- type: roleType
id: TeamAntagonist
name: role-type-team-antagonist-name
color: '#d82000'
symbol: "⚔"
- type: roleType
id: FreeAgent
name: role-type-free-agent-name
color: '#ffff00'
symbol: "☯"
- type: roleType
id: Familiar
name: role-type-familiar-name
color: '#6495ed'
symbol: "🗡" # Should never be antag, but just in case.
- type: roleType
id: Silicon
name: role-type-silicon-name
color: '#6495ed'
symbol: "🗡" # Should never be antag, but just in case.
- type: roleType
id: SiliconAntagonist
name: role-type-silicon-antagonist-name
color: '#c832e6'
symbol: "⛞"

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

View file

@ -0,0 +1,26 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/a8056c6ba7f5367934ef829116e57d743226e1f0",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "equipped-BACKPACK",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,7 +1,8 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/master/icons/mob/huds/hud.dmi | Pilot icon made by poemota (Discord)",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/blob/e71d6c4fba5a51f99b81c295dcaec4fc2f58fb19/icons/mob/screen1.dmi | Brigmedic icon made by PuroSlavKing (Github) | Zombie icon made by RamZ | Zookeper by netwy (discort) | Rev and Head Rev icon taken from https://tgstation13.org/wiki/HUD and edited by coolmankid12345 (Discord) | Mindshield icon taken from https://github.com/tgstation/tgstation/blob/ce6beb8a4d61235d9a597a7126c407160ed674ea/icons/mob/huds/hud.dmi | Admin recolored from MedicalIntern by TsjipTsjip | StationAi resprite to 8x8 size by lunarcomets | Service Worker resprite by anno_midi (Discord) and spanky-spanky (Github) | service icons darkened by frobnic8 (Discord and Github)",
"size": {
"x": 8,
"y": 8
@ -203,6 +204,9 @@
{
"name": "Admin"
},
{
"name": "ParadoxClone"
},
{
"name": "Changeling"
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a, inhands by mubururu_ (github)",
"copyright": "Taken from https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a, inhands by mubururu_ (github), Growth stages, harvest, dead, and produce sprites created by Chaoticaa (GitHub)",
"size": {
"x": 32,
"y": 32

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 824 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/commit/1dbcf389b0ec6b2c51b002df5fef8dd1519f8068, inhands by mubururu_ (github)",
"copyright": "Taken from https://github.com/vgstation-coders/vgstation13/commit/1dbcf389b0ec6b2c51b002df5fef8dd1519f8068, inhands by mubururu_ (github), Growth stages, harvest, dead, and produce sprites created by Chaoticaa (GitHub)",
"size": {
"x": 32,
"y": 32

Some files were not shown because too many files have changed in this diff Show more