Compare commits

..

5 Commits

Author SHA1 Message Date
Ed
bb0f7c9632 Update meta.json 2024-09-29 12:41:22 +03:00
Ed
4d8d5d5c82 Update weather.yml 2024-09-29 12:37:34 +03:00
Ed
60ca78ed9b maps update 2024-09-29 12:28:00 +03:00
Ed
beb681ce7b mist weather 2024-09-29 12:27:51 +03:00
Ed
752d9b8ff4 lamppost 2024-09-29 12:27:25 +03:00
2510 changed files with 184190 additions and 171762 deletions

2
.github/CODEOWNERS vendored
View File

@@ -19,7 +19,7 @@
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer /Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/Prototypes/Maps/** @Emisse /Resources/Prototypes/Maps/ @Emisse
/Resources/Prototypes/Body/ @DrSmugleaf # suffering /Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf /Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf

View File

@@ -5,8 +5,8 @@ concurrency:
on: on:
workflow_dispatch: workflow_dispatch:
# schedule: schedule:
# - cron: '0 10 * * *' - cron: '0 10 * * *'
jobs: jobs:
build: build:

View File

@@ -23,7 +23,6 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
{ {
private readonly IEntityManager _entManager; private readonly IEntityManager _entManager;
private readonly SpriteSystem _spriteSystem; private readonly SpriteSystem _spriteSystem;
private readonly SharedNavMapSystem _navMapSystem;
private EntityUid? _owner; private EntityUid? _owner;
private NetEntity? _trackedEntity; private NetEntity? _trackedEntity;
@@ -43,32 +42,19 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
private const float SilencingDuration = 2.5f; private const float SilencingDuration = 2.5f;
// Colors
private Color _wallColor = new Color(64, 64, 64);
private Color _tileColor = new Color(28, 28, 28);
private Color _monitorBlipColor = Color.Cyan;
private Color _untrackedEntColor = Color.DimGray;
private Color _regionBaseColor = new Color(154, 154, 154);
private Color _inactiveColor = StyleNano.DisabledFore;
private Color _statusTextColor = StyleNano.GoodGreenFore;
private Color _goodColor = Color.LimeGreen;
private Color _warningColor = new Color(255, 182, 72);
private Color _dangerColor = new Color(255, 67, 67);
public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner) public AtmosAlertsComputerWindow(AtmosAlertsComputerBoundUserInterface userInterface, EntityUid? owner)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_entManager = IoCManager.Resolve<IEntityManager>(); _entManager = IoCManager.Resolve<IEntityManager>();
_spriteSystem = _entManager.System<SpriteSystem>(); _spriteSystem = _entManager.System<SpriteSystem>();
_navMapSystem = _entManager.System<SharedNavMapSystem>();
// Pass the owner to nav map // Pass the owner to nav map
_owner = owner; _owner = owner;
NavMap.Owner = _owner; NavMap.Owner = _owner;
// Set nav map colors // Set nav map colors
NavMap.WallColor = _wallColor; NavMap.WallColor = new Color(64, 64, 64);
NavMap.TileColor = _tileColor; NavMap.TileColor = Color.DimGray * NavMap.WallColor;
// Set nav map grid uid // Set nav map grid uid
var stationName = Loc.GetString("atmos-alerts-window-unknown-location"); var stationName = Loc.GetString("atmos-alerts-window-unknown-location");
@@ -193,9 +179,6 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
// Add tracked entities to the nav map // Add tracked entities to the nav map
foreach (var device in console.AtmosDevices) foreach (var device in console.AtmosDevices)
{ {
if (!device.NetEntity.Valid)
continue;
if (!NavMap.Visible) if (!NavMap.Visible)
continue; continue;
@@ -226,7 +209,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
if (consoleCoords != null && consoleUid != null) if (consoleCoords != null && consoleUid != null)
{ {
var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png"))); var texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")));
var blip = new NavMapBlip(consoleCoords.Value, texture, _monitorBlipColor, true, false); var blip = new NavMapBlip(consoleCoords.Value, texture, Color.Cyan, true, false);
NavMap.TrackedEntities[consoleUid.Value] = blip; NavMap.TrackedEntities[consoleUid.Value] = blip;
} }
@@ -275,7 +258,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
VerticalAlignment = VAlignment.Center, VerticalAlignment = VAlignment.Center,
}; };
label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", _statusTextColor.ToHexNoAlpha()))); label.SetMarkup(Loc.GetString("atmos-alerts-window-no-active-alerts", ("color", StyleNano.GoodGreenFore.ToHexNoAlpha())));
AlertsTable.AddChild(label); AlertsTable.AddChild(label);
} }
@@ -287,34 +270,6 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
else else
MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount))); MasterTabContainer.SetTabTitle(0, Loc.GetString("atmos-alerts-window-tab-alerts", ("value", activeAlarmCount)));
// Update sensor regions
NavMap.RegionOverlays.Clear();
var prioritizedRegionOverlays = new Dictionary<NavMapRegionOverlay, int>();
if (_owner != null &&
_entManager.TryGetComponent<TransformComponent>(_owner, out var xform) &&
_entManager.TryGetComponent<NavMapComponent>(xform.GridUid, out var navMap))
{
var regionOverlays = _navMapSystem.GetNavMapRegionOverlays(_owner.Value, navMap, AtmosAlertsComputerUiKey.Key);
foreach (var (regionOwner, regionOverlay) in regionOverlays)
{
var alarmState = GetAlarmState(regionOwner);
if (!TryGetSensorRegionColor(regionOwner, alarmState, out var regionColor))
continue;
regionOverlay.Color = regionColor;
var priority = (_trackedEntity == regionOwner) ? 999 : (int)alarmState;
prioritizedRegionOverlays.Add(regionOverlay, priority);
}
// Sort overlays according to their priority
var sortedOverlays = prioritizedRegionOverlays.OrderBy(x => x.Value).Select(x => x.Key).ToList();
NavMap.RegionOverlays = sortedOverlays;
}
// Auto-scroll re-enable // Auto-scroll re-enable
if (_autoScrollAwaitsUpdate) if (_autoScrollAwaitsUpdate)
{ {
@@ -335,7 +290,7 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
var coords = _entManager.GetCoordinates(metaData.NetCoordinates); var coords = _entManager.GetCoordinates(metaData.NetCoordinates);
if (_trackedEntity != null && _trackedEntity != metaData.NetEntity) if (_trackedEntity != null && _trackedEntity != metaData.NetEntity)
color *= _untrackedEntColor; color *= Color.DimGray;
var selectable = true; var selectable = true;
var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable); var blip = new NavMapBlip(coords, _spriteSystem.Frame0(texture), color, _trackedEntity == metaData.NetEntity, selectable);
@@ -343,24 +298,6 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
NavMap.TrackedEntities[metaData.NetEntity] = blip; NavMap.TrackedEntities[metaData.NetEntity] = blip;
} }
private bool TryGetSensorRegionColor(NetEntity regionOwner, AtmosAlarmType alarmState, out Color color)
{
color = Color.White;
var blip = GetBlipTexture(alarmState);
if (blip == null)
return false;
// Color the region based on alarm state and entity tracking
color = blip.Value.Item2 * _regionBaseColor;
if (_trackedEntity != null && _trackedEntity != regionOwner)
color *= _untrackedEntColor;
return true;
}
private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null) private void UpdateUIEntry(AtmosAlertsComputerEntry entry, int index, Control table, AtmosAlertsComputerComponent console, AtmosAlertsFocusDeviceData? focusData = null)
{ {
// Make new UI entry if required // Make new UI entry if required
@@ -597,13 +534,13 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
switch (alarmState) switch (alarmState)
{ {
case AtmosAlarmType.Invalid: case AtmosAlarmType.Invalid:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _inactiveColor); break; output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), StyleNano.DisabledFore); break;
case AtmosAlarmType.Normal: case AtmosAlarmType.Normal:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), _goodColor); break; output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_circle.png")), Color.LimeGreen); break;
case AtmosAlarmType.Warning: case AtmosAlarmType.Warning:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), _warningColor); break; output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_triangle.png")), new Color(255, 182, 72)); break;
case AtmosAlarmType.Danger: case AtmosAlarmType.Danger:
output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), _dangerColor); break; output = (new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/NavMap/beveled_square.png")), new Color(255, 67, 67)); break;
} }
return output; return output;

View File

@@ -43,8 +43,6 @@ public sealed partial class ContentAudioSystem
private void CP14UpdateAmbientLoops() private void CP14UpdateAmbientLoops()
{ {
return; //DISABLED UNTIL CLIENT ERROR SPAM FIXED
if (_timing.CurTime <= _nextUpdateTime) if (_timing.CurTime <= _nextUpdateTime)
return; return;

View File

@@ -20,6 +20,7 @@ public sealed partial class ContentAudioSystem
{ {
[Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!; [Dependency] private readonly ClientGameTicker _gameTicker = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f); private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
@@ -70,7 +71,7 @@ public sealed partial class ContentAudioSystem
Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged); Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged); Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
_state.OnStateChanged += StateManagerOnStateChanged; _stateManager.OnStateChanged += StateManagerOnStateChanged;
_client.PlayerLeaveServer += OnLeave; _client.PlayerLeaveServer += OnLeave;
@@ -114,7 +115,7 @@ public sealed partial class ContentAudioSystem
private void LobbyMusicCVarChanged(bool musicEnabled) private void LobbyMusicCVarChanged(bool musicEnabled)
{ {
if (musicEnabled && _state.CurrentState is LobbyState) if (musicEnabled && _stateManager.CurrentState is LobbyState)
{ {
StartLobbyMusic(); StartLobbyMusic();
} }
@@ -233,7 +234,7 @@ public sealed partial class ContentAudioSystem
private void ShutdownLobbyMusic() private void ShutdownLobbyMusic()
{ {
_state.OnStateChanged -= StateManagerOnStateChanged; _stateManager.OnStateChanged -= StateManagerOnStateChanged;
_client.PlayerLeaveServer -= OnLeave; _client.PlayerLeaveServer -= OnLeave;

View File

@@ -1,21 +0,0 @@
using Content.Shared.Timing;
using Content.Shared.Cargo.Systems;
namespace Content.Client.Cargo.Systems;
/// <summary>
/// This handles...
/// </summary>
public sealed class ClientPriceGunSystem : SharedPriceGunSystem
{
[Dependency] private readonly UseDelaySystem _useDelay = default!;
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
{
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
return false;
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
return true;
}
}

View File

@@ -21,16 +21,6 @@ internal sealed class ChatManager : IChatManager
_sawmill.Level = LogLevel.Info; _sawmill.Level = LogLevel.Info;
} }
public void SendAdminAlert(string message)
{
// See server-side manager. This just exists for shared code.
}
public void SendAdminAlert(EntityUid player, string message)
{
// See server-side manager. This just exists for shared code.
}
public void SendMessage(string text, ChatSelectChannel channel) public void SendMessage(string text, ChatSelectChannel channel)
{ {
var str = text.ToString(); var str = text.ToString();

View File

@@ -2,8 +2,10 @@ using Content.Shared.Chat;
namespace Content.Client.Chat.Managers namespace Content.Client.Chat.Managers
{ {
public interface IChatManager : ISharedChatManager public interface IChatManager
{ {
void Initialize();
public void SendMessage(string text, ChatSelectChannel channel); public void SendMessage(string text, ChatSelectChannel channel);
} }
} }

View File

@@ -327,8 +327,7 @@ public sealed class ClientClothingSystem : ClothingSystem
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId)) if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
continue; continue;
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers)) _displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
index++;
} }
} }

View File

@@ -1,8 +1,6 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.Overlays; using Content.Shared.Overlays;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.Prototypes;
using System.Linq; using System.Linq;
namespace Content.Client.Commands; namespace Content.Client.Commands;
@@ -36,7 +34,7 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands
{ {
var showHealthBarsComponent = new ShowHealthBarsComponent var showHealthBarsComponent = new ShowHealthBarsComponent
{ {
DamageContainers = args.Select(arg => new ProtoId<DamageContainerPrototype>(arg)).ToList(), DamageContainers = args.ToList(),
HealthStatusIcon = null, HealthStatusIcon = null,
NetSyncEnabled = false NetSyncEnabled = false
}; };

View File

@@ -4,7 +4,6 @@ using Content.Client.Chat.Managers;
using Content.Client.DebugMon; using Content.Client.DebugMon;
using Content.Client.Eui; using Content.Client.Eui;
using Content.Client.Fullscreen; using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick; using Content.Client.GhostKick;
using Content.Client.Guidebook; using Content.Client.Guidebook;
using Content.Client.Input; using Content.Client.Input;
@@ -71,8 +70,8 @@ namespace Content.Client.Entry
[Dependency] private readonly IResourceManager _resourceManager = default!; [Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IReplayLoadManager _replayLoad = default!; [Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!; [Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
public override void Init() public override void Init()
{ {
@@ -142,12 +141,6 @@ namespace Content.Client.Entry
_configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f); _configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f);
} }
public override void Shutdown()
{
base.Shutdown();
_titleWindowManager.Shutdown();
}
public override void PostInit() public override void PostInit()
{ {
base.PostInit(); base.PostInit();
@@ -168,7 +161,6 @@ namespace Content.Client.Entry
_userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme)); _userInterfaceManager.SetDefaultTheme(_configManager.GetCVar(CCVars.UIDefaultInterfaceTheme));
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme)); _userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize(); _documentParsingManager.Initialize();
_titleWindowManager.Initialize();
_baseClient.RunLevelChanged += (_, args) => _baseClient.RunLevelChanged += (_, args) =>
{ {
@@ -200,7 +192,7 @@ namespace Content.Client.Entry
_resourceManager, _resourceManager,
ReplayConstants.ReplayZipFolder.ToRootedPath()); ReplayConstants.ReplayZipFolder.ToRootedPath());
_playbackMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath()); _replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
_replayLoad.LoadAndStartReplay(reader); _replayLoad.LoadAndStartReplay(reader);
} }
else if (_gameController.LaunchState.FromLauncher) else if (_gameController.LaunchState.FromLauncher)

View File

@@ -1,62 +0,0 @@
using Content.Shared.CCVar;
using Robust.Client;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
namespace Content.Client.GameTicking.Managers;
public sealed class TitleWindowManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameController _gameController = default!;
public void Initialize()
{
_cfg.OnValueChanged(CVars.GameHostName, OnHostnameChange, true);
_cfg.OnValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange, true);
_client.RunLevelChanged += OnRunLevelChangedChange;
}
public void Shutdown()
{
_cfg.UnsubValueChanged(CVars.GameHostName, OnHostnameChange);
_cfg.UnsubValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange);
}
private void OnHostnameChange(string hostname)
{
var defaultWindowTitle = _gameController.GameTitle();
// Since the game assumes the server name is MyServer and that GameHostnameInTitlebar CCVar is true by default
// Lets just... not show anything. This also is used to revert back to just the game title on disconnect.
if (_client.RunLevel == ClientRunLevel.Initialize)
{
_clyde.SetWindowTitle(defaultWindowTitle);
return;
}
if (_cfg.GetCVar(CCVars.GameHostnameInTitlebar))
// If you really dislike the dash I guess change it here
_clyde.SetWindowTitle(hostname + " - " + defaultWindowTitle);
else
_clyde.SetWindowTitle(defaultWindowTitle);
}
// Clients by default assume game.hostname_in_titlebar is true
// but we need to clear it as soon as we join and actually receive the servers preference on this.
// This will ensure we rerun OnHostnameChange and set the correct title bar name.
private void OnHostnameTitleChange(bool colonthree)
{
OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
}
// This is just used we can rerun the hostname change function when we disconnect to revert back to just the games title.
private void OnRunLevelChangedChange(object? sender, RunLevelChangedEventArgs runLevelChangedEventArgs)
{
OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
}
}

View File

@@ -130,9 +130,9 @@ namespace Content.Client.Hands.Systems
OnPlayerHandsAdded?.Invoke(hands); OnPlayerHandsAdded?.Invoke(hands);
} }
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true) public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null)
{ {
base.DoDrop(uid, hand, doDropInteraction, hands, log); base.DoDrop(uid, hand, doDropInteraction, hands);
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite)) if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value; sprite.RenderOrder = EntityManager.CurrentTick.Value;

View File

@@ -1,4 +1,3 @@
using Content.Shared.Localizations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -17,10 +16,19 @@ public sealed partial class PlaytimeStatsEntry : ContainerButton
RoleLabel.Text = role; RoleLabel.Text = role;
Playtime = playtime; // store the TimeSpan value directly Playtime = playtime; // store the TimeSpan value directly
PlaytimeLabel.Text = ContentLocalizationManager.FormatPlaytime(playtime); // convert to string for display PlaytimeLabel.Text = ConvertTimeSpanToHoursMinutes(playtime); // convert to string for display
BackgroundColorPanel.PanelOverride = styleBox; BackgroundColorPanel.PanelOverride = styleBox;
} }
private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
{
var hours = (int)timeSpan.TotalHours;
var minutes = timeSpan.Minutes;
var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
return formattedTimeLoc;
}
public void UpdateShading(StyleBoxFlat styleBox) public void UpdateShading(StyleBoxFlat styleBox)
{ {
BackgroundColorPanel.PanelOverride = styleBox; BackgroundColorPanel.PanelOverride = styleBox;

View File

@@ -104,7 +104,8 @@ public sealed partial class PlaytimeStatsWindow : FancyWindow
{ {
var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime(); var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime();
OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", overallPlaytime)); var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime);
OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime));
var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles(); var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles();
@@ -133,4 +134,13 @@ public sealed partial class PlaytimeStatsWindow : FancyWindow
_sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format."); _sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format.");
} }
} }
private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
{
var hours = (int) timeSpan.TotalHours;
var minutes = timeSpan.Minutes;
var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
return formattedTimeLoc;
}
} }

View File

@@ -98,7 +98,7 @@ namespace Content.Client.Inventory
} }
} }
if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp) && handsComp.CanBeStripped) if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp))
{ {
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands // good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
// and not gui hands... which are different... // and not gui hands... which are different...

View File

@@ -5,7 +5,6 @@ using Content.Client.Clickable;
using Content.Client.DebugMon; using Content.Client.DebugMon;
using Content.Client.Eui; using Content.Client.Eui;
using Content.Client.Fullscreen; using Content.Client.Fullscreen;
using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick; using Content.Client.GhostKick;
using Content.Client.Guidebook; using Content.Client.Guidebook;
using Content.Client.Launcher; using Content.Client.Launcher;
@@ -19,11 +18,8 @@ using Content.Client.Viewport;
using Content.Client.Voting; using Content.Client.Voting;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Client.Lobby; using Content.Client.Lobby;
using Content.Client.Players.RateLimiting;
using Content.Shared.Administration.Managers; using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Players.RateLimiting;
namespace Content.Client.IoC namespace Content.Client.IoC
{ {
@@ -35,7 +31,6 @@ namespace Content.Client.IoC
collection.Register<IParallaxManager, ParallaxManager>(); collection.Register<IParallaxManager, ParallaxManager>();
collection.Register<IChatManager, ChatManager>(); collection.Register<IChatManager, ChatManager>();
collection.Register<ISharedChatManager, ChatManager>();
collection.Register<IClientPreferencesManager, ClientPreferencesManager>(); collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
collection.Register<IStylesheetManager, StylesheetManager>(); collection.Register<IStylesheetManager, StylesheetManager>();
collection.Register<IScreenshotHook, ScreenshotHook>(); collection.Register<IScreenshotHook, ScreenshotHook>();
@@ -52,13 +47,10 @@ namespace Content.Client.IoC
collection.Register<ExtendedDisconnectInformationManager>(); collection.Register<ExtendedDisconnectInformationManager>();
collection.Register<JobRequirementsManager>(); collection.Register<JobRequirementsManager>();
collection.Register<DocumentParsingManager>(); collection.Register<DocumentParsingManager>();
collection.Register<ContentReplayPlaybackManager>(); collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>(); collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
collection.Register<MappingManager>(); collection.Register<MappingManager>();
collection.Register<DebugMonitorManager>(); collection.Register<DebugMonitorManager>();
collection.Register<PlayerRateLimitManager>();
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
collection.Register<TitleWindowManager>();
} }
} }
} }

View File

@@ -0,0 +1,149 @@
using System.Numerics;
using Content.Client.Buckle;
using Content.Client.Gravity;
using Content.Shared.ActionBlocker;
using Content.Shared.Mobs.Systems;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
namespace Content.Client.Movement.Systems;
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
{
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
public override void Initialize()
{
base.Initialize();
SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
}
private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StartWaddling((GetEntity(msg.Entity), comp));
}
private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
{
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
StopWaddling((GetEntity(msg.Entity), comp));
}
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
{
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
if (_gravity.IsWeightless(entity.Owner))
return;
if (!_actionBlocker.CanMove(entity.Owner, mover))
return;
// Do nothing if buckled in
if (_buckle.IsBuckled(entity.Owner))
return;
// Do nothing if crit or dead (for obvious reasons)
if (_mobState.IsIncapacitated(entity.Owner))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
{
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
}
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
{
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
}
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
{
if (args.Key != entity.Comp.KeyName)
return;
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
return;
PlayWaddleAnimationUsing(
(entity.Owner, entity.Comp),
CalculateAnimationLength(entity.Comp, mover),
CalculateTumbleIntensity(entity.Comp)
);
}
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
{
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
return;
_animation.Stop(entity.Owner, entity.Comp.KeyName);
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
return;
sprite.Offset = new Vector2();
sprite.Rotation = Angle.FromDegrees(0);
}
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
{
entity.Comp.LastStep = !entity.Comp.LastStep;
var anim = new Animation()
{
Length = TimeSpan.FromSeconds(len),
AnimationTracks =
{
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Rotation),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
}
},
new AnimationTrackComponentProperty()
{
ComponentType = typeof(SpriteComponent),
Property = nameof(SpriteComponent.Offset),
InterpolationMode = AnimationInterpolationMode.Linear,
KeyFrames =
{
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
}
}
}
};
_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
}
}

View File

@@ -22,6 +22,6 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsCo
return; return;
if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype)) if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype))
ev.StatusIcons.Add(iconPrototype); ev.StatusIcons.Add(iconPrototype!);
} }
} }

View File

@@ -76,16 +76,16 @@ public sealed partial class StencilOverlay : Overlay
//CP14 Overlays //CP14 Overlays
if (_entManager.TryGetComponent<CP14CloudShadowsComponent>(mapUid, out var shadows)) if (_entManager.TryGetComponent<CP14WorldEdgeComponent>(mapUid, out var worldEdge))
{ {
DrawCloudShadows(args, shadows, invMatrix); DrawWorldEdge(args, worldEdge, invMatrix);
} }
//CP14 Overlays end //CP14 Overlays end
//CP14 Overlays //CP14 Overlays
if (_entManager.TryGetComponent<CP14WorldEdgeComponent>(mapUid, out var worldEdge)) if (_entManager.TryGetComponent<CP14CloudShadowsComponent>(mapUid, out var shadows))
{ {
DrawWorldEdge(args, worldEdge, invMatrix); DrawCloudShadows(args, shadows, invMatrix);
} }
//CP14 Overlays end //CP14 Overlays end

View File

@@ -319,8 +319,6 @@ namespace Content.Client.Paper.UI
private void RunOnSaved() private void RunOnSaved()
{ {
// Prevent further saving while text processing still in
SaveButton.Disabled = true;
OnSaved?.Invoke(Rope.Collapse(Input.TextRope)); OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
} }

View File

@@ -1,303 +0,0 @@
using Content.Shared.Atmos;
using Content.Shared.Pinpointer;
using System.Linq;
namespace Content.Client.Pinpointer;
public sealed partial class NavMapSystem
{
private (AtmosDirection, Vector2i, AtmosDirection)[] _regionPropagationTable =
{
(AtmosDirection.East, new Vector2i(1, 0), AtmosDirection.West),
(AtmosDirection.West, new Vector2i(-1, 0), AtmosDirection.East),
(AtmosDirection.North, new Vector2i(0, 1), AtmosDirection.South),
(AtmosDirection.South, new Vector2i(0, -1), AtmosDirection.North),
};
public override void Update(float frameTime)
{
// To prevent compute spikes, only one region is flood filled per frame
var query = AllEntityQuery<NavMapComponent>();
while (query.MoveNext(out var ent, out var entNavMapRegions))
FloodFillNextEnqueuedRegion(ent, entNavMapRegions);
}
private void FloodFillNextEnqueuedRegion(EntityUid uid, NavMapComponent component)
{
if (!component.QueuedRegionsToFlood.Any())
return;
var regionOwner = component.QueuedRegionsToFlood.Dequeue();
// If the region is no longer valid, flood the next one in the queue
if (!component.RegionProperties.TryGetValue(regionOwner, out var regionProperties) ||
!regionProperties.Seeds.Any())
{
FloodFillNextEnqueuedRegion(uid, component);
return;
}
// Flood fill the region, using the region seeds as starting points
var (floodedTiles, floodedChunks) = FloodFillRegion(uid, component, regionProperties);
// Combine the flooded tiles into larger rectangles
var gridCoords = GetMergedRegionTiles(floodedTiles);
// Create and assign the new region overlay
var regionOverlay = new NavMapRegionOverlay(regionProperties.UiKey, gridCoords)
{
Color = regionProperties.Color
};
component.RegionOverlays[regionOwner] = regionOverlay;
// To reduce unnecessary future flood fills, we will track which chunks have been flooded by a region owner
// First remove an old assignments
if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var oldChunks))
{
foreach (var chunk in oldChunks)
{
if (component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var oldOwners))
{
oldOwners.Remove(regionOwner);
component.ChunkToRegionOwnerTable[chunk] = oldOwners;
}
}
}
// Now update with the new assignments
component.RegionOwnerToChunkTable[regionOwner] = floodedChunks;
foreach (var chunk in floodedChunks)
{
if (!component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var owners))
owners = new();
owners.Add(regionOwner);
component.ChunkToRegionOwnerTable[chunk] = owners;
}
}
private (HashSet<Vector2i>, HashSet<Vector2i>) FloodFillRegion(EntityUid uid, NavMapComponent component, NavMapRegionProperties regionProperties)
{
if (!regionProperties.Seeds.Any())
return (new(), new());
var visitedChunks = new HashSet<Vector2i>();
var visitedTiles = new HashSet<Vector2i>();
var tilesToVisit = new Stack<Vector2i>();
foreach (var regionSeed in regionProperties.Seeds)
{
tilesToVisit.Push(regionSeed);
while (tilesToVisit.Count > 0)
{
// If the max region area is hit, exit
if (visitedTiles.Count > regionProperties.MaxArea)
return (new(), new());
// Pop the top tile from the stack
var current = tilesToVisit.Pop();
// If the current tile position has already been visited,
// or is too far away from the seed, continue
if ((regionSeed - current).Length > regionProperties.MaxRadius)
continue;
if (visitedTiles.Contains(current))
continue;
// Determine the tile's chunk index
var chunkOrigin = SharedMapSystem.GetChunkIndices(current, ChunkSize);
var relative = SharedMapSystem.GetChunkRelative(current, ChunkSize);
var idx = GetTileIndex(relative);
// Extract the tile data
if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk))
continue;
var flag = chunk.TileData[idx];
// If the current tile is entirely occupied, continue
if ((FloorMask & flag) == 0)
continue;
if ((WallMask & flag) == WallMask)
continue;
if ((AirlockMask & flag) == AirlockMask)
continue;
// Otherwise the tile can be added to this region
visitedTiles.Add(current);
visitedChunks.Add(chunkOrigin);
// Determine if we can propagate the region into its cardinally adjacent neighbors
// To propagate to a neighbor, movement into the neighbors closest edge must not be
// blocked, and vice versa
foreach (var (direction, tileOffset, reverseDirection) in _regionPropagationTable)
{
if (!RegionCanPropagateInDirection(chunk, current, direction))
continue;
var neighbor = current + tileOffset;
var neighborOrigin = SharedMapSystem.GetChunkIndices(neighbor, ChunkSize);
if (!component.Chunks.TryGetValue(neighborOrigin, out var neighborChunk))
continue;
visitedChunks.Add(neighborOrigin);
if (!RegionCanPropagateInDirection(neighborChunk, neighbor, reverseDirection))
continue;
tilesToVisit.Push(neighbor);
}
}
}
return (visitedTiles, visitedChunks);
}
private bool RegionCanPropagateInDirection(NavMapChunk chunk, Vector2i tile, AtmosDirection direction)
{
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
var idx = GetTileIndex(relative);
var flag = chunk.TileData[idx];
if ((FloorMask & flag) == 0)
return false;
var directionMask = 1 << (int)direction;
var wallMask = (int)direction << (int)NavMapChunkType.Wall;
var airlockMask = (int)direction << (int)NavMapChunkType.Airlock;
if ((wallMask & flag) > 0)
return false;
if ((airlockMask & flag) > 0)
return false;
return true;
}
private List<(Vector2i, Vector2i)> GetMergedRegionTiles(HashSet<Vector2i> tiles)
{
if (!tiles.Any())
return new();
var x = tiles.Select(t => t.X);
var minX = x.Min();
var maxX = x.Max();
var y = tiles.Select(t => t.Y);
var minY = y.Min();
var maxY = y.Max();
var matrix = new int[maxX - minX + 1, maxY - minY + 1];
foreach (var tile in tiles)
{
var a = tile.X - minX;
var b = tile.Y - minY;
matrix[a, b] = 1;
}
return GetMergedRegionTiles(matrix, new Vector2i(minX, minY));
}
private List<(Vector2i, Vector2i)> GetMergedRegionTiles(int[,] matrix, Vector2i offset)
{
var output = new List<(Vector2i, Vector2i)>();
var rows = matrix.GetLength(0);
var cols = matrix.GetLength(1);
var dp = new int[rows, cols];
var coords = (new Vector2i(), new Vector2i());
var maxArea = 0;
var count = 0;
while (!IsArrayEmpty(matrix))
{
count++;
if (count > rows * cols)
break;
// Clear old values
dp = new int[rows, cols];
coords = (new Vector2i(), new Vector2i());
maxArea = 0;
// Initialize the first row of dp
for (int j = 0; j < cols; j++)
{
dp[0, j] = matrix[0, j];
}
// Calculate dp values for remaining rows
for (int i = 1; i < rows; i++)
{
for (int j = 0; j < cols; j++)
dp[i, j] = matrix[i, j] == 1 ? dp[i - 1, j] + 1 : 0;
}
// Find the largest rectangular area seeded for each position in the matrix
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
int minWidth = dp[i, j];
for (int k = j; k >= 0; k--)
{
if (dp[i, k] <= 0)
break;
minWidth = Math.Min(minWidth, dp[i, k]);
var currArea = Math.Max(maxArea, minWidth * (j - k + 1));
if (currArea > maxArea)
{
maxArea = currArea;
coords = (new Vector2i(i - minWidth + 1, k), new Vector2i(i, j));
}
}
}
}
// Save the recorded rectangle vertices
output.Add((coords.Item1 + offset, coords.Item2 + offset));
// Removed the tiles covered by the rectangle from matrix
for (int i = coords.Item1.X; i <= coords.Item2.X; i++)
{
for (int j = coords.Item1.Y; j <= coords.Item2.Y; j++)
matrix[i, j] = 0;
}
}
return output;
}
private bool IsArrayEmpty(int[,] matrix)
{
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
if (matrix[i, j] == 1)
return false;
}
}
return true;
}
}

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Content.Shared.Pinpointer; using Content.Shared.Pinpointer;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -17,7 +16,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{ {
Dictionary<Vector2i, int[]> modifiedChunks; Dictionary<Vector2i, int[]> modifiedChunks;
Dictionary<NetEntity, NavMapBeacon> beacons; Dictionary<NetEntity, NavMapBeacon> beacons;
Dictionary<NetEntity, NavMapRegionProperties> regions;
switch (args.Current) switch (args.Current)
{ {
@@ -25,8 +23,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{ {
modifiedChunks = delta.ModifiedChunks; modifiedChunks = delta.ModifiedChunks;
beacons = delta.Beacons; beacons = delta.Beacons;
regions = delta.Regions;
foreach (var index in component.Chunks.Keys) foreach (var index in component.Chunks.Keys)
{ {
if (!delta.AllChunks!.Contains(index)) if (!delta.AllChunks!.Contains(index))
@@ -39,8 +35,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{ {
modifiedChunks = state.Chunks; modifiedChunks = state.Chunks;
beacons = state.Beacons; beacons = state.Beacons;
regions = state.Regions;
foreach (var index in component.Chunks.Keys) foreach (var index in component.Chunks.Keys)
{ {
if (!state.Chunks.ContainsKey(index)) if (!state.Chunks.ContainsKey(index))
@@ -53,54 +47,13 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
return; return;
} }
// Update region data and queue new regions for flooding
var prevRegionOwners = component.RegionProperties.Keys.ToList();
var validRegionOwners = new List<NetEntity>();
component.RegionProperties.Clear();
foreach (var (regionOwner, regionData) in regions)
{
if (!regionData.Seeds.Any())
continue;
component.RegionProperties[regionOwner] = regionData;
validRegionOwners.Add(regionOwner);
if (component.RegionOverlays.ContainsKey(regionOwner))
continue;
if (component.QueuedRegionsToFlood.Contains(regionOwner))
continue;
component.QueuedRegionsToFlood.Enqueue(regionOwner);
}
// Remove stale region owners
var regionOwnersToRemove = prevRegionOwners.Except(validRegionOwners);
foreach (var regionOwnerRemoved in regionOwnersToRemove)
RemoveNavMapRegion(uid, component, regionOwnerRemoved);
// Modify chunks
foreach (var (origin, chunk) in modifiedChunks) foreach (var (origin, chunk) in modifiedChunks)
{ {
var newChunk = new NavMapChunk(origin); var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length); Array.Copy(chunk, newChunk.TileData, chunk.Length);
component.Chunks[origin] = newChunk; component.Chunks[origin] = newChunk;
// If the affected chunk intersects one or more regions, re-flood them
if (!component.ChunkToRegionOwnerTable.TryGetValue(origin, out var affectedOwners))
continue;
foreach (var affectedOwner in affectedOwners)
{
if (!component.QueuedRegionsToFlood.Contains(affectedOwner))
component.QueuedRegionsToFlood.Enqueue(affectedOwner);
}
} }
// Refresh beacons
component.Beacons.Clear(); component.Beacons.Clear();
foreach (var (nuid, beacon) in beacons) foreach (var (nuid, beacon) in beacons)
{ {

View File

@@ -48,7 +48,6 @@ public partial class NavMapControl : MapGridControl
public List<(Vector2, Vector2)> TileLines = new(); public List<(Vector2, Vector2)> TileLines = new();
public List<(Vector2, Vector2)> TileRects = new(); public List<(Vector2, Vector2)> TileRects = new();
public List<(Vector2[], Color)> TilePolygons = new(); public List<(Vector2[], Color)> TilePolygons = new();
public List<NavMapRegionOverlay> RegionOverlays = new();
// Default colors // Default colors
public Color WallColor = new(102, 217, 102); public Color WallColor = new(102, 217, 102);
@@ -320,22 +319,6 @@ public partial class NavMapControl : MapGridControl
} }
} }
// Draw region overlays
if (_grid != null)
{
foreach (var regionOverlay in RegionOverlays)
{
foreach (var gridCoords in regionOverlay.GridCoords)
{
var positionTopLeft = ScalePosition(new Vector2(gridCoords.Item1.X, -gridCoords.Item1.Y) - new Vector2(offset.X, -offset.Y));
var positionBottomRight = ScalePosition(new Vector2(gridCoords.Item2.X + _grid.TileSize, -gridCoords.Item2.Y - _grid.TileSize) - new Vector2(offset.X, -offset.Y));
var box = new UIBox2(positionTopLeft, positionBottomRight);
handle.DrawRect(box, regionOverlay.Color);
}
}
}
// Draw map lines // Draw map lines
if (TileLines.Any()) if (TileLines.Any())
{ {

View File

@@ -51,8 +51,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
{ {
// Reset on disconnect, just in case. // Reset on disconnect, just in case.
_roles.Clear(); _roles.Clear();
_jobWhitelists.Clear();
_roleBans.Clear();
} }
} }
@@ -60,6 +58,9 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
{ {
_sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries."); _sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries.");
if (_roleBans.Equals(message.Bans))
return;
_roleBans.Clear(); _roleBans.Clear();
_roleBans.AddRange(message.Bans); _roleBans.AddRange(message.Bans);
Updated?.Invoke(); Updated?.Invoke();

View File

@@ -1,23 +0,0 @@
using Content.Shared.Players.RateLimiting;
using Robust.Shared.Player;
namespace Content.Client.Players.RateLimiting;
public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
{
public override RateLimitStatus CountAction(ICommonSession player, string key)
{
// TODO Rate-Limit
// Add support for rate limit prediction
// I.e., dont mis-predict just because somebody is clicking too quickly.
return RateLimitStatus.Allowed;
}
public override void Register(string key, RateLimitRegistration registration)
{
}
public override void Initialize()
{
}
}

View File

@@ -1,9 +1,8 @@
using Content.Client.Power.APC.UI; using Content.Client.Power.APC.UI;
using Content.Shared.Access.Systems;
using Content.Shared.APC; using Content.Shared.APC;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Shared.Player;
namespace Content.Client.Power.APC namespace Content.Client.Power.APC
{ {
@@ -23,14 +22,6 @@ namespace Content.Client.Power.APC
_menu = this.CreateWindow<ApcMenu>(); _menu = this.CreateWindow<ApcMenu>();
_menu.SetEntity(Owner); _menu.SetEntity(Owner);
_menu.OnBreaker += BreakerPressed; _menu.OnBreaker += BreakerPressed;
var hasAccess = false;
if (PlayerManager.LocalEntity != null)
{
var accessReader = EntMan.System<AccessReaderSystem>();
hasAccess = accessReader.IsAllowed((EntityUid)PlayerManager.LocalEntity, Owner);
}
_menu?.SetAccessEnabled(hasAccess);
} }
protected override void UpdateState(BoundUserInterfaceState state) protected override void UpdateState(BoundUserInterfaceState state)

View File

@@ -1,4 +1,4 @@
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -36,9 +36,19 @@ namespace Content.Client.Power.APC.UI
{ {
var castState = (ApcBoundInterfaceState) state; var castState = (ApcBoundInterfaceState) state;
if (!BreakerButton.Disabled) if (BreakerButton != null)
{ {
BreakerButton.Pressed = castState.MainBreaker; if(castState.HasAccess == false)
{
BreakerButton.Disabled = true;
BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
}
else
{
BreakerButton.Disabled = false;
BreakerButton.ToolTip = null;
BreakerButton.Pressed = castState.MainBreaker;
}
} }
if (PowerLabel != null) if (PowerLabel != null)
@@ -76,20 +86,6 @@ namespace Content.Client.Power.APC.UI
} }
} }
public void SetAccessEnabled(bool hasAccess)
{
if(hasAccess)
{
BreakerButton.Disabled = false;
BreakerButton.ToolTip = null;
}
else
{
BreakerButton.Disabled = true;
BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
}
}
private void UpdateChargeBarColor(float charge) private void UpdateChargeBarColor(float charge)
{ {
if (ChargeBar == null) if (ChargeBar == null)

View File

@@ -1,5 +1,4 @@
using Content.Shared.Doors.Components; using Content.Shared.Doors.Components;
using Content.Shared.Electrocution;
using Content.Shared.Silicons.StationAi; using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -7,69 +6,25 @@ namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem public sealed partial class StationAiSystem
{ {
private readonly ResPath _aiActionsRsi = new ResPath("/Textures/Interface/Actions/actions_ai.rsi");
private void InitializeAirlock() private void InitializeAirlock()
{ {
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial); SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
SubscribeLocalEvent<AirlockComponent, GetStationAiRadialEvent>(OnEmergencyAccessGetRadial);
SubscribeLocalEvent<ElectrifiedComponent, GetStationAiRadialEvent>(OnDoorElectrifiedGetRadial);
} }
private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args) private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
{ {
args.Actions.Add( args.Actions.Add(new StationAiRadial()
new StationAiRadial {
Sprite = ent.Comp.BoltsDown ?
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
new SpriteSpecifier.Rsi(
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
Event = new StationAiBoltEvent()
{ {
Sprite = ent.Comp.BoltsDown Bolted = !ent.Comp.BoltsDown,
? new SpriteSpecifier.Rsi(_aiActionsRsi, "unbolt_door")
: new SpriteSpecifier.Rsi(_aiActionsRsi, "bolt_door"),
Tooltip = ent.Comp.BoltsDown
? Loc.GetString("bolt-open")
: Loc.GetString("bolt-close"),
Event = new StationAiBoltEvent
{
Bolted = !ent.Comp.BoltsDown,
}
} }
); });
}
private void OnEmergencyAccessGetRadial(Entity<AirlockComponent> ent, ref GetStationAiRadialEvent args)
{
args.Actions.Add(
new StationAiRadial
{
Sprite = ent.Comp.EmergencyAccess
? new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_off")
: new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_on"),
Tooltip = ent.Comp.EmergencyAccess
? Loc.GetString("emergency-access-off")
: Loc.GetString("emergency-access-on"),
Event = new StationAiEmergencyAccessEvent
{
EmergencyAccess = !ent.Comp.EmergencyAccess,
}
}
);
}
private void OnDoorElectrifiedGetRadial(Entity<ElectrifiedComponent> ent, ref GetStationAiRadialEvent args)
{
args.Actions.Add(
new StationAiRadial
{
Sprite = ent.Comp.Enabled
? new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_off")
: new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_on"),
Tooltip = ent.Comp.Enabled
? Loc.GetString("electrify-door-off")
: Loc.GetString("electrify-door-on"),
Event = new StationAiElectrifiedEvent
{
Electrified = !ent.Comp.Enabled,
}
}
);
} }
} }

View File

@@ -100,7 +100,7 @@ namespace Content.Client.Stylesheets
public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B"); public static readonly Color ButtonColorDefaultRed = Color.FromHex("#D43B3B");
public static readonly Color ButtonColorHovered = Color.FromHex("#7f6357"); public static readonly Color ButtonColorHovered = Color.FromHex("#7f6357");
public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B"); public static readonly Color ButtonColorHoveredRed = Color.FromHex("#DF6B6B");
public static readonly Color ButtonColorPressed = Color.FromHex("#4a332b"); public static readonly Color ButtonColorPressed = Color.FromHex("#6c4a3e");
public static readonly Color ButtonColorDisabled = Color.FromHex("#3c3330"); public static readonly Color ButtonColorDisabled = Color.FromHex("#3c3330");
public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232"); public static readonly Color ButtonColorCautionDefault = Color.FromHex("#ab3232");

View File

@@ -398,6 +398,10 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
{ {
QueueWindowUpdate(); QueueWindowUpdate();
// TODO ACTIONS allow buttons to persist across state applications
// Then we don't have to interrupt drags any time the buttons get rebuilt.
_menuDragHelper.EndDrag();
if (_actionsSystem != null) if (_actionsSystem != null)
_container?.SetActionData(_actionsSystem, _actions.ToArray()); _container?.SetActionData(_actionsSystem, _actions.ToArray());
} }

View File

@@ -28,26 +28,14 @@ public class ActionButtonContainer : GridContainer
get => (ActionButton) GetChild(index); get => (ActionButton) GetChild(index);
} }
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes) private void BuildActionButtons(int count)
{ {
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
var keys = ContentKeyFunctions.GetHotbarBoundKeys(); var keys = ContentKeyFunctions.GetHotbarBoundKeys();
for (var i = 0; i < uniqueCount; i++) Children.Clear();
for (var index = 0; index < count; index++)
{ {
if (i >= ChildCount) Children.Add(MakeButton(index));
{
AddChild(MakeButton(i));
}
if (!actionTypes.TryGetValue(i, out var action))
action = null;
((ActionButton) GetChild(i)).UpdateData(action, system);
}
for (var i = ChildCount - 1; i >= uniqueCount; i--)
{
RemoveChild(GetChild(i));
} }
ActionButton MakeButton(int index) ActionButton MakeButton(int index)
@@ -67,6 +55,20 @@ public class ActionButtonContainer : GridContainer
} }
} }
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
{
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
if (ChildCount != uniqueCount)
BuildActionButtons(uniqueCount);
for (var i = 0; i < uniqueCount; i++)
{
if (!actionTypes.TryGetValue(i, out var action))
action = null;
((ActionButton) GetChild(i)).UpdateData(action, system);
}
}
public void ClearActionData() public void ClearActionData()
{ {
foreach (var button in Children) foreach (var button in Children)

View File

@@ -1,9 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical"
Margin="8 0 8 0">
<BoxContainer Name="Buttons"
Orientation="Vertical"
SeparationOverride="5">
<!-- Buttons are added here by code -->
</BoxContainer>
</BoxContainer>

View File

@@ -1,15 +1,15 @@
<BoxContainer xmlns="https://spacestation14.io" <BoxContainer xmlns="https://spacestation14.io"
Orientation="Horizontal" Orientation="Horizontal">
HorizontalAlignment="Stretch">
<Button Name="RequestButton" <Button Name="RequestButton"
Access="Public" Access="Public"
Text="{Loc 'ghost-roles-window-request-role-button'}" Text="{Loc 'ghost-roles-window-request-role-button'}"
StyleClasses="OpenRight" StyleClasses="OpenRight"
HorizontalExpand="True" HorizontalAlignment="Left"
SizeFlagsStretchRatio="3"/> SetWidth="300"/>
<Button Name="FollowButton" <Button Name="FollowButton"
Access="Public" Access="Public"
Text="{Loc 'ghost-roles-window-follow-role-button'}" Text="{Loc 'ghost-roles-window-follow-role-button'}"
StyleClasses="OpenLeft" StyleClasses="OpenLeft"
HorizontalExpand="True"/> HorizontalAlignment="Right"
SetWidth="150"/>
</BoxContainer> </BoxContainer>

View File

@@ -1,8 +0,0 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical">
<Label Name="Title"
StyleClasses="LabelKeyText"/>
<PanelContainer StyleClasses="HighDivider" />
<RichTextLabel Name="Description"
Margin="0 4"/>
</BoxContainer>

View File

@@ -1,18 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
{
[GenerateTypedNameReferences]
public sealed partial class GhostRoleInfoBox : BoxContainer
{
public GhostRoleInfoBox(string name, string description)
{
RobustXamlLoader.Load(this);
Title.Text = name;
Description.SetMessage(description);
}
}
}

View File

@@ -0,0 +1,16 @@
<BoxContainer xmlns="https://spacestation14.io"
Orientation="Vertical"
HorizontalExpand="True"
Margin="0 0 8 8">
<Label Name="Title"
StyleClasses="LabelKeyText"/>
<PanelContainer StyleClasses="HighDivider" />
<RichTextLabel Name="Description"
Margin="0 4"/>
<BoxContainer Name="Buttons"
HorizontalAlignment="Left"
Orientation="Vertical"
SeparationOverride="5">
<!-- Buttons are added here by code -->
</BoxContainer>
</BoxContainer>

View File

@@ -10,17 +10,20 @@ using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
{ {
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class GhostRoleButtonsBox : BoxContainer public sealed partial class GhostRolesEntry : BoxContainer
{ {
private SpriteSystem _spriteSystem; private SpriteSystem _spriteSystem;
public event Action<GhostRoleInfo>? OnRoleSelected; public event Action<GhostRoleInfo>? OnRoleSelected;
public event Action<GhostRoleInfo>? OnRoleFollow; public event Action<GhostRoleInfo>? OnRoleFollow;
public GhostRoleButtonsBox(bool hasAccess, FormattedMessage? reason, IEnumerable<GhostRoleInfo> roles, SpriteSystem spriteSystem) public GhostRolesEntry(string name, string description, bool hasAccess, FormattedMessage? reason, IEnumerable<GhostRoleInfo> roles, SpriteSystem spriteSystem)
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
_spriteSystem = spriteSystem; _spriteSystem = spriteSystem;
Title.Text = name;
Description.SetMessage(description);
foreach (var role in roles) foreach (var role in roles)
{ {
var button = new GhostRoleEntryButtons(role); var button = new GhostRoleEntryButtons(role);

View File

@@ -5,6 +5,7 @@ using Content.Shared.Eui;
using Content.Shared.Ghost.Roles; using Content.Shared.Ghost.Roles;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
{ {
@@ -76,13 +77,6 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
if (state is not GhostRolesEuiState ghostState) if (state is not GhostRolesEuiState ghostState)
return; return;
// We must save BodyVisible state, so all Collapsible boxes will not close
// on adding new ghost role.
// Save the current state of each Collapsible box being visible or not
_window.SaveCollapsibleBoxesStates();
// Clearing the container before adding new roles
_window.ClearEntries(); _window.ClearEntries();
var entityManager = IoCManager.Resolve<IEntityManager>(); var entityManager = IoCManager.Resolve<IEntityManager>();
@@ -90,32 +84,28 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
var spriteSystem = sysManager.GetEntitySystem<SpriteSystem>(); var spriteSystem = sysManager.GetEntitySystem<SpriteSystem>();
var requirementsManager = IoCManager.Resolve<JobRequirementsManager>(); var requirementsManager = IoCManager.Resolve<JobRequirementsManager>();
// TODO: role.Requirements value doesn't work at all as an equality key, this must be fixed
// Grouping roles
var groupedRoles = ghostState.GhostRoles.GroupBy( var groupedRoles = ghostState.GhostRoles.GroupBy(
role => (role.Name, role.Description, role.Requirements)); role => (role.Name, role.Description, role.Requirements));
// Add a new entry for each role group
foreach (var group in groupedRoles) foreach (var group in groupedRoles)
{ {
var name = group.Key.Name; var name = group.Key.Name;
var description = group.Key.Description; var description = group.Key.Description;
var hasAccess = requirementsManager.CheckRoleRequirements( bool hasAccess = true;
group.Key.Requirements, FormattedMessage? reason;
null,
out var reason); if (!requirementsManager.CheckRoleRequirements(group.Key.Requirements, null, out reason))
{
hasAccess = false;
}
// Adding a new role
_window.AddEntry(name, description, hasAccess, reason, group, spriteSystem); _window.AddEntry(name, description, hasAccess, reason, group, spriteSystem);
} }
// Restore the Collapsible box state if it is saved
_window.RestoreCollapsibleBoxesStates();
// Close the rules window if it is no longer needed
var closeRulesWindow = ghostState.GhostRoles.All(role => role.Identifier != _windowRulesId); var closeRulesWindow = ghostState.GhostRoles.All(role => role.Identifier != _windowRulesId);
if (closeRulesWindow) if (closeRulesWindow)
{
_windowRules?.Close(); _windowRules?.Close();
}
} }
} }
} }

View File

@@ -1,10 +1,7 @@
using System.Linq;
using Content.Shared.Ghost.Roles; using Content.Shared.Ghost.Roles;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
@@ -15,86 +12,20 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
public event Action<GhostRoleInfo>? OnRoleRequestButtonClicked; public event Action<GhostRoleInfo>? OnRoleRequestButtonClicked;
public event Action<GhostRoleInfo>? OnRoleFollow; public event Action<GhostRoleInfo>? OnRoleFollow;
private Dictionary<(string name, string description), Collapsible> _collapsibleBoxes = new();
private HashSet<(string name, string description)> _uncollapsedStates = new();
public GhostRolesWindow()
{
RobustXamlLoader.Load(this);
}
public void ClearEntries() public void ClearEntries()
{ {
NoRolesMessage.Visible = true; NoRolesMessage.Visible = true;
EntryContainer.DisposeAllChildren(); EntryContainer.DisposeAllChildren();
_collapsibleBoxes.Clear();
}
public void SaveCollapsibleBoxesStates()
{
_uncollapsedStates.Clear();
foreach (var (key, collapsible) in _collapsibleBoxes)
{
if (collapsible.BodyVisible)
{
_uncollapsedStates.Add(key);
}
}
}
public void RestoreCollapsibleBoxesStates()
{
foreach (var (key, collapsible) in _collapsibleBoxes)
{
collapsible.BodyVisible = _uncollapsedStates.Contains(key);
}
} }
public void AddEntry(string name, string description, bool hasAccess, FormattedMessage? reason, IEnumerable<GhostRoleInfo> roles, SpriteSystem spriteSystem) public void AddEntry(string name, string description, bool hasAccess, FormattedMessage? reason, IEnumerable<GhostRoleInfo> roles, SpriteSystem spriteSystem)
{ {
NoRolesMessage.Visible = false; NoRolesMessage.Visible = false;
var ghostRoleInfos = roles.ToList(); var entry = new GhostRolesEntry(name, description, hasAccess, reason, roles, spriteSystem);
var rolesCount = ghostRoleInfos.Count; entry.OnRoleSelected += OnRoleRequestButtonClicked;
entry.OnRoleFollow += OnRoleFollow;
var info = new GhostRoleInfoBox(name, description); EntryContainer.AddChild(entry);
var buttons = new GhostRoleButtonsBox(hasAccess, reason, ghostRoleInfos, spriteSystem);
buttons.OnRoleSelected += OnRoleRequestButtonClicked;
buttons.OnRoleFollow += OnRoleFollow;
EntryContainer.AddChild(info);
if (rolesCount > 1)
{
var buttonHeading = new CollapsibleHeading(Loc.GetString("ghost-roles-window-available-button", ("rolesCount", rolesCount)));
buttonHeading.AddStyleClass(ContainerButton.StyleClassButton);
buttonHeading.Label.HorizontalAlignment = HAlignment.Center;
buttonHeading.Label.HorizontalExpand = true;
var body = new CollapsibleBody
{
Margin = new Thickness(0, 5, 0, 0),
};
// TODO: Add Requirements to this key when it'll be fixed and work as an equality key in GhostRolesEui
var key = (name, description);
var collapsible = new Collapsible(buttonHeading, body)
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(0, 0, 0, 8),
};
body.AddChild(buttons);
EntryContainer.AddChild(collapsible);
_collapsibleBoxes.Add(key, collapsible);
}
else
{
EntryContainer.AddChild(buttons);
}
} }
} }
} }

View File

@@ -2,8 +2,7 @@
xmlns="https://spacestation14.io" xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls" xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
MinHeight="210">
<BoxContainer Name="MainContainer" Orientation="Vertical"> <BoxContainer Name="MainContainer" Orientation="Vertical">
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/> <LineEdit Name="SearchBar" PlaceHolder="{Loc 'vending-machine-component-search-filter'}" HorizontalExpand="True" Margin ="4 4"/>
<co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/> <co:SearchListContainer Name="VendingContents" VerticalExpand="True" Margin="4 4"/>

View File

@@ -1,6 +1,5 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Client.Gameplay;
using Content.Client.Stylesheets; using Content.Client.Stylesheets;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
@@ -9,7 +8,6 @@ using Content.Shared.Voting;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.Console; using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
@@ -30,7 +28,6 @@ namespace Content.Client.Voting.UI
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntityNetworkManager _entNetManager = default!; [Dependency] private readonly IEntityNetworkManager _entNetManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IStateManager _state = default!;
private VotingSystem _votingSystem; private VotingSystem _votingSystem;
@@ -65,7 +62,7 @@ namespace Content.Client.Voting.UI
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace; Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
CloseButton.OnPressed += _ => Close(); CloseButton.OnPressed += _ => Close();
VoteNotTrustedLabel.Text = Loc.GetString("ui-vote-trusted-users-notice", ("timeReq", _cfg.GetCVar(CCVars.VotekickEligibleVoterDeathtime))); VoteNotTrustedLabel.Text = Loc.GetString("ui-vote-trusted-users-notice", ("timeReq", _cfg.GetCVar(CCVars.VotekickEligibleVoterDeathtime) / 60));
foreach (StandardVoteType voteType in Enum.GetValues<StandardVoteType>()) foreach (StandardVoteType voteType in Enum.GetValues<StandardVoteType>())
{ {
@@ -73,7 +70,6 @@ namespace Content.Client.Voting.UI
VoteTypeButton.AddItem(Loc.GetString(option.Name), (int)voteType); VoteTypeButton.AddItem(Loc.GetString(option.Name), (int)voteType);
} }
_state.OnStateChanged += OnStateChanged;
VoteTypeButton.OnItemSelected += VoteTypeSelected; VoteTypeButton.OnItemSelected += VoteTypeSelected;
CreateButton.OnPressed += CreatePressed; CreateButton.OnPressed += CreatePressed;
FollowButton.OnPressed += FollowSelected; FollowButton.OnPressed += FollowSelected;
@@ -105,14 +101,6 @@ namespace Content.Client.Voting.UI
UpdateVoteTimeout(); UpdateVoteTimeout();
} }
private void OnStateChanged(StateChangedEventArgs obj)
{
if (obj.NewState is not GameplayState)
return;
Close();
}
private void CanCallVoteChanged(bool obj) private void CanCallVoteChanged(bool obj)
{ {
if (!obj) if (!obj)

View File

@@ -19,6 +19,11 @@ public sealed class VotingSystem : EntitySystem
private void OnVotePlayerListResponseEvent(VotePlayerListResponseEvent msg) private void OnVotePlayerListResponseEvent(VotePlayerListResponseEvent msg)
{ {
if (!_ghostSystem.IsGhost)
{
return;
}
VotePlayerListResponse?.Invoke(msg); VotePlayerListResponse?.Invoke(msg);
} }

View File

@@ -28,7 +28,6 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
[Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
[Dependency] private readonly MapSystem _map = default!;
private EntityQuery<TransformComponent> _xformQuery; private EntityQuery<TransformComponent> _xformQuery;
@@ -110,11 +109,11 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _))
{ {
coordinates = TransformSystem.ToCoordinates(gridUid, mousePos); coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager);
} }
else else
{ {
coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos); coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager);
} }
// Heavy attack. // Heavy attack.

View File

@@ -1,31 +0,0 @@
namespace Content.Client._CP14.Localization;
/// <summary>
/// Controls the visual of the sprite, depending on the localization. Useful for drawn lettering
/// </summary>
[RegisterComponent]
public sealed partial class CP14LocalizationVisualsComponent : Component
{
/// <summary>
/// map(map,(lang, state))
/// in yml:
///
/// - type: Sprite
/// layers:
/// - state: stateName0
/// map: ["map1"]
/// - state: stateName0
/// map: ["map2"]
/// - type: CP14LocalizationVisuals
/// mapStates:
/// map1:
/// ru-RU: stateName1
/// en-US: stateName2
/// map2:
/// ru-RU: stateName3
/// en-US: stateName4
///
/// </summary>
[DataField]
public Dictionary<string, Dictionary<string, string>> MapStates;
}

View File

@@ -1,29 +0,0 @@
using Content.Shared.Localizations;
using Robust.Client.GameObjects;
namespace Content.Client._CP14.Localization;
public sealed class CP14LocalizationVisualsSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14LocalizationVisualsComponent, ComponentInit>(OnCompInit);
}
private void OnCompInit(Entity<CP14LocalizationVisualsComponent> visuals, ref ComponentInit args)
{
if (!TryComp<SpriteComponent>(visuals, out var sprite))
return;
foreach (var (map, pDictionary) in visuals.Comp.MapStates)
{
if (!pDictionary.TryGetValue(ContentLocalizationManager.Culture, out var state))
return;
if (sprite.LayerMapTryGet(map, out _))
sprite.LayerSetState(map, state);
}
}
}

View File

@@ -1,35 +0,0 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Client.GameObjects;
namespace Content.Server._CP14.MagicRituals;
public partial class CP14ClientRitualSystem : CP14SharedRitualSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14MagicRitualComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAppearanceChange(Entity<CP14MagicRitualComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!args.Sprite.LayerMapTryGet(ent.Comp.RitualLayerMap, out var ritualLayer))
return;
if (_appearance.TryGetData<Color>(ent, RitualVisuals.Color, out var ritualColor, args.Component))
{
args.Sprite.LayerSetColor(ritualLayer, ritualColor);
}
if (_appearance.TryGetData<bool>(ent, RitualVisuals.Enabled, out var enabled, args.Component))
{
args.Sprite.LayerSetVisible(ritualLayer, enabled);
}
}
}

View File

@@ -1,7 +0,0 @@
using Content.Shared._CP14.MagicSpell;
namespace Content.Client._CP14.MagicSpell;
public sealed partial class CP14ClientMagicSystem : CP14SharedMagicSystem
{
}

View File

@@ -1,17 +0,0 @@
using Robust.Shared;
using Robust.Shared.Configuration;
namespace Content.Client._CP14.Shitcode;
/// <summary>
/// Эта система - сборник разного мелкого барахла, который слишком мелкий чтобы иметь свои собственные системы. В идеале в будущем разнести по отдельным файлам.
/// </summary>
public sealed class CP14EdSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override void Initialize()
{
_cfg.SetCVar(CVars.EntitiesCategoryFilter, "ForkFiltered");
}
}

View File

@@ -1,22 +0,0 @@
<Control xmlns="https://spacestation14.io">
<GridContainer Columns="6">
<TextureRect Name="GoldView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="GoldText"
Margin="0,0,5,0"/>
<TextureRect Name="SilverView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="SilverText"
Margin="0,0,5,0"/>
<TextureRect Name="CopperView"
MinSize="10 10"
Stretch="KeepAspectCentered"/>
<Label Name="CopperText"
Margin="0,0,5,0"/>
</GridContainer>
</Control>

View File

@@ -1,46 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14PriceControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly SpriteSystem _sprite;
public CP14PriceControl(int price)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entity.System<SpriteSystem>();
var rsiPath = new ResPath("_CP14/Interface/Misc/coins.rsi");
var total = price;
var gp = total / 100;
total %= 100;
var sp = total / 10;
total %= 10;
var cp = total;
CopperView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "c"));
CopperText.Text = cp.ToString();
SilverView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "s"));
SilverText.Text = sp.ToString();
GoldView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "g"));
GoldText.Text = gp.ToString();
}
}

View File

@@ -1,34 +0,0 @@
using Content.Shared._CP14.Cargo;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client._CP14.TravelingStoreShip;
public sealed class CP14StoreBoundUserInterface : BoundUserInterface
{
private CP14StoreWindow? _window;
public CP14StoreBoundUserInterface(EntityUid owner, [NotNull] Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = this.CreateWindow<CP14StoreWindow>();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
switch (state)
{
case CP14StoreUiState storeState:
_window?.UpdateUI(storeState);
break;
}
}
}

View File

@@ -1,15 +0,0 @@
<Control xmlns="https://spacestation14.io">
<Button Name="ProductButton" Access="Public">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<TextureRect Name="View"
MinSize="48 48"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Stretch="KeepAspectCentered"/>
<RichTextLabel Name="ProductName" VerticalAlignment="Center" Access="Public"/>
<BoxContainer Name="PriceHolder" VerticalAlignment="Center" HorizontalExpand="True" HorizontalAlignment="Right"/>
</BoxContainer>
</BoxContainer>
</Button>
</Control>

View File

@@ -1,44 +0,0 @@
using Content.Shared._CP14.Cargo;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Utility;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14StoreProductControl : Control
{
[Dependency] private readonly IEntityManager _entity = default!;
private readonly SpriteSystem _sprite;
public CP14StoreProductControl(CP14StoreUiProductEntry entry)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entity.System<SpriteSystem>();
UpdateName(entry.Name);
UpdateView(entry.Icon);
UpdatePrice(entry.Price);
}
private void UpdatePrice(int price)
{
PriceHolder.RemoveAllChildren();
PriceHolder.AddChild(new CP14PriceControl(price));
}
private void UpdateName(string name)
{
ProductName.Text = $"[bold]{name}[/bold]";
}
private void UpdateView(SpriteSpecifier spriteSpecifier)
{
View.Texture = _sprite.Frame0(spriteSpecifier);
}
}

View File

@@ -1,31 +0,0 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'cp14-store-ui-title'}"
MinSize="800 600"
SetSize="800 600">
<BoxContainer Orientation="Horizontal">
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
<!-- Product list (left side UI) -->
<TabContainer SizeFlagsStretchRatio="0.5" Name="Tabs" HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="BuyProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
</ScrollContainer>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
<BoxContainer Name="SellProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
</ScrollContainer>
</TabContainer>
<!-- Station trading data (right side UI) -->
<BoxContainer SizeFlagsStretchRatio="0.5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 10 0">
<controls:StripeBack>
<PanelContainer>
<Label Text="{Loc 'cp14-store-ui-order'}" Align="Center" Margin="0 5 0 3"/>
</PanelContainer>
</controls:StripeBack>
<Label Name="TravelTimeLabel" Text="00:00" HorizontalAlignment="Center" HorizontalExpand="True" Margin="0 15 0 0"/>
<controls:HLine Color="#404040" Thickness="2" Margin="0 5"/>
<RichTextLabel Name="SelectedName" HorizontalExpand="True" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5"/>
<RichTextLabel Name="SelectedDesc" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top" Margin="5"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</DefaultWindow>

View File

@@ -1,79 +0,0 @@
using Content.Shared._CP14.Cargo;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client._CP14.TravelingStoreShip;
[GenerateTypedNameReferences]
public sealed partial class CP14StoreWindow : DefaultWindow
{
[Dependency] private readonly IGameTiming _timing = default!;
private TimeSpan? _nextTravelTime;
private bool _onStation;
public CP14StoreWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Tabs.SetTabTitle(0, Loc.GetString("cp14-store-ui-tab-buy"));
Tabs.SetTabTitle(1, Loc.GetString("cp14-store-ui-tab-sell"));
}
public void UpdateUI(CP14StoreUiState state)
{
UpdateProducts(state);
_nextTravelTime = state.NextTravelTime;
_onStation = state.OnStation;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
//Updating time
if (_nextTravelTime is not null)
{
var time = _nextTravelTime.Value - _timing.CurTime;
TravelTimeLabel.Text =
$"{Loc.GetString(_onStation ? "cp14-store-ui-next-travel-out" : "cp14-store-ui-next-travel-in")} {Math.Max(time.Minutes, 0):00}:{Math.Max(time.Seconds, 0):00}";
}
}
private void UpdateProducts(CP14StoreUiState state)
{
BuyProductsContainer.RemoveAllChildren();
SellProductsContainer.RemoveAllChildren();
foreach (var product in state.ProductsBuy)
{
var control = new CP14StoreProductControl(product);
control.ProductButton.OnPressed += _ =>
{
SelectProduct(product);
};
BuyProductsContainer.AddChild(control);
}
foreach (var product in state.ProductsSell)
{
var control = new CP14StoreProductControl(product);
control.ProductButton.OnPressed += _ =>
{
SelectProduct(product);
};
SellProductsContainer.AddChild(control);
}
}
private void SelectProduct(CP14StoreUiProductEntry? entry)
{
SelectedName.Text = entry is null ? string.Empty : $"[bold]{entry.Value.Name}[/bold]";
SelectedDesc.Text = entry is null ? string.Empty : entry.Value.Desc;
}
}

View File

@@ -35,9 +35,9 @@ public sealed partial class TestPair
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0); mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
var plating = tileDefinitionManager[tile]; var plating = tileDefinitionManager[tile];
var platingTile = new Tile(plating.TileId); var platingTile = new Tile(plating.TileId);
Server.System<SharedMapSystem>().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile); mapData.Grid.Comp.SetTile(mapData.GridCoords, platingTile);
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId); mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
mapData.Tile = Server.System<SharedMapSystem>().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First(); mapData.Tile = mapData.Grid.Comp.GetAllTiles().First();
}); });
TestMap = mapData; TestMap = mapData;
@@ -107,41 +107,13 @@ public sealed partial class TestPair
/// <summary> /// <summary>
/// Retrieve all entity prototypes that have some component. /// Retrieve all entity prototypes that have some component.
/// </summary> /// </summary>
public List<(EntityPrototype, T)> GetPrototypesWithComponent<T>( public List<EntityPrototype> GetPrototypesWithComponent<T>(
HashSet<string>? ignored = null, HashSet<string>? ignored = null,
bool ignoreAbstract = true, bool ignoreAbstract = true,
bool ignoreTestPrototypes = true) bool ignoreTestPrototypes = true)
where T : IComponent where T : IComponent
{ {
var id = Server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(T)); var id = Server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(T));
var list = new List<(EntityPrototype, T)>();
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
{
if (ignored != null && ignored.Contains(proto.ID))
continue;
if (ignoreAbstract && proto.Abstract)
continue;
if (ignoreTestPrototypes && IsTestPrototype(proto))
continue;
if (proto.Components.TryGetComponent(id, out var cmp))
list.Add((proto, (T)cmp));
}
return list;
}
/// <summary>
/// Retrieve all entity prototypes that have some component.
/// </summary>
public List<EntityPrototype> GetPrototypesWithComponent(Type type,
HashSet<string>? ignored = null,
bool ignoreAbstract = true,
bool ignoreTestPrototypes = true)
{
var id = Server.ResolveDependency<IComponentFactory>().GetComponentName(type);
var list = new List<EntityPrototype>(); var list = new List<EntityPrototype>();
foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>()) foreach (var proto in Server.ProtoMan.EnumeratePrototypes<EntityPrototype>())
{ {
@@ -155,7 +127,7 @@ public sealed partial class TestPair
continue; continue;
if (proto.Components.ContainsKey(id)) if (proto.Components.ContainsKey(id))
list.Add((proto)); list.Add(proto);
} }
return list; return list;

View File

@@ -36,9 +36,7 @@ public static partial class PoolManager
(CCVars.ConfigPresetDevelopment.Name, "false"), (CCVars.ConfigPresetDevelopment.Name, "false"),
(CCVars.AdminLogsEnabled.Name, "false"), (CCVars.AdminLogsEnabled.Name, "false"),
(CCVars.AutosaveEnabled.Name, "false"), (CCVars.AutosaveEnabled.Name, "false"),
(CVars.NetBufferSize.Name, "0"), (CVars.NetBufferSize.Name, "0")
(CCVars.InteractionRateLimitCount.Name, "9999999"),
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
}; };
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings) public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)

View File

@@ -15,7 +15,7 @@ namespace Content.IntegrationTests.Tests.GameRules;
// Lets not let that happen again. // Lets not let that happen again.
[TestFixture] [TestFixture]
public sealed class AntagPreferenceTest public sealed class AntagPreferenceTest
{/* CP14 disabled {
[Test] [Test]
public async Task TestLobbyPlayersValid() public async Task TestLobbyPlayersValid()
{ {
@@ -72,5 +72,5 @@ public sealed class AntagPreferenceTest
await server.WaitPost(() => server.EntMan.DeleteEntity(uid)); await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
await pair.CleanReturnAsync(); await pair.CleanReturnAsync();
}*/ }
} }

