Merge pull request #853 from space-syndicate/upstream-sync

Upstream sync
This commit is contained in:
Morb 2023-02-28 09:55:55 -08:00 • committed by GitHub
commit 090aa51074
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
153 changed files with 30152 additions and 1739 deletions

View file

@ -71,3 +71,9 @@ jobs:
username: ${{ secrets.BUILDS_USERNAME }}
key: ${{ secrets.BUILDS_SSH_KEY }}
script: node ~/scripts/push_to_manifest.js -fork syndicate -id ${{ github.sha }}
- name: Publish changelog
run: Tools/actions_changelogs_since_last_run.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}

View file

@ -1,5 +1,6 @@
using Content.Shared.Verbs;
using Robust.Client.Console;
using Robust.Shared.Utility;
namespace Content.Client.Administration.Systems
{
@ -26,7 +27,7 @@ namespace Content.Client.Administration.Systems
Verb verb = new();
verb.Category = VerbCategory.Debug;
verb.Text = "View Variables";
verb.IconTexture = "/Textures/Interface/VerbIcons/vv.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/vv.svg.192dpi.png"));
verb.Act = () => _clientConsoleHost.ExecuteCommand($"vv {args.Target}");
verb.ClientExclusive = true; // opening VV window is client-side. Don't ask server to run this verb.
args.Verbs.Add(verb);

View file

@ -32,6 +32,8 @@
<BoxContainer Orientation="Vertical" MinWidth="200">
<LineEdit Name="PlayerSearch" Access="Public" StyleClasses="actionSearchBox"
HorizontalExpand="true" PlaceHolder="{Loc admin-logs-search-players-placeholder}"/>
<Button Name="IncludeNonPlayersButton" Text="{Loc admin-logs-include-non-player}"
MinWidth="100" StyleClasses="ButtonSquare" ToggleMode="True" />
<BoxContainer Orientation="Horizontal">
<Button Name="SelectAllPlayersButton" Text="{Loc admin-logs-select-all}"
MinWidth="100" StyleClasses="ButtonSquare" />

View file

@ -34,6 +34,7 @@ public sealed partial class AdminLogsControl : Control
SelectAllTypesButton.OnPressed += SelectAllTypes;
SelectNoTypesButton.OnPressed += SelectNoTypes;
IncludeNonPlayersButton.OnPressed += IncludeNonPlayers;
SelectAllPlayersButton.OnPressed += SelectAllPlayers;
SelectNoPlayersButton.OnPressed += SelectNoPlayers;
@ -53,6 +54,7 @@ public sealed partial class AdminLogsControl : Control
public string Search => LogSearch.Text;
private int ShownLogs { get; set; }
private int TotalLogs { get; set; }
public bool IncludeNonPlayerLogs { get; set; }
public HashSet<LogType> SelectedTypes { get; } = new();
@ -139,6 +141,13 @@ public sealed partial class AdminLogsControl : Control
UpdateLogs();
}
private void IncludeNonPlayers(ButtonEventArgs args)
{
IncludeNonPlayerLogs = args.Button.Pressed;
UpdateLogs();
}
private void SelectAllPlayers(ButtonEventArgs args)
{
SelectedPlayers.Clear();
@ -259,12 +268,33 @@ public sealed partial class AdminLogsControl : Control
button.Text.Contains(PlayerSearch.Text, StringComparison.OrdinalIgnoreCase);
}
private bool LogMatchesPlayerFilter(AdminLogLabel label)
{
if (label.Log.Players.Length == 0)
return SelectedPlayers.Count == 0 || IncludeNonPlayerLogs;
return SelectedPlayers.Overlaps(label.Log.Players);
}
private bool ShouldShowLog(AdminLogLabel label)
{
return SelectedTypes.Contains(label.Log.Type) &&
(SelectedPlayers.Count + label.Log.Players.Length == 0 || SelectedPlayers.Overlaps(label.Log.Players)) &&
SelectedImpacts.Contains(label.Log.Impact) &&
label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase);
// Check log type
if (!SelectedTypes.Contains(label.Log.Type))
return false;
// Check players
if (!LogMatchesPlayerFilter(label))
return false;
// Check impact
if (!SelectedImpacts.Contains(label.Log.Impact))
return false;
// Check search
if (!label.Log.Message.Contains(LogSearch.Text, StringComparison.OrdinalIgnoreCase))
return false;
return true;
}
private void TypeButtonPressed(ButtonEventArgs args)
@ -481,6 +511,7 @@ public sealed partial class AdminLogsControl : Control
SelectAllTypesButton.OnPressed -= SelectAllTypes;
SelectNoTypesButton.OnPressed -= SelectNoTypes;
IncludeNonPlayersButton.OnPressed -= IncludeNonPlayers;
SelectAllPlayersButton.OnPressed -= SelectAllPlayers;
SelectNoPlayersButton.OnPressed -= SelectNoPlayers;

View file

@ -51,8 +51,10 @@ public sealed class AdminLogsEui : BaseEui
null,
null,
null,
LogsControl.SelectedPlayers.Count != 0,
LogsControl.SelectedPlayers.ToArray(),
null,
LogsControl.IncludeNonPlayerLogs,
null,
DateOrder.Descending);

View file

@ -41,7 +41,7 @@ namespace Content.Client.Audio
private float _ambienceVolume = 0.0f;
private static AudioParams _params = AudioParams.Default.WithVariation(0.01f).WithLoop(true).WithAttenuation(Attenuation.LinearDistance);
/// <summary>
/// How many times we can be playing 1 particular sound at once.
/// </summary>
@ -221,13 +221,18 @@ namespace Content.Client.Audio
private void ProcessNearbyAmbience(TransformComponent playerXform)
{
var query = GetEntityQuery<TransformComponent>();
var metaQuery = GetEntityQuery<MetaDataComponent>();
var mapPos = playerXform.MapPosition;
// Remove out-of-range ambiences
foreach (var (comp, sound) in _playingSounds)
{
var entity = comp.Owner;
if (comp.Enabled && query.TryGetComponent(entity, out var xform) && xform.MapID == playerXform.MapID)
if (comp.Enabled &&
query.TryGetComponent(entity, out var xform) &&
xform.MapID == playerXform.MapID &&
!metaQuery.GetComponent(entity).EntityPaused)
{
var distance = (xform.ParentUid == playerXform.ParentUid)
? xform.LocalPosition - playerXform.LocalPosition
@ -265,7 +270,10 @@ namespace Content.Client.Audio
foreach (var (_, comp) in sources)
{
if (_playingSounds.ContainsKey(comp))
var uid = comp.Owner;
if (_playingSounds.ContainsKey(comp) ||
metaQuery.GetComponent(uid).EntityPaused)
continue;
var audioParams = _params
@ -274,7 +282,7 @@ namespace Content.Client.Audio
.WithPlayOffset(_random.NextFloat(0.0f, 100.0f))
.WithMaxDistance(comp.Range);
var stream = _audio.PlayPvs(comp.Sound, comp.Owner, audioParams);
var stream = _audio.PlayPvs(comp.Sound, uid, audioParams);
if (stream == null)
continue;

View file

@ -49,11 +49,9 @@ public sealed class ExamineButton : ContainerButton
SetHeight = ElementHeight
};
if (verb.IconTexture != null)
if (verb.Icon != null)
{
var icon = new SpriteSpecifier.Texture(new ResourcePath(verb.IconTexture));
Icon.Texture = icon.Frame0();
Icon.Texture = verb.Icon.Frame0();
Icon.Stretch = TextureRect.StretchMode.KeepAspectCentered;
AddChild(Icon);

View file

@ -49,7 +49,7 @@ namespace Content.Client.Examine
CommandBinds.Builder
.Bind(ContentKeyFunctions.ExamineEntity, new PointerInputCmdHandler(HandleExamine, outsidePrediction: true))
.Register<ExamineSystem>();
_idCounter = 0;
}
@ -117,7 +117,7 @@ namespace Content.Client.Examine
// Center it on the entity if they use the verb instead.
verb.Act = () => DoExamine(args.Target, false);
verb.Text = Loc.GetString("examine-verb-name");
verb.IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/examine.svg.192dpi.png"));
verb.ShowOnExamineTooltip = false;
verb.ClientExclusive = true;
args.Verbs.Add(verb);
@ -132,7 +132,7 @@ namespace Content.Client.Examine
// Prevent updating a new tooltip.
if (ev.Id != 0 && ev.Id != _idCounter)
return;
// Tooltips coming in from the server generally prioritize
// opening at the old tooltip rather than the cursor/another entity,
// since there's probably one open already if it's coming in from the server.
@ -286,7 +286,7 @@ namespace Content.Client.Examine
if (verb is not ExamineVerb examine)
continue;
if (examine.IconTexture == null)
if (examine.Icon == null)
continue;
if (!examine.ShowOnExamineTooltip)

View file

@ -14,6 +14,7 @@ using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook;
@ -56,7 +57,7 @@ public sealed class GuidebookSystem : EntitySystem
args.Verbs.Add(new()
{
Text = Loc.GetString("guide-help-verb"),
IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
Act = () => OpenGuidebook(component.Guides, includeChildren: component.IncludeChildren, selected: component.Guides[0]),
ClientExclusive = true,
CloseMenu = true

View file

@ -4,6 +4,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Pointing;
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
using DrawDepth = Content.Shared.DrawDepth.DrawDepth;
namespace Content.Client.Pointing;
@ -54,7 +55,7 @@ public sealed class PointingSystem : SharedPointingSystem
Verb verb = new()
{
Text = Loc.GetString("pointing-verb-get-data-text"),
IconTexture = "/Textures/Interface/VerbIcons/point.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/point.svg.192dpi.png")),
ClientExclusive = true,
Act = () => RaiseNetworkEvent(new PointingAttemptEvent(args.Target))
};

View file

@ -0,0 +1,10 @@
using Content.Shared.Storage.Components;
using Robust.Shared.GameStates;
namespace Content.Client.Storage.Components;
[RegisterComponent, ComponentReference(typeof(SharedEntityStorageComponent))]
public sealed class EntityStorageComponent : SharedEntityStorageComponent
{
}

View file

@ -0,0 +1,8 @@
using Content.Shared.Storage.EntitySystems;
namespace Content.Client.Storage.Systems;
public sealed class EntityStorageSystem : SharedEntityStorageSystem
{
}

View file

@ -13,7 +13,10 @@
VerticalExpand="False"
VerticalAlignment="Bottom"
HorizontalAlignment="Center">
<SplitContainer Name="ScreenContainer" HorizontalExpand="True" VerticalExpand="True">
<SplitContainer Name="ScreenContainer"
HorizontalExpand="True"
VerticalExpand="True"
SplitWidth="0">
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
<controls:MainViewport Name="MainViewport"/>
<widgets:GhostGui Name="Ghost" Access="Protected" />

View file

@ -17,59 +17,60 @@ namespace Content.IntegrationTests.Tests.Access
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
var server = pairTracker.Pair.Server;
await server.WaitAssertion(() =>
{
var system = EntitySystem.Get<AccessReaderSystem>();
// test empty
var reader = new AccessReaderComponent();
Assert.That(system.IsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "Bar" }, reader), Is.True);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "Bar" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.True);
// test deny
reader = new AccessReaderComponent();
reader.DenyTags.Add("A");
Assert.That(system.IsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "A", "Foo" }, reader), Is.False);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "Foo" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.True);
// test one list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False);
// test one list - two items
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A", "B" });
Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False);
// test two list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
reader.AccessLists.Add(new HashSet<string> { "B", "C" });
Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "C", "B" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "C", "B", "A" }, reader), Is.True);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B", "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False);
// test deny list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
reader.DenyTags.Add("B");
Assert.That(system.IsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.IsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.IsAllowed(new[] { "A", "B" }, reader), Is.False);
Assert.That(system.IsAllowed(new string[] { }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new string[] { }, reader), Is.False);
});
await pairTracker.CleanReturnAsync();
}

View file

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using Content.Server.Salvage;
using NUnit.Framework;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests
{
[TestFixture]
public sealed class SalvageTest
{
[Test]
public async Task SalvageGridBoundsTest()
{
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
var server = pairTracker.Pair.Server;
await server.WaitIdleAsync();
var mapMan = server.ResolveDependency<IMapManager>();
var protoManager = server.ResolveDependency<IPrototypeManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
await server.WaitAssertion(() =>
{
foreach (var salvage in protoManager.EnumeratePrototypes<SalvageMapPrototype>())
{
var mapId = mapMan.CreateMap();
mapLoader.TryLoad(mapId, salvage.MapPath.ToString(), out var rootUids);
Assert.That(rootUids is { Count: 1 }, $"Salvage map {salvage.ID} does not have a single grid");
var grid = rootUids[0];
Assert.That(entManager.TryGetComponent<MapGridComponent>(grid, out var gridComp), $"Salvage {salvage.ID}'s grid does not have GridComponent.");
Assert.That(gridComp.LocalAABB, Is.EqualTo(salvage.Bounds), $"Salvage {salvage.ID}'s bounds {gridComp.LocalAABB} are not equal to the bounds on the prototype {salvage.Bounds}");
}
});
await pairTracker.CleanReturnAsync();
}
}
}

View file

@ -1,6 +1,6 @@
using System.Linq;
using Content.Server.Station.Systems;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Logs;

View file

@ -36,7 +36,7 @@ public sealed class IdExaminableSystem : EntitySystem
Category = VerbCategory.Examine,
Disabled = !detailsRange,
Message = Loc.GetString("id-examinable-component-verb-disabled"),
IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/character.svg.192dpi.png"))
};
args.Verbs.Add(verb);

View file

@ -122,14 +122,25 @@ public sealed partial class AdminLogManager
query = query.Where(log => log.Date > filter.After);
}
if (filter.AnyPlayers != null)
if (filter.IncludePlayers)
{
query = query.Where(log => filter.AnyPlayers.Any(filterPlayer => log.Players.Contains(filterPlayer)));
}
if (filter.AnyPlayers != null)
{
query = query.Where(log =>
filter.AnyPlayers.Any(filterPlayer => log.Players.Contains(filterPlayer)) ||
log.Players.Length == 0 && filter.IncludeNonPlayers);
}
if (filter.AllPlayers != null)
if (filter.AllPlayers != null)
{
query = query.Where(log =>
filter.AllPlayers.All(filterPlayer => log.Players.Contains(filterPlayer)) ||
log.Players.Length == 0 && filter.IncludeNonPlayers);
}
}
else
{
query = query.Where(log => filter.AllPlayers.All(filterPlayer => log.Players.Contains(filterPlayer)));
query = query.Where(log => log.Players.Length == 0);
}
if (filter.LogsSent != 0)

View file

@ -119,8 +119,10 @@ public sealed class AdminLogsEui : BaseEui
Impacts = request.Impacts,
Before = request.Before,
After = request.After,
IncludePlayers = request.IncludePlayers,
AnyPlayers = request.AnyPlayers,
AllPlayers = request.AllPlayers,
IncludeNonPlayers = request.IncludeNonPlayers,
LastLogId = 0,
Limit = _clientBatchSize
};

View file

@ -20,10 +20,14 @@ public sealed class LogFilter
public DateTime? After { get; set; }
public bool IncludePlayers { get; set; } = true;
public Guid[]? AnyPlayers { get; set; }
public Guid[]? AllPlayers { get; set; }
public bool IncludeNonPlayers { get; set; }
public int? LastLogId { get; set; }
public int LogsSent { get; set; }

View file

@ -57,6 +57,14 @@ namespace Content.Server.Administration.Managers
return null;
}
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
{
if (_playerManager.TryGetSessionByEntity(uid, out var session) && session is IPlayerSession playerSession)
return GetAdminData(playerSession, includeDeAdmin);
return null;
}
public void DeAdmin(IPlayerSession session)
{
if (!_admins.TryGetValue(session, out var reg))

View file

@ -1,4 +1,4 @@
using Content.Shared.Administration;
using Content.Shared.Administration;
using Robust.Server.Player;
@ -47,6 +47,26 @@ namespace Content.Server.Administration.Managers
/// <returns><see langword="null" /> if the player is not an admin.</returns>
AdminData? GetAdminData(IPlayerSession session, bool includeDeAdmin = false);
/// <summary>
/// Gets the admin data for a player, if they are an admin.
/// </summary>
/// <param name="uid">The entity being controlled by the player.</param>
/// <param name="includeDeAdmin">
/// Whether to return admin data for admins that are current de-adminned.
/// </param>
/// <returns><see langword="null" /> if the player is not an admin.</returns>
AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false);
/// <summary>
/// See if a player has an admin flag.
/// </summary>
/// <returns>True if the player is and admin and has the specified flags.</returns>
bool HasAdminFlag(EntityUid player, AdminFlags flag)
{
var data = GetAdminData(player);
return data != null && data.HasFlag(flag);
}
/// <summary>
/// See if a player has an admin flag.
/// </summary>

View file

@ -3,6 +3,7 @@ using Content.Shared.Database;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Notes;
@ -33,7 +34,7 @@ public sealed class AdminNotesSystem : EntitySystem
{
Text = Loc.GetString("admin-notes-verb-text"),
Category = VerbCategory.Admin,
IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/examine.svg.192dpi.png")),
Act = () => _console.RemoteExecuteCommand(user, $"{OpenAdminNotesCommand.CommandName} \"{target.UserId}\""),
Impact = LogImpact.Low
};

View file

@ -6,6 +6,7 @@ using Content.Shared.Database;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Systems;
@ -35,7 +36,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Traitor",
Category = VerbCategory.Antag,
IconTexture = "/Textures/Structures/Wallmounts/posters.rsi/poster5_contraband.png",
Icon = new SpriteSpecifier.Rsi((new ResourcePath("/Textures/Structures/Wallmounts/posters.rsi")), "poster5_contraband"),
Act = () =>
{
if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null)
@ -52,7 +53,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Zombie",
Category = VerbCategory.Antag,
IconTexture = "/Textures/Structures/Wallmounts/signs.rsi/bio.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Wallmounts/signs.rsi"), "bio"),
Act = () =>
{
TryComp(args.Target, out MindComponent? mindComp);
@ -71,7 +72,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make nuclear operative",
Category = VerbCategory.Antag,
IconTexture = "/Textures/Structures/Wallmounts/signs.rsi/radiation.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"),
Act = () =>
{
if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null)
@ -88,7 +89,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Pirate",
Category = VerbCategory.Antag,
IconTexture = "/Textures/Clothing/Head/Hats/pirate.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"),
Act = () =>
{
if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null)

