Revert "Upstream (#148)"

This reverts commit 9f00d4b9aa.
This commit is contained in:
Jabak
2024-10-22 22:47:57 +03:00
parent 9f00d4b9aa
commit dbc492f3f3
96 changed files with 1080 additions and 1047 deletions

View File

@@ -1,5 +1,4 @@
using Content.Client.Atmos.Overlays; using Content.Client.Atmos.Overlays;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components; using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems; using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -37,38 +36,28 @@ namespace Content.Client.Atmos.EntitySystems
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args) private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
{ {
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks; if (args.Current is not GasTileOverlayState state)
return;
switch (args.Current) // is this a delta or full state?
if (!state.FullState)
{ {
// is this a delta or full state? foreach (var index in comp.Chunks.Keys)
case GasTileOverlayDeltaState delta:
{ {
modifiedChunks = delta.ModifiedChunks; if (!state.AllChunks!.Contains(index))
foreach (var index in comp.Chunks.Keys) comp.Chunks.Remove(index);
{
if (!delta.AllChunks.Contains(index))
comp.Chunks.Remove(index);
}
break;
} }
case GasTileOverlayState state: }
else
{
foreach (var index in comp.Chunks.Keys)
{ {
modifiedChunks = state.Chunks; if (!state.Chunks.ContainsKey(index))
foreach (var index in comp.Chunks.Keys) comp.Chunks.Remove(index);
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
break;
} }
default:
return;
} }
foreach (var (index, data) in modifiedChunks) foreach (var (index, data) in state.Chunks)
{ {
comp.Chunks[index] = data; comp.Chunks[index] = data;
} }

View File

@@ -56,43 +56,34 @@ namespace Content.Client.Decals
private void OnHandleState(EntityUid gridUid, DecalGridComponent gridComp, ref ComponentHandleState args) 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? // is this a delta or full state?
_removedChunks.Clear(); _removedChunks.Clear();
Dictionary<Vector2i, DecalChunk> modifiedChunks;
switch (args.Current) if (!state.FullState)
{ {
case DecalGridDeltaState delta: foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
{ {
modifiedChunks = delta.ModifiedChunks; if (!state.AllChunks!.Contains(key))
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) _removedChunks.Add(key);
{
if (!delta.AllChunks.Contains(key))
_removedChunks.Add(key);
}
break;
} }
case DecalGridState state: }
else
{
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys)
{ {
modifiedChunks = state.Chunks; if (!state.Chunks.ContainsKey(key))
foreach (var key in gridComp.ChunkCollection.ChunkCollection.Keys) _removedChunks.Add(key);
{
if (!state.Chunks.ContainsKey(key))
_removedChunks.Add(key);
}
break;
} }
default:
return;
} }
if (_removedChunks.Count > 0) if (_removedChunks.Count > 0)
RemoveChunks(gridUid, gridComp, _removedChunks); RemoveChunks(gridUid, gridComp, _removedChunks);
if (modifiedChunks.Count > 0) if (state.Chunks.Count > 0)
UpdateChunks(gridUid, gridComp, modifiedChunks); UpdateChunks(gridUid, gridComp, state.Chunks);
} }
private void OnChunkUpdate(DecalChunkUpdateEvent ev) private void OnChunkUpdate(DecalChunkUpdateEvent ev)

View File

@@ -239,10 +239,8 @@ namespace Content.Client.Examine
if (knowTarget) if (knowTarget)
{ {
// TODO: FormattedMessage.RemoveMarkupPermissive var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player));
// var itemName = FormattedMessage.RemoveMarkupPermissive(Identity.Name(target, EntityManager, player)); var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]");
var itemName = FormattedMessage.FromMarkupPermissive(Identity.Name(target, EntityManager, player)).ToString();
var labelMessage = FormattedMessage.FromMarkupPermissive($"[bold]{itemName}[/bold]");
var label = new RichTextLabel(); var label = new RichTextLabel();
label.SetMessage(labelMessage); label.SetMessage(labelMessage);
hBox.AddChild(label); hBox.AddChild(label);

View File

@@ -37,8 +37,14 @@ namespace Content.Client.Instruments.UI
protected override void ReceiveMessage(BoundUserInterfaceMessage message) protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{ {
if (message is InstrumentBandResponseBuiMessage bandRx) switch (message)
_bandMenu?.Populate(bandRx.Nearby, EntMan); {
case InstrumentBandResponseBuiMessage bandRx:
_bandMenu?.Populate(bandRx.Nearby, EntMan);
break;
default:
break;
}
} }
protected override void Open() protected override void Open()

View File

@@ -4,6 +4,24 @@ using Robust.Shared.Containers;
namespace Content.Client.Interactable namespace Content.Client.Interactable
{ {
// TODO Remove Shared prefix public sealed class InteractionSystem : SharedInteractionSystem
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;
}
}
} }

View File

@@ -73,6 +73,11 @@ public sealed class DragDropHelper<T>
_cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true); _cfg.OnValueChanged(CCVars.DragDropDeadZone, SetDeadZone, true);
} }
~DragDropHelper()
{
_cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone);
}
/// <summary> /// <summary>
/// Tell the helper that the mouse button was pressed down on /// Tell the helper that the mouse button was pressed down on
/// a target, thus a drag has the possibility to begin for this target. /// a target, thus a drag has the possibility to begin for this target.

View File

@@ -1,11 +1,13 @@
<DefaultWindow xmlns="https://spacestation14.io" <DefaultWindow xmlns="https://spacestation14.io"
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs" xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
Title="{Loc 'ui-options-title'}" Title="{Loc 'ui-options-title'}"
MinSize="800 450"> MinSize="980 580">
<TabContainer Name="Tabs" Access="Public"> <TabContainer Name="Tabs" Access="Public">
<tabs:MiscTab Name="MiscTab" /> <tabs:MiscTab Name="MiscTab" />
<tabs:GraphicsTab Name="GraphicsTab" /> <tabs:GraphicsTab Name="GraphicsTab" />
<tabs:KeyRebindTab Name="KeyRebindTab" /> <tabs:KeyRebindTab Name="KeyRebindTab" />
<tabs:AudioTab Name="AudioTab" /> <tabs:AudioTab Name="AudioTab" />
<tabs:NetworkTab Name="NetworkTab" />
<tabs:AdminSettingsTab Name="AdminSettingsTab"/>
</TabContainer> </TabContainer>
</DefaultWindow> </DefaultWindow>

View File

@@ -1,15 +1,17 @@
using Content.Client.Administration.Managers;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Content.Client.Options.UI.Tabs; using Content.Client.Options.UI.Tabs;
using Robust.Shared.Timing;
namespace Content.Client.Options.UI namespace Content.Client.Options.UI
{ {
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class OptionsMenu : DefaultWindow public sealed partial class OptionsMenu : DefaultWindow
{ {
[Dependency] private readonly IClientAdminManager _clientAdminManager = default!;
public OptionsMenu() public OptionsMenu()
{ {
RobustXamlLoader.Load(this); RobustXamlLoader.Load(this);
@@ -19,6 +21,8 @@ namespace Content.Client.Options.UI
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics")); Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls")); Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio")); Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network"));
Tabs.SetTabTitle(5, "Админ");
UpdateTabs(); UpdateTabs();
} }
@@ -27,5 +31,11 @@ namespace Content.Client.Options.UI
{ {
GraphicsTab.UpdateProperties(); GraphicsTab.UpdateProperties();
} }
protected override void FrameUpdate(FrameEventArgs args)
{
Tabs.SetTabVisible(5, _clientAdminManager.IsActive());
base.FrameUpdate(args);
}
} }
} }

View File

@@ -0,0 +1,103 @@
<Control xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Content.Client.Options.UI.Tabs.NetworkTab">
<BoxContainer Orientation="Vertical" >
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<CheckBox Name="NetPredictCheckbox" Text="{Loc 'ui-options-net-predict'}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
<Control MinSize="8 0" />
<Slider Name="NetInterpRatioSlider"
ToolTip="{Loc 'ui-options-net-interp-ratio-tooltip'}"
MaxValue="8"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetInterpRatioLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-predict-tick-bias'}" />
<Control MinSize="8 0" />
<Slider Name="NetPredictTickBiasSlider"
ToolTip="{Loc 'ui-options-net-predict-tick-bias-tooltip'}"
MaxValue="6"
MinValue="0"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetPredictTickBiasLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-pvs-spawn'}" />
<Control MinSize="8 0" />
<Slider Name="NetPvsSpawnSlider"
ToolTip="{Loc 'ui-options-net-pvs-spawn-tooltip'}"
MaxValue="150"
MinValue="20"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetPvsSpawnLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 0">
<Label Text="{Loc 'ui-options-net-pvs-entry'}" />
<Control MinSize="8 0" />
<Slider Name="NetPvsEntrySlider"
ToolTip="{Loc 'ui-options-net-pvs-entry-tooltip'}"
MaxValue="500"
MinValue="20"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetPvsEntryLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4 10 4 10">
<Label Text="{Loc 'ui-options-net-pvs-leave'}" />
<Control MinSize="8 0" />
<Slider Name="NetPvsLeaveSlider"
ToolTip="{Loc 'ui-options-net-pvs-leave-tooltip'}"
MaxValue="300"
MinValue="20"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="NetPvsLeaveLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
</BoxContainer>
<controls:StripeBack HasBottomEdge="False" HasMargins="False">
<BoxContainer Orientation="Horizontal"
Align="End"
HorizontalExpand="True"
Margin="8 8"
VerticalExpand="True">
<Button Name="ResetButton"
Text="{Loc 'ui-options-reset-all'}"
StyleClasses="Caution"
HorizontalExpand="True"
HorizontalAlignment="Right" />
<Button Name="DefaultButton"
Text="{Loc 'ui-options-default'}"
TextAlign="Center"
HorizontalAlignment="Right" />
<Control MinSize="2 0" />
<Button Name="ApplyButton"
Text="{Loc 'ui-options-apply'}"
TextAlign="Center"
HorizontalAlignment="Right" />
</BoxContainer>
</controls:StripeBack>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,125 @@
using System.Globalization;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Client.GameStates;
using Content.Client.Entry;
namespace Content.Client.Options.UI.Tabs
{
[GenerateTypedNameReferences]
public sealed partial class NetworkTab : Control
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IClientGameStateManager _stateMan = default!;
public NetworkTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
ApplyButton.OnPressed += OnApplyButtonPressed;
ResetButton.OnPressed += OnResetButtonPressed;
DefaultButton.OnPressed += OnDefaultButtonPressed;
NetPredictCheckbox.OnToggled += OnPredictToggled;
NetInterpRatioSlider.OnValueChanged += OnSliderChanged;
NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
NetPredictTickBiasSlider.OnValueChanged += OnSliderChanged;
NetPvsSpawnSlider.OnValueChanged += OnSliderChanged;
NetPvsEntrySlider.OnValueChanged += OnSliderChanged;
NetPvsLeaveSlider.OnValueChanged += OnSliderChanged;
Reset();
}
protected override void Dispose(bool disposing)
{
ApplyButton.OnPressed -= OnApplyButtonPressed;
ResetButton.OnPressed -= OnResetButtonPressed;
DefaultButton.OnPressed -= OnDefaultButtonPressed;
NetPredictCheckbox.OnToggled -= OnPredictToggled;
NetInterpRatioSlider.OnValueChanged -= OnSliderChanged;
NetPredictTickBiasSlider.OnValueChanged -= OnSliderChanged;
NetPvsSpawnSlider.OnValueChanged -= OnSliderChanged;
NetPvsEntrySlider.OnValueChanged -= OnSliderChanged;
NetPvsLeaveSlider.OnValueChanged -= OnSliderChanged;
base.Dispose(disposing);
}
private void OnPredictToggled(BaseButton.ButtonToggledEventArgs obj)
{
UpdateChanges();
}
private void OnSliderChanged(Robust.Client.UserInterface.Controls.Range range)
{
UpdateChanges();
}
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
{
_cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
_cfg.SetCVar(CVars.NetPredictTickBias, (int) NetPredictTickBiasSlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityBudget, (int) NetPvsSpawnSlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityEnterBudget, (int) NetPvsEntrySlider.Value);
_cfg.SetCVar(CVars.NetPVSEntityExitBudget, (int) NetPvsLeaveSlider.Value);
_cfg.SetCVar(CVars.NetPredict, NetPredictCheckbox.Pressed);
_cfg.SaveToFile();
UpdateChanges();
}
private void OnResetButtonPressed(BaseButton.ButtonEventArgs args)
{
Reset();
}
private void OnDefaultButtonPressed(BaseButton.ButtonEventArgs obj)
{
NetPredictTickBiasSlider.Value = CVars.NetPredictTickBias.DefaultValue;
NetPvsSpawnSlider.Value = CVars.NetPVSEntityBudget.DefaultValue;
NetPvsEntrySlider.Value = CVars.NetPVSEntityEnterBudget.DefaultValue;
NetPvsLeaveSlider.Value = CVars.NetPVSEntityExitBudget.DefaultValue;
NetInterpRatioSlider.Value = CVars.NetBufferSize.DefaultValue + _stateMan.MinBufferSize;
UpdateChanges();
}
private void Reset()
{
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
NetPredictTickBiasSlider.Value = _cfg.GetCVar(CVars.NetPredictTickBias);
NetPvsSpawnSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityBudget);
NetPvsEntrySlider.Value = _cfg.GetCVar(CVars.NetPVSEntityEnterBudget);
NetPvsLeaveSlider.Value = _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
NetPredictCheckbox.Pressed = _cfg.GetCVar(CVars.NetPredict);
UpdateChanges();
}
private void UpdateChanges()
{
var isEverythingSame =
NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize &&
NetPredictTickBiasSlider.Value == _cfg.GetCVar(CVars.NetPredictTickBias) &&
NetPredictCheckbox.Pressed == _cfg.GetCVar(CVars.NetPredict) &&
NetPvsSpawnSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityBudget) &&
NetPvsEntrySlider.Value == _cfg.GetCVar(CVars.NetPVSEntityEnterBudget) &&
NetPvsLeaveSlider.Value == _cfg.GetCVar(CVars.NetPVSEntityExitBudget);
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString(CultureInfo.InvariantCulture);
NetPredictTickBiasLabel.Text = NetPredictTickBiasSlider.Value.ToString(CultureInfo.InvariantCulture);
NetPvsSpawnLabel.Text = NetPvsSpawnSlider.Value.ToString(CultureInfo.InvariantCulture);
NetPvsEntryLabel.Text = NetPvsEntrySlider.Value.ToString(CultureInfo.InvariantCulture);
NetPvsLeaveLabel.Text = NetPvsLeaveSlider.Value.ToString(CultureInfo.InvariantCulture);
// TODO disable / grey-out the predict and interp sliders if prediction is disabled.
// Currently no option to do this, but should be added to the slider control in general
}
}
}

View File

@@ -1,11 +1,8 @@
using System.Numerics;
using Content.Client.UserInterface.Systems;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Shared.Enums; using Robust.Shared.Enums;
@@ -88,10 +85,6 @@ public sealed class EntityHealthBarOverlay : Overlay
continue; continue;
} }
// we are all progressing towards death every day
if (CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent) is not { } deathProgress)
continue;
var worldPosition = _transform.GetWorldPosition(xform); var worldPosition = _transform.GetWorldPosition(xform);
var worldMatrix = Matrix3.CreateTranslation(worldPosition); var worldMatrix = Matrix3.CreateTranslation(worldPosition);
@@ -104,6 +97,10 @@ public sealed class EntityHealthBarOverlay : Overlay
var widthOfMob = bounds.Width * EyeManager.PixelsPerMeter; var widthOfMob = bounds.Width * EyeManager.PixelsPerMeter;
var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter); var position = new Vector2(-widthOfMob / EyeManager.PixelsPerMeter / 2, yOffset / EyeManager.PixelsPerMeter);
// we are all progressing towards death every day
(float ratio, bool inCrit) deathProgress = CalcProgress(uid, mobStateComponent, damageableComponent, mobThresholdsComponent);
var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit); var color = GetProgressColor(deathProgress.ratio, deathProgress.inCrit);
// Hardcoded width of the progress bar because it doesn't match the texture. // Hardcoded width of the progress bar because it doesn't match the texture.
@@ -131,13 +128,10 @@ public sealed class EntityHealthBarOverlay : Overlay
/// <summary> /// <summary>
/// Returns a ratio between 0 and 1, and whether the entity is in crit. /// Returns a ratio between 0 and 1, and whether the entity is in crit.
/// </summary> /// </summary>
private (float ratio, bool inCrit)? CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds) private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
{ {
if (_mobStateSystem.IsAlive(uid, component)) if (_mobStateSystem.IsAlive(uid, component))
{ {
if (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold)
return null;
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) && if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) &&
!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds)) !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds))
return (1, false); return (1, false);

