Merge pull request #252 from crystallpunk-14/ed-17-06-2024-upstream
Ed 17 06 2024 upstream
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
charset = utf-8
|
||||
@@ -278,7 +279,7 @@ dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case
|
||||
dotnet_naming_style.t_upper_camel_case_style.required_prefix = T
|
||||
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.constants_symbols.required_modifiers = const
|
||||
|
||||
@@ -317,20 +318,20 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
|
||||
|
||||
dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.property_symbols.applicable_kinds = property
|
||||
|
||||
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
|
||||
|
||||
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
|
||||
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
|
||||
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly
|
||||
|
||||
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
|
||||
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate
|
||||
|
||||
dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
|
||||
@@ -342,6 +343,7 @@ resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
resharper_csharp_trailing_comma_in_multiline_lists = true
|
||||
|
||||
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Content.Benchmarks
|
||||
|
||||
var componentFactory = new Mock<IComponentFactory>();
|
||||
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
|
||||
componentFactory.Setup(m => m.GetIndex(typeof(DummyComponent))).Returns(CompIdx.Index<DummyComponent>());
|
||||
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg);
|
||||
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
|
||||
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() });
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
if (info.Antag && info.ActiveThisRound)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
if (info.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
sb.Append(new Rune(0x23F2)); // ⏲
|
||||
|
||||
sb.AppendFormat("\"{0}\"", text);
|
||||
@@ -226,7 +226,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
if (pl.Antag)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
if (pl.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
if (pl.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
sb.Append(new Rune(0x23F2)); // ⏲
|
||||
|
||||
sb.AppendFormat("\"{0}\"", pl.CharacterName);
|
||||
@@ -243,9 +243,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
{
|
||||
UpdateButtons();
|
||||
|
||||
AHelpHelper.HideAllPanels();
|
||||
if (ch != null)
|
||||
{
|
||||
AHelpHelper.HideAllPanels();
|
||||
var panel = AHelpHelper.EnsurePanel(ch.Value);
|
||||
panel.Visible = true;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
{
|
||||
if (sel is null)
|
||||
{
|
||||
Title = Loc.GetString("bwoink-none-selected");
|
||||
Title = Loc.GetString("bwoink-title-none-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Server.Bed.Sleep;
|
||||
|
||||
namespace Content.Client.Bed;
|
||||
|
||||
public sealed class SleepingSystem : SharedSleepingSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Inventory;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
@@ -120,6 +121,7 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
i++;
|
||||
}
|
||||
|
||||
item.MappedLayer = key;
|
||||
args.Layers.Add((key, layer));
|
||||
}
|
||||
}
|
||||
@@ -160,13 +162,9 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
|
||||
// species specific
|
||||
if (speciesId != null && rsi.TryGetState($"{state}-{speciesId}", out _))
|
||||
{
|
||||
state = $"{state}-{speciesId}";
|
||||
}
|
||||
else if (!rsi.TryGetState(state, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var layer = new PrototypeLayerData();
|
||||
layer.RsiPath = rsi.Path.ToString();
|
||||
@@ -294,6 +292,8 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
|
||||
if (layerData.Color != null)
|
||||
sprite.LayerSetColor(key, layerData.Color.Value);
|
||||
if (layerData.Scale != null)
|
||||
sprite.LayerSetScale(key, layerData.Scale.Value);
|
||||
}
|
||||
else
|
||||
index = sprite.LayerMapReserveBlank(key);
|
||||
|
||||
48
Content.Client/Clothing/FlippableClothingVisualizerSystem.cs
Normal file
48
Content.Client/Clothing/FlippableClothingVisualizerSystem.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Foldable;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Clothing;
|
||||
|
||||
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedItemSystem _itemSys = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FlippableClothingVisualsComponent, GetEquipmentVisualsEvent>(OnGetVisuals, after: [typeof(ClothingSystem)]);
|
||||
SubscribeLocalEvent<FlippableClothingVisualsComponent, FoldedEvent>(OnFolded);
|
||||
}
|
||||
|
||||
private void OnFolded(Entity<FlippableClothingVisualsComponent> ent, ref FoldedEvent args)
|
||||
{
|
||||
_itemSys.VisualsChanged(ent);
|
||||
}
|
||||
|
||||
private void OnGetVisuals(Entity<FlippableClothingVisualsComponent> ent, ref GetEquipmentVisualsEvent args)
|
||||
{
|
||||
if (!TryComp(ent, out SpriteComponent? sprite) ||
|
||||
!TryComp(ent, out ClothingComponent? clothing))
|
||||
return;
|
||||
|
||||
if (clothing.MappedLayer == null ||
|
||||
!AppearanceSystem.TryGetData<bool>(ent, FoldableSystem.FoldedVisuals.State, out var folding) ||
|
||||
!sprite.LayerMapTryGet(folding ? ent.Comp.FoldingLayer : ent.Comp.UnfoldingLayer, out var idx))
|
||||
return;
|
||||
|
||||
// add each layer to the visuals
|
||||
var spriteLayer = sprite[idx];
|
||||
foreach (var layer in args.Layers)
|
||||
{
|
||||
if (layer.Item1 != clothing.MappedLayer)
|
||||
continue;
|
||||
|
||||
layer.Item2.Scale = spriteLayer.Scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Content.Client/Clothing/FlippableClothingVisualsComponent.cs
Normal file
16
Content.Client/Clothing/FlippableClothingVisualsComponent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Content.Client.Clothing;
|
||||
|
||||
/// <summary>
|
||||
/// Communicates folded layers data (currently only Scale to handle flipping)
|
||||
/// to the wearer clothing sprite layer
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(FlippableClothingVisualizerSystem))]
|
||||
public sealed partial class FlippableClothingVisualsComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public string FoldingLayer = "foldedLayer";
|
||||
|
||||
[DataField]
|
||||
public string UnfoldingLayer = "unfoldedLayer";
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Client.Clothing;
|
||||
|
||||
public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -25,15 +25,12 @@ public sealed class FirelockSystem : SharedFirelockSystem
|
||||
if (!_appearanceSystem.TryGetData<DoorState>(uid, DoorVisuals.State, out var state, args.Component))
|
||||
state = DoorState.Closed;
|
||||
|
||||
if (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
|
||||
{
|
||||
boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
|
||||
unlitVisible =
|
||||
state == DoorState.Closing
|
||||
|| state == DoorState.Opening
|
||||
|| state == DoorState.Denying
|
||||
|| (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
|
||||
}
|
||||
boltedVisible = _appearanceSystem.TryGetData<bool>(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
|
||||
unlitVisible =
|
||||
state == DoorState.Closing
|
||||
|| state == DoorState.Opening
|
||||
|| state == DoorState.Denying
|
||||
|| (_appearanceSystem.TryGetData<bool>(uid, DoorVisuals.ClosedLights, out var closedLights, args.Component) && closedLights);
|
||||
|
||||
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && !boltedVisible);
|
||||
args.Sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible);
|
||||
|
||||
@@ -294,7 +294,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
|
||||
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
|
||||
{
|
||||
var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), profile.Species, EntityManager, _prototypeManager);
|
||||
var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), _playerManager.LocalSession, profile.Species, EntityManager, _prototypeManager);
|
||||
GiveDummyLoadout(dummy, loadout);
|
||||
}
|
||||
}
|
||||
@@ -414,7 +414,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
|
||||
if (_prototypeManager.HasIndex<RoleLoadoutPrototype>(LoadoutSystem.GetJobPrototype(job.ID)))
|
||||
{
|
||||
var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), humanoid.Species, EntityManager, _prototypeManager);
|
||||
var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), _playerManager.LocalSession, humanoid.Species, EntityManager, _prototypeManager);
|
||||
GiveDummyLoadout(dummyEnt, loadout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,7 +947,7 @@ namespace Content.Client.Lobby.UI
|
||||
if (loadout == null)
|
||||
{
|
||||
loadout = new RoleLoadout(roleLoadoutProto.ID);
|
||||
loadout.SetDefault(_prototypeManager);
|
||||
loadout.SetDefault(Profile, _playerManager.LocalSession, _prototypeManager);
|
||||
}
|
||||
|
||||
OpenLoadout(job, loadout, roleLoadoutProto);
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed partial class ApcVisualsComponent : Component
|
||||
/// </summary>
|
||||
[DataField("screenColors")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color[] ScreenColors = new Color[(byte)ApcChargeState.NumStates]{Color.FromHex("#d1332e"), Color.FromHex("#2e8ad1"), Color.FromHex("#3db83b"), Color.FromHex("#ffac1c")};
|
||||
public Color[] ScreenColors = new Color[(byte)ApcChargeState.NumStates]{Color.FromHex("#d1332e"), Color.FromHex("#dcdc28"), Color.FromHex("#82ff4c"), Color.FromHex("#ffac1c")};
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state of the unlit overlay used for the APC screen when the APC has been emagged.
|
||||
|
||||
@@ -482,7 +482,7 @@ public sealed class PowerMonitoringButton : Button
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
Align = Label.AlignMode.Right,
|
||||
SetWidth = 72f,
|
||||
SetWidth = 80f,
|
||||
Margin = new Thickness(10, 0, 0, 0),
|
||||
ClipText = true,
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
||||
BorgInfo.SetMessage(text);
|
||||
|
||||
// how the turntables
|
||||
DisableButton.Disabled = !data.HasBrain;
|
||||
DisableButton.Disabled = !(data.HasBrain && data.CanDisable);
|
||||
DestroyButton.Disabled = _timing.CurTime < _console.Comp1.NextDestroy;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,9 +43,6 @@ public sealed class RandomSpriteSystem : SharedRandomSpriteSystem
|
||||
if (!Resolve(uid, ref clothing, false))
|
||||
return;
|
||||
|
||||
if (clothing.ClothingVisuals == null)
|
||||
return;
|
||||
|
||||
foreach (var slotPair in clothing.ClothingVisuals)
|
||||
{
|
||||
foreach (var keyColorPair in component.Selected)
|
||||
|
||||
@@ -16,9 +16,8 @@ using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -626,7 +625,7 @@ public sealed class ChatUIController : UIController
|
||||
var predicate = static (EntityUid uid, (EntityUid compOwner, EntityUid? attachedEntity) data)
|
||||
=> uid == data.compOwner || uid == data.attachedEntity;
|
||||
var playerPos = player != null
|
||||
? _transform?.GetMapCoordinates(player.Value) ?? MapCoordinates.Nullspace
|
||||
? _eye.CurrentEye.Position
|
||||
: MapCoordinates.Nullspace;
|
||||
|
||||
var occluded = player != null && _examine.IsOccluded(player.Value);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Info;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Info;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -14,21 +12,27 @@ namespace Content.Client.UserInterface.Systems.Info;
|
||||
|
||||
public sealed class InfoUIController : UIController, IOnStateExited<GameplayState>
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private RulesPopup? _rulesPopup;
|
||||
private RulesAndInfoWindow? _infoWindow;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ValidatePrototypeId<GuideEntryPrototype>]
|
||||
private const string DefaultRuleset = "DefaultRuleset";
|
||||
|
||||
public ProtoId<GuideEntryPrototype> RulesEntryId = DefaultRuleset;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
_sawmill = _logMan.GetSawmill("rules");
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>();
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>(OnShowRulesPopupMessage);
|
||||
_netManager.RegisterNetMessage<SendRulesInformationMessage>(OnRulesInformationMessage);
|
||||
|
||||
_consoleHost.RegisterCommand("fuckrules",
|
||||
"",
|
||||
@@ -39,9 +43,12 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
});
|
||||
}
|
||||
|
||||
private void OnShowRulesPopupMessage(ShowRulesPopupMessage message)
|
||||
private void OnRulesInformationMessage(SendRulesInformationMessage message)
|
||||
{
|
||||
ShowRules(message.PopupTime);
|
||||
RulesEntryId = message.CoreRules;
|
||||
|
||||
if (message.ShouldShowRules)
|
||||
ShowRules(message.PopupTime);
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
@@ -84,8 +91,13 @@ public sealed class InfoUIController : UIController, IOnStateExited<GameplayStat
|
||||
|
||||
public GuideEntryPrototype GetCoreRuleEntry()
|
||||
{
|
||||
var guide = _cfg.GetCVar(CCVars.RulesFile);
|
||||
var guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(guide);
|
||||
if (!_prototype.TryIndex(RulesEntryId, out var guideEntryPrototype))
|
||||
{
|
||||
guideEntryPrototype = _prototype.Index<GuideEntryPrototype>(DefaultRuleset);
|
||||
_sawmill.Error($"Couldn't find the following prototype: {RulesEntryId}. Falling back to {DefaultRuleset}, please check that the server has the rules set up correctly");
|
||||
return guideEntryPrototype;
|
||||
}
|
||||
|
||||
return guideEntryPrototype;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,8 @@ public sealed class TestRuleSystem : EntitySystem
|
||||
while (query.MoveNext(out _, out _, out var gameRule))
|
||||
{
|
||||
var minPlayers = gameRule.MinPlayers;
|
||||
if (!gameRule.CancelPresetOnTooFewPlayers)
|
||||
continue;
|
||||
if (args.Players.Length >= minPlayers)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Server.Kitchen.EntitySystems;
|
||||
|
||||
namespace Content.Server.Access.Systems;
|
||||
|
||||
@@ -18,6 +19,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly MicrowaveSystem _microwave = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,12 +29,13 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
|
||||
private void OnMicrowaved(EntityUid uid, IdCardComponent component, BeingMicrowavedEvent args)
|
||||
{
|
||||
if (!component.CanMicrowave)
|
||||
return;
|
||||
if (!component.CanMicrowave || !TryComp<MicrowaveComponent>(args.Microwave, out var micro) || micro.Broken)
|
||||
return;
|
||||
|
||||
if (TryComp<AccessComponent>(uid, out var access))
|
||||
{
|
||||
float randomPick = _random.NextFloat();
|
||||
|
||||
// if really unlucky, burn card
|
||||
if (randomPick <= 0.15f)
|
||||
{
|
||||
@@ -49,6 +52,14 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
//Explode if the microwave can't handle it
|
||||
if (!micro.CanMicrowaveIdsSafely)
|
||||
{
|
||||
_microwave.Explode((args.Microwave, micro));
|
||||
return;
|
||||
}
|
||||
|
||||
// If they're unlucky, brick their ID
|
||||
if (randomPick <= 0.25f)
|
||||
{
|
||||
@@ -73,6 +84,7 @@ public sealed class IdCardSystem : SharedIdCardSystem
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(args.Microwave)} added {random.ID} access to {ToPrettyString(uid):entity}");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
// Unfortunately can't be friends yet due to magboots.
|
||||
[RegisterComponent]
|
||||
public sealed partial class MovedByPressureComponent : Component
|
||||
{
|
||||
public const float MoveForcePushRatio = 1f;
|
||||
public const float MoveForceForcePushRatio = 1f;
|
||||
public const float ProbabilityOffset = 25f;
|
||||
public const float ProbabilityBasePercent = 10f;
|
||||
public const float ThrowForce = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Accumulates time when yeeted by high pressure deltas.
|
||||
/// </summary>
|
||||
[DataField("accumulator")]
|
||||
public float Accumulator = 0f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("pressureResistance")]
|
||||
public float PressureResistance { get; set; } = 1f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("moveResist")]
|
||||
public float MoveResist { get; set; } = 100f;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int LastHighPressureMovementAirCycle { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
@@ -135,6 +135,10 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("depressurizePressure")]
|
||||
public float DepressurizePressure = 0;
|
||||
|
||||
// When true, ignore under-pressure lockout. Used to re-fill rooms in air alarm "Fill" mode.
|
||||
[DataField]
|
||||
public bool PressureLockoutOverride = false;
|
||||
#endregion
|
||||
|
||||
public GasVentPumpData ToAirAlarmData()
|
||||
@@ -146,7 +150,8 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
PumpDirection = PumpDirection,
|
||||
PressureChecks = PressureChecks,
|
||||
ExternalPressureBound = ExternalPressureBound,
|
||||
InternalPressureBound = InternalPressureBound
|
||||
InternalPressureBound = InternalPressureBound,
|
||||
PressureLockoutOverride = PressureLockoutOverride
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,6 +163,7 @@ namespace Content.Server.Atmos.Piping.Unary.Components
|
||||
PressureChecks = data.PressureChecks;
|
||||
ExternalPressureBound = data.ExternalPressureBound;
|
||||
InternalPressureBound = data.InternalPressureBound;
|
||||
PressureLockoutOverride = data.PressureLockoutOverride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
// (ignoring temperature differences because I am lazy)
|
||||
var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R);
|
||||
|
||||
if (vent.UnderPressureLockout)
|
||||
// Only run if the device is under lockout and not being overriden
|
||||
if (vent.UnderPressureLockout & !vent.PressureLockoutOverride)
|
||||
{
|
||||
// Leak only a small amount of gas as a proportion of supply pipe pressure.
|
||||
var pipeDelta = pipe.Air.Pressure - environment.Pressure;
|
||||
@@ -280,7 +281,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
if (pumpComponent.UnderPressureLockout)
|
||||
if (pumpComponent.UnderPressureLockout & !pumpComponent.PressureLockoutOverride)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("gas-vent-pump-uvlo"));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Bed.Components;
|
||||
using Content.Server.Bed.Sleep;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Sound;
|
||||
using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
public sealed class SleepingSystem : SharedSleepingSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly EmitSoundSystem _emitSound = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] public const string SleepActionId = "ActionSleep";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
|
||||
SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
|
||||
SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
|
||||
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
|
||||
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
|
||||
SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// when sleeping component is added or removed, we do some stuff with other components.
|
||||
/// </summary>
|
||||
private void OnSleepStateChanged(EntityUid uid, MobStateComponent component, SleepStateChangedEvent args)
|
||||
{
|
||||
if (args.FellAsleep)
|
||||
{
|
||||
// Expiring status effects would remove the components needed for sleeping
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(uid, "Stun");
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(uid, "KnockedDown");
|
||||
|
||||
EnsureComp<StunnedComponent>(uid);
|
||||
EnsureComp<KnockedDownComponent>(uid);
|
||||
|
||||
if (TryComp<SleepEmitSoundComponent>(uid, out var sleepSound))
|
||||
{
|
||||
var emitSound = EnsureComp<SpamEmitSoundComponent>(uid);
|
||||
if (HasComp<SnoringComponent>(uid))
|
||||
{
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
}
|
||||
emitSound.MinInterval = sleepSound.Interval;
|
||||
emitSound.MaxInterval = sleepSound.MaxInterval;
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemComp<StunnedComponent>(uid);
|
||||
RemComp<KnockedDownComponent>(uid);
|
||||
RemComp<SpamEmitSoundComponent>(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wake up on taking an instance of damage at least the value of WakeThreshold.
|
||||
/// </summary>
|
||||
private void OnDamageChanged(EntityUid uid, SleepingComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.GetTotal() >= component.WakeThreshold)
|
||||
TryWaking(uid, component);
|
||||
}
|
||||
|
||||
private void OnSleepAction(EntityUid uid, MobStateComponent component, SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(uid);
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(EntityUid uid, ActionsContainerComponent component, SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(args.Performer);
|
||||
}
|
||||
|
||||
private void OnWakeAction(EntityUid uid, MobStateComponent component, WakeActionEvent args)
|
||||
{
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
if (TryWaking(uid))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In crit, we wake up if we are not being forced to sleep.
|
||||
/// And, you can't sleep when dead...
|
||||
/// </summary>
|
||||
private void OnMobStateChanged(EntityUid uid, SleepingComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
RemComp<SpamEmitSoundComponent>(uid);
|
||||
RemComp<SleepingComponent>(uid);
|
||||
return;
|
||||
}
|
||||
if (TryComp<SpamEmitSoundComponent>(uid, out var spam))
|
||||
_emitSound.SetEnabled((uid, spam), args.NewMobState == MobState.Alive);
|
||||
}
|
||||
|
||||
private void AddWakeVerb(EntityUid uid, SleepingComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
TryWaking(args.Target, user: args.User);
|
||||
},
|
||||
Text = Loc.GetString("action-name-wake"),
|
||||
Priority = 2
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When you click on a sleeping person with an empty hand, try to wake them.
|
||||
/// </summary>
|
||||
private void OnInteractHand(EntityUid uid, SleepingComponent component, InteractHandEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
if (!TryWakeCooldown(uid))
|
||||
return;
|
||||
|
||||
TryWaking(args.Target, user: args.User);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, SleepingComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(uid, EntityManager))));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlip(EntityUid uid, SleepingComponent component, SlipAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnConsciousAttempt(EntityUid uid, SleepingComponent component, ConsciousAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
|
||||
private void OnInit(EntityUid uid, ForcedSleepingComponent component, ComponentInit args)
|
||||
{
|
||||
TrySleeping(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try sleeping. Only mobs can sleep.
|
||||
/// </summary>
|
||||
public bool TrySleeping(EntityUid uid)
|
||||
{
|
||||
if (!HasComp<MobStateComponent>(uid))
|
||||
return false;
|
||||
|
||||
var tryingToSleepEvent = new TryingToSleepEvent(uid);
|
||||
RaiseLocalEvent(uid, ref tryingToSleepEvent);
|
||||
if (tryingToSleepEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
EnsureComp<SleepingComponent>(uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryWakeCooldown(EntityUid uid, SleepingComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
|
||||
if (curTime < component.CoolDownEnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
component.CoolDownEnd = curTime + component.Cooldown;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to wake up.
|
||||
/// </summary>
|
||||
public bool TryWaking(EntityUid uid, SleepingComponent? component = null, bool force = false, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
return false;
|
||||
|
||||
if (!force && HasComp<ForcedSleepingComponent>(uid))
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
|
||||
_popupSystem.PopupEntity(Loc.GetString("wake-other-failure", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true, Shared.Popups.PopupType.SmallCaution);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPvs("/Audio/Effects/thudswoosh.ogg", uid, AudioHelpers.WithVariation(0.05f, _robustRandom));
|
||||
_popupSystem.PopupEntity(Loc.GetString("wake-other-success", ("target", Identity.Entity(uid, EntityManager))), uid, Filter.Entities(user.Value), true);
|
||||
}
|
||||
RemComp<SleepingComponent>(uid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,6 +506,7 @@ namespace Content.Server.Cargo.Systems
|
||||
"cargo-console-paper-print-text",
|
||||
("orderNumber", order.OrderId),
|
||||
("itemName", MetaData(item).EntityName),
|
||||
("orderQuantity", order.OrderQuantity),
|
||||
("requester", order.Requester),
|
||||
("reason", order.Reason),
|
||||
("approver", order.Approver ?? string.Empty)),
|
||||
|
||||
@@ -14,14 +14,6 @@ namespace Content.Server.Chemistry.Components;
|
||||
[RegisterComponent, Access(typeof(TransformableContainerSystem))]
|
||||
public sealed partial class TransformableContainerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the initial metadata name for the container.
|
||||
/// It will revert to this when emptied.
|
||||
/// It defaults to the name of the parent entity unless overwritten.
|
||||
/// </summary>
|
||||
[DataField("initialName")]
|
||||
public string? InitialName;
|
||||
|
||||
/// <summary>
|
||||
/// This is the initial metadata description for the container.
|
||||
/// It will revert to this when emptied.
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Server.Chemistry.Components;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
@@ -11,6 +12,7 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadataSystem = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -18,15 +20,12 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<TransformableContainerComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<TransformableContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
|
||||
SubscribeLocalEvent<TransformableContainerComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
private void OnMapInit(Entity<TransformableContainerComponent> entity, ref MapInitEvent args)
|
||||
{
|
||||
var meta = MetaData(entity.Owner);
|
||||
if (string.IsNullOrEmpty(entity.Comp.InitialName))
|
||||
{
|
||||
entity.Comp.InitialName = meta.EntityName;
|
||||
}
|
||||
if (string.IsNullOrEmpty(entity.Comp.InitialDescription))
|
||||
{
|
||||
entity.Comp.InitialDescription = meta.EntityDescription;
|
||||
@@ -58,12 +57,20 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
&& _prototypeManager.TryIndex(reagentId.Value.Prototype, out ReagentPrototype? proto))
|
||||
{
|
||||
var metadata = MetaData(entity.Owner);
|
||||
var val = Loc.GetString("transformable-container-component-glass", ("name", proto.LocalizedName));
|
||||
_metadataSystem.SetEntityName(entity.Owner, val, metadata);
|
||||
_metadataSystem.SetEntityDescription(entity.Owner, proto.LocalizedDescription, metadata);
|
||||
entity.Comp.CurrentReagent = proto;
|
||||
entity.Comp.Transformed = true;
|
||||
}
|
||||
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<TransformableContainerComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
if (entity.Comp.CurrentReagent is { } currentReagent)
|
||||
{
|
||||
args.AddModifier("transformable-container-component-glass", priority: -1, ("reagent", currentReagent.LocalizedName));
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelTransformation(Entity<TransformableContainerComponent> entity)
|
||||
@@ -73,10 +80,8 @@ public sealed class TransformableContainerSystem : EntitySystem
|
||||
|
||||
var metadata = MetaData(entity);
|
||||
|
||||
if (!string.IsNullOrEmpty(entity.Comp.InitialName))
|
||||
{
|
||||
_metadataSystem.SetEntityName(entity.Owner, entity.Comp.InitialName, metadata);
|
||||
}
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
|
||||
if (!string.IsNullOrEmpty(entity.Comp.InitialDescription))
|
||||
{
|
||||
_metadataSystem.SetEntityDescription(entity.Owner, entity.Comp.InitialDescription, metadata);
|
||||
|
||||
@@ -46,7 +46,9 @@ namespace Content.Server.Chemistry.ReactionEffects
|
||||
public override bool ShouldLog => true;
|
||||
|
||||
protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
=> Loc.GetString("reagent-effect-guidebook-missing");
|
||||
=> Loc.GetString("reagent-effect-guidebook-area-reaction",
|
||||
("duration", _duration)
|
||||
);
|
||||
|
||||
public override LogImpact LogImpact => LogImpact.High;
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffectConditions
|
||||
{
|
||||
public sealed partial class JobCondition : ReagentEffectCondition
|
||||
{
|
||||
[DataField(required: true)] public List<ProtoId<JobPrototype>> Job;
|
||||
|
||||
public override bool Condition(ReagentEffectArgs args)
|
||||
{
|
||||
args.EntityManager.TryGetComponent<MindContainerComponent>(args.SolutionEntity, out var mindContainer);
|
||||
if (mindContainer != null && mindContainer.Mind != null)
|
||||
{
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype))
|
||||
{
|
||||
foreach (var jobId in Job)
|
||||
{
|
||||
if (prototype.ID == jobId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string GuidebookExplanation(IPrototypeManager prototype)
|
||||
{
|
||||
var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList();
|
||||
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ namespace Content.Server.Chemistry.ReagentEffects
|
||||
}
|
||||
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys) =>
|
||||
Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
|
||||
Loc.GetString("reagent-effect-guidebook-add-to-solution-reaction", ("chance", Probability));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Clothing;
|
||||
|
||||
namespace Content.Server.Clothing;
|
||||
|
||||
public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
state = state && component.On;
|
||||
|
||||
if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
|
||||
{
|
||||
movedByPressure.Enabled = !state;
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
_alerts.ShowAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, false, component);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, true, component);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ using Content.Server.Emoting.Systems;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Cluwne;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
|
||||
namespace Content.Server.Cluwne;
|
||||
|
||||
@@ -30,6 +30,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -39,6 +40,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
SubscribeLocalEvent<CluwneComponent, MobStateChangedEvent>(OnMobState);
|
||||
SubscribeLocalEvent<CluwneComponent, EmoteEvent>(OnEmote, before:
|
||||
new[] { typeof(VocalSystem), typeof(BodyEmotesSystem) });
|
||||
SubscribeLocalEvent<CluwneComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,19 +49,19 @@ public sealed class CluwneSystem : EntitySystem
|
||||
private void OnMobState(EntityUid uid, CluwneComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
{
|
||||
RemComp<CluwneComponent>(uid);
|
||||
RemComp<ClumsyComponent>(uid);
|
||||
RemComp<AutoEmoteComponent>(uid);
|
||||
var damageSpec = new DamageSpecifier(_prototypeManager.Index<DamageGroupPrototype>("Genetic"), 300);
|
||||
_damageableSystem.TryChangeDamage(uid, damageSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EmoteSoundsPrototype? EmoteSounds;
|
||||
|
||||
/// <summary>
|
||||
/// OnStartup gives the cluwne outfit, ensures clumsy, gives name prefix and makes sure emote sounds are laugh.
|
||||
/// OnStartup gives the cluwne outfit, ensures clumsy, and makes sure emote sounds are laugh.
|
||||
/// </summary>
|
||||
private void OnComponentStartup(EntityUid uid, CluwneComponent component, ComponentStartup args)
|
||||
{
|
||||
@@ -67,9 +69,6 @@ public sealed class CluwneSystem : EntitySystem
|
||||
return;
|
||||
_prototypeManager.TryIndex(component.EmoteSoundsId, out EmoteSounds);
|
||||
|
||||
var meta = MetaData(uid);
|
||||
var name = meta.EntityName;
|
||||
|
||||
EnsureComp<AutoEmoteComponent>(uid);
|
||||
_autoEmote.AddEmote(uid, "CluwneGiggle");
|
||||
EnsureComp<ClumsyComponent>(uid);
|
||||
@@ -77,7 +76,7 @@ public sealed class CluwneSystem : EntitySystem
|
||||
_popupSystem.PopupEntity(Loc.GetString("cluwne-transform", ("target", uid)), uid, PopupType.LargeCaution);
|
||||
_audio.PlayPvs(component.SpawnSound, uid);
|
||||
|
||||
_metaData.SetEntityName(uid, Loc.GetString("cluwne-name-prefix", ("target", name)), meta);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
|
||||
SetOutfitCommand.SetOutfit(uid, "CluwneGear", EntityManager);
|
||||
}
|
||||
@@ -104,4 +103,12 @@ public sealed class CluwneSystem : EntitySystem
|
||||
_chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies "Cluwnified" prefix
|
||||
/// </summary>
|
||||
private void OnRefreshNameModifiers(Entity<CluwneComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("cluwne-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager));
|
||||
var newEntity = Spawn(newEntityProto, _transformSystem.ToMapCoordinates(coords), rotation: angle);
|
||||
var newEntity = EntityManager.SpawnAttachedTo(newEntityProto, coords, rotation: angle);
|
||||
|
||||
if (!TryComp(newEntity, out ConstructionComponent? construction))
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
@@ -90,6 +91,16 @@ namespace Content.Server.Destructible
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetDestroyedAt(Entity<DestructibleComponent?> ent, [NotNullWhen(true)] out FixedPoint2? destroyedAt)
|
||||
{
|
||||
destroyedAt = null;
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
destroyedAt = DestroyedAt(ent, ent.Comp);
|
||||
return true;
|
||||
}
|
||||
|
||||
// FFS this shouldn't be this hard. Maybe this should just be a field of the destructible component. Its not
|
||||
// like there is currently any entity that is NOT just destroyed upon reaching a total-damage value.
|
||||
/// <summary>
|
||||
|
||||
@@ -6,9 +6,9 @@ using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Doors.Systems
|
||||
@@ -20,6 +20,7 @@ namespace Content.Server.Doors.Systems
|
||||
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapping = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLight = default!;
|
||||
|
||||
private const int UpdateInterval = 30;
|
||||
private int _accumulatedTicks;
|
||||
@@ -53,6 +54,7 @@ namespace Content.Server.Doors.Systems
|
||||
var airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
var appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var pointLightQuery = GetEntityQuery<PointLightComponent>();
|
||||
|
||||
var query = EntityQueryEnumerator<FirelockComponent, DoorComponent>();
|
||||
while (query.MoveNext(out var uid, out var firelock, out var door))
|
||||
@@ -74,6 +76,11 @@ namespace Content.Server.Doors.Systems
|
||||
firelock.Temperature = fire;
|
||||
firelock.Pressure = pressure;
|
||||
Dirty(uid, firelock);
|
||||
|
||||
if (pointLightQuery.TryComp(uid, out var pointLight))
|
||||
{
|
||||
_pointLight.SetEnabled(uid, fire | pressure, pointLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Content.Server.Explosion.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Disallows starting the timer by hand, must be stuck or triggered by a system.
|
||||
/// Disallows starting the timer by hand, must be stuck or triggered by a system using <c>StartTimer</c>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AutomatedTimerComponent : Component
|
||||
|
||||
@@ -26,13 +26,7 @@ public sealed partial class TriggerSystem
|
||||
if (!component.StartOnStick)
|
||||
return;
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound);
|
||||
StartTimer((uid, component), args.User);
|
||||
}
|
||||
|
||||
private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args)
|
||||
@@ -54,14 +48,7 @@ public sealed partial class TriggerSystem
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
{
|
||||
Text = Loc.GetString("verb-start-detonation"),
|
||||
Act = () => HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound
|
||||
),
|
||||
Act = () => StartTimer((uid, component), args.User),
|
||||
Priority = 2
|
||||
});
|
||||
}
|
||||
@@ -174,13 +161,7 @@ public sealed partial class TriggerSystem
|
||||
if (component.DoPopup)
|
||||
_popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
|
||||
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.User,
|
||||
component.Delay,
|
||||
component.BeepInterval,
|
||||
component.InitialBeepDelay,
|
||||
component.BeepSound);
|
||||
StartTimer((uid, component), args.User);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -265,6 +265,18 @@ namespace Content.Server.Explosion.EntitySystems
|
||||
comp.TimeRemaining += amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start the timer for triggering the device.
|
||||
/// </summary>
|
||||
public void StartTimer(Entity<OnUseTimerTriggerComponent?> ent, EntityUid? user)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
var comp = ent.Comp;
|
||||
HandleTimerTrigger(ent, user, comp.Delay, comp.BeepInterval, comp.InitialBeepDelay, comp.BeepSound);
|
||||
}
|
||||
|
||||
public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay, float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound)
|
||||
{
|
||||
if (delay <= 0)
|
||||
|
||||
@@ -29,6 +29,7 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.NameModifier.Components;
|
||||
|
||||
namespace Content.Server.Fax;
|
||||
|
||||
@@ -464,10 +465,11 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
|
||||
|
||||
// TODO: See comment in 'Send()' about not being able to copy whole entities
|
||||
var printout = new FaxPrintout(paper.Content,
|
||||
labelComponent?.OriginalName ?? metadata.EntityName,
|
||||
nameMod?.BaseName ?? metadata.EntityName,
|
||||
labelComponent?.CurrentLabel,
|
||||
metadata.EntityPrototype?.ID ?? DefaultPaperPrototypeId,
|
||||
paper.StampState,
|
||||
@@ -510,12 +512,14 @@ public sealed class FaxSystem : EntitySystem
|
||||
!TryComp<PaperComponent>(sendEntity, out var paper))
|
||||
return;
|
||||
|
||||
TryComp<NameModifierComponent>(sendEntity, out var nameMod);
|
||||
|
||||
TryComp<LabelComponent>(sendEntity, out var labelComponent);
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
{ DeviceNetworkConstants.Command, FaxConstants.FaxPrintCommand },
|
||||
{ FaxConstants.FaxPaperNameData, labelComponent?.OriginalName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperNameData, nameMod?.BaseName ?? metadata.EntityName },
|
||||
{ FaxConstants.FaxPaperLabelData, labelComponent?.CurrentLabel },
|
||||
{ FaxConstants.FaxPaperContentData, paper.Content },
|
||||
};
|
||||
|
||||
@@ -251,7 +251,9 @@ namespace Content.Server.GameTicking
|
||||
// (If the mob survives, that's a bug. Ghosting is kept regardless.)
|
||||
var canReturn = canReturnGlobal && _mind.IsCharacterDeadPhysically(mind);
|
||||
|
||||
if (canReturnGlobal && TryComp(playerEntity, out MobStateComponent? mobState))
|
||||
if (_configurationManager.GetCVar(CCVars.GhostKillCrit) &&
|
||||
canReturnGlobal &&
|
||||
TryComp(playerEntity, out MobStateComponent? mobState))
|
||||
{
|
||||
if (_mobState.IsCritical(playerEntity.Value, mobState))
|
||||
{
|
||||
|
||||
@@ -41,11 +41,18 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
||||
if (args.Players.Length >= minPlayers)
|
||||
continue;
|
||||
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||
("readyPlayersCount", args.Players.Length),
|
||||
("minimumPlayers", minPlayers),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
args.Cancel();
|
||||
if (gameRule.CancelPresetOnTooFewPlayers)
|
||||
{
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||
("readyPlayersCount", args.Players.Length),
|
||||
("minimumPlayers", minPlayers),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
args.Cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ruleComp.MinPlayers > players)
|
||||
if (ruleComp.MinPlayers > players && ruleComp.CancelPresetOnTooFewPlayers)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Hands;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -20,9 +21,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly OpenableSystem _openable = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,6 +33,7 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
SubscribeLocalEvent<GluedComponent, ComponentInit>(OnGluedInit);
|
||||
SubscribeLocalEvent<GlueComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
|
||||
SubscribeLocalEvent<GluedComponent, GotEquippedHandEvent>(OnHandPickUp);
|
||||
SubscribeLocalEvent<GluedComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
// When glue bottle is used on item it will apply the glued and unremoveable components.
|
||||
@@ -95,27 +97,22 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<GluedComponent, UnremoveableComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var glue, out var _, out var meta))
|
||||
var query = EntityQueryEnumerator<GluedComponent, UnremoveableComponent>();
|
||||
while (query.MoveNext(out var uid, out var glue, out var _))
|
||||
{
|
||||
if (_timing.CurTime < glue.Until)
|
||||
continue;
|
||||
|
||||
// Instead of string matching, just reconstruct the expected name and compare
|
||||
if (meta.EntityName == Loc.GetString("glued-name-prefix", ("target", glue.BeforeGluedEntityName)))
|
||||
_metaData.SetEntityName(uid, glue.BeforeGluedEntityName);
|
||||
|
||||
RemComp<UnremoveableComponent>(uid);
|
||||
RemComp<GluedComponent>(uid);
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGluedInit(Entity<GluedComponent> entity, ref ComponentInit args)
|
||||
{
|
||||
var meta = MetaData(entity);
|
||||
var name = meta.EntityName;
|
||||
entity.Comp.BeforeGluedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(entity.Owner, Loc.GetString("glued-name-prefix", ("target", name)));
|
||||
_nameMod.RefreshNameModifiers(entity.Owner);
|
||||
}
|
||||
|
||||
private void OnHandPickUp(Entity<GluedComponent> entity, ref GotEquippedHandEvent args)
|
||||
@@ -124,4 +121,9 @@ public sealed class GlueSystem : SharedGlueSystem
|
||||
comp.DeleteOnDrop = false;
|
||||
entity.Comp.Until = _timing.CurTime + entity.Comp.Duration;
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<GluedComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("glued-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Gravity
|
||||
{
|
||||
|
||||
@@ -18,22 +18,25 @@ public sealed class RulesManager
|
||||
public void Initialize()
|
||||
{
|
||||
_netManager.Connected += OnConnected;
|
||||
_netManager.RegisterNetMessage<ShowRulesPopupMessage>();
|
||||
_netManager.RegisterNetMessage<SendRulesInformationMessage>();
|
||||
_netManager.RegisterNetMessage<RulesAcceptedMessage>(OnRulesAccepted);
|
||||
}
|
||||
|
||||
private async void OnConnected(object? sender, NetChannelArgs e)
|
||||
{
|
||||
if (IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) && _cfg.GetCVar(CCVars.RulesExemptLocal))
|
||||
return;
|
||||
var isLocalhost = IPAddress.IsLoopback(e.Channel.RemoteEndPoint.Address) &&
|
||||
_cfg.GetCVar(CCVars.RulesExemptLocal);
|
||||
|
||||
var lastRead = await _dbManager.GetLastReadRules(e.Channel.UserId);
|
||||
if (lastRead > LastValidReadTime)
|
||||
return;
|
||||
var hasCooldown = lastRead > LastValidReadTime;
|
||||
|
||||
var message = new ShowRulesPopupMessage();
|
||||
message.PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime);
|
||||
_netManager.ServerSendMessage(message, e.Channel);
|
||||
var showRulesMessage = new SendRulesInformationMessage
|
||||
{
|
||||
PopupTime = _cfg.GetCVar(CCVars.RulesWaitTime),
|
||||
CoreRules = _cfg.GetCVar(CCVars.RulesFile),
|
||||
ShouldShowRules = !isLocalhost && !hasCooldown
|
||||
};
|
||||
_netManager.ServerSendMessage(showRulesMessage, e.Channel);
|
||||
}
|
||||
|
||||
private async void OnRulesAccepted(RulesAcceptedMessage message)
|
||||
|
||||
@@ -12,6 +12,10 @@ namespace Content.Server.Info;
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class ShowRulesCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public string Command => "showrules";
|
||||
public string Description => "Opens the rules popup for the specified player.";
|
||||
public string Help => "showrules <username> [seconds]";
|
||||
@@ -25,8 +29,7 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
case 1:
|
||||
{
|
||||
target = args[0];
|
||||
var configurationManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
seconds = configurationManager.GetCVar(CCVars.RulesWaitTime);
|
||||
seconds = _configuration.GetCVar(CCVars.RulesWaitTime);
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
@@ -48,15 +51,14 @@ public sealed class ShowRulesCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
|
||||
var message = new ShowRulesPopupMessage { PopupTime = seconds };
|
||||
|
||||
if (!IoCManager.Resolve<IPlayerManager>().TryGetSessionByUsername(target, out var player))
|
||||
if (!_player.TryGetSessionByUsername(target, out var player))
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var netManager = IoCManager.Resolve<INetManager>();
|
||||
netManager.ServerSendMessage(message, player.Channel);
|
||||
var coreRules = _configuration.GetCVar(CCVars.RulesFile);
|
||||
var message = new SendRulesInformationMessage { PopupTime = seconds, CoreRules = coreRules, ShouldShowRules = true};
|
||||
_net.ServerSendMessage(message, player.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ namespace Content.Server.Kitchen.Components
|
||||
/// Chance of lightning occurring when we microwave a metallic object
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LightningChance = .75f;
|
||||
|
||||
/// <summary>
|
||||
/// If this microwave can give ids accesses without exploding
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanMicrowaveIdsSafely = true;
|
||||
}
|
||||
|
||||
public sealed class BeingMicrowavedEvent : HandledEntityEventArgs
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Chemistry.Containers.EntitySystems;
|
||||
using Content.Server.Construction;
|
||||
@@ -15,6 +16,7 @@ using Content.Shared.Body.Part;
|
||||
using Content.Shared.Chemistry.Components.SolutionManager;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -36,6 +38,7 @@ using System.Linq;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Server.Construction.Components;
|
||||
|
||||
namespace Content.Server.Kitchen.EntitySystems
|
||||
{
|
||||
@@ -61,6 +64,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
[Dependency] private readonly SharedItemSystem _item = default!;
|
||||
[Dependency] private readonly SharedStackSystem _stack = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>]
|
||||
private const string MalfunctionSpark = "Spark";
|
||||
@@ -408,6 +412,23 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
return component.Storage.ContainedEntities.Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explodes the microwave internally, turning it into a broken state, destroying its board, and spitting out its machine parts
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
public void Explode(Entity<MicrowaveComponent> ent)
|
||||
{
|
||||
ent.Comp.Broken = true; // Make broken so we stop processing stuff
|
||||
_explosion.TriggerExplosive(ent);
|
||||
if (TryComp<MachineComponent>(ent, out var machine))
|
||||
{
|
||||
_container.CleanContainer(machine.BoardContainer);
|
||||
_container.EmptyContainer(machine.PartContainer);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(ent)} exploded from unsafe cooking!");
|
||||
}
|
||||
/// <summary>
|
||||
/// Handles the attempted cooking of unsafe objects
|
||||
/// </summary>
|
||||
@@ -425,7 +446,7 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
ent.Comp1.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(ent.Comp2.MalfunctionInterval);
|
||||
if (_random.Prob(ent.Comp2.ExplosionChance))
|
||||
{
|
||||
_explosion.TriggerExplosive(ent);
|
||||
Explode((ent, ent.Comp2));
|
||||
return; // microwave is fucked, stop the cooking.
|
||||
}
|
||||
|
||||
@@ -532,7 +553,8 @@ namespace Content.Server.Kitchen.EntitySystems
|
||||
activeComp.CookTimeRemaining = component.CurrentCookTimerTime * component.CookTimeMultiplier;
|
||||
activeComp.TotalTime = component.CurrentCookTimerTime; //this doesn't scale so that we can have the "actual" time
|
||||
activeComp.PortionedRecipe = portionedRecipe;
|
||||
component.CurrentCookTimeEnd = _gameTiming.CurTime + TimeSpan.FromSeconds(component.CurrentCookTimerTime);
|
||||
//Scale tiems with cook times
|
||||
component.CurrentCookTimeEnd = _gameTiming.CurTime + TimeSpan.FromSeconds(component.CurrentCookTimerTime * component.CookTimeMultiplier);
|
||||
if (malfunctioning)
|
||||
activeComp.MalfunctionTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.MalfunctionInterval);
|
||||
UpdateUserInterfaceState(uid, component);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Labels;
|
||||
using Content.Shared.Labels.Components;
|
||||
using Content.Shared.Labels.EntitySystems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -18,7 +19,7 @@ namespace Content.Server.Labels
|
||||
{
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public const string ContainerName = "paper_label";
|
||||
|
||||
@@ -41,6 +42,8 @@ namespace Content.Server.Labels
|
||||
component.CurrentLabel = Loc.GetString(component.CurrentLabel);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,30 +55,11 @@ namespace Content.Server.Labels
|
||||
/// <param name="metadata">metadata component for resolve</param>
|
||||
public override void Label(EntityUid uid, string? text, MetaDataComponent? metadata = null, LabelComponent? label = null)
|
||||
{
|
||||
if (!Resolve(uid, ref metadata))
|
||||
return;
|
||||
if (!Resolve(uid, ref label, false))
|
||||
label = EnsureComp<LabelComponent>(uid);
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (label.OriginalName is null)
|
||||
return;
|
||||
|
||||
// Remove label
|
||||
_metaData.SetEntityName(uid, label.OriginalName, metadata);
|
||||
label.CurrentLabel = null;
|
||||
label.OriginalName = null;
|
||||
|
||||
Dirty(uid, label);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update label
|
||||
label.OriginalName ??= metadata.EntityName;
|
||||
label.CurrentLabel = text;
|
||||
_metaData.SetEntityName(uid, $"{label.OriginalName} ({text})", metadata);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
|
||||
Dirty(uid, label);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Lube;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -9,11 +10,11 @@ namespace Content.Server.Lube;
|
||||
|
||||
public sealed class LubedSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -21,14 +22,12 @@ public sealed class LubedSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<LubedComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LubedComponent, ContainerGettingInsertedAttemptEvent>(OnHandPickUp);
|
||||
SubscribeLocalEvent<LubedComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, LubedComponent component, ComponentInit args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
var name = meta.EntityName;
|
||||
component.BeforeLubedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(uid, Loc.GetString("lubed-name-prefix", ("target", name)));
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
|
||||
private void OnHandPickUp(EntityUid uid, LubedComponent component, ContainerGettingInsertedAttemptEvent args)
|
||||
@@ -36,7 +35,7 @@ public sealed class LubedSystem : EntitySystem
|
||||
if (component.SlipsLeft <= 0)
|
||||
{
|
||||
RemComp<LubedComponent>(uid);
|
||||
_metaData.SetEntityName(uid, component.BeforeLubedEntityName);
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
return;
|
||||
}
|
||||
component.SlipsLeft--;
|
||||
@@ -47,4 +46,9 @@ public sealed class LubedSystem : EntitySystem
|
||||
_throwing.TryThrow(uid, _random.NextVector2(), strength: component.SlipStrength);
|
||||
_popup.PopupEntity(Loc.GetString("lube-slip", ("target", Identity.Entity(uid, EntityManager))), user, user, PopupType.MediumCaution);
|
||||
}
|
||||
|
||||
private void OnRefreshNameModifiers(Entity<LubedComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
args.AddModifier("lubed-name-prefix");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ public sealed class SuitSensorSystem : EntitySystem
|
||||
return null;
|
||||
|
||||
// check if sensor is enabled and worn by user
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || transform.GridUid == null)
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User) || transform.GridUid == null)
|
||||
return null;
|
||||
|
||||
// try to get mobs id from ID slot
|
||||
|
||||
25
Content.Server/Mining/MeteorComponent.cs
Normal file
25
Content.Server/Mining/MeteorComponent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Mining;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for meteors which hit objects, dealing damage to destroy/kill the object and dealing equal damage back to itself.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(MeteorSystem))]
|
||||
public sealed partial class MeteorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Damage specifier that is multiplied against the calculated damage amount to determine what damage is applied to the colliding entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of this should add up to 1 or else the damage will be scaled.
|
||||
/// </remarks>
|
||||
[DataField]
|
||||
public DamageSpecifier DamageTypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of entities that this meteor has collided with. used to ensure no double collisions occur.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<EntityUid> HitList = new();
|
||||
}
|
||||
65
Content.Server/Mining/MeteorSystem.cs
Normal file
65
Content.Server/Mining/MeteorSystem.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Destructible;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Mining;
|
||||
|
||||
public sealed class MeteorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly DestructibleSystem _destructible = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<MeteorComponent, StartCollideEvent>(OnCollide);
|
||||
}
|
||||
|
||||
private void OnCollide(EntityUid uid, MeteorComponent component, ref StartCollideEvent args)
|
||||
{
|
||||
if (TerminatingOrDeleted(args.OtherEntity) || TerminatingOrDeleted(uid))
|
||||
return;
|
||||
|
||||
if (component.HitList.Contains(args.OtherEntity))
|
||||
return;
|
||||
|
||||
FixedPoint2 threshold;
|
||||
if (_mobThreshold.TryGetDeadThreshold(args.OtherEntity, out var mobThreshold))
|
||||
{
|
||||
threshold = mobThreshold.Value;
|
||||
if (HasComp<ActorComponent>(args.OtherEntity))
|
||||
_adminLog.Add(LogType.Action, LogImpact.Extreme, $"{ToPrettyString(args.OtherEntity):player} was struck by meteor {ToPrettyString(uid):ent} and killed instantly.");
|
||||
}
|
||||
else if (_destructible.TryGetDestroyedAt(args.OtherEntity, out var destroyThreshold))
|
||||
{
|
||||
threshold = destroyThreshold.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
threshold = FixedPoint2.MaxValue;
|
||||
}
|
||||
var otherEntDamage = CompOrNull<DamageableComponent>(args.OtherEntity)?.TotalDamage ?? FixedPoint2.Zero;
|
||||
// account for the damage that the other entity has already taken: don't overkill
|
||||
threshold -= otherEntDamage;
|
||||
|
||||
// The max amount of damage our meteor can take before breaking.
|
||||
var maxMeteorDamage = _destructible.DestroyedAt(uid) - CompOrNull<DamageableComponent>(uid)?.TotalDamage ?? FixedPoint2.Zero;
|
||||
|
||||
// Cap damage so we don't overkill the meteor
|
||||
var trueDamage = FixedPoint2.Min(maxMeteorDamage, threshold);
|
||||
|
||||
var damage = component.DamageTypes * trueDamage;
|
||||
_damageable.TryChangeDamage(args.OtherEntity, damage, true, origin: uid);
|
||||
_damageable.TryChangeDamage(uid, damage);
|
||||
|
||||
if (!TerminatingOrDeleted(args.OtherEntity))
|
||||
component.HitList.Add(args.OtherEntity);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Content.Server.Movement.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to an entity that is ctrl-click moving their pulled object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This just exists so we don't have MoveEvent subs going off for every single mob constantly.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
public sealed partial class PullMoverComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Physics.Controllers;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Movement.Systems;
|
||||
|
||||
@@ -91,7 +92,7 @@ public sealed class PullController : VirtualController
|
||||
|
||||
UpdatesAfter.Add(typeof(MoverController));
|
||||
SubscribeLocalEvent<PullMovingComponent, PullStoppedMessage>(OnPullStop);
|
||||
SubscribeLocalEvent<PullMoverComponent, MoveEvent>(OnPullerMove);
|
||||
SubscribeLocalEvent<ActivePullerComponent, MoveEvent>(OnPullerMove);
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
@@ -155,19 +156,22 @@ public sealed class PullController : VirtualController
|
||||
coords = fromUserCoords.WithEntityId(coords.EntityId);
|
||||
}
|
||||
|
||||
EnsureComp<PullMoverComponent>(player);
|
||||
var moving = EnsureComp<PullMovingComponent>(pulled!.Value);
|
||||
moving.MovingTo = coords;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnPullerMove(EntityUid uid, PullMoverComponent component, ref MoveEvent args)
|
||||
private void OnPullerMove(EntityUid uid, ActivePullerComponent component, ref MoveEvent args)
|
||||
{
|
||||
if (!_pullerQuery.TryComp(uid, out var puller))
|
||||
return;
|
||||
|
||||
if (puller.Pulling is not { } pullable)
|
||||
{
|
||||
DebugTools.Assert($"Failed to clean up puller: {ToPrettyString(uid)}");
|
||||
RemCompDeferred(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePulledRotation(uid, pullable);
|
||||
|
||||
@@ -182,13 +186,7 @@ public sealed class PullController : VirtualController
|
||||
if (_physicsQuery.TryComp(uid, out var physics))
|
||||
PhysicsSystem.WakeBody(uid, body: physics);
|
||||
|
||||
StopMove(uid, pullable);
|
||||
}
|
||||
|
||||
private void StopMove(Entity<PullMoverComponent?> mover, Entity<PullMovingComponent?> moving)
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(mover.Owner);
|
||||
RemCompDeferred<PullMovingComponent>(moving.Owner);
|
||||
RemCompDeferred<PullMovingComponent>(pullable);
|
||||
}
|
||||
|
||||
private void UpdatePulledRotation(EntityUid puller, EntityUid pulled)
|
||||
@@ -302,17 +300,5 @@ public sealed class PullController : VirtualController
|
||||
PhysicsSystem.ApplyLinearImpulse(puller, -impulse);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup PullMover
|
||||
var moverQuery = EntityQueryEnumerator<PullMoverComponent, PullerComponent>();
|
||||
|
||||
while (moverQuery.MoveNext(out var uid, out _, out var puller))
|
||||
{
|
||||
if (!HasComp<PullMovingComponent>(puller.Pulling))
|
||||
{
|
||||
RemCompDeferred<PullMoverComponent>(uid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
@@ -29,12 +28,12 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
[Dependency] private readonly IAdminLogManager _adminLog = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _failedAttempts = new();
|
||||
private readonly HashSet<EntityUid> _birthQueue = new();
|
||||
@@ -43,8 +42,7 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<ReproductiveComponent, MindAddedMessage>(OnMindAdded);
|
||||
SubscribeLocalEvent<InfantComponent, ComponentStartup>(OnInfantStartup);
|
||||
SubscribeLocalEvent<InfantComponent, ComponentShutdown>(OnInfantShutdown);
|
||||
SubscribeLocalEvent<InfantComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
|
||||
}
|
||||
|
||||
// we express EZ-pass terminate the pregnancy if a player takes the role
|
||||
@@ -54,16 +52,11 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
component.GestationEndTime = null;
|
||||
}
|
||||
|
||||
private void OnInfantStartup(EntityUid uid, InfantComponent component, ComponentStartup args)
|
||||
private void OnRefreshNameModifiers(Entity<InfantComponent> entity, ref RefreshNameModifiersEvent args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
component.OriginalName = meta.EntityName;
|
||||
_metaData.SetEntityName(uid, Loc.GetString("infant-name-prefix", ("name", meta.EntityName)), meta);
|
||||
}
|
||||
|
||||
private void OnInfantShutdown(EntityUid uid, InfantComponent component, ComponentShutdown args)
|
||||
{
|
||||
_metaData.SetEntityName(uid, component.OriginalName);
|
||||
// This check may seem redundant, but it makes sure that the prefix is removed before the component is removed
|
||||
if (_timing.CurTime < entity.Comp.InfantEndTime)
|
||||
args.AddModifier("infant-name-prefix");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,6 +195,8 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
{
|
||||
var infant = AddComp<InfantComponent>(offspring);
|
||||
infant.InfantEndTime = _timing.CurTime + infant.InfantDuration;
|
||||
// Make sure the name prefix is applied
|
||||
_nameMod.RefreshNameModifiers(offspring);
|
||||
}
|
||||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} gave birth to {ToPrettyString(offspring)}.");
|
||||
}
|
||||
@@ -249,6 +244,8 @@ public sealed class AnimalHusbandrySystem : EntitySystem
|
||||
if (_timing.CurTime < infant.InfantEndTime)
|
||||
continue;
|
||||
RemCompDeferred(uid, infant);
|
||||
// Make sure the name prefix gets removed
|
||||
_nameMod.RefreshNameModifiers(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,19 @@ namespace Content.Server.PDA.Ringer
|
||||
SubscribeLocalEvent<RingerComponent, RingerPlayRingtoneMessage>(RingerPlayRingtone);
|
||||
SubscribeLocalEvent<RingerComponent, RingerRequestUpdateInterfaceMessage>(UpdateRingerUserInterfaceDriver);
|
||||
|
||||
SubscribeLocalEvent<RingerUplinkComponent, CurrencyInsertAttemptEvent>(OnCurrencyInsert);
|
||||
SubscribeLocalEvent<RingerComponent, CurrencyInsertAttemptEvent>(OnCurrencyInsert);
|
||||
}
|
||||
|
||||
//Event Functions
|
||||
|
||||
private void OnCurrencyInsert(EntityUid uid, RingerUplinkComponent uplink, CurrencyInsertAttemptEvent args)
|
||||
private void OnCurrencyInsert(EntityUid uid, RingerComponent ringer, CurrencyInsertAttemptEvent args)
|
||||
{
|
||||
if (!TryComp<RingerUplinkComponent>(uid, out var uplink))
|
||||
{
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the store can be locked, it must be unlocked first before inserting currency. Stops traitor checking.
|
||||
if (!uplink.Unlocked)
|
||||
args.Cancel();
|
||||
|
||||
@@ -128,7 +128,6 @@ public sealed class TegSystem : EntitySystem
|
||||
// Shift ramp position based on demand and generation from previous tick.
|
||||
var curRamp = component.RampPosition;
|
||||
var lastDraw = supplier.CurrentSupply;
|
||||
// Limit amount lost/gained based on power factor.
|
||||
curRamp = MathHelper.Clamp(lastDraw, curRamp / component.RampFactor, curRamp * component.RampFactor);
|
||||
curRamp = MathF.Max(curRamp, component.RampMinimum);
|
||||
component.RampPosition = curRamp;
|
||||
@@ -138,17 +137,28 @@ public sealed class TegSystem : EntitySystem
|
||||
if (airA.Pressure > 0 && airB.Pressure > 0)
|
||||
{
|
||||
var hotA = airA.Temperature > airB.Temperature;
|
||||
var cHot = hotA ? cA : cB;
|
||||
|
||||
// Calculate maximum amount of energy to generate this tick based on ramping above.
|
||||
// This clamps the thermal energy transfer as well.
|
||||
var targetEnergy = curRamp / _atmosphere.AtmosTickRate;
|
||||
var transferMax = targetEnergy / (component.ThermalEfficiency * component.PowerFactor);
|
||||
|
||||
// Calculate thermal and electrical energy transfer between the two sides.
|
||||
var δT = MathF.Abs(airA.Temperature - airB.Temperature);
|
||||
var transfer = Math.Min(δT * cA * cB / (cA + cB - cHot * component.ThermalEfficiency), transferMax);
|
||||
electricalEnergy = transfer * component.ThermalEfficiency * component.PowerFactor;
|
||||
// Assume temperature equalizes, i.e. Ta*cA + Tb*cB = Tf*(cA+cB)
|
||||
var Tf = (airA.Temperature * cA + airB.Temperature * cB) / (cA + cB);
|
||||
// The maximum energy we can extract is (Ta - Tf)*cA, which is equal to (Tf - Tb)*cB
|
||||
var Wmax = MathF.Abs(airA.Temperature - Tf) * cA;
|
||||
|
||||
var N = component.ThermalEfficiency;
|
||||
|
||||
// Calculate Carnot efficiency
|
||||
var Thot = hotA ? airA.Temperature : airB.Temperature;
|
||||
var Tcold = hotA ? airB.Temperature : airA.Temperature;
|
||||
var Nmax = 1 - Tcold / Thot;
|
||||
N = MathF.Min(N, Nmax); // clamp by Carnot efficiency
|
||||
|
||||
// Reduce efficiency at low temperature differences to encourage burn chambers (instead
|
||||
// of just feeding the TEG room temperature gas from an infinite gas miner).
|
||||
var dT = Thot - Tcold;
|
||||
N *= MathF.Tanh(dT/700); // https://www.wolframalpha.com/input?i=tanh(x/700)+from+0+to+1000
|
||||
|
||||
var transfer = Wmax * N;
|
||||
electricalEnergy = transfer * component.PowerFactor;
|
||||
var outTransfer = transfer * (1 - component.ThermalEfficiency);
|
||||
|
||||
// Adjust thermal energy in transferred gas mixtures.
|
||||
@@ -169,7 +179,7 @@ public sealed class TegSystem : EntitySystem
|
||||
component.LastGeneration = electricalEnergy;
|
||||
|
||||
// Turn energy (at atmos tick rate) into wattage.
|
||||
var power = electricalEnergy * _atmosphere.AtmosTickRate;
|
||||
var power = electricalEnergy / args.dt;
|
||||
// Add ramp factor. This magics slight power into existence, but allows us to ramp up.
|
||||
supplier.MaxSupply = power * component.RampFactor;
|
||||
|
||||
|
||||
@@ -130,17 +130,20 @@ public sealed partial class SalvageSystem
|
||||
}
|
||||
|
||||
// Uhh yeah don't delete mobs or whatever
|
||||
var mobQuery = AllEntityQuery<HumanoidAppearanceComponent, MobStateComponent, TransformComponent>();
|
||||
var mobQuery = AllEntityQuery<MobStateComponent, TransformComponent>();
|
||||
_detachEnts.Clear();
|
||||
|
||||
while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform))
|
||||
while (mobQuery.MoveNext(out var mobUid, out _, out var xform))
|
||||
{
|
||||
if (xform.GridUid == null || !data.Comp.ActiveEntities.Contains(xform.GridUid.Value) || xform.MapUid == null)
|
||||
continue;
|
||||
|
||||
if (_salvMobQuery.HasComp(mobUid))
|
||||
continue;
|
||||
|
||||
// Can't parent directly to map as it runs grid traversal.
|
||||
_detachEnts.Add(((mobUid, xform), xform.MapUid.Value, _transform.GetWorldPosition(xform)));
|
||||
_transform.DetachParentToNull(mobUid, xform);
|
||||
_transform.DetachEntity(mobUid, xform);
|
||||
}
|
||||
|
||||
// Go and cleanup the active ents.
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Server.Station.Events;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
@@ -63,6 +64,16 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
/// </summary>
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags if all players must arrive via the Arrivals system, or if they can spawn in other ways.
|
||||
/// </summary>
|
||||
public bool Forced { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags if all players spawning at the departure terminal have godmode until they leave the terminal.
|
||||
/// </summary>
|
||||
public bool ArrivalsGodmode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first arrival is a little early, to save everyone 10s
|
||||
/// </summary>
|
||||
@@ -94,7 +105,12 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
|
||||
// Don't invoke immediately as it will get set in the natural course of things.
|
||||
Enabled = _cfgManager.GetCVar(CCVars.ArrivalsShuttles);
|
||||
Subs.CVar(_cfgManager, CCVars.ArrivalsShuttles, SetArrivals);
|
||||
Forced = _cfgManager.GetCVar(CCVars.ForceArrivals);
|
||||
ArrivalsGodmode = _cfgManager.GetCVar(CCVars.GodmodeArrivals);
|
||||
|
||||
_cfgManager.OnValueChanged(CCVars.ArrivalsShuttles, SetArrivals);
|
||||
_cfgManager.OnValueChanged(CCVars.ForceArrivals, b => Forced = b);
|
||||
_cfgManager.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b);
|
||||
|
||||
// Command so admins can set these for funsies
|
||||
_console.RegisterCommand("arrivals", ArrivalsCommand, ArrivalsCompletion);
|
||||
@@ -250,6 +266,9 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
// The player has successfully left arrivals and is also not on the shuttle. Remove their warp coupon.
|
||||
RemCompDeferred<PendingClockInComponent>(pUid);
|
||||
RemCompDeferred<AutoOrientComponent>(pUid);
|
||||
|
||||
if (ArrivalsGodmode)
|
||||
RemCompDeferred<GodmodeComponent>(pUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +328,7 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Only works on latejoin even if enabled.
|
||||
if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound)
|
||||
if (!Enabled || !Forced && _ticker.RunLevel != GameRunLevel.InRound)
|
||||
return;
|
||||
|
||||
if (!HasComp<StationArrivalsComponent>(ev.Station))
|
||||
@@ -317,33 +336,37 @@ public sealed class ArrivalsSystem : EntitySystem
|
||||
|
||||
TryGetArrivals(out var arrivals);
|
||||
|
||||
if (TryComp(arrivals, out TransformComponent? arrivalsXform))
|
||||
if (!TryComp(arrivals, out TransformComponent? arrivalsXform))
|
||||
return;
|
||||
|
||||
var mapId = arrivalsXform.MapID;
|
||||
|
||||
var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
|
||||
var possiblePositions = new List<EntityCoordinates>();
|
||||
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
|
||||
{
|
||||
var mapId = arrivalsXform.MapID;
|
||||
if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
|
||||
var possiblePositions = new List<EntityCoordinates>();
|
||||
while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
|
||||
{
|
||||
if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
if (possiblePositions.Count > 0)
|
||||
{
|
||||
var spawnLoc = _random.Pick(possiblePositions);
|
||||
ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
spawnLoc,
|
||||
ev.Job,
|
||||
ev.HumanoidCharacterProfile,
|
||||
ev.Station);
|
||||
|
||||
EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
|
||||
EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
|
||||
}
|
||||
possiblePositions.Add(xform.Coordinates);
|
||||
}
|
||||
|
||||
if (possiblePositions.Count <= 0)
|
||||
return;
|
||||
|
||||
var spawnLoc = _random.Pick(possiblePositions);
|
||||
ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
|
||||
spawnLoc,
|
||||
ev.Job,
|
||||
ev.HumanoidCharacterProfile,
|
||||
ev.Station);
|
||||
|
||||
EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
|
||||
EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
|
||||
|
||||
// If you're forced to spawn, you're invincible until you leave wherever you were forced to spawn.
|
||||
if (ArrivalsGodmode)
|
||||
EnsureComp<GodmodeComponent>(ev.SpawnResult.Value);
|
||||
}
|
||||
|
||||
private bool TryTeleportToMapSpawn(EntityUid player, EntityUid stationId, TransformComponent? transform = null)
|
||||
|
||||
@@ -274,6 +274,23 @@ public sealed partial class BorgSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryComp<ItemBorgModuleComponent>(module, out var itemModuleComp))
|
||||
{
|
||||
foreach (var containedModuleUid in component.ModuleContainer.ContainedEntities)
|
||||
{
|
||||
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
|
||||
continue;
|
||||
|
||||
if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count &&
|
||||
containedItemModuleComp.Items.All(itemModuleComp.Items.Contains))
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Robotics;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
@@ -26,6 +27,9 @@ public sealed partial class BorgSystem
|
||||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||
{
|
||||
if (comp.NextDisable is {} nextDisable && now >= nextDisable)
|
||||
DoDisable((uid, comp, chassis, meta));
|
||||
|
||||
if (now < comp.NextBroadcast)
|
||||
continue;
|
||||
|
||||
@@ -33,13 +37,16 @@ public sealed partial class BorgSystem
|
||||
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
|
||||
charge = battery.CurrentCharge / battery.MaxCharge;
|
||||
|
||||
var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled;
|
||||
var canDisable = comp.NextDisable == null && !comp.FakeDisabling;
|
||||
var data = new CyborgControlData(
|
||||
comp.Sprite,
|
||||
comp.Name,
|
||||
meta.EntityName,
|
||||
charge,
|
||||
chassis.ModuleCount,
|
||||
chassis.BrainEntity != null);
|
||||
hasBrain,
|
||||
canDisable);
|
||||
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
@@ -52,6 +59,24 @@ public sealed partial class BorgSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void DoDisable(Entity<BorgTransponderComponent, BorgChassisComponent, MetaDataComponent> ent)
|
||||
{
|
||||
ent.Comp1.NextDisable = null;
|
||||
if (ent.Comp1.FakeDisabling)
|
||||
{
|
||||
ent.Comp1.FakeDisabled = true;
|
||||
ent.Comp1.FakeDisabling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent.Comp2.BrainEntity is not {} brain)
|
||||
return;
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_container.Remove(brain, ent.Comp2.BrainContainer);
|
||||
}
|
||||
|
||||
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
||||
{
|
||||
var payload = args.Data;
|
||||
@@ -61,28 +86,28 @@ public sealed partial class BorgSystem
|
||||
if (command == RoboticsConsoleConstants.NET_DISABLE_COMMAND)
|
||||
Disable(ent);
|
||||
else if (command == RoboticsConsoleConstants.NET_DESTROY_COMMAND)
|
||||
Destroy(ent.Owner);
|
||||
Destroy(ent);
|
||||
}
|
||||
|
||||
private void Disable(Entity<BorgTransponderComponent, BorgChassisComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp2) || ent.Comp2.BrainEntity is not {} brain)
|
||||
if (!Resolve(ent, ref ent.Comp2) || ent.Comp2.BrainEntity == null || ent.Comp1.NextDisable != null)
|
||||
return;
|
||||
|
||||
// this won't exactly be stealthy but if you are malf its better than actually disabling you
|
||||
// update ui immediately
|
||||
ent.Comp1.NextBroadcast = _timing.CurTime;
|
||||
|
||||
// pretend the borg is being disabled forever now
|
||||
if (CheckEmagged(ent, "disabled"))
|
||||
return;
|
||||
ent.Comp1.FakeDisabling = true;
|
||||
else
|
||||
Popup.PopupEntity(Loc.GetString(ent.Comp1.DisablingPopup), ent);
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_container.Remove(brain, ent.Comp2.BrainContainer);
|
||||
ent.Comp1.NextDisable = _timing.CurTime + ent.Comp1.DisableDelay;
|
||||
}
|
||||
|
||||
private void Destroy(Entity<ExplosiveComponent?> ent)
|
||||
private void Destroy(Entity<BorgTransponderComponent> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
// this is stealthy until someone realises you havent exploded
|
||||
if (CheckEmagged(ent, "destroyed"))
|
||||
{
|
||||
@@ -91,7 +116,12 @@ public sealed partial class BorgSystem
|
||||
return;
|
||||
}
|
||||
|
||||
_explosion.TriggerExplosive(ent, ent.Comp, delete: false);
|
||||
var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
|
||||
Popup.PopupEntity(message, ent);
|
||||
_trigger.StartTimer(ent.Owner, user: null);
|
||||
|
||||
// prevent a shitter borg running into people
|
||||
RemComp<InputMoverComponent>(ent);
|
||||
}
|
||||
|
||||
private bool CheckEmagged(EntityUid uid, string name)
|
||||
|
||||
@@ -43,8 +43,8 @@ public sealed partial class BorgSystem : SharedBorgSystem
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosion = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TriggerSystem _trigger = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Singularity.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Singularity.EntitySystems;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -26,6 +26,7 @@ using Content.Shared.StatusIcon;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -39,18 +40,18 @@ namespace Content.Server.Station.Systems;
|
||||
[PublicAPI]
|
||||
public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
|
||||
[Dependency] private readonly ActorSystem _actors = default!;
|
||||
[Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!;
|
||||
[Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!;
|
||||
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _cardSystem = default!;
|
||||
[Dependency] private readonly IdentitySystem _identity = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly PdaSystem _pdaSystem = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _accessSystem = default!;
|
||||
|
||||
private bool _randomizeCharacters;
|
||||
|
||||
@@ -65,7 +66,15 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
_spawnerCallbacks = new Dictionary<SpawnPriorityPreference, Action<PlayerSpawningEvent>>()
|
||||
{
|
||||
{ SpawnPriorityPreference.Arrivals, _arrivalsSystem.HandlePlayerSpawning },
|
||||
{ SpawnPriorityPreference.Cryosleep, _containerSpawnPointSystem.HandlePlayerSpawning }
|
||||
{
|
||||
SpawnPriorityPreference.Cryosleep, ev =>
|
||||
{
|
||||
if (_arrivalsSystem.Forced)
|
||||
_arrivalsSystem.HandlePlayerSpawning(ev);
|
||||
else
|
||||
_containerSpawnPointSystem.HandlePlayerSpawning(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
|
||||
if (loadout == null)
|
||||
{
|
||||
loadout = new RoleLoadout(jobLoadout);
|
||||
loadout.SetDefault(_prototypeManager);
|
||||
loadout.SetDefault(profile, _actors.GetSession(entity), _prototypeManager);
|
||||
}
|
||||
|
||||
EquipRoleLoadout(entity.Value, loadout, roleProto);
|
||||
@@ -308,4 +317,4 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
|
||||
HumanoidCharacterProfile = humanoidCharacterProfile;
|
||||
Station = station;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Random;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for running meteor swarm events at regular intervals.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(MeteorSchedulerSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class MeteorSchedulerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The weights for which swarms will be selected.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<WeightedRandomEntityPrototype> Config = "DefaultConfig";
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the next swarm occurs.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextSwarmTime = TimeSpan.Zero;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.Destructible.Thresholds;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MeteorSwarmSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class MeteorSwarmComponent : Component
|
||||
{
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextWaveTime;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int WaveCounter;
|
||||
|
||||
[DataField]
|
||||
public float MeteorVelocity = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, meteors will be thrown from all angles instead of from a singular source
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NonDirectional;
|
||||
|
||||
/// <summary>
|
||||
/// The announcement played when a meteor swarm begins.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? Announcement = "station-event-meteor-swarm-start-announcement";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? AnnouncementSound = new SoundPathSpecifier("/Audio/Announcements/meteors.ogg")
|
||||
{
|
||||
Params = new()
|
||||
{
|
||||
Volume = -4
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Each meteor entity prototype and their corresponding weight in being picked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<EntProtoId, float> Meteors = new();
|
||||
|
||||
[DataField]
|
||||
public MinMax Waves = new(3, 3);
|
||||
|
||||
[DataField]
|
||||
public MinMax MeteorsPerWave = new(3, 4);
|
||||
|
||||
[DataField]
|
||||
public MinMax WaveCooldown = new (10, 60);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using Content.Server.StationEvents.Events;
|
||||
|
||||
namespace Content.Server.StationEvents.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(MeteorSwarmRule))]
|
||||
public sealed partial class MeteorSwarmRuleComponent : Component
|
||||
{
|
||||
[DataField("cooldown")]
|
||||
public float Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// We'll send a specific amount of waves of meteors towards the station per ending rather than using a timer.
|
||||
/// </summary>
|
||||
[DataField("waveCounter")]
|
||||
public int WaveCounter;
|
||||
|
||||
[DataField("minimumWaves")]
|
||||
public int MinimumWaves = 3;
|
||||
|
||||
[DataField("maximumWaves")]
|
||||
public int MaximumWaves = 8;
|
||||
|
||||
[DataField("minimumCooldown")]
|
||||
public float MinimumCooldown = 10f;
|
||||
|
||||
[DataField("maximumCooldown")]
|
||||
public float MaximumCooldown = 60f;
|
||||
|
||||
[DataField("meteorsPerWave")]
|
||||
public int MeteorsPerWave = 5;
|
||||
|
||||
[DataField("meteorVelocity")]
|
||||
public float MeteorVelocity = 10f;
|
||||
|
||||
[DataField("maxAngularVelocity")]
|
||||
public float MaxAngularVelocity = 0.25f;
|
||||
|
||||
[DataField("minAngularVelocity")]
|
||||
public float MinAngularVelocity = -0.25f;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Spawners;
|
||||
|
||||
namespace Content.Server.StationEvents.Events
|
||||
{
|
||||
public sealed class MeteorSwarmRule : StationEventSystem<MeteorSwarmRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
protected override void Started(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.WaveCounter = RobustRandom.Next(component.MinimumWaves, component.MaximumWaves);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSwarmRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
if (component.WaveCounter <= 0)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
component.Cooldown -= frameTime;
|
||||
|
||||
if (component.Cooldown > 0f)
|
||||
return;
|
||||
|
||||
component.WaveCounter--;
|
||||
|
||||
component.Cooldown += (component.MaximumCooldown - component.MinimumCooldown) * RobustRandom.NextFloat() + component.MinimumCooldown;
|
||||
|
||||
Box2? playableArea = null;
|
||||
var mapId = GameTicker.DefaultMap;
|
||||
|
||||
var query = AllEntityQuery<MapGridComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var gridId, out _, out var xform))
|
||||
{
|
||||
if (xform.MapID != mapId)
|
||||
continue;
|
||||
|
||||
var aabb = _physics.GetWorldAABB(gridId);
|
||||
playableArea = playableArea?.Union(aabb) ?? aabb;
|
||||
}
|
||||
|
||||
if (playableArea == null)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
return;
|
||||
}
|
||||
|
||||
var minimumDistance = (playableArea.Value.TopRight - playableArea.Value.Center).Length() + 50f;
|
||||
var maximumDistance = minimumDistance + 100f;
|
||||
|
||||
var center = playableArea.Value.Center;
|
||||
|
||||
for (var i = 0; i < component.MeteorsPerWave; i++)
|
||||
{
|
||||
var angle = new Angle(RobustRandom.NextFloat() * MathF.Tau);
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
var spawnPosition = new MapCoordinates(center + offset, mapId);
|
||||
var meteor = Spawn("MeteorLarge", spawnPosition);
|
||||
var physics = EntityManager.GetComponent<PhysicsComponent>(meteor);
|
||||
_physics.SetBodyStatus(meteor, physics, BodyStatus.InAir);
|
||||
_physics.SetLinearDamping(meteor, physics, 0f);
|
||||
_physics.SetAngularDamping(meteor, physics, 0f);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics);
|
||||
_physics.ApplyAngularImpulse(
|
||||
meteor,
|
||||
physics.Mass * ((component.MaxAngularVelocity - component.MinAngularVelocity) * RobustRandom.NextFloat() + component.MinAngularVelocity),
|
||||
body: physics);
|
||||
|
||||
EnsureComp<TimedDespawnComponent>(meteor).Lifetime = 120f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Content.Server/StationEvents/Events/MeteorSwarmSystem.cs
Normal file
89
Content.Server/StationEvents/Events/MeteorSwarmSystem.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
public sealed class MeteorSwarmSystem : GameRuleSystem<MeteorSwarmComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
protected override void Added(EntityUid uid, MeteorSwarmComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
|
||||
{
|
||||
base.Added(uid, component, gameRule, args);
|
||||
|
||||
component.WaveCounter = component.Waves.Next(RobustRandom);
|
||||
|
||||
if (component.Announcement is { } locId)
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString(locId), playSound: false, colorOverride: Color.Gold);
|
||||
_audio.PlayGlobal(component.AnnouncementSound, Filter.Broadcast(), true);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSwarmComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
if (Timing.CurTime < component.NextWaveTime)
|
||||
return;
|
||||
|
||||
component.NextWaveTime += TimeSpan.FromSeconds(component.WaveCooldown.Next(RobustRandom));
|
||||
|
||||
|
||||
if (_station.GetStations().Count == 0)
|
||||
return;
|
||||
|
||||
var station = RobustRandom.Pick(_station.GetStations());
|
||||
if (_station.GetLargestGrid(Comp<StationDataComponent>(station)) is not { } grid)
|
||||
return;
|
||||
|
||||
var mapId = Transform(grid).MapID;
|
||||
var playableArea = _physics.GetWorldAABB(grid);
|
||||
|
||||
var minimumDistance = (playableArea.TopRight - playableArea.Center).Length() + 50f;
|
||||
var maximumDistance = minimumDistance + 100f;
|
||||
|
||||
var center = playableArea.Center;
|
||||
|
||||
var meteorsToSpawn = component.MeteorsPerWave.Next(RobustRandom);
|
||||
for (var i = 0; i < meteorsToSpawn; i++)
|
||||
{
|
||||
var spawnProto = RobustRandom.Pick(component.Meteors);
|
||||
|
||||
var angle = component.NonDirectional
|
||||
? RobustRandom.NextAngle()
|
||||
: new Random(uid.Id).NextAngle();
|
||||
|
||||
var offset = angle.RotateVec(new Vector2((maximumDistance - minimumDistance) * RobustRandom.NextFloat() + minimumDistance, 0));
|
||||
|
||||
// the line at which spawns occur is perpendicular to the offset.
|
||||
// This means the meteors are less likely to bunch up and hit the same thing.
|
||||
var subOffsetAngle = RobustRandom.Prob(0.5f)
|
||||
? angle + Math.PI / 2
|
||||
: angle - Math.PI / 2;
|
||||
var subOffset = subOffsetAngle.RotateVec(new Vector2( (playableArea.TopRight - playableArea.Center).Length() / 3 * RobustRandom.NextFloat(), 0));
|
||||
|
||||
var spawnPosition = new MapCoordinates(center + offset + subOffset, mapId);
|
||||
var meteor = Spawn(spawnProto, spawnPosition);
|
||||
var physics = Comp<PhysicsComponent>(meteor);
|
||||
_physics.ApplyLinearImpulse(meteor, -offset.Normalized() * component.MeteorVelocity * physics.Mass, body: physics);
|
||||
}
|
||||
|
||||
component.WaveCounter--;
|
||||
if (component.WaveCounter <= 0)
|
||||
{
|
||||
ForceEndSelf(uid, gameRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Content.Server/StationEvents/MeteorSchedulerSystem.cs
Normal file
54
Content.Server/StationEvents/MeteorSchedulerSystem.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.StationEvents;
|
||||
|
||||
/// <summary>
|
||||
/// This handles scheduling and launching meteors at a station at regular intervals.
|
||||
/// TODO: there is 100% a world in which this is genericized and can be used for lots of basic event scheduling
|
||||
/// </summary>
|
||||
public sealed class MeteorSchedulerSystem : GameRuleSystem<MeteorSchedulerComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private TimeSpan _meteorMinDelay;
|
||||
private TimeSpan _meteorMaxDelay;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_cfg.OnValueChanged(CCVars.MeteorSwarmMinTime, f => { _meteorMinDelay = TimeSpan.FromMinutes(f); }, true);
|
||||
_cfg.OnValueChanged(CCVars.MeteorSwarmMaxTime, f => { _meteorMaxDelay = TimeSpan.FromMinutes(f); }, true);
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.NextSwarmTime = Timing.CurTime + RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, MeteorSchedulerComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (Timing.CurTime < component.NextSwarmTime)
|
||||
return;
|
||||
RunSwarm((uid, component));
|
||||
|
||||
component.NextSwarmTime += RobustRandom.Next(_meteorMinDelay, _meteorMaxDelay);
|
||||
}
|
||||
|
||||
private void RunSwarm(Entity<MeteorSchedulerComponent> ent)
|
||||
{
|
||||
var swarmWeights = _prototypeManager.Index(ent.Comp.Config);
|
||||
GameTicker.StartGameRule(swarmWeights.Pick(RobustRandom));
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Store.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Linq;
|
||||
using Content.Shared.Store.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Store.Systems;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Content.Server.VendingMachines
|
||||
SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
|
||||
SubscribeLocalEvent<VendingMachineComponent, GotEmaggedEvent>(OnEmagged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamage);
|
||||
SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
|
||||
SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
|
||||
@@ -149,8 +149,15 @@ namespace Content.Server.VendingMachines
|
||||
args.Handled = component.EmaggedInventory.Count > 0;
|
||||
}
|
||||
|
||||
private void OnDamage(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased && component.Broken)
|
||||
{
|
||||
component.Broken = false;
|
||||
TryUpdateVisualState(uid, component);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Broken || component.DispenseOnHitCoolingDown ||
|
||||
component.DispenseOnHitChance == null || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,7 @@ using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Weapons.Reflect;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -202,6 +203,20 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
break;
|
||||
|
||||
var result = rayCastResults[0];
|
||||
|
||||
// Checks if the laser should pass over unless targeted by its user
|
||||
foreach (var collide in rayCastResults)
|
||||
{
|
||||
if (collide.HitEntity != gun.Target &&
|
||||
CompOrNull<RequireProjectileTargetComponent>(collide.HitEntity)?.Active == true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = collide;
|
||||
break;
|
||||
}
|
||||
|
||||
var hit = result.HitEntity;
|
||||
lastHit = hit;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Xenoarchaeology.Equipment.Components;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server.Xenoarchaeology.Equipment.Systems;
|
||||
|
||||
@@ -14,23 +15,44 @@ public sealed class NodeScannerSystem : EntitySystem
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<NodeScannerComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<NodeScannerComponent, BeforeRangedInteractEvent>(OnBeforeRangedInteract);
|
||||
SubscribeLocalEvent<NodeScannerComponent, GetVerbsEvent<UtilityVerb>>(AddScanVerb);
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, NodeScannerComponent component, AfterInteractEvent args)
|
||||
private void OnBeforeRangedInteract(EntityUid uid, NodeScannerComponent component, BeforeRangedInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach || args.Target == null)
|
||||
if (args.Handled || !args.CanReach || args.Target is not {} target)
|
||||
return;
|
||||
|
||||
if (!TryComp<ArtifactComponent>(target, out var artifact) || artifact.CurrentNodeId == null)
|
||||
return;
|
||||
|
||||
CreatePopup(uid, target, artifact);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void AddScanVerb(EntityUid uid, NodeScannerComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess)
|
||||
return;
|
||||
|
||||
if (!TryComp<ArtifactComponent>(args.Target, out var artifact) || artifact.CurrentNodeId == null)
|
||||
return;
|
||||
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
var verb = new UtilityVerb()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
CreatePopup(uid, args.Target, artifact);
|
||||
},
|
||||
Text = Loc.GetString("node-scan-tooltip")
|
||||
};
|
||||
|
||||
var target = args.Target.Value;
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void CreatePopup(EntityUid uid, EntityUid target, ArtifactComponent artifact)
|
||||
{
|
||||
if (TryComp(uid, out UseDelayComponent? useDelay)
|
||||
&& !_useDelay.TryResetDelay((uid, useDelay), true))
|
||||
return;
|
||||
|
||||
@@ -222,9 +222,7 @@ namespace Content.Server.Zombies
|
||||
_faction.AddFaction(target, "Zombie");
|
||||
|
||||
//gives it the funny "Zombie ___" name.
|
||||
var meta = MetaData(target);
|
||||
zombiecomp.BeforeZombifiedEntityName = meta.EntityName;
|
||||
_metaData.SetEntityName(target, Loc.GetString("zombie-name-prefix", ("target", meta.EntityName)), meta);
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
|
||||
_identity.QueueIdentityUpdate(target);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Zombies;
|
||||
@@ -34,9 +35,9 @@ namespace Content.Server.Zombies
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
|
||||
[Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly NameModifierSystem _nameMod = default!;
|
||||
|
||||
public const SlotFlags ProtectiveSlots =
|
||||
SlotFlags.FEET |
|
||||
@@ -281,7 +282,7 @@ namespace Content.Server.Zombies
|
||||
_humanoidAppearance.SetSkinColor(target, zombiecomp.BeforeZombifiedSkinColor, false);
|
||||
_bloodstream.ChangeBloodReagent(target, zombiecomp.BeforeZombifiedBloodReagent);
|
||||
|
||||
_metaData.SetEntityName(target, zombiecomp.BeforeZombifiedEntityName);
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
31
Content.Shared/Atmos/Components/MovedByPressureComponent.cs
Normal file
31
Content.Shared/Atmos/Components/MovedByPressureComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
|
||||
// Unfortunately can't be friends yet due to magboots.
|
||||
[RegisterComponent]
|
||||
public sealed partial class MovedByPressureComponent : Component
|
||||
{
|
||||
public const float MoveForcePushRatio = 1f;
|
||||
public const float MoveForceForcePushRatio = 1f;
|
||||
public const float ProbabilityOffset = 25f;
|
||||
public const float ProbabilityBasePercent = 10f;
|
||||
public const float ThrowForce = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Accumulates time when yeeted by high pressure deltas.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Accumulator;
|
||||
|
||||
[DataField]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
[DataField]
|
||||
public float PressureResistance { get; set; } = 1f;
|
||||
|
||||
[DataField]
|
||||
public float MoveResist { get; set; } = 100f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int LastHighPressureMovementAirCycle { get; set; } = 0;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
public VentPressureBound PressureChecks { get; set; } = VentPressureBound.ExternalBound;
|
||||
public float ExternalPressureBound { get; set; } = Atmospherics.OneAtmosphere;
|
||||
public float InternalPressureBound { get; set; } = 0f;
|
||||
public bool PressureLockoutOverride { get; set; } = false;
|
||||
|
||||
// Presets for 'dumb' air alarm modes
|
||||
|
||||
@@ -22,7 +23,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
|
||||
public static GasVentPumpData FillModePreset = new GasVentPumpData
|
||||
@@ -32,7 +34,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere * 50,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = true
|
||||
};
|
||||
|
||||
public static GasVentPumpData PanicModePreset = new GasVentPumpData
|
||||
@@ -42,7 +45,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
|
||||
public static GasVentPumpData ReplaceModePreset = new GasVentPumpData
|
||||
@@ -53,7 +57,8 @@ namespace Content.Shared.Atmos.Piping.Unary.Components
|
||||
PumpDirection = VentPumpDirection.Releasing,
|
||||
PressureChecks = VentPressureBound.ExternalBound,
|
||||
ExternalPressureBound = Atmospherics.OneAtmosphere,
|
||||
InternalPressureBound = 0f
|
||||
InternalPressureBound = 0f,
|
||||
PressureLockoutOverride = false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Bed.Sleep
|
||||
{
|
||||
public abstract class SharedSleepingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
|
||||
[ValidatePrototypeId<EntityPrototype>] private const string WakeActionId = "ActionWake";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SleepingComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
|
||||
}
|
||||
|
||||
|
||||
private void OnMapInit(EntityUid uid, SleepingComponent component, MapInitEvent args)
|
||||
{
|
||||
var ev = new SleepStateChangedEvent(true);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
_actionsSystem.AddAction(uid, ref component.WakeAction, WakeActionId, uid);
|
||||
|
||||
// TODO remove hardcoded time.
|
||||
_actionsSystem.SetCooldown(component.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, SleepingComponent component, ComponentShutdown args)
|
||||
{
|
||||
_actionsSystem.RemoveAction(uid, component.WakeAction);
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(uid, ev);
|
||||
_blindableSystem.UpdateIsBlind(uid);
|
||||
}
|
||||
|
||||
private void OnSpeakAttempt(EntityUid uid, SleepingComponent component, SpeakAttemptEvent args)
|
||||
{
|
||||
// TODO reduce duplication of this behavior with MobStateSystem somehow
|
||||
if (HasComp<AllowNextCritSpeechComponent>(uid))
|
||||
{
|
||||
RemCompDeferred<AllowNextCritSpeechComponent>(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSeeAttempt(EntityUid uid, SleepingComponent component, CanSeeAttemptEvent args)
|
||||
{
|
||||
if (component.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPointAttempt(EntityUid uid, SleepingComponent component, PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed partial class SleepActionEvent : InstantActionEvent {}
|
||||
|
||||
public sealed partial class WakeActionEvent : InstantActionEvent {}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when they fall asleep or wake up.
|
||||
/// </summary>
|
||||
public sealed class SleepStateChangedEvent : EntityEventArgs
|
||||
{
|
||||
public bool FellAsleep = false;
|
||||
|
||||
public SleepStateChangedEvent(bool fellAsleep)
|
||||
{
|
||||
FellAsleep = fellAsleep;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,42 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// Added to entities when they go to sleep.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent, AutoGenerateComponentPause(Dirty = true)]
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause(Dirty = true)]
|
||||
public sealed partial class SleepingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How much damage of any type it takes to wake this entity.
|
||||
/// </summary>
|
||||
[DataField("wakeThreshold")]
|
||||
[DataField]
|
||||
public FixedPoint2 WakeThreshold = FixedPoint2.New(2);
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown time between users hand interaction.
|
||||
/// </summary>
|
||||
[DataField("cooldown")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(1f);
|
||||
|
||||
[DataField("cooldownEnd", customTypeSerializer:typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan CoolDownEnd;
|
||||
[DataField]
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan CooldownEnd;
|
||||
|
||||
[DataField("wakeAction")] public EntityUid? WakeAction;
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public EntityUid? WakeAction;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when another player attempts to wake this entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier WakeAttemptSound = new SoundPathSpecifier("/Audio/Effects/thudswoosh.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.05f)
|
||||
};
|
||||
}
|
||||
|
||||
314
Content.Shared/Bed/Sleep/SleepingSystem.cs
Normal file
314
Content.Shared/Bed/Sleep/SleepingSystem.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Eye.Blinding.Systems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Pointing;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Slippery;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Sound.Components;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
public sealed partial class SleepingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly BlindableSystem _blindableSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedEmitSoundSystem _emitSound = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
|
||||
public static readonly ProtoId<EntityPrototype> SleepActionId = "ActionSleep";
|
||||
public static readonly ProtoId<EntityPrototype> WakeActionId = "ActionWake";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ActionsContainerComponent, SleepActionEvent>(OnBedSleepAction);
|
||||
|
||||
SubscribeLocalEvent<MobStateComponent, SleepStateChangedEvent>(OnSleepStateChanged);
|
||||
SubscribeLocalEvent<MobStateComponent, WakeActionEvent>(OnWakeAction);
|
||||
SubscribeLocalEvent<MobStateComponent, SleepActionEvent>(OnSleepAction);
|
||||
|
||||
SubscribeLocalEvent<SleepingComponent, DamageChangedEvent>(OnDamageChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<SleepingComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SleepingComponent, SpeakAttemptEvent>(OnSpeakAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, CanSeeAttemptEvent>(OnSeeAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, PointAttemptEvent>(OnPointAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, SlipAttemptEvent>(OnSlip);
|
||||
SubscribeLocalEvent<SleepingComponent, ConsciousAttemptEvent>(OnConsciousAttempt);
|
||||
SubscribeLocalEvent<SleepingComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<SleepingComponent, GetVerbsEvent<AlternativeVerb>>(AddWakeVerb);
|
||||
SubscribeLocalEvent<SleepingComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<ForcedSleepingComponent, ComponentInit>(OnInit);
|
||||
}
|
||||
|
||||
private void OnBedSleepAction(Entity<ActionsContainerComponent> ent, ref SleepActionEvent args)
|
||||
{
|
||||
TrySleeping(args.Performer);
|
||||
}
|
||||
|
||||
private void OnWakeAction(Entity<MobStateComponent> ent, ref WakeActionEvent args)
|
||||
{
|
||||
if (TryWakeWithCooldown(ent.Owner))
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSleepAction(Entity<MobStateComponent> ent, ref SleepActionEvent args)
|
||||
{
|
||||
TrySleeping((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// when sleeping component is added or removed, we do some stuff with other components.
|
||||
/// </summary>
|
||||
private void OnSleepStateChanged(Entity<MobStateComponent> ent, ref SleepStateChangedEvent args)
|
||||
{
|
||||
if (args.FellAsleep)
|
||||
{
|
||||
// Expiring status effects would remove the components needed for sleeping
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "Stun");
|
||||
_statusEffectsSystem.TryRemoveStatusEffect(ent.Owner, "KnockedDown");
|
||||
|
||||
EnsureComp<StunnedComponent>(ent);
|
||||
EnsureComp<KnockedDownComponent>(ent);
|
||||
|
||||
if (TryComp<SleepEmitSoundComponent>(ent, out var sleepSound))
|
||||
{
|
||||
var emitSound = EnsureComp<SpamEmitSoundComponent>(ent);
|
||||
if (HasComp<SnoringComponent>(ent))
|
||||
{
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
}
|
||||
emitSound.MinInterval = sleepSound.Interval;
|
||||
emitSound.MaxInterval = sleepSound.MaxInterval;
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
Dirty(ent.Owner, emitSound);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RemComp<StunnedComponent>(ent);
|
||||
RemComp<KnockedDownComponent>(ent);
|
||||
RemComp<SpamEmitSoundComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<SleepingComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
var ev = new SleepStateChangedEvent(true);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
_blindableSystem.UpdateIsBlind(ent.Owner);
|
||||
_actionsSystem.AddAction(ent, ref ent.Comp.WakeAction, WakeActionId, ent);
|
||||
|
||||
// TODO remove hardcoded time.
|
||||
_actionsSystem.SetCooldown(ent.Comp.WakeAction, _gameTiming.CurTime, _gameTiming.CurTime + TimeSpan.FromSeconds(2f));
|
||||
}
|
||||
|
||||
private void OnSpeakAttempt(Entity<SleepingComponent> ent, ref SpeakAttemptEvent args)
|
||||
{
|
||||
// TODO reduce duplication of this behavior with MobStateSystem somehow
|
||||
if (HasComp<AllowNextCritSpeechComponent>(ent))
|
||||
{
|
||||
RemCompDeferred<AllowNextCritSpeechComponent>(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSeeAttempt(Entity<SleepingComponent> ent, ref CanSeeAttemptEvent args)
|
||||
{
|
||||
if (ent.Comp.LifeStage <= ComponentLifeStage.Running)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnPointAttempt(Entity<SleepingComponent> ent, ref PointAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSlip(Entity<SleepingComponent> ent, ref SlipAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnConsciousAttempt(Entity<SleepingComponent> ent, ref ConsciousAttemptEvent args)
|
||||
{
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<SleepingComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("sleep-examined", ("target", Identity.Entity(ent, EntityManager))));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWakeVerb(Entity<SleepingComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanInteract || !args.CanAccess)
|
||||
return;
|
||||
|
||||
var target = args.Target;
|
||||
var user = args.User;
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
TryWakeWithCooldown((ent, ent.Comp), user: user);
|
||||
},
|
||||
Text = Loc.GetString("action-name-wake"),
|
||||
Priority = 2
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When you click on a sleeping person with an empty hand, try to wake them.
|
||||
/// </summary>
|
||||
private void OnInteractHand(Entity<SleepingComponent> ent, ref InteractHandEvent args)
|
||||
{
|
||||
args.Handled = true;
|
||||
|
||||
TryWakeWithCooldown((ent, ent.Comp), args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wake up on taking an instance of damage at least the value of WakeThreshold.
|
||||
/// </summary>
|
||||
private void OnDamageChanged(Entity<SleepingComponent> ent, ref DamageChangedEvent args)
|
||||
{
|
||||
if (!args.DamageIncreased || args.DamageDelta == null)
|
||||
return;
|
||||
|
||||
if (args.DamageDelta.GetTotal() >= ent.Comp.WakeThreshold)
|
||||
TryWaking((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In crit, we wake up if we are not being forced to sleep.
|
||||
/// And, you can't sleep when dead...
|
||||
/// </summary>
|
||||
private void OnMobStateChanged(Entity<SleepingComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (args.NewMobState == MobState.Dead)
|
||||
{
|
||||
RemComp<SpamEmitSoundComponent>(ent);
|
||||
RemComp<SleepingComponent>(ent);
|
||||
return;
|
||||
}
|
||||
if (TryComp<SpamEmitSoundComponent>(ent, out var spam))
|
||||
_emitSound.SetEnabled((ent, spam), args.NewMobState == MobState.Alive);
|
||||
}
|
||||
|
||||
private void OnInit(Entity<ForcedSleepingComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
TrySleeping(ent.Owner);
|
||||
}
|
||||
|
||||
private void Wake(Entity<SleepingComponent> ent)
|
||||
{
|
||||
RemComp<SleepingComponent>(ent);
|
||||
_actionsSystem.RemoveAction(ent, ent.Comp.WakeAction);
|
||||
|
||||
var ev = new SleepStateChangedEvent(false);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
|
||||
_blindableSystem.UpdateIsBlind(ent.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try sleeping. Only mobs can sleep.
|
||||
/// </summary>
|
||||
public bool TrySleeping(Entity<MobStateComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
var tryingToSleepEvent = new TryingToSleepEvent(ent);
|
||||
RaiseLocalEvent(ent, ref tryingToSleepEvent);
|
||||
if (tryingToSleepEvent.Cancelled)
|
||||
return false;
|
||||
|
||||
EnsureComp<SleepingComponent>(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to wake up <paramref name="ent"/>, with a cooldown between attempts to prevent spam.
|
||||
/// </summary>
|
||||
public bool TryWakeWithCooldown(Entity<SleepingComponent?> ent, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
|
||||
if (curTime < ent.Comp.CooldownEnd)
|
||||
return false;
|
||||
|
||||
ent.Comp.CooldownEnd = curTime + ent.Comp.Cooldown;
|
||||
Dirty(ent, ent.Comp);
|
||||
return TryWaking(ent, user: user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to wake up <paramref name="ent"/>.
|
||||
/// </summary>
|
||||
public bool TryWaking(Entity<SleepingComponent?> ent, bool force = false, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!force && HasComp<ForcedSleepingComponent>(ent))
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
|
||||
_popupSystem.PopupClient(Loc.GetString("wake-other-failure", ("target", Identity.Entity(ent, EntityManager))), ent, user, PopupType.SmallCaution);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_audio.PlayPredicted(ent.Comp.WakeAttemptSound, ent, user);
|
||||
_popupSystem.PopupClient(Loc.GetString("wake-other-success", ("target", Identity.Entity(ent, EntityManager))), ent, user);
|
||||
}
|
||||
|
||||
Wake((ent, ent.Comp));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed partial class SleepActionEvent : InstantActionEvent;
|
||||
|
||||
public sealed partial class WakeActionEvent : InstantActionEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when they fall asleep or wake up.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SleepStateChangedEvent(bool FellAsleep);
|
||||
@@ -1,9 +1,11 @@
|
||||
namespace Content.Server.Bed.Sleep;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the snoring trait.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class SnoringComponent : Component
|
||||
{
|
||||
|
||||
@@ -124,6 +124,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<float>
|
||||
EventsRampingAverageChaos = CVarDef.Create("events.ramping_average_chaos", 6f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum time between meteor swarms in minutes.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
MeteorSwarmMinTime = CVarDef.Create("events.meteor_swarm_min_time", 7.5f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time between meteor swarms in minutes.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float>
|
||||
MeteorSwarmMaxTime = CVarDef.Create("events.meteor_swarm_max_time", 12.5f, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/*
|
||||
* Game
|
||||
*/
|
||||
@@ -1428,6 +1440,18 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> ArrivalsReturns =
|
||||
CVarDef.Create("shuttle.arrivals_returns", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should all players be forced to spawn at departures, even on roundstart, even if their loadout says they spawn in cryo?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ForceArrivals =
|
||||
CVarDef.Create("shuttle.force_arrivals", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Should all players who spawn at arrivals have godmode until they leave the map?
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GodmodeArrivals =
|
||||
CVarDef.Create("shuttle.godmode_arrivals", false, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether to automatically spawn escape shuttles.
|
||||
/// </summary>
|
||||
@@ -1847,7 +1871,7 @@ namespace Content.Shared.CCVar
|
||||
/// Don't show rules to localhost/loopback interface.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> RulesExemptLocal =
|
||||
CVarDef.Create("rules.exempt_local", false, CVar.SERVERONLY);
|
||||
CVarDef.Create("rules.exempt_local", true, CVar.SERVERONLY);
|
||||
|
||||
|
||||
/*
|
||||
@@ -1927,6 +1951,12 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<float> GhostRoleTime =
|
||||
CVarDef.Create("ghost.role_time", 3f, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to kill the player's mob on ghosting, when it is in a critical health state.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GhostKillCrit =
|
||||
CVarDef.Create("ghost.kill_crit", true, CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/*
|
||||
* Fire alarm
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,7 @@ public sealed class SolutionTransferSystem : EntitySystem
|
||||
var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(sourceSolution.Volume, targetSolution.AvailableVolume));
|
||||
|
||||
var solution = _solution.SplitSolution(source, actualAmount);
|
||||
_solution.Refill(targetEntity, target, solution);
|
||||
_solution.AddSolution(target, solution);
|
||||
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Medium,
|
||||
$"{ToPrettyString(user):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(targetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Database;
|
||||
@@ -28,7 +28,7 @@ namespace Content.Shared.Chemistry.Reagent
|
||||
|
||||
public virtual string ReagentEffectFormat => "guidebook-reagent-effect-description";
|
||||
|
||||
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys); // => Loc.GetString("reagent-effect-guidebook-missing", ("chance", Probability));
|
||||
protected abstract string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys);
|
||||
|
||||
/// <summary>
|
||||
/// What's the chance, from 0 to 1, that this effect will occur?
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for clothing that makes an entity weightless when worn.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AntiGravityClothingComponent : Component;
|
||||
@@ -16,9 +16,14 @@ namespace Content.Shared.Clothing.Components;
|
||||
public sealed partial class ClothingComponent : Component
|
||||
{
|
||||
[DataField("clothingVisuals")]
|
||||
[Access(typeof(ClothingSystem), typeof(InventorySystem), Other = AccessPermissions.ReadExecute)] // TODO remove execute permissions.
|
||||
public Dictionary<string, List<PrototypeLayerData>> ClothingVisuals = new();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the layer in the user that this piece of clothing will map to
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? MappedLayer;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("quickEquip")]
|
||||
public bool QuickEquip = true;
|
||||
@@ -124,4 +129,3 @@ public sealed partial class ClothingUnequipDoAfterEvent : DoAfterEvent
|
||||
|
||||
public override DoAfterEvent Clone() => this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Clothing.Components;
|
||||
|
||||
/// <summary>
|
||||
/// When equipped, adds the wearer to a faction.
|
||||
/// When removed, removes the wearer from a faction.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(FactionClothingSystem))]
|
||||
public sealed partial class FactionClothingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Faction to add and remove.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<NpcFactionPrototype> Faction = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the wearer was already part of the faction.
|
||||
/// This prevents wrongly removing them after removing the item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AlreadyMember;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Inventory;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
public sealed class AntiGravityClothingSystem : EntitySystem
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
|
||||
}
|
||||
|
||||
private void OnIsWeightless(Entity<AntiGravityClothingComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
|
||||
{
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
args.Args.Handled = true;
|
||||
args.Args.IsWeightless = true;
|
||||
}
|
||||
}
|
||||
@@ -92,26 +92,29 @@ public abstract class ClothingSystem : EntitySystem
|
||||
InventorySystem.InventorySlotEnumerator enumerator = _invSystem.GetSlotEnumerator(equipee);
|
||||
|
||||
bool shouldLayerShow = true;
|
||||
while (enumerator.NextItem(out EntityUid item))
|
||||
while (enumerator.NextItem(out EntityUid item, out SlotDefinition? slot))
|
||||
{
|
||||
if (TryComp(item, out HideLayerClothingComponent? comp))
|
||||
{
|
||||
if (comp.Slots.Contains(layer))
|
||||
{
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask) && TryComp(item, out ClothingComponent? clothing))
|
||||
if (TryComp(item, out ClothingComponent? clothing) && clothing.Slots == slot.SlotFlags)
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
//Checks for mask toggling. TODO: Make a generic system for this
|
||||
if (comp.HideOnToggle && TryComp(item, out MaskComponent? mask))
|
||||
{
|
||||
if (clothing.EquippedPrefix != mask.EquippedPrefix)
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldLayerShow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,9 +241,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
|
||||
public void SetLayerColor(ClothingComponent clothing, string slot, string mapKey, Color? color)
|
||||
{
|
||||
if (clothing.ClothingVisuals == null)
|
||||
return;
|
||||
|
||||
foreach (var layer in clothing.ClothingVisuals[slot])
|
||||
{
|
||||
if (layer.MapKeys == null)
|
||||
@@ -254,9 +254,6 @@ public abstract class ClothingSystem : EntitySystem
|
||||
}
|
||||
public void SetLayerState(ClothingComponent clothing, string slot, string mapKey, string state)
|
||||
{
|
||||
if (clothing.ClothingVisuals == null)
|
||||
return;
|
||||
|
||||
foreach (var layer in clothing.ClothingVisuals[slot])
|
||||
{
|
||||
if (layer.MapKeys == null)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
|
||||
namespace Content.Shared.Clothing.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="FactionClothingComponent"/> faction adding and removal.
|
||||
/// </summary>
|
||||
public sealed class FactionClothingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NpcFactionSystem _faction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FactionClothingComponent, GotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<FactionClothingComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
}
|
||||
|
||||
private void OnEquipped(Entity<FactionClothingComponent> ent, ref GotEquippedEvent args)
|
||||
{
|
||||
TryComp<NpcFactionMemberComponent>(args.Equipee, out var factionComp);
|
||||
var faction = (args.Equipee, factionComp);
|
||||
ent.Comp.AlreadyMember = _faction.IsMember(faction, ent.Comp.Faction);
|
||||
|
||||
_faction.AddFaction(faction, ent.Comp.Faction);
|
||||
}
|
||||
|
||||
private void OnUnequipped(Entity<FactionClothingComponent> ent, ref GotUnequippedEvent args)
|
||||
{
|
||||
if (ent.Comp.AlreadyMember)
|
||||
{
|
||||
ent.Comp.AlreadyMember = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_faction.RemoveFaction(args.Equipee, ent.Comp.Faction);
|
||||
}
|
||||
}
|
||||
@@ -33,32 +33,32 @@ public sealed class FoldableClothingSystem : EntitySystem
|
||||
|
||||
private void OnFolded(Entity<FoldableClothingComponent> ent, ref FoldedEvent args)
|
||||
{
|
||||
if (TryComp<ClothingComponent>(ent.Owner, out var clothingComp) &&
|
||||
TryComp<ItemComponent>(ent.Owner, out var itemComp))
|
||||
if (!TryComp<ClothingComponent>(ent.Owner, out var clothingComp) ||
|
||||
!TryComp<ItemComponent>(ent.Owner, out var itemComp))
|
||||
return;
|
||||
|
||||
if (args.IsFolded)
|
||||
{
|
||||
if (args.IsFolded)
|
||||
{
|
||||
if (ent.Comp.FoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.FoldedSlots.Value, clothingComp);
|
||||
if (ent.Comp.FoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.FoldedSlots.Value, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, ent.Comp.FoldedEquippedPrefix, clothingComp);
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, ent.Comp.FoldedEquippedPrefix, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent.Comp.UnfoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.UnfoldedSlots.Value, clothingComp);
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, ent.Comp.FoldedHeldPrefix, false, itemComp);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ent.Comp.UnfoldedSlots.HasValue)
|
||||
_clothingSystem.SetSlots(ent.Owner, ent.Comp.UnfoldedSlots.Value, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, null, clothingComp);
|
||||
if (ent.Comp.FoldedEquippedPrefix != null)
|
||||
_clothingSystem.SetEquippedPrefix(ent.Owner, null, clothingComp);
|
||||
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
|
||||
if (ent.Comp.FoldedHeldPrefix != null)
|
||||
_itemSystem.SetHeldPrefix(ent.Owner, null, false, itemComp);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -15,6 +18,7 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
{
|
||||
// Shared so we can predict it for placement manager.
|
||||
|
||||
[Dependency] private readonly ActorSystem _actors = default!;
|
||||
[Dependency] private readonly SharedStationSpawningSystem _station = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
@@ -125,7 +129,17 @@ public sealed class LoadoutSystem : EntitySystem
|
||||
var id = _random.Pick(component.RoleLoadout);
|
||||
var proto = _protoMan.Index(id);
|
||||
var loadout = new RoleLoadout(id);
|
||||
loadout.SetDefault(_protoMan, true);
|
||||
loadout.SetDefault(GetProfile(uid), _actors.GetSession(uid), _protoMan, true);
|
||||
_station.EquipRoleLoadout(uid, loadout, proto);
|
||||
}
|
||||
|
||||
public HumanoidCharacterProfile GetProfile(EntityUid? uid)
|
||||
{
|
||||
if (TryComp(uid, out HumanoidAppearanceComponent? appearance))
|
||||
{
|
||||
return HumanoidCharacterProfile.DefaultWithSpecies(appearance.Species);
|
||||
}
|
||||
|
||||
return HumanoidCharacterProfile.Random();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,10 @@ public sealed partial class MagbootsComponent : Component
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> MagbootsAlert = "Magboots";
|
||||
|
||||
/// <summary>
|
||||
/// If true, the user must be standing on a grid or planet map to experience the weightlessness-canceling effect
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool RequiresGrid = true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Clothing.EntitySystems;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Slippery;
|
||||
@@ -9,10 +12,12 @@ using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Clothing;
|
||||
|
||||
public abstract class SharedMagbootsSystem : EntitySystem
|
||||
public sealed class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly ClothingSpeedModifierSystem _clothingSpeedModifier = default!;
|
||||
[Dependency] private readonly ClothingSystem _clothing = default!;
|
||||
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _sharedActions = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionContainer = default!;
|
||||
@@ -29,6 +34,11 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
SubscribeLocalEvent<MagbootsComponent, GetItemActionsEvent>(OnGetActions);
|
||||
SubscribeLocalEvent<MagbootsComponent, ToggleMagbootsEvent>(OnToggleMagboots);
|
||||
SubscribeLocalEvent<MagbootsComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MagbootsComponent component, MapInitEvent args)
|
||||
@@ -37,6 +47,16 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, false, component);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
UpdateMagbootEffects(args.Wearer, uid, true, component);
|
||||
}
|
||||
|
||||
private void OnToggleMagboots(EntityUid uid, MagbootsComponent component, ToggleMagbootsEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -51,9 +71,11 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
magboots.On = !magboots.On;
|
||||
|
||||
if (_sharedContainer.TryGetContainingContainer(uid, out var container) &&
|
||||
if (_sharedContainer.TryGetContainingContainer((uid, Transform(uid)), out var container) &&
|
||||
_inventory.TryGetSlotEntity(container.Owner, "shoes", out var entityUid) && entityUid == uid)
|
||||
{
|
||||
UpdateMagbootEffects(container.Owner, uid, true, magboots);
|
||||
}
|
||||
|
||||
if (TryComp<ItemComponent>(uid, out var item))
|
||||
{
|
||||
@@ -66,9 +88,28 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
Dirty(uid, magboots);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component) { }
|
||||
public void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
state = state && component.On;
|
||||
|
||||
protected void OnChanged(EntityUid uid, MagbootsComponent component)
|
||||
if (TryComp(parent, out MovedByPressureComponent? movedByPressure))
|
||||
{
|
||||
movedByPressure.Enabled = !state;
|
||||
}
|
||||
|
||||
if (state)
|
||||
{
|
||||
_alerts.ShowAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
else
|
||||
{
|
||||
_alerts.ClearAlert(parent, component.MagbootsAlert);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChanged(EntityUid uid, MagbootsComponent component)
|
||||
{
|
||||
_sharedActions.SetToggled(component.ToggleActionEntity, component.On);
|
||||
_clothingSpeedModifier.SetClothingSpeedModifierEnabled(uid, component.On);
|
||||
@@ -79,10 +120,12 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
ActivationVerb verb = new();
|
||||
verb.Text = Loc.GetString("toggle-magboots-verb-get-data-text");
|
||||
verb.Act = () => ToggleMagboots(uid, component);
|
||||
// TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
|
||||
ActivationVerb verb = new()
|
||||
{
|
||||
Text = Loc.GetString("toggle-magboots-verb-get-data-text"),
|
||||
Act = () => ToggleMagboots(uid, component),
|
||||
// TODO VERB ICON add toggle icon? maybe a computer on/off symbol?
|
||||
};
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
@@ -96,6 +139,22 @@ public abstract class SharedMagbootsSystem : EntitySystem
|
||||
{
|
||||
args.AddAction(ref component.ToggleActionEntity, component.ToggleAction);
|
||||
}
|
||||
|
||||
private void OnIsWeightless(Entity<MagbootsComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
|
||||
{
|
||||
if (args.Args.Handled)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.On)
|
||||
return;
|
||||
|
||||
// do not cancel weightlessness if the person is in off-grid.
|
||||
if (ent.Comp.RequiresGrid && !_gravity.EntityOnGravitySupportingGridOrMap(ent.Owner))
|
||||
return;
|
||||
|
||||
args.Args.IsWeightless = false;
|
||||
args.Args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class ToggleMagbootsEvent : InstantActionEvent {}
|
||||
public sealed partial class ToggleMagbootsEvent : InstantActionEvent;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user