mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-02-14 19:30:01 +01:00
upstream
This commit is contained in:
@@ -20,6 +20,7 @@ public sealed class AnomalyScannerBoundUserInterface : BoundUserInterface
|
||||
|
||||
_menu = new AnomalyScannerMenu();
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Content.Client.Atmos.UI
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
@@ -29,15 +30,6 @@ namespace Content.Client.Atmos.UI
|
||||
_window.Populate(cast);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes UI and tells the server to disable the analyzer
|
||||
/// </summary>
|
||||
private void OnClose()
|
||||
{
|
||||
SendMessage(new GasAnalyzerDisableMessage());
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
@@ -2,16 +2,16 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed class AmbientOverlayCommand : IConsoleCommand
|
||||
public sealed class AmbientOverlayCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showambient";
|
||||
public string Description => "Shows all AmbientSoundComponents in the viewport";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AmbientSoundSystem>();
|
||||
system.OverlayEnabled ^= true;
|
||||
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
|
||||
|
||||
shell.WriteLine($"Ambient sound overlay set to {system.OverlayEnabled}");
|
||||
public override string Command => "showambient";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_ambient.OverlayEnabled ^= true;
|
||||
|
||||
shell.WriteLine(Loc.GetString($"cmd-showambient-status", ("status", _ambient.OverlayEnabled)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace Content.Client.Changelog
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ChangelogWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
|
||||
public ChangelogWindow()
|
||||
{
|
||||
@@ -112,15 +112,15 @@ namespace Content.Client.Changelog
|
||||
}
|
||||
|
||||
[UsedImplicitly, AnyCommand]
|
||||
public sealed class ChangelogCommand : IConsoleCommand
|
||||
public sealed class ChangelogCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "changelog";
|
||||
public string Description => "Opens the changelog";
|
||||
public string Help => "Usage: changelog";
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "changelog";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<IUserInterfaceManager>().GetUIController<ChangelogUIController>().OpenWindow();
|
||||
_uiManager.GetUIController<ChangelogUIController>().OpenWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
public abstract class SpeechBubble : Control
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] protected readonly IConfigurationManager ConfigManager = default!;
|
||||
@@ -30,12 +31,12 @@ namespace Content.Client.Chat.UI
|
||||
/// <summary>
|
||||
/// The total time a speech bubble stays on screen.
|
||||
/// </summary>
|
||||
private const float TotalTime = 4;
|
||||
private static readonly TimeSpan TotalTime = TimeSpan.FromSeconds(4);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time at the end of the bubble's life at which it starts fading.
|
||||
/// </summary>
|
||||
private const float FadeTime = 0.25f;
|
||||
private static readonly TimeSpan FadeTime = TimeSpan.FromSeconds(0.25f);
|
||||
|
||||
/// <summary>
|
||||
/// The distance in world space to offset the speech bubble from the center of the entity.
|
||||
@@ -50,7 +51,10 @@ namespace Content.Client.Chat.UI
|
||||
|
||||
private readonly EntityUid _senderEntity;
|
||||
|
||||
private float _timeLeft = TotalTime;
|
||||
/// <summary>
|
||||
/// The time at which this bubble will die.
|
||||
/// </summary>
|
||||
private TimeSpan _deathTime;
|
||||
|
||||
public float VerticalOffset { get; set; }
|
||||
private float _verticalOffsetAchieved;
|
||||
@@ -99,6 +103,7 @@ namespace Content.Client.Chat.UI
|
||||
bubble.Measure(Vector2Helpers.Infinity);
|
||||
ContentSize = bubble.DesiredSize;
|
||||
_verticalOffsetAchieved = -ContentSize.Y;
|
||||
_deathTime = _timing.RealTime + TotalTime;
|
||||
}
|
||||
|
||||
protected abstract Control BuildBubble(ChatMessage message, string speechStyleClass, Color? fontColor = null);
|
||||
@@ -107,8 +112,8 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
_timeLeft -= args.DeltaSeconds;
|
||||
if (_entityManager.Deleted(_senderEntity) || _timeLeft <= 0)
|
||||
var timeLeft = (float)(_deathTime - _timing.RealTime).TotalSeconds;
|
||||
if (_entityManager.Deleted(_senderEntity) || timeLeft <= 0)
|
||||
{
|
||||
// Timer spawn to prevent concurrent modification exception.
|
||||
Timer.Spawn(0, Die);
|
||||
@@ -131,10 +136,10 @@ namespace Content.Client.Chat.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (_timeLeft <= FadeTime)
|
||||
if (timeLeft <= FadeTime.TotalSeconds)
|
||||
{
|
||||
// Update alpha if we're fading.
|
||||
Modulate = Color.White.WithAlpha(_timeLeft / FadeTime);
|
||||
Modulate = Color.White.WithAlpha(timeLeft / (float)FadeTime.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -144,7 +149,7 @@ namespace Content.Client.Chat.UI
|
||||
|
||||
var baseOffset = 0f;
|
||||
|
||||
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
|
||||
if (_entityManager.TryGetComponent<SpeechComponent>(_senderEntity, out var speech))
|
||||
baseOffset = speech.SpeechBubbleOffset;
|
||||
|
||||
var offset = (-_eyeManager.CurrentEye.Rotation).ToWorldVec() * -(EntityVerticalOffset + baseOffset);
|
||||
@@ -175,9 +180,9 @@ namespace Content.Client.Chat.UI
|
||||
/// </summary>
|
||||
public void FadeNow()
|
||||
{
|
||||
if (_timeLeft > FadeTime)
|
||||
if (_deathTime > _timing.RealTime)
|
||||
{
|
||||
_timeLeft = FadeTime;
|
||||
_deathTime = _timing.RealTime + FadeTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ using Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
namespace Content.Client.Chemistry.EntitySystems;
|
||||
|
||||
public sealed class HypospraySystem : SharedHypospraySystem
|
||||
public sealed class HyposprayStatusControlSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,7 +38,7 @@ namespace Content.Client.Construction.UI
|
||||
private ConstructionSystem? _constructionSystem;
|
||||
private ConstructionPrototype? _selected;
|
||||
private List<ConstructionPrototype> _favoritedRecipes = [];
|
||||
private Dictionary<string, ContainerButton> _recipeButtons = new();
|
||||
private readonly Dictionary<string, ContainerButton> _recipeButtons = new();
|
||||
private string _selectedCategory = string.Empty;
|
||||
|
||||
private const string FavoriteCatName = "construction-category-favorites";
|
||||
@@ -217,8 +217,8 @@ namespace Content.Client.Construction.UI
|
||||
var itemButton = new ContainerButton()
|
||||
{
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.TargetPrototype.Name,
|
||||
ToolTip = recipe.TargetPrototype.Name,
|
||||
Name = recipe.Prototype.Name,
|
||||
ToolTip = recipe.Prototype.Name,
|
||||
ToggleMode = true,
|
||||
Children = { protoView },
|
||||
};
|
||||
@@ -235,7 +235,7 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name!, out var oldButton))
|
||||
_recipeButtons.TryGetValue(_selected.ID, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
@@ -245,7 +245,7 @@ namespace Content.Client.Construction.UI
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Prototype.Name!] = itemButton;
|
||||
_recipeButtons[recipe.Prototype.ID] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe.Prototype;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
@@ -307,7 +307,7 @@ namespace Content.Client.Construction.UI
|
||||
if (button.Parent is not PanelContainer buttonPanel)
|
||||
return;
|
||||
|
||||
button.Modulate = select ? Color.Green : Color.Transparent;
|
||||
button.Children.Single().Modulate = select ? Color.Green : Color.White;
|
||||
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
||||
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.Decals;
|
||||
|
||||
public sealed class ToggleDecalCommand : IConsoleCommand
|
||||
public sealed class ToggleDecalCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly DecalSystem _decal = default!;
|
||||
|
||||
public string Command => "toggledecals";
|
||||
public string Description => "Toggles decaloverlay";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "toggledecals";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_e.System<DecalSystem>().ToggleOverlay();
|
||||
_decal.ToggleOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Client.Lobby;
|
||||
using Content.Client.MainMenu;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Playtime;
|
||||
using Content.Client.Radiation.Overlays;
|
||||
using Content.Client.Replay;
|
||||
using Content.Client.Screenshot;
|
||||
@@ -76,6 +77,7 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
|
||||
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ClientsidePlaytimeTrackingManager _clientsidePlaytimeManager = default!;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
@@ -139,6 +141,7 @@ namespace Content.Client.Entry
|
||||
_extendedDisconnectInformation.Initialize();
|
||||
_jobRequirements.Initialize();
|
||||
_playbackMan.Initialize();
|
||||
_clientsidePlaytimeManager.Initialize();
|
||||
|
||||
//AUTOSCALING default Setup!
|
||||
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
|
||||
|
||||
@@ -2,25 +2,17 @@
|
||||
|
||||
namespace Content.Client.Ghost.Commands;
|
||||
|
||||
public sealed class ToggleGhostVisibilityCommand : IConsoleCommand
|
||||
public sealed class ToggleGhostVisibilityCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entSysMan = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
public string Command => "toggleghostvisibility";
|
||||
public string Description => "Toggles ghost visibility on the client.";
|
||||
public string Help => "toggleghostvisibility [bool]";
|
||||
public override string Command => "toggleghostvisibility";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ghostSystem = _entSysMan.GetEntitySystem<GhostSystem>();
|
||||
|
||||
if (args.Length != 0 && bool.TryParse(args[0], out var visibility))
|
||||
{
|
||||
ghostSystem.ToggleGhostVisibility(visibility);
|
||||
}
|
||||
_ghost.ToggleGhostVisibility(visibility);
|
||||
else
|
||||
{
|
||||
ghostSystem.ToggleGhostVisibility();
|
||||
}
|
||||
_ghost.ToggleGhostVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,27 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Ghost;
|
||||
|
||||
public sealed class GhostToggleSelfVisibility : IConsoleCommand
|
||||
public sealed class GhostToggleSelfVisibility : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "toggleselfghost";
|
||||
public string Description => "Toggles seeing your own ghost.";
|
||||
public string Help => "toggleselfghost";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override string Command => "toggleselfghost";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var attachedEntity = shell.Player?.AttachedEntity;
|
||||
if (!attachedEntity.HasValue)
|
||||
return;
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entityManager.HasComponent<GhostComponent>(attachedEntity))
|
||||
if (!EntityManager.HasComponent<GhostComponent>(attachedEntity))
|
||||
{
|
||||
shell.WriteError("Entity must be a ghost.");
|
||||
shell.WriteError(Loc.GetString($"cmd-toggleselfghost-must-be-ghost"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entityManager.TryGetComponent(attachedEntity, out SpriteComponent? spriteComponent))
|
||||
if (!EntityManager.TryGetComponent(attachedEntity, out SpriteComponent? spriteComponent))
|
||||
return;
|
||||
|
||||
var spriteSys = entityManager.System<SpriteSystem>();
|
||||
spriteSys.SetVisible((attachedEntity.Value, spriteComponent), !spriteComponent.Visible);
|
||||
_sprite.SetVisible((attachedEntity.Value, spriteComponent), !spriteComponent.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
54
Content.Client/Instruments/InstrumentSystem.MidiParsing.cs
Normal file
54
Content.Client/Instruments/InstrumentSystem.MidiParsing.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Instruments;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
|
||||
namespace Content.Client.Instruments;
|
||||
|
||||
public sealed partial class InstrumentSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to parse the input data as a midi and set the channel names respectively.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Thank you to http://www.somascape.org/midi/tech/mfile.html for providing an awesome resource for midi files.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// This method has exception tolerance and does not throw, even if the midi file is invalid.
|
||||
/// </remarks>
|
||||
private bool TrySetChannels(EntityUid uid, byte[] data)
|
||||
{
|
||||
if (!MidiParser.MidiParser.TryGetMidiTracks(data, out var tracks, out var error))
|
||||
{
|
||||
Log.Error(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var resolvedTracks = new List<MidiTrack?>();
|
||||
for (var index = 0; index < tracks.Length; index++)
|
||||
{
|
||||
var midiTrack = tracks[index];
|
||||
if (midiTrack is { TrackName: null, ProgramName: null, InstrumentName: null})
|
||||
continue;
|
||||
|
||||
switch (midiTrack)
|
||||
{
|
||||
case { TrackName: not null, ProgramName: not null }:
|
||||
case { TrackName: not null, InstrumentName: not null }:
|
||||
case { TrackName: not null }:
|
||||
case { ProgramName: not null }:
|
||||
resolvedTracks.Add(midiTrack);
|
||||
break;
|
||||
default:
|
||||
resolvedTracks.Add(null); // Used so the channel still displays as MIDI Channel X and doesn't just take the next valid one in the UI
|
||||
break;
|
||||
}
|
||||
|
||||
Log.Debug($"Channel name: {resolvedTracks.Last()}");
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(new InstrumentSetChannelsEvent(GetNetEntity(uid), resolvedTracks.Take(RobustMidiEvent.MaxChannels).ToArray()));
|
||||
Log.Debug($"Resolved {resolvedTracks.Count} channels.");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Instruments;
|
||||
@@ -12,7 +13,7 @@ using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Instruments;
|
||||
|
||||
public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
@@ -23,6 +24,8 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
public int MaxMidiEventsPerBatch { get; private set; }
|
||||
public int MaxMidiEventsPerSecond { get; private set; }
|
||||
|
||||
public event Action? OnChannelsUpdated;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,6 +41,26 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
SubscribeLocalEvent<InstrumentComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<InstrumentComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<ActiveInstrumentComponent, AfterAutoHandleStateEvent>(OnActiveInstrumentAfterHandleState);
|
||||
}
|
||||
|
||||
private bool _isUpdateQueued = false;
|
||||
|
||||
private void OnActiveInstrumentAfterHandleState(Entity<ActiveInstrumentComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
// Called in the update loop so that the components update client side for resolving them in TryComps.
|
||||
_isUpdateQueued = true;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
if (!_isUpdateQueued)
|
||||
return;
|
||||
|
||||
_isUpdateQueued = false;
|
||||
OnChannelsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, SharedInstrumentComponent component, ref ComponentHandleState args)
|
||||
@@ -252,7 +275,13 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
}
|
||||
|
||||
[Obsolete("Use overload that takes in byte[] instead.")]
|
||||
public bool OpenMidi(EntityUid uid, ReadOnlySpan<byte> data, InstrumentComponent? instrument = null)
|
||||
{
|
||||
return OpenMidi(uid, data.ToArray(), instrument);
|
||||
}
|
||||
|
||||
public bool OpenMidi(EntityUid uid, byte[] data, InstrumentComponent? instrument = null)
|
||||
{
|
||||
if (!Resolve(uid, ref instrument))
|
||||
return false;
|
||||
@@ -263,6 +292,8 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
return false;
|
||||
|
||||
SetMaster(uid, null);
|
||||
TrySetChannels(uid, data);
|
||||
|
||||
instrument.MidiEventBuffer.Clear();
|
||||
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
|
||||
return true;
|
||||
|
||||
147
Content.Client/Instruments/MidiParser/MidiInstrument.cs
Normal file
147
Content.Client/Instruments/MidiParser/MidiInstrument.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Instruments.MidiParser;
|
||||
|
||||
// This file was autogenerated. Based on https://www.ccarh.org/courses/253/handout/gminstruments/
|
||||
public enum MidiInstrument : byte
|
||||
{
|
||||
AcousticGrandPiano = 0,
|
||||
BrightAcousticPiano = 1,
|
||||
ElectricGrandPiano = 2,
|
||||
HonkyTonkPiano = 3,
|
||||
RhodesPiano = 4,
|
||||
ChorusedPiano = 5,
|
||||
Harpsichord = 6,
|
||||
Clavinet = 7,
|
||||
Celesta = 8,
|
||||
Glockenspiel = 9,
|
||||
MusicBox = 10,
|
||||
Vibraphone = 11,
|
||||
Marimba = 12,
|
||||
Xylophone = 13,
|
||||
TubularBells = 14,
|
||||
Dulcimer = 15,
|
||||
HammondOrgan = 16,
|
||||
PercussiveOrgan = 17,
|
||||
RockOrgan = 18,
|
||||
ChurchOrgan = 19,
|
||||
ReedOrgan = 20,
|
||||
Accordion = 21,
|
||||
Harmonica = 22,
|
||||
TangoAccordion = 23,
|
||||
AcousticNylonGuitar = 24,
|
||||
AcousticSteelGuitar = 25,
|
||||
ElectricJazzGuitar = 26,
|
||||
ElectricCleanGuitar = 27,
|
||||
ElectricMutedGuitar = 28,
|
||||
OverdrivenGuitar = 29,
|
||||
DistortionGuitar = 30,
|
||||
GuitarHarmonics = 31,
|
||||
AcousticBass = 32,
|
||||
FingeredElectricBass = 33,
|
||||
PluckedElectricBass = 34,
|
||||
FretlessBass = 35,
|
||||
SlapBass1 = 36,
|
||||
SlapBass2 = 37,
|
||||
SynthBass1 = 38,
|
||||
SynthBass2 = 39,
|
||||
Violin = 40,
|
||||
Viola = 41,
|
||||
Cello = 42,
|
||||
Contrabass = 43,
|
||||
TremoloStrings = 44,
|
||||
PizzicatoStrings = 45,
|
||||
OrchestralHarp = 46,
|
||||
Timpani = 47,
|
||||
StringEnsemble1 = 48,
|
||||
StringEnsemble2 = 49,
|
||||
SynthStrings1 = 50,
|
||||
SynthStrings2 = 51,
|
||||
ChoirAah = 52,
|
||||
VoiceOoh = 53,
|
||||
SynthChoir = 54,
|
||||
OrchestraHit = 55,
|
||||
Trumpet = 56,
|
||||
Trombone = 57,
|
||||
Tuba = 58,
|
||||
MutedTrumpet = 59,
|
||||
FrenchHorn = 60,
|
||||
BrassSection = 61,
|
||||
SynthBrass1 = 62,
|
||||
SynthBrass2 = 63,
|
||||
SopranoSax = 64,
|
||||
AltoSax = 65,
|
||||
TenorSax = 66,
|
||||
BaritoneSax = 67,
|
||||
Oboe = 68,
|
||||
EnglishHorn = 69,
|
||||
Bassoon = 70,
|
||||
Clarinet = 71,
|
||||
Piccolo = 72,
|
||||
Flute = 73,
|
||||
Recorder = 74,
|
||||
PanFlute = 75,
|
||||
BottleBlow = 76,
|
||||
Shakuhachi = 77,
|
||||
Whistle = 78,
|
||||
Ocarina = 79,
|
||||
SquareWaveLead = 80,
|
||||
SawtoothWaveLead = 81,
|
||||
CalliopeLead = 82,
|
||||
ChiffLead = 83,
|
||||
CharangLead = 84,
|
||||
VoiceLead = 85,
|
||||
FithsLead = 86,
|
||||
BassLead = 87,
|
||||
NewAgePad = 88,
|
||||
WarmPad = 89,
|
||||
PolysynthPad = 90,
|
||||
ChoirPad = 91,
|
||||
BowedPad = 92,
|
||||
MetallicPad = 93,
|
||||
HaloPad = 94,
|
||||
SweepPad = 95,
|
||||
RainEffect = 96,
|
||||
SoundtrackEffect = 97,
|
||||
CrystalEffect = 98,
|
||||
AtmosphereEffect = 99,
|
||||
BrightnessEffect = 100,
|
||||
GoblinsEffect = 101,
|
||||
EchoesEffect = 102,
|
||||
SciFiEffect = 103,
|
||||
Sitar = 104,
|
||||
Banjo = 105,
|
||||
Shamisen = 106,
|
||||
Koto = 107,
|
||||
Kalimba = 108,
|
||||
Bagpipe = 109,
|
||||
Fiddle = 110,
|
||||
Shanai = 111,
|
||||
TinkleBell = 112,
|
||||
Agogo = 113,
|
||||
SteelDrums = 114,
|
||||
Woodblock = 115,
|
||||
TaikoDrum = 116,
|
||||
MelodicTom = 117,
|
||||
SynthDrum = 118,
|
||||
ReverseCymbal = 119,
|
||||
GuitarFretNoise = 120,
|
||||
BreathNoise = 121,
|
||||
Seashore = 122,
|
||||
BirdTweet = 123,
|
||||
TelephoneRing = 124,
|
||||
Helicopter = 125,
|
||||
Applause = 126,
|
||||
Gunshot = 127,
|
||||
}
|
||||
|
||||
public static class MidiInstrumentExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Turns the given enum value into it's string representation to be used in localization.
|
||||
/// </summary>
|
||||
public static string GetStringRep(this MidiInstrument instrument)
|
||||
{
|
||||
return CaseConversion.PascalToKebab(instrument.ToString());
|
||||
}
|
||||
}
|
||||
184
Content.Client/Instruments/MidiParser/MidiParser.cs
Normal file
184
Content.Client/Instruments/MidiParser/MidiParser.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Content.Shared.Instruments;
|
||||
|
||||
namespace Content.Client.Instruments.MidiParser;
|
||||
|
||||
public static class MidiParser
|
||||
{
|
||||
// Thanks again to http://www.somascape.org/midi/tech/mfile.html
|
||||
public static bool TryGetMidiTracks(
|
||||
byte[] data,
|
||||
[NotNullWhen(true)] out MidiTrack[]? tracks,
|
||||
[NotNullWhen(false)] out string? error)
|
||||
{
|
||||
tracks = null;
|
||||
error = null;
|
||||
|
||||
var stream = new MidiStreamWrapper(data);
|
||||
|
||||
if (stream.ReadString(4) != "MThd")
|
||||
{
|
||||
error = "Invalid file header";
|
||||
return false;
|
||||
}
|
||||
|
||||
var headerLength = stream.ReadUInt32();
|
||||
// MIDI specs define that the header is 6 bytes, we only look at the 6 bytes, if its more, we skip ahead.
|
||||
|
||||
stream.Skip(2); // format
|
||||
var trackCount = stream.ReadUInt16();
|
||||
stream.Skip(2); // time div
|
||||
|
||||
// We now skip ahead if we still have any header length left
|
||||
stream.Skip((int)(headerLength - 6));
|
||||
|
||||
var parsedTracks = new List<MidiTrack>();
|
||||
|
||||
for (var i = 0; i < trackCount; i++)
|
||||
{
|
||||
if (stream.ReadString(4) != "MTrk")
|
||||
{
|
||||
tracks = null;
|
||||
error = "Track contains invalid header";
|
||||
return false;
|
||||
}
|
||||
|
||||
var track = new MidiTrack();
|
||||
|
||||
var trackLength = stream.ReadUInt32();
|
||||
var trackEnd = stream.StreamPosition + trackLength;
|
||||
var hasMidiEvent = false;
|
||||
byte? lastStatusByte = null;
|
||||
|
||||
while (stream.StreamPosition < trackEnd)
|
||||
{
|
||||
stream.ReadVariableLengthQuantity();
|
||||
|
||||
/*
|
||||
* If the first (status) byte is less than 128 (hex 80), this implies that running status is in effect,
|
||||
* and that this byte is actually the first data byte (the status carrying over from the previous MIDI event).
|
||||
* This can only be the case if the immediately previous event was also a MIDI event,
|
||||
* i.e. SysEx and Meta events interrupt (clear) running status.
|
||||
* See http://www.somascape.org/midi/tech/mfile.html#events
|
||||
*/
|
||||
|
||||
var firstByte = stream.ReadByte();
|
||||
if (firstByte >= 0x80)
|
||||
{
|
||||
lastStatusByte = firstByte;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Running status: push byte back for reading as data
|
||||
stream.Skip(-1);
|
||||
}
|
||||
|
||||
// The first event in each MTrk chunk must specify status.
|
||||
if (lastStatusByte == null)
|
||||
{
|
||||
tracks = null;
|
||||
error = "Track data not valid, expected status byte, got nothing.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var eventType = (byte)(lastStatusByte & 0xF0);
|
||||
|
||||
switch (lastStatusByte)
|
||||
{
|
||||
// Meta events
|
||||
case 0xFF:
|
||||
{
|
||||
var metaType = stream.ReadByte();
|
||||
var metaLength = stream.ReadVariableLengthQuantity();
|
||||
var metaData = stream.ReadBytes((int)metaLength);
|
||||
if (metaType == 0x00) // SequenceNumber event
|
||||
continue;
|
||||
|
||||
// Meta event types 01 through 0F are reserved for text and all follow the basic FF 01 len text format
|
||||
if (metaType is < 0x01 or > 0x0F)
|
||||
break;
|
||||
|
||||
// 0x03 is TrackName,
|
||||
// 0x04 is InstrumentName
|
||||
|
||||
var text = Encoding.ASCII.GetString(metaData, 0, (int)metaLength);
|
||||
switch (metaType)
|
||||
{
|
||||
case 0x03 when track.TrackName == null:
|
||||
track.TrackName = text;
|
||||
break;
|
||||
case 0x04 when track.InstrumentName == null:
|
||||
track.InstrumentName = text;
|
||||
break;
|
||||
}
|
||||
|
||||
// still here? then we dont care about the event
|
||||
break;
|
||||
}
|
||||
|
||||
// SysEx events
|
||||
case 0xF0:
|
||||
case 0xF7:
|
||||
{
|
||||
var sysexLength = stream.ReadVariableLengthQuantity();
|
||||
stream.Skip((int)sysexLength);
|
||||
// Sysex events and meta-events cancel any running status which was in effect.
|
||||
// Running status does not apply to and may not be used for these messages.
|
||||
lastStatusByte = null;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
switch (eventType)
|
||||
{
|
||||
// Program Change
|
||||
case 0xC0:
|
||||
{
|
||||
var programNumber = stream.ReadByte();
|
||||
if (track.ProgramName == null)
|
||||
{
|
||||
if (programNumber < Enum.GetValues<MidiInstrument>().Length)
|
||||
track.ProgramName = Loc.GetString($"instruments-component-menu-midi-channel-{((MidiInstrument)programNumber).GetStringRep()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x80: // Note Off
|
||||
case 0x90: // Note On
|
||||
case 0xA0: // Polyphonic Key Pressure
|
||||
case 0xB0: // Control Change
|
||||
case 0xE0: // Pitch Bend
|
||||
{
|
||||
hasMidiEvent = true;
|
||||
stream.Skip(2);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xD0: // Channel Pressure
|
||||
{
|
||||
hasMidiEvent = true;
|
||||
stream.Skip(1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
error = $"Unknown MIDI event type {lastStatusByte:X2}";
|
||||
tracks = null;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hasMidiEvent)
|
||||
parsedTracks.Add(track);
|
||||
}
|
||||
|
||||
tracks = parsedTracks.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
103
Content.Client/Instruments/MidiParser/MidiStreamWrapper.cs
Normal file
103
Content.Client/Instruments/MidiParser/MidiStreamWrapper.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Content.Client.Instruments.MidiParser;
|
||||
|
||||
public sealed class MidiStreamWrapper
|
||||
{
|
||||
private readonly MemoryStream _stream;
|
||||
private byte[] _buffer;
|
||||
|
||||
public long StreamPosition => _stream.Position;
|
||||
|
||||
public MidiStreamWrapper(byte[] data)
|
||||
{
|
||||
_stream = new MemoryStream(data, writable: false);
|
||||
_buffer = new byte[4];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips X number of bytes in the stream.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes to skip. If 0, no operations on the stream are performed.</param>
|
||||
public void Skip(int count)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
_stream.Seek(count, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
var b = _stream.ReadByte();
|
||||
if (b == -1)
|
||||
throw new Exception("Unexpected end of stream");
|
||||
|
||||
return (byte)b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads N bytes using the buffer.
|
||||
/// </summary>
|
||||
public byte[] ReadBytes(int count)
|
||||
{
|
||||
if (_buffer.Length < count)
|
||||
{
|
||||
Array.Resize(ref _buffer, count);
|
||||
}
|
||||
|
||||
var read = _stream.Read(_buffer, 0, count);
|
||||
if (read != count)
|
||||
throw new Exception("Unexpected end of stream");
|
||||
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 4 byte big-endian uint.
|
||||
/// </summary>
|
||||
public uint ReadUInt32()
|
||||
{
|
||||
var bytes = ReadBytes(4);
|
||||
return (uint)((bytes[0] << 24) |
|
||||
(bytes[1] << 16) |
|
||||
(bytes[2] << 8) |
|
||||
(bytes[3]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 2 byte big-endian ushort.
|
||||
/// </summary>
|
||||
public ushort ReadUInt16()
|
||||
{
|
||||
var bytes = ReadBytes(2);
|
||||
return (ushort)((bytes[0] << 8) | bytes[1]);
|
||||
}
|
||||
|
||||
public string ReadString(int count)
|
||||
{
|
||||
var bytes = ReadBytes(count);
|
||||
return Encoding.UTF8.GetString(bytes, 0, count);
|
||||
}
|
||||
|
||||
public uint ReadVariableLengthQuantity()
|
||||
{
|
||||
uint value = 0;
|
||||
|
||||
// variable-length-quantities encode ints using 7 bits per byte
|
||||
// the highest bit (7) is used for a continuation flag. We read until the high bit is 0
|
||||
|
||||
while (true)
|
||||
{
|
||||
var b = ReadByte();
|
||||
value = (value << 7) | (uint)(b & 0x7f); // Shift current value and add 7 bits
|
||||
// value << 7, make room for the next 7 bits
|
||||
// b & 0x7F mask out the high bit to just get the 7 bit payload
|
||||
if ((b & 0x80) == 0)
|
||||
break; // This was the last bit.
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,7 @@
|
||||
<Button Name="AllButton" Text="{Loc 'instruments-component-channels-all-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||
<Button Name="ClearButton" Text="{Loc 'instruments-component-channels-clear-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||
</BoxContainer>
|
||||
<CheckButton Name="DisplayTrackNames"
|
||||
Text="{Loc 'instruments-component-channels-track-names-toggle'}" />
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,26 +1,56 @@
|
||||
using Content.Shared.Instruments;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Instruments.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ChannelsMenu : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = null!;
|
||||
|
||||
private readonly InstrumentBoundUserInterface _owner;
|
||||
|
||||
public ChannelsMenu(InstrumentBoundUserInterface owner) : base()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_owner = owner;
|
||||
|
||||
ChannelList.OnItemSelected += OnItemSelected;
|
||||
ChannelList.OnItemDeselected += OnItemDeselected;
|
||||
AllButton.OnPressed += OnAllPressed;
|
||||
ClearButton.OnPressed += OnClearPressed;
|
||||
DisplayTrackNames.OnPressed += OnDisplayTrackNamesPressed;
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_owner.Instruments.OnChannelsUpdated += UpdateChannelList;
|
||||
}
|
||||
|
||||
private void OnDisplayTrackNamesPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
DisplayTrackNames.SetClickPressed(!DisplayTrackNames.Pressed);
|
||||
Populate();
|
||||
}
|
||||
|
||||
private void UpdateChannelList()
|
||||
{
|
||||
Populate(); // This is kind of in-efficent because we don't filter for which instrument updated its channels, but idc
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_owner.Instruments.OnChannelsUpdated -= UpdateChannelList;
|
||||
}
|
||||
|
||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
@@ -51,15 +81,71 @@ public sealed partial class ChannelsMenu : DefaultWindow
|
||||
}
|
||||
}
|
||||
|
||||
public void Populate(InstrumentComponent? instrument)
|
||||
/// <summary>
|
||||
/// Walks up the tree of instrument masters to find the truest master of them all.
|
||||
/// </summary>
|
||||
private ActiveInstrumentComponent ResolveActiveInstrument(InstrumentComponent? comp)
|
||||
{
|
||||
comp ??= _entityManager.GetComponent<InstrumentComponent>(_owner.Owner);
|
||||
|
||||
var instrument = new Entity<InstrumentComponent>(_owner.Owner, comp);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (instrument.Comp.Master == null)
|
||||
break;
|
||||
|
||||
instrument = new Entity<InstrumentComponent>((EntityUid)instrument.Comp.Master,
|
||||
_entityManager.GetComponent<InstrumentComponent>((EntityUid)instrument.Comp.Master));
|
||||
}
|
||||
|
||||
return _entityManager.GetComponent<ActiveInstrumentComponent>(instrument.Owner);
|
||||
}
|
||||
|
||||
public void Populate()
|
||||
{
|
||||
ChannelList.Clear();
|
||||
var instrument = _entityManager.GetComponent<InstrumentComponent>(_owner.Owner);
|
||||
var activeInstrument = ResolveActiveInstrument(instrument);
|
||||
|
||||
for (int i = 0; i < RobustMidiEvent.MaxChannels; i++)
|
||||
{
|
||||
var item = ChannelList.AddItem(_owner.Loc.GetString("instrument-component-channel-name",
|
||||
("number", i)), null, true, i);
|
||||
var label = _owner.Loc.GetString("instrument-component-channel-name",
|
||||
("number", i));
|
||||
if (activeInstrument != null
|
||||
&& activeInstrument.Tracks.TryGetValue(i, out var resolvedMidiChannel)
|
||||
&& resolvedMidiChannel != null)
|
||||
{
|
||||
if (DisplayTrackNames.Pressed)
|
||||
{
|
||||
label = resolvedMidiChannel switch
|
||||
{
|
||||
{ TrackName: not null, InstrumentName: not null } =>
|
||||
Loc.GetString("instruments-component-channels-multi",
|
||||
("channel", i),
|
||||
("name", resolvedMidiChannel.TrackName),
|
||||
("other", resolvedMidiChannel.InstrumentName)),
|
||||
{ TrackName: not null } =>
|
||||
Loc.GetString("instruments-component-channels-single",
|
||||
("channel", i),
|
||||
("name", resolvedMidiChannel.TrackName)),
|
||||
_ => label,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
label = resolvedMidiChannel switch
|
||||
{
|
||||
{ ProgramName: not null } =>
|
||||
Loc.GetString("instruments-component-channels-single",
|
||||
("channel", i),
|
||||
("name", resolvedMidiChannel.ProgramName)),
|
||||
_ => label,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var item = ChannelList.AddItem(label, null, true, i);
|
||||
|
||||
item.Selected = !instrument?.FilteredChannels[i] ?? false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Client.Audio.Midi;
|
||||
@@ -101,9 +102,7 @@ namespace Content.Client.Instruments.UI
|
||||
public void OpenChannelsMenu()
|
||||
{
|
||||
_channelsMenu ??= new ChannelsMenu(this);
|
||||
EntMan.TryGetComponent(Owner, out InstrumentComponent? instrument);
|
||||
|
||||
_channelsMenu.Populate(instrument);
|
||||
_channelsMenu.Populate();
|
||||
_channelsMenu.OpenCenteredRight();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||
|
||||
@@ -145,10 +146,6 @@ namespace Content.Client.Instruments.UI
|
||||
if (!PlayCheck())
|
||||
return;
|
||||
|
||||
await using var memStream = new MemoryStream((int) file.Length);
|
||||
|
||||
await file.CopyToAsync(memStream);
|
||||
|
||||
if (!_entManager.TryGetComponent<InstrumentComponent>(Entity, out var instrument))
|
||||
{
|
||||
return;
|
||||
@@ -156,7 +153,7 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
if (!_entManager.System<InstrumentSystem>()
|
||||
.OpenMidi(Entity,
|
||||
memStream.GetBuffer().AsSpan(0, (int) memStream.Length),
|
||||
file.CopyToArray(),
|
||||
instrument))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Client.Launcher;
|
||||
using Content.Client.Mapping;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Playtime;
|
||||
using Content.Client.Replay;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Stylesheets;
|
||||
@@ -62,6 +63,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<PlayerRateLimitManager>();
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
collection.Register<TitleWindowManager>();
|
||||
collection.Register<ClientsidePlaytimeTrackingManager>();
|
||||
collection.Register<InteractionPanelManager>(); // Corvax-Wega
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.LateJoin;
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Playtime;
|
||||
using Content.Client.UserInterface.Systems.Chat;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -26,6 +27,7 @@ namespace Content.Client.Lobby
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly ClientsidePlaytimeTrackingManager _playtimeTracking = default!;
|
||||
|
||||
private ClientGameTicker _gameTicker = default!;
|
||||
private ContentAudioSystem _contentAudioSystem = default!;
|
||||
@@ -195,6 +197,26 @@ namespace Content.Client.Lobby
|
||||
{
|
||||
Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
|
||||
}
|
||||
|
||||
var minutesToday = _playtimeTracking.PlaytimeMinutesToday;
|
||||
if (minutesToday > 60)
|
||||
{
|
||||
Lobby!.PlaytimeComment.Visible = true;
|
||||
|
||||
var hoursToday = Math.Round(minutesToday / 60f, 1);
|
||||
|
||||
var chosenString = minutesToday switch
|
||||
{
|
||||
< 180 => "lobby-state-playtime-comment-normal",
|
||||
< 360 => "lobby-state-playtime-comment-concerning",
|
||||
< 720 => "lobby-state-playtime-comment-grasstouchless",
|
||||
_ => "lobby-state-playtime-comment-selfdestructive"
|
||||
};
|
||||
|
||||
Lobby.PlaytimeComment.SetMarkup(Loc.GetString(chosenString, ("hours", hoursToday)));
|
||||
}
|
||||
else
|
||||
Lobby!.PlaytimeComment.Visible = false;
|
||||
}
|
||||
|
||||
private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
|
||||
|
||||
@@ -20,6 +20,12 @@ public sealed partial class LoadoutContainer : BoxContainer
|
||||
|
||||
public Button Select => SelectButton;
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => SelectButton.Text;
|
||||
set => SelectButton.Text = value;
|
||||
}
|
||||
|
||||
public LoadoutContainer(ProtoId<LoadoutPrototype> proto, bool disabled, FormattedMessage? reason)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -54,22 +60,9 @@ public sealed partial class LoadoutContainer : BoxContainer
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_entManager.DeleteEntity(_entity);
|
||||
}
|
||||
|
||||
public bool Pressed
|
||||
{
|
||||
get => SelectButton.Pressed;
|
||||
set => SelectButton.Pressed = value;
|
||||
}
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => SelectButton.Text;
|
||||
set => SelectButton.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Orientation="Vertical">
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
|
||||
<BoxContainer Name="LoadoutsContainer" Orientation="Vertical"/>
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True" Margin="5">
|
||||
<BoxContainer Name="LoadoutsContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True"/>
|
||||
</PanelContainer>
|
||||
<!-- Buffer space so we have 10 margin between controls but also 10 to the borders -->
|
||||
<Label Text="{Loc 'loadout-restrictions'}" Margin="5 0 5 5"/>
|
||||
<BoxContainer Name="RestrictionsContainer" Orientation="Vertical" HorizontalExpand="True" />
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True" Margin="5">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'loadout-restrictions'}"/>
|
||||
<BoxContainer Name="RestrictionsContainer" Orientation="Vertical" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using Content.Corvax.Interfaces.Shared;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Preferences;
|
||||
@@ -8,12 +7,21 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Loadouts;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
{
|
||||
private const string ClosedGroupMark = "▶";
|
||||
private const string OpenedGroupMark = "▼";
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary that stores open groups
|
||||
/// </summary>
|
||||
private Dictionary<string, bool> _openedGroups = new();
|
||||
|
||||
private readonly LoadoutGroupPrototype _groupProto;
|
||||
|
||||
public event Action<ProtoId<LoadoutPrototype>>? OnLoadoutPressed;
|
||||
@@ -22,6 +30,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
public LoadoutGroupContainer(HumanoidCharacterProfile profile, RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_groupProto = groupProto;
|
||||
|
||||
RefreshLoadouts(profile, loadout, session, collection);
|
||||
@@ -64,9 +73,6 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
}
|
||||
|
||||
LoadoutsContainer.DisposeAllChildren();
|
||||
// Didn't use options because this is more robust in future.
|
||||
|
||||
var selected = loadout.SelectedLoadouts[_groupProto.ID];
|
||||
|
||||
// Corvax-Loadouts-Start
|
||||
var groupLoadouts = _groupProto.Loadouts;
|
||||
@@ -76,28 +82,167 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
|
||||
}
|
||||
// Corvax-Loadouts-End
|
||||
|
||||
foreach (var loadoutProto in groupLoadouts) // Corvax-Loadouts
|
||||
// Get all loadout prototypes for this group.
|
||||
var validProtos = groupLoadouts.Select(id => protoMan.Index(id)); // Corvax-Loadouts-Edit
|
||||
|
||||
// Get all loadout prototypes for this group.
|
||||
//var validProtos = _groupProto.Loadouts.Select(id => protoMan.Index(id));
|
||||
|
||||
/*
|
||||
* Group the prototypes based on their GroupBy field.
|
||||
* - If GroupBy is null or empty, fallback to grouping by the prototype ID itself.
|
||||
* - The result is a dictionary where:
|
||||
* - The key is either GroupBy or ID (if GroupBy is not set).
|
||||
* - The value is the list of prototypes that belong to that group.
|
||||
*
|
||||
* This allows grouping loadouts into sub-categories within the group.
|
||||
*/
|
||||
var groups = validProtos
|
||||
.GroupBy(p => string.IsNullOrEmpty(p.GroupBy)
|
||||
? p.ID
|
||||
: p.GroupBy)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
foreach (var kvp in groups)
|
||||
{
|
||||
if (!protoMan.TryIndex(loadoutProto, out var loadProto))
|
||||
continue;
|
||||
var protos = kvp.Value;
|
||||
|
||||
var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
|
||||
var pressed = matchingLoadout != null;
|
||||
|
||||
var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason);
|
||||
var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
|
||||
loadoutContainer.Select.Pressed = pressed;
|
||||
loadoutContainer.Text = loadoutSystem.GetName(loadProto);
|
||||
|
||||
loadoutContainer.Select.OnPressed += args =>
|
||||
if (protos.Count > 1)
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
OnLoadoutPressed?.Invoke(loadoutProto);
|
||||
else
|
||||
OnLoadoutUnpressed?.Invoke(loadoutProto);
|
||||
};
|
||||
/*
|
||||
* Build the list of UI elements for each loadout prototype:
|
||||
* - For each prototype, create its corresponding LoadoutContainer UI element.
|
||||
* - Set HorizontalExpand to true so elements properly stretch in layout.
|
||||
* - Collect all UI elements into a list for further processing.
|
||||
*/
|
||||
var uiElements = protos
|
||||
.Select(proto =>
|
||||
{
|
||||
var elem = CreateLoadoutUI(proto, profile, loadout, session, collection, loadoutSystem);
|
||||
elem.HorizontalExpand = true;
|
||||
return elem;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
LoadoutsContainer.AddChild(loadoutContainer);
|
||||
/*
|
||||
* Determine which element should be displayed first:
|
||||
* - If any element is currently selected (its button is pressed), use it.
|
||||
* - Otherwise, fallback to the first element in the list.
|
||||
*
|
||||
* This moves the selected item outside of the sublist for better usability,
|
||||
* making it easier for players to quickly toggle loadout options (e.g. clothing, accessories)
|
||||
* without having to search inside expanded subgroups.
|
||||
*/
|
||||
var firstElement = uiElements.FirstOrDefault(e => e.Select.Pressed) ?? uiElements[0];
|
||||
|
||||
/*
|
||||
* Get all remaining elements except the first one:
|
||||
* - Use ReferenceEquals to ensure we exclude the exact instance used as firstElement.
|
||||
*/
|
||||
var otherElements = uiElements.Where(e => !ReferenceEquals(e, firstElement)).ToList();
|
||||
|
||||
firstElement.HorizontalExpand = true;
|
||||
var subContainer = new SubLoadoutContainer()
|
||||
{
|
||||
Visible = _openedGroups.GetValueOrDefault(kvp.Key, false)
|
||||
};
|
||||
var toggle = CreateToggleButton(kvp, firstElement, subContainer);
|
||||
|
||||
LoadoutsContainer.AddChild(firstElement);
|
||||
LoadoutsContainer.AddChild(subContainer);
|
||||
|
||||
var subList = subContainer.Grid;
|
||||
foreach (var proto in otherElements)
|
||||
{
|
||||
subList.AddChild(proto);
|
||||
}
|
||||
|
||||
UpdateToggleColor(toggle, subList);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadoutsContainer.AddChild(
|
||||
CreateLoadoutUI(protos[0], profile, loadout, session, collection, loadoutSystem)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ToggleLoadoutButton CreateToggleButton(KeyValuePair<string, List<LoadoutPrototype>> kvp, LoadoutContainer firstElement, SubLoadoutContainer subContainer)
|
||||
{
|
||||
var toggle = new ToggleLoadoutButton
|
||||
{
|
||||
Text = ClosedGroupMark
|
||||
};
|
||||
|
||||
toggle.Text = subContainer.Visible ? OpenedGroupMark : ClosedGroupMark;
|
||||
|
||||
toggle.OnPressed += _ =>
|
||||
{
|
||||
var willOpen = !subContainer.Visible;
|
||||
subContainer.Visible = willOpen;
|
||||
toggle.Text = willOpen ? OpenedGroupMark : ClosedGroupMark;
|
||||
_openedGroups[kvp.Key] = willOpen;
|
||||
};
|
||||
|
||||
firstElement.AddChild(toggle);
|
||||
toggle.SetPositionFirst();
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private void UpdateToggleColor(Button toggle, BoxContainer subList)
|
||||
{
|
||||
var anyActive = subList.Children
|
||||
.OfType<LoadoutContainer>()
|
||||
.Any(c => c.Select.Pressed);
|
||||
|
||||
toggle.Modulate = anyActive
|
||||
? Color.Green
|
||||
: Color.White;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a UI container for a single Loadout item.
|
||||
///
|
||||
/// This method was extracted from RefreshLoadouts because the logic for creating
|
||||
/// individual loadout items is used multiple times inside that method, and duplicating
|
||||
/// the code made it harder to maintain.
|
||||
///
|
||||
/// Logic:
|
||||
/// - Checks if the item is currently selected in the loadout.
|
||||
/// - Checks if the item is valid for selection (IsValid).
|
||||
/// - Creates a LoadoutContainer with the appropriate status (disabled / active).
|
||||
/// - Subscribes to button press events to handle selection and deselection.
|
||||
/// </summary>
|
||||
/// <param name="proto">The loadout item prototype.</param>
|
||||
/// <param name="profile">The humanoid character profile.</param>
|
||||
/// <param name="loadout">The current role loadout for the user.</param>
|
||||
/// <param name="session">The user's session.</param>
|
||||
/// <param name="collection">The dependency injection container.</param>
|
||||
/// <param name="loadoutSystem">The loadout system instance.</param>
|
||||
/// <returns>A fully initialized LoadoutContainer for UI display.</returns>
|
||||
private LoadoutContainer CreateLoadoutUI(LoadoutPrototype proto, HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, LoadoutSystem loadoutSystem)
|
||||
{
|
||||
var selected = loadout.SelectedLoadouts[_groupProto.ID];
|
||||
|
||||
var pressed = selected.Any(e => e.Prototype == proto.ID);
|
||||
|
||||
var enabled = loadout.IsValid(profile, session, proto.ID, collection, out var reason);
|
||||
|
||||
var cont = new LoadoutContainer(proto, !enabled, reason);
|
||||
|
||||
cont.Text = loadoutSystem.GetName(proto);
|
||||
|
||||
cont.Select.Pressed = pressed;
|
||||
|
||||
cont.Select.OnPressed += args =>
|
||||
{
|
||||
if (args.Button.Pressed)
|
||||
OnLoadoutPressed?.Invoke(proto.ID);
|
||||
else
|
||||
OnLoadoutUnpressed?.Invoke(proto.ID);
|
||||
};
|
||||
|
||||
return cont;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<PanelContainer Name="SubContainer"
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<BoxContainer Name="SubGridContainer"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="true"/>
|
||||
|
||||
</PanelContainer>
|
||||
21
Content.Client/Lobby/UI/Loadouts/SubLoadoutContainer.xaml.cs
Normal file
21
Content.Client/Lobby/UI/Loadouts/SubLoadoutContainer.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Loadouts;
|
||||
|
||||
/// <summary>
|
||||
/// A simple container used to group additional loadout UI elements
|
||||
/// that are part of the same GroupBy subgroup.
|
||||
///
|
||||
/// - Used when a loadout group contains multiple prototypes.
|
||||
/// - The first prototype is shown directly; the remaining ones are placed inside this container.
|
||||
/// - Allows toggling visibility of the subgroup via expandable UI (collapsible behavior).
|
||||
///
|
||||
/// Internally inherits from PanelContainer to allow for border/background styling if needed.
|
||||
/// Exposes its internal BoxContainer (SubGridContainer) via the <see cref="Grid"/> property for adding children.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SubLoadoutContainer : PanelContainer
|
||||
{
|
||||
public BoxContainer Grid => SubGridContainer;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<Button Name="ToggleButton"
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
VerticalExpand="False"
|
||||
HorizontalExpand="False"
|
||||
SetSize="64 64"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 5 0"/>
|
||||
10
Content.Client/Lobby/UI/Loadouts/ToggleLoadoutButton.xaml.cs
Normal file
10
Content.Client/Lobby/UI/Loadouts/ToggleLoadoutButton.xaml.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Lobby.UI.Loadouts;
|
||||
|
||||
/// <summary>
|
||||
/// A button that toggles the loadout groups. Needs for override default styles.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ToggleLoadoutButton : Button;
|
||||
@@ -41,6 +41,7 @@
|
||||
StyleClasses="ButtonBig" MinWidth="137" />
|
||||
</BoxContainer>
|
||||
</controls:StripeBack>
|
||||
<RichTextLabel Name="PlaytimeComment" Visible="False" Access="Public" HorizontalAlignment="Center" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<!-- Voting Popups -->
|
||||
|
||||
@@ -454,7 +454,7 @@ public sealed class MappingState : GameplayStateBase
|
||||
switch (prototype)
|
||||
{
|
||||
case EntityPrototype entity:
|
||||
textures.AddRange(SpriteComponent.GetPrototypeTextures(entity, _resources).Select(t => t.Default));
|
||||
textures.AddRange(_sprite.GetPrototypeTextures(entity).Select(t => t.Default));
|
||||
break;
|
||||
case DecalPrototype decal:
|
||||
textures.Add(_sprite.Frame0(decal.Sprite));
|
||||
|
||||
@@ -3,14 +3,14 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.NPC;
|
||||
|
||||
public sealed class ShowHTNCommand : IConsoleCommand
|
||||
public sealed class ShowHtnCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showhtn";
|
||||
public string Description => "Shows the current status for HTN NPCs";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
[Dependency] private readonly HTNSystem _htnSystem = default!;
|
||||
|
||||
public override string Command => "showhtn";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var npcs = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<HTNSystem>();
|
||||
npcs.EnableOverlay ^= true;
|
||||
_htnSystem.EnableOverlay ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,15 +137,14 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ClearAllNetworkLinkOverlays : IConsoleCommand
|
||||
public sealed class ClearAllNetworkLinkOverlays : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly NetworkConfiguratorSystem _network = default!;
|
||||
|
||||
public string Command => "clearnetworklinkoverlays";
|
||||
public string Description => "Clear all network link overlays.";
|
||||
public string Help => Command;
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "clearnetworklinkoverlays";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_e.System<NetworkConfiguratorSystem>().ClearAllOverlays();
|
||||
_network.ClearAllOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.NodeContainer
|
||||
{
|
||||
public sealed class NodeVisCommand : IConsoleCommand
|
||||
public sealed class NodeVisCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly NodeGroupSystem _nodeSystem = default!;
|
||||
|
||||
public string Command => "nodevis";
|
||||
public string Description => "Toggles node group visualization";
|
||||
public string Help => "";
|
||||
public override string Command => "nodevis";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var adminMan = IoCManager.Resolve<IClientAdminManager>();
|
||||
if (!adminMan.HasFlag(AdminFlags.Debug))
|
||||
if (!_adminManager.HasFlag(AdminFlags.Debug))
|
||||
{
|
||||
shell.WriteError("You need +DEBUG for this command");
|
||||
shell.WriteError(Loc.GetString($"shell-missing-required-permission", ("perm", "+DEBUG")));
|
||||
return;
|
||||
}
|
||||
|
||||
var sys = _e.System<NodeGroupSystem>();
|
||||
sys.SetVisEnabled(!sys.VisEnabled);
|
||||
_nodeSystem.SetVisEnabled(!_nodeSystem.VisEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NodeVisFilterCommand : IConsoleCommand
|
||||
public sealed class NodeVisFilterCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly NodeGroupSystem _nodeSystem = default!;
|
||||
|
||||
public string Command => "nodevisfilter";
|
||||
public string Description => "Toggles showing a specific group on nodevis";
|
||||
public string Help => "Usage: nodevis [filter]\nOmit filter to list currently masked-off";
|
||||
public override string Command => "nodevisfilter";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var sys = _e.System<NodeGroupSystem>();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
foreach (var filtered in sys.Filtered)
|
||||
foreach (var filtered in _nodeSystem.Filtered)
|
||||
{
|
||||
shell.WriteLine(filtered);
|
||||
}
|
||||
@@ -50,10 +41,8 @@ namespace Content.Client.NodeContainer
|
||||
else
|
||||
{
|
||||
var filter = args[0];
|
||||
if (!sys.Filtered.Add(filter))
|
||||
{
|
||||
sys.Filtered.Remove(filter);
|
||||
}
|
||||
if (!_nodeSystem.Filtered.Add(filter))
|
||||
_nodeSystem.Filtered.Remove(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
Normal file
108
Content.Client/Playtime/ClientsidePlaytimeTrackingManager.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Playtime;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of how long the player has played today.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Playtime is treated as any time in which the player is attached to an entity.
|
||||
/// This notably excludes scenarios like the lobby.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class ClientsidePlaytimeTrackingManager
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _clientNetManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private const string InternalDateFormat = "yyyy-MM-dd";
|
||||
|
||||
[ViewVariables]
|
||||
private TimeSpan? _mobAttachmentTime;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of time played today, in minutes.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float PlaytimeMinutesToday
|
||||
{
|
||||
get
|
||||
{
|
||||
var cvarValue = _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
|
||||
if (_mobAttachmentTime == null)
|
||||
return cvarValue;
|
||||
|
||||
return cvarValue + (float)(_gameTiming.RealTime - _mobAttachmentTime.Value).TotalMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("clientplaytime");
|
||||
_clientNetManager.Connected += OnConnected;
|
||||
|
||||
// The downside to relying on playerattached and playerdetached is that unsaved playtime won't be saved in the event of a crash
|
||||
// But then again, the config doesn't get saved in the event of a crash, either, so /shrug
|
||||
// Playerdetached gets called on quit, though, so at least that's covered.
|
||||
_playerManager.LocalPlayerAttached += OnPlayerAttached;
|
||||
_playerManager.LocalPlayerDetached += OnPlayerDetached;
|
||||
}
|
||||
|
||||
private void OnConnected(object? sender, NetChannelArgs args)
|
||||
{
|
||||
var datatimey = DateTime.Now;
|
||||
_sawmill.Info($"Current day: {datatimey.Day} Current Date: {datatimey.Date.ToString(InternalDateFormat)}");
|
||||
|
||||
var recordedDateString = _configurationManager.GetCVar(CCVars.PlaytimeLastConnectDate);
|
||||
var formattedDate = datatimey.Date.ToString(InternalDateFormat);
|
||||
|
||||
if (formattedDate == recordedDateString)
|
||||
return;
|
||||
|
||||
_configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, 0);
|
||||
_configurationManager.SetCVar(CCVars.PlaytimeLastConnectDate, formattedDate);
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid entity)
|
||||
{
|
||||
_mobAttachmentTime = _gameTiming.RealTime;
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid entity)
|
||||
{
|
||||
if (_mobAttachmentTime == null)
|
||||
return;
|
||||
|
||||
var newTimeValue = PlaytimeMinutesToday;
|
||||
|
||||
_mobAttachmentTime = null;
|
||||
|
||||
var timeDiffMinutes = newTimeValue - _configurationManager.GetCVar(CCVars.PlaytimeMinutesToday);
|
||||
if (timeDiffMinutes < 0)
|
||||
{
|
||||
_sawmill.Error("Time differential on player detachment somehow less than zero!");
|
||||
return;
|
||||
}
|
||||
|
||||
// At less than 1 minute of time diff, there's not much point, and saving regardless will brick tests
|
||||
// The reason this isn't checking for 0 is because TotalMinutes is fractional, rather than solely whole minutes
|
||||
if (timeDiffMinutes < 1)
|
||||
return;
|
||||
|
||||
_configurationManager.SetCVar(CCVars.PlaytimeMinutesToday, newTimeValue);
|
||||
|
||||
_sawmill.Info($"Recorded {timeDiffMinutes} minutes of living playtime!");
|
||||
|
||||
_configurationManager.SaveToFile(); // We don't like that we have to save the entire config just to store playtime stats '^'
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,17 @@ namespace Content.Client.UserInterface.Systems.Chat;
|
||||
/// </summary>
|
||||
public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSystem>
|
||||
{
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
||||
|
||||
private static readonly Regex StartDoubleQuote = new("\"$");
|
||||
private static readonly Regex EndDoubleQuote = new("^\"|(?<=^@)\"");
|
||||
private static readonly Regex StartAtSign = new("^@");
|
||||
|
||||
/// <summary>
|
||||
/// The list of words to be highlighted in the chatbox.
|
||||
/// </summary>
|
||||
private List<string> _highlights = new();
|
||||
private readonly List<string> _highlights = new();
|
||||
|
||||
/// <summary>
|
||||
/// The string holding the hex color used to highlight words.
|
||||
@@ -42,7 +47,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
_config.OnValueChanged(CCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }, true);
|
||||
|
||||
// Load highlights if any were saved.
|
||||
string highlights = _config.GetCVar(CCVars.ChatHighlights);
|
||||
var highlights = _config.GetCVar(CCVars.ChatHighlights);
|
||||
|
||||
if (!string.IsNullOrEmpty(highlights))
|
||||
{
|
||||
@@ -84,12 +89,12 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
|
||||
// We first subdivide the highlights based on newlines to prevent replacing
|
||||
// a valid "\n" tag and adding it to the final regex.
|
||||
string[] splittedHighlights = newHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
var splittedHighlights = newHighlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
for (int i = 0; i < splittedHighlights.Length; i++)
|
||||
for (var i = 0; i < splittedHighlights.Length; i++)
|
||||
{
|
||||
// Replace every "\" character with a "\\" to prevent "\n", "\0", etc...
|
||||
string keyword = splittedHighlights[i].Replace(@"\", @"\\");
|
||||
var keyword = splittedHighlights[i].Replace(@"\", @"\\");
|
||||
|
||||
// Escape the keyword to prevent special characters like "(" and ")" to be considered valid regex.
|
||||
keyword = Regex.Escape(keyword);
|
||||
@@ -102,17 +107,17 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
// that make sure the words to match are separated by spaces or punctuation.
|
||||
// NOTE: The reason why we don't use \b tags is that \b doesn't match reverse slash characters "\" so
|
||||
// a pre-sanitized (see 1.) string like "\[test]" wouldn't get picked up by the \b.
|
||||
if (keyword.Count(c => (c == '"')) > 0)
|
||||
if (keyword.Any(c => c == '"'))
|
||||
{
|
||||
// Matches the last double quote character.
|
||||
keyword = Regex.Replace(keyword, "\"$", "(?!\\w)");
|
||||
keyword = StartDoubleQuote.Replace(keyword, "(?!\\w)");
|
||||
// When matching for the first double quote character we also consider the possibility
|
||||
// of the double quote being preceded by a @ character.
|
||||
keyword = Regex.Replace(keyword, "^\"|(?<=^@)\"", "(?<!\\w)");
|
||||
keyword = EndDoubleQuote.Replace(keyword, "(?<!\\w)");
|
||||
}
|
||||
|
||||
// Make sure any name tagged as ours gets highlighted only when others say it.
|
||||
keyword = Regex.Replace(keyword, "^@", "(?<=(?<=/name.*)|(?<=,.*\"\".*))");
|
||||
keyword = StartAtSign.Replace(keyword, "(?<=(?<=/name.*)|(?<=,.*\"\".*))");
|
||||
|
||||
_highlights.Add(keyword);
|
||||
}
|
||||
@@ -132,7 +137,7 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
var (_, job, _, _, entityName) = data;
|
||||
|
||||
// Mark this entity's name as our character name for the "UpdateHighlights" function.
|
||||
string newHighlights = "@" + entityName;
|
||||
var newHighlights = "@" + entityName;
|
||||
|
||||
// Subdivide the character's name based on spaces or hyphens so that every word gets highlighted.
|
||||
if (newHighlights.Count(c => (c == ' ' || c == '-')) == 1)
|
||||
@@ -144,9 +149,9 @@ public sealed partial class ChatUIController : IOnSystemChanged<CharacterInfoSys
|
||||
newHighlights = newHighlights.Split('-')[0] + "\n@" + newHighlights.Split('-')[^1];
|
||||
|
||||
// Convert the job title to kebab-case and use it as a key for the loc file.
|
||||
string jobKey = job.Replace(' ', '-').ToLower();
|
||||
var jobKey = job.Replace(' ', '-').ToLower();
|
||||
|
||||
if (Loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
|
||||
if (_loc.TryGetString($"highlights-{jobKey}", out var jobMatches))
|
||||
newHighlights += '\n' + jobMatches.Replace(", ", "\n");
|
||||
|
||||
UpdateHighlights(newHighlights);
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
[Virtual]
|
||||
public partial class ChatBox : UIWidget
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly ChatUIController _controller;
|
||||
private readonly IEntityManager _entManager;
|
||||
|
||||
public bool Main { get; set; }
|
||||
|
||||
@@ -29,7 +32,7 @@ public partial class ChatBox : UIWidget
|
||||
public ChatBox()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
_sawmill = _log.GetSawmill("chat");
|
||||
|
||||
ChatInput.Input.OnTextEntered += OnTextEntered;
|
||||
ChatInput.Input.OnKeyBindDown += OnInputKeyBindDown;
|
||||
@@ -52,7 +55,7 @@ public partial class ChatBox : UIWidget
|
||||
|
||||
private void OnMessageAdded(ChatMessage msg)
|
||||
{
|
||||
Logger.DebugS("chat", $"{msg.Channel}: {msg.Message}");
|
||||
_sawmill.Debug($"{msg.Channel}: {msg.Message}");
|
||||
if (!ChatInput.FilterButton.Popup.IsActive(msg.Channel))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -225,6 +225,10 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
if (!IsDragging && EntityManager.System<HandsSystem>().GetActiveHandEntity() == null)
|
||||
return;
|
||||
|
||||
// Do not rotate items unless we are either dragging them or hovering over a storage window.
|
||||
if (DraggingGhost is null && UIManager.CurrentlyHovered is not StorageWindow)
|
||||
return;
|
||||
|
||||
//clamp it to a cardinal.
|
||||
DraggingRotation = (DraggingRotation + Math.PI / 2f).GetCardinalDir().ToAngle();
|
||||
if (DraggingGhost != null)
|
||||
|
||||
@@ -274,13 +274,11 @@ namespace Content.Client.Voting.UI
|
||||
}
|
||||
|
||||
[UsedImplicitly, AnyCommand]
|
||||
public sealed class VoteMenuCommand : IConsoleCommand
|
||||
public sealed class VoteMenuCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "votemenu";
|
||||
public string Description => Loc.GetString("ui-vote-menu-command-description");
|
||||
public string Help => Loc.GetString("ui-vote-menu-command-help-text");
|
||||
public override string Command => "votemenu";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new VoteCallMenu().OpenCentered();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using Content.Client.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged;
|
||||
namespace Content.Client.Weapons.Ranged.Commands;
|
||||
|
||||
public sealed class ShowSpreadCommand : IConsoleCommand
|
||||
public sealed class ShowSpreadCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showgunspread";
|
||||
public string Description => $"Shows gun spread overlay for debugging";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GunSystem>();
|
||||
system.SpreadOverlay ^= true;
|
||||
[Dependency] private readonly GunSystem _gunSystem = default!;
|
||||
|
||||
shell.WriteLine($"Set spread overlay to {system.SpreadOverlay}");
|
||||
public override string Command => "showgunspread";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_gunSystem.SpreadOverlay ^= true;
|
||||
|
||||
shell.WriteLine(Loc.GetString($"cmd-showgunspread-status", ("status", _gunSystem.SpreadOverlay)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Content.Client.Implants;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Implants;
|
||||
@@ -11,17 +9,17 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.IntegrationTests.Tests.Chameleon;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all round <see cref="IsProbablyRoundStartJob">"round start jobs"</see> have an associated chameleon loadout.
|
||||
/// Ensures all <see cref="IsProbablyRoundStartJob">"round start jobs"</see> have an associated chameleon loadout.
|
||||
/// </summary>
|
||||
public sealed class ChameleonJobLoadoutTest : InteractionTest
|
||||
{
|
||||
private readonly List<ProtoId<JobPrototype>> JobBlacklist =
|
||||
private static readonly List<ProtoId<JobPrototype>> JobBlacklist =
|
||||
[
|
||||
|
||||
];
|
||||
|
||||
[Test]
|
||||
public async Task CheckAllJobs()
|
||||
public Task CheckAllJobs()
|
||||
{
|
||||
var alljobs = ProtoMan.EnumeratePrototypes<JobPrototype>();
|
||||
|
||||
@@ -47,24 +45,16 @@ public sealed class ChameleonJobLoadoutTest : InteractionTest
|
||||
validJobs[chameleon.Job.Value] += 1;
|
||||
}
|
||||
|
||||
var errorMessage = new StringBuilder();
|
||||
errorMessage.AppendLine("The following job(s) have no chameleon prototype(s):");
|
||||
var invalid = false;
|
||||
|
||||
// All round start jobs have a chameleon loadout
|
||||
foreach (var job in validJobs)
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
if (job.Value != 0)
|
||||
continue;
|
||||
foreach (var job in validJobs)
|
||||
{
|
||||
Assert.That(job.Value, Is.Not.Zero,
|
||||
$"{job.Key} has no chameleonOutfit prototype.");
|
||||
}
|
||||
});
|
||||
|
||||
errorMessage.AppendLine(job.Key + " has no chameleonOutfit prototype.");
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (!invalid)
|
||||
return;
|
||||
|
||||
Assert.Fail(errorMessage.ToString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.IntegrationTests.Tests
|
||||
var client = pair.Client;
|
||||
var prototypeManager = client.ResolveDependency<IPrototypeManager>();
|
||||
var resourceCache = client.ResolveDependency<IResourceCache>();
|
||||
var spriteSys = client.System<SpriteSystem>();
|
||||
|
||||
await client.WaitAssertion(() =>
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
var _ = SpriteComponent.GetPrototypeTextures(proto, resourceCache).ToList();
|
||||
var _ = spriteSys.GetPrototypeTextures(proto).ToList();
|
||||
}, "Prototype {0} threw an exception when getting its textures.",
|
||||
proto.ID);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Localization;
|
||||
|
||||
public sealed class EntityPrototypeLocalizationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// An explanation of why LocIds should not be used for entity prototype names/descriptions.
|
||||
/// Appended to the error message when the test is failed.
|
||||
/// </summary>
|
||||
private const string NoLocIdExplanation = "Entity prototypes should not use LocIds for names/descriptions, as localization IDs are automated for entity prototypes. See https://docs.spacestation14.com/en/ss14-by-example/fluent-and-localization.html#localizing-prototypes for more information.";
|
||||
|
||||
/// <summary>
|
||||
/// Checks that no entity prototypes have a LocId as their name or description.
|
||||
/// See <see href="https://docs.spacestation14.com/en/ss14-by-example/fluent-and-localization.html#localizing-prototypes"/> for why this is important.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestNoManualEntityLocStrings()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var protoMan = server.ProtoMan;
|
||||
var locMan = server.ResolveDependency<ILocalizationManager>();
|
||||
|
||||
var protos = protoMan.EnumeratePrototypes<EntityPrototype>();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in protos)
|
||||
{
|
||||
// Check name
|
||||
if (!string.IsNullOrEmpty(proto.SetName))
|
||||
{
|
||||
Assert.That(locMan.HasString(proto.SetName), Is.False,
|
||||
$"Entity prototype {proto.ID} has a LocId ({proto.SetName}) as a name. {NoLocIdExplanation}");
|
||||
}
|
||||
|
||||
// Check description
|
||||
if (!string.IsNullOrEmpty(proto.SetDesc))
|
||||
{
|
||||
Assert.That(locMan.HasString(proto.SetDesc), Is.False,
|
||||
$"Entity prototype {proto.ID} has a LocId ({proto.SetDesc}) as a description. {NoLocIdExplanation}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"Elkridge",
|
||||
"Relic",
|
||||
"dm01-entryway",
|
||||
|
||||
"Exo",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -51,7 +51,7 @@ public static class ServerPackaging
|
||||
// Python script had Npgsql. though we want Npgsql.dll as well soooo
|
||||
"Npgsql",
|
||||
"Microsoft",
|
||||
"Discord",
|
||||
"NetCord",
|
||||
};
|
||||
|
||||
private static readonly List<string> ServerNotExtraAssemblies = new()
|
||||
|
||||
74
Content.Server/Actions/Commands/AddActionCommand.cs
Normal file
74
Content.Server/Actions/Commands/AddActionCommand.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Prototypes;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Actions.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class AddActionCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override string Command => "addaction";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString(Loc.GetString("cmd-addaction-invalid-args")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var targetUidNet) || !EntityManager.TryGetEntity(targetUidNet, out var targetEntity))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-entity-uid-must-be-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.HasComponent<ActionsComponent>(targetEntity))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-addaction-actions-not-found"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex<EntityPrototype>(args[1], out var proto) ||
|
||||
!proto.HasComponent<ActionComponent>())
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-addaction-action-not-found", ("action", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actions.AddAction(targetEntity.Value, args[1]) == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-addaction-adding-failed"));
|
||||
}
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.Components<ActionsComponent>(args[0]),
|
||||
Loc.GetString("cmd-addaction-player-completion"));
|
||||
}
|
||||
|
||||
if (args.Length != 2)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var actionPrototypes = _prototypeManager.EnumeratePrototypes<EntityPrototype>()
|
||||
.Where(p => p.HasComponent<ActionComponent>())
|
||||
.Select(p => p.ID)
|
||||
.Order();
|
||||
|
||||
return CompletionResult.FromHintOptions(
|
||||
actionPrototypes,
|
||||
Loc.GetString("cmd-addaction-action-completion"));
|
||||
}
|
||||
}
|
||||
84
Content.Server/Actions/Commands/RemoveActionCommand.cs
Normal file
84
Content.Server/Actions/Commands/RemoveActionCommand.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Actions.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class RemoveActionCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
|
||||
public override string Command => "rmaction";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString(Loc.GetString("cmd-rmaction-invalid-args")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var targetUidNet) || !EntityManager.TryGetEntity(targetUidNet, out var targetEntity))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-could-not-find-entity-with-uid", ("uid", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[1], out var targetActionUidNet) || !EntityManager.TryGetEntity(targetActionUidNet, out var targetActionEntity))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-could-not-find-entity-with-uid", ("uid", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityManager.HasComponent<ActionsComponent>(targetEntity))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-rmaction-actions-not-found"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actions.GetAction(targetActionEntity) is not { } ent)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-rmaction-not-an-action"));
|
||||
return;
|
||||
}
|
||||
|
||||
_actions.SetTemporary(ent.Owner, true);
|
||||
|
||||
_actions.RemoveAction(ent.Owner);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(
|
||||
CompletionHelper.Components<ActionsComponent>(args[0]),
|
||||
Loc.GetString("cmd-rmaction-player-completion"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
if (!NetEntity.TryParse(args[0], out var targetUidNet) || !EntityManager.TryGetEntity(targetUidNet, out var targetEntity))
|
||||
return CompletionResult.Empty;
|
||||
|
||||
if (!EntityManager.HasComponent<ActionsComponent>(targetEntity))
|
||||
return CompletionResult.Empty;
|
||||
|
||||
var actions = _actions.GetActions(targetEntity.Value);
|
||||
|
||||
var options = new List<CompletionOption>();
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var hint = Loc.GetString("cmd-rmaction-action-info", ("action", action));
|
||||
options.Add(new CompletionOption(action.Owner.ToString(), hint));
|
||||
}
|
||||
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-rmaction-action-completion"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Afk;
|
||||
using Content.Shared.Administration;
|
||||
@@ -8,32 +8,30 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AnyCommand] // Corvax: Allow use to everyone
|
||||
public sealed class AdminWhoCommand : IConsoleCommand
|
||||
public sealed class AdminWhoCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "adminwho";
|
||||
public string Description => "Returns a list of all admins on the server";
|
||||
public string Help => "Usage: adminwho";
|
||||
[Dependency] private readonly IAfkManager _afkManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "adminwho";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var adminMgr = IoCManager.Resolve<IAdminManager>();
|
||||
var afk = IoCManager.Resolve<IAfkManager>();
|
||||
|
||||
var seeStealth = true;
|
||||
|
||||
// If null it (hopefully) means it is being called from the console.
|
||||
if (shell.Player != null)
|
||||
{
|
||||
var playerData = adminMgr.GetAdminData(shell.Player);
|
||||
var playerData = _adminManager.GetAdminData(shell.Player);
|
||||
|
||||
seeStealth = playerData != null && playerData.CanStealth();
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var first = true;
|
||||
foreach (var admin in adminMgr.ActiveAdmins)
|
||||
foreach (var admin in _adminManager.ActiveAdmins)
|
||||
{
|
||||
var adminData = adminMgr.GetAdminData(admin)!;
|
||||
var adminData = _adminManager.GetAdminData(admin)!;
|
||||
DebugTools.AssertNotNull(adminData);
|
||||
|
||||
if (adminData.Stealth && !seeStealth)
|
||||
@@ -50,9 +48,9 @@ public sealed class AdminWhoCommand : IConsoleCommand
|
||||
if (adminData.Stealth)
|
||||
sb.Append(" (S)");
|
||||
|
||||
if (shell.Player is { } player && adminMgr.HasAdminFlag(player, AdminFlags.Admin))
|
||||
if (shell.Player is { } player && _adminManager.HasAdminFlag(player, AdminFlags.Admin))
|
||||
{
|
||||
if (afk.IsAfk(admin))
|
||||
if (_afkManager.IsAfk(admin))
|
||||
sb.Append(" [AFK]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,23 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class AnnounceUiCommand : IConsoleCommand
|
||||
public sealed class AnnounceUiCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "announceui";
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
|
||||
public string Description => "Opens the announcement UI";
|
||||
public override string Command => "announceui";
|
||||
|
||||
public string Help => $"{Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("This does not work from the server console.");
|
||||
shell.WriteLine(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new AdminAnnounceEui();
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,22 @@ namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.None)]
|
||||
public sealed class DeAdminCommand : IConsoleCommand
|
||||
public sealed class DeAdminCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "deadmin";
|
||||
public string Description => "Temporarily de-admins you so you can experience the round as a normal player.";
|
||||
public string Help => "Usage: deadmin\nUse readmin to re-admin after using this.";
|
||||
[Dependency] private readonly IAdminManager _admin = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "deadmin";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("You cannot use this command from the server console.");
|
||||
shell.WriteLine(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IAdminManager>();
|
||||
mgr.DeAdmin(player);
|
||||
_admin.DeAdmin(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,42 +4,40 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Spawn)]
|
||||
public sealed class DeleteComponent : IConsoleCommand
|
||||
public sealed class DeleteComponent : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "deletecomponent";
|
||||
public string Description => "Deletes all instances of the specified component.";
|
||||
public string Help => $"Usage: {Command} <name>";
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "deletecomponent";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 0:
|
||||
shell.WriteLine($"Not enough arguments.\n{Help}");
|
||||
shell.WriteLine(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
break;
|
||||
default:
|
||||
var name = string.Join(" ", args);
|
||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
if (!componentFactory.TryGetRegistration(name, out var registration))
|
||||
if (!_compFactory.TryGetRegistration(name, out var registration))
|
||||
{
|
||||
shell.WriteLine($"No component exists with name {name}.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-deletecomponent-no-component-exists", ("name", name)));
|
||||
break;
|
||||
}
|
||||
|
||||
var componentType = registration.Type;
|
||||
var components = entityManager.GetAllComponents(componentType, true);
|
||||
var components = EntityManager.GetAllComponents(componentType, true);
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var (uid, component) in components)
|
||||
{
|
||||
entityManager.RemoveComponent(uid, component);
|
||||
EntityManager.RemoveComponent(uid, component);
|
||||
i++;
|
||||
}
|
||||
|
||||
shell.WriteLine($"Removed {i} components with name {name}.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-deletecomponent-success", ("count", i), ("name", name)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -13,72 +13,73 @@ using Robust.Server.GameObjects;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class OpenExplosionEui : IConsoleCommand
|
||||
public sealed class OpenExplosionEui : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "explosionui";
|
||||
public string Description => "Opens a window for easy access to station destruction";
|
||||
public string Help => $"Usage: {Command}";
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "explosionui";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError("This does not work from the server console.");
|
||||
shell.WriteError(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new SpawnExplosionEui();
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)] // for the admin. Not so much for anyone else.
|
||||
public sealed class ExplosionCommand : IConsoleCommand
|
||||
public sealed class ExplosionCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "explosion";
|
||||
public string Description => "Train go boom";
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "explosion";
|
||||
|
||||
// Note that if you change the arguments, you should also update the client-side SpawnExplosionWindow, as that just
|
||||
// uses this command.
|
||||
public string Help => "Usage: explosion [intensity] [slope] [maxIntensity] [x y] [mapId] [prototypeId]";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0 || args.Length == 4 || args.Length > 7)
|
||||
{
|
||||
shell.WriteError("Wrong number of arguments.");
|
||||
shell.WriteError(Loc.GetString($"shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(args[0], out var intensity))
|
||||
{
|
||||
shell.WriteError($"Failed to parse intensity: {args[0]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-failed-to-parse-intensity", ("value", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
float slope = 5;
|
||||
if (args.Length > 1 && !float.TryParse(args[1], out slope))
|
||||
{
|
||||
shell.WriteError($"Failed to parse float: {args[1]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-failed-to-parse-float", ("value", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
float maxIntensity = 100;
|
||||
if (args.Length > 2 && !float.TryParse(args[2], out maxIntensity))
|
||||
{
|
||||
shell.WriteError($"Failed to parse float: {args[2]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-failed-to-parse-float", ("value", args[2])));
|
||||
return;
|
||||
}
|
||||
|
||||
float x = 0, y = 0;
|
||||
if (args.Length > 4)
|
||||
{
|
||||
if (!float.TryParse(args[3], out x) ||
|
||||
!float.TryParse(args[4], out y))
|
||||
if (!float.TryParse(args[3], out x) || !float.TryParse(args[4], out y))
|
||||
{
|
||||
shell.WriteError($"Failed to parse coordinates: {(args[3], args[4])}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-failed-to-parse-coords",
|
||||
("value1", args[3]),
|
||||
("value2", args[4])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +89,7 @@ public sealed class ExplosionCommand : IConsoleCommand
|
||||
{
|
||||
if (!int.TryParse(args[5], out var parsed))
|
||||
{
|
||||
shell.WriteError($"Failed to parse map ID: {args[5]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-failed-to-parse-map-id", ("value", args[5])));
|
||||
return;
|
||||
}
|
||||
coords = new MapCoordinates(new Vector2(x, y), new(parsed));
|
||||
@@ -96,42 +97,39 @@ public sealed class ExplosionCommand : IConsoleCommand
|
||||
else
|
||||
{
|
||||
// attempt to find the player's current position
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entMan.TryGetComponent(shell.Player?.AttachedEntity, out TransformComponent? xform))
|
||||
if (!EntityManager.TryGetComponent(shell.Player?.AttachedEntity, out TransformComponent? xform))
|
||||
{
|
||||
shell.WriteError($"Failed get default coordinates/map via player's transform. Need to specify explicitly.");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-need-coords-explicit"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 4)
|
||||
coords = new MapCoordinates(new Vector2(x, y), xform.MapID);
|
||||
else
|
||||
coords = entMan.System<TransformSystem>().GetMapCoordinates(shell.Player.AttachedEntity.Value, xform: xform);
|
||||
coords = _transform.GetMapCoordinates(shell.Player.AttachedEntity.Value, xform: xform);
|
||||
}
|
||||
|
||||
ExplosionPrototype? type;
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
if (args.Length > 6)
|
||||
{
|
||||
if (!protoMan.TryIndex(args[6], out type))
|
||||
if (!_prototypeManager.TryIndex(args[6], out type))
|
||||
{
|
||||
shell.WriteError($"Unknown explosion prototype: {args[6]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-unknown-prototype", ("value", args[6])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!protoMan.TryIndex(ExplosionSystem.DefaultExplosionPrototypeId, out type))
|
||||
else if (!_prototypeManager.TryIndex(ExplosionSystem.DefaultExplosionPrototypeId, out type))
|
||||
{
|
||||
// no prototype was specified, so lets default to whichever one was defined first
|
||||
type = protoMan.EnumeratePrototypes<ExplosionPrototype>().FirstOrDefault();
|
||||
type = _prototypeManager.EnumeratePrototypes<ExplosionPrototype>().FirstOrDefault();
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
shell.WriteError($"Prototype manager has no explosion prototypes?");
|
||||
shell.WriteError(Loc.GetString($"cmd-explosion-no-prototypes"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var sysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
sysMan.GetEntitySystem<ExplosionSystem>().QueueExplosion(coords, type.ID, intensity, slope, maxIntensity, null);
|
||||
_explosion.QueueExplosion(coords, type.ID, intensity, slope, maxIntensity, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class FaxUiCommand : IConsoleCommand
|
||||
public sealed class FaxUiCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "faxui";
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
|
||||
public string Description => Loc.GetString("cmd-faxui-desc");
|
||||
public string Help => Loc.GetString("cmd-faxui-help");
|
||||
public override string Command => "faxui";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
@@ -21,8 +20,7 @@ public sealed class FaxUiCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new AdminFaxEui();
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,42 +3,36 @@ using Content.Server.GameTicking;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round | AdminFlags.Spawn)]
|
||||
public sealed class LoadGameMapCommand : IConsoleCommand
|
||||
public sealed class LoadGameMapCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "loadgamemap";
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public string Description => "Loads the given game map at the given coordinates.";
|
||||
public override string Command => "loadgamemap";
|
||||
|
||||
public string Help => "loadgamemap <mapid> <gamemap> [<x> <y> [<name>]] ";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gameTicker = entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
|
||||
var mapSys = entityManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (args.Length is not (2 or 4 or 5))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
|
||||
if (!_prototypeManager.TryIndex<GameMapPrototype>(args[1], out var gameMap))
|
||||
{
|
||||
shell.WriteError($"The given map prototype {args[0]} is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapId))
|
||||
return;
|
||||
return;
|
||||
|
||||
var stationName = args.Length == 5 ? args[4] : null;
|
||||
|
||||
@@ -48,14 +42,14 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
var id = new MapId(mapId);
|
||||
|
||||
var grids = mapSys.MapExists(id)
|
||||
? gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset)
|
||||
: gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset);
|
||||
var grids = _mapSystem.MapExists(id)
|
||||
? _gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset)
|
||||
: _gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset);
|
||||
|
||||
shell.WriteLine($"Loaded {grids.Count} grids.");
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
@@ -79,26 +73,20 @@ namespace Content.Server.Administration.Commands
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Round | AdminFlags.Spawn)]
|
||||
public sealed class ListGameMaps : IConsoleCommand
|
||||
public sealed class ListGameMaps : LocalizedCommands
|
||||
{
|
||||
public string Command => "listgamemaps";
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Description => "Lists the game maps that can be used by loadgamemap";
|
||||
public override string Command => "listgamemaps";
|
||||
|
||||
public string Help => "listgamemaps";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gameTicker = entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
|
||||
|
||||
if (args.Length != 0)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
foreach (var prototype in prototypeManager.EnumeratePrototypes<GameMapPrototype>())
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<GameMapPrototype>())
|
||||
{
|
||||
shell.WriteLine($"{prototype.ID} - {prototype.MapName}");
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Logs)]
|
||||
public sealed class OpenAdminLogsCommand : IConsoleCommand
|
||||
public sealed class OpenAdminLogsCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "adminlogs";
|
||||
public string Description => "Opens the admin logs panel.";
|
||||
public string Help => $"Usage: {Command}";
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "adminlogs";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
@@ -20,8 +20,7 @@ public sealed class OpenAdminLogsCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new AdminLogsEui();
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,23 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Permissions)]
|
||||
public sealed class OpenPermissionsCommand : IConsoleCommand
|
||||
public sealed class OpenPermissionsCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "permissions";
|
||||
public string Description => "Opens the admin permissions panel.";
|
||||
public string Help => "Usage: permissions";
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "permissions";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("This does not work from the server console.");
|
||||
shell.WriteLine(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new PermissionsEui();
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
using System.Text;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class PardonCommand : IConsoleCommand
|
||||
public sealed class PardonCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "pardon";
|
||||
public string Description => "Pardons somebody's ban";
|
||||
public string Help => $"Usage: {Command} <ban id>";
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "pardon";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
var dbMan = IoCManager.Resolve<IServerDbManager>();
|
||||
|
||||
if (args.Length != 1)
|
||||
{
|
||||
@@ -25,11 +23,11 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
if (!int.TryParse(args[0], out var banId))
|
||||
{
|
||||
shell.WriteLine($"Unable to parse {args[0]} as a ban id integer.\n{Help}");
|
||||
shell.WriteLine(Loc.GetString($"cmd-pardon-unable-to-parse", ("id", args[0]), ("help", Help)));
|
||||
return;
|
||||
}
|
||||
|
||||
var ban = await dbMan.GetServerBanAsync(banId);
|
||||
var ban = await _dbManager.GetServerBanAsync(banId);
|
||||
|
||||
if (ban == null)
|
||||
{
|
||||
@@ -39,22 +37,22 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
if (ban.Unban != null)
|
||||
{
|
||||
var response = new StringBuilder("This ban has already been pardoned");
|
||||
|
||||
if (ban.Unban.UnbanningAdmin != null)
|
||||
{
|
||||
response.Append($" by {ban.Unban.UnbanningAdmin.Value}");
|
||||
shell.WriteLine(Loc.GetString($"cmd-pardon-already-pardoned-specific",
|
||||
("admin", ban.Unban.UnbanningAdmin.Value),
|
||||
("time", ban.Unban.UnbanTime)));
|
||||
}
|
||||
|
||||
response.Append($" in {ban.Unban.UnbanTime}.");
|
||||
else
|
||||
shell.WriteLine(Loc.GetString($"cmd-pardon-already-pardoned"));
|
||||
|
||||
shell.WriteLine(response.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
await dbMan.AddServerUnbanAsync(new ServerUnbanDef(banId, player?.UserId, DateTimeOffset.Now));
|
||||
await _dbManager.AddServerUnbanAsync(new ServerUnbanDef(banId, player?.UserId, DateTimeOffset.Now));
|
||||
|
||||
shell.WriteLine($"Pardoned ban with id {banId}");
|
||||
shell.WriteLine(Loc.GetString($"cmd-pardon-success", ("id", banId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,28 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class PromoteHostCommand : IConsoleCommand
|
||||
public sealed class PromoteHostCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "promotehost";
|
||||
public string Description => "Grants client temporary full host admin privileges. Use this to bootstrap admins.";
|
||||
public string Help => "Usage promotehost <player>";
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "promotehost";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Expected exactly one argument.");
|
||||
shell.WriteLine(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
var plyMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
if (!plyMgr.TryGetSessionByUsername(args[0], out var targetPlayer))
|
||||
if (!_playerManager.TryGetSessionByUsername(args[0], out var targetPlayer))
|
||||
{
|
||||
shell.WriteLine("Unable to find a player by that name.");
|
||||
shell.WriteLine(Loc.GetString($"shell-target-player-does-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
var adminMgr = IoCManager.Resolve<IAdminManager>();
|
||||
adminMgr.PromoteHost(targetPlayer);
|
||||
_adminManager.PromoteHost(targetPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,28 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
public sealed class ReAdminCommand : IConsoleCommand
|
||||
public sealed class ReAdminCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "readmin";
|
||||
public string Description => "Re-admins you if you previously de-adminned.";
|
||||
public string Help => "Usage: readmin";
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "readmin";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("You cannot use this command from the server console.");
|
||||
shell.WriteLine(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IAdminManager>();
|
||||
|
||||
if (mgr.GetAdminData(player, includeDeAdmin: true) == null)
|
||||
if (_adminManager.GetAdminData(player, includeDeAdmin: true) == null)
|
||||
{
|
||||
shell.WriteLine("You're not an admin.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-readmin-not-an-admin"));
|
||||
return;
|
||||
}
|
||||
|
||||
mgr.ReAdmin(player);
|
||||
_adminManager.ReAdmin(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,46 +5,43 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Mapping)]
|
||||
public sealed class RemoveExtraComponents : IConsoleCommand
|
||||
public sealed class RemoveExtraComponents : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "removeextracomponents";
|
||||
public string Description => "Removes all components from all entities of the specified id if that component is not in its prototype.\nIf no id is specified, it matches all entities.";
|
||||
public string Help => $"{Command} <entityId> / {Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override string Command => "removeextracomponents";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var id = args.Length == 0 ? null : string.Join(" ", args);
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
var fac = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
EntityPrototype? prototype = null;
|
||||
var checkPrototype = !string.IsNullOrEmpty(id);
|
||||
|
||||
if (checkPrototype && !prototypeManager.TryIndex(id!, out prototype))
|
||||
if (checkPrototype && !_prototypeManager.TryIndex(id!, out prototype))
|
||||
{
|
||||
shell.WriteError($"Can't find entity prototype with id \"{id}\"!");
|
||||
shell.WriteError(Loc.GetString($"cmd-removeextracomponents-invalid-prototype-id", ("id", $"{id}")));
|
||||
return;
|
||||
}
|
||||
|
||||
var entities = 0;
|
||||
var components = 0;
|
||||
|
||||
foreach (var entity in entityManager.GetEntities())
|
||||
foreach (var entity in EntityManager.GetEntities())
|
||||
{
|
||||
var metaData = entityManager.GetComponent<MetaDataComponent>(entity);
|
||||
var metaData = EntityManager.GetComponent<MetaDataComponent>(entity);
|
||||
if (checkPrototype && metaData.EntityPrototype != prototype || metaData.EntityPrototype == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var modified = false;
|
||||
|
||||
foreach (var component in entityManager.GetComponents(entity))
|
||||
foreach (var component in EntityManager.GetComponents(entity))
|
||||
{
|
||||
if (metaData.EntityPrototype.Components.ContainsKey(fac.GetComponentName(component.GetType())))
|
||||
if (metaData.EntityPrototype.Components.ContainsKey(_compFactory.GetComponentName(component.GetType())))
|
||||
continue;
|
||||
|
||||
entityManager.RemoveComponent(entity, component);
|
||||
EntityManager.RemoveComponent(entity, component);
|
||||
components++;
|
||||
|
||||
modified = true;
|
||||
@@ -54,7 +51,18 @@ namespace Content.Server.Administration.Commands
|
||||
entities++;
|
||||
}
|
||||
|
||||
shell.WriteLine($"Removed {components} components from {entities} entities{(id == null ? "." : $" with id {id}")}");
|
||||
if (id != null)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"cmd-removeextracomponents-success-with-id",
|
||||
("count", components),
|
||||
("entities", entities),
|
||||
("id", id)));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString($"cmd-removeextracomponents-success",
|
||||
("count", components),
|
||||
("entities", entities)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class RoleUnbanCommand : IConsoleCommand
|
||||
public sealed class RoleUnbanCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "roleunban";
|
||||
public string Description => Loc.GetString("cmd-roleunban-desc");
|
||||
public string Help => Loc.GetString("cmd-roleunban-help");
|
||||
[Dependency] private readonly IBanManager _banManager = default!;
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "roleunban";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
@@ -21,16 +21,15 @@ public sealed class RoleUnbanCommand : IConsoleCommand
|
||||
|
||||
if (!int.TryParse(args[0], out var banId))
|
||||
{
|
||||
shell.WriteLine($"Unable to parse {args[0]} as a ban id integer.\n{Help}");
|
||||
shell.WriteLine(Loc.GetString($"cmd-roleunban-unable-to-parse-id", ("id", args[0]), ("help", Help)));
|
||||
return;
|
||||
}
|
||||
|
||||
var banManager = IoCManager.Resolve<IBanManager>();
|
||||
var response = await banManager.PardonRoleBan(banId, shell.Player?.UserId, DateTimeOffset.Now);
|
||||
var response = await _banManager.PardonRoleBan(banId, shell.Player?.UserId, DateTimeOffset.Now);
|
||||
shell.WriteLine(response);
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
// Can't think of good way to do hint options for this
|
||||
return args.Length switch
|
||||
|
||||
@@ -6,13 +6,14 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.NameColor)]
|
||||
internal sealed class SetAdminOOC : IConsoleCommand
|
||||
internal sealed class SetAdminOOC : LocalizedCommands
|
||||
{
|
||||
public string Command => "setadminooc";
|
||||
public string Description => Loc.GetString("set-admin-ooc-command-description", ("command", Command));
|
||||
public string Help => Loc.GetString("set-admin-ooc-command-help-text", ("command", Command));
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
[Dependency] private readonly IServerPreferencesManager _preferenceManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "setadminooc";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player == null)
|
||||
{
|
||||
@@ -36,11 +37,9 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
var userId = shell.Player.UserId;
|
||||
// Save the DB
|
||||
var dbMan = IoCManager.Resolve<IServerDbManager>();
|
||||
dbMan.SaveAdminOOCColorAsync(userId, color.Value);
|
||||
_dbManager.SaveAdminOOCColorAsync(userId, color.Value);
|
||||
// Update the cached preference
|
||||
var prefManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = prefManager.GetPreferences(userId);
|
||||
var prefs = _preferenceManager.GetPreferences(userId);
|
||||
prefs.AdminOOCColor = color.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Players;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
@@ -9,17 +8,16 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
sealed class SetMindCommand : IConsoleCommand
|
||||
public sealed class SetMindCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
|
||||
public string Command => "setmind";
|
||||
public override string Command => "setmind";
|
||||
|
||||
public string Description => Loc.GetString("set-mind-command-description", ("requiredComponent", nameof(MindContainerComponent)));
|
||||
public override string Description => Loc.GetString("cmd-setmind-desc", ("requiredComponent", nameof(MindContainerComponent)));
|
||||
|
||||
public string Help => Loc.GetString("set-mind-command-help-text", ("command", Command));
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
@@ -33,7 +31,7 @@ namespace Content.Server.Administration.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
bool ghostOverride = true;
|
||||
var ghostOverride = true;
|
||||
if (args.Length > 2)
|
||||
{
|
||||
ghostOverride = bool.Parse(args[2]);
|
||||
@@ -41,19 +39,19 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
var nent = new NetEntity(entInt);
|
||||
|
||||
if (!_entManager.TryGetEntity(nent, out var eUid))
|
||||
if (!EntityManager.TryGetEntity(nent, out var eUid))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-invalid-entity-id"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entManager.HasComponent<MindContainerComponent>(eUid))
|
||||
if (!EntityManager.HasComponent<MindContainerComponent>(eUid))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("set-mind-command-target-has-no-mind-message"));
|
||||
shell.WriteLine(Loc.GetString("cmd-setmind-target-has-no-mind-message"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(args[1], out var session))
|
||||
if (!_playerManager.TryGetSessionByUsername(args[1], out var session))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-target-player-does-not-exist"));
|
||||
return;
|
||||
@@ -63,24 +61,21 @@ namespace Content.Server.Administration.Commands
|
||||
var playerCData = session.ContentData();
|
||||
if (playerCData == null)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("set-mind-command-target-has-no-content-data-message"));
|
||||
shell.WriteLine(Loc.GetString("cmd-setmind-target-has-no-content-data-message"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mindSystem = _entManager.System<SharedMindSystem>();
|
||||
var metadata = _entManager.GetComponent<MetaDataComponent>(eUid.Value);
|
||||
var metadata = EntityManager.GetComponent<MetaDataComponent>(eUid.Value);
|
||||
|
||||
var mind = playerCData.Mind ?? mindSystem.CreateMind(session.UserId, metadata.EntityName);
|
||||
var mind = playerCData.Mind ?? _mindSystem.CreateMind(session.UserId, metadata.EntityName);
|
||||
|
||||
mindSystem.TransferTo(mind, eUid, ghostOverride);
|
||||
_mindSystem.TransferTo(mind, eUid, ghostOverride);
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 2)
|
||||
{
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Loc.GetString("cmd-mind-command-hint"));
|
||||
}
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.SessionNames(), Help);
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,22 @@
|
||||
using Content.Server.Administration.UI;
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class SetOutfitCommand : IConsoleCommand
|
||||
public sealed class SetOutfitCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly OutfitSystem _outfitSystem = default!;
|
||||
|
||||
public string Command => "setoutfit";
|
||||
public override string Command => "setoutfit";
|
||||
public override string Description => Loc.GetString("cmd-setoutfit-desc", ("requiredComponent", nameof(InventoryComponent)));
|
||||
|
||||
public string Description => Loc.GetString("set-outfit-command-description", ("requiredComponent", nameof(InventoryComponent)));
|
||||
|
||||
public string Help => Loc.GetString("set-outfit-command-help-text", ("command", Command));
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
@@ -46,13 +32,13 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
var nent = new NetEntity(entInt);
|
||||
|
||||
if (!_entities.TryGetEntity(nent, out var target))
|
||||
if (!EntityManager.TryGetEntity(nent, out var target))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-invalid-entity-id"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_entities.HasComponent<InventoryComponent>(target))
|
||||
if (!EntityManager.HasComponent<InventoryComponent>(target))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-target-entity-does-not-have-message", ("missing", "inventory")));
|
||||
return;
|
||||
@@ -62,109 +48,17 @@ namespace Content.Server.Administration.Commands
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("set-outfit-command-is-not-player-error"));
|
||||
shell.WriteError(Loc.GetString("cmd-setoutfit-is-not-player-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
var eui = IoCManager.Resolve<EuiManager>();
|
||||
var ui = new SetOutfitEui(nent);
|
||||
eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetOutfit(target.Value, args[1], _entities))
|
||||
shell.WriteLine(Loc.GetString("set-outfit-command-invalid-outfit-id-error"));
|
||||
}
|
||||
|
||||
public static bool SetOutfit(EntityUid target, string gear, IEntityManager entityManager, Action<EntityUid, EntityUid>? onEquipped = null)
|
||||
{
|
||||
if (!entityManager.TryGetComponent(target, out InventoryComponent? inventoryComponent))
|
||||
return false;
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
if (!prototypeManager.TryIndex<StartingGearPrototype>(gear, out var startingGear))
|
||||
return false;
|
||||
|
||||
HumanoidCharacterProfile? profile = null;
|
||||
ICommonSession? session = null;
|
||||
// Check if we are setting the outfit of a player to respect the preferences
|
||||
if (entityManager.TryGetComponent(target, out ActorComponent? actorComponent))
|
||||
{
|
||||
session = actorComponent.PlayerSession;
|
||||
var userId = actorComponent.PlayerSession.UserId;
|
||||
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = preferencesManager.GetPreferences(userId);
|
||||
profile = prefs.SelectedCharacter as HumanoidCharacterProfile;
|
||||
}
|
||||
|
||||
var invSystem = entityManager.System<InventorySystem>();
|
||||
if (invSystem.TryGetSlots(target, out var slots))
|
||||
{
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent);
|
||||
var gearStr = ((IEquipmentLoadout) startingGear).GetGear(slot.Name);
|
||||
if (gearStr == string.Empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var equipmentEntity = entityManager.SpawnEntity(gearStr, entityManager.GetComponent<TransformComponent>(target).Coordinates);
|
||||
if (slot.Name == "id" &&
|
||||
entityManager.TryGetComponent(equipmentEntity, out PdaComponent? pdaComponent) &&
|
||||
entityManager.TryGetComponent<IdCardComponent>(pdaComponent.ContainedId, out var id))
|
||||
{
|
||||
id.FullName = entityManager.GetComponent<MetaDataComponent>(target).EntityName;
|
||||
}
|
||||
|
||||
invSystem.TryEquip(target, equipmentEntity, slot.Name, silent: true, force: true, inventory: inventoryComponent);
|
||||
|
||||
onEquipped?.Invoke(target, equipmentEntity);
|
||||
}
|
||||
}
|
||||
|
||||
if (entityManager.TryGetComponent(target, out HandsComponent? handsComponent))
|
||||
{
|
||||
var handsSystem = entityManager.System<HandsSystem>();
|
||||
var coords = entityManager.GetComponent<TransformComponent>(target).Coordinates;
|
||||
foreach (var prototype in startingGear.Inhand)
|
||||
{
|
||||
var inhandEntity = entityManager.SpawnEntity(prototype, coords);
|
||||
handsSystem.TryPickup(target, inhandEntity, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// See if this starting gear is associated with a job
|
||||
var jobs = prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (job.StartingGear != gear)
|
||||
continue;
|
||||
|
||||
var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID);
|
||||
if (!prototypeManager.TryIndex<RoleLoadoutPrototype>(jobProtoId, out var jobProto))
|
||||
break;
|
||||
|
||||
// Don't require a player, so this works on Urists
|
||||
profile ??= entityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var comp)
|
||||
? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species)
|
||||
: new HumanoidCharacterProfile();
|
||||
// Try to get the user's existing loadout for the role
|
||||
profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout);
|
||||
|
||||
if (roleLoadout == null)
|
||||
{
|
||||
// If they don't have a loadout for the role, make a default one
|
||||
roleLoadout = new RoleLoadout(jobProtoId);
|
||||
roleLoadout.SetDefault(profile, session, prototypeManager);
|
||||
}
|
||||
|
||||
// Equip the target with the job loadout
|
||||
var stationSpawning = entityManager.System<SharedStationSpawningSystem>();
|
||||
stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (!_outfitSystem.SetOutfit(target.Value, args[1]))
|
||||
shell.WriteLine(Loc.GetString("cmd-setoutfit-invalid-outfit-id-error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,46 +6,36 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
public sealed class CallShuttleCommand : IConsoleCommand
|
||||
public sealed class CallShuttleCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
|
||||
public string Command => "callshuttle";
|
||||
public string Description => Loc.GetString("call-shuttle-command-description");
|
||||
public string Help => Loc.GetString("call-shuttle-command-help-text", ("command",Command));
|
||||
public override string Command => "callshuttle";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToSwitchStatement
|
||||
if (args.Length == 1 && TimeSpan.TryParseExact(args[0], ContentLocalizationManager.TimeSpanMinutesFormats, loc.DefaultCulture, out var timeSpan))
|
||||
{
|
||||
_e.System<RoundEndSystem>().RequestRoundEnd(timeSpan, shell.Player?.AttachedEntity, false);
|
||||
}
|
||||
if (args.Length == 1 && TimeSpan.TryParseExact(args[0], ContentLocalizationManager.TimeSpanMinutesFormats, LocalizationManager.DefaultCulture, out var timeSpan))
|
||||
_roundEndSystem.RequestRoundEnd(timeSpan, shell.Player?.AttachedEntity, false);
|
||||
|
||||
else if (args.Length == 1)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-timespan-minutes-must-be-correct"));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_e.System<RoundEndSystem>().RequestRoundEnd(shell.Player?.AttachedEntity, false);
|
||||
}
|
||||
_roundEndSystem.RequestRoundEnd(shell.Player?.AttachedEntity, false);
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
public sealed class RecallShuttleCommand : IConsoleCommand
|
||||
public sealed class RecallShuttleCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
|
||||
|
||||
public string Command => "recallshuttle";
|
||||
public string Description => Loc.GetString("recall-shuttle-command-description");
|
||||
public string Help => Loc.GetString("recall-shuttle-command-help-text", ("command",Command));
|
||||
public override string Command => "recallshuttle";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_e.System<RoundEndSystem>().CancelRoundEndCountdown(shell.Player?.AttachedEntity, false);
|
||||
_roundEndSystem.CancelRoundEndCountdown(shell.Player?.AttachedEntity, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Zombies;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind.Components;
|
||||
@@ -20,6 +20,7 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ZombieSystem _zombie = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly OutfitSystem _outfit = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string DefaultTraitorRule = "Traitor";
|
||||
@@ -132,7 +133,7 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
// pirates just get an outfit because they don't really have logic associated with them
|
||||
SetOutfitCommand.SetOutfit(args.Target, PirateGearId, EntityManager);
|
||||
_outfit.SetOutfit(args.Target, PirateGearId);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = string.Join(": ", pirateName, Loc.GetString("admin-verb-make-pirate")),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Threading;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Administration.Components;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Disease; // Corvax-Wega-Disease
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.GhostKick;
|
||||
@@ -45,7 +45,6 @@ using Content.Shared.Slippery;
|
||||
using Content.Shared.Tabletop.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -83,6 +82,7 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly SuperBonkSystem _superBonkSystem = default!;
|
||||
[Dependency] private readonly SlipperySystem _slipperySystem = default!;
|
||||
[Dependency] private readonly OutfitSystem _outfitSystem = default!;
|
||||
[Dependency] private readonly DiseaseSystem _diseaseSystem = default!; // Corvax-Wega-Disease
|
||||
|
||||
// All smite verbs have names so invokeverb works.
|
||||
@@ -105,7 +105,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = explodeName,
|
||||
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 = () =>
|
||||
{
|
||||
var coords = _transformSystem.GetMapCoordinates(args.Target);
|
||||
@@ -126,7 +126,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = chessName,
|
||||
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 = () =>
|
||||
{
|
||||
_sharedGodmodeSystem.EnableGodmode(args.Target); // So they don't suffocate.
|
||||
@@ -155,7 +155,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = flamesName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Alerts/Fire/fire.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Alerts/Fire/fire.png")),
|
||||
Act = () =>
|
||||
{
|
||||
// Fuck you. Burn Forever.
|
||||
@@ -178,7 +178,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = monkeyName,
|
||||
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 = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
|
||||
@@ -195,7 +195,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = "Lung Cancer",
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "lung-l"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Species/Human/organs.rsi"), "lung-l"),
|
||||
Act = () =>
|
||||
{
|
||||
_diseaseSystem.TryInfect(carrier, _prototypeManager.Index<DiseasePrototype>("StageIIIALungCancer"),
|
||||
@@ -213,7 +213,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = disposalBinName,
|
||||
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 = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite");
|
||||
@@ -231,20 +231,21 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = hardElectrocuteName,
|
||||
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 = () =>
|
||||
{
|
||||
int damageToDeal;
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Critical, out var criticalThreshold)) {
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Critical, out var criticalThreshold))
|
||||
{
|
||||
// We can't crit them so try killing them.
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead,
|
||||
out var deadThreshold))
|
||||
return;// whelp.
|
||||
damageToDeal = deadThreshold.Value.Int() - (int) damageable.TotalDamage;
|
||||
damageToDeal = deadThreshold.Value.Int() - (int)damageable.TotalDamage;
|
||||
}
|
||||
else
|
||||
{
|
||||
damageToDeal = criticalThreshold.Value.Int() - (int) damageable.TotalDamage;
|
||||
damageToDeal = criticalThreshold.Value.Int() - (int)damageable.TotalDamage;
|
||||
}
|
||||
|
||||
if (damageToDeal <= 0)
|
||||
@@ -277,7 +278,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = creamPieName,
|
||||
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 = () =>
|
||||
{
|
||||
_creamPieSystem.SetCreamPied(args.Target, creamPied, true);
|
||||
@@ -295,7 +296,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = bloodRemovalName,
|
||||
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 = () =>
|
||||
{
|
||||
_bloodstreamSystem.SpillAllSolutions(args.Target, bloodstream);
|
||||
@@ -348,7 +349,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = handsRemovalName,
|
||||
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 = () =>
|
||||
{
|
||||
var baseXform = Transform(args.Target);
|
||||
@@ -371,7 +372,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = handRemovalName,
|
||||
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 = () =>
|
||||
{
|
||||
var baseXform = Transform(args.Target);
|
||||
@@ -395,7 +396,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = stomachRemovalName,
|
||||
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 = () =>
|
||||
{
|
||||
foreach (var entity in _bodySystem.GetBodyOrganEntityComps<StomachComponent>((args.Target, body)))
|
||||
@@ -416,7 +417,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = lungRemovalName,
|
||||
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 = () =>
|
||||
{
|
||||
foreach (var entity in _bodySystem.GetBodyOrganEntityComps<LungComponent>((args.Target, body)))
|
||||
@@ -440,7 +441,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = pinballName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/Balls/basketball.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
@@ -475,7 +476,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = yeetName,
|
||||
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 = () =>
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
@@ -507,7 +508,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = breadName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Consumable/Food/Baked/bread.rsi"), "plain"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Consumable/Food/Baked/bread.rsi"), "plain"),
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
|
||||
@@ -522,7 +523,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = mouseName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Animals/mouse.rsi"), "icon-0"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Mobs/Animals/mouse.rsi"), "icon-0"),
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminMouseSmite");
|
||||
@@ -539,7 +540,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = ghostKickName,
|
||||
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 = () =>
|
||||
{
|
||||
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.Channel, "Smitten.");
|
||||
@@ -558,7 +559,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = nyanifyName,
|
||||
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 = () =>
|
||||
{
|
||||
var ears = Spawn("ClothingHeadHatCatEars", Transform(args.Target).Coordinates);
|
||||
@@ -576,7 +577,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = killSignName,
|
||||
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 = () =>
|
||||
{
|
||||
EnsureComp<KillSignComponent>(args.Target);
|
||||
@@ -592,7 +593,7 @@ public sealed partial class AdminVerbSystem
|
||||
Text = cluwneName,
|
||||
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"),
|
||||
|
||||
Act = () =>
|
||||
{
|
||||
@@ -608,10 +609,10 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = maidenName,
|
||||
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 = () =>
|
||||
{
|
||||
SetOutfitCommand.SetOutfit(args.Target, "JanitorMaidGear", EntityManager, (_, clothing) =>
|
||||
_outfitSystem.SetOutfit(args.Target, "JanitorMaidGear", (_, clothing) =>
|
||||
{
|
||||
if (HasComp<ClothingComponent>(clothing))
|
||||
EnsureComp<UnremoveableComponent>(clothing);
|
||||
@@ -629,7 +630,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = angerPointingArrowsName,
|
||||
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 = () =>
|
||||
{
|
||||
EnsureComp<PointingArrowAngeringComponent>(args.Target);
|
||||
@@ -644,7 +645,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = dustName,
|
||||
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 = () =>
|
||||
{
|
||||
EntityManager.QueueDeleteEntity(args.Target);
|
||||
@@ -661,7 +662,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = youtubeVideoSimulationName,
|
||||
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 = () =>
|
||||
{
|
||||
EnsureComp<BufferingComponent>(args.Target);
|
||||
@@ -676,7 +677,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = instrumentationName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Instruments/h_synthesizer.rsi"), "supersynth"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/Instruments/h_synthesizer.rsi"), "supersynth"),
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
|
||||
@@ -709,7 +710,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = reptilianName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"),
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/Plushies/lizard.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
_polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
|
||||
@@ -724,7 +725,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = lockerName,
|
||||
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 = () =>
|
||||
{
|
||||
var xform = Transform(args.Target);
|
||||
@@ -747,7 +748,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = headstandName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<HeadstandComponent>(args.Target);
|
||||
@@ -762,7 +763,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = zoomInName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/zoom.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/zoom.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var eye = EnsureComp<ContentEyeComponent>(args.Target);
|
||||
@@ -778,7 +779,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = flipEyeName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/flip.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/flip.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var eye = EnsureComp<ContentEyeComponent>(args.Target);
|
||||
@@ -794,7 +795,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = runWalkSwapName,
|
||||
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 = () =>
|
||||
{
|
||||
var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(args.Target);
|
||||
@@ -815,7 +816,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = backwardsAccentName,
|
||||
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 = () =>
|
||||
{
|
||||
EnsureComp<BackwardsAccentComponent>(args.Target);
|
||||
@@ -830,7 +831,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = disarmProneName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/disarm.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/Actions/disarm.png")),
|
||||
Act = () =>
|
||||
{
|
||||
EnsureComp<DisarmProneComponent>(args.Target);
|
||||
@@ -845,7 +846,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = superSpeedName,
|
||||
Category = VerbCategory.Smite,
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/super_speed.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/super_speed.png")),
|
||||
Act = () =>
|
||||
{
|
||||
var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(args.Target);
|
||||
|
||||
@@ -6,34 +6,31 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Afk
|
||||
{
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class IsAfkCommand : IConsoleCommand
|
||||
public sealed class IsAfkCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IAfkManager _afkManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
|
||||
public string Command => "isafk";
|
||||
public string Description => "Checks if a specified player is AFK";
|
||||
public string Help => "Usage: isafk <playerName>";
|
||||
public override string Command => "isafk";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var afkManager = IoCManager.Resolve<IAfkManager>();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteError("Need one argument");
|
||||
shell.WriteError(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_players.TryGetSessionByUsername(args[0], out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find that player");
|
||||
shell.WriteError(Loc.GetString($"shell-target-player-does-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(afkManager.IsAfk(player) ? "They are indeed AFK" : "They are not AFK");
|
||||
shell.WriteLine(Loc.GetString(_afkManager.IsAfk(player) ? "cmd-isafk-true" : "cmd-isafk-false"));
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed partial class AtmosPipeLayersSystem : SharedAtmosPipeLayersSystem
|
||||
if (ent.Comp.PipeLayersLocked)
|
||||
return;
|
||||
|
||||
base.SetPipeLayer(ent, layer);
|
||||
base.SetPipeLayer(ent, layer, user, used);
|
||||
|
||||
if (!TryComp<NodeContainerComponent>(ent, out var nodeContainer))
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -18,13 +19,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly SharedMagbootsSystem _magboots = default!; // Corvax-Wega-AdvMagboots
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!; // Corvax-Wega-AdvMagboots
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultSpaceWindSounds = "SpaceWind";
|
||||
|
||||
private const int SpaceWindSoundCooldownCycles = 75;
|
||||
|
||||
private int _spaceWindSoundCooldown = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? SpaceWindSound { get; private set; } = "/Audio/Effects/space_wind.ogg";
|
||||
public SoundSpecifier? SpaceWindSound { get; private set; } = new SoundCollectionSpecifier(DefaultSpaceWindSounds, AudioParams.Default.WithVariation(0.125f));
|
||||
|
||||
private readonly HashSet<Entity<MovedByPressureComponent>> _activePressures = new(8);
|
||||
|
||||
@@ -110,10 +112,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Don't play the space wind sound on tiles that are on fire...
|
||||
if (tile.PressureDifference > 15 && !tile.Hotspot.Valid)
|
||||
{
|
||||
if (_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound))
|
||||
if (_spaceWindSoundCooldown == 0 && SpaceWindSound != null)
|
||||
{
|
||||
var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices);
|
||||
_audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
|
||||
_audio.PlayPvs(SpaceWindSound, coordinates, SpaceWindSound.Params.WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,15 @@ using Content.Shared.Database;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
private static readonly ProtoId<SoundCollectionPrototype> DefaultHotspotSounds = "AtmosHotspot";
|
||||
|
||||
[Dependency] private readonly DecalSystem _decalSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
@@ -21,7 +24,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private int _hotspotSoundCooldown = 0;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? HotspotSound { get; private set; } = "/Audio/Effects/fire.ogg";
|
||||
public SoundSpecifier? HotspotSound { get; private set; } = new SoundCollectionSpecifier(DefaultHotspotSounds);
|
||||
|
||||
private void ProcessHotspot(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
@@ -105,14 +108,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (tile.Hotspot.Temperature > tile.MaxFireTemperatureSustained)
|
||||
tile.MaxFireTemperatureSustained = tile.Hotspot.Temperature;
|
||||
|
||||
if (_hotspotSoundCooldown++ == 0 && !string.IsNullOrEmpty(HotspotSound))
|
||||
if (_hotspotSoundCooldown++ == 0 && HotspotSound != null)
|
||||
{
|
||||
var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices);
|
||||
|
||||
// A few details on the audio parameters for fire.
|
||||
// The greater the fire state, the lesser the pitch variation.
|
||||
// The greater the fire state, the greater the volume.
|
||||
_audio.PlayPvs(HotspotSound, coordinates, AudioParams.Default.WithVariation(0.15f/tile.Hotspot.State).WithVolume(-5f + 5f * tile.Hotspot.State));
|
||||
_audio.PlayPvs(HotspotSound, coordinates, HotspotSound.Params.WithVariation(0.15f / tile.Hotspot.State).WithVolume(-5f + 5f * tile.Hotspot.State));
|
||||
}
|
||||
|
||||
if (_hotspotSoundCooldown > HotspotSoundCooldownCycles)
|
||||
|
||||
@@ -33,9 +33,12 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GasAnalyzerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<GasAnalyzerComponent, GasAnalyzerDisableMessage>(OnDisabledMessage);
|
||||
SubscribeLocalEvent<GasAnalyzerComponent, DroppedEvent>(OnDropped);
|
||||
SubscribeLocalEvent<GasAnalyzerComponent, UseInHandEvent>(OnUseInHand);
|
||||
|
||||
Subs.BuiEvents<GasAnalyzerComponent>(GasAnalyzerUiKey.Key, subs =>
|
||||
{
|
||||
subs.Event<BoundUIOpenedEvent>(OnBoundUIOpened);
|
||||
subs.Event<BoundUIClosedEvent>(OnBoundUIClosed);
|
||||
});
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -72,21 +75,6 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the analyzer with no target, so it only scans the tile the user was on when activated
|
||||
/// </summary>
|
||||
private void OnUseInHand(Entity<GasAnalyzerComponent> entity, ref UseInHandEvent args)
|
||||
{
|
||||
// Not checking for Handled because ActivatableUISystem already marks it as such.
|
||||
|
||||
if (!entity.Comp.Enabled)
|
||||
ActivateAnalyzer(entity, args.User);
|
||||
else
|
||||
DisableAnalyzer(entity, args.User);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles analyzer activation logic
|
||||
/// </summary>
|
||||
@@ -104,16 +92,6 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
UpdateAnalyzer(entity.Owner, entity.Comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the UI, turn the analyzer off, and don't update when it's dropped
|
||||
/// </summary>
|
||||
private void OnDropped(Entity<GasAnalyzerComponent> entity, ref DroppedEvent args)
|
||||
{
|
||||
if (args.User is var userId && entity.Comp.Enabled)
|
||||
_popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
|
||||
DisableAnalyzer(entity, args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the UI, sets the icon to off, and removes it from the update list
|
||||
/// </summary>
|
||||
@@ -121,6 +99,9 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
{
|
||||
_userInterface.CloseUi(entity.Owner, GasAnalyzerUiKey.Key, user);
|
||||
|
||||
if (user.HasValue && entity.Comp.Enabled)
|
||||
_popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), user.Value, user.Value);
|
||||
|
||||
entity.Comp.Enabled = false;
|
||||
Dirty(entity);
|
||||
_appearance.SetData(entity.Owner, GasAnalyzerVisuals.Enabled, entity.Comp.Enabled);
|
||||
@@ -130,9 +111,25 @@ public sealed class GasAnalyzerSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Disables the analyzer when the user closes the UI
|
||||
/// </summary>
|
||||
private void OnDisabledMessage(Entity<GasAnalyzerComponent> entity, ref GasAnalyzerDisableMessage message)
|
||||
private void OnBoundUIClosed(Entity<GasAnalyzerComponent> entity, ref BoundUIClosedEvent args)
|
||||
{
|
||||
DisableAnalyzer(entity);
|
||||
if (HasComp<ActiveGasAnalyzerComponent>(entity.Owner)
|
||||
&& !_userInterface.IsUiOpen(entity.Owner, args.UiKey))
|
||||
{
|
||||
DisableAnalyzer(entity, args.Actor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the analyzer when the user opens the UI
|
||||
/// </summary>
|
||||
private void OnBoundUIOpened(Entity<GasAnalyzerComponent> entity, ref BoundUIOpenedEvent args)
|
||||
{
|
||||
if (!HasComp<ActiveGasAnalyzerComponent>(entity.Owner)
|
||||
&& _userInterface.IsUiOpen(entity.Owner, args.UiKey))
|
||||
{
|
||||
ActivateAnalyzer(entity, args.Actor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,23 +3,23 @@ using Content.Server.Body.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Body.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
sealed class DestroyMechanismCommand : IConsoleCommand
|
||||
internal sealed class DestroyMechanismCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "destroymechanism";
|
||||
public string Description => "Destroys a mechanism from your entity";
|
||||
public string Help => $"Usage: {Command} <mechanism>";
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "destroymechanism";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine("Only a player can run this command.");
|
||||
shell.WriteLine(Loc.GetString($"shell-only-players-can-run-this-command"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,36 +31,29 @@ namespace Content.Server.Body.Commands
|
||||
|
||||
if (player.AttachedEntity is not {} attached)
|
||||
{
|
||||
shell.WriteLine("You have no entity.");
|
||||
shell.WriteLine(Loc.GetString($"shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var fac = IoCManager.Resolve<IComponentFactory>();
|
||||
|
||||
if (!entityManager.TryGetComponent(attached, out BodyComponent? body))
|
||||
if (!EntityManager.TryGetComponent(attached, out BodyComponent? body))
|
||||
{
|
||||
var random = IoCManager.Resolve<IRobustRandom>();
|
||||
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||
|
||||
shell.WriteLine(text);
|
||||
shell.WriteLine(Loc.GetString($"shell-must-have-body"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mechanismName = string.Join(" ", args).ToLowerInvariant();
|
||||
var bodySystem = entityManager.System<BodySystem>();
|
||||
|
||||
foreach (var organ in bodySystem.GetBodyOrgans(attached, body))
|
||||
foreach (var organ in _bodySystem.GetBodyOrgans(attached, body))
|
||||
{
|
||||
if (fac.GetComponentName(organ.Component.GetType()).ToLowerInvariant() == mechanismName)
|
||||
if (_compFactory.GetComponentName(organ.Component.GetType()).ToLowerInvariant() == mechanismName)
|
||||
{
|
||||
entityManager.QueueDeleteEntity(organ.Id);
|
||||
shell.WriteLine($"Mechanism with name {mechanismName} has been destroyed.");
|
||||
EntityManager.QueueDeleteEntity(organ.Id);
|
||||
shell.WriteLine(Loc.GetString($"cmd-destroymechanism-success", ("name", mechanismName)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell.WriteLine($"No mechanism was found with name {mechanismName}.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-destroymechanism-no-mechanism-found", ("name", mechanismName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,10 +236,13 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
}
|
||||
|
||||
// TODO probably cache this or something. humans get hurt a lot
|
||||
if (!_prototypeManager.TryIndex<DamageModifierSetPrototype>(ent.Comp.DamageBleedModifiers, out var modifiers))
|
||||
if (!_prototypeManager.TryIndex(ent.Comp.DamageBleedModifiers, out var modifiers))
|
||||
return;
|
||||
|
||||
var bloodloss = DamageSpecifier.ApplyModifierSet(args.DamageDelta, modifiers);
|
||||
// some reagents may deal and heal different damage types in the same tick, which means DamageIncreased will be true
|
||||
// but we only want to consider the dealt damage when causing bleeding
|
||||
var damage = DamageSpecifier.GetPositive(args.DamageDelta);
|
||||
var bloodloss = DamageSpecifier.ApplyModifierSet(damage, modifiers);
|
||||
|
||||
if (bloodloss.Empty)
|
||||
return;
|
||||
@@ -258,7 +261,7 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
var prob = Math.Clamp(totalFloat / 25, 0, 1);
|
||||
if (totalFloat > 0 && _robustRandom.Prob(prob))
|
||||
{
|
||||
TryModifyBloodLevel(ent, (-total) / 5, ent);
|
||||
TryModifyBloodLevel(ent, -total / 5, ent);
|
||||
_audio.PlayPvs(ent.Comp.InstantBloodSound, ent);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Adminchat)]
|
||||
internal sealed class AdminChatCommand : IConsoleCommand
|
||||
internal sealed class AdminChatCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "asay";
|
||||
public string Description => "Send chat messages to the private admin chat channel.";
|
||||
public string Help => "asay <text>";
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "asay";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteError("You can't run this command locally.");
|
||||
shell.WriteError(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Server.Chat.Commands
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IChatManager>().TrySendOOCMessage(player, message, OOCChatType.Admin);
|
||||
_chatManager.TrySendOOCMessage(player, message, OOCChatType.Admin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ using Robust.Shared.Enums;
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
internal sealed class MeCommand : IConsoleCommand
|
||||
internal sealed class MeCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "me";
|
||||
public string Description => "Perform an action.";
|
||||
public string Help => "me <text>";
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "me";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteError("This command cannot be run from the server.");
|
||||
shell.WriteError(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.Chat.Commands
|
||||
|
||||
if (player.AttachedEntity is not {} playerEntity)
|
||||
{
|
||||
shell.WriteError("You don't have an entity!");
|
||||
shell.WriteError(Loc.GetString($"shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ namespace Content.Server.Chat.Commands
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>()
|
||||
.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Emote, ChatTransmitRange.Normal, false, shell, player);
|
||||
_chatSystem.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Emote, ChatTransmitRange.Normal, false, shell, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
internal sealed class OOCCommand : IConsoleCommand
|
||||
internal sealed class OOCCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "ooc";
|
||||
public string Description => "Send Out Of Character chat messages.";
|
||||
public string Help => "ooc <text>";
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "ooc";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteError("This command cannot be run from the server.");
|
||||
shell.WriteError(Loc.GetString($"shell-cannot-run-command-from-server"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Content.Server.Chat.Commands
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IChatManager>().TrySendOOCMessage(player, message, OOCChatType.OOC);
|
||||
_chatManager.TrySendOOCMessage(player, message, OOCChatType.OOC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ using Robust.Shared.Enums;
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
internal sealed class SayCommand : IConsoleCommand
|
||||
internal sealed class SayCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "say";
|
||||
public string Description => "Send chat messages to the local channel or a specified radio channel.";
|
||||
public string Help => "say <text>";
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
public override string Command => "say";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
@@ -25,7 +24,7 @@ namespace Content.Server.Chat.Commands
|
||||
|
||||
if (player.AttachedEntity is not {} playerEntity)
|
||||
{
|
||||
shell.WriteError("You don't have an entity!");
|
||||
shell.WriteError(Loc.GetString($"shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +35,7 @@ namespace Content.Server.Chat.Commands
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>()
|
||||
.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, ChatTransmitRange.Normal, false, shell, player);
|
||||
_chatSystem.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, ChatTransmitRange.Normal, false, shell, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,21 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Chat.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Server)]
|
||||
public sealed class SetLOOCCommand : IConsoleCommand
|
||||
public sealed class SetLoocCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "setlooc";
|
||||
public string Description => Loc.GetString("set-looc-command-description");
|
||||
public string Help => Loc.GetString("set-looc-command-help");
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var cfg = IoCManager.Resolve<IConfigurationManager>();
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public override string Command => "setlooc";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("set-looc-command-too-many-arguments-error"));
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
var looc = cfg.GetCVar(CCVars.LoocEnabled);
|
||||
var looc = _configManager.GetCVar(CCVars.LoocEnabled);
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
@@ -31,12 +30,12 @@ public sealed class SetLOOCCommand : IConsoleCommand
|
||||
|
||||
if (args.Length == 1 && !bool.TryParse(args[0], out looc))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("set-looc-command-invalid-argument-error"));
|
||||
shell.WriteError(Loc.GetString("shell-invalid-bool"));
|
||||
return;
|
||||
}
|
||||
|
||||
cfg.SetCVar(CCVars.LoocEnabled, looc);
|
||||
_configManager.SetCVar(CCVars.LoocEnabled, looc);
|
||||
|
||||
shell.WriteLine(Loc.GetString(looc ? "set-looc-command-looc-enabled" : "set-looc-command-looc-disabled"));
|
||||
shell.WriteLine(Loc.GetString(looc ? "cmd-setlooc-looc-enabled" : "cmd-setlooc-looc-disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,21 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.Chat.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class SetOOCCommand : IConsoleCommand
|
||||
public sealed class SetOOCCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "setooc";
|
||||
public string Description => Loc.GetString("set-ooc-command-description");
|
||||
public string Help => Loc.GetString("set-ooc-command-help");
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var cfg = IoCManager.Resolve<IConfigurationManager>();
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public override string Command => "setooc";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length > 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("set-ooc-command-too-many-arguments-error"));
|
||||
shell.WriteError(Loc.GetString("shell-need-between-arguments", ("lower", 0), ("upper", 1)));
|
||||
return;
|
||||
}
|
||||
|
||||
var ooc = cfg.GetCVar(CCVars.OocEnabled);
|
||||
var ooc = _configManager.GetCVar(CCVars.OocEnabled);
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
@@ -31,12 +30,12 @@ public sealed class SetOOCCommand : IConsoleCommand
|
||||
|
||||
if (args.Length == 1 && !bool.TryParse(args[0], out ooc))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("set-ooc-command-invalid-argument-error"));
|
||||
shell.WriteError(Loc.GetString("shell-invalid-bool"));
|
||||
return;
|
||||
}
|
||||
|
||||
cfg.SetCVar(CCVars.OocEnabled, ooc);
|
||||
_configManager.SetCVar(CCVars.OocEnabled, ooc);
|
||||
|
||||
shell.WriteLine(Loc.GetString(ooc ? "set-ooc-command-ooc-enabled" : "set-ooc-command-ooc-disabled"));
|
||||
shell.WriteLine(Loc.GetString(ooc ? "cmd-setooc-ooc-enabled" : "cmd-setooc-ooc-disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ using Robust.Shared.Enums;
|
||||
namespace Content.Server.Chat.Commands
|
||||
{
|
||||
[AnyCommand]
|
||||
internal sealed class WhisperCommand : IConsoleCommand
|
||||
internal sealed class WhisperCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "whisper";
|
||||
public string Description => "Send chat messages to the local channel as a whisper";
|
||||
public string Help => "whisper <text>";
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
public override string Command => "whisper";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
@@ -25,7 +24,7 @@ namespace Content.Server.Chat.Commands
|
||||
|
||||
if (player.AttachedEntity is not {} playerEntity)
|
||||
{
|
||||
shell.WriteError("You don't have an entity!");
|
||||
shell.WriteError(Loc.GetString($"shell-must-be-attached-to-entity"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +35,7 @@ namespace Content.Server.Chat.Commands
|
||||
if (string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ChatSystem>()
|
||||
.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Whisper, ChatTransmitRange.Normal, false, shell, player);
|
||||
_chatSystem.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Whisper, ChatTransmitRange.Normal, false, shell, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
Content.Server/Clothing/Systems/OutfitSystem.cs
Normal file
109
Content.Server/Clothing/Systems/OutfitSystem.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Clothing.Systems;
|
||||
|
||||
public sealed class OutfitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IServerPreferencesManager _preferenceManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly HandsSystem _handSystem = default!;
|
||||
[Dependency] private readonly InventorySystem _invSystem = default!;
|
||||
[Dependency] private readonly SharedStationSpawningSystem _spawningSystem = default!;
|
||||
|
||||
public bool SetOutfit(EntityUid target, string gear, Action<EntityUid, EntityUid>? onEquipped = null)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(target, out InventoryComponent? inventoryComponent))
|
||||
return false;
|
||||
|
||||
if (!_prototypeManager.TryIndex<StartingGearPrototype>(gear, out var startingGear))
|
||||
return false;
|
||||
|
||||
HumanoidCharacterProfile? profile = null;
|
||||
ICommonSession? session = null;
|
||||
// Check if we are setting the outfit of a player to respect the preferences
|
||||
if (EntityManager.TryGetComponent(target, out ActorComponent? actorComponent))
|
||||
{
|
||||
session = actorComponent.PlayerSession;
|
||||
var userId = actorComponent.PlayerSession.UserId;
|
||||
var prefs = _preferenceManager.GetPreferences(userId);
|
||||
profile = prefs.SelectedCharacter as HumanoidCharacterProfile;
|
||||
}
|
||||
|
||||
if (_invSystem.TryGetSlots(target, out var slots))
|
||||
{
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
_invSystem.TryUnequip(target, slot.Name, true, true, false, inventoryComponent);
|
||||
var gearStr = ((IEquipmentLoadout) startingGear).GetGear(slot.Name);
|
||||
if (gearStr == string.Empty)
|
||||
continue;
|
||||
|
||||
var equipmentEntity = EntityManager.SpawnEntity(gearStr, EntityManager.GetComponent<TransformComponent>(target).Coordinates);
|
||||
if (slot.Name == "id" &&
|
||||
EntityManager.TryGetComponent(equipmentEntity, out PdaComponent? pdaComponent) &&
|
||||
EntityManager.TryGetComponent<IdCardComponent>(pdaComponent.ContainedId, out var id))
|
||||
{
|
||||
id.FullName = EntityManager.GetComponent<MetaDataComponent>(target).EntityName;
|
||||
}
|
||||
|
||||
_invSystem.TryEquip(target, equipmentEntity, slot.Name, silent: true, force: true, inventory: inventoryComponent);
|
||||
|
||||
onEquipped?.Invoke(target, equipmentEntity);
|
||||
}
|
||||
}
|
||||
|
||||
if (EntityManager.TryGetComponent(target, out HandsComponent? handsComponent))
|
||||
{
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(target).Coordinates;
|
||||
foreach (var prototype in startingGear.Inhand)
|
||||
{
|
||||
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
|
||||
_handSystem.TryPickup(target, inhandEntity, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// See if this starting gear is associated with a job
|
||||
var jobs = _prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (job.StartingGear != gear)
|
||||
continue;
|
||||
|
||||
var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID);
|
||||
if (!_prototypeManager.TryIndex<RoleLoadoutPrototype>(jobProtoId, out var jobProto))
|
||||
break;
|
||||
|
||||
// Don't require a player, so this works on Urists
|
||||
profile ??= EntityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var comp)
|
||||
? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species)
|
||||
: new HumanoidCharacterProfile();
|
||||
// Try to get the user's existing loadout for the role
|
||||
profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout);
|
||||
|
||||
if (roleLoadout == null)
|
||||
{
|
||||
// If they don't have a loadout for the role, make a default one
|
||||
roleLoadout = new RoleLoadout(jobProtoId);
|
||||
roleLoadout.SetDefault(profile, session, _prototypeManager);
|
||||
}
|
||||
|
||||
// Equip the target with the job loadout
|
||||
_spawningSystem.EquipRoleLoadout(target, roleLoadout, jobProto);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Server.Chat;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Stunnable;
|
||||
@@ -13,7 +13,6 @@ using Robust.Shared.Prototypes;
|
||||
using Content.Server.Emoting.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Cluwne;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Clumsy;
|
||||
@@ -31,6 +30,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
[Dependency] private readonly OutfitSystem _outfitSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -78,7 +78,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
|
||||
SetOutfitCommand.SetOutfit(uid, "CluwneGear", EntityManager);
|
||||
_outfitSystem.SetOutfit(uid, "CluwneGear");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="NetCord" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Discord.WebSocket;
|
||||
using NetCord.Gateway;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
@@ -59,18 +58,18 @@ public sealed class DiscordChatLink : IPostInjectInit
|
||||
_adminChannelId = ulong.Parse(channelId);
|
||||
}
|
||||
|
||||
private void OnMessageReceived(SocketMessage message)
|
||||
private void OnMessageReceived(Message message)
|
||||
{
|
||||
if (message.Author.IsBot)
|
||||
return;
|
||||
|
||||
var contents = message.Content.ReplaceLineEndings(" ");
|
||||
|
||||
if (message.Channel.Id == _oocChannelId)
|
||||
if (message.ChannelId == _oocChannelId)
|
||||
{
|
||||
_taskManager.RunOnMainThread(() => _chatManager.SendHookOOC(message.Author.Username, contents));
|
||||
}
|
||||
else if (message.Channel.Id == _adminChannelId)
|
||||
else if (message.ChannelId == _adminChannelId)
|
||||
{
|
||||
_taskManager.RunOnMainThread(() => _chatManager.SendHookAdmin(message.Author.Username, contents));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.CCVar;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NetCord;
|
||||
using NetCord.Gateway;
|
||||
using NetCord.Rest;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
using LogMessage = Discord.LogMessage;
|
||||
|
||||
namespace Content.Server.Discord.DiscordLink;
|
||||
|
||||
@@ -28,7 +25,7 @@ public sealed class CommandReceivedEventArgs
|
||||
/// Information about the message that the command was received from. This includes the message content, author, etc.
|
||||
/// Use this to reply to the message, delete it, etc.
|
||||
/// </summary>
|
||||
public SocketMessage Message { get; init; } = default!;
|
||||
public Message Message { get; init; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,7 +42,7 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
/// <remarks>
|
||||
/// This should not be used directly outside of DiscordLink. So please do not make it public. Use the methods in this class instead.
|
||||
/// </remarks>
|
||||
private DiscordSocketClient? _client;
|
||||
private GatewayClient? _client;
|
||||
private ISawmill _sawmill = default!;
|
||||
private ISawmill _sawmillLog = default!;
|
||||
|
||||
@@ -67,7 +64,7 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
/// <summary>
|
||||
/// Event that is raised when a message is received from Discord. This is raised for every message, including commands.
|
||||
/// </summary>
|
||||
public event Action<SocketMessage>? OnMessageReceived;
|
||||
public event Action<Message>? OnMessageReceived;
|
||||
|
||||
public void RegisterCommandCallback(Action<CommandReceivedEventArgs> callback, string command)
|
||||
{
|
||||
@@ -101,33 +98,34 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
return;
|
||||
}
|
||||
|
||||
_client = new DiscordSocketClient(new DiscordSocketConfig()
|
||||
_client = new GatewayClient(new BotToken(token), new GatewayClientConfiguration()
|
||||
{
|
||||
GatewayIntents = GatewayIntents.Guilds
|
||||
| GatewayIntents.GuildMembers
|
||||
Intents = GatewayIntents.Guilds
|
||||
| GatewayIntents.GuildUsers
|
||||
| GatewayIntents.GuildMessages
|
||||
| GatewayIntents.MessageContent
|
||||
| GatewayIntents.DirectMessages,
|
||||
Logger = new DiscordSawmillLogger(_sawmillLog),
|
||||
});
|
||||
_client.Log += Log;
|
||||
_client.MessageReceived += OnCommandReceivedInternal;
|
||||
_client.MessageReceived += OnMessageReceivedInternal;
|
||||
_client.MessageCreate += OnCommandReceivedInternal;
|
||||
_client.MessageCreate += OnMessageReceivedInternal;
|
||||
|
||||
_botToken = token;
|
||||
// Since you cannot change the token while the server is running / the DiscordLink is initialized,
|
||||
// we can just set the token without updating it every time the cvar changes.
|
||||
|
||||
_client.Ready += () =>
|
||||
_client.Ready += _ =>
|
||||
{
|
||||
_sawmill.Info("Discord client ready.");
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
};
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoginAsync(token);
|
||||
await _client.StartAsync();
|
||||
_sawmill.Info("Connected to Discord.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -143,12 +141,11 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
_sawmill.Info("Disconnecting from Discord.");
|
||||
|
||||
// Unsubscribe from the events.
|
||||
_client.MessageReceived -= OnCommandReceivedInternal;
|
||||
_client.MessageReceived -= OnMessageReceivedInternal;
|
||||
_client.MessageCreate -= OnCommandReceivedInternal;
|
||||
_client.MessageCreate -= OnMessageReceivedInternal;
|
||||
|
||||
await _client.LogoutAsync();
|
||||
await _client.StopAsync();
|
||||
await _client.DisposeAsync();
|
||||
await _client.CloseAsync();
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
}
|
||||
|
||||
@@ -172,45 +169,12 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
BotPrefix = prefix;
|
||||
}
|
||||
|
||||
private async Task LoginAsync(string token)
|
||||
{
|
||||
DebugTools.Assert(_client != null);
|
||||
DebugTools.Assert(_client.LoginState == LoginState.LoggedOut);
|
||||
|
||||
await _client.LoginAsync(TokenType.Bot, token);
|
||||
await _client.StartAsync();
|
||||
|
||||
|
||||
_sawmill.Info("Connected to Discord.");
|
||||
}
|
||||
|
||||
private string FormatLog(LogMessage msg)
|
||||
{
|
||||
return msg.Exception is null
|
||||
? $"{msg.Source}: {msg.Message}"
|
||||
: $"{msg.Source}: {msg.Message}\n{msg.Exception}";
|
||||
}
|
||||
|
||||
private Task Log(LogMessage msg)
|
||||
{
|
||||
var logLevel = msg.Severity switch
|
||||
{
|
||||
LogSeverity.Critical => LogLevel.Fatal,
|
||||
LogSeverity.Error => LogLevel.Error,
|
||||
LogSeverity.Warning => LogLevel.Warning,
|
||||
_ => LogLevel.Debug
|
||||
};
|
||||
|
||||
_sawmillLog.Log(logLevel, FormatLog(msg));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnCommandReceivedInternal(SocketMessage message)
|
||||
private ValueTask OnCommandReceivedInternal(Message message)
|
||||
{
|
||||
var content = message.Content;
|
||||
// If the message doesn't start with the bot prefix, ignore it.
|
||||
if (!content.StartsWith(BotPrefix))
|
||||
return Task.CompletedTask;
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
// Split the message into the command and the arguments.
|
||||
var trimmedInput = content[BotPrefix.Length..].Trim();
|
||||
@@ -236,13 +200,13 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
Arguments = arguments,
|
||||
Message = message,
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnMessageReceivedInternal(SocketMessage message)
|
||||
private ValueTask OnMessageReceivedInternal(Message message)
|
||||
{
|
||||
OnMessageReceived?.Invoke(message);
|
||||
return Task.CompletedTask;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
#region Proxy methods
|
||||
@@ -257,14 +221,18 @@ public sealed class DiscordLink : IPostInjectInit
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = _client.GetChannel(channelId) as IMessageChannel;
|
||||
var channel = await _client.Rest.GetChannelAsync(channelId) as TextChannel;
|
||||
if (channel == null)
|
||||
{
|
||||
_sawmill.Error("Tried to send a message to Discord but the channel {Channel} was not found.", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.SendMessageAsync(message, allowedMentions: AllowedMentions.None);
|
||||
await channel.SendMessageAsync(new MessageProperties()
|
||||
{
|
||||
AllowedMentions = AllowedMentionsProperties.None,
|
||||
Content = message,
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
34
Content.Server/Discord/DiscordLink/DiscordSawmillLogger.cs
Normal file
34
Content.Server/Discord/DiscordLink/DiscordSawmillLogger.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using NetCord.Logging;
|
||||
using NLogLevel = NetCord.Logging.LogLevel;
|
||||
using LogLevel = Robust.Shared.Log.LogLevel;
|
||||
|
||||
namespace Content.Server.Discord.DiscordLink;
|
||||
|
||||
public sealed class DiscordSawmillLogger(ISawmill sawmill) : IGatewayLogger, IRestLogger, IVoiceLogger
|
||||
{
|
||||
private static LogLevel GetLogLevel(NLogLevel logLevel)
|
||||
{
|
||||
return logLevel switch
|
||||
{
|
||||
NLogLevel.Critical => LogLevel.Fatal,
|
||||
NLogLevel.Error => LogLevel.Error,
|
||||
NLogLevel.Warning => LogLevel.Warning,
|
||||
_ => LogLevel.Debug,
|
||||
};
|
||||
}
|
||||
|
||||
void IGatewayLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||
}
|
||||
|
||||
void IRestLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||
}
|
||||
|
||||
void IVoiceLogger.Log<TState>(NetCord.Logging.LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
sawmill.Log(GetLogLevel(logLevel), exception, formatter(state, exception));
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,17 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.EntityList
|
||||
{
|
||||
[AdminCommand(AdminFlags.Spawn)]
|
||||
public sealed class SpawnEntityListCommand : IConsoleCommand
|
||||
public sealed class SpawnEntityListCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "spawnentitylist";
|
||||
public string Description => "Spawns a list of entities around you";
|
||||
public string Help => $"Usage: {Command} <entityListPrototypeId>";
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "spawnentitylist";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError($"Invalid arguments.\n{Help}");
|
||||
shell.WriteError(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,24 +33,23 @@ namespace Content.Server.EntityList
|
||||
return;
|
||||
}
|
||||
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
|
||||
if (!prototypeManager.TryIndex(args[0], out EntityListPrototype? prototype))
|
||||
if (!_prototypeManager.TryIndex(args[0], out EntityListPrototype? prototype))
|
||||
{
|
||||
shell.WriteError($"No {nameof(EntityListPrototype)} found with id {args[0]}");
|
||||
shell.WriteError(Loc.GetString($"cmd-spawnentitylist-failed",
|
||||
("prototype", nameof(EntityListPrototype)),
|
||||
("id", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var i = 0;
|
||||
|
||||
foreach (var entity in prototype.Entities(prototypeManager))
|
||||
foreach (var entity in prototype.Entities(_prototypeManager))
|
||||
{
|
||||
entityManager.SpawnEntity(entity.ID, entityManager.GetComponent<TransformComponent>(attached).Coordinates);
|
||||
EntityManager.SpawnEntity(entity.ID, EntityManager.GetComponent<TransformComponent>(attached).Coordinates);
|
||||
i++;
|
||||
}
|
||||
|
||||
shell.WriteLine($"Spawned {i} entities.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-spawnentitylist-success", ("count", i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,29 +5,31 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Tools;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Fax;
|
||||
using Content.Shared.Fax.Systems;
|
||||
using Content.Shared.Fax.Components;
|
||||
using Content.Shared.Fax.Systems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
using Content.Shared.Paper;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Fax;
|
||||
|
||||
@@ -50,6 +52,8 @@ public sealed class FaxSystem : EntitySystem
|
||||
[Dependency] private readonly FaxecuteSystem _faxecute = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
|
||||
private static readonly ProtoId<ToolQualityPrototype> ScrewingQuality = "Screwing";
|
||||
|
||||
private const string PaperSlotId = "Paper";
|
||||
|
||||
public override void Initialize()
|
||||
@@ -209,7 +213,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
{
|
||||
if (args.Handled ||
|
||||
!TryComp<ActorComponent>(args.User, out var actor) ||
|
||||
!_toolSystem.HasQuality(args.Used, "Screwing")) // Screwing because Pulsing already used by device linking
|
||||
!_toolSystem.HasQuality(args.Used, ScrewingQuality)) // Screwing because Pulsing already used by device linking
|
||||
return;
|
||||
|
||||
_quickDialog.OpenDialog(actor.PlayerSession,
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Content.Server.Flash
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FlashImmunityComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
|
||||
// ran before toggling light for extra-bright lantern
|
||||
SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new[] { typeof(HandheldLightSystem) });
|
||||
@@ -56,7 +56,13 @@ namespace Content.Server.Flash
|
||||
SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
|
||||
SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<FlashImmunityComponent> ent, ref ExaminedEvent args)
|
||||
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("flash-protection"));
|
||||
}
|
||||
|
||||
private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent args)
|
||||
{
|
||||
if (!args.IsHit ||
|
||||
|
||||
@@ -10,50 +10,49 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
sealed class ForceMapCommand : IConsoleCommand
|
||||
public sealed class ForceMapCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IGameMapManager _gameMapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public string Command => "forcemap";
|
||||
public string Description => Loc.GetString("forcemap-command-description");
|
||||
public string Help => Loc.GetString("forcemap-command-help");
|
||||
public override string Command => "forcemap";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-need-one-argument"));
|
||||
shell.WriteLine(Loc.GetString(Loc.GetString($"shell-need-exactly-one-argument")));
|
||||
return;
|
||||
}
|
||||
|
||||
var gameMap = IoCManager.Resolve<IGameMapManager>();
|
||||
var name = args[0];
|
||||
|
||||
// An empty string clears the forced map
|
||||
if (!string.IsNullOrEmpty(name) && !gameMap.CheckMapExists(name))
|
||||
if (!string.IsNullOrEmpty(name) && !_gameMapManager.CheckMapExists(name))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-map-not-found", ("map", name)));
|
||||
shell.WriteLine(Loc.GetString("cmd-forcemap-map-not-found", ("map", name)));
|
||||
return;
|
||||
}
|
||||
|
||||
_configurationManager.SetCVar(CCVars.GameMap, name);
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-cleared"));
|
||||
shell.WriteLine(Loc.GetString("cmd-forcemap-cleared"));
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("forcemap-command-success", ("map", name)));
|
||||
shell.WriteLine(Loc.GetString("cmd-forcemap-success", ("map", name)));
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var options = IoCManager.Resolve<IPrototypeManager>()
|
||||
var options = _prototypeManager
|
||||
.EnumeratePrototypes<GameMapPrototype>()
|
||||
.Select(p => new CompletionOption(p.ID, p.MapName))
|
||||
.OrderBy(p => p.Value);
|
||||
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("forcemap-command-arg-map"));
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString($"cmd-forcemap-hint"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -8,51 +8,49 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
sealed class ForcePresetCommand : IConsoleCommand
|
||||
public sealed class ForcePresetCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
|
||||
public string Command => "forcepreset";
|
||||
public string Description => "Forces a specific game preset to start for the current lobby.";
|
||||
public string Help => $"Usage: {Command} <preset>";
|
||||
public override string Command => "forcepreset";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = _e.System<GameTicker>();
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
if (_ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-forcepreset-preround-lobby-only"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Need exactly one argument.");
|
||||
shell.WriteLine(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
var name = args[0];
|
||||
if (!ticker.TryFindGamePreset(name, out var type))
|
||||
if (!_ticker.TryFindGamePreset(name, out var type))
|
||||
{
|
||||
shell.WriteLine($"No preset exists with name {name}.");
|
||||
shell.WriteLine(Loc.GetString($"cmd-forcepreset-no-preset-found", ("preset", name)));
|
||||
return;
|
||||
}
|
||||
|
||||
ticker.SetGamePreset(type, true);
|
||||
shell.WriteLine($"Forced the game to start with preset {name}.");
|
||||
ticker.UpdateInfoText();
|
||||
_ticker.SetGamePreset(type, true);
|
||||
shell.WriteLine(Loc.GetString($"cmd-forcepreset-success", ("preset", name)));
|
||||
_ticker.UpdateInfoText();
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var options = IoCManager.Resolve<IPrototypeManager>()
|
||||
var options = _prototypeManager
|
||||
.EnumeratePrototypes<GamePresetPrototype>()
|
||||
.OrderBy(p => p.ID)
|
||||
.Select(p => p.ID);
|
||||
|
||||
return CompletionResult.FromHintOptions(options, "<preset>");
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString($"cmd-forcepreset-hint"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -8,40 +8,35 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
public sealed class GoLobbyCommand : IConsoleCommand
|
||||
public sealed class GoLobbyCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
public string Command => "golobby";
|
||||
public string Description => "Enables the lobby and restarts the round.";
|
||||
public string Help => $"Usage: {Command} / {Command} <preset>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "golobby";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
GamePresetPrototype? preset = null;
|
||||
var presetName = string.Join(" ", args);
|
||||
|
||||
var ticker = _e.System<GameTicker>();
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
if (!ticker.TryFindGamePreset(presetName, out preset))
|
||||
if (!_gameTicker.TryFindGamePreset(presetName, out preset))
|
||||
{
|
||||
shell.WriteLine($"No preset found with name {presetName}");
|
||||
shell.WriteLine(Loc.GetString($"cmd-forcepreset-no-preset-found", ("preset", presetName)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var config = IoCManager.Resolve<IConfigurationManager>();
|
||||
config.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
_configManager.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
|
||||
ticker.RestartRound();
|
||||
_gameTicker.RestartRound();
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
ticker.SetGamePreset(preset);
|
||||
}
|
||||
_gameTicker.SetGamePreset(preset);
|
||||
|
||||
shell.WriteLine($"Enabling the lobby and restarting the round.{(preset == null ? "" : $"\nPreset set to {presetName}")}");
|
||||
shell.WriteLine(Loc.GetString(preset == null ? "cmd-golobby-success" : "cmd-golobby-success-with-preset", ("preset", presetName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,31 +7,27 @@ using Robust.Shared.Console;
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Round)]
|
||||
sealed class ToggleDisallowLateJoinCommand : IConsoleCommand
|
||||
public sealed class ToggleDisallowLateJoinCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "toggledisallowlatejoin";
|
||||
public string Description => "Allows or disallows latejoining during mid-game.";
|
||||
public string Help => $"Usage: {Command} <disallow>";
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "toggledisallowlatejoin";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Need exactly one argument.");
|
||||
shell.WriteLine(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
var cfgMan = IoCManager.Resolve<IConfigurationManager>();
|
||||
|
||||
if (bool.TryParse(args[0], out var result))
|
||||
{
|
||||
cfgMan.SetCVar(CCVars.GameDisallowLateJoins, bool.Parse(args[0]));
|
||||
shell.WriteLine(result ? "Late joining has been disabled." : "Late joining has been enabled.");
|
||||
_configManager.SetCVar(CCVars.GameDisallowLateJoins, bool.Parse(args[0]));
|
||||
shell.WriteLine(Loc.GetString(result ? "cmd-toggledisallowlatejoin-disabled" : "cmd-toggledisallowlatejoin-enabled"));
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteLine("Invalid argument.");
|
||||
}
|
||||
shell.WriteLine(Loc.GetString($"shell-invalid-bool"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Clothing.Systems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.KillTracking;
|
||||
using Content.Server.Mind;
|
||||
@@ -23,6 +23,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponen
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly OutfitSystem _outfitSystem = default!;
|
||||
[Dependency] private readonly PointSystem _point = default!;
|
||||
[Dependency] private readonly RespawnRuleSystem _respawn = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
@@ -55,7 +56,7 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponen
|
||||
var mob = mobMaybe!.Value;
|
||||
|
||||
_mind.TransferTo(newMind, mob);
|
||||
SetOutfitCommand.SetOutfit(mob, dm.Gear, EntityManager);
|
||||
_outfitSystem.SetOutfit(mob, dm.Gear);
|
||||
EnsureComp<KillTrackerComponent>(mob);
|
||||
_respawn.AddToTracker(ev.Player.UserId, (uid, tracker));
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Server.GhostKick;
|
||||
|
||||
// Handles logic for "ghost kicking".
|
||||
// Basically we boot the client off the server without telling them, so the game shits itself.
|
||||
// Hilariously isn't it?
|
||||
// Hilarious, isn't it?
|
||||
|
||||
public sealed class GhostKickManager
|
||||
{
|
||||
@@ -45,32 +45,30 @@ public sealed class GhostKickManager
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Moderator)]
|
||||
public sealed class GhostKickCommand : IConsoleCommand
|
||||
public sealed class GhostKickCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "ghostkick";
|
||||
public string Description => "Kick a client from the server as if their network just dropped.";
|
||||
public string Help => "Usage: ghostkick <Player> [Reason]";
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly GhostKickManager _ghostKick = default!;
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "ghostkick";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
shell.WriteError("Need at least one argument");
|
||||
shell.WriteError(Loc.GetString($"shell-need-exactly-one-argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
var playerName = args[0];
|
||||
var reason = args.Length > 1 ? args[1] : "Ghost kicked by console";
|
||||
var reason = args.Length > 1 ? args[1] : Loc.GetString($"cmd-ghostkick-default-reason");
|
||||
|
||||
var players = IoCManager.Resolve<IPlayerManager>();
|
||||
var ghostKick = IoCManager.Resolve<GhostKickManager>();
|
||||
|
||||
if (!players.TryGetSessionByUsername(playerName, out var player))
|
||||
if (!_playerManager.TryGetSessionByUsername(playerName, out var player))
|
||||
{
|
||||
shell.WriteError($"Unable to find player: '{playerName}'.");
|
||||
shell.WriteError(Loc.GetString($"shell-target-player-does-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
ghostKick.DoDisconnect(player.Channel, reason);
|
||||
_ghostKick.DoDisconnect(player.Channel, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
private bool TryGlue(Entity<GlueComponent> entity, EntityUid target, EntityUid actor)
|
||||
{
|
||||
// if item is glued then don't apply glue again so it can be removed for reasonable time
|
||||
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target))
|
||||
// If glue is applied to an unremoveable item, the component will disappear after the duration.
|
||||
// This effecitvely means any unremoveable item could be removed with a bottle of glue.
|
||||
if (HasComp<GluedComponent>(target) || !HasComp<ItemComponent>(target) || HasComp<UnremoveableComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("glue-failure", ("target", target)), actor, actor, PopupType.Medium);
|
||||
return false;
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mech.EntitySystems;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -56,6 +57,8 @@ namespace Content.Server.Guardian
|
||||
SubscribeLocalEvent<GuardianHostComponent, GuardianToggleActionEvent>(OnPerformAction);
|
||||
|
||||
SubscribeLocalEvent<GuardianComponent, AttackAttemptEvent>(OnGuardianAttackAttempt);
|
||||
|
||||
SubscribeLocalEvent<GuardianHostComponent, MechPilotRelayedEvent<GettingAttackedAttemptEvent>>(OnPilotAttackAttempt);
|
||||
}
|
||||
|
||||
private void OnGuardianShutdown(EntityUid uid, GuardianComponent component, ComponentShutdown args)
|
||||
@@ -144,6 +147,16 @@ namespace Content.Server.Guardian
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPilotAttackAttempt(Entity<GuardianHostComponent> uid, ref MechPilotRelayedEvent<GettingAttackedAttemptEvent> args)
|
||||
{
|
||||
if (args.Args.Cancelled)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupCursor(Loc.GetString("guardian-attack-host"), args.Args.Attacker, PopupType.LargeCaution);
|
||||
|
||||
args.Args.Cancelled = true;
|
||||
}
|
||||
|
||||
public void ToggleGuardian(EntityUid user, GuardianHostComponent hostComponent)
|
||||
{
|
||||
if (!TryComp<GuardianComponent>(hostComponent.HostedGuardian, out var guardianComponent))
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
@@ -17,6 +21,7 @@ using Robust.Shared.Console;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Instruments;
|
||||
|
||||
@@ -31,6 +36,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly IAdminLogManager _admingLogSystem = default!;
|
||||
|
||||
private const float MaxInstrumentBandRange = 10f;
|
||||
|
||||
@@ -50,6 +56,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
SubscribeNetworkEvent<InstrumentStopMidiEvent>(OnMidiStop);
|
||||
SubscribeNetworkEvent<InstrumentSetMasterEvent>(OnMidiSetMaster);
|
||||
SubscribeNetworkEvent<InstrumentSetFilteredChannelEvent>(OnMidiSetFilteredChannel);
|
||||
SubscribeNetworkEvent<InstrumentSetChannelsEvent>(OnMidiSetChannels);
|
||||
|
||||
Subs.BuiEvents<InstrumentComponent>(InstrumentUiKey.Key, subs =>
|
||||
{
|
||||
@@ -132,6 +139,44 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
|
||||
|
||||
private void OnMidiSetChannels(InstrumentSetChannelsEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = GetEntity(msg.Uid);
|
||||
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument) || !TryComp(uid, out ActiveInstrumentComponent? activeInstrument))
|
||||
return;
|
||||
|
||||
if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer)
|
||||
return;
|
||||
|
||||
if (msg.Tracks.Length > RobustMidiEvent.MaxChannels)
|
||||
{
|
||||
Log.Warning($"{args.SenderSession.UserId.ToString()} - Tried to send tracks over the limit! Received: {msg.Tracks.Length}; Limit: {RobustMidiEvent.MaxChannels}");
|
||||
return;
|
||||
}
|
||||
|
||||
var tracksString = string.Join("\n",
|
||||
msg.Tracks
|
||||
.Where(t => t != null)
|
||||
.Select(t => t!.ToString()));
|
||||
|
||||
_admingLogSystem.Add(
|
||||
LogType.Instrument,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(args.SenderSession.AttachedEntity)} set the midi channels for {ToPrettyString(uid)} to {tracksString}");
|
||||
|
||||
// Truncate any track names too long.
|
||||
foreach (var t in msg.Tracks)
|
||||
{
|
||||
t?.TruncateFields(_cfg.GetCVar(CCVars.MidiMaxChannelNameLength));
|
||||
}
|
||||
|
||||
activeInstrument.Tracks = msg.Tracks;
|
||||
|
||||
Dirty(uid, activeInstrument);
|
||||
}
|
||||
|
||||
private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = GetEntity(msg.Uid);
|
||||
|
||||
@@ -5,19 +5,21 @@ using Content.Shared.Maps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Interaction;
|
||||
|
||||
[AdminCommand(AdminFlags.Debug)]
|
||||
public sealed class TilePryCommand : IConsoleCommand
|
||||
public sealed class TilePryCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public string Command => "tilepry";
|
||||
public string Description => "Pries up all tiles in a radius around the user.";
|
||||
public string Help => $"Usage: {Command} <radius>";
|
||||
private readonly string _platingId = "Plating";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "tilepry";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player?.AttachedEntity is not { } attached)
|
||||
@@ -33,39 +35,38 @@ public sealed class TilePryCommand : IConsoleCommand
|
||||
|
||||
if (!int.TryParse(args[0], out var radius))
|
||||
{
|
||||
shell.WriteError($"{args[0]} isn't a valid integer.");
|
||||
shell.WriteError(Loc.GetString($"cmd-tilepry-arg-must-be-number", ("arg", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (radius < 0)
|
||||
{
|
||||
shell.WriteError("Radius must be positive.");
|
||||
shell.WriteError(Loc.GetString($"cmd-tilepry-radius-must-be-positive"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mapSystem = _entities.System<SharedMapSystem>();
|
||||
var xform = _entities.GetComponent<TransformComponent>(attached);
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(attached);
|
||||
|
||||
var playerGrid = xform.GridUid;
|
||||
|
||||
if (!_entities.TryGetComponent<MapGridComponent>(playerGrid, out var mapGrid))
|
||||
if (!EntityManager.TryGetComponent<MapGridComponent>(playerGrid, out var mapGrid))
|
||||
return;
|
||||
|
||||
var playerPosition = xform.Coordinates;
|
||||
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
|
||||
for (var i = -radius; i <= radius; i++)
|
||||
{
|
||||
for (var j = -radius; j <= radius; j++)
|
||||
{
|
||||
var tile = mapSystem.GetTileRef(playerGrid.Value, mapGrid, playerPosition.Offset(new Vector2(i, j)));
|
||||
var coordinates = mapSystem.GridTileToLocal(playerGrid.Value, mapGrid, tile.GridIndices);
|
||||
var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.Tile.TypeId];
|
||||
var tile = _mapSystem.GetTileRef(playerGrid.Value, mapGrid, playerPosition.Offset(new Vector2(i, j)));
|
||||
var coordinates = _mapSystem.GridTileToLocal(playerGrid.Value, mapGrid, tile.GridIndices);
|
||||
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId];
|
||||
|
||||
if (!tileDef.CanCrowbar) continue;
|
||||
if (!tileDef.CanCrowbar)
|
||||
continue;
|
||||
|
||||
var plating = tileDefinitionManager["Plating"];
|
||||
mapSystem.SetTile(playerGrid.Value, mapGrid, coordinates, new Tile(plating.TileId));
|
||||
var plating = _tileDefinitionManager[_platingId];
|
||||
_mapSystem.SetTile(playerGrid.Value, mapGrid, coordinates, new Tile(plating.TileId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user