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.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations;
@@ -37,38 +36,28 @@ namespace Content.Client.Atmos.EntitySystems
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?
case GasTileOverlayDeltaState delta:
foreach (var index in comp.Chunks.Keys)
{
modifiedChunks = delta.ModifiedChunks;
foreach (var index in comp.Chunks.Keys)
{
if (!delta.AllChunks.Contains(index))
comp.Chunks.Remove(index);
}
break;
if (!state.AllChunks!.Contains(index))
comp.Chunks.Remove(index);
}
case GasTileOverlayState state:
}
else
{
foreach (var index in comp.Chunks.Keys)
{
modifiedChunks = state.Chunks;
foreach (var index in comp.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
break;
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
default:
return;
}
foreach (var (index, data) in modifiedChunks)
foreach (var (index, data) in state.Chunks)
{
comp.Chunks[index] = data;
}

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,24 @@ using Robust.Shared.Containers;
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);
}
~DragDropHelper()
{
_cfg.UnsubValueChanged(CCVars.DragDropDeadZone, SetDeadZone);
}
/// <summary>
/// Tell the helper that the mouse button was pressed down on
/// a target, thus a drag has the possibility to begin for this target.

View File

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

View File

@@ -1,15 +1,17 @@
using Content.Client.Administration.Managers;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Content.Client.Options.UI.Tabs;
using Robust.Shared.Timing;
namespace Content.Client.Options.UI
{
[GenerateTypedNameReferences]
public sealed partial class OptionsMenu : DefaultWindow
{
[Dependency] private readonly IClientAdminManager _clientAdminManager = default!;
public OptionsMenu()
{
RobustXamlLoader.Load(this);
@@ -19,6 +21,8 @@ namespace Content.Client.Options.UI
Tabs.SetTabTitle(1, Loc.GetString("ui-options-tab-graphics"));
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-network"));
Tabs.SetTabTitle(5, "Админ");
UpdateTabs();
}
@@ -27,5 +31,11 @@ namespace Content.Client.Options.UI
{
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.FixedPoint;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.StatusIcon.Components;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
@@ -88,10 +85,6 @@ public sealed class EntityHealthBarOverlay : Overlay
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 worldMatrix = Matrix3.CreateTranslation(worldPosition);
@@ -104,6 +97,10 @@ public sealed class EntityHealthBarOverlay : Overlay
var widthOfMob = bounds.Width * 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);
// Hardcoded width of the progress bar because it doesn't match the texture.
@@ -131,13 +128,10 @@ public sealed class EntityHealthBarOverlay : Overlay
/// <summary>
/// Returns a ratio between 0 and 1, and whether the entity is in crit.
/// </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 (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold)
return null;
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) &&
!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds))
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)
{
Dictionary<Vector2i, int[]> modifiedChunks;
Dictionary<NetEntity, NavMapBeacon> beacons;
if (args.Current is not NavMapComponentState state)
return;
switch (args.Current)
if (!state.FullState)
{
case NavMapDeltaState delta:
foreach (var index in component.Chunks.Keys)
{
modifiedChunks = delta.ModifiedChunks;
beacons = delta.Beacons;
foreach (var index in component.Chunks.Keys)
{
if (!delta.AllChunks!.Contains(index))
component.Chunks.Remove(index);
}
break;
if (!state.AllChunks!.Contains(index))
component.Chunks.Remove(index);
}
case NavMapState state:
}
else
{
foreach (var index in component.Chunks.Keys)
{
modifiedChunks = state.Chunks;
beacons = state.Beacons;
foreach (var index in component.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
component.Chunks.Remove(index);
}
break;
if (!state.Chunks.ContainsKey(index))
component.Chunks.Remove(index);
}
default:
return;
}
foreach (var (origin, chunk) in modifiedChunks)
foreach (var (origin, chunk) in state.Chunks)
{
var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length);
@@ -55,7 +42,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
}
component.Beacons.Clear();
foreach (var (nuid, beacon) in beacons)
foreach (var (nuid, beacon) in state.Beacons)
{
component.Beacons[nuid] = beacon;
}

View File

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

View File

@@ -22,4 +22,9 @@ public sealed class ItemSlotButtonContainer : ItemSlotUIContainer<SlotControl>
{
_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;
public sealed class ItemGridPiece : Control, IEntityControl
public sealed class ItemGridPiece : Control
{
private readonly IEntityManager _entityManager;
private readonly StorageUIController _storageController;
@@ -287,8 +287,6 @@ public sealed class ItemGridPiece : Control, IEntityControl
var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1);
return actualSize * new Vector2i(8, 8);
}
public EntityUid? UiEntity => Entity;
}
public enum ItemGridPieceMarks

View File

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

View File

@@ -350,12 +350,8 @@ namespace Content.IntegrationTests.Tests
"DebrisFeaturePlacerController", // Above.
"LoadedChunk", // Worldgen chunk loading malding.
"BiomeSelection", // Whaddya know, requires config.
"ActivatableUI", // Requires enum key
};
// TODO TESTS
// auto ignore any components that have a "required" data field.
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var entityManager = server.ResolveDependency<IEntityManager>();

View File

@@ -44,7 +44,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
if (args.Actor is not { Valid: true } player)
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);
}
@@ -152,9 +153,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
_idCard.TryChangeJobDepartment(targetId, job);
}
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job, newJobIcon);
if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
{
_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>
/// Helper to get just the mind entities and not names.
/// Helper specifically for <see cref="ObjectivesTextGetInfoEvent"/>
/// </remarks>
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.Components;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
@@ -26,11 +25,10 @@ using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Server._Miracle.GulagSystem;
using Content.Server._White.Sponsors;
using Content.Server.Inventory;
using Content.Shared.GameTicking;
using FastAccessors;
using Robust.Shared.Utility;
namespace Content.Server.Antag;
@@ -61,8 +59,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning);
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned);
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
@@ -460,15 +456,6 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
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)
{
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.
/// </summary>
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]

View File

