Merge remote-tracking branch 'upstream/stable' into ed-08-09-2025-upstream-sync

# Conflicts:
#	.github/CODEOWNERS
#	Content.IntegrationTests/Tests/CargoTest.cs
#	Content.Server/Chat/Systems/ChatSystem.cs
#	Content.Shared/Chat/SharedChatSystem.cs
#	Content.Shared/Lock/LockSystem.cs
#	Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
#	Content.Shared/Storage/Components/EntityStorageComponent.cs
#	Resources/Prototypes/Entities/Mobs/Customization/Markings/human_hair.yml
#	Resources/Prototypes/game_presets.yml
This commit is contained in:
Ed
2025-09-08 13:12:50 +03:00
656 changed files with 86668 additions and 45617 deletions

View File

@@ -15,6 +15,7 @@ namespace Content.Client.Administration.Managers
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientNetManager _netMgr = default!;
[Dependency] private readonly IClientConGroupController _conGroup = default!;
[Dependency] private readonly IClientConsoleHost _host = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
@@ -86,12 +87,12 @@ namespace Content.Client.Administration.Managers
private void UpdateMessageRx(MsgUpdateAdminStatus message)
{
_availableCommands.Clear();
var host = IoCManager.Resolve<IClientConsoleHost>();
// Anything marked as Any we'll just add even if the server doesn't know about it.
foreach (var (command, instance) in host.AvailableCommands)
foreach (var (command, instance) in _host.AvailableCommands)
{
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null) continue;
if (Attribute.GetCustomAttribute(instance.GetType(), typeof(AnyCommandAttribute)) == null)
continue;
_availableCommands.Add(command);
}

View File

@@ -1,7 +0,0 @@
using Content.Shared.Administration;
namespace Content.Client.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
}

View File

@@ -57,12 +57,43 @@ public sealed partial class ObjectsTab : Control
private void TeleportTo(NetEntity nent)
{
_console.ExecuteCommand($"tpto {nent}");
var selection = _selections[ObjectTypeOptions.SelectedId];
switch (selection)
{
case ObjectsTabSelection.Grids:
{
// directly teleport to the entity
_console.ExecuteCommand($"tpto {nent}");
}
break;
case ObjectsTabSelection.Maps:
{
// teleport to the map, not to the map entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var map) || !_entityManager.TryGetComponent<MapComponent>(map, out var mapComp))
break;
_console.ExecuteCommand($"tp 0 0 {mapComp.MapId}");
break;
}
case ObjectsTabSelection.Stations:
{
// teleport to the station's largest grid, not to the station entity (which is in nullspace)
if (!_entityManager.TryGetEntity(nent, out var station))
break;
var largestGrid = _entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetLargestGrid(station.Value);
if (largestGrid == null)
break;
_console.ExecuteCommand($"tpto {largestGrid.Value}");
break;
}
default:
throw new NotImplementedException();
}
}
private void Delete(NetEntity nent)
{
_console.ExecuteCommand($"delete {nent}");
RefreshObjectList();
}
public void RefreshObjectList()
@@ -79,25 +110,21 @@ public sealed partial class ObjectsTab : Control
entities.AddRange(_entityManager.EntitySysManager.GetEntitySystem<StationSystem>().GetStationNames());
break;
case ObjectsTabSelection.Grids:
{
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
}
break;
}
case ObjectsTabSelection.Maps:
{
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
{
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
}
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
while (query.MoveNext(out var uid, out _, out var metadata))
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
break;
}
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}

View File

@@ -1,5 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
@@ -20,7 +21,7 @@
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="DeleteButton"
<controls:ConfirmButton Name="DeleteButton"
Text="{Loc object-tab-entity-delete}"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"

View File

@@ -3,17 +3,13 @@ using Content.Shared.CCVar;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Audio;
using Robust.Shared.Log;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
@@ -31,6 +27,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -65,18 +62,19 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
get => _overlayEnabled;
set
{
if (_overlayEnabled == value) return;
if (_overlayEnabled == value)
return;
_overlayEnabled = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_overlayEnabled)
{
_overlay = new AmbientSoundOverlay(EntityManager, this, EntityManager.System<EntityLookupSystem>());
overlayManager.AddOverlay(_overlay);
_overlayManager.AddOverlay(_overlay);
}
else
{
overlayManager.RemoveOverlay(_overlay!);
_overlayManager.RemoveOverlay(_overlay!);
_overlay = null;
}
}

View File

