Merge remote-tracking branch 'space-station-14/master' into ed-25-05-2024-upstream
# Conflicts: # Content.Server/Atmos/Components/FlammableComponent.cs # Content.Shared/Lock/LockSystem.cs # Content.Shared/Storage/EntitySystems/SharedStorageSystem.cs
This commit is contained in:
@@ -91,7 +91,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
ClearAlerts?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void AlertClicked(AlertType alertType)
|
||||
public void AlertClicked(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
RaiseNetworkEvent(new ClickAlertEvent(alertType));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Atmos.Overlays;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
@@ -36,28 +37,38 @@ namespace Content.Client.Atmos.EntitySystems
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not GasTileOverlayState state)
|
||||
return;
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
|
||||
|
||||
// is this a delta or full state?
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
// is this a delta or full state?
|
||||
case GasTileOverlayDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
case GasTileOverlayState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var index in comp.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
comp.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (index, data) in state.Chunks)
|
||||
foreach (var (index, data) in modifiedChunks)
|
||||
{
|
||||
comp.Chunks[index] = data;
|
||||
}
|
||||
|
||||
@@ -56,34 +56,43 @@ namespace Content.Client.Decals
|
||||
|
||||
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DecalGridState state)
|
||||
return;
|
||||
|
||||
// is this a delta or full state?
|
||||
_removedChunks.Clear();
|
||||
Dictionary<Vector2i, DecalChunk> modifiedChunks;
|
||||
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
case DecalGridDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(key))
|
||||
_removedChunks.Add(key);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
{
|
||||
if (!delta.AllChunks.Contains(key))
|
||||
_removedChunks.Add(key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
case DecalGridState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(key))
|
||||
_removedChunks.Add(key);
|
||||
modifiedChunks = state.Chunks;
|
||||
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(key))
|
||||
_removedChunks.Add(key);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (_removedChunks.Count > 0)
|
||||
RemoveChunks(gridUid, gridComp, _removedChunks);
|
||||
|
||||
if (state.Chunks.Count > 0)
|
||||
UpdateChunks(gridUid, gridComp, state.Chunks);
|
||||
if (modifiedChunks.Count > 0)
|
||||
UpdateChunks(gridUid, gridComp, modifiedChunks);
|
||||
}
|
||||
|
||||
private void OnChunkUpdate(DecalChunkUpdateEvent ev)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
|
||||
public sealed class FirelockSystem : EntitySystem
|
||||
public sealed class FirelockSystem : SharedFirelockSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
|
||||
@@ -239,8 +239,10 @@ namespace Content.Client.Examine
|
||||
|
||||
if (knowTarget)
|
||||
{
|
||||
var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player));
|
||||
var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]");
|
||||
// TODO: FormattedMessage.RemoveMarkupPermissive
|
||||
// var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player));
|
||||
var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString();
|
||||
var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]");
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(labelMessage);
|
||||
hBox.AddChild(label);
|
||||
|
||||
@@ -37,14 +37,8 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case InstrumentBandResponseBuiMessage bandRx:
|
||||
_bandMenu?.Populate(bandRx.Nearby, EntMan);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (message is InstrumentBandResponseBuiMessage bandRx)
|
||||
_bandMenu?.Populate(bandRx.Nearby, EntMan);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
|
||||
@@ -4,24 +4,6 @@ using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Client.Interactable
|
||||
{
|
||||
public sealed class InteractionSystem : SharedInteractionSystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!EntityManager.EntityExists(target))
|
||||
return false;
|
||||
|
||||
if (!_container.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
if (!HasComp<StorageComponent>(container.Owner))
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
// Need to return if UI is open or not
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO Remove Shared prefix
|
||||
public sealed class InteractionSystem : SharedInteractionSystem;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed partial class ArticleEditorPanel : Control
|
||||
|
||||
TextEditPanel.Visible = !_preview;
|
||||
PreviewPanel.Visible = _preview;
|
||||
PreviewLabel.SetMarkup(Rope.Collapse(ContentField.TextRope));
|
||||
PreviewLabel.SetMarkupPermissive(Rope.Collapse(ContentField.TextRope));
|
||||
}
|
||||
|
||||
private void OnCancel(BaseButton.ButtonEventArgs eventArgs)
|
||||
|
||||
@@ -5,9 +5,27 @@ namespace Content.Client.Message;
|
||||
|
||||
public static class RichTextLabelExt
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the labels markup.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invalid markup will cause exceptions to be thrown. Don't use this for user input!
|
||||
/// </remarks>
|
||||
public static RichTextLabel SetMarkup(this RichTextLabel label, string markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkup(markup));
|
||||
return label;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the labels markup.<br/>
|
||||
/// Uses <c>FormatedMessage.FromMarkupPermissive</c> which treats invalid markup as text.
|
||||
/// </summary>
|
||||
public static RichTextLabel SetMarkupPermissive(this RichTextLabel label, string markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkupPermissive(markup));
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,27 +14,40 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
|
||||
private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not NavMapComponentState state)
|
||||
return;
|
||||
Dictionary<Vector2i, int[]> modifiedChunks;
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons;
|
||||
|
||||
if (!state.FullState)
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
case NavMapDeltaState delta:
|
||||
{
|
||||
if (!state.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
modifiedChunks = delta.ModifiedChunks;
|
||||
beacons = delta.Beacons;
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!delta.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
case NavMapState state:
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
modifiedChunks = state.Chunks;
|
||||
beacons = state.Beacons;
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (origin, chunk) in state.Chunks)
|
||||
foreach (var (origin, chunk) in modifiedChunks)
|
||||
{
|
||||
var newChunk = new NavMapChunk(origin);
|
||||
Array.Copy(chunk, newChunk.TileData, chunk.Length);
|
||||
@@ -42,7 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
}
|
||||
|
||||
component.Beacons.Clear();
|
||||
foreach (var (nuid, beacon) in state.Beacons)
|
||||
foreach (var (nuid, beacon) in beacons)
|
||||
{
|
||||
component.Beacons[nuid] = beacon;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Client.Alerts;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Revenant;
|
||||
using Content.Shared.Revenant.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -42,7 +41,7 @@ public sealed class RevenantSystem : EntitySystem
|
||||
|
||||
private void OnUpdateAlert(Entity<RevenantComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
{
|
||||
if (args.Alert.AlertType != AlertType.Essence)
|
||||
if (args.Alert.ID != ent.Comp.EssenceAlert)
|
||||
return;
|
||||
|
||||
var sprite = args.SpriteViewEnt.Comp;
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class TippyUIController : UIController
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
}
|
||||
sprite.Rotation = 0;
|
||||
tippy.Label.SetMarkup(_currentMessage.Msg);
|
||||
tippy.Label.SetMarkupPermissive(_currentMessage.Msg);
|
||||
tippy.Label.Visible = false;
|
||||
tippy.LabelPanel.Visible = false;
|
||||
tippy.Visible = true;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts;
|
||||
|
||||
@@ -43,7 +44,7 @@ public sealed class AlertsUIController : UIController, IOnStateEntered<GameplayS
|
||||
SyncAlerts();
|
||||
}
|
||||
|
||||
private void OnAlertPressed(object? sender, AlertType e)
|
||||
private void OnAlertPressed(object? sender, ProtoId<AlertPrototype> e)
|
||||
{
|
||||
_alertsSystem?.AlertClicked(e);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Alerts.Widgets;
|
||||
|
||||
@@ -21,8 +22,10 @@ public sealed partial class AlertsUI : UIWidget
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void SyncControls(AlertsSystem alertsSystem, AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
public void SyncControls(AlertsSystem alertsSystem,
|
||||
AlertOrderPrototype? alertOrderPrototype,
|
||||
IReadOnlyDictionary<AlertKey,
|
||||
AlertState> alertStates)
|
||||
{
|
||||
// remove any controls with keys no longer present
|
||||
if (SyncRemoveControls(alertStates))
|
||||
@@ -46,7 +49,7 @@ public sealed partial class AlertsUI : UIWidget
|
||||
_alertControls.Clear();
|
||||
}
|
||||
|
||||
public event EventHandler<AlertType>? AlertPressed;
|
||||
public event EventHandler<ProtoId<AlertPrototype>>? AlertPressed;
|
||||
|
||||
private bool SyncRemoveControls(IReadOnlyDictionary<AlertKey, AlertState> alertStates)
|
||||
{
|
||||
@@ -88,7 +91,7 @@ public sealed partial class AlertsUI : UIWidget
|
||||
}
|
||||
|
||||
if (_alertControls.TryGetValue(newAlert.AlertKey, out var existingAlertControl) &&
|
||||
existingAlertControl.Alert.AlertType == newAlert.AlertType)
|
||||
existingAlertControl.Alert.ID == newAlert.ID)
|
||||
{
|
||||
// key is the same, simply update the existing control severity / cooldown
|
||||
existingAlertControl.SetSeverity(alertState.Severity);
|
||||
@@ -155,6 +158,6 @@ public sealed partial class AlertsUI : UIWidget
|
||||
if (args.Event.Function != EngineKeyFunctions.UIClick)
|
||||
return;
|
||||
|
||||
AlertPressed?.Invoke(this, control.Alert.AlertType);
|
||||
AlertPressed?.Invoke(this, control.Alert.ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,8 +350,12 @@ namespace Content.IntegrationTests.Tests
|
||||
"DebrisFeaturePlacerController", // Above.
|
||||
"LoadedChunk", // Worldgen chunk loading malding.
|
||||
"BiomeSelection", // Whaddya know, requires config.
|
||||
"ActivatableUI", // Requires enum key
|
||||
};
|
||||
|
||||
// TODO TESTS
|
||||
// auto ignore any components that have a "required" data field.
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Shared.Alert;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
{
|
||||
@@ -45,8 +44,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
Assert.That(alerts, Is.Not.Null);
|
||||
var alertCount = alerts.Count;
|
||||
|
||||
alertsSystem.ShowAlert(playerUid, AlertType.Debug1);
|
||||
alertsSystem.ShowAlert(playerUid, AlertType.Debug2);
|
||||
alertsSystem.ShowAlert(playerUid, "Debug1");
|
||||
alertsSystem.ShowAlert(playerUid, "Debug2");
|
||||
|
||||
Assert.That(alerts, Has.Count.EqualTo(alertCount + 2));
|
||||
});
|
||||
@@ -87,14 +86,14 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
// we should be seeing 3 alerts - our health, and the 2 debug alerts, in a specific order.
|
||||
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(3));
|
||||
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||
var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug1, AlertType.Debug2 };
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
|
||||
var expectedIDs = new[] { "HumanHealth", "Debug1", "Debug2" };
|
||||
Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
|
||||
});
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
alertsSystem.ClearAlert(playerUid, AlertType.Debug1);
|
||||
alertsSystem.ClearAlert(playerUid, "Debug1");
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(5);
|
||||
@@ -104,8 +103,8 @@ namespace Content.IntegrationTests.Tests.GameObjects.Components.Mobs
|
||||
// we should be seeing 2 alerts now because one was cleared
|
||||
Assert.That(clientAlertsUI.AlertContainer.ChildCount, Is.GreaterThanOrEqualTo(2));
|
||||
var alertControls = clientAlertsUI.AlertContainer.Children.Select(c => (AlertControl) c);
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.AlertType).ToArray();
|
||||
var expectedIDs = new[] { AlertType.HumanHealth, AlertType.Debug2 };
|
||||
var alertIDs = alertControls.Select(ac => ac.Alert.ID).ToArray();
|
||||
var expectedIDs = new[] { "HumanHealth", "Debug2" };
|
||||
Assert.That(alertIDs, Is.SupersetOf(expectedIDs));
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Gravity;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Gravity
|
||||
@@ -38,6 +39,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var alertsSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<AlertsSystem>();
|
||||
var weightlessAlert = SharedGravitySystem.WeightlessAlert;
|
||||
|
||||
EntityUid human = default;
|
||||
|
||||
@@ -56,7 +58,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// No gravity without a gravity generator
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless));
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert));
|
||||
|
||||
generatorUid = entityManager.SpawnEntity("WeightlessGravityGeneratorDummy", entityManager.GetComponent<TransformComponent>(human).Coordinates);
|
||||
});
|
||||
@@ -66,7 +68,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless), Is.False);
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert), Is.False);
|
||||
|
||||
// This should kill gravity
|
||||
entityManager.DeleteEntity(generatorUid);
|
||||
@@ -76,7 +78,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, AlertType.Weightless));
|
||||
Assert.That(alertsSystem.IsShowingAlert(human, weightlessAlert));
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(10);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
@@ -47,5 +48,12 @@ namespace Content.Server.Abilities.Mime
|
||||
/// </summary>
|
||||
[DataField("vowCooldown")]
|
||||
public TimeSpan VowCooldown = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowAlert = "VowOfSilence";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> VowBrokenAlert = "VowBroken";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Speech.Muting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Alert;
|
||||
@@ -54,7 +53,7 @@ namespace Content.Server.Abilities.Mime
|
||||
private void OnComponentInit(EntityUid uid, MimePowersComponent component, ComponentInit args)
|
||||
{
|
||||
EnsureComp<MutedComponent>(uid);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ShowAlert(uid, component.VowAlert);
|
||||
_actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid);
|
||||
}
|
||||
|
||||
@@ -115,8 +114,8 @@ namespace Content.Server.Abilities.Mime
|
||||
mimePowers.VowBroken = true;
|
||||
mimePowers.VowRepentTime = _timing.CurTime + mimePowers.VowCooldown;
|
||||
RemComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowBroken);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_actionsSystem.RemoveAction(uid, mimePowers.InvisibleWallActionEntity);
|
||||
}
|
||||
|
||||
@@ -138,8 +137,8 @@ namespace Content.Server.Abilities.Mime
|
||||
mimePowers.ReadyToRepent = false;
|
||||
mimePowers.VowBroken = false;
|
||||
AddComp<MutedComponent>(uid);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.VowBroken);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.VowOfSilence);
|
||||
_alertsSystem.ClearAlert(uid, mimePowers.VowAlert);
|
||||
_alertsSystem.ShowAlert(uid, mimePowers.VowBrokenAlert);
|
||||
_actionsSystem.AddAction(uid, ref mimePowers.InvisibleWallActionEntity, mimePowers.InvisibleWallAction, uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ namespace Content.Server.Alert.Commands
|
||||
|
||||
var alertType = args[0];
|
||||
var alertsSystem = _e.System<AlertsSystem>();
|
||||
if (!alertsSystem.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
|
||||
if (!alertsSystem.TryGet(alertType, out var alert))
|
||||
{
|
||||
shell.WriteLine("unrecognized alertType " + alertType);
|
||||
return;
|
||||
}
|
||||
|
||||
alertsSystem.ClearAlert(attachedEntity, alert.AlertType);
|
||||
alertsSystem.ClearAlert(attachedEntity, alert.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.Alert.Commands
|
||||
var alertType = args[0];
|
||||
var severity = args[1];
|
||||
var alertsSystem = _e.System<AlertsSystem>();
|
||||
if (!alertsSystem.TryGet(Enum.Parse<AlertType>(alertType), out var alert))
|
||||
if (!alertsSystem.TryGet(alertType, out var alert))
|
||||
{
|
||||
shell.WriteLine("unrecognized alertType " + alertType);
|
||||
return;
|
||||
@@ -53,7 +53,7 @@ namespace Content.Server.Alert.Commands
|
||||
}
|
||||
|
||||
short? severity1 = sevint == -1 ? null : sevint;
|
||||
alertsSystem.ShowAlert(attachedEntity, alert.AlertType, severity1, null);
|
||||
alertsSystem.ShowAlert(attachedEntity, alert.ID, severity1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
@@ -46,5 +48,13 @@ namespace Content.Server.Atmos.Components
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool HasImmunity = false;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> HighPressureAlert = "HighPressure";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> LowPressureAlert = "LowPressure";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> PressureAlertCategory = "Pressure";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
@@ -78,6 +80,9 @@ namespace Content.Server.Atmos.Components
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float FirestackFade = -0.1f;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> FireAlert = "Fire";
|
||||
|
||||
/// <summary>
|
||||
/// CrystallPunk fireplace fuel
|
||||
/// </summary>
|
||||
|
||||
@@ -245,7 +245,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage");
|
||||
}
|
||||
|
||||
_alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2);
|
||||
_alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 2);
|
||||
}
|
||||
else if (pressure >= Atmospherics.HazardHighPressure)
|
||||
{
|
||||
@@ -260,7 +260,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage");
|
||||
}
|
||||
|
||||
_alertsSystem.ShowAlert(uid, AlertType.HighPressure, 2);
|
||||
_alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -275,13 +275,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
switch (pressure)
|
||||
{
|
||||
case <= Atmospherics.WarningLowPressure:
|
||||
_alertsSystem.ShowAlert(uid, AlertType.LowPressure, 1);
|
||||
_alertsSystem.ShowAlert(uid, barotrauma.LowPressureAlert, 1);
|
||||
break;
|
||||
case >= Atmospherics.WarningHighPressure:
|
||||
_alertsSystem.ShowAlert(uid, AlertType.HighPressure, 1);
|
||||
_alertsSystem.ShowAlert(uid, barotrauma.HighPressureAlert, 1);
|
||||
break;
|
||||
default:
|
||||
_alertsSystem.ClearAlertCategory(uid, AlertCategory.Pressure);
|
||||
_alertsSystem.ClearAlertCategory(uid, barotrauma.PressureAlertCategory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,11 +427,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
if (!flammable.OnFire)
|
||||
{
|
||||
_alertsSystem.ClearAlert(uid, AlertType.Fire);
|
||||
_alertsSystem.ClearAlert(uid, flammable.FireAlert);
|
||||
continue;
|
||||
}
|
||||
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Fire);
|
||||
_alertsSystem.ShowAlert(uid, flammable.FireAlert);
|
||||
|
||||
if (flammable.FireStacks > 0)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.EntitySystems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Damage;
|
||||
@@ -171,5 +172,8 @@ namespace Content.Server.Body.Components
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public TimeSpan StatusTime;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> BleedingAlert = "Bleed";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Body.Components
|
||||
{
|
||||
/// <summary>
|
||||
@@ -18,5 +21,8 @@ namespace Content.Server.Body.Components
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> InternalsAlert = "Internals";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Body.Components;
|
||||
|
||||
@@ -33,5 +33,5 @@ public sealed partial class LungComponent : Component
|
||||
/// The type of gas this lung needs. Used only for the breathing alerts, not actual metabolism.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public AlertType Alert = AlertType.LowOxygen;
|
||||
public ProtoId<AlertPrototype> Alert = "LowOxygen";
|
||||
}
|
||||
|
||||
@@ -392,11 +392,11 @@ public sealed class BloodstreamSystem : EntitySystem
|
||||
component.BleedAmount = Math.Clamp(component.BleedAmount, 0, component.MaxBleedAmount);
|
||||
|
||||
if (component.BleedAmount == 0)
|
||||
_alertsSystem.ClearAlert(uid, AlertType.Bleed);
|
||||
_alertsSystem.ClearAlert(uid, component.BleedingAlert);
|
||||
else
|
||||
{
|
||||
var severity = (short) Math.Clamp(Math.Round(component.BleedAmount, MidpointRounding.ToZero), 0, 10);
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Bleed, severity);
|
||||
_alertsSystem.ShowAlert(uid, component.BleedingAlert, severity);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -144,12 +144,12 @@ public sealed class InternalsSystem : EntitySystem
|
||||
|
||||
private void OnInternalsStartup(Entity<InternalsComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
private void OnInternalsShutdown(Entity<InternalsComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
_alerts.ClearAlert(ent, AlertType.Internals);
|
||||
_alerts.ClearAlert(ent, ent.Comp.InternalsAlert);
|
||||
}
|
||||
|
||||
private void OnInhaleLocation(Entity<InternalsComponent> ent, ref InhaleLocationEvent args)
|
||||
@@ -159,7 +159,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
var gasTank = Comp<GasTankComponent>(ent.Comp.GasTankEntity!.Value);
|
||||
args.Gas = _gasTank.RemoveAirVolume((ent.Comp.GasTankEntity.Value, gasTank), Atmospherics.BreathVolume);
|
||||
// TODO: Should listen to gas tank updates instead I guess?
|
||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
}
|
||||
public void DisconnectBreathTool(Entity<InternalsComponent> ent)
|
||||
@@ -173,7 +173,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
DisconnectTank(ent);
|
||||
}
|
||||
|
||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(Entity<InternalsComponent> ent, EntityUid toolEntity)
|
||||
@@ -184,7 +184,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
}
|
||||
|
||||
ent.Comp.BreathToolEntity = toolEntity;
|
||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
}
|
||||
|
||||
public void DisconnectTank(InternalsComponent? component)
|
||||
@@ -196,7 +196,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
_gasTank.DisconnectFromInternals((component.GasTankEntity.Value, tank));
|
||||
|
||||
component.GasTankEntity = null;
|
||||
_alerts.ShowAlert(component.Owner, AlertType.Internals, GetSeverity(component));
|
||||
_alerts.ShowAlert(component.Owner, component.InternalsAlert, GetSeverity(component));
|
||||
}
|
||||
|
||||
public bool TryConnectTank(Entity<InternalsComponent> ent, EntityUid tankEntity)
|
||||
@@ -208,7 +208,7 @@ public sealed class InternalsSystem : EntitySystem
|
||||
_gasTank.DisconnectFromInternals((ent.Comp.GasTankEntity.Value, tank));
|
||||
|
||||
ent.Comp.GasTankEntity = tankEntity;
|
||||
_alerts.ShowAlert(ent, AlertType.Internals, GetSeverity(ent));
|
||||
_alerts.ShowAlert(ent, ent.Comp.InternalsAlert, GetSeverity(ent));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ public sealed partial class AdjustAlert : ReagentEffect
|
||||
/// <summary>
|
||||
/// The specific Alert that will be adjusted
|
||||
/// </summary>
|
||||
[DataField("alertType", required: true)]
|
||||
public AlertType Type;
|
||||
[DataField(required: true)]
|
||||
public ProtoId<AlertPrototype> AlertType;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the alert is removed after Time seconds. If Time was not specified the alert is removed immediately.
|
||||
@@ -42,7 +42,7 @@ public sealed partial class AdjustAlert : ReagentEffect
|
||||
|
||||
if (Clear && Time <= 0)
|
||||
{
|
||||
alertSys.ClearAlert(args.SolutionEntity, Type);
|
||||
alertSys.ClearAlert(args.SolutionEntity, AlertType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -52,7 +52,7 @@ public sealed partial class AdjustAlert : ReagentEffect
|
||||
if ((ShowCooldown || Clear) && Time > 0)
|
||||
cooldown = (timing.CurTime, timing.CurTime + TimeSpan.FromSeconds(Time));
|
||||
|
||||
alertSys.ShowAlert(args.SolutionEntity, Type, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
|
||||
alertSys.ShowAlert(args.SolutionEntity, AlertType, cooldown: cooldown, autoRemove: Clear, showCooldown: ShowCooldown);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
|
||||
if (state)
|
||||
{
|
||||
_alerts.ShowAlert(parent, AlertType.Magboots);
|
||||
_alerts.ShowAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(parent, AlertType.Magboots);
|
||||
_alerts.ClearAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed class ConfigurationSystem : EntitySystem
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args)
|
||||
{
|
||||
// TODO use activatable ui system
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
|
||||
@@ -1,67 +1,54 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Monitor.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Prying.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Doors.Systems
|
||||
{
|
||||
public sealed class FirelockSystem : EntitySystem
|
||||
public sealed class FirelockSystem : SharedFirelockSystem
|
||||
{
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapping = default!;
|
||||
|
||||
private static float _visualUpdateInterval = 0.5f;
|
||||
private float _accumulatedFrameTime;
|
||||
private const int UpdateInterval = 30;
|
||||
private int _accumulatedTicks;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||
SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||
SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
|
||||
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
|
||||
SubscribeLocalEvent<FirelockComponent, AtmosAlarmEvent>(OnAtmosAlarm);
|
||||
|
||||
// Visuals
|
||||
SubscribeLocalEvent<FirelockComponent, MapInitEvent>(UpdateVisuals);
|
||||
SubscribeLocalEvent<FirelockComponent, ComponentStartup>(UpdateVisuals);
|
||||
SubscribeLocalEvent<FirelockComponent, PowerChangedEvent>(PowerChanged);
|
||||
|
||||
}
|
||||
|
||||
private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
// TODO this should REALLLLY not be door specific appearance thing.
|
||||
_appearance.SetData(uid, DoorVisuals.Powered, args.Powered);
|
||||
component.Powered = args.Powered;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
#region Visuals
|
||||
private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component);
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
_accumulatedFrameTime += frameTime;
|
||||
if (_accumulatedFrameTime < _visualUpdateInterval)
|
||||
_accumulatedTicks += 1;
|
||||
if (_accumulatedTicks < UpdateInterval)
|
||||
return;
|
||||
|
||||
_accumulatedFrameTime -= _visualUpdateInterval;
|
||||
_accumulatedTicks = 0;
|
||||
|
||||
var airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
var appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
@@ -84,107 +71,13 @@ namespace Content.Server.Doors.Systems
|
||||
{
|
||||
var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery);
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
|
||||
firelock.Temperature = fire;
|
||||
firelock.Pressure = pressure;
|
||||
Dirty(uid, firelock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisuals(EntityUid uid,
|
||||
FirelockComponent? firelock = null,
|
||||
DoorComponent? door = null,
|
||||
AirtightComponent? airtight = null,
|
||||
AppearanceComponent? appearance = null,
|
||||
TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref door, ref appearance, false))
|
||||
return;
|
||||
|
||||
// only bother to check pressure on doors that are some variation of closed.
|
||||
if (door.State != DoorState.Closed
|
||||
&& door.State != DoorState.Welded
|
||||
&& door.State != DoorState.Denying)
|
||||
{
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance);
|
||||
return;
|
||||
}
|
||||
|
||||
var query = GetEntityQuery<AirtightComponent>();
|
||||
if (!Resolve(uid, ref firelock, ref airtight, ref appearance, ref xform, false) || !query.Resolve(uid, ref airtight, false))
|
||||
return;
|
||||
|
||||
var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, query);
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null)
|
||||
{
|
||||
if (!Resolve(uid, ref firelock, ref door))
|
||||
return false;
|
||||
|
||||
if (door.State == DoorState.Open)
|
||||
{
|
||||
if (_doorSystem.TryClose(uid, door))
|
||||
{
|
||||
return _doorSystem.OnPartialClose(uid, door);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
|
||||
{
|
||||
// Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas
|
||||
var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid);
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager) || (!overrideAccess && IsHoldingPressureOrFire(uid, component)))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
|
||||
{
|
||||
var state = CheckPressureAndFire(uid, component);
|
||||
|
||||
if (state.Fire)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"),
|
||||
uid, args.User, PopupType.MediumCaution);
|
||||
}
|
||||
else if (state.Pressure)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"),
|
||||
uid, args.User, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
if (state.Fire || state.Pressure)
|
||||
args.PryTimeModifier *= component.LockedPryTimeModifier;
|
||||
}
|
||||
|
||||
private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args)
|
||||
{
|
||||
var ev = new BeforeDoorAutoCloseEvent();
|
||||
RaiseLocalEvent(uid, ev);
|
||||
UpdateVisuals(uid, component, args);
|
||||
if (ev.Cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_doorSystem.SetNextStateChange(uid, component.AutocloseDelay);
|
||||
}
|
||||
|
||||
private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
args.Cancel();
|
||||
|
||||
// Make firelocks autoclose, but only if the last alarm type it
|
||||
// remembers was a danger. This is to prevent people from
|
||||
// flooding hallways with endless bad air/fire.
|
||||
if (component.AlarmAutoClose &&
|
||||
(_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarmEvent args)
|
||||
{
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
@@ -204,12 +97,6 @@ namespace Content.Server.Doors.Systems
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock)
|
||||
{
|
||||
var result = CheckPressureAndFire(uid, firelock);
|
||||
return result.Pressure || result.Fire;
|
||||
}
|
||||
|
||||
public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock)
|
||||
{
|
||||
var query = GetEntityQuery<AirtightComponent>();
|
||||
@@ -234,17 +121,17 @@ namespace Content.Server.Doors.Systems
|
||||
return (false, false);
|
||||
}
|
||||
|
||||
if (!TryComp(xform.ParentUid, out GridAtmosphereComponent? gridAtmosphere))
|
||||
if (!HasComp<GridAtmosphereComponent>(xform.ParentUid))
|
||||
return (false, false);
|
||||
|
||||
var grid = Comp<MapGridComponent>(xform.ParentUid);
|
||||
var pos = grid.CoordinatesToTile(xform.Coordinates);
|
||||
var pos = _mapping.CoordinatesToTile(xform.ParentUid, grid, xform.Coordinates);
|
||||
var minPressure = float.MaxValue;
|
||||
var maxPressure = float.MinValue;
|
||||
var minTemperature = float.MaxValue;
|
||||
var maxTemperature = float.MinValue;
|
||||
bool holdingFire = false;
|
||||
bool holdingPressure = false;
|
||||
var holdingFire = false;
|
||||
var holdingPressure = false;
|
||||
|
||||
// We cannot simply use `_atmosSystem.GetAdjacentTileMixtures` because of how the `includeBlocked` option
|
||||
// works, we want to ignore the firelock's blocking, while including blockers on other tiles.
|
||||
@@ -284,7 +171,7 @@ namespace Content.Server.Doors.Systems
|
||||
{
|
||||
// Is there some airtight entity blocking this direction? If yes, don't include this direction in the
|
||||
// pressure differential
|
||||
if (HasAirtightBlocker(grid.GetAnchoredEntities(adjacentPos), dir.GetOpposite(), airtightQuery))
|
||||
if (HasAirtightBlocker(_mapping.GetAnchoredEntities(xform.ParentUid, grid, adjacentPos), dir.GetOpposite(), airtightQuery))
|
||||
continue;
|
||||
|
||||
var p = gas.Pressure;
|
||||
|
||||
@@ -163,8 +163,8 @@ public sealed partial class EnsnareableSystem
|
||||
public void UpdateAlert(EntityUid target, EnsnareableComponent component)
|
||||
{
|
||||
if (!component.IsEnsnared)
|
||||
_alerts.ClearAlert(target, AlertType.Ensnared);
|
||||
_alerts.ClearAlert(target, component.EnsnaredAlert);
|
||||
else
|
||||
_alerts.ShowAlert(target, AlertType.Ensnared);
|
||||
_alerts.ShowAlert(target, component.EnsnaredAlert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,31 +7,6 @@ using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Governs interactions during clicking on entities
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class InteractionSystem : SharedInteractionSystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (Deleted(target))
|
||||
return false;
|
||||
|
||||
if (!_container.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
if (!TryComp(container.Owner, out StorageComponent? storage))
|
||||
return false;
|
||||
|
||||
if (storage.Container?.ID != container.ID)
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
return _uiSystem.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user);
|
||||
}
|
||||
}
|
||||
// TODO Remove Shared prefix
|
||||
public sealed class InteractionSystem : SharedInteractionSystem;
|
||||
}
|
||||
|
||||
@@ -102,20 +102,23 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
/// </summary>
|
||||
public void SetSuitPowerAlert(EntityUid uid, SpaceNinjaComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp, false) || comp.Deleted || comp.Suit == null)
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return;
|
||||
|
||||
if (comp.Deleted || comp.Suit == null)
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.SuitPower);
|
||||
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetNinjaBattery(uid, out _, out var battery))
|
||||
{
|
||||
var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, battery.CurrentCharge), battery.MaxCharge, 8);
|
||||
_alerts.ShowAlert(uid, AlertType.SuitPower, (short) severity);
|
||||
_alerts.ShowAlert(uid, comp.SuitPowerAlert, (short) severity);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.SuitPower);
|
||||
_alerts.ClearAlert(uid, comp.SuitPowerAlert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ public sealed partial class RevenantSystem : EntitySystem
|
||||
if (TryComp<StoreComponent>(uid, out var store))
|
||||
_store.UpdateUserInterface(uid, uid, store);
|
||||
|
||||
_alerts.ShowAlert(uid, AlertType.Essence);
|
||||
_alerts.ShowAlert(uid, component.EssenceAlert);
|
||||
|
||||
if (component.Essence <= 0)
|
||||
{
|
||||
|
||||
@@ -317,7 +317,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
|
||||
component.SubscribedPilots.Add(entity);
|
||||
|
||||
_alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);
|
||||
_alertsSystem.ShowAlert(entity, pilotComponent.PilotingAlert);
|
||||
|
||||
pilotComponent.Console = uid;
|
||||
ActionBlockerSystem.UpdateCanMove(entity);
|
||||
@@ -339,7 +339,7 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
||||
if (!helm.SubscribedPilots.Remove(pilotUid))
|
||||
return;
|
||||
|
||||
_alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
|
||||
_alertsSystem.ClearAlert(pilotUid, pilotComponent.PilotingAlert);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
|
||||
private void OnMapInit(EntityUid uid, BorgChassisComponent component, MapInitEvent args)
|
||||
{
|
||||
UpdateBatteryAlert(uid);
|
||||
UpdateBatteryAlert((uid, component));
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
|
||||
private void OnPowerCellChanged(EntityUid uid, BorgChassisComponent component, PowerCellChangedEvent args)
|
||||
{
|
||||
UpdateBatteryAlert(uid);
|
||||
UpdateBatteryAlert((uid, component));
|
||||
|
||||
if (!TryComp<PowerCellDrawComponent>(uid, out var draw))
|
||||
return;
|
||||
@@ -256,12 +256,12 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void UpdateBatteryAlert(EntityUid uid, PowerCellSlotComponent? slotComponent = null)
|
||||
private void UpdateBatteryAlert(Entity<BorgChassisComponent> ent, PowerCellSlotComponent? slotComponent = null)
|
||||
{
|
||||
if (!_powerCell.TryGetBatteryFromSlot(uid, out var battery, slotComponent))
|
||||
if (!_powerCell.TryGetBatteryFromSlot(ent, out var battery, slotComponent))
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.BorgBattery);
|
||||
_alerts.ShowAlert(uid, AlertType.BorgBatteryNone);
|
||||
_alerts.ClearAlert(ent, ent.Comp.BatteryAlert);
|
||||
_alerts.ShowAlert(ent, ent.Comp.NoBatteryAlert);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,13 +269,13 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
|
||||
// we make sure 0 only shows if they have absolutely no battery.
|
||||
// also account for floating point imprecision
|
||||
if (chargePercent == 0 && _powerCell.HasDrawCharge(uid, cell: slotComponent))
|
||||
if (chargePercent == 0 && _powerCell.HasDrawCharge(ent, cell: slotComponent))
|
||||
{
|
||||
chargePercent = 1;
|
||||
}
|
||||
|
||||
_alerts.ClearAlert(uid, AlertType.BorgBatteryNone);
|
||||
_alerts.ShowAlert(uid, AlertType.BorgBattery, chargePercent);
|
||||
_alerts.ClearAlert(ent, ent.Comp.NoBatteryAlert);
|
||||
_alerts.ShowAlert(ent, ent.Comp.BatteryAlert, chargePercent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,7 +28,9 @@ public sealed class ImmovableRodRule : StationEventSystem<ImmovableRodRuleCompon
|
||||
|
||||
if (proto.TryGetComponent<ImmovableRodComponent>(out var rod) && proto.TryGetComponent<TimedDespawnComponent>(out var despawn))
|
||||
{
|
||||
TryFindRandomTile(out _, out _, out _, out var targetCoords);
|
||||
if (!TryFindRandomTile(out _, out _, out _, out var targetCoords))
|
||||
return;
|
||||
|
||||
var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed);
|
||||
var angle = RobustRandom.NextAngle();
|
||||
var direction = angle.ToVec();
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Server.Temperature.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Temperature.Components;
|
||||
|
||||
@@ -78,4 +80,10 @@ public sealed partial class TemperatureComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool TakingDamage = false;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> HotAlert = "Hot";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> ColdAlert = "Cold";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Rejuvenate;
|
||||
using Content.Shared.Temperature;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Temperature.Systems;
|
||||
|
||||
@@ -33,6 +34,9 @@ public sealed class TemperatureSystem : EntitySystem
|
||||
|
||||
private float _accumulatedFrametime;
|
||||
|
||||
[ValidatePrototypeId<AlertCategoryPrototype>]
|
||||
public const string TemperatureAlertCategory = "Temperature";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<TemperatureComponent, OnTemperatureChangeEvent>(EnqueueDamage);
|
||||
@@ -184,13 +188,13 @@ public sealed class TemperatureSystem : EntitySystem
|
||||
|
||||
private void ServerAlert(EntityUid uid, AlertsComponent status, OnTemperatureChangeEvent args)
|
||||
{
|
||||
AlertType type;
|
||||
ProtoId<AlertPrototype> type;
|
||||
float threshold;
|
||||
float idealTemp;
|
||||
|
||||
if (!TryComp<TemperatureComponent>(uid, out var temperature))
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Temperature);
|
||||
_alerts.ClearAlertCategory(uid, TemperatureAlertCategory);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -207,12 +211,12 @@ public sealed class TemperatureSystem : EntitySystem
|
||||
|
||||
if (args.CurrentTemperature <= idealTemp)
|
||||
{
|
||||
type = AlertType.Cold;
|
||||
type = temperature.ColdAlert;
|
||||
threshold = temperature.ColdDamageThreshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = AlertType.Hot;
|
||||
type = temperature.HotAlert;
|
||||
threshold = temperature.HeatDamageThreshold;
|
||||
}
|
||||
|
||||
@@ -234,7 +238,7 @@ public sealed class TemperatureSystem : EntitySystem
|
||||
break;
|
||||
|
||||
case > 0.66f:
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Temperature);
|
||||
_alerts.ClearAlertCategory(uid, TemperatureAlertCategory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,15 +187,10 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
if (session is { } pSession)
|
||||
{
|
||||
(targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession);
|
||||
}
|
||||
else
|
||||
{
|
||||
var xform = Transform(target);
|
||||
targetCoordinates = xform.Coordinates;
|
||||
targetLocalAngle = xform.LocalRotation;
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
||||
}
|
||||
|
||||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
|
||||
return Interaction.InRangeUnobstructed(user, target, range);
|
||||
}
|
||||
|
||||
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
|
||||
@@ -502,13 +502,7 @@ public abstract class SharedActionsSystem : EntitySystem
|
||||
return distance <= action.Range;
|
||||
}
|
||||
|
||||
if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range)
|
||||
&& _containerSystem.IsInSameOrParentContainer(user, target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _interactionSystem.CanAccessViaStorage(user, target);
|
||||
return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range);
|
||||
}
|
||||
|
||||
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity<WorldTargetActionComponent> action)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// Every category of alert. Corresponds to category field in alert prototypes defined in YML
|
||||
/// </summary>
|
||||
public enum AlertCategory
|
||||
{
|
||||
Pressure,
|
||||
Temperature,
|
||||
Breathing,
|
||||
Buckled,
|
||||
Health,
|
||||
Internals,
|
||||
Stamina,
|
||||
Piloting,
|
||||
Hunger,
|
||||
Thirst,
|
||||
Toxins,
|
||||
Battery
|
||||
}
|
||||
14
Content.Shared/Alert/AlertCategoryPrototype.cs
Normal file
14
Content.Shared/Alert/AlertCategoryPrototype.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// This is a prototype for a category for marking alerts as mutually exclusive.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class AlertCategoryPrototype : IPrototype
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
[IdDataField]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Content.Shared.Alert;
|
||||
[Serializable, NetSerializable]
|
||||
public struct AlertKey
|
||||
{
|
||||
public AlertType? AlertType { get; private set; } = Alert.AlertType.Error;
|
||||
public readonly AlertCategory? AlertCategory;
|
||||
public ProtoId<AlertPrototype>? AlertType { get; private set; } = default!;
|
||||
public readonly ProtoId<AlertCategoryPrototype>? AlertCategory;
|
||||
|
||||
/// NOTE: if the alert has a category you must pass the category for this to work
|
||||
/// properly as a key. I.e. if the alert has a category and you pass only the alert type, and you
|
||||
/// compare this to another AlertKey that has both the category and the same alert type, it will not consider them equal.
|
||||
public AlertKey(AlertType? alertType, AlertCategory? alertCategory)
|
||||
public AlertKey(ProtoId<AlertPrototype>? alertType, ProtoId<AlertCategoryPrototype>? alertCategory)
|
||||
{
|
||||
AlertCategory = alertCategory;
|
||||
AlertType = alertType;
|
||||
@@ -49,7 +49,7 @@ public struct AlertKey
|
||||
/// <param name="category">alert category, must not be null</param>
|
||||
/// <returns>An alert key for the provided alert category. This must only be used for
|
||||
/// queries and never storage, as it is lacking an alert type.</returns>
|
||||
public static AlertKey ForCategory(AlertCategory category)
|
||||
public static AlertKey ForCategory(ProtoId<AlertCategoryPrototype> category)
|
||||
{
|
||||
return new(null, category);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Content.Shared.Alert
|
||||
/// <summary>
|
||||
/// Defines the order of alerts so they show up in a consistent order.
|
||||
/// </summary>
|
||||
[Prototype("alertOrder")]
|
||||
[Prototype]
|
||||
[DataDefinition]
|
||||
public sealed partial class AlertOrderPrototype : IPrototype, IComparer<AlertPrototype>
|
||||
{
|
||||
@@ -15,7 +15,7 @@ namespace Content.Shared.Alert
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("order")]
|
||||
[DataField]
|
||||
private (string type, string alert)[] Order
|
||||
{
|
||||
// why would paul do this to me.
|
||||
@@ -46,10 +46,10 @@ namespace Content.Shared.Alert
|
||||
switch (type)
|
||||
{
|
||||
case "alertType":
|
||||
_typeToIdx[Enum.Parse<AlertType>(alert)] = i++;
|
||||
_typeToIdx[alert] = i++;
|
||||
break;
|
||||
case "category":
|
||||
_categoryToIdx[Enum.Parse<AlertCategory>(alert)] = i++;
|
||||
_categoryToIdx[alert] = i++;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
@@ -58,17 +58,17 @@ namespace Content.Shared.Alert
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<AlertType, int> _typeToIdx = new();
|
||||
private readonly Dictionary<AlertCategory, int> _categoryToIdx = new();
|
||||
private readonly Dictionary<ProtoId<AlertPrototype>, int> _typeToIdx = new();
|
||||
private readonly Dictionary<ProtoId<AlertCategoryPrototype>, int> _categoryToIdx = new();
|
||||
|
||||
private int GetOrderIndex(AlertPrototype alert)
|
||||
{
|
||||
if (_typeToIdx.TryGetValue(alert.AlertType, out var idx))
|
||||
if (_typeToIdx.TryGetValue(alert.ID, out var idx))
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
if (alert.Category != null &&
|
||||
_categoryToIdx.TryGetValue((AlertCategory) alert.Category, out idx))
|
||||
_categoryToIdx.TryGetValue(alert.Category.Value, out idx))
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
@@ -78,20 +78,25 @@ namespace Content.Shared.Alert
|
||||
|
||||
public int Compare(AlertPrototype? x, AlertPrototype? y)
|
||||
{
|
||||
if ((x == null) && (y == null)) return 0;
|
||||
if (x == null) return 1;
|
||||
if (y == null) return -1;
|
||||
if (x == null && y == null)
|
||||
return 0;
|
||||
if (x == null)
|
||||
return 1;
|
||||
if (y == null)
|
||||
return -1;
|
||||
var idx = GetOrderIndex(x);
|
||||
var idy = GetOrderIndex(y);
|
||||
if (idx == -1 && idy == -1)
|
||||
{
|
||||
// break ties by type value
|
||||
// Must cast to int to avoid integer overflow when subtracting (enum's unsigned)
|
||||
return (int)x.AlertType - (int)y.AlertType;
|
||||
return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
if (idx == -1) return 1;
|
||||
if (idy == -1) return -1;
|
||||
if (idx == -1)
|
||||
return 1;
|
||||
if (idy == -1)
|
||||
return -1;
|
||||
var result = idx - idy;
|
||||
// not strictly necessary (we don't care about ones that go at the same index)
|
||||
// but it makes the sort stable
|
||||
@@ -99,7 +104,7 @@ namespace Content.Shared.Alert
|
||||
{
|
||||
// break ties by type value
|
||||
// Must cast to int to avoid integer overflow when subtracting (enum's unsigned)
|
||||
return (int)x.AlertType - (int)y.AlertType;
|
||||
return string.Compare(x.ID, y.ID, StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,120 +1,116 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Alert
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
/// <summary>
|
||||
/// An alert popup with associated icon, tooltip, and other data.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
public sealed partial class AlertPrototype : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// An alert popup with associated icon, tooltip, and other data.
|
||||
/// Type of alert, no 2 alert prototypes should have the same one.
|
||||
/// </summary>
|
||||
[Prototype("alert")]
|
||||
public sealed partial class AlertPrototype : IPrototype
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the
|
||||
/// minimum and incrementing upwards. If severities are not supported, the first entry is used.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public List<SpriteSpecifier> Icons = new();
|
||||
|
||||
/// <summary>
|
||||
/// An entity used for displaying the <see cref="Icons"/> in the UI control.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId AlertViewEntity = "AlertSpriteView";
|
||||
|
||||
/// <summary>
|
||||
/// Name to show in tooltip window. Accepts formatting.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Description to show in tooltip window. Accepts formatting.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Category the alert belongs to. Only one alert of a given category
|
||||
/// can be shown at a time. If one is shown while another is already being shown,
|
||||
/// it will be replaced. This can be useful for categories of alerts which should naturally
|
||||
/// replace each other and are mutually exclusive, for example lowpressure / highpressure,
|
||||
/// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype>? Category { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key which is unique w.r.t category semantics (alerts with same category have equal keys,
|
||||
/// alerts with no category have different keys).
|
||||
/// </summary>
|
||||
public AlertKey AlertKey => new(ID, Category);
|
||||
|
||||
/// <summary>
|
||||
/// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state.
|
||||
/// </summary>
|
||||
public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity;
|
||||
|
||||
[DataField("minSeverity")] private short _minSeverity = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum severity level supported by this state. -1 (default) indicates
|
||||
/// no severity levels are supported by the state.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public short MaxSeverity = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this state support severity levels
|
||||
/// </summary>
|
||||
public bool SupportsSeverity => MaxSeverity != -1;
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when the alert is clicked.
|
||||
/// This will always be null on clientside.
|
||||
/// </summary>
|
||||
[DataField(serverOnly: true)]
|
||||
public IAlertClick? OnClick { get; private set; }
|
||||
|
||||
/// <param name="severity">severity level, if supported by this alert</param>
|
||||
/// <returns>the icon path to the texture for the provided severity level</returns>
|
||||
public SpriteSpecifier GetIcon(short? severity = null)
|
||||
{
|
||||
[ViewVariables]
|
||||
string IPrototype.ID => AlertType.ToString();
|
||||
var minIcons = SupportsSeverity
|
||||
? MaxSeverity - MinSeverity
|
||||
: 1;
|
||||
|
||||
/// <summary>
|
||||
/// Type of alert, no 2 alert prototypes should have the same one.
|
||||
/// </summary>
|
||||
[IdDataField]
|
||||
public AlertType AlertType { get; private set; }
|
||||
if (Icons.Count < minIcons)
|
||||
throw new InvalidOperationException($"Insufficient number of icons given for alert {ID}");
|
||||
|
||||
/// <summary>
|
||||
/// List of icons to use for this alert. Each entry corresponds to a different severity level, starting from the
|
||||
/// minimum and incrementing upwards. If severities are not supported, the first entry is used.
|
||||
/// </summary>
|
||||
[DataField("icons", required: true)]
|
||||
public List<SpriteSpecifier> Icons = new();
|
||||
if (!SupportsSeverity)
|
||||
return Icons[0];
|
||||
|
||||
/// <summary>
|
||||
/// An entity used for displaying the <see cref="Icons"/> in the UI control.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId AlertViewEntity = "AlertSpriteView";
|
||||
|
||||
/// <summary>
|
||||
/// Name to show in tooltip window. Accepts formatting.
|
||||
/// </summary>
|
||||
[DataField("name")]
|
||||
public string Name { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Description to show in tooltip window. Accepts formatting.
|
||||
/// </summary>
|
||||
[DataField("description")]
|
||||
public string Description { get; private set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Category the alert belongs to. Only one alert of a given category
|
||||
/// can be shown at a time. If one is shown while another is already being shown,
|
||||
/// it will be replaced. This can be useful for categories of alerts which should naturally
|
||||
/// replace each other and are mutually exclusive, for example lowpressure / highpressure,
|
||||
/// hot / cold. If left unspecified, the alert will not replace or be replaced by any other alerts.
|
||||
/// </summary>
|
||||
[DataField("category")]
|
||||
public AlertCategory? Category { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key which is unique w.r.t category semantics (alerts with same category have equal keys,
|
||||
/// alerts with no category have different keys).
|
||||
/// </summary>
|
||||
public AlertKey AlertKey => new(AlertType, Category);
|
||||
|
||||
/// <summary>
|
||||
/// -1 (no effect) unless MaxSeverity is specified. Defaults to 1. Minimum severity level supported by this state.
|
||||
/// </summary>
|
||||
public short MinSeverity => MaxSeverity == -1 ? (short) -1 : _minSeverity;
|
||||
|
||||
[DataField("minSeverity")] private short _minSeverity = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum severity level supported by this state. -1 (default) indicates
|
||||
/// no severity levels are supported by the state.
|
||||
/// </summary>
|
||||
[DataField("maxSeverity")]
|
||||
public short MaxSeverity = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this state support severity levels
|
||||
/// </summary>
|
||||
public bool SupportsSeverity => MaxSeverity != -1;
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when the alert is clicked.
|
||||
/// This will always be null on clientside.
|
||||
/// </summary>
|
||||
[DataField("onClick", serverOnly: true)]
|
||||
public IAlertClick? OnClick { get; private set; }
|
||||
|
||||
/// <param name="severity">severity level, if supported by this alert</param>
|
||||
/// <returns>the icon path to the texture for the provided severity level</returns>
|
||||
public SpriteSpecifier GetIcon(short? severity = null)
|
||||
if (severity == null)
|
||||
{
|
||||
var minIcons = SupportsSeverity
|
||||
? MaxSeverity - MinSeverity
|
||||
: 1;
|
||||
|
||||
if (Icons.Count < minIcons)
|
||||
throw new InvalidOperationException($"Insufficient number of icons given for alert {AlertType}");
|
||||
|
||||
if (!SupportsSeverity)
|
||||
return Icons[0];
|
||||
|
||||
if (severity == null)
|
||||
{
|
||||
throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity));
|
||||
}
|
||||
|
||||
if (severity < MinSeverity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}.");
|
||||
}
|
||||
|
||||
if (severity > MaxSeverity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}.");
|
||||
}
|
||||
|
||||
return Icons[severity.Value - _minSeverity];
|
||||
throw new ArgumentException($"No severity specified but this alert ({AlertKey}) has severity.", nameof(severity));
|
||||
}
|
||||
|
||||
if (severity < MinSeverity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), $"Severity below minimum severity in {AlertKey}.");
|
||||
}
|
||||
|
||||
if (severity > MaxSeverity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), $"Severity above maximum severity in {AlertKey}.");
|
||||
}
|
||||
|
||||
return Icons[severity.Value - _minSeverity];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
@@ -9,5 +10,5 @@ public struct AlertState
|
||||
public (TimeSpan, TimeSpan)? Cooldown;
|
||||
public bool AutoRemove;
|
||||
public bool ShowCooldown;
|
||||
public AlertType Type;
|
||||
public ProtoId<AlertPrototype> Type;
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
namespace Content.Shared.Alert
|
||||
{
|
||||
/// <summary>
|
||||
/// Every kind of alert. Corresponds to alertType field in alert prototypes defined in YML
|
||||
/// NOTE: Using byte for a compact encoding when sending this in messages, can upgrade
|
||||
/// to ushort
|
||||
/// </summary>
|
||||
public enum AlertType : byte
|
||||
{
|
||||
Error,
|
||||
LowOxygen,
|
||||
LowNitrogen,
|
||||
LowPressure,
|
||||
HighPressure,
|
||||
Fire,
|
||||
Cold,
|
||||
Hot,
|
||||
Weightless,
|
||||
Stun,
|
||||
Handcuffed,
|
||||
Ensnared,
|
||||
Buckled,
|
||||
HumanCrit,
|
||||
HumanDead,
|
||||
HumanHealth,
|
||||
BorgBattery,
|
||||
BorgBatteryNone,
|
||||
PilotingShuttle,
|
||||
Peckish,
|
||||
Starving,
|
||||
Thirsty,
|
||||
Parched,
|
||||
Stamina,
|
||||
Pulled,
|
||||
Pulling,
|
||||
Magboots,
|
||||
Internals,
|
||||
Toxins,
|
||||
Muted,
|
||||
VowOfSilence,
|
||||
VowBroken,
|
||||
Essence,
|
||||
Corporeal,
|
||||
Bleed,
|
||||
Pacified,
|
||||
Debug1,
|
||||
Debug2,
|
||||
Debug3,
|
||||
Debug4,
|
||||
Debug5,
|
||||
Debug6,
|
||||
SuitPower,
|
||||
BorgHealth,
|
||||
BorgCrit,
|
||||
BorgDead,
|
||||
Deflecting
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private FrozenDictionary<AlertType, AlertPrototype> _typeToAlert = default!;
|
||||
private FrozenDictionary<ProtoId<AlertPrototype>, AlertPrototype> _typeToAlert = default!;
|
||||
|
||||
public IReadOnlyDictionary<AlertKey, AlertState>? GetActiveAlerts(EntityUid euid)
|
||||
{
|
||||
@@ -20,23 +20,23 @@ public abstract class AlertsSystem : EntitySystem
|
||||
: null;
|
||||
}
|
||||
|
||||
public short GetSeverityRange(AlertType alertType)
|
||||
public short GetSeverityRange(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
var minSeverity = _typeToAlert[alertType].MinSeverity;
|
||||
return (short)MathF.Max(minSeverity,_typeToAlert[alertType].MaxSeverity - minSeverity);
|
||||
}
|
||||
|
||||
public short GetMaxSeverity(AlertType alertType)
|
||||
public short GetMaxSeverity(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
return _typeToAlert[alertType].MaxSeverity;
|
||||
}
|
||||
|
||||
public short GetMinSeverity(AlertType alertType)
|
||||
public short GetMinSeverity(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
return _typeToAlert[alertType].MinSeverity;
|
||||
}
|
||||
|
||||
public bool IsShowingAlert(EntityUid euid, AlertType alertType)
|
||||
public bool IsShowingAlert(EntityUid euid, ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent))
|
||||
return false;
|
||||
@@ -51,7 +51,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <returns>true iff an alert of the indicated alert category is currently showing</returns>
|
||||
public bool IsShowingAlertCategory(EntityUid euid, AlertCategory alertCategory)
|
||||
public bool IsShowingAlertCategory(EntityUid euid, ProtoId<AlertCategoryPrototype> alertCategory)
|
||||
{
|
||||
return EntityManager.TryGetComponent(euid, out AlertsComponent? alertsComponent)
|
||||
&& alertsComponent.Alerts.ContainsKey(AlertKey.ForCategory(alertCategory));
|
||||
@@ -78,7 +78,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
/// be erased if there is currently a cooldown for the alert)</param>
|
||||
/// <param name="autoRemove">if true, the alert will be removed at the end of the cooldown</param>
|
||||
/// <param name="showCooldown">if true, the cooldown will be visibly shown over the alert icon</param>
|
||||
public void ShowAlert(EntityUid euid, AlertType alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true )
|
||||
public void ShowAlert(EntityUid euid, ProtoId<AlertPrototype> alertType, short? severity = null, (TimeSpan, TimeSpan)? cooldown = null, bool autoRemove = false, bool showCooldown = true )
|
||||
{
|
||||
// This should be handled as part of networking.
|
||||
if (_timing.ApplyingState)
|
||||
@@ -131,7 +131,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Clear the alert with the given category, if one is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlertCategory(EntityUid euid, AlertCategory category)
|
||||
public void ClearAlertCategory(EntityUid euid, ProtoId<AlertCategoryPrototype> category)
|
||||
{
|
||||
if(!TryComp(euid, out AlertsComponent? alertsComponent))
|
||||
return;
|
||||
@@ -150,7 +150,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Clear the alert of the given type if it is currently showing.
|
||||
/// </summary>
|
||||
public void ClearAlert(EntityUid euid, AlertType alertType)
|
||||
public void ClearAlert(EntityUid euid, ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
if (_timing.ApplyingState)
|
||||
return;
|
||||
@@ -286,13 +286,13 @@ public abstract class AlertsSystem : EntitySystem
|
||||
|
||||
protected virtual void LoadPrototypes()
|
||||
{
|
||||
var dict = new Dictionary<AlertType, AlertPrototype>();
|
||||
var dict = new Dictionary<ProtoId<AlertPrototype>, AlertPrototype>();
|
||||
foreach (var alert in _prototypeManager.EnumeratePrototypes<AlertPrototype>())
|
||||
{
|
||||
if (!dict.TryAdd(alert.AlertType, alert))
|
||||
if (!dict.TryAdd(alert.ID, alert))
|
||||
{
|
||||
Log.Error("Found alert with duplicate alertType {0} - all alerts must have" +
|
||||
" a unique alertType, this one will be skipped", alert.AlertType);
|
||||
" a unique alertType, this one will be skipped", alert.ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ public abstract class AlertsSystem : EntitySystem
|
||||
/// Tries to get the alert of the indicated type
|
||||
/// </summary>
|
||||
/// <returns>true if found</returns>
|
||||
public bool TryGet(AlertType alertType, [NotNullWhen(true)] out AlertPrototype? alert)
|
||||
public bool TryGet(ProtoId<AlertPrototype> alertType, [NotNullWhen(true)] out AlertPrototype? alert)
|
||||
{
|
||||
return _typeToAlert.TryGetValue(alertType, out alert);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Alert;
|
||||
|
||||
@@ -8,9 +9,9 @@ namespace Content.Shared.Alert;
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class ClickAlertEvent : EntityEventArgs
|
||||
{
|
||||
public readonly AlertType Type;
|
||||
public readonly ProtoId<AlertPrototype> Type;
|
||||
|
||||
public ClickAlertEvent(AlertType alertType)
|
||||
public ClickAlertEvent(ProtoId<AlertPrototype> alertType)
|
||||
{
|
||||
Type = alertType;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
|
||||
@@ -24,55 +23,47 @@ public sealed partial class GasTileOverlayComponent : Component
|
||||
public GameTick ForceTick { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GasTileOverlayState(Dictionary<Vector2i, GasOverlayChunk> chunks) : ComponentState
|
||||
{
|
||||
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks = chunks;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState
|
||||
public sealed class GasTileOverlayDeltaState(
|
||||
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks,
|
||||
HashSet<Vector2i> allChunks)
|
||||
: ComponentState, IComponentDeltaState<GasTileOverlayState>
|
||||
{
|
||||
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks;
|
||||
public bool FullState => AllChunks == null;
|
||||
public readonly Dictionary<Vector2i, GasOverlayChunk> ModifiedChunks = modifiedChunks;
|
||||
public readonly HashSet<Vector2i> AllChunks = allChunks;
|
||||
|
||||
// required to infer deleted/missing chunks for delta states
|
||||
public HashSet<Vector2i>? AllChunks;
|
||||
|
||||
public GasTileOverlayState(Dictionary<Vector2i, GasOverlayChunk> chunks)
|
||||
public void ApplyToFullState(GasTileOverlayState state)
|
||||
{
|
||||
Chunks = chunks;
|
||||
}
|
||||
|
||||
public void ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (GasTileOverlayState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
foreach (var key in state.Chunks.Keys)
|
||||
{
|
||||
if (!AllChunks!.Contains(key))
|
||||
if (!AllChunks.Contains(key))
|
||||
state.Chunks.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
foreach (var (chunk, data) in ModifiedChunks)
|
||||
{
|
||||
state.Chunks[chunk] = new(data);
|
||||
}
|
||||
}
|
||||
|
||||
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||
public GasTileOverlayState CreateNewFullState(GasTileOverlayState state)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (GasTileOverlayState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
var chunks = new Dictionary<Vector2i, GasOverlayChunk>(AllChunks.Count);
|
||||
|
||||
var chunks = new Dictionary<Vector2i, GasOverlayChunk>(state.Chunks.Count);
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
foreach (var (chunk, data) in ModifiedChunks)
|
||||
{
|
||||
chunks[chunk] = new(data);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in state.Chunks)
|
||||
{
|
||||
if (AllChunks!.Contains(chunk))
|
||||
if (AllChunks.Contains(chunk))
|
||||
chunks.TryAdd(chunk, new(data));
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Content.Shared.Atmos.EntitySystems
|
||||
data[index] = chunk;
|
||||
}
|
||||
|
||||
args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) };
|
||||
args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys));
|
||||
}
|
||||
|
||||
public static Vector2i GetGasChunkIndices(Vector2i indices)
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Buckle.Components;
|
||||
@@ -115,7 +116,7 @@ public sealed partial class StrapComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public AlertType BuckledAlertType = AlertType.Buckled;
|
||||
public ProtoId<AlertPrototype> BuckledAlertType = "Buckled";
|
||||
|
||||
/// <summary>
|
||||
/// The sum of the sizes of all the buckled entities in this strap
|
||||
|
||||
@@ -40,6 +40,9 @@ public abstract partial class SharedBuckleSystem
|
||||
SubscribeLocalEvent<BuckleComponent, UpdateCanMoveEvent>(OnBuckleUpdateCanMove);
|
||||
}
|
||||
|
||||
[ValidatePrototypeId<AlertCategoryPrototype>]
|
||||
public const string BuckledAlertCategory = "Buckled";
|
||||
|
||||
private void OnBuckleComponentStartup(EntityUid uid, BuckleComponent component, ComponentStartup args)
|
||||
{
|
||||
UpdateBuckleStatus(uid, component);
|
||||
@@ -61,7 +64,7 @@ public abstract partial class SharedBuckleSystem
|
||||
return;
|
||||
|
||||
var strapPosition = Transform(strapUid).Coordinates;
|
||||
if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance))
|
||||
if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance))
|
||||
return;
|
||||
|
||||
TryUnbuckle(uid, uid, true, component);
|
||||
@@ -165,7 +168,7 @@ public abstract partial class SharedBuckleSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Buckled);
|
||||
_alerts.ClearAlertCategory(uid, BuckledAlertCategory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
@@ -16,4 +17,7 @@ public sealed partial class MagbootsComponent : Component
|
||||
|
||||
[DataField("on"), AutoNetworkedField]
|
||||
public bool On;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> MagbootsAlert = "Magboots";
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.CombatMode.Pacification;
|
||||
@@ -109,7 +108,7 @@ public sealed class PacificationSystem : EntitySystem
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, false);
|
||||
}
|
||||
|
||||
_alertsSystem.ShowAlert(uid, AlertType.Pacified);
|
||||
_alertsSystem.ShowAlert(uid, component.PacifiedAlert);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, PacifiedComponent component, ComponentShutdown args)
|
||||
@@ -121,7 +120,7 @@ public sealed class PacificationSystem : EntitySystem
|
||||
_combatSystem.SetCanDisarm(uid, true, combatMode);
|
||||
|
||||
_actionsSystem.SetEnabled(combatMode.CombatToggleActionEntity, true);
|
||||
_alertsSystem.ClearAlert(uid, AlertType.Pacified);
|
||||
_alertsSystem.ClearAlert(uid, component.PacifiedAlert);
|
||||
}
|
||||
|
||||
private void OnBeforeThrow(Entity<PacifiedComponent> ent, ref BeforeThrowEvent args)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.CombatMode.Pacification;
|
||||
|
||||
@@ -42,4 +44,6 @@ public sealed partial class PacifiedComponent : Component
|
||||
[DataField]
|
||||
public EntityUid? LastAttackedEntity = null;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> PacifiedAlert = "Pacified";
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!EntityManager.TryGetComponent(args.User, out HandsComponent? hands))
|
||||
return;
|
||||
|
||||
var slots = new List<ItemSlot>();
|
||||
foreach (var slot in itemSlots.Slots.Values)
|
||||
{
|
||||
if (!slot.InsertOnInteract)
|
||||
@@ -205,10 +206,20 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!CanInsert(uid, args.Used, args.User, slot, swap: slot.Swap, popup: args.User))
|
||||
continue;
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
|
||||
return;
|
||||
slots.Add(slot);
|
||||
}
|
||||
|
||||
if (slots.Count == 0)
|
||||
return;
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
|
||||
return;
|
||||
|
||||
slots.Sort(SortEmpty);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (slot.Item != null)
|
||||
_handsSystem.TryPickupAnyHand(args.User, slot.Item.Value, handsComp: hands);
|
||||
|
||||
@@ -333,6 +344,65 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to insert an item into any empty slot.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity that has the item slots.</param>
|
||||
/// <param name="item">The item to be inserted.</param>
|
||||
/// <param name="user">The entity performing the interaction.</param>
|
||||
/// <param name="excludeUserAudio">
|
||||
/// If true, will exclude the user when playing sound. Does nothing client-side.
|
||||
/// Useful for predicted interactions
|
||||
/// </param>
|
||||
/// <returns>False if failed to insert item</returns>
|
||||
public bool TryInsertEmpty(Entity<ItemSlotsComponent?> ent, EntityUid item, EntityUid? user, bool excludeUserAudio = false)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
var slots = new List<ItemSlot>();
|
||||
foreach (var slot in ent.Comp.Slots.Values)
|
||||
{
|
||||
if (slot.ContainerSlot?.ContainedEntity != null)
|
||||
continue;
|
||||
|
||||
if (CanInsert(ent, item, user, slot))
|
||||
slots.Add(slot);
|
||||
}
|
||||
|
||||
if (slots.Count == 0)
|
||||
return false;
|
||||
|
||||
if (user != null && _handsSystem.IsHolding(user.Value, item))
|
||||
{
|
||||
if (!_handsSystem.TryDrop(user.Value, item))
|
||||
return false;
|
||||
}
|
||||
|
||||
slots.Sort(SortEmpty);
|
||||
|
||||
foreach (var slot in slots)
|
||||
{
|
||||
if (TryInsert(ent, slot, item, user, excludeUserAudio: excludeUserAudio))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int SortEmpty(ItemSlot a, ItemSlot b)
|
||||
{
|
||||
var aEnt = a.ContainerSlot?.ContainedEntity;
|
||||
var bEnt = b.ContainerSlot?.ContainedEntity;
|
||||
if (aEnt == null && bEnt == null)
|
||||
return a.Priority.CompareTo(b.Priority);
|
||||
|
||||
if (aEnt == null)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Eject
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -39,6 +41,9 @@ public sealed partial class CuffableComponent : Component
|
||||
/// </summary>
|
||||
[DataField("canStillInteract"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanStillInteract = true;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> CuffedAlert = "Handcuffed";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -172,9 +172,9 @@ namespace Content.Shared.Cuffs
|
||||
_actionBlocker.UpdateCanMove(uid);
|
||||
|
||||
if (component.CanStillInteract)
|
||||
_alerts.ClearAlert(uid, AlertType.Handcuffed);
|
||||
_alerts.ClearAlert(uid, component.CuffedAlert);
|
||||
else
|
||||
_alerts.ShowAlert(uid, AlertType.Handcuffed);
|
||||
_alerts.ShowAlert(uid, component.CuffedAlert);
|
||||
|
||||
var ev = new CuffedStateChangeEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Damage.Components;
|
||||
@@ -51,4 +53,7 @@ public sealed partial class StaminaComponent : Component
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> StaminaAlert = "Stamina";
|
||||
}
|
||||
|
||||
@@ -79,8 +79,7 @@ public sealed partial class StaminaSystem : EntitySystem
|
||||
{
|
||||
RemCompDeferred<ActiveStaminaComponent>(uid);
|
||||
}
|
||||
|
||||
SetStaminaAlert(uid);
|
||||
_alerts.ClearAlert(uid, component.StaminaAlert);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, StaminaComponent component, ComponentStartup args)
|
||||
@@ -204,13 +203,10 @@ public sealed partial class StaminaSystem : EntitySystem
|
||||
private void SetStaminaAlert(EntityUid uid, StaminaComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false) || component.Deleted)
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.Stamina);
|
||||
return;
|
||||
}
|
||||
|
||||
var severity = ContentHelpers.RoundToLevels(MathF.Max(0f, component.CritThreshold - component.StaminaDamage), component.CritThreshold, 7);
|
||||
_alerts.ShowAlert(uid, AlertType.Stamina, (short) severity);
|
||||
_alerts.ShowAlert(uid, component.StaminaAlert, (short) severity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -62,46 +62,37 @@ namespace Content.Shared.Decals
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DecalGridState : ComponentState, IComponentDeltaState
|
||||
public sealed class DecalGridState(Dictionary<Vector2i, DecalChunk> chunks) : ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, DecalChunk> Chunks;
|
||||
public bool FullState => AllChunks == null;
|
||||
public Dictionary<Vector2i, DecalChunk> Chunks = chunks;
|
||||
}
|
||||
|
||||
// required to infer deleted/missing chunks for delta states
|
||||
public HashSet<Vector2i>? AllChunks;
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class DecalGridDeltaState(Dictionary<Vector2i, DecalChunk> modifiedChunks, HashSet<Vector2i> allChunks)
|
||||
: ComponentState, IComponentDeltaState<DecalGridState>
|
||||
{
|
||||
public Dictionary<Vector2i, DecalChunk> ModifiedChunks = modifiedChunks;
|
||||
public HashSet<Vector2i> AllChunks = allChunks;
|
||||
|
||||
public DecalGridState(Dictionary<Vector2i, DecalChunk> chunks)
|
||||
public void ApplyToFullState(DecalGridState state)
|
||||
{
|
||||
Chunks = chunks;
|
||||
}
|
||||
|
||||
public void ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (DecalGridState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
foreach (var key in state.Chunks.Keys)
|
||||
{
|
||||
if (!AllChunks!.Contains(key))
|
||||
state.Chunks.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
foreach (var (chunk, data) in ModifiedChunks)
|
||||
{
|
||||
state.Chunks[chunk] = new(data);
|
||||
}
|
||||
}
|
||||
|
||||
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||
public DecalGridState CreateNewFullState(DecalGridState state)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (DecalGridState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
var chunks = new Dictionary<Vector2i, DecalChunk>(state.Chunks.Count);
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
foreach (var (chunk, data) in ModifiedChunks)
|
||||
{
|
||||
chunks[chunk] = new(data);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Content.Shared.Decals
|
||||
data[index] = chunk;
|
||||
}
|
||||
|
||||
args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) };
|
||||
args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys));
|
||||
}
|
||||
|
||||
private void OnGridInitialize(GridInitializeEvent msg)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Doors.Components
|
||||
{
|
||||
@@ -7,9 +7,11 @@ namespace Content.Shared.Doors.Components
|
||||
/// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when
|
||||
/// retaining pressure..
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class FirelockComponent : Component
|
||||
{
|
||||
#region Settings
|
||||
|
||||
/// <summary>
|
||||
/// Pry time modifier to be used when the firelock is currently closed due to fire or pressure.
|
||||
/// </summary>
|
||||
@@ -17,8 +19,6 @@ namespace Content.Shared.Doors.Components
|
||||
[DataField("lockedPryTimeModifier"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LockedPryTimeModifier = 1.5f;
|
||||
|
||||
[DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum pressure difference before the firelock will refuse to open, in kPa.
|
||||
/// </summary>
|
||||
@@ -39,5 +39,47 @@ namespace Content.Shared.Doors.Components
|
||||
/// </summary>
|
||||
[DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AlarmAutoClose = true;
|
||||
|
||||
/// <summary>
|
||||
/// The cooldown duration before a firelock can automatically close due to a hazardous environment after it has
|
||||
/// been pried open. Measured in seconds.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan EmergencyCloseCooldownDuration = TimeSpan.FromSeconds(2);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Set by system
|
||||
|
||||
/// <summary>
|
||||
/// When the firelock will be allowed to automatically close again due to a hazardous environment.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan? EmergencyCloseCooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the firelock can open, or is locked due to its environment.
|
||||
/// </summary>
|
||||
public bool IsLocked => Pressure || Temperature;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the firelock is holding back a hazardous pressure.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Pressure;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the firelock is holding back extreme temperatures.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Temperature;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the airlock is powered.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Powered;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
@@ -466,7 +465,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
|
||||
|
||||
door.Partial = true;
|
||||
|
||||
// Make sure no entity waled into the airlock when it started closing.
|
||||
// Make sure no entity walked into the airlock when it started closing.
|
||||
if (!CanClose(uid, door))
|
||||
{
|
||||
door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo;
|
||||
|
||||
110
Content.Shared/Doors/Systems/SharedFirelockSystem.cs
Normal file
110
Content.Shared/Doors/Systems/SharedFirelockSystem.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Prying.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Doors.Systems;
|
||||
|
||||
public abstract class SharedFirelockSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Access/Prying
|
||||
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
|
||||
SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
|
||||
SubscribeLocalEvent<FirelockComponent, PriedEvent>(OnAfterPried);
|
||||
|
||||
// Visuals
|
||||
SubscribeLocalEvent<FirelockComponent, MapInitEvent>(UpdateVisuals);
|
||||
SubscribeLocalEvent<FirelockComponent, ComponentStartup>(UpdateVisuals);
|
||||
}
|
||||
|
||||
public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null)
|
||||
{
|
||||
if (!Resolve(uid, ref firelock, ref door))
|
||||
return false;
|
||||
|
||||
if (door.State != DoorState.Open
|
||||
|| firelock.EmergencyCloseCooldown != null
|
||||
&& _gameTiming.CurTime < firelock.EmergencyCloseCooldown)
|
||||
return false;
|
||||
|
||||
if (!_doorSystem.TryClose(uid, door))
|
||||
return false;
|
||||
|
||||
return _doorSystem.OnPartialClose(uid, door);
|
||||
}
|
||||
|
||||
#region Access/Prying
|
||||
|
||||
private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
|
||||
{
|
||||
// Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas
|
||||
var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid);
|
||||
|
||||
if (!component.Powered || (!overrideAccess && component.IsLocked))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
|
||||
{
|
||||
if (component.Temperature)
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-fire-message"),
|
||||
uid, args.User, PopupType.MediumCaution);
|
||||
}
|
||||
else if (component.Pressure)
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-pressure-message"),
|
||||
uid, args.User, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
if (component.IsLocked)
|
||||
args.PryTimeModifier *= component.LockedPryTimeModifier;
|
||||
}
|
||||
|
||||
private void OnAfterPried(EntityUid uid, FirelockComponent component, ref PriedEvent args)
|
||||
{
|
||||
component.EmergencyCloseCooldown = _gameTiming.CurTime + component.EmergencyCloseCooldownDuration;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Visuals
|
||||
|
||||
private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component);
|
||||
|
||||
private void UpdateVisuals(EntityUid uid,
|
||||
FirelockComponent? firelock = null,
|
||||
DoorComponent? door = null,
|
||||
AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref door, ref appearance, false))
|
||||
return;
|
||||
|
||||
// only bother to check pressure on doors that are some variation of closed.
|
||||
if (door.State != DoorState.Closed
|
||||
&& door.State != DoorState.Welded
|
||||
&& door.State != DoorState.Denying)
|
||||
{
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Resolve(uid, ref firelock, ref appearance, false))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, firelock.IsLocked, appearance);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Ensnaring.Components;
|
||||
@@ -40,6 +42,9 @@ public sealed partial class EnsnareableComponent : Component
|
||||
|
||||
[DataField("state")]
|
||||
public string? State;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> EnsnaredAlert = "Ensnared";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Content.Shared.Gravity
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
[ValidatePrototypeId<AlertPrototype>]
|
||||
public const string WeightlessAlert = "Weightless";
|
||||
|
||||
public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null)
|
||||
{
|
||||
Resolve(uid, ref body, false);
|
||||
@@ -93,11 +96,11 @@ namespace Content.Shared.Gravity
|
||||
|
||||
if (!ev.HasGravity)
|
||||
{
|
||||
_alerts.ShowAlert(uid, AlertType.Weightless);
|
||||
_alerts.ShowAlert(uid, WeightlessAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.Weightless);
|
||||
_alerts.ClearAlert(uid, WeightlessAlert);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,11 +109,11 @@ namespace Content.Shared.Gravity
|
||||
{
|
||||
if (IsWeightless(ev.Euid))
|
||||
{
|
||||
_alerts.ShowAlert(ev.Euid, AlertType.Weightless);
|
||||
_alerts.ShowAlert(ev.Euid, WeightlessAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(ev.Euid, AlertType.Weightless);
|
||||
_alerts.ClearAlert(ev.Euid, WeightlessAlert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,11 +121,11 @@ namespace Content.Shared.Gravity
|
||||
{
|
||||
if (IsWeightless(uid))
|
||||
{
|
||||
_alerts.ShowAlert(uid, AlertType.Weightless);
|
||||
_alerts.ShowAlert(uid, WeightlessAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(uid, AlertType.Weightless);
|
||||
_alerts.ClearAlert(uid, WeightlessAlert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Interaction.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class BypassInteractionChecksComponent : Component;
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Input;
|
||||
@@ -15,12 +14,13 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wall;
|
||||
using JetBrains.Annotations;
|
||||
@@ -36,6 +36,7 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
#pragma warning disable 618
|
||||
|
||||
@@ -50,12 +51,11 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _broadphase = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
@@ -64,6 +64,18 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUiRangeQuery;
|
||||
private EntityQuery<FixturesComponent> _fixtureQuery;
|
||||
private EntityQuery<ItemComponent> _itemQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<HandsComponent> _handsQuery;
|
||||
private EntityQuery<InteractionRelayComponent> _relayQuery;
|
||||
private EntityQuery<CombatModeComponent> _combatQuery;
|
||||
private EntityQuery<WallMountComponent> _wallMountQuery;
|
||||
private EntityQuery<UseDelayComponent> _delayQuery;
|
||||
private EntityQuery<ActivatableUIComponent> _uiQuery;
|
||||
|
||||
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
|
||||
|
||||
@@ -76,6 +88,17 @@ namespace Content.Shared.Interaction
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_ignoreUiRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
_fixtureQuery = GetEntityQuery<FixturesComponent>();
|
||||
_itemQuery = GetEntityQuery<ItemComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_handsQuery = GetEntityQuery<HandsComponent>();
|
||||
_relayQuery = GetEntityQuery<InteractionRelayComponent>();
|
||||
_combatQuery = GetEntityQuery<CombatModeComponent>();
|
||||
_wallMountQuery = GetEntityQuery<WallMountComponent>();
|
||||
_delayQuery = GetEntityQuery<UseDelayComponent>();
|
||||
_uiQuery = GetEntityQuery<ActivatableUIComponent>();
|
||||
|
||||
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
|
||||
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
|
||||
|
||||
@@ -111,29 +134,57 @@ namespace Content.Shared.Interaction
|
||||
/// </summary>
|
||||
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
|
||||
{
|
||||
var user = ev.Actor;
|
||||
_uiQuery.TryComp(ev.Target, out var uiComp);
|
||||
if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target))
|
||||
{
|
||||
// We permit ghosts to open uis unless explicitly blocked
|
||||
if (ev.Message is not OpenBoundInterfaceMessage || !HasComp<GhostComponent>(ev.Actor) || uiComp?.BlockSpectators == true)
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_actionBlockerSystem.CanInteract(user, ev.Target))
|
||||
var range = _ui.GetUiRange(ev.Target, ev.UiKey);
|
||||
|
||||
// As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range.
|
||||
DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range));
|
||||
|
||||
if (range <= 0 && !IsAccessible(ev.Actor, ev.Target))
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that
|
||||
// they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into
|
||||
// other people's backpacks).
|
||||
if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target)
|
||||
&& !CanAccessViaStorage(user, ev.Target)
|
||||
&& !_adminManager.HasAdminFlag(user, AdminFlags.Admin))
|
||||
if (uiComp == null)
|
||||
return;
|
||||
|
||||
if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor)
|
||||
{
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InRangeUnobstructed(user, ev.Target))
|
||||
{
|
||||
if (!uiComp.RequireHands)
|
||||
return;
|
||||
|
||||
if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0)
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private bool UiRangeCheck(Entity<TransformComponent?> user, Entity<TransformComponent?> target, float range)
|
||||
{
|
||||
if (!Resolve(target, ref target.Comp))
|
||||
return false;
|
||||
|
||||
if (user.Owner == target.Owner)
|
||||
return true;
|
||||
|
||||
// Fast check: if the user is the parent of the entity (e.g., holding it), we always assume that it is in range
|
||||
if (target.Comp.ParentUid == user.Owner)
|
||||
return true;
|
||||
|
||||
return InRangeAndAccessible(user, target, range) || _ignoreUiRangeQuery.HasComp(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -190,10 +241,7 @@ namespace Content.Shared.Interaction
|
||||
if (!InRangeUnobstructed(userEntity.Value, uid, popup: true))
|
||||
return false;
|
||||
|
||||
if (!TryComp(uid, out PullableComponent? pull))
|
||||
return false;
|
||||
|
||||
_pullSystem.TogglePull(uid, userEntity.Value, pull);
|
||||
_pullSystem.TogglePull(uid, userEntity.Value);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -269,7 +317,7 @@ namespace Content.Shared.Interaction
|
||||
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
|
||||
{
|
||||
// Always allow attack in these cases
|
||||
if (target == null || !TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
|
||||
if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
|
||||
return false;
|
||||
|
||||
// Only eat input if:
|
||||
@@ -277,7 +325,7 @@ namespace Content.Shared.Interaction
|
||||
// - Target doesn't cancel should-interact event
|
||||
// This is intended to allow items to be picked up in combat mode,
|
||||
// but to also allow items to force attacks anyway (like mobs which are items, e.g. mice)
|
||||
if (!HasComp<ItemComponent>(target))
|
||||
if (!_itemQuery.HasComp(target))
|
||||
return false;
|
||||
|
||||
var combatEv = new CombatModeShouldHandInteractEvent();
|
||||
@@ -307,7 +355,7 @@ namespace Content.Shared.Interaction
|
||||
bool checkAccess = true,
|
||||
bool checkCanUse = true)
|
||||
{
|
||||
if (TryComp<InteractionRelayComponent>(user, out var relay) && relay.RelayEntity is not null)
|
||||
if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null)
|
||||
{
|
||||
// TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways.
|
||||
if (_actionBlockerSystem.CanInteract(user, target))
|
||||
@@ -321,7 +369,7 @@ namespace Content.Shared.Interaction
|
||||
if (target != null && Deleted(target.Value))
|
||||
return;
|
||||
|
||||
if (!altInteract && TryComp<CombatModeComponent>(user, out var combatMode) && combatMode.IsInCombatMode)
|
||||
if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode)
|
||||
{
|
||||
if (!CombatModeCanHandInteract(user, target))
|
||||
return;
|
||||
@@ -343,10 +391,7 @@ namespace Content.Shared.Interaction
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
// Also checks if the item is accessible via some storage UI (e.g., open backpack)
|
||||
if (checkAccess
|
||||
&& target != null
|
||||
&& !_containerSystem.IsInSameOrParentContainer(user, target.Value)
|
||||
&& !CanAccessViaStorage(user, target.Value))
|
||||
if (checkAccess && target != null && !IsAccessible(user, target.Value))
|
||||
return;
|
||||
|
||||
var inRangeUnobstructed = target == null
|
||||
@@ -354,7 +399,7 @@ namespace Content.Shared.Interaction
|
||||
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
|
||||
|
||||
// Does the user have hands?
|
||||
if (!TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand == null)
|
||||
if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null)
|
||||
{
|
||||
var ev = new InteractNoHandEvent(user, target, coordinates);
|
||||
RaiseLocalEvent(user, ev);
|
||||
@@ -494,7 +539,7 @@ namespace Content.Shared.Interaction
|
||||
|
||||
predicate ??= _ => false;
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask);
|
||||
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
|
||||
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
|
||||
|
||||
if (rayResults.Count == 0)
|
||||
return dir.Length();
|
||||
@@ -557,23 +602,29 @@ namespace Content.Shared.Interaction
|
||||
}
|
||||
|
||||
var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask);
|
||||
var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
|
||||
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
|
||||
|
||||
return rayResults.Count == 0;
|
||||
}
|
||||
|
||||
public bool InRangeUnobstructed(
|
||||
EntityUid origin,
|
||||
EntityUid other,
|
||||
Entity<TransformComponent?> origin,
|
||||
Entity<TransformComponent?> other,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
{
|
||||
if (!TryComp(other, out TransformComponent? otherXform))
|
||||
if (!Resolve(other, ref other.Comp))
|
||||
return false;
|
||||
|
||||
return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
|
||||
return InRangeUnobstructed(origin,
|
||||
other,
|
||||
other.Comp.Coordinates,
|
||||
other.Comp.LocalRotation,
|
||||
range,
|
||||
collisionMask,
|
||||
predicate,
|
||||
popup);
|
||||
}
|
||||
|
||||
@@ -605,8 +656,8 @@ namespace Content.Shared.Interaction
|
||||
/// True if the two points are within a given range without being obstructed.
|
||||
/// </returns>
|
||||
public bool InRangeUnobstructed(
|
||||
EntityUid origin,
|
||||
EntityUid other,
|
||||
Entity<TransformComponent?> origin,
|
||||
Entity<TransformComponent?> other,
|
||||
EntityCoordinates otherCoordinates,
|
||||
Angle otherAngle,
|
||||
float range = InteractionRange,
|
||||
@@ -614,10 +665,10 @@ namespace Content.Shared.Interaction
|
||||
Ignored? predicate = null,
|
||||
bool popup = false)
|
||||
{
|
||||
Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
|
||||
Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false);
|
||||
var inRange = true;
|
||||
MapCoordinates originPos = default;
|
||||
var targetPos = otherCoordinates.ToMap(EntityManager, _transform);
|
||||
var targetPos = _transform.ToMapCoordinates(otherCoordinates);
|
||||
Angle targetRot = default;
|
||||
|
||||
// So essentially:
|
||||
@@ -627,23 +678,30 @@ namespace Content.Shared.Interaction
|
||||
// Alternatively we could check centre distances first though
|
||||
// that means we wouldn't be able to easily check overlap interactions.
|
||||
if (range > 0f &&
|
||||
TryComp<FixturesComponent>(origin, out var fixtureA) &&
|
||||
_fixtureQuery.TryComp(origin, out var fixtureA) &&
|
||||
// These fixture counts are stuff that has the component but no fixtures for <reasons> (e.g. buttons).
|
||||
// At least until they get removed.
|
||||
fixtureA.FixtureCount > 0 &&
|
||||
TryComp<FixturesComponent>(other, out var fixtureB) &&
|
||||
_fixtureQuery.TryComp(other, out var fixtureB) &&
|
||||
fixtureB.FixtureCount > 0 &&
|
||||
TryComp(origin, out TransformComponent? xformA))
|
||||
Resolve(origin, ref origin.Comp))
|
||||
{
|
||||
var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation();
|
||||
var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation();
|
||||
var xfA = new Transform(worldPosA, worldRotA);
|
||||
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
|
||||
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
|
||||
|
||||
// Different map or the likes.
|
||||
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
|
||||
out _, out _, out var distance,
|
||||
xfA, xfB, fixtureA, fixtureB))
|
||||
if (!_broadphase.TryGetNearest(
|
||||
origin,
|
||||
other,
|
||||
out _,
|
||||
out _,
|
||||
out var distance,
|
||||
xfA,
|
||||
xfB,
|
||||
fixtureA,
|
||||
fixtureB))
|
||||
{
|
||||
inRange = false;
|
||||
}
|
||||
@@ -665,15 +723,15 @@ namespace Content.Shared.Interaction
|
||||
else
|
||||
{
|
||||
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
|
||||
originPos = _transform.GetMapCoordinates(origin, xform: xformA);
|
||||
originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp);
|
||||
range = (originPos.Position - targetPos.Position).Length();
|
||||
}
|
||||
}
|
||||
// No fixtures, e.g. wallmounts.
|
||||
else
|
||||
{
|
||||
originPos = _transform.GetMapCoordinates(origin);
|
||||
var otherParent = Transform(other).ParentUid;
|
||||
originPos = _transform.GetMapCoordinates(origin, origin);
|
||||
var otherParent = (other.Comp ?? Transform(other)).ParentUid;
|
||||
targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle;
|
||||
}
|
||||
|
||||
@@ -724,13 +782,13 @@ namespace Content.Shared.Interaction
|
||||
{
|
||||
HashSet<EntityUid> ignored = new();
|
||||
|
||||
if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
|
||||
if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide)
|
||||
{
|
||||
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck
|
||||
// inside of walls, users can still pick them up.
|
||||
ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
|
||||
ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
|
||||
}
|
||||
else if (TryComp(target, out WallMountComponent? wallMount))
|
||||
else if (_wallMountQuery.TryComp(target, out var wallMount))
|
||||
{
|
||||
// wall-mount exemptions may be restricted to a specific angle range.da
|
||||
|
||||
@@ -748,13 +806,7 @@ namespace Content.Shared.Interaction
|
||||
ignored.UnionWith(grid.GetAnchoredEntities(targetCoords));
|
||||
}
|
||||
|
||||
Ignored combinedPredicate = e =>
|
||||
{
|
||||
return e == target
|
||||
|| (predicate?.Invoke(e) ?? false)
|
||||
|| ignored.Contains(e);
|
||||
};
|
||||
|
||||
Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e);
|
||||
return combinedPredicate;
|
||||
}
|
||||
|
||||
@@ -951,10 +1003,8 @@ namespace Content.Shared.Interaction
|
||||
bool checkUseDelay = true,
|
||||
bool checkAccess = true)
|
||||
{
|
||||
UseDelayComponent? delayComponent = null;
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& _useDelay.IsDelayed((used, delayComponent)))
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return false;
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
@@ -965,11 +1015,11 @@ namespace Content.Shared.Interaction
|
||||
|
||||
// Check if interacted entity is in the same container, the direct child, or direct parent of the user.
|
||||
// This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
|
||||
if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
|
||||
if (checkAccess && !IsAccessible(user, used))
|
||||
return false;
|
||||
|
||||
// Does the user have hands?
|
||||
if (!HasComp<HandsComponent>(user))
|
||||
if (!_handsQuery.HasComp(user))
|
||||
return false;
|
||||
|
||||
var activateMsg = new ActivateInWorldEvent(user, used);
|
||||
@@ -979,7 +1029,9 @@ namespace Content.Shared.Interaction
|
||||
|
||||
DoContactInteraction(user, used, activateMsg);
|
||||
// Still need to call this even without checkUseDelay in case this gets relayed from Activate.
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
if (delayComponent != null)
|
||||
_useDelay.TryResetDelay(used, component: delayComponent);
|
||||
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
return true;
|
||||
@@ -1000,11 +1052,8 @@ namespace Content.Shared.Interaction
|
||||
bool checkCanInteract = true,
|
||||
bool checkUseDelay = true)
|
||||
{
|
||||
UseDelayComponent? delayComponent = null;
|
||||
|
||||
if (checkUseDelay
|
||||
&& TryComp(used, out delayComponent)
|
||||
&& _useDelay.IsDelayed((used, delayComponent)))
|
||||
_delayQuery.TryComp(used, out var delayComponent);
|
||||
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
|
||||
return true; // if the item is on cooldown, we consider this handled.
|
||||
|
||||
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
|
||||
@@ -1066,11 +1115,60 @@ namespace Content.Shared.Interaction
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Check if a user can access a target (stored in the same containers) and is in range without obstructions.
|
||||
/// </summary>
|
||||
public bool InRangeAndAccessible(
|
||||
Entity<TransformComponent?> user,
|
||||
Entity<TransformComponent?> target,
|
||||
float range = InteractionRange,
|
||||
CollisionGroup collisionMask = InRangeUnobstructedMask,
|
||||
Ignored? predicate = null)
|
||||
{
|
||||
if (user == target)
|
||||
return true;
|
||||
|
||||
if (!Resolve(user, ref user.Comp))
|
||||
return false;
|
||||
|
||||
if (!Resolve(target, ref target.Comp))
|
||||
return false;
|
||||
|
||||
return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a user can access a target or if they are stored in different containers.
|
||||
/// </summary>
|
||||
public bool IsAccessible(Entity<TransformComponent?> user, Entity<TransformComponent?> target)
|
||||
{
|
||||
if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container))
|
||||
return true;
|
||||
|
||||
return container != null && CanAccessViaStorage(user, target, container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
|
||||
/// checks if the user can access the item in these situations.
|
||||
/// </summary>
|
||||
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
|
||||
public bool CanAccessViaStorage(EntityUid user, EntityUid target)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainingContainer(target, out var container))
|
||||
return false;
|
||||
|
||||
return CanAccessViaStorage(user, target, container);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CanAccessViaStorage(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid)"/>
|
||||
public bool CanAccessViaStorage(EntityUid user, EntityUid target, BaseContainer container)
|
||||
{
|
||||
if (StorageComponent.ContainerId != container.ID)
|
||||
return false;
|
||||
|
||||
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
|
||||
return _ui.IsUiOpen(target, StorageComponent.StorageUiKey.Key, user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't
|
||||
@@ -1151,19 +1249,15 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
|
||||
}
|
||||
|
||||
|
||||
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
|
||||
{
|
||||
if (ev.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
return;
|
||||
|
||||
if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange))
|
||||
{
|
||||
ev.Result = BoundUserInterfaceRangeResult.Pass;
|
||||
}
|
||||
else
|
||||
{
|
||||
ev.Result = BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange)
|
||||
? BoundUserInterfaceRangeResult.Pass
|
||||
: BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -210,11 +210,7 @@ public abstract partial class InventorySystem
|
||||
return false;
|
||||
|
||||
// Can the actor reach the item?
|
||||
if (_interactionSystem.InRangeUnobstructed(actor, itemUid) && _containerSystem.IsInSameOrParentContainer(actor, itemUid))
|
||||
return true;
|
||||
|
||||
// Is the item in an open storage UI, i.e., is the user quick-equipping from an open backpack?
|
||||
if (_interactionSystem.CanAccessViaStorage(actor, itemUid))
|
||||
if (_interactionSystem.InRangeAndAccessible(actor, itemUid))
|
||||
return true;
|
||||
|
||||
// Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but
|
||||
|
||||
@@ -75,7 +75,7 @@ public partial class InventorySystem : EntitySystem
|
||||
|
||||
if (TryGetSlotEntity(uid, ev.Slot, out var entityUid) && TryComp<StorageComponent>(entityUid, out var storageComponent))
|
||||
{
|
||||
_storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent);
|
||||
_storageSystem.OpenStorageUI(entityUid.Value, uid, storageComponent, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wires;
|
||||
@@ -46,11 +47,13 @@ public sealed class LockSystem : EntitySystem
|
||||
SubscribeLocalEvent<LockComponent, LockDoAfter>(OnDoAfterLock);
|
||||
SubscribeLocalEvent<LockComponent, UnlockDoAfter>(OnDoAfterUnlock);
|
||||
SubscribeLocalEvent<LockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened); //CrystallPunk Lock System Adapt
|
||||
SubscribeLocalEvent<LockComponent, StorageInteractAttemptEvent>(OnStorageInteractAttempt);
|
||||
|
||||
SubscribeLocalEvent<LockedWiresPanelComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
|
||||
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
|
||||
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args)
|
||||
{
|
||||
_appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked);
|
||||
@@ -324,6 +327,12 @@ public sealed class LockSystem : EntitySystem
|
||||
TryUnlock(uid, args.User, skipDoAfter: true);
|
||||
}
|
||||
|
||||
private void OnStorageInteractAttempt(Entity<LockComponent> ent, ref StorageInteractAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.Locked)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void OnLockToggleAttempt(Entity<LockedWiresPanelComponent> ent, ref LockToggleAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.MagicMirror;
|
||||
|
||||
@@ -21,10 +22,13 @@ public abstract class SharedMagicMirrorSystem : EntitySystem
|
||||
|
||||
private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args)
|
||||
{
|
||||
if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value))
|
||||
{
|
||||
if (args.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(component.Target != null && Exists(component.Target));
|
||||
|
||||
if (!_interaction.InRangeUnobstructed(uid, component.Target.Value))
|
||||
args.Result = BoundUserInterfaceRangeResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeforeUIOpen(Entity<MagicMirrorComponent> ent, ref BeforeActivatableUIOpenEvent args)
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Mobs.Components;
|
||||
@@ -24,13 +25,16 @@ public sealed partial class MobThresholdsComponent : Component
|
||||
/// Used for alternate health alerts (silicons, for example)
|
||||
/// </summary>
|
||||
[DataField("stateAlertDict")]
|
||||
public Dictionary<MobState, AlertType> StateAlertDict = new()
|
||||
public Dictionary<MobState, ProtoId<AlertPrototype>> StateAlertDict = new()
|
||||
{
|
||||
{MobState.Alive, AlertType.HumanHealth},
|
||||
{MobState.Critical, AlertType.HumanCrit},
|
||||
{MobState.Dead, AlertType.HumanDead},
|
||||
{MobState.Alive, "HumanHealth"},
|
||||
{MobState.Critical, "HumanCrit"},
|
||||
{MobState.Dead, "HumanDead"},
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> HealthAlertCategory = "Health";
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this entity should display damage overlays (robots don't feel pain, black out etc.)
|
||||
/// </summary>
|
||||
@@ -53,19 +57,19 @@ public sealed class MobThresholdsComponentState : ComponentState
|
||||
|
||||
public MobState CurrentThresholdState;
|
||||
|
||||
public Dictionary<MobState, AlertType> StateAlertDict = new()
|
||||
{
|
||||
{MobState.Alive, AlertType.HumanHealth},
|
||||
{MobState.Critical, AlertType.HumanCrit},
|
||||
{MobState.Dead, AlertType.HumanDead},
|
||||
};
|
||||
public Dictionary<MobState, ProtoId<AlertPrototype>> StateAlertDict;
|
||||
|
||||
public bool ShowOverlays;
|
||||
|
||||
public bool AllowRevives;
|
||||
|
||||
public MobThresholdsComponentState(Dictionary<FixedPoint2, MobState> unsortedThresholds, bool triggersAlerts, MobState currentThresholdState,
|
||||
Dictionary<MobState, AlertType> stateAlertDict, bool showOverlays, bool allowRevives)
|
||||
public MobThresholdsComponentState(Dictionary<FixedPoint2, MobState> unsortedThresholds,
|
||||
bool triggersAlerts,
|
||||
MobState currentThresholdState,
|
||||
Dictionary<MobState,
|
||||
ProtoId<AlertPrototype>> stateAlertDict,
|
||||
bool showOverlays,
|
||||
bool allowRevives)
|
||||
{
|
||||
UnsortedThresholds = unsortedThresholds;
|
||||
TriggersAlerts = triggersAlerts;
|
||||
|
||||
@@ -431,7 +431,7 @@ public sealed class MobThresholdSystem : EntitySystem
|
||||
private void MobThresholdShutdown(EntityUid target, MobThresholdsComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (component.TriggersAlerts)
|
||||
_alerts.ClearAlertCategory(target, AlertCategory.Health);
|
||||
_alerts.ClearAlertCategory(target, component.HealthAlertCategory);
|
||||
}
|
||||
|
||||
private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Movement.Pulling.Components;
|
||||
|
||||
@@ -36,4 +38,7 @@ public sealed partial class PullableComponent : Component
|
||||
[Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)]
|
||||
[AutoNetworkedField, DataField]
|
||||
public bool PrevFixedRotation;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> PulledAlert = "Pulled";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Movement.Pulling.Components;
|
||||
@@ -38,4 +40,7 @@ public sealed partial class PullerComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NeedsHands = true;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> PullingAlert = "Pulling";
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
if (TryComp<PullerComponent>(oldPuller, out var pullerComp))
|
||||
{
|
||||
var pullerUid = oldPuller.Value;
|
||||
_alertsSystem.ClearAlert(pullerUid, AlertType.Pulling);
|
||||
_alertsSystem.ClearAlert(pullerUid, pullerComp.PullingAlert);
|
||||
pullerComp.Pulling = null;
|
||||
Dirty(oldPuller.Value, pullerComp);
|
||||
|
||||
@@ -237,7 +237,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
}
|
||||
|
||||
|
||||
_alertsSystem.ClearAlert(pullableUid, AlertType.Pulled);
|
||||
_alertsSystem.ClearAlert(pullableUid, pullableComp.PulledAlert);
|
||||
}
|
||||
|
||||
public bool IsPulled(EntityUid uid, PullableComponent? component = null)
|
||||
@@ -362,14 +362,17 @@ public sealed class PullingSystem : EntitySystem
|
||||
return !startPull.Cancelled && !getPulled.Cancelled;
|
||||
}
|
||||
|
||||
public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable)
|
||||
public bool TogglePull(Entity<PullableComponent?> pullable, EntityUid pullerUid)
|
||||
{
|
||||
if (pullable.Puller == pullerUid)
|
||||
if (!Resolve(pullable, ref pullable.Comp, false))
|
||||
return false;
|
||||
|
||||
if (pullable.Comp.Puller == pullerUid)
|
||||
{
|
||||
return TryStopPull(pullableUid, pullable);
|
||||
return TryStopPull(pullable, pullable.Comp);
|
||||
}
|
||||
|
||||
return TryStartPull(pullerUid, pullableUid, pullableComp: pullable);
|
||||
return TryStartPull(pullerUid, pullable, pullableComp: pullable);
|
||||
}
|
||||
|
||||
public bool TogglePull(EntityUid pullerUid, PullerComponent puller)
|
||||
@@ -377,7 +380,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
if (!TryComp<PullableComponent>(puller.Pulling, out var pullable))
|
||||
return false;
|
||||
|
||||
return TogglePull(puller.Pulling.Value, pullerUid, pullable);
|
||||
return TogglePull((puller.Pulling.Value, pullable), pullerUid);
|
||||
}
|
||||
|
||||
public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid,
|
||||
@@ -460,8 +463,8 @@ public sealed class PullingSystem : EntitySystem
|
||||
|
||||
// Messaging
|
||||
var message = new PullStartedMessage(pullerUid, pullableUid);
|
||||
_alertsSystem.ShowAlert(pullerUid, AlertType.Pulling);
|
||||
_alertsSystem.ShowAlert(pullableUid, AlertType.Pulled);
|
||||
_alertsSystem.ShowAlert(pullerUid, pullerComp.PullingAlert);
|
||||
_alertsSystem.ShowAlert(pullableUid, pullableComp.PulledAlert);
|
||||
|
||||
RaiseLocalEvent(pullerUid, message);
|
||||
RaiseLocalEvent(pullableUid, message);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -53,4 +54,7 @@ public sealed partial class SpaceNinjaComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId SpiderChargeObjective = "SpiderChargeObjective";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> SuitPowerAlert = "SuitPower";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Alert;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
||||
@@ -65,15 +66,18 @@ public sealed partial class HungerComponent : Component
|
||||
/// <summary>
|
||||
/// A dictionary relating hunger thresholds to corresponding alerts.
|
||||
/// </summary>
|
||||
[DataField("hungerThresholdAlerts", customTypeSerializer: typeof(DictionarySerializer<HungerThreshold, AlertType>))]
|
||||
[DataField("hungerThresholdAlerts")]
|
||||
[AutoNetworkedField]
|
||||
public Dictionary<HungerThreshold, AlertType> HungerThresholdAlerts = new()
|
||||
public Dictionary<HungerThreshold, ProtoId<AlertPrototype>> HungerThresholdAlerts = new()
|
||||
{
|
||||
{ HungerThreshold.Peckish, AlertType.Peckish },
|
||||
{ HungerThreshold.Starving, AlertType.Starving },
|
||||
{ HungerThreshold.Dead, AlertType.Starving }
|
||||
{ HungerThreshold.Peckish, "Peckish" },
|
||||
{ HungerThreshold.Starving, "Starving" },
|
||||
{ HungerThreshold.Dead, "Starving" }
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> HungerAlertCategory = "Hunger";
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary relating HungerThreshold to how much they modify <see cref="BaseDecayRate"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Nutrition.Components;
|
||||
@@ -56,11 +57,14 @@ public sealed partial class ThirstComponent : Component
|
||||
{ThirstThreshold.Dead, 0.0f},
|
||||
};
|
||||
|
||||
public static readonly Dictionary<ThirstThreshold, AlertType> ThirstThresholdAlertTypes = new()
|
||||
[DataField]
|
||||
public ProtoId<AlertCategoryPrototype> ThirstyCategory = "Thirst";
|
||||
|
||||
public static readonly Dictionary<ThirstThreshold, ProtoId<AlertPrototype>> ThirstThresholdAlertTypes = new()
|
||||
{
|
||||
{ThirstThreshold.Thirsty, AlertType.Thirsty},
|
||||
{ThirstThreshold.Parched, AlertType.Parched},
|
||||
{ThirstThreshold.Dead, AlertType.Parched},
|
||||
{ThirstThreshold.Thirsty, "Thirsty"},
|
||||
{ThirstThreshold.Parched, "Parched"},
|
||||
{ThirstThreshold.Dead, "Parched"},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public sealed class HungerSystem : EntitySystem
|
||||
|
||||
private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutdown args)
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Hunger);
|
||||
_alerts.ClearAlertCategory(uid, component.HungerAlertCategory);
|
||||
}
|
||||
|
||||
private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args)
|
||||
@@ -142,7 +142,7 @@ public sealed class HungerSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Hunger);
|
||||
_alerts.ClearAlertCategory(uid, component.HungerAlertCategory);
|
||||
}
|
||||
|
||||
if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
|
||||
|
||||
@@ -165,7 +165,7 @@ public sealed class ThirstSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlertCategory(uid, AlertCategory.Thirst);
|
||||
_alerts.ClearAlertCategory(uid, component.ThirstyCategory);
|
||||
}
|
||||
|
||||
switch (component.CurrentThirstThreshold)
|
||||
|
||||
@@ -96,7 +96,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
args.State = new NavMapComponentState(chunks, component.Beacons);
|
||||
args.State = new NavMapState(chunks, component.Beacons);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,12 +109,7 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
args.State = new NavMapComponentState(chunks, component.Beacons)
|
||||
{
|
||||
// TODO NAVMAP cache a single AllChunks hashset in the component.
|
||||
// Or maybe just only send them if a chunk gets removed.
|
||||
AllChunks = new(component.Chunks.Keys),
|
||||
};
|
||||
args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -122,32 +117,35 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
#region: System messages
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapComponentState(
|
||||
protected sealed class NavMapState(
|
||||
Dictionary<Vector2i, int[]> chunks,
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons)
|
||||
: ComponentState, IComponentDeltaState
|
||||
: ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, int[]> Chunks = chunks;
|
||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||
}
|
||||
|
||||
// Required to infer deleted/missing chunks for delta states
|
||||
public HashSet<Vector2i>? AllChunks;
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapDeltaState(
|
||||
Dictionary<Vector2i, int[]> modifiedChunks,
|
||||
Dictionary<NetEntity, NavMapBeacon> beacons,
|
||||
HashSet<Vector2i> allChunks)
|
||||
: ComponentState, IComponentDeltaState<NavMapState>
|
||||
{
|
||||
public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
|
||||
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
|
||||
public HashSet<Vector2i> AllChunks = allChunks;
|
||||
|
||||
public bool FullState => AllChunks == null;
|
||||
|
||||
public void ApplyToFullState(IComponentState fullState)
|
||||
public void ApplyToFullState(NavMapState state)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
foreach (var key in state.Chunks.Keys)
|
||||
{
|
||||
if (!AllChunks!.Contains(key))
|
||||
state.Chunks.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (index, data) in Chunks)
|
||||
foreach (var (index, data) in ModifiedChunks)
|
||||
{
|
||||
if (!state.Chunks.TryGetValue(index, out var stateValue))
|
||||
state.Chunks[index] = stateValue = new int[data.Length];
|
||||
@@ -162,12 +160,8 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||
public NavMapState CreateNewFullState(NavMapState state)
|
||||
{
|
||||
DebugTools.Assert(!FullState);
|
||||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
|
||||
foreach (var (index, data) in state.Chunks)
|
||||
{
|
||||
@@ -176,13 +170,13 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
|
||||
var newData = chunks[index] = new int[ArraySize];
|
||||
|
||||
if (Chunks.TryGetValue(index, out var updatedData))
|
||||
if (ModifiedChunks.TryGetValue(index, out var updatedData))
|
||||
Array.Copy(newData, updatedData, ArraySize);
|
||||
else
|
||||
Array.Copy(newData, data, ArraySize);
|
||||
}
|
||||
|
||||
return new NavMapComponentState(chunks, new(Beacons));
|
||||
return new NavMapState(chunks, new(Beacons));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,4 +8,6 @@ namespace Content.Shared.Prying.Components;
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class PryUnpoweredComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float PryModifier = 0.1f;
|
||||
}
|
||||
|
||||
@@ -93,17 +93,17 @@ public sealed class PryingSystem : EntitySystem
|
||||
id = null;
|
||||
|
||||
// We don't care about displaying a message if no tool was used.
|
||||
if (!CanPry(target, user, out _))
|
||||
if (!TryComp<PryUnpoweredComponent>(target, out var unpoweredComp) || !CanPry(target, user, out _, unpoweredComp: unpoweredComp))
|
||||
// If we have reached this point we want the event that caused this
|
||||
// to be marked as handled.
|
||||
return true;
|
||||
|
||||
// hand-prying is much slower
|
||||
var modifier = CompOrNull<PryingComponent>(user)?.SpeedModifier ?? 0.1f;
|
||||
var modifier = CompOrNull<PryingComponent>(user)?.SpeedModifier ?? unpoweredComp.PryModifier;
|
||||
return StartPry(target, user, null, modifier, out id);
|
||||
}
|
||||
|
||||
private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null)
|
||||
private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null, PryUnpoweredComponent? unpoweredComp = null)
|
||||
{
|
||||
BeforePryEvent canev;
|
||||
|
||||
@@ -113,7 +113,7 @@ public sealed class PryingSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TryComp<PryUnpoweredComponent>(target, out _))
|
||||
if (!Resolve(target, ref unpoweredComp))
|
||||
{
|
||||
message = null;
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Whitelist;
|
||||
@@ -200,6 +201,9 @@ public sealed partial class RevenantComponent : Component
|
||||
public EntityWhitelist? MalfunctionBlacklist;
|
||||
#endregion
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> EssenceAlert = "Essence";
|
||||
|
||||
#region Visualizer
|
||||
[DataField("state")]
|
||||
public string State = "idle";
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Shuttles.Components
|
||||
@@ -32,6 +34,9 @@ namespace Content.Shared.Shuttles.Components
|
||||
[ViewVariables]
|
||||
public ShuttleButtons HeldButtons = ShuttleButtons.None;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> PilotingAlert = "PilotingShuttle";
|
||||
|
||||
public override bool SendOnlyToOwner => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Whitelist;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
@@ -76,6 +78,12 @@ public sealed partial class BorgChassisComponent : Component
|
||||
[DataField("noMindState")]
|
||||
public string NoMindState = string.Empty;
|
||||
#endregion
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> BatteryAlert = "BorgBattery";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> NoBatteryAlert = "BorgBatteryNone";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Shared.StatusEffect
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
[DataField("alert")]
|
||||
public AlertType? Alert { get; private set; }
|
||||
public ProtoId<AlertPrototype>? Alert { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether a status effect should be able to apply to any entity,
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Content.Shared.StatusEffect
|
||||
/// This is mostly for stuns, since Stun and Knockdown share an alert key. Other times this pretty much
|
||||
/// will not be useful.
|
||||
/// </remarks>
|
||||
private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, AlertType alert, StatusEffectsComponent status)
|
||||
private (TimeSpan, TimeSpan)? GetAlertCooldown(EntityUid uid, ProtoId<AlertPrototype> alert, StatusEffectsComponent status)
|
||||
{
|
||||
(TimeSpan, TimeSpan)? maxCooldown = null;
|
||||
foreach (var kvp in status.ActiveEffects)
|
||||
|
||||
@@ -9,12 +9,12 @@ using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Lock;
|
||||
@@ -42,7 +42,6 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] protected readonly IRobustRandom Random = default!;
|
||||
[Dependency] private readonly ISharedAdminManager _admin = default!;
|
||||
[Dependency] protected readonly ActionBlockerSystem ActionBlocker = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
@@ -251,17 +250,8 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
private void AddUiVerb(EntityUid uid, StorageComponent component, GetVerbsEvent<ActivationVerb> args)
|
||||
{
|
||||
var silent = false;
|
||||
if (!args.CanAccess || !args.CanInteract || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
|
||||
{
|
||||
// we allow admins to open the storage anyways
|
||||
if (!_admin.HasAdminFlag(args.User, AdminFlags.Admin))
|
||||
return;
|
||||
|
||||
silent = true;
|
||||
}
|
||||
|
||||
silent |= HasComp<GhostComponent>(args.User);
|
||||
if (!CanInteract(args.User, (uid, component), args.CanAccess && args.CanInteract))
|
||||
return;
|
||||
|
||||
// Does this player currently have the storage UI open?
|
||||
var uiOpen = _ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key, args.User);
|
||||
@@ -276,7 +266,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenStorageUI(uid, args.User, component, silent);
|
||||
OpenStorageUI(uid, args.User, component);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -300,13 +290,16 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
/// Opens the storage UI for an entity
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to open the UI for</param>
|
||||
public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = false)
|
||||
public void OpenStorageUI(EntityUid uid, EntityUid entity, StorageComponent? storageComp = null, bool silent = true)
|
||||
{
|
||||
if (!Resolve(uid, ref storageComp, false))
|
||||
return;
|
||||
|
||||
// prevent spamming bag open / honkerton honk sound
|
||||
silent |= TryComp<UseDelayComponent>(uid, out var useDelay) && UseDelay.IsDelayed((uid, useDelay));
|
||||
if (!CanInteract(entity, (uid, storageComp), silent: silent))
|
||||
return;
|
||||
|
||||
if (!silent)
|
||||
{
|
||||
if (!_ui.IsUiOpen(uid, StorageComponent.StorageUiKey.Key))
|
||||
@@ -328,7 +321,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
var entities = component.Container.ContainedEntities;
|
||||
|
||||
if (entities.Count == 0 || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
|
||||
if (entities.Count == 0 || !CanInteract(args.User, (uid, component)))
|
||||
return;
|
||||
|
||||
// if the target is storage, add a verb to transfer storage.
|
||||
@@ -339,7 +332,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
{
|
||||
Text = Loc.GetString("storage-component-transfer-verb"),
|
||||
IconEntity = GetNetEntity(args.Using),
|
||||
Act = () => TransferEntities(uid, args.Target, args.User, component, lockComponent, targetStorage, targetLock)
|
||||
Act = () => TransferEntities(uid, args.Target, args.User, component, null, targetStorage, targetLock)
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
@@ -352,7 +345,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
/// <returns>true if inserted, false otherwise</returns>
|
||||
private void OnInteractUsing(EntityUid uid, StorageComponent storageComp, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled || !storageComp.ClickInsert || TryComp(uid, out LockComponent? lockComponent) && lockComponent.Locked)
|
||||
if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert, false))
|
||||
return;
|
||||
|
||||
if (HasComp<PlaceableSurfaceComponent>(uid))
|
||||
@@ -373,7 +366,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void OnActivate(EntityUid uid, StorageComponent storageComp, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled || TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
|
||||
if (args.Handled || !CanInteract(args.User, (uid, storageComp), storageComp.ClickInsert))
|
||||
return;
|
||||
|
||||
// Toggle
|
||||
@@ -383,7 +376,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenStorageUI(uid, args.User, storageComp);
|
||||
OpenStorageUI(uid, args.User, storageComp, false);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
@@ -397,7 +390,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
OpenStorageUI(uid, args.Performer, storageComp);
|
||||
OpenStorageUI(uid, args.Performer, storageComp, false);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -1092,7 +1085,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
/// <returns>true if inserted, false otherwise</returns>
|
||||
public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert)
|
||||
{
|
||||
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid))
|
||||
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner))
|
||||
return false;
|
||||
|
||||
if (!Insert(uid, toInsert, out _, user: player, uid.Comp))
|
||||
@@ -1408,7 +1401,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a storage's UI is open by anyone when locked, and closes it unless they're an admin.
|
||||
/// Checks if a storage's UI is open by anyone when locked, and closes it.
|
||||
/// </summary>
|
||||
private void OnLockToggled(EntityUid uid, StorageComponent component, ref LockToggledEvent args)
|
||||
{
|
||||
@@ -1418,11 +1411,8 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
// Gets everyone looking at the UI
|
||||
foreach (var actor in _ui.GetActors(uid, StorageComponent.StorageUiKey.Key).ToList())
|
||||
{
|
||||
if (_admin.HasAdminFlag(actor, AdminFlags.Admin))
|
||||
continue;
|
||||
|
||||
// And closes it unless they're an admin
|
||||
_ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
|
||||
if (!CanInteract(actor, (uid, component)))
|
||||
_ui.CloseUi(uid, StorageComponent.StorageUiKey.Key, actor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1462,7 +1452,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
if (!_ui.IsUiOpen(storageEnt.Value, StorageComponent.StorageUiKey.Key, playerEnt))
|
||||
{
|
||||
OpenStorageUI(storageEnt.Value, playerEnt);
|
||||
OpenStorageUI(storageEnt.Value, playerEnt, silent: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1477,6 +1467,20 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool CanInteract(EntityUid user, Entity<StorageComponent> storage, bool canInteract = true, bool silent = true)
|
||||
{
|
||||
if (HasComp<BypassInteractionChecksComponent>(user))
|
||||
return true;
|
||||
|
||||
if (!canInteract)
|
||||
return false;
|
||||
|
||||
var ev = new StorageInteractAttemptEvent(silent);
|
||||
RaiseLocalEvent(storage, ref ev);
|
||||
|
||||
return !ev.Cancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays a clientside pickup animation for the specified uid.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,7 +5,6 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -236,6 +235,9 @@ namespace Content.Shared.Storage
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct StorageInteractAttemptEvent(bool Silent, bool Cancelled = false);
|
||||
|
||||
[NetSerializable]
|
||||
[Serializable]
|
||||
public enum StorageVisuals : byte
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Content.Shared.UserInterface
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public bool AllowSpectator = true;
|
||||
public bool BlockSpectators;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the item must be in the user's currently selected/active hand.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user