Merge pull request #190 from crystallpunk-14/ed-30-05-2024-upstream
Ed 30 05 2024 upstream
This commit is contained in:
@@ -1,27 +0,0 @@
|
||||
using Content.Shared.Cabinet;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Cabinet;
|
||||
|
||||
public sealed class ItemCabinetSystem : SharedItemCabinetSystem
|
||||
{
|
||||
protected override void UpdateAppearance(EntityUid uid, ItemCabinetComponent? cabinet = null)
|
||||
{
|
||||
if (!Resolve(uid, ref cabinet))
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
var state = cabinet.Opened ? cabinet.OpenState : cabinet.ClosedState;
|
||||
if (state != null)
|
||||
sprite.LayerSetState(ItemCabinetVisualLayers.Door, state);
|
||||
sprite.LayerSetVisible(ItemCabinetVisualLayers.ContainsItem, cabinet.CabinetSlot.HasItem);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ItemCabinetVisualLayers
|
||||
{
|
||||
Door,
|
||||
ContainsItem
|
||||
}
|
||||
@@ -73,11 +73,6 @@ 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.
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'observe-warning-1'}"/>
|
||||
<Label Text="{Loc 'observe-warning-2'}"/>
|
||||
<BoxContainer Orientation="Horizontal" >
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="NevermindButton" Text="{Loc 'observe-nevermind'}" SizeFlagsStretchRatio="1"/>
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<cc:CommandButton Command="observe" Name="ObserveButton" StyleClasses="Caution" Text="{Loc 'observe-confirm'}" SizeFlagsStretchRatio="1"/>
|
||||
<cc:CommandButton Command="observe" Name="ObserveButton" StyleClasses="Caution" Text="{Loc 'observe-confirm'}" SizeFlagsStretchRatio="1"/>
|
||||
<cc:CommandButton Command="observe admin" Name="ObserveAsAdminButton" Text="{Loc 'observe-as-admin'}" SizeFlagsStretchRatio="1" Visible="False"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Shared.Administration.Managers;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -9,11 +11,22 @@ namespace Content.Client.Lobby.UI;
|
||||
[UsedImplicitly]
|
||||
public sealed partial class ObserveWarningWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public ObserveWarningWindow()
|
||||
{
|
||||
Title = Loc.GetString("observe-warning-window-title");
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
var player = _playerManager.LocalSession;
|
||||
|
||||
if (player != null && _adminManager.IsAdmin(player))
|
||||
{
|
||||
ObserveButton.Text = Loc.GetString("observe-as-player");
|
||||
ObserveAsAdminButton.Visible = true;
|
||||
ObserveAsAdminButton.OnPressed += _ => { this.Close(); };
|
||||
}
|
||||
|
||||
ObserveButton.OnPressed += _ => { this.Close(); };
|
||||
NevermindButton.OnPressed += _ => { this.Close(); };
|
||||
|
||||
@@ -10,51 +10,56 @@ public sealed class FloorOcclusionSystem : SharedFloorOcclusionSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, ComponentStartup>(OnOcclusionStartup);
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, ComponentShutdown>(OnOcclusionShutdown);
|
||||
SubscribeLocalEvent<FloorOcclusionComponent, AfterAutoHandleStateEvent>(OnOcclusionAuto);
|
||||
}
|
||||
|
||||
private void OnOcclusionAuto(EntityUid uid, FloorOcclusionComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnOcclusionAuto(Entity<FloorOcclusionComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
SetEnabled(uid, component, component.Enabled);
|
||||
SetShader(ent.Owner, ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void OnOcclusionStartup(EntityUid uid, FloorOcclusionComponent component, ComponentStartup args)
|
||||
private void OnOcclusionStartup(Entity<FloorOcclusionComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
if (component.Enabled && TryComp<SpriteComponent>(uid, out var sprite))
|
||||
SetShader(sprite, true);
|
||||
SetShader(ent.Owner, ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
protected override void SetEnabled(EntityUid uid, FloorOcclusionComponent component, bool enabled)
|
||||
private void OnOcclusionShutdown(Entity<FloorOcclusionComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (component.Enabled == enabled)
|
||||
SetShader(ent.Owner, false);
|
||||
}
|
||||
|
||||
protected override void SetEnabled(Entity<FloorOcclusionComponent> entity)
|
||||
{
|
||||
SetShader(entity.Owner, entity.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void SetShader(Entity<SpriteComponent?> sprite, bool enabled)
|
||||
{
|
||||
if (!_spriteQuery.Resolve(sprite.Owner, ref sprite.Comp, false))
|
||||
return;
|
||||
|
||||
base.SetEnabled(uid, component, enabled);
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
SetShader(sprite, enabled);
|
||||
}
|
||||
|
||||
private void SetShader(SpriteComponent sprite, bool enabled)
|
||||
{
|
||||
var shader = _proto.Index<ShaderPrototype>("HorizontalCut").Instance();
|
||||
|
||||
if (sprite.PostShader is not null && sprite.PostShader != shader)
|
||||
if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
sprite.PostShader = shader;
|
||||
sprite.Comp.PostShader = shader;
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.PostShader = null;
|
||||
sprite.Comp.PostShader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,5 @@
|
||||
<tabs:GraphicsTab Name="GraphicsTab" />
|
||||
<tabs:KeyRebindTab Name="KeyRebindTab" />
|
||||
<tabs:AudioTab Name="AudioTab" />
|
||||
<tabs:NetworkTab Name="NetworkTab" />
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -19,7 +19,6 @@ 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"));
|
||||
|
||||
UpdateTabs();
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<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"
|
||||
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>
|
||||
@@ -1,125 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
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;
|
||||
using System.Numerics;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Client.UserInterface.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
@@ -79,6 +78,10 @@ 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);
|
||||
|
||||
@@ -91,10 +94,6 @@ 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.
|
||||
@@ -122,10 +121,13 @@ public sealed class EntityHealthBarOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Returns a ratio between 0 and 1, and whether the entity is in crit.
|
||||
/// </summary>
|
||||
private (float, bool) CalcProgress(EntityUid uid, MobStateComponent component, DamageableComponent dmg, MobThresholdsComponent thresholds)
|
||||
private (float ratio, bool inCrit)? 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);
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Containers;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Popups
|
||||
{
|
||||
@@ -29,11 +31,11 @@ namespace Content.Client.Popups
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public IReadOnlyList<WorldPopupLabel> WorldLabels => _aliveWorldLabels;
|
||||
public IReadOnlyList<CursorPopupLabel> CursorLabels => _aliveCursorLabels;
|
||||
public IReadOnlyCollection<WorldPopupLabel> WorldLabels => _aliveWorldLabels.Values;
|
||||
public IReadOnlyCollection<CursorPopupLabel> CursorLabels => _aliveCursorLabels.Values;
|
||||
|
||||
private readonly List<WorldPopupLabel> _aliveWorldLabels = new();
|
||||
private readonly List<CursorPopupLabel> _aliveCursorLabels = new();
|
||||
private readonly Dictionary<WorldPopupData, WorldPopupLabel> _aliveWorldLabels = new();
|
||||
private readonly Dictionary<CursorPopupData, CursorPopupLabel> _aliveCursorLabels = new();
|
||||
|
||||
public const float MinimumPopupLifetime = 0.7f;
|
||||
public const float MaximumPopupLifetime = 5f;
|
||||
@@ -65,6 +67,15 @@ namespace Content.Client.Popups
|
||||
.RemoveOverlay<PopupOverlay>();
|
||||
}
|
||||
|
||||
private void WrapAndRepeatPopup(PopupLabel existingLabel, string popupMessage)
|
||||
{
|
||||
existingLabel.TotalTime = 0;
|
||||
existingLabel.Repeats += 1;
|
||||
existingLabel.Text = Loc.GetString("popup-system-repeated-popup-stacking-wrap",
|
||||
("popup-message", popupMessage),
|
||||
("count", existingLabel.Repeats));
|
||||
}
|
||||
|
||||
private void PopupMessage(string? message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
|
||||
{
|
||||
if (message == null)
|
||||
@@ -78,13 +89,20 @@ namespace Content.Client.Popups
|
||||
_replayRecording.RecordClientMessage(new PopupCoordinatesEvent(message, type, GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
var popupData = new WorldPopupData(message, type, coordinates, entity);
|
||||
if (_aliveWorldLabels.TryGetValue(popupData, out var existingLabel))
|
||||
{
|
||||
WrapAndRepeatPopup(existingLabel, popupData.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var label = new WorldPopupLabel(coordinates)
|
||||
{
|
||||
Text = message,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
_aliveWorldLabels.Add(label);
|
||||
_aliveWorldLabels.Add(popupData, label);
|
||||
}
|
||||
|
||||
#region Abstract Method Implementations
|
||||
@@ -113,13 +131,20 @@ namespace Content.Client.Popups
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
_replayRecording.RecordClientMessage(new PopupCursorEvent(message, type));
|
||||
|
||||
var popupData = new CursorPopupData(message, type);
|
||||
if (_aliveCursorLabels.TryGetValue(popupData, out var existingLabel))
|
||||
{
|
||||
WrapAndRepeatPopup(existingLabel, popupData.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
var label = new CursorPopupLabel(_inputManager.MouseScreenPosition)
|
||||
{
|
||||
Text = message,
|
||||
Type = type,
|
||||
};
|
||||
|
||||
_aliveCursorLabels.Add(label);
|
||||
_aliveCursorLabels.Add(popupData, label);
|
||||
}
|
||||
|
||||
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
|
||||
@@ -249,27 +274,37 @@ namespace Content.Client.Popups
|
||||
if (_aliveWorldLabels.Count == 0 && _aliveCursorLabels.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _aliveWorldLabels.Count; i++)
|
||||
if (_aliveWorldLabels.Count > 0)
|
||||
{
|
||||
var label = _aliveWorldLabels[i];
|
||||
label.TotalTime += frameTime;
|
||||
|
||||
if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
|
||||
var aliveWorldToRemove = new ValueList<WorldPopupData>();
|
||||
foreach (var (data, label) in _aliveWorldLabels)
|
||||
{
|
||||
_aliveWorldLabels.RemoveSwap(i);
|
||||
i--;
|
||||
label.TotalTime += frameTime;
|
||||
if (label.TotalTime > GetPopupLifetime(label) || Deleted(label.InitialPos.EntityId))
|
||||
{
|
||||
aliveWorldToRemove.Add(data);
|
||||
}
|
||||
}
|
||||
foreach (var data in aliveWorldToRemove)
|
||||
{
|
||||
_aliveWorldLabels.Remove(data);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _aliveCursorLabels.Count; i++)
|
||||
if (_aliveCursorLabels.Count > 0)
|
||||
{
|
||||
var label = _aliveCursorLabels[i];
|
||||
label.TotalTime += frameTime;
|
||||
|
||||
if (label.TotalTime > GetPopupLifetime(label))
|
||||
var aliveCursorToRemove = new ValueList<CursorPopupData>();
|
||||
foreach (var (data, label) in _aliveCursorLabels)
|
||||
{
|
||||
_aliveCursorLabels.RemoveSwap(i);
|
||||
i--;
|
||||
label.TotalTime += frameTime;
|
||||
if (label.TotalTime > GetPopupLifetime(label))
|
||||
{
|
||||
aliveCursorToRemove.Add(data);
|
||||
}
|
||||
}
|
||||
foreach (var data in aliveCursorToRemove)
|
||||
{
|
||||
_aliveCursorLabels.Remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,29 +314,32 @@ namespace Content.Client.Popups
|
||||
public PopupType Type = PopupType.Small;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
public float TotalTime { get; set; }
|
||||
public int Repeats = 1;
|
||||
}
|
||||
|
||||
public sealed class CursorPopupLabel : PopupLabel
|
||||
{
|
||||
public ScreenCoordinates InitialPos;
|
||||
|
||||
public CursorPopupLabel(ScreenCoordinates screenCoords)
|
||||
{
|
||||
InitialPos = screenCoords;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WorldPopupLabel : PopupLabel
|
||||
public sealed class WorldPopupLabel(EntityCoordinates coordinates) : PopupLabel
|
||||
{
|
||||
/// <summary>
|
||||
/// The original EntityCoordinates of the label.
|
||||
/// </summary>
|
||||
public EntityCoordinates InitialPos;
|
||||
|
||||
public WorldPopupLabel(EntityCoordinates coordinates)
|
||||
{
|
||||
InitialPos = coordinates;
|
||||
}
|
||||
public EntityCoordinates InitialPos = coordinates;
|
||||
}
|
||||
|
||||
public sealed class CursorPopupLabel(ScreenCoordinates screenCoords) : PopupLabel
|
||||
{
|
||||
public ScreenCoordinates InitialPos = screenCoords;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private record struct WorldPopupData(
|
||||
string Message,
|
||||
PopupType Type,
|
||||
EntityCoordinates Coordinates,
|
||||
EntityUid? Entity);
|
||||
|
||||
[UsedImplicitly]
|
||||
private record struct CursorPopupData(
|
||||
string Message,
|
||||
PopupType Type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Client.Power;
|
||||
|
||||
public sealed class ActivatableUIRequiresPowerSystem : EntitySystem
|
||||
public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, ActivatableUIOpenAttemptEvent>(OnActivate);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
protected override void OnActivate(Entity<ActivatableUIRequiresPowerComponent> ent, ref ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
// Client can't predict the power properly at the moment so rely upon the server to do it.
|
||||
if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryComp<WiresPanelComponent>(ent.Owner, out var panel) && panel.Open)
|
||||
return;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent.Owner)), args.User, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Power.Components;
|
||||
|
||||
namespace Content.Client.Power.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
|
||||
{
|
||||
}
|
||||
23
Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
Normal file
23
Content.Client/Power/EntitySystems/PowerReceiverSystem.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Content.Client.Power.Components;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not ApcPowerReceiverComponentState state)
|
||||
return;
|
||||
|
||||
component.Powered = state.Powered;
|
||||
}
|
||||
}
|
||||
16
Content.Client/Power/EntitySystems/StaticPowerSystem.cs
Normal file
16
Content.Client/Power/EntitySystems/StaticPowerSystem.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Content.Client.Power.Components;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public static class StaticPowerSystem
|
||||
{
|
||||
// Using this makes the call shorter.
|
||||
// ReSharper disable once UnusedParameter.Global
|
||||
public static bool IsPowered(this EntitySystem system, EntityUid uid, IEntityManager entManager, ApcPowerReceiverComponent? receiver = null)
|
||||
{
|
||||
if (receiver == null && !entManager.TryGetComponent(uid, out receiver))
|
||||
return false;
|
||||
|
||||
return receiver.Powered;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
_window.OpenCentered();
|
||||
_window.OnNameConfirm += SendDeviceName;
|
||||
_window.OnNetworkConfirm += SendSelectedNetwork;
|
||||
|
||||
_window.OnClose += Close;
|
||||
}
|
||||
|
||||
private void SendSelectedNetwork(int idx)
|
||||
@@ -63,7 +63,8 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window!.Dispose();
|
||||
_window?.Dispose();
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +119,4 @@ public class ActionButtonContainer : GridContainer
|
||||
yield return button;
|
||||
}
|
||||
}
|
||||
|
||||
~ActionButtonContainer()
|
||||
{
|
||||
UserInterfaceManager.GetUIController<ActionUIController>().RemoveActionContainer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,4 @@ public sealed class ItemSlotButtonContainer : ItemSlotUIContainer<SlotControl>
|
||||
{
|
||||
_inventoryController = UserInterfaceManager.GetUIController<InventoryUIController>();
|
||||
}
|
||||
|
||||
~ItemSlotButtonContainer()
|
||||
{
|
||||
_inventoryController.RemoveSlotGroup(SlotGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
|
||||
public sealed class ItemGridPiece : Control
|
||||
public sealed class ItemGridPiece : Control, IEntityControl
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly StorageUIController _storageController;
|
||||
@@ -287,6 +287,8 @@ public sealed class ItemGridPiece : Control
|
||||
var actualSize = new Vector2(boxSize.X + 1, boxSize.Y + 1);
|
||||
return actualSize * new Vector2i(8, 8);
|
||||
}
|
||||
|
||||
public EntityUid? UiEntity => Entity;
|
||||
}
|
||||
|
||||
public enum ItemGridPieceMarks
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -128,4 +131,29 @@ public sealed partial class TestPair
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for enabling or disabling a antag role
|
||||
/// </summary>
|
||||
public async Task SetAntagPref(ProtoId<AntagPrototype> id, bool value)
|
||||
{
|
||||
var prefMan = Server.ResolveDependency<IServerPreferencesManager>();
|
||||
|
||||
var prefs = prefMan.GetPreferences(Client.User!.Value);
|
||||
// what even is the point of ICharacterProfile if we always cast it to HumanoidCharacterProfile to make it usable?
|
||||
var profile = (HumanoidCharacterProfile) prefs.SelectedCharacter;
|
||||
|
||||
Assert.That(profile.AntagPreferences.Contains(id), Is.EqualTo(!value));
|
||||
var newProfile = profile.WithAntagPreference(id, value);
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
prefMan.SetProfile(Client.User.Value, prefs.SelectedCharacterIndex, newProfile).Wait();
|
||||
});
|
||||
|
||||
// And why the fuck does it always create a new preference and profile object instead of just reusing them?
|
||||
var newPrefs = prefMan.GetPreferences(Client.User.Value);
|
||||
var newProf = (HumanoidCharacterProfile) newPrefs.SelectedCharacter;
|
||||
Assert.That(newProf.AntagPreferences.Contains(id), Is.EqualTo(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +65,11 @@ public static partial class PoolManager
|
||||
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
// Server-only systems (i.e., systems that subscribe to events with server-only components)
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
entSysMan.LoadExtraSystemType<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
|
||||
entSysMan.LoadExtraSystemType<InteractionSystemTests.TestInteractionSystem>();
|
||||
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
|
||||
entSysMan.LoadExtraSystemType<TestDestructibleListenerSystem>();
|
||||
|
||||
IoCManager.Resolve<ILogManager>().GetSawmill("loc").Level = LogLevel.Error;
|
||||
IoCManager.Resolve<IConfigurationManager>()
|
||||
.OnValueChanged(RTCVars.FailureLogLevel, value => logHandler.FailureLevel = value, true);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Antag.Components;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.GameRules;
|
||||
|
||||
// Once upon a time, players in the lobby weren't ever considered eligible for antag roles.
|
||||
// Lets not let that happen again.
|
||||
[TestFixture]
|
||||
public sealed class AntagPreferenceTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestLobbyPlayersValid()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
DummyTicker = false,
|
||||
Connected = true,
|
||||
InLobby = true
|
||||
});
|
||||
|
||||
var server = pair.Server;
|
||||
var client = pair.Client;
|
||||
var ticker = server.System<GameTicker>();
|
||||
var sys = server.System<AntagSelectionSystem>();
|
||||
|
||||
// Initially in the lobby
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
EntityUid uid = default;
|
||||
await server.WaitPost(() => uid = server.EntMan.Spawn("Traitor"));
|
||||
var rule = new Entity<AntagSelectionComponent>(uid, server.EntMan.GetComponent<AntagSelectionComponent>(uid));
|
||||
var def = rule.Comp.Definitions.Single();
|
||||
|
||||
// IsSessionValid & IsEntityValid are preference agnostic and should always be true for players in the lobby.
|
||||
// Though maybe that will change in the future, but then GetPlayerPool() needs to be updated to reflect that.
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
|
||||
// By default, traitor/antag preferences are disabled, so the pool should be empty.
|
||||
var sessions = new List<ICommonSession>{pair.Player!};
|
||||
var pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
// Opt into the traitor role.
|
||||
await pair.SetAntagPref("Traitor", true);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(1));
|
||||
pool.TryPickAndTake(pair.Server.ResolveDependency<IRobustRandom>(), out var picked);
|
||||
Assert.That(picked, Is.EqualTo(pair.Player));
|
||||
Assert.That(sessions.Count, Is.EqualTo(1));
|
||||
|
||||
// opt back out
|
||||
await pair.SetAntagPref("Traitor", false);
|
||||
|
||||
Assert.That(sys.IsSessionValid(rule, pair.Player, def), Is.True);
|
||||
Assert.That(sys.IsEntityValid(client.AttachedEntity, def), Is.True);
|
||||
pool = sys.GetPlayerPool(rule, sessions, def);
|
||||
Assert.That(pool.Count, Is.EqualTo(0));
|
||||
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@ public sealed class NukeOpsTest
|
||||
Assert.That(client.AttachedEntity, Is.Null);
|
||||
Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay));
|
||||
|
||||
// Opt into the nukies role.
|
||||
await pair.SetAntagPref("NukeopsCommander", true);
|
||||
|
||||
// There are no grids or maps
|
||||
Assert.That(entMan.Count<MapComponent>(), Is.Zero);
|
||||
Assert.That(entMan.Count<MapGridComponent>(), Is.Zero);
|
||||
@@ -198,6 +201,7 @@ public sealed class NukeOpsTest
|
||||
|
||||
ticker.SetGamePreset((GamePresetPrototype?)null);
|
||||
server.CfgMan.SetCVar(CCVars.GridFill, false);
|
||||
await pair.SetAntagPref("NukeopsCommander", false);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,6 @@ namespace Content.IntegrationTests.Tests.Interaction.Click
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Reflect(false)]
|
||||
public sealed class TestInteractionSystem : EntitySystem
|
||||
{
|
||||
public EntityEventHandler<InteractUsingEvent>? InteractUsingEvent;
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Content.IntegrationTests.Tests
|
||||
[TestOf(typeof(RoundRestartCleanupEvent))]
|
||||
public sealed class ResettingEntitySystemTests
|
||||
{
|
||||
[Reflect(false)]
|
||||
public sealed class TestRoundRestartCleanupEvent : EntitySystem
|
||||
{
|
||||
public bool HasBeenReset { get; set; }
|
||||
@@ -49,8 +48,6 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
system.HasBeenReset = false;
|
||||
|
||||
Assert.That(system.HasBeenReset, Is.False);
|
||||
|
||||
gameTicker.RestartRound();
|
||||
|
||||
Assert.That(system.HasBeenReset);
|
||||
|
||||
@@ -135,6 +135,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
_idCard.TryChangeJobDepartment(targetId, job);
|
||||
}
|
||||
|
||||
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);
|
||||
|
||||
if (!newAccessList.TrueForAll(x => component.AccessLevels.Contains(x)))
|
||||
{
|
||||
_sawmill.Warning($"User {ToPrettyString(uid)} tried to write unknown access tag.");
|
||||
@@ -168,8 +170,6 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
This current implementation is pretty shit as it logs 27 entries (27 lines) if someone decides to give themselves AA*/
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
|
||||
|
||||
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -18,8 +18,6 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -69,20 +67,21 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
ChangeAnomalyStability(anomaly, Random.NextFloat(anomaly.Comp.InitialStabilityRange.Item1 , anomaly.Comp.InitialStabilityRange.Item2), anomaly.Comp);
|
||||
ChangeAnomalySeverity(anomaly, Random.NextFloat(anomaly.Comp.InitialSeverityRange.Item1, anomaly.Comp.InitialSeverityRange.Item2), anomaly.Comp);
|
||||
|
||||
ShuffleParticlesEffect(anomaly.Comp);
|
||||
ShuffleParticlesEffect(anomaly);
|
||||
anomaly.Comp.Continuity = _random.NextFloat(anomaly.Comp.MinContituty, anomaly.Comp.MaxContituty);
|
||||
SetBehavior(anomaly, GetRandomBehavior());
|
||||
}
|
||||
|
||||
public void ShuffleParticlesEffect(AnomalyComponent anomaly)
|
||||
public void ShuffleParticlesEffect(Entity<AnomalyComponent> anomaly)
|
||||
{
|
||||
var particles = new List<AnomalousParticleType>
|
||||
{ AnomalousParticleType.Delta, AnomalousParticleType.Epsilon, AnomalousParticleType.Zeta, AnomalousParticleType.Sigma };
|
||||
|
||||
anomaly.SeverityParticleType = Random.PickAndTake(particles);
|
||||
anomaly.DestabilizingParticleType = Random.PickAndTake(particles);
|
||||
anomaly.WeakeningParticleType = Random.PickAndTake(particles);
|
||||
anomaly.TransformationParticleType = Random.PickAndTake(particles);
|
||||
anomaly.Comp.SeverityParticleType = Random.PickAndTake(particles);
|
||||
anomaly.Comp.DestabilizingParticleType = Random.PickAndTake(particles);
|
||||
anomaly.Comp.WeakeningParticleType = Random.PickAndTake(particles);
|
||||
anomaly.Comp.TransformationParticleType = Random.PickAndTake(particles);
|
||||
Dirty(anomaly);
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<AnomalyComponent> anomaly, ref ComponentShutdown args)
|
||||
@@ -198,14 +197,12 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
if (anomaly.Comp.CurrentBehavior != null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
//event broadcast
|
||||
var ev = new AnomalyBehaviorChangedEvent(anomaly, anomaly.Comp.CurrentBehavior, behaviorProto);
|
||||
anomaly.Comp.CurrentBehavior = behaviorProto;
|
||||
RaiseLocalEvent(anomaly, ref ev, true);
|
||||
|
||||
var behavior = _prototype.Index(behaviorProto);
|
||||
|
||||
EntityManager.AddComponents(anomaly, behavior.Components);
|
||||
|
||||
var ev = new AnomalyBehaviorChangedEvent(anomaly, anomaly.Comp.CurrentBehavior, behaviorProto);
|
||||
RaiseLocalEvent(anomaly, ref ev, true);
|
||||
}
|
||||
|
||||
private void RemoveBehavior(Entity<AnomalyComponent> anomaly, ProtoId<AnomalyBehaviorPrototype> behaviorProto)
|
||||
@@ -213,7 +210,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
if (anomaly.Comp.CurrentBehavior == null)
|
||||
return;
|
||||
|
||||
var behavior = _prototype.Index(anomaly.Comp.CurrentBehavior.Value);
|
||||
var behavior = _prototype.Index(behaviorProto);
|
||||
|
||||
EntityManager.RemoveComponents(anomaly, behavior.Components);
|
||||
}
|
||||
|
||||
@@ -15,26 +15,26 @@ public sealed class ShuffleParticlesAnomalySystem : EntitySystem
|
||||
SubscribeLocalEvent<ShuffleParticlesAnomalyComponent, StartCollideEvent>(OnStartCollide);
|
||||
}
|
||||
|
||||
private void OnStartCollide(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, StartCollideEvent args)
|
||||
private void OnStartCollide(Entity<ShuffleParticlesAnomalyComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (!TryComp<AnomalyComponent>(uid, out var anomaly))
|
||||
if (!TryComp<AnomalyComponent>(ent, out var anomaly))
|
||||
return;
|
||||
|
||||
if (shuffle.ShuffleOnParticleHit && _random.Prob(shuffle.Prob))
|
||||
_anomaly.ShuffleParticlesEffect(anomaly);
|
||||
|
||||
if (!TryComp<AnomalousParticleComponent>(args.OtherEntity, out var particle))
|
||||
if (!HasComp<AnomalousParticleComponent>(args.OtherEntity))
|
||||
return;
|
||||
|
||||
if (ent.Comp.ShuffleOnParticleHit && _random.Prob(ent.Comp.Prob))
|
||||
_anomaly.ShuffleParticlesEffect((ent, anomaly));
|
||||
}
|
||||
|
||||
private void OnPulse(EntityUid uid, ShuffleParticlesAnomalyComponent shuffle, AnomalyPulseEvent args)
|
||||
private void OnPulse(Entity<ShuffleParticlesAnomalyComponent> ent, ref AnomalyPulseEvent args)
|
||||
{
|
||||
if (!TryComp<AnomalyComponent>(uid, out var anomaly))
|
||||
if (!TryComp<AnomalyComponent>(ent, out var anomaly))
|
||||
return;
|
||||
|
||||
if (shuffle.ShuffleOnPulse && _random.Prob(shuffle.Prob))
|
||||
if (ent.Comp.ShuffleOnPulse && _random.Prob(ent.Comp.Prob))
|
||||
{
|
||||
_anomaly.ShuffleParticlesEffect(anomaly);
|
||||
_anomaly.ShuffleParticlesEffect((ent, anomaly));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
Content.Server/Antag/AntagObjectivesSystem.cs
Normal file
35
Content.Server/Antag/AntagObjectivesSystem.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Content.Server/Antag/AntagRandomObjectivesSystem.cs
Normal file
52
Content.Server/Antag/AntagRandomObjectivesSystem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,11 @@ public sealed partial class AntagSelectionSystem
|
||||
if (mindCount >= totalTargetCount)
|
||||
return false;
|
||||
|
||||
// TODO ANTAG fix this
|
||||
// If here are two definitions with 1/10 and 10/10 slots filled, this will always return the second definition
|
||||
// even though it has already met its target
|
||||
// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA I fucking hate game ticker code.
|
||||
// It needs to track selected minds for each definition independently.
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
var target = GetTargetAntagCount(ent, null, def);
|
||||
@@ -47,12 +52,26 @@ public sealed partial class AntagSelectionSystem
|
||||
/// Gets the number of antagonists that should be present for a given rule based on the provided pool.
|
||||
/// A null pool will simply use the player count.
|
||||
/// </summary>
|
||||
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelectionPlayerPool? pool = null)
|
||||
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, int? playerCount = null)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
count += GetTargetAntagCount(ent, pool, def);
|
||||
count += GetTargetAntagCount(ent, playerCount, def);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public int GetTotalPlayerCount(IList<ICommonSession> pool)
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var session in pool)
|
||||
{
|
||||
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
|
||||
continue;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
@@ -62,10 +81,13 @@ public sealed partial class AntagSelectionSystem
|
||||
/// Gets the number of antagonists that should be present for a given antag definition based on the provided pool.
|
||||
/// A null pool will simply use the player count.
|
||||
/// </summary>
|
||||
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def)
|
||||
public int GetTargetAntagCount(Entity<AntagSelectionComponent> ent, int? playerCount, AntagSelectionDefinition def)
|
||||
{
|
||||
var poolSize = pool?.Count ?? _playerManager.Sessions
|
||||
.Count(s => s.State.Status is not SessionStatus.Disconnected and not SessionStatus.Zombie);
|
||||
// TODO ANTAG
|
||||
// make pool non-nullable
|
||||
// Review uses and ensure that people are INTENTIONALLY including players in the lobby if this is a mid-round
|
||||
// antag selection.
|
||||
var poolSize = playerCount ?? GetTotalPlayerCount(_playerManager.Sessions);
|
||||
|
||||
// factor in other definitions' affect on the count.
|
||||
var countOffset = 0;
|
||||
@@ -124,7 +146,7 @@ public sealed partial class AntagSelectionSystem
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Helper specifically for <see cref="ObjectivesTextGetInfoEvent"/>
|
||||
/// Helper to get just the mind entities and not names.
|
||||
/// </remarks>
|
||||
public List<EntityUid> GetAntagMindEntityUids(Entity<AntagSelectionComponent?> ent)
|
||||
{
|
||||
|
||||
@@ -7,12 +7,14 @@ 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;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Antag;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Players;
|
||||
@@ -24,6 +26,7 @@ using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
|
||||
@@ -50,6 +53,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
SubscribeLocalEvent<GhostRoleAntagSpawnerComponent, TakeGhostRoleEvent>(OnTakeGhostRole);
|
||||
|
||||
SubscribeLocalEvent<AntagSelectionComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
|
||||
|
||||
SubscribeLocalEvent<RulePlayerSpawningEvent>(OnPlayerSpawning);
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnJobsAssigned);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
|
||||
@@ -82,10 +87,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
continue;
|
||||
|
||||
if (comp.SelectionsComplete)
|
||||
return;
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp), pool);
|
||||
comp.SelectionsComplete = true;
|
||||
|
||||
foreach (var session in comp.SelectedSessions)
|
||||
{
|
||||
@@ -103,11 +107,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn)
|
||||
continue;
|
||||
|
||||
if (comp.SelectionsComplete)
|
||||
continue;
|
||||
|
||||
ChooseAntags((uid, comp));
|
||||
comp.SelectionsComplete = true;
|
||||
ChooseAntags((uid, comp), args.Players);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,12 +123,18 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var antag, out _))
|
||||
{
|
||||
// TODO ANTAG
|
||||
// what why aasdiuhasdopiuasdfhksad
|
||||
// stop this insanity please
|
||||
// probability of antag assignment shouldn't depend on the order in which rules are returned by the query.
|
||||
if (!RobustRandom.Prob(LateJoinRandomChance))
|
||||
continue;
|
||||
|
||||
if (!antag.Definitions.Any(p => p.LateJoinAdditional))
|
||||
continue;
|
||||
|
||||
DebugTools.AssertEqual(antag.SelectionTime, AntagSelectionTime.PostPlayerSpawn);
|
||||
|
||||
if (!TryGetNextAvailableDefinition((uid, antag), out var def))
|
||||
continue;
|
||||
|
||||
@@ -161,57 +167,62 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
if (component.SelectionsComplete)
|
||||
return;
|
||||
|
||||
// If the round has not yet started, we defer antag selection until roundstart
|
||||
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
||||
if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn)
|
||||
if (component.SelectionsComplete)
|
||||
return;
|
||||
|
||||
ChooseAntags((uid, component));
|
||||
component.SelectionsComplete = true;
|
||||
}
|
||||
var players = _playerManager.Sessions
|
||||
.Where(x => GameTicker.PlayerGameStatuses[x.UserId] == PlayerGameStatus.JoinedGame)
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the current selection of players
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent)
|
||||
{
|
||||
var sessions = _playerManager.Sessions.ToList();
|
||||
ChooseAntags(ent, sessions);
|
||||
ChooseAntags((uid, component), players);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, List<ICommonSession> pool)
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool)
|
||||
{
|
||||
if (ent.Comp.SelectionsComplete)
|
||||
return;
|
||||
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
ChooseAntags(ent, pool, def);
|
||||
}
|
||||
|
||||
ent.Comp.SelectionsComplete = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chooses antagonists from the given selection of players for the given antag definition.
|
||||
/// </summary>
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, List<ICommonSession> pool, AntagSelectionDefinition def)
|
||||
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def)
|
||||
{
|
||||
var playerPool = GetPlayerPool(ent, pool, def);
|
||||
var count = GetTargetAntagCount(ent, playerPool, def);
|
||||
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
|
||||
|
||||
// if there is both a spawner and players getting picked, let it fall back to a spawner.
|
||||
var noSpawner = def.SpawnerPrototype == null;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var session = (ICommonSession?) null;
|
||||
if (def.PickPlayer)
|
||||
{
|
||||
if (!playerPool.TryPickAndTake(RobustRandom, out session))
|
||||
if (!playerPool.TryPickAndTake(RobustRandom, out session) && noSpawner)
|
||||
{
|
||||
Log.Warning($"Couldn't pick a player for {ToPrettyString(ent):rule}, no longer choosing antags for this definition");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ent.Comp.SelectedSessions.Contains(session))
|
||||
if (session != null && ent.Comp.SelectedSessions.Contains(session))
|
||||
{
|
||||
Log.Warning($"Somehow picked {session} for an antag when this rule already selected them previously");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
MakeAntag(ent, session, def);
|
||||
@@ -321,20 +332,15 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Gets an ordered player pool based on player preferences and the antagonist definition.
|
||||
/// </summary>
|
||||
public AntagSelectionPlayerPool GetPlayerPool(Entity<AntagSelectionComponent> ent, List<ICommonSession> sessions, AntagSelectionDefinition def)
|
||||
public AntagSelectionPlayerPool GetPlayerPool(Entity<AntagSelectionComponent> ent, IList<ICommonSession> sessions, AntagSelectionDefinition def)
|
||||
{
|
||||
var preferredList = new List<ICommonSession>();
|
||||
var fallbackList = new List<ICommonSession>();
|
||||
var unwantedList = new List<ICommonSession>();
|
||||
var invalidList = new List<ICommonSession>();
|
||||
foreach (var session in sessions)
|
||||
{
|
||||
if (!IsSessionValid(ent, session, def) ||
|
||||
!IsEntityValid(session.AttachedEntity, def))
|
||||
{
|
||||
invalidList.Add(session);
|
||||
continue;
|
||||
}
|
||||
|
||||
var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter;
|
||||
if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p)))
|
||||
@@ -345,13 +351,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
{
|
||||
fallbackList.Add(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
unwantedList.Add(session);
|
||||
}
|
||||
}
|
||||
|
||||
return new AntagSelectionPlayerPool(new() { preferredList, fallbackList, unwantedList, invalidList });
|
||||
return new AntagSelectionPlayerPool(new() { preferredList, fallbackList });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,14 +364,18 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
if (session == null)
|
||||
return true;
|
||||
|
||||
mind ??= session.GetMind();
|
||||
|
||||
if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie)
|
||||
return false;
|
||||
|
||||
if (ent.Comp.SelectedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
mind ??= session.GetMind();
|
||||
|
||||
// If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity.
|
||||
if (mind == null)
|
||||
return true;
|
||||
|
||||
//todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds)
|
||||
|
||||
switch (def.MultiAntagSetting)
|
||||
@@ -398,10 +404,11 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// <summary>
|
||||
/// Checks if a given entity (mind/session not included) is valid for a given antagonist.
|
||||
/// </summary>
|
||||
private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
|
||||
public bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def)
|
||||
{
|
||||
// If the player has not spawned in as any entity (e.g., in the lobby), they can be given an antag role/entity.
|
||||
if (entity == null)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
if (HasComp<PendingClockInComponent>(entity))
|
||||
return false;
|
||||
@@ -423,6 +430,15 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
18
Content.Server/Antag/Components/AntagObjectivesComponent.cs
Normal file
18
Content.Server/Antag/Components/AntagObjectivesComponent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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;
|
||||
}
|
||||
@@ -42,6 +42,13 @@ 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]
|
||||
@@ -97,6 +104,7 @@ public partial struct AntagSelectionDefinition()
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not players should be picked to inhabit this antag or not.
|
||||
/// If no players are left and <see cref="SpawnerPrototype"/> is set, it will make a ghost role.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PickPlayer = true;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for restricting anchoring pipes so that they do not overlap.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(PipeRestrictOverlapSystem))]
|
||||
public sealed partial class PipeRestrictOverlapComponent : Component;
|
||||
@@ -68,7 +68,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
}
|
||||
ActivateAnalyzer(uid, component, args.User, args.Target);
|
||||
OpenUserInterface(uid, args.User, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -86,6 +85,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
private void ActivateAnalyzer(EntityUid uid, GasAnalyzerComponent component, EntityUid user, EntityUid? target = null)
|
||||
{
|
||||
if (!TryOpenUserInterface(uid, user, component))
|
||||
return;
|
||||
|
||||
component.Target = target;
|
||||
component.User = user;
|
||||
if (target != null)
|
||||
@@ -97,7 +99,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
UpdateAppearance(uid, component);
|
||||
EnsureComp<ActiveGasAnalyzerComponent>(uid);
|
||||
UpdateAnalyzer(uid, component);
|
||||
OpenUserInterface(uid, user, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -134,12 +135,12 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
DisableAnalyzer(uid, component);
|
||||
}
|
||||
|
||||
private void OpenUserInterface(EntityUid uid, EntityUid user, GasAnalyzerComponent? component = null)
|
||||
private bool TryOpenUserInterface(EntityUid uid, EntityUid user, GasAnalyzerComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return;
|
||||
return false;
|
||||
|
||||
_userInterface.OpenUi(uid, GasAnalyzerUiKey.Key, user);
|
||||
return _userInterface.TryOpenUi(uid, GasAnalyzerUiKey.Key, user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
123
Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs
Normal file
123
Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Construction.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles restricting pipe-based entities from overlapping outlets/inlets with other entities.
|
||||
/// </summary>
|
||||
public sealed class PipeRestrictOverlapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly TransformSystem _xform = default!;
|
||||
|
||||
private readonly List<EntityUid> _anchoredEntities = new();
|
||||
private EntityQuery<NodeContainerComponent> _nodeContainerQuery;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<PipeRestrictOverlapComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||
SubscribeLocalEvent<PipeRestrictOverlapComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
||||
|
||||
_nodeContainerQuery = GetEntityQuery<NodeContainerComponent>();
|
||||
}
|
||||
|
||||
private void OnAnchorStateChanged(Entity<PipeRestrictOverlapComponent> ent, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (!args.Anchored)
|
||||
return;
|
||||
|
||||
if (HasComp<AnchorableComponent>(ent) && CheckOverlap(ent))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent);
|
||||
_xform.Unanchor(ent, Transform(ent));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnchorAttempt(Entity<PipeRestrictOverlapComponent> ent, ref AnchorAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (!_nodeContainerQuery.TryComp(ent, out var node))
|
||||
return;
|
||||
|
||||
var xform = Transform(ent);
|
||||
if (CheckOverlap((ent, node, xform)))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool CheckOverlap(EntityUid uid)
|
||||
{
|
||||
if (!_nodeContainerQuery.TryComp(uid, out var node))
|
||||
return false;
|
||||
|
||||
return CheckOverlap((uid, node, Transform(uid)));
|
||||
}
|
||||
|
||||
public bool CheckOverlap(Entity<NodeContainerComponent, TransformComponent> ent)
|
||||
{
|
||||
if (ent.Comp2.GridUid is not { } grid || !TryComp<MapGridComponent>(grid, out var gridComp))
|
||||
return false;
|
||||
|
||||
var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates);
|
||||
_anchoredEntities.Clear();
|
||||
_map.GetAnchoredEntities((grid, gridComp), indices, _anchoredEntities);
|
||||
|
||||
foreach (var otherEnt in _anchoredEntities)
|
||||
{
|
||||
// this should never actually happen but just for safety
|
||||
if (otherEnt == ent.Owner)
|
||||
continue;
|
||||
|
||||
if (!_nodeContainerQuery.TryComp(otherEnt, out var otherComp))
|
||||
continue;
|
||||
|
||||
if (PipeNodesOverlap(ent, (otherEnt, otherComp, Transform(otherEnt))))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PipeNodesOverlap(Entity<NodeContainerComponent, TransformComponent> ent, Entity<NodeContainerComponent, TransformComponent> other)
|
||||
{
|
||||
var entDirs = GetAllDirections(ent).ToList();
|
||||
var otherDirs = GetAllDirections(other).ToList();
|
||||
|
||||
foreach (var dir in entDirs)
|
||||
{
|
||||
foreach (var otherDir in otherDirs)
|
||||
{
|
||||
if ((dir & otherDir) != 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
IEnumerable<PipeDirection> GetAllDirections(Entity<NodeContainerComponent, TransformComponent> pipe)
|
||||
{
|
||||
foreach (var node in pipe.Comp1.Nodes.Values)
|
||||
{
|
||||
// we need to rotate the pipe manually like this because the rotation doesn't update for pipes that are unanchored.
|
||||
if (node is PipeNode pipeNode)
|
||||
yield return pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
var T2 = outlet.Air.Temperature;
|
||||
var pressureDelta = P1 - P2;
|
||||
|
||||
float dt = 1/_atmosphereSystem.AtmosTickRate;
|
||||
float dt = args.dt;
|
||||
float dV = 0;
|
||||
var denom = (T1*V2 + T2*V1);
|
||||
|
||||
@@ -63,7 +63,9 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
var transferMoles = n1 - (n1+n2)*T2*V1 / denom;
|
||||
|
||||
// Get the volume transfered to update our flow meter.
|
||||
dV = n1*Atmospherics.R*T1/P1;
|
||||
// When you remove x from one side and add x to the other the total difference is 2x.
|
||||
// Also account for atmos speedup so that measured flow rate matches the setting on the volume pump.
|
||||
dV = 2*transferMoles*Atmospherics.R*T1/P1 / _atmosphereSystem.Speedup;
|
||||
|
||||
// Actually transfer the gas.
|
||||
_atmosphereSystem.Merge(outlet.Air, inlet.Air.Remove(transferMoles));
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("underPressureLockoutThreshold")]
|
||||
public float UnderPressureLockoutThreshold = 2;
|
||||
public float UnderPressureLockoutThreshold = 60; // this must be tuned in conjunction with atmos.mmos_spacing_speed
|
||||
|
||||
/// <summary>
|
||||
/// Pressure locked vents still leak a little (leading to eventual pressurization of sealed sections)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Body.Components
|
||||
@@ -50,10 +52,16 @@ namespace Content.Server.Body.Components
|
||||
public DamageSpecifier DamageRecovery = default!;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan GaspPopupCooldown = TimeSpan.FromSeconds(8);
|
||||
public TimeSpan GaspEmoteCooldown = TimeSpan.FromSeconds(8);
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan LastGaspPopupTime;
|
||||
public TimeSpan LastGaspEmoteTime;
|
||||
|
||||
/// <summary>
|
||||
/// The emote when gasps
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<EmotePrototype> GaspEmote = "Gasp";
|
||||
|
||||
/// <summary>
|
||||
/// How many cycles in a row has the mob been under-saturated?
|
||||
|
||||
@@ -2,8 +2,8 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Body.Components;
|
||||
@@ -25,9 +25,9 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
[Dependency] private readonly BodySystem _bodySystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSys = default!;
|
||||
[Dependency] private readonly LungSystem _lungSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -84,10 +84,10 @@ public sealed class RespiratorSystem : EntitySystem
|
||||
|
||||
if (respirator.Saturation < respirator.SuffocationThreshold)
|
||||
{
|
||||
if (_gameTiming.CurTime >= respirator.LastGaspPopupTime + respirator.GaspPopupCooldown)
|
||||
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
|
||||
{
|
||||
respirator.LastGaspPopupTime = _gameTiming.CurTime;
|
||||
_popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid);
|
||||
respirator.LastGaspEmoteTime = _gameTiming.CurTime;
|
||||
_chat.TryEmoteWithChat(uid, respirator.GaspEmote, ignoreActionBlocker: true);
|
||||
}
|
||||
|
||||
TakeSuffocationDamage((uid, respirator));
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.Cabinet;
|
||||
|
||||
namespace Content.Server.Cabinet;
|
||||
|
||||
public sealed class ItemCabinetSystem : SharedItemCabinetSystem
|
||||
{
|
||||
// shitposting on main???
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.NameIdentifier;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Whitelist;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -23,6 +24,7 @@ public sealed partial class CargoSystem
|
||||
{
|
||||
[Dependency] private readonly ContainerSystem _container = default!;
|
||||
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
|
||||
|
||||
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
||||
private const string BountyNameIdentifierGroup = "Bounty";
|
||||
@@ -311,7 +313,7 @@ public sealed partial class CargoSystem
|
||||
var temp = new HashSet<EntityUid>();
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (!entry.Whitelist.IsValid(entity, EntityManager))
|
||||
if (!_whitelistSys.IsValid(entry.Whitelist, entity) || (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity)))
|
||||
continue;
|
||||
|
||||
count += _stackQuery.CompOrNull(entity)?.Count ?? 1;
|
||||
|
||||
@@ -20,6 +20,8 @@ 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>
|
||||
@@ -489,6 +491,9 @@ 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))
|
||||
|
||||
@@ -150,6 +150,14 @@ namespace Content.Server.Chat.Managers
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Admin announcement: {message}");
|
||||
}
|
||||
|
||||
public void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true)
|
||||
{
|
||||
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
|
||||
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")),
|
||||
("message", FormattedMessage.EscapeText(message)));
|
||||
ChatMessageToOne(ChatChannel.Admin, message, wrappedMessage, default, false, player.Channel);
|
||||
}
|
||||
|
||||
public void SendAdminAlert(string message)
|
||||
{
|
||||
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Content.Server.Chat.Managers
|
||||
|
||||
void SendHookOOC(string sender, string message);
|
||||
void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist = null, AdminFlags? flagWhitelist = null);
|
||||
void SendAdminAnnouncementMessage(ICommonSession player, string message, bool suppressLog = true);
|
||||
void SendAdminAlert(string message);
|
||||
void SendAdminAlert(EntityUid player, string message);
|
||||
|
||||
|
||||
@@ -35,16 +35,16 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
SubscribeLocalEvent<HyposprayComponent, UseInHandEvent>(OnUseInHand);
|
||||
}
|
||||
|
||||
private void UseHypospray(Entity<HyposprayComponent> entity, EntityUid target, EntityUid user)
|
||||
private bool TryUseHypospray(Entity<HyposprayComponent> entity, EntityUid target, EntityUid user)
|
||||
{
|
||||
// if target is ineligible but is a container, try to draw from the container
|
||||
if (!EligibleEntity(target, EntityManager, entity)
|
||||
&& _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
|
||||
{
|
||||
TryDraw(entity, target, drawableSolution.Value, user);
|
||||
return TryDraw(entity, target, drawableSolution.Value, user);
|
||||
}
|
||||
|
||||
TryDoInject(entity, target, user);
|
||||
return TryDoInject(entity, target, user);
|
||||
}
|
||||
|
||||
private void OnUseInHand(Entity<HyposprayComponent> entity, ref UseInHandEvent args)
|
||||
@@ -52,8 +52,7 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
TryDoInject(entity, args.User, args.User);
|
||||
args.Handled = true;
|
||||
args.Handled = TryDoInject(entity, args.User, args.User);
|
||||
}
|
||||
|
||||
public void OnAfterInteract(Entity<HyposprayComponent> entity, ref AfterInteractEvent args)
|
||||
@@ -61,8 +60,7 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
if (args.Handled || !args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
UseHypospray(entity, args.Target.Value, args.User);
|
||||
args.Handled = true;
|
||||
args.Handled = TryUseHypospray(entity, args.Target.Value, args.User);
|
||||
}
|
||||
|
||||
public void OnAttack(Entity<HyposprayComponent> entity, ref MeleeHitEvent args)
|
||||
@@ -150,12 +148,12 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryDraw(Entity<HyposprayComponent> entity, Entity<BloodstreamComponent?> target, Entity<SolutionComponent> targetSolution, EntityUid user)
|
||||
private bool TryDraw(Entity<HyposprayComponent> entity, Entity<BloodstreamComponent?> target, Entity<SolutionComponent> targetSolution, EntityUid user)
|
||||
{
|
||||
if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln,
|
||||
out var solution) || solution.AvailableVolume == 0)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
|
||||
@@ -168,19 +166,20 @@ public sealed class HypospraySystem : SharedHypospraySystem
|
||||
Loc.GetString("injector-component-target-is-empty-message",
|
||||
("target", Identity.Entity(target, EntityManager))),
|
||||
entity.Owner, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var removedSolution = _solutionContainers.Draw(target.Owner, targetSolution, realTransferAmount);
|
||||
|
||||
if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
|
||||
("amount", removedSolution.Volume),
|
||||
("target", Identity.Entity(target, EntityManager))), entity.Owner, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EligibleEntity(EntityUid entity, IEntityManager entMan, HyposprayComponent component)
|
||||
|
||||
@@ -29,50 +29,43 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
SubscribeLocalEvent<InjectorComponent, AfterInteractEvent>(OnInjectorAfterInteract);
|
||||
}
|
||||
|
||||
private void UseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user)
|
||||
private bool TryUseInjector(Entity<InjectorComponent> injector, EntityUid target, EntityUid user)
|
||||
{
|
||||
// Handle injecting/drawing for solutions
|
||||
if (injector.Comp.ToggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
if (SolutionContainers.TryGetInjectableSolution(target, out var injectableSolution, out _))
|
||||
{
|
||||
TryInject(injector, target, injectableSolution.Value, user, false);
|
||||
}
|
||||
else if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _))
|
||||
{
|
||||
TryInject(injector, target, refillableSolution.Value, user, true);
|
||||
}
|
||||
else if (TryComp<BloodstreamComponent>(target, out var bloodstream))
|
||||
{
|
||||
TryInjectIntoBloodstream(injector, (target, bloodstream), user);
|
||||
}
|
||||
else
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector, user);
|
||||
}
|
||||
return TryInject(injector, target, injectableSolution.Value, user, false);
|
||||
|
||||
if (SolutionContainers.TryGetRefillableSolution(target, out var refillableSolution, out _))
|
||||
return TryInject(injector, target, refillableSolution.Value, user, true);
|
||||
|
||||
if (TryComp<BloodstreamComponent>(target, out var bloodstream))
|
||||
return TryInjectIntoBloodstream(injector, (target, bloodstream), user);
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-cannot-transfer-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector, user);
|
||||
return false;
|
||||
}
|
||||
else if (injector.Comp.ToggleState == InjectorToggleMode.Draw)
|
||||
|
||||
if (injector.Comp.ToggleState == InjectorToggleMode.Draw)
|
||||
{
|
||||
// Draw from a bloodstream, if the target has that
|
||||
if (TryComp<BloodstreamComponent>(target, out var stream) &&
|
||||
SolutionContainers.ResolveSolution(target, stream.BloodSolutionName, ref stream.BloodSolution))
|
||||
{
|
||||
TryDraw(injector, (target, stream), stream.BloodSolution.Value, user);
|
||||
return;
|
||||
return TryDraw(injector, (target, stream), stream.BloodSolution.Value, user);
|
||||
}
|
||||
|
||||
// Draw from an object (food, beaker, etc)
|
||||
if (SolutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
|
||||
{
|
||||
TryDraw(injector, target, drawableSolution.Value, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector.Owner, user);
|
||||
}
|
||||
return TryDraw(injector, target, drawableSolution.Value, user);
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-cannot-draw-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector.Owner, user);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnInjectDoAfter(Entity<InjectorComponent> entity, ref InjectorDoAfterEvent args)
|
||||
@@ -80,8 +73,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
if (args.Cancelled || args.Handled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
UseInjector(entity, args.Args.Target.Value, args.Args.User);
|
||||
args.Handled = true;
|
||||
args.Handled = TryUseInjector(entity, args.Args.Target.Value, args.Args.User);
|
||||
}
|
||||
|
||||
private void OnInjectorAfterInteract(Entity<InjectorComponent> entity, ref AfterInteractEvent args)
|
||||
@@ -105,8 +97,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
return;
|
||||
}
|
||||
|
||||
UseInjector(entity, target, args.User);
|
||||
args.Handled = true;
|
||||
args.Handled = TryUseInjector(entity, target, args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -214,7 +205,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
});
|
||||
}
|
||||
|
||||
private void TryInjectIntoBloodstream(Entity<InjectorComponent> injector, Entity<BloodstreamComponent> target,
|
||||
private bool TryInjectIntoBloodstream(Entity<InjectorComponent> injector, Entity<BloodstreamComponent> target,
|
||||
EntityUid user)
|
||||
{
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
@@ -224,7 +215,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
Popup.PopupEntity(
|
||||
Loc.GetString("injector-component-cannot-inject-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector.Owner, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var realTransferAmount = FixedPoint2.Min(injector.Comp.TransferAmount, chemSolution.AvailableVolume);
|
||||
@@ -233,7 +224,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
Popup.PopupEntity(
|
||||
Loc.GetString("injector-component-cannot-inject-message",
|
||||
("target", Identity.Entity(target, EntityManager))), injector.Owner, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
@@ -249,14 +240,15 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
|
||||
Dirty(injector);
|
||||
AfterInject(injector, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryInject(Entity<InjectorComponent> injector, EntityUid targetEntity,
|
||||
private bool TryInject(Entity<InjectorComponent> injector, EntityUid targetEntity,
|
||||
Entity<SolutionComponent> targetSolution, EntityUid user, bool asRefill)
|
||||
{
|
||||
if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln,
|
||||
out var solution) || solution.Volume == 0)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount =
|
||||
@@ -268,7 +260,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
Loc.GetString("injector-component-target-already-full-message",
|
||||
("target", Identity.Entity(targetEntity, EntityManager))),
|
||||
injector.Owner, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
@@ -291,6 +283,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
|
||||
Dirty(injector);
|
||||
AfterInject(injector, targetEntity);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AfterInject(Entity<InjectorComponent> injector, EntityUid target)
|
||||
@@ -321,13 +314,13 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
RaiseLocalEvent(target, ref ev);
|
||||
}
|
||||
|
||||
private void TryDraw(Entity<InjectorComponent> injector, Entity<BloodstreamComponent?> target,
|
||||
private bool TryDraw(Entity<InjectorComponent> injector, Entity<BloodstreamComponent?> target,
|
||||
Entity<SolutionComponent> targetSolution, EntityUid user)
|
||||
{
|
||||
if (!SolutionContainers.TryGetSolution(injector.Owner, injector.Comp.SolutionName, out var soln,
|
||||
out var solution) || solution.AvailableVolume == 0)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
|
||||
@@ -340,14 +333,14 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
Loc.GetString("injector-component-target-is-empty-message",
|
||||
("target", Identity.Entity(target, EntityManager))),
|
||||
injector.Owner, user);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have some snowflaked behavior for streams.
|
||||
if (target.Comp != null)
|
||||
{
|
||||
DrawFromBlood(injector, (target.Owner, target.Comp), soln.Value, realTransferAmount, user);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
@@ -355,7 +348,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
|
||||
if (!SolutionContainers.TryAddSolution(soln.Value, removedSolution))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Popup.PopupEntity(Loc.GetString("injector-component-draw-success-message",
|
||||
@@ -364,6 +357,7 @@ public sealed class InjectorSystem : SharedInjectorSystem
|
||||
|
||||
Dirty(injector);
|
||||
AfterDraw(injector, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DrawFromBlood(Entity<InjectorComponent> injector, Entity<BloodstreamComponent> target,
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Tools;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -7,18 +8,30 @@ namespace Content.Server.Construction.Components;
|
||||
/// Used for something that can be refined by welder.
|
||||
/// For example, glass shard can be refined to glass sheet.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, Access(typeof(RefiningSystem))]
|
||||
public sealed partial class WelderRefinableComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public HashSet<EntProtoId>? RefineResult = new();
|
||||
/// <summary>
|
||||
/// The items created when the item is refined.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public List<EntitySpawnEntry> RefineResult = new();
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time it takes to refine a given item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float RefineTime = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of fuel it takes to refine a given item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float RefineFuel;
|
||||
|
||||
/// <summary>
|
||||
/// The tool type needed in order to refine this item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<ToolQualityPrototype> QualityNeeded = "Welding";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -91,7 +92,14 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
// LEGACY CODE. See warning at the top of the file!
|
||||
private async Task<EntityUid?> Construct(EntityUid user, string materialContainer, ConstructionGraphPrototype graph, ConstructionGraphEdge edge, ConstructionGraphNode targetNode)
|
||||
private async Task<EntityUid?> Construct(
|
||||
EntityUid user,
|
||||
string materialContainer,
|
||||
ConstructionGraphPrototype graph,
|
||||
ConstructionGraphEdge edge,
|
||||
ConstructionGraphNode targetNode,
|
||||
EntityCoordinates coords,
|
||||
Angle angle = default)
|
||||
{
|
||||
// We need a place to hold our construction items!
|
||||
var container = _container.EnsureContainer<Container>(user, materialContainer, out var existed);
|
||||
@@ -261,7 +269,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager));
|
||||
var newEntity = EntityManager.SpawnEntity(newEntityProto, EntityManager.GetComponent<TransformComponent>(user).Coordinates);
|
||||
var newEntity = Spawn(newEntityProto, _transformSystem.ToMapCoordinates(coords), rotation: angle);
|
||||
|
||||
if (!TryComp(newEntity, out ConstructionComponent? construction))
|
||||
{
|
||||
@@ -376,7 +384,13 @@ namespace Content.Server.Construction
|
||||
}
|
||||
}
|
||||
|
||||
if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is not { Valid: true } item)
|
||||
if (await Construct(
|
||||
user,
|
||||
"item_construction",
|
||||
constructionGraph,
|
||||
edge,
|
||||
targetNode,
|
||||
Transform(user).Coordinates) is not { Valid: true } item)
|
||||
return false;
|
||||
|
||||
// Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up
|
||||
@@ -511,23 +525,18 @@ namespace Content.Server.Construction
|
||||
return;
|
||||
}
|
||||
|
||||
if (await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph,
|
||||
edge, targetNode) is not {Valid: true} structure)
|
||||
if (await Construct(user,
|
||||
(ev.Ack + constructionPrototype.GetHashCode()).ToString(),
|
||||
constructionGraph,
|
||||
edge,
|
||||
targetNode,
|
||||
GetCoordinates(ev.Location),
|
||||
constructionPrototype.CanRotate ? ev.Angle : Angle.Zero) is not {Valid: true} structure)
|
||||
{
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// We do this to be able to move the construction to its proper position in case it's anchored...
|
||||
// Oh wow transform anchoring is amazing wow I love it!!!!
|
||||
// ikr
|
||||
var xform = Transform(structure);
|
||||
var wasAnchored = xform.Anchored;
|
||||
xform.Anchored = false;
|
||||
xform.Coordinates = GetCoordinates(ev.Location);
|
||||
xform.LocalRotation = constructionPrototype.CanRotate ? ev.Angle : Angle.Zero;
|
||||
xform.Anchored = wasAnchored;
|
||||
|
||||
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack, GetNetEntity(structure)));
|
||||
_adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(user):player} has turned a {ev.PrototypeName} construction ghost into {ToPrettyString(structure)} at {Transform(structure).Coordinates}");
|
||||
Cleanup();
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Stack;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Stacks;
|
||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Construction
|
||||
namespace Content.Server.Construction;
|
||||
|
||||
public sealed class RefiningSystem : EntitySystem
|
||||
{
|
||||
public sealed class RefiningSystem : EntitySystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly SharedToolSystem _toolSystem = default!;
|
||||
[Dependency] private readonly StackSystem _stackSystem = default!;
|
||||
public override void Initialize()
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WelderRefinableComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<WelderRefinableComponent, WelderRefineDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = _toolSystem.UseTool(
|
||||
args.Used,
|
||||
args.User,
|
||||
uid,
|
||||
component.RefineTime,
|
||||
component.QualityNeeded,
|
||||
new WelderRefineDoAfterEvent(),
|
||||
fuel: component.RefineFuel);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
var spawns = EntitySpawnCollection.GetSpawns(component.RefineResult, _random);
|
||||
foreach (var spawn in spawns)
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WelderRefinableComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<WelderRefinableComponent, WelderRefineDoAfterEvent>(OnDoAfter);
|
||||
SpawnNextToOrDrop(spawn, uid, xform);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, WelderRefinableComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), fuel: component.RefineFuel);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
// get last owner coordinates and delete it
|
||||
var resultPosition = Transform(uid).Coordinates;
|
||||
EntityManager.DeleteEntity(uid);
|
||||
|
||||
// spawn each result after refine
|
||||
foreach (var result in component.RefineResult!)
|
||||
{
|
||||
var droppedEnt = Spawn(result, resultPosition);
|
||||
|
||||
// TODO: If something has a stack... Just use a prototype with a single thing in the stack.
|
||||
// This is not a good way to do it.
|
||||
if (TryComp<StackComponent>(droppedEnt, out var stack))
|
||||
_stackSystem.SetCount(droppedEnt, 1, stack);
|
||||
}
|
||||
}
|
||||
Del(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,9 +63,21 @@ 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);
|
||||
@@ -75,23 +87,6 @@ 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);
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Content.Server.Doors.Systems
|
||||
&& xformQuery.TryGetComponent(uid, out var xform)
|
||||
&& appearanceQuery.TryGetComponent(uid, out var appearance))
|
||||
{
|
||||
var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery);
|
||||
var (pressure, fire) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery);
|
||||
_appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
|
||||
firelock.Temperature = fire;
|
||||
firelock.Pressure = pressure;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -12,6 +15,8 @@ namespace Content.Server.GameTicking.Commands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public string Command => "joingame";
|
||||
public string Description => "";
|
||||
@@ -67,6 +72,12 @@ namespace Content.Server.GameTicking.Commands
|
||||
shell.WriteLine($"{jobPrototype.LocalizedName} has no available slots.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_adminManager.IsAdmin(player) && _cfg.GetCVar(CCVars.AdminDeadminOnJoin))
|
||||
{
|
||||
_adminManager.DeAdmin(player);
|
||||
}
|
||||
|
||||
ticker.MakeJoinGame(player, station, id);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Console;
|
||||
@@ -8,6 +9,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
sealed class ObserveCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _e = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public string Command => "observe";
|
||||
public string Description => "";
|
||||
@@ -28,6 +30,13 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var isAdminCommand = args.Length > 0 && args[0].ToLower() == "admin";
|
||||
|
||||
if (!isAdminCommand && _adminManager.IsAdmin(player))
|
||||
{
|
||||
_adminManager.DeAdmin(player);
|
||||
}
|
||||
|
||||
if (ticker.PlayerGameStatuses.TryGetValue(player.UserId, out var status) &&
|
||||
status != PlayerGameStatus.JoinedGame)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.GameTicking.Components;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Prototypes;
|
||||
@@ -42,6 +43,14 @@ public sealed partial class GameTicker
|
||||
string.Empty,
|
||||
"cleargamerules",
|
||||
ClearGameRulesCommand);
|
||||
|
||||
// List game rules command.
|
||||
var localizedHelp = Loc.GetString("listgamerules-command-help");
|
||||
|
||||
_consoleHost.RegisterCommand("listgamerules",
|
||||
string.Empty,
|
||||
$"listgamerules - {localizedHelp}",
|
||||
ListGameRuleCommand);
|
||||
}
|
||||
|
||||
private void ShutdownGameRules()
|
||||
@@ -49,6 +58,7 @@ public sealed partial class GameTicker
|
||||
_consoleHost.UnregisterCommand("addgamerule");
|
||||
_consoleHost.UnregisterCommand("endgamerule");
|
||||
_consoleHost.UnregisterCommand("cleargamerules");
|
||||
_consoleHost.UnregisterCommand("listgamerules");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,6 +74,13 @@ public sealed partial class GameTicker
|
||||
|
||||
var ev = new GameRuleAddedEvent(ruleEntity, ruleId);
|
||||
RaiseLocalEvent(ruleEntity, ref ev, true);
|
||||
|
||||
var currentTime = RunLevel == GameRunLevel.PreRoundLobby ? TimeSpan.Zero : RoundDuration();
|
||||
if (!HasComp<RoundstartStationVariationRuleComponent>(ruleEntity) && !HasComp<StationVariationPassRuleComponent>(ruleEntity))
|
||||
{
|
||||
_allPreviousGameRules.Add((currentTime, ruleId + " (Pending)"));
|
||||
}
|
||||
|
||||
return ruleEntity;
|
||||
}
|
||||
|
||||
@@ -110,7 +127,8 @@ public sealed partial class GameTicker
|
||||
if (delayTime > TimeSpan.Zero)
|
||||
{
|
||||
_sawmill.Info($"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}");
|
||||
_adminLogger.Add(LogType.EventStarted, $"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}");
|
||||
_adminLogger.Add(LogType.EventStarted,
|
||||
$"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}");
|
||||
|
||||
var delayed = EnsureComp<DelayedStartRuleComponent>(ruleEntity);
|
||||
delayed.RuleStartTime = _gameTiming.CurTime + (delayTime);
|
||||
@@ -118,7 +136,20 @@ public sealed partial class GameTicker
|
||||
}
|
||||
}
|
||||
|
||||
_allPreviousGameRules.Add((RoundDuration(), id));
|
||||
var currentTime = RunLevel == GameRunLevel.PreRoundLobby ? TimeSpan.Zero : RoundDuration();
|
||||
|
||||
// Remove the first occurrence of the pending entry before adding the started entry
|
||||
var pendingRuleIndex = _allPreviousGameRules.FindIndex(rule => rule.Item2 == id + " (Pending)");
|
||||
if (pendingRuleIndex >= 0)
|
||||
{
|
||||
_allPreviousGameRules.RemoveAt(pendingRuleIndex);
|
||||
}
|
||||
|
||||
if (!HasComp<RoundstartStationVariationRuleComponent>(ruleEntity) && !HasComp<StationVariationPassRuleComponent>(ruleEntity))
|
||||
{
|
||||
_allPreviousGameRules.Add((currentTime, id));
|
||||
}
|
||||
|
||||
_sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}");
|
||||
_adminLogger.Add(LogType.EventStarted, $"Started game rule {ToPrettyString(ruleEntity)}");
|
||||
|
||||
@@ -296,6 +327,7 @@ public sealed partial class GameTicker
|
||||
if (shell.Player != null)
|
||||
{
|
||||
_adminLogger.Add(LogType.EventStarted, $"{shell.Player} tried to add game rule [{rule}] via command");
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("add-gamerule-admin", ("rule", rule), ("admin", shell.Player)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -306,6 +338,7 @@ public sealed partial class GameTicker
|
||||
// Start rule if we're already in the middle of a round
|
||||
if(RunLevel == GameRunLevel.InRound)
|
||||
StartGameRule(ent);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,5 +382,42 @@ public sealed partial class GameTicker
|
||||
ClearGameRules();
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
private void ListGameRuleCommand(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
_sawmill.Info($"{shell.Player} tried to get list of game rules via command");
|
||||
_adminLogger.Add(LogType.Action, $"{shell.Player} tried to get list of game rules via command");
|
||||
var message = GetGameRulesListMessage(false);
|
||||
shell.WriteLine(message);
|
||||
}
|
||||
private string GetGameRulesListMessage(bool forChatWindow)
|
||||
{
|
||||
if (_allPreviousGameRules.Count > 0)
|
||||
{
|
||||
var sortedRules = _allPreviousGameRules.OrderBy(rule => rule.Item1).ToList();
|
||||
var message = "\n";
|
||||
|
||||
if (!forChatWindow)
|
||||
{
|
||||
var header = Loc.GetString("list-gamerule-admin-header");
|
||||
message += $"\n{header}\n";
|
||||
message += "|------------|------------------\n";
|
||||
}
|
||||
|
||||
foreach (var (time, rule) in sortedRules)
|
||||
{
|
||||
var formattedTime = time.ToString(@"hh\:mm\:ss");
|
||||
message += $"| {formattedTime,-10} | {rule,-16} \n";
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Loc.GetString("list-gamerule-admin-no-rules");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.GameWindow;
|
||||
@@ -196,6 +198,15 @@ namespace Content.Server.GameTicking
|
||||
_playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
|
||||
_db.AddRoundPlayers(RoundId, session.UserId);
|
||||
|
||||
if (_adminManager.HasAdminFlag(session, AdminFlags.Admin))
|
||||
{
|
||||
if (_allPreviousGameRules.Count > 0)
|
||||
{
|
||||
var rulesMessage = GetGameRulesListMessage(true);
|
||||
_chatManager.SendAdminAnnouncementMessage(session, Loc.GetString("starting-rule-selected-preset", ("preset", rulesMessage)));
|
||||
}
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(new TickerJoinGameEvent(), session.Channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -795,7 +795,7 @@ namespace Content.Server.GameTicking
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after players were assigned jobs by the GameTicker.
|
||||
/// Event raised after players were assigned jobs by the GameTicker and have been spawned in.
|
||||
/// You can give on-station people special roles by listening to this event.
|
||||
/// </summary>
|
||||
public sealed class RulePlayerJobsAssignedEvent
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
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;
|
||||
@@ -8,23 +8,4 @@ namespace Content.Server.GameTicking.Rules.Components;
|
||||
/// Stores data for <see cref="ThiefRuleSystem"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(ThiefRuleSystem))]
|
||||
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;
|
||||
}
|
||||
public sealed partial class ThiefRuleComponent : Component;
|
||||
|
||||
@@ -22,9 +22,6 @@ public sealed partial class TraitorRuleComponent : Component
|
||||
[DataField]
|
||||
public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<WeightedRandomPrototype> ObjectiveGroup = "TraitorObjectiveGroups";
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DatasetPrototype> CodewordAdjectives = "adjectives";
|
||||
|
||||
@@ -72,7 +69,4 @@ public sealed partial class TraitorRuleComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int StartingBalance = 20;
|
||||
|
||||
[DataField]
|
||||
public int MaxDifficulty = 5;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -47,7 +49,8 @@ public sealed class GenericAntagRuleSystem : GameRuleSystem<GenericAntagRuleComp
|
||||
|
||||
private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
|
||||
{
|
||||
args.Minds = comp.Minds;
|
||||
// just temporary until this is deleted
|
||||
args.Minds = comp.Minds.Select(mindId => (mindId, Comp<MindComponent>(mindId).CharacterName ?? "?")).ToList();
|
||||
args.AgentName = Loc.GetString(comp.AgentName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
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;
|
||||
@@ -13,20 +11,16 @@ 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;
|
||||
@@ -36,10 +30,7 @@ namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
{
|
||||
[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!;
|
||||
@@ -71,7 +62,6 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
|
||||
SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt);
|
||||
|
||||
SubscribeLocalEvent<NukeopsRuleComponent, AntagSelectEntityEvent>(OnAntagSelectEntity);
|
||||
SubscribeLocalEvent<NukeopsRuleComponent, AfterAntagEntitySelectedEvent>(OnAfterAntagEntSelected);
|
||||
}
|
||||
|
||||
@@ -471,24 +461,6 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
|
||||
}
|
||||
|
||||
// this should really go anywhere else but im tired.
|
||||
private void OnAntagSelectEntity(Entity<NukeopsRuleComponent> ent, ref AntagSelectEntityEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var profile = args.Session != null
|
||||
? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile
|
||||
: HumanoidCharacterProfile.RandomWithSpecies();
|
||||
if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species))
|
||||
{
|
||||
species = _prototypeManager.Index<SpeciesPrototype>(SharedHumanoidAppearanceSystem.DefaultSpecies);
|
||||
}
|
||||
|
||||
args.Entity = Spawn(species.Prototype);
|
||||
_humanoid.LoadProfile(args.Entity.Value, profile);
|
||||
}
|
||||
|
||||
private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
if (ent.Comp.TargetStation is not { } station)
|
||||
|
||||
@@ -46,7 +46,6 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
|
||||
Log.Info($"Selected {preset.ID} as the secret preset.");
|
||||
_adminLogger.Add(LogType.EventStarted, $"Selected {preset.ID} as the secret preset.");
|
||||
_chatManager.SendAdminAnnouncement(Loc.GetString("rule-secret-selected-preset", ("preset", preset.ID)));
|
||||
|
||||
foreach (var rule in preset.Rules)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@ 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)
|
||||
@@ -33,41 +32,9 @@ 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)
|
||||
{
|
||||
@@ -87,10 +54,4 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly ObjectivesSystem _objectives = default!;
|
||||
|
||||
public const int MaxPicks = 20;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
|
||||
|
||||
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
|
||||
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
|
||||
}
|
||||
|
||||
@@ -67,7 +64,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
}
|
||||
}
|
||||
|
||||
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true)
|
||||
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
|
||||
{
|
||||
//Grab the mind if it wasnt provided
|
||||
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
|
||||
@@ -112,37 +109,16 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
_npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false);
|
||||
_npcFaction.AddFaction(traitor, component.SyndicateFaction);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args)
|
||||
{
|
||||
args.Minds = _antag.GetAntagMindEntityUids(uid);
|
||||
args.AgentName = Loc.GetString("traitor-round-end-agent-name");
|
||||
}
|
||||
|
||||
// TODO: AntagCodewordsComponent
|
||||
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();
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Server.DoAfter;
|
||||
using Content.Server.Humanoid;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
@@ -24,7 +23,6 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MagicMirrorComponent, ActivatableUIOpenAttemptEvent>(OnOpenUIAttempt);
|
||||
|
||||
Subs.BuiEvents<MagicMirrorComponent>(MagicMirrorUiKey.Key,
|
||||
subs =>
|
||||
@@ -36,7 +34,6 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
subs.Event<MagicMirrorRemoveSlotMessage>(OnTryMagicMirrorRemoveSlot);
|
||||
});
|
||||
|
||||
SubscribeLocalEvent<MagicMirrorComponent, AfterInteractEvent>(OnMagicMirrorInteract);
|
||||
|
||||
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorSelectDoAfterEvent>(OnSelectSlotDoAfter);
|
||||
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorChangeColorDoAfterEvent>(OnChangeColorDoAfter);
|
||||
@@ -44,23 +41,6 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
SubscribeLocalEvent<MagicMirrorComponent, MagicMirrorAddSlotDoAfterEvent>(OnAddSlotDoAfter);
|
||||
}
|
||||
|
||||
private void OnMagicMirrorInteract(Entity<MagicMirrorComponent> mirror, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null)
|
||||
return;
|
||||
|
||||
if (!_uiSystem.TryOpenUi(mirror.Owner, MagicMirrorUiKey.Key, args.User))
|
||||
return;
|
||||
|
||||
UpdateInterface(mirror.Owner, args.Target.Value, mirror.Comp);
|
||||
}
|
||||
|
||||
private void OnOpenUIAttempt(EntityUid uid, MagicMirrorComponent mirror, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.User))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnMagicMirrorSelect(EntityUid uid, MagicMirrorComponent component, MagicMirrorSelectMessage message)
|
||||
{
|
||||
if (component.Target is not { } target)
|
||||
@@ -83,7 +63,8 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
BreakOnMove = true,
|
||||
BreakOnHandChange = false,
|
||||
NeedHand = true
|
||||
}, out var doAfterId);
|
||||
},
|
||||
out var doAfterId);
|
||||
|
||||
component.DoAfter = doAfterId;
|
||||
_audio.PlayPvs(component.ChangeHairSound, uid);
|
||||
@@ -137,7 +118,8 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
BreakOnMove = true,
|
||||
BreakOnHandChange = false,
|
||||
NeedHand = true
|
||||
}, out var doAfterId);
|
||||
},
|
||||
out var doAfterId);
|
||||
|
||||
component.DoAfter = doAfterId;
|
||||
}
|
||||
@@ -189,7 +171,8 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
BreakOnDamage = true,
|
||||
BreakOnHandChange = false,
|
||||
NeedHand = true
|
||||
}, out var doAfterId);
|
||||
},
|
||||
out var doAfterId);
|
||||
|
||||
component.DoAfter = doAfterId;
|
||||
_audio.PlayPvs(component.ChangeHairSound, uid);
|
||||
@@ -241,7 +224,8 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
|
||||
BreakOnMove = true,
|
||||
BreakOnHandChange = false,
|
||||
NeedHand = true
|
||||
}, out var doAfterId);
|
||||
},
|
||||
out var doAfterId);
|
||||
|
||||
component.DoAfter = doAfterId;
|
||||
_audio.PlayPvs(component.ChangeHairSound, uid);
|
||||
|
||||
13
Content.Server/Movement/Components/PullMoverComponent.cs
Normal file
13
Content.Server/Movement/Components/PullMoverComponent.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an entity that is ctrl-click moving their pulled object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This just exists so we don't have MoveEvent subs going off for every single mob constantly.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PullMoverComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
14
Content.Server/Movement/Components/PullMovingComponent.cs
Normal file
14
Content.Server/Movement/Components/PullMovingComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added when an entity is being ctrl-click moved when pulled.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PullMovingComponent : Component
|
||||
{
|
||||
// Not serialized to indicate THIS CODE SUCKS, fix pullcontroller first
|
||||
[ViewVariables]
|
||||
public EntityCoordinates MovingTo;
|
||||
}
|
||||
318
Content.Server/Movement/Systems/PullController.cs
Normal file
318
Content.Server/Movement/Systems/PullController.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Movement.Components;
|
||||
using Content.Server.Physics.Controllers;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Rotatable;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Movement.Systems;
|
||||
|
||||
public sealed class PullController : VirtualController
|
||||
{
|
||||
/*
|
||||
* This code is awful. If you try to tweak this without refactoring it I'm gonna revert it.
|
||||
*/
|
||||
|
||||
// Parameterization for pulling:
|
||||
// Speeds. Note that the speed is mass-independent (multiplied by mass).
|
||||
// Instead, tuning to mass is done via the mass values below.
|
||||
// Note that setting the speed too high results in overshoots (stabilized by drag, but bad)
|
||||
private const float AccelModifierHigh = 15f;
|
||||
private const float AccelModifierLow = 60.0f;
|
||||
// High/low-mass marks. Curve is constant-lerp-constant, i.e. if you can even pull an item,
|
||||
// you'll always get at least AccelModifierLow and no more than AccelModifierHigh.
|
||||
private const float AccelModifierHighMass = 70.0f; // roundstart saltern emergency closet
|
||||
private const float AccelModifierLowMass = 5.0f; // roundstart saltern emergency crowbar
|
||||
// Used to control settling (turns off pulling).
|
||||
private const float MaximumSettleVelocity = 0.1f;
|
||||
private const float MaximumSettleDistance = 0.1f;
|
||||
// Settle shutdown control.
|
||||
// Mustn't be too massive, as that causes severe mispredicts *and can prevent it ever resolving*.
|
||||
// Exists to bleed off "I pulled my crowbar" overshoots.
|
||||
// Minimum velocity for shutdown to be necessary. This prevents stuff getting stuck b/c too much shutdown.
|
||||
private const float SettleMinimumShutdownVelocity = 0.25f;
|
||||
// Distance in which settle shutdown multiplier is at 0. It then scales upwards linearly with closer distances.
|
||||
private const float SettleShutdownDistance = 1.0f;
|
||||
// Velocity change of -LinearVelocity * frameTime * this
|
||||
private const float SettleShutdownMultiplier = 20.0f;
|
||||
|
||||
// How much you must move for the puller movement check to actually hit.
|
||||
private const float MinimumMovementDistance = 0.005f;
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
|
||||
/// <summary>
|
||||
/// If distance between puller and pulled entity lower that this threshold,
|
||||
/// pulled entity will not change its rotation.
|
||||
/// Helps with small distance jittering
|
||||
/// </summary>
|
||||
private const float ThresholdRotDistance = 1;
|
||||
|
||||
/// <summary>
|
||||
/// If difference between puller and pulled angle lower that this threshold,
|
||||
/// pulled entity will not change its rotation.
|
||||
/// Helps with diagonal movement jittering
|
||||
/// As of further adjustments, should divide cleanly into 90 degrees
|
||||
/// </summary>
|
||||
private const float ThresholdRotAngle = 22.5f;
|
||||
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<PullableComponent> _pullableQuery;
|
||||
private EntityQuery<PullerComponent> _pullerQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject))
|
||||
.Register<PullingSystem>();
|
||||
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_pullableQuery = GetEntityQuery<PullableComponent>();
|
||||
_pullerQuery = GetEntityQuery<PullerComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
UpdatesAfter.Add(typeof(MoverController));
|
||||
SubscribeLocalEvent<PullMovingComponent, PullStoppedMessage>(OnPullStop);
|
||||
SubscribeLocalEvent<PullMoverComponent, MoveEvent>(OnPullerMove);
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
CommandBinds.Unregister<PullController>();
|
||||
}
|
||||
|
||||
private void OnPullStop(Entity<PullMovingComponent> ent, ref PullStoppedMessage args)
|
||||
{
|
||||
RemCompDeferred<PullMovingComponent>(ent);
|
||||
}
|
||||
|
||||
private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
if (session?.AttachedEntity is not { } player ||
|
||||
!player.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_pullerQuery.TryComp(player, out var pullerComp))
|
||||
return false;
|
||||
|
||||
var pulled = pullerComp.Pulling;
|
||||
|
||||
if (!_pullableQuery.TryComp(pulled, out var pullable))
|
||||
return false;
|
||||
|
||||
if (_container.IsEntityInContainer(player))
|
||||
return false;
|
||||
|
||||
// Cooldown buddy
|
||||
if (_timing.CurTime < pullerComp.NextThrow)
|
||||
return false;
|
||||
|
||||
pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown;
|
||||
|
||||
// Cap the distance
|
||||
var range = 2f;
|
||||
var fromUserCoords = coords.WithEntityId(player, EntityManager);
|
||||
var userCoords = new EntityCoordinates(player, Vector2.Zero);
|
||||
|
||||
if (!coords.InRange(EntityManager, TransformSystem, userCoords, range))
|
||||
{
|
||||
var direction = fromUserCoords.Position - userCoords.Position;
|
||||
|
||||
// TODO: Joint API not ass
|
||||
// with that being said I think throwing is the way to go but.
|
||||
if (pullable.PullJointId != null &&
|
||||
TryComp(player, out JointComponent? joint) &&
|
||||
joint.GetJoints.TryGetValue(pullable.PullJointId, out var pullJoint) &&
|
||||
pullJoint is DistanceJoint distance)
|
||||
{
|
||||
range = MathF.Max(0.01f, distance.MaxLength - 0.01f);
|
||||
}
|
||||
|
||||
fromUserCoords = new EntityCoordinates(player, direction.Normalized() * (range - 0.01f));
|
||||
coords = fromUserCoords.WithEntityId(coords.EntityId);
|
||||
}
|
||||
|
||||
EnsureComp<PullMoverComponent>(player);
|
||||
var moving = EnsureComp<PullMovingComponent>(pulled!.Value);
|
||||
moving.MovingTo = coords;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnPullerMove(EntityUid uid, PullMoverComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!_pullerQuery.TryComp(uid, out var puller))
|
||||
return;
|
||||
|
||||
if (puller.Pulling is not { } pullable)
|
||||
return;
|
||||
|
||||
UpdatePulledRotation(uid, pullable);
|
||||
|
||||
// WHY
|
||||
if (args.NewPosition.EntityId == args.OldPosition.EntityId &&
|
||||
(args.NewPosition.Position - args.OldPosition.Position).LengthSquared() <
|
||||
MinimumMovementDistance * MinimumMovementDistance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_physicsQuery.TryComp(uid, out var physics))
|
||||
PhysicsSystem.WakeBody(uid, body: physics);
|
||||
|
||||
StopMove(uid, pullable);
|
||||
}
|
||||
|
||||
private void StopMove(Entity<PullMoverComponent?> mover, Entity<PullMovingComponent?> moving)
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(mover.Owner);
|
||||
RemCompDeferred<PullMovingComponent>(moving.Owner);
|
||||
}
|
||||
|
||||
private void UpdatePulledRotation(EntityUid puller, EntityUid pulled)
|
||||
{
|
||||
// TODO: update once ComponentReference works with directed event bus.
|
||||
if (!TryComp(pulled, out RotatableComponent? rotatable))
|
||||
return;
|
||||
|
||||
if (!rotatable.RotateWhilePulling)
|
||||
return;
|
||||
|
||||
var pulledXform = _xformQuery.GetComponent(pulled);
|
||||
var pullerXform = _xformQuery.GetComponent(puller);
|
||||
|
||||
var pullerData = TransformSystem.GetWorldPositionRotation(pullerXform);
|
||||
var pulledData = TransformSystem.GetWorldPositionRotation(pulledXform);
|
||||
|
||||
var dir = pullerData.WorldPosition - pulledData.WorldPosition;
|
||||
if (dir.LengthSquared() > ThresholdRotDistance * ThresholdRotDistance)
|
||||
{
|
||||
var oldAngle = pulledData.WorldRotation;
|
||||
var newAngle = Angle.FromWorldVec(dir);
|
||||
|
||||
var diff = newAngle - oldAngle;
|
||||
if (Math.Abs(diff.Degrees) > ThresholdRotAngle / 2f)
|
||||
{
|
||||
// Ok, so this bit is difficult because ideally it would look like it's snapping to sane angles.
|
||||
// Otherwise PIANO DOOR STUCK! happens.
|
||||
// But it also needs to work with station rotation / align to the local parent.
|
||||
// So...
|
||||
var baseRotation = pulledData.WorldRotation - pulledXform.LocalRotation;
|
||||
var localRotation = newAngle - baseRotation;
|
||||
var localRotationSnapped = Angle.FromDegrees(Math.Floor((localRotation.Degrees / ThresholdRotAngle) + 0.5f) * ThresholdRotAngle);
|
||||
TransformSystem.SetLocalRotation(pulled, localRotationSnapped, pulledXform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBeforeSolve(bool prediction, float frameTime)
|
||||
{
|
||||
base.UpdateBeforeSolve(prediction, frameTime);
|
||||
var movingQuery = EntityQueryEnumerator<PullMovingComponent, PullableComponent, TransformComponent>();
|
||||
|
||||
while (movingQuery.MoveNext(out var pullableEnt, out var mover, out var pullable, out var pullableXform))
|
||||
{
|
||||
if (!mover.MovingTo.IsValid(EntityManager))
|
||||
{
|
||||
RemCompDeferred<PullMovingComponent>(pullableEnt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pullable.Puller is not {Valid: true} puller)
|
||||
continue;
|
||||
|
||||
var pullerXform = _xformQuery.Get(puller);
|
||||
var pullerPosition = TransformSystem.GetMapCoordinates(pullerXform);
|
||||
|
||||
var movingTo = mover.MovingTo.ToMap(EntityManager, TransformSystem);
|
||||
|
||||
if (movingTo.MapId != pullerPosition.MapId)
|
||||
{
|
||||
RemCompDeferred<PullMovingComponent>(pullableEnt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryComp<PhysicsComponent>(pullableEnt, out var physics) ||
|
||||
physics.BodyType == BodyType.Static ||
|
||||
movingTo.MapId != pullableXform.MapID)
|
||||
{
|
||||
RemCompDeferred<PullMovingComponent>(pullableEnt);
|
||||
continue;
|
||||
}
|
||||
|
||||
var movingPosition = movingTo.Position;
|
||||
var ownerPosition = TransformSystem.GetWorldPosition(pullableXform);
|
||||
|
||||
var diff = movingPosition - ownerPosition;
|
||||
var diffLength = diff.Length();
|
||||
|
||||
if (diffLength < MaximumSettleDistance && physics.LinearVelocity.Length() < MaximumSettleVelocity)
|
||||
{
|
||||
PhysicsSystem.SetLinearVelocity(pullableEnt, Vector2.Zero, body: physics);
|
||||
RemCompDeferred<PullMovingComponent>(pullableEnt);
|
||||
continue;
|
||||
}
|
||||
|
||||
var impulseModifierLerp = Math.Min(1.0f, Math.Max(0.0f, (physics.Mass - AccelModifierLowMass) / (AccelModifierHighMass - AccelModifierLowMass)));
|
||||
var impulseModifier = MathHelper.Lerp(AccelModifierLow, AccelModifierHigh, impulseModifierLerp);
|
||||
var multiplier = diffLength < 1 ? impulseModifier * diffLength : impulseModifier;
|
||||
// Note the implication that the real rules of physics don't apply to pulling control.
|
||||
var accel = diff.Normalized() * multiplier;
|
||||
// Now for the part where velocity gets shutdown...
|
||||
if (diffLength < SettleShutdownDistance && physics.LinearVelocity.Length() >= SettleMinimumShutdownVelocity)
|
||||
{
|
||||
// Shutdown velocity increases as we get closer to centre
|
||||
var scaling = (SettleShutdownDistance - diffLength) / SettleShutdownDistance;
|
||||
accel -= physics.LinearVelocity * SettleShutdownMultiplier * scaling;
|
||||
}
|
||||
|
||||
PhysicsSystem.WakeBody(pullableEnt, body: physics);
|
||||
|
||||
var impulse = accel * physics.Mass * frameTime;
|
||||
PhysicsSystem.ApplyLinearImpulse(pullableEnt, impulse, body: physics);
|
||||
|
||||
// if the puller is weightless or can't move, then we apply the inverse impulse (Newton's third law).
|
||||
// doing it under gravity produces an unsatisfying wiggling when pulling.
|
||||
// If player can't move, assume they are on a chair and we need to prevent pull-moving.
|
||||
if (_gravity.IsWeightless(puller) && pullerXform.Comp.GridUid == null || !_actionBlockerSystem.CanMove(puller))
|
||||
{
|
||||
PhysicsSystem.WakeBody(puller);
|
||||
PhysicsSystem.ApplyLinearImpulse(puller, -impulse);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup PullMover
|
||||
var moverQuery = EntityQueryEnumerator<PullMoverComponent, PullerComponent>();
|
||||
|
||||
while (moverQuery.MoveNext(out var uid, out _, out var puller))
|
||||
{
|
||||
if (!HasComp<PullMovingComponent>(puller.Pulling))
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
/// The directions in which this pipe can connect to other pipes around it.
|
||||
/// </summary>
|
||||
[DataField("pipeDirection")]
|
||||
private PipeDirection _originalPipeDirection;
|
||||
public PipeDirection OriginalPipeDirection;
|
||||
|
||||
/// <summary>
|
||||
/// The *current* pipe directions (accounting for rotation)
|
||||
@@ -110,26 +110,26 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
return;
|
||||
|
||||
var xform = entMan.GetComponent<TransformComponent>(owner);
|
||||
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation);
|
||||
CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
|
||||
}
|
||||
|
||||
bool IRotatableNode.RotateNode(in MoveEvent ev)
|
||||
{
|
||||
if (_originalPipeDirection == PipeDirection.Fourway)
|
||||
if (OriginalPipeDirection == PipeDirection.Fourway)
|
||||
return false;
|
||||
|
||||
// update valid pipe direction
|
||||
if (!RotationsEnabled)
|
||||
{
|
||||
if (CurrentPipeDirection == _originalPipeDirection)
|
||||
if (CurrentPipeDirection == OriginalPipeDirection)
|
||||
return false;
|
||||
|
||||
CurrentPipeDirection = _originalPipeDirection;
|
||||
CurrentPipeDirection = OriginalPipeDirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldDirection = CurrentPipeDirection;
|
||||
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation);
|
||||
CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(ev.NewRotation);
|
||||
return oldDirection != CurrentPipeDirection;
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
|
||||
if (!RotationsEnabled)
|
||||
{
|
||||
CurrentPipeDirection = _originalPipeDirection;
|
||||
CurrentPipeDirection = OriginalPipeDirection;
|
||||
return;
|
||||
}
|
||||
|
||||
var xform = entityManager.GetComponent<TransformComponent>(Owner);
|
||||
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation);
|
||||
CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
|
||||
}
|
||||
|
||||
public override IEnumerable<Node> GetReachableNodes(TransformComponent xform,
|
||||
|
||||
@@ -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>>>();
|
||||
var summaries = new Dictionary<string, Dictionary<string, List<(EntityUid, string)>>>();
|
||||
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.Empty);
|
||||
var info = new ObjectivesTextGetInfoEvent(new List<(EntityUid, string)>(), 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>>();
|
||||
summaries[agent] = new Dictionary<string, List<(EntityUid, string)>>();
|
||||
|
||||
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(m => IsInCustody(m)).Count();
|
||||
totalInCustody += minds.Where(pair => IsInCustody(pair.Item1)).Count();
|
||||
}
|
||||
|
||||
var result = new StringBuilder();
|
||||
@@ -104,19 +104,16 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSummary(StringBuilder result, string agent, List<EntityUid> minds)
|
||||
private void AddSummary(StringBuilder result, string agent, List<(EntityUid, string)> minds)
|
||||
{
|
||||
var agentSummaries = new List<(string summary, float successRate, int completedObjectives)>();
|
||||
|
||||
foreach (var mindId in minds)
|
||||
foreach (var (mindId, name) in minds)
|
||||
{
|
||||
if (!TryComp(mindId, out MindComponent? mind))
|
||||
continue;
|
||||
|
||||
var title = GetTitle(mindId, mind);
|
||||
if (title == null)
|
||||
if (!TryComp<MindComponent>(mindId, out var mind))
|
||||
continue;
|
||||
|
||||
var title = GetTitle((mindId, mind), name);
|
||||
var custody = IsInCustody(mindId, mind) ? Loc.GetString("objectives-in-custody") : string.Empty;
|
||||
|
||||
var objectives = mind.Objectives;
|
||||
@@ -238,34 +235,18 @@ 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(EntityUid mindId, MindComponent? mind = null)
|
||||
public string GetTitle(Entity<MindComponent?> mind, string name)
|
||||
{
|
||||
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))
|
||||
if (Resolve(mind, ref mind.Comp) &&
|
||||
mind.Comp.OriginalOwnerUserId != null &&
|
||||
_player.TryGetPlayerData(mind.Comp.OriginalOwnerUserId.Value, out var sessionData))
|
||||
{
|
||||
username = sessionData.UserName;
|
||||
var username = sessionData.UserName;
|
||||
return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
|
||||
}
|
||||
|
||||
|
||||
if (username != null)
|
||||
{
|
||||
if (name != null)
|
||||
return Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
|
||||
|
||||
return Loc.GetString("objectives-player-user", ("user", username));
|
||||
}
|
||||
|
||||
// nothing to identify the player by, just give up
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
return Loc.GetString("objectives-player-named", ("name", name));
|
||||
}
|
||||
}
|
||||
@@ -279,7 +260,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> Minds, string AgentName);
|
||||
public record struct ObjectivesTextGetInfoEvent(List<(EntityUid, string)> Minds, string AgentName);
|
||||
|
||||
/// <summary>
|
||||
/// Raised on the game rule before text for each agent's objectives is added, letting you prepend something.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Power.NodeGroups;
|
||||
using Content.Server.Power.Pow3r;
|
||||
using Content.Shared.Power.Components;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
@@ -8,11 +9,8 @@ namespace Content.Server.Power.Components
|
||||
/// so that it can receive power from a <see cref="IApcNet"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ApcPowerReceiverComponent : Component
|
||||
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
public bool Powered => (MathHelper.CloseToPercent(NetworkLoad.ReceivingPower, Load) || !NeedsPower) && !PowerDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of charge this needs from an APC per second to function.
|
||||
/// </summary>
|
||||
@@ -33,7 +31,7 @@ namespace Content.Server.Power.Components
|
||||
{
|
||||
_needsPower = value;
|
||||
// Reset this so next tick will do a power update.
|
||||
PoweredLastUpdate = null;
|
||||
Recalculate = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +48,8 @@ namespace Content.Server.Power.Components
|
||||
set => NetworkLoad.Enabled = !value;
|
||||
}
|
||||
|
||||
public bool? PoweredLastUpdate;
|
||||
// TODO Is this needed? It forces a PowerChangedEvent when NeedsPower is toggled even if it changes to the same state.
|
||||
public bool Recalculate;
|
||||
|
||||
[ViewVariables]
|
||||
public PowerState.Load NetworkLoad { get; } = new PowerState.Load
|
||||
@@ -66,10 +65,5 @@ namespace Content.Server.Power.Components
|
||||
/// Does nothing on the client.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower)
|
||||
{
|
||||
public readonly bool Powered = Powered;
|
||||
public readonly float ReceivingPower = ReceivingPower;
|
||||
}
|
||||
|
||||
public readonly record struct PowerChangedEvent(bool Powered, float ReceivingPower);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
using Content.Shared.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Wires;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.Wires;
|
||||
using ActivatableUISystem = Content.Shared.UserInterface.ActivatableUISystem;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems;
|
||||
|
||||
public sealed class ActivatableUIRequiresPowerSystem : EntitySystem
|
||||
public sealed class ActivatableUIRequiresPowerSystem : SharedActivatableUIRequiresPowerSystem
|
||||
{
|
||||
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, ActivatableUIOpenAttemptEvent>(OnActivate);
|
||||
SubscribeLocalEvent<ActivatableUIRequiresPowerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIRequiresPowerComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
protected override void OnActivate(Entity<ActivatableUIRequiresPowerComponent> ent, ref ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled) return;
|
||||
if (this.IsPowered(uid, EntityManager)) return;
|
||||
if (TryComp<WiresPanelComponent>(uid, out var panel) && panel.Open)
|
||||
if (args.Cancelled || this.IsPowered(ent.Owner, EntityManager))
|
||||
{
|
||||
return;
|
||||
_popup.PopupCursor(Loc.GetString("base-computer-ui-component-not-powered", ("machine", uid)), args.User);
|
||||
}
|
||||
|
||||
if (TryComp<WiresPanelComponent>(ent.Owner, out var panel) && panel.Open)
|
||||
return;
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class ApcSystem : EntitySystem
|
||||
var query = EntityQueryEnumerator<ApcComponent, PowerNetworkBatteryComponent, UserInterfaceComponent>();
|
||||
while (query.MoveNext(out var uid, out var apc, out var battery, out var ui))
|
||||
{
|
||||
if (apc.LastUiUpdate + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime)
|
||||
if (apc.LastUiUpdate + ApcComponent.VisualsChangeDelay < _gameTiming.CurTime && _ui.IsUiOpen((uid, ui), ApcUiKey.Key))
|
||||
{
|
||||
apc.LastUiUpdate = _gameTiming.CurTime;
|
||||
UpdateUIState(uid, apc, battery);
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Content.Server.Power.EntitySystems
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!;
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _powerReceiver = default!;
|
||||
|
||||
private readonly PowerState _powerState = new();
|
||||
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
|
||||
@@ -302,19 +303,27 @@ namespace Content.Server.Power.EntitySystems
|
||||
var enumerator = AllEntityQuery<ApcPowerReceiverComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var apcReceiver))
|
||||
{
|
||||
var powered = apcReceiver.Powered;
|
||||
if (powered == apcReceiver.PoweredLastUpdate)
|
||||
var powered = !apcReceiver.PowerDisabled
|
||||
&& (!apcReceiver.NeedsPower
|
||||
|| MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower,
|
||||
apcReceiver.Load));
|
||||
|
||||
// If new value is the same as the old, then exit
|
||||
if (!apcReceiver.Recalculate && apcReceiver.Powered == powered)
|
||||
continue;
|
||||
|
||||
if (metaQuery.GetComponent(uid).EntityPaused)
|
||||
var metadata = metaQuery.Comp(uid);
|
||||
if (metadata.EntityPaused)
|
||||
continue;
|
||||
|
||||
apcReceiver.PoweredLastUpdate = powered;
|
||||
var ev = new PowerChangedEvent(apcReceiver.Powered, apcReceiver.NetworkLoad.ReceivingPower);
|
||||
apcReceiver.Recalculate = false;
|
||||
apcReceiver.Powered = powered;
|
||||
Dirty(uid, apcReceiver, metadata);
|
||||
|
||||
var ev = new PowerChangedEvent(powered, apcReceiver.NetworkLoad.ReceivingPower);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
if (appearanceQuery.TryGetComponent(uid, out var appearance))
|
||||
if (appearanceQuery.TryComp(uid, out var appearance))
|
||||
_appearance.SetData(uid, PowerDeviceVisuals.Powered, powered, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
public sealed class PowerReceiverSystem : EntitySystem
|
||||
public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
@@ -38,6 +41,8 @@ namespace Content.Server.Power.EntitySystems
|
||||
SubscribeLocalEvent<ApcPowerReceiverComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<PowerSwitchComponent, GetVerbsEvent<AlternativeVerb>>(AddSwitchPowerVerb);
|
||||
|
||||
SubscribeLocalEvent<ApcPowerReceiverComponent, ComponentGetState>(OnGetState);
|
||||
|
||||
_recQuery = GetEntityQuery<ApcPowerReceiverComponent>();
|
||||
_provQuery = GetEntityQuery<ApcPowerProviderComponent>();
|
||||
}
|
||||
@@ -140,14 +145,18 @@ namespace Content.Server.Power.EntitySystems
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, ApcPowerReceiverComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new ApcPowerReceiverComponentState
|
||||
{
|
||||
Powered = component.Powered
|
||||
};
|
||||
}
|
||||
|
||||
private void ProviderChanged(Entity<ApcPowerReceiverComponent> receiver)
|
||||
{
|
||||
var comp = receiver.Comp;
|
||||
comp.NetworkLoad.LinkedNetwork = default;
|
||||
var ev = new PowerChangedEvent(comp.Powered, comp.NetworkLoad.ReceivingPower);
|
||||
|
||||
RaiseLocalEvent(receiver, ref ev);
|
||||
_appearance.SetData(receiver, PowerDeviceVisuals.Powered, comp.Powered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,12 +164,10 @@ namespace Content.Server.Power.EntitySystems
|
||||
/// Otherwise, it returns 'true' because if something doesn't take power
|
||||
/// it's effectively always powered.
|
||||
/// </summary>
|
||||
/// <returns>True when entity has no ApcPowerReceiverComponent or is Powered. False when not.</returns>
|
||||
public bool IsPowered(EntityUid uid, ApcPowerReceiverComponent? receiver = null)
|
||||
{
|
||||
if (!_recQuery.Resolve(uid, ref receiver, false))
|
||||
return true;
|
||||
|
||||
return receiver.Powered;
|
||||
return !_recQuery.Resolve(uid, ref receiver, false) || receiver.Powered;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -192,5 +199,10 @@ namespace Content.Server.Power.EntitySystems
|
||||
|
||||
return !receiver.PowerDisabled; // i.e. PowerEnabled
|
||||
}
|
||||
|
||||
public void SetLoad(ApcPowerReceiverComponent comp, float load)
|
||||
{
|
||||
comp.Load = load;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,7 @@ namespace Content.Server.Preferences.Managers
|
||||
PlayerPreferences? GetPreferencesOrNull(NetUserId? userId);
|
||||
IEnumerable<KeyValuePair<NetUserId, ICharacterProfile>> GetSelectedProfilesForPlayers(List<NetUserId> userIds);
|
||||
bool HavePreferencesLoaded(ICommonSession session);
|
||||
|
||||
Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ 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!;
|
||||
|
||||
// Cache player prefs on the server so we don't need as much async hell related to them.
|
||||
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
|
||||
new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private int MaxCharacterSlots => _cfg.GetCVar(CCVars.GameMaxCharacterSlots);
|
||||
|
||||
public void Init()
|
||||
@@ -42,6 +45,7 @@ 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)
|
||||
@@ -78,27 +82,25 @@ namespace Content.Server.Preferences.Managers
|
||||
|
||||
private async void HandleUpdateCharacterMessage(MsgUpdateCharacter message)
|
||||
{
|
||||
var slot = message.Slot;
|
||||
var profile = message.Profile;
|
||||
var userId = message.MsgChannel.UserId;
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
Logger.WarningS("prefs",
|
||||
$"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {slot}.");
|
||||
return;
|
||||
}
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (message.Profile == null)
|
||||
_sawmill.Error($"User {userId} sent a {nameof(MsgUpdateCharacter)} with a null profile in slot {message.Slot}.");
|
||||
else
|
||||
await SetProfile(userId, message.Slot, message.Profile);
|
||||
}
|
||||
|
||||
public async Task SetProfile(NetUserId userId, int slot, ICharacterProfile profile)
|
||||
{
|
||||
if (!_cachedPlayerPrefs.TryGetValue(userId, out var prefsData) || !prefsData.PrefsLoaded)
|
||||
{
|
||||
Logger.WarningS("prefs", $"User {userId} tried to modify preferences before they loaded.");
|
||||
_sawmill.Error($"Tried to modify user {userId} preferences before they loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= MaxCharacterSlots)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var curPrefs = prefsData.Prefs!;
|
||||
var session = _playerManager.GetSessionById(userId);
|
||||
@@ -112,10 +114,8 @@ namespace Content.Server.Preferences.Managers
|
||||
|
||||
prefsData.Prefs = new PlayerPreferences(profiles, slot, curPrefs.AdminOOCColor);
|
||||
|
||||
if (ShouldStorePrefs(message.MsgChannel.AuthType))
|
||||
{
|
||||
await _db.SaveCharacterSlotAsync(message.MsgChannel.UserId, message.Profile, message.Slot);
|
||||
}
|
||||
if (ShouldStorePrefs(session.Channel.AuthType))
|
||||
await _db.SaveCharacterSlotAsync(userId, profile, slot);
|
||||
}
|
||||
|
||||
private async void HandleDeleteCharacterMessage(MsgDeleteCharacter message)
|
||||
|
||||
@@ -50,6 +50,9 @@ public sealed partial class FTLComponent : Component
|
||||
Params = AudioParams.Default.WithVolume(-3f).WithLoop(true)
|
||||
};
|
||||
|
||||
[DataField]
|
||||
public EntityUid? StartupStream;
|
||||
|
||||
[DataField]
|
||||
public EntityUid? TravelStream;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Content.Shared.Shuttles.Events;
|
||||
using Content.Shared.Tag;
|
||||
@@ -287,7 +288,9 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
|
||||
if (TryComp(targetGrid.Value, out TransformComponent? targetXform))
|
||||
{
|
||||
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
|
||||
var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
|
||||
var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false);
|
||||
}
|
||||
|
||||
// shuttle timers
|
||||
@@ -313,8 +316,13 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", location)), playDefaultSound: false);
|
||||
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
|
||||
{
|
||||
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
|
||||
var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
|
||||
var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
|
||||
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false);
|
||||
}
|
||||
|
||||
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
|
||||
// TODO: Need filter extensions or something don't blame me.
|
||||
|
||||
@@ -24,6 +24,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using FTLMapComponent = Content.Shared.Shuttles.Components.FTLMapComponent;
|
||||
|
||||
@@ -343,12 +344,8 @@ public sealed partial class ShuttleSystem
|
||||
component = AddComp<FTLComponent>(uid);
|
||||
component.State = FTLState.Starting;
|
||||
var audio = _audio.PlayPvs(_startupSound, uid);
|
||||
audio.Value.Component.Flags |= AudioFlags.GridAudio;
|
||||
|
||||
if (_physicsQuery.TryGetComponent(uid, out var gridPhysics))
|
||||
{
|
||||
_transform.SetLocalPosition(audio.Value.Entity, gridPhysics.LocalCenter);
|
||||
}
|
||||
_audio.SetGridAudio(audio);
|
||||
component.StartupStream = audio?.Entity;
|
||||
|
||||
// TODO: Play previs here for docking arrival.
|
||||
|
||||
@@ -377,6 +374,17 @@ public sealed partial class ShuttleSystem
|
||||
var body = _physicsQuery.GetComponent(entity);
|
||||
var shuttleCenter = body.LocalCenter;
|
||||
|
||||
// Leave audio at the old spot
|
||||
// Just so we don't clip
|
||||
if (fromMapUid != null && TryComp(comp.StartupStream, out AudioComponent? startupAudio))
|
||||
{
|
||||
var clippedAudio = _audio.PlayStatic(_startupSound, Filter.Broadcast(),
|
||||
new EntityCoordinates(fromMapUid.Value, _maps.GetGridPosition(entity.Owner)), true, startupAudio.Params);
|
||||
|
||||
_audio.SetPlaybackPosition(clippedAudio, entity.Comp1.StartupTime);
|
||||
clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion;
|
||||
}
|
||||
|
||||
// Offset the start by buffer range just to avoid overlap.
|
||||
var ftlStart = new EntityCoordinates(ftlMap, new Vector2(_index + width / 2f, 0f) - shuttleCenter);
|
||||
|
||||
@@ -402,15 +410,7 @@ public sealed partial class ShuttleSystem
|
||||
// Audio
|
||||
var wowdio = _audio.PlayPvs(comp.TravelSound, uid);
|
||||
comp.TravelStream = wowdio?.Entity;
|
||||
if (wowdio?.Component != null)
|
||||
{
|
||||
wowdio.Value.Component.Flags |= AudioFlags.GridAudio;
|
||||
|
||||
if (_physicsQuery.TryGetComponent(uid, out var gridPhysics))
|
||||
{
|
||||
_transform.SetLocalPosition(wowdio.Value.Entity, gridPhysics.LocalCenter);
|
||||
}
|
||||
}
|
||||
_audio.SetGridAudio(wowdio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -509,13 +509,7 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
comp.TravelStream = _audio.Stop(comp.TravelStream);
|
||||
var audio = _audio.PlayPvs(_arrivalSound, uid);
|
||||
audio.Value.Component.Flags |= AudioFlags.GridAudio;
|
||||
// TODO: Shitcode til engine fix
|
||||
|
||||
if (_physicsQuery.TryGetComponent(uid, out var gridPhysics))
|
||||
{
|
||||
_transform.SetLocalPosition(audio.Value.Entity, gridPhysics.LocalCenter);
|
||||
}
|
||||
_audio.SetGridAudio(audio);
|
||||
|
||||
if (TryComp<FTLDestinationComponent>(uid, out var dest))
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
|
||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -70,7 +69,6 @@ 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);
|
||||
@@ -214,13 +212,6 @@ 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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -10,7 +9,6 @@ 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;
|
||||
@@ -18,7 +16,6 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Strip;
|
||||
using Content.Shared.Strip.Components;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -28,7 +25,6 @@ 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!;
|
||||
@@ -45,7 +41,6 @@ namespace Content.Server.Strip
|
||||
|
||||
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<Verb>>(AddStripVerb);
|
||||
SubscribeLocalEvent<StrippableComponent, GetVerbsEvent<ExamineVerb>>(AddStripExamineVerb);
|
||||
SubscribeLocalEvent<StrippableComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||
|
||||
// BUI
|
||||
SubscribeLocalEvent<StrippableComponent, StrippingSlotButtonPressed>(OnStripButtonPressed);
|
||||
@@ -68,7 +63,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 = () => StartOpeningStripper(args.User, (uid, component), true),
|
||||
Act = () => TryOpenStrippingUi(args.User, (uid, component), true),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
@@ -86,37 +81,13 @@ 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 = () => StartOpeningStripper(args.User, (uid, component), true),
|
||||
Act = () => TryOpenStrippingUi(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 ||
|
||||
@@ -442,6 +413,9 @@ namespace Content.Server.Strip
|
||||
|
||||
var (time, stealth) = GetStripTimeModifiers(user, target, targetStrippable.HandStripDelay);
|
||||
|
||||
if (!stealth)
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand", ("user", Identity.Entity(user, EntityManager)), ("item", user.Comp.ActiveHandEntity!.Value)), target, target, PopupType.Large);
|
||||
|
||||
var prefix = stealth ? "stealthily " : "";
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.UserInterface;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wall;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -39,6 +40,8 @@ namespace Content.Server.VendingMachines
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SpeakOnUIClosedSystem _speakOnUIClosed = default!;
|
||||
|
||||
private const float WallVendEjectDistanceFromWall = 1f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -384,7 +387,20 @@ namespace Content.Server.VendingMachines
|
||||
return;
|
||||
}
|
||||
|
||||
var ent = Spawn(vendComponent.NextItemToEject, Transform(uid).Coordinates);
|
||||
// Default spawn coordinates
|
||||
var spawnCoordinates = Transform(uid).Coordinates;
|
||||
|
||||
//Make sure the wallvends spawn outside of the wall.
|
||||
|
||||
if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
|
||||
{
|
||||
|
||||
var offset = wallMountComponent.Direction.ToWorldVec() * WallVendEjectDistanceFromWall;
|
||||
spawnCoordinates = spawnCoordinates.Offset(offset);
|
||||
}
|
||||
|
||||
var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates);
|
||||
|
||||
if (vendComponent.ThrowNextItem)
|
||||
{
|
||||
var range = vendComponent.NonLimitedEjectRange;
|
||||
|
||||
@@ -4,27 +4,23 @@ 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!;
|
||||
@@ -52,8 +48,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -473,23 +467,6 @@ 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))
|
||||
|
||||
@@ -152,25 +152,25 @@ public sealed partial class AnomalyComponent : Component
|
||||
/// <summary>
|
||||
/// The particle type that increases the severity of the anomaly.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public AnomalousParticleType SeverityParticleType;
|
||||
|
||||
/// <summary>
|
||||
/// The particle type that destabilizes the anomaly.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public AnomalousParticleType DestabilizingParticleType;
|
||||
|
||||
/// <summary>
|
||||
/// The particle type that weakens the anomalys health.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public AnomalousParticleType WeakeningParticleType;
|
||||
|
||||
/// <summary>
|
||||
/// The particle type that change anomaly behaviour.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public AnomalousParticleType TransformationParticleType;
|
||||
|
||||
#region Points and Vessels
|
||||
@@ -317,6 +317,7 @@ public readonly record struct AnomalyHealthChangedEvent(EntityUid Anomaly, float
|
||||
|
||||
/// <summary>
|
||||
/// Event broadcast when an anomaly's behavior is changed.
|
||||
/// This is raised after the relevant components are applied
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AnomalyBehaviorChangedEvent(EntityUid Anomaly, ProtoId<AnomalyBehaviorPrototype>? Old, ProtoId<AnomalyBehaviorPrototype>? New);
|
||||
|
||||
@@ -22,6 +22,14 @@ public enum AntagAcceptability
|
||||
|
||||
public enum AntagSelectionTime : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Antag roles are assigned before players are assigned jobs and spawned in.
|
||||
/// This prevents antag selection from happening if the round is on-going.
|
||||
/// </summary>
|
||||
PrePlayerSpawn,
|
||||
|
||||
/// <summary>
|
||||
/// Antag roles get assigned after players have been assigned jobs and have spawned in.
|
||||
/// </summary>
|
||||
PostPlayerSpawn
|
||||
}
|
||||
|
||||
@@ -1050,7 +1050,7 @@ namespace Content.Shared.CCVar
|
||||
/// 1.0 for instant spacing, 0.2 means 20% of remaining air lost each time
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AtmosSpacingEscapeRatio =
|
||||
CVarDef.Create("atmos.mmos_spacing_speed", 0.05f, CVar.SERVERONLY);
|
||||
CVarDef.Create("atmos.mmos_spacing_speed", 0.15f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum amount of air allowed on a spaced tile before it is reset to 0 immediately in kPa
|
||||
@@ -1254,7 +1254,7 @@ namespace Content.Shared.CCVar
|
||||
/// Config for when the restart vote should be allowed to be called based on percentage of ghosts.
|
||||
///
|
||||
public static readonly CVarDef<int> VoteRestartGhostPercentage =
|
||||
CVarDef.Create("vote.restart_ghost_percentage", 75, CVar.SERVERONLY);
|
||||
CVarDef.Create("vote.restart_ghost_percentage", 55, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// See vote.enabled, but specific to preset votes
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Cabinet;
|
||||
|
||||
/// <summary>
|
||||
/// Used for entities that can be opened, closed, and can hold one item. E.g., fire extinguisher cabinets.
|
||||
/// Used for entities that can be opened, closed, and can hold one item. E.g., fire extinguisher cabinets.
|
||||
/// Requires <c>OpenableComponent</c>.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(ItemCabinetSystem))]
|
||||
public sealed partial class ItemCabinetComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Sound to be played when the cabinet door is opened.
|
||||
/// Name of the <see cref="ItemSlot"/> that stores the actual item.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier? DoorSound;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ItemSlot"/> that stores the actual item. The entity whitelist, sounds, and other
|
||||
/// behaviours are specified by this <see cref="ItemSlot"/> definition.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables]
|
||||
public ItemSlot CabinetSlot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the cabinet is currently open or not.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Opened;
|
||||
|
||||
/// <summary>
|
||||
/// The state for when the cabinet is open
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? OpenState;
|
||||
|
||||
/// <summary>
|
||||
/// The state for when the cabinet is closed
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? ClosedState;
|
||||
[DataField]
|
||||
public string Slot = "ItemCabinet";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum ItemCabinetVisuals : byte
|
||||
{
|
||||
ContainsItem,
|
||||
Layer
|
||||
}
|
||||
|
||||
95
Content.Shared/Cabinet/ItemCabinetSystem.cs
Normal file
95
Content.Shared/Cabinet/ItemCabinetSystem.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Shared.Cabinet;
|
||||
|
||||
/// <summary>
|
||||
/// Controls ItemCabinet slot locking and visuals.
|
||||
/// </summary>
|
||||
public sealed class ItemCabinetSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, OpenableOpenedEvent>(OnOpened);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, OpenableClosedEvent>(OnClosed);
|
||||
}
|
||||
|
||||
private void OnStartup(Entity<ItemCabinetComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
UpdateAppearance(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<ItemCabinetComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
// update at mapinit to avoid copy pasting locked: true and locked: false for each closed/open prototype
|
||||
SetSlotLock(ent, !_openable.IsOpen(ent));
|
||||
}
|
||||
|
||||
private void UpdateAppearance(Entity<ItemCabinetComponent> ent)
|
||||
{
|
||||
_appearance.SetData(ent, ItemCabinetVisuals.ContainsItem, HasItem(ent));
|
||||
}
|
||||
|
||||
private void OnContainerModified(EntityUid uid, ItemCabinetComponent component, ContainerModifiedMessage args)
|
||||
{
|
||||
if (args.Container.ID == component.Slot)
|
||||
UpdateAppearance((uid, component));
|
||||
}
|
||||
|
||||
private void OnOpened(Entity<ItemCabinetComponent> ent, ref OpenableOpenedEvent args)
|
||||
{
|
||||
SetSlotLock(ent, false);
|
||||
}
|
||||
|
||||
private void OnClosed(Entity<ItemCabinetComponent> ent, ref OpenableClosedEvent args)
|
||||
{
|
||||
SetSlotLock(ent, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the cabinet's item slot.
|
||||
/// </summary>
|
||||
public bool TryGetSlot(Entity<ItemCabinetComponent> ent, [NotNullWhen(true)] out ItemSlot? slot)
|
||||
{
|
||||
slot = null;
|
||||
if (!TryComp<ItemSlotsComponent>(ent, out var slots))
|
||||
return false;
|
||||
|
||||
return _slots.TryGetSlot(ent, ent.Comp.Slot, out slot, slots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the cabinet contains an item.
|
||||
/// </summary>
|
||||
public bool HasItem(Entity<ItemCabinetComponent> ent)
|
||||
{
|
||||
return TryGetSlot(ent, out var slot) && slot.HasItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock or unlock the underlying item slot.
|
||||
/// </summary>
|
||||
public void SetSlotLock(Entity<ItemCabinetComponent> ent, bool closed)
|
||||
{
|
||||
if (!TryComp<ItemSlotsComponent>(ent, out var slots))
|
||||
return;
|
||||
|
||||
if (_slots.TryGetSlot(ent, ent.Comp.Slot, out var slot, slots))
|
||||
_slots.SetLock(ent, slot, closed, slots);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Lock;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Cabinet;
|
||||
|
||||
public abstract class SharedItemCabinetSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentRemove>(OnComponentRemove);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ComponentStartup>(OnComponentStartup);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, AfterAutoHandleStateEvent>(OnComponentHandleState);
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, ActivateInWorldEvent>(OnActivateInWorld);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, GetVerbsEvent<AlternativeVerb>>(AddToggleOpenVerb);
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntInsertedIntoContainerMessage>(OnContainerModified);
|
||||
SubscribeLocalEvent<ItemCabinetComponent, EntRemovedFromContainerMessage>(OnContainerModified);
|
||||
|
||||
SubscribeLocalEvent<ItemCabinetComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, ItemCabinetComponent cabinet, ComponentInit args)
|
||||
{
|
||||
_itemSlots.AddItemSlot(uid, "ItemCabinet", cabinet.CabinetSlot);
|
||||
}
|
||||
|
||||
private void OnComponentRemove(EntityUid uid, ItemCabinetComponent cabinet, ComponentRemove args)
|
||||
{
|
||||
_itemSlots.RemoveItemSlot(uid, cabinet.CabinetSlot);
|
||||
}
|
||||
|
||||
private void OnComponentStartup(EntityUid uid, ItemCabinetComponent cabinet, ComponentStartup args)
|
||||
{
|
||||
UpdateAppearance(uid, cabinet);
|
||||
_itemSlots.SetLock(uid, cabinet.CabinetSlot, !cabinet.Opened);
|
||||
}
|
||||
|
||||
private void OnComponentHandleState(Entity<ItemCabinetComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateAppearance(ent, ent);
|
||||
}
|
||||
|
||||
protected virtual void UpdateAppearance(EntityUid uid, ItemCabinetComponent? cabinet = null)
|
||||
{
|
||||
// we don't fuck with appearance data, and instead just manually update the sprite on the client
|
||||
}
|
||||
|
||||
private void OnContainerModified(EntityUid uid, ItemCabinetComponent cabinet, ContainerModifiedMessage args)
|
||||
{
|
||||
if (!cabinet.Initialized)
|
||||
return;
|
||||
|
||||
if (args.Container.ID == cabinet.CabinetSlot.ID)
|
||||
UpdateAppearance(uid, cabinet);
|
||||
}
|
||||
|
||||
private void OnLockToggleAttempt(EntityUid uid, ItemCabinetComponent cabinet, ref LockToggleAttemptEvent args)
|
||||
{
|
||||
// Cannot lock or unlock while open.
|
||||
if (cabinet.Opened)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
private void AddToggleOpenVerb(EntityUid uid, ItemCabinetComponent cabinet, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
|
||||
return;
|
||||
|
||||
// Toggle open verb
|
||||
AlternativeVerb toggleVerb = new()
|
||||
{
|
||||
Act = () => ToggleItemCabinet(uid, args.User, cabinet)
|
||||
};
|
||||
if (cabinet.Opened)
|
||||
{
|
||||
toggleVerb.Text = Loc.GetString("verb-common-close");
|
||||
toggleVerb.Icon =
|
||||
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/close.svg.192dpi.png"));
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleVerb.Text = Loc.GetString("verb-common-open");
|
||||
toggleVerb.Icon =
|
||||
new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/open.svg.192dpi.png"));
|
||||
}
|
||||
args.Verbs.Add(toggleVerb);
|
||||
}
|
||||
|
||||
private void OnActivateInWorld(EntityUid uid, ItemCabinetComponent comp, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
ToggleItemCabinet(uid, args.User, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the ItemCabinet's state.
|
||||
/// </summary>
|
||||
public void ToggleItemCabinet(EntityUid uid, EntityUid? user = null, ItemCabinetComponent? cabinet = null)
|
||||
{
|
||||
if (!Resolve(uid, ref cabinet))
|
||||
return;
|
||||
|
||||
if (TryComp<LockComponent>(uid, out var lockComponent) && lockComponent.Locked)
|
||||
return;
|
||||
|
||||
cabinet.Opened = !cabinet.Opened;
|
||||
Dirty(uid, cabinet);
|
||||
_itemSlots.SetLock(uid, cabinet.CabinetSlot, !cabinet.Opened);
|
||||
|
||||
if (_timing.IsFirstTimePredicted)
|
||||
{
|
||||
UpdateAppearance(uid, cabinet);
|
||||
_audio.PlayPredicted(cabinet.DoorSound, uid, user, AudioParams.Default.WithVariation(0.15f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public sealed partial class CargoBountyPrototype : IPrototype
|
||||
/// <summary>
|
||||
/// The entries that must be satisfied for the cargo bounty to be complete.
|
||||
/// </summary>
|
||||
[DataField( required: true)]
|
||||
[DataField(required: true)]
|
||||
public List<CargoBountyItemEntry> Entries = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -50,6 +50,12 @@ public readonly partial record struct CargoBountyItemEntry()
|
||||
[DataField(required: true)]
|
||||
public EntityWhitelist Whitelist { get; init; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// A blacklist that can be used to exclude items in the whitelist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist { get; init; } = null;
|
||||
|
||||
// todo: implement some kind of simple generic condition system
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,8 +5,6 @@ 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
|
||||
{
|
||||
@@ -18,7 +16,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
|
||||
{
|
||||
@@ -26,8 +24,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", customTypeSerializer: typeof(PrototypeIdSerializer<DamageContainerPrototype>))]
|
||||
public string? DamageContainerID;
|
||||
[DataField("damageContainer")]
|
||||
public ProtoId<DamageContainerPrototype>? DamageContainerID;
|
||||
|
||||
/// <summary>
|
||||
/// This <see cref="DamageModifierSetPrototype"/> will be applied to any damage that is dealt to this container,
|
||||
@@ -37,8 +35,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", customTypeSerializer: typeof(PrototypeIdSerializer<DamageModifierSetPrototype>))]
|
||||
public string? DamageModifierSetId;
|
||||
[DataField("damageModifierSet")]
|
||||
public ProtoId<DamageModifierSetPrototype>? DamageModifierSetId;
|
||||
|
||||
/// <summary>
|
||||
/// All the damage information is stored in this <see cref="DamageSpecifier"/>.
|
||||
@@ -46,7 +44,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("damage", readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier
|
||||
[DataField(readOnly: true)] //todo remove this readonly when implementing writing to damagespecifier
|
||||
public DamageSpecifier Damage = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -64,8 +62,8 @@ namespace Content.Shared.Damage
|
||||
[ViewVariables]
|
||||
public FixedPoint2 TotalDamage;
|
||||
|
||||
[DataField("radiationDamageTypes", customTypeSerializer: typeof(PrototypeIdListSerializer<DamageTypePrototype>))]
|
||||
public List<string> RadiationDamageTypeIDs = new() { "Radiation" };
|
||||
[DataField("radiationDamageTypes")]
|
||||
public List<ProtoId<DamageTypePrototype>> RadiationDamageTypeIDs = new() { "Radiation" };
|
||||
|
||||
[DataField]
|
||||
public Dictionary<MobState, ProtoId<StatusIconPrototype>> HealthIcons = new()
|
||||
@@ -77,6 +75,9 @@ namespace Content.Shared.Damage
|
||||
|
||||
[DataField]
|
||||
public ProtoId<StatusIconPrototype> RottingIcon = "HealthIconRotting";
|
||||
|
||||
[DataField]
|
||||
public FixedPoint2? HealthBarThreshold;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -84,13 +85,16 @@ 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)
|
||||
string? modifierSetId,
|
||||
FixedPoint2? healthBarThreshold)
|
||||
{
|
||||
DamageDict = damageDict;
|
||||
ModifierSetId = modifierSetId;
|
||||
HealthBarThreshold = healthBarThreshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user