View File

@@ -14,40 +14,27 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args) private void OnHandleState(EntityUid uid, NavMapComponent component, ref ComponentHandleState args)
{ {
Dictionary<Vector2i, int[]> modifiedChunks; if (args.Current is not NavMapComponentState state)
Dictionary<NetEntity, NavMapBeacon> beacons; return;
switch (args.Current) if (!state.FullState)
{ {
case NavMapDeltaState delta: foreach (var index in component.Chunks.Keys)
{ {
modifiedChunks = delta.ModifiedChunks; if (!state.AllChunks!.Contains(index))
beacons = delta.Beacons; component.Chunks.Remove(index);
foreach (var index in component.Chunks.Keys)
{
if (!delta.AllChunks!.Contains(index))
component.Chunks.Remove(index);
}
break;
} }
case NavMapState state: }
else
{
foreach (var index in component.Chunks.Keys)
{ {
modifiedChunks = state.Chunks; if (!state.Chunks.ContainsKey(index))
beacons = state.Beacons; component.Chunks.Remove(index);
foreach (var index in component.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
component.Chunks.Remove(index);
}
break;
} }
default:
return;
} }
foreach (var (origin, chunk) in modifiedChunks) foreach (var (origin, chunk) in state.Chunks)
{ {
var newChunk = new NavMapChunk(origin); var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length); Array.Copy(chunk, newChunk.TileData, chunk.Length);
@@ -55,7 +42,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
} }
component.Beacons.Clear(); component.Beacons.Clear();
foreach (var (nuid, beacon) in beacons) foreach (var (nuid, beacon) in state.Beacons)
{ {
component.Beacons[nuid] = beacon; component.Beacons[nuid] = beacon;
} }

View File

@@ -119,4 +119,9 @@ public class ActionButtonContainer : GridContainer
yield return button; yield return button;
} }
} }
~ActionButtonContainer()
{
UserInterfaceManager.GetUIController<ActionUIController>().RemoveActionContainer();
}
} }

View File

@@ -22,4 +22,9 @@ public sealed class ItemSlotButtonContainer : ItemSlotUIContainer<SlotControl>
{ {
_inventoryController = UserInterfaceManager.GetUIController<InventoryUIController>(); _inventoryController = UserInterfaceManager.GetUIController<InventoryUIController>();
} }
~ItemSlotButtonContainer()
{
_inventoryController.RemoveSlotGroup(SlotGroup);
}
} }

View File

@@ -9,7 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
namespace Content.Client.UserInterface.Systems.Storage.Controls; namespace Content.Client.UserInterface.Systems.Storage.Controls;
public sealed class ItemGridPiece : Control, IEntityControl public sealed class ItemGridPiece : Control
{ {
private readonly IEntityManager _entityManager; private readonly IEntityManager _entityManager;
private readonly StorageUIController _storageController; private readonly StorageUIController _storageController;
@@ -287,8 +287,6 @@ public sealed class ItemGridPiece : Control, IEntityControl
var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1); var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1);
return actualSize * new Vector2i(8, 8); return actualSize * new Vector2i(8, 8);
} }
public EntityUid? UiEntity => Entity;
} }
public enum ItemGridPieceMarks public enum ItemGridPieceMarks

View File

@@ -137,6 +137,7 @@ public sealed partial class TestPair
/// </summary> /// </summary>
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value) public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
{ {
/* Впадлу фиксить тесты
var prefMan = Server.ResolveDependency<IServerPreferencesManager>(); var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
var prefs = prefMan.GetPreferences(Client.User!.Value); var prefs = prefMan.GetPreferences(Client.User!.Value);
@@ -155,5 +156,6 @@ public sealed partial class TestPair
var newPrefs = prefMan.GetPreferences(Client.User.Value); var newPrefs = prefMan.GetPreferences(Client.User.Value);
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter; var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value)); Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
*/
} }
} }

View File

@@ -350,12 +350,8 @@ namespace Content.IntegrationTests.Tests
"DebrisFeaturePlacerController", // Above. "DebrisFeaturePlacerController", // Above.
"LoadedChunk", // Worldgen chunk loading malding. "LoadedChunk", // Worldgen chunk loading malding.
"BiomeSelection", // Whaddya know, requires config. "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(); await using var pair = await PoolManager.GetServerClient();
var server = pair.Server; var server = pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>(); var entityManager = server.ResolveDependency<IEntityManager>();

View File

@@ -44,7 +44,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
if (args.Actor is not { Valid: true } player) if (args.Actor is not { Valid: true } player)
return; return;
TryWriteToTargetId(uid, args.FullName, args.JobTitle, args.AccessList, args.JobPrototype, args.SelectedIcon, player, component); TryWriteToTargetId(uid, args.FullName, args.JobTitle, args.AccessList, args.JobPrototype, args.SelectedIcon,
player, component);
UpdateUserInterface(uid, component, args); UpdateUserInterface(uid, component, args);
} }
@@ -152,9 +153,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
_idCard.TryChangeJobDepartment(targetId, job); _idCard.TryChangeJobDepartment(targetId, job);
} }
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job, newJobIcon);
if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x))) if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
{ {
_sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag."); _sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag.");

View File

@@ -1,35 +0,0 @@
using Content.Server.Antag.Components;
using Content.Server.Objectives;
using Content.Shared.Mind;
using Content.Shared.Objectives.Systems;
namespace Content.Server.Antag;
/// <summary>
/// Adds fixed objectives to an antag made with <c>AntagObjectivesComponent</c>.
/// </summary>
public sealed class AntagObjectivesSystem : EntitySystem
{
[Dependency] private readonly SharedMindSystem _mind = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AntagObjectivesComponent, AfterAntagEntitySelectedEvent>(OnAntagSelected);
}
private void OnAntagSelected(Entity<AntagObjectivesComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
if (!_mind.TryGetMind(args.Session, out var mindId, out var mind))
{
Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!");
return;
}
foreach (var id in ent.Comp.Objectives)
{
_mind.TryAddObjective(mindId, mind, id);
}
}
}

View File

@@ -1,52 +0,0 @@
using Content.Server.Antag.Components;
using Content.Server.Objectives;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Robust.Shared.Random;
namespace Content.Server.Antag;
/// <summary>
/// Adds fixed objectives to an antag made with <c>AntagRandomObjectivesComponent</c>.
/// </summary>
public sealed class AntagRandomObjectivesSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly ObjectivesSystem _objectives = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AntagRandomObjectivesComponent, AfterAntagEntitySelectedEvent>(OnAntagSelected);
}
private void OnAntagSelected(Entity<AntagRandomObjectivesComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
if (!_mind.TryGetMind(args.Session, out var mindId, out var mind))
{
Log.Error($"Antag {ToPrettyString(args.EntityUid):player} was selected by {ToPrettyString(ent):rule} but had no mind attached!");
return;
}
var difficulty = 0f;
foreach (var set in ent.Comp.Sets)
{
if (!_random.Prob(set.Prob))
continue;
for (var pick = 0; pick < set.MaxPicks && ent.Comp.MaxDifficulty > difficulty; pick++)
{
if (_objectives.GetRandomObjective(mindId, mind, set.Groups) is not {} objective)
continue;
_mind.AddObjective(mindId, mind, objective);
var adding = Comp<ObjectiveComponent>(objective).Difficulty;
difficulty += adding;
Log.Debug($"Added objective {ToPrettyString(objective):objective} to {ToPrettyString(args.EntityUid):player} with {adding} difficulty");
}
}
}
}

View File

@@ -147,7 +147,7 @@ public sealed partial class AntagSelectionSystem
} }
/// <remarks> /// <remarks>
/// Helper to get just the mind entities and not names. /// Helper specifically for <see cref="ObjectivesTextGetInfoEvent"/>
/// </remarks> /// </remarks>
public List<EntityUid> GetAntagMindEntityUids(Entity<AntagSelectionComponent?> ent) public List<EntityUid> GetAntagMindEntityUids(Entity<AntagSelectionComponent?> ent)
{ {

View File

@@ -7,7 +7,6 @@ using Content.Server.GameTicking.Rules;
using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Preferences.Managers; using Content.Server.Preferences.Managers;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Roles.Jobs; using Content.Server.Roles.Jobs;
@@ -26,11 +25,10 @@ using Robust.Shared.Enums;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Server._Miracle.GulagSystem; using Content.Server._Miracle.GulagSystem;
using Content.Server._White.Sponsors; using Content.Server._White.Sponsors;
using Content.Server.Inventory; using Content.Server.Inventory;
using Content.Shared.GameTicking; using FastAccessors;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Antag; namespace Content.Server.Antag;
@@ -61,8 +59,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole); SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning); SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned); SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned);
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete); SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
@@ -460,15 +456,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
return true; return true;
} }
private void OnObjectivesTextGetInfo(Entity<AntagSelectionComponent> ent, ref ObjectivesTextGetInfoEvent args)
{
if (ent.Comp.AgentName is not {} name)
return;
args.Minds = ent.Comp.SelectedMinds;
args.AgentName = Loc.GetString(name);
}
public float GetPremiumPoolChance(ICommonSession session) public float GetPremiumPoolChance(ICommonSession session)
{ {
if (!_sponsors.TryGetInfo(session.UserId, out var info)) if (!_sponsors.TryGetInfo(session.UserId, out var info))

View File

@@ -1,18 +0,0 @@
using Content.Server.Antag;
using Content.Shared.Objectives.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Antag.Components;
/// <summary>
/// Gives antags selected by this rule a fixed list of objectives.
/// </summary>
[RegisterComponent, Access(typeof(AntagObjectivesSystem))]
public sealed partial class AntagObjectivesComponent : Component
{
/// <summary>
/// List of static objectives to give.
/// </summary>
[DataField(required: true)]
public List<EntProtoId<ObjectiveComponent>> Objectives = new();
}

View File

@@ -1,52 +0,0 @@
using Content.Server.Antag;
using Content.Shared.Random;
using Robust.Shared.Prototypes;
namespace Content.Server.Antag.Components;
/// <summary>
/// Gives antags selected by this rule a random list of objectives.
/// </summary>
[RegisterComponent, Access(typeof(AntagRandomObjectivesSystem))]
public sealed partial class AntagRandomObjectivesComponent : Component
{
/// <summary>
/// Each set of objectives to add.
/// </summary>
[DataField(required: true)]
public List<AntagObjectiveSet> Sets = new();
/// <summary>
/// If the total difficulty of the currently given objectives exceeds, no more will be given.
/// </summary>
[DataField(required: true)]
public float MaxDifficulty;
}
/// <summary>
/// A set of objectives to try picking.
/// Difficulty is checked over all sets, but each set has its own probability and pick count.
/// </summary>
[DataRecord]
public record struct AntagObjectiveSet()
{
/// <summary>
/// The grouping used by the objective system to pick random objectives.
/// First a group is picked from these, then an objective from that group.
/// </summary>
[DataField(required: true)]
public ProtoId<WeightedRandomPrototype> Groups = string.Empty;
/// <summary>
/// Probability of this set being used.
/// </summary>
[DataField]
public float Prob = 1f;
/// <summary>
/// Number of times to try picking objectives from this set.
/// Even if there is enough difficulty remaining, no more will be given after this.
/// </summary>
[DataField]
public int MaxPicks = 20;
}

View File

@@ -41,13 +41,6 @@ public sealed partial class AntagSelectionComponent : Component
/// Is not serialized. /// Is not serialized.
/// </summary> /// </summary>
public HashSet<ICommonSession> SelectedSessions = new(); public HashSet<ICommonSession> SelectedSessions = new();
/// <summary>
/// Locale id for the name of the antag.
/// If this is set then the antag is listed in the round-end summary.
/// </summary>
[DataField]
public LocId? AgentName;
} }
[DataDefinition] [DataDefinition]

View File

