Kick mines (real) (#8056)

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
Pieter-Jan Briers
2022-05-18 06:07:35 +02:00
committed by GitHub
parent 2f604ce05c
commit ebfe5e888f
29 changed files with 635 additions and 220 deletions

View File

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

View File

@@ -321,6 +321,8 @@ namespace Content.Client.Entry
"Armor", "Armor",
"AtmosMonitor", "AtmosMonitor",
"AtmosAlarmable", "AtmosAlarmable",
"LandMine",
"GhostKickUserOnTrigger",
"FireAlarm", "FireAlarm",
"AirAlarm", "AirAlarm",
"RadarConsole", "RadarConsole",

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,7 @@
namespace Content.Server.GhostKick;
[RegisterComponent]
public sealed class GhostKickUserOnTriggerComponent : Component
{
}

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

View File

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

View File

@@ -0,0 +1,7 @@
namespace Content.Server.LandMines;
[RegisterComponent]
public sealed class LandMineComponent : Component
{
}

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

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

View File

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

View File

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

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

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

View File

@@ -0,0 +1 @@
land-mine-triggered = You step on the { $mine }!

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B