View file

@ -50,6 +50,7 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Administration.Systems;
@ -71,7 +72,6 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
[Dependency] private readonly PolymorphableSystem _polymorphableSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
@ -98,7 +98,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Explode",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
Act = () =>
{
var coords = Transform(args.Target).MapPosition;
@ -118,7 +118,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Chess Dimension",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Fun/Tabletop/chessboard.rsi/chessboard.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/Tabletop/chessboard.rsi"), "chessboard"),
Act = () =>
{
_godmodeSystem.EnableGodmode(args.Target); // So they don't suffocate.
@ -146,7 +146,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Set Alight",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/Alerts/Fire/fire.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Alerts/Fire/fire.png")),
Act = () =>
{
// Fuck you. Burn Forever.
@ -168,7 +168,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Monkeyify",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Mobs/Animals/monkey.rsi/dead.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Animals/monkey.rsi"), "dead"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
@ -182,7 +182,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Garbage Can",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Structures/Piping/disposal.rsi/disposal.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Piping/disposal.rsi"), "disposal"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite");
@ -198,7 +198,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Lung Cancer",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/lung-l.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Species/Human/organs.rsi"), "lung-l"),
Act = () =>
{
_diseaseSystem.TryInfect(carrier, _prototypeManager.Index<DiseasePrototype>("StageIIIALungCancer"),
@ -211,13 +211,13 @@ public sealed partial class AdminVerbSystem
}
if (TryComp<DamageableComponent>(args.Target, out var damageable) &&
TryComp<MobStateComponent>(args.Target, out var mobState))
HasComp<MobStateComponent>(args.Target))
{
Verb hardElectrocute = new()
{
Text = "Electrocute",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Clothing/Hands/Gloves/Color/yellow.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Hands/Gloves/Color/yellow.rsi"), "icon"),
Act = () =>
{
int damageToDeal;
@ -262,7 +262,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Creampie",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Consumable/Food/Baked/pie.rsi/plain-slice.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"),
Act = () =>
{
_creamPieSystem.SetCreamPied(args.Target, creamPied, true);
@ -279,7 +279,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Remove blood",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Fluids/tomato_splat.rsi/puddle-1.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Fluids/tomato_splat.rsi"), "puddle-1"),
Act = () =>
{
_bloodstreamSystem.SpillAllSolutions(args.Target, bloodstream);
@ -302,7 +302,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Vomit organs",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Fluids/vomit_toxin.rsi/vomit_toxin-1.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Fluids/vomit_toxin.rsi"), "vomit_toxin-1"),
Act = () =>
{
_vomitSystem.Vomit(args.Target, -1000, -1000); // You feel hollow!
@ -331,7 +331,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Remove hands",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/remove-hands.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/remove-hands.png")),
Act = () =>
{
var baseXform = Transform(args.Target);
@ -353,7 +353,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Remove hands",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/remove-hand.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/remove-hand.png")),
Act = () =>
{
var baseXform = Transform(args.Target);
@ -376,7 +376,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Stomach Removal",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/stomach.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"),
Act = () =>
{
foreach (var (component, _) in _bodySystem.GetBodyOrganComponents<StomachComponent>(args.Target, body))
@ -396,7 +396,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Lungs Removal",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Mobs/Species/Human/organs.rsi/lung-r.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"),
Act = () =>
{
foreach (var (component, _) in _bodySystem.GetBodyOrganComponents<LungComponent>(args.Target, body))
@ -419,7 +419,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Pinball",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Fun/toys.rsi/basketball.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/toys.rsi"), "basketball"),
Act = () =>
{
var xform = Transform(args.Target);
@ -453,7 +453,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Yeet",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
Act = () =>
{
var xform = Transform(args.Target);
@ -484,7 +484,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Become Bread",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Consumable/Food/Baked/bread.rsi/plain.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Consumable/Food/Baked/bread.rsi"), "plain"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
@ -498,7 +498,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Become Mouse",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Mobs/Animals/mouse.rsi/icon-0.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Animals/mouse.rsi"), "icon-0"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminMouseSmite");
@ -514,7 +514,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Ghostkick",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/gavel.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/gavel.svg.192dpi.png")),
Act = () =>
{
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.ConnectedClient, "Smitten.");
@ -530,7 +530,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Nyanify",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Clothing/Head/Hats/catears.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Head/Hats/catears.rsi"), "icon"),
Act = () =>
{
var ears = Spawn("ClothingHeadHatCatEars", Transform(args.Target).Coordinates);
@ -547,7 +547,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Kill sign",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Misc/killsign.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Misc/killsign.rsi"), "icon"),
Act = () =>
{
EnsureComp<KillSignComponent>(args.Target);
@ -562,7 +562,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Clown",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Fun/bikehorn.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/bikehorn.rsi"), "icon"),
Act = () =>
{
SetOutfitCommand.SetOutfit(args.Target, "ClownGear", EntityManager, (_, clothing) =>
@ -581,7 +581,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Maid",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi"), "icon"),
Act = () =>
{
SetOutfitCommand.SetOutfit(args.Target, "JanitorMaidGear", EntityManager, (_, clothing) =>
@ -595,15 +595,13 @@ public sealed partial class AdminVerbSystem
Message = Loc.GetString("admin-smite-maid-description")
};
args.Verbs.Add(maiden);
}
Verb angerPointingArrows = new()
{
Text = "Anger Pointing Arrows",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/Misc/pointing.rsi/pointing.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Interface/Misc/pointing.rsi"), "pointing"),
Act = () =>
{
EnsureComp<PointingArrowAngeringComponent>(args.Target);
@ -617,7 +615,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Dust",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Materials/materials.rsi/ash.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Materials/materials"), "ash"),
Act = () =>
{
EntityManager.QueueDeleteEntity(args.Target);
@ -633,7 +631,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Buffering",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/Misc/buffering_smite_icon.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Misc/buffering_smite_icon.png")),
Act = () =>
{
EnsureComp<BufferingComponent>(args.Target);
@ -647,7 +645,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Become Instrument",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Fun/Instruments/h_synthesizer.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/Instruments/h_synthesizer.rsi"), "icon"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
@ -661,7 +659,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Remove gravity",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Structures/Machines/gravity_generator.rsi/off.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Machines/gravity_generator.rsi"), "off"),
Act = () =>
{
var grav = EnsureComp<MovementIgnoreGravityComponent>(args.Target);
@ -678,7 +676,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Reptilian Species Swap",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Objects/Fun/toys.rsi/plushie_lizard.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"),
Act = () =>
{
_polymorphableSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
@ -692,7 +690,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Locker stuff",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Structures/Storage/closet.rsi/generic.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Structures/Storage/closet.rsi"), "generic"),
Act = () =>
{
var xform = Transform(args.Target);
@ -714,7 +712,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Headstand",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")),
Act = () =>
{
EnsureComp<HeadstandComponent>(args.Target);
@ -728,7 +726,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Zoom in",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/zoom.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/zoom.png")),
Act = () =>
{
var eye = EnsureComp<EyeComponent>(args.Target);
@ -746,7 +744,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Flip eye",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/flip.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/flip.png")),
Act = () =>
{
var eye = EnsureComp<EyeComponent>(args.Target);
@ -764,7 +762,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Run Walk Swap",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/run-walk-swap.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/run-walk-swap.png")),
Act = () =>
{
var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(args.Target);
@ -784,7 +782,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Speak Backwards",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/help-backwards.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/help-backwards.png")),
Act = () =>
{
EnsureComp<BackwardsAccentComponent>(args.Target);
@ -798,7 +796,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Disarm Prone",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/Actions/disarm.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/Actions/disarm.png")),
Act = () =>
{
EnsureComp<DisarmProneComponent>(args.Target);
@ -812,7 +810,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Super speed",
Category = VerbCategory.Smite,
IconTexture = "/Textures/Interface/AdminActions/super_speed.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/super_speed.png")),
Act = () =>
{
var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(args.Target);

View file

@ -36,6 +36,7 @@ using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Systems;
@ -68,9 +69,9 @@ public sealed partial class AdminVerbSystem
{
Text = airlock.BoltsDown ? "Unbolt" : "Bolt",
Category = VerbCategory.Tricks,
IconTexture = airlock.BoltsDown
? "/Textures/Interface/AdminActions/unbolt.png"
: "/Textures/Interface/AdminActions/bolt.png",
Icon = airlock.BoltsDown
? new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/unbolt.png"))
: new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/bolt.png")),
Act = () =>
{
_airlockSystem.SetBoltsWithAudio(args.Target, airlock, !airlock.BoltsDown);
@ -88,7 +89,7 @@ public sealed partial class AdminVerbSystem
{
Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/emergency_access.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/emergency_access.png")),
Act = () =>
{
_airlockSystem.ToggleEmergencyAccess(args.Target, airlock);
@ -110,7 +111,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Rejuvenate",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/rejuvenate.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/rejuvenate.png")),
Act = () =>
{
RejuvenateCommand.PerformRejuvenate(args.Target);
@ -128,7 +129,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Indestructible",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/VerbIcons/plus.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
Act = () =>
{
_godmodeSystem.EnableGodmode(args.Target);
@ -145,7 +146,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Vulnerable",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/VerbIcons/plus.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/plus.svg.192dpi.png")),
Act = () =>
{
_godmodeSystem.DisableGodmode(args.Target);
@ -163,7 +164,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/fill_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/fill_battery.png")),
Act = () =>
{
battery.CurrentCharge = battery.MaxCharge;
@ -179,7 +180,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Drain Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/drain_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/drain_battery.png")),
Act = () =>
{
battery.CurrentCharge = 0;
@ -195,7 +196,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Infinite Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/infinite_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/infinite_battery.png")),
Act = () =>
{
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
@ -215,7 +216,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Block Unanchoring",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/VerbIcons/anchor.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/anchor.svg.192dpi.png")),
Act = () =>
{
RemComp(args.Target, anchor);
@ -233,7 +234,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Internals Oxygen",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/oxygen.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
Act = () =>
{
RefillGasTank(args.Target, Gas.Oxygen, tank);
@ -248,7 +249,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Internals Nitrogen",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/red.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/red.rsi"), "icon"),
Act = () =>
{
RefillGasTank(args.Target, Gas.Nitrogen, tank);
@ -263,7 +264,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Internals Plasma",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/plasma.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
Act = () =>
{
RefillGasTank(args.Target, Gas.Plasma, tank);
@ -275,13 +276,13 @@ public sealed partial class AdminVerbSystem
args.Verbs.Add(refillInternalsPlasma);
}
if (TryComp<InventoryComponent>(args.Target, out var inventory))
if (HasComp<InventoryComponent>(args.Target))
{
Verb refillInternalsO2 = new()
{
Text = "Refill Internals Oxygen",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/oxygen.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/oxygen.rsi"), "icon"),
Act = () =>
{
foreach (var slot in _inventorySystem.GetSlots(args.Target))
@ -289,7 +290,7 @@ public sealed partial class AdminVerbSystem
if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
continue;
if (!TryComp<GasTankComponent>(entity, out var tank))
if (!TryComp(entity, out tank))
continue;
RefillGasTank(entity.Value, Gas.Oxygen, tank);
@ -297,7 +298,7 @@ public sealed partial class AdminVerbSystem
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(held, out var tank))
if (!TryComp(held, out tank))
continue;
RefillGasTank(held, Gas.Oxygen, tank);
@ -313,7 +314,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Internals Nitrogen",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/red.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/red.rsi"), "icon"),
Act = () =>
{
foreach (var slot in _inventorySystem.GetSlots(args.Target))
@ -321,7 +322,7 @@ public sealed partial class AdminVerbSystem
if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
continue;
if (!TryComp<GasTankComponent>(entity, out var tank))
if (!TryComp(entity, out tank))
continue;
RefillGasTank(entity.Value, Gas.Nitrogen, tank);
@ -329,7 +330,7 @@ public sealed partial class AdminVerbSystem
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(held, out var tank))
if (!TryComp(held, out tank))
continue;
RefillGasTank(held, Gas.Nitrogen, tank);
@ -345,7 +346,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Internals Plasma",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Tanks/plasma.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Tanks/plasma.rsi"), "icon"),
Act = () =>
{
foreach (var slot in _inventorySystem.GetSlots(args.Target))
@ -353,7 +354,7 @@ public sealed partial class AdminVerbSystem
if (!_inventorySystem.TryGetSlotEntity(args.Target, slot.Name, out var entity))
continue;
if (!TryComp<GasTankComponent>(entity, out var tank))
if (!TryComp(entity, out tank))
continue;
RefillGasTank(entity.Value, Gas.Plasma, tank);
@ -361,7 +362,7 @@ public sealed partial class AdminVerbSystem
foreach (var held in _handsSystem.EnumerateHeld(args.Target))
{
if (!TryComp<GasTankComponent>(held, out var tank))
if (!TryComp(held, out tank))
continue;
RefillGasTank(held, Gas.Plasma, tank);
@ -378,7 +379,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Send to test arena",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
Act = () =>
{
@ -399,7 +400,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Grant All Access",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Misc/id_cards.rsi/centcom.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
Act = () =>
{
GiveAllAccess(activeId.Value);
@ -414,7 +415,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Revoke All Access",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Misc/id_cards.rsi/default.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Misc/id_cards.rsi"), "default"),
Act = () =>
{
RevokeAllAccess(activeId.Value);
@ -432,7 +433,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Grant All Access",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Misc/id_cards.rsi/centcom.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Misc/id_cards.rsi"), "centcom"),
Act = () =>
{
GiveAllAccess(args.Target);
@ -447,7 +448,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Revoke All Access",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Misc/id_cards.rsi/default.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Misc/id_cards.rsi"), "default"),
Act = () =>
{
RevokeAllAccess(args.Target);
@ -466,7 +467,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Adjust Stack",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/adjust-stack.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/adjust-stack.png")),
Act = () =>
{
// Unbounded intentionally.
@ -485,7 +486,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Fill Stack",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/fill-stack.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/fill-stack.png")),
Act = () =>
{
_stackSystem.SetCount(args.Target, _stackSystem.GetMaxCount(stack), stack);
@ -501,7 +502,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Rename",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/rename.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/rename.png")),
Act = () =>
{
_quickDialog.OpenDialog(player, "Rename", "Name", (string newName) =>
@ -519,7 +520,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Redescribe",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/redescribe.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/redescribe.png")),
Act = () =>
{
_quickDialog.OpenDialog(player, "Redescribe", "Description", (LongString newDescription) =>
@ -537,7 +538,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Redescribe",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/rename_and_redescribe.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/rename_and_redescribe.png")),
Act = () =>
{
_quickDialog.OpenDialog(player, "Rename & Redescribe", "Name", "Description",
@ -562,7 +563,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Bar job slots",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/bar_jobslots.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/bar_jobslots.png")),
Act = () =>
{
foreach (var (job, _) in _stationJobsSystem.GetJobs(args.Target))
@ -581,7 +582,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Locate Cargo Shuttle",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Clothing/Head/Soft/cargosoft.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Clothing/Head/Soft/cargosoft.rsi"), "icon"),
Act = () =>
{
var shuttle = Comp<StationCargoOrderDatabaseComponent>(args.Target).Shuttle;
@ -604,7 +605,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Refill Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/fill_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/fill_battery.png")),
Act = () =>
{
foreach (var ent in childEnum)
@ -626,7 +627,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Drain Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/drain_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/drain_battery.png")),
Act = () =>
{
foreach (var ent in childEnum)
@ -648,7 +649,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Infinite Battery",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/infinite_battery.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/infinite_battery.png")),
Act = () =>
{
// this kills the sloth
@ -677,7 +678,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Halt Movement",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/halt.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/halt.png")),
Act = () =>
{
_physics.SetLinearVelocity(args.Target, Vector2.Zero, body: physics);
@ -700,7 +701,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Unpause Map",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/play.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/play.png")),
Act = () =>
{
_mapManager.SetMapPaused(map.WorldMap, false);
@ -717,7 +718,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Pause Map",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/pause.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/pause.png")),
Act = () =>
{
_mapManager.SetMapPaused(map.WorldMap, true);
@ -737,7 +738,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Snap Joints",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Interface/AdminActions/snap_joints.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/snap_joints.png")),
Act = () =>
{
_jointSystem.ClearJoints(joints);
@ -755,7 +756,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Make Minigun",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Weapons/Guns/HMGs/minigun.rsi"), "icon"),
Act = () =>
{
gun.FireRate = 15;
@ -773,7 +774,7 @@ public sealed partial class AdminVerbSystem
{
Text = "Set Bullet Amount",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Objects/Fun/caps.rsi/mag-6.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Objects/Fun/caps.rsi"), "mag-6"),
Act = () =>
{
_quickDialog.OpenDialog(player, "Set Bullet Amount", $"Amount (max {ballisticAmmo.Capacity}):", (int amount) =>

View file

@ -29,6 +29,7 @@ using Robust.Shared.Console;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Content.Shared.Configurable.ConfigurationComponent;
namespace Content.Server.Administration.Systems
@ -79,7 +80,7 @@ namespace Content.Server.Administration.Systems
Verb verb = new();
verb.Text = Loc.GetString("ahelp-verb-get-data-text");
verb.Category = VerbCategory.Admin;
verb.IconTexture = "/Textures/Interface/gavel.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/gavel.svg.192dpi.png"));
verb.Act = () =>
_console.RemoteExecuteCommand(player, $"openahelp \"{targetActor.PlayerSession.UserId}\"");
verb.Impact = LogImpact.Low;
@ -89,7 +90,7 @@ namespace Content.Server.Administration.Systems
Verb prayerVerb = new();
prayerVerb.Text = Loc.GetString("prayer-verbs-subtle-message");
prayerVerb.Category = VerbCategory.Admin;
prayerVerb.IconTexture = "/Textures/Interface/pray.svg.png";
prayerVerb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/pray.svg.png"));
prayerVerb.Act = () =>
{
_quickDialog.OpenDialog(player, "Subtle Message", "Message", "Popup Message", (string message, string popupMessage) =>
@ -109,7 +110,7 @@ namespace Content.Server.Administration.Systems
? Loc.GetString("admin-verbs-unfreeze")
: Loc.GetString("admin-verbs-freeze"),
Category = VerbCategory.Admin,
IconTexture = "/Textures/Interface/VerbIcons/snow.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/snow.svg.192dpi.png")),
Act = () =>
{
if (frozen)
@ -145,7 +146,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("admin-verbs-teleport-to"),
Category = VerbCategory.Admin,
IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/open.svg.192dpi.png")),
Act = () => _console.ExecuteCommand(player, $"tpto {args.Target}"),
Impact = LogImpact.Low
});
@ -155,7 +156,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("admin-verbs-teleport-here"),
Category = VerbCategory.Admin,
IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/close.svg.192dpi.png")),
Act = () => _console.ExecuteCommand(player, $"tpto {args.Target} {args.User}"),
Impact = LogImpact.Low
});
@ -194,7 +195,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("delete-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png")),
Act = () => EntityManager.DeleteEntity(args.Target),
Impact = LogImpact.Medium,
ConfirmationPopup = true
@ -209,7 +210,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("rejuvenate-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png")),
Act = () => RejuvenateCommand.PerformRejuvenate(args.Target),
Impact = LogImpact.Medium
};
@ -268,7 +269,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("make-sentient-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/sentient.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/sentient.svg.192dpi.png")),
Act = () => MakeSentientCommand.MakeSentient(args.Target, EntityManager),
Impact = LogImpact.Medium
};
@ -283,7 +284,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("set-outfit-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => _euiManager.OpenEui(new SetOutfitEui(args.Target), player),
Impact = LogImpact.Medium
};
@ -297,7 +298,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("in-range-unoccluded-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
Act = () =>
{
var message = args.User.InRangeUnOccluded(args.Target)
@ -317,7 +318,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("tube-direction-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/information.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
Act = () => tube.PopupDirections(args.User)
};
args.Verbs.Add(verb);
@ -343,7 +344,7 @@ namespace Content.Server.Administration.Systems
Verb verb = new()
{
Text = Loc.GetString("configure-verb-get-data-text"),
IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
Category = VerbCategory.Debug,
Act = () => _uiSystem.TryOpen(args.Target, ConfigurationUiKey.Key, actor.PlayerSession)
};
@ -358,7 +359,7 @@ namespace Content.Server.Administration.Systems
{
Text = Loc.GetString("edit-solutions-verb-get-data-text"),
Category = VerbCategory.Debug,
IconTexture = "/Textures/Interface/VerbIcons/spill.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/spill.svg.192dpi.png")),
Act = () => OpenEditSolutionsEui(player, args.Target),
Impact = LogImpact.Medium // maybe high depending on WHAT reagents they add...
};