@@ -20,8 +20,6 @@ namespace Content.Server.Cargo.Systems
{ {
public sealed partial class CargoSystem public sealed partial class CargoSystem
{ {
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
/// <summary> /// <summary>
/// How much time to wait (in seconds) before increasing bank accounts balance. /// How much time to wait (in seconds) before increasing bank accounts balance.
/// </summary> /// </summary>
@@ -496,9 +494,6 @@ namespace Content.Server.Cargo.Systems
// Create the item itself // Create the item itself
var item = Spawn(order.ProductId, spawn); var item = Spawn(order.ProductId, spawn);
// Ensure the item doesn't start anchored
_transformSystem.Unanchor(item, Transform(item));
// Create a sheet of paper to write the order details on // Create a sheet of paper to write the order details on
var printed = EntityManager.SpawnEntity(paperProto, spawn); var printed = EntityManager.SpawnEntity(paperProto, spawn);
if (TryComp<PaperComponent>(printed, out var paper)) if (TryComp<PaperComponent>(printed, out var paper))

View File

@@ -1,11 +1,9 @@
using System.Linq;
using Content.Server._Miracle.GulagSystem; using Content.Server._Miracle.GulagSystem;
using Content.Server.Antag; using Content.Server.Antag;
using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.Objectives; using Content.Server.Objectives;
using Content.Shared._White.Mood; using Content.Shared._White.Mood;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Components;
namespace Content.Server.Changeling; namespace Content.Server.Changeling;
@@ -32,7 +30,7 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
ChangelingRuleComponent comp, ChangelingRuleComponent comp,
ref ObjectivesTextGetInfoEvent args) ref ObjectivesTextGetInfoEvent args)
{ {
args.Minds = comp.ChangelingMinds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList(); args.Minds = comp.ChangelingMinds;
args.AgentName = Loc.GetString("changeling-round-end-agent-name"); args.AgentName = Loc.GetString("changeling-round-end-agent-name");
} }

View File

@@ -2,7 +2,6 @@ using Content.Server.Zombies;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;

View File

@@ -2,7 +2,6 @@ using Content.Server.Zombies;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
namespace Content.Server.Chemistry.ReagentEffects; namespace Content.Server.Chemistry.ReagentEffects;

View File

@@ -24,7 +24,6 @@ public sealed class ConfigurationSystem : EntitySystem
private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args) private void OnInteractUsing(EntityUid uid, ConfigurationComponent component, InteractUsingEvent args)
{ {
// TODO use activatable ui system
if (args.Handled) if (args.Handled)
return; return;

View File

@@ -63,21 +63,9 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks); SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks);
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed); SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed);
SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUserInterfaceCheckRangeEvent>(OnUiRangeCheck);
SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved); SubscribeLocalEvent<DeviceListComponent, ComponentRemove>(OnComponentRemoved);
} }
private void OnUiRangeCheck(Entity<NetworkConfiguratorComponent> ent, ref BoundUserInterfaceCheckRangeEvent args)
{
if (ent.Comp.ActiveDeviceList == null || args.Result == BoundUserInterfaceRangeResult.Fail)
return;
DebugTools.Assert(Exists(ent.Comp.ActiveDeviceList));
if (!_interactionSystem.InRangeUnobstructed(args.Actor!, ent.Comp.ActiveDeviceList.Value))
args.Result = BoundUserInterfaceRangeResult.Fail;
}
private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, ComponentShutdown args) private void OnShutdown(EntityUid uid, NetworkConfiguratorComponent component, ComponentShutdown args)
{ {
ClearDevices(uid, component); ClearDevices(uid, component);
@@ -87,6 +75,23 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
component.ActiveDeviceList = null; component.ActiveDeviceList = null;
} }
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<NetworkConfiguratorComponent>();
while (query.MoveNext(out var uid, out var component))
{
if (component.ActiveDeviceList != null
&& EntityManager.EntityExists(component.ActiveDeviceList.Value)
&& _interactionSystem.InRangeUnobstructed(uid, component.ActiveDeviceList.Value))
continue;
//The network configurator is a handheld device. There can only ever be an ui session open for the player holding the device.
_uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure);
}
}
private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, NetworkConfiguratorComponent component, MapInitEvent args)
{ {
UpdateListUiState(uid, component); UpdateListUiState(uid, component);

View File

@@ -1,39 +0,0 @@
using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Humanoid;
using Content.Server.Preferences.Managers;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules;
public sealed class AntagLoadProfileRuleSystem : GameRuleSystem<AntagLoadProfileRuleComponent>
{
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AntagLoadProfileRuleComponent, AntagSelectEntityEvent>(OnSelectEntity);
}
private void OnSelectEntity(Entity<AntagLoadProfileRuleComponent> ent, ref AntagSelectEntityEvent args)
{
if (args.Handled)
return;
var profile = args.Session != null
? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile
: HumanoidCharacterProfile.RandomWithSpecies();
if (profile?.Species is not {} speciesId || !_proto.TryIndex<SpeciesPrototype>(speciesId, out var species))
species = _proto.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
args.Entity = Spawn(species.Prototype);
_humanoid.LoadProfile(args.Entity.Value, profile);
}
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.GameTicking.Rules.Components;
/// <summary>
/// Makes this rules antags spawn a humanoid, either from the player's profile or a random one.
/// </summary>
[RegisterComponent]
public sealed partial class AntagLoadProfileRuleComponent : Component;

View File

@@ -8,4 +8,23 @@ namespace Content.Server.GameTicking.Rules.Components;
/// Stores data for <see cref="ThiefRuleSystem"/>. /// Stores data for <see cref="ThiefRuleSystem"/>.
/// </summary> /// </summary>
[RegisterComponent, Access(typeof(ThiefRuleSystem))] [RegisterComponent, Access(typeof(ThiefRuleSystem))]
public sealed partial class ThiefRuleComponent : Component; public sealed partial class ThiefRuleComponent : Component
{
[DataField]
public ProtoId<WeightedRandomPrototype> BigObjectiveGroup = "ThiefBigObjectiveGroups";
[DataField]
public ProtoId<WeightedRandomPrototype> SmallObjectiveGroup = "ThiefObjectiveGroups";
[DataField]
public ProtoId<WeightedRandomPrototype> EscapeObjectiveGroup = "ThiefEscapeObjectiveGroups";
[DataField]
public float BigObjectiveChance = 0.7f;
[DataField]
public float MaxObjectiveDifficulty = 2.5f;
[DataField]
public int MaxStealObjectives = 10;
}

View File

@@ -23,6 +23,9 @@ public sealed partial class TraitorRuleComponent : Component
[DataField] [DataField]
public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate"; public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
[DataField]
public ProtoId<WeightedRandomPrototype> ObjectiveGroup = "TraitorObjectiveGroups";
[DataField] [DataField]
public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives"; public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives";
@@ -75,4 +78,7 @@ public sealed partial class TraitorRuleComponent : Component
/// </summary> /// </summary>
[DataField] [DataField]
public int StartingBalance = 20; public int StartingBalance = 20;
[DataField]
public int MaxDifficulty = 5;
} }

View File

@@ -1,8 +1,6 @@
using Content.Server.GameTicking.Rules.Components; using Content.Server.GameTicking.Rules.Components;
using Content.Server.Objectives; using Content.Server.Objectives;
using Content.Shared.Mind;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Content.Server.GameTicking.Rules; namespace Content.Server.GameTicking.Rules;
@@ -49,8 +47,7 @@ public sealed class GenericAntagRuleSystem : GameRuleSystem<GenericAntagRuleComp
private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args) private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
{ {
// just temporary until this is deleted args.Minds = comp.Minds;
args.Minds = comp.Minds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList();
args.AgentName = Loc.GetString(comp.AgentName); args.AgentName = Loc.GetString(comp.AgentName);
} }
} }

View File

@@ -1,9 +1,11 @@
using Content.Server.Antag; using Content.Server.Antag;
using Content.Server.Communications; using Content.Server.Communications;
using Content.Server.GameTicking.Rules.Components; using Content.Server.GameTicking.Rules.Components;
using Content.Server.Humanoid;
using Content.Server.Nuke; using Content.Server.Nuke;
using Content.Server.NukeOps; using Content.Server.NukeOps;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Preferences.Managers;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.RoundEnd; using Content.Server.RoundEnd;
using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Events;
@@ -11,39 +13,37 @@ using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Server.Store.Components; using Content.Server.Store.Components;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.NPC.Components; using Content.Shared.NPC.Components;
using Content.Shared.NPC.Systems; using Content.Shared.NPC.Systems;
using Content.Shared.Nuke; using Content.Shared.Nuke;
using Content.Shared.NukeOps; using Content.Shared.NukeOps;
using Content.Shared.Preferences;
using Content.Shared.Store; using Content.Shared.Store;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Zombies; using Content.Shared.Zombies;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Components;
using Content.Server.Humanoid;
using Content.Server.Preferences.Managers;
using Content.Server.StationEvents.Components; using Content.Server.StationEvents.Components;
using Content.Shared._White.Antag; using Content.Shared._White.Antag;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules; namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent> public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{ {
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -75,6 +75,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared); SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt); SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt);
SubscribeLocalEvent<NukeopsRuleComponent, AntagSelectEntityEvent>(OnAntagSelectEntity);
SubscribeLocalEvent<NukeopsRuleComponent, AfterAntagEntitySelectedEvent>(OnAfterAntagEntSelected); SubscribeLocalEvent<NukeopsRuleComponent, AfterAntagEntitySelectedEvent>(OnAfterAntagEntSelected);
} }

View File

@@ -24,6 +24,7 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
SubscribeLocalEvent<ThiefRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagSelected); SubscribeLocalEvent<ThiefRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagSelected);
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing); SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<ThiefRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
} }
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args) private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
@@ -32,9 +33,41 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
return; return;
//Generate objectives //Generate objectives
GenerateObjectives(mindId, mind, ent);
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
} }
private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule)
{
// Give thieves their objectives
var difficulty = 0f;
if (_random.Prob(thiefRule.BigObjectiveChance)) // 70% chance to 1 big objective (structure or animal)
{
var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.BigObjectiveGroup);
if (objective != null)
{
_mindSystem.AddObjective(mindId, mind, objective.Value);
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
}
}
for (var i = 0; i < thiefRule.MaxStealObjectives && thiefRule.MaxObjectiveDifficulty > difficulty; i++) // Many small objectives
{
var objective = _objectives.GetRandomObjective(mindId, mind, thiefRule.SmallObjectiveGroup);
if (objective == null)
continue;
_mindSystem.AddObjective(mindId, mind, objective.Value);
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
}
//Escape target
var escapeObjective = _objectives.GetRandomObjective(mindId, mind, thiefRule.EscapeObjectiveGroup);
if (escapeObjective != null)
_mindSystem.AddObjective(mindId, mind, escapeObjective.Value);
}
//Add mind briefing //Add mind briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args) private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args)
{ {
@@ -54,4 +87,10 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
return briefing; return briefing;
} }
private void OnObjectivesTextGetInfo(Entity<ThiefRuleComponent> ent, ref ObjectivesTextGetInfoEvent args)
{
args.Minds = _antag.GetAntagMindEntityUids(ent.Owner);
args.AgentName = Loc.GetString("thief-round-end-agent-name");
}
} }

View File

@@ -38,12 +38,15 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly GameTicker _gameTicker = default!;
public const int MaxPicks = 20;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected); SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend); SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
} }
@@ -71,7 +74,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
} }
} }
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true)
{ {
//Grab the mind if it wasnt provided //Grab the mind if it wasnt provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
@@ -124,16 +127,37 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
if (richAspect) // WD if (richAspect) // WD
TraitorRichAspect.NotifyTraitor(mind, _chatManager); TraitorRichAspect.NotifyTraitor(mind, _chatManager);
// Give traitors their objectives
if (giveObjectives)
{
var difficulty = 0f;
for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++)
{
var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup);
if (objective == null)
continue;
_mindSystem.AddObjective(mindId, mind, objective.Value);
var adding = Comp<ObjectiveComponent>(objective.Value).Difficulty;
difficulty += adding;
Log.Debug($"Added objective {ToPrettyString(objective):objective} with {adding} difficulty");
}
}
return true; return true;
} }
// TODO: AntagCodewordsComponent private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
{
args.Minds = _antag.GetAntagMindEntityUids(uid);
args.AgentName = Loc.GetString("traitor-round-end-agent-name");
}
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args) private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{ {
args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords))); args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
} }
// TODO: figure out how to handle this? add priority to briefing event?
private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null) private string GenerateBriefing(string[] codewords, Note[]? uplinkCode, string? objectiveIssuer = null)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

View File

@@ -7,6 +7,31 @@ using Robust.Shared.Player;
namespace Content.Server.Interaction namespace Content.Server.Interaction
{ {
// TODO Remove Shared prefix /// <summary>
public sealed class InteractionSystem : SharedInteractionSystem; /// 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);
}
}
} }

View File

@@ -1,17 +1,15 @@
using Robust.Shared.GameStates; namespace Content.Server.Lock.Components;
namespace Content.Shared.Lock;
/// <summary> /// <summary>
/// This is used for activatable UIs that require the entity to have a lock in a certain state. /// This is used for activatable UIs that require the entity to have a lock in a certain state.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] [RegisterComponent]
public sealed partial class ActivatableUIRequiresLockComponent : Component public sealed partial class ActivatableUIRequiresLockComponent : Component
{ {
/// <summary> /// <summary>
/// TRUE: the lock must be locked to access the UI. /// TRUE: the lock must be locked to access the UI.
/// FALSE: the lock must be unlocked to access the UI. /// FALSE: the lock must be unlocked to access the UI.
/// </summary> /// </summary>
[DataField] [DataField("requireLocked"), ViewVariables(VVAccess.ReadWrite)]
public bool RequireLocked; public bool requireLocked = false;
} }

View File

@@ -0,0 +1,43 @@
using Content.Server.Lock.Components;
using Content.Server.Popups;
using Content.Shared.UserInterface;
using Content.Shared.Lock;
using Content.Server.UserInterface;
using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem;
namespace Content.Server.Lock.EntitySystems;
public sealed class ActivatableUIRequiresLockSystem : EntitySystem
{
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, LockToggledEvent>(LockToggled);
}
private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.requireLocked)
{
args.Cancel();
if (lockComp.Locked)
_popupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User);
}
}
private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args)
{
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.requireLocked)
return;
_activatableUI.CloseAll(uid);
}
}

View File

@@ -36,14 +36,14 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
private void OnRoundEndText(RoundEndTextAppendEvent ev) private void OnRoundEndText(RoundEndTextAppendEvent ev)
{ {
// go through each gamerule getting data for the roundend summary. // go through each gamerule getting data for the roundend summary.
var summaries = new Dictionary<string, Dictionary<string, List<(EntityUid, string)>>>(); var summaries = new Dictionary<string, Dictionary<string, List<EntityUid>>>();
var query = EntityQueryEnumerator<GameRuleComponent>(); var query = EntityQueryEnumerator<GameRuleComponent>();
while (query.MoveNext(out var uid, out var gameRule)) while (query.MoveNext(out var uid, out var gameRule))
{ {
if (!_gameTicker.IsGameRuleAdded(uid, gameRule)) if (!_gameTicker.IsGameRuleAdded(uid, gameRule))
continue; continue;
var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), string.Empty); var info = new ObjectivesTextGetInfoEvent(new List<EntityUid>(), string.Empty);
RaiseLocalEvent(uid, ref info); RaiseLocalEvent(uid, ref info);
if (info.Minds.Count == 0) if (info.Minds.Count == 0)
continue; continue;
@@ -51,7 +51,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
// first group the gamerules by their agents, for example 2 different dragons // first group the gamerules by their agents, for example 2 different dragons
var agent = info.AgentName; var agent = info.AgentName;
if (!summaries.ContainsKey(agent)) if (!summaries.ContainsKey(agent))
summaries[agent] = new Dictionary<string, List<(EntityUid, string)>>(); summaries[agent] = new Dictionary<string, List<EntityUid>>();
var prepend = new ObjectivesTextPrependEvent(""); var prepend = new ObjectivesTextPrependEvent("");
RaiseLocalEvent(uid, ref prepend); RaiseLocalEvent(uid, ref prepend);
@@ -79,7 +79,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
foreach (var (_, minds) in summary) foreach (var (_, minds) in summary)
{ {
total += minds.Count; total += minds.Count;
totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count(); totalInCustody += minds.Where(m => IsInCustody(m)).Count();
} }
var result = new StringBuilder(); var result = new StringBuilder();
@@ -104,16 +104,19 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
} }
} }
private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds) private void AddSummary(StringBuilder result, string agent, List<EntityUid> minds)
{ {
var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>(); var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>();
foreach (var (mindId, name) in minds) foreach (var mindId in minds)
{ {
if (!TryComp<MindComponent>(mindId, out var mind)) if (!TryComp(mindId, out MindComponent? mind))
continue;
var title = GetTitle(mindId, mind);
if (title == null)
continue; continue;
var title = GetTitle((mindId, mind), name);
var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty; var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty;
var objectives = mind.Objectives; var objectives = mind.Objectives;
@@ -235,18 +238,34 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
/// <summary> /// <summary>
/// Get the title for a player's mind used in round end. /// Get the title for a player's mind used in round end.
/// Pass in the original entity name which is shown alongside username.
/// </summary> /// </summary>
public string GetTitle(Entity<MindComponent?> mind, string name) public string? GetTitle(EntityUid mindId, MindComponent? mind = null)
{ {
if (Resolve(mind, ref mind.Comp) && if (!Resolve(mindId, ref mind))
mind.Comp.OriginalOwnerUserId != null && return null;
_player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData))
var name = mind.CharacterName;
var username = (string?) null;
if (mind.OriginalOwnerUserId != null &&
_player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData))
{ {
var username = sessionData.UserName; username = sessionData.UserName;
return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
} }
if (username != null)
{
if (name != null)
return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
return Loc.GetString("objectives-player-user", ("user", username));
}
// nothing to identify the player by, just give up
if (name == null)
return null;
return Loc.GetString("objectives-player-named", ("name", name)); return Loc.GetString("objectives-player-named", ("name", name));
} }
} }
@@ -260,7 +279,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
/// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler. /// The objectives system already checks if the game rule is added so you don't need to check that in this event's handler.
/// </remarks> /// </remarks>
[ByRefEvent] [ByRefEvent]
public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName); public record struct ObjectivesTextGetInfoEvent(List<EntityUid> Minds, string AgentName);
/// <summary> /// <summary>
/// Raised on the game rule before text for each agent's objectives is added, letting you prepend something. /// Raised on the game rule before text for each agent's objectives is added, letting you prepend something.

View File

@@ -26,7 +26,6 @@ namespace Content.Server.Preferences.Managers
[Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IDependencyCollection _dependencies = default!; [Dependency] private readonly IDependencyCollection _dependencies = default!;
[Dependency] private readonly ILogManager _log = default!;
// WD-EDIT // WD-EDIT
[Dependency] private readonly SponsorsManager _sponsors = default!; [Dependency] private readonly SponsorsManager _sponsors = default!;
@@ -37,9 +36,7 @@ namespace Content.Server.Preferences.Managers
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs = private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
new(); new();
private ISawmill _sawmill = default!; private readonly ISawmill _sawmill = default!;
private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots);
public void Init() public void Init()
{ {
@@ -47,7 +44,6 @@ namespace Content.Server.Preferences.Managers
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage); _netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage); _netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage); _netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
_sawmill = _log.GetSawmill("prefs");
} }
private async void HandleSelectCharacterMessage(MsgSelectCharacter message) private async void HandleSelectCharacterMessage(MsgSelectCharacter message)