@@ -3,11 +3,8 @@ using Content.Client.Gameplay;
using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Random;
using Content.Shared.Random.Rules;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
@@ -25,6 +22,7 @@ public sealed partial class ContentAudioSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
@@ -61,7 +59,7 @@ public sealed partial class ContentAudioSystem
private void InitializeAmbientMusic()
{
Subs.CVar(_configManager, CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
_sawmill = _logManager.GetSawmill("audio.ambience");
// Reset audio
_nextAudio = TimeSpan.MaxValue;

View File

@@ -0,0 +1,30 @@
using Content.Shared.Changeling.Components;
using Content.Shared.Changeling.Systems;
using Robust.Client.GameObjects;
namespace Content.Client.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem
{
[Dependency] private readonly UserInterfaceSystem _ui = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChangelingIdentityComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<ChangelingIdentityComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateUi(ent);
}
public void UpdateUi(EntityUid uid)
{
if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -1,8 +1,8 @@
using Content.Shared.Changeling.Transform;
using Content.Shared.Changeling.Systems;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
namespace Content.Client.Changeling.Transform;
namespace Content.Client.Changeling.UI;
[UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
@@ -16,16 +16,16 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne
_window = this.CreateWindow<ChangelingTransformMenu>();
_window.OnIdentitySelect += SendIdentitySelect;
_window.Update(Owner);
}
protected override void UpdateState(BoundUserInterfaceState state)
public override void Update()
{
base.UpdateState(state);
if (state is not ChangelingTransformBoundUserInterfaceState current)
if (_window == null)
return;
_window?.UpdateState(current);
_window.Update(Owner);
}
public void SendIdentitySelect(NetEntity identityId)

View File

@@ -1,11 +1,11 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Transform;
using Content.Shared.Changeling.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Changeling.Transform;
namespace Content.Client.Changeling.UI;
[GenerateTypedNameReferences]
public sealed partial class ChangelingTransformMenu : RadialMenu
@@ -19,13 +19,15 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
IoCManager.InjectDependencies(this);
}
public void UpdateState(ChangelingTransformBoundUserInterfaceState state)
public void Update(EntityUid uid)
{
Main.DisposeAllChildren();
foreach (var identity in state.Identites)
{
var identityUid = _entity.GetEntity(identity);
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
return;
foreach (var identityUid in identityComp.ConsumedIdentities)
{
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
continue;
@@ -48,7 +50,7 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
entView.SetEntity(identityUid);
button.OnButtonUp += _ =>
{
OnIdentitySelect?.Invoke(identity);
OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
Close();
};
button.AddChild(entView);

View File

@@ -80,7 +80,7 @@ public sealed partial class CreditsWindow : DefaultWindow
private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
{
attributionsContainer.DisposeAllChildren();
attributionsContainer.RemoveAllChildren();
if (_attributions.Count == 0)
{
@@ -253,6 +253,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateLicenses(BoxContainer licensesContainer)
{
licensesContainer.RemoveAllChildren();
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
{
licensesContainer.AddChild(new Label
@@ -269,6 +271,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulatePatrons(BoxContainer patronsContainer)
{
patronsContainer.RemoveAllChildren();
var patrons = LoadPatrons();
// Do not show "become a patron" button on Steam builds
@@ -318,6 +322,8 @@ public sealed partial class CreditsWindow : DefaultWindow
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
{
ss14ContributorsContainer.RemoveAllChildren();
Button contributeButton;
ss14ContributorsContainer.AddChild(new BoxContainer

View File

@@ -1,5 +1,6 @@
using Content.Shared.Drunk;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Enums;
@@ -43,19 +44,21 @@ public sealed class DrunkOverlay : Overlay
if (playerEntity == null)
return;
if (!_entityManager.HasComponent<DrunkComponent>(playerEntity)
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
var statusSys = _sysMan.GetEntitySystem<Shared.StatusEffectNew.StatusEffectsSystem>();
if (!statusSys.TryGetMaxTime<DrunkStatusEffectComponent>(playerEntity.Value, out var status))
return;
var statusSys = _sysMan.GetEntitySystem<StatusEffectsSystem>();
if (!statusSys.TryGetTime(playerEntity.Value, SharedDrunkSystem.DrunkKey, out var time, status))
return;
var time = status.Item2;
var curTime = _timing.CurTime;
var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
var power = SharedDrunkSystem.MagicNumber;
if (time != null)
{
var curTime = _timing.CurTime;
power = (float) (time - curTime).Value.TotalSeconds;
}
CurrentBoozePower += 8f * (0.5f*timeLeft - CurrentBoozePower) * args.DeltaSeconds / (timeLeft+1);
CurrentBoozePower += 8f * (power * 0.5f - CurrentBoozePower) * args.DeltaSeconds / (power+1);
}
protected override bool BeforeDraw(in OverlayDrawArgs args)

View File

@@ -1,4 +1,5 @@
using Content.Shared.Drunk;
using Content.Shared.StatusEffectNew;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -16,38 +17,41 @@ public sealed class DrunkSystem : SharedDrunkSystem
{
base.Initialize();
SubscribeLocalEvent<DrunkComponent, ComponentInit>(OnDrunkInit);
SubscribeLocalEvent<DrunkComponent, ComponentShutdown>(OnDrunkShutdown);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectAppliedEvent>(OnStatusApplied);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRemovedEvent>(OnStatusRemoved);
SubscribeLocalEvent<DrunkComponent, LocalPlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<DrunkComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerAttachedEvent>>(OnPlayerAttached);
SubscribeLocalEvent<DrunkStatusEffectComponent, StatusEffectRelayedEvent<LocalPlayerDetachedEvent>>(OnPlayerDetached);
_overlay = new();
}
private void OnPlayerAttached(EntityUid uid, DrunkComponent component, LocalPlayerAttachedEvent args)
private void OnStatusApplied(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectAppliedEvent args)
{
_overlayMan.AddOverlay(_overlay);
if (!_overlayMan.HasOverlay<DrunkOverlay>())
_overlayMan.AddOverlay(_overlay);
}
private void OnPlayerDetached(EntityUid uid, DrunkComponent component, LocalPlayerDetachedEvent args)
private void OnStatusRemoved(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRemovedEvent args)
{
if (Status.HasEffectComp<DrunkStatusEffectComponent>(args.Target))
return;
if (_player.LocalEntity != args.Target)
return;
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
private void OnDrunkInit(EntityUid uid, DrunkComponent component, ComponentInit args)
private void OnPlayerAttached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerAttachedEvent> args)
{
if (_player.LocalEntity == uid)
_overlayMan.AddOverlay(_overlay);
_overlayMan.AddOverlay(_overlay);
}
private void OnDrunkShutdown(EntityUid uid, DrunkComponent component, ComponentShutdown args)
private void OnPlayerDetached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
{
if (_player.LocalEntity == uid)
{
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
_overlay.CurrentBoozePower = 0;
_overlayMan.RemoveOverlay(_overlay);
}
}

View File

@@ -34,6 +34,8 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public new bool Paused { get; private set; }
public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => new List<(TimeSpan, string)>();
[ViewVariables] public IReadOnlyDictionary<NetEntity, Dictionary<ProtoId<JobPrototype>, int?>> JobsAvailable => _jobsAvailable;
[ViewVariables] public IReadOnlyDictionary<NetEntity, string> StationNames => _stationNames;

View File

@@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem
{
if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
{
// TODO: Don't use protoId for deimplanting
// and especially not raw strings!
Dictionary<string, string> implants = new();
foreach (var implant in component.DeimplantWhitelist)
{

View File

@@ -0,0 +1,5 @@
using Content.Shared.Implants;
namespace Content.Client.Implants;
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;

View File

@@ -21,7 +21,6 @@ namespace Content.Client.Inventory
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ClientClothingSystem _clothingVisualsSystem = default!;
[Dependency] private readonly ExamineSystem _examine = default!;

View File

@@ -1,8 +0,0 @@
using Content.Shared.Kitchen;
namespace Content.Client.Kitchen;
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
{
}

View File

@@ -30,6 +30,10 @@ namespace Content.Client.Lathe.UI
{
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
};
_menu.QueueDeleteAction += index => SendMessage(new LatheDeleteRequestMessage(index));
_menu.QueueMoveUpAction += index => SendMessage(new LatheMoveRequestMessage(index, -1));
_menu.QueueMoveDownAction += index => SendMessage(new LatheMoveRequestMessage(index, 1));
_menu.DeleteFabricatingAction += () => SendMessage(new LatheAbortFabricationMessage());
}
protected override void UpdateState(BoundUserInterfaceState state)

View File

@@ -1,6 +1,7 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="clr-namespace:Content.Client.Materials.UI"
Title="{Loc 'lathe-menu-title'}"
MinSize="550 450"
@@ -110,6 +111,18 @@
HorizontalAlignment="Left"
Margin="130 0 0 0">
</Label>
<Button
Name="DeleteFabricating"
Margin="0"
Text="✖"
SetSize="38 32"
HorizontalAlignment="Right"
ToolTip="{Loc 'lathe-menu-delete-fabricating-tooltip'}">
<Button.StyleClasses>
<system:String>Caution</system:String>
<system:String>OpenLeft</system:String>
</Button.StyleClasses>
</Button>
</PanelContainer>
</BoxContainer>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">

View File

@@ -26,6 +26,10 @@ public sealed partial class LatheMenu : DefaultWindow
public event Action<BaseButton.ButtonEventArgs>? OnServerListButtonPressed;
public event Action<string, int>? RecipeQueueAction;
public event Action<int>? QueueDeleteAction;
public event Action<int>? QueueMoveUpAction;
public event Action<int>? QueueMoveDownAction;
public event Action? DeleteFabricatingAction;
public List<ProtoId<LatheRecipePrototype>> Recipes = new();
@@ -50,12 +54,21 @@ public sealed partial class LatheMenu : DefaultWindow
};
AmountLineEdit.OnTextChanged += _ =>
{
if (int.TryParse(AmountLineEdit.Text, out var amount))
{
if (amount > LatheSystem.MaxItemsPerRequest)
AmountLineEdit.Text = LatheSystem.MaxItemsPerRequest.ToString();
else if (amount < 0)
AmountLineEdit.Text = "0";
}
PopulateRecipes();
};
FilterOption.OnItemSelected += OnItemSelected;
ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a);
DeleteFabricating.OnPressed += _ => DeleteFabricatingAction?.Invoke();
}
public void SetEntity(EntityUid uid)
@@ -223,22 +236,27 @@ public sealed partial class LatheMenu : DefaultWindow
/// Populates the build queue list with all queued items
/// </summary>
/// <param name="queue"></param>
public void PopulateQueueList(IReadOnlyCollection<ProtoId<LatheRecipePrototype>> queue)
public void PopulateQueueList(IReadOnlyCollection<LatheRecipeBatch> queue)
{
QueueList.DisposeAllChildren();
var idx = 1;
foreach (var recipeProto in queue)
foreach (var batch in queue)
{
var recipe = _prototypeManager.Index(recipeProto);
var queuedRecipeBox = new BoxContainer();
queuedRecipeBox.Orientation = BoxContainer.LayoutOrientation.Horizontal;
var recipe = _prototypeManager.Index(batch.Recipe);
queuedRecipeBox.AddChild(GetRecipeDisplayControl(recipe));
var itemName = _lathe.GetRecipeName(batch.Recipe);
string displayText;
if (batch.ItemsRequested > 1)
displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested));
else
displayText = Loc.GetString("lathe-menu-item-single", ("index", idx), ("name", itemName));
var queuedRecipeBox = new QueuedRecipeControl(displayText, idx - 1, GetRecipeDisplayControl(recipe));
queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s);
queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s);
queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s);
var queuedRecipeLabel = new Label();
queuedRecipeLabel.Text = $"{idx}. {_lathe.GetRecipeName(recipe)}";
queuedRecipeBox.AddChild(queuedRecipeLabel);
QueueList.AddChild(queuedRecipeBox);
idx++;
}

View File

@@ -0,0 +1,35 @@
<Control xmlns="https://spacestation14.io"
xmlns:system="clr-namespace:System;assembly=System.Runtime">
<BoxContainer Orientation="Horizontal">
<BoxContainer
Name="RecipeDisplayContainer"
Margin="0 0 4 0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MinSize="32 32"
/>
<Label Name="RecipeName" HorizontalExpand="True" />
<Button
Name="MoveUp"
Margin="0"
Text="⏶"
StyleClasses="OpenRight"
ToolTip="{Loc 'lathe-menu-move-up-tooltip'}"/>
<Button
Name="MoveDown"
Margin="0"
Text="⏷"
StyleClasses="OpenBoth"
ToolTip="{Loc 'lathe-menu-move-down-tooltip'}"/>
<Button
Name="Delete"
Margin="0"
Text="✖"
ToolTip="{Loc 'lathe-menu-delete-item-tooltip'}">
<Button.StyleClasses>
<system:String>Caution</system:String>
<system:String>OpenLeft</system:String>
</Button.StyleClasses>
</Button>
</BoxContainer>
</Control>

View File

@@ -0,0 +1,36 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Lathe.UI;
[GenerateTypedNameReferences]
public sealed partial class QueuedRecipeControl : Control
{
public Action<int>? OnDeletePressed;
public Action<int>? OnMoveUpPressed;
public Action<int>? OnMoveDownPressed;
public QueuedRecipeControl(string displayText, int index, Control displayControl)
{
RobustXamlLoader.Load(this);
RecipeName.Text = displayText;
RecipeDisplayContainer.AddChild(displayControl);
MoveUp.OnPressed += (_) =>
{
OnMoveUpPressed?.Invoke(index);
};
MoveDown.OnPressed += (_) =>
{
OnMoveDownPressed?.Invoke(index);
};
Delete.OnPressed += (_) =>
{
OnDeletePressed?.Invoke(index);
};
}
}

View File

@@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class CrematoriumSystem : SharedCrematoriumSystem;

View File

@@ -0,0 +1,5 @@
using Content.Shared.Morgue;
namespace Content.Client.Morgue;
public sealed class MorgueSystem : SharedMorgueSystem;

View File

@@ -1,7 +1,6 @@
using System.Numerics;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Client.Player;
namespace Content.Client.Movement.Systems;
@@ -63,4 +62,15 @@ public sealed class ContentEyeSystem : SharedContentEyeSystem
UpdateEyeOffset((entity, eyeComponent));
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// TODO: Ideally we wouldn't want this to run in both FrameUpdate and Update, but we kind of have to since the visual update happens in FrameUpdate, but interaction update happens in Update. It's a workaround and a better solution should be found.
var eyeEntities = AllEntityQuery<ContentEyeComponent, EyeComponent>();
while (eyeEntities.MoveNext(out var entity, out ContentEyeComponent? contentComponent, out EyeComponent? eyeComponent))
{
UpdateEyeOffset((entity, eyeComponent));
}
}
}

View File

@@ -1,10 +1,10 @@
using System.Numerics;
using Content.Client.Movement.Components;
using Content.Client.Viewport;
using Content.Shared.Camera;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.Map;
using Robust.Client.Player;
namespace Content.Client.Movement.Systems;
@@ -12,13 +12,10 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IClyde _clyde = default!;
// This value is here to make sure the user doesn't have to move their mouse
// all the way out to the edge of the screen to get the full offset.
static private float _edgeOffset = 0.9f;
private static float _edgeOffset = 0.8f;
public override void Initialize()
{
@@ -38,25 +35,29 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component)
{
var localPlayer = _player.LocalEntity;
var mousePos = _inputManager.MouseScreenPosition;
var screenSize = _clyde.MainWindow.Size;
var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset;
var mouseNormalizedPos = new Vector2(-(mousePos.X - screenSize.X / 2) / minValue, (mousePos.Y - screenSize.Y / 2) / minValue); // X needs to be inverted here for some reason, otherwise it ends up flipped.
if (localPlayer == null)
// We need the main viewport where the game content is displayed, as certain UI layouts (e.g. Separated HUD) can make it a different size to the game window.
if (_eyeManager.MainViewport is not ScalingViewport vp)
return null;
var playerPos = _transform.GetWorldPosition(localPlayer.Value);
var mousePos = _inputManager.MouseScreenPosition.Position; // TODO: If we ever get a right-aligned Separated HUD setting, this might need to be adjusted for that.
var viewportSize = vp.PixelSize; // The size of the game viewport, including black bars - does not include the chatbox in Separated HUD view.
var scalingViewportSize = vp.ViewportSize * vp.CurrentRenderScale; // The size of the viewport in which the game is rendered (i.e. not including black bars). Note! Can extend outside the game window with certain zoom settings!
var visibleViewportSize = Vector2.Min(viewportSize, scalingViewportSize); // The size of the game viewport that is "actually visible" to the player, cutting off over-extensions and not counting black bar padding.
Matrix3x2.Invert(_eyeManager.MainViewport.GetLocalToScreenMatrix(), out var matrix);
var mouseCoords = Vector2.Transform(mousePos, matrix); // Gives the mouse position inside of the *scaling viewport*, i.e. 0,0 is inside the black bars. Note! 0,0 can be outside the game window with certain zoom settings!
var boundedMousePos = Vector2.Clamp(Vector2.Min(mouseCoords, mousePos), Vector2.Zero, visibleViewportSize); // Mouse position inside the visible game viewport's bounds.
var offsetRadius = MathF.Min(visibleViewportSize.X / 2f, visibleViewportSize.Y / 2f) * _edgeOffset;
var mouseNormalizedPos = new Vector2(-(boundedMousePos.X - visibleViewportSize.X / 2f) / offsetRadius, (boundedMousePos.Y - visibleViewportSize.Y / 2f) / offsetRadius);
if (component == null)
{
component = EnsureComp<EyeCursorOffsetComponent>(uid);
}
// Doesn't move the offset if the mouse has left the game window!
if (mousePos.Window != WindowId.Invalid)
if (_inputManager.MouseScreenPosition.Window != WindowId.Invalid)
{
// The offset must account for the in-world rotation.
var eyeRotation = _eyeManager.CurrentEye.Rotation;
@@ -77,7 +78,7 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
Vector2 vectorOffset = component.TargetPosition - component.CurrentPosition;
if (vectorOffset.Length() > component.OffsetSpeed)
{
vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed;
vectorOffset = vectorOffset.Normalized() * component.OffsetSpeed; // TODO: Probably needs to properly account for time delta or something.
}
component.CurrentPosition += vectorOffset;
}

View File

@@ -20,6 +20,7 @@ namespace Content.Client.NPC
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly NPCSteeringSystem _steering = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
@@ -30,17 +31,15 @@ namespace Content.Client.NPC
get => _modes;
set
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (value == PathfindingDebugMode.None)
{
Breadcrumbs.Clear();
Polys.Clear();
overlayManager.RemoveOverlay<PathfindingOverlay>();
_overlayManager.RemoveOverlay<PathfindingOverlay>();
}
else if (!overlayManager.HasOverlay<PathfindingOverlay>())
else if (!_overlayManager.HasOverlay<PathfindingOverlay>())
{
overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
_overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
}
if ((value & PathfindingDebugMode.Steering) != 0x0)

View File

@@ -11,6 +11,8 @@ namespace Content.Client.Radiation.Overlays;
public sealed class RadiationDebugOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IResourceCache _cache = default!;
private readonly SharedMapSystem _mapSystem;
private readonly RadiationSystem _radiation;
@@ -24,8 +26,7 @@ public sealed class RadiationDebugOverlay : Overlay
_radiation = _entityManager.System<RadiationSystem>();
_mapSystem = _entityManager.System<SharedMapSystem>();
var cache = IoCManager.Resolve<IResourceCache>();
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
_font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 8);
}
protected override void Draw(in OverlayDrawArgs args)

View File

@@ -16,20 +16,20 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
get => _enableShuttlePosition;
set
{
if (_enableShuttlePosition == value) return;
if (_enableShuttlePosition == value)
return;
_enableShuttlePosition = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enableShuttlePosition)
{
_overlay = new EmergencyShuttleOverlay(EntityManager.TransformQuery, XformSystem);
overlayManager.AddOverlay(_overlay);
_overlays.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
}
else
{
overlayManager.RemoveOverlay(_overlay!);
_overlays.RemoveOverlay(_overlay!);
_overlay = null;
}
}

View File

@@ -12,7 +12,6 @@ namespace Content.Client.Stack
{
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly ItemCounterSystem _counterSystem = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public override void Initialize()
{

View File

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

View File

@@ -31,7 +31,7 @@ public sealed class EntityStorageSystem : SharedEntityStorageSystem
SubscribeLocalEvent<EntityStorageComponent, ComponentHandleState>(OnHandleState);
}
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref SharedEntityStorageComponent? component)
public override bool ResolveStorage(EntityUid uid, [NotNullWhen(true)] ref EntityStorageComponent? component)
{
if (component != null)
return true;

View File

@@ -25,7 +25,7 @@ public sealed class MenuButton : ContainerButton
private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal;
private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered;
private BoundKeyFunction _function;
private BoundKeyFunction? _function;
private readonly BoxContainer _root;
private readonly TextureRect? _buttonIcon;
private readonly Label? _buttonLabel;
@@ -33,13 +33,13 @@ public sealed class MenuButton : ContainerButton
public string AppendStyleClass { set => AddStyleClass(value); }
public Texture? Icon { get => _buttonIcon!.Texture; set => _buttonIcon!.Texture = value; }
public BoundKeyFunction BoundKey
public BoundKeyFunction? BoundKey
{
get => _function;
set
{
_function = value;
_buttonLabel!.Text = BoundKeyHelper.ShortKeyName(value);
_buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
}
@@ -95,12 +95,12 @@ public sealed class MenuButton : ContainerButton
private void OnKeyBindingChanged(IKeyBinding obj)
{
_buttonLabel!.Text = BoundKeyHelper.ShortKeyName(_function);
_buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
private void OnKeyBindingChanged()
{
_buttonLabel!.Text = BoundKeyHelper.ShortKeyName(_function);
_buttonLabel!.Text = _function == null ? "" : BoundKeyHelper.ShortKeyName(_function.Value);
}
protected override void StylePropertiesChanged()

View File

@@ -30,6 +30,7 @@ public sealed partial class GunSystem : SharedGunSystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IStateManager _state = default!;
[Dependency] private readonly AnimationPlayerSystem _animPlayer = default!;
@@ -50,11 +51,10 @@ public sealed partial class GunSystem : SharedGunSystem
return;
_spreadOverlay = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_spreadOverlay)
{
overlayManager.AddOverlay(new GunSpreadOverlay(
_overlayManager.AddOverlay(new GunSpreadOverlay(
EntityManager,
_eyeManager,
Timing,
@@ -65,7 +65,7 @@ public sealed partial class GunSystem : SharedGunSystem
}
else
{
overlayManager.RemoveOverlay<GunSpreadOverlay>();
_overlayManager.RemoveOverlay<GunSpreadOverlay>();
}
}
}

View File

@@ -29,7 +29,10 @@ public sealed class WieldableSystem : SharedWieldableSystem
return;
if (_gameTiming.IsFirstTimePredicted)
{
cursorOffsetComp.CurrentPosition = Vector2.Zero;
cursorOffsetComp.TargetPosition = Vector2.Zero;
}
}
public void OnGetEyeOffset(Entity<CursorOffsetRequiresWieldComponent> entity, ref HeldRelayedEvent<GetEyeOffsetRelayedEvent> args)

View File

@@ -6,6 +6,7 @@ using Content.Server.Cargo.Systems;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Mobs.Components;
using Content.Shared.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
@@ -248,6 +249,27 @@ public sealed class CargoTest
Assert.That(price, Is.EqualTo(100.0));
});
await pair.CleanReturnAsync();
}
[Test]
public async Task MobPrice()
{
await using var pair = await PoolManager.GetServerClient();
var componentFactory = pair.Server.ResolveDependency<IComponentFactory>();
await pair.Server.WaitAssertion(() =>
{
Assert.Multiple(() =>
{
foreach (var (proto, comp) in pair.GetPrototypesWithComponent<MobPriceComponent>())
{
Assert.That(proto.TryGetComponent<MobStateComponent>(out _, componentFactory), $"Found MobPriceComponent on {proto.ID}, but no MobStateComponent!");
}
});
});
await pair.CleanReturnAsync();
}*/
}

View File

@@ -0,0 +1,54 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Server.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Disposal;
public sealed class DisposalUnitInteractionTest : InteractionTest
{
private static readonly EntProtoId DisposalUnit = "DisposalUnit";
private static readonly EntProtoId TrashItem = "BrokenBottle";
private const string TestDisposalUnitId = "TestDisposalUnit";
[TestPrototypes]
private static readonly string TestPrototypes = $@"
# A modified disposal unit with a 100% chance of a thrown item being inserted
- type: entity
parent: {DisposalUnit.Id}
id: {TestDisposalUnitId}
components:
- type: ThrowInsertContainer
probability: 1
";
/// <summary>
/// Spawns a disposal unit, gives the player a trash item, and makes the
/// player throw the item at the disposal unit.
/// After a short delay, verifies that the thrown item is contained inside
/// the disposal unit.
/// </summary>
[Test]
public async Task ThrowItemIntoDisposalUnitTest()
{
var containerSys = Server.System<SharedContainerSystem>();
// Spawn the target disposal unit
var disposalUnit = await SpawnTarget(TestDisposalUnitId);
// Give the player some trash to throw
var trash = await PlaceInHands(TrashItem);
// Throw the item at the disposal unit
await ThrowItem();
// Wait a moment
await RunTicks(10);
// Make sure the trash is in the disposal unit
var throwInsertComp = Comp<ThrowInsertContainerComponent>();
var container = containerSys.GetContainer(ToServer(disposalUnit), throwInsertComp.ContainerId);
Assert.That(container.ContainedEntities, Contains.Item(ToServer(trash)));
}
}

View File

@@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
components:
- type: Physics
bodyType: Dynamic
- type: GravityAffected
- type: Fixtures
fixtures:
fix1:

View File

@@ -0,0 +1,20 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Engineering.Systems;
namespace Content.IntegrationTests.Tests.Engineering;
[TestFixture]
[TestOf(typeof(InflatableSafeDisassemblySystem))]
public sealed class InflatablesDeflateTest : InteractionTest
{
[Test]
public async Task Test()
{
await SpawnTarget(InflatableWall);
await InteractUsing(Needle);
AssertDeleted();
await AssertEntityLookup(new EntitySpecifier(InflatableWallStack.Id, 1));
}
}

View File

@@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
- type: Alerts
- type: Physics
bodyType: Dynamic
- type: GravityAffected
- type: entity
name: WeightlessGravityGeneratorDummy

View File

@@ -76,8 +76,8 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.True);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).EnabledVV);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).Enabled);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled);
});
// Re-enable needs power so it turns off again.
@@ -94,7 +94,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() =>
{
Assert.That(generatorComponent.GravityActive, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled, Is.False);
});
});

