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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared.Administration;
|
||||
|
||||
namespace Content.Client.Administration.Systems;
|
||||
|
||||
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
|
||||
{
|
||||
}
|
||||
@@ -57,12 +57,43 @@ public sealed partial class ObjectsTab : Control
|
||||
|
||||
private void TeleportTo(NetEntity 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()
|
||||
@@ -82,9 +113,7 @@ public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapGridComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
{
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -92,9 +121,7 @@ public sealed partial class ObjectsTab : Control
|
||||
{
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MapComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var metadata))
|
||||
{
|
||||
entities.Add((metadata.EntityName, _entityManager.GetNetEntity(uid)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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 power = SharedDrunkSystem.MagicNumber;
|
||||
|
||||
if (time != null)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private void OnDrunkShutdown(EntityUid uid, DrunkComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_player.LocalEntity == uid)
|
||||
private void OnPlayerDetached(Entity<DrunkStatusEffectComponent> entity, ref StatusEffectRelayedEvent<LocalPlayerDetachedEvent> args)
|
||||
{
|
||||
_overlay.CurrentBoozePower = 0;
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
5
Content.Client/Implants/SubdermalImplantSystem.cs
Normal file
5
Content.Client/Implants/SubdermalImplantSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Implants;
|
||||
|
||||
namespace Content.Client.Implants;
|
||||
|
||||
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Shared.Kitchen;
|
||||
|
||||
namespace Content.Client.Kitchen;
|
||||
|
||||
public sealed class KitchenSpikeSystem : SharedKitchenSpikeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
35
Content.Client/Lathe/UI/QueuedRecipeControl.xaml
Normal file
35
Content.Client/Lathe/UI/QueuedRecipeControl.xaml
Normal 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>
|
||||
36
Content.Client/Lathe/UI/QueuedRecipeControl.xaml.cs
Normal file
36
Content.Client/Lathe/UI/QueuedRecipeControl.xaml.cs
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
5
Content.Client/Morgue/CrematoriumSystem.cs
Normal file
5
Content.Client/Morgue/CrematoriumSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Morgue;
|
||||
|
||||
namespace Content.Client.Morgue;
|
||||
|
||||
public sealed class CrematoriumSystem : SharedCrematoriumSystem;
|
||||
5
Content.Client/Morgue/MorgueSystem.cs
Normal file
5
Content.Client/Morgue/MorgueSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Morgue;
|
||||
|
||||
namespace Content.Client.Morgue;
|
||||
|
||||
public sealed class MorgueSystem : SharedMorgueSystem;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
|
||||
components:
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: GravityAffected
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
|
||||
- type: Alerts
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: GravityAffected
|
||||
|
||||
- type: entity
|
||||
name: WeightlessGravityGeneratorDummy
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ public abstract partial class InteractionTest
|
||||
- type: Stripping
|
||||
- type: Puller
|
||||
- type: Physics
|
||||
- type: GravityAffected
|
||||
- type: Tag
|
||||
tags:
|
||||
- CanPilot
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"))
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Changeling.Systems;
|
||||
|
||||
namespace Content.Server.Changeling.Systems;
|
||||
|
||||
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem;
|
||||
@@ -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}");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
if (TargetOnly)
|
||||
popup.PopupCoordinates(Loc.GetString(Popup), coords, uid, PopupType);
|
||||
else
|
||||
popup.PopupCoordinates(Loc.GetString(Popup), coords, PopupType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
103
Content.Server/GameTicking/Commands/DynamicRuleCommand.cs
Normal file
103
Content.Server/GameTicking/Commands/DynamicRuleCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
195
Content.Server/GameTicking/Rules/DynamicRuleSystem.cs
Normal file
195
Content.Server/GameTicking/Rules/DynamicRuleSystem.cs
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,12 +170,16 @@ 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)
|
||||
@@ -180,10 +187,15 @@ namespace Content.Server.Lathe
|
||||
var adjustedAmount = recipe.ApplyMaterialDiscount
|
||||
? (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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +1,27 @@
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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);
|
||||
ent.Comp.NextBeep = _timing.CurTime + ent.Comp.NextBeep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,17 +31,16 @@ public sealed class MorgueSystem : EntitySystem
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -55,6 +55,7 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<PolymorphedEntityComponent, BeforeFullySlicedEvent>(OnBeforeFullySliced);
|
||||
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
|
||||
SubscribeLocalEvent<PolymorphedEntityComponent, EntityTerminatingEvent>(OnPolymorphedTerminating);
|
||||
|
||||
InitializeMap();
|
||||
}
|
||||
@@ -127,13 +128,12 @@ public sealed partial class PolymorphSystem : EntitySystem
|
||||
|
||||
private void OnBeforeFullySliced(Entity<PolymorphedEntityComponent> ent, ref BeforeFullySlicedEvent args)
|
||||
{
|
||||
var (_, comp) = ent;
|
||||
if (comp.Configuration.RevertOnEat)
|
||||
{
|
||||
if (ent.Comp.Reverted || !ent.Comp.Configuration.RevertOnEat)
|
||||
return;
|
||||
|
||||
args.Cancel();
|
||||
Revert((ent, ent));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// It is possible to be polymorphed into an entity that can't "die", but is instead
|
||||
@@ -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)
|
||||
{
|
||||
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) &&
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,6 +159,8 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
// Don't handle any of this logic if in lobby
|
||||
if (_ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
UpdateEmergencyConsole(frameTime);
|
||||
}
|
||||
|
||||
|
||||
@@ -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!;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
// 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;
|
||||
var timeLeft = (float) (time.Value.Item2 - curTime).TotalSeconds;
|
||||
return Math.Clamp((timeLeft - 80) / 1100, 0f, 1f);
|
||||
magic = (float) (time.Item2 - curTime).Value.TotalSeconds - 80f;
|
||||
}
|
||||
|
||||
private void OnAccent(EntityUid uid, SlurredAccentComponent component, AccentGetEvent args)
|
||||
return Math.Clamp(magic / SharedDrunkSystem.MagicNumber, 0f, 1f);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user