Kick mines (real) (#8056)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
2f604ce05c
commit
ebfe5e888f
@@ -6,6 +6,7 @@ using Content.Client.Chat.Managers;
|
|||||||
using Content.Client.EscapeMenu;
|
using Content.Client.EscapeMenu;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
using Content.Client.Flash;
|
using Content.Client.Flash;
|
||||||
|
using Content.Client.GhostKick;
|
||||||
using Content.Client.HUD;
|
using Content.Client.HUD;
|
||||||
using Content.Client.Info;
|
using Content.Client.Info;
|
||||||
using Content.Client.Input;
|
using Content.Client.Input;
|
||||||
@@ -127,6 +128,7 @@ namespace Content.Client.Entry
|
|||||||
IoCManager.Resolve<ChangelogManager>().Initialize();
|
IoCManager.Resolve<ChangelogManager>().Initialize();
|
||||||
IoCManager.Resolve<RulesManager>().Initialize();
|
IoCManager.Resolve<RulesManager>().Initialize();
|
||||||
IoCManager.Resolve<ViewportManager>().Initialize();
|
IoCManager.Resolve<ViewportManager>().Initialize();
|
||||||
|
IoCManager.Resolve<GhostKickManager>().Initialize();
|
||||||
|
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
|||||||
@@ -321,6 +321,8 @@ namespace Content.Client.Entry
|
|||||||
"Armor",
|
"Armor",
|
||||||
"AtmosMonitor",
|
"AtmosMonitor",
|
||||||
"AtmosAlarmable",
|
"AtmosAlarmable",
|
||||||
|
"LandMine",
|
||||||
|
"GhostKickUserOnTrigger",
|
||||||
"FireAlarm",
|
"FireAlarm",
|
||||||
"AirAlarm",
|
"AirAlarm",
|
||||||
"RadarConsole",
|
"RadarConsole",
|
||||||
|
|||||||
40
Content.Client/GhostKick/GhostKickManager.cs
Normal file
40
Content.Client/GhostKick/GhostKickManager.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Shared.GhostKick;
|
||||||
|
using Robust.Client;
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Client.GhostKick;
|
||||||
|
|
||||||
|
public sealed class GhostKickManager
|
||||||
|
{
|
||||||
|
private bool _fakeLossEnabled;
|
||||||
|
|
||||||
|
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||||
|
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_netManager.RegisterNetMessage<MsgGhostKick>(RxCallback);
|
||||||
|
|
||||||
|
_baseClient.RunLevelChanged += BaseClientOnRunLevelChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BaseClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_fakeLossEnabled && e.OldLevel == ClientRunLevel.InGame)
|
||||||
|
{
|
||||||
|
_cfg.SetCVar(CVars.NetFakeLoss, 0);
|
||||||
|
|
||||||
|
_fakeLossEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RxCallback(MsgGhostKick message)
|
||||||
|
{
|
||||||
|
_fakeLossEnabled = true;
|
||||||
|
|
||||||
|
_cfg.SetCVar(CVars.NetFakeLoss, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using Content.Client.Chat.Managers;
|
|||||||
using Content.Client.Clickable;
|
using Content.Client.Clickable;
|
||||||
using Content.Client.EscapeMenu;
|
using Content.Client.EscapeMenu;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
|
using Content.Client.GhostKick;
|
||||||
using Content.Client.HUD;
|
using Content.Client.HUD;
|
||||||
using Content.Client.Info;
|
using Content.Client.Info;
|
||||||
using Content.Client.Items.Managers;
|
using Content.Client.Items.Managers;
|
||||||
@@ -43,6 +44,7 @@ namespace Content.Client.IoC
|
|||||||
IoCManager.Register<ViewportManager, ViewportManager>();
|
IoCManager.Register<ViewportManager, ViewportManager>();
|
||||||
IoCManager.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
IoCManager.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||||
IoCManager.Register<NetworkResourceManager>();
|
IoCManager.Register<NetworkResourceManager>();
|
||||||
|
IoCManager.Register<GhostKickManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Robust.Client.UserInterface.Controls;
|
|||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Client.Popups
|
namespace Content.Client.Popups
|
||||||
{
|
{
|
||||||
@@ -19,6 +18,7 @@ namespace Content.Client.Popups
|
|||||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||||
|
[Dependency] private readonly IMapManager _map = default!;
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||||
|
|
||||||
@@ -55,7 +55,13 @@ namespace Content.Client.Popups
|
|||||||
PopupMessage(message, _eyeManager.CoordinatesToScreen(transform.Coordinates), uid);
|
PopupMessage(message, _eyeManager.CoordinatesToScreen(transform.Coordinates), uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PopupMessage(string message, ScreenCoordinates coordinates, EntityUid? entity = null)
|
private void PopupMessage(string message, ScreenCoordinates coordinates, EntityUid? entity = null)
|
||||||
|
{
|
||||||
|
var mapCoords = _eyeManager.ScreenToMap(coordinates);
|
||||||
|
PopupMessage(message, EntityCoordinates.FromMap(_map, mapCoords), entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopupMessage(string message, EntityCoordinates coordinates, EntityUid? entity = null)
|
||||||
{
|
{
|
||||||
var label = new PopupLabel(_eyeManager, EntityManager)
|
var label = new PopupLabel(_eyeManager, EntityManager)
|
||||||
{
|
{
|
||||||
@@ -67,8 +73,7 @@ namespace Content.Client.Popups
|
|||||||
_userInterfaceManager.PopupRoot.AddChild(label);
|
_userInterfaceManager.PopupRoot.AddChild(label);
|
||||||
label.Measure(Vector2.Infinity);
|
label.Measure(Vector2.Infinity);
|
||||||
|
|
||||||
var mapCoordinates = _eyeManager.ScreenToMap(coordinates.Position);
|
label.InitialPos = coordinates;
|
||||||
label.InitialPos = mapCoordinates;
|
|
||||||
LayoutContainer.SetPosition(label, label.InitialPos.Position);
|
LayoutContainer.SetPosition(label, label.InitialPos.Position);
|
||||||
_aliveLabels.Add(label);
|
_aliveLabels.Add(label);
|
||||||
}
|
}
|
||||||
@@ -161,7 +166,7 @@ namespace Content.Client.Popups
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherPos = label.Entity != null ? Transform(label.Entity.Value).MapPosition : label.InitialPos;
|
var otherPos = label.Entity != null ? Transform(label.Entity.Value).MapPosition : label.InitialPos.ToMap(EntityManager);
|
||||||
|
|
||||||
if (occluded && !ExamineSystemShared.InRangeUnOccluded(
|
if (occluded && !ExamineSystemShared.InRangeUnOccluded(
|
||||||
playerPos,
|
playerPos,
|
||||||
@@ -188,7 +193,7 @@ namespace Content.Client.Popups
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Yes that's right it's not technically MapCoordinates.
|
/// Yes that's right it's not technically MapCoordinates.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public MapCoordinates InitialPos { get; set; }
|
public EntityCoordinates InitialPos { get; set; }
|
||||||
public EntityUid? Entity { get; set; }
|
public EntityUid? Entity { get; set; }
|
||||||
|
|
||||||
public PopupLabel(IEyeManager eyeManager, IEntityManager entityManager)
|
public PopupLabel(IEyeManager eyeManager, IEntityManager entityManager)
|
||||||
@@ -204,11 +209,12 @@ namespace Content.Client.Popups
|
|||||||
{
|
{
|
||||||
TotalTime += eventArgs.DeltaSeconds;
|
TotalTime += eventArgs.DeltaSeconds;
|
||||||
|
|
||||||
Vector2 position;
|
ScreenCoordinates screenCoords;
|
||||||
|
|
||||||
if (Entity == null)
|
if (Entity == null)
|
||||||
position = _eyeManager.WorldToScreen(InitialPos.Position) / UIScale - DesiredSize / 2;
|
screenCoords = _eyeManager.CoordinatesToScreen(InitialPos);
|
||||||
else if (_entityManager.TryGetComponent(Entity.Value, out TransformComponent xform))
|
else if (_entityManager.TryGetComponent(Entity.Value, out TransformComponent xform))
|
||||||
position = (_eyeManager.CoordinatesToScreen(xform.Coordinates).Position / UIScale) - DesiredSize / 2;
|
screenCoords = _eyeManager.CoordinatesToScreen(xform.Coordinates);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Entity has probably been deleted.
|
// Entity has probably been deleted.
|
||||||
@@ -217,6 +223,7 @@ namespace Content.Client.Popups
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var position = screenCoords.Position / UIScale - DesiredSize / 2;
|
||||||
LayoutContainer.SetPosition(this, position - (0, 20 * (TotalTime * TotalTime + TotalTime)));
|
LayoutContainer.SetPosition(this, position - (0, 20 * (TotalTime * TotalTime + TotalTime)));
|
||||||
|
|
||||||
if (TotalTime > 0.5f)
|
if (TotalTime > 0.5f)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Shared.Chemistry.Reaction;
|
|||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Slippery;
|
using Content.Shared.Slippery;
|
||||||
|
using Content.Shared.StepTrigger;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
@@ -28,9 +29,10 @@ namespace Content.Server.Chemistry.TileReactions
|
|||||||
|
|
||||||
if (puddle != null)
|
if (puddle != null)
|
||||||
{
|
{
|
||||||
var slippery = IoCManager.Resolve<IEntityManager>().GetComponent<SlipperyComponent>(puddle.Owner);
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var slippery = entityManager.GetComponent<SlipperyComponent>(puddle.Owner);
|
||||||
slippery.LaunchForwardsMultiplier = _launchForwardsMultiplier;
|
slippery.LaunchForwardsMultiplier = _launchForwardsMultiplier;
|
||||||
slippery.RequiredSlipSpeed = _requiredSlipSpeed;
|
EntitySystem.Get<StepTriggerSystem>().SetRequiredTriggerSpeed(puddle.Owner, _requiredSlipSpeed);
|
||||||
slippery.ParalyzeTime = _paralyzeTime;
|
slippery.ParalyzeTime = _paralyzeTime;
|
||||||
|
|
||||||
return reactVolume;
|
return reactVolume;
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ using Content.Server.Connection;
|
|||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.GhostKick;
|
||||||
using Content.Server.GuideGenerator;
|
using Content.Server.GuideGenerator;
|
||||||
using Content.Server.Info;
|
using Content.Server.Info;
|
||||||
using Content.Server.IoC;
|
using Content.Server.IoC;
|
||||||
|
using Content.Server.LandMines;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
@@ -85,6 +87,8 @@ namespace Content.Server.Entry
|
|||||||
IoCManager.Resolve<INodeGroupFactory>().Initialize();
|
IoCManager.Resolve<INodeGroupFactory>().Initialize();
|
||||||
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();
|
IoCManager.Resolve<IGamePrototypeLoadManager>().Initialize();
|
||||||
IoCManager.Resolve<NetworkResourceManager>().Initialize();
|
IoCManager.Resolve<NetworkResourceManager>().Initialize();
|
||||||
|
IoCManager.Resolve<GhostKickManager>().Initialize();
|
||||||
|
|
||||||
_voteManager.Initialize();
|
_voteManager.Initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Content.Shared.Chemistry.Components;
|
|||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.FixedPoint;
|
using Content.Shared.FixedPoint;
|
||||||
using Content.Shared.Fluids;
|
using Content.Shared.Fluids;
|
||||||
using Content.Shared.Slippery;
|
using Content.Shared.StepTrigger;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
@@ -16,6 +16,7 @@ namespace Content.Server.Fluids.EntitySystems
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||||
[Dependency] private readonly FluidSpreaderSystem _fluidSpreaderSystem = default!;
|
[Dependency] private readonly FluidSpreaderSystem _fluidSpreaderSystem = default!;
|
||||||
|
[Dependency] private readonly StepTriggerSystem _stepTrigger = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -71,20 +72,20 @@ namespace Content.Server.Fluids.EntitySystems
|
|||||||
{
|
{
|
||||||
if ((puddleComponent.SlipThreshold == FixedPoint2.New(-1) ||
|
if ((puddleComponent.SlipThreshold == FixedPoint2.New(-1) ||
|
||||||
puddleComponent.CurrentVolume < puddleComponent.SlipThreshold) &&
|
puddleComponent.CurrentVolume < puddleComponent.SlipThreshold) &&
|
||||||
EntityManager.TryGetComponent(entityUid, out SlipperyComponent? oldSlippery))
|
TryComp(entityUid, out StepTriggerComponent? stepTrigger))
|
||||||
{
|
{
|
||||||
oldSlippery.Slippery = false;
|
_stepTrigger.SetActive(entityUid, false, stepTrigger);
|
||||||
}
|
}
|
||||||
else if (puddleComponent.CurrentVolume >= puddleComponent.SlipThreshold)
|
else if (puddleComponent.CurrentVolume >= puddleComponent.SlipThreshold)
|
||||||
{
|
{
|
||||||
var newSlippery = EntityManager.EnsureComponent<SlipperyComponent>(entityUid);
|
var comp = EnsureComp<StepTriggerComponent>(entityUid);
|
||||||
newSlippery.Slippery = true;
|
_stepTrigger.SetActive(entityUid, true, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args)
|
private void HandlePuddleExamined(EntityUid uid, PuddleComponent component, ExaminedEvent args)
|
||||||
{
|
{
|
||||||
if (EntityManager.TryGetComponent<SlipperyComponent>(uid, out var slippery) && slippery.Slippery)
|
if (TryComp<StepTriggerComponent>(uid, out var slippery) && slippery.Active)
|
||||||
{
|
{
|
||||||
args.PushText(Loc.GetString("puddle-component-examine-is-slipper-text"));
|
args.PushText(Loc.GetString("puddle-component-examine-is-slipper-text"));
|
||||||
}
|
}
|
||||||
|
|||||||
76
Content.Server/GhostKick/GhostKickManager.cs
Normal file
76
Content.Server/GhostKick/GhostKickManager.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.GhostKick;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
|
namespace Content.Server.GhostKick;
|
||||||
|
|
||||||
|
// Handles logic for "ghost kicking".
|
||||||
|
// Basically we boot the client off the server without telling them, so the game shits itself.
|
||||||
|
// Hilariously isn't it?
|
||||||
|
|
||||||
|
public sealed class GhostKickManager
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IServerNetManager _netManager = default!;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_netManager.RegisterNetMessage<MsgGhostKick>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoDisconnect(INetChannel channel, string reason)
|
||||||
|
{
|
||||||
|
Timer.Spawn(TimeSpan.FromMilliseconds(100), () =>
|
||||||
|
{
|
||||||
|
if (!channel.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// We do this so the client can set net.fakeloss 1 before getting ghosted.
|
||||||
|
// This avoids it spamming messages at the server that cause warnings due to unconnected client.
|
||||||
|
channel.SendMessage(new MsgGhostKick());
|
||||||
|
|
||||||
|
Timer.Spawn(TimeSpan.FromMilliseconds(100), () =>
|
||||||
|
{
|
||||||
|
if (!channel.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Actually just remove the client entirely.
|
||||||
|
channel.Disconnect(reason, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Admin)]
|
||||||
|
public sealed class GhostKickCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public string Command => "ghostkick";
|
||||||
|
public string Description => "Kick a client from the server as if their network just dropped.";
|
||||||
|
public string Help => "Usage: ghostkick <Player> [Reason]";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length < 1)
|
||||||
|
{
|
||||||
|
shell.WriteError("Need at least one argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerName = args[0];
|
||||||
|
var reason = args.Length > 1 ? args[1] : "Ghost kicked by console";
|
||||||
|
|
||||||
|
var players = IoCManager.Resolve<IPlayerManager>();
|
||||||
|
var ghostKick = IoCManager.Resolve<GhostKickManager>();
|
||||||
|
|
||||||
|
if (!players.TryGetSessionByUsername(playerName, out var player))
|
||||||
|
{
|
||||||
|
shell.WriteError($"Unable to find player: '{playerName}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostKick.DoDisconnect(player.ConnectedClient, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Server.GhostKick;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class GhostKickUserOnTriggerComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
24
Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs
Normal file
24
Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Content.Server.Explosion.EntitySystems;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.GhostKick;
|
||||||
|
|
||||||
|
public sealed class GhostKickUserOnTriggerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly GhostKickManager _ghostKickManager = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<GhostKickUserOnTriggerComponent, TriggerEvent>(HandleMineTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleMineTriggered(EntityUid uid, GhostKickUserOnTriggerComponent userOnTriggerComponent, TriggerEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp(args.User, out ActorComponent? actor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ghostKickManager.DoDisconnect(
|
||||||
|
actor.PlayerSession.ConnectedClient,
|
||||||
|
"Tripped over a kick mine, crashed through the fourth wall");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ using Content.Server.Chat.Managers;
|
|||||||
using Content.Server.Connection;
|
using Content.Server.Connection;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
|
using Content.Server.GhostKick;
|
||||||
using Content.Server.Info;
|
using Content.Server.Info;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
using Content.Server.Module;
|
using Content.Server.Module;
|
||||||
@@ -52,6 +53,7 @@ namespace Content.Server.IoC
|
|||||||
IoCManager.Register<RoleBanManager, RoleBanManager>();
|
IoCManager.Register<RoleBanManager, RoleBanManager>();
|
||||||
IoCManager.Register<NetworkResourceManager>();
|
IoCManager.Register<NetworkResourceManager>();
|
||||||
IoCManager.Register<IAdminNotesManager, AdminNotesManager>();
|
IoCManager.Register<IAdminNotesManager, AdminNotesManager>();
|
||||||
|
IoCManager.Register<GhostKickManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
Content.Server/LandMines/LandMineComponent.cs
Normal file
7
Content.Server/LandMines/LandMineComponent.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Content.Server.LandMines;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class LandMineComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
40
Content.Server/LandMines/LandMineSystem.cs
Normal file
40
Content.Server/LandMines/LandMineSystem.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.Explosion.EntitySystems;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Content.Shared.StepTrigger;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.LandMines;
|
||||||
|
|
||||||
|
public sealed class LandMineSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly TriggerSystem _trigger = default!;
|
||||||
|
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<LandMineComponent, StepTriggeredEvent>(HandleTriggered);
|
||||||
|
SubscribeLocalEvent<LandMineComponent, StepTriggerAttemptEvent>(HandleTriggerAttempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleTriggerAttempt(
|
||||||
|
EntityUid uid,
|
||||||
|
LandMineComponent component,
|
||||||
|
ref StepTriggerAttemptEvent args)
|
||||||
|
{
|
||||||
|
args.Continue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredEvent args)
|
||||||
|
{
|
||||||
|
_popupSystem.PopupCoordinates(
|
||||||
|
Loc.GetString("land-mine-triggered", ("mine", uid)),
|
||||||
|
Transform(uid).Coordinates,
|
||||||
|
Filter.Entities(args.Tripper));
|
||||||
|
|
||||||
|
_trigger.Trigger(uid, args.Tripper);
|
||||||
|
|
||||||
|
QueueDel(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
17
Content.Shared/GhostKick/MsgGhostKick.cs
Normal file
17
Content.Shared/GhostKick/MsgGhostKick.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Shared.GhostKick;
|
||||||
|
|
||||||
|
public sealed class MsgGhostKick : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Linq;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Inventory;
|
using Content.Shared.Inventory;
|
||||||
using Content.Shared.StatusEffect;
|
using Content.Shared.StatusEffect;
|
||||||
|
using Content.Shared.StepTrigger;
|
||||||
using Content.Shared.Stunnable;
|
using Content.Shared.Stunnable;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Physics.Dynamics;
|
|
||||||
|
|
||||||
namespace Content.Shared.Slippery
|
namespace Content.Shared.Slippery
|
||||||
{
|
{
|
||||||
@@ -17,144 +15,68 @@ namespace Content.Shared.Slippery
|
|||||||
[Dependency] private readonly SharedAdminLogSystem _adminLog = default!;
|
[Dependency] private readonly SharedAdminLogSystem _adminLog = default!;
|
||||||
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
|
||||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
private readonly List<SlipperyComponent> _slipped = new();
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
UpdatesOutsidePrediction = true;
|
SubscribeLocalEvent<SlipperyComponent, StepTriggerAttemptEvent>(HandleAttemptCollide);
|
||||||
|
SubscribeLocalEvent<SlipperyComponent, StepTriggeredEvent>(HandleStepTrigger);
|
||||||
SubscribeLocalEvent<SlipperyComponent, StartCollideEvent>(HandleCollide);
|
|
||||||
SubscribeLocalEvent<NoSlipComponent, SlipAttemptEvent>(OnNoSlipAttempt);
|
SubscribeLocalEvent<NoSlipComponent, SlipAttemptEvent>(OnNoSlipAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollide(EntityUid uid, SlipperyComponent component, StartCollideEvent args)
|
private void HandleStepTrigger(EntityUid uid, SlipperyComponent component, ref StepTriggeredEvent args)
|
||||||
{
|
{
|
||||||
var otherUid = args.OtherFixture.Body.Owner;
|
TrySlip(component, args.Tripper);
|
||||||
|
|
||||||
if (!CanSlip(component, otherUid)) return;
|
|
||||||
|
|
||||||
if (!_slipped.Contains(component))
|
|
||||||
_slipped.Add(component);
|
|
||||||
|
|
||||||
component.Colliding.Add(otherUid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNoSlipAttempt(EntityUid uid, NoSlipComponent component, SlipAttemptEvent args)
|
private void HandleAttemptCollide(
|
||||||
|
EntityUid uid,
|
||||||
|
SlipperyComponent component,
|
||||||
|
ref StepTriggerAttemptEvent args)
|
||||||
|
{
|
||||||
|
args.Continue |= CanSlip(uid, args.Tripper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnNoSlipAttempt(EntityUid uid, NoSlipComponent component, SlipAttemptEvent args)
|
||||||
{
|
{
|
||||||
args.Cancel();
|
args.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private bool CanSlip(EntityUid uid, EntityUid toSlip)
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
{
|
||||||
for (var i = _slipped.Count - 1; i >= 0; i--)
|
return !_container.IsEntityInContainer(uid)
|
||||||
{
|
&& _statusEffectsSystem.CanApplyEffect(toSlip, "Stun"); //Should be KnockedDown instead?
|
||||||
var slipperyComp = _slipped[i];
|
|
||||||
if (!Update(slipperyComp)) continue;
|
|
||||||
_slipped.RemoveAt(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanSlip(SlipperyComponent component, EntityUid uid)
|
private void TrySlip(SlipperyComponent component, EntityUid other)
|
||||||
{
|
{
|
||||||
if (!component.Slippery
|
if (HasComp<KnockedDownComponent>(other))
|
||||||
|| component.Owner.IsInContainer()
|
return;
|
||||||
|| component.Slipped.Contains(uid)
|
|
||||||
|| !_statusEffectsSystem.CanApplyEffect(uid, "Stun")) //Should be KnockedDown instead?
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TrySlip(SlipperyComponent component, IPhysBody ourBody, IPhysBody otherBody)
|
|
||||||
{
|
|
||||||
if (!CanSlip(component, otherBody.Owner)) return false;
|
|
||||||
|
|
||||||
if (otherBody.LinearVelocity.Length < component.RequiredSlipSpeed)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var percentage = otherBody.GetWorldAABB().IntersectPercentage(ourBody.GetWorldAABB());
|
|
||||||
|
|
||||||
if (percentage < component.IntersectPercentage)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EntityManager.HasComponent<KnockedDownComponent>(otherBody.Owner))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ev = new SlipAttemptEvent();
|
var ev = new SlipAttemptEvent();
|
||||||
RaiseLocalEvent(otherBody.Owner, ev, false);
|
RaiseLocalEvent(other, ev, false);
|
||||||
if (ev.Cancelled)
|
if (ev.Cancelled)
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
otherBody.LinearVelocity *= component.LaunchForwardsMultiplier;
|
if (TryComp(other, out PhysicsComponent? physics))
|
||||||
|
physics.LinearVelocity *= component.LaunchForwardsMultiplier;
|
||||||
|
|
||||||
bool playSound = !_statusEffectsSystem.HasStatusEffect(otherBody.Owner, "KnockedDown");
|
var playSound = !_statusEffectsSystem.HasStatusEffect(other, "KnockedDown");
|
||||||
|
|
||||||
_stunSystem.TryParalyze(otherBody.Owner, TimeSpan.FromSeconds(component.ParalyzeTime), true);
|
_stunSystem.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true);
|
||||||
component.Slipped.Add(otherBody.Owner);
|
|
||||||
component.Dirty();
|
|
||||||
|
|
||||||
//Preventing from playing the slip sound when you are already knocked down.
|
// Preventing from playing the slip sound when you are already knocked down.
|
||||||
if(playSound)
|
if (playSound)
|
||||||
{
|
|
||||||
PlaySound(component);
|
PlaySound(component);
|
||||||
}
|
|
||||||
|
|
||||||
_adminLog.Add(LogType.Slip, LogImpact.Low, $"{ToPrettyString(otherBody.Owner):mob} slipped on collision with {ToPrettyString(component.Owner):entity}");
|
_adminLog.Add(LogType.Slip, LogImpact.Low,
|
||||||
|
$"{ToPrettyString(other):mob} slipped on collision with {ToPrettyString(component.Owner):entity}");
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Until we get predicted slip sounds TM?
|
// Until we get predicted slip sounds TM?
|
||||||
protected abstract void PlaySound(SlipperyComponent component);
|
protected abstract void PlaySound(SlipperyComponent component);
|
||||||
|
|
||||||
private bool Update(SlipperyComponent component)
|
|
||||||
{
|
|
||||||
if (component.Deleted || !component.Slippery || component.Colliding.Count == 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? body))
|
|
||||||
{
|
|
||||||
component.Colliding.Clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var uid in component.Colliding.ToArray())
|
|
||||||
{
|
|
||||||
if (!uid.IsValid())
|
|
||||||
{
|
|
||||||
component.Colliding.Remove(uid);
|
|
||||||
component.Slipped.Remove(uid);
|
|
||||||
component.Dirty();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? otherPhysics) ||
|
|
||||||
!body.GetWorldAABB().Intersects(otherPhysics.GetWorldAABB()))
|
|
||||||
{
|
|
||||||
component.Colliding.Remove(uid);
|
|
||||||
component.Slipped.Remove(uid);
|
|
||||||
component.Dirty();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!component.Slipped.Contains(uid))
|
|
||||||
TrySlip(component, body, otherPhysics);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Shared.Sound;
|
using Content.Shared.Sound;
|
||||||
|
using Content.Shared.Sound;
|
||||||
|
using Content.Shared.StepTrigger;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Slippery
|
namespace Content.Shared.Slippery
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Causes somebody to slip when they walk over this entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Requires <see cref="StepTriggerComponent"/>, see that component for some additional properties.
|
||||||
|
/// </remarks>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[NetworkedComponent()]
|
[NetworkedComponent]
|
||||||
public sealed class SlipperyComponent : Component
|
public sealed class SlipperyComponent : Component
|
||||||
{
|
{
|
||||||
private float _paralyzeTime = 3f;
|
private float _paralyzeTime = 3f;
|
||||||
private float _intersectPercentage = 0.3f;
|
|
||||||
private float _requiredSlipSpeed = 3.5f;
|
|
||||||
private float _launchForwardsMultiplier = 1f;
|
private float _launchForwardsMultiplier = 1f;
|
||||||
private bool _slippery = true;
|
|
||||||
private SoundSpecifier _slipSound = new SoundPathSpecifier("/Audio/Effects/slip.ogg");
|
private SoundSpecifier _slipSound = new SoundPathSpecifier("/Audio/Effects/slip.ogg");
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of entities that are currently colliding with the entity.
|
|
||||||
/// </summary>
|
|
||||||
public readonly HashSet<EntityUid> Colliding = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of entities that have been slipped by this component, which shouldn't be slipped again.
|
|
||||||
/// </summary>
|
|
||||||
public readonly HashSet<EntityUid> Slipped = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path to the sound to be played when a mob slips.
|
/// Path to the sound to be played when a mob slips.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -61,40 +56,6 @@ namespace Content.Shared.Slippery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Percentage of shape intersection for a slip to occur.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("intersectPercentage")]
|
|
||||||
public float IntersectPercentage
|
|
||||||
{
|
|
||||||
get => _intersectPercentage;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (MathHelper.CloseToPercent(_intersectPercentage, value)) return;
|
|
||||||
|
|
||||||
_intersectPercentage = value;
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Entities will only be slipped if their speed exceeds this limit.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("requiredSlipSpeed")]
|
|
||||||
public float RequiredSlipSpeed
|
|
||||||
{
|
|
||||||
get => _requiredSlipSpeed;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (MathHelper.CloseToPercent(_requiredSlipSpeed, value)) return;
|
|
||||||
|
|
||||||
_requiredSlipSpeed = value;
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entity's speed will be multiplied by this to slip it forwards.
|
/// The entity's speed will be multiplied by this to slip it forwards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -112,44 +73,18 @@ namespace Content.Shared.Slippery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether or not this component will try to slip entities.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
[DataField("slippery")]
|
|
||||||
public bool Slippery
|
|
||||||
{
|
|
||||||
get => _slippery;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_slippery == value) return;
|
|
||||||
|
|
||||||
_slippery = value;
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ComponentState GetComponentState()
|
public override ComponentState GetComponentState()
|
||||||
{
|
{
|
||||||
return new SlipperyComponentState(ParalyzeTime, IntersectPercentage, RequiredSlipSpeed, LaunchForwardsMultiplier, Slippery, SlipSound.GetSound(), Slipped.ToArray());
|
return new SlipperyComponentState(ParalyzeTime, LaunchForwardsMultiplier, SlipSound.GetSound());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||||
{
|
{
|
||||||
if (curState is not SlipperyComponentState state) return;
|
if (curState is not SlipperyComponentState state) return;
|
||||||
|
|
||||||
_slippery = state.Slippery;
|
|
||||||
_intersectPercentage = state.IntersectPercentage;
|
|
||||||
_paralyzeTime = state.ParalyzeTime;
|
_paralyzeTime = state.ParalyzeTime;
|
||||||
_requiredSlipSpeed = state.RequiredSlipSpeed;
|
|
||||||
_launchForwardsMultiplier = state.LaunchForwardsMultiplier;
|
_launchForwardsMultiplier = state.LaunchForwardsMultiplier;
|
||||||
_slipSound = new SoundPathSpecifier(state.SlipSound);
|
_slipSound = new SoundPathSpecifier(state.SlipSound);
|
||||||
Slipped.Clear();
|
|
||||||
|
|
||||||
foreach (var slipped in state.Slipped)
|
|
||||||
{
|
|
||||||
Slipped.Add(slipped);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,22 +92,14 @@ namespace Content.Shared.Slippery
|
|||||||
public sealed class SlipperyComponentState : ComponentState
|
public sealed class SlipperyComponentState : ComponentState
|
||||||
{
|
{
|
||||||
public float ParalyzeTime { get; }
|
public float ParalyzeTime { get; }
|
||||||
public float IntersectPercentage { get; }
|
|
||||||
public float RequiredSlipSpeed { get; }
|
|
||||||
public float LaunchForwardsMultiplier { get; }
|
public float LaunchForwardsMultiplier { get; }
|
||||||
public bool Slippery { get; }
|
|
||||||
public string SlipSound { get; }
|
public string SlipSound { get; }
|
||||||
public readonly EntityUid[] Slipped;
|
|
||||||
|
|
||||||
public SlipperyComponentState(float paralyzeTime, float intersectPercentage, float requiredSlipSpeed, float launchForwardsMultiplier, bool slippery, string slipSound, EntityUid[] slipped)
|
public SlipperyComponentState(float paralyzeTime, float launchForwardsMultiplier, string slipSound)
|
||||||
{
|
{
|
||||||
ParalyzeTime = paralyzeTime;
|
ParalyzeTime = paralyzeTime;
|
||||||
IntersectPercentage = intersectPercentage;
|
|
||||||
RequiredSlipSpeed = requiredSlipSpeed;
|
|
||||||
LaunchForwardsMultiplier = launchForwardsMultiplier;
|
LaunchForwardsMultiplier = launchForwardsMultiplier;
|
||||||
Slippery = slippery;
|
|
||||||
SlipSound = slipSound;
|
SlipSound = slipSound;
|
||||||
Slipped = slipped;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
Content.Shared/StepTrigger/StepTriggerComponent.cs
Normal file
62
Content.Shared/StepTrigger/StepTriggerComponent.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.StepTrigger;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[NetworkedComponent]
|
||||||
|
[Friend(typeof(StepTriggerSystem))]
|
||||||
|
public sealed class StepTriggerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of entities that are currently colliding with the entity.
|
||||||
|
/// </summary>
|
||||||
|
public readonly HashSet<EntityUid> Colliding = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of entities that are standing on this entity,
|
||||||
|
/// which shouldn't be able to trigger it again until stepping off.
|
||||||
|
/// </summary>
|
||||||
|
public readonly HashSet<EntityUid> CurrentlySteppedOn = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this component will currently try to trigger for entities.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("active")]
|
||||||
|
public bool Active { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ratio of shape intersection for a trigger to occur.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("intersectRatio")]
|
||||||
|
public float IntersectRatio { get; set; } = 0.3f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entities will only be triggered if their speed exceeds this limit.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("requiredTriggeredSpeed")]
|
||||||
|
public float RequiredTriggerSpeed { get; set; } = 3.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[Friend(typeof(StepTriggerSystem))]
|
||||||
|
public sealed class StepTriggerActiveComponent : Component
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class StepTriggerComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public float IntersectRatio { get; }
|
||||||
|
public float RequiredTriggerSpeed { get; }
|
||||||
|
public readonly EntityUid[] CurrentlySteppedOn;
|
||||||
|
|
||||||
|
public StepTriggerComponentState(float intersectRatio, EntityUid[] currentlySteppedOn, float requiredTriggerSpeed)
|
||||||
|
{
|
||||||
|
IntersectRatio = intersectRatio;
|
||||||
|
CurrentlySteppedOn = currentlySteppedOn;
|
||||||
|
RequiredTriggerSpeed = requiredTriggerSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
177
Content.Shared/StepTrigger/StepTriggerSystem.cs
Normal file
177
Content.Shared/StepTrigger/StepTriggerSystem.cs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Physics.Dynamics;
|
||||||
|
|
||||||
|
namespace Content.Shared.StepTrigger;
|
||||||
|
|
||||||
|
public sealed class StepTriggerSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<StepTriggerComponent, ComponentGetState>(TriggerGetState);
|
||||||
|
SubscribeLocalEvent<StepTriggerComponent, ComponentHandleState>(TriggerHandleState);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StepTriggerComponent, StartCollideEvent>(HandleCollide);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var (_, trigger) in EntityQuery<StepTriggerActiveComponent, StepTriggerComponent>())
|
||||||
|
{
|
||||||
|
if (!Update(trigger))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
RemComp<StepTriggerActiveComponent>(trigger.Owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Update(StepTriggerComponent component)
|
||||||
|
{
|
||||||
|
if (component.Deleted || !component.Active || component.Colliding.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var otherUid in component.Colliding.ToArray())
|
||||||
|
{
|
||||||
|
if (!otherUid.IsValid())
|
||||||
|
{
|
||||||
|
component.Colliding.Remove(otherUid);
|
||||||
|
component.CurrentlySteppedOn.Remove(otherUid);
|
||||||
|
component.Dirty();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This shouldn't be calculating based on world AABBs.
|
||||||
|
var ourAabb = _entityLookup.GetWorldAABB(component.Owner);
|
||||||
|
var otherAabb = _entityLookup.GetWorldAABB(otherUid);
|
||||||
|
|
||||||
|
if (!TryComp(otherUid, out PhysicsComponent? otherPhysics) || !ourAabb.Intersects(otherAabb))
|
||||||
|
{
|
||||||
|
component.Colliding.Remove(otherUid);
|
||||||
|
component.CurrentlySteppedOn.Remove(otherUid);
|
||||||
|
component.Dirty();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.CurrentlySteppedOn.Contains(otherUid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!CanTrigger(component.Owner, otherUid, component))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (otherPhysics.LinearVelocity.Length < component.RequiredTriggerSpeed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var percentage = otherAabb.IntersectPercentage(ourAabb);
|
||||||
|
if (percentage < component.IntersectRatio)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var ev = new StepTriggeredEvent { Source = component.Owner, Tripper = otherUid };
|
||||||
|
RaiseLocalEvent(component.Owner, ref ev);
|
||||||
|
|
||||||
|
component.CurrentlySteppedOn.Add(otherUid);
|
||||||
|
component.Dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent component)
|
||||||
|
{
|
||||||
|
if (!component.Active || component.CurrentlySteppedOn.Contains(otherUid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid };
|
||||||
|
|
||||||
|
RaiseLocalEvent(uid, ref msg);
|
||||||
|
|
||||||
|
return msg.Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCollide(EntityUid uid, StepTriggerComponent component, StartCollideEvent args)
|
||||||
|
{
|
||||||
|
var otherUid = args.OtherFixture.Body.Owner;
|
||||||
|
|
||||||
|
if (!CanTrigger(uid, otherUid, component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EnsureComp<StepTriggerActiveComponent>(uid);
|
||||||
|
|
||||||
|
component.Colliding.Add(otherUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TriggerHandleState(EntityUid uid, StepTriggerComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not StepTriggerComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.RequiredTriggerSpeed = state.RequiredTriggerSpeed;
|
||||||
|
component.IntersectRatio = state.IntersectRatio;
|
||||||
|
component.CurrentlySteppedOn.Clear();
|
||||||
|
|
||||||
|
foreach (var slipped in state.CurrentlySteppedOn)
|
||||||
|
{
|
||||||
|
component.CurrentlySteppedOn.Add(slipped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TriggerGetState(EntityUid uid, StepTriggerComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new StepTriggerComponentState(
|
||||||
|
component.IntersectRatio,
|
||||||
|
component.CurrentlySteppedOn.ToArray(),
|
||||||
|
component.RequiredTriggerSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetIntersectRatio(EntityUid uid, float ratio, StepTriggerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (MathHelper.CloseToPercent(component.IntersectRatio, ratio))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.IntersectRatio = ratio;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRequiredTriggerSpeed(EntityUid uid, float speed, StepTriggerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (MathHelper.CloseToPercent(component.RequiredTriggerSpeed, speed))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.RequiredTriggerSpeed = speed;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetActive(EntityUid uid, bool active, StepTriggerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (active == component.Active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Active = active;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public struct StepTriggerAttemptEvent
|
||||||
|
{
|
||||||
|
public EntityUid Source;
|
||||||
|
public EntityUid Tripper;
|
||||||
|
public bool Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public struct StepTriggeredEvent
|
||||||
|
{
|
||||||
|
public EntityUid Source;
|
||||||
|
public EntityUid Tripper;
|
||||||
|
}
|
||||||
1
Resources/Locale/en-US/land-mines/land-mines.ftl
Normal file
1
Resources/Locale/en-US/land-mines/land-mines.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
land-mine-triggered = You step on the { $mine }!
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
maxVol: 600
|
maxVol: 600
|
||||||
canReact: false
|
canReact: false
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: IronMetalFoam
|
id: IronMetalFoam
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
- type: PuddleVisualizer
|
- type: PuddleVisualizer
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: puddle
|
name: puddle
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
recolor: true
|
recolor: true
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: puddle
|
name: puddle
|
||||||
@@ -92,6 +94,7 @@
|
|||||||
- type: PuddleVisualizer
|
- type: PuddleVisualizer
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PuddleBlood
|
id: PuddleBlood
|
||||||
@@ -137,6 +140,7 @@
|
|||||||
- type: PuddleVisualizer
|
- type: PuddleVisualizer
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: toxins vomit
|
name: toxins vomit
|
||||||
@@ -162,6 +166,7 @@
|
|||||||
- type: PuddleVisualizer
|
- type: PuddleVisualizer
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: writing
|
name: writing
|
||||||
@@ -180,4 +185,4 @@
|
|||||||
- type: PuddleVisualizer
|
- type: PuddleVisualizer
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
launchForwardsMultiplier: 2.0
|
launchForwardsMultiplier: 2.0
|
||||||
|
- type: StepTrigger
|
||||||
|
|||||||
@@ -177,8 +177,9 @@
|
|||||||
sprite: Objects/Specific/Hydroponics/banana.rsi
|
sprite: Objects/Specific/Hydroponics/banana.rsi
|
||||||
HeldPrefix: peel
|
HeldPrefix: peel
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
intersectPercentage: 0.2
|
|
||||||
launchForwardsMultiplier: 6.0
|
launchForwardsMultiplier: 6.0
|
||||||
|
- type: StepTrigger
|
||||||
|
intersectRatio: 0.2
|
||||||
- type: CollisionWake
|
- type: CollisionWake
|
||||||
enabled: false
|
enabled: false
|
||||||
- type: Physics
|
- type: Physics
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
- type: Slippery
|
- type: Slippery
|
||||||
paralyzeTime: 4
|
paralyzeTime: 4
|
||||||
launchForwardsMultiplier: 9.0
|
launchForwardsMultiplier: 9.0
|
||||||
|
- type: StepTrigger
|
||||||
- type: CollisionWake
|
- type: CollisionWake
|
||||||
enabled: false
|
enabled: false
|
||||||
- type: Physics
|
- type: Physics
|
||||||
|
|||||||
46
Resources/Prototypes/Entities/Objects/Misc/land_mine.yml
Normal file
46
Resources/Prototypes/Entities/Objects/Misc/land_mine.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
- type: entity
|
||||||
|
id: BaseLandMine
|
||||||
|
abstract: true
|
||||||
|
components:
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: MovedByPressure
|
||||||
|
- type: Physics
|
||||||
|
bodyType: Static
|
||||||
|
fixedRotation: true
|
||||||
|
- type: Fixtures
|
||||||
|
fixtures:
|
||||||
|
- shape:
|
||||||
|
!type:PhysShapeAabb
|
||||||
|
bounds: "-0.2,-0.2,0.2,0.2"
|
||||||
|
id: "slips"
|
||||||
|
hard: false
|
||||||
|
layer:
|
||||||
|
- LowImpassable
|
||||||
|
- type: Sprite
|
||||||
|
drawdepth: FloorObjects
|
||||||
|
sprite: Objects/Misc/uglymine.rsi
|
||||||
|
state: uglymine
|
||||||
|
- type: LandMine
|
||||||
|
- type: StepTrigger
|
||||||
|
requiredTriggeredSpeed: 0
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: kick mine
|
||||||
|
parent: BaseLandMine
|
||||||
|
id: LandMineKick
|
||||||
|
components:
|
||||||
|
- type: GhostKickUserOnTrigger
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: explosive mine
|
||||||
|
parent: BaseLandMine
|
||||||
|
id: LandMineExplosive
|
||||||
|
components:
|
||||||
|
- type: ExplodeOnTrigger
|
||||||
|
- type: Explosive
|
||||||
|
explosionType: Default
|
||||||
|
maxIntensity: 10
|
||||||
|
intensitySlope: 3
|
||||||
|
totalIntensity: 120 # about a ~4 tile radius
|
||||||
|
canCreateVacuum: false
|
||||||
@@ -14,8 +14,9 @@
|
|||||||
sprite: Objects/Specific/Janitorial/soap.rsi
|
sprite: Objects/Specific/Janitorial/soap.rsi
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
paralyzeTime: 2
|
paralyzeTime: 2
|
||||||
intersectPercentage: 0.2
|
|
||||||
launchForwardsMultiplier: 6.0
|
launchForwardsMultiplier: 6.0
|
||||||
|
- type: StepTrigger
|
||||||
|
intersectRatio: 0.2
|
||||||
- type: CollisionWake
|
- type: CollisionWake
|
||||||
enabled: false
|
enabled: false
|
||||||
- type: Physics
|
- type: Physics
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
- type: Slippery
|
- type: Slippery
|
||||||
paralyzeTime: 5
|
paralyzeTime: 5
|
||||||
launchForwardsMultiplier: 9.0
|
launchForwardsMultiplier: 9.0
|
||||||
|
- type: StepTrigger
|
||||||
- type: Item
|
- type: Item
|
||||||
HeldPrefix: syndie
|
HeldPrefix: syndie
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@
|
|||||||
state: gibs
|
state: gibs
|
||||||
- type: Slippery
|
- type: Slippery
|
||||||
paralyzeTime: 2
|
paralyzeTime: 2
|
||||||
|
- type: StepTrigger
|
||||||
- type: Item
|
- type: Item
|
||||||
HeldPrefix: gibs
|
HeldPrefix: gibs
|
||||||
|
|
||||||
@@ -96,5 +99,6 @@
|
|||||||
- type: Slippery
|
- type: Slippery
|
||||||
paralyzeTime: 7
|
paralyzeTime: 7
|
||||||
launchForwardsMultiplier: 9.0
|
launchForwardsMultiplier: 9.0
|
||||||
|
- type: StepTrigger
|
||||||
- type: Item
|
- type: Item
|
||||||
HeldPrefix: omega
|
HeldPrefix: omega
|
||||||
|
|||||||
35
Resources/Textures/Objects/Misc/uglymine.rsi/meta.json
Normal file
35
Resources/Textures/Objects/Misc/uglymine.rsi/meta.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"size":
|
||||||
|
{
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"copyright": "https://github.com/tgstation/tgstation/blob/4323540b6bec3ae93bbf13d685c2dbe0cb40a36e/icons/obj/items_and_weapons.dmi",
|
||||||
|
"states":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "uglymine",
|
||||||
|
"directions": 1,
|
||||||
|
"delays":
|
||||||
|
[
|
||||||
|
[
|
||||||
|
0.3,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uglymine-inactive",
|
||||||
|
"directions": 1,
|
||||||
|
"delays":
|
||||||
|
[
|
||||||
|
[
|
||||||
|
0.3,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 247 B |
BIN
Resources/Textures/Objects/Misc/uglymine.rsi/uglymine.png
Normal file
BIN
Resources/Textures/Objects/Misc/uglymine.rsi/uglymine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 246 B |
Reference in New Issue
Block a user