View File

@@ -1,3 +1,6 @@
using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Interaction;
// This partial class contains various constant prototype IDs common to interaction tests.
@@ -32,4 +35,9 @@ public abstract partial class InteractionTest
protected const string Manipulator1 = "MicroManipulatorStockPart";
protected const string Battery1 = "PowerCellSmall";
protected const string Battery4 = "PowerCellHyper";
// Inflatables & Needle used to pop them
protected static readonly EntProtoId InflatableWall = "InflatableWall";
protected static readonly EntProtoId Needle = "WeaponMeleeNeedle";
protected static readonly ProtoId<StackPrototype> InflatableWallStack = "InflatableWall";
}

View File

@@ -144,6 +144,7 @@ public abstract partial class InteractionTest
- type: Stripping
- type: Puller
- type: Physics
- type: GravityAffected
- type: Tag
tags:
- CanPilot

View File

@@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View File

@@ -1,5 +1,6 @@
using Content.Server.Storage.Components;
using Content.Shared.Administration;
using Content.Shared.Storage.Components;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;

View File

@@ -1,4 +1,4 @@
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Administration;
using Robust.Shared.Console;

View File

@@ -1,16 +0,0 @@
using Content.Shared.Administration;
namespace Content.Server.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
/// <summary>
/// Freezes and mutes the given entity.
/// </summary>
public void FreezeAndMute(EntityUid uid)
{
var comp = EnsureComp<AdminFrozenComponent>(uid);
comp.Muted = true;
Dirty(uid, comp);
}
}

View File

@@ -1,10 +1,8 @@
using System.Threading;
using Content.Server.Administration.Components;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Clothing.Systems;
using Content.Server.Electrocution;
using Content.Server.Explosion.EntitySystems;
using Content.Server.GhostKick;
@@ -14,12 +12,12 @@ using Content.Server.Pointing.Components;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
using Content.Server.Speech.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Server.Tabletop;
using Content.Server.Tabletop.Components;
using Content.Shared.Administration;
using Content.Shared.Administration.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Body.Part;
using Content.Shared.Clumsy;
@@ -29,6 +27,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Systems;
using Content.Shared.Database;
using Content.Shared.Electrocution;
using Content.Shared.Gravity;
using Content.Shared.Interaction.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
@@ -39,7 +38,7 @@ using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Slippery;
using Content.Shared.Stunnable;
using Content.Shared.Storage.Components;
using Content.Shared.Tabletop.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.Verbs;
@@ -49,7 +48,6 @@ using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
@@ -675,6 +673,11 @@ public sealed partial class AdminVerbSystem
grav.Weightless = true;
Dirty(args.Target, grav);
EnsureComp<GravityAffectedComponent>(args.Target, out var weightless);
weightless.Weightless = true;
Dirty(args.Target, weightless);
},
Impact = LogImpact.Extreme,
Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description"))

View File

@@ -29,12 +29,10 @@ public sealed partial class ParrotMemorySystem : SharedParrotMemorySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly PopupSystem _popup = default!;
public override void Initialize()
{

View File

@@ -1,7 +1,7 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Anomaly.Components;
using Content.Shared.Anomaly.Effects.Components;
using Content.Shared.Atmos.Components;
using Robust.Shared.Map;
namespace Content.Server.Anomaly.Effects;

View File

@@ -1,5 +1,4 @@
using Content.Server.Botany.Components;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Botany;
@@ -16,6 +15,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Kitchen.Components;
namespace Content.Server.Botany.Systems;

View File

@@ -1,7 +1,7 @@
using Content.Server.Botany.Components;
using Content.Server.Kitchen.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components;
using Content.Shared.Random;
using Robust.Shared.Containers;

View File

@@ -1,7 +1,6 @@
using Content.Server.Atmos.EntitySystems;
using Content.Server.Botany.Components;
using Content.Server.Hands.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Atmos;
@@ -26,6 +25,7 @@ using Robust.Shared.Timing;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Database;
using Content.Shared.Kitchen.Components;
using Content.Shared.Labels.Components;
namespace Content.Server.Botany.Systems;

View File

@@ -0,0 +1,5 @@
using Content.Shared.Changeling.Systems;
namespace Content.Server.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem;

View File

@@ -324,7 +324,7 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
if (playSound)
{
_audio.PlayGlobal(announcementSound == null ? DefaultAnnouncementSound : _audio.ResolveSound(announcementSound), Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
_audio.PlayGlobal(announcementSound ?? DefaultAnnouncementSound, Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
}
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Global station announcement from {sender}: {message}");
}
@@ -354,7 +354,7 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, wrappedMessage, source ?? default, false, true, colorOverride);
if (playSound)
{
_audio.PlayGlobal(announcementSound == null ? DefaultAnnouncementSound : _audio.ResolveSound(announcementSound), filter, true, AudioParams.Default.WithVolume(-2f)); //CP14 bugfix with sound resolving
_audio.PlayGlobal(announcementSound ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
}
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement from {sender}: {message}");
}
@@ -394,7 +394,7 @@ public sealed partial class ChatSystem : SharedChatSystem
if (playDefaultSound)
{
_audio.PlayGlobal(announcementSound?.ToString() ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
_audio.PlayGlobal(announcementSound ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
}
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement on {station} from {sender}: {message}");

View File

@@ -1,6 +1,6 @@
using Content.Server.Storage.Components;
using Content.Shared.Construction;
using Content.Shared.Examine;
using Content.Shared.Storage.Components;
using Content.Shared.Tools.Systems;
using JetBrains.Annotations;

View File

@@ -20,11 +20,21 @@ public sealed partial class PopupBehavior : IThresholdBehavior
[DataField("popupType")]
public PopupType PopupType;
/// <summary>
/// Only the affected entity will see the popup.
/// </summary>
[DataField]
public bool TargetOnly;
public void Execute(EntityUid uid, DestructibleSystem system, EntityUid? cause = null)
{
var popup = system.EntityManager.System<SharedPopupSystem>();
// popup is placed at coords since the entity could be deleted after, no more popup then
var coords = system.EntityManager.GetComponent<TransformComponent>(uid).Coordinates;
popup.PopupCoordinates(Loc.GetString(Popup), coords, PopupType);
if (TargetOnly)
popup.PopupCoordinates(Loc.GetString(Popup), coords, uid, PopupType);
else
popup.PopupCoordinates(Loc.GetString(Popup), coords, PopupType);
}
}

View File

@@ -0,0 +1,12 @@
using Content.Server.Medical;
namespace Content.Server.Destructible.Thresholds.Behaviors;
[DataDefinition]
public sealed partial class VomitBehavior : IThresholdBehavior
{
public void Execute(EntityUid uid, DestructibleSystem system, EntityUid? cause = null)
{
system.EntityManager.System<VomitSystem>().Vomit(uid);
}
}

View File

@@ -418,7 +418,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
}
}
_stuttering.DoStutter(uid, time * StutteringTimeMultiplier, refresh, statusEffects);
_stuttering.DoStutter(uid, time * StutteringTimeMultiplier, refresh);
_jittering.DoJitter(uid, time * JitterTimeMultiplier, refresh, JitterAmplitude, JitterFrequency, true, statusEffects);
_popup.PopupEntity(Loc.GetString("electrocuted-component-mob-shocked-popup-player"), uid, uid);