View File

@@ -1,5 +1,4 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.GameTicking; using Content.Server.GameTicking;
@@ -121,8 +120,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind)); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
var roles = roleSys.MindGetAllRoleInfo(mind); var roles = roleSys.MindGetAllRoles(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander"); var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
Assert.That(cmdRoles.Count(), Is.EqualTo(1)); Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The second dummy player should be a medic // The second dummy player should be a medic
@@ -132,8 +131,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind)); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
roles = roleSys.MindGetAllRoleInfo(dummyMind); roles = roleSys.MindGetAllRoles(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic"); cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
Assert.That(cmdRoles.Count(), Is.EqualTo(1)); Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The other two players should have just spawned in as normal. // The other two players should have just spawned in as normal.
@@ -142,14 +141,13 @@ public sealed class NukeOpsTest
void CheckDummy(int i) void CheckDummy(int i)
{ {
var ent = dummyEnts[i]; var ent = dummyEnts[i];
var mindCrew = mindSys.GetMind(ent)!.Value; var mind = mindSys.GetMind(ent)!.Value;
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False); Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False); Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" }; Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
} }
// The game rule exists, and all the stations/shuttles/maps are properly initialized // The game rule exists, and all the stations/shuttles/maps are properly initialized
@@ -240,8 +238,7 @@ public sealed class NukeOpsTest
for (var i = 0; i < nukies.Length - 1; i++) for (var i = 0; i < nukies.Length - 1; i++)
{ {
entMan.DeleteEntity(nukies[i]); entMan.DeleteEntity(nukies[i]);
Assert.That(roundEndSys.IsRoundEndRequested, Assert.That(roundEndSys.IsRoundEndRequested, Is.False,
Is.False,
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!"); $"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
} }
// Delete the last nukie and make sure the round ends. // Delete the last nukie and make sure the round ends.

View File

@@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Roles.Jobs;
namespace Content.IntegrationTests.Tests.Internals; namespace Content.IntegrationTests.Tests.Internals;
@@ -24,7 +25,10 @@ public sealed class AutoInternalsTests
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var profile = new HumanoidCharacterProfile(); var profile = new HumanoidCharacterProfile();
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null); var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
{
Prototype = "TestInternalsDummy"
}, profile, station: null);
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!"); Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!"); Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");