@@ -20,8 +20,6 @@ namespace Content.Server.Cargo.Systems
{
public sealed partial class CargoSystem
{
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
/// <summary>
/// How much time to wait (in seconds) before increasing bank accounts balance.
/// </summary>
@@ -496,9 +494,6 @@ namespace Content.Server.Cargo.Systems
// Create the item itself
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
var printed = EntityManager.SpawnEntity(paperProto, spawn);
if (TryComp<PaperComponent>(printed, out var paper))

View File

@@ -1,11 +1,9 @@
using System.Linq;
using Content.Server._Miracle.GulagSystem;
using Content.Server.Antag;
using Content.Server.GameTicking.Rules;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Shared._White.Mood;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
namespace Content.Server.Changeling;
@@ -32,7 +30,7 @@ public sealed class ChangelingRuleSystem : GameRuleSystem<ChangelingRuleComponen
ChangelingRuleComponent comp,
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");
}

View File

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

View File

@@ -2,7 +2,6 @@ using Content.Server.Zombies;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Content.Shared.Zombies;
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)
{
// TODO use activatable ui system
if (args.Handled)
return;

View File

@@ -63,21 +63,9 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorToggleLinkMessage>(OnToggleLinks);
SubscribeLocalEvent<NetworkConfiguratorComponent, NetworkConfiguratorButtonPressedMessage>(OnConfigButtonPressed);
SubscribeLocalEvent<NetworkConfiguratorComponent, BoundUserInterfaceCheckRangeEvent>(OnUiRangeCheck);
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)
{
ClearDevices(uid, component);
@@ -87,6 +75,23 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem
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)
{
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"/>.
/// </summary>
[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]
public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
[DataField]
public ProtoId<WeightedRandomPrototype> ObjectiveGroup = "TraitorObjectiveGroups";
[DataField]
public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives";
@@ -75,4 +78,7 @@ public sealed partial class TraitorRuleComponent : Component
/// </summary>
[DataField]
public int StartingBalance = 20;
[DataField]
public int MaxDifficulty = 5;
}

View File

@@ -1,8 +1,6 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Objectives;
using Content.Shared.Mind;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
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)
{
// just temporary until this is deleted
args.Minds = comp.Minds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList();
args.Minds = comp.Minds;
args.AgentName = Loc.GetString(comp.AgentName);
}
}

View File

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

View File