View File

@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
@@ -22,6 +21,7 @@ using Content.Server.Temperature.Systems;
using Content.Server.Traits.Assorted;
using Content.Server.Zombies;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.Coordinates.Helpers;
using Content.Shared.EntityEffects.EffectConditions;
@@ -889,7 +889,7 @@ public sealed class EntityEffectSystem : EntitySystem
if (plantholder.Seed == null)
return;
var gasses = plantholder.Seed.ExudeGasses;
var gasses = plantholder.Seed.ConsumeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);
@@ -911,7 +911,7 @@ public sealed class EntityEffectSystem : EntitySystem
if (plantholder.Seed == null)
return;
var gasses = plantholder.Seed.ConsumeGasses;
var gasses = plantholder.Seed.ExudeGasses;
// Add a random amount of a random gas to this gas dictionary
float amount = _random.NextFloat(args.Effect.MinValue, args.Effect.MaxValue);

View File

@@ -4,6 +4,7 @@ using Content.Server.Administration.Logs;
using Content.Server.Atmos.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Atmos.Components;
using Content.Shared.Camera;
using Content.Shared.CCVar;
using Content.Shared.Damage;

View File

@@ -166,7 +166,7 @@ public sealed class SpraySystem : EntitySystem
if (TryComp<PhysicsComponent>(user, out var body))
{
if (_gravity.IsWeightless(user, body))
if (_gravity.IsWeightless(user))
{
// push back the player
_physics.ApplyLinearImpulse(user, -impulseDirection * entity.Comp.PushbackAmount, body: body);

View File

@@ -0,0 +1,103 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.GameTicking.Rules;
using Content.Shared.Administration;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
namespace Content.Server.GameTicking.Commands;
[ToolshedCommand, AdminCommand(AdminFlags.Round)]
public sealed class DynamicRuleCommand : ToolshedCommand
{
private DynamicRuleSystem? _dynamicRuleSystem;
[CommandImplementation("list")]
public IEnumerable<EntityUid> List()
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.GetDynamicRules();
}
[CommandImplementation("get")]
public EntityUid Get()
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.GetDynamicRules().FirstOrDefault();
}
[CommandImplementation("budget")]
public IEnumerable<float?> Budget([PipedArgument] IEnumerable<EntityUid> input)
=> input.Select(Budget);
[CommandImplementation("budget")]
public float? Budget([PipedArgument] EntityUid input)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.GetRuleBudget(input);
}
[CommandImplementation("adjust")]
public IEnumerable<float?> Adjust([PipedArgument] IEnumerable<EntityUid> input, float value)
=> input.Select(i => Adjust(i,value));
[CommandImplementation("adjust")]
public float? Adjust([PipedArgument] EntityUid input, float value)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.AdjustBudget(input, value);
}
[CommandImplementation("set")]
public IEnumerable<float?> Set([PipedArgument] IEnumerable<EntityUid> input, float value)
=> input.Select(i => Set(i,value));
[CommandImplementation("set")]
public float? Set([PipedArgument] EntityUid input, float value)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.SetBudget(input, value);
}
[CommandImplementation("dryrun")]
public IEnumerable<IEnumerable<EntProtoId>> DryRun([PipedArgument] IEnumerable<EntityUid> input)
=> input.Select(DryRun);
[CommandImplementation("dryrun")]
public IEnumerable<EntProtoId> DryRun([PipedArgument] EntityUid input)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.DryRun(input);
}
[CommandImplementation("executenow")]
public IEnumerable<IEnumerable<EntityUid>> ExecuteNow([PipedArgument] IEnumerable<EntityUid> input)
=> input.Select(ExecuteNow);
[CommandImplementation("executenow")]
public IEnumerable<EntityUid> ExecuteNow([PipedArgument] EntityUid input)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.ExecuteNow(input);
}
[CommandImplementation("rules")]
public IEnumerable<IEnumerable<EntityUid>> Rules([PipedArgument] IEnumerable<EntityUid> input)
=> input.Select(Rules);
[CommandImplementation("rules")]
public IEnumerable<EntityUid> Rules([PipedArgument] EntityUid input)
{
_dynamicRuleSystem ??= GetSys<DynamicRuleSystem>();
return _dynamicRuleSystem.Rules(input);
}
}

View File