View File

@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems; using Content.Server.Hands.Systems;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Database; using Content.Shared.Database;
@@ -69,6 +70,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged); SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty); SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC); SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded); SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
@@ -212,6 +214,13 @@ public sealed partial class BorgSystem : SharedBorgSystem
UpdateUI(uid, component); UpdateUI(uid, component);
} }
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
{
// borgs can't view their own ui
if (args.User == uid)
args.Cancel();
}
private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args) private void OnGetDeadIC(EntityUid uid, BorgChassisComponent component, ref GetCharactedDeadIcEvent args)
{ {
args.Dead = true; args.Dead = true;

View File

@@ -28,9 +28,7 @@ public sealed class ImmovableRodRule : StationEventSystem<ImmovableRodRuleCompon
if (proto.TryGetComponent<ImmovableRodComponent>(out var rod) && proto.TryGetComponent<TimedDespawnComponent>(out var despawn)) if (proto.TryGetComponent<ImmovableRodComponent>(out var rod) && proto.TryGetComponent<TimedDespawnComponent>(out var despawn))
{ {
if (!TryFindRandomTile(out _, out _, out _, out var targetCoords)) TryFindRandomTile(out _, out _, out _, out var targetCoords);
return;
var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed); var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed);
var angle = RobustRandom.NextAngle(); var angle = RobustRandom.NextAngle();
var direction = angle.ToVec(); var direction = angle.ToVec();

View File

@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Ensnaring; using Content.Server.Ensnaring;
using Content.Shared.CombatMode;
using Content.Shared.Cuffs; using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components; using Content.Shared.Cuffs.Components;
using Content.Shared.Database; using Content.Shared.Database;
@@ -9,6 +10,7 @@ using Content.Shared.Ensnaring.Components;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems; using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Inventory.VirtualItem; using Content.Shared.Inventory.VirtualItem;
@@ -27,6 +29,7 @@ namespace Content.Server.Strip
{ {
[Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly EnsnareableSystem _ensnaringSystem = default!; [Dependency] private readonly EnsnareableSystem _ensnaringSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!; [Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
@@ -43,6 +46,7 @@ namespace Content.Server.Strip
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb); SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb);
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb); SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb);
SubscribeLocalEvent<StrippableComponent, ActivateInWorldEvent>(OnActivateInWorld);
// BUI // BUI
SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed); SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed);
@@ -65,7 +69,7 @@ namespace Content.Server.Strip
{ {
Text = Loc.GetString("strip-verb-get-data-text"), Text = Loc.GetString("strip-verb-get-data-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => TryOpenStrippingUi(args.User, (uid, component), true), Act = () => StartOpeningStripper(args.User, (uid, component), true),
}; };
args.Verbs.Add(verb); args.Verbs.Add(verb);
@@ -83,13 +87,37 @@ namespace Content.Server.Strip
{ {
Text = Loc.GetString("strip-verb-get-data-text"), Text = Loc.GetString("strip-verb-get-data-text"),
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")), Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/outfit.svg.192dpi.png")),
Act = () => TryOpenStrippingUi(args.User, (uid, component), true), Act = () => StartOpeningStripper(args.User, (uid, component), true),
Category = VerbCategory.Examine, Category = VerbCategory.Examine,
}; };
args.Verbs.Add(verb); args.Verbs.Add(verb);
} }
private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args)
{
if (args.Target == args.User)
return;
if (!HasComp<ActorComponent>(args.User))
return;
StartOpeningStripper(args.User, (uid, component));
}
public override void StartOpeningStripper(EntityUid user, Entity<StrippableComponent> strippable, bool openInCombat = false)
{
base.StartOpeningStripper(user, strippable, openInCombat);
if (TryComp<CombatModeComponent>(user, out var mode) && mode.IsInCombatMode && !openInCombat)
return;
if (HasComp<StrippingComponent>(user))
{
_userInterfaceSystem.OpenUi(strippable.Owner, StrippingUiKey.Key, user);
}
}
private void OnStripButtonPressed(Entity<StrippableComponent> strippable, ref StrippingSlotButtonPressed args) private void OnStripButtonPressed(Entity<StrippableComponent> strippable, ref StrippingSlotButtonPressed args)
{ {
if (args.Actor is not { Valid: true } user || if (args.Actor is not { Valid: true } user ||

View File

@@ -187,10 +187,15 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
if (session is { } pSession) if (session is { } pSession)
{ {
(targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession); (targetCoordinates, targetLocalAngle) = _lag.GetCoordinatesAngle(target, pSession);
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range); }
else
{
var xform = Transform(target);
targetCoordinates = xform.Coordinates;
targetLocalAngle = xform.LocalRotation;
} }
return Interaction.InRangeUnobstructed(user, target, range); return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range);
} }
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform) protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)

View File

@@ -1,17 +1,15 @@
using Robust.Shared.GameStates; namespace Content.Server.Wires;
namespace Content.Shared.Wires;
/// <summary> /// <summary>
/// This is used for activatable UIs that require the entity to have a panel in a certain state. /// This is used for activatable UIs that require the entity to have a panel in a certain state.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedWiresSystem))] [RegisterComponent]
public sealed partial class ActivatableUIRequiresPanelComponent : Component public sealed partial class ActivatableUIRequiresPanelComponent : Component
{ {
/// <summary> /// <summary>
/// TRUE: the panel must be open to access the UI. /// TRUE: the panel must be open to access the UI.
/// FALSE: the panel must be closed to access the UI. /// FALSE: the panel must be closed to access the UI.
/// </summary> /// </summary>
[DataField] [DataField("requireOpen"), ViewVariables(VVAccess.ReadWrite)]
public bool RequireOpen = true; public bool RequireOpen = true;
} }

View File

@@ -4,23 +4,27 @@ using System.Threading;
using Content.Server.Construction; using Content.Server.Construction;
using Content.Server.Construction.Components; using Content.Server.Construction.Components;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Tools.Components; using Content.Shared.Tools.Components;
using Content.Shared.UserInterface;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem;
namespace Content.Server.Wires; namespace Content.Server.Wires;
public sealed class WiresSystem : SharedWiresSystem public sealed class WiresSystem : SharedWiresSystem
{ {
[Dependency] private readonly IPrototypeManager _protoMan = default!; [Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -48,6 +52,8 @@ public sealed class WiresSystem : SharedWiresSystem
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire); SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered); SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter); SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, PanelChangedEvent>(OnActivatableUIPanelChanged);
SubscribeLocalEvent<WiresPanelSecurityComponent, WiresPanelSecurityEvent>(SetWiresPanelSecurity); SubscribeLocalEvent<WiresPanelSecurityComponent, WiresPanelSecurityEvent>(SetWiresPanelSecurity);
} }
@@ -467,6 +473,23 @@ public sealed class WiresSystem : SharedWiresSystem
_uiSystem.CloseUi(ent.Owner, WiresUiKey.Key); _uiSystem.CloseUi(ent.Owner, WiresUiKey.Key);
} }
private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled || !TryComp<WiresPanelComponent>(uid, out var wires))
return;
if (component.RequireOpen != wires.Open)
args.Cancel();
}
private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args)
{
if (args.Open == component.RequireOpen)
return;
_activatableUI.CloseAll(uid);
}
private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, WiresComponent component, MapInitEvent args)
{ {
if (!string.IsNullOrEmpty(component.LayoutId)) if (!string.IsNullOrEmpty(component.LayoutId))

View File

@@ -1,13 +1,12 @@
using Content.Shared.Damage; using Content.Shared.Damage;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Zombies; namespace Content.Server.Zombies;
/// <summary> /// <summary>
/// Temporary because diseases suck. /// Temporary because diseases suck.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent]
public sealed partial class PendingZombieComponent : Component public sealed partial class PendingZombieComponent : Component
{ {
/// <summary> /// <summary>

View File

@@ -39,7 +39,7 @@ public sealed class WizardRuleSystem : GameRuleSystem<WizardRuleComponent>
private void OnObjectivesTextGetInfo(Entity<WizardRuleComponent> ent, ref ObjectivesTextGetInfoEvent args) private void OnObjectivesTextGetInfo(Entity<WizardRuleComponent> ent, ref ObjectivesTextGetInfoEvent args)
{ {
args.Minds = ent.Comp.WizardMinds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList(); args.Minds = ent.Comp.WizardMinds;
args.AgentName = Loc.GetString("wizard-round-end-agent-name"); args.AgentName = Loc.GetString("wizard-round-end-agent-name");
} }

View File

@@ -508,7 +508,13 @@ public abstract class SharedActionsSystem : EntitySystem
return distance <= action.Range; return distance <= action.Range;
} }
return _interactionSystem.InRangeAndAccessible(user, target, range: action.Range); if (_interactionSystem.InRangeUnobstructed(user, target, range: action.Range)
&& _containerSystem.IsInSameOrParentContainer(user, target))
{
return true;
}
return _interactionSystem.CanAccessViaStorage(user, target);
} }
public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity<WorldTargetActionComponent> action) public bool ValidateWorldTarget(EntityUid user, EntityCoordinates coords, Entity<WorldTargetActionComponent> action)

View File

@@ -1,6 +1,7 @@
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Atmos.Components; namespace Content.Shared.Atmos.Components;
@@ -23,47 +24,55 @@ public sealed partial class GasTileOverlayComponent : Component
public GameTick ForceTick { get; set; } 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] [Serializable, NetSerializable]
public sealed class GasTileOverlayDeltaState( public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks,
HashSet<Vector2i> allChunks)
: ComponentState, IComponentDeltaState<GasTileOverlayState>
{ {
public readonly Dictionary<Vector2i, GasOverlayChunk> ModifiedChunks = modifiedChunks; public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks;
public readonly HashSet<Vector2i> AllChunks = allChunks; public bool FullState => AllChunks == null;
public void ApplyToFullState(GasTileOverlayState state) // required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? AllChunks;
public GasTileOverlayState(Dictionary<Vector2i, GasOverlayChunk> chunks)
{ {
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) foreach (var key in state.Chunks.Keys)
{ {
if (!AllChunks.Contains(key)) if (!AllChunks!.Contains(key))
state.Chunks.Remove(key); state.Chunks.Remove(key);
} }
foreach (var (chunk, data) in ModifiedChunks) foreach (var (chunk, data) in Chunks)
{ {
state.Chunks[chunk] = new(data); state.Chunks[chunk] = new(data);
} }
} }
public GasTileOverlayState CreateNewFullState(GasTileOverlayState state) public IComponentState CreateNewFullState(IComponentState fullState)
{ {
var chunks = new Dictionary<Vector2i, GasOverlayChunk>(AllChunks.Count); DebugTools.Assert(!FullState);
var state = (GasTileOverlayState) fullState;
DebugTools.Assert(state.FullState);
foreach (var (chunk, data) in ModifiedChunks) var chunks = new Dictionary<Vector2i, GasOverlayChunk>(state.Chunks.Count);
foreach (var (chunk, data) in Chunks)
{ {
chunks[chunk] = new(data); chunks[chunk] = new(data);
} }
foreach (var (chunk, data) in state.Chunks) foreach (var (chunk, data) in state.Chunks)
{ {
if (AllChunks.Contains(chunk)) if (AllChunks!.Contains(chunk))
chunks.TryAdd(chunk, new(data)); chunks.TryAdd(chunk, new(data));
} }

View File

@@ -55,7 +55,7 @@ namespace Content.Shared.Atmos.EntitySystems
data[index] = chunk; data[index] = chunk;
} }
args.State = new GasTileOverlayDeltaState(data, new(component.Chunks.Keys)); args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) };
} }
public static Vector2i GetGasChunkIndices(Vector2i indices) public static Vector2i GetGasChunkIndices(Vector2i indices)

View File

@@ -58,7 +58,7 @@ public abstract partial class SharedBuckleSystem
return; return;
var strapPosition = Transform(strapUid).Coordinates; var strapPosition = Transform(strapUid).Coordinates;
if (ev.NewPosition.EntityId.IsValid() && ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance)) if (ev.NewPosition.InRange(EntityManager, _transform, strapPosition, strapComp.MaxBuckleDistance))
return; return;
TryUnbuckle(uid, uid, true, component); TryUnbuckle(uid, uid, true, component);

View File