@@ -24,6 +24,7 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
SubscribeLocalEvent<ThiefRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagSelected);
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<ThiefRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
}
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
@@ -32,9 +33,41 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
return;
//Generate objectives
GenerateObjectives(mindId, mind, ent);
_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
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";
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 GameTicker _gameTicker = default!;
public const int MaxPicks = 20;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
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
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
@@ -124,16 +127,37 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
if (richAspect) // WD
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;
}
// 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)
{
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)
{
var sb = new StringBuilder();

View File

@@ -7,6 +7,31 @@ using Robust.Shared.Player;
namespace Content.Server.Interaction
{
// TODO Remove Shared prefix
public sealed class InteractionSystem : SharedInteractionSystem;
/// <summary>
/// Governs interactions during clicking on entities
/// </summary>
[UsedImplicitly]
public sealed partial class InteractionSystem : SharedInteractionSystem
{
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
public override bool CanAccessViaStorage(EntityUid user, EntityUid target)
{
if (Deleted(target))
return false;
if (!_container.TryGetContainingContainer(target, out var container))
return false;
if (!TryComp(container.Owner, out StorageComponent? storage))
return false;
if (storage.Container?.ID != container.ID)
return false;
// we don't check if the user can access the storage entity itself. This should be handed by the UI system.
return _uiSystem.IsUiOpen(container.Owner, StorageComponent.StorageUiKey.Key, user);
}
}
}

View File

@@ -1,17 +1,15 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Lock;
namespace Content.Server.Lock.Components;
/// <summary>
/// This is used for activatable UIs that require the entity to have a lock in a certain state.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
[RegisterComponent]
public sealed partial class ActivatableUIRequiresLockComponent : Component
{
/// <summary>
/// TRUE: the lock must be locked to access the UI.
/// FALSE: the lock must be unlocked to access the UI.
/// </summary>
[DataField]
public bool RequireLocked;
[DataField("requireLocked"), ViewVariables(VVAccess.ReadWrite)]
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)
{
// 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>();
while (query.MoveNext(out var uid, out var gameRule))
{
if (!_gameTicker.IsGameRuleAdded(uid, gameRule))
continue;
var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), string.Empty);
var info = new ObjectivesTextGetInfoEvent(new List<EntityUid>(), string.Empty);
RaiseLocalEvent(uid, ref info);
if (info.Minds.Count == 0)
continue;
@@ -51,7 +51,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
// first group the gamerules by their agents, for example 2 different dragons
var agent = info.AgentName;
if (!summaries.ContainsKey(agent))
summaries[agent] = new Dictionary<string, List<(EntityUid, string)>>();
summaries[agent] = new Dictionary<string, List<EntityUid>>();
var prepend = new ObjectivesTextPrependEvent("");
RaiseLocalEvent(uid, ref prepend);
@@ -79,7 +79,7 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
foreach (var (_, minds) in summary)
{
total += minds.Count;
totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count();
totalInCustody += minds.Where(m => IsInCustody(m)).Count();
}
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)>();
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;
var title = GetTitle((mindId, mind), name);
var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty;
var objectives = mind.Objectives;
@@ -235,18 +238,34 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
/// <summary>
/// Get the title for a player's mind used in round end.
/// Pass in the original entity name which is shown alongside username.
/// </summary>
public string GetTitle(Entity<MindComponent?> mind, string name)
public string? GetTitle(EntityUid mindId, MindComponent? mind = null)
{
if (Resolve(mind, ref mind.Comp) &&
mind.Comp.OriginalOwnerUserId != null &&
_player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData))
if (!Resolve(mindId, ref mind))
return null;
var name = mind.CharacterName;
var username = (string?) null;
if (mind.OriginalOwnerUserId != null &&
_player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData))
{
var username = sessionData.UserName;
return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
username = sessionData.UserName;
}
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));
}
}
@@ -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.
/// </remarks>
[ByRefEvent]
public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName);
public record struct ObjectivesTextGetInfoEvent(List<EntityUid> Minds, string AgentName);
/// <summary>
/// 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 IPlayerManager _playerManager = default!;
[Dependency] private readonly IDependencyCollection _dependencies = default!;
[Dependency] private readonly ILogManager _log = default!;
// WD-EDIT
[Dependency] private readonly SponsorsManager _sponsors = default!;
@@ -37,9 +36,7 @@ namespace Content.Server.Preferences.Managers
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
new();
private ISawmill _sawmill = default!;
private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots);
private readonly ISawmill _sawmill = default!;
public void Init()
{
@@ -47,7 +44,6 @@ namespace Content.Server.Preferences.Managers
_netManager.RegisterNetMessage<MsgSelectCharacter>(HandleSelectCharacterMessage);
_netManager.RegisterNetMessage<MsgUpdateCharacter>(HandleUpdateCharacterMessage);
_netManager.RegisterNetMessage<MsgDeleteCharacter>(HandleDeleteCharacterMessage);
_sawmill = _log.GetSawmill("prefs");
}
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.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.Alert;
using Content.Shared.Database;
@@ -69,6 +70,7 @@ public sealed partial class BorgSystem : SharedBorgSystem
SubscribeLocalEvent<BorgChassisComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellChangedEvent>(OnPowerCellChanged);
SubscribeLocalEvent<BorgChassisComponent, PowerCellSlotEmptyEvent>(OnPowerCellSlotEmpty);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<BorgChassisComponent, GetCharactedDeadIcEvent>(OnGetDeadIC);
SubscribeLocalEvent<BorgBrainComponent, MindAddedMessage>(OnBrainMindAdded);
@@ -212,6 +214,13 @@ public sealed partial class BorgSystem : SharedBorgSystem
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)
{
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 (!TryFindRandomTile(out _, out _, out _, out var targetCoords))
return;
TryFindRandomTile(out _, out _, out _, out var targetCoords);
var speed = RobustRandom.NextFloat(rod.MinSpeed, rod.MaxSpeed);
var angle = RobustRandom.NextAngle();
var direction = angle.ToVec();

View File

@@ -1,6 +1,7 @@
using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Ensnaring;
using Content.Shared.CombatMode;
using Content.Shared.Cuffs;
using Content.Shared.Cuffs.Components;
using Content.Shared.Database;
@@ -9,6 +10,7 @@ using Content.Shared.Ensnaring.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.VirtualItem;
@@ -27,6 +29,7 @@ namespace Content.Server.Strip
{
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly EnsnareableSystem _ensnaringSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly SharedCuffableSystem _cuffableSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
@@ -43,6 +46,7 @@ namespace Content.Server.Strip
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb);
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb);
SubscribeLocalEvent<StrippableComponent, ActivateInWorldEvent>(OnActivateInWorld);
// BUI
SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed);
@@ -65,7 +69,7 @@ namespace Content.Server.Strip
{
Text = Loc.GetString("strip-verb-get-data-text"),
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);
@@ -83,13 +87,37 @@ namespace Content.Server.Strip
{
Text = Loc.GetString("strip-verb-get-data-text"),
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,
};
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)
{
if (args.Actor is not { Valid: true } user ||

View File

@@ -187,10 +187,15 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem
if (session is { } 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)

View File

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

View File

@@ -4,23 +4,27 @@ using System.Threading;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.DoAfter;
using Content.Shared.GameTicking;
using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tools.Components;
using Content.Shared.UserInterface;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem;
namespace Content.Server.Wires;
public sealed class WiresSystem : SharedWiresSystem
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
@@ -48,6 +52,8 @@ public sealed class WiresSystem : SharedWiresSystem
SubscribeLocalEvent<WiresComponent, TimedWireEvent>(OnTimedWire);
SubscribeLocalEvent<WiresComponent, PowerChangedEvent>(OnWiresPowered);
SubscribeLocalEvent<WiresComponent, WireDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, PanelChangedEvent>(OnActivatableUIPanelChanged);
SubscribeLocalEvent<WiresPanelSecurityComponent, WiresPanelSecurityEvent>(SetWiresPanelSecurity);
}
@@ -467,6 +473,23 @@ public sealed class WiresSystem : SharedWiresSystem
_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)
{
if (!string.IsNullOrEmpty(component.LayoutId))

View File

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

View File

@@ -39,7 +39,7 @@ public sealed class WizardRuleSystem : GameRuleSystem<WizardRuleComponent>
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");
}

View File

@@ -508,7 +508,13 @@ public abstract class SharedActionsSystem : EntitySystem
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)

View File

@@ -1,6 +1,7 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Atmos.Components;
@@ -23,47 +24,55 @@ public sealed partial class GasTileOverlayComponent : Component
public GameTick ForceTick { get; set; }
}
[Serializable, NetSerializable]
public sealed class GasTileOverlayState(Dictionary<Vector2i, GasOverlayChunk> chunks) : ComponentState
{
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks = chunks;
}
[Serializable, NetSerializable]
public sealed class GasTileOverlayDeltaState(
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks,
HashSet<Vector2i> allChunks)
: ComponentState, IComponentDeltaState<GasTileOverlayState>
public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState
{
public readonly Dictionary<Vector2i, GasOverlayChunk> ModifiedChunks = modifiedChunks;
public readonly HashSet<Vector2i> AllChunks = allChunks;
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks;
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)
{
if (!AllChunks.Contains(key))
if (!AllChunks!.Contains(key))
state.Chunks.Remove(key);
}
foreach (var (chunk, data) in ModifiedChunks)
foreach (var (chunk, data) in Chunks)
{
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);
}
foreach (var (chunk, data) in state.Chunks)
{
if (AllChunks.Contains(chunk))
if (AllChunks!.Contains(chunk))
chunks.TryAdd(chunk, new(data));
}

View File

@@ -55,7 +55,7 @@ namespace Content.Shared.Atmos.EntitySystems
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)

View File

@@ -58,7 +58,7 @@ public abstract partial class SharedBuckleSystem
return;
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;
TryUnbuckle(uid, uid, true, component);

View File