@@ -21,7 +21,7 @@ public sealed partial class GameTicker
/// A list storing the start times of all game rules that have been started this round.
/// Game rules can be started and stopped at any time, including midround.
/// </summary>
public IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => _allPreviousGameRules;
public override IReadOnlyList<(TimeSpan, string)> AllPreviousGameRules => _allPreviousGameRules;
private void InitializeGameRules()
{

View File

@@ -0,0 +1,195 @@
using System.Diagnostics;
using Content.Server.Administration.Logs;
using Content.Server.RoundEnd;
using Content.Shared.Database;
using Content.Shared.EntityTable;
using Content.Shared.EntityTable.Conditions;
using Content.Shared.GameTicking.Components;
using Content.Shared.GameTicking.Rules;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.GameTicking.Rules;
public sealed class DynamicRuleSystem : GameRuleSystem<DynamicRuleComponent>
{
[Dependency] private readonly IAdminLogManager _adminLog = default!;
[Dependency] private readonly EntityTableSystem _entityTable = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly IRobustRandom _random = default!;
protected override void Added(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
component.Budget = _random.Next(component.StartingBudgetMin, component.StartingBudgetMax);;
component.NextRuleTime = Timing.CurTime + _random.Next(component.MinRuleInterval, component.MaxRuleInterval);
}
protected override void Started(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);
// Since we don't know how long until this rule is activated, we need to
// set the last budget update to now so it doesn't immediately give the component a bunch of points.
component.LastBudgetUpdate = Timing.CurTime;
Execute((uid, component));
}
protected override void Ended(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
{
base.Ended(uid, component, gameRule, args);
foreach (var rule in component.Rules)
{
GameTicker.EndGameRule(rule);
}
}
protected override void ActiveTick(EntityUid uid, DynamicRuleComponent component, GameRuleComponent gameRule, float frameTime)
{
base.ActiveTick(uid, component, gameRule, frameTime);
if (Timing.CurTime < component.NextRuleTime)
return;
// don't spawn antags during evac
if (_roundEnd.IsRoundEndRequested())
return;
Execute((uid, component));
}
/// <summary>
/// Generates and returns a list of randomly selected,
/// valid rules to spawn based on <see cref="DynamicRuleComponent.Table"/>.
/// </summary>
private IEnumerable<EntProtoId> GetRuleSpawns(Entity<DynamicRuleComponent> entity)
{
UpdateBudget((entity.Owner, entity.Comp));
var ctx = new EntityTableContext(new Dictionary<string, object>
{
{ HasBudgetCondition.BudgetContextKey, entity.Comp.Budget },
});
return _entityTable.GetSpawns(entity.Comp.Table, ctx: ctx);
}
/// <summary>
/// Updates the budget of the provided dynamic rule component based on the amount of time since the last update
/// multiplied by the <see cref="DynamicRuleComponent.BudgetPerSecond"/> value.
/// </summary>
private void UpdateBudget(Entity<DynamicRuleComponent> entity)
{
var duration = (float) (Timing.CurTime - entity.Comp.LastBudgetUpdate).TotalSeconds;
entity.Comp.Budget += duration * entity.Comp.BudgetPerSecond;
entity.Comp.LastBudgetUpdate = Timing.CurTime;
}
/// <summary>
/// Executes this rule, generating new dynamic rules and starting them.
/// </summary>
/// <returns>
/// Returns a list of the rules that were executed.
/// </returns>
private List<EntityUid> Execute(Entity<DynamicRuleComponent> entity)
{
entity.Comp.NextRuleTime =
Timing.CurTime + _random.Next(entity.Comp.MinRuleInterval, entity.Comp.MaxRuleInterval);
var executedRules = new List<EntityUid>();
foreach (var rule in GetRuleSpawns(entity))
{
var res = GameTicker.StartGameRule(rule, out var ruleUid);
Debug.Assert(res);
executedRules.Add(ruleUid);
if (TryComp<DynamicRuleCostComponent>(ruleUid, out var cost))
{
entity.Comp.Budget -= cost.Cost;
_adminLog.Add(LogType.EventRan, LogImpact.High, $"{ToPrettyString(entity)} ran rule {ToPrettyString(ruleUid)} with cost {cost.Cost} on budget {entity.Comp.Budget}.");
}
else
{
_adminLog.Add(LogType.EventRan, LogImpact.High, $"{ToPrettyString(entity)} ran rule {ToPrettyString(ruleUid)} which had no cost.");
}
}
entity.Comp.Rules.AddRange(executedRules);
return executedRules;
}
#region Command Methods
public List<EntityUid> GetDynamicRules()
{
var rules = new List<EntityUid>();
var query = EntityQueryEnumerator<DynamicRuleComponent, GameRuleComponent>();
while (query.MoveNext(out var uid, out _, out var comp))
{
if (!GameTicker.IsGameRuleActive(uid, comp))
continue;
rules.Add(uid);
}
return rules;
}
public float? GetRuleBudget(Entity<DynamicRuleComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return null;
UpdateBudget((entity.Owner, entity.Comp));
return entity.Comp.Budget;
}
public float? AdjustBudget(Entity<DynamicRuleComponent?> entity, float amount)
{
if (!Resolve(entity, ref entity.Comp))
return null;
UpdateBudget((entity.Owner, entity.Comp));
entity.Comp.Budget += amount;
return entity.Comp.Budget;
}
public float? SetBudget(Entity<DynamicRuleComponent?> entity, float amount)
{
if (!Resolve(entity, ref entity.Comp))
return null;
entity.Comp.LastBudgetUpdate = Timing.CurTime;
entity.Comp.Budget = amount;
return entity.Comp.Budget;
}
public IEnumerable<EntProtoId> DryRun(Entity<DynamicRuleComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return new List<EntProtoId>();
return GetRuleSpawns((entity.Owner, entity.Comp));
}
public IEnumerable<EntityUid> ExecuteNow(Entity<DynamicRuleComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return new List<EntityUid>();
return Execute((entity.Owner, entity.Comp));
}
public IEnumerable<EntityUid> Rules(Entity<DynamicRuleComponent?> entity)
{
if (!Resolve(entity, ref entity.Comp))
return new List<EntityUid>();
return entity.Comp.Rules;
}
#endregion
}

View File

@@ -18,7 +18,7 @@ namespace Content.Server.Gravity
/// </summary>
public void RefreshGravity(EntityUid uid, GravityComponent? gravity = null)
{
if (!Resolve(uid, ref gravity))
if (!GravityQuery.Resolve(uid, ref gravity))
return;
if (gravity.Inherent)
@@ -61,7 +61,7 @@ namespace Content.Server.Gravity
/// </summary>
public void EnableGravity(EntityUid uid, GravityComponent? gravity = null)
{
if (!Resolve(uid, ref gravity))
if (!GravityQuery.Resolve(uid, ref gravity))
return;
if (gravity.Enabled || gravity.Inherent)

View File

@@ -201,9 +201,6 @@ namespace Content.Server.Hands.Systems
var holderVelocity = _physicsQuery.TryComp(entity, out var physics) ? physics.LinearVelocity : Vector2.Zero;
var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread);
var fellEvent = new FellDownEvent(entity);
RaiseLocalEvent(entity, fellEvent);
foreach (var hand in entity.Comp.Hands.Keys)
{
if (!TryGetHeldItem(entity.AsNullable(), hand, out var heldEntity))

View File

@@ -29,17 +29,17 @@ public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem
{
base.Initialize();
SubscribeLocalEvent<SubdermalImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
SubscribeLocalEvent<ChameleonControllerImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
SubscribeLocalEvent<ChameleonClothingComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected);
}
private void OnSelected(Entity<SubdermalImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
private void OnSelected(Entity<ChameleonControllerImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
{
if (!_delay.TryResetDelay(ent.Owner, true) || ent.Comp.ImplantedEntity == null || !HasComp<ChameleonControllerImplantComponent>(ent))
if (!TryComp<SubdermalImplantComponent>(ent, out var implantComp) || implantComp.ImplantedEntity == null || !_delay.TryResetDelay(ent.Owner, true))
return;
ChangeChameleonClothingToOutfit(ent.Comp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
ChangeChameleonClothingToOutfit(implantComp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
}
/// <summary>

View File

@@ -27,6 +27,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
}
// TODO: This all needs to be moved to shared and predicted.
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
{
if (args.Target == null || !args.CanReach || args.Handled)

View File

@@ -1,7 +1,6 @@
using Content.Server.Radio.Components;
using Content.Shared.Implants;
using Content.Shared.Implants.Components;
using Robust.Shared.Containers;
namespace Content.Server.Implants;
@@ -12,7 +11,7 @@ public sealed class RadioImplantSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
SubscribeLocalEvent<RadioImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
}
/// <summary>
@@ -20,19 +19,16 @@ public sealed class RadioImplantSystem : EntitySystem
/// </summary>
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
{
if (args.Implanted == null)
return;
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted);
foreach (var channel in ent.Comp.RadioChannels)
{
if (activeRadio.Channels.Add(channel))
ent.Comp.ActiveAddedChannels.Add(channel);
}
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted);
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted);
foreach (var channel in ent.Comp.RadioChannels)
{
if (intrinsicRadioTransmitter.Channels.Add(channel))
@@ -43,9 +39,9 @@ public sealed class RadioImplantSystem : EntitySystem
/// <summary>
/// Removes intrinsic radio components once the Radio Implant is removed
/// </summary>
private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
private void OnImplantRemoved(Entity<RadioImplantComponent> ent, ref ImplantRemovedEvent args)
{
if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
if (TryComp<ActiveRadioComponent>(args.Implanted, out var activeRadioComponent))
{
foreach (var channel in ent.Comp.ActiveAddedChannels)
{
@@ -55,11 +51,11 @@ public sealed class RadioImplantSystem : EntitySystem
if (activeRadioComponent.Channels.Count == 0)
{
RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
RemCompDeferred<ActiveRadioComponent>(args.Implanted);
}
}
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Implanted, out var radioTransmitterComponent))
return;
foreach (var channel in ent.Comp.TransmitterAddedChannels)
@@ -70,7 +66,7 @@ public sealed class RadioImplantSystem : EntitySystem
if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
{
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Implanted);
}
}
}

View File

@@ -18,6 +18,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay);
}
// TODO: This shouldn't be in the SubdermalImplantSystem
private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
{
var args = implantRelay.Event;

View File

@@ -1,15 +0,0 @@
namespace Content.Server.Kitchen.Components;
/// <summary>
/// Applies to items that are capable of butchering entities, or
/// are otherwise sharp for some purpose.
/// </summary>
[RegisterComponent]
public sealed partial class SharpComponent : Component
{
// TODO just make this a tool type.
public HashSet<EntityUid> Butchering = new();
[DataField("butcherDelayModifier")]
public float ButcherDelayModifier = 1.0f;
}

View File

@@ -1,292 +0,0 @@
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.Kitchen.Components;
using Content.Server.Popups;
using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Kitchen;
using Content.Shared.Kitchen.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Random;
using static Content.Shared.Kitchen.Components.KitchenSpikeComponent;
namespace Content.Server.Kitchen.EntitySystems
{
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly IAdminLogManager _logger = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly BodySystem _bodySystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedSuicideSystem _suicide = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<KitchenSpikeComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<KitchenSpikeComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<KitchenSpikeComponent, DragDropTargetEvent>(OnDragDrop);
//DoAfter
SubscribeLocalEvent<KitchenSpikeComponent, SpikeDoAfterEvent>(OnDoAfter);
SubscribeLocalEvent<KitchenSpikeComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
SubscribeLocalEvent<ButcherableComponent, CanDropDraggedEvent>(OnButcherableCanDrop);
}
private void OnButcherableCanDrop(Entity<ButcherableComponent> entity, ref CanDropDraggedEvent args)
{
args.Handled = true;
args.CanDrop |= entity.Comp.Type != ButcheringType.Knife;
}
/// <summary>
/// TODO: Update this so it actually meatspikes the user instead of applying lethal damage to them.
/// </summary>
private void OnSuicideByEnvironment(Entity<KitchenSpikeComponent> entity, ref SuicideByEnvironmentEvent args)
{
if (args.Handled)
return;
if (!TryComp<DamageableComponent>(args.Victim, out var damageableComponent))
return;
_suicide.ApplyLethalDamage((args.Victim, damageableComponent), "Piercing");
var othersMessage = Loc.GetString("comp-kitchen-spike-suicide-other",
("victim", Identity.Entity(args.Victim, EntityManager)),
("this", entity));
_popupSystem.PopupEntity(othersMessage, args.Victim, Filter.PvsExcept(args.Victim), true);
var selfMessage = Loc.GetString("comp-kitchen-spike-suicide-self",
("this", entity));
_popupSystem.PopupEntity(selfMessage, args.Victim, args.Victim);
args.Handled = true;
}
private void OnDoAfter(Entity<KitchenSpikeComponent> entity, ref SpikeDoAfterEvent args)
{
if (args.Args.Target == null)
return;
if (TryComp<ButcherableComponent>(args.Args.Target.Value, out var butcherable))
butcherable.BeingButchered = false;
if (args.Cancelled)
{
entity.Comp.InUse = false;
return;
}
if (args.Handled)
return;
if (Spikeable(entity, args.Args.User, args.Args.Target.Value, entity.Comp, butcherable))
Spike(entity, args.Args.User, args.Args.Target.Value, entity.Comp);
entity.Comp.InUse = false;
args.Handled = true;
}
private void OnDragDrop(Entity<KitchenSpikeComponent> entity, ref DragDropTargetEvent args)
{
if (args.Handled)
return;
args.Handled = true;
if (Spikeable(entity, args.User, args.Dragged, entity.Comp))
TrySpike(entity, args.User, args.Dragged, entity.Comp);
}
private void OnInteractHand(Entity<KitchenSpikeComponent> entity, ref InteractHandEvent args)
{
if (args.Handled)
return;
if (entity.Comp.PrototypesToSpawn?.Count > 0)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-knife-needed"), entity, args.User);
args.Handled = true;
}
}
private void OnInteractUsing(Entity<KitchenSpikeComponent> entity, ref InteractUsingEvent args)
{
if (args.Handled)
return;
if (TryGetPiece(entity, args.User, args.Used))
args.Handled = true;
}
private void Spike(EntityUid uid, EntityUid userUid, EntityUid victimUid,
KitchenSpikeComponent? component = null, ButcherableComponent? butcherable = null)
{
if (!Resolve(uid, ref component) || !Resolve(victimUid, ref butcherable))
return;
var logImpact = LogImpact.Medium;
if (HasComp<HumanoidAppearanceComponent>(victimUid))
logImpact = LogImpact.Extreme;
_logger.Add(LogType.Gib, logImpact, $"{ToPrettyString(userUid):user} kitchen spiked {ToPrettyString(victimUid):target}");
// TODO VERY SUS
component.PrototypesToSpawn = EntitySpawnCollection.GetSpawns(butcherable.SpawnedEntities, _random);
// This feels not okay, but entity is getting deleted on "Spike", for now...
component.MeatSource1p = Loc.GetString("comp-kitchen-spike-remove-meat", ("victim", victimUid));
component.MeatSource0 = Loc.GetString("comp-kitchen-spike-remove-meat-last", ("victim", victimUid));
component.Victim = Name(victimUid);
UpdateAppearance(uid, null, component);
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-kill",
("user", Identity.Entity(userUid, EntityManager)),
("victim", Identity.Entity(victimUid, EntityManager)),
("this", uid)),
uid, PopupType.LargeCaution);
_transform.SetCoordinates(victimUid, Transform(uid).Coordinates);
// THE WHAT?
// TODO: Need to be able to leave them on the spike to do DoT, see ss13.
var gibs = _bodySystem.GibBody(victimUid);
foreach (var gib in gibs) {
QueueDel(gib);
}
_audio.PlayPvs(component.SpikeSound, uid);
}
private bool TryGetPiece(EntityUid uid, EntityUid user, EntityUid used,
KitchenSpikeComponent? component = null, SharpComponent? sharp = null)
{
if (!Resolve(uid, ref component) || component.PrototypesToSpawn == null || component.PrototypesToSpawn.Count == 0)
return false;
// Is using knife
if (!Resolve(used, ref sharp, false) )
{
return false;
}
var item = _random.PickAndTake(component.PrototypesToSpawn);
var ent = Spawn(item, Transform(uid).Coordinates);
_metaData.SetEntityName(ent,
Loc.GetString("comp-kitchen-spike-meat-name", ("name", Name(ent)), ("victim", component.Victim)));
if (component.PrototypesToSpawn.Count != 0)
_popupSystem.PopupEntity(component.MeatSource1p, uid, user, PopupType.MediumCaution);
else
{
UpdateAppearance(uid, null, component);
_popupSystem.PopupEntity(component.MeatSource0, uid, user, PopupType.MediumCaution);
}
return true;
}
private void UpdateAppearance(EntityUid uid, AppearanceComponent? appearance = null, KitchenSpikeComponent? component = null)
{
if (!Resolve(uid, ref component, ref appearance, false))
return;
_appearance.SetData(uid, KitchenSpikeVisuals.Status, component.PrototypesToSpawn?.Count > 0 ? KitchenSpikeStatus.Bloody : KitchenSpikeStatus.Empty, appearance);
}
private bool Spikeable(EntityUid uid, EntityUid userUid, EntityUid victimUid,
KitchenSpikeComponent? component = null, ButcherableComponent? butcherable = null)
{
if (!Resolve(uid, ref component))
return false;
if (component.PrototypesToSpawn?.Count > 0)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-collect", ("this", uid)), uid, userUid);
return false;
}
if (!Resolve(victimUid, ref butcherable, false))
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
}
switch (butcherable.Type)
{
case ButcheringType.Spike:
return true;
case ButcheringType.Knife:
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher-knife", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
default:
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-butcher", ("victim", Identity.Entity(victimUid, EntityManager)), ("this", uid)), victimUid, userUid);
return false;
}
}
public bool TrySpike(EntityUid uid, EntityUid userUid, EntityUid victimUid, KitchenSpikeComponent? component = null,
ButcherableComponent? butcherable = null, MobStateComponent? mobState = null)
{
if (!Resolve(uid, ref component) || component.InUse ||
!Resolve(victimUid, ref butcherable) || butcherable.BeingButchered)
return false;
// THE WHAT? (again)
// Prevent dead from being spiked TODO: Maybe remove when rounds can be played and DOT is implemented
if (Resolve(victimUid, ref mobState, false) &&
_mobStateSystem.IsAlive(victimUid, mobState))
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-deny-not-dead", ("victim", Identity.Entity(victimUid, EntityManager))),
victimUid, userUid);
return true;
}
if (userUid != victimUid)
{
_popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-begin-hook-victim", ("user", Identity.Entity(userUid, EntityManager)), ("this", uid)), victimUid, victimUid, PopupType.LargeCaution);
}
// TODO: make it work when SuicideEvent is implemented
// else
// _popupSystem.PopupEntity(Loc.GetString("comp-kitchen-spike-begin-hook-self", ("this", uid)), victimUid, Filter.Pvs(uid)); // This is actually unreachable and should be in SuicideEvent
butcherable.BeingButchered = true;
component.InUse = true;
var doAfterArgs = new DoAfterArgs(EntityManager, userUid, component.SpikeDelay + butcherable.ButcherDelay, new SpikeDoAfterEvent(), uid, target: victimUid, used: uid)
{
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
BreakOnDropItem = false,
};
_doAfter.TryStartDoAfter(doAfterArgs);
return true;
}
}
}