View file

@ -11,6 +11,7 @@ using Content.Shared.Verbs;
using Content.Server.Popups;
using Content.Server.DoAfter;
using Content.Shared.DoAfter;
using Robust.Shared.Utility;
namespace Content.Server.Body.Systems;
@ -48,7 +49,7 @@ public sealed class InternalsSystem : EntitySystem
ToggleInternals(uid, args.User, false, component);
},
Message = Loc.GetString("action-description-internals-toggle"),
IconTexture = "/Textures/Interface/VerbIcons/dot.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/dot.svg.192dpi.png")),
Text = Loc.GetString("action-name-internals-toggle"),
};

View file

@ -18,7 +18,7 @@ public sealed class BotanySwabSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<BotanySwabComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<BotanySwabComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<BotanySwabComponent, DoAfterEvent>(OnDoAfter);
}
/// <summary>
@ -41,7 +41,7 @@ public sealed class BotanySwabSystem : EntitySystem
/// </summary>
private void OnAfterInteract(EntityUid uid, BotanySwabComponent swab, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach)
if (args.Target == null || !args.CanReach || !HasComp<PlantHolderComponent>(args.Target))
return;
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
@ -57,7 +57,7 @@ public sealed class BotanySwabSystem : EntitySystem
/// <summary>
/// Save seed data or cross-pollenate.
/// </summary>
private void OnDoAfter(DoAfterEvent args)
private void OnDoAfter(EntityUid uid, BotanySwabComponent component, DoAfterEvent args)
{
if (args.Cancelled || args.Handled || !TryComp<PlantHolderComponent>(args.Args.Target, out var plant) || !TryComp<BotanySwabComponent>(args.Args.Used, out var swab))
return;

View file

@ -15,6 +15,7 @@ using Content.Shared.Stunnable;
using Content.Shared.Vehicle.Components;
using Content.Shared.Verbs;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Server.Buckle.Systems;
@ -44,7 +45,7 @@ public sealed partial class BuckleSystem
{
Act = () => TryUnbuckle(uid, args.User, buckle: component),
Text = Loc.GetString("verb-categories-unbuckle"),
IconTexture = "/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/unbuckle.svg.192dpi.png"))
};
if (args.Target == args.User && args.Using == null)
@ -76,7 +77,8 @@ public sealed partial class BuckleSystem
private void HandleInteractHand(EntityUid uid, BuckleComponent component, InteractHandEvent args)
{
args.Handled = TryUnbuckle(uid, args.User, buckle: component);
if (TryUnbuckle(uid, args.User, buckle: component))
args.Handled = true;
}
private void MoveEvent(EntityUid uid, BuckleComponent buckle, ref MoveEvent ev)

View file

@ -3,7 +3,6 @@ using Content.Server.Administration;
using Content.Server.Body.Systems;
using Content.Server.Cargo.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Stack;
using Content.Shared.Administration;
using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Reagent;
@ -88,6 +87,9 @@ public sealed class PricingSystem : EntitySystem
private void CalculateMobPrice(EntityUid uid, MobPriceComponent component, ref PriceCalculationEvent args)
{
if (args.Handled)
return;
if (!TryComp<BodyComponent>(uid, out var body) || !TryComp<MobStateComponent>(uid, out var state))
{
Logger.ErrorS("pricing", $"Tried to get the mob price of {ToPrettyString(uid)}, which has no {nameof(BodyComponent)} and no {nameof(MobStateComponent)}.");
@ -106,6 +108,9 @@ public sealed class PricingSystem : EntitySystem
private void CalculateStackPrice(EntityUid uid, StackPriceComponent component, ref PriceCalculationEvent args)
{
if (args.Handled)
return;
if (!TryComp<StackComponent>(uid, out var stack))
{
Logger.ErrorS("pricing", $"Tried to get the stack price of {ToPrettyString(uid)}, which has no {nameof(StackComponent)}.");
@ -117,6 +122,9 @@ public sealed class PricingSystem : EntitySystem
private void CalculateSolutionPrice(EntityUid uid, SolutionContainerManagerComponent component, ref PriceCalculationEvent args)
{
if (args.Handled)
return;
var price = 0f;
foreach (var solution in component.Solutions.Values)
@ -133,6 +141,9 @@ public sealed class PricingSystem : EntitySystem
private void CalculateStaticPrice(EntityUid uid, StaticPriceComponent component, ref PriceCalculationEvent args)
{
if (args.Handled)
return;
args.Price += component.Price;
}
@ -187,6 +198,9 @@ public sealed class PricingSystem : EntitySystem
var ev = new PriceCalculationEvent();
RaiseLocalEvent(uid, ref ev);
if (ev.Handled)
return ev.Price;
//TODO: Add an OpaqueToAppraisal component or similar for blocking the recursive descent into containers, or preventing material pricing.
if (TryComp<MaterialComponent>(uid, out var material) && !HasComp<StackPriceComponent>(uid))
@ -249,5 +263,10 @@ public struct PriceCalculationEvent
/// </summary>
public double Price = 0;
/// <summary>
/// Whether this event was already handled.
/// </summary>
public bool Handled = false;
public PriceCalculationEvent() { }
}

View file

@ -79,6 +79,7 @@ public sealed partial class ChatSystem : SharedChatSystem
base.Shutdown();
ShutdownEmotes();
_configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged);
_configurationManager.UnsubValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged);
}
private void OnLoocEnabledChanged(bool val)
@ -101,13 +102,17 @@ public sealed partial class ChatSystem : SharedChatSystem
private void OnGameChange(GameRunLevelChangedEvent ev)
{
if (_configurationManager.GetCVar(CCVars.OocEnableDuringRound))
return;
if (ev.New == GameRunLevel.InRound)
_configurationManager.SetCVar(CCVars.OocEnabled, false);
else if (ev.New == GameRunLevel.PostRound)
_configurationManager.SetCVar(CCVars.OocEnabled, true);
switch(ev.New)
{
case GameRunLevel.InRound:
if(!_configurationManager.GetCVar(CCVars.OocEnableDuringRound))
_configurationManager.SetCVar(CCVars.OocEnabled, false);
break;
case GameRunLevel.PostRound:
if(!_configurationManager.GetCVar(CCVars.OocEnableDuringRound))
_configurationManager.SetCVar(CCVars.OocEnabled, true);
break;
}
}
/// <summary>

View file

@ -129,11 +129,18 @@ public sealed partial class ChemistrySystem
private void OnInjectDoAfter(EntityUid uid, InjectorComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
if (args.Cancelled)
{
component.IsInjecting = false;
return;
}
if (args.Handled || args.Args.Target == null)
return;
UseInjector(args.Args.Target.Value, args.Args.User, uid, component);
component.IsInjecting = false;
args.Handled = true;
}
@ -216,12 +223,18 @@ public sealed partial class ChemistrySystem
if (!_solutions.TryGetSolution(injector, InjectorComponent.SolutionName, out var solution))
return;
//If it found it's injecting
if (component.IsInjecting)
return;
var actualDelay = MathF.Max(component.Delay, 1f);
// Injections take 1 second longer per additional 5u
actualDelay += (float) component.TransferAmount / component.Delay - 1;
if (user != target)
var isTarget = user != target;
if (isTarget)
{
// Create a pop-up for the target
var userName = Identity.Entity(user, EntityManager);
@ -256,8 +269,12 @@ public sealed partial class ChemistrySystem
_adminLogger.Add(LogType.Ingestion, $"{EntityManager.ToPrettyString(user):user} is attempting to inject themselves with a solution {SolutionContainerSystem.ToPrettyString(solution):solution}.");
}
component.IsInjecting = true;
_doAfter.DoAfter(new DoAfterEventArgs(user, actualDelay, target:target, used:injector)
{
RaiseOnTarget = isTarget,
RaiseOnUser = !isTarget,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,

View file

@ -11,12 +11,15 @@ using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Timing;
using Robust.Shared.Player;
namespace Content.Server.Chemistry.EntitySystems
{
public sealed partial class ChemistrySystem
{
[Dependency] private readonly UseDelaySystem _useDelay = default!;
private void InitializeHypospray()
{
SubscribeLocalEvent<HyposprayComponent, AfterInteractEvent>(OnAfterInteract);
@ -65,6 +68,10 @@ namespace Content.Server.Chemistry.EntitySystems
if (!EligibleEntity(target, _entMan))
return false;
if (TryComp(uid, out UseDelayComponent? delayComp))
if (_useDelay.ActiveDelay(uid, delayComp))
return false;
string? msgFormat = null;
if (target == user)
@ -102,6 +109,11 @@ namespace Content.Server.Chemistry.EntitySystems
_audio.PlayPvs(component.InjectSound, user);
// Medipens and such use this system and don't have a delay, requiring extra checks
// BeginDelay function returns if item is already on delay
if (delayComp is not null)
_useDelay.BeginDelay(uid, delayComp);
// Get transfer amount. May be smaller than component.TransferAmount if not enough room
var realTransferAmount = FixedPoint2.Min(component.TransferAmount, targetSolution.AvailableVolume);

View file

@ -7,6 +7,7 @@ using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Clothing.Systems;
@ -47,7 +48,7 @@ public sealed class ChameleonClothingSystem : SharedChameleonClothingSystem
args.Verbs.Add(new InteractionVerb()
{
Text = Loc.GetString("chameleon-component-verb-text"),
IconTexture = "/Textures/Interface/VerbIcons/settings.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
Act = () => TryOpenUi(uid, args.User, component)
});
}

View file

@ -319,6 +319,9 @@ namespace Content.Server.Construction
newConstruction.InteractionQueue.Enqueue(ev);
}
if (newConstruction.InteractionQueue.Count > 0 && _queuedUpdates.Add(newUid))
_constructionUpdateQueue.Enqueue(newUid);
// Transform transferring.
var newTransform = Transform(newUid);
newTransform.LocalRotation = transform.LocalRotation;

View file

@ -6,6 +6,7 @@ using Content.Shared.Examine;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Construction
{
@ -44,7 +45,8 @@ namespace Content.Server.Construction
//verb.Category = VerbCategories.Construction;
//TODO VERBS add more construction verbs? Until then, removing construction category
verb.Text = Loc.GetString("deconstructible-verb-begin-deconstruct");
verb.IconTexture = "/Textures/Interface/hammer_scaled.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(
new ResourcePath("/Textures/Interface/hammer_scaled.svg.192dpi.png"));
verb.Act = () =>
{

View file

@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Construction.Components;
using Content.Server.Temperature.Components;
@ -8,6 +9,7 @@ using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Tools.Components;
using Robust.Shared.Containers;
using Robust.Shared.Utility;
#if EXCEPTION_TOLERANCE
// ReSharper disable once RedundantUsingDirective
using Robust.Shared.Exceptions;
@ -22,7 +24,8 @@ namespace Content.Server.Construction
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
private readonly HashSet<EntityUid> _constructionUpdateQueue = new();
private readonly Queue<EntityUid> _constructionUpdateQueue = new();
private readonly HashSet<EntityUid> _queuedUpdates = new();
private void InitializeInteractions()
{
@ -480,19 +483,42 @@ namespace Content.Server.Construction
// We iterate all entities waiting for their interactions to be handled.
// This is much more performant than making an EntityQuery for ConstructionComponent,
// since, for example, every single wall has a ConstructionComponent....
foreach (var uid in _constructionUpdateQueue)
while (_constructionUpdateQueue.TryDequeue(out var uid))
{
_queuedUpdates.Remove(uid);
// Ensure the entity exists and has a Construction component.
if (!Exists(uid) || !TryComp(uid, out ConstructionComponent? construction))
if (!TryComp(uid, out ConstructionComponent? construction))
continue;
#if EXCEPTION_TOLERANCE
try
{
#endif
// temporary code for debugging a grafana exception. Something is fishy with the girder graph.
object? prev = null;
var queued = string.Join(", ", construction.InteractionQueue.Select(x => x.GetType().Name));
// Handle all queued interactions!
while (construction.InteractionQueue.TryDequeue(out var interaction))
{
if (construction.Deleted)
{
// I suspect the error might just happen if two users try to deconstruction or otherwise modify an entity at the exact same tick?
// In which case this isn't really an error, but should just be a `if (deleted) -> break`
// But might as well verify this.
_sawmill.Error($"Construction component was deleted while still processing interactions." +
$"Entity {ToPrettyString(uid)}, graph: {construction.Graph}, " +
$"Previous: {prev?.GetType()?.Name ?? "null"}, " +
$"Next: {interaction.GetType().Name}, " +
$"Initial Queue: {queued}, " +
$"Remaining Queue: {string.Join(", ", construction.InteractionQueue.Select(x => x.GetType().Name))}");
break;
}
prev = interaction;
// We set validation to false because we actually want to perform the interaction here.
HandleEvent(uid, interaction, false, construction);
}
@ -507,7 +533,7 @@ namespace Content.Server.Construction
#endif
}
_constructionUpdateQueue.Clear();
DebugTools.Assert(_queuedUpdates.Count == 0);
}
#region Event Handlers
@ -543,7 +569,8 @@ namespace Content.Server.Construction
construction.InteractionQueue.Enqueue(args);
// Add this entity to the queue so it'll be updated next tick.
_constructionUpdateQueue.Add(uid);
if (_queuedUpdates.Add(uid))
_constructionUpdateQueue.Enqueue(uid);
}
private void OnDoAfter(EntityUid uid, ConstructionComponent component, DoAfterEvent<ConstructionData> args)

View file

@ -53,7 +53,7 @@ public sealed partial class ConstructionSystem
Text = Loc.GetString("machine-upgrade-examinable-verb-text"),
Message = Loc.GetString("machine-upgrade-examinable-verb-message"),
Category = VerbCategory.Examine,
IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"))
};
args.Verbs.Add(verb);

View file

@ -23,6 +23,8 @@ public sealed class CrayonSystem : SharedCrayonSystem
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly DecalSystem _decals = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
@ -68,7 +70,7 @@ public sealed class CrayonSystem : SharedCrayonSystem
return;
if (component.UseSound != null)
SoundSystem.Play(component.UseSound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.125f));
_audio.PlayPvs(component.UseSound, uid, AudioHelpers.WithVariation(0.125f));
// Decrease "Ammo"
component.Charges--;
@ -86,14 +88,18 @@ public sealed class CrayonSystem : SharedCrayonSystem
if (args.Handled)
return;
if (!TryComp<ActorComponent>(args.User, out var actor)) return;
if (!TryComp<ActorComponent>(args.User, out var actor) ||
component.UserInterface == null)
{
return;
}
component.UserInterface?.Toggle(actor.PlayerSession);
_uiSystem.ToggleUi(component.UserInterface, actor.PlayerSession);
if (component.UserInterface?.SessionHasOpen(actor.PlayerSession) == true)
if (component.UserInterface?.SubscribedSessions.Contains(actor.PlayerSession) == true)
{
// Tell the user interface the selected stuff
component.UserInterface.SetState(new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
_uiSystem.SetUiState(component.UserInterface, new CrayonBoundUserInterfaceState(component.SelectedState, component.SelectableColor, component.Color));
}
args.Handled = true;
@ -134,7 +140,7 @@ public sealed class CrayonSystem : SharedCrayonSystem
private void OnCrayonDropped(EntityUid uid, CrayonComponent component, DroppedEvent args)
{
if (TryComp<ActorComponent>(args.User, out var actor))
component.UserInterface?.Close(actor.PlayerSession);
_uiSystem.TryClose(uid, SharedCrayonComponent.CrayonUiKey.Key, actor.PlayerSession);
}
private void UseUpCrayon(EntityUid uid, EntityUid user)

View file

@ -1,22 +1,17 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.EUI;
using Content.Server.GameTicking;
using Content.Server.Station.Systems;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.CrewManifest;
using Content.Shared.GameTicking;
using Content.Shared.Roles;
using Content.Shared.StationRecords;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
namespace Content.Server.CrewManifest;

View file

@ -748,14 +748,25 @@ namespace Content.Server.Database
query = query.Where(log => log.Date > filter.After);
}
if (filter.AnyPlayers != null)
if (filter.IncludePlayers)
{
query = query.Where(log => log.Players.Any(p => filter.AnyPlayers.Contains(p.PlayerUserId)));
}
if (filter.AnyPlayers != null)
{
query = query.Where(log =>
log.Players.Any(p => filter.AnyPlayers.Contains(p.PlayerUserId)) ||
log.Players.Count == 0 && filter.IncludeNonPlayers);
}
if (filter.AllPlayers != null)
if (filter.AllPlayers != null)
{
query = query.Where(log =>
log.Players.All(p => filter.AllPlayers.Contains(p.PlayerUserId)) ||
log.Players.Count == 0 && filter.IncludeNonPlayers);
}
}
else
{
query = query.Where(log => log.Players.All(p => filter.AllPlayers.Contains(p.PlayerUserId)));
query = query.Where(log => log.Players.Count == 0);
}
if (filter.LastLogId != null)

View file

@ -32,7 +32,7 @@ namespace Content.Server.DetailExaminable
Category = VerbCategory.Examine,
Disabled = !detailsRange,
Message = Loc.GetString("detail-examinable-verb-disabled"),
IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/examine.svg.192dpi.png"))
};
args.Verbs.Add(verb);

View file

@ -1,20 +1,17 @@
using System.Linq;
using Content.Server.DeviceNetwork.Components;
using Content.Server.UserInterface;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Database;
using Content.Shared.DeviceNetwork;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Movement;
using Content.Shared.Popups;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.DeviceNetwork.Systems;
@ -56,14 +53,16 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
foreach (var component in EntityManager.EntityQuery<NetworkConfiguratorComponent>())
{
var uid = component.Owner;
if (component.ActiveDeviceList != null && EntityManager.EntityExists(component.ActiveDeviceList.Value) &&
_interactionSystem.InRangeUnobstructed(component.Owner, component.ActiveDeviceList.Value))
_interactionSystem.InRangeUnobstructed(uid, component.ActiveDeviceList.Value))
{
return;
continue;
}
//The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device.
_uiSystem.GetUiOrNull(component.Owner, NetworkConfiguratorUiKey.Configure)?.CloseAll();
_uiSystem.GetUiOrNull(uid, NetworkConfiguratorUiKey.Configure)?.CloseAll();
}
}
@ -138,7 +137,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
private void OnComponentRemoved(EntityUid uid, DeviceListComponent component, ComponentRemove args)
{
_uiSystem.GetUiOrNull(component.Owner, NetworkConfiguratorUiKey.Configure)?.CloseAll();
_uiSystem.GetUiOrNull(uid, NetworkConfiguratorUiKey.Configure)?.CloseAll();
}
#region Interactions
@ -177,7 +176,9 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
UtilityVerb verb = new()
{
Text = Loc.GetString(isDeviceList ? "network-configurator-configure" : "network-configurator-save-device"),
IconTexture = isDeviceList ? "/Textures/Interface/VerbIcons/settings.svg.192dpi.png" : "/Textures/Interface/VerbIcons/in.svg.192dpi.png",
Icon = isDeviceList ?
new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")) :
new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
Act = () => OnUsed(uid, component, args.Target, args.User),
Impact = LogImpact.Low
};
@ -199,7 +200,7 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
AlternativeVerb verb = new()
{
Text = Loc.GetString("network-configurator-save-device"),
IconTexture = "/Textures/Interface/VerbIcons/in.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/in.svg.192dpi.png")),
Act = () => TryAddNetworkDevice(args.Target, args.Using.Value, args.User),
Impact = LogImpact.Low
};