@@ -5,6 +5,8 @@ using Content.Shared.StatusIcon;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Damage namespace Content.Shared.Damage
{ {
@@ -16,7 +18,7 @@ namespace Content.Shared.Damage
/// may also have resistances to certain damage types, defined via a <see cref="DamageModifierSetPrototype"/>. /// may also have resistances to certain damage types, defined via a <see cref="DamageModifierSetPrototype"/>.
/// </remarks> /// </remarks>
[RegisterComponent] [RegisterComponent]
[NetworkedComponent] [NetworkedComponent()]
[Access(typeof(DamageableSystem), Other = AccessPermissions.ReadExecute)] [Access(typeof(DamageableSystem), Other = AccessPermissions.ReadExecute)]
public sealed partial class DamageableComponent : Component public sealed partial class DamageableComponent : Component
{ {
@@ -24,8 +26,8 @@ namespace Content.Shared.Damage
/// This <see cref="DamageContainerPrototype"/> specifies what damage types are supported by this component. /// This <see cref="DamageContainerPrototype"/> specifies what damage types are supported by this component.
/// If null, all damage types will be supported. /// If null, all damage types will be supported.
/// </summary> /// </summary>
[DataField("damageContainer")] [DataField("damageContainer", customTypeSerializer: typeof(PrototypeIdSerializer<DamageContainerPrototype>))]
public ProtoId<DamageContainerPrototype>? DamageContainerID; public string? DamageContainerID;
/// <summary> /// <summary>
/// This <see cref="DamageModifierSetPrototype"/> will be applied to any damage that is dealt to this container, /// This <see cref="DamageModifierSetPrototype"/> will be applied to any damage that is dealt to this container,
@@ -35,8 +37,8 @@ namespace Content.Shared.Damage
/// Though DamageModifierSets can be deserialized directly, we only want to use the prototype version here /// Though DamageModifierSets can be deserialized directly, we only want to use the prototype version here
/// to reduce duplication. /// to reduce duplication.
/// </remarks> /// </remarks>
[DataField("damageModifierSet")] [DataField("damageModifierSet", customTypeSerializer: typeof(PrototypeIdSerializer<DamageModifierSetPrototype>))]
public ProtoId<DamageModifierSetPrototype>? DamageModifierSetId; public string? DamageModifierSetId;
/// <summary> /// <summary>
/// All the damage information is stored in this <see cref="DamageSpecifier"/>. /// All the damage information is stored in this <see cref="DamageSpecifier"/>.
@@ -44,7 +46,7 @@ namespace Content.Shared.Damage
/// <remarks> /// <remarks>
/// If this data-field is specified, this allows damageable components to be initialized with non-zero damage. /// If this data-field is specified, this allows damageable components to be initialized with non-zero damage.
/// </remarks> /// </remarks>
[DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier [DataField("damage", readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier
public DamageSpecifier Damage = new(); public DamageSpecifier Damage = new();
/// <summary> /// <summary>
@@ -62,8 +64,8 @@ namespace Content.Shared.Damage
[ViewVariables] [ViewVariables]
public FixedPoint2 TotalDamage; public FixedPoint2 TotalDamage;
[DataField("radiationDamageTypes")] [DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
public List<ProtoId<DamageTypePrototype>> RadiationDamageTypeIDs = new() { "Radiation" }; public List<string> RadiationDamageTypeIDs = new() { "Radiation" };
[DataField] [DataField]
public Dictionary<MobState, ProtoId<StatusIconPrototype>> HealthIcons = new() public Dictionary<MobState, ProtoId<StatusIconPrototype>> HealthIcons = new()
@@ -75,9 +77,6 @@ namespace Content.Shared.Damage
[DataField] [DataField]
public ProtoId<StatusIconPrototype> RottingIcon = "HealthIconRotting"; public ProtoId<StatusIconPrototype> RottingIcon = "HealthIconRotting";
[DataField]
public FixedPoint2? HealthBarThreshold;
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
@@ -85,16 +84,13 @@ namespace Content.Shared.Damage
{ {
public readonly Dictionary<string, FixedPoint2> DamageDict; public readonly Dictionary<string, FixedPoint2> DamageDict;
public readonly string? ModifierSetId; public readonly string? ModifierSetId;
public readonly FixedPoint2? HealthBarThreshold;
public DamageableComponentState( public DamageableComponentState(
Dictionary<string, FixedPoint2> damageDict, Dictionary<string, FixedPoint2> damageDict,
string? modifierSetId, string? modifierSetId)
FixedPoint2? healthBarThreshold)
{ {
DamageDict = damageDict; DamageDict = damageDict;
ModifierSetId = modifierSetId; ModifierSetId = modifierSetId;
HealthBarThreshold = healthBarThreshold;
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Inventory; using Content.Shared.Inventory;
@@ -239,12 +240,12 @@ namespace Content.Shared.Damage
{ {
if (_netMan.IsServer) if (_netMan.IsServer)
{ {
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold); args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId);
} }
else else
{ {
// avoid mispredicting damage on newly spawned entities. // avoid mispredicting damage on newly spawned entities.
args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId, component.HealthBarThreshold); args.State = new DamageableComponentState(component.Damage.DamageDict.ShallowClone(), component.DamageModifierSetId);
} }
} }
@@ -278,7 +279,6 @@ namespace Content.Shared.Damage
} }
component.DamageModifierSetId = state.ModifierSetId; component.DamageModifierSetId = state.ModifierSetId;
component.HealthBarThreshold = state.HealthBarThreshold;
// Has the damage actually changed? // Has the damage actually changed?
DamageSpecifier newDamage = new() { DamageDict = new(state.DamageDict) }; DamageSpecifier newDamage = new() { DamageDict = new(state.DamageDict) };

View File

@@ -62,37 +62,46 @@ namespace Content.Shared.Decals
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class DecalGridState(Dictionary<Vector2i, DecalChunk> chunks) : ComponentState public sealed class DecalGridState : ComponentState, IComponentDeltaState
{ {
public Dictionary<Vector2i, DecalChunk> Chunks = chunks; public Dictionary<Vector2i, DecalChunk> Chunks;
} public bool FullState => AllChunks == null;
[Serializable, NetSerializable] // required to infer deleted/missing chunks for delta states
public sealed class DecalGridDeltaState(Dictionary<Vector2i, DecalChunk> modifiedChunks, HashSet<Vector2i> allChunks) public HashSet<Vector2i>? AllChunks;
: ComponentState, IComponentDeltaState<DecalGridState>
{
public Dictionary<Vector2i, DecalChunk> ModifiedChunks = modifiedChunks;
public HashSet<Vector2i> AllChunks = allChunks;
public void ApplyToFullState(DecalGridState state) public DecalGridState(Dictionary<Vector2i, DecalChunk> chunks)
{ {
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) foreach (var key in state.Chunks.Keys)
{ {
if (!AllChunks!.Contains(key)) if (!AllChunks!.Contains(key))
state.Chunks.Remove(key); state.Chunks.Remove(key);
} }
foreach (var (chunk, data) in ModifiedChunks) foreach (var (chunk, data) in Chunks)
{ {
state.Chunks[chunk] = new(data); state.Chunks[chunk] = new(data);
} }
} }
public DecalGridState CreateNewFullState(DecalGridState state) public IComponentState CreateNewFullState(IComponentState fullState)
{ {
DebugTools.Assert(!FullState);
var state = (DecalGridState) fullState;
DebugTools.Assert(state.FullState);
var chunks = new Dictionary<Vector2i, DecalChunk>(state.Chunks.Count); var chunks = new Dictionary<Vector2i, DecalChunk>(state.Chunks.Count);
foreach (var (chunk, data) in ModifiedChunks) foreach (var (chunk, data) in Chunks)
{ {
chunks[chunk] = new(data); chunks[chunk] = new(data);
} }

View File

@@ -49,7 +49,7 @@ namespace Content.Shared.Decals
data[index] = chunk; data[index] = chunk;
} }
args.State = new DecalGridDeltaState(data, new(component.ChunkCollection.ChunkCollection.Keys)); args.State = new DecalGridState(data) { AllChunks = new(component.ChunkCollection.ChunkCollection.Keys) };
} }
private void OnGridInitialize(GridInitializeEvent msg) private void OnGridInitialize(GridInitializeEvent msg)

View File

@@ -1,10 +1,11 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Input; using Content.Shared.Input;
@@ -14,20 +15,16 @@ using Content.Shared.Inventory;
using Content.Shared.Inventory.Events; using Content.Shared.Inventory.Events;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Timing; using Content.Shared.Timing;
using Content.Shared.UserInterface;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Wall; using Content.Shared.Wall;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared._White.MeatyOre; using Content.Shared._White.MeatyOre;
using Content.Shared.Ghost;
using Content.Shared.Storage;
using Content.Shared.UserInterface;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Input; using Robust.Shared.Input;
@@ -41,8 +38,6 @@ using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Utility;
#pragma warning disable 618 #pragma warning disable 618
@@ -57,11 +52,12 @@ namespace Content.Shared.Interaction
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _net = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!; [Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _broadphase = default!; [Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -70,18 +66,6 @@ namespace Content.Shared.Interaction
[Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tagSystem = 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; private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
@@ -94,17 +78,6 @@ namespace Content.Shared.Interaction
public override void Initialize() 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<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt); SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
@@ -140,57 +113,34 @@ namespace Content.Shared.Interaction
/// </summary> /// </summary>
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev) private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{ {
_uiQuery.TryComp(ev.Target, out var uiComp); var user = ev.Actor;
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;
}
}
var range = _ui.GetUiRange(ev.Target, ev.UiKey); if (!_actionBlockerSystem.CanInteract(user, ev.Target))
// 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(); ev.Cancel();
return; return;
} }
if (uiComp == null) // Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that
return; // they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into
// other people's backpacks).
if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor) if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target)
&& !CanAccessViaStorage(user, ev.Target)
&& !_adminManager.HasAdminFlag(user, AdminFlags.Admin))
{ {
ev.Cancel(); ev.Cancel();
return; return;
} }
if (!uiComp.RequireHands) if (CompOrNull<IgnorBUIInteractionRangeComponent>(ev.Target) != null)
{
return; return;
}
if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0) if (!InRangeUnobstructed(user, ev.Target))
{
ev.Cancel(); 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> /// <summary>
@@ -247,7 +197,10 @@ namespace Content.Shared.Interaction
if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) if (!InRangeUnobstructed(userEntity.Value, uid, popup: true))
return false; return false;
_pullSystem.TogglePull(uid, userEntity.Value); if (!TryComp(uid, out PullableComponent? pull))
return false;
_pullSystem.TogglePull(uid, userEntity.Value, pull);
return false; return false;
} }
@@ -323,7 +276,7 @@ namespace Content.Shared.Interaction
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target) public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
{ {
// Always allow attack in these cases // Always allow attack in these cases
if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null) if (target == null || !TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
return false; return false;
// Only eat input if: // Only eat input if:
@@ -331,7 +284,7 @@ namespace Content.Shared.Interaction
// - Target doesn't cancel should-interact event // - Target doesn't cancel should-interact event
// This is intended to allow items to be picked up in combat mode, // 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) // but to also allow items to force attacks anyway (like mobs which are items, e.g. mice)
if (!_itemQuery.HasComp(target)) if (!HasComp<ItemComponent>(target))
return false; return false;
var combatEv = new CombatModeShouldHandInteractEvent(); var combatEv = new CombatModeShouldHandInteractEvent();
@@ -361,7 +314,7 @@ namespace Content.Shared.Interaction
bool checkAccess = true, bool checkAccess = true,
bool checkCanUse = true) bool checkCanUse = true)
{ {
if (_relayQuery.TryComp(user, out var relay) && relay.RelayEntity is not null) if (TryComp<InteractionRelayComponent>(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. // TODO this needs to be handled better. This probably bypasses many complex can-interact checks in weird roundabout ways.
if (_actionBlockerSystem.CanInteract(user, target)) if (_actionBlockerSystem.CanInteract(user, target))
@@ -375,7 +328,7 @@ namespace Content.Shared.Interaction
if (target != null && Deleted(target.Value)) if (target != null && Deleted(target.Value))
return; return;
if (!altInteract && _combatQuery.TryComp(user, out var combatMode) && combatMode.IsInCombatMode) if (!altInteract && TryComp<CombatModeComponent>(user, out var combatMode) && combatMode.IsInCombatMode)
{ {
if (!CombatModeCanHandInteract(user, target)) if (!CombatModeCanHandInteract(user, target))
return; return;
@@ -397,7 +350,10 @@ namespace Content.Shared.Interaction
// Check if interacted entity is in the same container, the direct child, or direct parent of the user. // 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) // Also checks if the item is accessible via some storage UI (e.g., open backpack)
if (checkAccess && target != null && !IsAccessible(user, target.Value)) if (checkAccess
&& target != null
&& !_containerSystem.IsInSameOrParentContainer(user, target.Value)
&& !CanAccessViaStorage(user, target.Value))
return; return;
var inRangeUnobstructed = target == null var inRangeUnobstructed = target == null
@@ -405,7 +361,7 @@ namespace Content.Shared.Interaction
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities : !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
// Does the user have hands? // Does the user have hands?
if (!_handsQuery.TryComp(user, out var hands) || hands.ActiveHand == null) if (!TryComp<HandsComponent>(user, out var hands) || hands.ActiveHand == null)
{ {
var ev = new InteractNoHandEvent(user, target, coordinates); var ev = new InteractNoHandEvent(user, target, coordinates);
RaiseLocalEvent(user, ev); RaiseLocalEvent(user, ev);
@@ -545,7 +501,7 @@ namespace Content.Shared.Interaction
predicate ??= _ => false; predicate ??= _ => false;
var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask); var ray = new CollisionRay(origin.Position, dir.Normalized(), collisionMask);
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList(); var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, dir.Length(), predicate.Invoke, false).ToList();
if (rayResults.Count == 0) if (rayResults.Count == 0)
return dir.Length(); return dir.Length();
@@ -608,29 +564,23 @@ namespace Content.Shared.Interaction
} }
var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask); var ray = new CollisionRay(origin.Position, dir.Normalized(), (int) collisionMask);
var rayResults = _broadphase.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList(); var rayResults = _sharedBroadphaseSystem.IntersectRayWithPredicate(origin.MapId, ray, length, predicate.Invoke, false).ToList();
return rayResults.Count == 0; return rayResults.Count == 0;
} }
public bool InRangeUnobstructed( public bool InRangeUnobstructed(
Entity<TransformComponent?> origin, EntityUid origin,
Entity<TransformComponent?> other, EntityUid other,
float range = InteractionRange, float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask, CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null, Ignored? predicate = null,
bool popup = false) bool popup = false)
{ {
if (!Resolve(other, ref other.Comp)) if (!TryComp(other, out TransformComponent? otherXform))
return false; return false;
return InRangeUnobstructed(origin, return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
other,
other.Comp.Coordinates,
other.Comp.LocalRotation,
range,
collisionMask,
predicate,
popup); popup);
} }
@@ -662,8 +612,8 @@ namespace Content.Shared.Interaction
/// True if the two points are within a given range without being obstructed. /// True if the two points are within a given range without being obstructed.
/// </returns> /// </returns>
public bool InRangeUnobstructed( public bool InRangeUnobstructed(
Entity<TransformComponent?> origin, EntityUid origin,
Entity<TransformComponent?> other, EntityUid other,
EntityCoordinates otherCoordinates, EntityCoordinates otherCoordinates,
Angle otherAngle, Angle otherAngle,
float range = InteractionRange, float range = InteractionRange,
@@ -671,10 +621,10 @@ namespace Content.Shared.Interaction
Ignored? predicate = null, Ignored? predicate = null,
bool popup = false) bool popup = false)
{ {
Ignored combinedPredicate = e => e == origin.Owner || (predicate?.Invoke(e) ?? false); Ignored combinedPredicate = e => e == origin || (predicate?.Invoke(e) ?? false);
var inRange = true; var inRange = true;
MapCoordinates originPos = default; MapCoordinates originPos = default;
var targetPos = _transform.ToMapCoordinates(otherCoordinates); var targetPos = otherCoordinates.ToMap(EntityManager, _transform);
Angle targetRot = default; Angle targetRot = default;
// So essentially: // So essentially:
@@ -684,30 +634,23 @@ namespace Content.Shared.Interaction
// Alternatively we could check centre distances first though // Alternatively we could check centre distances first though
// that means we wouldn't be able to easily check overlap interactions. // that means we wouldn't be able to easily check overlap interactions.
if (range > 0f && if (range > 0f &&
_fixtureQuery.TryComp(origin, out var fixtureA) && TryComp<FixturesComponent>(origin, out var fixtureA) &&
// These fixture counts are stuff that has the component but no fixtures for <reasons> (e.g. buttons). // These fixture counts are stuff that has the component but no fixtures for <reasons> (e.g. buttons).
// At least until they get removed. // At least until they get removed.
fixtureA.FixtureCount > 0 && fixtureA.FixtureCount > 0 &&
_fixtureQuery.TryComp(other, out var fixtureB) && TryComp<FixturesComponent>(other, out var fixtureB) &&
fixtureB.FixtureCount > 0 && fixtureB.FixtureCount > 0 &&
Resolve(origin, ref origin.Comp)) TryComp(origin, out TransformComponent? xformA))
{ {
var (worldPosA, worldRotA) = origin.Comp.GetWorldPositionRotation(); var (worldPosA, worldRotA) = xformA.GetWorldPositionRotation();
var xfA = new Transform(worldPosA, worldRotA); var xfA = new Transform(worldPosA, worldRotA);
var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId); var parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle); var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
// Different map or the likes. // Different map or the likes.
if (!_broadphase.TryGetNearest( if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
origin, out _, out _, out var distance,
other, xfA, xfB, fixtureA, fixtureB))
out _,
out _,
out var distance,
xfA,
xfB,
fixtureA,
fixtureB))
{ {
inRange = false; inRange = false;
} }
@@ -729,15 +672,15 @@ namespace Content.Shared.Interaction
else else
{ {
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range. // 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: origin.Comp); originPos = _transform.GetMapCoordinates(origin, xform: xformA);
range = (originPos.Position - targetPos.Position).Length(); range = (originPos.Position - targetPos.Position).Length();
} }
} }
// No fixtures, e.g. wallmounts. // No fixtures, e.g. wallmounts.
else else
{ {
originPos = _transform.GetMapCoordinates(origin, origin); originPos = _transform.GetMapCoordinates(origin);
var otherParent = (other.Comp ?? Transform(other)).ParentUid; var otherParent = Transform(other).ParentUid;
targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle; targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle;
} }
@@ -788,13 +731,13 @@ namespace Content.Shared.Interaction
{ {
HashSet<EntityUid> ignored = new(); HashSet<EntityUid> ignored = new();
if (_itemQuery.HasComp(target) && _physicsQuery.TryComp(target, out var physics) && physics.CanCollide) if (HasComp<ItemComponent>(target) && TryComp(target, out PhysicsComponent? physics) && physics.CanCollide)
{ {
// If the target is an item, we ignore any colliding entities. Currently done so that if items get stuck // 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. // inside of walls, users can still pick them up.
ignored.UnionWith(_broadphase.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics)); ignored.UnionWith(_sharedBroadphaseSystem.GetEntitiesIntersectingBody(target, (int) collisionMask, false, physics));
} }
else if (_wallMountQuery.TryComp(target, out var wallMount)) else if (TryComp(target, out WallMountComponent? wallMount))
{ {
// wall-mount exemptions may be restricted to a specific angle range.da // wall-mount exemptions may be restricted to a specific angle range.da
@@ -812,7 +755,13 @@ namespace Content.Shared.Interaction
ignored.UnionWith(grid.GetAnchoredEntities(targetCoords)); ignored.UnionWith(grid.GetAnchoredEntities(targetCoords));
} }
Ignored combinedPredicate = e => e == target || (predicate?.Invoke(e) ?? false) || ignored.Contains(e); Ignored combinedPredicate = e =>
{
return e == target
|| (predicate?.Invoke(e) ?? false)
|| ignored.Contains(e);
};
return combinedPredicate; return combinedPredicate;
} }
@@ -1009,8 +958,10 @@ namespace Content.Shared.Interaction
bool checkUseDelay = true, bool checkUseDelay = true,
bool checkAccess = true) bool checkAccess = true)
{ {
_delayQuery.TryComp(used, out var delayComponent); UseDelayComponent? delayComponent = null;
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent))) if (checkUseDelay
&& TryComp(used, out delayComponent)
&& _useDelay.IsDelayed((used, delayComponent)))
return false; return false;
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
@@ -1021,11 +972,11 @@ namespace Content.Shared.Interaction
// Check if interacted entity is in the same container, the direct child, or direct parent of the user. // 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) // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI)
if (checkAccess && !IsAccessible(user, used)) if (checkAccess && !_containerSystem.IsInSameOrParentContainer(user, used) && !CanAccessViaStorage(user, used))
return false; return false;
// Does the user have hands? // Does the user have hands?
if (!_handsQuery.HasComp(user)) if (!HasComp<HandsComponent>(user))
return false; return false;
var activateMsg = new ActivateInWorldEvent(user, used); var activateMsg = new ActivateInWorldEvent(user, used);
@@ -1035,9 +986,7 @@ namespace Content.Shared.Interaction
DoContactInteraction(user, used, activateMsg); DoContactInteraction(user, used, activateMsg);
// Still need to call this even without checkUseDelay in case this gets relayed from Activate. // Still need to call this even without checkUseDelay in case this gets relayed from Activate.
if (delayComponent != null) _useDelay.TryResetDelay(used, component: delayComponent);
_useDelay.TryResetDelay(used, component: delayComponent);
if (!activateMsg.WasLogged) if (!activateMsg.WasLogged)
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}"); _adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
return true; return true;
@@ -1058,8 +1007,11 @@ namespace Content.Shared.Interaction
bool checkCanInteract = true, bool checkCanInteract = true,
bool checkUseDelay = true) bool checkUseDelay = true)
{ {
_delayQuery.TryComp(used, out var delayComponent); UseDelayComponent? delayComponent = null;
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
if (checkUseDelay
&& TryComp(used, out delayComponent)
&& _useDelay.IsDelayed((used, delayComponent)))
return true; // if the item is on cooldown, we consider this handled. return true; // if the item is on cooldown, we consider this handled.
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used)) if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
@@ -1121,60 +1073,11 @@ namespace Content.Shared.Interaction
} }
#endregion #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> /// <summary>
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This /// 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. /// checks if the user can access the item in these situations.
/// </summary> /// </summary>
public bool CanAccessViaStorage(EntityUid user, EntityUid target) public abstract 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(container.Owner, StorageComponent.StorageUiKey.Key, user);
}
/// <summary> /// <summary>
/// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't /// Checks whether an entity currently equipped by another player is accessible to some user. This shouldn't
@@ -1255,15 +1158,19 @@ namespace Content.Shared.Interaction
RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA)); RaiseLocalEvent(uidB.Value, new ContactInteractionEvent(uidA));
} }
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev) private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
{ {
if (ev.Result == BoundUserInterfaceRangeResult.Fail) if (ev.Result == BoundUserInterfaceRangeResult.Fail)
return; return;
ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange) if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange))
? BoundUserInterfaceRangeResult.Pass {
: BoundUserInterfaceRangeResult.Fail; ev.Result = BoundUserInterfaceRangeResult.Pass;
}
else
{
ev.Result = BoundUserInterfaceRangeResult.Fail;
}
} }
} }

