Merge pull request #252 from crystallpunk-14/ed-17-06-2024-upstream

Ed 17 06 2024 upstream
This commit is contained in:
Ed
2024-06-17 16:44:01 +03:00
committed by GitHub
355 changed files with 27681 additions and 9600 deletions

View File

@@ -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

View File

@@ -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>() });

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1,8 +0,0 @@
using Content.Server.Bed.Sleep;
namespace Content.Client.Bed;
public sealed class SleepingSystem : SharedSleepingSystem
{
}

View File

@@ -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);

View 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;
}
}
}

View 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";
}

View File

@@ -1,8 +0,0 @@
using Content.Shared.Clothing;
namespace Content.Client.Clothing;
public sealed class MagbootsSystem : SharedMagbootsSystem
{
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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,
};

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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}");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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"));
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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)),

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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)));
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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))
{

View File

@@ -1,3 +1,4 @@
using Content.Shared.Bed.Sleep;
using Content.Shared.Damage;
using Content.Shared.Damage.ForceSay;
using Content.Shared.FixedPoint;

View File

@@ -1,4 +1,4 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos.Components;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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 },
};

View File

@@ -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))
{

View File

@@ -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);
}
}
}

View File

@@ -164,7 +164,7 @@ public sealed class SecretRuleSystem : GameRuleSystem<SecretRuleComponent>
return false;
}
if (ruleComp.MinPlayers > players)
if (ruleComp.MinPlayers > players && ruleComp.CancelPresetOnTooFewPlayers)
return false;
}

View File

@@ -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");
}
}

View File

@@ -1,7 +1,6 @@
using Content.Shared.Gravity;
using JetBrains.Annotations;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Content.Server.Gravity
{

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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");
}
}

View File

@@ -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

View 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();
}

View 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);
}
}

View File

@@ -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
{
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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.

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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!;

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View 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);
}
}
}

View 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));
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View 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;
}

View File

@@ -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
};
}

View File

@@ -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;
}
}

View File

@@ -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)
};
}

View 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);

View File

@@ -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
{

View File

@@ -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
*/

View File

@@ -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)}");

View File

@@ -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?

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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