@@ -5,6 +5,8 @@ using Content.Shared.StatusIcon;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
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
{
@@ -16,7 +18,7 @@ namespace Content.Shared.Damage
/// may also have resistances to certain damage types, defined via a <see cref="DamageModifierSetPrototype"/>.
/// </remarks>
[RegisterComponent]
[NetworkedComponent]
[NetworkedComponent()]
[Access(typeof(DamageableSystem), Other = AccessPermissions.ReadExecute)]
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.
/// If null, all damage types will be supported.
/// </summary>
[DataField("damageContainer")]
public ProtoId<DamageContainerPrototype>? DamageContainerID;
[DataField("damageContainer", customTypeSerializer: typeof(PrototypeIdSerializer<DamageContainerPrototype>))]
public string? DamageContainerID;
/// <summary>
/// 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
/// to reduce duplication.
/// </remarks>
[DataField("damageModifierSet")]
public ProtoId<DamageModifierSetPrototype>? DamageModifierSetId;
[DataField("damageModifierSet", customTypeSerializer: typeof(PrototypeIdSerializer<DamageModifierSetPrototype>))]
public string? DamageModifierSetId;
/// <summary>
/// All the damage information is stored in this <see cref="DamageSpecifier"/>.
@@ -44,7 +46,7 @@ namespace Content.Shared.Damage
/// <remarks>
/// If this data-field is specified, this allows damageable components to be initialized with non-zero damage.
/// </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();
/// <summary>
@@ -62,8 +64,8 @@ namespace Content.Shared.Damage
[ViewVariables]
public FixedPoint2 TotalDamage;
[DataField("radiationDamageTypes")]
public List<ProtoId<DamageTypePrototype>> RadiationDamageTypeIDs = new() { "Radiation" };
[DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
public List<string> RadiationDamageTypeIDs = new() { "Radiation" };
[DataField]
public Dictionary<MobState, ProtoId<StatusIconPrototype>> HealthIcons = new()
@@ -75,9 +77,6 @@ namespace Content.Shared.Damage
[DataField]
public ProtoId<StatusIconPrototype> RottingIcon = "HealthIconRotting";
[DataField]
public FixedPoint2? HealthBarThreshold;
}
[Serializable, NetSerializable]
@@ -85,16 +84,13 @@ namespace Content.Shared.Damage
{
public readonly Dictionary<string, FixedPoint2> DamageDict;
public readonly string? ModifierSetId;
public readonly FixedPoint2? HealthBarThreshold;
public DamageableComponentState(
Dictionary<string, FixedPoint2> damageDict,
string? modifierSetId,
FixedPoint2? healthBarThreshold)
string? modifierSetId)
{
DamageDict = damageDict;
ModifierSetId = modifierSetId;
HealthBarThreshold = healthBarThreshold;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Inventory;
@@ -239,12 +240,12 @@ namespace Content.Shared.Damage
{
if (_netMan.IsServer)
{
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId, component.HealthBarThreshold);
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId);
}
else
{
// 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.HealthBarThreshold = state.HealthBarThreshold;
// Has the damage actually changed?
DamageSpecifier newDamage = new() { DamageDict = new(state.DamageDict) };

View File

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

View File

@@ -49,7 +49,7 @@ namespace Content.Shared.Decals
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)

View File

@@ -1,10 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.CombatMode;
using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Input;
@@ -14,20 +15,16 @@ using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Tag;
using Content.Shared.Timing;
using Content.Shared.UserInterface;
using Content.Shared.Verbs;
using Content.Shared.Wall;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared._White.MeatyOre;
using Content.Shared.Ghost;
using Content.Shared.Storage;
using Content.Shared.UserInterface;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Input;
@@ -41,8 +38,6 @@ using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Utility;
#pragma warning disable 618
@@ -57,11 +52,12 @@ namespace Content.Shared.Interaction
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _broadphase = default!;
[Dependency] private readonly SharedPhysicsSystem _sharedBroadphaseSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedVerbSystem _verbSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
@@ -70,18 +66,6 @@ namespace Content.Shared.Interaction
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
private EntityQuery<IgnoreUIRangeComponent> _ignoreUiRangeQuery;
private EntityQuery<FixturesComponent> _fixtureQuery;
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<HandsComponent> _handsQuery;
private EntityQuery<InteractionRelayComponent> _relayQuery;
private EntityQuery<CombatModeComponent> _combatQuery;
private EntityQuery<WallMountComponent> _wallMountQuery;
private EntityQuery<UseDelayComponent> _delayQuery;
private EntityQuery<ActivatableUIComponent> _uiQuery;
private const CollisionGroup InRangeUnobstructedMask = CollisionGroup.Impassable | CollisionGroup.InteractImpassable;
@@ -94,17 +78,6 @@ namespace Content.Shared.Interaction
public override void Initialize()
{
_ignoreUiRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
_fixtureQuery = GetEntityQuery<FixturesComponent>();
_itemQuery = GetEntityQuery<ItemComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_handsQuery = GetEntityQuery<HandsComponent>();
_relayQuery = GetEntityQuery<InteractionRelayComponent>();
_combatQuery = GetEntityQuery<CombatModeComponent>();
_wallMountQuery = GetEntityQuery<WallMountComponent>();
_delayQuery = GetEntityQuery<UseDelayComponent>();
_uiQuery = GetEntityQuery<ActivatableUIComponent>();
SubscribeLocalEvent<BoundUserInterfaceCheckRangeEvent>(HandleUserInterfaceRangeCheck);
SubscribeLocalEvent<BoundUserInterfaceMessageAttempt>(OnBoundInterfaceInteractAttempt);
@@ -140,57 +113,34 @@ namespace Content.Shared.Interaction
/// </summary>
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{
_uiQuery.TryComp(ev.Target, out var uiComp);
if (!_actionBlockerSystem.CanInteract(ev.Actor, ev.Target))
{
// We permit ghosts to open uis unless explicitly blocked
if (ev.Message is not OpenBoundInterfaceMessage || !HasComp<GhostComponent>(ev.Actor) || uiComp?.BlockSpectators == true)
{
ev.Cancel();
return;
}
}
var user = ev.Actor;
var range = _ui.GetUiRange(ev.Target, ev.UiKey);
// As long as range>0, the UI frame updates should have auto-closed the UI if it is out of range.
DebugTools.Assert(range <= 0 || UiRangeCheck(ev.Actor, ev.Target, range));
if (range <= 0 && !IsAccessible(ev.Actor, ev.Target))
if (!_actionBlockerSystem.CanInteract(user, ev.Target))
{
ev.Cancel();
return;
}
if (uiComp == null)
return;
if (uiComp.SingleUser && uiComp.CurrentSingleUser != ev.Actor)
// Check if the bound entity is accessible. Note that we allow admins to ignore this restriction, so that
// they can fiddle with UI's that people can't normally interact with (e.g., placing things directly into
// other people's backpacks).
if (!_containerSystem.IsInSameOrParentContainer(user, ev.Target)
&& !CanAccessViaStorage(user, ev.Target)
&& !_adminManager.HasAdminFlag(user, AdminFlags.Admin))
{
ev.Cancel();
return;
}
if (!uiComp.RequireHands)
if (CompOrNull<IgnorBUIInteractionRangeComponent>(ev.Target) != null)
{
return;
}
if (!_handsQuery.TryComp(ev.Actor, out var hands) || hands.Hands.Count == 0)
if (!InRangeUnobstructed(user, ev.Target))
{
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>
@@ -247,7 +197,10 @@ namespace Content.Shared.Interaction
if (!InRangeUnobstructed(userEntity.Value, uid, popup: true))
return false;
_pullSystem.TogglePull(uid, userEntity.Value);
if (!TryComp(uid, out PullableComponent? pull))
return false;
_pullSystem.TogglePull(uid, userEntity.Value, pull);
return false;
}
@@ -323,7 +276,7 @@ namespace Content.Shared.Interaction
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
{
// 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;
// Only eat input if:
@@ -331,7 +284,7 @@ namespace Content.Shared.Interaction
// - Target doesn't cancel should-interact event
// This is intended to allow items to be picked up in combat mode,
// but to also allow items to force attacks anyway (like mobs which are items, e.g. mice)
if (!_itemQuery.HasComp(target))
if (!HasComp<ItemComponent>(target))
return false;
var combatEv = new CombatModeShouldHandInteractEvent();
@@ -361,7 +314,7 @@ namespace Content.Shared.Interaction
bool checkAccess = 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.
if (_actionBlockerSystem.CanInteract(user, target))
@@ -375,7 +328,7 @@ namespace Content.Shared.Interaction
if (target != null && Deleted(target.Value))
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))
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.
// 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;
var inRangeUnobstructed = target == null
@@ -405,7 +361,7 @@ namespace Content.Shared.Interaction
: !checkAccess || InRangeUnobstructed(user, target.Value); // permits interactions with wall mounted entities
// 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);
RaiseLocalEvent(user, ev);
@@ -545,7 +501,7 @@ namespace Content.Shared.Interaction
predicate ??= _ => false;
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)
return dir.Length();
@@ -608,29 +564,23 @@ namespace Content.Shared.Interaction
}
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;
}
public bool InRangeUnobstructed(
Entity<TransformComponent?> origin,
Entity<TransformComponent?> other,
EntityUid origin,
EntityUid other,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null,
bool popup = false)
{
if (!Resolve(other, ref other.Comp))
if (!TryComp(other, out TransformComponent? otherXform))
return false;
return InRangeUnobstructed(origin,
other,
other.Comp.Coordinates,
other.Comp.LocalRotation,
range,
collisionMask,
predicate,
return InRangeUnobstructed(origin, other, otherXform.Coordinates, otherXform.LocalRotation, range, collisionMask, predicate,
popup);
}
@@ -662,8 +612,8 @@ namespace Content.Shared.Interaction
/// True if the two points are within a given range without being obstructed.
/// </returns>
public bool InRangeUnobstructed(
Entity<TransformComponent?> origin,
Entity<TransformComponent?> other,
EntityUid origin,
EntityUid other,
EntityCoordinates otherCoordinates,
Angle otherAngle,
float range = InteractionRange,
@@ -671,10 +621,10 @@ namespace Content.Shared.Interaction
Ignored? predicate = null,
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;
MapCoordinates originPos = default;
var targetPos = _transform.ToMapCoordinates(otherCoordinates);
var targetPos = otherCoordinates.ToMap(EntityManager, _transform);
Angle targetRot = default;
// So essentially:
@@ -684,30 +634,23 @@ namespace Content.Shared.Interaction
// Alternatively we could check centre distances first though
// that means we wouldn't be able to easily check overlap interactions.
if (range > 0f &&
_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).
// At least until they get removed.
fixtureA.FixtureCount > 0 &&
_fixtureQuery.TryComp(other, out var fixtureB) &&
TryComp<FixturesComponent>(other, out var fixtureB) &&
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 parentRotB = _transform.GetWorldRotation(otherCoordinates.EntityId);
var xfB = new Transform(targetPos.Position, parentRotB + otherAngle);
// Different map or the likes.
if (!_broadphase.TryGetNearest(
origin,
other,
out _,
out _,
out var distance,
xfA,
xfB,
fixtureA,
fixtureB))
if (!_sharedBroadphaseSystem.TryGetNearest(origin, other,
out _, out _, out var distance,
xfA, xfB, fixtureA, fixtureB))
{
inRange = false;
}
@@ -729,15 +672,15 @@ namespace Content.Shared.Interaction
else
{
// We'll still do the raycast from the centres but we'll bump the range as we know they're in range.
originPos = _transform.GetMapCoordinates(origin, xform: origin.Comp);
originPos = _transform.GetMapCoordinates(origin, xform: xformA);
range = (originPos.Position - targetPos.Position).Length();
}
}
// No fixtures, e.g. wallmounts.
else
{
originPos = _transform.GetMapCoordinates(origin, origin);
var otherParent = (other.Comp ?? Transform(other)).ParentUid;
originPos = _transform.GetMapCoordinates(origin);
var otherParent = Transform(other).ParentUid;
targetRot = otherParent.IsValid() ? Transform(otherParent).LocalRotation + otherAngle : otherAngle;
}
@@ -788,13 +731,13 @@ namespace Content.Shared.Interaction
{
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
// 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
@@ -812,7 +755,13 @@ namespace Content.Shared.Interaction
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;
}
@@ -1009,8 +958,10 @@ namespace Content.Shared.Interaction
bool checkUseDelay = true,
bool checkAccess = true)
{
_delayQuery.TryComp(used, out var delayComponent);
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
UseDelayComponent? delayComponent = null;
if (checkUseDelay
&& TryComp(used, out delayComponent)
&& _useDelay.IsDelayed((used, delayComponent)))
return false;
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.
// 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;
// Does the user have hands?
if (!_handsQuery.HasComp(user))
if (!HasComp<HandsComponent>(user))
return false;
var activateMsg = new ActivateInWorldEvent(user, used);
@@ -1035,9 +986,7 @@ namespace Content.Shared.Interaction
DoContactInteraction(user, used, activateMsg);
// 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)
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
return true;
@@ -1058,8 +1007,11 @@ namespace Content.Shared.Interaction
bool checkCanInteract = true,
bool checkUseDelay = true)
{
_delayQuery.TryComp(used, out var delayComponent);
if (checkUseDelay && delayComponent != null && _useDelay.IsDelayed((used, delayComponent)))
UseDelayComponent? delayComponent = null;
if (checkUseDelay
&& TryComp(used, out delayComponent)
&& _useDelay.IsDelayed((used, delayComponent)))
return true; // if the item is on cooldown, we consider this handled.
if (checkCanInteract && !_actionBlockerSystem.CanInteract(user, used))
@@ -1121,60 +1073,11 @@ namespace Content.Shared.Interaction
}
#endregion
/// <summary>
/// Check if a user can access a target (stored in the same containers) and is in range without obstructions.
/// </summary>
public bool InRangeAndAccessible(
Entity<TransformComponent?> user,
Entity<TransformComponent?> target,
float range = InteractionRange,
CollisionGroup collisionMask = InRangeUnobstructedMask,
Ignored? predicate = null)
{
if (user == target)
return true;
if (!Resolve(user, ref user.Comp))
return false;
if (!Resolve(target, ref target.Comp))
return false;
return IsAccessible(user, target) && InRangeUnobstructed(user, target, range, collisionMask, predicate);
}
/// <summary>
/// Check if a user can access a target or if they are stored in different containers.
/// </summary>
public bool IsAccessible(Entity<TransformComponent?> user, Entity<TransformComponent?> target)
{
if (_containerSystem.IsInSameOrParentContainer(user, target, out _, out var container))
return true;
return container != null && CanAccessViaStorage(user, target, container);
}
/// <summary>
/// If a target is in range, but not in the same container as the user, it may be inside of a backpack. This
/// checks if the user can access the item in these situations.
/// </summary>
public 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);
}
public abstract bool CanAccessViaStorage(EntityUid user, EntityUid target);
/// <summary>
/// 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));
}
private void HandleUserInterfaceRangeCheck(ref BoundUserInterfaceCheckRangeEvent ev)
{
if (ev.Result == BoundUserInterfaceRangeResult.Fail)
return;
ev.Result = UiRangeCheck(ev.Actor!, ev.Target, ev.Data.InteractionRange)
? BoundUserInterfaceRangeResult.Pass
: BoundUserInterfaceRangeResult.Fail;
if (InRangeUnobstructed(ev.Actor, ev.Target, ev.Data.InteractionRange))
{
ev.Result = BoundUserInterfaceRangeResult.Pass;
}
else
{
ev.Result = BoundUserInterfaceRangeResult.Fail;
}
}
}

