using System.Numerics;
using Content.Shared.Gravity;
using Content.Shared.Interaction;
-using Content.Shared.Movement.Components;
using Content.Shared.Projectiles;
using Content.Shared.Tag;
using Robust.Shared.Map;
/// </summary>
public const float FlyTime = 0.15f;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedGravitySystem _gravity = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
var comp = EnsureComp<ThrownItemComponent>(uid);
comp.Thrower = user;
+
+ // Estimate time to arrival so we can apply OnGround status and slow it much faster.
+ var time = direction.Length() / strength;
+ comp.ThrownTime = _gameTiming.CurTime;
+ comp.LandTime = time < FlyTime ? default : comp.ThrownTime + TimeSpan.FromSeconds(time - FlyTime);
+ comp.PlayLandSound = playSound;
+
ThrowingAngleComponent? throwingAngle = null;
// Give it a l'il spin.
var impulseVector = direction.Normalized() * strength * physics.Mass;
_physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
- // Estimate time to arrival so we can apply OnGround status and slow it much faster.
- var time = direction.Length() / strength;
-
- if (time < FlyTime)
+ if (comp.LandTime <= TimeSpan.Zero)
{
_thrownSystem.LandComponent(uid, comp, physics, playSound);
}
else
{
_physics.SetBodyStatus(physics, BodyStatus.InAir);
-
- Timer.Spawn(TimeSpan.FromSeconds(time - FlyTime), () =>
- {
- if (physics.Deleted)
- return;
-
- _thrownSystem.LandComponent(uid, comp, physics, playSound);
- });
}
// Give thrower an impulse in the other direction
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
+using Robust.Shared.Timing;
namespace Content.Shared.Throwing
{
- [RegisterComponent, NetworkedComponent]
+ [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ThrownItemComponent : Component
{
- [ViewVariables(VVAccess.ReadWrite), DataField("thrower")]
+ /// <summary>
+ /// The entity that threw this entity.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public EntityUid? Thrower { get; set; }
+
+ /// <summary>
+ /// The <see cref="IGameTiming.CurTime"/> timestamp at which this entity was thrown.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public TimeSpan? ThrownTime { get; set; }
+
+ /// <summary>
+ /// Compared to <see cref="IGameTiming.CurTime"/> to land this entity, if any.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public TimeSpan? LandTime { get; set; }
+
+ /// <summary>
+ /// Whether or not this entity was already landed.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public bool Landed { get; set; }
+
+ /// <summary>
+ /// Whether or not to play a sound when the entity lands.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public bool PlayLandSound { get; set; }
}
[Serializable, NetSerializable]
using Content.Shared.Database;
using Content.Shared.Physics;
using Content.Shared.Physics.Pull;
-using Robust.Shared.Containers;
-using Robust.Shared.GameStates;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
+using Robust.Shared.Timing;
namespace Content.Shared.Throwing
{
public sealed class ThrownItemSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
public override void Initialize()
{
base.Initialize();
+ SubscribeLocalEvent<ThrownItemComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ThrownItemComponent, PhysicsSleepEvent>(OnSleep);
SubscribeLocalEvent<ThrownItemComponent, StartCollideEvent>(HandleCollision);
SubscribeLocalEvent<ThrownItemComponent, PreventCollideEvent>(PreventCollision);
SubscribeLocalEvent<ThrownItemComponent, ThrownEvent>(ThrowItem);
- SubscribeLocalEvent<ThrownItemComponent, ComponentGetState>(OnGetState);
- SubscribeLocalEvent<ThrownItemComponent, ComponentHandleState>(OnHandleState);
+ SubscribeLocalEvent<ThrownItemComponent, EntityUnpausedEvent>(OnThrownUnpaused);
SubscribeLocalEvent<PullStartedMessage>(HandlePullStarted);
}
- private void OnGetState(EntityUid uid, ThrownItemComponent component, ref ComponentGetState args)
+ private void OnMapInit(EntityUid uid, ThrownItemComponent component, MapInitEvent args)
{
- args.State = new ThrownItemComponentState(GetNetEntity(component.Thrower));
- }
-
- private void OnHandleState(EntityUid uid, ThrownItemComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not ThrownItemComponentState { Thrower: not null } state ||
- !state.Thrower.Value.IsValid())
- {
- return;
- }
-
- component.Thrower = EnsureEntity<ThrownItemComponent>(state.Thrower.Value, uid);
+ component.ThrownTime ??= _gameTiming.CurTime;
}
private void ThrowItem(EntityUid uid, ThrownItemComponent component, ThrownEvent args)
_fixtures.TryCreateFixture(uid, shape, ThrowingFixture, hard: false, collisionMask: (int) CollisionGroup.ThrownItem, manager: fixturesComponent, body: body);
}
+ private void OnThrownUnpaused(EntityUid uid, ThrownItemComponent component, ref EntityUnpausedEvent args)
+ {
+ if (component.LandTime != null)
+ {
+ component.LandTime = component.LandTime.Value + args.PausedTime;
+ }
+ }
+
private void HandleCollision(EntityUid uid, ThrownItemComponent component, ref StartCollideEvent args)
{
if (!args.OtherFixture.Hard)
public void LandComponent(EntityUid uid, ThrownItemComponent thrownItem, PhysicsComponent physics, bool playSound)
{
- if (thrownItem.Deleted || Deleted(uid))
+ if (thrownItem.Landed || thrownItem.Deleted || Deleted(uid))
return;
+ thrownItem.Landed = true;
+
// Assume it's uninteresting if it has no thrower. For now anyway.
if (thrownItem.Thrower is not null)
_adminLogger.Add(LogType.Landed, LogImpact.Low, $"{ToPrettyString(uid):entity} thrown by {ToPrettyString(thrownItem.Thrower.Value):thrower} landed.");
_broadphase.RegenerateContacts(uid, physics);
var landEvent = new LandEvent(thrownItem.Thrower, playSound);
RaiseLocalEvent(uid, ref landEvent);
-
- StopThrow(uid, thrownItem);
}
/// <summary>
RaiseLocalEvent(target, new ThrowHitByEvent(thrown, target, component), true);
RaiseLocalEvent(thrown, new ThrowDoHitEvent(thrown, target, component), true);
}
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<ThrownItemComponent, PhysicsComponent>();
+ while (query.MoveNext(out var uid, out var thrown, out var physics))
+ {
+ if (thrown.LandTime <= _gameTiming.CurTime)
+ {
+ LandComponent(uid, thrown, physics, thrown.PlayLandSound);
+ }
+
+ var stopThrowTime = (thrown.LandTime ?? thrown.ThrownTime) + TimeSpan.FromSeconds(ThrowingSystem.FlyTime);
+ if (stopThrowTime <= _gameTiming.CurTime)
+ {
+ StopThrow(uid, thrown);
+ }
+ }
+ }
}
}