View File

@@ -1,8 +1,10 @@
#nullable enable #nullable enable
using System.Linq; using System.Linq;
using Content.Server.Ghost;
using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Commands; using Content.Server.Mind.Commands;
using Content.Server.Players;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
@@ -16,6 +18,7 @@ using Robust.Server.Console;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -284,27 +287,27 @@ public sealed partial class MindTests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
}); });
var traitorRole = "MindRoleTraitor"; var traitorRole = new TraitorRoleComponent();
roleSystem.MindAddRole(mindId, traitorRole); roleSystem.MindAddRole(mindId, traitorRole);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
}); });
var jobRole = ""; var jobRole = new JobComponent();
roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole); roleSystem.MindAddRole(mindId, jobRole);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
}); });
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId); roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
@@ -312,15 +315,15 @@ public sealed partial class MindTests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
}); });
roleSystem.MindRemoveRole<JobRoleComponent>(mindId); roleSystem.MindRemoveRole<JobComponent>(mindId);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
}); });
}); });

View File

@@ -1,95 +0,0 @@
using System.Linq;
using Content.Server.Roles;
using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Reflection;
namespace Content.IntegrationTests.Tests.Minds;
[TestFixture]
public sealed class RoleTests
{
/// <summary>
/// Check that any prototype with a <see cref="MindRoleComponent"/> is properly configured
/// </summary>
[Test]
public async Task ValidateRolePrototypes()
{
await using var pair = await PoolManager.GetServerClient();
var jobComp = pair.Server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(JobRoleComponent));
Assert.Multiple(() =>
{
foreach (var (proto, comp) in pair.GetPrototypesWithComponent<MindRoleComponent>())
{
Assert.That(comp.AntagPrototype == null || comp.JobPrototype == null, $"Role {proto.ID} has both a job and antag prototype.");
Assert.That(!comp.ExclusiveAntag || comp.Antag, $"Role {proto.ID} is marked as an exclusive antag, despite not being an antag.");
Assert.That(comp.Antag || comp.AntagPrototype == null, $"Role {proto.ID} has an antag prototype, despite not being an antag.");
if (comp.JobPrototype != null)
Assert.That(proto.Components.ContainsKey(jobComp), $"Role {proto.ID} is a job, despite not having a job prototype.");
// It is possible that this is meant to be supported? Though I would assume that it would be for
// admin / prototype uploads, and that pre-defined roles should still check this.
Assert.That(!comp.Antag || comp.AntagPrototype != null , $"Role {proto.ID} is an antag, despite not having a antag prototype.");
}
});
await pair.CleanReturnAsync();
}
/// <summary>
/// Check that any prototype with a <see cref="JobRoleComponent"/> also has a properly configured
/// <see cref="MindRoleComponent"/>
/// </summary>
[Test]
public async Task ValidateJobPrototypes()
{
await using var pair = await PoolManager.GetServerClient();
var mindCompId = pair.Server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(MindRoleComponent));
Assert.Multiple(() =>
{
foreach (var (proto, comp) in pair.GetPrototypesWithComponent<JobRoleComponent>())
{
if (proto.Components.TryGetComponent(mindCompId, out var mindComp))
Assert.That(((MindRoleComponent)mindComp).JobPrototype, Is.Not.Null);
}
});
await pair.CleanReturnAsync();
}
/// <summary>
/// Check that any prototype with a component that inherits from <see cref="BaseMindRoleComponent"/> also has a
/// <see cref="MindRoleComponent"/>
/// </summary>
[Test]
public async Task ValidateRolesHaveMindRoleComp()
{
await using var pair = await PoolManager.GetServerClient();
var refMan = pair.Server.ResolveDependency<IReflectionManager>();
var mindCompId = pair.Server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(MindRoleComponent));
var compTypes = refMan.GetAllChildren(typeof(BaseMindRoleComponent))
.Append(typeof(RoleBriefingComponent))
.Where(x => !x.IsAbstract);
Assert.Multiple(() =>
{
foreach (var comp in compTypes)
{
foreach (var proto in pair.GetPrototypesWithComponent(comp))
{
Assert.That(proto.Components.ContainsKey(mindCompId), $"Role {proto.ID} does not have a {nameof(MindRoleComponent)} despite having a {comp.Name}");
}
}
});
await pair.CleanReturnAsync();
}
}