View file

@ -47,7 +47,7 @@ namespace Content.Server.Disease
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
SubscribeLocalEvent<DoAfterEvent>(OnSwabDoAfter);
SubscribeLocalEvent<DiseaseSwabComponent, DoAfterEvent>(OnSwabDoAfter);
}
private Queue<EntityUid> AddQueue = new();
@ -99,7 +99,7 @@ namespace Content.Server.Disease
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach)
if (args.Target == null || !args.CanReach || !HasComp<DiseaseCarrierComponent>(args.Target))
return;
if (swab.Used)
@ -116,8 +116,12 @@ namespace Content.Server.Disease
return;
}
var isTarget = args.User != args.Target;
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, target: args.Target, used: uid)
{
RaiseOnTarget = isTarget,
RaiseOnUser = !isTarget,
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
@ -301,9 +305,9 @@ namespace Content.Server.Disease
/// Copies a disease prototype to the swab
/// after the doafter completes.
/// </summary>
private void OnSwabDoAfter(DoAfterEvent args)
private void OnSwabDoAfter(EntityUid uid, DiseaseSwabComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Target, out var swab))
if (args.Handled || args.Cancelled || !TryComp<DiseaseCarrierComponent>(args.Args.Target, out var carrier) || !TryComp<DiseaseSwabComponent>(args.Args.Used, out var swab))
return;
swab.Used = true;
@ -313,7 +317,6 @@ namespace Content.Server.Disease
return;
swab.Disease = _random.Pick(carrier.Diseases);
}

View file

@ -31,6 +31,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Content.Server.Disposal.Unit.EntitySystems
{
@ -96,7 +97,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
AlternativeVerb flushVerb = new();
flushVerb.Act = () => Engage(uid, component);
flushVerb.Text = Loc.GetString("disposal-flush-verb-get-data-text");
flushVerb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png";
flushVerb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"));
flushVerb.Priority = 1;
args.Verbs.Add(flushVerb);

View file

@ -396,7 +396,7 @@ public sealed partial class ExplosionSystem : EntitySystem
{
var damageStr = string.Join(", ", damage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}"));
_adminLogger.Add(LogType.Explosion, LogImpact.Medium,
$"Explosion caused {{{damageStr}}} to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
$"Explosion caused [{damageStr}] to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
}
}
else
@ -407,7 +407,7 @@ public sealed partial class ExplosionSystem : EntitySystem
{
var damageStr = string.Join(", ", appliedDamage.DamageDict.Select(entry => $"{entry.Key}: {entry.Value}"));
_adminLogger.Add(LogType.Explosion, LogImpact.Medium,
$"Explosion caused {{{damageStr}}} to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
$"Explosion caused [{damageStr}] to {ToPrettyString(uid):target} at {Transform(uid).Coordinates}");
}
}
}

View file

@ -7,6 +7,7 @@ using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Utility;
namespace Content.Server.Foldable
{
@ -32,9 +33,9 @@ namespace Content.Server.Foldable
args.Cancelled = true;
}
public bool TryToggleFold(FoldableComponent comp)
public bool TryToggleFold(EntityUid uid, FoldableComponent comp)
{
return TrySetFolded(comp, !comp.IsFolded);
return TrySetFolded(uid, comp, !comp.IsFolded);
}
public bool CanToggleFold(EntityUid uid, FoldableComponent? fold = null)
@ -62,18 +63,15 @@ namespace Content.Server.Foldable
/// <summary>
/// Try to fold/unfold
/// </summary>
/// <param name="comp"></param>
/// <param name="state">Folded state we want</param>
/// <returns>True if successful</returns>
public bool TrySetFolded(FoldableComponent comp, bool state)
public bool TrySetFolded(EntityUid uid, FoldableComponent comp, bool state)
{
if (state == comp.IsFolded)
return false;
if (!CanToggleFold(comp.Owner, comp))
if (!CanToggleFold(uid, comp))
return false;
SetFolded(comp, state);
SetFolded(uid, comp, state);
return true;
}
@ -82,12 +80,12 @@ namespace Content.Server.Foldable
/// </summary>
/// <param name="component"></param>
/// <param name="folded">If true, the component will become folded, else unfolded</param>
public override void SetFolded(FoldableComponent component, bool folded)
public override void SetFolded(EntityUid uid, FoldableComponent component, bool folded)
{
base.SetFolded(component, folded);
base.SetFolded(uid, component, folded);
// You can't buckle an entity to a folded object
_buckle.StrapSetEnabled(component.Owner, !component.IsFolded);
_buckle.StrapSetEnabled(uid, !component.IsFolded);
}
public void OnStoreThisAttempt(EntityUid uid, FoldableComponent comp, ref StoreMobInItemContainerAttemptEvent args)
@ -107,9 +105,9 @@ namespace Content.Server.Foldable
AlternativeVerb verb = new()
{
Act = () => TryToggleFold(component),
Act = () => TryToggleFold(uid, component),
Text = component.IsFolded ? Loc.GetString("unfold-verb") : Loc.GetString("fold-verb"),
IconTexture = "/Textures/Interface/VerbIcons/fold.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/fold.svg.192dpi.png")),
// If the object is unfolded and they click it, they want to fold it, if it's folded, they want to pick it up
Priority = component.IsFolded ? 0 : 2,

View file

@ -36,7 +36,7 @@ public sealed class HealthExaminableSystem : EntitySystem
Category = VerbCategory.Examine,
Disabled = !detailsRange,
Message = Loc.GetString("health-examinable-verb-disabled"),
IconTexture = "/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/rejuvenate.svg.192dpi.png"))
};
args.Verbs.Add(verb);

View file

@ -129,6 +129,8 @@ public sealed partial class HumanoidAppearanceSystem : SharedHumanoidAppearanceS
targetHumanoid.Species = sourceHumanoid.Species;
targetHumanoid.SkinColor = sourceHumanoid.SkinColor;
targetHumanoid.EyeColor = sourceHumanoid.EyeColor;
targetHumanoid.Age = sourceHumanoid.Age;
SetSex(target, sourceHumanoid.Sex, false, targetHumanoid);
targetHumanoid.CustomBaseLayers = new(sourceHumanoid.CustomBaseLayers);
targetHumanoid.MarkingSet = new(sourceHumanoid.MarkingSet);

View file

@ -4,6 +4,7 @@ using Content.Shared.Humanoid;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Utility;
namespace Content.Server.Humanoid;
@ -28,7 +29,7 @@ public sealed partial class HumanoidAppearanceSystem
{
Text = "Modify markings",
Category = VerbCategory.Tricks,
IconTexture = "/Textures/Mobs/Customization/reptilian_parts.rsi/tail_smooth.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("/Textures/Mobs/Customization/reptilian_parts.rsi"), "tail_smooth"),
Act = () =>
{
_uiSystem.TryOpen(uid, HumanoidMarkingModifierKey.Key, actor.PlayerSession);

View file

@ -34,6 +34,8 @@ public sealed class InteractionPopupSystem : EntitySystem
if (HasComp<SleepingComponent>(uid))
return;
args.Handled = true;
var curTime = _gameTiming.CurTime;
if (curTime < component.LastInteractTime + component.InteractDelay)
@ -85,6 +87,5 @@ public sealed class InteractionPopupSystem : EntitySystem
}
component.LastInteractTime = curTime;
args.Handled = true;
}
}

View file

@ -17,6 +17,8 @@ using Content.Shared.Kitchen;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Content.Server.Body.Systems;
namespace Content.Server.Kitchen.EntitySystems
{
@ -27,6 +29,8 @@ namespace Content.Server.Kitchen.EntitySystems
[Dependency] private readonly IAdminLogManager _logger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
@ -133,9 +137,13 @@ namespace Content.Server.Kitchen.EntitySystems
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-kill", ("user", Identity.Entity(userUid, EntityManager)), ("victim", victimUid)), uid, PopupType.LargeCaution);
_transform.SetCoordinates(victimUid, Transform(uid).Coordinates);
// THE WHAT?
// TODO: Need to be able to leave them on the spike to do DoT, see ss13.
EntityManager.QueueDeleteEntity(victimUid);
var gibs = _bodySystem.GibBody(victimUid);
foreach (var gib in gibs) {
QueueDel(gib);
}
_audio.Play(component.SpikeSound, Filter.Pvs(uid), uid, true);
}

View file

@ -1,4 +1,4 @@
using Content.Server.Body.Systems;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
using Content.Server.Kitchen.Components;
using Content.Shared.Body.Components;
@ -13,6 +13,7 @@ using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Server.Containers;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Kitchen.EntitySystems;
@ -149,7 +150,7 @@ public sealed class SharpSystem : EntitySystem
},
Message = message,
Disabled = disabled,
IconTexture = "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png")),
Text = Loc.GetString("butcherable-verb-name"),
};

View file

@ -11,6 +11,7 @@ using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Light.EntitySystems
{
@ -183,7 +184,7 @@ namespace Content.Server.Light.EntitySystems
ActivationVerb verb = new()
{
Text = Loc.GetString("expendable-light-start-verb"),
IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
Act = () => TryActivate(component)
};
args.Verbs.Add(verb);

View file

@ -58,7 +58,7 @@ namespace Content.Server.Light.EntitySystems
EntInsertedIntoContainerMessage args)
{
// Not guaranteed to be the correct container for our slot, I don't care.
UpdateLevel(component);
UpdateLevel(uid, component);
}
private void OnEntRemoved(
@ -67,7 +67,7 @@ namespace Content.Server.Light.EntitySystems
EntRemovedFromContainerMessage args)
{
// Ditto above
UpdateLevel(component);
UpdateLevel(uid, component);
}
private void OnGetActions(EntityUid uid, HandheldLightComponent component, GetItemActionsEvent args)
@ -88,24 +88,24 @@ namespace Content.Server.Light.EntitySystems
return;
if (component.Activated)
TurnOff(component);
TurnOff(uid, component);
else
TurnOn(args.Performer, component);
TurnOn(args.Performer, uid, component);
args.Handled = true;
}
private void OnGetState(EntityUid uid, HandheldLightComponent component, ref ComponentGetState args)
{
args.State = new HandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(component));
args.State = new HandheldLightComponent.HandheldLightComponentState(component.Activated, GetLevel(uid, component));
}
private byte? GetLevel(HandheldLightComponent component)
private byte? GetLevel(EntityUid uid, HandheldLightComponent component)
{
// Curently every single flashlight has the same number of levels for status and that's all it uses the charge for
// Thus we'll just check if the level changes.
if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery))
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery))
return null;
if (MathHelper.CloseToPercent(battery.CurrentCharge, 0) || component.Wattage > battery.CurrentCharge)
@ -121,9 +121,10 @@ namespace Content.Server.Light.EntitySystems
private void OnActivate(EntityUid uid, HandheldLightComponent component, ActivateInWorldEvent args)
{
if (args.Handled) return;
if (args.Handled)
return;
if (ToggleStatus(args.User, component))
if (ToggleStatus(args.User, uid, component))
args.Handled = true;
}
@ -131,9 +132,9 @@ namespace Content.Server.Light.EntitySystems
/// Illuminates the light if it is not active, extinguishes it if it is active.
/// </summary>
/// <returns>True if the light's status was toggled, false otherwise.</returns>
public bool ToggleStatus(EntityUid user, HandheldLightComponent component)
public bool ToggleStatus(EntityUid user, EntityUid uid, HandheldLightComponent component)
{
return component.Activated ? TurnOff(component) : TurnOn(user, component);
return component.Activated ? TurnOff(uid, component) : TurnOn(user, uid, component);
}
private void OnExamine(EntityUid uid, HandheldLightComponent component, ExaminedEvent args)
@ -155,14 +156,16 @@ namespace Content.Server.Light.EntitySystems
foreach (var handheld in _activeLights)
{
var uid = handheld.Owner;
if (handheld.Deleted)
{
toRemove.Add(handheld);
continue;
}
if (Paused(handheld.Owner)) continue;
TryUpdate(handheld, frameTime);
if (Paused(uid)) continue;
TryUpdate(uid, handheld, frameTime);
}
foreach (var light in toRemove)
@ -173,46 +176,47 @@ namespace Content.Server.Light.EntitySystems
private void AddToggleLightVerb(EntityUid uid, HandheldLightComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract) return;
if (!args.CanAccess || !args.CanInteract)
return;
ActivationVerb verb = new()
{
Text = Loc.GetString("verb-common-toggle-light"),
IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/light.svg.192dpi.png")),
Act = component.Activated
? () => TurnOff(component)
: () => TurnOn(args.User, component)
? () => TurnOff(uid, component)
: () => TurnOn(args.User, uid, component)
};
args.Verbs.Add(verb);
}
public bool TurnOff(HandheldLightComponent component, bool makeNoise = true)
public bool TurnOff(EntityUid uid, HandheldLightComponent component, bool makeNoise = true)
{
if (!component.Activated || !TryComp<PointLightComponent>(component.Owner, out var pointLightComponent))
if (!component.Activated || !TryComp<PointLightComponent>(uid, out var pointLightComponent))
{
return false;
}
pointLightComponent.Enabled = false;
SetActivated(component.Owner, false, component, makeNoise);
SetActivated(uid, false, component, makeNoise);
component.Level = null;
_activeLights.Remove(component);
return true;
}
public bool TurnOn(EntityUid user, HandheldLightComponent component)
public bool TurnOn(EntityUid user, EntityUid uid, HandheldLightComponent component)
{
if (component.Activated || !TryComp<PointLightComponent>(component.Owner, out var pointLightComponent))
if (component.Activated || !TryComp<PointLightComponent>(uid, out var pointLightComponent))
{
return false;
}
if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery) &&
!TryComp(component.Owner, out battery))
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery) &&
!TryComp(uid, out battery))
{
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), component.Owner);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-missing-message"), component.Owner, user);
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), uid);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-missing-message"), uid, user);
return false;
}
@ -221,52 +225,52 @@ namespace Content.Server.Light.EntitySystems
// Simple enough.
if (component.Wattage > battery.CurrentCharge)
{
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), component.Owner);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-dead-message"), component.Owner, user);
_audio.PlayPvs(_audio.GetSound(component.TurnOnFailSound), uid);
_popup.PopupEntity(Loc.GetString("handheld-light-component-cell-dead-message"), uid, user);
return false;
}
pointLightComponent.Enabled = true;
SetActivated(component.Owner, true, component, true);
SetActivated(uid, true, component, true);
_activeLights.Add(component);
return true;
}
public void TryUpdate(HandheldLightComponent component, float frameTime)
public void TryUpdate(EntityUid uid, HandheldLightComponent component, float frameTime)
{
if (!_powerCell.TryGetBatteryFromSlot(component.Owner, out var battery) &&
!TryComp(component.Owner, out battery))
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery) &&
!TryComp(uid, out battery))
{
TurnOff(component, false);
TurnOff(uid, component, false);
return;
}
var appearanceComponent = EntityManager.GetComponent<AppearanceComponent>(component.Owner);
var appearanceComponent = EntityManager.GetComponentOrNull<AppearanceComponent>(uid);
var fraction = battery.CurrentCharge / battery.MaxCharge;
if (fraction >= 0.30)
{
_appearance.SetData(component.Owner, HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower, appearanceComponent);
_appearance.SetData(uid, HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower, appearanceComponent);
}
else if (fraction >= 0.10)
{
_appearance.SetData(component.Owner, HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower, appearanceComponent);
_appearance.SetData(uid, HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower, appearanceComponent);
}
else
{
_appearance.SetData(component.Owner, HandheldLightVisuals.Power, HandheldLightPowerStates.Dying, appearanceComponent);
_appearance.SetData(uid, HandheldLightVisuals.Power, HandheldLightPowerStates.Dying, appearanceComponent);
}
if (component.Activated && !battery.TryUseCharge(component.Wattage * frameTime))
TurnOff(component, false);
TurnOff(uid, component, false);
UpdateLevel(component);
UpdateLevel(uid, component);
}
private void UpdateLevel(HandheldLightComponent comp)
private void UpdateLevel(EntityUid uid, HandheldLightComponent comp)
{
var level = GetLevel(comp);
var level = GetLevel(uid, comp);
if (level == comp.Level)
return;

View file

@ -8,6 +8,7 @@ using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Light.EntitySystems
{
@ -48,7 +49,7 @@ namespace Content.Server.Light.EntitySystems
ActivationVerb verb = new();
verb.Text = Loc.GetString("toggle-flashlight-verb-get-data-text");
verb.IconTexture = "/Textures/Interface/VerbIcons/light.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/light.svg.192dpi.png"));
verb.Act = () => ToggleLight(uid, component);
verb.Priority = -1; // For things like PDA's, Open-UI and other verbs that should be higher priority.