View File

@@ -210,7 +210,11 @@ public abstract partial class InventorySystem
return false; return false;
// Can the actor reach the item? // Can the actor reach the item?
if (_interactionSystem.InRangeAndAccessible(actor, itemUid)) 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))
return true; return true;
// Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but // Is the actor currently stripping the target? Here we could check if the actor has the stripping UI open, but

View File

@@ -9,7 +9,6 @@ using Content.Shared.IdentityManagement;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Content.Shared.UserInterface;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Wires; using Content.Shared.Wires;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -25,7 +24,6 @@ namespace Content.Shared.Lock;
public sealed class LockSystem : EntitySystem public sealed class LockSystem : EntitySystem
{ {
[Dependency] private readonly AccessReaderSystem _accessReader = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
@@ -48,9 +46,6 @@ public sealed class LockSystem : EntitySystem
SubscribeLocalEvent<LockedWiresPanelComponent, LockToggleAttemptEvent>(OnLockToggleAttempt); SubscribeLocalEvent<LockedWiresPanelComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel); SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt); SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, LockToggledEvent>(LockToggled);
} }
private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args)
@@ -352,25 +347,4 @@ public sealed class LockSystem : EntitySystem
args.User); args.User);
args.Cancel(); args.Cancel();
} }
}
private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled)
return;
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.RequireLocked)
{
args.Cancel();
if (lockComp.Locked)
_sharedPopupSystem.PopupEntity(Loc.GetString("entity-storage-component-locked-message"), uid, args.User);
}
}
private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args)
{
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
return;
_activatableUI.CloseAll(uid);
}
}

View File

@@ -2,7 +2,6 @@ using Content.Shared.DoAfter;
using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Markings;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.MagicMirror; namespace Content.Shared.MagicMirror;
@@ -18,13 +17,10 @@ public abstract class SharedMagicMirrorSystem : EntitySystem
private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args) private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args)
{ {
if (args.Result == BoundUserInterfaceRangeResult.Fail) if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value))
return; {
DebugTools.Assert(component.Target != null && Exists(component.Target));
if (!_interaction.InRangeUnobstructed(uid, component.Target.Value))
args.Result = BoundUserInterfaceRangeResult.Fail; args.Result = BoundUserInterfaceRangeResult.Fail;
}
} }
} }

View File

@@ -344,17 +344,14 @@ public sealed class PullingSystem : EntitySystem
return !startPull.Cancelled && !getPulled.Cancelled; return !startPull.Cancelled && !getPulled.Cancelled;
} }
public bool TogglePull(Entity<PullableComponent?> pullable, EntityUid pullerUid) public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable)
{ {
if (!Resolve(pullable, ref pullable.Comp, false)) if (pullable.Puller == pullerUid)
return false;
if (pullable.Comp.Puller == pullerUid)
{ {
return TryStopPull(pullable, pullable.Comp); return TryStopPull(pullableUid, pullable);
} }
return TryStartPull(pullerUid, pullable, pullableComp: pullable); return TryStartPull(pullerUid, pullableUid, pullableComp: pullable);
} }
public bool TogglePull(EntityUid pullerUid, PullerComponent puller) public bool TogglePull(EntityUid pullerUid, PullerComponent puller)
@@ -362,7 +359,7 @@ public sealed class PullingSystem : EntitySystem
if (!TryComp<PullableComponent>(puller.Pulling, out var pullable)) if (!TryComp<PullableComponent>(puller.Pulling, out var pullable))
return false; return false;
return TogglePull((puller.Pulling.Value, pullable), pullerUid); return TogglePull(puller.Pulling.Value, pullerUid, pullable);
} }
public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid,

View File

@@ -96,7 +96,7 @@ public abstract class SharedNavMapSystem : EntitySystem
chunks.Add(origin, chunk.TileData); chunks.Add(origin, chunk.TileData);
} }
args.State = new NavMapState(chunks, component.Beacons); args.State = new NavMapComponentState(chunks, component.Beacons);
return; return;
} }
@@ -109,7 +109,12 @@ public abstract class SharedNavMapSystem : EntitySystem
chunks.Add(origin, chunk.TileData); chunks.Add(origin, chunk.TileData);
} }
args.State = new NavMapDeltaState(chunks, component.Beacons, new(component.Chunks.Keys)); 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),
};
} }
#endregion #endregion
@@ -117,35 +122,32 @@ public abstract class SharedNavMapSystem : EntitySystem
#region: System messages #region: System messages
[Serializable, NetSerializable] [Serializable, NetSerializable]
protected sealed class NavMapState( protected sealed class NavMapComponentState(
Dictionary<Vector2i, int[]> chunks, Dictionary<Vector2i, int[]> chunks,
Dictionary<NetEntity, NavMapBeacon> beacons) Dictionary<NetEntity, NavMapBeacon> beacons)
: ComponentState : ComponentState, IComponentDeltaState
{ {
public Dictionary<Vector2i, int[]> Chunks = chunks; public Dictionary<Vector2i, int[]> Chunks = chunks;
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons; public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
}
[Serializable, NetSerializable] // Required to infer deleted/missing chunks for delta states
protected sealed class NavMapDeltaState( public HashSet<Vector2i>? AllChunks;
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 void ApplyToFullState(NavMapState state) public bool FullState => AllChunks == null;
public void ApplyToFullState(IComponentState fullState)
{ {
DebugTools.Assert(!FullState);
var state = (NavMapComponentState) fullState;
DebugTools.Assert(state.FullState);
foreach (var key in state.Chunks.Keys) foreach (var key in state.Chunks.Keys)
{ {
if (!AllChunks!.Contains(key)) if (!AllChunks!.Contains(key))
state.Chunks.Remove(key); state.Chunks.Remove(key);
} }
foreach (var (index, data) in ModifiedChunks) foreach (var (index, data) in Chunks)
{ {
if (!state.Chunks.TryGetValue(index, out var stateValue)) if (!state.Chunks.TryGetValue(index, out var stateValue))
state.Chunks[index] = stateValue = new int[data.Length]; state.Chunks[index] = stateValue = new int[data.Length];
@@ -160,8 +162,12 @@ public abstract class SharedNavMapSystem : EntitySystem
} }
} }
public NavMapState CreateNewFullState(NavMapState state) public IComponentState CreateNewFullState(IComponentState fullState)
{ {
DebugTools.Assert(!FullState);
var state = (NavMapComponentState) fullState;
DebugTools.Assert(state.FullState);
var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count); var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
foreach (var (index, data) in state.Chunks) foreach (var (index, data) in state.Chunks)
{ {
@@ -170,13 +176,13 @@ public abstract class SharedNavMapSystem : EntitySystem
var newData = chunks[index] = new int[ArraySize]; var newData = chunks[index] = new int[ArraySize];
if (ModifiedChunks.TryGetValue(index, out var updatedData)) if (Chunks.TryGetValue(index, out var updatedData))
Array.Copy(newData, updatedData, ArraySize); Array.Copy(newData, updatedData, ArraySize);
else else
Array.Copy(newData, data, ArraySize); Array.Copy(newData, data, ArraySize);
} }
return new NavMapState(chunks, new(Beacons)); return new NavMapComponentState(chunks, new(Beacons));
} }
} }

View File

@@ -74,19 +74,18 @@ public sealed class RoleLoadout
{ {
var loadout = loadouts[i]; var loadout = loadouts[i];
// Old prototype or otherwise invalid.
if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto)) if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto))
{ {
loadouts.RemoveAt(i); loadouts.RemoveAt(i);
continue; continue;
} }
// Malicious client maybe, check the group even has it. // Похуй FIXME
if (!groupProto.Loadouts.Contains(loadout.Prototype)) //if (!IsValid(profile, session, loadout.Prototype, collection, out _))
{ // {
loadouts.RemoveAt(i); // loadouts.RemoveAt(i);
continue; // continue;
} // }
Apply(loadoutProto); Apply(loadoutProto);
} }

View File

@@ -6,7 +6,6 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.PowerCell.Components; using Content.Shared.PowerCell.Components;
using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.UserInterface;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -38,8 +37,6 @@ public abstract partial class SharedBorgSystem : EntitySystem
SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted); SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved); SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved);
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers); SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
//Honk //Honk
SubscribeLocalEvent<SharedTTSComponent, ComponentInit>(RandomTTS); SubscribeLocalEvent<SharedTTSComponent, ComponentInit>(RandomTTS);
@@ -110,13 +107,6 @@ public abstract partial class SharedBorgSystem : EntitySystem
component.ModuleContainer = Container.EnsureContainer<Container>(uid, component.ModuleContainerId, containerManager); component.ModuleContainer = Container.EnsureContainer<Container>(uid, component.ModuleContainerId, containerManager);
} }
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
{
// borgs can't view their own ui
if (args.User == uid)
args.Cancel();
}
protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args) protected virtual void OnInserted(EntityUid uid, BorgChassisComponent component, EntInsertedIntoContainerMessage args)
{ {

View File

@@ -25,7 +25,6 @@ using Content.Shared.Stacks;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Content.Shared.Timing; using Content.Shared.Timing;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -157,9 +156,7 @@ public abstract class SharedStorageSystem : EntitySystem
Grid = new List<Box2i>(component.Grid), Grid = new List<Box2i>(component.Grid),
MaxItemSize = component.MaxItemSize, MaxItemSize = component.MaxItemSize,
StoredItems = storedItems, StoredItems = storedItems,
SavedLocations = component.SavedLocations, SavedLocations = component.SavedLocations
Whitelist = component.Whitelist,
Blacklist = component.Blacklist
}; };
} }
@@ -171,8 +168,6 @@ public abstract class SharedStorageSystem : EntitySystem
component.Grid.Clear(); component.Grid.Clear();
component.Grid.AddRange(state.Grid); component.Grid.AddRange(state.Grid);
component.MaxItemSize = state.MaxItemSize; component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.StoredItems.Clear(); component.StoredItems.Clear();
@@ -1105,7 +1100,7 @@ public abstract class SharedStorageSystem : EntitySystem
/// <returns>true if inserted, false otherwise</returns> /// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert) public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert)
{ {
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner)) if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid))
return false; return false;
if (!Insert(uid, toInsert, out _, user: player, uid.Comp)) if (!Insert(uid, toInsert, out _, user: player, uid.Comp))
@@ -1506,9 +1501,5 @@ public abstract class SharedStorageSystem : EntitySystem
public List<Box2i> Grid = new(); public List<Box2i> Grid = new();
public ProtoId<ItemSizePrototype>? MaxItemSize; public ProtoId<ItemSizePrototype>? MaxItemSize;
public EntityWhitelist? Whitelist;
public EntityWhitelist? Blacklist;
} }
} }

View File

@@ -1,31 +1,17 @@
using Content.Shared.CombatMode;
using Content.Shared.DragDrop; using Content.Shared.DragDrop;
using Content.Shared.Hands.Components; using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Strip.Components; using Content.Shared.Strip.Components;
namespace Content.Shared.Strip; namespace Content.Shared.Strip;
public abstract class SharedStrippableSystem : EntitySystem public abstract class SharedStrippableSystem : EntitySystem
{ {
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<StrippingComponent, CanDropTargetEvent>(OnCanDropOn); SubscribeLocalEvent<StrippingComponent, CanDropTargetEvent>(OnCanDropOn);
SubscribeLocalEvent<StrippableComponent, CanDropDraggedEvent>(OnCanDrop); SubscribeLocalEvent<StrippableComponent, CanDropDraggedEvent>(OnCanDrop);
SubscribeLocalEvent<StrippableComponent, DragDropDraggedEvent>(OnDragDrop); SubscribeLocalEvent<StrippableComponent, DragDropDraggedEvent>(OnDragDrop);
SubscribeLocalEvent<StrippableComponent, ActivateInWorldEvent>(OnActivateInWorld);
}
private void OnActivateInWorld(EntityUid uid, StrippableComponent component, ActivateInWorldEvent args)
{
if (args.Handled || args.Target == args.User)
return;
if (TryOpenStrippingUi(args.User, (uid, component)))
args.Handled = true;
} }
public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime) public (TimeSpan Time, bool Stealth) GetStripTimeModifiers(EntityUid user, EntityUid target, TimeSpan initialTime)
@@ -43,20 +29,13 @@ public abstract class SharedStrippableSystem : EntitySystem
if (args.Handled || args.Target != args.User) if (args.Handled || args.Target != args.User)
return; return;
if (TryOpenStrippingUi(args.User, (uid, component))) StartOpeningStripper(args.User, (uid, component));
args.Handled = true; args.Handled = true;
} }
public bool TryOpenStrippingUi(EntityUid user, Entity<StrippableComponent> target, bool openInCombat = false) public virtual void StartOpeningStripper(EntityUid user, Entity<StrippableComponent> component, bool openInCombat = false)
{ {
if (!openInCombat && TryComp<CombatModeComponent>(user, out var mode) && mode.IsInCombatMode)
return false;
if (!HasComp<StrippingComponent>(user))
return false;
_ui.OpenUi(target.Owner, StrippingUiKey.Key, user);
return true;
} }
private void OnCanDropOn(EntityUid uid, StrippingComponent component, ref CanDropTargetEvent args) private void OnCanDropOn(EntityUid uid, StrippingComponent component, ref CanDropTargetEvent args)