View File

@@ -50,8 +50,9 @@ namespace Content.IntegrationTests.Tests
"MeteorArena", "MeteorArena",
//CrystallPunk maps //CrystallPunk maps
"Village", "AlchemyTest",
"Island", "BattleRoyale",
"ExpeditionTest",
//CrystallPunk Map replacement end //CrystallPunk Map replacement end
}; };
@@ -235,6 +236,7 @@ namespace Content.IntegrationTests.Tests
// Test all availableJobs have spawnPoints // Test all availableJobs have spawnPoints
// This is done inside gamemap test because loading the map takes ages and we already have it. // This is done inside gamemap test because loading the map takes ages and we already have it.
/*
var comp = entManager.GetComponent<StationJobsComponent>(station); var comp = entManager.GetComponent<StationJobsComponent>(station);
var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys); var jobs = new HashSet<ProtoId<JobPrototype>>(comp.SetupAvailableJobs.Keys);
@@ -251,6 +253,7 @@ namespace Content.IntegrationTests.Tests
jobs.ExceptWith(spawnPoints); jobs.ExceptWith(spawnPoints);
Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}."); Assert.That(jobs, Is.Empty, $"There is no spawnpoints for {string.Join(", ", jobs)} on {mapProto}.");
*/ //CP14 disable job spawners test
} }
try try

View File

@@ -3,6 +3,7 @@ using Content.Server.Station.Systems;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -67,7 +68,10 @@ public sealed class LoadoutTests
profile.SetLoadout(new RoleLoadout("LoadoutTester")); profile.SetLoadout(new RoleLoadout("LoadoutTester"));
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null); var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
{
Prototype = "LoadoutTester"
}, profile, station: null);
var slotQuery = inventorySystem.GetSlotEnumerator(tester); var slotQuery = inventorySystem.GetSlotEnumerator(tester);
var checkedCount = 0; var checkedCount = 0;

View File

@@ -35,16 +35,15 @@ public sealed class StartingGearPrototypeStorageTest
{ {
foreach (var gearProto in protos) foreach (var gearProto in protos)
{ {
var backpackProto = ((IEquipmentLoadout) gearProto).GetGear("back");
if (backpackProto == string.Empty)
continue;
var bag = server.EntMan.SpawnEntity(backpackProto, coords);
var ents = new ValueList<EntityUid>(); var ents = new ValueList<EntityUid>();
foreach (var (slot, entProtos) in gearProto.Storage) foreach (var (slot, entProtos) in gearProto.Storage)
{ {
ents.Clear();
var storageProto = ((IEquipmentLoadout)gearProto).GetGear(slot);
if (storageProto == string.Empty)
continue;
var bag = server.EntMan.SpawnEntity(storageProto, coords);
if (entProtos.Count == 0) if (entProtos.Count == 0)
continue; continue;
@@ -60,8 +59,9 @@ public sealed class StartingGearPrototypeStorageTest
server.EntMan.DeleteEntity(ent); server.EntMan.DeleteEntity(ent);
} }
server.EntMan.DeleteEntity(bag);
} }
server.EntMan.DeleteEntity(bag);
} }
mapManager.DeleteMap(testMap.MapId); mapManager.DeleteMap(testMap.MapId);

View File

@@ -40,7 +40,7 @@ public sealed class PrototypeSaveTest
await pair.Client.WaitPost(() => await pair.Client.WaitPost(() =>
{ {
foreach (var (proto, _) in pair.GetPrototypesWithComponent<ItemComponent>(Ignored)) foreach (var proto in pair.GetPrototypesWithComponent<ItemComponent>(Ignored))
{ {
var dummy = pair.Client.EntMan.Spawn(proto.ID); var dummy = pair.Client.EntMan.Spawn(proto.ID);
pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy)); pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy));

View File

@@ -94,13 +94,14 @@ namespace Content.IntegrationTests.Tests
await Assert.MultipleAsync(async () => await Assert.MultipleAsync(async () =>
{ {
foreach (var (proto, fill) in pair.GetPrototypesWithComponent<StorageFillComponent>()) foreach (var proto in pair.GetPrototypesWithComponent<StorageFillComponent>())
{ {
if (proto.HasComponent<EntityStorageComponent>(compFact)) if (proto.HasComponent<EntityStorageComponent>(compFact))
continue; continue;
StorageComponent? storage = null; StorageComponent? storage = null;
ItemComponent? item = null; ItemComponent? item = null;
StorageFillComponent fill = default!;
var size = 0; var size = 0;
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
@@ -111,6 +112,7 @@ namespace Content.IntegrationTests.Tests
} }
proto.TryGetComponent("Item", out item); proto.TryGetComponent("Item", out item);
fill = (StorageFillComponent) proto.Components[id].Component;
size = GetFillSize(fill, false, protoMan, itemSys); size = GetFillSize(fill, false, protoMan, itemSys);
}); });
@@ -177,7 +179,7 @@ namespace Content.IntegrationTests.Tests
var itemSys = entMan.System<SharedItemSystem>(); var itemSys = entMan.System<SharedItemSystem>();
foreach (var (proto, fill) in pair.GetPrototypesWithComponent<StorageFillComponent>()) foreach (var proto in pair.GetPrototypesWithComponent<StorageFillComponent>())
{ {
if (proto.HasComponent<StorageComponent>(compFact)) if (proto.HasComponent<StorageComponent>(compFact))
continue; continue;
@@ -190,6 +192,7 @@ namespace Content.IntegrationTests.Tests
if (entStorage == null) if (entStorage == null)
return; return;
var fill = (StorageFillComponent) proto.Components[id].Component;
var size = GetFillSize(fill, true, protoMan, itemSys); var size = GetFillSize(fill, true, protoMan, itemSys);
Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity),
$"{proto.ID} storage fill is too large."); $"{proto.ID} storage fill is too large.");

View File

@@ -1,37 +0,0 @@
using System.Collections.Generic;
using Content.Shared._CP14.Cargo.Prototype;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests._CP14;
#nullable enable
[TestFixture]
public sealed class CP14CargoTest
{
[Test]
public async Task CheckAllBuyPositionsUniqueCode()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
HashSet<string> existedCodes = new();
foreach (var proto in protoManager.EnumeratePrototypes<CP14StoreBuyPositionPrototype>())
{
Assert.That(!existedCodes.Contains(proto.Code), $"Repeated purchasing code {proto.Code} of the {proto}");
existedCodes.Add(proto.Code);
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -1,41 +0,0 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests._CP14;
#nullable enable
[TestFixture]
public sealed class CP14EntityTest
{
[Test]
public async Task CheckAllCP14EntityHasForkFilteredCategory()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
if (!protoManager.TryIndex<EntityCategoryPrototype>("ForkFiltered", out var indexedFilter))
return;
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
{
if (!proto.ID.StartsWith("CP14"))
continue;
if (proto.Abstract || proto.HideSpawnMenu)
continue;
Assert.That(proto.Categories.Contains(indexedFilter), $"CP14 fork proto: {proto} does not marked abstract, or have a HideSpawnMenu or ForkFiltered category");
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -1,53 +0,0 @@
using Content.Shared._CP14.MagicRitual;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests._CP14;
#nullable enable
[TestFixture]
public sealed class CP14RitualTest
{
/// <summary>
/// States that all edges of the ritual phase have triggers.
/// </summary>
[Test]
public async Task RitualHasAllTriggersTest()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var compFactory = server.ResolveDependency<IComponentFactory>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
await server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
{
if (!proto.TryGetComponent(out CP14MagicRitualPhaseComponent? phase, compFactory))
continue;
if (phase.DeadEnd)
{
Assert.That(phase.Edges.Count == 0, $"{proto} is a ritual node, but has no paths to other nodes. Either add deadEnd = true, or add paths to other nodes.");
}
else
{
Assert.That(phase.Edges.Count > 0, $"{proto} is a deadEnd ritual node, but has {phase.Edges.Count} edges! Remove all edges, or make it a non dead-end node");
}
foreach (var edge in phase.Edges)
{
Assert.That(edge.Triggers.Count > 0, $"{proto} is ritual node, but edge to {edge.Target} has no triggers and cannot be activated.");
}
}
});
});
await pair.CleanReturnAsync();
}
}

View File