View File

@@ -210,7 +210,11 @@ public abstract partial class InventorySystem
return false;
// 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;
// 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.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.UserInterface;
using Content.Shared.Verbs;
using Content.Shared.Wires;
using JetBrains.Annotations;
@@ -25,7 +24,6 @@ namespace Content.Shared.Lock;
public sealed class LockSystem : EntitySystem
{
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
@@ -48,9 +46,6 @@ public sealed class LockSystem : EntitySystem
SubscribeLocalEvent<LockedWiresPanelComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, LockToggledEvent>(LockToggled);
}
private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args)
@@ -352,25 +347,4 @@ public sealed class LockSystem : EntitySystem
args.User);
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.Interaction;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.MagicMirror;
@@ -18,13 +17,10 @@ public abstract class SharedMagicMirrorSystem : EntitySystem
private void OnMirrorRangeCheck(EntityUid uid, MagicMirrorComponent component, ref BoundUserInterfaceCheckRangeEvent args)
{
if (args.Result == BoundUserInterfaceRangeResult.Fail)
return;
DebugTools.Assert(component.Target != null && Exists(component.Target));
if (!_interaction.InRangeUnobstructed(uid, component.Target.Value))
if (!Exists(component.Target) || !_interaction.InRangeUnobstructed(uid, component.Target.Value))
{
args.Result = BoundUserInterfaceRangeResult.Fail;
}
}
}