View File

@@ -1,5 +1,4 @@
using Content.Server.Body.Systems;
using Content.Server.Kitchen.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Body.Components;
using Content.Shared.Database;
@@ -8,6 +7,7 @@ using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Kitchen;
using Content.Shared.Kitchen.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;

View File

@@ -74,6 +74,9 @@ namespace Content.Server.Lathe
SubscribeLocalEvent<LatheComponent, LatheQueueRecipeMessage>(OnLatheQueueRecipeMessage);
SubscribeLocalEvent<LatheComponent, LatheSyncRequestMessage>(OnLatheSyncRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheDeleteRequestMessage>(OnLatheDeleteRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheMoveRequestMessage>(OnLatheMoveRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheAbortFabricationMessage>(OnLatheAbortFabricationMessage);
SubscribeLocalEvent<LatheComponent, BeforeActivatableUIOpenEvent>((u, c, _) => UpdateUserInterfaceState(u, c));
SubscribeLocalEvent<LatheComponent, MaterialAmountChangedEvent>(OnMaterialAmountChanged);
@@ -167,23 +170,32 @@ namespace Content.Server.Lathe
return ev.Recipes.ToList();
}
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, LatheComponent? component = null)
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, int quantity, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!CanProduce(uid, recipe, 1, component))
if (quantity <= 0)
return false;
quantity = int.Min(quantity, MaxItemsPerRequest);
if (!CanProduce(uid, recipe, quantity, component))
return false;
foreach (var (mat, amount) in recipe.Materials)
{
var adjustedAmount = recipe.ApplyMaterialDiscount
? (int) (-amount * component.MaterialUseMultiplier)
? (int)(-amount * component.MaterialUseMultiplier)
: -amount;
adjustedAmount *= quantity;
_materialStorage.TryChangeMaterialAmount(uid, mat, adjustedAmount);
}
component.Queue.Enqueue(recipe);
if (component.Queue.Last is { } node && node.ValueRef.Recipe == recipe.ID)
node.ValueRef.ItemsRequested += quantity;
else
component.Queue.AddLast(new LatheRecipeBatch(recipe.ID, 0, quantity));
return true;
}
@@ -195,8 +207,11 @@ namespace Content.Server.Lathe
if (component.CurrentRecipe != null || component.Queue.Count <= 0 || !this.IsPowered(uid, EntityManager))
return false;
var recipeProto = component.Queue.Dequeue();
var recipe = _proto.Index(recipeProto);
var batch = component.Queue.First();
batch.ItemsPrinted++;
if (batch.ItemsPrinted >= batch.ItemsRequested || batch.ItemsPrinted < 0) // Rollover sanity check
component.Queue.RemoveFirst();
var recipe = _proto.Index(batch.Recipe);
var time = _reagentSpeed.ApplySpeed(uid, recipe.CompleteTime) * component.TimeMultiplier;
@@ -271,8 +286,8 @@ namespace Content.Server.Lathe
return;
var producing = component.CurrentRecipe;
if (producing == null && component.Queue.TryPeek(out var next))
producing = next;
if (producing == null && component.Queue.First is { } node)
producing = node.Value.Recipe;
var state = new LatheUpdateState(GetAvailableRecipes(uid, component), component.Queue.ToArray(), producing);
_uiSys.SetUiState(uid, LatheUiKey.Key, state);
@@ -349,12 +364,10 @@ namespace Content.Server.Lathe
{
if (!args.Powered)
{
RemComp<LatheProducingComponent>(uid);
UpdateRunningAppearance(uid, false);
AbortProduction(uid);
}
else if (component.CurrentRecipe != null)
else
{
EnsureComp<LatheProducingComponent>(uid);
TryStartProducing(uid, component);
}
}
@@ -416,25 +429,46 @@ namespace Content.Server.Lathe
return GetAvailableRecipes(uid, component).Contains(recipe.ID);
}
public void AbortProduction(EntityUid uid, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (component.CurrentRecipe != null)
{
if (component.Queue.Count > 0)
{
// Batch abandoned while printing last item, need to create a one-item batch
var batch = component.Queue.First();
if (batch.Recipe != component.CurrentRecipe)
{
var newBatch = new LatheRecipeBatch(component.CurrentRecipe.Value, 0, 1);
component.Queue.AddFirst(newBatch);
}
else if (batch.ItemsPrinted > 0)
{
batch.ItemsPrinted--;
}
}
component.CurrentRecipe = null;
}
RemCompDeferred<LatheProducingComponent>(uid);
UpdateUserInterfaceState(uid, component);
UpdateRunningAppearance(uid, false);
}
#region UI Messages
private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, LatheQueueRecipeMessage args)
{
if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe))
{
var count = 0;
for (var i = 0; i < args.Quantity; i++)
{
if (TryAddToQueue(uid, recipe, component))
count++;
else
break;
}
if (count > 0)
if (TryAddToQueue(uid, recipe, args.Quantity, component))
{
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} queued {count} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
$"{ToPrettyString(args.Actor):player} queued {args.Quantity} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
}
}
TryStartProducing(uid, component);
@@ -445,6 +479,92 @@ namespace Content.Server.Lathe
{
UpdateUserInterfaceState(uid, component);
}
/// <summary>
/// Removes a batch from the batch queue by index.
/// If the index given does not exist or is outside of the bounds of the lathe's batch queue, nothing happens.
/// </summary>
/// <param name="uid">The lathe whose queue is being altered.</param>
/// <param name="component"></param>
/// <param name="args"></param>
public void OnLatheDeleteRequestMessage(EntityUid uid, LatheComponent component, ref LatheDeleteRequestMessage args)
{
if (args.Index < 0 || args.Index >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Shouldn't happen with checks above.
return;
var batch = node.Value;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} deleted a lathe job for ({batch.ItemsPrinted}/{batch.ItemsRequested}) {GetRecipeName(batch.Recipe)} at {ToPrettyString(uid):lathe}");
component.Queue.Remove(node);
UpdateUserInterfaceState(uid, component);
}
public void OnLatheMoveRequestMessage(EntityUid uid, LatheComponent component, ref LatheMoveRequestMessage args)
{
if (args.Change == 0 || args.Index < 0 || args.Index >= component.Queue.Count)
return;
// New index must be within the bounds of the batch.
var newIndex = args.Index + args.Change;
if (newIndex < 0 || newIndex >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Something went wrong.
return;
if (args.Change > 0)
{
var newRelativeNode = node.Next;
for (int i = 1; i < args.Change; i++) // 1-indexed: starting from Next
newRelativeNode = newRelativeNode?.Next;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddAfter(newRelativeNode, node);
}
else
{
var newRelativeNode = node.Previous;
for (int i = 1; i < -args.Change; i++) // 1-indexed: starting from Previous
newRelativeNode = newRelativeNode?.Previous;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddBefore(newRelativeNode, node);
}
UpdateUserInterfaceState(uid, component);
}
public void OnLatheAbortFabricationMessage(EntityUid uid, LatheComponent component, ref LatheAbortFabricationMessage args)
{
if (component.CurrentRecipe == null)
return;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} aborted printing {GetRecipeName(component.CurrentRecipe.Value)} at {ToPrettyString(uid):lathe}");
component.CurrentRecipe = null;
FinishProducing(uid, component);
}
#endregion
}
}

View File

@@ -27,7 +27,7 @@ public sealed class MindShieldSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<MindShieldImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<MindShieldImplantComponent, EntGotRemovedFromContainerMessage>(OnImplantDraw);
SubscribeLocalEvent<MindShieldImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
}
private void OnImplantImplanted(Entity<MindShieldImplantComponent> ent, ref ImplantImplantedEvent ev)
@@ -35,8 +35,8 @@ public sealed class MindShieldSystem : EntitySystem
if (ev.Implanted == null)
return;
EnsureComp<MindShieldComponent>(ev.Implanted.Value);
MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant);
EnsureComp<MindShieldComponent>(ev.Implanted);
MindShieldRemovalCheck(ev.Implanted, ev.Implant);
}
/// <summary>
@@ -58,9 +58,9 @@ public sealed class MindShieldSystem : EntitySystem
}
}
private void OnImplantDraw(Entity<MindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
private void OnImplantRemoved(Entity<MindShieldImplantComponent> ent, ref ImplantRemovedEvent args)
{
RemComp<MindShieldComponent>(args.Container.Owner);
RemComp<MindShieldComponent>(args.Implanted);
}
}

View File

@@ -1,11 +0,0 @@
namespace Content.Server.Morgue.Components;
/// <summary>
/// used to track actively cooking crematoriums
/// </summary>
[RegisterComponent]
public sealed partial class ActiveCrematoriumComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
public float Accumulator = 0;
}

View File

@@ -1,22 +0,0 @@
using Robust.Shared.Audio;
namespace Content.Server.Morgue.Components;
[RegisterComponent]
public sealed partial class CrematoriumComponent : Component
{
/// <summary>
/// The time it takes to cook in second
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int CookTime = 5;
[DataField("cremateStartSound")]
public SoundSpecifier CremateStartSound = new SoundPathSpecifier("/Audio/Items/Lighters/lighter1.ogg");
[DataField("crematingSound")]
public SoundSpecifier CrematingSound = new SoundPathSpecifier("/Audio/Effects/burning.ogg");
[DataField("cremateFinishSound")]
public SoundSpecifier CremateFinishSound = new SoundPathSpecifier("/Audio/Machines/ding.ogg");
}

View File