@@ -67,7 +67,7 @@ namespace Content.Server.Access.Systems
if (!TryComp<IdCardComponent>(uid, out var idCard)) if (!TryComp<IdCardComponent>(uid, out var idCard))
return; return;
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon); var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon);
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state); _uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
} }

View File

@@ -96,7 +96,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
PrivilegedIdIsAuthorized(uid, component), PrivilegedIdIsAuthorized(uid, component),
true, true,
targetIdComponent.FullName, targetIdComponent.FullName,
targetIdComponent.LocalizedJobTitle, targetIdComponent.JobTitle,
targetAccessComponent.Tags.ToList(), targetAccessComponent.Tags.ToList(),
possibleAccess, possibleAccess,
jobProto, jobProto,

View File

@@ -14,13 +14,13 @@ using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Asynchronous; using Robust.Shared.Asynchronous;
using Robust.Shared.Collections;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Managers; namespace Content.Server.Administration.Managers;
@@ -45,12 +45,14 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
public const string SawmillId = "admin.bans"; public const string SawmillId = "admin.bans";
public const string JobPrefix = "Job:"; public const string JobPrefix = "Job:";
private readonly Dictionary<ICommonSession, List<ServerRoleBanDef>> _cachedRoleBans = new(); private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
// Cached ban exemption flags are used to handle // Cached ban exemption flags are used to handle
private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new(); private readonly Dictionary<ICommonSession, ServerBanExemptFlags> _cachedBanExemptions = new();
public void Initialize() public void Initialize()
{ {
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_netManager.RegisterNetMessage<MsgRoleBans>(); _netManager.RegisterNetMessage<MsgRoleBans>();
_db.SubscribeToNotifications(OnDatabaseNotification); _db.SubscribeToNotifications(OnDatabaseNotification);
@@ -61,23 +63,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
private async Task CachePlayerData(ICommonSession player, CancellationToken cancel) private async Task CachePlayerData(ICommonSession player, CancellationToken cancel)
{ {
// Yeah so role ban loading code isn't integrated with exempt flag loading code.
// Have you seen how garbage role ban code code is? I don't feel like refactoring it right now.
var flags = await _db.GetBanExemption(player.UserId, cancel); var flags = await _db.GetBanExemption(player.UserId, cancel);
var netChannel = player.Channel;
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false);
var userRoleBans = new List<ServerRoleBanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
cancel.ThrowIfCancellationRequested(); cancel.ThrowIfCancellationRequested();
_cachedBanExemptions[player] = flags; _cachedBanExemptions[player] = flags;
_cachedRoleBans[player] = userRoleBans;
SendRoleBans(player);
} }
private void ClearPlayerData(ICommonSession player) private void ClearPlayerData(ICommonSession player)
@@ -85,15 +76,25 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
_cachedBanExemptions.Remove(player); _cachedBanExemptions.Remove(player);
} }
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected || _cachedRoleBans.ContainsKey(e.Session.UserId))
return;
var netChannel = e.Session.Channel;
ImmutableArray<byte>? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, hwId);
SendRoleBans(e.Session);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef) private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{ {
banDef = await _db.AddServerRoleBanAsync(banDef); banDef = await _db.AddServerRoleBanAsync(banDef);
if (banDef.UserId != null if (banDef.UserId != null)
&& _playerManager.TryGetSessionById(banDef.UserId, out var player)
&& _cachedRoleBans.TryGetValue(player, out var cachedBans))
{ {
cachedBans.Add(banDef); _cachedRoleBans.GetOrNew(banDef.UserId.Value).Add(banDef);
} }
return true; return true;
@@ -101,21 +102,31 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
public HashSet<string>? GetRoleBans(NetUserId playerUserId) public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{ {
if (!_playerManager.TryGetSessionById(playerUserId, out var session)) return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans)
return null;
return _cachedRoleBans.TryGetValue(session, out var roleBans)
? roleBans.Select(banDef => banDef.Role).ToHashSet() ? roleBans.Select(banDef => banDef.Role).ToHashSet()
: null; : null;
} }
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
{
var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false);
var userRoleBans = new HashSet<ServerRoleBanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
_cachedRoleBans[userId] = userRoleBans;
}
public void Restart() public void Restart()
{ {
// Clear out players that have disconnected. // Clear out players that have disconnected.
var toRemove = new ValueList<ICommonSession>(); var toRemove = new List<NetUserId>();
foreach (var player in _cachedRoleBans.Keys) foreach (var player in _cachedRoleBans.Keys)
{ {
if (player.Status == SessionStatus.Disconnected) if (!_playerManager.TryGetSessionById(player, out _))
toRemove.Add(player); toRemove.Add(player);
} }
@@ -127,7 +138,7 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
// Check for expired bans // Check for expired bans
foreach (var roleBans in _cachedRoleBans.Values) foreach (var roleBans in _cachedRoleBans.Values)
{ {
roleBans.RemoveAll(ban => DateTimeOffset.Now > ban.ExpirationTime); roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime);
} }
} }
@@ -270,9 +281,9 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires)); var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length))); _chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
if (target != null && _playerManager.TryGetSessionById(target.Value, out var session)) if (target != null)
{ {
SendRoleBans(session); SendRoleBans(target.Value);
} }
} }
@@ -300,12 +311,10 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now)); await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
if (ban.UserId is { } player if (ban.UserId is { } player && _cachedRoleBans.TryGetValue(player, out var roleBans))
&& _playerManager.TryGetSessionById(player, out var session)
&& _cachedRoleBans.TryGetValue(session, out var roleBans))
{ {
roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id); roleBans.RemoveWhere(roleBan => roleBan.Id == ban.Id);
SendRoleBans(session); SendRoleBans(player);
} }
return $"Pardoned ban with id {banId}"; return $"Pardoned ban with id {banId}";
@@ -313,12 +322,8 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId) public HashSet<ProtoId<JobPrototype>>? GetJobBans(NetUserId playerUserId)
{ {
if (!_playerManager.TryGetSessionById(playerUserId, out var session)) if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
return null; return null;
if (!_cachedRoleBans.TryGetValue(session, out var roleBans))
return null;
return roleBans return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal)) .Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
.Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..])) .Select(ban => new ProtoId<JobPrototype>(ban.Role[JobPrefix.Length..]))
@@ -326,9 +331,19 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
} }
#endregion #endregion
public void SendRoleBans(NetUserId userId)
{
if (!_playerManager.TryGetSessionById(userId, out var player))
{
return;
}
SendRoleBans(player);
}
public void SendRoleBans(ICommonSession pSession) public void SendRoleBans(ICommonSession pSession)
{ {
var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List<ServerRoleBanDef>(); var roleBans = _cachedRoleBans.GetValueOrDefault(pSession.UserId) ?? new HashSet<ServerRoleBanDef>();
var bans = new MsgRoleBans() var bans = new MsgRoleBans()
{ {
Bans = roleBans.Select(o => o.Role).ToList() Bans = roleBans.Select(o => o.Role).ToList()

View File

@@ -47,6 +47,12 @@ public interface IBanManager
/// <param name="unbanTime">The time at which this role ban was pardoned.</param> /// <param name="unbanTime">The time at which this role ban was pardoned.</param>
public Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime); public Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime);
/// <summary>
/// Sends role bans to the target
/// </summary>
/// <param name="pSession">Player's user ID</param>
public void SendRoleBans(NetUserId userId);
/// <summary> /// <summary>
/// Sends role bans to the target /// Sends role bans to the target
/// </summary> /// </summary>

View File

@@ -1,4 +1,3 @@
using Content.Server._CP14.Roles;
using Content.Server.Administration.Commands; using Content.Server.Administration.Commands;
using Content.Server.Antag; using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components; using Content.Server.GameTicking.Rules.Components;
@@ -53,23 +52,6 @@ public sealed partial class AdminVerbSystem
var targetPlayer = targetActor.PlayerSession; var targetPlayer = targetActor.PlayerSession;
Verb CP14Sociopath = new()
{
Text = Loc.GetString("cp14-admin-verb-text-make-sociopath"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/Color/black.rsi"),
"icon"), //TODO
Act = () =>
{
//_antag.ForceMakeAntag<CP14BanditRoleComponent>(targetPlayer, "CP14Bandit"); //TODO
},
Impact = LogImpact.High,
Message = Loc.GetString("cp14-admin-verb-make-sociopath"),
};
args.Verbs.Add(CP14Sociopath);
/* CP14 disable default antags
Verb traitor = new() Verb traitor = new()
{ {
Text = Loc.GetString("admin-verb-text-make-traitor"), Text = Loc.GetString("admin-verb-text-make-traitor"),
@@ -169,6 +151,5 @@ public sealed partial class AdminVerbSystem
Message = Loc.GetString("admin-verb-make-thief"), Message = Loc.GetString("admin-verb-make-thief"),
}; };
args.Verbs.Add(thief); args.Verbs.Add(thief);
*/
} }
} }

View File