View file

@ -3,6 +3,7 @@ using Content.Shared.Interaction;
using Content.Shared.MachineLinking;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Utility;
namespace Content.Server.MachineLinking.System
{
@ -65,7 +66,7 @@ namespace Content.Server.MachineLinking.System
Category = VerbCategory.Lever,
Message = Loc.GetString("two-way-lever-cant"),
Disabled = component.State == TwoWayLeverState.Left,
IconTexture = $"/Textures/Interface/VerbIcons/{_leftToggleImage}",
Icon = new SpriteSpecifier.Texture(new ResourcePath($"/Textures/Interface/VerbIcons/{_leftToggleImage}")),
Text = Loc.GetString("two-way-lever-left"),
};
@ -86,7 +87,7 @@ namespace Content.Server.MachineLinking.System
Category = VerbCategory.Lever,
Message = Loc.GetString("two-way-lever-cant"),
Disabled = component.State == TwoWayLeverState.Right,
IconTexture = $"/Textures/Interface/VerbIcons/{_rightToggleImage}",
Icon = new SpriteSpecifier.Texture(new ResourcePath($"/Textures/Interface/VerbIcons/{_rightToggleImage}")),
Text = Loc.GetString("two-way-lever-right"),
};

View file

@ -39,6 +39,12 @@ namespace Content.Server.Medical.Components
[DataField("delay")]
public float Delay = 3f;
/// <summary>
/// Cancel token to prevent rapid healing
/// </summary>
[DataField("cancelToken")]
public CancellationTokenSource? CancelToken;
/// <summary>
/// Delay multiplier when healing yourself.
/// </summary>

View file

@ -1,3 +1,4 @@
using System.Threading;
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.DoAfter;
@ -40,6 +41,12 @@ public sealed class HealingSystem : EntitySystem
private void OnDoAfter(EntityUid uid, DamageableComponent component, DoAfterEvent<HealingData> args)
{
if (args.Cancelled)
{
args.AdditionalData.HealingComponent.CancelToken = null;
return;
}
if (args.Handled || args.Cancelled || _mobStateSystem.IsDead(uid) || args.Args.Used == null)
return;
@ -67,6 +74,7 @@ public sealed class HealingSystem : EntitySystem
if (args.AdditionalData.HealingComponent.HealingEndSound != null)
_audio.PlayPvs(args.AdditionalData.HealingComponent.HealingEndSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
args.AdditionalData.HealingComponent.CancelToken = null;
args.Handled = true;
}
@ -90,7 +98,7 @@ public sealed class HealingSystem : EntitySystem
private bool TryHeal(EntityUid uid, EntityUid user, EntityUid target, HealingComponent component)
{
if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage))
if (_mobStateSystem.IsDead(target) || !TryComp<DamageableComponent>(target, out var targetDamage) || component.CancelToken != null)
return false;
if (targetDamage.TotalDamage == 0)
@ -108,14 +116,21 @@ public sealed class HealingSystem : EntitySystem
if (component.HealingBeginSound != null)
_audio.PlayPvs(component.HealingBeginSound, uid, AudioHelpers.WithVariation(0.125f, _random).WithVolume(-5f));
var delay = user != target
var isNotSelf = user != target;
var delay = isNotSelf
? component.Delay
: component.Delay * GetScaledHealingPenalty(user, component);
component.CancelToken = new CancellationTokenSource();
var healingData = new HealingData(component, stack);
var doAfterEventArgs = new DoAfterEventArgs(user, delay, target: target, used: uid)
var doAfterEventArgs = new DoAfterEventArgs(user, delay, cancelToken: component.CancelToken.Token,target: target, used: uid)
{
//Raise the event on the target if it's not self, otherwise raise it on self.
RaiseOnTarget = isNotSelf,
RaiseOnUser = !isNotSelf,
BreakOnUserMove = true,
BreakOnTargetMove = true,
// Didn't break on damage as they may be trying to prevent it and

View file

@ -11,6 +11,7 @@ using Content.Shared.Verbs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.DoAfter;
using Robust.Shared.Utility;
namespace Content.Server.Medical
{
@ -82,7 +83,7 @@ namespace Content.Server.Medical
StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter
},
Text = Loc.GetString("stethoscope-verb"),
IconTexture = "Clothing/Neck/Misc/stethoscope.rsi/icon.png",
Icon = new SpriteSpecifier.Rsi(new ResourcePath("Clothing/Neck/Misc/stethoscope.rsi"), "icon"),
Priority = 2
};
args.Verbs.Add(verb);

View file

@ -1,10 +1,9 @@
using Content.Server.Access.Systems;
using Content.Server.Administration;
using Content.Server.Administration.Systems;
using Content.Server.Cloning;
using Content.Server.Mind.Components;
using Content.Server.PDA;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Administration;
using Content.Shared.PDA;

View file

@ -0,0 +1,20 @@
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Motd;
/// <summary>
/// A command that can be used by any player to print the Message of the Day.
/// </summary>
[AnyCommand]
public sealed class GetMotdCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Command => "get-motd";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entityManager.EntitySysManager.GetEntitySystem<MOTDSystem>().TrySendMOTD(shell);
}
}

View file

@ -0,0 +1,36 @@
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
namespace Content.Server.Motd;
/// <summary>
/// A console command which acts as an alias for <see cref="GetMotdCommand"/> or <see cref="SetMotdCommand"/> depending on the number of arguments given.
/// </summary>
[AnyCommand]
internal sealed class MOTDCommand : LocalizedCommands
{
[Dependency] private readonly IAdminManager _adminManager = default!;
public override string Command => "motd";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = (IPlayerSession?)shell.Player;
if (args.Length < 1 || (player != null && _adminManager is AdminManager aMan && !aMan.CanCommand(player, "set-motd")))
shell.ConsoleHost.ExecuteCommand(shell.Player, "get-motd");
else
shell.ConsoleHost.ExecuteCommand(shell.Player, $"set-motd {string.Join(" ", args)}");
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
var player = (IPlayerSession?)shell.Player;
if (player != null && _adminManager is AdminManager aMan && !aMan.CanCommand(player, "set-motd"))
return CompletionResult.Empty;
if (args.Length == 1)
return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-head"));
return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-cont"));
}
}

View file

@ -0,0 +1,101 @@
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Shared.CCVar;
using Content.Shared.Chat;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Configuration;
namespace Content.Server.Motd;
/// <summary>
/// The system that handles broadcasting the Message Of The Day to players when they join the lobby/the MOTD changes/they ask for it to be printed.
/// </summary>
public sealed class MOTDSystem : EntitySystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
/// <summary>
/// The cached value of the Message of the Day. Used for fast access.
/// </summary>
private string _messageOfTheDay = "";
public override void Initialize()
{
base.Initialize();
_configurationManager.OnValueChanged(CCVars.MOTD, OnMOTDChanged, invokeImmediately: true);
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
}
public override void Shutdown()
{
_configurationManager.UnsubValueChanged(CCVars.MOTD, OnMOTDChanged);
base.Shutdown();
}
/// <summary>
/// Sends the Message Of The Day, if any, to all connected players.
/// </summary>
public void TrySendMOTD()
{
if (string.IsNullOrEmpty(_messageOfTheDay))
return;
var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay));
_chatManager.ChatMessageToAll(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, recordReplay: true);
}
/// <summary>
/// Sends the Message Of The Day, if any, to a specific player.
/// </summary>
public void TrySendMOTD(IPlayerSession player)
{
if (string.IsNullOrEmpty(_messageOfTheDay))
return;
var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay));
_chatManager.ChatMessageToOne(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, client: player.ConnectedClient);
}
/// <summary>
/// Sends the Message Of The Day, if any, to a specific player's console and chat.
/// </summary>
/// <remarks>
/// This is used by the MOTD console command because we can't tell whether the player is using `console or /console so we send the message to both.
/// </remarks>
public void TrySendMOTD(IConsoleShell shell)
{
if (string.IsNullOrEmpty(_messageOfTheDay))
return;
var wrappedMessage = Loc.GetString("motd-wrap-message", ("motd", _messageOfTheDay));
shell.WriteLine(wrappedMessage);
if (shell.Player is IPlayerSession player)
_chatManager.ChatMessageToOne(ChatChannel.Server, _messageOfTheDay, wrappedMessage, source: EntityUid.Invalid, hideChat: false, client: player.ConnectedClient);
}
#region Event Handlers
/// <summary>
/// Posts the Message Of The Day to any players who join the lobby.
/// </summary>
private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev)
{
TrySendMOTD(ev.PlayerSession);
}
/// <summary>
/// Broadcasts changes to the Message Of The Day to all players.
/// </summary>
private void OnMOTDChanged(string val)
{
if (val == _messageOfTheDay)
return;
_messageOfTheDay = val;
TrySendMOTD();
}
#endregion Event Handlers
}

View file

@ -0,0 +1,55 @@
using Content.Server.Administration;
using Content.Server.Administration.Logs;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.CCVar;
using Content.Server.Chat.Managers;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Motd;
/// <summary>
/// A console command usable by any user which prints or sets the Message of the Day.
/// </summary>
[AdminCommand(AdminFlags.Admin)]
public sealed class SetMotdCommand : LocalizedCommands
{
[Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override string Command => "set-motd";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
string motd = "";
var player = (IPlayerSession?)shell.Player;
if (args.Length > 0)
{
motd = string.Join(" ", args).Trim();
if (player != null && _chatManager.MessageCharacterLimit(player, motd))
return; // check function prints its own error response
}
_configurationManager.SetCVar(CCVars.MOTD, motd); // A hook in MOTDSystem broadcasts changes to the MOTD to everyone so we don't need to do it here.
if (string.IsNullOrEmpty(motd))
{
shell.WriteLine(Loc.GetString("cmd-set-motd-cleared-motd-message"));
_adminLogManager.Add(LogType.Chat, LogImpact.Low, $"{(player == null ? "LOCALHOST" : player.ConnectedClient.UserName):Player} cleared the MOTD for the server.");
}
else
{
shell.WriteLine(Loc.GetString("cmd-set-motd-set-motd-message", ("motd", motd)));
_adminLogManager.Add(LogType.Chat, LogImpact.Low, $"{(player == null ? "LOCALHOST" : player.ConnectedClient.UserName):Player} set the MOTD for the server to \"{motd:motd}\"");
}
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-head"));
return CompletionResult.FromHint(Loc.GetString("cmd-set-motd-hint-cont"));
}
}

View file

@ -34,6 +34,13 @@ namespace Content.Server.Nutrition.Components
[DataField("burstSound")]
public SoundSpecifier BurstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg");
/// <summary>
/// Is this drink being forced on someone else?
/// </summary>
/// <returns></returns>
[DataField("forceDrink")]
public bool ForceDrink;
/// <summary>
/// How long it takes to drink this yourself.
/// </summary>

View file

@ -40,6 +40,13 @@ namespace Content.Server.Nutrition.Components
[DataField("eatMessage")]
public string EatMessage = "food-nom";
/// <summary>
/// Is this entity being forcefed?
/// Prevents the entity from being forced to eat multiple times if not self
/// </summary>
[DataField("forceFeed")]
public bool ForceFeed;
/// <summary>
/// How long it takes to eat the food personally.
/// </summary>

View file