View File

@@ -57,7 +57,7 @@ namespace Content.Shared.UserInterface
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField] [DataField]
public bool BlockSpectators; public bool AllowSpectator = true;
/// <summary> /// <summary>
/// Whether the item must be in the user's currently selected/active hand. /// Whether the item must be in the user's currently selected/active hand.

View File

@@ -8,7 +8,7 @@ using Content.Shared.Interaction;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.Utility; using Robust.Shared.Containers;
namespace Content.Shared.UserInterface; namespace Content.Shared.UserInterface;
@@ -19,12 +19,15 @@ public sealed partial class ActivatableUISystem : EntitySystem
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!; [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
private readonly List<EntityUid> _toClose = new();
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ActivatableUIComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand); SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate); SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing); SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
@@ -34,24 +37,28 @@ public sealed partial class ActivatableUISystem : EntitySystem
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb); SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb);
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<Verb>>(GetVerb); SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<Verb>>(GetVerb);
// TODO ActivatableUI
// Add UI-user component, and listen for user container changes.
// I.e., should lose a computer UI if a player gets shut into a locker.
SubscribeLocalEvent<ActivatableUIComponent, EntGotInsertedIntoContainerMessage>(OnGotInserted);
SubscribeLocalEvent<ActivatableUIComponent, EntGotRemovedFromContainerMessage>(OnGotRemoved);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
SubscribeLocalEvent<UserInterfaceComponent, OpenUiActionEvent>(OnActionPerform); SubscribeLocalEvent<UserInterfaceComponent, OpenUiActionEvent>(OnActionPerform);
InitializePower(); InitializePower();
} }
private void OnStartup(Entity<ActivatableUIComponent> ent, ref ComponentStartup args) private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{ {
if (ent.Comp.Key == null) if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
{
Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}");
return; return;
}
// TODO BUI if (!comp.RequireHands)
// set interaction range to zero to avoid constant range checks. return;
//
// if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data)) if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0)
// data.InteractionRange = 0; ev.Cancel();
} }
private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args) private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args)
@@ -70,10 +77,9 @@ public sealed partial class ActivatableUISystem : EntitySystem
args.Verbs.Add(new ActivationVerb args.Verbs.Add(new ActivationVerb
{ {
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component), Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText), Text = Loc.GetString(component.VerbText)
// TODO VERB ICON find a better icon
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
}); });
} }
@@ -84,10 +90,9 @@ public sealed partial class ActivatableUISystem : EntitySystem
args.Verbs.Add(new Verb args.Verbs.Add(new Verb
{ {
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component), Act = () => InteractUI(args.User, uid, component),
Text = Loc.GetString(component.VerbText), Text = Loc.GetString(component.VerbText)
// TODO VERB ICON find a better icon
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
}); });
} }
@@ -114,7 +119,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
} }
} }
return args.CanInteract || HasComp<GhostComponent>(args.User) && !component.BlockSpectators; return args.CanInteract || component.AllowSpectator && HasComp<GhostComponent>(args.User);
} }
private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args) private void OnUseInHand(EntityUid uid, ActivatableUIComponent component, UseInHandEvent args)
@@ -186,7 +191,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
return true; return true;
} }
if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators)) if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
return false; return false;
if (aui.RequireHands) if (aui.RequireHands)
@@ -281,4 +286,47 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly) if (ent.Comp.RequireHands && ent.Comp.InHandsOnly)
CloseAll(ent, ent); CloseAll(ent, ent);
} }
private void OnGotInserted(Entity<ActivatableUIComponent> ent, ref EntGotInsertedIntoContainerMessage args)
{
CheckAccess((ent, ent));
}
private void OnGotRemoved(Entity<ActivatableUIComponent> ent, ref EntGotRemovedFromContainerMessage args)
{
CheckAccess((ent, ent));
}
public void CheckAccess(Entity<ActivatableUIComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp))
return;
if (ent.Comp.Key == null)
{
Log.Error($"Encountered null key in activatable ui on entity {ToPrettyString(ent)}");
return;
}
foreach (var user in _uiSystem.GetActors(ent.Owner, ent.Comp.Key))
{
if (!_container.IsInSameOrParentContainer(user, ent)
&& !_interaction.CanAccessViaStorage(user, ent))
{
_toClose.Add(user);
continue;
}
if (!_interaction.InRangeUnobstructed(user, ent))
_toClose.Add(user);
}
foreach (var user in _toClose)
{
_uiSystem.CloseUi(ent.Owner, ent.Comp.Key, user);
}
_toClose.Clear();
}
} }

View File

@@ -72,7 +72,19 @@ namespace Content.Shared.Verbs
extraCategories = new(); extraCategories = new();
// accessibility checks // accessibility checks
var canAccess = force || _interactionSystem.InRangeAndAccessible(user, target); bool canAccess = false;
if (force || target == user)
canAccess = true;
else if (_interactionSystem.InRangeUnobstructed(user, target))
{
// Note that being in a container does not count as an obstruction for InRangeUnobstructed
// Therefore, we need extra checks to ensure the item is actually accessible:
if (ContainerSystem.IsInSameOrParentContainer(user, target))
canAccess = true;
else
// the item might be in a backpack that the user has open
canAccess = _interactionSystem.CanAccessViaStorage(user, target);
}
// A large number of verbs need to check action blockers. Instead of repeatedly having each system individually // A large number of verbs need to check action blockers. Instead of repeatedly having each system individually
// call ActionBlocker checks, just cache it for the verb request. // call ActionBlocker checks, just cache it for the verb request.

View File

@@ -1,8 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Shared._White;
using Content.Shared._White.Implants.NeuroControl;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
@@ -15,7 +13,6 @@ using Content.Shared.Hands.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Movement.Components;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Weapons.Melee.Components; using Content.Shared.Weapons.Melee.Components;
@@ -23,6 +20,9 @@ using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems; using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared._White;
using Content.Shared._White.Implants.NeuroControl;
using Content.Shared.Movement.Components;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Physics; using Robust.Shared.Physics;
@@ -205,39 +205,49 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args)
{ {
if (args.SenderSession.AttachedEntity is not {} user) var user = args.SenderSession.AttachedEntity;
if (user == null)
return; return;
if (!TryGetWeapon(user, out var weaponUid, out var weapon) || if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) ||
weaponUid != GetEntity(msg.Weapon)) weaponUid != GetEntity(msg.Weapon))
{ {
return; return;
} }
AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession);
} }
private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args)
{ {
if (args.SenderSession.AttachedEntity is not {} user) if (args.SenderSession.AttachedEntity == null)
{
return; return;
}
if (!TryGetWeapon(user, out var weaponUid, out var weapon) || if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) ||
weaponUid != GetEntity(msg.Weapon)) weaponUid != GetEntity(msg.Weapon))
{ {
return; return;
} }
AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession);
} }
private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args) private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args)
{ {
if (args.SenderSession.AttachedEntity is not {} user) if (args.SenderSession.AttachedEntity == null)
{
return; return;
}
if (TryGetWeapon(user, out var weaponUid, out var weapon)) if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon))
AttemptAttack(user, weaponUid, weapon, msg, args.SenderSession); {
return;
}
AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession);
} }
/// <summary> /// <summary>
@@ -371,22 +381,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
return false; return false;
var update = UpdateNextAttack.Both; // WD var update = UpdateNextAttack.Both; // WD
EntityUid? target = null;
switch (attack) switch (attack)
{ {
case LightAttackEvent light: case LightAttackEvent light:
var lightTarget = GetEntity(light.Target); var lightTarget = GetEntity(light.Target);
if (light.Target != null && !TryGetEntity(light.Target, out target)) update = lightTarget == null ? UpdateNextAttack.Both :
{ IsMob(lightTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD
// Target was lightly attacked & deleted.
return false;
}
if (!Blocker.CanAttack(user, target, (weaponUid, weapon))) if (!Blocker.CanAttack(user, lightTarget, (weaponUid, weapon)))
return false; return false;
// Can't self-attack if you're the weapon // Can't self-attack if you're the weapon
if (weaponUid == target) if (weaponUid == lightTarget)
return false; return false;
// WD START // WD START
@@ -415,13 +421,11 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
break; break;
case DisarmAttackEvent disarm: case DisarmAttackEvent disarm:
if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) var disarmTarget = GetEntity(disarm.Target);
{ update = disarmTarget == null ? UpdateNextAttack.Both :
// Target was lightly attacked & deleted. IsMob(disarmTarget.Value) ? UpdateNextAttack.Mob : UpdateNextAttack.NonMob; // WD
return false;
}
if (!Blocker.CanAttack(user, target, (weaponUid, weapon), true)) if (!Blocker.CanAttack(user, disarmTarget, (weaponUid, weapon), true))
return false; return false;
break; break;
default: default:

View File

@@ -3,7 +3,6 @@ using Content.Shared.Database;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Tools.Systems; using Content.Shared.Tools.Systems;
using Content.Shared.UserInterface;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
namespace Content.Shared.Wires; namespace Content.Shared.Wires;
@@ -11,7 +10,6 @@ namespace Content.Shared.Wires;
public abstract class SharedWiresSystem : EntitySystem public abstract class SharedWiresSystem : EntitySystem
{ {
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!; [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!; [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!; [Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] protected readonly SharedToolSystem Tool = default!; [Dependency] protected readonly SharedToolSystem Tool = default!;
@@ -23,9 +21,6 @@ public abstract class SharedWiresSystem : EntitySystem
SubscribeLocalEvent<WiresPanelComponent, WirePanelDoAfterEvent>(OnPanelDoAfter); SubscribeLocalEvent<WiresPanelComponent, WirePanelDoAfterEvent>(OnPanelDoAfter);
SubscribeLocalEvent<WiresPanelComponent, InteractUsingEvent>(OnInteractUsing); SubscribeLocalEvent<WiresPanelComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<WiresPanelComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<WiresPanelComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, PanelChangedEvent>(OnActivatableUIPanelChanged);
} }
private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args) private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args)
@@ -137,21 +132,4 @@ public abstract class SharedWiresSystem : EntitySystem
return entity.Comp.Open; return entity.Comp.Open;
} }
private void OnAttemptOpenActivatableUI(EntityUid uid, ActivatableUIRequiresPanelComponent component, ActivatableUIOpenAttemptEvent args)
{
if (args.Cancelled || !TryComp<WiresPanelComponent>(uid, out var wires))
return;
if (component.RequireOpen != wires.Open)
args.Cancel();
}
private void OnActivatableUIPanelChanged(EntityUid uid, ActivatableUIRequiresPanelComponent component, ref PanelChangedEvent args)
{
if (args.Open == component.RequireOpen)
return;
_activatableUI.CloseAll(uid);
}
} }

View File

@@ -6,6 +6,7 @@ objectives-round-end-result = {$count ->
objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL($agent)} were in custody. objectives-round-end-result-in-custody = {$custody} out of {$count} {MAKEPLURAL($agent)} were in custody.
objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color]) objectives-player-user-named = [color=White]{$name}[/color] ([color=gray]{$user}[/color])
objectives-player-user = [color=gray]{$user}[/color]
objectives-player-named = [color=White]{$name}[/color] objectives-player-named = [color=White]{$name}[/color]
objectives-no-objectives = {$custody}{$title} was a {$agent}. objectives-no-objectives = {$custody}{$title} was a {$agent}.

View File

@@ -133,7 +133,7 @@
icon: icon:
sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi sprite: Structures/Piping/Atmospherics/Portable/portable_sheater.rsi
state: sheaterOff state: sheaterOff
product: CrateEngineeringSpaceHeater product: SpaceHeaterAnchored
cost: 300 cost: 300
category: cargoproduct-category-name-engineering category: cargoproduct-category-name-engineering
group: market group: market

View File

@@ -194,13 +194,3 @@
contents: contents:
- id: WeaponParticleDecelerator - id: WeaponParticleDecelerator
amount: 3 amount: 3
- type: entity
id: CrateEngineeringSpaceHeater
parent: CrateEngineering
name: space heater crate
description: Contains a space heater for climate control.
components:
- type: StorageFill
contents:
- id: SpaceHeaterFlatpack

View File

@@ -42,7 +42,7 @@
components: components:
- type: StorageFill - type: StorageFill
contents: contents:
- id: EmitterFlatpack - id: Emitter # TODO change to flatpack
- type: entity - type: entity
id: CrateEngineeringSingularityCollector id: CrateEngineeringSingularityCollector

View File

@@ -1,7 +1,6 @@
- type: entity - type: entity
id: MobSpawnCrabQuartz id: MobSpawnCrabQuartz
name: mobspawner quartzcrab name: mobspawner quartzcrab
noSpawn: True
components: components:
- type: Transform - type: Transform
anchored: True anchored: True
@@ -31,7 +30,6 @@
id: MobSpawnCrabIron id: MobSpawnCrabIron
parent: MobSpawnCrabQuartz parent: MobSpawnCrabQuartz
name: mobspawner ironcrab name: mobspawner ironcrab
noSpawn: True
components: components:
- type: Sprite - type: Sprite
sprite: /Textures/Effects/mobspawn.rsi sprite: /Textures/Effects/mobspawn.rsi
@@ -43,7 +41,6 @@
id: MobSpawnCrabSilver id: MobSpawnCrabSilver
parent: MobSpawnCrabQuartz parent: MobSpawnCrabQuartz
name: mobspawner silvercrab name: mobspawner silvercrab
noSpawn: True
components: components:
- type: Sprite - type: Sprite
sprite: /Textures/Effects/mobspawn.rsi sprite: /Textures/Effects/mobspawn.rsi
@@ -55,7 +52,6 @@
id: MobSpawnCrabUranium id: MobSpawnCrabUranium
parent: MobSpawnCrabQuartz parent: MobSpawnCrabQuartz
name: mobspawner uraniumcrab name: mobspawner uraniumcrab
noSpawn: True
components: components:
- type: Sprite - type: Sprite
sprite: /Textures/Effects/mobspawn.rsi sprite: /Textures/Effects/mobspawn.rsi

View File