View File

@@ -344,17 +344,14 @@ public sealed class PullingSystem : EntitySystem
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))
return false;
if (pullable.Comp.Puller == pullerUid)
if (pullable.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)
@@ -362,7 +359,7 @@ public sealed class PullingSystem : EntitySystem
if (!TryComp<PullableComponent>(puller.Pulling, out var pullable))
return false;
return TogglePull((puller.Pulling.Value, pullable), pullerUid);
return TogglePull(puller.Pulling.Value, pullerUid, pullable);
}
public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid,

View File

@@ -96,7 +96,7 @@ public abstract class SharedNavMapSystem : EntitySystem
chunks.Add(origin, chunk.TileData);
}
args.State = new NavMapState(chunks, component.Beacons);
args.State = new NavMapComponentState(chunks, component.Beacons);
return;
}
@@ -109,7 +109,12 @@ public abstract class SharedNavMapSystem : EntitySystem
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
@@ -117,35 +122,32 @@ public abstract class SharedNavMapSystem : EntitySystem
#region: System messages
[Serializable, NetSerializable]
protected sealed class NavMapState(
protected sealed class NavMapComponentState(
Dictionary<Vector2i, int[]> chunks,
Dictionary<NetEntity, NavMapBeacon> beacons)
: ComponentState
: ComponentState, IComponentDeltaState
{
public Dictionary<Vector2i, int[]> Chunks = chunks;
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
}
[Serializable, NetSerializable]
protected sealed class NavMapDeltaState(
Dictionary<Vector2i, int[]> modifiedChunks,
Dictionary<NetEntity, NavMapBeacon> beacons,
HashSet<Vector2i> allChunks)
: ComponentState, IComponentDeltaState<NavMapState>
{
public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
public HashSet<Vector2i> AllChunks = allChunks;
// Required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? 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)
{
if (!AllChunks!.Contains(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))
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);
foreach (var (index, data) in state.Chunks)
{
@@ -170,13 +176,13 @@ public abstract class SharedNavMapSystem : EntitySystem
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);
else
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];
// Old prototype or otherwise invalid.
if (!protoManager.TryIndex(loadout.Prototype, out var loadoutProto))
{
loadouts.RemoveAt(i);
continue;
}
// Malicious client maybe, check the group even has it.
if (!groupProto.Loadouts.Contains(loadout.Prototype))
{
loadouts.RemoveAt(i);
continue;
}
// Похуй FIXME
//if (!IsValid(profile, session, loadout.Prototype, collection, out _))
// {
// loadouts.RemoveAt(i);
// continue;
// }
Apply(loadoutProto);
}