@@ -95,10 +95,9 @@ public sealed partial class AdminVerbSystem
if (HasComp<MapComponent>(args.Target) || HasComp<MapGridComponent>(args.Target)) if (HasComp<MapComponent>(args.Target) || HasComp<MapGridComponent>(args.Target))
return; return;
var explodeName = Loc.GetString("admin-smite-explode-name").ToLowerInvariant();
Verb explode = new() Verb explode = new()
{ {
Text = explodeName, Text = "admin-smite-explode-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
Act = () => Act = () =>
@@ -112,14 +111,13 @@ public sealed partial class AdminVerbSystem
_bodySystem.GibBody(args.Target); _bodySystem.GibBody(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", explodeName, Loc.GetString("admin-smite-explode-description")) // we do this so the description tells admins the Text to run it via console. Message = Loc.GetString("admin-smite-explode-description")
}; };
args.Verbs.Add(explode); args.Verbs.Add(explode);
var chessName = Loc.GetString("admin-smite-chess-dimension-name").ToLowerInvariant();
Verb chess = new() Verb chess = new()
{ {
Text = chessName, Text = "admin-smite-chess-dimension-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Tabletop/chessboard.rsi"), "chessboard"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Tabletop/chessboard.rsi"), "chessboard"),
Act = () => Act = () =>
@@ -139,13 +137,12 @@ public sealed partial class AdminVerbSystem
xform.WorldRotation = Angle.Zero; xform.WorldRotation = Angle.Zero;
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description")) Message = Loc.GetString("admin-smite-chess-dimension-description")
}; };
args.Verbs.Add(chess); args.Verbs.Add(chess);
if (TryComp<FlammableComponent>(args.Target, out var flammable)) if (TryComp<FlammableComponent>(args.Target, out var flammable))
{ {
var flamesName = Loc.GetString("admin-smite-set-alight-name").ToLowerInvariant();
Verb flames = new() Verb flames = new()
{ {
Text = "admin-smite-set-alight-name", Text = "admin-smite-set-alight-name",
@@ -163,15 +160,14 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", flamesName, Loc.GetString("admin-smite-set-alight-description")) Message = Loc.GetString("admin-smite-set-alight-description")
}; };
args.Verbs.Add(flames); args.Verbs.Add(flames);
} }
var monkeyName = Loc.GetString("admin-smite-monkeyify-name").ToLowerInvariant();
Verb monkey = new() Verb monkey = new()
{ {
Text = monkeyName, Text = "admin-smite-monkeyify-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Animals/monkey.rsi"), "monkey"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Animals/monkey.rsi"), "monkey"),
Act = () => Act = () =>
@@ -179,14 +175,13 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminMonkeySmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", monkeyName, Loc.GetString("admin-smite-monkeyify-description")) Message = Loc.GetString("admin-smite-monkeyify-description")
}; };
args.Verbs.Add(monkey); args.Verbs.Add(monkey);
var disposalBinName = Loc.GetString("admin-smite-garbage-can-name").ToLowerInvariant();
Verb disposalBin = new() Verb disposalBin = new()
{ {
Text = disposalBinName, Text = "admin-smite-electrocute-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Piping/disposal.rsi"), "disposal"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Piping/disposal.rsi"), "disposal"),
Act = () => Act = () =>
@@ -194,17 +189,16 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", disposalBinName, Loc.GetString("admin-smite-garbage-can-description")) Message = Loc.GetString("admin-smite-garbage-can-description")
}; };
args.Verbs.Add(disposalBin); args.Verbs.Add(disposalBin);
if (TryComp<DamageableComponent>(args.Target, out var damageable) && if (TryComp<DamageableComponent>(args.Target, out var damageable) &&
HasComp<MobStateComponent>(args.Target)) HasComp<MobStateComponent>(args.Target))
{ {
var hardElectrocuteName = Loc.GetString("admin-smite-electrocute-name").ToLowerInvariant();
Verb hardElectrocute = new() Verb hardElectrocute = new()
{ {
Text = hardElectrocuteName, Text = "admin-smite-creampie-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Hands/Gloves/Color/yellow.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Hands/Gloves/Color/yellow.rsi"), "icon"),
Act = () => Act = () =>
@@ -240,17 +234,16 @@ public sealed partial class AdminVerbSystem
TimeSpan.FromSeconds(30), refresh: true, ignoreInsulation: true); TimeSpan.FromSeconds(30), refresh: true, ignoreInsulation: true);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", hardElectrocuteName, Loc.GetString("admin-smite-electrocute-description")) Message = Loc.GetString("admin-smite-electrocute-description")
}; };
args.Verbs.Add(hardElectrocute); args.Verbs.Add(hardElectrocute);
} }
if (TryComp<CreamPiedComponent>(args.Target, out var creamPied)) if (TryComp<CreamPiedComponent>(args.Target, out var creamPied))
{ {
var creamPieName = Loc.GetString("admin-smite-creampie-name").ToLowerInvariant();
Verb creamPie = new() Verb creamPie = new()
{ {
Text = creamPieName, Text = "admin-smite-remove-blood-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"),
Act = () => Act = () =>
@@ -258,17 +251,16 @@ public sealed partial class AdminVerbSystem
_creamPieSystem.SetCreamPied(args.Target, creamPied, true); _creamPieSystem.SetCreamPied(args.Target, creamPied, true);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", creamPieName, Loc.GetString("admin-smite-creampie-description")) Message = Loc.GetString("admin-smite-creampie-description")
}; };
args.Verbs.Add(creamPie); args.Verbs.Add(creamPie);
} }
if (TryComp<BloodstreamComponent>(args.Target, out var bloodstream)) if (TryComp<BloodstreamComponent>(args.Target, out var bloodstream))
{ {
var bloodRemovalName = Loc.GetString("admin-smite-remove-blood-name").ToLowerInvariant();
Verb bloodRemoval = new() Verb bloodRemoval = new()
{ {
Text = bloodRemovalName, Text = "admin-smite-vomit-organs-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Fluids/tomato_splat.rsi"), "puddle-1"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Fluids/tomato_splat.rsi"), "puddle-1"),
Act = () => Act = () =>
@@ -281,7 +273,7 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", bloodRemovalName, Loc.GetString("admin-smite-remove-blood-description")) Message = Loc.GetString("admin-smite-remove-blood-description")
}; };
args.Verbs.Add(bloodRemoval); args.Verbs.Add(bloodRemoval);
} }
@@ -289,10 +281,9 @@ public sealed partial class AdminVerbSystem
// bobby... // bobby...
if (TryComp<BodyComponent>(args.Target, out var body)) if (TryComp<BodyComponent>(args.Target, out var body))
{ {
var vomitOrgansName = Loc.GetString("admin-smite-vomit-organs-name").ToLowerInvariant();
Verb vomitOrgans = new() Verb vomitOrgans = new()
{ {
Text = vomitOrgansName, Text = "admin-smite-remove-hands-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Fluids/vomit_toxin.rsi"), "vomit_toxin-1"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Fluids/vomit_toxin.rsi"), "vomit_toxin-1"),
Act = () => Act = () =>
@@ -314,14 +305,13 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution); Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", vomitOrgansName, Loc.GetString("admin-smite-vomit-organs-description")) Message = Loc.GetString("admin-smite-vomit-organs-description")
}; };
args.Verbs.Add(vomitOrgans); args.Verbs.Add(vomitOrgans);
var handsRemovalName = Loc.GetString("admin-smite-remove-hands-name").ToLowerInvariant();
Verb handsRemoval = new() Verb handsRemoval = new()
{ {
Text = handsRemovalName, Text = "admin-smite-remove-hand-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hands.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hands.png")),
Act = () => Act = () =>
@@ -337,14 +327,13 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), true, PopupType.Medium); Filter.PvsExcept(args.Target), true, PopupType.Medium);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", handsRemovalName, Loc.GetString("admin-smite-remove-hands-description")) Message = Loc.GetString("admin-smite-remove-hands-description")
}; };
args.Verbs.Add(handsRemoval); args.Verbs.Add(handsRemoval);
var handRemovalName = Loc.GetString("admin-smite-remove-hand-name").ToLowerInvariant();
Verb handRemoval = new() Verb handRemoval = new()
{ {
Text = handRemovalName, Text = "admin-smite-pinball-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hand.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hand.png")),
Act = () => Act = () =>
@@ -361,14 +350,13 @@ public sealed partial class AdminVerbSystem
Filter.PvsExcept(args.Target), true, PopupType.Medium); Filter.PvsExcept(args.Target), true, PopupType.Medium);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", handRemovalName, Loc.GetString("admin-smite-remove-hand-description")) Message = Loc.GetString("admin-smite-remove-hand-description")
}; };
args.Verbs.Add(handRemoval); args.Verbs.Add(handRemoval);
var stomachRemovalName = Loc.GetString("admin-smite-stomach-removal-name").ToLowerInvariant();
Verb stomachRemoval = new() Verb stomachRemoval = new()
{ {
Text = stomachRemovalName, Text = "admin-smite-yeet-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"),
Act = () => Act = () =>
@@ -382,14 +370,13 @@ public sealed partial class AdminVerbSystem
args.Target, PopupType.LargeCaution); args.Target, PopupType.LargeCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", stomachRemovalName, Loc.GetString("admin-smite-stomach-removal-description")) Message = Loc.GetString("admin-smite-stomach-removal-description"),
}; };
args.Verbs.Add(stomachRemoval); args.Verbs.Add(stomachRemoval);
var lungRemovalName = Loc.GetString("admin-smite-lung-removal-name").ToLowerInvariant();
Verb lungRemoval = new() Verb lungRemoval = new()
{ {
Text = lungRemovalName, Text = "admin-smite-become-bread-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"),
Act = () => Act = () =>
@@ -403,17 +390,16 @@ public sealed partial class AdminVerbSystem
args.Target, PopupType.LargeCaution); args.Target, PopupType.LargeCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", lungRemovalName, Loc.GetString("admin-smite-lung-removal-description")) Message = Loc.GetString("admin-smite-lung-removal-description"),
}; };
args.Verbs.Add(lungRemoval); args.Verbs.Add(lungRemoval);
} }
if (TryComp<PhysicsComponent>(args.Target, out var physics)) if (TryComp<PhysicsComponent>(args.Target, out var physics))
{ {
var pinballName = Loc.GetString("admin-smite-pinball-name").ToLowerInvariant();
Verb pinball = new() Verb pinball = new()
{ {
Text = pinballName, Text = "admin-smite-ghostkick-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"),
Act = () => Act = () =>
@@ -441,14 +427,13 @@ public sealed partial class AdminVerbSystem
_physics.SetAngularDamping(args.Target, physics, 0f); _physics.SetAngularDamping(args.Target, physics, 0f);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", pinballName, Loc.GetString("admin-smite-pinball-description")) Message = Loc.GetString("admin-smite-pinball-description")
}; };
args.Verbs.Add(pinball); args.Verbs.Add(pinball);
var yeetName = Loc.GetString("admin-smite-yeet-name").ToLowerInvariant();
Verb yeet = new() Verb yeet = new()
{ {
Text = yeetName, Text = "admin-smite-nyanify-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
Act = () => Act = () =>
@@ -472,12 +457,11 @@ public sealed partial class AdminVerbSystem
_physics.SetAngularDamping(args.Target, physics, 0f); _physics.SetAngularDamping(args.Target, physics, 0f);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", yeetName, Loc.GetString("admin-smite-yeet-description")) Message = Loc.GetString("admin-smite-yeet-description")
}; };
args.Verbs.Add(yeet); args.Verbs.Add(yeet);
} }
var breadName = Loc.GetString("admin-smite-become-bread-name").ToLowerInvariant(); // Will I get cancelled for breadName-ing you?
Verb bread = new() Verb bread = new()
{ {
Text = "admin-smite-kill-sign-name", Text = "admin-smite-kill-sign-name",
@@ -488,11 +472,10 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminBreadSmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", breadName, Loc.GetString("admin-smite-become-bread-description")) Message = Loc.GetString("admin-smite-become-bread-description")
}; };
args.Verbs.Add(bread); args.Verbs.Add(bread);
var mouseName = Loc.GetString("admin-smite-become-mouse-name").ToLowerInvariant();
Verb mouse = new() Verb mouse = new()
{ {
Text = "admin-smite-cluwne-name", Text = "admin-smite-cluwne-name",
@@ -503,16 +486,15 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminMouseSmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminMouseSmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", mouseName, Loc.GetString("admin-smite-become-mouse-description")) Message = Loc.GetString("admin-smite-become-mouse-description")
}; };
args.Verbs.Add(mouse); args.Verbs.Add(mouse);
if (TryComp<ActorComponent>(args.Target, out var actorComponent)) if (TryComp<ActorComponent>(args.Target, out var actorComponent))
{ {
var ghostKickName = Loc.GetString("admin-smite-ghostkick-name").ToLowerInvariant();
Verb ghostKick = new() Verb ghostKick = new()
{ {
Text = ghostKickName, Text = "admin-smite-anger-pointing-arrows-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/gavel.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/gavel.svg.192dpi.png")),
Act = () => Act = () =>
@@ -520,18 +502,15 @@ public sealed partial class AdminVerbSystem
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.Channel, "Smitten."); _ghostKickManager.DoDisconnect(actorComponent.PlayerSession.Channel, "Smitten.");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", ghostKickName, Loc.GetString("admin-smite-ghostkick-description")) Message = Loc.GetString("admin-smite-ghostkick-description")
}; };
args.Verbs.Add(ghostKick); args.Verbs.Add(ghostKick);
} }
if (TryComp<InventoryComponent>(args.Target, out var inventory)) if (TryComp<InventoryComponent>(args.Target, out var inventory)) {
{
var nyanifyName = Loc.GetString("admin-smite-nyanify-name").ToLowerInvariant();
Verb nyanify = new() Verb nyanify = new()
{ {
Text = nyanifyName, Text = "admin-smite-dust-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Head/Hats/catears.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Head/Hats/catears.rsi"), "icon"),
Act = () => Act = () =>
@@ -542,14 +521,13 @@ public sealed partial class AdminVerbSystem
_inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory); _inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", nyanifyName, Loc.GetString("admin-smite-nyanify-description")) Message = Loc.GetString("admin-smite-nyanify-description")
}; };
args.Verbs.Add(nyanify); args.Verbs.Add(nyanify);
var killSignName = Loc.GetString("admin-smite-kill-sign-name").ToLowerInvariant();
Verb killSign = new() Verb killSign = new()
{ {
Text = killSignName, Text = "admin-smite-buffering-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Misc/killsign.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Misc/killsign.rsi"), "icon"),
Act = () => Act = () =>
@@ -557,14 +535,13 @@ public sealed partial class AdminVerbSystem
EnsureComp<KillSignComponent>(args.Target); EnsureComp<KillSignComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", killSignName, Loc.GetString("admin-smite-kill-sign-description")) Message = Loc.GetString("admin-smite-kill-sign-description")
}; };
args.Verbs.Add(killSign); args.Verbs.Add(killSign);
var cluwneName = Loc.GetString("admin-smite-cluwne-name").ToLowerInvariant();
Verb cluwne = new() Verb cluwne = new()
{ {
Text = cluwneName, Text = "admin-smite-become-instrument-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Mask/cluwne.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Mask/cluwne.rsi"), "icon"),
@@ -574,14 +551,13 @@ public sealed partial class AdminVerbSystem
EnsureComp<CluwneComponent>(args.Target); EnsureComp<CluwneComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", cluwneName, Loc.GetString("admin-smite-cluwne-description")) Message = Loc.GetString("admin-smite-cluwne-description")
}; };
args.Verbs.Add(cluwne); args.Verbs.Add(cluwne);
var maidenName = Loc.GetString("admin-smite-maid-name").ToLowerInvariant();
Verb maiden = new() Verb maiden = new()
{ {
Text = maidenName, Text = "admin-smite-remove-gravity-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi"), "icon"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi"), "icon"),
Act = () => Act = () =>
@@ -594,15 +570,14 @@ public sealed partial class AdminVerbSystem
}); });
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", maidenName, Loc.GetString("admin-smite-maid-description")) Message = Loc.GetString("admin-smite-maid-description")
}; };
args.Verbs.Add(maiden); args.Verbs.Add(maiden);
} }
var angerPointingArrowsName = Loc.GetString("admin-smite-anger-pointing-arrows-name").ToLowerInvariant();
Verb angerPointingArrows = new() Verb angerPointingArrows = new()
{ {
Text = angerPointingArrowsName, Text = "admin-smite-reptilian-species-swap-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/pointing.rsi"), "pointing"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/pointing.rsi"), "pointing"),
Act = () => Act = () =>
@@ -610,14 +585,13 @@ public sealed partial class AdminVerbSystem
EnsureComp<PointingArrowAngeringComponent>(args.Target); EnsureComp<PointingArrowAngeringComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", angerPointingArrowsName, Loc.GetString("admin-smite-anger-pointing-arrows-description")) Message = Loc.GetString("admin-smite-anger-pointing-arrows-description")
}; };
args.Verbs.Add(angerPointingArrows); args.Verbs.Add(angerPointingArrows);
var dustName = Loc.GetString("admin-smite-dust-name").ToLowerInvariant();
Verb dust = new() Verb dust = new()
{ {
Text = dustName, Text = "admin-smite-locker-stuff-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Materials/materials.rsi"), "ash"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Materials/materials.rsi"), "ash"),
Act = () => Act = () =>
@@ -627,14 +601,13 @@ public sealed partial class AdminVerbSystem
_popupSystem.PopupEntity(Loc.GetString("admin-smite-turned-ash-other", ("name", args.Target)), args.Target, PopupType.LargeCaution); _popupSystem.PopupEntity(Loc.GetString("admin-smite-turned-ash-other", ("name", args.Target)), args.Target, PopupType.LargeCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", dustName, Loc.GetString("admin-smite-dust-description")) Message = Loc.GetString("admin-smite-dust-description"),
}; };
args.Verbs.Add(dust); args.Verbs.Add(dust);
var youtubeVideoSimulationName = Loc.GetString("admin-smite-buffering-name").ToLowerInvariant();
Verb youtubeVideoSimulation = new() Verb youtubeVideoSimulation = new()
{ {
Text = youtubeVideoSimulationName, Text = "admin-smite-headstand-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Misc/buffering_smite_icon.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Misc/buffering_smite_icon.png")),
Act = () => Act = () =>
@@ -642,11 +615,10 @@ public sealed partial class AdminVerbSystem
EnsureComp<BufferingComponent>(args.Target); EnsureComp<BufferingComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", youtubeVideoSimulationName, Loc.GetString("admin-smite-buffering-description")) Message = Loc.GetString("admin-smite-buffering-description"),
}; };
args.Verbs.Add(youtubeVideoSimulation); args.Verbs.Add(youtubeVideoSimulation);
var instrumentationName = Loc.GetString("admin-smite-become-instrument-name").ToLowerInvariant();
Verb instrumentation = new() Verb instrumentation = new()
{ {
Text = "admin-smite-become-mouse-name", Text = "admin-smite-become-mouse-name",
@@ -657,14 +629,13 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", instrumentationName, Loc.GetString("admin-smite-become-instrument-description")) Message = Loc.GetString("admin-smite-become-instrument-description"),
}; };
args.Verbs.Add(instrumentation); args.Verbs.Add(instrumentation);
var noGravityName = Loc.GetString("admin-smite-remove-gravity-name").ToLowerInvariant();
Verb noGravity = new() Verb noGravity = new()
{ {
Text = noGravityName, Text = "admin-smite-maid-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Machines/gravity_generator.rsi"), "off"), Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Machines/gravity_generator.rsi"), "off"),
Act = () => Act = () =>
@@ -675,14 +646,13 @@ public sealed partial class AdminVerbSystem
Dirty(args.Target, grav); Dirty(args.Target, grav);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description")) Message = Loc.GetString("admin-smite-remove-gravity-description"),
}; };
args.Verbs.Add(noGravity); args.Verbs.Add(noGravity);
var reptilianName = Loc.GetString("admin-smite-reptilian-species-swap-name").ToLowerInvariant();
Verb reptilian = new() Verb reptilian = new()
{ {
Text = reptilianName, Text = "admin-smite-zoom-in-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"),
Act = () => Act = () =>
@@ -690,14 +660,13 @@ public sealed partial class AdminVerbSystem
_polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite"); _polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", reptilianName, Loc.GetString("admin-smite-reptilian-species-swap-description")) Message = Loc.GetString("admin-smite-reptilian-species-swap-description"),
}; };
args.Verbs.Add(reptilian); args.Verbs.Add(reptilian);
var lockerName = Loc.GetString("admin-smite-locker-stuff-name").ToLowerInvariant();
Verb locker = new() Verb locker = new()
{ {
Text = lockerName, Text = "admin-smite-flip-eye-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Storage/closet.rsi"), "generic"), Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Storage/closet.rsi"), "generic"),
Act = () => Act = () =>
@@ -713,11 +682,10 @@ public sealed partial class AdminVerbSystem
_weldableSystem.SetWeldedState(locker, true); _weldableSystem.SetWeldedState(locker, true);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", lockerName, Loc.GetString("admin-smite-locker-stuff-description")) Message = Loc.GetString("admin-smite-locker-stuff-description"),
}; };
args.Verbs.Add(locker); args.Verbs.Add(locker);
var headstandName = Loc.GetString("admin-smite-headstand-name").ToLowerInvariant();
Verb headstand = new() Verb headstand = new()
{ {
Text = "admin-smite-run-walk-swap-name", Text = "admin-smite-run-walk-swap-name",
@@ -728,14 +696,13 @@ public sealed partial class AdminVerbSystem
EnsureComp<HeadstandComponent>(args.Target); EnsureComp<HeadstandComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", headstandName, Loc.GetString("admin-smite-headstand-description")) Message = Loc.GetString("admin-smite-headstand-description"),
}; };
args.Verbs.Add(headstand); args.Verbs.Add(headstand);
var zoomInName = Loc.GetString("admin-smite-zoom-in-name").ToLowerInvariant();
Verb zoomIn = new() Verb zoomIn = new()
{ {
Text = zoomInName, Text = "admin-smite-super-speed-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/zoom.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/zoom.png")),
Act = () => Act = () =>
@@ -744,14 +711,13 @@ public sealed partial class AdminVerbSystem
_eyeSystem.SetZoom(args.Target, eye.TargetZoom * 0.2f, ignoreLimits: true); _eyeSystem.SetZoom(args.Target, eye.TargetZoom * 0.2f, ignoreLimits: true);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", zoomInName, Loc.GetString("admin-smite-zoom-in-description")) Message = Loc.GetString("admin-smite-zoom-in-description"),
}; };
args.Verbs.Add(zoomIn); args.Verbs.Add(zoomIn);
var flipEyeName = Loc.GetString("admin-smite-flip-eye-name").ToLowerInvariant();
Verb flipEye = new() Verb flipEye = new()
{ {
Text = flipEyeName, Text = "admin-smite-stomach-removal-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/flip.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/flip.png")),
Act = () => Act = () =>
@@ -760,14 +726,13 @@ public sealed partial class AdminVerbSystem
_eyeSystem.SetZoom(args.Target, eye.TargetZoom * -1, ignoreLimits: true); _eyeSystem.SetZoom(args.Target, eye.TargetZoom * -1, ignoreLimits: true);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", flipEyeName, Loc.GetString("admin-smite-flip-eye-description")) Message = Loc.GetString("admin-smite-flip-eye-description"),
}; };
args.Verbs.Add(flipEye); args.Verbs.Add(flipEye);
var runWalkSwapName = Loc.GetString("admin-smite-run-walk-swap-name").ToLowerInvariant();
Verb runWalkSwap = new() Verb runWalkSwap = new()
{ {
Text = runWalkSwapName, Text = "admin-smite-speak-backwards-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/run-walk-swap.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/run-walk-swap.png")),
Act = () => Act = () =>
@@ -781,14 +746,13 @@ public sealed partial class AdminVerbSystem
args.Target, PopupType.LargeCaution); args.Target, PopupType.LargeCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", runWalkSwapName, Loc.GetString("admin-smite-run-walk-swap-description")) Message = Loc.GetString("admin-smite-run-walk-swap-description"),
}; };
args.Verbs.Add(runWalkSwap); args.Verbs.Add(runWalkSwap);
var backwardsAccentName = Loc.GetString("admin-smite-speak-backwards-name").ToLowerInvariant();
Verb backwardsAccent = new() Verb backwardsAccent = new()
{ {
Text = backwardsAccentName, Text = "admin-smite-lung-removal-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/help-backwards.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/help-backwards.png")),
Act = () => Act = () =>
@@ -796,14 +760,13 @@ public sealed partial class AdminVerbSystem
EnsureComp<BackwardsAccentComponent>(args.Target); EnsureComp<BackwardsAccentComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", backwardsAccentName, Loc.GetString("admin-smite-speak-backwards-description")) Message = Loc.GetString("admin-smite-speak-backwards-description"),
}; };
args.Verbs.Add(backwardsAccent); args.Verbs.Add(backwardsAccent);
var disarmProneName = Loc.GetString("admin-smite-disarm-prone-name").ToLowerInvariant();
Verb disarmProne = new() Verb disarmProne = new()
{ {
Text = disarmProneName, Text = "admin-smite-disarm-prone-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/disarm.png")), Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/disarm.png")),
Act = () => Act = () =>
@@ -811,11 +774,10 @@ public sealed partial class AdminVerbSystem
EnsureComp<DisarmProneComponent>(args.Target); EnsureComp<DisarmProneComponent>(args.Target);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", disarmProneName, Loc.GetString("admin-smite-disarm-prone-description")) Message = Loc.GetString("admin-smite-disarm-prone-description"),
}; };
args.Verbs.Add(disarmProne); args.Verbs.Add(disarmProne);
var superSpeedName = Loc.GetString("admin-smite-super-speed-name").ToLowerInvariant();
Verb superSpeed = new() Verb superSpeed = new()
{ {
Text = "admin-smite-garbage-can-name", Text = "admin-smite-garbage-can-name",
@@ -830,45 +792,41 @@ public sealed partial class AdminVerbSystem
args.Target, PopupType.LargeCaution); args.Target, PopupType.LargeCaution);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", superSpeedName, Loc.GetString("admin-smite-super-speed-description")) Message = Loc.GetString("admin-smite-super-speed-description"),
}; };
args.Verbs.Add(superSpeed); args.Verbs.Add(superSpeed);
//Bonk //Bonk
var superBonkLiteName = Loc.GetString("admin-smite-super-bonk-lite-name").ToLowerInvariant();
Verb superBonkLite = new() Verb superBonkLite = new()
{ {
Text = superBonkLiteName, Text = "admin-smite-super-bonk-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/glass.rsi"), "full"), Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/glass.rsi"), "full"),
Act = () => Act = () =>
{ {
_superBonkSystem.StartSuperBonk(args.Target, stopWhenDead: true); _superBonkSystem.StartSuperBonk(args.Target, stopWhenDead: true);
}, },
Message = Loc.GetString("admin-smite-super-bonk-lite-description"),
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", superBonkLiteName, Loc.GetString("admin-smite-super-bonk-lite-description"))
}; };
args.Verbs.Add(superBonkLite); args.Verbs.Add(superBonkLite);
var superBonkName = Loc.GetString("admin-smite-super-bonk-name").ToLowerInvariant();
Verb superBonk= new() Verb superBonk= new()
{ {
Text = superBonkName, Text = "admin-smite-super-bonk-lite-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/generic.rsi"), "full"), Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/generic.rsi"), "full"),
Act = () => Act = () =>
{ {
_superBonkSystem.StartSuperBonk(args.Target); _superBonkSystem.StartSuperBonk(args.Target);
}, },
Message = Loc.GetString("admin-smite-super-bonk-description"),
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", superBonkName, Loc.GetString("admin-smite-super-bonk-description"))
}; };
args.Verbs.Add(superBonk); args.Verbs.Add(superBonk);
var superslipName = Loc.GetString("admin-smite-super-slip-name").ToLowerInvariant();
Verb superslip = new() Verb superslip = new()
{ {
Text = superslipName, Text = "admin-smite-super-slip-name",
Category = VerbCategory.Smite, Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Janitorial/soap.rsi"), "omega-4"), Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Janitorial/soap.rsi"), "omega-4"),
Act = () => Act = () =>
@@ -888,7 +846,7 @@ public sealed partial class AdminVerbSystem
} }
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description")) Message = Loc.GetString("admin-smite-super-slip-description")
}; };
args.Verbs.Add(superslip); args.Verbs.Add(superslip);
} }

View File

@@ -52,6 +52,7 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly StationJobsSystem _stationJobsSystem = default!; [Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
[Dependency] private readonly JointSystem _jointSystem = default!; [Dependency] private readonly JointSystem _jointSystem = default!;
[Dependency] private readonly BatterySystem _batterySystem = default!; [Dependency] private readonly BatterySystem _batterySystem = default!;
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly GunSystem _gun = default!; [Dependency] private readonly GunSystem _gun = default!;
@@ -89,22 +90,22 @@ public sealed partial class AdminVerbSystem
args.Verbs.Add(bolt); args.Verbs.Add(bolt);
} }
if (TryComp<AirlockComponent>(args.Target, out var airlockComp)) if (TryComp<AirlockComponent>(args.Target, out var airlock))
{ {
Verb emergencyAccess = new() Verb emergencyAccess = new()
{ {
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On", Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
Category = VerbCategory.Tricks, Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
Act = () => Act = () =>
{ {
_airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess); _airlockSystem.ToggleEmergencyAccess(args.Target, airlock);
}, },
Impact = LogImpact.Medium, Impact = LogImpact.Medium,
Message = Loc.GetString(airlockComp.EmergencyAccess Message = Loc.GetString(airlock.EmergencyAccess
? "admin-trick-emergency-access-off-description" ? "admin-trick-emergency-access-off-description"
: "admin-trick-emergency-access-on-description"), : "admin-trick-emergency-access-on-description"),
Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn), Priority = (int) (airlock.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
}; };
args.Verbs.Add(emergencyAccess); args.Verbs.Add(emergencyAccess);
} }
@@ -326,7 +327,7 @@ public sealed partial class AdminVerbSystem
Act = () => Act = () =>
{ {
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player); var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
_transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One)); _xformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
}, },
Impact = LogImpact.Medium, Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-send-to-test-arena-description"), Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
@@ -532,7 +533,7 @@ public sealed partial class AdminVerbSystem
if (shuttle is null) if (shuttle is null)
return; return;
_transformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero)); _xformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
}, },
Impact = LogImpact.Low, Impact = LogImpact.Low,
Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"), Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"),

View File

@@ -35,10 +35,10 @@ using Robust.Shared.Toolshed;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using Content.Server.Silicons.Laws; using Content.Server.Silicons.Laws;
using Content.Shared.Movement.Components; using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components; using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player; using Robust.Server.Player;
using Content.Shared.Silicons.StationAi; using Content.Shared.Mind;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using static Content.Shared.Configurable.ConfigurationComponent; using static Content.Shared.Configurable.ConfigurationComponent;
@@ -63,6 +63,7 @@ namespace Content.Server.Administration.Systems
[Dependency] private readonly ArtifactSystem _artifactSystem = default!; [Dependency] private readonly ArtifactSystem _artifactSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly PrayerSystem _prayerSystem = default!; [Dependency] private readonly PrayerSystem _prayerSystem = default!;
[Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly ToolshedManager _toolshed = default!; [Dependency] private readonly ToolshedManager _toolshed = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!; [Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
@@ -293,7 +294,7 @@ namespace Content.Server.Administration.Systems
Act = () => Act = () =>
{ {
var ui = new AdminLogsEui(); var ui = new AdminLogsEui();
_euiManager.OpenEui(ui, player); _eui.OpenEui(ui, player);
ui.SetLogFilter(search:args.Target.Id.ToString()); ui.SetLogFilter(search:args.Target.Id.ToString());
}, },
Impact = LogImpact.Low Impact = LogImpact.Low
@@ -347,30 +348,7 @@ namespace Content.Server.Administration.Systems
Impact = LogImpact.Low Impact = LogImpact.Low
}); });
// This logic is needed to be able to modify the AI's laws through its core and eye. if (TryComp<SiliconLawBoundComponent>(args.Target, out var lawBoundComponent))
EntityUid? target = null;
SiliconLawBoundComponent? lawBoundComponent = null;
if (TryComp(args.Target, out lawBoundComponent))
{
target = args.Target;
}
// When inspecting the core we can find the entity with its laws by looking at the AiHolderComponent.
else if (TryComp<StationAiHolderComponent>(args.Target, out var holder) && holder.Slot.Item != null
&& TryComp(holder.Slot.Item, out lawBoundComponent))
{
target = holder.Slot.Item.Value;
// For the eye we can find the entity with its laws as the source of the movement relay since the eye
// is just a proxy for it to move around and look around the station.
}
else if (TryComp<MovementRelayTargetComponent>(args.Target, out var relay)
&& TryComp(relay.Source, out lawBoundComponent))
{
target = relay.Source;
}
if (lawBoundComponent != null && target != null)
{ {
args.Verbs.Add(new Verb() args.Verbs.Add(new Verb()
{ {
@@ -384,7 +362,7 @@ namespace Content.Server.Administration.Systems
return; return;
} }
_euiManager.OpenEui(ui, session); _euiManager.OpenEui(ui, session);
ui.UpdateLaws(lawBoundComponent, target.Value); ui.UpdateLaws(lawBoundComponent, args.Target);
}, },
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"), Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Actions/actions_borg.rsi"), "state-laws"),
}); });

View File