@ -217,7 +217,7 @@ namespace Content.Server.Nutrition.EntitySystems
private bool TryDrink(EntityUid user, EntityUid target, DrinkComponent drink, EntityUid item)
{
if (!EntityManager.HasComponent<BodyComponent>(target))
if (!EntityManager.HasComponent<BodyComponent>(target) || drink.ForceDrink)
return false;
if (!drink.Opened)
@ -241,9 +241,9 @@ namespace Content.Server.Nutrition.EntitySystems
if (!_interactionSystem.InRangeUnobstructed(user, item, popup: true))
return true;
var forceDrink = user != target;
drink.ForceDrink = user != target;
if (forceDrink)
if (drink.ForceDrink)
{
var userName = Identity.Entity(user, EntityManager);
@ -264,7 +264,7 @@ namespace Content.Server.Nutrition.EntitySystems
var drinkData = new DrinkData(drinkSolution, flavors);
var doAfterEventArgs = new DoAfterEventArgs(user, forceDrink ? drink.ForceFeedDelay : drink.Delay,
var doAfterEventArgs = new DoAfterEventArgs(user, drink.ForceDrink ? drink.ForceFeedDelay : drink.Delay,
target: target, used: item)
{
BreakOnUserMove = moveBreak,
@ -286,6 +286,14 @@ namespace Content.Server.Nutrition.EntitySystems
/// </summary>
private void OnDoAfter(EntityUid uid, DrinkComponent component, DoAfterEvent<DrinkData> args)
{
//Special cancel if they're force feeding someone.
//Allows self to drink multiple times but prevents force feeding drinks to others rapidly.
if (args.Cancelled && component.ForceDrink)
{
component.ForceDrink = false;
return;
}
if (args.Handled || args.Cancelled || component.Deleted)
return;
@ -295,11 +303,11 @@ namespace Content.Server.Nutrition.EntitySystems
var transferAmount = FixedPoint2.Min(component.TransferAmount, args.AdditionalData.DrinkSolution.Volume);
var drained = _solutionContainerSystem.Drain(uid, args.AdditionalData.DrinkSolution, transferAmount);
var forceDrink = args.Args.Target.Value != args.Args.User;
//var forceDrink = args.Args.Target.Value != args.Args.User;
if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(args.Args.Target.Value, out var stomachs, body))
{
_popupSystem.PopupEntity(forceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
_popupSystem.PopupEntity(component.ForceDrink ? Loc.GetString("drink-component-try-use-drink-cannot-drink-other") : Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.User);
if (HasComp<RefillableSolutionComponent>(args.Args.Target.Value))
{
@ -320,7 +328,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough"), args.Args.Target.Value, args.Args.Target.Value);
if (forceDrink)
if (component.ForceDrink)
{
_popupSystem.PopupEntity(Loc.GetString("drink-component-try-use-drink-had-enough-other"), args.Args.Target.Value, args.Args.User);
_spillableSystem.SpillAt(args.Args.Target.Value, drained, "PuddleSmear");
@ -334,7 +342,7 @@ namespace Content.Server.Nutrition.EntitySystems
var flavors = args.AdditionalData.FlavorMessage;
if (forceDrink)
if (component.ForceDrink)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
@ -366,6 +374,7 @@ namespace Content.Server.Nutrition.EntitySystems
//TODO: Grab the stomach UIDs somehow without using Owner
_stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, drained, firstStomach.Value.Comp);
component.ForceDrink = false;
args.Handled = true;
}
@ -387,7 +396,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
TryDrink(ev.User, ev.User, component, uid);
},
IconTexture = "/Textures/Interface/VerbIcons/drink.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/drink.svg.192dpi.png")),
Text = Loc.GetString("drink-system-verb-drink"),
Priority = 2
};

View file

@ -86,8 +86,8 @@ namespace Content.Server.Nutrition.EntitySystems
if (food == user || EntityManager.TryGetComponent<MobStateComponent>(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs
return false;
// Target can't be fed
if (!EntityManager.HasComponent<BodyComponent>(target))
// Target can't be fed or they're already forcefeeding
if (!EntityManager.HasComponent<BodyComponent>(target) || foodComp.ForceFeed)
return false;
if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution))
@ -111,9 +111,9 @@ namespace Content.Server.Nutrition.EntitySystems
if (!_interactionSystem.InRangeUnobstructed(user, food, popup: true))
return true;
var forceFeed = user != target;
foodComp.ForceFeed = user != target;
if (forceFeed)
if (foodComp.ForceFeed)
{
var userName = Identity.Entity(user, EntityManager);
_popupSystem.PopupEntity(Loc.GetString("food-system-force-feed", ("user", userName)),
@ -128,16 +128,16 @@ namespace Content.Server.Nutrition.EntitySystems
_adminLogger.Add(LogType.Ingestion, LogImpact.Low, $"{ToPrettyString(target):target} is eating {ToPrettyString(food):food} {SolutionContainerSystem.ToPrettyString(foodSolution)}");
}
var moveBreak = user != target;
var foodData = new FoodData(foodSolution, flavors, utensils);
var doAfterEventArgs = new DoAfterEventArgs(user, forceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
var doAfterEventArgs = new DoAfterEventArgs(user, foodComp.ForceFeed ? foodComp.ForceFeedDelay : foodComp.Delay, target: target, used: food)
{
BreakOnUserMove = moveBreak,
RaiseOnTarget = foodComp.ForceFeed,
RaiseOnUser = !foodComp.ForceFeed,
BreakOnUserMove = foodComp.ForceFeed,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = moveBreak,
BreakOnTargetMove = foodComp.ForceFeed,
MovementThreshold = 0.01f,
DistanceThreshold = 1.0f,
NeedHand = true
@ -151,6 +151,13 @@ namespace Content.Server.Nutrition.EntitySystems
private void OnDoAfter(EntityUid uid, FoodComponent component, DoAfterEvent<FoodData> args)
{
//Prevents the target from being force fed food but allows the user to chow down
if (args.Cancelled && component.ForceFeed)
{
component.ForceFeed = false;
return;
}
if (args.Cancelled || args.Handled || component.Deleted || args.Args.Target == null)
return;
@ -166,13 +173,11 @@ namespace Content.Server.Nutrition.EntitySystems
//TODO: Get the stomach UID somehow without nabbing owner
var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
var forceFeed = args.Args.Target.Value != args.Args.User;
// No stomach so just popup a message that they can't eat.
if (firstStomach == null)
{
_solutionContainerSystem.TryAddSolution(uid, args.AdditionalData.FoodSolution, split);
_popupSystem.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
_popupSystem.PopupEntity(component.ForceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Args.Target.Value, args.Args.User);
args.Handled = true;
return;
}
@ -182,7 +187,7 @@ namespace Content.Server.Nutrition.EntitySystems
var flavors = args.AdditionalData.FlavorMessage;
if (forceFeed)
if (component.ForceFeed)
{
var targetName = Identity.Entity(args.Args.Target.Value, EntityManager);
var userName = Identity.Entity(args.Args.User, EntityManager);
@ -264,7 +269,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
TryFeed(ev.User, ev.User, uid, component);
},
IconTexture = "/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/cutlery.svg.192dpi.png")),
Text = Loc.GetString("food-system-verb-eat"),
Priority = -1
};

View file

@ -1,6 +1,7 @@
using Content.Server.Cargo.Systems;
using Content.Server.Power.Components;
using Content.Shared.Examine;
using Content.Shared.Rejuvenate;
using JetBrains.Annotations;
namespace Content.Server.Power.EntitySystems
@ -13,12 +14,24 @@ namespace Content.Server.Power.EntitySystems
base.Initialize();
SubscribeLocalEvent<ExaminableBatteryComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate);
SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
SubscribeLocalEvent<NetworkBatteryPreSync>(PreSync);
SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
}
private void OnNetBatteryRejuvenate(EntityUid uid, PowerNetworkBatteryComponent component, RejuvenateEvent args)
{
component.NetworkBattery.CurrentStorage = component.NetworkBattery.Capacity;
}
private void OnBatteryRejuvenate(EntityUid uid, BatteryComponent component, RejuvenateEvent args)
{
component.CurrentCharge = component.MaxCharge;
}
private void OnExamine(EntityUid uid, ExaminableBatteryComponent component, ExaminedEvent args)
{
if (!TryComp<BatteryComponent>(uid, out var batteryComponent))

View file

@ -52,7 +52,7 @@ namespace Content.Server.Power.EntitySystems
Message = Loc.GetString("cable-multitool-system-verb-tooltip"),
Text = Loc.GetString("cable-multitool-system-verb-name"),
Category = VerbCategory.Examine,
IconTexture = "/Textures/Interface/VerbIcons/zap.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")),
Act = () =>
{
var markup = FormattedMessage.FromMarkup(GenerateCableMarkup(uid));

View file

@ -7,14 +7,19 @@ using Content.Shared.Verbs;
using Content.Shared.Database;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Robust.Shared.Utility;
namespace Content.Server.Power.EntitySystems
{
public sealed class PowerReceiverSystem : EntitySystem
{
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
@ -27,9 +32,25 @@ namespace Content.Server.Power.EntitySystems
SubscribeLocalEvent<ApcPowerProviderComponent, ExtensionCableSystem.ReceiverConnectedEvent>(OnReceiverConnected);
SubscribeLocalEvent<ApcPowerProviderComponent, ExtensionCableSystem.ReceiverDisconnectedEvent>(OnReceiverDisconnected);
SubscribeLocalEvent<ApcPowerReceiverComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
SubscribeLocalEvent<PowerSwitchComponent, GetVerbsEvent<AlternativeVerb>>(AddSwitchPowerVerb);
}
private void OnGetVerbs(EntityUid uid, ApcPowerReceiverComponent component, GetVerbsEvent<Verb> args)
{
if (!_adminManager.HasAdminFlag(args.User, AdminFlags.Admin))
return;
// add debug verb to toggle power requirements
args.Verbs.Add(new()
{
Text = Loc.GetString("verb-debug-toggle-need-power"),
Category = VerbCategory.Debug,
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")), // "smite" is a lightning bolt
Act = () => component.NeedsPower = !component.NeedsPower
});
}
///<summary>
///Adds some markup to the examine text of whatever object is using this component to tell you if it's powered or not, even if it doesn't have an icon state to do this for you.
///</summary>
@ -106,7 +127,7 @@ namespace Content.Server.Power.EntitySystems
{
TogglePower(uid, user: args.User);
},
IconTexture = "/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")),
Text = Loc.GetString("power-switch-component-toggle-verb"),
Priority = -3
};

View file

@ -11,6 +11,7 @@ using Robust.Shared.Containers;
using System.Diagnostics.CodeAnalysis;
using Content.Server.Kitchen.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Rejuvenate;
namespace Content.Server.PowerCell;
@ -29,6 +30,7 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<PowerCellComponent, ChargeChangedEvent>(OnChargeChanged);
SubscribeLocalEvent<PowerCellComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<PowerCellComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<PowerCellComponent, ExaminedEvent>(OnCellExamined);
@ -37,6 +39,11 @@ public sealed class PowerCellSystem : SharedPowerCellSystem
SubscribeLocalEvent<BatteryComponent, BeingMicrowavedEvent>(OnMicrowaved);
}
private void OnRejuvenate(EntityUid uid, PowerCellComponent component, RejuvenateEvent args)
{
component.IsRigged = false;
}
private void OnSlotMicrowaved(EntityUid uid, PowerCellSlotComponent component, BeingMicrowavedEvent args)
{
if (_itemSlotsSystem.TryGetSlot(uid, component.CellSlotId, out ItemSlot? slot))

View file

@ -1,4 +1,6 @@
namespace Content.Server.Prayer
using Robust.Shared.Utility;
namespace Content.Server.Prayer
{
/// <summary>
/// Allows an entity to be prayed on in the context menu
@ -39,7 +41,7 @@
/// </summary>
[DataField("verbImage")]
[ViewVariables(VVAccess.ReadOnly)]
public string VerbImage = "/Textures/Interface/pray.svg.png";
public SpriteSpecifier? VerbImage = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/pray.svg.png"));
}
}

View file

@ -45,7 +45,7 @@ public sealed class PrayerSystem : EntitySystem
var prayerVerb = new ActivationVerb
{
Text = Loc.GetString(comp.Verb),
IconTexture = comp.VerbImage == "" ? null : comp.VerbImage,
Icon = comp.VerbImage,
Act = () =>
{
if (comp.BibleUserOnly && !EntityManager.TryGetComponent<BibleUserComponent>(args.User, out var bibleUser))

View file

@ -1,8 +1,10 @@
using Content.Server.Popups;
using Content.Shared.Popups;
using Content.Shared.Rotatable;
using Content.Shared.Verbs;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
namespace Content.Server.Rotatable
{
@ -11,6 +13,8 @@ namespace Content.Server.Rotatable
/// </summary>
public sealed class RotatableSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popup = default!;
public override void Initialize()
{
SubscribeLocalEvent<FlippableComponent, GetVerbsEvent<Verb>>(AddFlipVerb);
@ -19,13 +23,15 @@ namespace Content.Server.Rotatable
private void AddFlipVerb(EntityUid uid, FlippableComponent component, GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || component.MirrorEntity == null)
if (!args.CanAccess || !args.CanInteract)
return;
Verb verb = new();
verb.Act = () => TryFlip(component, args.User);
verb.Text = Loc.GetString("flippable-verb-get-data-text");
verb.DoContactInteraction = true;
Verb verb = new()
{
Act = () => TryFlip(uid, component, args.User),
Text = Loc.GetString("flippable-verb-get-data-text"),
DoContactInteraction = true
};
// TODO VERB ICONS Add Uno reverse card style icon?
args.Verbs.Add(verb);
}
@ -39,16 +45,16 @@ namespace Content.Server.Rotatable
// Check if the object is anchored, and whether we are still allowed to rotate it.
if (!component.RotateWhileAnchored &&
EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physics) &&
EntityManager.TryGetComponent(uid, out PhysicsComponent? physics) &&
physics.BodyType == BodyType.Static)
return;
Verb resetRotation = new ()
{
DoContactInteraction = true,
Act = () => EntityManager.GetComponent<TransformComponent>(component.Owner).LocalRotation = Angle.Zero,
Act = () => EntityManager.GetComponent<TransformComponent>(uid).LocalRotation = Angle.Zero,
Category = VerbCategory.Rotate,
IconTexture = "/Textures/Interface/VerbIcons/refresh.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/refresh.svg.192dpi.png")),
Text = "Reset",
Priority = -2, // show CCW, then CW, then reset
CloseMenu = false,
@ -58,9 +64,9 @@ namespace Content.Server.Rotatable
// rotate clockwise
Verb rotateCW = new()
{
Act = () => EntityManager.GetComponent<TransformComponent>(component.Owner).LocalRotation -= component.Increment,
Act = () => EntityManager.GetComponent<TransformComponent>(uid).LocalRotation -= component.Increment,
Category = VerbCategory.Rotate,
IconTexture = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png")),
Priority = -1,
CloseMenu = false, // allow for easy double rotations.
};
@ -69,9 +75,9 @@ namespace Content.Server.Rotatable
// rotate counter-clockwise
Verb rotateCCW = new()
{
Act = () => EntityManager.GetComponent<TransformComponent>(component.Owner).LocalRotation += component.Increment,
Act = () => EntityManager.GetComponent<TransformComponent>(uid).LocalRotation += component.Increment,
Category = VerbCategory.Rotate,
IconTexture = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png")),
Priority = 0,
CloseMenu = false, // allow for easy double rotations.
};
@ -81,21 +87,21 @@ namespace Content.Server.Rotatable
/// <summary>
/// Replace a flippable entity with it's flipped / mirror-symmetric entity.
/// </summary>
public void TryFlip(FlippableComponent component, EntityUid user)
public void TryFlip(EntityUid uid, FlippableComponent component, EntityUid user)
{
if (EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physics) &&
if (EntityManager.TryGetComponent(uid, out PhysicsComponent? physics) &&
physics.BodyType == BodyType.Static)
{
component.Owner.PopupMessage(user, Loc.GetString("flippable-component-try-flip-is-stuck"));
_popup.PopupEntity(Loc.GetString("flippable-component-try-flip-is-stuck"), uid, user);
return;
}
var oldTransform = EntityManager.GetComponent<TransformComponent>(component.Owner);
var oldTransform = EntityManager.GetComponent<TransformComponent>(uid);
var entity = EntityManager.SpawnEntity(component.MirrorEntity, oldTransform.Coordinates);
var newTransform = EntityManager.GetComponent<TransformComponent>(entity);
newTransform.LocalRotation = oldTransform.LocalRotation;
newTransform.Anchored = false;
EntityManager.DeleteEntity(component.Owner);
EntityManager.DeleteEntity(uid);
}
}
}

View file

@ -1,13 +0,0 @@
using Content.Shared.StationRecords;
namespace Content.Server.StationRecords;
[RegisterComponent]
public sealed class StationRecordKeyStorageComponent : Component
{
/// <summary>
/// The key stored in this component.
/// </summary>
[ViewVariables]
public StationRecordKey? Key;
}

View file

@ -1,3 +1,5 @@
using Content.Server.StationRecords.Systems;
namespace Content.Server.StationRecords;
[Access(typeof(StationRecordsSystem))]

View file

@ -1,9 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Access.Systems;
using System.Linq;
using Content.Server.GameTicking;
using Content.Server.Station.Systems;
using Content.Server.StationRecords;
using Content.Server.StationRecords.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Inventory;
using Content.Shared.PDA;
@ -13,6 +11,8 @@ using Content.Shared.StationRecords;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Server.StationRecords.Systems;
/// <summary>
/// Station records.
///

View file

@ -10,6 +10,7 @@ using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Content.Server.Sticky.Systems;
@ -57,7 +58,7 @@ public sealed class StickySystem : EntitySystem
{
DoContactInteraction = true,
Text = Loc.GetString("comp-sticky-unstick-verb-text"),
IconTexture = "/Textures/Interface/VerbIcons/eject.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
Act = () => StartUnsticking(uid, args.User, component)
});
}

View file

@ -1,97 +1,12 @@
using Content.Server.Atmos;
using Content.Shared.Physics;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Content.Shared.Storage.Components;
using Robust.Shared.GameStates;
namespace Content.Server.Storage.Components;
[RegisterComponent]
public sealed class EntityStorageComponent : Component, IGasMixtureHolder
[RegisterComponent, ComponentReference(typeof(SharedEntityStorageComponent))]
public sealed class EntityStorageComponent : SharedEntityStorageComponent, IGasMixtureHolder
{
public readonly float MaxSize = 1.0f; // maximum width or height of an entity allowed inside the storage.
public const float GasMixVolume = 70f;
public static readonly TimeSpan InternalOpenAttemptDelay = TimeSpan.FromSeconds(0.5);
public TimeSpan LastInternalOpenAttempt;
/// <summary>
/// Collision masks that get removed when the storage gets opened.
/// </summary>
public readonly int MasksToRemove = (int) (
CollisionGroup.MidImpassable |
CollisionGroup.HighImpassable |
CollisionGroup.LowImpassable);
/// <summary>
/// Collision masks that were removed from ANY layer when the storage was opened;
/// </summary>
[DataField("removedMasks")]
public int RemovedMasks;
[DataField("capacity")]
public int Capacity = 30;
[DataField("isCollidableWhenOpen")]
public bool IsCollidableWhenOpen;
/// <summary>
/// If true, it opens the storage when the entity inside of it moves
/// If false, it prevents the storage from opening when the entity inside of it moves.
/// This is for objects that you want the player to move while inside, like large cardboard boxes, without opening the storage.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("openOnMove")]
public bool OpenOnMove = true;
//The offset for where items are emptied/vacuumed for the EntityStorage.
[DataField("enteringOffset")]
public Vector2 EnteringOffset = new(0, 0);
//The collision groups checked, so that items are depositied or grabbed from inside walls.
[DataField("enteringOffsetCollisionFlags")]
public readonly CollisionGroup EnteringOffsetCollisionFlags = CollisionGroup.Impassable | CollisionGroup.MidImpassable;
[DataField("enteringRange")]
public float EnteringRange = 0.18f;
[DataField("showContents")]
public bool ShowContents;
[DataField("occludesLight")]
public bool OccludesLight = true;
[DataField("deleteContentsOnDestruction"), ViewVariables(VVAccess.ReadWrite)]
public bool DeleteContentsOnDestruction = false;
/// <summary>
/// Whether or not the container is sealed and traps air inside of it
/// </summary>
[DataField("airtight"), ViewVariables(VVAccess.ReadWrite)]
public bool Airtight = true;
[DataField("open")]
public bool Open;
[DataField("closeSound")]
public SoundSpecifier CloseSound = new SoundPathSpecifier("/Audio/Effects/closetclose.ogg");
[DataField("openSound")]
public SoundSpecifier OpenSound = new SoundPathSpecifier("/Audio/Effects/closetopen.ogg");
/// <summary>
/// Whitelist for what entities are allowed to be inserted into this container. If this is not null, the
/// standard requirement that the entity must be an item or mob is waived.
/// </summary>
[DataField("whitelist")]
public EntityWhitelist? Whitelist;
[ViewVariables]
public Container Contents = default!;
[ViewVariables(VVAccess.ReadWrite)]
public bool IsWeldedShut;
/// <summary>
/// Gas currently contained in this entity storage.
/// None while open. Grabs gas from the atmosphere when closed, and exposes any entities inside to it.

View file

@ -12,7 +12,6 @@ namespace Content.Server.Storage.Components
/// <summary>
/// The list of entities to spawn, with amounts and orGroups.
/// </summary>
/// <returns></returns>
[DataField("items", required: true)]
public List<EntitySpawnEntry> Items = new();
@ -25,7 +24,6 @@ namespace Content.Server.Storage.Components
/// <summary>
/// How many uses before the item should delete itself.
/// </summary>
/// <returns></returns>
[DataField("uses")]
public int Uses = 1;
}

View file

@ -11,6 +11,7 @@ using Content.Shared.Placeable;
using Content.Shared.Storage;
using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Storage.EntitySystems
{
@ -54,7 +55,7 @@ namespace Content.Server.Storage.EntitySystems
StartDoAfter(uid, null, args.User, dumpable);//Had multiplier of 0.6f
},
Text = Loc.GetString("dump-verb-name"),
IconTexture = "/Textures/Interface/VerbIcons/drop.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/drop.svg.192dpi.png")),
};
args.Verbs.Add(verb);
}

