Stable release (#41558)
This commit is contained in:
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -10,13 +10,13 @@
|
||||
<!-- Summary of code changes for easier review. -->
|
||||
|
||||
## Media
|
||||
<!-- Attach media if the PR makes ingame changes (clothing, items, features, etc).
|
||||
<!-- Attach media if the PR makes in-game changes (clothing, items, features, etc).
|
||||
Small fixes/refactors are exempt. Media may be used in SS14 progress reports with credit. -->
|
||||
|
||||
## Requirements
|
||||
<!-- Confirm the following by placing an X in the brackets [X]: -->
|
||||
- [ ] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
|
||||
- [ ] I have added media to this PR or it does not require an ingame showcase.
|
||||
- [ ] I have added media to this PR or it does not require an in-game showcase.
|
||||
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->
|
||||
|
||||
## Breaking changes
|
||||
|
||||
@@ -23,6 +23,8 @@ public class RaiseEventBenchmark
|
||||
PoolManager.Startup(typeof(BenchSystem).Assembly);
|
||||
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
|
||||
var entMan = _pair.Server.EntMan;
|
||||
var fact = _pair.Server.ResolveDependency<IComponentFactory>();
|
||||
var bus = (EntityEventBus)entMan.EventBus;
|
||||
_sys = entMan.System<BenchSystem>();
|
||||
|
||||
_pair.Server.WaitPost(() =>
|
||||
@@ -30,6 +32,8 @@ public class RaiseEventBenchmark
|
||||
var uid = entMan.Spawn();
|
||||
_sys.Ent = new(uid, entMan.GetComponent<TransformComponent>(uid));
|
||||
_sys.Ent2 = new(_sys.Ent.Owner, _sys.Ent.Comp);
|
||||
_sys.NetId = fact.GetRegistration<TransformComponent>().NetID!.Value;
|
||||
_sys.EvSubs = bus.GetNetCompEventHandlers<BenchSystem.BenchEv>();
|
||||
})
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
@@ -60,6 +64,12 @@ public class RaiseEventBenchmark
|
||||
return _sys.RaiseICompEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseNetEvent()
|
||||
{
|
||||
return _sys.RaiseNetIdEvent();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int RaiseCSharpEvent()
|
||||
{
|
||||
@@ -74,6 +84,8 @@ public class RaiseEventBenchmark
|
||||
public delegate void EntityEventHandler(EntityUid uid, TransformComponent comp, ref BenchEv ev);
|
||||
|
||||
public event EntityEventHandler? OnCSharpEvent;
|
||||
public ushort NetId;
|
||||
internal EntityEventBus.DirectedEventHandler?[] EvSubs = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -92,7 +104,7 @@ public class RaiseEventBenchmark
|
||||
public int RaiseCompEvent()
|
||||
{
|
||||
var ev = new BenchEv();
|
||||
EntityManager.EventBus.RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
|
||||
RaiseComponentEvent(Ent.Owner, Ent.Comp, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
@@ -100,7 +112,16 @@ public class RaiseEventBenchmark
|
||||
{
|
||||
// Raise with an IComponent instead of concrete type
|
||||
var ev = new BenchEv();
|
||||
EntityManager.EventBus.RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
|
||||
RaiseComponentEvent(Ent2.Owner, Ent2.Comp, ref ev);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
public int RaiseNetIdEvent()
|
||||
{
|
||||
// Raise a "IComponent" event using a net-id index delegate array (for PVS & client game-state events)
|
||||
var ev = new BenchEv();
|
||||
ref var unitEv = ref Unsafe.As<BenchEv, EntityEventBus.Unit>(ref ev);
|
||||
EvSubs[NetId]?.Invoke(Ent2.Owner, Ent2.Comp, ref unitEv);
|
||||
return ev.N;
|
||||
}
|
||||
|
||||
@@ -118,6 +139,7 @@ public class RaiseEventBenchmark
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
[ComponentEvent(Exclusive = false)]
|
||||
public struct BenchEv
|
||||
{
|
||||
public int N;
|
||||
|
||||
52
Content.Client/Commands/QuickInspectCommand.cs
Normal file
52
Content.Client/Commands/QuickInspectCommand.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the a <see cref="CCVars.DebugQuickInspect"/> CVar to the name of a component, which allows the client to quickly open a VV window for that component
|
||||
/// by using the Alt+C or Alt+B hotkeys.
|
||||
/// </summary>
|
||||
public sealed class QuickInspectCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
public override string Command => "quickinspect";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("shell-wrong-arguments-number"));
|
||||
return;
|
||||
}
|
||||
|
||||
_configurationManager.SetCVar(CCVars.DebugQuickInspect, args[0]);
|
||||
|
||||
var serverKey = _inputManager.GetKeyFunctionButtonString(ContentKeyFunctions.InspectServerComponent);
|
||||
var clientKey = _inputManager.GetKeyFunctionButtonString(ContentKeyFunctions.InspectClientComponent);
|
||||
shell.WriteLine(Loc.GetString($"cmd-quickinspect-success", ("component", args[0]), ("serverKeybind", serverKey), ("clientKeybind", clientKey)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
// Not ideal since it only shows client-side components, but you can still type in any name you want.
|
||||
// If you know how to get server component names on the client then please fix this.
|
||||
var options = EntityManager.ComponentFactory.AllRegisteredTypes
|
||||
.Select(p => new CompletionOption(
|
||||
EntityManager.ComponentFactory.GetComponentName(p)
|
||||
));
|
||||
|
||||
return CompletionResult.FromOptions(options);
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,6 @@ namespace Content.Client.Entry
|
||||
_componentFactory.IgnoreMissingComponents();
|
||||
|
||||
// Do not add to these, they are legacy.
|
||||
_componentFactory.RegisterClass<SharedGravityGeneratorComponent>();
|
||||
_componentFactory.RegisterClass<SharedAmeControllerComponent>();
|
||||
// Do not add to the above, they are legacy
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Numerics;
|
||||
using Content.Client.Clickable;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.ComponentTrees;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -13,6 +14,7 @@ using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
@@ -40,6 +42,7 @@ namespace Content.Client.Gameplay
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IViewVariablesManager _vvm = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private ClickableEntityComparer _comparer = default!;
|
||||
|
||||
@@ -83,6 +86,8 @@ namespace Content.Client.Gameplay
|
||||
_comparer = new ClickableEntityComparer();
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.InspectEntity, new PointerInputCmdHandler(HandleInspect, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.InspectServerComponent, new PointerInputCmdHandler(HandleInspectServerComponent, outsidePrediction: true))
|
||||
.Bind(ContentKeyFunctions.InspectClientComponent, new PointerInputCmdHandler(HandleInspectClientComponent, outsidePrediction: true))
|
||||
.Register<GameplayStateBase>();
|
||||
}
|
||||
|
||||
@@ -99,6 +104,21 @@ namespace Content.Client.Gameplay
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleInspectServerComponent(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
var component = _configurationManager.GetCVar(CCVars.DebugQuickInspect);
|
||||
if (_entityManager.TryGetNetEntity(uid, out var net))
|
||||
_conHost.ExecuteCommand($"vv /entity/{net.Value.Id}/{component}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HandleInspectClientComponent(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
var component = _configurationManager.GetCVar(CCVars.DebugQuickInspect);
|
||||
_conHost.ExecuteCommand($"vv /c/entity/{uid}/{component}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public EntityUid? GetClickedEntity(MapCoordinates coordinates)
|
||||
{
|
||||
return GetClickedEntity(coordinates, _eyeManager.CurrentEye);
|
||||
|
||||
8
Content.Client/Gravity/GravityGeneratorSystem.cs
Normal file
8
Content.Client/Gravity/GravityGeneratorSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Gravity;
|
||||
|
||||
namespace Content.Client.Gravity;
|
||||
|
||||
public sealed class GravityGeneratorSystem : SharedGravityGeneratorSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -12,14 +12,14 @@ public sealed partial class GravitySystem : SharedGravitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedGravityGeneratorComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<GravityGeneratorComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
InitializeShake();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the visible state of gravity generators are synced with their sprites.
|
||||
/// </summary>
|
||||
private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent comp, ref AppearanceChangeEvent args)
|
||||
private void OnAppearanceChange(EntityUid uid, GravityGeneratorComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
@@ -38,6 +38,8 @@ namespace Content.Client.Input
|
||||
common.AddFunction(ContentKeyFunctions.ZoomIn);
|
||||
common.AddFunction(ContentKeyFunctions.ResetZoom);
|
||||
common.AddFunction(ContentKeyFunctions.InspectEntity);
|
||||
common.AddFunction(ContentKeyFunctions.InspectServerComponent);
|
||||
common.AddFunction(ContentKeyFunctions.InspectClientComponent);
|
||||
common.AddFunction(ContentKeyFunctions.ToggleRoundEndSummaryWindow);
|
||||
|
||||
// Not in engine, because engine cannot check for sanbox/admin status before starting placement.
|
||||
|
||||
@@ -268,6 +268,8 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(EngineKeyFunctions.ShowDebugMonitors);
|
||||
AddButton(EngineKeyFunctions.HideUI);
|
||||
AddButton(ContentKeyFunctions.InspectEntity);
|
||||
AddButton(ContentKeyFunctions.InspectServerComponent);
|
||||
AddButton(ContentKeyFunctions.InspectClientComponent);
|
||||
|
||||
AddHeader("ui-options-header-text-cursor");
|
||||
AddButton(EngineKeyFunctions.TextCursorLeft);
|
||||
|
||||
5
Content.Client/Tips/TipsSystem.cs
Normal file
5
Content.Client/Tips/TipsSystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Tips;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class TipsSystem : SharedTipsSystem;
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server.Gravity;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -402,8 +402,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
battery = entityManager.GetComponent<BatteryComponent>(generatorEnt);
|
||||
consumer = entityManager.GetComponent<PowerConsumerComponent>(consumerEnt);
|
||||
|
||||
batterySys.SetMaxCharge(generatorEnt, startingCharge, battery);
|
||||
batterySys.SetCharge(generatorEnt, startingCharge, battery);
|
||||
batterySys.SetMaxCharge((generatorEnt, battery), startingCharge);
|
||||
batterySys.SetCharge((generatorEnt, battery), startingCharge);
|
||||
netBattery.MaxSupply = 400;
|
||||
netBattery.SupplyRampRate = 400;
|
||||
netBattery.SupplyRampTolerance = 100;
|
||||
@@ -513,8 +513,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
supplier.SupplyRampRate = rampRate;
|
||||
supplier.SupplyRampTolerance = rampTol;
|
||||
|
||||
batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetMaxCharge((batteryEnt, battery), 100_000);
|
||||
batterySys.SetCharge((batteryEnt, battery), 100_000);
|
||||
netBattery.MaxSupply = draw / 2;
|
||||
netBattery.SupplyRampRate = rampRate;
|
||||
netBattery.SupplyRampTolerance = rampTol;
|
||||
@@ -600,7 +600,7 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
|
||||
supplier.MaxSupply = 500;
|
||||
supplier.SupplyRampTolerance = 500;
|
||||
batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetMaxCharge((batteryEnt, battery), 100_000);
|
||||
netBattery.MaxChargeRate = 1_000;
|
||||
netBattery.Efficiency = 0.5f;
|
||||
});
|
||||
@@ -670,8 +670,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
netBattery.MaxSupply = 400;
|
||||
netBattery.SupplyRampTolerance = 400;
|
||||
netBattery.SupplyRampRate = 100_000;
|
||||
batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetMaxCharge((batteryEnt, battery), 100_000);
|
||||
batterySys.SetCharge((batteryEnt, battery), 100_000);
|
||||
});
|
||||
|
||||
// Run some ticks so everything is stable.
|
||||
@@ -750,8 +750,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
netBattery.SupplyRampTolerance = 400;
|
||||
netBattery.SupplyRampRate = 100_000;
|
||||
netBattery.Efficiency = 0.5f;
|
||||
batterySys.SetMaxCharge(batteryEnt, 1_000_000, battery);
|
||||
batterySys.SetCharge(batteryEnt, 1_000_000, battery);
|
||||
batterySys.SetMaxCharge((batteryEnt, battery), 1_000_000);
|
||||
batterySys.SetCharge((batteryEnt, battery), 1_000_000);
|
||||
});
|
||||
|
||||
// Run some ticks so everything is stable.
|
||||
@@ -841,8 +841,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
supplier.MaxSupply = 1000;
|
||||
supplier.SupplyRampTolerance = 1000;
|
||||
|
||||
batterySys.SetMaxCharge(batteryEnt1, 1_000_000, battery1);
|
||||
batterySys.SetMaxCharge(batteryEnt2, 1_000_000, battery2);
|
||||
batterySys.SetMaxCharge((batteryEnt1, battery1), 1_000_000);
|
||||
batterySys.SetMaxCharge((batteryEnt2, battery2), 1_000_000);
|
||||
|
||||
netBattery1.MaxChargeRate = 1_000;
|
||||
netBattery2.MaxChargeRate = 1_000;
|
||||
@@ -945,10 +945,10 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
netBattery2.SupplyRampTolerance = 1000;
|
||||
netBattery1.SupplyRampRate = 100_000;
|
||||
netBattery2.SupplyRampRate = 100_000;
|
||||
batterySys.SetMaxCharge(batteryEnt1, 100_000, battery1);
|
||||
batterySys.SetMaxCharge(batteryEnt2, 100_000, battery2);
|
||||
batterySys.SetCharge(batteryEnt1, 100_000, battery1);
|
||||
batterySys.SetCharge(batteryEnt2, 100_000, battery2);
|
||||
batterySys.SetMaxCharge((batteryEnt1, battery1), 100_000);
|
||||
batterySys.SetMaxCharge((batteryEnt2, battery2), 100_000);
|
||||
batterySys.SetCharge((batteryEnt1, battery1), 100_000);
|
||||
batterySys.SetCharge((batteryEnt2, battery2), 100_000);
|
||||
});
|
||||
|
||||
// Run some ticks so everything is stable.
|
||||
@@ -1031,8 +1031,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
supplier.MaxSupply = 1000;
|
||||
supplier.SupplyRampTolerance = 1000;
|
||||
|
||||
batterySys.SetMaxCharge(batteryEnt1, 1_000_000, battery1);
|
||||
batterySys.SetMaxCharge(batteryEnt2, 1_000_000, battery2);
|
||||
batterySys.SetMaxCharge((batteryEnt1, battery1), 1_000_000);
|
||||
batterySys.SetMaxCharge((batteryEnt2, battery2), 1_000_000);
|
||||
|
||||
netBattery1.MaxChargeRate = 20;
|
||||
netBattery2.MaxChargeRate = 20;
|
||||
@@ -1107,8 +1107,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
netBattery.MaxSupply = 1000;
|
||||
netBattery.SupplyRampTolerance = 200;
|
||||
netBattery.SupplyRampRate = 10;
|
||||
batterySys.SetMaxCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetCharge(batteryEnt, 100_000, battery);
|
||||
batterySys.SetMaxCharge((batteryEnt, battery), 100_000);
|
||||
batterySys.SetCharge((batteryEnt, battery), 100_000);
|
||||
});
|
||||
|
||||
// Run some ticks so everything is stable.
|
||||
@@ -1253,7 +1253,7 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
generatorSupplier.MaxSupply = 1000;
|
||||
generatorSupplier.SupplyRampTolerance = 1000;
|
||||
|
||||
batterySys.SetCharge(apcEnt, 0, apcBattery);
|
||||
batterySys.SetCharge((apcEnt, apcBattery), 0);
|
||||
});
|
||||
|
||||
server.RunTicks(5); //let run a few ticks for PowerNets to reevaluate and start charging apc
|
||||
@@ -1314,8 +1314,8 @@ namespace Content.IntegrationTests.Tests.Power
|
||||
extensionCableSystem.SetProviderTransferRange(apcExtensionEnt, range);
|
||||
extensionCableSystem.SetReceiverReceptionRange(powerReceiverEnt, range);
|
||||
|
||||
batterySys.SetMaxCharge(apcEnt, 10000, battery); //arbitrary nonzero amount of charge
|
||||
batterySys.SetCharge(apcEnt, battery.MaxCharge, battery); //fill battery
|
||||
batterySys.SetMaxCharge((apcEnt, battery), 10000); //arbitrary nonzero amount of charge
|
||||
batterySys.SetCharge((apcEnt, battery), battery.MaxCharge); //fill battery
|
||||
|
||||
receiver.Load = 1; //arbitrary small amount of power
|
||||
});
|
||||
|
||||
103
Content.Server/Administration/Commands/TippyCommand.cs
Normal file
103
Content.Server/Administration/Commands/TippyCommand.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class TippyCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedTipsSystem _tips = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override string Command => "tippy";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-help"));
|
||||
return;
|
||||
}
|
||||
|
||||
ICommonSession? targetSession = null;
|
||||
if (args[0] != "all")
|
||||
{
|
||||
if (!_player.TryGetSessionByUsername(args[0], out targetSession))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var msg = args[1];
|
||||
|
||||
EntProtoId? prototype = null;
|
||||
if (args.Length > 2)
|
||||
{
|
||||
if (args[2] == "null")
|
||||
prototype = null;
|
||||
else if (!_prototype.HasIndex<EntityPrototype>(args[2]))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-tippy-error-no-prototype", ("proto", args[2])));
|
||||
return;
|
||||
}
|
||||
else
|
||||
prototype = args[2];
|
||||
}
|
||||
|
||||
var speakTime = _tips.GetSpeechTime(msg);
|
||||
var slideTime = 3f;
|
||||
var waddleInterval = 0.5f;
|
||||
|
||||
if (args.Length > 3 && float.TryParse(args[3], out var parsedSpeakTime))
|
||||
speakTime = parsedSpeakTime;
|
||||
|
||||
if (args.Length > 4 && float.TryParse(args[4], out var parsedSlideTime))
|
||||
slideTime = parsedSlideTime;
|
||||
|
||||
if (args.Length > 5 && float.TryParse(args[5], out var parsedWaddleInterval))
|
||||
waddleInterval = parsedWaddleInterval;
|
||||
|
||||
if (targetSession != null) // send to specified player
|
||||
_tips.SendTippy(targetSession, msg, prototype, speakTime, slideTime, waddleInterval);
|
||||
else // send to everyone
|
||||
_tips.SendTippy(msg, prototype, speakTime, slideTime, waddleInterval);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.SessionNames(players: _player),
|
||||
Loc.GetString("cmd-tippy-auto-1")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")),
|
||||
3 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIdsLimited<EntityPrototype>(args[2], _prototype),
|
||||
Loc.GetString("cmd-tippy-auto-3")),
|
||||
4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")),
|
||||
5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")),
|
||||
6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class TipCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SharedTipsSystem _tips = default!;
|
||||
|
||||
public override string Command => "tip";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_tips.AnnounceRandomTip();
|
||||
_tips.RecalculateNextTipTime();
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/fill_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, battery.MaxCharge, battery);
|
||||
_batterySystem.SetCharge((args.Target, battery), battery.MaxCharge);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-refill-battery-description"),
|
||||
@@ -184,7 +184,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/drain_battery.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_batterySystem.SetCharge(args.Target, 0, battery);
|
||||
_batterySystem.SetCharge((args.Target, battery), 0);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-drain-battery-description"),
|
||||
@@ -200,9 +200,8 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
var recharger = EnsureComp<BatterySelfRechargerComponent>(args.Target);
|
||||
recharger.AutoRecharge = true;
|
||||
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
|
||||
recharger.AutoRechargePause = false; // No delay.
|
||||
recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay.
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-infinite-battery-object-description"),
|
||||
@@ -553,7 +552,7 @@ public sealed partial class AdminVerbSystem
|
||||
if (!HasComp<StationInfiniteBatteryTargetComponent>(ent))
|
||||
continue;
|
||||
var battery = EnsureComp<BatteryComponent>(ent);
|
||||
_batterySystem.SetCharge(ent, battery.MaxCharge, battery);
|
||||
_batterySystem.SetCharge((ent, battery), battery.MaxCharge);
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
@@ -574,7 +573,7 @@ public sealed partial class AdminVerbSystem
|
||||
if (!HasComp<StationInfiniteBatteryTargetComponent>(ent))
|
||||
continue;
|
||||
var battery = EnsureComp<BatteryComponent>(ent);
|
||||
_batterySystem.SetCharge(ent, 0, battery);
|
||||
_batterySystem.SetCharge((ent, battery), 0);
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
@@ -599,9 +598,8 @@ public sealed partial class AdminVerbSystem
|
||||
var recharger = EnsureComp<BatterySelfRechargerComponent>(ent);
|
||||
var battery = EnsureComp<BatteryComponent>(ent);
|
||||
|
||||
recharger.AutoRecharge = true;
|
||||
recharger.AutoRechargeRate = battery.MaxCharge; // Instant refill.
|
||||
recharger.AutoRechargePause = false; // No delay.
|
||||
recharger.AutoRechargePauseTime = TimeSpan.Zero; // No delay.
|
||||
}
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
|
||||
40
Content.Server/Antag/AntagMultipleRoleSpawnerSystem.cs
Normal file
40
Content.Server/Antag/AntagMultipleRoleSpawnerSystem.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Antag.Components;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Antag;
|
||||
|
||||
public sealed class AntagMultipleRoleSpawnerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AntagMultipleRoleSpawnerComponent, AntagSelectEntityEvent>(OnSelectEntity);
|
||||
|
||||
_sawmill = _log.GetSawmill("antag_multiple_spawner");
|
||||
}
|
||||
|
||||
private void OnSelectEntity(Entity<AntagMultipleRoleSpawnerComponent> ent, ref AntagSelectEntityEvent args)
|
||||
{
|
||||
// If its more than one the logic breaks
|
||||
if (args.AntagRoles.Count != 1)
|
||||
{
|
||||
_sawmill.Fatal($"Antag multiple role spawner had more than one antag ({args.AntagRoles.Count})");
|
||||
return;
|
||||
}
|
||||
|
||||
var role = args.AntagRoles[0];
|
||||
|
||||
var entProtos = ent.Comp.AntagRoleToPrototypes[role];
|
||||
|
||||
if (entProtos.Count == 0)
|
||||
return; // You will just get a normal job
|
||||
|
||||
args.Entity = Spawn(ent.Comp.PickAndTake ? _random.PickAndTake(entProtos) : _random.Pick(entProtos));
|
||||
}
|
||||
}
|
||||
@@ -396,7 +396,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
|
||||
if (!antagEnt.HasValue)
|
||||
{
|
||||
var getEntEv = new AntagSelectEntityEvent(session, ent);
|
||||
var getEntEv = new AntagSelectEntityEvent(session, ent, def.PrefRoles);
|
||||
|
||||
RaiseLocalEvent(ent, ref getEntEv, true);
|
||||
antagEnt = getEntEv.Entity;
|
||||
}
|
||||
@@ -419,7 +420,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
// Therefore any component subscribing to this has to make sure both subscriptions return the same value
|
||||
// or the ghost role raffle location preview will be wrong.
|
||||
|
||||
var getPosEv = new AntagSelectLocationEvent(session, ent);
|
||||
var getPosEv = new AntagSelectLocationEvent(session, ent, player);
|
||||
RaiseLocalEvent(ent, ref getPosEv, true);
|
||||
if (getPosEv.Handled)
|
||||
{
|
||||
@@ -607,10 +608,13 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
|
||||
/// Only raised if the selected player's current entity is invalid.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AntagSelectEntityEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule)
|
||||
public record struct AntagSelectEntityEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule, List<ProtoId<AntagPrototype>> AntagRoles)
|
||||
{
|
||||
public readonly ICommonSession? Session = Session;
|
||||
|
||||
/// list of antag role prototypes associated with a entity. used by the <see cref="AntagMultipleRoleSpawnerComponent"/>
|
||||
public readonly List<ProtoId<AntagPrototype>> AntagRoles = AntagRoles;
|
||||
|
||||
public bool Handled => Entity != null;
|
||||
|
||||
public EntityUid? Entity;
|
||||
@@ -620,12 +624,15 @@ public record struct AntagSelectEntityEvent(ICommonSession? Session, Entity<Anta
|
||||
/// Event raised on a game rule entity to determine the location for the antagonist.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AntagSelectLocationEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule)
|
||||
public record struct AntagSelectLocationEvent(ICommonSession? Session, Entity<AntagSelectionComponent> GameRule, EntityUid Entity)
|
||||
{
|
||||
public readonly ICommonSession? Session = Session;
|
||||
|
||||
public bool Handled => Coordinates.Any();
|
||||
|
||||
// the entity of the antagonist
|
||||
public EntityUid Entity = Entity;
|
||||
|
||||
public List<MapCoordinates> Coordinates = new();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Antag.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Selects and spawns one prototype from a list for each antag prototype selected by the <see cref="AntagSelectionSystem"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class AntagMultipleRoleSpawnerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// antag prototype -> list of possible entities to spawn for that antag prototype. Will choose from the list randomly once with replacement unless <see cref="PickAndTake"/> is set to true
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<ProtoId<AntagPrototype>, List<EntProtoId>> AntagRoleToPrototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Should you remove ent prototypes from the list after spawning one.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool PickAndTake;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
@@ -14,6 +13,22 @@ namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public partial class AtmosphereSystem
|
||||
{
|
||||
/*
|
||||
General API for interacting with AtmosphereSystem.
|
||||
|
||||
If you feel like you're stepping on eggshells because you can't access things in AtmosphereSystem,
|
||||
consider adding a method here instead of making your own way to work around it.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GasMixture"/> that an entity is contained within.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to get the mixture for.</param>
|
||||
/// <param name="ignoreExposed">If true, will ignore mixtures that the entity is contained in
|
||||
/// (ex. lockers and cryopods) and just get the tile mixture.</param>
|
||||
/// <param name="excite">If true, will mark the tile as active for atmosphere processing.</param>
|
||||
/// <returns>A <see cref="GasMixture"/> if one could be found, null otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public GasMixture? GetContainingMixture(Entity<TransformComponent?> ent, bool ignoreExposed = false, bool excite = false)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
@@ -22,6 +37,17 @@ public partial class AtmosphereSystem
|
||||
return GetContainingMixture(ent, ent.Comp.GridUid, ent.Comp.MapUid, ignoreExposed, excite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GasMixture"/> that an entity is contained within.
|
||||
/// </summary>
|
||||
/// <param name="ent">The entity to get the mixture for.</param>
|
||||
/// <param name="grid">The grid that the entity may be on.</param>
|
||||
/// <param name="map">The map that the entity may be on.</param>
|
||||
/// <param name="ignoreExposed">If true, will ignore mixtures that the entity is contained in
|
||||
/// (ex. lockers and cryopods) and just get the tile mixture.</param>
|
||||
/// <param name="excite">If true, will mark the tile as active for atmosphere processing.</param>
|
||||
/// <returns>A <see cref="GasMixture"/> if one could be found, null otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public GasMixture? GetContainingMixture(
|
||||
Entity<TransformComponent?> ent,
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
@@ -49,16 +75,38 @@ public partial class AtmosphereSystem
|
||||
return GetTileMixture(grid, map, position, excite);
|
||||
}
|
||||
|
||||
public bool HasAtmosphere(EntityUid gridUid) => _atmosQuery.HasComponent(gridUid);
|
||||
/// <summary>
|
||||
/// Checks if a grid has an atmosphere.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to check.</param>
|
||||
/// <returns>True if the grid has an atmosphere, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool HasAtmosphere(EntityUid gridUid)
|
||||
{
|
||||
return _atmosQuery.HasComponent(gridUid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether a grid is simulated by Atmospherics.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to set.</param>
|
||||
/// <param name="simulated">Whether the grid should be simulated.</param>
|
||||
/// <returns>>True if the grid's simulated state was changed, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool SetSimulatedGrid(EntityUid gridUid, bool simulated)
|
||||
{
|
||||
// TODO ATMOS this event literally has no subscribers. Did this just get silently refactored out?
|
||||
var ev = new SetSimulatedGridMethodEvent(gridUid, simulated);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
|
||||
return ev.Handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a grid is simulated by Atmospherics.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to check.</param>
|
||||
/// <returns>>True if the grid is simulated, false otherwise.</returns>
|
||||
public bool IsSimulatedGrid(EntityUid gridUid)
|
||||
{
|
||||
var ev = new IsSimulatedGridMethodEvent(gridUid);
|
||||
@@ -67,24 +115,53 @@ public partial class AtmosphereSystem
|
||||
return ev.Simulated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all <see cref="TileAtmosphere"/> <see cref="GasMixture"/>s on a grid.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to get mixtures for.</param>
|
||||
/// <param name="excite">Whether to mark all tiles as active for atmosphere processing.</param>
|
||||
/// <returns>An enumerable of all gas mixtures on the grid.</returns>
|
||||
[PublicAPI]
|
||||
public IEnumerable<GasMixture> GetAllMixtures(EntityUid gridUid, bool excite = false)
|
||||
{
|
||||
var ev = new GetAllMixturesMethodEvent(gridUid, excite);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
|
||||
if (!ev.Handled)
|
||||
return Enumerable.Empty<GasMixture>();
|
||||
return [];
|
||||
|
||||
DebugTools.AssertNotNull(ev.Mixtures);
|
||||
return ev.Mixtures!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Invalidates a tile on a grid, marking it for revalidation.</para>
|
||||
///
|
||||
/// <para>Frequently used tile data like <see cref="AirtightData"/> are determined once and cached.
|
||||
/// If this tile's state changes, ex. being added or removed, then this position in the map needs to
|
||||
/// be updated.</para>
|
||||
///
|
||||
/// <para>Tiles that need to be updated are marked as invalid and revalidated before all other
|
||||
/// processing stages.</para>
|
||||
/// </summary>
|
||||
/// <param name="entity">The grid entity.</param>
|
||||
/// <param name="tile">The tile to invalidate.</param>
|
||||
[PublicAPI]
|
||||
public void InvalidateTile(Entity<GridAtmosphereComponent?> entity, Vector2i tile)
|
||||
{
|
||||
if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
||||
entity.Comp.InvalidatedCoords.Add(tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gas mixtures for a list of tiles on a grid or map.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to get mixtures from.</param>
|
||||
/// <param name="map">The map to get mixtures from.</param>
|
||||
/// <param name="tiles">The list of tiles to get mixtures for.</param>
|
||||
/// <param name="excite">Whether to mark the tiles as active for atmosphere processing.</param>
|
||||
/// <returns>>An array of gas mixtures corresponding to the input tiles.</returns>
|
||||
[PublicAPI]
|
||||
public GasMixture?[]? GetTileMixtures(
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
Entity<MapAtmosphereComponent?>? map,
|
||||
@@ -95,7 +172,7 @@ public partial class AtmosphereSystem
|
||||
var handled = false;
|
||||
|
||||
// If we've been passed a grid, try to let it handle it.
|
||||
if (grid is {} gridEnt && Resolve(gridEnt, ref gridEnt.Comp1))
|
||||
if (grid is { } gridEnt && _atmosQuery.Resolve(gridEnt, ref gridEnt.Comp1))
|
||||
{
|
||||
if (excite)
|
||||
Resolve(gridEnt, ref gridEnt.Comp2);
|
||||
@@ -145,9 +222,20 @@ public partial class AtmosphereSystem
|
||||
{
|
||||
mixtures[i] ??= GasMixture.SpaceGas;
|
||||
}
|
||||
|
||||
return mixtures;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gas mixture for a specific tile that an entity is on.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to get the tile mixture for.</param>
|
||||
/// <param name="excite">Whether to mark the tile as active for atmosphere processing.</param>
|
||||
/// <returns>A <see cref="GasMixture"/> if one could be found, null otherwise.</returns>
|
||||
/// <remarks>This does not return the <see cref="GasMixture"/> that the entity
|
||||
/// may be contained in, ex. if the entity is currently in a locker/crate with its own
|
||||
/// <see cref="GasMixture"/>.</remarks>
|
||||
[PublicAPI]
|
||||
public GasMixture? GetTileMixture(Entity<TransformComponent?> entity, bool excite = false)
|
||||
{
|
||||
if (!Resolve(entity.Owner, ref entity.Comp))
|
||||
@@ -157,6 +245,15 @@ public partial class AtmosphereSystem
|
||||
return GetTileMixture(entity.Comp.GridUid, entity.Comp.MapUid, indices, excite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gas mixture for a specific tile on a grid or map.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to get the mixture from.</param>
|
||||
/// <param name="map">The map to get the mixture from.</param>
|
||||
/// <param name="gridTile">The tile to get the mixture from.</param>
|
||||
/// <param name="excite">Whether to mark the tile as active for atmosphere processing.</param>
|
||||
/// <returns>>A <see cref="GasMixture"/> if one could be found, null otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public GasMixture? GetTileMixture(
|
||||
Entity<GridAtmosphereComponent?, GasTileOverlayComponent?>? grid,
|
||||
Entity<MapAtmosphereComponent?>? map,
|
||||
@@ -165,7 +262,7 @@ public partial class AtmosphereSystem
|
||||
{
|
||||
// If we've been passed a grid, try to let it handle it.
|
||||
if (grid is { } gridEnt
|
||||
&& Resolve(gridEnt, ref gridEnt.Comp1, false)
|
||||
&& _atmosQuery.Resolve(gridEnt, ref gridEnt.Comp1, false)
|
||||
&& gridEnt.Comp1.Tiles.TryGetValue(gridTile, out var tile))
|
||||
{
|
||||
if (excite)
|
||||
@@ -184,6 +281,13 @@ public partial class AtmosphereSystem
|
||||
return GasMixture.SpaceGas;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers a tile's <see cref="GasMixture"/> to react.
|
||||
/// </summary>
|
||||
/// <param name="gridId">The grid to react the tile on.</param>
|
||||
/// <param name="tile">The tile to react.</param>
|
||||
/// <returns>The result of the reaction.</returns>
|
||||
[PublicAPI]
|
||||
public ReactionResult ReactTile(EntityUid gridId, Vector2i tile)
|
||||
{
|
||||
var ev = new ReactTileMethodEvent(gridId, tile);
|
||||
@@ -194,15 +298,40 @@ public partial class AtmosphereSystem
|
||||
return ev.Result;
|
||||
}
|
||||
|
||||
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
|
||||
/// <summary>
|
||||
/// Checks if a tile on a grid is air-blocked in the specified directions.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to check.</param>
|
||||
/// <param name="tile">The tile on the grid to check.</param>
|
||||
/// <param name="directions">The directions to check for air-blockage.</param>
|
||||
/// <param name="mapGridComp">Optional map grid component associated with the grid.</param>
|
||||
/// <returns>True if the tile is air-blocked in the specified directions, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool IsTileAirBlocked(EntityUid gridUid,
|
||||
Vector2i tile,
|
||||
AtmosDirection directions = AtmosDirection.All,
|
||||
MapGridComponent? mapGridComp = null)
|
||||
{
|
||||
if (!Resolve(gridUid, ref mapGridComp, false))
|
||||
return false;
|
||||
|
||||
// TODO ATMOS: This reconstructs the data instead of getting the cached version. Might want to include a method to get the cached version later.
|
||||
var data = GetAirtightData(gridUid, mapGridComp, tile);
|
||||
return data.BlockedDirections.IsFlagSet(directions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a tile on a grid or map is space as defined by a tile's definition of space.
|
||||
/// Some tiles can hold back space and others cannot - for example, plating can hold
|
||||
/// back space, whereas scaffolding cannot, exposing the map atmosphere beneath.
|
||||
/// </summary>
|
||||
/// <remarks>This does not check if the <see cref="GasMixture"/> on the tile is space,
|
||||
/// it only checks the current tile's ability to hold back space.</remarks>
|
||||
/// <param name="grid">The grid to check.</param>
|
||||
/// <param name="map">The map to check.</param>
|
||||
/// <param name="tile">The tile to check.</param>
|
||||
/// <returns>True if the tile is space, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool IsTileSpace(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?>? map, Vector2i tile)
|
||||
{
|
||||
if (grid is { } gridEnt && _atmosQuery.Resolve(gridEnt, ref gridEnt.Comp, false)
|
||||
@@ -219,28 +348,77 @@ public partial class AtmosphereSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the gas mixture on a tile is "probably safe".
|
||||
/// Probably safe is defined as having at least air alarm-grade safe pressure and temperature.
|
||||
/// (more than 260K, less than 360K, and between safe low and high pressure as defined in
|
||||
/// <see cref="Atmospherics.WarningLowPressure"/> and <see cref="Atmospherics.WarningHighPressure"/>)
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to check.</param>
|
||||
/// <param name="map">The map to check.</param>
|
||||
/// <param name="tile">The tile to check.</param>
|
||||
/// <returns>True if the tile's mixture is probably safe, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool IsTileMixtureProbablySafe(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?> map, Vector2i tile)
|
||||
{
|
||||
return IsMixtureProbablySafe(GetTileMixture(grid, map, tile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the heat capacity of the gas mixture on a tile.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to check.</param>
|
||||
/// <param name="map">The map to check.</param>
|
||||
/// <param name="tile">The tile on the grid/map to check.</param>
|
||||
/// <returns>>The heat capacity of the tile's mixture, or the heat capacity of space if a mixture could not be found.</returns>
|
||||
[PublicAPI]
|
||||
public float GetTileHeatCapacity(Entity<GridAtmosphereComponent?>? grid, Entity<MapAtmosphereComponent?> map, Vector2i tile)
|
||||
{
|
||||
return GetHeatCapacity(GetTileMixture(grid, map, tile) ?? GasMixture.SpaceGas);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator for the adjacent tile mixtures of a tile on a grid.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to get adjacent tile mixtures from.</param>
|
||||
/// <param name="tile">The tile to get adjacent mixtures for.</param>
|
||||
/// <param name="includeBlocked">Whether to include blocked adjacent tiles.</param>
|
||||
/// <param name="excite">Whether to mark the adjacent tiles as active for atmosphere processing.</param>
|
||||
/// <returns>An enumerator for the adjacent tile mixtures.</returns>
|
||||
[PublicAPI]
|
||||
public TileMixtureEnumerator GetAdjacentTileMixtures(Entity<GridAtmosphereComponent?> grid, Vector2i tile, bool includeBlocked = false, bool excite = false)
|
||||
{
|
||||
// TODO ATMOS includeBlocked and excite parameters are unhandled currently.
|
||||
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
|
||||
return TileMixtureEnumerator.Empty;
|
||||
|
||||
return !grid.Comp.Tiles.TryGetValue(tile, out var atmosTile)
|
||||
? TileMixtureEnumerator.Empty
|
||||
: new(atmosTile.AdjacentTiles);
|
||||
: new TileMixtureEnumerator(atmosTile.AdjacentTiles);
|
||||
}
|
||||
|
||||
public void HotspotExpose(Entity<GridAtmosphereComponent?> grid, Vector2i tile, float exposedTemperature, float exposedVolume,
|
||||
EntityUid? sparkSourceUid = null, bool soh = false)
|
||||
/// <summary>
|
||||
/// Exposes a tile to a hotspot of given temperature and volume, igniting it if conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to expose the tile on.</param>
|
||||
/// <param name="tile">The tile to expose.</param>
|
||||
/// <param name="exposedTemperature">The temperature of the hotspot to expose.
|
||||
/// You can think of this as exposing a temperature of a flame.</param>
|
||||
/// <param name="exposedVolume">The volume of the hotspot to expose.
|
||||
/// You can think of this as how big the flame is initially.
|
||||
/// Bigger flames will ramp a fire faster.</param>
|
||||
/// <param name="soh">Whether to "boost" a fire that's currently on the tile already.
|
||||
/// Does nothing if the tile isn't already a hotspot.
|
||||
/// This clamps the temperature and volume of the hotspot to the maximum
|
||||
/// of the provided parameters and whatever's on the tile.</param>
|
||||
/// <param name="sparkSourceUid">Entity that started the exposure for admin logging.</param>
|
||||
[PublicAPI]
|
||||
public void HotspotExpose(Entity<GridAtmosphereComponent?> grid,
|
||||
Vector2i tile,
|
||||
float exposedTemperature,
|
||||
float exposedVolume,
|
||||
EntityUid? sparkSourceUid = null,
|
||||
bool soh = false)
|
||||
{
|
||||
if (!_atmosQuery.Resolve(grid, ref grid.Comp, false))
|
||||
return;
|
||||
@@ -249,8 +427,26 @@ public partial class AtmosphereSystem
|
||||
HotspotExpose(grid.Comp, atmosTile, exposedTemperature, exposedVolume, soh, sparkSourceUid);
|
||||
}
|
||||
|
||||
public void HotspotExpose(TileAtmosphere tile, float exposedTemperature, float exposedVolume,
|
||||
EntityUid? sparkSourceUid = null, bool soh = false)
|
||||
/// <summary>
|
||||
/// Exposes a tile to a hotspot of given temperature and volume, igniting it if conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="tile">The <see cref="TileAtmosphere"/> to expose.</param>
|
||||
/// <param name="exposedTemperature">The temperature of the hotspot to expose.
|
||||
/// You can think of this as exposing a temperature of a flame.</param>
|
||||
/// <param name="exposedVolume">The volume of the hotspot to expose.
|
||||
/// You can think of this as how big the flame is initially.
|
||||
/// Bigger flames will ramp a fire faster.</param>
|
||||
/// <param name="soh">Whether to "boost" a fire that's currently on the tile already.
|
||||
/// Does nothing if the tile isn't already a hotspot.
|
||||
/// This clamps the temperature and volume of the hotspot to the maximum
|
||||
/// of the provided parameters and whatever's on the tile.</param>
|
||||
/// <param name="sparkSourceUid">Entity that started the exposure for admin logging.</param>
|
||||
[PublicAPI]
|
||||
public void HotspotExpose(TileAtmosphere tile,
|
||||
float exposedTemperature,
|
||||
float exposedVolume,
|
||||
EntityUid? sparkSourceUid = null,
|
||||
bool soh = false)
|
||||
{
|
||||
if (!_atmosQuery.TryGetComponent(tile.GridIndex, out var atmos))
|
||||
return;
|
||||
@@ -259,12 +455,25 @@ public partial class AtmosphereSystem
|
||||
HotspotExpose(atmos, tile, exposedTemperature, exposedVolume, soh, sparkSourceUid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extinguishes a hotspot on a tile.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to extinguish the hotspot on.</param>
|
||||
/// <param name="tile">The tile on the grid to extinguish the hotspot on.</param>
|
||||
[PublicAPI]
|
||||
public void HotspotExtinguish(EntityUid gridUid, Vector2i tile)
|
||||
{
|
||||
var ev = new HotspotExtinguishMethodEvent(gridUid, tile);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a hotspot is active on a tile.
|
||||
/// </summary>
|
||||
/// <param name="gridUid">The grid to check.</param>
|
||||
/// <param name="tile">The tile on the grid to check.</param>
|
||||
/// <returns>True if a hotspot is active on the tile, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool IsHotspotActive(EntityUid gridUid, Vector2i tile)
|
||||
{
|
||||
var ev = new IsHotspotActiveMethodEvent(gridUid, tile);
|
||||
@@ -274,11 +483,25 @@ public partial class AtmosphereSystem
|
||||
return ev.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="PipeNet"/> to a grid.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to add the pipe net to.</param>
|
||||
/// <param name="pipeNet">The pipe net to add.</param>
|
||||
/// <returns>True if the pipe net was added, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool AddPipeNet(Entity<GridAtmosphereComponent?> grid, PipeNet pipeNet)
|
||||
{
|
||||
return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Add(pipeNet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <see cref="PipeNet"/> from a grid.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to remove the pipe net from.</param>
|
||||
/// <param name="pipeNet">The pipe net to remove.</param>
|
||||
/// <returns>True if the pipe net was removed, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool RemovePipeNet(Entity<GridAtmosphereComponent?> grid, PipeNet pipeNet)
|
||||
{
|
||||
// Technically this event can be fired even on grids that don't
|
||||
@@ -292,6 +515,13 @@ public partial class AtmosphereSystem
|
||||
return _atmosQuery.Resolve(grid, ref grid.Comp, false) && grid.Comp.PipeNets.Remove(pipeNet);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an entity with an <see cref="AtmosDeviceComponent"/> to a grid's list of atmos devices.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to add the device to.</param>
|
||||
/// <param name="device">The device to add.</param>
|
||||
/// <returns>True if the device was added, false otherwise.</returns>
|
||||
[PublicAPI]
|
||||
public bool AddAtmosDevice(Entity<GridAtmosphereComponent?> grid, Entity<AtmosDeviceComponent> device)
|
||||
{
|
||||
DebugTools.Assert(device.Comp.JoinedGrid == null);
|
||||
@@ -307,6 +537,12 @@ public partial class AtmosphereSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity with an <see cref="AtmosDeviceComponent"/> from a grid's list of atmos devices.
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid to remove the device from.</param>
|
||||
/// <param name="device">The device to remove.</param>
|
||||
/// <returns>True if the device was removed, false otherwise.</returns>
|
||||
public bool RemoveAtmosDevice(Entity<GridAtmosphereComponent?> grid, Entity<AtmosDeviceComponent> device)
|
||||
{
|
||||
DebugTools.Assert(device.Comp.JoinedGrid == grid);
|
||||
@@ -418,23 +654,44 @@ public partial class AtmosphereSystem
|
||||
return contains;
|
||||
}
|
||||
|
||||
[ByRefEvent] private record struct SetSimulatedGridMethodEvent
|
||||
(EntityUid Grid, bool Simulated, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct SetSimulatedGridMethodEvent(
|
||||
EntityUid Grid,
|
||||
bool Simulated,
|
||||
bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct IsSimulatedGridMethodEvent
|
||||
(EntityUid Grid, bool Simulated = false, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct IsSimulatedGridMethodEvent(
|
||||
EntityUid Grid,
|
||||
bool Simulated = false,
|
||||
bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct GetAllMixturesMethodEvent
|
||||
(EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct GetAllMixturesMethodEvent(
|
||||
EntityUid Grid,
|
||||
bool Excite = false,
|
||||
IEnumerable<GasMixture>? Mixtures = null,
|
||||
bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct ReactTileMethodEvent
|
||||
(EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct ReactTileMethodEvent(
|
||||
EntityUid GridId,
|
||||
Vector2i Tile,
|
||||
ReactionResult Result = default,
|
||||
bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct HotspotExtinguishMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct HotspotExtinguishMethodEvent(
|
||||
EntityUid Grid,
|
||||
Vector2i Tile,
|
||||
bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct IsHotspotActiveMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
|
||||
[ByRefEvent]
|
||||
private record struct IsHotspotActiveMethodEvent(
|
||||
EntityUid Grid,
|
||||
Vector2i Tile,
|
||||
bool Result = false,
|
||||
bool Handled = false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,9 +11,15 @@ namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public partial class AtmosphereSystem
|
||||
{
|
||||
/*
|
||||
Partial class that stores miscellaneous utility methods for Atmospherics.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Gets the particular price of an air mixture.
|
||||
/// Gets the particular price of a <see cref="GasMixture"/>.
|
||||
/// </summary>
|
||||
/// <param name="mixture">The <see cref="GasMixture"/> to get the price of.</param>
|
||||
/// <returns>The price of the gas mixture.</returns>
|
||||
public double GetPrice(GasMixture mixture)
|
||||
{
|
||||
float basePrice = 0; // moles of gas * price/mole
|
||||
@@ -26,7 +32,7 @@ public partial class AtmosphereSystem
|
||||
maxComponent = Math.Max(maxComponent, mixture.Moles[i]);
|
||||
}
|
||||
|
||||
// Pay more for gas canisters that are more pure
|
||||
// Pay more for gas canisters that are purer
|
||||
float purity = 1;
|
||||
if (totalMoles > 0)
|
||||
{
|
||||
@@ -36,12 +42,32 @@ public partial class AtmosphereSystem
|
||||
return basePrice * purity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks a tile's visual overlay as needing to be redetermined.</para>
|
||||
///
|
||||
/// <para>A tile's overlay (how it looks like, ex. water vapor's texture)
|
||||
/// is determined via determining how much gas there is on the tile.
|
||||
/// This is expensive to do for every tile/gas that may have a custom overlay,
|
||||
/// so its done once and only updated when it needs to be updated.</para>
|
||||
/// </summary>
|
||||
/// <param name="grid">The grid the tile is on.</param>
|
||||
/// <param name="tile">The tile to invalidate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void InvalidateVisuals(Entity<GasTileOverlayComponent?> grid, Vector2i tile)
|
||||
{
|
||||
_gasTileOverlaySystem.Invalidate(grid, tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks a tile's visual overlay as needing to be redetermined.</para>
|
||||
///
|
||||
/// <para>A tile's overlay (how it looks like, ex. water vapor's texture)
|
||||
/// is determined via determining how much gas there is on the tile.
|
||||
/// This is expensive to do for every tile/gas that may have a custom overlay,
|
||||
/// so its done once and only updated when it needs to be updated.</para>
|
||||
/// </summary>
|
||||
/// <param name="ent">The grid the tile is on.</param>
|
||||
/// <param name="tile">The tile to invalidate.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void InvalidateVisuals(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
@@ -79,6 +105,18 @@ public partial class AtmosphereSystem
|
||||
bool NoAirWhenBlocked,
|
||||
bool FixVacuum);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="AirtightData"/> for a <see cref="TileAtmosphere"/>
|
||||
/// immediately.
|
||||
/// </summary>
|
||||
/// <remarks>This method is extremely important if you are doing something in Atmospherics
|
||||
/// that is time-sensitive! <see cref="AirtightData"/> is cached and invalidated on
|
||||
/// a cycle, so airtight changes performed during or after an invalidation will
|
||||
/// not take effect until the next Atmospherics tick!</remarks>
|
||||
/// <param name="uid">The entity the grid is on.</param>
|
||||
/// <param name="atmos">The <see cref="GridAtmosphereComponent"/> the tile is on.</param>
|
||||
/// <param name="grid">The <see cref="MapGridComponent"/> the tile is on.</param>
|
||||
/// <param name="tile">The <see cref="TileAtmosphere"/> to update.</param>
|
||||
private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
|
||||
{
|
||||
var oldBlocked = tile.AirtightData.BlockedDirections;
|
||||
@@ -91,6 +129,15 @@ public partial class AtmosphereSystem
|
||||
ExcitedGroupDispose(atmos, tile.ExcitedGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves current <see cref="AirtightData"/> for a tile on a grid.
|
||||
/// This is determined on-the-fly, not from cached data, so it will reflect
|
||||
/// changes done in the current Atmospherics tick.
|
||||
/// </summary>
|
||||
/// <param name="uid">The entity the grid is on.</param>
|
||||
/// <param name="grid">The <see cref="MapGridComponent"/> the tile is on.</param>
|
||||
/// <param name="tile">The indices of the tile.</param>
|
||||
/// <returns>The current <see cref="AirtightData"/> for the tile.</returns>
|
||||
private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
|
||||
{
|
||||
var blockedDirs = AtmosDirection.Invalid;
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed partial class GameTicker
|
||||
|
||||
private bool StartPreset(ICommonSession[] origReadyPlayers, bool force)
|
||||
{
|
||||
_sawmill.Info($"Attempting to start preset '{CurrentPreset?.ID}'");
|
||||
var startAttempt = new RoundStartAttemptEvent(origReadyPlayers, force);
|
||||
RaiseLocalEvent(startAttempt);
|
||||
|
||||
@@ -56,9 +57,12 @@ public sealed partial class GameTicker
|
||||
var fallbackPresets = _cfg.GetCVar(CCVars.GameLobbyFallbackPreset).Split(",");
|
||||
var startFailed = true;
|
||||
|
||||
_sawmill.Info($"Fallback - Failed to start round, attempting to start fallback presets.");
|
||||
foreach (var preset in fallbackPresets)
|
||||
{
|
||||
_sawmill.Info($"Fallback - Clearing up gamerules");
|
||||
ClearGameRules();
|
||||
_sawmill.Info($"Fallback - Attempting to start '{preset}'");
|
||||
SetGamePreset(preset, resetDelay: 1);
|
||||
AddGamePresetRules();
|
||||
StartGamePresetRules();
|
||||
@@ -76,6 +80,7 @@ public sealed partial class GameTicker
|
||||
startFailed = false;
|
||||
break;
|
||||
}
|
||||
_sawmill.Info($"Fallback - '{preset}' failed to start.");
|
||||
}
|
||||
|
||||
if (startFailed)
|
||||
@@ -87,6 +92,7 @@ public sealed partial class GameTicker
|
||||
|
||||
else
|
||||
{
|
||||
_sawmill.Info($"Fallback - Failed to start preset but fallbacks are disabled. Returning to Lobby.");
|
||||
FailedPresetRestart();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(XenoborgsRuleSystem))]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class XenoborgsRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// When the round will next check for round end.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan? NextRoundEndCheck;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time between each check for the end of the round.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan EndCheckDelay = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// After this amount of the crew become xenoborgs, the shuttle will be automatically called.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float XenoborgShuttleCallPercentage = 0.7f;
|
||||
|
||||
/// <summary>
|
||||
/// If the announcment of the death of the mothership core was sent
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool MothershipCoreDeathAnnouncmentSent = false;
|
||||
}
|
||||
@@ -129,5 +129,6 @@ public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponen
|
||||
}
|
||||
args.AddLine(Loc.GetString("point-scoreboard-header"));
|
||||
args.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup());
|
||||
args.AddLine("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
||||
while (query.MoveNext(out var uid, out _, out var gameRule))
|
||||
{
|
||||
var minPlayers = gameRule.MinPlayers;
|
||||
var name = ToPrettyString(uid);
|
||||
|
||||
if (args.Players.Length >= minPlayers)
|
||||
continue;
|
||||
|
||||
@@ -46,8 +48,10 @@ public abstract partial class GameRuleSystem<T> : EntitySystem where T : ICompon
|
||||
ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players",
|
||||
("readyPlayersCount", args.Players.Length),
|
||||
("minimumPlayers", minPlayers),
|
||||
("presetName", ToPrettyString(uid))));
|
||||
("presetName", name)));
|
||||
args.Cancel();
|
||||
//TODO remove this once announcements are logged
|
||||
Log.Info($"Rule '{name}' requires {minPlayers} players, but only {args.Players.Length} are ready.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -111,6 +111,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
||||
{
|
||||
args.AddLine(Loc.GetString("nukeops-list-name-user", ("name", name), ("user", sessionData.UserName)));
|
||||
}
|
||||
args.AddLine("");
|
||||
}
|
||||
|
||||
private void OnNukeExploded(NukeExplodedEvent ev)
|
||||
|
||||
@@ -118,6 +118,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
||||
|
||||
// TODO: someone suggested listing all alive? revs maybe implement at some point
|
||||
}
|
||||
args.AddLine("");
|
||||
}
|
||||
|
||||
private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
|
||||
|
||||
@@ -65,6 +65,12 @@ public sealed class RuleGridsSystem : GameRuleSystem<RuleGridsComponent>
|
||||
if (_whitelist.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid))
|
||||
continue;
|
||||
|
||||
if (TryComp<GridSpawnPointWhitelistComponent>(uid, out var gridSpawnPointWhitelistComponent))
|
||||
{
|
||||
if (!_whitelist.CheckBoth(args.Entity, gridSpawnPointWhitelistComponent.Blacklist, gridSpawnPointWhitelistComponent.Whitelist))
|
||||
continue;
|
||||
}
|
||||
|
||||
args.Coordinates.Add(_transform.GetMapCoordinates(xform));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ public sealed class SurvivorRuleSystem : GameRuleSystem<SurvivorRuleComponent>
|
||||
args.AddLine(Loc.GetString("survivor-round-end-dead-count", ("deadCount", deadSurvivors)));
|
||||
args.AddLine(Loc.GetString("survivor-round-end-alive-count", ("aliveCount", aliveMarooned)));
|
||||
args.AddLine(Loc.GetString("survivor-round-end-alive-on-shuttle-count", ("aliveCount", aliveOnShuttle)));
|
||||
args.AddLine("");
|
||||
|
||||
// Player manifest at EOR shows who's a survivor so no need for extra info here.
|
||||
}
|
||||
|
||||
168
Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs
Normal file
168
Content.Server/GameTicking/Rules/XenoborgsRuleSystem.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Xenoborgs.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class XenoborgsRuleSystem : GameRuleSystem<XenoborgsRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private static readonly Color AnnouncmentColor = Color.Gold;
|
||||
|
||||
public void SendXenoborgDeathAnnouncement(Entity<XenoborgsRuleComponent> ent, bool mothershipCoreAlive)
|
||||
{
|
||||
if (ent.Comp.MothershipCoreDeathAnnouncmentSent)
|
||||
return;
|
||||
|
||||
var status = mothershipCoreAlive ? "alive" : "dead";
|
||||
_chatSystem.DispatchGlobalAnnouncement(
|
||||
Loc.GetString($"xenoborgs-no-more-threat-mothership-core-{status}-announcement"),
|
||||
colorOverride: AnnouncmentColor);
|
||||
}
|
||||
|
||||
public void SendMothershipDeathAnnouncement(Entity<XenoborgsRuleComponent> ent)
|
||||
{
|
||||
_chatSystem.DispatchGlobalAnnouncement(
|
||||
Loc.GetString("mothership-destroyed-announcement"),
|
||||
colorOverride: AnnouncmentColor);
|
||||
|
||||
ent.Comp.MothershipCoreDeathAnnouncmentSent = true;
|
||||
}
|
||||
|
||||
// TODO: Refactor the end of round text
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
XenoborgsRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
base.AppendRoundEndText(uid, component, gameRule, ref args);
|
||||
|
||||
var numXenoborgs = GetNumberXenoborgs();
|
||||
var numHumans = _mindSystem.GetAliveHumans().Count;
|
||||
|
||||
if (numXenoborgs < 5)
|
||||
args.AddLine(Loc.GetString("xenoborgs-crewmajor"));
|
||||
else if (4 * numXenoborgs < numHumans)
|
||||
args.AddLine(Loc.GetString("xenoborgs-crewmajor"));
|
||||
else if (2 * numXenoborgs < numHumans)
|
||||
args.AddLine(Loc.GetString("xenoborgs-crewminor"));
|
||||
else if (1.5 * numXenoborgs < numHumans)
|
||||
args.AddLine(Loc.GetString("xenoborgs-neutral"));
|
||||
else if (numXenoborgs < numHumans)
|
||||
args.AddLine(Loc.GetString("xenoborgs-borgsminor"));
|
||||
else
|
||||
args.AddLine(Loc.GetString("xenoborgs-borgsmajor"));
|
||||
|
||||
var numMothershipCores = GetNumberMothershipCores();
|
||||
|
||||
if (numMothershipCores == 0)
|
||||
args.AddLine(Loc.GetString("xenoborgs-cond-all-xenoborgs-dead-core-dead"));
|
||||
else if (numXenoborgs == 0)
|
||||
args.AddLine(Loc.GetString("xenoborgs-cond-all-xenoborgs-dead-core-alive"));
|
||||
else
|
||||
args.AddLine(Loc.GetString("xenoborgs-cond-xenoborgs-alive", ("count", numXenoborgs)));
|
||||
|
||||
args.AddLine(Loc.GetString("xenoborgs-list-start"));
|
||||
|
||||
var antags = _antag.GetAntagIdentifiers(uid);
|
||||
|
||||
foreach (var (_, sessionData, name) in antags)
|
||||
{
|
||||
args.AddLine(Loc.GetString("xenoborgs-list", ("name", name), ("user", sessionData.UserName)));
|
||||
}
|
||||
args.AddLine("");
|
||||
}
|
||||
|
||||
private void CheckRoundEnd(XenoborgsRuleComponent xenoborgsRuleComponent)
|
||||
{
|
||||
var numXenoborgs = GetNumberXenoborgs();
|
||||
var numHumans = _mindSystem.GetAliveHumans().Count;
|
||||
|
||||
if ((float)numXenoborgs / (numHumans + numXenoborgs) > xenoborgsRuleComponent.XenoborgShuttleCallPercentage)
|
||||
{
|
||||
foreach (var station in _station.GetStations())
|
||||
{
|
||||
_chatSystem.DispatchStationAnnouncement(station, Loc.GetString("xenoborg-shuttle-call"), colorOverride: Color.BlueViolet);
|
||||
}
|
||||
_roundEnd.RequestRoundEnd(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, XenoborgsRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, XenoborgsRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (!component.NextRoundEndCheck.HasValue || component.NextRoundEndCheck > _timing.CurTime)
|
||||
return;
|
||||
|
||||
CheckRoundEnd(component);
|
||||
component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of xenoborgs
|
||||
/// </summary>
|
||||
/// <param name="playerControlled">if it should only include xenoborgs with a mind</param>
|
||||
/// <param name="alive">if it should only include xenoborgs that are alive</param>
|
||||
/// <returns>the number of xenoborgs</returns>
|
||||
private int GetNumberXenoborgs(bool playerControlled = true, bool alive = true)
|
||||
{
|
||||
var numberXenoborgs = 0;
|
||||
|
||||
var query = EntityQueryEnumerator<XenoborgComponent>();
|
||||
while (query.MoveNext(out var xenoborg, out _))
|
||||
{
|
||||
if (HasComp<MothershipCoreComponent>(xenoborg))
|
||||
continue;
|
||||
|
||||
if (playerControlled && !_mindSystem.TryGetMind(xenoborg, out _, out _))
|
||||
continue;
|
||||
|
||||
if (alive && !_mobState.IsAlive(xenoborg))
|
||||
continue;
|
||||
|
||||
numberXenoborgs++;
|
||||
}
|
||||
|
||||
return numberXenoborgs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of xenoborg cores
|
||||
/// </summary>
|
||||
/// <returns>the number of xenoborg cores</returns>
|
||||
private int GetNumberMothershipCores()
|
||||
{
|
||||
var numberMothershipCores = 0;
|
||||
|
||||
var mothershipCoreQuery = EntityQueryEnumerator<MothershipCoreComponent>();
|
||||
while (mothershipCoreQuery.MoveNext(out _, out _))
|
||||
{
|
||||
numberMothershipCores++;
|
||||
}
|
||||
|
||||
return numberMothershipCores;
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
||||
("name", meta.EntityName),
|
||||
("username", username)));
|
||||
}
|
||||
args.AddLine("");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -333,7 +333,8 @@ namespace Content.Server.Ghost
|
||||
if (_followerSystem.GetMostGhostFollowed() is not {} target)
|
||||
return;
|
||||
|
||||
WarpTo(uid, target);
|
||||
// If there is a ghostnado happening you almost definitely wanna join it, so we automatically follow instead of just warping.
|
||||
_followerSystem.StartFollowingEntity(uid, target);
|
||||
}
|
||||
|
||||
private void WarpTo(EntityUid uid, EntityUid target)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Gravity
|
||||
{
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GravityGeneratorSystem))]
|
||||
public sealed partial class GravityGeneratorComponent : SharedGravityGeneratorComponent
|
||||
{
|
||||
[DataField("lightRadiusMin")] public float LightRadiusMin { get; set; }
|
||||
[DataField("lightRadiusMax")] public float LightRadiusMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the gravity generator currently "producing" gravity?
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool GravityActive { get; set; } = false;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Content.Shared.Gravity;
|
||||
|
||||
namespace Content.Server.Gravity;
|
||||
|
||||
public sealed class GravityGeneratorSystem : EntitySystem
|
||||
public sealed class GravityGeneratorSystem : SharedGravityGeneratorSystem
|
||||
{
|
||||
[Dependency] private readonly GravitySystem _gravitySystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _lights = default!;
|
||||
@@ -36,6 +36,7 @@ public sealed class GravityGeneratorSystem : EntitySystem
|
||||
private void OnActivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineActivatedEvent args)
|
||||
{
|
||||
ent.Comp.GravityActive = true;
|
||||
Dirty(ent, ent.Comp);
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
@@ -48,6 +49,7 @@ public sealed class GravityGeneratorSystem : EntitySystem
|
||||
private void OnDeactivated(Entity<GravityGeneratorComponent> ent, ref ChargedMachineDeactivatedEvent args)
|
||||
{
|
||||
ent.Comp.GravityActive = false;
|
||||
Dirty(ent, ent.Comp);
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
|
||||
{
|
||||
if (entity.Comp.State == EmergencyLightState.On)
|
||||
{
|
||||
if (!_battery.TryUseCharge(entity.Owner, entity.Comp.Wattage * frameTime, battery))
|
||||
if (!_battery.TryUseCharge((entity.Owner, battery), entity.Comp.Wattage * frameTime))
|
||||
{
|
||||
SetState(entity.Owner, entity.Comp, EmergencyLightState.Empty);
|
||||
TurnOff(entity);
|
||||
@@ -153,8 +153,8 @@ public sealed class EmergencyLightSystem : SharedEmergencyLightSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
_battery.SetCharge(entity.Owner, battery.CurrentCharge + entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency, battery);
|
||||
if (_battery.IsFull(entity, battery))
|
||||
_battery.SetCharge((entity.Owner, battery), battery.CurrentCharge + entity.Comp.ChargingWattage * frameTime * entity.Comp.ChargingEfficiency);
|
||||
if (_battery.IsFull((entity.Owner, battery)))
|
||||
{
|
||||
if (TryComp<ApcPowerReceiverComponent>(entity.Owner, out var receiver))
|
||||
{
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace Content.Server.Light.EntitySystems
|
||||
_appearance.SetData(uid, HandheldLightVisuals.Power, HandheldLightPowerStates.Dying, appearanceComponent);
|
||||
}
|
||||
|
||||
if (component.Activated && !_battery.TryUseCharge(batteryUid.Value, component.Wattage * frameTime, battery))
|
||||
if (component.Activated && !_battery.TryUseCharge((batteryUid.Value, battery), component.Wattage * frameTime))
|
||||
TurnOff(uid, false);
|
||||
|
||||
UpdateLevel(uid);
|
||||
|
||||
@@ -340,7 +340,7 @@ public sealed partial class MechSystem : SharedMechSystem
|
||||
if (!TryComp<BatteryComponent>(battery, out var batteryComp))
|
||||
return false;
|
||||
|
||||
_battery.SetCharge(battery!.Value, batteryComp.CurrentCharge + delta.Float(), batteryComp);
|
||||
_battery.SetCharge((battery.Value, batteryComp), batteryComp.CurrentCharge + delta.Float());
|
||||
if (batteryComp.CurrentCharge != component.Energy) //if there's a discrepency, we have to resync them
|
||||
{
|
||||
Log.Debug($"Battery charge was not equal to mech charge. Battery {batteryComp.CurrentCharge}. Mech {component.Energy}");
|
||||
|
||||
@@ -95,17 +95,17 @@ public sealed class BatteryDrainerSystem : SharedBatteryDrainerSystem
|
||||
// higher tier storages can charge more
|
||||
var maxDrained = pnb.MaxSupply * comp.DrainTime;
|
||||
var input = Math.Min(Math.Min(available, required / comp.DrainEfficiency), maxDrained);
|
||||
if (!_battery.TryUseCharge(target, input, targetBattery))
|
||||
if (!_battery.TryUseCharge((target, targetBattery), input))
|
||||
return false;
|
||||
|
||||
var output = input * comp.DrainEfficiency;
|
||||
_battery.SetCharge(comp.BatteryUid.Value, battery.CurrentCharge + output, battery);
|
||||
_battery.SetCharge((comp.BatteryUid.Value, battery), battery.CurrentCharge + output);
|
||||
// TODO: create effect message or something
|
||||
Spawn("EffectSparks", Transform(target).Coordinates);
|
||||
_audio.PlayPvs(comp.SparkSound, target);
|
||||
_popup.PopupEntity(Loc.GetString("battery-drainer-success", ("battery", target)), uid, uid);
|
||||
|
||||
// repeat the doafter until battery is full
|
||||
return !_battery.IsFull(comp.BatteryUid.Value, battery);
|
||||
return !_battery.IsFull((comp.BatteryUid.Value, battery));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
/// <inheritdoc/>
|
||||
public override bool TryUseCharge(EntityUid user, float charge)
|
||||
{
|
||||
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery);
|
||||
return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge((uid.Value, battery), charge);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Content.Server.Power.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Self-recharging battery.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class BatterySelfRechargerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Does the entity auto recharge?
|
||||
/// </summary>
|
||||
[DataField] public bool AutoRecharge;
|
||||
|
||||
/// <summary>
|
||||
/// At what rate does the entity automatically recharge?
|
||||
/// </summary>
|
||||
[DataField] public float AutoRechargeRate;
|
||||
|
||||
/// <summary>
|
||||
/// Should this entity stop automatically recharging if a charge is used?
|
||||
/// </summary>
|
||||
[DataField] public bool AutoRechargePause = false;
|
||||
|
||||
/// <summary>
|
||||
/// How long should the entity stop automatically recharging if a charge is used?
|
||||
/// </summary>
|
||||
[DataField] public float AutoRechargePauseTime = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system.
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0f);
|
||||
}
|
||||
}
|
||||
104
Content.Server/Power/EntitySystems/BatterySystem.API.cs
Normal file
104
Content.Server/Power/EntitySystems/BatterySystem.API.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.Components;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems;
|
||||
|
||||
public sealed partial class BatterySystem
|
||||
{
|
||||
public override float ChangeCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return 0;
|
||||
|
||||
var newValue = Math.Clamp(ent.Comp.CurrentCharge + amount, 0, ent.Comp.MaxCharge);
|
||||
var delta = newValue - ent.Comp.CurrentCharge;
|
||||
ent.Comp.CurrentCharge = newValue;
|
||||
|
||||
TrySetChargeCooldown(ent.Owner);
|
||||
|
||||
var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
return delta;
|
||||
}
|
||||
|
||||
public override float UseCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
if (amount <= 0 || !Resolve(ent, ref ent.Comp) || ent.Comp.CurrentCharge == 0)
|
||||
return 0;
|
||||
|
||||
return ChangeCharge(ent, -amount);
|
||||
}
|
||||
|
||||
public override bool TryUseCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false) || amount > ent.Comp.CurrentCharge)
|
||||
return false;
|
||||
|
||||
UseCharge(ent, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void SetCharge(Entity<BatteryComponent?> ent, float value)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
var oldCharge = ent.Comp.CurrentCharge;
|
||||
ent.Comp.CurrentCharge = MathHelper.Clamp(value, 0, ent.Comp.MaxCharge);
|
||||
if (MathHelper.CloseTo(ent.Comp.CurrentCharge, oldCharge) &&
|
||||
!(oldCharge != ent.Comp.CurrentCharge && ent.Comp.CurrentCharge == ent.Comp.MaxCharge))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
public override void SetMaxCharge(Entity<BatteryComponent?> ent, float value)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
var old = ent.Comp.MaxCharge;
|
||||
ent.Comp.MaxCharge = Math.Max(value, 0);
|
||||
ent.Comp.CurrentCharge = Math.Min(ent.Comp.CurrentCharge, ent.Comp.MaxCharge);
|
||||
if (MathHelper.CloseTo(ent.Comp.MaxCharge, old))
|
||||
return;
|
||||
|
||||
var ev = new ChargeChangedEvent(ent.Comp.CurrentCharge, ent.Comp.MaxCharge);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
||||
public override void TrySetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (ent.Comp.AutoRechargePauseTime == TimeSpan.Zero)
|
||||
return; // no recharge pause
|
||||
|
||||
if (_timing.CurTime + ent.Comp.AutoRechargePauseTime <= ent.Comp.NextAutoRecharge)
|
||||
return; // the current pause is already longer
|
||||
|
||||
SetChargeCooldown(ent, ent.Comp.AutoRechargePauseTime);
|
||||
}
|
||||
|
||||
public override void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent, TimeSpan cooldown)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.NextAutoRecharge = _timing.CurTime + cooldown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the battery is full.
|
||||
/// </summary>
|
||||
public bool IsFull(Entity<BatteryComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
return ent.Comp.CurrentCharge >= ent.Comp.MaxCharge;
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@ using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Power.EntitySystems
|
||||
{
|
||||
namespace Content.Server.Power.EntitySystems;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class BatterySystem : SharedBatterySystem
|
||||
public sealed partial class BatterySystem : SharedBatterySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace Content.Server.Power.EntitySystems
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ExaminableBatteryComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
|
||||
SubscribeLocalEvent<BatteryComponent, RejuvenateEvent>(OnBatteryRejuvenate);
|
||||
SubscribeLocalEvent<PowerNetworkBatteryComponent, RejuvenateEvent>(OnNetBatteryRejuvenate);
|
||||
SubscribeLocalEvent<BatteryComponent, PriceCalculationEvent>(CalculateBatteryPrice);
|
||||
SubscribeLocalEvent<BatteryComponent, ChangeChargeEvent>(OnChangeCharge);
|
||||
SubscribeLocalEvent<BatteryComponent, GetChargeEvent>(OnGetCharge);
|
||||
@@ -31,27 +31,27 @@ namespace Content.Server.Power.EntitySystems
|
||||
SubscribeLocalEvent<NetworkBatteryPostSync>(PostSync);
|
||||
}
|
||||
|
||||
private void OnNetBatteryRejuvenate(EntityUid uid, PowerNetworkBatteryComponent component, RejuvenateEvent args)
|
||||
private void OnNetBatteryRejuvenate(Entity<PowerNetworkBatteryComponent> ent, ref RejuvenateEvent args)
|
||||
{
|
||||
component.NetworkBattery.CurrentStorage = component.NetworkBattery.Capacity;
|
||||
ent.Comp.NetworkBattery.CurrentStorage = ent.Comp.NetworkBattery.Capacity;
|
||||
}
|
||||
|
||||
private void OnBatteryRejuvenate(EntityUid uid, BatteryComponent component, RejuvenateEvent args)
|
||||
private void OnBatteryRejuvenate(Entity<BatteryComponent> ent, ref RejuvenateEvent args)
|
||||
{
|
||||
SetCharge(uid, component.MaxCharge, component);
|
||||
SetCharge(ent.AsNullable(), ent.Comp.MaxCharge);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, ExaminableBatteryComponent component, ExaminedEvent args)
|
||||
private void OnExamine(Entity<ExaminableBatteryComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!TryComp<BatteryComponent>(uid, out var batteryComponent))
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
if (args.IsInDetailsRange)
|
||||
{
|
||||
var effectiveMax = batteryComponent.MaxCharge;
|
||||
if (effectiveMax == 0)
|
||||
effectiveMax = 1;
|
||||
var chargeFraction = batteryComponent.CurrentCharge / effectiveMax;
|
||||
var chargePercentRounded = (int)(chargeFraction * 100);
|
||||
|
||||
if (!TryComp<BatteryComponent>(ent, out var battery))
|
||||
return;
|
||||
|
||||
var chargePercentRounded = 0;
|
||||
if (battery.MaxCharge != 0)
|
||||
chargePercentRounded = (int)(100 * battery.CurrentCharge / battery.MaxCharge);
|
||||
args.PushMarkup(
|
||||
Loc.GetString(
|
||||
"examinable-battery-component-examine-detail",
|
||||
@@ -60,7 +60,6 @@ namespace Content.Server.Power.EntitySystems
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void PreSync(NetworkBatteryPreSync ev)
|
||||
{
|
||||
@@ -80,41 +79,24 @@ namespace Content.Server.Power.EntitySystems
|
||||
var enumerator = AllEntityQuery<PowerNetworkBatteryComponent, BatteryComponent>();
|
||||
while (enumerator.MoveNext(out var uid, out var netBat, out var bat))
|
||||
{
|
||||
SetCharge(uid, netBat.NetworkBattery.CurrentStorage, bat);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var batt))
|
||||
{
|
||||
if (!comp.AutoRecharge || IsFull(uid, batt))
|
||||
continue;
|
||||
|
||||
if (comp.AutoRechargePause)
|
||||
{
|
||||
if (comp.NextAutoRecharge > _timing.CurTime)
|
||||
continue;
|
||||
}
|
||||
|
||||
SetCharge(uid, batt.CurrentCharge + comp.AutoRechargeRate * frameTime, batt);
|
||||
SetCharge((uid, bat), netBat.NetworkBattery.CurrentStorage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the price for the power contained in an entity's battery.
|
||||
/// </summary>
|
||||
private void CalculateBatteryPrice(EntityUid uid, BatteryComponent component, ref PriceCalculationEvent args)
|
||||
private void CalculateBatteryPrice(Entity<BatteryComponent> ent, ref PriceCalculationEvent args)
|
||||
{
|
||||
args.Price += component.CurrentCharge * component.PricePerJoule;
|
||||
args.Price += ent.Comp.CurrentCharge * ent.Comp.PricePerJoule;
|
||||
}
|
||||
private void OnChangeCharge(Entity<BatteryComponent> entity, ref ChangeChargeEvent args)
|
||||
|
||||
private void OnChangeCharge(Entity<BatteryComponent> ent, ref ChangeChargeEvent args)
|
||||
{
|
||||
if (args.ResidualValue == 0)
|
||||
return;
|
||||
|
||||
args.ResidualValue -= ChangeCharge(entity, args.ResidualValue);
|
||||
args.ResidualValue -= ChangeCharge(ent.AsNullable(), args.ResidualValue);
|
||||
}
|
||||
|
||||
private void OnGetCharge(Entity<BatteryComponent> entity, ref GetChargeEvent args)
|
||||
@@ -123,118 +105,19 @@ namespace Content.Server.Power.EntitySystems
|
||||
args.MaxCharge += entity.Comp.MaxCharge;
|
||||
}
|
||||
|
||||
public override float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (value <= 0 || !Resolve(uid, ref battery) || battery.CurrentCharge == 0)
|
||||
return 0;
|
||||
|
||||
return ChangeCharge(uid, -value, battery);
|
||||
}
|
||||
|
||||
public override void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
var query = EntityQueryEnumerator<BatterySelfRechargerComponent, BatteryComponent>();
|
||||
var curTime = _timing.CurTime;
|
||||
while (query.MoveNext(out var uid, out var comp, out var bat))
|
||||
{
|
||||
if (!Resolve(uid, ref battery))
|
||||
return;
|
||||
if (!comp.AutoRecharge || IsFull((uid, bat)))
|
||||
continue;
|
||||
|
||||
var old = battery.MaxCharge;
|
||||
battery.MaxCharge = Math.Max(value, 0);
|
||||
battery.CurrentCharge = Math.Min(battery.CurrentCharge, battery.MaxCharge);
|
||||
if (MathHelper.CloseTo(battery.MaxCharge, old))
|
||||
return;
|
||||
if (comp.NextAutoRecharge > curTime)
|
||||
continue;
|
||||
|
||||
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
public void SetCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
{
|
||||
if (!Resolve(uid, ref battery))
|
||||
return;
|
||||
|
||||
var old = battery.CurrentCharge;
|
||||
battery.CurrentCharge = MathHelper.Clamp(value, 0, battery.MaxCharge);
|
||||
if (MathHelper.CloseTo(battery.CurrentCharge, old) &&
|
||||
!(old != battery.CurrentCharge && battery.CurrentCharge == battery.MaxCharge))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current battery charge by some value
|
||||
/// </summary>
|
||||
public override float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
{
|
||||
if (!Resolve(uid, ref battery))
|
||||
return 0;
|
||||
|
||||
var newValue = Math.Clamp(battery.CurrentCharge + value, 0, battery.MaxCharge);
|
||||
var delta = newValue - battery.CurrentCharge;
|
||||
battery.CurrentCharge = newValue;
|
||||
|
||||
TrySetChargeCooldown(uid);
|
||||
|
||||
var ev = new ChargeChangedEvent(battery.CurrentCharge, battery.MaxCharge);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
return delta;
|
||||
}
|
||||
|
||||
public override void TrySetChargeCooldown(EntityUid uid, float value = -1)
|
||||
{
|
||||
if (!TryComp<BatterySelfRechargerComponent>(uid, out var batteryself))
|
||||
return;
|
||||
|
||||
if (!batteryself.AutoRechargePause)
|
||||
return;
|
||||
|
||||
// If no answer or a negative is given for value, use the default from AutoRechargePauseTime.
|
||||
if (value < 0)
|
||||
value = batteryself.AutoRechargePauseTime;
|
||||
|
||||
if (_timing.CurTime + TimeSpan.FromSeconds(value) <= batteryself.NextAutoRecharge)
|
||||
return;
|
||||
|
||||
SetChargeCooldown(uid, batteryself.AutoRechargePauseTime, batteryself);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts the entity's self recharge on cooldown for the specified time.
|
||||
/// </summary>
|
||||
public void SetChargeCooldown(EntityUid uid, float value, BatterySelfRechargerComponent? batteryself = null)
|
||||
{
|
||||
if (!Resolve(uid, ref batteryself))
|
||||
return;
|
||||
|
||||
if (value >= 0)
|
||||
batteryself.NextAutoRecharge = _timing.CurTime + TimeSpan.FromSeconds(value);
|
||||
else
|
||||
batteryself.NextAutoRecharge = _timing.CurTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If sufficient charge is available on the battery, use it. Otherwise, don't.
|
||||
/// </summary>
|
||||
public override bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
{
|
||||
if (!Resolve(uid, ref battery, false) || value > battery.CurrentCharge)
|
||||
return false;
|
||||
|
||||
UseCharge(uid, value, battery);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the battery is full.
|
||||
/// </summary>
|
||||
public bool IsFull(EntityUid uid, BatteryComponent? battery = null)
|
||||
{
|
||||
if (!Resolve(uid, ref battery))
|
||||
return false;
|
||||
|
||||
return battery.CurrentCharge >= battery.MaxCharge;
|
||||
SetCharge((uid, bat), bat.CurrentCharge + comp.AutoRechargeRate * frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ public sealed class ChargerSystem : SharedChargerSystem
|
||||
if (!SearchForBattery(container.ContainedEntities[0], out var heldEnt, out var heldBattery))
|
||||
return CellChargerStatus.Off;
|
||||
|
||||
if (_battery.IsFull(heldEnt.Value, heldBattery))
|
||||
if (_battery.IsFull((heldEnt.Value, heldBattery)))
|
||||
return CellChargerStatus.Charged;
|
||||
|
||||
return CellChargerStatus.Charging;
|
||||
@@ -241,7 +241,7 @@ public sealed class ChargerSystem : SharedChargerSystem
|
||||
if (!SearchForBattery(targetEntity, out var batteryUid, out var heldBattery))
|
||||
return;
|
||||
|
||||
_battery.SetCharge(batteryUid.Value, heldBattery.CurrentCharge + component.ChargeRate * frameTime, heldBattery);
|
||||
_battery.SetCharge((batteryUid.Value, heldBattery), heldBattery.CurrentCharge + component.ChargeRate * frameTime);
|
||||
UpdateStatus(uid, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -358,13 +358,13 @@ namespace Content.Server.Power.EntitySystems
|
||||
|
||||
if (requireBattery)
|
||||
{
|
||||
_battery.SetCharge(uid, battery.CurrentCharge - apcBattery.IdleLoad * frameTime, battery);
|
||||
_battery.SetCharge((uid, battery), battery.CurrentCharge - apcBattery.IdleLoad * frameTime);
|
||||
}
|
||||
// Otherwise try to charge the battery
|
||||
else if (powered && !_battery.IsFull(uid, battery))
|
||||
else if (powered && !_battery.IsFull((uid, battery)))
|
||||
{
|
||||
apcReceiver.Load += apcBattery.BatteryRechargeRate * apcBattery.BatteryRechargeEfficiency;
|
||||
_battery.SetCharge(uid, battery.CurrentCharge + apcBattery.BatteryRechargeRate * frameTime, battery);
|
||||
_battery.SetCharge((uid, battery), battery.CurrentCharge + apcBattery.BatteryRechargeRate * frameTime);
|
||||
}
|
||||
|
||||
// Enable / disable the battery if the state changed
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Content.Server.Power
|
||||
shell.WriteLine(Loc.GetString($"cmd-setbatterypercent-battery-not-found", ("id", id)));
|
||||
return;
|
||||
}
|
||||
_batterySystem.SetCharge(id.Value, battery.MaxCharge * percent / 100, battery);
|
||||
_batterySystem.SetCharge((id.Value, battery), battery.MaxCharge * percent / 100);
|
||||
// Don't acknowledge b/c people WILL forall this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed partial class PowerCellSystem
|
||||
if (!TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery, slot))
|
||||
continue;
|
||||
|
||||
if (_battery.TryUseCharge(batteryEnt.Value, comp.DrawRate * (float)comp.Delay.TotalSeconds, battery))
|
||||
if (_battery.TryUseCharge((batteryEnt.Value, battery), comp.DrawRate * (float)comp.Delay.TotalSeconds))
|
||||
continue;
|
||||
|
||||
var ev = new PowerCellSlotEmptyEvent();
|
||||
|
||||
@@ -174,7 +174,7 @@ public sealed partial class PowerCellSystem : SharedPowerCellSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_battery.TryUseCharge(batteryEnt.Value, charge, battery))
|
||||
if (!_battery.TryUseCharge((batteryEnt.Value, battery), charge))
|
||||
{
|
||||
if (user != null)
|
||||
_popup.PopupEntity(Loc.GetString("power-cell-insufficient"), uid, user.Value);
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Content.Server.PowerSink
|
||||
if (!transform.Anchored)
|
||||
continue;
|
||||
|
||||
_battery.ChangeCharge(entity, networkLoad.NetworkLoad.ReceivingPower * frameTime, battery);
|
||||
_battery.ChangeCharge((entity, battery), networkLoad.NetworkLoad.ReceivingPower * frameTime);
|
||||
|
||||
var currentBatteryThreshold = battery.CurrentCharge / battery.MaxCharge;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class JammerSystem : SharedJammerSystem
|
||||
|
||||
if (_powerCell.TryGetBatteryFromSlot(uid, out var batteryUid, out var battery))
|
||||
{
|
||||
if (!_battery.TryUseCharge(batteryUid.Value, GetCurrentWattage((uid, jam)) * frameTime, battery))
|
||||
if (!_battery.TryUseCharge((batteryUid.Value, battery), GetCurrentWattage((uid, jam)) * frameTime))
|
||||
{
|
||||
ChangeLEDState(uid, false);
|
||||
RemComp<ActiveRadioJammerComponent>(uid);
|
||||
|
||||
@@ -15,7 +15,7 @@ public sealed class RoleSystem : SharedRoleSystem
|
||||
{
|
||||
if (mindId == null)
|
||||
{
|
||||
Log.Error($"MingGetBriefing failed for mind {mindId}");
|
||||
Log.Error($"MindGetBriefing failed for mind {mindId}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class RoleSystem : SharedRoleSystem
|
||||
|
||||
if (mindComp is null)
|
||||
{
|
||||
Log.Error($"MingGetBriefing failed for mind {mindId}");
|
||||
Log.Error($"MindGetBriefing failed for mind {mindId}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -801,7 +801,11 @@ public sealed partial class ShuttleSystem
|
||||
while (iteration < FTLProximityIterations)
|
||||
{
|
||||
grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, targetAABB, ref grids);
|
||||
// We pass in an expanded offset here so we can safely do a random offset later.
|
||||
// We don't include this in the actual targetAABB because then we would be double-expanding it.
|
||||
// Once in this loop, then again when placing the shuttle later.
|
||||
// Note that targetAABB already has expansionAmount factored in already.
|
||||
_mapManager.FindGridsIntersecting(mapId, targetAABB.Enlarged(maxOffset), ref grids);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
@@ -834,10 +838,6 @@ public sealed partial class ShuttleSystem
|
||||
if (nearbyGrids.Contains(uid))
|
||||
continue;
|
||||
|
||||
// We pass in an expanded offset here so we can safely do a random offset later.
|
||||
// We don't include this in the actual targetAABB because then we would be double-expanding it.
|
||||
// Once in this loop, then again when placing the shuttle later.
|
||||
// Note that targetAABB already has expansionAmount factored in already.
|
||||
targetAABB = targetAABB.Union(
|
||||
_transform.GetWorldMatrix(uid)
|
||||
.TransformBox(Comp<MapGridComponent>(uid).LocalAABB.Enlarged(expansionAmount)));
|
||||
@@ -857,7 +857,7 @@ public sealed partial class ShuttleSystem
|
||||
|
||||
// TODO: This should prefer the position's angle instead.
|
||||
// TODO: This is pretty crude for multiple landings.
|
||||
if (nearbyGrids.Count >= 1)
|
||||
if (nearbyGrids.Count > 1 || !HasComp<MapComponent>(targetXform.GridUid))
|
||||
{
|
||||
// Pick a random angle
|
||||
var offsetAngle = _random.NextAngle();
|
||||
@@ -866,9 +866,13 @@ public sealed partial class ShuttleSystem
|
||||
var minRadius = MathF.Max(targetAABB.Width / 2f, targetAABB.Height / 2f);
|
||||
spawnPos = targetAABB.Center + offsetAngle.RotateVec(new Vector2(_random.NextFloat(minRadius + minOffset, minRadius + maxOffset), 0f));
|
||||
}
|
||||
else if (shuttleBody != null)
|
||||
{
|
||||
(spawnPos, angle) = _transform.GetWorldPositionRotation(targetXform);
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnPos = _transform.ToWorldPosition(targetCoordinates);
|
||||
spawnPos = _transform.GetWorldPosition(targetXform);
|
||||
}
|
||||
|
||||
var offset = Vector2.Zero;
|
||||
@@ -889,10 +893,10 @@ public sealed partial class ShuttleSystem
|
||||
}
|
||||
|
||||
// Rotate our localcenter around so we spawn exactly where we "think" we should (center of grid on the dot).
|
||||
var transform = new Transform(_transform.ToWorldPosition(xform.Coordinates), angle);
|
||||
var adjustedOffset = Robust.Shared.Physics.Transform.Mul(transform, offset);
|
||||
var transform = new Transform(spawnPos, angle);
|
||||
spawnPos = Robust.Shared.Physics.Transform.Mul(transform, offset);
|
||||
|
||||
coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos + adjustedOffset);
|
||||
coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos - offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ public sealed partial class BorgSystem
|
||||
if (command == RoboticsConsoleConstants.NET_DISABLE_COMMAND)
|
||||
Disable(ent);
|
||||
else if (command == RoboticsConsoleConstants.NET_DESTROY_COMMAND)
|
||||
Destroy(ent);
|
||||
Destroy(ent.Owner);
|
||||
}
|
||||
|
||||
private void Disable(Entity<BorgTransponderComponent, BorgChassisComponent?> ent)
|
||||
@@ -118,8 +118,15 @@ public sealed partial class BorgSystem
|
||||
ent.Comp1.NextDisable = _timing.CurTime + ent.Comp1.DisableDelay;
|
||||
}
|
||||
|
||||
private void Destroy(Entity<BorgTransponderComponent> ent)
|
||||
/// <summary>
|
||||
/// Makes a borg with <see cref="BorgTransponderComponent"/> explode
|
||||
/// </summary>
|
||||
/// <param name="ent">the entity of the borg</param>
|
||||
public 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"))
|
||||
{
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed class StationAiSystem : SharedStationAiSystem
|
||||
// into an AI core that has a full battery and full integrity.
|
||||
if (TryComp<BatteryComponent>(ent, out var battery))
|
||||
{
|
||||
_battery.SetCharge(ent, battery.MaxCharge);
|
||||
_battery.SetCharge((ent, battery), battery.MaxCharge);
|
||||
}
|
||||
|
||||
_damageable.ClearAllDamage(ent.Owner);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Server.Spawners.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Defines whitelist and blacklist for entities that can spawn at a spawnpoint when they are spawned via the <see cref="RuleGridsSystem"/>
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GridSpawnPointWhitelistComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whitelist of entities that can be spawned at this SpawnPoint
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// Whitelist of entities that can't be spawned at this SpawnPoint
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace Content.Server.Stunnable.Systems
|
||||
private void OnStaminaHitAttempt(Entity<StunbatonComponent> entity, ref StaminaDamageOnHitAttemptEvent args)
|
||||
{
|
||||
if (!_itemToggle.IsActivated(entity.Owner) ||
|
||||
!TryComp<BatteryComponent>(entity.Owner, out var battery) || !_battery.TryUseCharge(entity.Owner, entity.Comp.EnergyPerUse, battery))
|
||||
!TryComp<BatteryComponent>(entity.Owner, out var battery) || !_battery.TryUseCharge((entity.Owner, battery), entity.Comp.EnergyPerUse))
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class TeslaCoilSystem : EntitySystem
|
||||
{
|
||||
if (TryComp<BatteryComponent>(coil, out var batteryComponent))
|
||||
{
|
||||
_battery.SetCharge(coil, batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning);
|
||||
_battery.SetCharge((coil, batteryComponent), batteryComponent.CurrentCharge + coil.Comp.ChargeFromLightning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -15,10 +12,7 @@ using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Tips;
|
||||
|
||||
/// <summary>
|
||||
/// Handles periodically displaying gameplay tips to all players ingame.
|
||||
/// </summary>
|
||||
public sealed class TipsSystem : EntitySystem
|
||||
public sealed class TipsSystem : SharedTipsSystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
@@ -26,8 +20,6 @@ public sealed class TipsSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private bool _tipsEnabled;
|
||||
private float _tipTimeOutOfRound;
|
||||
@@ -35,16 +27,6 @@ public sealed class TipsSystem : EntitySystem
|
||||
private string _tipsDataset = "";
|
||||
private float _tipTippyChance;
|
||||
|
||||
/// <summary>
|
||||
/// Always adds this time to a speech message. This is so really short message stay around for a bit.
|
||||
/// </summary>
|
||||
private const float SpeechBuffer = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Expected reading speed.
|
||||
/// </summary>
|
||||
private const float Wpm = 180f;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private TimeSpan _nextTipTime = TimeSpan.Zero;
|
||||
|
||||
@@ -53,110 +35,45 @@ public sealed class TipsSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
|
||||
Subs.CVar(_cfg, CCVars.TipFrequencyOutOfRound, SetOutOfRound, true);
|
||||
Subs.CVar(_cfg, CCVars.TipFrequencyInRound, SetInRound, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsEnabled, SetEnabled, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsDataset, SetDataset, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsTippyChance, SetTippyChance, true);
|
||||
Subs.CVar(_cfg, CCVars.TipFrequencyOutOfRound, value => _tipTimeOutOfRound = value, true);
|
||||
Subs.CVar(_cfg, CCVars.TipFrequencyInRound, value => _tipTimeInRound = value, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsDataset, value => _tipsDataset = value, true);
|
||||
Subs.CVar(_cfg, CCVars.TipsTippyChance, value => _tipTippyChance = value, true);
|
||||
|
||||
RecalculateNextTipTime();
|
||||
_conHost.RegisterCommand("tippy", Loc.GetString("cmd-tippy-desc"), Loc.GetString("cmd-tippy-help"), SendTippy, SendTippyHelper);
|
||||
_conHost.RegisterCommand("tip", Loc.GetString("cmd-tip-desc"), "tip", SendTip);
|
||||
}
|
||||
|
||||
private CompletionResult SendTippyHelper(IConsoleShell shell, string[] args)
|
||||
private void OnGameRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
return args.Length switch
|
||||
// reset for lobby -> inround
|
||||
// reset for inround -> post but not post -> lobby
|
||||
if (ev.New == GameRunLevel.InRound || ev.Old == GameRunLevel.InRound)
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.SessionNames(players: _playerManager),
|
||||
Loc.GetString("cmd-tippy-auto-1")),
|
||||
2 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-2")),
|
||||
3 => CompletionResult.FromHintOptions(
|
||||
CompletionHelper.PrototypeIdsLimited<EntityPrototype>(args[2], _prototype),
|
||||
Loc.GetString("cmd-tippy-auto-3")),
|
||||
4 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-4")),
|
||||
5 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-5")),
|
||||
6 => CompletionResult.FromHint(Loc.GetString("cmd-tippy-auto-6")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
RecalculateNextTipTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendTip(IConsoleShell shell, string argstr, string[] args)
|
||||
private void SetEnabled(bool value)
|
||||
{
|
||||
AnnounceRandomTip();
|
||||
_tipsEnabled = value;
|
||||
|
||||
if (_nextTipTime != TimeSpan.Zero)
|
||||
RecalculateNextTipTime();
|
||||
}
|
||||
|
||||
private void SendTippy(IConsoleShell shell, string argstr, string[] args)
|
||||
public override void RecalculateNextTipTime()
|
||||
{
|
||||
if (args.Length < 2)
|
||||
if (_ticker.RunLevel == GameRunLevel.InRound)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-help"));
|
||||
return;
|
||||
}
|
||||
|
||||
ActorComponent? actor = null;
|
||||
if (args[0] != "all")
|
||||
{
|
||||
ICommonSession? session;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// Get player entity
|
||||
if (!_playerManager.TryGetSessionByUsername(args[0], out session))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
_nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeInRound);
|
||||
}
|
||||
else
|
||||
{
|
||||
session = shell.Player;
|
||||
}
|
||||
|
||||
if (session?.AttachedEntity is not { } user)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp(user, out actor))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-tippy-error-no-user"));
|
||||
return;
|
||||
_nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeOutOfRound);
|
||||
}
|
||||
}
|
||||
|
||||
var ev = new TippyEvent(args[1]);
|
||||
|
||||
if (args.Length > 2)
|
||||
{
|
||||
ev.Proto = args[2];
|
||||
if (!_prototype.HasIndex<EntityPrototype>(args[2]))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-tippy-error-no-prototype", ("proto", args[2])));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Length > 3)
|
||||
ev.SpeakTime = float.Parse(args[3]);
|
||||
else
|
||||
ev.SpeakTime = GetSpeechTime(ev.Msg);
|
||||
|
||||
if (args.Length > 4)
|
||||
ev.SlideTime = float.Parse(args[4]);
|
||||
|
||||
if (args.Length > 5)
|
||||
ev.WaddleInterval = float.Parse(args[5]);
|
||||
|
||||
if (actor != null)
|
||||
RaiseNetworkEvent(ev, actor.PlayerSession);
|
||||
else
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
@@ -171,41 +88,30 @@ public sealed class TipsSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void SetOutOfRound(float value)
|
||||
public override void SendTippy(
|
||||
string message,
|
||||
EntProtoId? prototype = null,
|
||||
float speakTime = 5f,
|
||||
float slideTime = 3f,
|
||||
float waddleInterval = 0.5f)
|
||||
{
|
||||
_tipTimeOutOfRound = value;
|
||||
var ev = new TippyEvent(message, prototype, speakTime, slideTime, waddleInterval);
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
|
||||
private void SetInRound(float value)
|
||||
public override void SendTippy(
|
||||
ICommonSession session,
|
||||
string message,
|
||||
EntProtoId? prototype = null,
|
||||
float speakTime = 5f,
|
||||
float slideTime = 3f,
|
||||
float waddleInterval = 0.5f)
|
||||
{
|
||||
_tipTimeInRound = value;
|
||||
var ev = new TippyEvent(message, prototype, speakTime, slideTime, waddleInterval);
|
||||
RaiseNetworkEvent(ev, session);
|
||||
}
|
||||
|
||||
private void SetEnabled(bool value)
|
||||
{
|
||||
_tipsEnabled = value;
|
||||
|
||||
if (_nextTipTime != TimeSpan.Zero)
|
||||
RecalculateNextTipTime();
|
||||
}
|
||||
|
||||
private void SetDataset(string value)
|
||||
{
|
||||
_tipsDataset = value;
|
||||
}
|
||||
|
||||
private void SetTippyChance(float value)
|
||||
{
|
||||
_tipTippyChance = value;
|
||||
}
|
||||
|
||||
public static float GetSpeechTime(string text)
|
||||
{
|
||||
var wordCount = (float)text.Split().Length;
|
||||
return SpeechBuffer + wordCount * (60f / Wpm);
|
||||
}
|
||||
|
||||
private void AnnounceRandomTip()
|
||||
public override void AnnounceRandomTip()
|
||||
{
|
||||
if (!_prototype.TryIndex<LocalizedDatasetPrototype>(_tipsDataset, out var tips))
|
||||
return;
|
||||
@@ -215,35 +121,20 @@ public sealed class TipsSystem : EntitySystem
|
||||
|
||||
if (_random.Prob(_tipTippyChance))
|
||||
{
|
||||
var ev = new TippyEvent(msg);
|
||||
ev.SpeakTime = GetSpeechTime(msg);
|
||||
RaiseNetworkEvent(ev);
|
||||
} else
|
||||
{
|
||||
_chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg,
|
||||
EntityUid.Invalid, false, false, Color.MediumPurple);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateNextTipTime()
|
||||
{
|
||||
if (_ticker.RunLevel == GameRunLevel.InRound)
|
||||
{
|
||||
_nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeInRound);
|
||||
var speakTime = GetSpeechTime(msg);
|
||||
SendTippy(msg, speakTime: speakTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeOutOfRound);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
// reset for lobby -> inround
|
||||
// reset for inround -> post but not post -> lobby
|
||||
if (ev.New == GameRunLevel.InRound || ev.Old == GameRunLevel.InRound)
|
||||
{
|
||||
RecalculateNextTipTime();
|
||||
_chat.ChatMessageToManyFiltered(
|
||||
Filter.Broadcast(),
|
||||
ChatChannel.OOC,
|
||||
tip,
|
||||
msg,
|
||||
EntityUid.Invalid,
|
||||
false,
|
||||
false,
|
||||
Color.MediumPurple);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ public sealed partial class GunSystem
|
||||
|
||||
protected override void TakeCharge(Entity<BatteryAmmoProviderComponent> entity)
|
||||
{
|
||||
// Take charge from either the BatteryComponent or PowerCellSlotComponent.
|
||||
var ev = new ChangeChargeEvent(-entity.Comp.FireCost);
|
||||
RaiseLocalEvent(entity, ref ev);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class XAEChargeBatterySystem : BaseXAESystem<XAEChargeBatteryCompo
|
||||
_lookup.GetEntitiesInRange(args.Coordinates, chargeBatteryComponent.Radius, _batteryEntities);
|
||||
foreach (var battery in _batteryEntities)
|
||||
{
|
||||
_battery.SetCharge(battery, battery.Comp.MaxCharge, battery);
|
||||
_battery.SetCharge(battery.AsNullable(), battery.Comp.MaxCharge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
101
Content.Server/Xenoborgs/XenoborgSystem.cs
Normal file
101
Content.Server/Xenoborgs/XenoborgSystem.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Silicons.Borgs;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Xenoborgs.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Xenoborgs;
|
||||
|
||||
public sealed partial class XenoborgSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly BorgSystem _borg = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly XenoborgsRuleSystem _xenoborgsRule = default!;
|
||||
|
||||
private static readonly Color XenoborgBriefingColor = Color.BlueViolet;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<XenoborgComponent, DestructionEventArgs>(OnXenoborgDestroyed);
|
||||
SubscribeLocalEvent<MothershipCoreComponent, DestructionEventArgs>(OnCoreDestroyed);
|
||||
|
||||
SubscribeLocalEvent<XenoborgComponent, MindAddedMessage>(OnXenoborgMindAdded);
|
||||
SubscribeLocalEvent<XenoborgComponent, MindRemovedMessage>(OnXenoborgMindRemoved);
|
||||
}
|
||||
|
||||
private void OnXenoborgDestroyed(EntityUid uid, XenoborgComponent component, DestructionEventArgs args)
|
||||
{
|
||||
// if a xenoborg is destroyed, it will check to see if it was the last one
|
||||
var xenoborgQuery = AllEntityQuery<XenoborgComponent>(); // paused xenoborgs still count
|
||||
while (xenoborgQuery.MoveNext(out var xenoborg, out _))
|
||||
{
|
||||
if (xenoborg != uid)
|
||||
return;
|
||||
}
|
||||
|
||||
var mothershipCoreQuery = AllEntityQuery<MothershipCoreComponent>(); // paused mothership cores still count
|
||||
var mothershipCoreAlive = mothershipCoreQuery.MoveNext(out _, out _);
|
||||
|
||||
var xenoborgsRuleQuery = EntityQueryEnumerator<XenoborgsRuleComponent>();
|
||||
if (xenoborgsRuleQuery.MoveNext(out var xenoborgsRuleEnt, out var xenoborgsRuleComp))
|
||||
_xenoborgsRule.SendXenoborgDeathAnnouncement((xenoborgsRuleEnt, xenoborgsRuleComp), mothershipCoreAlive);
|
||||
}
|
||||
|
||||
private void OnCoreDestroyed(EntityUid ent, MothershipCoreComponent component, DestructionEventArgs args)
|
||||
{
|
||||
// if a mothership core is destroyed, it will see if there are any others
|
||||
var mothershipCoreQuery = AllEntityQuery<MothershipCoreComponent>(); // paused mothership cores still count
|
||||
while (mothershipCoreQuery.MoveNext(out var mothershipCoreEnt, out _))
|
||||
{
|
||||
// if it finds a mothership core that is different from the one just destroyed,
|
||||
// it doesn't explode the xenoborgs
|
||||
if (mothershipCoreEnt != ent)
|
||||
return;
|
||||
}
|
||||
|
||||
var xenoborgsRuleQuery = EntityQueryEnumerator<XenoborgsRuleComponent>();
|
||||
if (xenoborgsRuleQuery.MoveNext(out var xenoborgsRuleEnt, out var xenoborgsRuleComp))
|
||||
_xenoborgsRule.SendMothershipDeathAnnouncement((xenoborgsRuleEnt, xenoborgsRuleComp));
|
||||
|
||||
// explode all xenoborgs
|
||||
var xenoborgQuery = AllEntityQuery<XenoborgComponent, BorgTransponderComponent>(); // paused xenoborgs still explode
|
||||
while (xenoborgQuery.MoveNext(out var xenoborgEnt, out _, out _))
|
||||
{
|
||||
if (HasComp<MothershipCoreComponent>(xenoborgEnt))
|
||||
continue;
|
||||
|
||||
// I got tired to trying to make this work via the device network.
|
||||
// so brute force it is...
|
||||
_borg.Destroy(xenoborgEnt);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnXenoborgMindAdded(EntityUid ent, XenoborgComponent comp, MindAddedMessage args)
|
||||
{
|
||||
_roles.MindAddRole(args.Mind, comp.MindRole, silent: true);
|
||||
|
||||
if (!TryComp<ActorComponent>(ent, out var actorComp))
|
||||
return;
|
||||
|
||||
_antag.SendBriefing(actorComp.PlayerSession,
|
||||
Loc.GetString(comp.BriefingText),
|
||||
XenoborgBriefingColor,
|
||||
comp.BriefingSound
|
||||
);
|
||||
}
|
||||
|
||||
private void OnXenoborgMindRemoved(EntityUid ent, XenoborgComponent comp, MindRemovedMessage args)
|
||||
{
|
||||
_roles.MindRemoveRole(args.Mind.Owner, comp.MindRole);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,8 @@ public ref struct LogStringHandler
|
||||
format = argument[0] == '@' ? argument[1..] : argument;
|
||||
}
|
||||
|
||||
if (Values.TryAdd(Logger.ConvertName(format), value)
|
||||
format = Logger.ConvertName(format);
|
||||
if (Values.TryAdd(format, value)
|
||||
|| Values[format] is T val && val.Equals(value) )
|
||||
{
|
||||
return;
|
||||
@@ -50,7 +51,7 @@ public ref struct LogStringHandler
|
||||
var i = 2;
|
||||
format = $"{originalFormat}_{i}";
|
||||
|
||||
while (!(Values.TryAdd(Logger.ConvertName(format), value)
|
||||
while (!(Values.TryAdd(format, value)
|
||||
|| Values[format] is T val2 && val2.Equals(value)))
|
||||
{
|
||||
format = $"{originalFormat}_{i}";
|
||||
|
||||
13
Content.Shared/CCVar/CCVars.Debug.cs
Normal file
13
Content.Shared/CCVar/CCVars.Debug.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared.CCVar;
|
||||
|
||||
public sealed partial class CCVars
|
||||
{
|
||||
/// <summary>
|
||||
/// Component to be inspected using the "Quick Inspect Component" keybind.
|
||||
/// Set by the "quickinspect" command.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DebugQuickInspect =
|
||||
CVarDef.Create("debug.quick_inspect", "", CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
}
|
||||
@@ -302,7 +302,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// If you only want the volume of a single reagent, use <see cref="GetReagentQuantity"/>
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(params string[] prototypes)
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(params ProtoId<ReagentPrototype>[] prototypes)
|
||||
{
|
||||
var total = FixedPoint2.Zero;
|
||||
foreach (var (reagent, quantity) in Contents)
|
||||
@@ -314,7 +314,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
return total;
|
||||
}
|
||||
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(string id)
|
||||
public FixedPoint2 GetTotalPrototypeQuantity(ProtoId<ReagentPrototype> id)
|
||||
{
|
||||
var total = FixedPoint2.Zero;
|
||||
foreach (var (reagent, quantity) in Contents)
|
||||
@@ -645,7 +645,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
/// <summary>
|
||||
/// Splits a solution with only the specified reagent prototypes.
|
||||
/// </summary>
|
||||
public Solution SplitSolutionWithOnly(FixedPoint2 toTake, params string[] includedPrototypes)
|
||||
public Solution SplitSolutionWithOnly(FixedPoint2 toTake, params ProtoId<ReagentPrototype>[] includedPrototypes)
|
||||
{
|
||||
// First remove the non-included prototypes
|
||||
List<ReagentQuantity> excluded = new();
|
||||
@@ -844,7 +844,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
ValidateSolution();
|
||||
}
|
||||
|
||||
public Color GetColorWithout(IPrototypeManager? protoMan, params string[] without)
|
||||
public Color GetColorWithout(IPrototypeManager? protoMan, params ProtoId<ReagentPrototype>[] without)
|
||||
{
|
||||
if (Volume == FixedPoint2.Zero)
|
||||
{
|
||||
@@ -887,7 +887,7 @@ namespace Content.Shared.Chemistry.Components
|
||||
return GetColorWithout(protoMan);
|
||||
}
|
||||
|
||||
public Color GetColorWithOnly(IPrototypeManager? protoMan, params string[] included)
|
||||
public Color GetColorWithOnly(IPrototypeManager? protoMan, params ProtoId<ReagentPrototype>[] included)
|
||||
{
|
||||
if (Volume == FixedPoint2.Zero)
|
||||
{
|
||||
|
||||
@@ -1217,7 +1217,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
||||
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
|
||||
AddComp(uid, relation);
|
||||
|
||||
MetaDataSys.SetEntityName(uid, $"solution - {name}");
|
||||
MetaDataSys.SetEntityName(uid, $"solution - {name}", raiseEvents: false);
|
||||
ContainerSystem.Insert(uid, container, force: true);
|
||||
|
||||
return (uid, solution, relation);
|
||||
|
||||
@@ -8,13 +8,41 @@ namespace Content.Shared.Damage.Systems;
|
||||
public sealed partial class DamageableSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Directly sets the damage specifier of a damageable component.
|
||||
/// Directly sets the damage in a damageable component.
|
||||
/// This method keeps the damage types supported by the DamageContainerPrototype in the component.
|
||||
/// If a type is given in <paramref name="damage"/>, but not supported then it will not be set.
|
||||
/// If a type is supported but not given in <paramref name="damage"/> then it will be set to 0.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for some unfriendly folk. Also ensures that cached values are updated and that a damage changed
|
||||
/// event is raised.
|
||||
/// </remarks>
|
||||
public void SetDamage(Entity<DamageableComponent?> ent, DamageSpecifier damage)
|
||||
{
|
||||
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
foreach (var type in ent.Comp.Damage.DamageDict.Keys)
|
||||
{
|
||||
if (damage.DamageDict.TryGetValue(type, out var value))
|
||||
ent.Comp.Damage.DamageDict[type] = value;
|
||||
else
|
||||
ent.Comp.Damage.DamageDict[type] = 0;
|
||||
}
|
||||
|
||||
OnEntityDamageChanged((ent, ent.Comp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Directly sets the damage specifier of a damageable component.
|
||||
/// This will overwrite the complete damage dict, meaning it will bulldoze the supported damage types.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This may break persistance as the supported types are reset in case the component is initialized again.
|
||||
/// So this only makes sense if you also change the DamageContainerPrototype in the component at the same time.
|
||||
/// Only use this method if you know what you are doing.
|
||||
/// </remarks>
|
||||
public void SetDamageSpecifier(Entity<DamageableComponent?> ent, DamageSpecifier damage)
|
||||
{
|
||||
if (!_damageableQuery.Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Fluids.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
|
||||
namespace Content.Shared.Fluids;
|
||||
|
||||
@@ -78,9 +80,9 @@ public abstract partial class SharedPuddleSystem
|
||||
}
|
||||
|
||||
|
||||
public string[] GetEvaporatingReagents(Solution solution)
|
||||
public ProtoId<ReagentPrototype>[] GetEvaporatingReagents(Solution solution)
|
||||
{
|
||||
List<string> evaporatingReagents = [];
|
||||
List<ProtoId<ReagentPrototype>> evaporatingReagents = [];
|
||||
foreach (var solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
|
||||
{
|
||||
if (solProto.EvaporationSpeed > FixedPoint2.Zero)
|
||||
@@ -89,10 +91,10 @@ public abstract partial class SharedPuddleSystem
|
||||
return evaporatingReagents.ToArray();
|
||||
}
|
||||
|
||||
public string[] GetAbsorbentReagents(Solution solution)
|
||||
public ProtoId<ReagentPrototype>[] GetAbsorbentReagents(Solution solution)
|
||||
{
|
||||
List<string> absorbentReagents = [];
|
||||
foreach (var solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
|
||||
var absorbentReagents = new List<ProtoId<ReagentPrototype>>();
|
||||
foreach (ReagentPrototype solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
|
||||
{
|
||||
if (solProto.Absorbent)
|
||||
absorbentReagents.Add(solProto.ID);
|
||||
@@ -109,9 +111,9 @@ public abstract partial class SharedPuddleSystem
|
||||
/// Gets a mapping of evaporating speed of the reagents within a solution.
|
||||
/// The speed at which a solution evaporates is the average of the speed of all evaporating reagents in it.
|
||||
/// </summary>
|
||||
public Dictionary<string, FixedPoint2> GetEvaporationSpeeds(Solution solution)
|
||||
public Dictionary<ProtoId<ReagentPrototype>, FixedPoint2> GetEvaporationSpeeds(Solution solution)
|
||||
{
|
||||
Dictionary<string, FixedPoint2> evaporatingSpeeds = [];
|
||||
Dictionary<ProtoId<ReagentPrototype>, FixedPoint2> evaporatingSpeeds = [];
|
||||
foreach (var solProto in solution.GetReagentPrototypes(_prototypeManager).Keys)
|
||||
{
|
||||
if (solProto.EvaporationSpeed > FixedPoint2.Zero)
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract partial class SharedPuddleSystem : EntitySystem
|
||||
[Dependency] private readonly StepTriggerSystem _stepTrigger = default!;
|
||||
[Dependency] private readonly TileFrictionController _tile = default!;
|
||||
|
||||
private string[] _standoutReagents = [];
|
||||
private ProtoId<ReagentPrototype>[] _standoutReagents = [];
|
||||
|
||||
/// <summary>
|
||||
/// The lowest threshold to be considered for puddle sprite states as well as slipperiness of a puddle.
|
||||
|
||||
@@ -35,6 +35,7 @@ public sealed class FollowerSystem : EntitySystem
|
||||
[Dependency] private readonly ISharedAdminManager _adminManager = default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> ForceableFollowTag = "ForceableFollow";
|
||||
private static readonly ProtoId<TagPrototype> PreventGhostnadoWarpTag = "NotGhostnadoWarpable";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -320,11 +321,17 @@ public sealed class FollowerSystem : EntitySystem
|
||||
var query = EntityQueryEnumerator<FollowerComponent, GhostComponent, ActorComponent>();
|
||||
while (query.MoveNext(out _, out var follower, out _, out var actor))
|
||||
{
|
||||
// Exclude admins
|
||||
// Don't count admin followers so that players cannot notice if admins are in stealth mode and following someone.
|
||||
if (_adminManager.IsAdmin(actor.PlayerSession))
|
||||
continue;
|
||||
|
||||
var followed = follower.Following;
|
||||
|
||||
// If the followed entity cannot be ghostnado'd to, we don't count it.
|
||||
// Used for making admins not warpable to, but IsAdmin isn't used for cases where the admin wants to be followed, for example during events.
|
||||
if (_tagSystem.HasTag(followed, PreventGhostnadoWarpTag))
|
||||
continue;
|
||||
|
||||
// Add new entry or increment existing
|
||||
followedEnts.TryGetValue(followed, out var currentValue);
|
||||
followedEnts[followed] = currentValue + 1;
|
||||
|
||||
@@ -3,42 +3,45 @@ using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Gravity;
|
||||
|
||||
[NetworkedComponent()]
|
||||
[Virtual]
|
||||
public partial class SharedGravityGeneratorComponent : Component
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class GravityGeneratorComponent : Component
|
||||
{
|
||||
[DataField] public float LightRadiusMin { get; set; }
|
||||
[DataField] public float LightRadiusMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A map of the sprites used by the gravity generator given its status.
|
||||
/// </summary>
|
||||
[DataField("spriteMap")]
|
||||
[Access(typeof(SharedGravitySystem))]
|
||||
public Dictionary<PowerChargeStatus, string> SpriteMap = new();
|
||||
[DataField, Access(typeof(SharedGravitySystem))]
|
||||
public Dictionary<PowerChargeStatus, string> SpriteMap = [];
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is starting up.
|
||||
/// </summary>
|
||||
[DataField("coreStartupState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string CoreStartupState = "startup";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is idle.
|
||||
/// </summary>
|
||||
[DataField("coreIdleState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string CoreIdleState = "idle";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is activating.
|
||||
/// </summary>
|
||||
[DataField("coreActivatingState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string CoreActivatingState = "activating";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite used by the core of the gravity generator when the gravity generator is active.
|
||||
/// </summary>
|
||||
[DataField("coreActivatedState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string CoreActivatedState = "activated";
|
||||
|
||||
/// <summary>
|
||||
/// Is the gravity generator currently "producing" gravity?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, Access(typeof(SharedGravityGeneratorSystem))]
|
||||
public bool GravityActive = false;
|
||||
}
|
||||
29
Content.Shared/Gravity/SharedGravityGeneratorSystem.cs
Normal file
29
Content.Shared/Gravity/SharedGravityGeneratorSystem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Construction.Components;
|
||||
|
||||
namespace Content.Shared.Gravity;
|
||||
|
||||
public abstract class SharedGravityGeneratorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GravityGeneratorComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevent unanchoring when gravity is active
|
||||
/// </summary>
|
||||
private void OnUnanchorAttempt(Entity<GravityGeneratorComponent> ent, ref UnanchorAttemptEvent args)
|
||||
{
|
||||
if (!ent.Comp.GravityActive)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupClient(Loc.GetString("gravity-generator-unanchoring-failed"), ent.Owner, args.User, PopupType.Medium);
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public abstract partial class SharedHandsSystem
|
||||
private void InitializeEventListeners()
|
||||
{
|
||||
SubscribeLocalEvent<HandsComponent, GetStandUpTimeEvent>(OnStandupArgs);
|
||||
SubscribeLocalEvent<HandsComponent, KnockedDownRefreshEvent>(OnKnockedDownRefresh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,4 +29,17 @@ public abstract partial class SharedHandsSystem
|
||||
|
||||
time.DoAfterTime *= (float)ent.Comp.Count / (hands + ent.Comp.Count);
|
||||
}
|
||||
|
||||
private void OnKnockedDownRefresh(Entity<HandsComponent> ent, ref KnockedDownRefreshEvent args)
|
||||
{
|
||||
var freeHands = CountFreeHands(ent.AsNullable());
|
||||
var totalHands = GetHandCount(ent.AsNullable());
|
||||
|
||||
// Can't crawl around without any hands.
|
||||
// Entities without the HandsComponent will always have full crawling speed.
|
||||
if (totalHands == 0)
|
||||
args.SpeedModifier = 0f;
|
||||
else
|
||||
args.SpeedModifier *= (float)freeHands / totalHands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,8 @@ namespace Content.Shared.Input
|
||||
public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject";
|
||||
public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject";
|
||||
public static readonly BoundKeyFunction InspectEntity = "InspectEntity";
|
||||
|
||||
public static readonly BoundKeyFunction InspectServerComponent = "InspectServerComponent";
|
||||
public static readonly BoundKeyFunction InspectClientComponent = "InspectClientComponent";
|
||||
public static readonly BoundKeyFunction MappingUnselect = "MappingUnselect";
|
||||
public static readonly BoundKeyFunction SaveMap = "SaveMap";
|
||||
public static readonly BoundKeyFunction MappingEnablePick = "MappingEnablePick";
|
||||
|
||||
@@ -119,7 +119,7 @@ public sealed class LockSystem : EntitySystem
|
||||
if (!lockComp.ShowExamine)
|
||||
return;
|
||||
|
||||
args.PushText(Loc.GetString(lockComp.Locked
|
||||
args.PushMarkup(Loc.GetString(lockComp.Locked
|
||||
? "lock-comp-on-examined-is-locked"
|
||||
: "lock-comp-on-examined-is-unlocked",
|
||||
("entityName", Identity.Name(uid, EntityManager))));
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -66,6 +67,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _charges = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine= default!;
|
||||
|
||||
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
||||
|
||||
@@ -399,22 +401,30 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
#endregion
|
||||
#region Knock Spells
|
||||
/// <summary>
|
||||
/// Opens all doors and locks within range
|
||||
/// Opens all doors and locks within range.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
private void OnKnockSpell(KnockSpellEvent args)
|
||||
{
|
||||
if (args.Handled || !PassesSpellPrerequisites(args.Action, args.Performer))
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
Knock(args.Performer, args.Range);
|
||||
}
|
||||
|
||||
var transform = Transform(args.Performer);
|
||||
/// <summary>
|
||||
/// Opens all doors and locks within range.
|
||||
/// </summary>
|
||||
/// <param name="performer">Performer of spell. </param>
|
||||
/// <param name="range">Radius around <see cref="performer"/> in which all doors and locks should be opened.</param>
|
||||
public void Knock(EntityUid performer, float range)
|
||||
{
|
||||
var transform = Transform(performer);
|
||||
|
||||
// Look for doors and lockers, and don't open/unlock them if they're already opened/unlocked.
|
||||
foreach (var target in _lookup.GetEntitiesInRange(_transform.GetMapCoordinates(args.Performer, transform), args.Range, flags: LookupFlags.Dynamic | LookupFlags.Static))
|
||||
foreach (var target in _lookup.GetEntitiesInRange(_transform.GetMapCoordinates(performer, transform), range, flags: LookupFlags.Dynamic | LookupFlags.Static))
|
||||
{
|
||||
if (!_interaction.InRangeUnobstructed(args.Performer, target, range: 0, collisionMask: CollisionGroup.Opaque))
|
||||
if (!_examine.InRangeUnOccluded(performer, target, range: 0))
|
||||
continue;
|
||||
|
||||
if (TryComp<DoorBoltComponent>(target, out var doorBoltComp) && doorBoltComp.BoltsDown)
|
||||
@@ -424,7 +434,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
_door.StartOpening(target);
|
||||
|
||||
if (TryComp<LockComponent>(target, out var lockComp) && lockComp.Locked)
|
||||
_lock.Unlock(target, args.Performer, lockComp);
|
||||
_lock.Unlock(target, performer, lockComp);
|
||||
}
|
||||
}
|
||||
// End Knock Spells
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -43,6 +44,8 @@ public abstract partial class SharedMindSystem : EntitySystem
|
||||
|
||||
private HashSet<Entity<MindComponent>> _pickingMinds = new();
|
||||
|
||||
private readonly EntProtoId _mindProto = "MindBase";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -226,7 +229,7 @@ public abstract partial class SharedMindSystem : EntitySystem
|
||||
|
||||
public Entity<MindComponent> CreateMind(NetUserId? userId, string? name = null)
|
||||
{
|
||||
var mindId = Spawn(null, MapCoordinates.Nullspace);
|
||||
var mindId = Spawn(_mindProto, MapCoordinates.Nullspace);
|
||||
_metadata.SetEntityName(mindId, name == null ? "mind" : $"mind ({name})");
|
||||
var mind = EnsureComp<MindComponent>(mindId);
|
||||
mind.CharacterName = name;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Conveyor;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Movement.Components;
|
||||
@@ -58,6 +58,9 @@ public abstract class SharedConveyorController : VirtualController
|
||||
|
||||
private void OnConveyedFriction(Entity<ConveyedComponent> ent, ref TileFrictionEvent args)
|
||||
{
|
||||
if(!ent.Comp.Conveying)
|
||||
return;
|
||||
|
||||
// Conveyed entities don't get friction, they just get wishdir applied so will inherently slowdown anyway.
|
||||
args.Modifier = 0f;
|
||||
}
|
||||
@@ -140,7 +143,15 @@ public abstract class SharedConveyorController : VirtualController
|
||||
continue;
|
||||
|
||||
var physics = ent.Entity.Comp3;
|
||||
|
||||
if (physics.BodyStatus != BodyStatus.OnGround)
|
||||
{
|
||||
SetConveying(ent.Entity.Owner, ent.Entity.Comp1, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
var velocity = physics.LinearVelocity;
|
||||
var angularVelocity = physics.AngularVelocity;
|
||||
var targetDir = ent.Direction;
|
||||
|
||||
// If mob is moving with the conveyor then combine the directions.
|
||||
@@ -163,6 +174,7 @@ public abstract class SharedConveyorController : VirtualController
|
||||
// We provide a small minimum friction speed as well for those times where the friction would stop large objects
|
||||
// snagged on corners from sliding into the centerline.
|
||||
_mover.Friction(0.2f, frameTime: frameTime, friction: 5f, ref velocity);
|
||||
_mover.Friction(0f, frameTime: frameTime, friction: 5f, ref angularVelocity);
|
||||
}
|
||||
|
||||
SharedMoverController.Accelerate(ref velocity, targetDir, 20f, frameTime);
|
||||
@@ -172,8 +184,10 @@ public abstract class SharedConveyorController : VirtualController
|
||||
// Need friction to outweigh the movement as it will bounce a bit against the wall.
|
||||
// This facilitates being able to sleep entities colliding into walls.
|
||||
_mover.Friction(0f, frameTime: frameTime, friction: 40f, ref velocity);
|
||||
_mover.Friction(0f, frameTime: frameTime, friction: 40f, ref angularVelocity);
|
||||
}
|
||||
|
||||
PhysicsSystem.SetAngularVelocity(ent.Entity.Owner, angularVelocity);
|
||||
PhysicsSystem.SetLinearVelocity(ent.Entity.Owner, velocity, wakeBody: false);
|
||||
|
||||
if (!IsConveyed((ent.Entity.Owner, ent.Entity.Comp2)))
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
|
||||
namespace Content.Shared.Power;
|
||||
|
||||
/// <summary>
|
||||
@@ -7,27 +10,36 @@ namespace Content.Shared.Power;
|
||||
public readonly record struct ChargeChangedEvent(float Charge, float MaxCharge);
|
||||
|
||||
/// <summary>
|
||||
/// Event that supports multiple battery types.
|
||||
/// Raised when it is necessary to get information about battery charges.
|
||||
/// Works with either <see cref="BatteryComponent"/> or <see cref="PowerCellSlotComponent"/>.
|
||||
/// If there are multiple batteries then the results will be summed up.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed class GetChargeEvent : EntityEventArgs
|
||||
public record struct GetChargeEvent
|
||||
{
|
||||
public float CurrentCharge;
|
||||
public float MaxCharge;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when it is necessary to change the current battery charge to a some value.
|
||||
/// Method event that supports multiple battery types.
|
||||
/// Raised when it is necessary to change the current battery charge by some value.
|
||||
/// Works with either <see cref="BatteryComponent"/> or <see cref="PowerCellSlotComponent"/>.
|
||||
/// If there are multiple batteries then they will be changed in order of subscription until the total value was reached.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public sealed class ChangeChargeEvent : EntityEventArgs
|
||||
public record struct ChangeChargeEvent(float Amount)
|
||||
{
|
||||
public float OriginalValue;
|
||||
public float ResidualValue;
|
||||
/// <summary>
|
||||
/// The total amount of charge to change the battery's storage by (in joule).
|
||||
/// A positive value adds charge, a negative value removes charge.
|
||||
/// </summary>
|
||||
public readonly float Amount = Amount;
|
||||
|
||||
public ChangeChargeEvent(float value)
|
||||
{
|
||||
OriginalValue = value;
|
||||
ResidualValue = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The amount of charge that still has to be removed.
|
||||
/// For cases where there are multiple batteries.
|
||||
/// </summary>
|
||||
public float ResidualValue = Amount;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,8 @@ namespace Content.Shared.Power.Components;
|
||||
[Access(typeof(SharedBatterySystem))]
|
||||
public partial class BatteryComponent : Component
|
||||
{
|
||||
public string SolutionName = "battery";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum charge of the battery in joules (ie. watt seconds)
|
||||
/// Maximum charge of the battery in joules (i.e. watt seconds)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[GuidebookData]
|
||||
@@ -23,11 +21,11 @@ public partial class BatteryComponent : Component
|
||||
/// <summary>
|
||||
/// Current charge of the battery in joules (ie. watt seconds)
|
||||
/// </summary>
|
||||
[DataField("startingCharge")]
|
||||
[DataField("startingCharge")] // TODO: rename this datafield to currentCharge
|
||||
public float CurrentCharge;
|
||||
|
||||
/// <summary>
|
||||
/// The price per one joule. Default is 1 credit for 10kJ.
|
||||
/// The price per one joule. Default is 1 speso for 10kJ.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PricePerJoule = 0.0001f;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Power.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Self-recharging battery.
|
||||
/// To be used in combination with <see cref="BatteryComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class BatterySelfRechargerComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the component currently enabled?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AutoRecharge = true;
|
||||
|
||||
/// <summary>
|
||||
/// At what rate does the entity automatically recharge?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float AutoRechargeRate;
|
||||
|
||||
/// <summary>
|
||||
/// How long should the entity stop automatically recharging if charge is used?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan AutoRechargePauseTime = TimeSpan.FromSeconds(0);
|
||||
|
||||
/// <summary>
|
||||
/// Do not auto recharge if this timestamp has yet to happen, set for the auto recharge pause system.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan NextAutoRecharge = TimeSpan.FromSeconds(0);
|
||||
}
|
||||
@@ -12,33 +12,61 @@ public abstract class SharedBatterySystem : EntitySystem
|
||||
SubscribeLocalEvent<BatteryComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
}
|
||||
|
||||
private void OnEmpPulse(Entity<BatteryComponent> entity, ref EmpPulseEvent args)
|
||||
private void OnEmpPulse(Entity<BatteryComponent> ent, ref EmpPulseEvent args)
|
||||
{
|
||||
args.Affected = true;
|
||||
UseCharge(entity, args.EnergyConsumption, entity.Comp);
|
||||
UseCharge(ent.AsNullable(), args.EnergyConsumption);
|
||||
// Apply a cooldown to the entity's self recharge if needed to avoid it immediately self recharging after an EMP.
|
||||
TrySetChargeCooldown(entity);
|
||||
TrySetChargeCooldown(ent.Owner);
|
||||
}
|
||||
|
||||
public virtual float UseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public virtual void SetMaxCharge(EntityUid uid, float value, BatteryComponent? battery = null) { }
|
||||
|
||||
public virtual float ChangeCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
/// <summary>
|
||||
/// Changes the battery's charge by the given amount.
|
||||
/// A positive value will add charge, a negative value will remove charge.
|
||||
/// </summary>
|
||||
/// <returns>The actually changed amount.</returns>
|
||||
public virtual float ChangeCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a self recharge and puts it on cooldown if applicable.
|
||||
/// Removes the given amount of charge from the battery.
|
||||
/// </summary>
|
||||
public virtual void TrySetChargeCooldown(EntityUid uid, float value = -1) { }
|
||||
/// <returns>The actually changed amount.</returns>
|
||||
public virtual float UseCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public virtual bool TryUseCharge(EntityUid uid, float value, BatteryComponent? battery = null)
|
||||
/// <summary>
|
||||
/// If sufficient charge is available on the battery, use it. Otherwise, don't.
|
||||
/// Always returns false on the client.
|
||||
/// </summary>
|
||||
/// <returns>If the full amount was able to be removed.</returns>
|
||||
public virtual bool TryUseCharge(Entity<BatteryComponent?> ent, float amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the battery's charge.
|
||||
/// </summary>
|
||||
public virtual void SetCharge(Entity<BatteryComponent?> ent, float value) { }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the battery's maximum charge.
|
||||
/// </summary>
|
||||
public virtual void SetMaxCharge(Entity<BatteryComponent?> ent, float value) { }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity has a self recharge and puts it on cooldown if applicable.
|
||||
/// Uses the cooldown time given in the component.
|
||||
/// </summary>
|
||||
public virtual void TrySetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent) { }
|
||||
|
||||
/// <summary>
|
||||
/// Puts the entity's self recharge on cooldown for the specified time.
|
||||
/// </summary>
|
||||
public virtual void SetChargeCooldown(Entity<BatterySelfRechargerComponent?> ent, TimeSpan cooldown) { }
|
||||
}
|
||||
|
||||
9
Content.Shared/Roles/Components/XenoborgRoleComponent.cs
Normal file
9
Content.Shared/Roles/Components/XenoborgRoleComponent.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Roles.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a xenoborg.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class XenoborgRoleComponent : Component;
|
||||
@@ -78,6 +78,12 @@ public sealed partial class BorgChassisComponent : Component
|
||||
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> NoBatteryAlert = "BorgBatteryNone";
|
||||
|
||||
/// <summary>
|
||||
/// If the entity can open own UI.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanOpenSelfUi;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Silicons.Borgs.Components;
|
||||
|
||||
@@ -24,6 +25,13 @@ public sealed partial class BorgModuleComponent : Component
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public bool DefaultModule;
|
||||
|
||||
/// <summary>
|
||||
/// List of types of borgs this module fits into.
|
||||
/// This only affects examine text. The actual whitelist for modules that can be inserted into a borg is defined in its <see cref="BorgChassisComponent"/>.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<LocId>? BorgFitTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Popups;
|
||||
@@ -35,10 +37,30 @@ public abstract partial class SharedBorgSystem : EntitySystem
|
||||
SubscribeLocalEvent<BorgChassisComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
|
||||
SubscribeLocalEvent<BorgChassisComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
|
||||
SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
|
||||
SubscribeLocalEvent<BorgModuleComponent, ExaminedEvent>(OnModuleExamine);
|
||||
|
||||
InitializeRelay();
|
||||
}
|
||||
|
||||
private void OnModuleExamine(Entity<BorgModuleComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.BorgFitTypes == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.BorgFitTypes.Count == 0)
|
||||
return;
|
||||
|
||||
var typeList = new List<string>();
|
||||
|
||||
foreach (var type in ent.Comp.BorgFitTypes)
|
||||
{
|
||||
typeList.Add(Loc.GetString(type));
|
||||
}
|
||||
|
||||
var types = ContentLocalizationManager.FormatList(typeList);
|
||||
args.PushMarkup(Loc.GetString("borg-module-fit", ("types", types)));
|
||||
}
|
||||
|
||||
private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -98,8 +120,8 @@ public abstract partial class SharedBorgSystem : EntitySystem
|
||||
|
||||
private void OnUIOpenAttempt(EntityUid uid, BorgChassisComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
// borgs can't view their own ui
|
||||
if (args.User == uid)
|
||||
// borgs generaly can't view their own ui
|
||||
if (args.User == uid && !component.CanOpenSelfUi)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Movement.Events;
|
||||
@@ -54,7 +54,7 @@ public abstract partial class SharedStunSystem
|
||||
SubscribeLocalEvent<KnockedDownComponent, BuckleAttemptEvent>(OnBuckleAttempt);
|
||||
SubscribeLocalEvent<KnockedDownComponent, StandAttemptEvent>(OnStandAttempt);
|
||||
|
||||
// Updating movement a friction
|
||||
// Updating movement and friction
|
||||
SubscribeLocalEvent<KnockedDownComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshKnockedSpeed);
|
||||
SubscribeLocalEvent<KnockedDownComponent, RefreshFrictionModifiersEvent>(OnRefreshFriction);
|
||||
SubscribeLocalEvent<KnockedDownComponent, TileFrictionEvent>(OnKnockedTileFriction);
|
||||
@@ -66,6 +66,9 @@ public abstract partial class SharedStunSystem
|
||||
SubscribeLocalEvent<CrawlerComponent, KnockedDownRefreshEvent>(OnKnockdownRefresh);
|
||||
SubscribeLocalEvent<CrawlerComponent, DamageChangedEvent>(OnDamaged);
|
||||
SubscribeLocalEvent<KnockedDownComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
|
||||
SubscribeLocalEvent<KnockedDownComponent, DidEquipHandEvent>(OnHandEquipped);
|
||||
SubscribeLocalEvent<KnockedDownComponent, DidUnequipHandEvent>(OnHandUnequipped);
|
||||
SubscribeLocalEvent<KnockedDownComponent, HandCountChangedEvent>(OnHandCountChanged);
|
||||
SubscribeLocalEvent<GravityAffectedComponent, KnockDownAttemptEvent>(OnKnockdownAttempt);
|
||||
SubscribeLocalEvent<GravityAffectedComponent, GetStandUpTimeEvent>(OnGetStandUpTime);
|
||||
|
||||
@@ -522,6 +525,30 @@ public abstract partial class SharedStunSystem
|
||||
RemCompDeferred<KnockedDownComponent>(entity);
|
||||
}
|
||||
|
||||
private void OnHandEquipped(Entity<KnockedDownComponent> entity, ref DidEquipHandEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnHandUnequipped(Entity<KnockedDownComponent> entity, ref DidUnequipHandEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnHandCountChanged(Entity<KnockedDownComponent> entity, ref HandCountChangedEvent args)
|
||||
{
|
||||
if (GameTiming.ApplyingState)
|
||||
return; // The result of the change is already networked separately in the same game state
|
||||
|
||||
RefreshKnockedMovement(entity);
|
||||
}
|
||||
|
||||
private void OnKnockdownAttempt(Entity<GravityAffectedComponent> entity, ref KnockDownAttemptEvent args)
|
||||
{
|
||||
// Directed, targeted moth attack.
|
||||
@@ -582,6 +609,7 @@ public abstract partial class SharedStunSystem
|
||||
|
||||
ent.Comp.SpeedModifier = ev.SpeedModifier;
|
||||
ent.Comp.FrictionModifier = ev.FrictionModifier;
|
||||
Dirty(ent);
|
||||
|
||||
_movementSpeedModifier.RefreshMovementSpeedModifiers(ent);
|
||||
_movementSpeedModifier.RefreshFrictionModifiers(ent);
|
||||
|
||||
74
Content.Shared/Tips/SharedTipsSystem.cs
Normal file
74
Content.Shared/Tips/SharedTipsSystem.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Tips;
|
||||
|
||||
/// <summary>
|
||||
/// Handles periodically displaying gameplay tips to all players ingame.
|
||||
/// </summary>
|
||||
public abstract class SharedTipsSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Always adds this time to a speech message. This is so really short message stay around for a bit.
|
||||
/// </summary>
|
||||
private const float SpeechBuffer = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Expected reading speed.
|
||||
/// </summary>
|
||||
private const float Wpm = 180f;
|
||||
|
||||
/// <summary>
|
||||
/// Send a tippy message to all clients.
|
||||
/// </summary>
|
||||
/// <param name="message">The text to show in the speech bubble.</param>
|
||||
/// <param name="prototype">The entity to show. Defaults to tippy.</param>
|
||||
/// <param name="speakTime">The time the speech bubble is shown, in seconds.</param>
|
||||
/// <param name="slideTime">The time the entity takes to walk onto the screen, in seconds.</param>
|
||||
/// <param name="waddleInterval">The time between waddle animation steps, in seconds.</param>
|
||||
public virtual void SendTippy(
|
||||
string message,
|
||||
EntProtoId? prototype = null,
|
||||
float speakTime = 5f,
|
||||
float slideTime = 3f,
|
||||
float waddleInterval = 0.5f)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Send a tippy message to the given player session.
|
||||
/// </summary>
|
||||
/// <param name="session">The player session to send the message to.</param>
|
||||
/// <param name="message">The text to show in the speech bubble.</param>
|
||||
/// <param name="prototype">The entity to show. Defaults to tippy.</param>
|
||||
/// <param name="speakTime">The time the speech bubble is shown, in seconds.</param>
|
||||
/// <param name="slideTime">The time the entity takes to walk onto the screen, in seconds.</param>
|
||||
/// <param name="waddleInterval">The time between waddle animation steps, in seconds.</param>
|
||||
public virtual void SendTippy(
|
||||
ICommonSession session,
|
||||
string message,
|
||||
EntProtoId? prototype = null,
|
||||
float speakTime = 5f,
|
||||
float slideTime = 3f,
|
||||
float waddleInterval = 0.5f)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Send a random tippy message from the dataset given in <see cref="CCVars.TipsDataset"/>.
|
||||
/// </summary>
|
||||
public virtual void AnnounceRandomTip() { }
|
||||
|
||||
/// <summary>
|
||||
/// Set a random time stamp for the next automatic game tip.
|
||||
/// </summary>
|
||||
public virtual void RecalculateNextTipTime() { }
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the recommended speak time for a given message.
|
||||
/// </summary>
|
||||
public float GetSpeechTime(string text)
|
||||
{
|
||||
var wordCount = (float)text.Split().Length;
|
||||
return SpeechBuffer + wordCount * (60f / Wpm);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,37 @@
|
||||
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Tips;
|
||||
|
||||
/// <summary>
|
||||
/// Networked event that makes a client show a message on their screen using tippy or another protoype.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TippyEvent : EntityEventArgs
|
||||
public sealed class TippyEvent(string msg, EntProtoId? proto, float speakTime, float slideTime, float waddleInterval) : EntityEventArgs
|
||||
{
|
||||
public TippyEvent(string msg)
|
||||
{
|
||||
Msg = msg;
|
||||
}
|
||||
/// <summary>
|
||||
/// The text to show in the speech bubble.
|
||||
/// </summary>
|
||||
public string Msg = msg;
|
||||
|
||||
public string Msg;
|
||||
public string? Proto;
|
||||
/// <summary>
|
||||
/// The entity to show. Defaults to tippy.
|
||||
/// </summary>
|
||||
public EntProtoId? Proto = proto;
|
||||
|
||||
// TODO: Why are these defaults even here, have the caller specify. This get overriden only most of the time.
|
||||
public float SpeakTime = 5;
|
||||
public float SlideTime = 3;
|
||||
public float WaddleInterval = 0.5f;
|
||||
/// <summary>
|
||||
/// The time the speech bubble is shown, in seconds.
|
||||
/// </summary>
|
||||
public float SpeakTime = speakTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time the entity takes to walk onto the screen, in seconds.
|
||||
/// </summary>
|
||||
public float SlideTime = slideTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time between waddle animation steps, in seconds.
|
||||
/// </summary>
|
||||
public float WaddleInterval = waddleInterval;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given tags when triggered.
|
||||
/// If TargetUser is true the tags will be added to the user instead.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class AddTagsOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The tags to add.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<ProtoId<TagPrototype>> Tags = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Trigger.Components.Triggers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// This component creates an admin log when receiving a trigger.
|
||||
/// <see cref="BaseXOnTriggerComponent.TargetUser"/> is ignored.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class AdminLogOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The message displayed in the logs describing what specifically was done by this trigger.
|
||||
/// This entity and the user will be included alongside the message.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public LocId Message = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// What type of action took place?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LogType LogType = LogType.Trigger;
|
||||
|
||||
/// <summary>
|
||||
/// How important is this trigger?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LogImpact LogImpact = LogImpact.Low;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger effect for removing and *deleting* all items in container(s) on the target.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be very careful when setting <see cref="BaseXOnTriggerComponent.TargetUser"/> to true or all your organs might fall out.
|
||||
/// In fact, never set it to true.
|
||||
/// </remarks>
|
||||
/// <seealso cref="EmptyContainersOnTriggerComponent"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class CleanContainersOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Names of containers to empty.
|
||||
/// If null, all containers will be emptied.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string>? Container;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger effect for removing all items in container(s) on the target.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Be very careful when setting <see cref="BaseXOnTriggerComponent.TargetUser"/> to true or all your organs might fall out.
|
||||
/// In fact, never set it to true.
|
||||
/// </remarks>
|
||||
/// <seealso cref="CleanContainersOnTriggerComponent"/>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class EmptyContainersOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Names of containers to empty.
|
||||
/// If null, all containers will be emptied.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<string>? Container;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the entity play a jitter animation when triggered.
|
||||
/// If TargetUser is true the user will jitter instead.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// The target requires <see cref="StatusEffectsComponent"/>.
|
||||
/// TODO: Convert jitter to the new status effects system.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class JitterOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Jitteriness of the animation.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Amplitude = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Frequency for jittering.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float Frequency = 4.0f;
|
||||
|
||||
/// <summary>
|
||||
/// For how much time to apply the effect.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan Time = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// The status effect cooldown should be refreshed (true) or accumulated (false).
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Refresh;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to change any existing jitter value even if they're greater than the ones we're setting.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ForceValueChange;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger effect for sending the target sidewise (crawling).
|
||||
/// Knockdowns the user if <see cref="BaseXOnTriggerComponent.TargetUser"/> is true.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class KnockdownOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// How long the target is forced to be on the ground.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan KnockdownAmount = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// If true, refresh the duration.
|
||||
/// If false, time is added on-top of any existing forced knockdown.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Refresh = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the entity try and stand automatically?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool AutoStand = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the entity drop their items upon first being knocked down?
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Drop = true;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Trigger.Components.Triggers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// When triggered this component will choose a key and send a new trigger.
|
||||
/// Trigger is sent to user if <see cref="BaseXOnTriggerComponent.TargetUser"/> is true.
|
||||
/// </summary>
|
||||
/// <remarks>Does not support recursive loops where this component triggers itself. Use <see cref="RepeatingTriggerComponent"/> instead.</remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class RandomTriggerOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The trigger keys and their weights.
|
||||
/// </summary>
|
||||
[DataField(required: true), AutoNetworkedField]
|
||||
public ProtoId<WeightedRandomPrototype> RandomKeyOut;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Remove the given tags when triggered.
|
||||
/// If TargetUser is true the tags will be added to the user instead.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class RemoveTagsOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The tags to remove.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<ProtoId<TagPrototype>> Tags = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger effect for stunning an entity.
|
||||
/// Stuns the user if <see cref="BaseXOnTriggerComponent.TargetUser"/> is true.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class StunOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// How long to stun the target.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan StunAmount = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// If true, refresh the stun duration.
|
||||
/// If false, stun is added on-top of any existing stun.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Refresh = true;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the location of the target and the user of the trigger when triggered.
|
||||
/// <see cref="BaseXOnTriggerComponent.TargetUser"/> is ignored.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class SwapLocationOnTriggerComponent : BaseXOnTriggerComponent;
|
||||
@@ -0,0 +1,66 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Sends a tippy message to either the entity or all players when triggered.
|
||||
/// If TargetUser is true the user will receive the message.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class TippyOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Unlocalized message text to send to the player(s).
|
||||
/// Intended only for admeme purposes. For anything else you should use <see cref="LocMessage"/> instead.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public string Message = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Localized message text to send to the player(s).
|
||||
/// This has priority over <see cref="Message"/>.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public LocId? LocMessage;
|
||||
|
||||
/// <summary>
|
||||
/// If true the message will be send to all players.
|
||||
/// If false it will be send to the user or owning entity, depending on <see cref="BaseXOnTriggerComponent.TargetUser"/>.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool SendToAll;
|
||||
|
||||
/// <summary>
|
||||
/// The entity prototype to show to the client.
|
||||
/// Will default to tippy if null.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntProtoId? Prototype;
|
||||
|
||||
/// <summary>
|
||||
/// Use the prototype of the entity owning this component?
|
||||
/// Will take priority over <see cref="Prototype"/>.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool UseOwnerPrototype;
|
||||
|
||||
/// <summary>
|
||||
/// The time the speech bubble is shown, in seconds.
|
||||
/// Will be calculated automatically from the message length if null.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float? SpeakTime;
|
||||
|
||||
/// <summary>
|
||||
/// The time the entity takes to walk onto the screen, in seconds.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float SlideTime = 3f;
|
||||
|
||||
/// <summary>
|
||||
/// The time between waddle animation steps, in seconds.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float WaddleInterval = 0.5f;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Trigger.Components.Triggers;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger for when this entity is thrown and then hits a second entity.
|
||||
/// User is the entity hit.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class TriggerOnThrowDoHitComponent : BaseTriggerOnXComponent;
|
||||
19
Content.Shared/Trigger/Systems/AdminLogOnTriggerSystem.cs
Normal file
19
Content.Shared/Trigger/Systems/AdminLogOnTriggerSystem.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
namespace Content.Shared.Trigger.Systems;
|
||||
|
||||
public sealed class AdminLogOnTriggerSystem : XOnTriggerSystem<AdminLogOnTriggerComponent>
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
|
||||
protected override void OnTrigger(Entity<AdminLogOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
|
||||
{
|
||||
_adminLogger.Add(
|
||||
ent.Comp.LogType,
|
||||
ent.Comp.LogImpact,
|
||||
$"{ToPrettyString(args.User)} sent a trigger using {ToPrettyString(ent)}: {Loc.GetString(ent.Comp.Message)}"
|
||||
);
|
||||
// Intentionally does not handle the event since this shouldn't affect the gamestate.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Shared.Trigger.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Empty containers trigger system.
|
||||
/// </summary>
|
||||
public sealed class EmptyContainersOnTriggerSystem : XOnTriggerSystem<EmptyContainersOnTriggerComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
protected override void OnTrigger(Entity<EmptyContainersOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<ContainerManagerComponent>(target, out var containerComp))
|
||||
return;
|
||||
|
||||
// Empty everything. Make sure a player isn't the target because they will get removed from their body along with their organs
|
||||
if (ent.Comp.Container is null)
|
||||
{
|
||||
foreach (var container in _container.GetAllContainers(target, containerComp))
|
||||
{
|
||||
_container.EmptyContainer(container);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
// Empty containers in a sane way
|
||||
else
|
||||
{
|
||||
foreach (var containerId in ent.Comp.Container)
|
||||
{
|
||||
if (!_container.TryGetContainer(target, containerId, out var container, containerComp))
|
||||
continue;
|
||||
|
||||
_container.EmptyContainer(container);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Empty containers and delete items trigger system.
|
||||
/// </summary>
|
||||
public sealed class CleanContainersOnTriggerSystem : XOnTriggerSystem<CleanContainersOnTriggerComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
protected override void OnTrigger(Entity<CleanContainersOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
|
||||
{
|
||||
if (!TryComp<ContainerManagerComponent>(target, out var containerComp))
|
||||
return;
|
||||
|
||||
// Empty everything. Make sure a player isn't the target because they will get DELETED
|
||||
if (ent.Comp.Container is null)
|
||||
{
|
||||
foreach (var container in _container.GetAllContainers(target, containerComp))
|
||||
{
|
||||
_container.CleanContainer(container);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
// Empty containers in a sane way
|
||||
else
|
||||
{
|
||||
foreach (var containerId in ent.Comp.Container)
|
||||
{
|
||||
if (!_container.TryGetContainer(target, containerId, out var container, containerComp))
|
||||
continue;
|
||||
|
||||
_container.CleanContainer(container);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
Content.Shared/Trigger/Systems/JitterOnTriggerSystem.cs
Normal file
20
Content.Shared/Trigger/Systems/JitterOnTriggerSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Trigger.Systems;
|
||||
|
||||
public sealed class JitterOnTriggerSystem : XOnTriggerSystem<JitterOnTriggerComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedJitteringSystem _jittering = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
protected override void OnTrigger(Entity<JitterOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
|
||||
{
|
||||
// DoJitter mispredicts at the moment.
|
||||
// TODO: Fix this and remove the IsServer check.
|
||||
if (_net.IsServer)
|
||||
_jittering.DoJitter(target, ent.Comp.Time, ent.Comp.Refresh, ent.Comp.Amplitude, ent.Comp.Frequency, ent.Comp.ForceValueChange);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user