View File

@@ -6,7 +6,6 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.PowerCell.Components;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.UserInterface;
using Content.Shared.Wires;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
@@ -38,8 +37,6 @@ public abstract partial class SharedBorgSystem : EntitySystem
SubscribeLocalEvent<BorgChassisComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<BorgChassisComponent, EntRemovedFromContainerMessage>(OnRemoved);
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
//Honk
SubscribeLocalEvent<SharedTTSComponent, ComponentInit>(RandomTTS);
@@ -110,13 +107,6 @@ public abstract partial class SharedBorgSystem : EntitySystem
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)
{

View File

@@ -25,7 +25,6 @@ using Content.Shared.Stacks;
using Content.Shared.Storage.Components;
using Content.Shared.Timing;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -157,9 +156,7 @@ public abstract class SharedStorageSystem : EntitySystem
Grid = new List<Box2i>(component.Grid),
MaxItemSize = component.MaxItemSize,
StoredItems = storedItems,
SavedLocations = component.SavedLocations,
Whitelist = component.Whitelist,
Blacklist = component.Blacklist
SavedLocations = component.SavedLocations
};
}
@@ -171,8 +168,6 @@ public abstract class SharedStorageSystem : EntitySystem
component.Grid.Clear();
component.Grid.AddRange(state.Grid);
component.MaxItemSize = state.MaxItemSize;
component.Whitelist = state.Whitelist;
component.Blacklist = state.Blacklist;
component.StoredItems.Clear();
@@ -1105,7 +1100,7 @@ public abstract class SharedStorageSystem : EntitySystem
/// <returns>true if inserted, false otherwise</returns>
public bool PlayerInsertEntityInWorld(Entity<StorageComponent?> uid, EntityUid player, EntityUid toInsert)
{
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid.Owner))
if (!Resolve(uid, ref uid.Comp) || !_interactionSystem.InRangeUnobstructed(player, uid))
return false;
if (!Insert(uid, toInsert, out _, user: player, uid.Comp))
@@ -1506,9 +1501,5 @@ public abstract class SharedStorageSystem : EntitySystem
public List<Box2i> Grid = new();
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.Hands.Components;
using Content.Shared.Interaction;
using Content.Shared.Strip.Components;
namespace Content.Shared.Strip;
public abstract class SharedStrippableSystem : EntitySystem
{
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StrippingComponent, CanDropTargetEvent>(OnCanDropOn);
SubscribeLocalEvent<StrippableComponent, CanDropDraggedEvent>(OnCanDrop);
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)
@@ -43,20 +29,13 @@ public abstract class SharedStrippableSystem : EntitySystem
if (args.Handled || args.Target != args.User)
return;
if (TryOpenStrippingUi(args.User, (uid, component)))
args.Handled = true;
StartOpeningStripper(args.User, (uid, component));
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)

View File

@@ -57,7 +57,7 @@ namespace Content.Shared.UserInterface
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool BlockSpectators;
public bool AllowSpectator = true;
/// <summary>
/// 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.Popups;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
using Robust.Shared.Containers;
namespace Content.Shared.UserInterface;
@@ -19,12 +19,15 @@ public sealed partial class ActivatableUISystem : EntitySystem
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = 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()
{
base.Initialize();
SubscribeLocalEvent<ActivatableUIComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
@@ -34,24 +37,28 @@ public sealed partial class ActivatableUISystem : EntitySystem
SubscribeLocalEvent<ActivatableUIComponent, GetVerbsEvent<ActivationVerb>>(GetActivationVerb);
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);
InitializePower();
}
private void OnStartup(Entity<ActivatableUIComponent> ent, ref ComponentStartup args)
private void OnBoundInterfaceInteractAttempt(BoundUserInterfaceMessageAttempt ev)
{
if (ent.Comp.Key == null)
{
Log.Error($"Missing UI Key for entity: {ToPrettyString(ent)}");
if (!TryComp(ev.Target, out ActivatableUIComponent? comp))
return;
}
// TODO BUI
// set interaction range to zero to avoid constant range checks.
//
// if (ent.Comp.InHandsOnly && _uiSystem.TryGetInterfaceData(ent.Owner, ent.Comp.Key, out var data))
// data.InteractionRange = 0;
if (!comp.RequireHands)
return;
if (!TryComp(ev.Actor, out HandsComponent? hands) || hands.Hands.Count == 0)
ev.Cancel();
}
private void OnActionPerform(EntityUid uid, UserInterfaceComponent component, OpenUiActionEvent args)
@@ -70,10 +77,9 @@ public sealed partial class ActivatableUISystem : EntitySystem
args.Verbs.Add(new ActivationVerb
{
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component),
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")),
Text = Loc.GetString(component.VerbText)
});
}
@@ -84,10 +90,9 @@ public sealed partial class ActivatableUISystem : EntitySystem
args.Verbs.Add(new Verb
{
// TODO VERBS add "open UI" icon
Act = () => InteractUI(args.User, uid, component),
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")),
Text = Loc.GetString(component.VerbText)
});
}
@@ -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)
@@ -186,7 +191,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
return true;
}
if (!_blockerSystem.CanInteract(user, uiEntity) && (!HasComp<GhostComponent>(user) || aui.BlockSpectators))
if (!_blockerSystem.CanInteract(user, uiEntity) && (!aui.AllowSpectator || !HasComp<GhostComponent>(user)))
return false;
if (aui.RequireHands)
@@ -281,4 +286,47 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (ent.Comp.RequireHands && ent.Comp.InHandsOnly)
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();
// 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
// call ActionBlocker checks, just cache it for the verb request.