View file

@ -1,93 +1,50 @@
using System.Linq;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Tools.Systems;
using Content.Shared.Body.Components;
using Content.Shared.Destructible;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Lock;
using Content.Shared.Placeable;
using Content.Shared.Storage;
using Content.Shared.Storage.Components;
using Content.Shared.Wall;
using Content.Shared.Whitelist;
using Robust.Server.Containers;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
namespace Content.Server.Storage.EntitySystems;
public sealed class EntityStorageSystem : EntitySystem
public sealed class EntityStorageSystem : SharedEntityStorageSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly ConstructionSystem _construction = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly PlaceableSurfaceSystem _placeableSurface = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmos = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IMapManager _map = default!;
public const string ContainerName = "entity_storage";
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EntityStorageComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EntityStorageComponent, ActivateInWorldEvent>(OnInteract);
SubscribeLocalEvent<EntityStorageComponent, WeldableAttemptEvent>(OnWeldableAttempt);
SubscribeLocalEvent<EntityStorageComponent, WeldableChangedEvent>(OnWelded);
SubscribeLocalEvent<EntityStorageComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
SubscribeLocalEvent<EntityStorageComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<InsideEntityStorageComponent, EntGotRemovedFromContainerMessage>(OnRemoved);
SubscribeLocalEvent<InsideEntityStorageComponent, InhaleLocationEvent>(OnInsideInhale);
SubscribeLocalEvent<InsideEntityStorageComponent, ExhaleLocationEvent>(OnInsideExhale);
SubscribeLocalEvent<InsideEntityStorageComponent, AtmosExposedGetAirEvent>(OnInsideExposed);
SubscribeLocalEvent<InsideEntityStorageComponent, EntGotRemovedFromContainerMessage>(OnRemoved);
}
private void OnInit(EntityUid uid, EntityStorageComponent component, ComponentInit args)
protected override void OnInit(EntityUid uid, SharedEntityStorageComponent component, ComponentInit args)
{
component.Contents = _container.EnsureContainer<Container>(uid, ContainerName);
component.Contents.ShowContents = component.ShowContents;
component.Contents.OccludesLight = component.OccludesLight;
base.OnInit(uid, component, args);
if (TryComp<ConstructionComponent>(uid, out var construction))
_construction.AddContainer(uid, ContainerName, construction);
if (TryComp<PlaceableSurfaceComponent>(uid, out var placeable))
_placeableSurface.SetPlaceable(uid, component.Open, placeable);
if (!component.Open)
{
// If we're closed on spawn, we need to pull some air into our environment from where we spawned,
// so that we have -something-. For example, if you bought an animal crate or something.
TakeGas(uid, component);
TakeGas(uid, (EntityStorageComponent) component);
}
}
private void OnInteract(EntityUid uid, EntityStorageComponent component, ActivateInWorldEvent args)
{
if (args.Handled)
return;
args.Handled = true;
ToggleOpen(args.User, uid, component);
}
private void OnWeldableAttempt(EntityUid uid, EntityStorageComponent component, WeldableAttemptEvent args)
{
if (component.Open)
@ -99,7 +56,7 @@ public sealed class EntityStorageSystem : EntitySystem
if (component.Contents.Contains(args.User))
{
var msg = Loc.GetString("entity-storage-component-already-contains-user-message");
_popupSystem.PopupEntity(msg, args.User, args.User);
Popup.PopupEntity(msg, args.User, args.User);
args.Cancel();
}
}
@ -107,334 +64,36 @@ public sealed class EntityStorageSystem : EntitySystem
private void OnWelded(EntityUid uid, EntityStorageComponent component, WeldableChangedEvent args)
{
component.IsWeldedShut = args.IsWelded;
Dirty(component);
}
private void OnLockToggleAttempt(EntityUid uid, EntityStorageComponent target, ref LockToggleAttemptEvent args)
{
// Cannot (un)lock open lockers.
if (target.Open)
args.Cancelled = true;
// Cannot (un)lock from the inside. Maybe a bad idea? Security jocks could trap nerds in lockers?
if (target.Contents.Contains(args.User))
args.Cancelled = true;
}
private void OnDestruction(EntityUid uid, EntityStorageComponent component, DestructionEventArgs args)
{
component.Open = true;
if (!component.DeleteContentsOnDestruction)
{
EmptyContents(uid, component);
return;
}
foreach (var ent in new List<EntityUid>(component.Contents.ContainedEntities))
{
EntityManager.DeleteEntity(ent);
}
}
public void ToggleOpen(EntityUid user, EntityUid target, EntityStorageComponent? component = null)
{
if (!Resolve(target, ref component))
return;
if (component.Open)
{
TryCloseStorage(target);
}
else
{
TryOpenStorage(user, target);
}
}
public void EmptyContents(EntityUid uid, EntityStorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var uidXform = Transform(uid);
var containedArr = component.Contents.ContainedEntities.ToArray();
foreach (var contained in containedArr)
{
Remove(contained, uid, component, uidXform);
}
}
public void OpenStorage(EntityUid uid, EntityStorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
var beforeev = new StorageBeforeOpenEvent();
RaiseLocalEvent(uid, ref beforeev);
component.Open = true;
EmptyContents(uid, component);
ModifyComponents(uid, component);
_audio.PlayPvs(component.OpenSound, uid);
ReleaseGas(uid, component);
var afterev = new StorageAfterOpenEvent();
RaiseLocalEvent(uid, ref afterev);
}
public void CloseStorage(EntityUid uid, EntityStorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
component.Open = false;
var targetCoordinates = new EntityCoordinates(uid, component.EnteringOffset);
var entities = _lookup.GetEntitiesInRange(targetCoordinates, component.EnteringRange, LookupFlags.Approximate | LookupFlags.Dynamic | LookupFlags.Sundries);
var ev = new StorageBeforeCloseEvent(entities, new());
RaiseLocalEvent(uid, ref ev);
var count = 0;
foreach (var entity in ev.Contents)
{
if (!ev.BypassChecks.Contains(entity))
{
if (!CanFit(entity, uid, component.Whitelist))
continue;
}
if (!AddToContents(entity, uid, component))
continue;
count++;
if (count >= component.Capacity)
break;
}
TakeGas(uid, component);
ModifyComponents(uid, component);
_audio.PlayPvs(component.CloseSound, uid);
component.LastInternalOpenAttempt = default;
var afterev = new StorageAfterCloseEvent();
RaiseLocalEvent(uid, ref afterev);
}
public bool Insert(EntityUid toInsert, EntityUid container, EntityStorageComponent? component = null)
{
if (!Resolve(container, ref component))
return false;
if (component.Open)
{
Transform(toInsert).WorldPosition = Transform(container).WorldPosition;
return true;
}
var inside = EnsureComp<InsideEntityStorageComponent>(toInsert);
inside.Storage = container;
return component.Contents.Insert(toInsert, EntityManager);
}
public bool Remove(EntityUid toRemove, EntityUid container, EntityStorageComponent? component = null, TransformComponent? xform = null)
{
if (!Resolve(container, ref component, ref xform, false))
return false;
RemComp<InsideEntityStorageComponent>(toRemove);
component.Contents.Remove(toRemove, EntityManager);
Transform(toRemove).WorldPosition = xform.WorldPosition + xform.WorldRotation.RotateVec(component.EnteringOffset);
return true;
}
public bool CanInsert(EntityUid container, EntityStorageComponent? component = null)
{
if (!Resolve(container, ref component))
return false;
if (component.Open)
return true;
if (component.Contents.ContainedEntities.Count >= component.Capacity)
return false;
return true;
}
public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false)
{
if (!CanOpen(user, target, silent))
return false;
OpenStorage(target);
return true;
}
public bool TryCloseStorage(EntityUid target)
{
if (!CanClose(target))
{
return false;
}
CloseStorage(target);
return true;
}
public bool CanOpen(EntityUid user, EntityUid target, bool silent = false, EntityStorageComponent? component = null)
{
if (!Resolve(target, ref component))
return false;
if (!HasComp<SharedHandsComponent>(user))
return false;
if (component.IsWeldedShut)
{
if (!silent && !component.Contents.Contains(user))
_popupSystem.PopupEntity(Loc.GetString("entity-storage-component-welded-shut-message"), target);
return false;
}
//Checks to see if the opening position, if offset, is inside of a wall.
if (component.EnteringOffset != (0, 0) && !HasComp<WallMountComponent>(target)) //if the entering position is offset
{
var newCoords = new EntityCoordinates(target, component.EnteringOffset);
if (!_interactionSystem.InRangeUnobstructed(target, newCoords, 0, collisionMask: component.EnteringOffsetCollisionFlags))
{
if (!silent)
_popupSystem.PopupEntity(Loc.GetString("entity-storage-component-cannot-open-no-space"), target);
return false;
}
}
var ev = new StorageOpenAttemptEvent(silent);
RaiseLocalEvent(target, ref ev, true);
return !ev.Cancelled;
}
public bool CanClose(EntityUid target, bool silent = false)
{
var ev = new StorageCloseAttemptEvent();
RaiseLocalEvent(target, ref ev, silent);
return !ev.Cancelled;
}
public bool AddToContents(EntityUid toAdd, EntityUid container, EntityStorageComponent? component = null)
{
if (!Resolve(container, ref component))
return false;
if (toAdd == container)
return false;
if (TryComp<PhysicsComponent>(toAdd, out var phys))
{
var aabb = _physics.GetWorldAABB(toAdd, body: phys);
if (component.MaxSize < aabb.Size.X || component.MaxSize < aabb.Size.Y)
return false;
}
return Insert(toAdd, container, component);
}
public bool CanFit(EntityUid toInsert, EntityUid container, EntityWhitelist? whitelist)
{
// conditions are complicated because of pizzabox-related issues, so follow this guide
// 0. Accomplish your goals at all costs.
// 1. AddToContents can block anything
// 2. maximum item count can block anything
// 3. ghosts can NEVER be eaten
// 4. items can always be eaten unless a previous law prevents it
// 5. if this is NOT AN ITEM, then mobs can always be eaten unless a previous
// law prevents it
// 6. if this is an item, then mobs must only be eaten if some other component prevents
// pick-up interactions while a mob is inside (e.g. foldable)
var attemptEvent = new InsertIntoEntityStorageAttemptEvent();
RaiseLocalEvent(toInsert, ref attemptEvent);
if (attemptEvent.Cancelled)
return false;
var targetIsMob = HasComp<BodyComponent>(toInsert);
var storageIsItem = HasComp<ItemComponent>(container);
var allowedToEat = whitelist?.IsValid(toInsert) ?? HasComp<ItemComponent>(toInsert);
// BEFORE REPLACING THIS WITH, I.E. A PROPERTY:
// Make absolutely 100% sure you have worked out how to stop people ending up in backpacks.
// Seriously, it is insanely hacky and weird to get someone out of a backpack once they end up in there.
// And to be clear, they should NOT be in there.
// For the record, what you need to do is empty the backpack onto a PlacableSurface (table, rack)
if (targetIsMob)
{
if (!storageIsItem)
allowedToEat = true;
else
{
var storeEv = new StoreMobInItemContainerAttemptEvent();
RaiseLocalEvent(container, ref storeEv);
allowedToEat = storeEv.Handled && !storeEv.Cancelled;
}
}
return allowedToEat;
}
public void ModifyComponents(EntityUid uid, EntityStorageComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (!component.IsCollidableWhenOpen && TryComp<FixturesComponent>(uid, out var fixtures) && fixtures.Fixtures.Count > 0)
{
// currently only works for single-fixture entities. If they have more than one fixture, then
// RemovedMasks needs to be tracked separately for each fixture, using a fixture Id Dictionary. Also the
// fixture IDs probably cant be automatically generated without causing issues, unless there is some
// guarantee that they will get deserialized with the same auto-generated ID when saving+loading the map.
var fixture = fixtures.Fixtures.Values.First();
if (component.Open)
{
component.RemovedMasks = fixture.CollisionLayer & component.MasksToRemove;
_physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer & ~component.MasksToRemove, manager: fixtures);
}
else
{
_physics.SetCollisionLayer(uid, fixture, fixture.CollisionLayer | component.RemovedMasks, manager: fixtures);
component.RemovedMasks = 0;
}
}
if (TryComp<PlaceableSurfaceComponent>(uid, out var surface))
_placeableSurface.SetPlaceable(uid, component.Open, surface);
_appearance.SetData(uid, StorageVisuals.Open, component.Open);
_appearance.SetData(uid, StorageVisuals.HasContents, component.Contents.ContainedEntities.Count > 0);
}
private void TakeGas(EntityUid uid, EntityStorageComponent component)
protected override void TakeGas(EntityUid uid, SharedEntityStorageComponent component)
{
if (!component.Airtight)
return;
var tile = GetOffsetTileRef(uid, component);
var serverComp = (EntityStorageComponent) component;
var tile = GetOffsetTileRef(uid, serverComp);
if (tile != null && _atmos.GetTileMixture(tile.Value.GridUid, null, tile.Value.GridIndices, true) is {} environment)
{
_atmos.Merge(component.Air, environment.RemoveVolume(EntityStorageComponent.GasMixVolume));
_atmos.Merge(serverComp.Air, environment.RemoveVolume(SharedEntityStorageComponent.GasMixVolume));
}
}
public void ReleaseGas(EntityUid uid, EntityStorageComponent component)
public override void ReleaseGas(EntityUid uid, SharedEntityStorageComponent component)
{
if (!component.Airtight)
var serverComp = (EntityStorageComponent) component;
if (!serverComp.Airtight)
return;
var tile = GetOffsetTileRef(uid, component);
var tile = GetOffsetTileRef(uid, serverComp);
if (tile != null && _atmos.GetTileMixture(tile.Value.GridUid, null, tile.Value.GridIndices, true) is {} environment)
{
_atmos.Merge(environment, component.Air);
component.Air.Clear();
_atmos.Merge(environment, serverComp.Air);
serverComp.Air.Clear();
}
}

View file

@ -1,12 +1,15 @@
using Content.Server.Administration.Logs;
using Content.Server.Cargo.Systems;
using Content.Server.Storage.Components;
using Content.Shared.Database;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction.Events;
using Content.Shared.Storage;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
using static Content.Shared.Storage.EntitySpawnCollection;
namespace Content.Server.Storage.EntitySystems
{
@ -14,13 +17,47 @@ namespace Content.Server.Storage.EntitySystems
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly PricingSystem _pricing = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpawnItemsOnUseComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<SpawnItemsOnUseComponent, PriceCalculationEvent>(CalculatePrice, before: new[] { typeof(PricingSystem) });
}
private void CalculatePrice(EntityUid uid, SpawnItemsOnUseComponent component, ref PriceCalculationEvent args)
{
var ungrouped = CollectOrGroups(component.Items, out var orGroups);
foreach (var entry in ungrouped)
{
var protUid = Spawn(entry.PrototypeId, MapCoordinates.Nullspace);
// Calculate the average price of the possible spawned items
args.Price += _pricing.GetPrice(protUid) * entry.SpawnProbability * entry.GetAmount(getAverage: true);
EntityManager.DeleteEntity(protUid);
}
foreach (var group in orGroups)
{
foreach (var entry in group.Entries)
{
var protUid = Spawn(entry.PrototypeId, MapCoordinates.Nullspace);
// Calculate the average price of the possible spawned items
args.Price += _pricing.GetPrice(protUid) *
(entry.SpawnProbability / group.CumulativeProbability) *
entry.GetAmount(getAverage: true);
EntityManager.DeleteEntity(protUid);
}
}
args.Handled = true;
}
private void OnUseInHand(EntityUid uid, SpawnItemsOnUseComponent component, UseInHandEvent args)
@ -29,7 +66,7 @@ namespace Content.Server.Storage.EntitySystems
return;
var coords = Transform(args.User).Coordinates;
var spawnEntities = EntitySpawnCollection.GetSpawns(component.Items, _random);
var spawnEntities = GetSpawns(component.Items, _random);
EntityUid? entityToPlaceInHands = null;
foreach (var proto in spawnEntities)
@ -50,7 +87,7 @@ namespace Content.Server.Storage.EntitySystems
if (entityToPlaceInHands != null)
{
_handsSystem.PickupOrDrop(args.User, entityToPlaceInHands.Value);
_hands.PickupOrDrop(args.User, entityToPlaceInHands.Value);
}
}
}

View file