@@ -1,195 +1,58 @@
using Content.Server.Ghost;
using Content.Server.Morgue.Components;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind;
using Content.Shared.Morgue;
using Content.Shared.Morgue.Components;
using Content.Shared.Popups;
using Content.Shared.Standing;
using Content.Shared.Storage;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Morgue;
public sealed class CrematoriumSystem : EntitySystem
public sealed class CrematoriumSystem : SharedCrematoriumSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly EntityStorageSystem _entityStorage = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly SharedContainerSystem _containers = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CrematoriumComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<CrematoriumComponent, GetVerbsEvent<AlternativeVerb>>(AddCremateVerb);
SubscribeLocalEvent<CrematoriumComponent, SuicideByEnvironmentEvent>(OnSuicideByEnvironment);
SubscribeLocalEvent<ActiveCrematoriumComponent, StorageOpenAttemptEvent>(OnAttemptOpen);
}
private void OnExamine(EntityUid uid, CrematoriumComponent component, ExaminedEvent args)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
using (args.PushGroup(nameof(CrematoriumComponent)))
{
if (_appearance.TryGetData<bool>(uid, CrematoriumVisuals.Burning, out var isBurning, appearance) &&
isBurning)
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-is-burning",
("owner", uid)));
}
if (_appearance.TryGetData<bool>(uid, StorageVisuals.HasContents, out var hasContents, appearance) &&
hasContents)
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-has-contents"));
}
else
{
args.PushMarkup(Loc.GetString("crematorium-entity-storage-component-on-examine-details-empty"));
}
}
}
private void OnAttemptOpen(EntityUid uid, ActiveCrematoriumComponent component, ref StorageOpenAttemptEvent args)
{
args.Cancelled = true;
}
private void AddCremateVerb(EntityUid uid, CrematoriumComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!TryComp<EntityStorageComponent>(uid, out var storage))
return;
if (!args.CanAccess || !args.CanInteract || args.Hands == null || storage.Open)
return;
if (HasComp<ActiveCrematoriumComponent>(uid))
return;
AlternativeVerb verb = new()
{
Text = Loc.GetString("cremate-verb-get-data-text"),
// TODO VERB ICON add flame/burn symbol?
Act = () => TryCremate(uid, component, storage),
Impact = LogImpact.High // could be a body? or evidence? I dunno.
};
args.Verbs.Add(verb);
}
public bool Cremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref component, ref storage))
return false;
if (HasComp<ActiveCrematoriumComponent>(uid))
return false;
_audio.PlayPvs(component.CremateStartSound, uid);
_appearance.SetData(uid, CrematoriumVisuals.Burning, true);
_audio.PlayPvs(component.CrematingSound, uid);
AddComp<ActiveCrematoriumComponent>(uid);
return true;
}
public bool TryCremate(EntityUid uid, CrematoriumComponent? component = null, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref component, ref storage))
return false;
if (storage.Open || storage.Contents.ContainedEntities.Count < 1)
return false;
return Cremate(uid, component, storage);
}
private void FinishCooking(EntityUid uid, CrematoriumComponent component, EntityStorageComponent? storage = null)
{
if (!Resolve(uid, ref storage))
return;
_appearance.SetData(uid, CrematoriumVisuals.Burning, false);
RemComp<ActiveCrematoriumComponent>(uid);
if (storage.Contents.ContainedEntities.Count > 0)
{
for (var i = storage.Contents.ContainedEntities.Count - 1; i >= 0; i--)
{
var item = storage.Contents.ContainedEntities[i];
_containers.Remove(item, storage.Contents);
Del(item);
}
var ash = Spawn("Ash", Transform(uid).Coordinates);
_containers.Insert(ash, storage.Contents);
}
_entityStorage.OpenStorage(uid, storage);
_audio.PlayPvs(component.CremateFinishSound, uid);
}
private void OnSuicideByEnvironment(EntityUid uid, CrematoriumComponent component, SuicideByEnvironmentEvent args)
private void OnSuicideByEnvironment(Entity<CrematoriumComponent> ent, ref SuicideByEnvironmentEvent args)
{
if (args.Handled)
return;
var victim = args.Victim;
if (TryComp(victim, out ActorComponent? actor) && _minds.TryGetMind(victim, out var mindId, out var mind))
if (HasComp<ActorComponent>(victim) && Mind.TryGetMind(victim, out var mindId, out var mind))
{
_ghostSystem.OnGhostAttempt(mindId, false, mind: mind);
if (mind.OwnedEntity is { Valid: true } entity)
{
_popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message"), entity);
}
}
_popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
Popup.PopupEntity(Loc.GetString("crematorium-entity-storage-component-suicide-message-others",
("victim", Identity.Entity(victim, EntityManager))),
victim, Filter.PvsExcept(victim), true, PopupType.LargeCaution);
victim,
Filter.PvsExcept(victim),
true,
PopupType.LargeCaution);
if (_entityStorage.CanInsert(victim, uid))
if (EntityStorage.CanInsert(victim, ent.Owner))
{
_entityStorage.CloseStorage(uid);
_standing.Down(victim, false);
_entityStorage.Insert(victim, uid);
EntityStorage.CloseStorage(ent.Owner);
Standing.Down(victim, false);
EntityStorage.Insert(victim, ent.Owner);
}
else
{
EntityStorage.CloseStorage(ent.Owner);
Del(victim);
}
_entityStorage.CloseStorage(uid);
Cremate(uid, component);
Cremate(ent.AsNullable());
args.Handled = true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ActiveCrematoriumComponent, CrematoriumComponent>();
while (query.MoveNext(out var uid, out var act, out var crem))
{
act.Accumulator += frameTime;
if (act.Accumulator >= crem.CookTime)
FinishCooking(uid, crem);
}
}
}

View File

@@ -1,95 +1,46 @@
using Content.Server.Storage.Components;
using Content.Shared.Examine;
using Content.Shared.Mobs.Components;
using Content.Shared.Morgue;
using Content.Shared.Morgue.Components;
using Content.Shared.Storage.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.Morgue;
public sealed class MorgueSystem : EntitySystem
public sealed class MorgueSystem : SharedMorgueSystem
{
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MorgueComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<MorgueComponent, MapInitEvent>(OnMapInit);
}
/// <summary>
/// Handles the examination text for looking at a morgue.
/// </summary>
private void OnExamine(Entity<MorgueComponent> ent, ref ExaminedEvent args)
private void OnMapInit(Entity<MorgueComponent> ent, ref MapInitEvent args)
{
if (!args.IsInDetailsRange)
return;
_appearance.TryGetData<MorgueContents>(ent.Owner, MorgueVisuals.Contents, out var contents);
var text = contents switch
{
MorgueContents.HasSoul => "morgue-entity-storage-component-on-examine-details-body-has-soul",
MorgueContents.HasContents => "morgue-entity-storage-component-on-examine-details-has-contents",
MorgueContents.HasMob => "morgue-entity-storage-component-on-examine-details-body-has-no-soul",
_ => "morgue-entity-storage-component-on-examine-details-empty"
};
args.PushMarkup(Loc.GetString(text));
ent.Comp.NextBeep = _timing.CurTime + ent.Comp.NextBeep;
}
/// <summary>
/// Updates data periodically in case something died/got deleted in the morgue.
/// </summary>
private void CheckContents(EntityUid uid, MorgueComponent? morgue = null, EntityStorageComponent? storage = null, AppearanceComponent? app = null)
{
if (!Resolve(uid, ref morgue, ref storage, ref app))
return;
if (storage.Contents.ContainedEntities.Count == 0)
{
_appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.Empty);
return;
}
var hasMob = false;
foreach (var ent in storage.Contents.ContainedEntities)
{
if (!hasMob && HasComp<MobStateComponent>(ent))
hasMob = true;
if (HasComp<ActorComponent>(ent))
{
_appearance.SetData(uid, MorgueVisuals.Contents, MorgueContents.HasSoul, app);
return;
}
}
_appearance.SetData(uid, MorgueVisuals.Contents, hasMob ? MorgueContents.HasMob : MorgueContents.HasContents, app);
}
/// <summary>
/// Handles the periodic beeping that morgues do when a live body is inside.
/// Handles the periodic beeping that morgues do when a live body is inside.
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
var curTime = _timing.CurTime;
var query = EntityQueryEnumerator<MorgueComponent, EntityStorageComponent, AppearanceComponent>();
while (query.MoveNext(out var uid, out var comp, out var storage, out var appearance))
{
comp.AccumulatedFrameTime += frameTime;
CheckContents(uid, comp, storage);
if (comp.AccumulatedFrameTime < comp.BeepTime)
if (curTime < comp.NextBeep)
continue;
comp.AccumulatedFrameTime -= comp.BeepTime;
comp.NextBeep += comp.BeepTime;
CheckContents(uid, comp, storage);
if (comp.DoSoulBeep && _appearance.TryGetData<MorgueContents>(uid, MorgueVisuals.Contents, out var contents, appearance) && contents == MorgueContents.HasSoul)
{

View File

@@ -1,4 +1,3 @@
using Content.Server.Atmos.Components;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.NPC.Queries;
@@ -6,20 +5,18 @@ using Content.Server.NPC.Queries.Considerations;
using Content.Server.NPC.Queries.Curves;
using Content.Server.NPC.Queries.Queries;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Storage.Components;
using Content.Server.Temperature.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.Examine;
using Content.Shared.Fluids.Components;
using Content.Shared.Hands.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Systems;
using Content.Shared.NPC.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Storage.Components;
using Content.Shared.Stunnable;
using Content.Shared.Tools.Systems;
using Content.Shared.Turrets;
@@ -31,6 +28,7 @@ using Microsoft.Extensions.ObjectPool;
using Robust.Server.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Content.Shared.Atmos.Components;
using System.Linq;
namespace Content.Server.NPC.Systems;
@@ -42,14 +40,12 @@ public sealed class NPCUtilitySystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ContainerSystem _container = default!;
[Dependency] private readonly DrinkSystem _drink = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IngestionSystem _ingestion = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly OpenableSystem _openable = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutions = default!;

View File

@@ -21,12 +21,13 @@ namespace Content.Server.Nutrition.EntitySystems
[UsedImplicitly]
public sealed class CreamPieSystem : SharedCreamPieSystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutions = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly IngestionSystem _ingestion = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutions = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
public override void Initialize()
{
@@ -39,26 +40,23 @@ namespace Content.Server.Nutrition.EntitySystems
SubscribeLocalEvent<CreamPiedComponent, RejuvenateEvent>(OnRejuvenate);
}
protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie)
protected override void SplattedCreamPie(Entity<CreamPieComponent, EdibleComponent?> entity)
{
// The entity is deleted, so play the sound at its position rather than parenting
var coordinates = Transform(uid).Coordinates;
_audio.PlayPvs(_audio.ResolveSound(creamPie.Sound), coordinates, AudioParams.Default.WithVariation(0.125f));
var coordinates = Transform(entity).Coordinates;
_audio.PlayPvs(_audio.ResolveSound(entity.Comp1.Sound), coordinates, AudioParams.Default.WithVariation(0.125f));
if (TryComp(uid, out FoodComponent? foodComp))
if (Resolve(entity, ref entity.Comp2, false))
{
if (_solutions.TryGetSolution(uid, foodComp.Solution, out _, out var solution))
{
_puddle.TrySpillAt(uid, solution, out _, false);
}
foreach (var trash in foodComp.Trash)
{
Spawn(trash, Transform(uid).Coordinates);
}
}
ActivatePayload(uid);
if (_solutions.TryGetSolution(entity.Owner, entity.Comp2.Solution, out _, out var solution))
_puddle.TrySpillAt(entity.Owner, solution, out _, false);
QueueDel(uid);
_ingestion.SpawnTrash((entity, entity.Comp2));
}
ActivatePayload(entity);
QueueDel(entity);
}
private void OnConsume(Entity<CreamPieComponent> entity, ref ConsumeDoAfterEvent args)

View File