@@ -1,6 +1,5 @@
- type: entity - type: entity
id: WallSpawnAsteroid id: WallSpawnAsteroid
noSpawn: True
components: components:
- type: Transform - type: Transform
anchored: True anchored: True
@@ -29,7 +28,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidUraniumCrab id: WallSpawnAsteroidUraniumCrab
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockUraniumCrab prototype: AsteroidRockUraniumCrab
@@ -37,7 +35,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidUranium id: WallSpawnAsteroidUranium
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockUranium prototype: AsteroidRockUranium
@@ -45,7 +42,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidQuartzCrab id: WallSpawnAsteroidQuartzCrab
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockQuartzCrab prototype: AsteroidRockQuartzCrab
@@ -53,7 +49,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidQuartz id: WallSpawnAsteroidQuartz
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockQuartz prototype: AsteroidRockQuartz
@@ -61,7 +56,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidSilverCrab id: WallSpawnAsteroidSilverCrab
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockSilverCrab prototype: AsteroidRockSilverCrab
@@ -69,7 +63,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidSilver id: WallSpawnAsteroidSilver
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockSilver prototype: AsteroidRockSilver
@@ -77,7 +70,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidIronCrab id: WallSpawnAsteroidIronCrab
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockTinCrab prototype: AsteroidRockTinCrab
@@ -85,7 +77,6 @@
- type: entity - type: entity
id: WallSpawnAsteroidIron id: WallSpawnAsteroidIron
parent: WallSpawnAsteroid parent: WallSpawnAsteroid
noSpawn: True
components: components:
- type: SpawnOnDespawn - type: SpawnOnDespawn
prototype: AsteroidRockTin prototype: AsteroidRockTin

View File

@@ -1,92 +0,0 @@
# Most of these have DO NOT MAP, since stations are completely unequipped to deal with ship combat + these are basically placeholder.
- type: entity
id: ShuttleGunSvalinnMachineGunCircuitboard
parent: BaseMachineCircuitboard
name: LSE-400c "Svalinn machine gun" machine board
description: A machine printed circuit board for an LSE-400c "Svalinn machine gun"
suffix: DO NOT MAP, Machine Board
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunSvalinnMachineGun
requirements:
MatterBin: 2
Manipulator: 4
materialRequirements:
Steel: 5
CableHV: 5
- type: entity
id: ShuttleGunPerforatorCircuitboard
parent: BaseMachineCircuitboard
name: LSE-1200c "Perforator" machine board
description: A machine printed circuit board for an LSE-1200c "Perforator"
suffix: DO NOT MAP, Machine Board
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunPerforator
requirements:
MatterBin: 4
Manipulator: 6
materialRequirements:
Steel: 10
CableHV: 5
- type: entity
id: ShuttleGunFriendshipCircuitboard
parent: BaseMachineCircuitboard
name: EXP-320g "Friendship" machine board
description: A machine printed circuit board for an EXP-320g "Friendship"
suffix: DO NOT MAP, Machine Board
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunFriendship
requirements:
MatterBin: 3
Manipulator: 2
materialRequirements:
Steel: 7
CableHV: 5
- type: entity
id: ShuttleGunDusterCircuitboard
parent: BaseMachineCircuitboard
name: EXP-2100g "Duster" machine board
description: A machine printed circuit board for an EXP-2100g "Duster"
suffix: DO NOT MAP, Machine Board
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunDuster
requirements:
MatterBin: 6
Manipulator: 4
materialRequirements:
Steel: 10
CableHV: 5
Uranium: 2
- type: entity
id: ShuttleGunKineticCircuitboard
parent: BaseMachineCircuitboard
name: PTK-800 "Matter Dematerializer" machine board
description: A machine printed circuit board for an PTK-800 "Matter Dematerializer"
suffix: DO NOT MAP, Machine Board
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunKinetic
requirements:
MatterBin: 2
Manipulator: 3
materialRequirements:
Steel: 5
CableHV: 2

View File

@@ -1277,6 +1277,92 @@
CableHV: 5 CableHV: 5
Uranium: 2 Uranium: 2
- type: entity
id: ShuttleGunSvalinnMachineGunCircuitboard
parent: BaseMachineCircuitboard
name: LSE-400c "Svalinn machine gun" machine board
description: A machine printed circuit board for an LSE-400c "Svalinn machine gun"
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunSvalinnMachineGun
requirements:
MatterBin: 2
Manipulator: 4
materialRequirements:
Steel: 5
CableHV: 5
- type: entity
id: ShuttleGunPerforatorCircuitboard
parent: BaseMachineCircuitboard
name: LSE-1200c "Perforator" machine board
description: A machine printed circuit board for an LSE-1200c "Perforator"
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunPerforator
requirements:
MatterBin: 4
Manipulator: 6
materialRequirements:
Steel: 10
CableHV: 5
- type: entity
id: ShuttleGunFriendshipCircuitboard
parent: BaseMachineCircuitboard
name: EXP-320g "Friendship" machine board
description: A machine printed circuit board for an EXP-320g "Friendship"
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunFriendship
requirements:
MatterBin: 3
Manipulator: 2
materialRequirements:
Steel: 7
CableHV: 5
- type: entity
id: ShuttleGunDusterCircuitboard
parent: BaseMachineCircuitboard
name: EXP-2100g "Duster" machine board
description: A machine printed circuit board for an EXP-2100g "Duster"
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunDuster
requirements:
MatterBin: 6
Manipulator: 4
materialRequirements:
Steel: 10
CableHV: 5
Uranium: 2
- type: entity
id: ShuttleGunKineticCircuitboard
parent: BaseMachineCircuitboard
name: PTK-800 "Matter Dematerializer" machine board
description: A machine printed circuit board for an PTK-800 "Matter Dematerializer"
components:
- type: Sprite
state: security
- type: MachineBoard
prototype: ShuttleGunKinetic
requirements:
MatterBin: 2
Manipulator: 3
materialRequirements:
Steel: 5
CableHV: 2
- type: entity - type: entity
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
id: ReagentGrinderIndustrialMachineCircuitboard id: ReagentGrinderIndustrialMachineCircuitboard

View File

@@ -184,12 +184,3 @@
- state: overlay - state: overlay
color: "#cec8ac" color: "#cec8ac"
- state: icon-default - state: icon-default
- type: entity
parent: BaseFlatpack
id: SpaceHeaterFlatpack
name: space heater flatpack
description: A flatpack used for constructing a space heater.
components:
- type: Flatpack
entity: SpaceHeaterAnchored

View File

@@ -29,7 +29,7 @@
components: components:
- type: Instrument - type: Instrument
- type: ActivatableUI - type: ActivatableUI
blockSpectators: true # otherwise they can play client-side music allowSpectator: false # otherwise they can play client-side music
inHandsOnly: false inHandsOnly: false
singleUser: true singleUser: true
requireHands: true requireHands: true

View File

@@ -126,8 +126,8 @@
maxVol: 7 maxVol: 7
- type: SolutionInjectOnEmbed - type: SolutionInjectOnEmbed
transferAmount: 7 transferAmount: 7
blockSlots: NONE
solution: melee solution: melee
blockSlots: NONE
- type: SolutionTransfer - type: SolutionTransfer
maxTransferAmount: 7 maxTransferAmount: 7

View File

@@ -325,12 +325,11 @@
emptyCase: { state: empty } emptyCase: { state: empty }
wiredCase: { state: wired } wiredCase: { state: wired }
caseWithTrigger: { state: no-payload } caseWithTrigger: { state: no-payload }
caseWithPayload: { state: no-trigger }
grenade: { state: complete } grenade: { state: complete }
enum.Trigger.TriggerVisuals.VisualState: enum.Trigger.TriggerVisuals.VisualState:
enum.ConstructionVisuals.Layer: enum.ConstructionVisuals.Layer:
Primed: { state: primed } Primed: { state: primed }
# Unprimed: <Use state determined by enum.ConstructionVisuals.Layer> Unprimed: { state: complete }
- type: StaticPrice - type: StaticPrice
price: 25 price: 25

View File

@@ -1,5 +1,3 @@
# Most of these have DO NOT MAP, since stations are completely unequipped to deal with ship combat + these are basically placeholder.
- type: entity - type: entity
id: ShuttleGunBase id: ShuttleGunBase
name: shittle gun name: shittle gun
@@ -60,8 +58,7 @@
id: ShuttleGunSvalinnMachineGun id: ShuttleGunSvalinnMachineGun
parent: [ ShuttleGunBase, ConstructibleMachine] parent: [ ShuttleGunBase, ConstructibleMachine]
name: LSE-400c "Svalinn machine gun" name: LSE-400c "Svalinn machine gun"
description: Basic stationary laser unit. Effective against live targets and electronics. Uses regular power cells to fire, and has an extremely high rate of fire. description: Basic stationary laser unit. Effective against live targets and electronics. Uses regular power cells to fire, and has an extremely high rate of fire
suffix: DO NOT MAP
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/laser.rsi sprite: Objects/Weapons/Guns/Shuttles/laser.rsi
@@ -116,7 +113,6 @@
parent: [ ShuttleGunBase, ConstructibleMachine] parent: [ ShuttleGunBase, ConstructibleMachine]
name: LSE-1200c "Perforator" name: LSE-1200c "Perforator"
description: Advanced stationary laser unit. Annihilates electronics and is extremely dangerous to health! Uses the power cage to fire. description: Advanced stationary laser unit. Annihilates electronics and is extremely dangerous to health! Uses the power cage to fire.
suffix: DO NOT MAP
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/laser.rsi sprite: Objects/Weapons/Guns/Shuttles/laser.rsi
@@ -173,7 +169,6 @@
parent: [ShuttleGunBase, ConstructibleMachine] parent: [ShuttleGunBase, ConstructibleMachine]
name: EXP-320g "Friendship" name: EXP-320g "Friendship"
description: A small stationary grenade launcher that holds 2 grenades. description: A small stationary grenade launcher that holds 2 grenades.
suffix: DO NOT MAP
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi
@@ -227,7 +222,6 @@
parent: [ShuttleGunBase, ConstructibleMachine] parent: [ShuttleGunBase, ConstructibleMachine]
name: EXP-2100g "Duster" name: EXP-2100g "Duster"
description: A powerful stationary grenade launcher. A cartridge is required for use. description: A powerful stationary grenade launcher. A cartridge is required for use.
suffix: DO NOT MAP
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi
@@ -325,7 +319,6 @@
parent: [ ShuttleGunBase, ConstructibleMachine] parent: [ ShuttleGunBase, ConstructibleMachine]
name: PTK-800 "Matter Dematerializer" name: PTK-800 "Matter Dematerializer"
description: Salvage stationary mining turret. Gradually accumulates charges on its own, extremely effective for asteroid excavation. description: Salvage stationary mining turret. Gradually accumulates charges on its own, extremely effective for asteroid excavation.
suffix: DO NOT MAP
components: components:
- type: Sprite - type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/kinetic.rsi sprite: Objects/Weapons/Guns/Shuttles/kinetic.rsi

View File

@@ -413,9 +413,9 @@
prototype: InitialInfected prototype: InitialInfected
- type: entity - type: entity
noSpawn: true
parent: BaseNukeopsRule
id: LoneOpsSpawn id: LoneOpsSpawn
parent: BaseGameRule
noSpawn: true
components: components:
- type: StationEvent - type: StationEvent
earliestStart: 35 earliestStart: 35
@@ -447,9 +447,9 @@
prototype: Nukeops prototype: Nukeops
- type: entity - type: entity
noSpawn: true
parent: BaseTraitorRule
id: SleeperAgentsRule id: SleeperAgentsRule
parent: BaseGameRule
noSpawn: true
components: components:
- type: StationEvent - type: StationEvent
earliestStart: 30 earliestStart: 30
@@ -460,6 +460,7 @@
startAudio: startAudio:
path: /Audio/Announcements/intercept.ogg path: /Audio/Announcements/intercept.ogg
- type: AlertLevelInterceptionRule - type: AlertLevelInterceptionRule
- type: TraitorRule
- type: AntagSelection - type: AntagSelection
definitions: definitions:
- prefRoles: [ Traitor ] - prefRoles: [ Traitor ]

View File

@@ -35,19 +35,7 @@
id: Thief id: Thief
components: components:
- type: ThiefRule - type: ThiefRule
- type: AntagObjectives
objectives:
- EscapeThiefShuttleObjective
- type: AntagRandomObjectives
sets:
- groups: ThiefBigObjectiveGroups
prob: 0.7
maxPicks: 1
- groups: ThiefObjectiveGroups
maxPicks: 10
maxDifficulty: 2.5
- type: AntagSelection - type: AntagSelection
agentName: thief-round-end-agent-name
definitions: definitions:
- prefRoles: [ Thief ] - prefRoles: [ Thief ]
maxRange: maxRange:

View File

@@ -64,9 +64,9 @@
roundEndDelay: 10 roundEndDelay: 10
- type: entity - type: entity
abstract: true id: Nukeops
parent: BaseGameRule parent: BaseGameRule
id: BaseNukeopsRule noSpawn: true
components: components:
- type: GameRule - type: GameRule
minPlayers: 20 # Honk minPlayers: 20 # Honk
@@ -75,16 +75,6 @@
- operationPrefix - operationPrefix
- operationSuffix - operationSuffix
- type: NukeopsRule - type: NukeopsRule
- type: AntagSelection
- type: AntagLoadProfileRule
- type: entity
noSpawn: true
parent: BaseNukeopsRule
id: Nukeops
components:
- type: GameRule
minPlayers: 20
- type: LoadMapRule - type: LoadMapRule
gameMap: NukieOutpost gameMap: NukieOutpost
- type: AntagSelection - type: AntagSelection
@@ -148,30 +138,16 @@
prototype: Nukeops prototype: Nukeops
- type: entity - type: entity
abstract: true
parent: BaseGameRule
id: BaseTraitorRule
components:
- type: TraitorRule
# TODO: codewords in yml
# TODO: uplink in yml
- type: AntagRandomObjectives
sets:
- groups: TraitorObjectiveGroups
maxDifficulty: 5
- type: AntagSelection
agentName: traitor-round-end-agent-name
- type: entity
noSpawn: true
parent: BaseTraitorRule
id: Traitor id: Traitor
parent: BaseGameRule
noSpawn: true
components: components:
- type: GameRule - type: GameRule
minPlayers: 5 minPlayers: 5
delay: delay:
min: 240 min: 240
max: 420 max: 420
- type: TraitorRule
- type: AntagSelection - type: AntagSelection
definitions: definitions:
- prefRoles: [ Traitor ] - prefRoles: [ Traitor ]

View File

@@ -55,6 +55,13 @@
ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added ThiefObjectiveGroupStructure: 0 #Temporarily disabled until obvious ways to steal structures are added
ThiefObjectiveGroupAnimal: 2 ThiefObjectiveGroupAnimal: 2
- type: weightedRandom
id: ThiefEscapeObjectiveGroups
weights:
ThiefObjectiveGroupEscape: 1
- type: weightedRandom - type: weightedRandom
id: ThiefObjectiveGroupCollection id: ThiefObjectiveGroupCollection
weights: weights:

View File

@@ -50,12 +50,6 @@
store: payloadTrigger store: payloadTrigger
name: триггер name: триггер
doAfter: 0.5 doAfter: 0.5
- to: caseWithPayload
steps:
- tag: Payload
store: payload
name: Payload
doAfter: 0.5
- node: caseWithTrigger - node: caseWithTrigger
actions: actions:
@@ -77,26 +71,6 @@
name: заряд name: заряд
doAfter: 0.5 doAfter: 0.5
- node: caseWithPayload
actions:
- !type:AppearanceChange
- !type:PlaySound
sound: /Audio/Machines/button.ogg
edges:
- to: wiredCase
steps:
- tool: Prying
doAfter: 0.5
completed:
- !type:EmptyContainer
container: payload
- to: grenade
steps:
- component: PayloadTrigger
store: payloadTrigger
name: Trigger
doAfter: 0.5
- node: grenade - node: grenade
actions: actions:
- !type:AppearanceChange - !type:AppearanceChange

View File

@@ -1,7 +1,7 @@
{ {
"version": 1, "version": 1,
"license": "CC-BY-SA-3.0", "license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept & ElectroSR", "copyright": "Taken from tgstation at commit https://github.com/tgstation/tgstation/commit/29c0ed1b000619cb5398ef921000a8d4502ba0b6 and modified by Swept",
"size": { "size": {
"x": 32, "x": 32,
"y": 32 "y": 32
@@ -19,10 +19,6 @@
"name": "no-payload", "name": "no-payload",
"directions": 1 "directions": 1
}, },
{
"name": "no-trigger",
"directions": 1
},
{ {
"name": "complete", "name": "complete",
"directions": 1 "directions": 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B