@ -77,9 +77,6 @@ namespace Content.Server.Storage.EntitySystems
SubscribeLocalEvent<ServerStorageComponent, DoAfterEvent<StorageData>>(OnDoAfter);
SubscribeLocalEvent<EntityStorageComponent, GetVerbsEvent<InteractionVerb>>(AddToggleOpenVerb);
SubscribeLocalEvent<EntityStorageComponent, ContainerRelayMovementEntityEvent>(OnRelayMovement);
SubscribeLocalEvent<StorageFillComponent, MapInitEvent>(OnStorageFillMapInit);
}
@ -95,39 +92,6 @@ namespace Content.Server.Storage.EntitySystems
UpdateStorageUI(uid, storageComp);
}
private void OnRelayMovement(EntityUid uid, EntityStorageComponent component, ref ContainerRelayMovementEntityEvent args)
{
if (!EntityManager.HasComponent<HandsComponent>(args.Entity) || _gameTiming.CurTime < component.LastInternalOpenAttempt + EntityStorageComponent.InternalOpenAttemptDelay)
return;
component.LastInternalOpenAttempt = _gameTiming.CurTime;
if (component.OpenOnMove)
{
_entityStorage.TryOpenStorage(args.Entity, component.Owner);
}
}
private void AddToggleOpenVerb(EntityUid uid, EntityStorageComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !_entityStorage.CanOpen(args.User, args.Target, silent: true, component))
return;
InteractionVerb verb = new();
if (component.Open)
{
verb.Text = Loc.GetString("verb-common-close");
verb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png";
}
else
{
verb.Text = Loc.GetString("verb-common-open");
verb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png";
}
verb.Act = () => _entityStorage.ToggleOpen(args.User, args.Target, component);
args.Verbs.Add(verb);
}
private void AddOpenUiVerb(EntityUid uid, ServerStorageComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
@ -147,12 +111,14 @@ namespace Content.Server.Storage.EntitySystems
if (uiOpen)
{
verb.Text = Loc.GetString("verb-common-close-ui");
verb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(
new ResourcePath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
}
else
{
verb.Text = Loc.GetString("verb-common-open-ui");
verb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(
new ResourcePath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
}
args.Verbs.Add(verb);
}

View file

@ -19,6 +19,7 @@ using Content.Shared.DoAfter;
using Content.Shared.Ensnaring.Components;
using Content.Shared.Interaction;
using Content.Shared.Strip;
using Robust.Shared.Utility;
namespace Content.Server.Strip
{
@ -132,7 +133,7 @@ namespace Content.Server.Strip
Verb verb = new()
{
Text = Loc.GetString("strip-verb-get-data-text"),
IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => StartOpeningStripper(args.User, component, true),
};
args.Verbs.Add(verb);
@ -149,7 +150,7 @@ namespace Content.Server.Strip
ExamineVerb verb = new()
{
Text = Loc.GetString("strip-verb-get-data-text"),
IconTexture = "/Textures/Interface/VerbIcons/outfit.svg.192dpi.png",
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => StartOpeningStripper(args.User, component, true),
Category = VerbCategory.Examine,
};

View file

@ -8,6 +8,7 @@ using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.Tabletop
{
@ -58,7 +59,7 @@ namespace Content.Server.Tabletop
ActivationVerb verb = new();
verb.Text = Loc.GetString("tabletop-verb-play-game");
verb.IconTexture = "/Textures/Interface/VerbIcons/die.svg.192dpi.png";
verb.Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/die.svg.192dpi.png"));
verb.Act = () => OpenSessionFor(actor.PlayerSession, uid);
args.Verbs.Add(verb);
}

View file

@ -15,5 +15,8 @@ namespace Content.Server.Tools.Components
[DataField("delay")]
public float Delay = 1f;
[DataField("cancelToken")]
public CancellationTokenSource? CancelToken;
}
}

View file

@ -19,10 +19,20 @@ public sealed partial class ToolSystem
{
SubscribeLocalEvent<TilePryingComponent, AfterInteractEvent>(OnTilePryingAfterInteract);
SubscribeLocalEvent<TilePryingComponent, TilePryingCompleteEvent>(OnTilePryComplete);
SubscribeLocalEvent<TilePryingComponent, TilePryingCancelledEvent>(OnTilePryCancelled);
}
private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
{
if (args.Handled || !args.CanReach || (args.Target != null && !HasComp<PuddleComponent>(args.Target))) return;
if (TryPryTile(uid, args.User, component, args.ClickLocation))
args.Handled = true;
}
private void OnTilePryComplete(EntityUid uid, TilePryingComponent component, TilePryingCompleteEvent args)
{
component.CancelToken = null;
var gridUid = args.Coordinates.GetGridUid(EntityManager);
if (!_mapManager.TryGetGrid(gridUid, out var grid))
{
@ -34,17 +44,14 @@ public sealed partial class ToolSystem
_tile.PryTile(tile);
}
private void OnTilePryingAfterInteract(EntityUid uid, TilePryingComponent component, AfterInteractEvent args)
private void OnTilePryCancelled(EntityUid uid, TilePryingComponent component, TilePryingCancelledEvent args)
{
if (args.Handled || !args.CanReach || (args.Target != null && !HasComp<PuddleComponent>(args.Target))) return;
if (TryPryTile(uid, args.User, component, args.ClickLocation))
args.Handled = true;
component.CancelToken = null;
}
private bool TryPryTile(EntityUid toolEntity, EntityUid user, TilePryingComponent component, EntityCoordinates clickLocation)
{
if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded)
if (!TryComp<ToolComponent?>(toolEntity, out var tool) && component.ToolComponentNeeded || component.CancelToken != null)
return false;
if (!_mapManager.TryGetGrid(clickLocation.GetGridUid(EntityManager), out var mapGrid))
@ -62,9 +69,11 @@ public sealed partial class ToolSystem
if (!tileDef.CanCrowbar)
return false;
var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), targetEntity:toolEntity);
component.CancelToken = new CancellationTokenSource();
if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool))
var toolEvData = new ToolEventData(new TilePryingCompleteEvent(clickLocation), cancelledEv:new TilePryingCancelledEvent() ,targetEntity:toolEntity);
if (!UseTool(toolEntity, user, null, component.Delay, new[] { component.QualityNeeded }, toolEvData, toolComponent: tool, cancelToken: component.CancelToken))
return false;
return true;

View file

@ -26,6 +26,7 @@ using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Server.Weapons.Melee;
@ -74,7 +75,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
Text = Loc.GetString("damage-examinable-verb-text"),
Message = Loc.GetString("damage-examinable-verb-message"),
Category = VerbCategory.Examine,
IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
};
args.Verbs.Add(verb);

View file

@ -6,6 +6,7 @@ using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Weapons.Ranged.Systems;
@ -38,11 +39,13 @@ public sealed partial class GunSystem
private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component)
{
if (!TryComp<BatteryComponent>(uid, out var battery)) return;
UpdateShots(component, battery);
if (!TryComp<BatteryComponent>(uid, out var battery))
return;
UpdateShots(uid, component, battery);
}
private void UpdateShots(BatteryAmmoProviderComponent component, BatteryComponent battery)
private void UpdateShots(EntityUid uid, BatteryAmmoProviderComponent component, BatteryComponent battery)
{
var shots = (int) (battery.CurrentCharge / component.FireCost);
var maxShots = (int) (battery.MaxCharge / component.FireCost);
@ -54,7 +57,7 @@ public sealed partial class GunSystem
component.Shots = shots;
component.Capacity = maxShots;
UpdateBatteryAppearance(component.Owner, component);
UpdateBatteryAppearance(uid, component);
}
private void OnBatteryExaminableVerb(EntityUid uid, BatteryAmmoProviderComponent component, GetVerbsEvent<ExamineVerb> args)
@ -91,7 +94,7 @@ public sealed partial class GunSystem
Text = Loc.GetString("damage-examinable-verb-text"),
Message = Loc.GetString("damage-examinable-verb-message"),
Category = VerbCategory.Examine,
IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
};
args.Verbs.Add(verb);
@ -125,9 +128,10 @@ public sealed partial class GunSystem
protected override void TakeCharge(EntityUid uid, BatteryAmmoProviderComponent component)
{
if (!TryComp<BatteryComponent>(uid, out var battery)) return;
if (!TryComp<BatteryComponent>(uid, out var battery))
return;
battery.CurrentCharge -= component.FireCost;
UpdateShots(component, battery);
UpdateShots(uid, component, battery);
}
}

View file

@ -5,6 +5,7 @@ using Content.Shared.Projectiles;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Weapons.Ranged.Systems;
@ -37,7 +38,7 @@ public sealed partial class GunSystem
Text = Loc.GetString("damage-examinable-verb-text"),
Message = Loc.GetString("damage-examinable-verb-message"),
Category = VerbCategory.Examine,
IconTexture = "/Textures/Interface/VerbIcons/smite.svg.192dpi.png"
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
};
args.Verbs.Add(verb);

View file

@ -1,22 +1,52 @@
namespace Content.Shared.Access.Components
using Content.Shared.Access.Systems;
using Content.Shared.StationRecords;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
namespace Content.Shared.Access.Components;
/// <summary>
/// Stores access levels necessary to "use" an entity
/// and allows checking if something or somebody is authorized with these access levels.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed class AccessReaderComponent : Component
{
/// <summary>
/// Stores access levels necessary to "use" an entity
/// and allows checking if something or somebody is authorized with these access levels.
/// The set of tags that will automatically deny an allowed check, if any of them are present.
/// </summary>
[RegisterComponent]
public sealed class AccessReaderComponent : Component
{
/// <summary>
/// The set of tags that will automatically deny an allowed check, if any of them are present.
/// </summary>
public HashSet<string> DenyTags = new();
[DataField("denyTags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
public HashSet<string> DenyTags = new();
/// <summary>
/// List of access lists to check allowed against. For an access check to pass
/// there has to be an access list that is a subset of the access in the checking list.
/// </summary>
[DataField("access")]
public List<HashSet<string>> AccessLists = new();
/// <summary>
/// List of access lists to check allowed against. For an access check to pass
/// there has to be an access list that is a subset of the access in the checking list.
/// </summary>
[DataField("access")]
public List<HashSet<string>> AccessLists = new();
/// <summary>
/// A list of valid stationrecordkeys
/// </summary>
[DataField("accessKeys")]
public HashSet<StationRecordKey> AccessKeys = new();
}
[Serializable, NetSerializable]
public sealed class AccessReaderComponentState : ComponentState
{
public HashSet<string> DenyTags;
public List<HashSet<string>> AccessLists;
public HashSet<StationRecordKey> AccessKeys;
public AccessReaderComponentState(HashSet<string> denyTags, List<HashSet<string>> accessLists, HashSet<StationRecordKey> accessKeys)
{
DenyTags = denyTags;
AccessLists = accessLists;
AccessKeys = accessKeys;
}
}

View file

@ -8,6 +8,8 @@ using Content.Shared.Access.Components;
using Robust.Shared.Prototypes;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.MachineLinking.Events;
using Content.Shared.StationRecords;
using Robust.Shared.GameStates;
namespace Content.Shared.Access.Systems
{
@ -23,6 +25,24 @@ namespace Content.Shared.Access.Systems
SubscribeLocalEvent<AccessReaderComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<AccessReaderComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<AccessReaderComponent, LinkAttemptEvent>(OnLinkAttempt);
SubscribeLocalEvent<AccessReaderComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<AccessReaderComponent, ComponentHandleState>(OnHandleState);
}
private void OnGetState(EntityUid uid, AccessReaderComponent component, ref ComponentGetState args)
{
args.State = new AccessReaderComponentState(component.DenyTags, component.AccessLists,
component.AccessKeys);
}
private void OnHandleState(EntityUid uid, AccessReaderComponent component, ref ComponentHandleState args)
{
if (args.Current is not AccessReaderComponentState state)
return;
component.AccessKeys = new (state.AccessKeys);
component.AccessLists = new (state.AccessLists);
component.DenyTags = new (state.DenyTags);
}
private void OnLinkAttempt(EntityUid uid, AccessReaderComponent component, LinkAttemptEvent args)
@ -62,8 +82,7 @@ namespace Content.Shared.Access.Systems
{
if (!Resolve(target, ref reader, false))
return true;
var tags = FindAccessTags(source);
return IsAllowed(tags, reader);
return IsAllowed(source, reader);
}
/// <summary>
@ -74,8 +93,15 @@ namespace Content.Shared.Access.Systems
/// <param name="reader">A reader from a different entity</param>
public bool IsAllowed(EntityUid entity, AccessReaderComponent reader)
{
var tags = FindAccessTags(entity);
return IsAllowed(tags, reader);
var allEnts = FindPotentialAccessItems(entity);
if (AreAccessTagsAllowed(FindAccessTags(entity, allEnts), reader))
return true;
if (AreStationRecordKeysAllowed(FindStationRecordKeys(entity, allEnts), reader))
return true;
return false;
}
/// <summary>
@ -83,7 +109,7 @@ namespace Content.Shared.Access.Systems
/// </summary>
/// <param name="accessTags">A list of access tags</param>
/// <param name="reader">An access reader to check against</param>
public bool IsAllowed(ICollection<string> accessTags, AccessReaderComponent reader)
public bool AreAccessTagsAllowed(ICollection<string> accessTags, AccessReaderComponent reader)
{
if (HasComp<EmaggedComponent>(reader.Owner))
{
@ -105,17 +131,18 @@ namespace Content.Shared.Access.Systems
}
/// <summary>
/// Finds the access tags on the given entity
/// Compares the given stationrecordkeys with the accessreader to see if it is allowed.
/// </summary>
/// <param name="uid">The entity that is being searched.</param>
public ICollection<string> FindAccessTags(EntityUid uid)
public bool AreStationRecordKeysAllowed(ICollection<StationRecordKey> keys, AccessReaderComponent reader)
{
HashSet<string>? tags = null;
var owned = false;
// check entity itself
FindAccessTagsItem(uid, ref tags, ref owned);
return keys.Any() && reader.AccessKeys.Any(keys.Contains);
}
/// <summary>
/// Finds all the items that could potentially give access to a given entity
/// </summary>
public HashSet<EntityUid> FindPotentialAccessItems(EntityUid uid)
{
FindAccessItemsInventory(uid, out var items);
var ev = new GetAdditionalAccessEvent
@ -123,7 +150,23 @@ namespace Content.Shared.Access.Systems
Entities = items
};
RaiseLocalEvent(uid, ref ev);
foreach (var ent in ev.Entities)
items.Add(uid);
return items;
}
/// <summary>
/// Finds the access tags on the given entity
/// </summary>
/// <param name="uid">The entity that is being searched.</param>
/// <param name="items">All of the items to search for access. If none are passed in, <see cref="FindPotentialAccessItems"/> will be used.</param>
public ICollection<string> FindAccessTags(EntityUid uid, HashSet<EntityUid>? items = null)
{
HashSet<string>? tags = null;
var owned = false;
items ??= FindPotentialAccessItems(uid);
foreach (var ent in items)
{
FindAccessTagsItem(ent, ref tags, ref owned);
}
@ -131,6 +174,26 @@ namespace Content.Shared.Access.Systems
return (ICollection<string>?) tags ?? Array.Empty<string>();
}
/// <summary>
/// Finds the access tags on the given entity
/// </summary>
/// <param name="uid">The entity that is being searched.</param>
/// <param name="items">All of the items to search for access. If none are passed in, <see cref="FindPotentialAccessItems"/> will be used.</param>
public ICollection<StationRecordKey> FindStationRecordKeys(EntityUid uid, HashSet<EntityUid>? items = null)
{
HashSet<StationRecordKey> keys = new();
items ??= FindPotentialAccessItems(uid);
foreach (var ent in items)
{
if (FindStationRecordKeyItem(ent, out var key))
keys.Add(key.Value);
}
return keys;
}
/// <summary>
/// Try to find <see cref="AccessComponent"/> on this item
/// or inside this item (if it's pda)
@ -203,5 +266,31 @@ namespace Content.Shared.Access.Systems
tags = null;
return false;
}
/// <summary>
/// Try to find <see cref="StationRecordKeyStorageComponent"/> on this item
/// or inside this item (if it's pda)
/// </summary>
private bool FindStationRecordKeyItem(EntityUid uid, [NotNullWhen(true)] out StationRecordKey? key)
{
if (EntityManager.TryGetComponent(uid, out StationRecordKeyStorageComponent? storage) && storage.Key != null)
{
key = storage.Key;
return true;
}
if (TryComp<PDAComponent>(uid, out var pda) &&
pda.ContainedID?.Owner is {Valid: true} id)
{
if (TryComp<StationRecordKeyStorageComponent>(id, out var pdastorage) && pdastorage.Key != null)
{
key = pdastorage.Key;
return true;
}
}
key = null;
return false;
}
}
}

View file

@ -67,8 +67,10 @@ public static class AdminLogsEuiMsg
HashSet<LogImpact>? impacts,
DateTime? before,
DateTime? after,
bool includePlayers,
Guid[]? anyPlayers,
Guid[]? allPlayers,
bool includeNonPlayers,
int? lastLogId,
DateOrder dateOrder)
{
@ -78,8 +80,10 @@ public static class AdminLogsEuiMsg
Impacts = impacts;
Before = before;
After = after;
IncludePlayers = includePlayers;
AnyPlayers = anyPlayers is { Length: > 0 } ? anyPlayers : null;
AllPlayers = allPlayers is { Length: > 0 } ? allPlayers : null;
IncludeNonPlayers = includeNonPlayers;
LastLogId = lastLogId;
DateOrder = dateOrder;
}
@ -90,8 +94,10 @@ public static class AdminLogsEuiMsg
public HashSet<LogImpact>? Impacts { get; set; }
public DateTime? Before { get; set; }
public DateTime? After { get; set; }
public bool IncludePlayers { get; set; }
public Guid[]? AnyPlayers { get; set; }
public Guid[]? AllPlayers { get; set; }
public bool IncludeNonPlayers { get; set; }
public int? LastLogId { get; set; }
public DateOrder DateOrder { get; set; }
}

View file

@ -1214,6 +1214,15 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> ChatShowTypingIndicator =
CVarDef.Create("chat.show_typing_indicator", true, CVar.CLIENTONLY);
/// <summary>
/// A message broadcast to each player that joins the lobby.
/// May be changed by admins ingame through use of the "set-motd" command.
/// In this case the new value, if not empty, is broadcast to all connected players and saved between rounds.
/// May be requested by any player through use of the "get-motd" command.
/// </summary>
public static readonly CVarDef<string> MOTD =
CVarDef.Create("chat.motd", "", CVar.SERVER | CVar.SERVERONLY | CVar.ARCHIVE, "A message broadcast to each player that joins the lobby.");
/*
* AFK
*/

View file

@ -6,6 +6,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Cabinet;
@ -105,12 +106,14 @@ public abstract class SharedItemCabinetSystem : EntitySystem
if (cabinet.Opened)
{
toggleVerb.Text = Loc.GetString("verb-common-close");
toggleVerb.IconTexture = "/Textures/Interface/VerbIcons/close.svg.192dpi.png";
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
}
else
{
toggleVerb.Text = Loc.GetString("verb-common-open");
toggleVerb.IconTexture = "/Textures/Interface/VerbIcons/open.svg.192dpi.png";
toggleVerb.Icon =
new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
}
args.Verbs.Add(toggleVerb);
}

View file

@ -10,6 +10,12 @@ namespace Content.Shared.Chemistry.Components
[NetworkedComponent, ComponentProtoName("Injector")]
public abstract class SharedInjectorComponent : Component
{
/// <summary>
/// Checks to see if the entity being injected
/// </summary>
[DataField("isInjecting")]
public bool IsInjecting;
/// <summary>
/// Component data used for net updates. Used by client for item status ui
/// </summary>

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