Merge remote-tracking branch 'upstream/stable' into ed-23-06-2025-upstream-sync
# Conflicts: # .github/workflows/check-trailing-whitespace.yml # Content.IntegrationTests/Tests/Access/AccessReaderTest.cs # Content.IntegrationTests/Tests/Chameleon/ChameleonJobLoadoutTest.cs # Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs # Content.IntegrationTests/Tests/PostMapInitTest.cs # Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs # Content.Shared/Light/Components/SunShadowCycleComponent.cs # Resources/Prototypes/Damage/modifier_sets.yml # Resources/Prototypes/Maps/Pools/default.yml
This commit is contained in:
59
.github/workflows/check-trailing-whitespace.yml
vendored
59
.github/workflows/check-trailing-whitespace.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: Trailing Whitespace Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Trailing Whitespace Check
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Get changed text files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46.0.5
|
||||
with:
|
||||
files: |
|
||||
**.cs
|
||||
**.yml
|
||||
**.swsl
|
||||
**.json
|
||||
**.py
|
||||
- name: Check for trailing whitespace and EOF newline
|
||||
env:
|
||||
ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
run: |
|
||||
has_trailing_whitespace=0
|
||||
has_missing_eof_newline=0
|
||||
|
||||
for file in ${ALL_CHANGED_FILES}; do
|
||||
# Ignore vanilla not CrystallEdge files
|
||||
if [[ "$file" != *CP14* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Checking $file"
|
||||
|
||||
# Check for trailing whitespace
|
||||
if grep -qP '[ \t]+$' "$file"; then
|
||||
echo "::error file=$file::Trailing whitespace found"
|
||||
has_trailing_whitespace=1
|
||||
fi
|
||||
|
||||
# Check for missing EOF newline
|
||||
if [ -f "$file" ] && [ -s "$file" ]; then
|
||||
last_char=$(tail -c 1 "$file")
|
||||
if [ "$last_char" != "" ] && [ "$last_char" != $'\n' ]; then
|
||||
echo "::error file=$file::Missing newline at end of file"
|
||||
has_missing_eof_newline=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$has_trailing_whitespace" -eq 1 ] || [ "$has_missing_eof_newline" -eq 1 ]; then
|
||||
echo "Issues found: trailing whitespace or missing EOF newline."
|
||||
echo "We recommend using an IDE to prevent this from happening."
|
||||
exit 1
|
||||
fi
|
||||
@@ -4,39 +4,20 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Access.Commands;
|
||||
|
||||
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||
public sealed class ShowAccessReadersCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showaccessreaders";
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
public string Description => "Toggles showing access reader permissions on the map";
|
||||
public string Help => """
|
||||
Overlay Info:
|
||||
-Disabled | The access reader is disabled
|
||||
+Unrestricted | The access reader has no restrictions
|
||||
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
|
||||
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
|
||||
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
|
||||
""";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "showaccessreaders";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var collection = IoCManager.Instance;
|
||||
var existing = _overlay.RemoveOverlay<AccessOverlay>();
|
||||
if (!existing)
|
||||
_overlay.AddOverlay(new AccessOverlay(EntityManager, _cache, _xform));
|
||||
|
||||
if (collection == null)
|
||||
return;
|
||||
|
||||
var overlay = collection.Resolve<IOverlayManager>();
|
||||
|
||||
if (overlay.RemoveOverlay<AccessOverlay>())
|
||||
{
|
||||
shell.WriteLine($"Set access reader debug overlay to false");
|
||||
return;
|
||||
}
|
||||
|
||||
var entManager = collection.Resolve<IEntityManager>();
|
||||
var cache = collection.Resolve<IResourceCache>();
|
||||
var xform = entManager.System<SharedTransformSystem>();
|
||||
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
|
||||
shell.WriteLine($"Set access reader debug overlay to true");
|
||||
shell.WriteLine(Loc.GetString($"cmd-showaccessreaders-status", ("status", !existing)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Content.Client.Actions
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
|
||||
public event Action<EntityUid>? OnActionAdded;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,9 @@ namespace Content.Client.Cargo.UI
|
||||
/// </summary>
|
||||
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
|
||||
{
|
||||
if (!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||
return;
|
||||
|
||||
Requests.DisposeAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
@@ -237,6 +240,7 @@ namespace Content.Client.Cargo.UI
|
||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
||||
|
||||
// TODO: Disable based on access.
|
||||
row.SetApproveVisible(orderConsole.Mode != CargoOrderConsoleMode.SendToPrimary);
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
@@ -290,8 +294,8 @@ namespace Content.Client.Cargo.UI
|
||||
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
||||
_timing.CurTime < orderConsole.NextAccountActionTime;
|
||||
|
||||
OrdersSpacer.Visible = !orderConsole.SlipPrinter;
|
||||
Orders.Visible = !orderConsole.SlipPrinter;
|
||||
OrdersSpacer.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||
Orders.Visible = orderConsole.Mode != CargoOrderConsoleMode.PrintSlip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,5 +14,15 @@ namespace Content.Client.Cargo.UI
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SetApproveVisible(bool visible)
|
||||
{
|
||||
Approve.Visible = visible;
|
||||
|
||||
if (visible)
|
||||
Cancel.AddStyleClass("OpenLeft");
|
||||
else
|
||||
Cancel.RemoveStyleClass("OpenLeft");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -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();
|
||||
@@ -11,8 +11,6 @@ namespace Content.Client.Clothing.Systems;
|
||||
// All valid items for chameleon are calculated on client startup and stored in dictionary.
|
||||
public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = 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);
|
||||
@@ -310,7 +310,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Content.Client.MainMenu;
|
||||
using Content.Client.Overlays;
|
||||
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;
|
||||
@@ -85,6 +86,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()
|
||||
{
|
||||
@@ -134,6 +136,8 @@ namespace Content.Client.Entry
|
||||
_prototypeManager.RegisterIgnore("alertLevels");
|
||||
_prototypeManager.RegisterIgnore("nukeopsRole");
|
||||
_prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
|
||||
_prototypeManager.RegisterIgnore("codewordGenerator");
|
||||
_prototypeManager.RegisterIgnore("codewordFaction");
|
||||
|
||||
_componentFactory.GenerateNetIds();
|
||||
_adminManager.Initialize();
|
||||
@@ -145,6 +149,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;
|
||||
|
||||
@@ -50,6 +50,18 @@ namespace Content.Client.Inventory
|
||||
[ViewVariables]
|
||||
private readonly EntityUid _virtualHiddenEntity;
|
||||
|
||||
/// <summary>
|
||||
/// The current amount of added hand buttons.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private int _handCount;
|
||||
|
||||
/// <summary>
|
||||
/// The current shape of the inventory, needed to calculate the window size.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private Vector2i _inventoryDimensions;
|
||||
|
||||
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_examine = EntMan.System<ExamineSystem>();
|
||||
@@ -93,6 +105,8 @@ namespace Content.Client.Inventory
|
||||
return;
|
||||
|
||||
_strippingMenu.ClearButtons();
|
||||
_handCount = 0;
|
||||
_inventoryDimensions = Vector2i.Zero;
|
||||
|
||||
if (EntMan.TryGetComponent<InventoryComponent>(Owner, out var inv))
|
||||
{
|
||||
@@ -152,9 +166,15 @@ namespace Content.Client.Inventory
|
||||
// TODO allow windows to resize based on content's desired size
|
||||
|
||||
// for now: shit-code
|
||||
// this breaks for drones (too many hands, lots of empty vertical space), and looks shit for monkeys and the like.
|
||||
// but the window is realizable, so eh.
|
||||
_strippingMenu.SetSize = new Vector2(220, snare?.IsEnsnared == true ? 550 : 530);
|
||||
// calculate the window size manually
|
||||
// +20 horizontally and vertically from the ContentsContainer margin
|
||||
// +16 vertically from the BoxContainer margin
|
||||
// +27 vertically from the window header
|
||||
var horizontalMenuSize = Math.Max(200, Math.Max(_handCount, _inventoryDimensions.X + 1) * (SlotControl.DefaultButtonSize + ButtonSeparation) + 20);
|
||||
var verticalMenuSize = Math.Max(200, (_inventoryDimensions.Y + (_handCount > 0 ? 2 : 1)) * (SlotControl.DefaultButtonSize + ButtonSeparation) + 53);
|
||||
if (snare?.IsEnsnared == true)
|
||||
verticalMenuSize += 20;
|
||||
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
|
||||
}
|
||||
|
||||
private void AddHandButton(Hand hand)
|
||||
@@ -172,6 +192,8 @@ namespace Content.Client.Inventory
|
||||
|
||||
UpdateEntityIcon(button, hand.HeldEntity);
|
||||
_strippingMenu!.HandsContainer.AddChild(button);
|
||||
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
||||
_handCount++;
|
||||
}
|
||||
|
||||
private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
|
||||
@@ -220,6 +242,10 @@ namespace Content.Client.Inventory
|
||||
UpdateEntityIcon(button, entity);
|
||||
|
||||
LayoutContainer.SetPosition(button, slotDef.StrippingWindowPos * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
||||
if (slotDef.StrippingWindowPos.X > _inventoryDimensions.X)
|
||||
_inventoryDimensions = new Vector2i(slotDef.StrippingWindowPos.X, _inventoryDimensions.Y);
|
||||
if (slotDef.StrippingWindowPos.Y > _inventoryDimensions.Y)
|
||||
_inventoryDimensions = new Vector2i(_inventoryDimensions.X, slotDef.StrippingWindowPos.Y);
|
||||
}
|
||||
|
||||
private void UpdateEntityIcon(SlotControl button, EntityUid? entity)
|
||||
|
||||
@@ -15,6 +15,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;
|
||||
@@ -69,6 +70,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<PlayerRateLimitManager>();
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
collection.Register<TitleWindowManager>();
|
||||
collection.Register<ClientsidePlaytimeTrackingManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
<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.Shared.Clothing;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
@@ -7,12 +6,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;
|
||||
@@ -21,6 +29,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);
|
||||
@@ -63,32 +72,165 @@ 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];
|
||||
// Get all loadout prototypes for this group.
|
||||
var validProtos = _groupProto.Loadouts.Select(id => protoMan.Index(id));
|
||||
|
||||
foreach (var loadoutProto in _groupProto.Loadouts)
|
||||
/*
|
||||
* 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);
|
||||
/*
|
||||
* 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();
|
||||
|
||||
/*
|
||||
* 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
|
||||
OnLoadoutUnpressed?.Invoke(loadoutProto);
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
LoadoutsContainer.AddChild(loadoutContainer);
|
||||
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));
|
||||
|
||||
@@ -16,7 +16,6 @@ public sealed class JetpackSystem : SharedJetpackSystem
|
||||
[Dependency] private readonly ClothingSystem _clothing = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
|
||||
@@ -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 '^'
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,15 @@ using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Shuttles.Commands;
|
||||
|
||||
public sealed class ShowEmergencyShuttleCommand : IConsoleCommand
|
||||
public sealed class ShowEmergencyShuttleCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showemergencyshuttle";
|
||||
public string Description => "Shows the expected position of the emergency shuttle";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
[Dependency] private readonly ShuttleSystem _shuttle = default!;
|
||||
|
||||
public override string Command => "showemergencyshuttle";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var tstalker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
|
||||
tstalker.EnableShuttlePosition ^= true;
|
||||
shell.WriteLine($"Set emergency shuttle debug to {tstalker.EnableShuttlePosition}");
|
||||
_shuttle.EnableShuttlePosition ^= true;
|
||||
shell.WriteLine(Loc.GetString($"cmd-showemergencyshuttle-status", ("status", _shuttle.EnableShuttlePosition)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Content.Client.Strip
|
||||
public sealed class StrippingMenu : DefaultWindow
|
||||
{
|
||||
public LayoutContainer InventoryContainer = new();
|
||||
public BoxContainer HandsContainer = new() { Orientation = LayoutOrientation.Horizontal };
|
||||
public LayoutContainer HandsContainer = new();
|
||||
public BoxContainer SnareContainer = new();
|
||||
public bool Dirty = true;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Client.Eui;
|
||||
using Content.Server.Ghost.Roles.Raffles;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.Ghost.Roles;
|
||||
using Content.Shared.Ghost.Roles.Raffles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Player;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Ghost.Roles.Raffles;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Ghost.Roles.Raffles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -3,39 +3,33 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Weapons.Melee;
|
||||
|
||||
|
||||
public sealed class MeleeSpreadCommand : IConsoleCommand
|
||||
public sealed class MeleeSpreadCommand : LocalizedEntityCommands
|
||||
{
|
||||
public string Command => "showmeleespread";
|
||||
public string Description => "Shows the current weapon's range and arc for debugging";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var collection = IoCManager.Instance;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly MeleeWeaponSystem _meleeSystem = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _combatSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
if (collection == null)
|
||||
public override string Command => "showmeleespread";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (_overlay.RemoveOverlay<MeleeArcOverlay>())
|
||||
return;
|
||||
|
||||
var overlayManager = collection.Resolve<IOverlayManager>();
|
||||
|
||||
if (overlayManager.RemoveOverlay<MeleeArcOverlay>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sysManager = collection.Resolve<IEntitySystemManager>();
|
||||
|
||||
overlayManager.AddOverlay(new MeleeArcOverlay(
|
||||
collection.Resolve<IEntityManager>(),
|
||||
collection.Resolve<IEyeManager>(),
|
||||
collection.Resolve<IInputManager>(),
|
||||
collection.Resolve<IPlayerManager>(),
|
||||
sysManager.GetEntitySystem<MeleeWeaponSystem>(),
|
||||
sysManager.GetEntitySystem<SharedCombatModeSystem>(),
|
||||
sysManager.GetEntitySystem<SharedTransformSystem>()));
|
||||
_overlay.AddOverlay(new MeleeArcOverlay(
|
||||
EntityManager,
|
||||
_eyeManager,
|
||||
_inputManager,
|
||||
_playerManager,
|
||||
_meleeSystem,
|
||||
_combatSystem,
|
||||
_transformSystem));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Access
|
||||
@@ -13,6 +13,15 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
public sealed class AccessReaderTest
|
||||
{
|
||||
/*
|
||||
[TestPrototypes]
|
||||
private const string Prototypes = @"
|
||||
- type: entity
|
||||
id: TestAccessReader
|
||||
name: access reader
|
||||
components:
|
||||
- type: AccessReader
|
||||
";
|
||||
|
||||
[Test]
|
||||
public async Task TestTags()
|
||||
{
|
||||
@@ -20,13 +29,13 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var system = entityManager.System<AccessReaderSystem>();
|
||||
var ent = entityManager.SpawnEntity("TestAccessReader", MapCoordinates.Nullspace);
|
||||
var reader = new Entity<AccessReaderComponent>(ent, entityManager.GetComponent<AccessReaderComponent>(ent));
|
||||
|
||||
// test empty
|
||||
var reader = new AccessReaderComponent();
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
|
||||
@@ -35,8 +44,7 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
});
|
||||
|
||||
// test deny
|
||||
reader = new AccessReaderComponent();
|
||||
reader.DenyTags.Add("A");
|
||||
system.AddDenyTag(reader, "A");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
|
||||
@@ -44,10 +52,10 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "Foo" }, reader), Is.False);
|
||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.True);
|
||||
});
|
||||
system.ClearDenyTags(reader);
|
||||
|
||||
// test one list
|
||||
reader = new AccessReaderComponent();
|
||||
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||
system.AddAccess(reader, "A");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||
@@ -55,10 +63,10 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
|
||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||
});
|
||||
system.ClearAccesses(reader);
|
||||
|
||||
// test one list - two items
|
||||
reader = new AccessReaderComponent();
|
||||
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A", "B" });
|
||||
system.AddAccess(reader, new HashSet<ProtoId<AccessLevelPrototype>> { "A", "B" });
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.False);
|
||||
@@ -66,11 +74,14 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
|
||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||
});
|
||||
system.ClearAccesses(reader);
|
||||
|
||||
// test two list
|
||||
reader = new AccessReaderComponent();
|
||||
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "B", "C" });
|
||||
var accesses = new List<HashSet<ProtoId<AccessLevelPrototype>>>() {
|
||||
new HashSet<ProtoId<AccessLevelPrototype>> () { "A" },
|
||||
new HashSet<ProtoId<AccessLevelPrototype>> () { "B", "C" }
|
||||
};
|
||||
system.AddAccesses(reader, accesses);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||
@@ -80,11 +91,11 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "C", "B", "A" }, reader), Is.True);
|
||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||
});
|
||||
system.ClearAccesses(reader);
|
||||
|
||||
// test deny list
|
||||
reader = new AccessReaderComponent();
|
||||
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||
reader.DenyTags.Add("B");
|
||||
system.AddAccess(reader, new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
|
||||
system.AddDenyTag(reader, "B");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
|
||||
@@ -92,6 +103,8 @@ namespace Content.IntegrationTests.Tests.Access
|
||||
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.False);
|
||||
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
|
||||
});
|
||||
system.ClearAccesses(reader);
|
||||
system.ClearDenyTags(reader);
|
||||
});
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{/* //CP14 we dont wanna chameleons, disabled test
|
||||
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
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var job in validJobs)
|
||||
{
|
||||
if (job.Value != 0)
|
||||
continue;
|
||||
|
||||
errorMessage.AppendLine(job.Key + " has no chameleonOutfit prototype.");
|
||||
invalid = true;
|
||||
Assert.That(job.Value, Is.Not.Zero,
|
||||
$"{job.Key} has no chameleonOutfit prototype.");
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.NukeOps;
|
||||
using Content.Shared.Pinpointer;
|
||||
@@ -23,12 +24,16 @@ using Content.Shared.Station.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameRules;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class NukeOpsTest
|
||||
{/*
|
||||
private static readonly ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||
private static readonly ProtoId<NpcFactionPrototype> NanotrasenFaction = "NanoTrasen";
|
||||
|
||||
/// <summary>
|
||||
/// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded.
|
||||
/// </summary>
|
||||
@@ -119,8 +124,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(player));
|
||||
Assert.That(roleSys.MindIsAntagonist(mind));
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
Assert.That(factionSys.IsMember(player, SyndicateFaction), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, NanotrasenFaction), Is.False);
|
||||
var roles = roleSys.MindGetAllRoleInfo(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
@@ -130,8 +135,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(dummyEnts[1]));
|
||||
Assert.That(roleSys.MindIsAntagonist(dummyMind));
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], SyndicateFaction), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], NanotrasenFaction), Is.False);
|
||||
roles = roleSys.MindGetAllRoleInfo(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
@@ -146,8 +151,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
||||
Assert.That(factionSys.IsMember(ent, SyndicateFaction), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, NanotrasenFaction), Is.True);
|
||||
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
|
||||
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.Roles;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -20,6 +21,8 @@ public sealed class TraitorRuleTest
|
||||
{/*
|
||||
private const string TraitorGameRuleProtoId = "Traitor";
|
||||
private const string TraitorAntagRoleName = "Traitor";
|
||||
private static readonly ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||
private static readonly ProtoId<NpcFactionPrototype> NanotrasenFaction = "NanoTrasen";
|
||||
|
||||
[Test]
|
||||
public async Task TestTraitorObjectives()
|
||||
@@ -108,8 +111,8 @@ public sealed class TraitorRuleTest
|
||||
// Make sure the player is a traitor.
|
||||
var mind = mindSys.GetMind(player)!.Value;
|
||||
Assert.That(roleSys.MindIsAntagonist(mind));
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
Assert.That(factionSys.IsMember(player, SyndicateFaction), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, NanotrasenFaction), Is.False);
|
||||
Assert.That(traitorRule.TotalTraitors, Is.EqualTo(1));
|
||||
Assert.That(traitorRule.TraitorMinds[0], Is.EqualTo(mind));
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -55,8 +55,6 @@ namespace Content.IntegrationTests.Tests
|
||||
//CrystallEdge Maps end
|
||||
"/Maps/centcomm.yml",
|
||||
"/Maps/bagel.yml", // Contains mime's rubber stamp --> Either fix this, remove the category, or remove this comment if intentional.
|
||||
"/Maps/gate.yml", // Contains positronic brain and LSE-1200c "Perforator"
|
||||
"/Maps/meta.yml", // Contains warden's rubber stamp
|
||||
"/Maps/reach.yml", // Contains handheld crew monitor
|
||||
"/Maps/Shuttles/ShuttleEvent/cruiser.yml", // Contains LSE-1200c "Perforator"
|
||||
"/Maps/Shuttles/ShuttleEvent/honki.yml", // Contains golden honker, clown's rubber stamp
|
||||
|
||||
@@ -47,7 +47,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()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Access;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
@@ -23,23 +24,21 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
public override bool Cut(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, PulseTimeoutKey.Key);
|
||||
comp.Enabled = false;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
EntityManager.System<AccessReaderSystem>().SetActive((wire.Owner, comp), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
comp.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
EntityManager.System<AccessReaderSystem>().SetActive((wire.Owner, comp), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
comp.Enabled = false;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
EntityManager.System<AccessReaderSystem>().SetActive((wire.Owner, comp), false);
|
||||
WiresSystem.StartWireAction(wire.Owner, _pulseTimeout, PulseTimeoutKey.Key, new TimedWireEvent(AwaitPulseCancel, wire));
|
||||
}
|
||||
|
||||
@@ -57,8 +56,7 @@ public sealed partial class AccessWireAction : ComponentWireAction<AccessReaderC
|
||||
{
|
||||
if (EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var access))
|
||||
{
|
||||
access.Enabled = true;
|
||||
EntityManager.Dirty(wire.Owner, access);
|
||||
EntityManager.System<AccessReaderSystem>().SetActive((wire.Owner, access), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Syntax;
|
||||
|
||||
namespace Content.Server.Access;
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class AddAccessLogCommand : ToolshedCommand
|
||||
ctx.WriteLine($"WARNING: Surpassing the limit of the log by {accessLogCount - accessReader.AccessLogLimit+1} entries!");
|
||||
|
||||
var accessTime = TimeSpan.FromSeconds(seconds);
|
||||
accessReader.AccessLog.Enqueue(new AccessRecord(accessTime, accessor));
|
||||
EntityManager.System<AccessReaderSystem>().LogAccess((input, accessReader), accessor, accessTime, true);
|
||||
ctx.WriteLine($"Successfully added access log to {input} with this information inside:\n " +
|
||||
$"Time of access: {accessTime}\n " +
|
||||
$"Accessed by: {accessor}");
|
||||
|
||||
@@ -37,21 +37,21 @@ public sealed partial class LogWireAction : ComponentWireAction<AccessReaderComp
|
||||
public override bool Cut(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
WiresSystem.TryCancelWireAction(wire.Owner, PulseTimeoutKey.Key);
|
||||
comp.LoggingDisabled = true;
|
||||
EntityManager.Dirty(wire.Owner, comp);
|
||||
EntityManager.System<AccessReaderSystem>().SetLoggingActive((wire.Owner, comp), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
comp.LoggingDisabled = false;
|
||||
EntityManager.System<AccessReaderSystem>().SetLoggingActive((wire.Owner, comp), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, AccessReaderComponent comp)
|
||||
{
|
||||
_access.LogAccess((wire.Owner, comp), Loc.GetString(PulseLog));
|
||||
comp.LoggingDisabled = true;
|
||||
EntityManager.System<AccessReaderSystem>().SetLoggingActive((wire.Owner, comp), false);
|
||||
WiresSystem.StartWireAction(wire.Owner, PulseTimeout, PulseTimeoutKey.Key, new TimedWireEvent(AwaitPulseCancel, wire));
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed partial class LogWireAction : ComponentWireAction<AccessReaderComp
|
||||
private void AwaitPulseCancel(Wire wire)
|
||||
{
|
||||
if (!wire.IsCut && EntityManager.TryGetComponent<AccessReaderComponent>(wire.Owner, out var comp))
|
||||
comp.LoggingDisabled = false;
|
||||
EntityManager.System<AccessReaderSystem>().SetLoggingActive((wire.Owner, comp), true);
|
||||
}
|
||||
|
||||
private enum PulseTimeoutKey : byte
|
||||
|
||||
@@ -168,21 +168,6 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
return accessList;
|
||||
}
|
||||
|
||||
private List<HashSet<ProtoId<AccessLevelPrototype>>> ConvertAccessListToHashSet(List<ProtoId<AccessLevelPrototype>> accessList)
|
||||
{
|
||||
List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets = new List<HashSet<ProtoId<AccessLevelPrototype>>>();
|
||||
|
||||
if (accessList != null && accessList.Any())
|
||||
{
|
||||
foreach (ProtoId<AccessLevelPrototype> access in accessList)
|
||||
{
|
||||
accessHashsets.Add(new HashSet<ProtoId<AccessLevelPrototype>>() { access });
|
||||
}
|
||||
}
|
||||
|
||||
return accessHashsets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader.
|
||||
/// </summary>
|
||||
@@ -244,12 +229,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
|
||||
_adminLogger.Add(LogType.Action, LogImpact.High,
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList);
|
||||
_accessReader.SetAccesses(accessReaderEnt.Value, newAccessList);
|
||||
|
||||
var ev = new OnAccessOverriderAccessUpdatedEvent(player);
|
||||
RaiseLocalEvent(component.TargetAccessReaderId, ref ev);
|
||||
|
||||
Dirty(accessReaderEnt.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Content.Server.Actions;
|
||||
public sealed class ActionOnInteractSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly ActionContainerSystem _actionContainer = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -8,32 +8,30 @@ using Robust.Shared.Utility;
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.AdminWho)]
|
||||
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,35 +3,29 @@ 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;
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,10 @@ namespace Content.Server.Administration.Commands;
|
||||
public sealed class PersistenceSave : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _mapLoader = default!;
|
||||
|
||||
public override string Command => "persistencesave";
|
||||
public override string Description => "Saves server data to a persistence file to be loaded later.";
|
||||
public override string Help => "persistencesave [mapId] [filePath - default: game.map (CCVar) ]";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
@@ -47,8 +45,7 @@ public sealed class PersistenceSave : LocalizedEntityCommands
|
||||
return;
|
||||
}
|
||||
|
||||
var mapLoader = _system.GetEntitySystem<MapLoaderSystem>();
|
||||
mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
|
||||
_mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath));
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -21,6 +22,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";
|
||||
@@ -148,7 +150,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,10 +1,10 @@
|
||||
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.Clothing.Systems;
|
||||
using Content.Server.Electrocution;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.GhostKick;
|
||||
@@ -42,7 +42,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;
|
||||
@@ -80,6 +79,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!;
|
||||
|
||||
// All smite verbs have names so invokeverb works.
|
||||
private void AddSmiteVerbs(GetVerbsEvent<Verb> args)
|
||||
@@ -416,7 +416,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);
|
||||
@@ -587,7 +587,7 @@ public sealed partial class AdminVerbSystem
|
||||
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);
|
||||
@@ -685,7 +685,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");
|
||||
|
||||
@@ -38,18 +38,21 @@ public sealed class SolutionCommand : ToolshedCommand
|
||||
public SolutionRef AdjReagent(
|
||||
[PipedArgument] SolutionRef input,
|
||||
ProtoId<ReagentPrototype> proto,
|
||||
FixedPoint2 amount
|
||||
float amount
|
||||
)
|
||||
{
|
||||
_solutionContainer ??= GetSys<SharedSolutionContainerSystem>();
|
||||
|
||||
if (amount > 0)
|
||||
// Convert float to FixedPoint2
|
||||
var amountFixed = FixedPoint2.New(amount);
|
||||
|
||||
if (amountFixed > 0)
|
||||
{
|
||||
_solutionContainer.TryAddReagent(input.Solution, proto, amount, out _);
|
||||
_solutionContainer.TryAddReagent(input.Solution, proto, amountFixed, out _);
|
||||
}
|
||||
else if (amount < 0)
|
||||
else if (amountFixed < 0)
|
||||
{
|
||||
_solutionContainer.RemoveReagent(input.Solution, proto, -amount);
|
||||
_solutionContainer.RemoveReagent(input.Solution, proto, -amountFixed);
|
||||
}
|
||||
|
||||
return input;
|
||||
@@ -59,7 +62,7 @@ public sealed class SolutionCommand : ToolshedCommand
|
||||
public IEnumerable<SolutionRef> AdjReagent(
|
||||
[PipedArgument] IEnumerable<SolutionRef> input,
|
||||
ProtoId<ReagentPrototype> name,
|
||||
FixedPoint2 amount
|
||||
float amount
|
||||
)
|
||||
=> input.Select(x => AdjReagent(x, name, amount));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,6 +7,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;
|
||||
|
||||
@@ -14,12 +15,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -105,10 +108,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>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Bed.Components;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Bed;
|
||||
using Content.Shared.Bed.Components;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Emag.Systems;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.EntityEffects.Effects;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reaction;
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Drunk;
|
||||
using Content.Shared.EntityEffects.Effects;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Forensics;
|
||||
using Content.Shared.Forensics.Components;
|
||||
@@ -204,10 +205,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;
|
||||
@@ -226,7 +230,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);
|
||||
}
|
||||
|
||||
@@ -247,18 +251,30 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void OnHealthBeingExamined(Entity<BloodstreamComponent> ent, ref HealthBeingExaminedEvent args)
|
||||
{
|
||||
// Shows profusely bleeding at half the max bleed rate.
|
||||
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount / 2)
|
||||
// Shows massively bleeding at 0.75x the max bleed rate.
|
||||
if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.75f)
|
||||
{
|
||||
args.Message.PushNewline();
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-profusely-bleeding", ("target", ent.Owner)));
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-massive-bleeding", ("target", ent.Owner)));
|
||||
}
|
||||
// Shows bleeding message when bleeding, but less than profusely.
|
||||
else if (ent.Comp.BleedAmount > 0)
|
||||
// Shows bleeding message when bleeding above half the max rate, but less than massively.
|
||||
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.5f)
|
||||
{
|
||||
args.Message.PushNewline();
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-strong-bleeding", ("target", ent.Owner)));
|
||||
}
|
||||
// Shows bleeding message when bleeding above 0.25x the max rate, but less than half the max.
|
||||
else if (ent.Comp.BleedAmount > ent.Comp.MaxBleedAmount * 0.25f)
|
||||
{
|
||||
args.Message.PushNewline();
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-bleeding", ("target", ent.Owner)));
|
||||
}
|
||||
// Shows bleeding message when bleeding below 0.25x the max cap
|
||||
else if (ent.Comp.BleedAmount > 0)
|
||||
{
|
||||
args.Message.PushNewline();
|
||||
args.Message.AddMarkupOrThrow(Loc.GetString("bloodstream-component-slight-bleeding", ("target", ent.Owner)));
|
||||
}
|
||||
|
||||
// If the mob's blood level is below the damage threshhold, the pale message is added.
|
||||
if (GetBloodLevelPercentage(ent, ent) < ent.Comp.BloodlossThreshold)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
@@ -11,8 +14,6 @@ using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Damage.Components;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Body.Events;
|
||||
using Content.Shared.Body.Organ;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.EntityEffects;
|
||||
@@ -231,29 +232,4 @@ namespace Content.Server.Body.Systems
|
||||
_solutionContainerSystem.UpdateChemicals(soln.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO REFACTOR THIS
|
||||
// This will cause rates to slowly drift over time due to floating point errors.
|
||||
// Instead, the system that raised this should trigger an update and subscribe to get-modifier events.
|
||||
[ByRefEvent]
|
||||
public readonly record struct ApplyMetabolicMultiplierEvent(
|
||||
EntityUid Uid,
|
||||
float Multiplier,
|
||||
bool Apply)
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity whose metabolism is being modified.
|
||||
/// </summary>
|
||||
public readonly EntityUid Uid = Uid;
|
||||
|
||||
/// <summary>
|
||||
/// What the metabolism's update rate will be multiplied by.
|
||||
/// </summary>
|
||||
public readonly float Multiplier = Multiplier;
|
||||
|
||||
/// <summary>
|
||||
/// If true, apply the multiplier. If false, revert it.
|
||||
/// </summary>
|
||||
public readonly bool Apply = Apply;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user