diff --git a/Content.Client/GameObjects/Components/Projectiles/ProjectileComponent.cs b/Content.Client/GameObjects/Components/Projectiles/ProjectileComponent.cs
new file mode 100644
index 0000000000..27e8ed6285
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Projectiles/ProjectileComponent.cs
@@ -0,0 +1,23 @@
+using Content.Shared.GameObjects.Components.Projectiles;
+using Robust.Shared.GameObjects;
+
+#nullable enable
+
+namespace Content.Client.GameObjects.Components.Projectiles
+{
+ [RegisterComponent]
+ public class ProjectileComponent : SharedProjectileComponent
+ {
+ protected override EntityUid Shooter => _shooter;
+ private EntityUid _shooter;
+
+ public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
+ {
+ if (curState is ProjectileComponentState compState)
+ {
+ _shooter = compState.Shooter;
+ IgnoreShooter = compState.IgnoreShooter;
+ }
+ }
+ }
+}
diff --git a/Content.Client/GameObjects/Components/Projectiles/ThrownItemComponent.cs b/Content.Client/GameObjects/Components/Projectiles/ThrownItemComponent.cs
new file mode 100644
index 0000000000..6f43d63505
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Projectiles/ThrownItemComponent.cs
@@ -0,0 +1,12 @@
+using Content.Shared.GameObjects;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.GameObjects.Components.Projectiles
+{
+ [RegisterComponent]
+ public class ThrownItemComponent : ProjectileComponent
+ {
+ public override string Name => "ThrownItem";
+ public override uint? NetID => ContentNetIDs.THROWN_ITEM;
+ }
+}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index dd1936e544..6a49b25753 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -31,7 +31,6 @@
"Multitool",
"Wrench",
"Crowbar",
- "Projectile",
"MeleeWeapon",
"Storeable",
"Dice",
diff --git a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
index c571a352a4..d1d0e45fd3 100644
--- a/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
+++ b/Content.Server/GameObjects/Components/Projectiles/ProjectileComponent.cs
@@ -1,23 +1,21 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.GameObjects;
+using Content.Shared.GameObjects.Components.Projectiles;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
-using Robust.Shared.Physics;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Projectiles
{
[RegisterComponent]
- public class ProjectileComponent : Component, ICollideSpecial, ICollideBehavior
+ public class ProjectileComponent : SharedProjectileComponent, ICollideBehavior
{
- public override string Name => "Projectile";
-
- public bool IgnoreShooter = true;
+ protected override EntityUid Shooter => _shooter;
private EntityUid _shooter = EntityUid.Invalid;
@@ -29,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
get => _damages;
set => _damages = value;
}
-
+
public bool DeleteOnCollide => _deleteOnCollide;
private bool _deleteOnCollide;
@@ -56,18 +54,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
public void IgnoreEntity(IEntity shooter)
{
_shooter = shooter.Uid;
- }
-
- ///
- /// Special collision override, can be used to give custom behaviors deciding when to collide
- ///
- ///
- ///
- bool ICollideSpecial.PreventCollide(IPhysBody collidedwith)
- {
- if (IgnoreShooter && collidedwith.Owner.Uid == _shooter)
- return true;
- return false;
+ Dirty();
}
///
@@ -83,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
{
EntitySystem.Get().PlayAtCoords(_soundHit, entity.Transform.GridPosition);
}
-
+
if (entity.TryGetComponent(out DamageableComponent damage))
{
Owner.EntityManager.TryGetEntity(_shooter, out var shooter);
@@ -106,5 +93,10 @@ namespace Content.Server.GameObjects.Components.Projectiles
{
if (collideCount > 0 && DeleteOnCollide) Owner.Delete();
}
+
+ public override ComponentState GetComponentState()
+ {
+ return new ProjectileComponentState(NetID!.Value, _shooter, IgnoreShooter);
+ }
}
}
diff --git a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
index c4f09636e1..1f5600b186 100644
--- a/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
+++ b/Content.Server/GameObjects/Components/Projectiles/ThrownItemComponent.cs
@@ -1,26 +1,27 @@
-using System.Collections.Generic;
-using Content.Server.GameObjects.Components.Projectiles;
+using Content.Server.GameObjects.Components.Projectiles;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.Physics;
-using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
+using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.Physics;
using Robust.Shared.IoC;
+using Robust.Shared.Maths;
+using Robust.Shared.Timers;
namespace Content.Server.GameObjects.Components
{
[RegisterComponent]
internal class ThrownItemComponent : ProjectileComponent, ICollideBehavior
{
-#pragma warning disable 649
- [Dependency] private readonly IEntitySystemManager _entitySystemManager;
-#pragma warning restore 649
+ public const float DefaultThrowTime = 0.25f;
private bool _shouldCollide = true;
public override string Name => "ThrownItem";
+ public override uint? NetID => ContentNetIDs.THROWN_ITEM;
///
/// User who threw the item.
@@ -34,6 +35,7 @@ namespace Content.Server.GameObjects.Components
{
damage.TakeDamage(DamageType.Brute, 10, Owner, User);
}
+
// Stop colliding with mobs, this mimics not having enough velocity to do damage
// after impacting the first object.
// For realism this should actually be changed when the velocity of the object is less than a threshold.
@@ -44,20 +46,56 @@ namespace Content.Server.GameObjects.Components
}
}
- public void PostCollide(int collideCount)
+ private void StopThrow()
{
-
- if (collideCount > 0 && Owner.TryGetComponent(out CollidableComponent body) && body.PhysicsShapes.Count >= 1)
+ if (Owner.TryGetComponent(out CollidableComponent body) && body.PhysicsShapes.Count >= 1)
{
- body.PhysicsShapes[0].CollisionMask &= (int)~CollisionGroup.MobImpassable;
+ body.PhysicsShapes[0].CollisionMask &= (int) ~CollisionGroup.ThrownItem;
- // KYS, your job is finished. Trigger ILand as well.
var physics = Owner.GetComponent();
- (physics.Controller as ThrowController).StopThrow();
- physics.RemoveController();
+ physics.LinearVelocity = Vector2.Zero;
+ physics.Status = BodyStatus.OnGround;
+ body.Status = BodyStatus.OnGround;
Owner.RemoveComponent();
- _entitySystemManager.GetEntitySystem().LandInteraction(User, Owner, Owner.Transform.GridPosition);
+ EntitySystem.Get().LandInteraction(User, Owner, Owner.Transform.GridPosition);
}
}
+
+ void ICollideBehavior.PostCollide(int collideCount)
+ {
+ if (collideCount > 0)
+ {
+ StopThrow();
+ }
+ }
+
+ public void StartThrow(Vector2 initialImpulse)
+ {
+ var comp = Owner.GetComponent();
+ comp.Status = BodyStatus.InAir;
+ comp.Momentum = initialImpulse;
+ StartStopTimer();
+ }
+
+ private void StartStopTimer()
+ {
+ Timer.Spawn((int) (DefaultThrowTime * 1000), MaybeStopThrow);
+ }
+
+ private void MaybeStopThrow()
+ {
+ if (Deleted)
+ {
+ return;
+ }
+
+ if (IoCManager.Resolve().IsWeightless(Owner.Transform.GridPosition))
+ {
+ StartStopTimer();
+ return;
+ }
+
+ StopThrow();
+ }
}
}
diff --git a/Content.Server/Throw/ThrowHelper.cs b/Content.Server/Throw/ThrowHelper.cs
index 4723f9ee8c..6866108b06 100644
--- a/Content.Server/Throw/ThrowHelper.cs
+++ b/Content.Server/Throw/ThrowHelper.cs
@@ -56,7 +56,7 @@ namespace Content.Server.Throw
if (colComp.PhysicsShapes.Count == 0)
colComp.PhysicsShapes.Add(new PhysShapeAabb());
- colComp.PhysicsShapes[0].CollisionMask |= (int) (CollisionGroup.MobImpassable | CollisionGroup.Impassable);
+ colComp.PhysicsShapes[0].CollisionMask |= (int) CollisionGroup.ThrownItem;
colComp.Status = BodyStatus.InAir;
}
var angle = new Angle(targetLoc.ToMapPos(mapManager) - sourceLoc.ToMapPos(mapManager));
@@ -83,8 +83,7 @@ namespace Content.Server.Throw
// scaling is handled elsewhere, this is just multiplying by 60 independent of timing as a fix until elsewhere values are updated
var spd = throwForce * 60;
- physComp.SetController();
- (physComp.Controller as ThrowController)?.StartThrow(angle.ToVec() * spd);
+ projComp.StartThrow(angle.ToVec() * spd);
if (throwSourceEnt != null && throwSourceEnt.TryGetComponent(out var physics)
&& physics.Controller is MoverController mover)
@@ -135,7 +134,7 @@ namespace Content.Server.Throw
// Calculate the force necessary to land a throw based on throw duration, mass and distance.
var distance = (targetLoc.ToMapPos(mapManager) - sourceLoc.ToMapPos(mapManager)).Length;
- var throwDuration = ThrowController.DefaultThrowTime;
+ var throwDuration = ThrownItemComponent.DefaultThrowTime;
var mass = 1f;
if (thrownEnt.TryGetComponent(out PhysicsComponent physicsComponent))
{
diff --git a/Content.Shared/GameObjects/Components/Projectiles/SharedProjectileComponent.cs b/Content.Shared/GameObjects/Components/Projectiles/SharedProjectileComponent.cs
new file mode 100644
index 0000000000..f3b3f26352
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Projectiles/SharedProjectileComponent.cs
@@ -0,0 +1,45 @@
+using System;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.GameObjects.Components.Projectiles
+{
+ public abstract class SharedProjectileComponent : Component, ICollideSpecial
+ {
+ private bool _ignoreShooter = true;
+ public override string Name => "Projectile";
+ public override uint? NetID => ContentNetIDs.PROJECTILE;
+
+ protected abstract EntityUid Shooter { get; }
+
+ public bool IgnoreShooter
+ {
+ get => _ignoreShooter;
+ set
+ {
+ _ignoreShooter = value;
+ Dirty();
+ }
+ }
+
+ [NetSerializable, Serializable]
+ protected class ProjectileComponentState : ComponentState
+ {
+ public ProjectileComponentState(uint netId, EntityUid shooter, bool ignoreShooter) : base(netId)
+ {
+ Shooter = shooter;
+ IgnoreShooter = ignoreShooter;
+ }
+
+ public EntityUid Shooter { get; }
+ public bool IgnoreShooter { get; }
+ }
+
+ public bool PreventCollide(IPhysBody collidedwith)
+ {
+ return IgnoreShooter && collidedwith.Owner.Uid == Shooter;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs
index 2fc90dbae7..e4249a5aa0 100644
--- a/Content.Shared/GameObjects/ContentNetIDs.cs
+++ b/Content.Shared/GameObjects/ContentNetIDs.cs
@@ -56,6 +56,8 @@
public const uint THIRST = 1050;
public const uint FLASHABLE = 1051;
+ public const uint PROJECTILE = 1052;
+ public const uint THROWN_ITEM = 1053;
// Net IDs for integration tests.
public const uint PREDICTION_TEST = 10001;
diff --git a/Content.Shared/Physics/CollisionGroup.cs b/Content.Shared/Physics/CollisionGroup.cs
index 988f52517c..688d4e7e0c 100644
--- a/Content.Shared/Physics/CollisionGroup.cs
+++ b/Content.Shared/Physics/CollisionGroup.cs
@@ -24,6 +24,7 @@ namespace Content.Shared.Physics
MapGrid = MapGridHelpers.CollisionGroup, // Map grids, like shuttles. This is the actual grid itself, not the walls or other entities connected to the grid.
MobMask = Impassable | MobImpassable | VaultImpassable | SmallImpassable,
+ ThrownItem = MobImpassable | Impassable,
// 32 possible groups
AllMask = -1,
}
diff --git a/Content.Shared/Physics/ThrowController.cs b/Content.Shared/Physics/ThrowController.cs
deleted file mode 100644
index 476fa0f6b7..0000000000
--- a/Content.Shared/Physics/ThrowController.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Robust.Shared.GameObjects.Components;
-using Robust.Shared.Interfaces.Physics;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Physics;
-using Robust.Shared.Timers;
-
-namespace Content.Shared.Physics
-{
- public class ThrowController: VirtualController
- {
- private float _throwTime;
- private PhysicsComponent _component;
-
- public const float DefaultThrowTime = 0.25f;
-
- public float ThrowTime
- {
- get => _throwTime;
- set => _throwTime = value;
- }
-
- public override PhysicsComponent ControlledComponent
- {
- set => _component = value;
- }
-
- public void StartThrow(Vector2 initialImpulse)
- {
- _component.Momentum = initialImpulse;
- _component.Status = BodyStatus.InAir;
- Timer.Spawn((int) (ThrowTime * 1000), StopThrow);
- }
-
- public void StopThrow()
- {
- if (_component == null || _component.Owner.Deleted) return;
- if (IoCManager.Resolve().IsWeightless(_component.Owner.Transform.GridPosition))
- {
- Timer.Spawn((int) (ThrowTime * 1000), StopThrow);
- return;
- }
- _component.Status = BodyStatus.OnGround;
- _component.LinearVelocity = Vector2.Zero;
- }
-
- public ThrowController()
- {
- ThrowTime = DefaultThrowTime;
- }
- }
-}