@@ -15,7 +15,6 @@ using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Players.RateLimiting;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared; using Robust.Shared;
@@ -47,23 +46,20 @@ namespace Content.Server.Administration.Systems
[GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")] [GeneratedRegex(@"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$")]
private static partial Regex DiscordRegex(); private static partial Regex DiscordRegex();
private string _webhookUrl = string.Empty;
private WebhookData? _webhookData;
private string _onCallUrl = string.Empty;
private WebhookData? _onCallData;
private ISawmill _sawmill = default!; private ISawmill _sawmill = default!;
private readonly HttpClient _httpClient = new(); private readonly HttpClient _httpClient = new();
private string _webhookUrl = string.Empty;
private WebhookData? _webhookData;
private string _footerIconUrl = string.Empty; private string _footerIconUrl = string.Empty;
private string _avatarUrl = string.Empty; private string _avatarUrl = string.Empty;
private string _serverName = string.Empty; private string _serverName = string.Empty;
private readonly Dictionary<NetUserId, DiscordRelayInteraction> _relayMessages = new(); private readonly
Dictionary<NetUserId, (string? id, string username, string description, string? characterName, GameRunLevel
lastRunLevel)> _relayMessages = new();
private Dictionary<NetUserId, string> _oldMessageIds = new(); private Dictionary<NetUserId, string> _oldMessageIds = new();
private readonly Dictionary<NetUserId, Queue<DiscordRelayedData>> _messageQueues = new(); private readonly Dictionary<NetUserId, Queue<string>> _messageQueues = new();
private readonly HashSet<NetUserId> _processingChannels = new(); private readonly HashSet<NetUserId> _processingChannels = new();
private readonly Dictionary<NetUserId, (TimeSpan Timestamp, bool Typing)> _typingUpdateTimestamps = new(); private readonly Dictionary<NetUserId, (TimeSpan Timestamp, bool Typing)> _typingUpdateTimestamps = new();
private string _overrideClientName = string.Empty; private string _overrideClientName = string.Empty;
@@ -85,16 +81,12 @@ namespace Content.Server.Administration.Systems
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
Subs.CVar(_config, CCVars.DiscordOnCallWebhook, OnCallChanged, true);
Subs.CVar(_config, CCVars.DiscordAHelpWebhook, OnWebhookChanged, true); Subs.CVar(_config, CCVars.DiscordAHelpWebhook, OnWebhookChanged, true);
Subs.CVar(_config, CCVars.DiscordAHelpFooterIcon, OnFooterIconChanged, true); Subs.CVar(_config, CCVars.DiscordAHelpFooterIcon, OnFooterIconChanged, true);
Subs.CVar(_config, CCVars.DiscordAHelpAvatar, OnAvatarChanged, true); Subs.CVar(_config, CCVars.DiscordAHelpAvatar, OnAvatarChanged, true);
Subs.CVar(_config, CVars.GameHostName, OnServerNameChanged, true); Subs.CVar(_config, CVars.GameHostName, OnServerNameChanged, true);
Subs.CVar(_config, CCVars.AdminAhelpOverrideClientName, OnOverrideChanged, true); Subs.CVar(_config, CCVars.AdminAhelpOverrideClientName, OnOverrideChanged, true);
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("AHELP"); _sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("AHELP");
var defaultParams = new AHelpMessageParams( var defaultParams = new AHelpMessageParams(
string.Empty, string.Empty,
string.Empty, string.Empty,
@@ -103,7 +95,7 @@ namespace Content.Server.Administration.Systems
_gameTicker.RunLevel, _gameTicker.RunLevel,
playedSound: false playedSound: false
); );
_maxAdditionalChars = GenerateAHelpMessage(defaultParams).Message.Length; _maxAdditionalChars = GenerateAHelpMessage(defaultParams).Length;
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged); SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
@@ -112,37 +104,12 @@ namespace Content.Server.Administration.Systems
_rateLimit.Register( _rateLimit.Register(
RateLimitKey, RateLimitKey,
new RateLimitRegistration(CCVars.AhelpRateLimitPeriod, new RateLimitRegistration
CCVars.AhelpRateLimitCount, {
PlayerRateLimitedAction) CVarLimitPeriodLength = CCVars.AhelpRateLimitPeriod,
); CVarLimitCount = CCVars.AhelpRateLimitCount,
} PlayerLimitedAction = PlayerRateLimitedAction
});
private async void OnCallChanged(string url)
{
_onCallUrl = url;
if (url == string.Empty)
return;
var match = DiscordRegex().Match(url);
if (!match.Success)
{
Log.Error("On call URL does not appear to be valid.");
return;
}
if (match.Groups.Count <= 2)
{
Log.Error("Could not get webhook ID or token for on call URL.");
return;
}
var webhookId = match.Groups[1].Value;
var webhookToken = match.Groups[2].Value;
_onCallData = await GetWebhookData(webhookId, webhookToken);
} }
private void PlayerRateLimitedAction(ICommonSession obj) private void PlayerRateLimitedAction(ICommonSession obj)
@@ -293,13 +260,13 @@ namespace Content.Server.Administration.Systems
// Store the Discord message IDs of the previous round // Store the Discord message IDs of the previous round
_oldMessageIds = new Dictionary<NetUserId, string>(); _oldMessageIds = new Dictionary<NetUserId, string>();
foreach (var (user, interaction) in _relayMessages) foreach (var message in _relayMessages)
{ {
var id = interaction.Id; var id = message.Value.id;
if (id == null) if (id == null)
return; return;
_oldMessageIds[user] = id; _oldMessageIds[message.Key] = id;
} }
_relayMessages.Clear(); _relayMessages.Clear();
@@ -364,10 +331,10 @@ namespace Content.Server.Administration.Systems
var webhookToken = match.Groups[2].Value; var webhookToken = match.Groups[2].Value;
// Fire and forget // Fire and forget
_webhookData = await GetWebhookData(webhookId, webhookToken); await SetWebhookData(webhookId, webhookToken);
} }
private async Task<WebhookData?> GetWebhookData(string id, string token) private async Task SetWebhookData(string id, string token)
{ {
var response = await _httpClient.GetAsync($"https://discord.com/api/v10/webhooks/{id}/{token}"); var response = await _httpClient.GetAsync($"https://discord.com/api/v10/webhooks/{id}/{token}");
@@ -376,10 +343,10 @@ namespace Content.Server.Administration.Systems
{ {
_sawmill.Log(LogLevel.Error, _sawmill.Log(LogLevel.Error,
$"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}"); $"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}");
return null; return;
} }
return JsonSerializer.Deserialize<WebhookData>(content); _webhookData = JsonSerializer.Deserialize<WebhookData>(content);
} }
private void OnFooterIconChanged(string url) private void OnFooterIconChanged(string url)
@@ -392,14 +359,14 @@ namespace Content.Server.Administration.Systems
_avatarUrl = url; _avatarUrl = url;
} }
private async void ProcessQueue(NetUserId userId, Queue<DiscordRelayedData> messages) private async void ProcessQueue(NetUserId userId, Queue<string> messages)
{ {
// Whether an embed already exists for this player // Whether an embed already exists for this player
var exists = _relayMessages.TryGetValue(userId, out var existingEmbed); var exists = _relayMessages.TryGetValue(userId, out var existingEmbed);
// Whether the message will become too long after adding these new messages // Whether the message will become too long after adding these new messages
var tooLong = exists && messages.Sum(msg => Math.Min(msg.Message.Length, MessageLengthCap) + "\n".Length) var tooLong = exists && messages.Sum(msg => Math.Min(msg.Length, MessageLengthCap) + "\n".Length)
+ existingEmbed?.Description.Length > DescriptionMax; + existingEmbed.description.Length > DescriptionMax;
// If there is no existing embed, or it is getting too long, we create a new embed // If there is no existing embed, or it is getting too long, we create a new embed
if (!exists || tooLong) if (!exists || tooLong)
@@ -419,10 +386,10 @@ namespace Content.Server.Administration.Systems
// If we have all the data required, we can link to the embed of the previous round or embed that was too long // If we have all the data required, we can link to the embed of the previous round or embed that was too long
if (_webhookData is { GuildId: { } guildId, ChannelId: { } channelId }) if (_webhookData is { GuildId: { } guildId, ChannelId: { } channelId })
{ {
if (tooLong && existingEmbed?.Id != null) if (tooLong && existingEmbed.id != null)
{ {
linkToPrevious = linkToPrevious =
$"**[Go to previous embed of this round](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.Id})**\n"; $"**[Go to previous embed of this round](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.id})**\n";
} }
else if (_oldMessageIds.TryGetValue(userId, out var id) && !string.IsNullOrEmpty(id)) else if (_oldMessageIds.TryGetValue(userId, out var id) && !string.IsNullOrEmpty(id))
{ {
@@ -432,22 +399,13 @@ namespace Content.Server.Administration.Systems
} }
var characterName = _minds.GetCharacterName(userId); var characterName = _minds.GetCharacterName(userId);
existingEmbed = new DiscordRelayInteraction() existingEmbed = (null, lookup.Username, linkToPrevious, characterName, _gameTicker.RunLevel);
{
Id = null,
CharacterName = characterName,
Description = linkToPrevious,
Username = lookup.Username,
LastRunLevel = _gameTicker.RunLevel,
};
_relayMessages[userId] = existingEmbed;
} }
// Previous message was in another RunLevel, so show that in the embed // Previous message was in another RunLevel, so show that in the embed
if (existingEmbed!.LastRunLevel != _gameTicker.RunLevel) if (existingEmbed.lastRunLevel != _gameTicker.RunLevel)
{ {
existingEmbed.Description += _gameTicker.RunLevel switch existingEmbed.description += _gameTicker.RunLevel switch
{ {
GameRunLevel.PreRoundLobby => "\n\n:arrow_forward: _**Pre-round lobby started**_\n", GameRunLevel.PreRoundLobby => "\n\n:arrow_forward: _**Pre-round lobby started**_\n",
GameRunLevel.InRound => "\n\n:arrow_forward: _**Round started**_\n", GameRunLevel.InRound => "\n\n:arrow_forward: _**Round started**_\n",
@@ -456,35 +414,26 @@ namespace Content.Server.Administration.Systems
$"{_gameTicker.RunLevel} was not matched."), $"{_gameTicker.RunLevel} was not matched."),
}; };
existingEmbed.LastRunLevel = _gameTicker.RunLevel; existingEmbed.lastRunLevel = _gameTicker.RunLevel;
} }
// If last message of the new batch is SOS then relay it to on-call.
// ... as long as it hasn't been relayed already.
var discordMention = messages.Last();
var onCallRelay = !discordMention.Receivers && !existingEmbed.OnCall;
// Add available messages to the embed description // Add available messages to the embed description
while (messages.TryDequeue(out var message)) while (messages.TryDequeue(out var message))
{ {
string text;
// In case someone thinks they're funny // In case someone thinks they're funny
if (message.Message.Length > MessageLengthCap) if (message.Length > MessageLengthCap)
text = message.Message[..(MessageLengthCap - TooLongText.Length)] + TooLongText; message = message[..(MessageLengthCap - TooLongText.Length)] + TooLongText;
else
text = message.Message;
existingEmbed.Description += $"\n{text}"; existingEmbed.description += $"\n{message}";
} }
var payload = GeneratePayload(existingEmbed.Description, var payload = GeneratePayload(existingEmbed.description,
existingEmbed.Username, existingEmbed.username,
existingEmbed.CharacterName); existingEmbed.characterName);
// If there is no existing embed, create a new one // If there is no existing embed, create a new one
// Otherwise patch (edit) it // Otherwise patch (edit) it
if (existingEmbed.Id == null) if (existingEmbed.id == null)
{ {
var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true", var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
@@ -507,11 +456,11 @@ namespace Content.Server.Administration.Systems
return; return;
} }
existingEmbed.Id = id.ToString(); existingEmbed.id = id.ToString();
} }
else else
{ {
var request = await _httpClient.PatchAsync($"{_webhookUrl}/messages/{existingEmbed.Id}", var request = await _httpClient.PatchAsync($"{_webhookUrl}/messages/{existingEmbed.id}",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")); new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
if (!request.IsSuccessStatusCode) if (!request.IsSuccessStatusCode)
@@ -526,43 +475,6 @@ namespace Content.Server.Administration.Systems
_relayMessages[userId] = existingEmbed; _relayMessages[userId] = existingEmbed;
// Actually do the on call relay last, we just need to grab it before we dequeue every message above.
if (onCallRelay &&
_onCallData != null)
{
existingEmbed.OnCall = true;
var roleMention = _config.GetCVar(CCVars.DiscordAhelpMention);
if (!string.IsNullOrEmpty(roleMention))
{
var message = new StringBuilder();
message.AppendLine($"<@&{roleMention}>");
message.AppendLine("Unanswered SOS");
// Need webhook data to get the correct link for that channel rather than on-call data.
if (_webhookData is { GuildId: { } guildId, ChannelId: { } channelId })
{
message.AppendLine(
$"**[Go to ahelp](https://discord.com/channels/{guildId}/{channelId}/{existingEmbed.Id})**");
}
payload = GeneratePayload(message.ToString(), existingEmbed.Username, existingEmbed.CharacterName);
var request = await _httpClient.PostAsync($"{_onCallUrl}?wait=true",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
var content = await request.Content.ReadAsStringAsync();
if (!request.IsSuccessStatusCode)
{
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting relay message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
}
}
}
else
{
existingEmbed.OnCall = false;
}
_processingChannels.Remove(userId); _processingChannels.Remove(userId);
} }
@@ -741,7 +653,7 @@ namespace Content.Server.Administration.Systems
if (sendsWebhook) if (sendsWebhook)
{ {
if (!_messageQueues.ContainsKey(msg.UserId)) if (!_messageQueues.ContainsKey(msg.UserId))
_messageQueues[msg.UserId] = new Queue<DiscordRelayedData>(); _messageQueues[msg.UserId] = new Queue<string>();
var str = message.Text; var str = message.Text;
var unameLength = senderSession.Name.Length; var unameLength = senderSession.Name.Length;
@@ -790,7 +702,7 @@ namespace Content.Server.Administration.Systems
.ToList(); .ToList();
} }
private static DiscordRelayedData GenerateAHelpMessage(AHelpMessageParams parameters) private static string GenerateAHelpMessage(AHelpMessageParams parameters)
{ {
var stringbuilder = new StringBuilder(); var stringbuilder = new StringBuilder();
@@ -807,57 +719,13 @@ namespace Content.Server.Administration.Systems
stringbuilder.Append($" **{parameters.RoundTime}**"); stringbuilder.Append($" **{parameters.RoundTime}**");
if (!parameters.PlayedSound) if (!parameters.PlayedSound)
stringbuilder.Append(" **(S)**"); stringbuilder.Append(" **(S)**");
if (parameters.Icon == null) if (parameters.Icon == null)
stringbuilder.Append($" **{parameters.Username}:** "); stringbuilder.Append($" **{parameters.Username}:** ");
else else
stringbuilder.Append($" **{parameters.Username}** "); stringbuilder.Append($" **{parameters.Username}** ");
stringbuilder.Append(parameters.Message); stringbuilder.Append(parameters.Message);
return stringbuilder.ToString();
return new DiscordRelayedData()
{
Receivers = !parameters.NoReceivers,
Message = stringbuilder.ToString(),
};
}
private record struct DiscordRelayedData
{
/// <summary>
/// Was anyone online to receive it.
/// </summary>
public bool Receivers;
/// <summary>
/// What's the payload to send to discord.
/// </summary>
public string Message;
}
/// <summary>
/// Class specifically for holding information regarding existing Discord embeds
/// </summary>
private sealed class DiscordRelayInteraction
{
public string? Id;
public string Username = String.Empty;
public string? CharacterName;
/// <summary>
/// Contents for the discord message.
/// </summary>
public string Description = string.Empty;
/// <summary>
/// Run level of the last interaction. If different we'll link to the last Id.
/// </summary>
public GameRunLevel LastRunLevel;
/// <summary>
/// Did we relay this interaction to OnCall previously.
/// </summary>
public bool OnCall;
} }
} }

View File

@@ -55,7 +55,7 @@ public sealed partial class AmeControllerComponent : SharedAmeControllerComponen
/// </summary> /// </summary>
[DataField("injectSound")] [DataField("injectSound")]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier InjectSound = new SoundPathSpecifier("/Audio/Machines/ame_fuelinjection.ogg"); public SoundSpecifier InjectSound = new SoundCollectionSpecifier("MetalThud");
/// <summary> /// <summary>
/// The last time this could have injected fuel into the AME. /// The last time this could have injected fuel into the AME.

View File

@@ -22,17 +22,11 @@ public sealed class TechAnomalySystem : EntitySystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<TechAnomalyComponent, MapInitEvent>(OnTechMapInit);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyPulseEvent>(OnPulse); SubscribeLocalEvent<TechAnomalyComponent, AnomalyPulseEvent>(OnPulse);
SubscribeLocalEvent<TechAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical); SubscribeLocalEvent<TechAnomalyComponent, AnomalySupercriticalEvent>(OnSupercritical);
SubscribeLocalEvent<TechAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged); SubscribeLocalEvent<TechAnomalyComponent, AnomalyStabilityChangedEvent>(OnStabilityChanged);
} }
private void OnTechMapInit(Entity<TechAnomalyComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextTimer = _timing.CurTime;
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);

View File