@@ -42,7 +42,7 @@ namespace Content.Server.Nutrition.EntitySystems
if (!args.CanReach
|| !_solutionContainerSystem.TryGetRefillableSolution(entity.Owner, out _, out var solution)
|| !HasComp<BloodstreamComponent>(args.Target)
|| _ingestion.HasMouthAvailable(args.Target.Value, args.User)
|| !_ingestion.HasMouthAvailable(args.Target.Value, args.User)
)
{
return;

View File

@@ -16,8 +16,6 @@ public sealed class PickObjectiveTargetSystem : EntitySystem
{
[Dependency] private readonly TargetObjectiveSystem _target = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
public override void Initialize()
{

View File

@@ -150,7 +150,9 @@ public sealed class PayloadSystem : EntitySystem
private void HandleChemicalPayloadTrigger(Entity<ChemicalPayloadComponent> entity, ref TriggerEvent args)
{
// TODO: Adjust to the new trigger system
if (args.Key != null && !entity.Comp.KeysIn.Contains(args.Key))
return;
if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA
|| entity.Comp.BeakerSlotB.Item is not EntityUid beakerB
|| !TryComp(beakerA, out FitsInDispenserComponent? compA)

View File

@@ -18,7 +18,13 @@ public sealed partial class PolymorphedEntityComponent : Component
/// The original entity that the player will revert back into
/// </summary>
[DataField(required: true)]
public EntityUid Parent;
public EntityUid? Parent;
/// <summary>
/// Whether this polymorph has been reverted.
/// </summary>
[DataField]
public bool Reverted;
/// <summary>
/// The amount of time that has passed since the entity was created

View File

@@ -55,6 +55,7 @@ public sealed partial class PolymorphSystem : EntitySystem
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
SubscribeLocalEvent<PolymorphedEntityComponent, EntityTerminatingEvent>(OnPolymorphedTerminating);
InitializeMap();
}
@@ -127,12 +128,11 @@ public sealed partial class PolymorphSystem : EntitySystem
private void OnBeforeFullySliced(Entity<PolymorphedEntityComponent> ent, ref BeforeFullySlicedEvent args)
{
var (_, comp) = ent;
if (comp.Configuration.RevertOnEat)
{
args.Cancel();
Revert((ent, ent));
}
if (ent.Comp.Reverted || !ent.Comp.Configuration.RevertOnEat)
return;
args.Cancel();
Revert((ent, ent));
}
/// <summary>
@@ -141,10 +141,23 @@ public sealed partial class PolymorphSystem : EntitySystem
/// </summary>
private void OnDestruction(Entity<PolymorphedEntityComponent> ent, ref DestructionEventArgs args)
{
if (ent.Comp.Configuration.RevertOnDeath)
{
Revert((ent, ent));
}
if (ent.Comp.Reverted || !ent.Comp.Configuration.RevertOnDeath)
return;
Revert((ent, ent));
}
private void OnPolymorphedTerminating(Entity<PolymorphedEntityComponent> ent, ref EntityTerminatingEvent args)
{
if (ent.Comp.Reverted)
return;
if (ent.Comp.Configuration.RevertOnDelete)
Revert(ent.AsNullable());
// Remove our original entity too
// Note that Revert will set Parent to null, so reverted entities will not be deleted
QueueDel(ent.Comp.Parent);
}
/// <summary>
@@ -248,7 +261,7 @@ public sealed partial class PolymorphSystem : EntitySystem
if (configuration.TransferHumanoidAppearance)
{
_humanoid.CloneAppearance(uid, child);
_humanoid.CloneAppearance(child, uid);
}
if (_mindSystem.TryGetMind(uid, out var mindId, out var mind))
@@ -284,19 +297,27 @@ public sealed partial class PolymorphSystem : EntitySystem
if (Deleted(uid))
return null;
var parent = component.Parent;
if (component.Parent is not { } parent)
return null;
if (Deleted(parent))
return null;
var uidXform = Transform(uid);
var parentXform = Transform(parent);
// Don't swap back onto a terminating grid
if (TerminatingOrDeleted(uidXform.ParentUid))
return null;
if (component.Configuration.ExitPolymorphSound != null)
_audio.PlayPvs(component.Configuration.ExitPolymorphSound, uidXform.Coordinates);
_transform.SetParent(parent, parentXform, uidXform.ParentUid);
_transform.SetCoordinates(parent, parentXform, uidXform.Coordinates, uidXform.LocalRotation);
component.Reverted = true;
if (component.Configuration.TransferDamage &&
TryComp<DamageableComponent>(parent, out var damageParent) &&
_mobThreshold.GetScaledDamage(uid, parent, out var damage) &&

View File

@@ -1,11 +1,11 @@
using Content.Server.Popups;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Lock;
using Content.Shared.Movement.Events;
using Content.Shared.Popups;
using Content.Shared.Resist;
using Content.Shared.Storage.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Content.Shared.ActionBlocker;

View File

@@ -3,7 +3,7 @@ using Content.Shared.Damage;
using Content.Shared.Revenant;
using Robust.Shared.Random;
using Content.Shared.Tag;
using Content.Server.Storage.Components;
using Content.Shared.Storage.Components;
using Content.Server.Light.Components;
using Content.Server.Ghost;
using Robust.Shared.Physics;

View File

@@ -19,11 +19,13 @@ public sealed class ParadoxCloneRoleSystem : EntitySystem
private void OnRefreshNameModifiers(Entity<ParadoxCloneRoleComponent> ent, ref MindRelayedEvent<RefreshNameModifiersEvent> args)
{
if (!TryComp<MindRoleComponent>(ent.Owner, out var roleComp))
var mindId = Transform(ent).ParentUid; // the mind role entity is in a container in the mind entity
if (!TryComp<MindComponent>(mindId, out var mindComp))
return;
// only show for ghosts
if (!HasComp<GhostComponent>(roleComp.Mind.Comp.OwnedEntity))
if (!HasComp<GhostComponent>(mindComp.OwnedEntity))
return;
if (ent.Comp.NameModifier != null)

View File

@@ -36,7 +36,7 @@ public sealed class RoleSystem : SharedRoleSystem
// Briefing is no longer raised on the mind entity itself
// because all the components that briefings subscribe to should be on Mind Role Entities
foreach(var role in mindComp.MindRoles)
foreach (var role in mindComp.MindRoleContainer.ContainedEntities)
{
RaiseLocalEvent(role, ref ev);
}

View File

@@ -7,6 +7,7 @@ using Content.Server.Administration.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Communications;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Events;
using Content.Server.Pinpointer;
using Content.Server.Popups;
@@ -57,6 +58,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!;
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
[Dependency] private readonly DockingSystem _dock = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly IdCardSystem _idSystem = default!;
[Dependency] private readonly NavMapSystem _navMap = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
@@ -157,7 +159,9 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateEmergencyConsole(frameTime);
// Don't handle any of this logic if in lobby
if (_ticker.RunLevel != GameRunLevel.PreRoundLobby)
UpdateEmergencyConsole(frameTime);
}
/// <summary>

View File

@@ -19,7 +19,6 @@ namespace Content.Server.Silicons.Borgs;
public sealed partial class BorgSystem
{
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;

View File

@@ -294,15 +294,16 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
protected override void OnUpdaterInsert(Entity<SiliconLawUpdaterComponent> ent, ref EntInsertedIntoContainerMessage args)
{
// TODO: Prediction dump this
if (!TryComp(args.Entity, out SiliconLawProviderComponent? provider))
if (!TryComp<SiliconLawProviderComponent>(args.Entity, out var provider))
return;
var lawset = GetLawset(provider.Laws).Laws;
var lawset = provider.Lawset ?? GetLawset(provider.Laws);
var query = EntityManager.CompRegistryQueryEnumerator(ent.Comp.Components);
while (query.MoveNext(out var update))
{
SetLaws(lawset, update, provider.LawUploadSound);
SetLaws(lawset.Laws, update, provider.LawUploadSound);
}
}
}

View File

@@ -28,7 +28,6 @@ namespace Content.Server.Singularity.EntitySystems
public sealed class EmitterSystem : SharedEmitterSystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;

View File

@@ -6,29 +6,25 @@ namespace Content.Server.Speech.Components
/// <summary>
/// Percentage chance that a stutter will occur if it matches.
/// </summary>
[DataField("matchRandomProb")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float MatchRandomProb = 0.8f;
/// <summary>
/// Percentage chance that a stutter occurs f-f-f-f-four times.
/// </summary>
[DataField("fourRandomProb")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float FourRandomProb = 0.1f;
/// <summary>
/// Percentage chance that a stutter occurs t-t-t-three times.
/// </summary>
[DataField("threeRandomProb")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float ThreeRandomProb = 0.2f;
/// <summary>
/// Percentage chance that a stutter cut off.
/// </summary>
[DataField("cutRandomProb")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float CutRandomProb = 0.05f;
}
}

View File

@@ -3,8 +3,7 @@ using Content.Server.Speech.Components;
using Content.Shared.Drunk;
using Content.Shared.Speech;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffect;
using Robust.Shared.Prototypes;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -12,26 +11,15 @@ namespace Content.Server.Speech.EntitySystems;
public sealed class SlurredSystem : SharedSlurredSystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly StatusEffectsSystem _status = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private static readonly ProtoId<StatusEffectPrototype> SlurKey = "SlurredSpeech";
public override void Initialize()
{
SubscribeLocalEvent<SlurredAccentComponent, AccentGetEvent>(OnAccent);
}
public override void DoSlur(EntityUid uid, TimeSpan time, StatusEffectsComponent? status = null)
{
if (!Resolve(uid, ref status, false))
return;
if (!_statusEffectsSystem.HasStatusEffect(uid, SlurKey, status))
_statusEffectsSystem.TryAddStatusEffect<SlurredAccentComponent>(uid, SlurKey, time, true, status);
else
_statusEffectsSystem.TryAddTime(uid, SlurKey, time, status);
SubscribeLocalEvent<SlurredAccentComponent, StatusEffectRelayedEvent<AccentGetEvent>>(OnAccentRelayed);
}
/// <summary>
@@ -39,15 +27,33 @@ public sealed class SlurredSystem : SharedSlurredSystem
/// </summary>
private float GetProbabilityScale(EntityUid uid)
{
if (!_statusEffectsSystem.TryGetTime(uid, SharedDrunkSystem.DrunkKey, out var time))
if (!_status.TryGetMaxTime<DrunkStatusEffectComponent>(uid, out var time))
return 0;
var curTime = _timing.CurTime;
var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
return Math.Clamp((timeLeft - 80) / 1100, 0f, 1f);
// This is a magic number. Why this value? No clue it was made 3 years before I refactored this.
var magic = SharedDrunkSystem.MagicNumber;
if (time.Item2 != null)
{
var curTime = _timing.CurTime;
magic = (float) (time.Item2 - curTime).Value.TotalSeconds - 80f;
}
return Math.Clamp(magic / SharedDrunkSystem.MagicNumber, 0f, 1f);
}
private void OnAccent(EntityUid uid, SlurredAccentComponent component, AccentGetEvent args)
private void OnAccent(Entity<SlurredAccentComponent> entity, ref AccentGetEvent args)
{
GetAccent(entity, ref args);
}
private void OnAccentRelayed(Entity<SlurredAccentComponent> entity, ref StatusEffectRelayedEvent<AccentGetEvent> args)
{
var ev = args.Args;
GetAccent(args.Args.Entity, ref ev);
}
private void GetAccent(EntityUid uid, ref AccentGetEvent args)
{
var scale = GetProbabilityScale(uid);
args.Message = Accentuate(args.Message, scale);

View File

@@ -3,14 +3,13 @@ using System.Text.RegularExpressions;
using Content.Server.Speech.Components;
using Content.Shared.Speech;
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffect;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Random;
namespace Content.Server.Speech.EntitySystems
{
public sealed class StutteringSystem : SharedStutteringSystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
// Regex of characters to stutter.
@@ -20,19 +19,36 @@ namespace Content.Server.Speech.EntitySystems
public override void Initialize()
{
SubscribeLocalEvent<StutteringAccentComponent, AccentGetEvent>(OnAccent);
SubscribeLocalEvent<StutteringAccentComponent, StatusEffectRelayedEvent<AccentGetEvent>>(OnAccent);
}
public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh, StatusEffectsComponent? status = null)
public override void DoStutter(EntityUid uid, TimeSpan time, bool refresh)
{
if (!Resolve(uid, ref status, false))
return;
_statusEffectsSystem.TryAddStatusEffect<StutteringAccentComponent>(uid, StutterKey, time, refresh, status);
if (refresh)
Status.TryUpdateStatusEffectDuration(uid, Stuttering, time);
else
Status.TryAddStatusEffectDuration(uid, Stuttering, time);
}
private void OnAccent(EntityUid uid, StutteringAccentComponent component, AccentGetEvent args)
public override void DoRemoveStutterTime(EntityUid uid, TimeSpan timeRemoved)
{
args.Message = Accentuate(args.Message, component);
Status.TryAddTime(uid, Stuttering, -timeRemoved);
}
public override void DoRemoveStutter(EntityUid uid)
{
Status.TryRemoveStatusEffect(uid, Stuttering);
}
private void OnAccent(Entity<StutteringAccentComponent> entity, ref AccentGetEvent args)
{
args.Message = Accentuate(args.Message, entity.Comp);
}
private void OnAccent(Entity<StutteringAccentComponent> entity, ref StatusEffectRelayedEvent<AccentGetEvent> args)
{
args.Args.Message = Accentuate(args.Args.Message, entity.Comp);
}
public string Accentuate(string message, StutteringAccentComponent component)

View File

@@ -16,13 +16,9 @@ namespace Content.Server.Stack
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public static readonly int[] DefaultSplitAmounts = { 1, 5, 10, 20, 30, 50 };
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StackComponent, GetVerbsEvent<AlternativeVerb>>(OnStackAlternativeInteract);
}
public override void SetCount(EntityUid uid, int amount, StackComponent? component = null)
@@ -165,42 +161,7 @@ namespace Content.Server.Stack
return amounts;
}
private void OnStackAlternativeInteract(EntityUid uid, StackComponent stack, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null || stack.Count == 1)
return;
AlternativeVerb halve = new()
{
Text = Loc.GetString("comp-stack-split-halve"),
Category = VerbCategory.Split,
Act = () => UserSplit(uid, args.User, stack.Count / 2, stack),
Priority = 1
};
args.Verbs.Add(halve);
var priority = 0;
foreach (var amount in DefaultSplitAmounts)
{
if (amount >= stack.Count)
continue;
AlternativeVerb verb = new()
{
Text = amount.ToString(),
Category = VerbCategory.Split,
Act = () => UserSplit(uid, args.User, amount, stack),
// we want to sort by size, not alphabetically by the verb text.
Priority = priority
};
priority--;
args.Verbs.Add(verb);
}
}
private void UserSplit(EntityUid uid, EntityUid userUid, int amount,
protected override void UserSplit(EntityUid uid, EntityUid userUid, int amount,
StackComponent? stack = null,
TransformComponent? userTransform = null)
{

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