View File

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

View File

@@ -3,7 +3,6 @@ using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Tools.Systems;
using Content.Shared.UserInterface;
using Robust.Shared.Audio.Systems;
namespace Content.Shared.Wires;
@@ -11,7 +10,6 @@ namespace Content.Shared.Wires;
public abstract class SharedWiresSystem : EntitySystem
{
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
[Dependency] protected readonly SharedAudioSystem Audio = default!;
[Dependency] protected readonly SharedToolSystem Tool = default!;
@@ -23,9 +21,6 @@ public abstract class SharedWiresSystem : EntitySystem
SubscribeLocalEvent<WiresPanelComponent, WirePanelDoAfterEvent>(OnPanelDoAfter);
SubscribeLocalEvent<WiresPanelComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<WiresPanelComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, ActivatableUIOpenAttemptEvent>(OnAttemptOpenActivatableUI);
SubscribeLocalEvent<ActivatableUIRequiresPanelComponent, PanelChangedEvent>(OnActivatableUIPanelChanged);
}
private void OnPanelDoAfter(EntityUid uid, WiresPanelComponent panel, WirePanelDoAfterEvent args)
@@ -137,21 +132,4 @@ public abstract class SharedWiresSystem : EntitySystem
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-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-no-objectives = {$custody}{$title} was a {$agent}.

View File

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

View File

@@ -194,13 +194,3 @@
contents:
- id: WeaponParticleDecelerator
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:
- type: StorageFill
contents:
- id: EmitterFlatpack
- id: Emitter # TODO change to flatpack
- type: entity
id: CrateEngineeringSingularityCollector

View File

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

View File

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

View File

@@ -184,12 +184,3 @@
- state: overlay
color: "#cec8ac"
- 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:
- type: Instrument
- type: ActivatableUI
blockSpectators: true # otherwise they can play client-side music
allowSpectator: false # otherwise they can play client-side music
inHandsOnly: false
singleUser: true
requireHands: true

View File

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

View File

@@ -325,12 +325,11 @@
emptyCase: { state: empty }
wiredCase: { state: wired }
caseWithTrigger: { state: no-payload }
caseWithPayload: { state: no-trigger }
grenade: { state: complete }
enum.Trigger.TriggerVisuals.VisualState:
enum.ConstructionVisuals.Layer:
Primed: { state: primed }
# Unprimed: <Use state determined by enum.ConstructionVisuals.Layer>
Unprimed: { state: complete }
- type: StaticPrice
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
id: ShuttleGunBase
name: shittle gun
@@ -60,8 +58,7 @@
id: ShuttleGunSvalinnMachineGun
parent: [ ShuttleGunBase, ConstructibleMachine]
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.
suffix: DO NOT MAP
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
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/laser.rsi
@@ -116,7 +113,6 @@
parent: [ ShuttleGunBase, ConstructibleMachine]
name: LSE-1200c "Perforator"
description: Advanced stationary laser unit. Annihilates electronics and is extremely dangerous to health! Uses the power cage to fire.
suffix: DO NOT MAP
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/laser.rsi
@@ -173,7 +169,6 @@
parent: [ShuttleGunBase, ConstructibleMachine]
name: EXP-320g "Friendship"
description: A small stationary grenade launcher that holds 2 grenades.
suffix: DO NOT MAP
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi
@@ -227,7 +222,6 @@
parent: [ShuttleGunBase, ConstructibleMachine]
name: EXP-2100g "Duster"
description: A powerful stationary grenade launcher. A cartridge is required for use.
suffix: DO NOT MAP
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/launcher.rsi
@@ -325,7 +319,6 @@
parent: [ ShuttleGunBase, ConstructibleMachine]
name: PTK-800 "Matter Dematerializer"
description: Salvage stationary mining turret. Gradually accumulates charges on its own, extremely effective for asteroid excavation.
suffix: DO NOT MAP
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Shuttles/kinetic.rsi

View File

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

View File

@@ -35,19 +35,7 @@
id: Thief
components:
- 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
agentName: thief-round-end-agent-name
definitions:
- prefRoles: [ Thief ]
maxRange:

View File

@@ -64,9 +64,9 @@
roundEndDelay: 10
- type: entity
abstract: true
id: Nukeops
parent: BaseGameRule
id: BaseNukeopsRule
noSpawn: true
components:
- type: GameRule
minPlayers: 20 # Honk
@@ -75,16 +75,6 @@
- operationPrefix
- operationSuffix
- type: NukeopsRule
- type: AntagSelection
- type: AntagLoadProfileRule
- type: entity
noSpawn: true
parent: BaseNukeopsRule
id: Nukeops
components:
- type: GameRule
minPlayers: 20
- type: LoadMapRule
gameMap: NukieOutpost
- type: AntagSelection
@@ -148,30 +138,16 @@
prototype: Nukeops
- 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
parent: BaseGameRule
noSpawn: true
components:
- type: GameRule
minPlayers: 5
delay:
min: 240
max: 420
- type: TraitorRule
- type: AntagSelection
definitions:
- prefRoles: [ Traitor ]

View File

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

View File

@@ -50,12 +50,6 @@
store: payloadTrigger
name: триггер
doAfter: 0.5
- to: caseWithPayload
steps:
- tag: Payload
store: payload
name: Payload
doAfter: 0.5
- node: caseWithTrigger
actions:
@@ -77,26 +71,6 @@
name: заряд
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
actions:
- !type:AppearanceChange

View File

@@ -1,7 +1,7 @@
{
"version": 1,
"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": {
"x": 32,
"y": 32
@@ -19,10 +19,6 @@
"name": "no-payload",
"directions": 1
},
{
"name": "no-trigger",
"directions": 1
},
{
"name": "complete",
"directions": 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B