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