@@ -11,6 +11,7 @@ using Content.Server.Preferences.Managers;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Roles.Jobs; using Content.Server.Roles.Jobs;
using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Shared.Antag; using Content.Shared.Antag;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
@@ -19,6 +20,7 @@ using Content.Shared.Ghost;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Players; using Content.Shared.Players;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Server.Audio; using Robust.Server.Audio;
@@ -35,14 +37,14 @@ namespace Content.Server.Antag;
public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent> public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent>
{ {
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly GhostRoleSystem _ghostRole = default!;
[Dependency] private readonly JobSystem _jobs = default!; [Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly LoadoutSystem _loadout = default!; [Dependency] private readonly LoadoutSystem _loadout = default!;
[Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerPreferencesManager _pref = default!;
[Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
@@ -191,9 +193,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary> /// <summary>
/// Chooses antagonists from the given selection of players /// Chooses antagonists from the given selection of players
/// </summary> /// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false) public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
{ {
if (ent.Comp.SelectionsComplete) if (ent.Comp.SelectionsComplete)
@@ -210,14 +209,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary> /// <summary>
/// Chooses antagonists from the given selection of players for the given antag definition. /// Chooses antagonists from the given selection of players for the given antag definition.
/// </summary> /// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="def">The antagonist selection parameters and criteria</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param> /// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def, bool midround = false)
IList<ICommonSession> pool,
AntagSelectionDefinition def,
bool midround = false)
{ {
var playerPool = GetPlayerPool(ent, pool, def); var playerPool = GetPlayerPool(ent, pool, def);
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
@@ -338,7 +331,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
EntityManager.AddComponents(player, def.Components); EntityManager.AddComponents(player, def.Components);
// Equip the entity's RoleLoadout and LoadoutGroup // Equip the entity's RoleLoadout and LoadoutGroup
List<ProtoId<StartingGearPrototype>> gear = new(); List<ProtoId<StartingGearPrototype>>? gear = new();
if (def.StartingGear is not null) if (def.StartingGear is not null)
gear.Add(def.StartingGear.Value); gear.Add(def.StartingGear.Value);
@@ -357,7 +350,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
} }
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
_role.MindAddRoles(curMind.Value, def.MindRoles, null, true); _role.MindAddRoles(curMind.Value, def.MindComponents, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing); SendBriefing(session, def.Briefing);
} }

View File

@@ -3,6 +3,7 @@ using Content.Shared.Antag;
using Content.Shared.Destructible.Thresholds; using Content.Shared.Destructible.Thresholds;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Storage;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -144,17 +145,10 @@ public partial struct AntagSelectionDefinition()
/// <summary> /// <summary>
/// Components added to the player's mind. /// Components added to the player's mind.
/// Do NOT use this to add role-type components. Add those as MindRoles instead
/// </summary> /// </summary>
[DataField] [DataField]
public ComponentRegistry MindComponents = new(); public ComponentRegistry MindComponents = new();
/// <summary>
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
/// <summary> /// <summary>
/// A set of starting gear that's equipped to the player. /// A set of starting gear that's equipped to the player.
/// </summary> /// </summary>

View File

@@ -1,11 +1,9 @@
using Robust.Shared.Prototypes;
namespace Content.Server.Atmos.Components namespace Content.Server.Atmos.Components
{ {
/// <summary> /// <summary>
/// Used by FixGridAtmos. Entities with this may get magically auto-deleted on map initialization in future. /// Used by FixGridAtmos. Entities with this may get magically auto-deleted on map initialization in future.
/// </summary> /// </summary>
[RegisterComponent, EntityCategory("Mapping")] [RegisterComponent]
public sealed partial class AtmosFixMarkerComponent : Component public sealed partial class AtmosFixMarkerComponent : Component
{ {
// See FixGridAtmos for more details // See FixGridAtmos for more details

View File

@@ -1,19 +1,15 @@
using Content.Server.Atmos.Monitor.Components; using Content.Server.Atmos.Monitor.Components;
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.Pinpointer;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Consoles; using Content.Shared.Atmos.Consoles;
using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components; using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Pinpointer; using Content.Shared.Pinpointer;
using Content.Shared.Tag;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Timing; using Robust.Shared.Player;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
@@ -25,12 +21,6 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
[Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!; [Dependency] private readonly AirAlarmSystem _airAlarmSystem = default!;
[Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly NavMapSystem _navMapSystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly DeviceListSystem _deviceListSystem = default!;
private const float UpdateTime = 1.0f; private const float UpdateTime = 1.0f;
@@ -48,9 +38,6 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
// Grid events // Grid events
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit); SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
// Alarm events
SubscribeLocalEvent<AtmosAlertsDeviceComponent, EntityTerminatingEvent>(OnDeviceTerminatingEvent);
SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged); SubscribeLocalEvent<AtmosAlertsDeviceComponent, AnchorStateChangedEvent>(OnDeviceAnchorChanged);
} }
@@ -94,16 +81,6 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
} }
private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args) private void OnDeviceAnchorChanged(EntityUid uid, AtmosAlertsDeviceComponent component, AnchorStateChangedEvent args)
{
OnDeviceAdditionOrRemoval(uid, component, args.Anchored);
}
private void OnDeviceTerminatingEvent(EntityUid uid, AtmosAlertsDeviceComponent component, ref EntityTerminatingEvent args)
{
OnDeviceAdditionOrRemoval(uid, component, false);
}
private void OnDeviceAdditionOrRemoval(EntityUid uid, AtmosAlertsDeviceComponent component, bool isAdding)
{ {
var xform = Transform(uid); var xform = Transform(uid);
var gridUid = xform.GridUid; var gridUid = xform.GridUid;
@@ -111,13 +88,10 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
if (gridUid == null) if (gridUid == null)
return; return;
if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap)) if (!TryGetAtmosDeviceNavMapData(uid, component, xform, gridUid.Value, out var data))
return; return;
if (!TryGetAtmosDeviceNavMapData(uid, component, xform, out var data)) var netEntity = EntityManager.GetNetEntity(uid);
return;
var netEntity = GetNetEntity(uid);
var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>(); var query = AllEntityQuery<AtmosAlertsComputerComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entConsole, out var entXform)) while (query.MoveNext(out var ent, out var entConsole, out var entXform))
@@ -125,18 +99,11 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
if (gridUid != entXform.GridUid) if (gridUid != entXform.GridUid)
continue; continue;
if (isAdding) if (args.Anchored)
{
entConsole.AtmosDevices.Add(data.Value); entConsole.AtmosDevices.Add(data.Value);
}
else else if (!args.Anchored)
{
entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity); entConsole.AtmosDevices.RemoveWhere(x => x.NetEntity == netEntity);
_navMapSystem.RemoveNavMapRegion(gridUid.Value, navMap, netEntity);
}
Dirty(ent, entConsole);
} }
} }
@@ -242,12 +209,6 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
if (entDevice.Group != group) if (entDevice.Group != group)
continue; continue;
if (!TryComp<MapGridComponent>(entXform.GridUid, out var mapGrid))
continue;
if (!TryComp<NavMapComponent>(entXform.GridUid, out var navMap))
continue;
// If emagged, change the alarm type to normal // If emagged, change the alarm type to normal
var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState; var alarmState = (entAtmosAlarmable.LastAlarmState == AtmosAlarmType.Emagged) ? AtmosAlarmType.Normal : entAtmosAlarmable.LastAlarmState;
@@ -255,45 +216,14 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
if (TryComp<ApcPowerReceiverComponent>(ent, out var entAPCPower) && !entAPCPower.Powered) if (TryComp<ApcPowerReceiverComponent>(ent, out var entAPCPower) && !entAPCPower.Powered)
alarmState = AtmosAlarmType.Invalid; alarmState = AtmosAlarmType.Invalid;
// Create entry
var netEnt = GetNetEntity(ent);
var entry = new AtmosAlertsComputerEntry var entry = new AtmosAlertsComputerEntry
(netEnt, (GetNetEntity(ent),
GetNetCoordinates(entXform.Coordinates), GetNetCoordinates(entXform.Coordinates),
entDevice.Group, entDevice.Group,
alarmState, alarmState,
MetaData(ent).EntityName, MetaData(ent).EntityName,
entDeviceNetwork.Address); entDeviceNetwork.Address);
// Get the list of sensors attached to the alarm
var sensorList = TryComp<DeviceListComponent>(ent, out var entDeviceList) ? _deviceListSystem.GetDeviceList(ent, entDeviceList) : null;
if (sensorList?.Any() == true)
{
var alarmRegionSeeds = new HashSet<Vector2i>();
// If valid and anchored, use the position of sensors as seeds for the region
foreach (var (address, sensorEnt) in sensorList)
{
if (!sensorEnt.IsValid() || !HasComp<AtmosMonitorComponent>(sensorEnt))
continue;
var sensorXform = Transform(sensorEnt);
if (sensorXform.Anchored && sensorXform.GridUid == entXform.GridUid)
alarmRegionSeeds.Add(_mapSystem.CoordinatesToTile(entXform.GridUid.Value, mapGrid, _transformSystem.GetMapCoordinates(sensorEnt, sensorXform)));
}
var regionProperties = new SharedNavMapSystem.NavMapRegionProperties(netEnt, AtmosAlertsComputerUiKey.Key, alarmRegionSeeds);
_navMapSystem.AddOrUpdateNavMapRegion(gridUid, navMap, netEnt, regionProperties);
}
else
{
_navMapSystem.RemoveNavMapRegion(entXform.GridUid.Value, navMap, netEnt);
}
alarmStateData.Add(entry); alarmStateData.Add(entry);
} }
@@ -376,10 +306,7 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
var query = AllEntityQuery<AtmosAlertsDeviceComponent, TransformComponent>(); var query = AllEntityQuery<AtmosAlertsDeviceComponent, TransformComponent>();
while (query.MoveNext(out var ent, out var entComponent, out var entXform)) while (query.MoveNext(out var ent, out var entComponent, out var entXform))
{ {
if (entXform.GridUid != gridUid) if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, gridUid, out var data))
continue;
if (TryGetAtmosDeviceNavMapData(ent, entComponent, entXform, out var data))
atmosDeviceNavMapData.Add(data.Value); atmosDeviceNavMapData.Add(data.Value);
} }
@@ -390,10 +317,14 @@ public sealed class AtmosAlertsComputerSystem : SharedAtmosAlertsComputerSystem
(EntityUid uid, (EntityUid uid,
AtmosAlertsDeviceComponent component, AtmosAlertsDeviceComponent component,
TransformComponent xform, TransformComponent xform,
EntityUid gridUid,
[NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output) [NotNullWhen(true)] out AtmosAlertsDeviceNavMapData? output)
{ {
output = null; output = null;
if (xform.GridUid != gridUid)
return false;
if (!xform.Anchored) if (!xform.Anchored)
return false; return false;

View File

@@ -281,17 +281,6 @@ namespace Content.Server.Atmos.EntitySystems
return true; return true;
} }
/// <summary>
/// Compares two TileAtmospheres to see if they are within acceptable ranges for group processing to be enabled.
/// </summary>
public GasCompareResult CompareExchange(TileAtmosphere sample, TileAtmosphere otherSample)
{
if (sample.AirArchived == null || otherSample.AirArchived == null)
return GasCompareResult.NoExchange;
return CompareExchange(sample.AirArchived, otherSample.AirArchived);
}
/// <summary> /// <summary>
/// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled. /// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled.
/// </summary> /// </summary>

View File

@@ -270,7 +270,7 @@ public sealed partial class AtmosphereSystem
{ {
DebugTools.AssertNotNull(tile.Air); DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable == false); DebugTools.Assert(tile.Air?.Immutable == false);
tile.AirArchived = null; Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0; tile.ArchivedCycle = 0;
var count = 0; var count = 0;

View File

@@ -210,7 +210,7 @@ namespace Content.Server.Atmos.EntitySystems
MovedByPressureComponent.ProbabilityOffset); MovedByPressureComponent.ProbabilityOffset);
// Can we yeet the thing (due to probability, strength, etc.) // Can we yeet the thing (due to probability, strength, etc.)
if (moveProb > MovedByPressureComponent.ProbabilityOffset && _random.Prob(MathF.Min(moveProb / 100f, 1f)) if (moveProb > MovedByPressureComponent.ProbabilityOffset && _robustRandom.Prob(MathF.Min(moveProb / 100f, 1f))
&& !float.IsPositiveInfinity(component.MoveResist) && !float.IsPositiveInfinity(component.MoveResist)
&& (physics.BodyType != BodyType.Static && (physics.BodyType != BodyType.Static
&& (maxForce >= (component.MoveResist * MovedByPressureComponent.MoveForcePushRatio))) && (maxForce >= (component.MoveResist * MovedByPressureComponent.MoveForcePushRatio)))

View File

@@ -54,7 +54,7 @@ namespace Content.Server.Atmos.EntitySystems
} }
shouldShareAir = true; shouldShareAir = true;
} else if (CompareExchange(tile, enemyTile) != GasCompareResult.NoExchange) } else if (CompareExchange(tile.Air, enemyTile.Air) != GasCompareResult.NoExchange)
{ {
AddActiveTile(gridAtmosphere, enemyTile); AddActiveTile(gridAtmosphere, enemyTile);
if (ExcitedGroups) if (ExcitedGroups)
@@ -117,8 +117,9 @@ namespace Content.Server.Atmos.EntitySystems
private void Archive(TileAtmosphere tile, int fireCount) private void Archive(TileAtmosphere tile, int fireCount)
{ {
if (tile.Air != null) if (tile.Air != null)
tile.AirArchived = new GasMixture(tile.Air); tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan());
tile.TemperatureArchived = tile.Temperature;
tile.ArchivedCycle = fireCount; tile.ArchivedCycle = fireCount;
} }
@@ -183,10 +184,10 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
public float GetHeatCapacityArchived(TileAtmosphere tile) public float GetHeatCapacityArchived(TileAtmosphere tile)
{ {
if (tile.AirArchived == null) if (tile.Air == null)
return tile.HeatCapacity; return tile.HeatCapacity;
return GetHeatCapacity(tile.AirArchived); return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
} }
/// <summary> /// <summary>
@@ -194,11 +195,10 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs) public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs)
{ {
if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer || if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer)
tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
return 0f; return 0f;
var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature; var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived;
var absTemperatureDelta = Math.Abs(temperatureDelta); var absTemperatureDelta = Math.Abs(temperatureDelta);
var oldHeatCapacity = 0f; var oldHeatCapacity = 0f;
var oldSharerHeatCapacity = 0f; var oldSharerHeatCapacity = 0f;
@@ -249,12 +249,12 @@ namespace Content.Server.Atmos.EntitySystems
// Transfer of thermal energy (via changed heat capacity) between self and sharer. // Transfer of thermal energy (via changed heat capacity) between self and sharer.
if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity) if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity)
{ {
receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.AirArchived.Temperature) + (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature)) / newHeatCapacity; receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.TemperatureArchived) + (heatCapacitySharerToThis * tileSharer.TemperatureArchived)) / newHeatCapacity;
} }
if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity) if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity)
{ {
sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) + (heatCapacityToSharer * tileReceiver.AirArchived.Temperature)) / newSharerHeatCapacity; sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.TemperatureArchived) + (heatCapacityToSharer * tileReceiver.TemperatureArchived)) / newSharerHeatCapacity;
} }
// Thermal energy of the system (self and sharer) is unchanged. // Thermal energy of the system (self and sharer) is unchanged.
@@ -273,7 +273,7 @@ namespace Content.Server.Atmos.EntitySystems
var moles = receiver.TotalMoles; var moles = receiver.TotalMoles;
var theirMoles = sharer.TotalMoles; var theirMoles = sharer.TotalMoles;
return (tileReceiver.AirArchived.Temperature * (moles + movedMoles)) - (tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; return (tileReceiver.TemperatureArchived * (moles + movedMoles)) - (tileSharer.TemperatureArchived * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume;
} }
/// <summary> /// <summary>
@@ -281,11 +281,10 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
public float TemperatureShare(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, float conductionCoefficient) public float TemperatureShare(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, float conductionCoefficient)
{ {
if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer || if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer)
tileReceiver.AirArchived == null || tileSharer.AirArchived == null)
return 0f; return 0f;
var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature; var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived;
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
{ {
var heatCapacity = GetHeatCapacityArchived(tileReceiver); var heatCapacity = GetHeatCapacityArchived(tileReceiver);
@@ -311,10 +310,10 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
public float TemperatureShare(TileAtmosphere tileReceiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity) public float TemperatureShare(TileAtmosphere tileReceiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity)
{ {
if (tileReceiver.Air is not {} receiver || tileReceiver.AirArchived == null) if (tileReceiver.Air is not {} receiver)
return 0; return 0;
var temperatureDelta = tileReceiver.AirArchived.Temperature - sharerTemperature; var temperatureDelta = tileReceiver.TemperatureArchived - sharerTemperature;
if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider)
{ {
var heatCapacity = GetHeatCapacityArchived(tileReceiver); var heatCapacity = GetHeatCapacityArchived(tileReceiver);

View File

@@ -355,7 +355,7 @@ namespace Content.Server.Atmos.EntitySystems
continue; continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.Air != null && CompareExchange(otherTile2, tile) == GasCompareResult.NoExchange) if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
continue; continue;
AddActiveTile(gridAtmosphere, otherTile2); AddActiveTile(gridAtmosphere, otherTile2);
@@ -689,7 +689,7 @@ namespace Content.Server.Atmos.EntitySystems
var chance = MathHelper.Clamp(0.01f + (sum / SpacingMaxWind) * 0.3f, 0.003f, 0.3f); var chance = MathHelper.Clamp(0.01f + (sum / SpacingMaxWind) * 0.3f, 0.003f, 0.3f);
if (sum > 20 && _random.Prob(chance)) if (sum > 20 && _robustRandom.Prob(chance))
PryTile(mapGrid, tile.GridIndices); PryTile(mapGrid, tile.GridIndices);
} }

View File

@@ -231,7 +231,7 @@ namespace Content.Server.Atmos.EntitySystems
tile.MapAtmosphere = false; tile.MapAtmosphere = false;
atmos.MapTiles.Remove(tile); atmos.MapTiles.Remove(tile);
tile.Air = null; tile.Air = null;
tile.AirArchived = null; Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0; tile.ArchivedCycle = 0;
tile.LastShare = 0f; tile.LastShare = 0f;
tile.Space = false; tile.Space = false;
@@ -261,7 +261,7 @@ namespace Content.Server.Atmos.EntitySystems
return; return;
tile.Air = null; tile.Air = null;
tile.AirArchived = null; Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0; tile.ArchivedCycle = 0;
tile.LastShare = 0f; tile.LastShare = 0f;
tile.Hotspot = new Hotspot(); tile.Hotspot = new Hotspot();

View File

@@ -131,10 +131,7 @@ namespace Content.Server.Atmos.EntitySystems
private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient) private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient)
{ {
if (tile.AirArchived == null || other.AirArchived == null) var deltaTemperature = (tile.TemperatureArchived - other.TemperatureArchived);
return;
var deltaTemperature = (tile.AirArchived.Temperature - other.AirArchived.Temperature);
if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider
&& tile.HeatCapacity != 0f && other.HeatCapacity != 0f) && tile.HeatCapacity != 0f && other.HeatCapacity != 0f)
{ {
@@ -148,14 +145,11 @@ namespace Content.Server.Atmos.EntitySystems
public void RadiateToSpace(TileAtmosphere tile) public void RadiateToSpace(TileAtmosphere tile)
{ {
if (tile.AirArchived == null)
return;
// Considering 0ºC as the break even point for radiation in and out. // Considering 0ºC as the break even point for radiation in and out.
if (tile.Temperature > Atmospherics.T0C) if (tile.Temperature > Atmospherics.T0C)
{ {
// Hardcoded space temperature. // Hardcoded space temperature.
var deltaTemperature = (tile.AirArchived.Temperature - Atmospherics.TCMB); var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB);
if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider)) if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider))
{ {
var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity * var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity *

View File

@@ -14,6 +14,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics.Systems; using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using System.Linq; using System.Linq;
namespace Content.Server.Atmos.EntitySystems; namespace Content.Server.Atmos.EntitySystems;
@@ -26,6 +27,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!; [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IAdminLogManager _adminLog = default!; [Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly InternalsSystem _internals = default!; [Dependency] private readonly InternalsSystem _internals = default!;
@@ -37,6 +39,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly TileSystem _tile = default!;
[Dependency] private readonly MapSystem _map = default!; [Dependency] private readonly MapSystem _map = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] public readonly PuddleSystem Puddle = default!; [Dependency] public readonly PuddleSystem Puddle = default!;
private const float ExposedUpdateDelay = 1f; private const float ExposedUpdateDelay = 1f;
@@ -121,6 +124,6 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
private void CacheDecals() private void CacheDecals()
{ {
_burntDecals = _protoMan.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray(); _burntDecals = _prototypeManager.EnumeratePrototypes<DecalPrototype>().Where(x => x.Tags.Contains("burnt")).Select(x => x.ID).ToArray();
} }
} }

View File

@@ -22,6 +22,9 @@ namespace Content.Server.Atmos
[ViewVariables] [ViewVariables]
public float Temperature { get; set; } = Atmospherics.T20C; public float Temperature { get; set; } = Atmospherics.T20C;
[ViewVariables]
public float TemperatureArchived { get; set; } = Atmospherics.T20C;
[ViewVariables] [ViewVariables]
public TileAtmosphere? PressureSpecificTarget { get; set; } public TileAtmosphere? PressureSpecificTarget { get; set; }
@@ -90,16 +93,12 @@ namespace Content.Server.Atmos
[Access(typeof(AtmosphereSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends [Access(typeof(AtmosphereSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
public GasMixture? Air { get; set; } public GasMixture? Air { get; set; }
/// <summary>
/// Like Air, but a copy stored each atmos tick before tile processing takes place. This lets us update Air
/// in-place without affecting the results based on update order.
/// </summary>
[ViewVariables]
public GasMixture? AirArchived;
[DataField("lastShare")] [DataField("lastShare")]
public float LastShare; public float LastShare;
[ViewVariables]
public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
GasMixture IGasMixtureHolder.Air GasMixture IGasMixtureHolder.Air
{ {
get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature }; get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature };
@@ -140,7 +139,6 @@ namespace Content.Server.Atmos
GridIndex = gridIndex; GridIndex = gridIndex;
GridIndices = gridIndices; GridIndices = gridIndices;
Air = mixture; Air = mixture;
AirArchived = Air != null ? Air.Clone() : null;
Space = space; Space = space;
if(immutable) if(immutable)
@@ -155,7 +153,7 @@ namespace Content.Server.Atmos
NoGridTile = other.NoGridTile; NoGridTile = other.NoGridTile;
MapAtmosphere = other.MapAtmosphere; MapAtmosphere = other.MapAtmosphere;
Air = other.Air?.Clone(); Air = other.Air?.Clone();
AirArchived = Air != null ? Air.Clone() : null; Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
} }
public TileAtmosphere() public TileAtmosphere()

View File

@@ -239,7 +239,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
Loc.GetString( Loc.GetString(
"earlyleave-cryo-announcement", "earlyleave-cryo-announcement",
("character", name), ("character", name),
("entity", ent.Owner), // gender things for supporting downstreams with other languages ("entity", ent.Owner),
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName)) ("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"), ), Loc.GetString("earlyleave-cryo-sender"),
playDefaultSound: false playDefaultSound: false

View File

@@ -472,7 +472,7 @@ public sealed class BloodstreamSystem : EntitySystem
return; return;
} }
var currentVolume = bloodSolution.RemoveReagent(component.BloodReagent, bloodSolution.Volume, ignoreReagentData: true); var currentVolume = bloodSolution.RemoveReagent(component.BloodReagent, bloodSolution.Volume);
component.BloodReagent = reagent; component.BloodReagent = reagent;

View File

@@ -14,6 +14,7 @@ public sealed class LungSystem : EntitySystem
[Dependency] private readonly AtmosphereSystem _atmos = default!; [Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly InternalsSystem _internals = default!; [Dependency] private readonly InternalsSystem _internals = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
public static string LungSolutionName = "Lung"; public static string LungSolutionName = "Lung";
@@ -28,7 +29,7 @@ public sealed class LungSystem : EntitySystem
private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args) private void OnGotUnequipped(Entity<BreathToolComponent> ent, ref GotUnequippedEvent args)
{ {
_atmos.DisconnectInternals(ent); _atmosphereSystem.DisconnectInternals(ent);
} }
private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent args) private void OnGotEquipped(Entity<BreathToolComponent> ent, ref GotEquippedEvent args)
@@ -92,7 +93,7 @@ public sealed class LungSystem : EntitySystem
if (moles <= 0) if (moles <= 0)
continue; continue;
var reagent = _atmos.GasReagents[i]; var reagent = _atmosphereSystem.GasReagents[i];
if (reagent is null) if (reagent is null)
continue; continue;

View File

@@ -1,6 +1,5 @@
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Audio;
namespace Content.Server.Botany.Components; namespace Content.Server.Botany.Components;
@@ -24,9 +23,6 @@ public sealed partial class PlantHolderComponent : Component
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
public TimeSpan LastCycle = TimeSpan.Zero; public TimeSpan LastCycle = TimeSpan.Zero;
[DataField]
public SoundSpecifier? WateringSound;
[DataField] [DataField]
public bool UpdateSpriteAfterUpdate; public bool UpdateSpriteAfterUpdate;

View File

@@ -27,7 +27,7 @@ public sealed class MutationSystem : EntitySystem
{ {
foreach (var mutation in _randomMutations.mutations) foreach (var mutation in _randomMutations.mutations)
{ {
if (Random(Math.Min(mutation.BaseOdds * severity, 1.0f))) if (Random(mutation.BaseOdds * severity))
{ {
if (mutation.AppliesToPlant) if (mutation.AppliesToPlant)
{ {

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