]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
GORILLA Gauntlets (#23012)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Thu, 28 Dec 2023 03:11:13 +0000 (22:11 -0500)
committerGitHub <noreply@github.com>
Thu, 28 Dec 2023 03:11:13 +0000 (20:11 -0700)
* GORILLA gauntlets

* oh shit this too

22 files changed:
Content.Server/Anomaly/Effects/AnomalyCoreSystem.cs
Content.Shared/Anomaly/Components/AnomalyCoreComponent.cs [moved from Content.Server/Anomaly/Components/AnomalyCoreComponent.cs with 72% similarity]
Content.Shared/Anomaly/Components/CorePoweredThrowerComponent.cs [new file with mode: 0644]
Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs [new file with mode: 0644]
Content.Shared/Anomaly/SharedAnomalySystem.cs
Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs [new file with mode: 0644]
Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs
Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs [new file with mode: 0644]
Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
Resources/Locale/en-US/anomaly/anomaly.ftl
Resources/Locale/en-US/research/technologies.ftl
Resources/Prototypes/Entities/Objects/Specific/Research/anomaly.yml
Resources/Prototypes/Entities/Objects/Weapons/Melee/baseball_bat.yml
Resources/Prototypes/Entities/Structures/Machines/lathe.yml
Resources/Prototypes/Entities/Structures/Specific/Anomaly/anomalies.yml
Resources/Prototypes/Entities/Structures/Specific/Anomaly/cores.yml
Resources/Prototypes/Recipes/Lathes/devices.yml
Resources/Prototypes/Research/experimental.yml
Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/icon.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-left.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-right.png [new file with mode: 0644]
Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/meta.json [new file with mode: 0644]

index eb45101373d8a520388b8d88ec10818081d94514..dea116a65e70cf5fee9524e4edbe6811cd674fa1 100644 (file)
@@ -1,6 +1,5 @@
-using Content.Server.Anomaly.Components;
 using Content.Server.Cargo.Systems;
-using Content.Shared.Anomaly;
+using Content.Shared.Anomaly.Components;
 using Robust.Shared.Timing;
 
 namespace Content.Server.Anomaly.Effects;
@@ -10,47 +9,19 @@ namespace Content.Server.Anomaly.Effects;
 /// </summary>
 public sealed class AnomalyCoreSystem : EntitySystem
 {
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
     [Dependency] private readonly IGameTiming _gameTiming = default!;
 
     public override void Initialize()
     {
-        SubscribeLocalEvent<AnomalyCoreComponent, MapInitEvent>(OnMapInit);
         SubscribeLocalEvent<AnomalyCoreComponent, PriceCalculationEvent>(OnGetPrice);
     }
 
-    private void OnMapInit(Entity<AnomalyCoreComponent> core, ref MapInitEvent args)
-    {
-        core.Comp.DecayMoment = _gameTiming.CurTime + TimeSpan.FromSeconds(core.Comp.TimeToDecay);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var query = EntityQueryEnumerator<AnomalyCoreComponent>();
-        while (query.MoveNext(out var uid, out var component))
-        {
-            if (component.IsDecayed)
-                continue;
-
-            //When time runs out, we completely decompose
-            if (component.DecayMoment < _gameTiming.CurTime)
-                Decay(uid, component);
-        }
-    }
     private void OnGetPrice(Entity<AnomalyCoreComponent> core, ref PriceCalculationEvent args)
     {
         var timeLeft = core.Comp.DecayMoment - _gameTiming.CurTime;
-        var lerp = (double) (timeLeft.TotalSeconds / core.Comp.TimeToDecay);
+        var lerp = timeLeft.TotalSeconds / core.Comp.TimeToDecay;
         lerp = Math.Clamp(lerp, 0, 1);
 
         args.Price = MathHelper.Lerp(core.Comp.EndPrice, core.Comp.StartPrice, lerp);
     }
-
-    private void Decay(EntityUid uid, AnomalyCoreComponent component)
-    {
-        _appearance.SetData(uid, AnomalyCoreVisuals.Decaying, false);
-        component.IsDecayed = true;
-    }
 }
similarity index 72%
rename from Content.Server/Anomaly/Components/AnomalyCoreComponent.cs
rename to Content.Shared/Anomaly/Components/AnomalyCoreComponent.cs
index f86f95c71a881914908adf25595abcfaca6cb119..68d00f03ecb96f0cec5394ef58af88a9e8e87b15 100644 (file)
@@ -1,15 +1,15 @@
-using Content.Server.Anomaly.Effects;
+using Robust.Shared.GameStates;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Server.Anomaly.Components;
+namespace Content.Shared.Anomaly.Components;
 
 /// <summary>
 /// This component exists for a limited time, and after it expires it modifies the entity, greatly reducing its value and changing its visuals
 /// </summary>
-[RegisterComponent, Access(typeof(AnomalyCoreSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedAnomalyCoreSystem))]
+[AutoGenerateComponentState]
 public sealed partial class AnomalyCoreComponent : Component
 {
-
     /// <summary>
     /// Amount of time required for the core to decompose into an inert core
     /// </summary>
@@ -20,6 +20,7 @@ public sealed partial class AnomalyCoreComponent : Component
     /// The moment of core decay. It is set during entity initialization.
     /// </summary>
     [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
     public TimeSpan DecayMoment;
 
     /// <summary>
@@ -38,5 +39,14 @@ public sealed partial class AnomalyCoreComponent : Component
     /// Has the core decayed?
     /// </summary>
     [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
     public bool IsDecayed;
+
+    /// <summary>
+    /// The amount of GORILLA charges the core has.
+    /// Not used when <see cref="IsDecayed"/> is false.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public int Charge = 5;
 }
diff --git a/Content.Shared/Anomaly/Components/CorePoweredThrowerComponent.cs b/Content.Shared/Anomaly/Components/CorePoweredThrowerComponent.cs
new file mode 100644 (file)
index 0000000..ee21ce1
--- /dev/null
@@ -0,0 +1,25 @@
+using System.Numerics;
+using Content.Shared.Weapons.Melee.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Anomaly.Components;
+
+/// <summary>
+/// This is used for an entity with <see cref="MeleeThrowOnHitComponent"/> that is governed by an anomaly core inside of it.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedAnomalyCoreSystem))]
+public sealed partial class CorePoweredThrowerComponent : Component
+{
+    /// <summary>
+    /// The ID of the item slot containing the core.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public string CoreSlotId = "core_slot";
+
+    /// <summary>
+    /// A range for how much the stability variable on the anomaly will increase with each throw.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public Vector2 StabilityPerThrow = new(0.1f, 0.2f);
+}
diff --git a/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs b/Content.Shared/Anomaly/SharedAnomalyCoreSystem.cs
new file mode 100644 (file)
index 0000000..f4864a5
--- /dev/null
@@ -0,0 +1,112 @@
+using Content.Shared.Anomaly.Components;
+using Content.Shared.Construction.Components;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Examine;
+using Content.Shared.Weapons.Melee.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Anomaly;
+
+/// <summary>
+/// This component reduces the value of the entity during decay
+/// </summary>
+public sealed class SharedAnomalyCoreSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
+
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<AnomalyCoreComponent, MapInitEvent>(OnMapInit);
+        SubscribeLocalEvent<CorePoweredThrowerComponent, AttemptMeleeThrowOnHitEvent>(OnAttemptMeleeThrowOnHit);
+        SubscribeLocalEvent<CorePoweredThrowerComponent, ExaminedEvent>(OnCorePoweredExamined);
+    }
+
+    private void OnMapInit(Entity<AnomalyCoreComponent> core, ref MapInitEvent args)
+    {
+        core.Comp.DecayMoment = _gameTiming.CurTime + TimeSpan.FromSeconds(core.Comp.TimeToDecay);
+        Dirty(core, core.Comp);
+    }
+
+    private void OnAttemptMeleeThrowOnHit(Entity<CorePoweredThrowerComponent> ent, ref AttemptMeleeThrowOnHitEvent args)
+    {
+        var (uid, comp) = ent;
+
+        // don't waste charges on non-anchorable non-anomalous static bodies.
+        if (!HasComp<AnomalyComponent>(args.Hit)
+            && !HasComp<AnchorableComponent>(args.Hit)
+            && TryComp<PhysicsComponent>(args.Hit, out var body)
+            && body.BodyType == BodyType.Static)
+            return;
+
+        args.Cancelled = true;
+        args.Handled = true;
+
+        if (!_itemSlots.TryGetSlot(uid, comp.CoreSlotId, out var slot))
+            return;
+
+        if (!TryComp<AnomalyCoreComponent>(slot.Item, out var coreComponent))
+            return;
+
+        if (coreComponent.IsDecayed)
+        {
+            if (coreComponent.Charge <= 0)
+                return;
+            args.Cancelled = false;
+            coreComponent.Charge--;
+        }
+        else
+        {
+            args.Cancelled = false;
+        }
+    }
+
+    private void OnCorePoweredExamined(Entity<CorePoweredThrowerComponent> ent, ref ExaminedEvent args)
+    {
+        var (uid, comp) = ent;
+        if (!args.IsInDetailsRange)
+            return;
+
+        if (!_itemSlots.TryGetSlot(uid, comp.CoreSlotId, out var slot) ||
+            !TryComp<AnomalyCoreComponent>(slot.Item, out var coreComponent))
+        {
+            args.PushMarkup(Loc.GetString("anomaly-gorilla-charge-none"));
+            return;
+        }
+
+        if (coreComponent.IsDecayed)
+        {
+            args.PushMarkup(Loc.GetString("anomaly-gorilla-charge-limit", ("count", coreComponent.Charge)));
+        }
+        else
+        {
+            args.PushMarkup(Loc.GetString("anomaly-gorilla-charge-infinite"));
+        }
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<AnomalyCoreComponent>();
+        while (query.MoveNext(out var uid, out var component))
+        {
+            if (component.IsDecayed)
+                continue;
+
+            //When time runs out, we completely decompose
+            if (component.DecayMoment < _gameTiming.CurTime)
+                Decay(uid, component);
+        }
+    }
+
+    private void Decay(EntityUid uid, AnomalyCoreComponent component)
+    {
+        _appearance.SetData(uid, AnomalyCoreVisuals.Decaying, false);
+        component.IsDecayed = true;
+        Dirty(uid, component);
+    }
+}
index cf937b761e3f36a78b09c541167aff741e8b552f..c014ff90e1159662b063056ea232b50f6b82570d 100644 (file)
@@ -4,10 +4,13 @@ using Content.Shared.Damage;
 using Content.Shared.Database;
 using Content.Shared.Interaction;
 using Content.Shared.Popups;
+using Content.Shared.Weapons.Melee.Components;
 using Content.Shared.Weapons.Melee.Events;
-using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Network;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Systems;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
@@ -23,6 +26,7 @@ public abstract class SharedAnomalySystem : EntitySystem
     [Dependency] private readonly DamageableSystem _damageable = default!;
     [Dependency] protected readonly SharedAudioSystem Audio = default!;
     [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
     [Dependency] protected readonly SharedPopupSystem Popup = default!;
 
     private ISawmill _sawmill = default!;
@@ -33,6 +37,8 @@ public abstract class SharedAnomalySystem : EntitySystem
 
         SubscribeLocalEvent<AnomalyComponent, InteractHandEvent>(OnInteractHand);
         SubscribeLocalEvent<AnomalyComponent, AttackedEvent>(OnAttacked);
+        SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitStartEvent>(OnAnomalyThrowStart);
+        SubscribeLocalEvent<AnomalyComponent, MeleeThrowOnHitEndEvent>(OnAnomalyThrowEnd);
 
         SubscribeLocalEvent<AnomalyComponent, EntityUnpausedEvent>(OnAnomalyUnpause);
         SubscribeLocalEvent<AnomalyPulsingComponent, EntityUnpausedEvent>(OnPulsingUnpause);
@@ -49,9 +55,25 @@ public abstract class SharedAnomalySystem : EntitySystem
 
     private void OnAttacked(EntityUid uid, AnomalyComponent component, AttackedEvent args)
     {
+        if (HasComp<CorePoweredThrowerComponent>(args.Used))
+            return;
+
         DoAnomalyBurnDamage(uid, args.User, component);
     }
 
+    private void OnAnomalyThrowStart(Entity<AnomalyComponent> ent, ref MeleeThrowOnHitStartEvent args)
+    {
+        if (!TryComp<CorePoweredThrowerComponent>(args.Used, out var corePowered) || !TryComp<PhysicsComponent>(ent, out var body))
+            return;
+        _physics.SetBodyType(ent, BodyType.Dynamic, body: body);
+        ChangeAnomalyStability(ent, Random.NextFloat(corePowered.StabilityPerThrow.X, corePowered.StabilityPerThrow.Y), ent.Comp);
+    }
+
+    private void OnAnomalyThrowEnd(Entity<AnomalyComponent> ent, ref MeleeThrowOnHitEndEvent args)
+    {
+        _physics.SetBodyType(ent, BodyType.Static);
+    }
+
     public void DoAnomalyBurnDamage(EntityUid source, EntityUid target, AnomalyComponent component)
     {
         _damageable.TryChangeDamage(target, component.AnomalyContactDamage, true);
@@ -111,7 +133,7 @@ public abstract class SharedAnomalySystem : EntitySystem
         var pulse = EnsureComp<AnomalyPulsingComponent>(uid);
         pulse.EndTime  = Timing.CurTime + pulse.PulseDuration;
         Appearance.SetData(uid, AnomalyVisuals.IsPulsing, true);
-        
+
         var ev = new AnomalyPulseEvent(uid, component.Stability, component.Severity);
         RaiseLocalEvent(uid, ref ev, true);
     }
diff --git a/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs b/Content.Shared/Weapons/Melee/Components/MeleeThrowOnHitComponent.cs
new file mode 100644 (file)
index 0000000..ef8ee3f
--- /dev/null
@@ -0,0 +1,105 @@
+using System.Numerics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Weapons.Melee.Components;
+
+/// <summary>
+/// This is used for a melee weapon that throws whatever gets hit by it in a line
+/// until it hits a wall or a time limit is exhausted.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(MeleeThrowOnHitSystem))]
+[AutoGenerateComponentState]
+public sealed partial class MeleeThrowOnHitComponent : Component
+{
+    /// <summary>
+    /// The speed at which hit entities should be thrown.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public float Speed = 10f;
+
+    /// <summary>
+    /// How long hit entities remain thrown, max.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public float Lifetime = 3f;
+
+    /// <summary>
+    /// How long we wait to start accepting collision.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MinLifetime = 0.05f;
+
+    /// <summary>
+    /// Whether or not anchorable entities should be unanchored when hit.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public bool UnanchorOnHit;
+
+    /// <summary>
+    /// Whether or not the throwing behavior occurs by default.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public bool Enabled = true;
+}
+
+/// <summary>
+/// Component used to track entities that have been yeeted by <see cref="MeleeThrowOnHitComponent"/>
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState]
+[Access(typeof(MeleeThrowOnHitSystem))]
+public sealed partial class MeleeThrownComponent : Component
+{
+    /// <summary>
+    /// The velocity of the throw
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public Vector2 Velocity;
+
+    /// <summary>
+    /// How long the throw will last.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [AutoNetworkedField]
+    public float Lifetime;
+
+    /// <summary>
+    /// How long we wait to start accepting collision.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public float MinLifetime;
+
+    /// <summary>
+    /// At what point in time will the throw be complete?
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField]
+    public TimeSpan ThrownEndTime;
+
+    /// <summary>
+    /// At what point in time will the <see cref="MinLifetime"/> be exhausted
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField]
+    public TimeSpan MinLifetimeTime;
+}
+
+/// <summary>
+/// Event raised before an entity is thrown by <see cref="MeleeThrowOnHitComponent"/> to see if a throw is allowed.
+/// If not handled, the enabled field on the component will be used instead.
+/// </summary>
+[ByRefEvent]
+public record struct AttemptMeleeThrowOnHitEvent(EntityUid Hit, bool Cancelled = false, bool Handled = false);
+
+[ByRefEvent]
+public record struct MeleeThrowOnHitStartEvent(EntityUid User, EntityUid Used);
+
+[ByRefEvent]
+public record struct MeleeThrowOnHitEndEvent();
index 5c7096e117dc75a81d596af220f7fe1e788b5ce6..55c01c1d6f4c6dbd269dc1970449c8488551dcae 100644 (file)
@@ -1,3 +1,4 @@
+using System.Numerics;
 using Content.Shared.Damage;
 using Content.Shared.FixedPoint;
 using Robust.Shared.Audio;
@@ -50,6 +51,12 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs
     /// </summary>
     public readonly EntityUid Weapon;
 
+    /// <summary>
+    /// The direction of the attack.
+    /// If null, it was a click-attack.
+    /// </summary>
+    public readonly Vector2? Direction;
+
     /// <summary>
     /// Check if this is true before attempting to do something during a melee attack other than changing/adding bonus damage. <br/>
     /// For example, do not spend charges unless <see cref="IsHit"/> equals true.
@@ -59,12 +66,13 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs
     /// </remarks>
     public bool IsHit = true;
 
-    public MeleeHitEvent(List<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage)
+    public MeleeHitEvent(List<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage, Vector2? direction)
     {
         HitEntities = hitEntities;
         User = user;
         Weapon = weapon;
         BaseDamage = baseDamage;
+        Direction = direction;
     }
 }
 
diff --git a/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs b/Content.Shared/Weapons/Melee/MeleeThrowOnHitSystem.cs
new file mode 100644 (file)
index 0000000..d755f7a
--- /dev/null
@@ -0,0 +1,126 @@
+using System.Numerics;
+using Content.Shared.Construction.Components;
+using Content.Shared.Weapons.Melee.Components;
+using Content.Shared.Weapons.Melee.Events;
+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.Weapons.Melee;
+
+/// <summary>
+/// This handles <see cref="MeleeThrowOnHitComponent"/>
+/// </summary>
+public sealed class MeleeThrowOnHitSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<MeleeThrowOnHitComponent, MeleeHitEvent>(OnMeleeHit);
+        SubscribeLocalEvent<MeleeThrownComponent, ComponentStartup>(OnThrownStartup);
+        SubscribeLocalEvent<MeleeThrownComponent, ComponentShutdown>(OnThrownShutdown);
+        SubscribeLocalEvent<MeleeThrownComponent, StartCollideEvent>(OnStartCollide);
+    }
+
+    private void OnMeleeHit(Entity<MeleeThrowOnHitComponent> ent, ref MeleeHitEvent args)
+    {
+        var (_, comp) = ent;
+        if (!args.IsHit)
+            return;
+
+        var mapPos = _transform.GetMapCoordinates(args.User).Position;
+        foreach (var hit in args.HitEntities)
+        {
+            var hitPos = _transform.GetMapCoordinates(hit).Position;
+            var angle = args.Direction ?? hitPos - mapPos;
+            if (angle == Vector2.Zero)
+                continue;
+
+            if (!CanThrowOnHit(ent, hit))
+                continue;
+
+            if (comp.UnanchorOnHit && HasComp<AnchorableComponent>(hit))
+            {
+                _transform.Unanchor(hit, Transform(hit));
+            }
+
+            RemComp<MeleeThrownComponent>(hit);
+            var ev = new MeleeThrowOnHitStartEvent(args.User, ent);
+            RaiseLocalEvent(hit, ref ev);
+            var thrownComp = new MeleeThrownComponent
+            {
+                Velocity = angle.Normalized() * comp.Speed,
+                Lifetime = comp.Lifetime,
+                MinLifetime = comp.MinLifetime
+            };
+            AddComp(hit, thrownComp);
+        }
+    }
+
+    private void OnThrownStartup(Entity<MeleeThrownComponent> ent, ref ComponentStartup args)
+    {
+        var (_, comp) = ent;
+
+        if (!TryComp<PhysicsComponent>(ent, out var body) ||
+            (body.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0)
+            return;
+
+        comp.ThrownEndTime = _timing.CurTime + TimeSpan.FromSeconds(comp.Lifetime);
+        comp.MinLifetimeTime = _timing.CurTime + TimeSpan.FromSeconds(comp.MinLifetime);
+        _physics.SetBodyStatus(body, BodyStatus.InAir);
+        _physics.SetLinearVelocity(ent, Vector2.Zero, body: body);
+        _physics.ApplyLinearImpulse(ent, comp.Velocity * body.Mass, body: body);
+        Dirty(ent, ent.Comp);
+    }
+
+    private void OnThrownShutdown(Entity<MeleeThrownComponent> ent, ref ComponentShutdown args)
+    {
+        if (TryComp<PhysicsComponent>(ent, out var body))
+            _physics.SetBodyStatus(body, BodyStatus.OnGround);
+        var ev = new MeleeThrowOnHitEndEvent();
+        RaiseLocalEvent(ent, ref ev);
+    }
+
+    private void OnStartCollide(Entity<MeleeThrownComponent> ent, ref StartCollideEvent args)
+    {
+        var (_, comp) = ent;
+        if (!args.OtherFixture.Hard || !args.OtherBody.CanCollide || !args.OurFixture.Hard || !args.OurBody.CanCollide)
+            return;
+
+        if (_timing.CurTime < comp.MinLifetimeTime)
+            return;
+
+        RemCompDeferred(ent, ent.Comp);
+    }
+
+    public bool CanThrowOnHit(Entity<MeleeThrowOnHitComponent> ent, EntityUid target)
+    {
+        var (uid, comp) = ent;
+
+        var ev = new AttemptMeleeThrowOnHitEvent(target);
+        RaiseLocalEvent(uid, ref ev);
+
+        if (ev.Handled)
+            return !ev.Cancelled;
+
+        return comp.Enabled;
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<MeleeThrownComponent>();
+        while (query.MoveNext(out var uid, out var comp))
+        {
+            if (_timing.CurTime > comp.ThrownEndTime)
+                RemCompDeferred(uid, comp);
+        }
+    }
+}
index 68f625d9494730ca684bd2498d0141eec6fa5bc2..482467297439fb7708f0cdaf5a7411dc450a3535 100644 (file)
@@ -467,7 +467,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
                 AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
                     $"{ToPrettyString(user):actor} melee attacked (light) using {ToPrettyString(meleeUid):tool} and missed");
             }
-            var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage);
+            var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, null);
             RaiseLocalEvent(meleeUid, missEvent);
             Audio.PlayPredicted(component.SwingSound, meleeUid, user);
             return;
@@ -476,7 +476,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
         // Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
 
         // Raise event before doing damage so we can cancel damage if the event is handled
-        var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage);
+        var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage, null);
         RaiseLocalEvent(meleeUid, hitEvent);
 
         if (hitEvent.Handled)
@@ -578,7 +578,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
                 AdminLogger.Add(LogType.MeleeHit, LogImpact.Low,
                     $"{ToPrettyString(user):actor} melee attacked (heavy) using {ToPrettyString(meleeUid):tool} and missed");
             }
-            var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage);
+            var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, direction);
             RaiseLocalEvent(meleeUid, missEvent);
 
             Audio.PlayPredicted(component.SwingSound, meleeUid, user);
@@ -619,7 +619,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
         // Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
 
         // Raise event before doing damage so we can cancel damage if the event is handled
-        var hitEvent = new MeleeHitEvent(targets, user, meleeUid, damage);
+        var hitEvent = new MeleeHitEvent(targets, user, meleeUid, damage, direction);
         RaiseLocalEvent(meleeUid, hitEvent);
 
         if (hitEvent.Handled)
index 8452725e5e842346ba04b3f4334829400ed2fd08..df4cbfe20cd8e8f6730cc6c56ce651cff3ebcc0b 100644 (file)
@@ -25,6 +25,20 @@ anomaly-scanner-particle-unstable = - [color=plum]Unstable type:[/color] {$type}
 anomaly-scanner-particle-containment = - [color=goldenrod]Containment type:[/color] {$type}
 anomaly-scanner-pulse-timer = Time until next pulse: [color=gray]{$time}[/color]
 
+anomaly-gorilla-core-slot-name = Anomaly core
+anomaly-gorilla-charge-none = It has no [bold]anomaly core[/bold] inside of it.
+anomaly-gorilla-charge-limit = It has [color={$count ->
+    [3]green
+    [2]yellow
+    [1]orange
+    [0]red
+    *[other]purple
+}]{$count} {$count ->
+    [one]charge
+    *[other]charges
+}[/color] remaining.
+anomaly-gorilla-charge-infinite = It has [color=gold]infinite charges[/color]. [italic]For now...[/italic]
+
 anomaly-sync-connected = Anomaly successfully attached
 anomaly-sync-disconnected = The connection to the anomaly has been lost!
 anomaly-sync-no-anomaly = No anomaly in range.
index 3b682d9284b53bdfbd28ae808a0144455949ba1d..9c95964750abad81d708a4ed11f5ce618e1bcb4a 100644 (file)
@@ -50,6 +50,7 @@ research-technology-basic-xenoarcheology = Basic XenoArcheology
 research-technology-alternative-research = Alternative Research
 research-technology-magnets-tech = Localized Magnetism
 research-technology-advanced-parts = Advanced Parts
+research-technology-anomaly-harnessing = Anomaly Core Harnessing
 research-technology-grappling = Grappling
 research-technology-abnormal-artifact-manipulation = Abnormal Artifact Manipulation
 research-technology-gravity-manipulation = Gravity Manipulation
index a5a592cf78014d49fe23d8fd1781fe9c66d2cd1a..048bad8e5be514d77aedf5f53cabc4c59acce5e1 100644 (file)
     slots:
       cell_slot:
         name: power-cell-slot-component-slot-name-default
+
+- type: entity
+  name: G.O.R.I.L.L.A. gauntlet
+  parent: BaseItem
+  id: WeaponGauntletGorilla
+  description: A robust piece of research equipment. When powered with an anomaly core, a single blow can launch anomalous objects through the air.
+  components:
+  - type: Sprite
+    sprite: Objects/Weapons/Melee/gorilla.rsi
+    state: icon
+  - type: Item
+    size: Large
+  - type: MeleeWeapon
+    attackRate: 0.5
+    angle: 0
+    animation: WeaponArcFist
+    wideAnimationRotation: -135
+    damage:
+      types:
+        Blunt: 20
+  - type: CorePoweredThrower
+  - type: MeleeThrowOnHit
+    unanchorOnHit: true
+    enabled: false
+  - type: ItemSlots
+    slots:
+      core_slot:
+        name: anomaly-gorilla-core-slot-name
+        insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
+        ejectSound: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg
+        whitelist:
+          components:
+          - AnomalyCore
+  - type: ContainerContainer
+    containers:
+      core_slot: !type:ContainerSlot
index a02efc13f554f879e1c0c7a8f632a1a37fde7fdd..b7fae7acede8db35d904b649af0035187bacb926 100644 (file)
   - type: Tag
     tags:
     - BaseballBat
+
+- type: entity
+  name: knockback stick
+  parent: BaseBallBat
+  id: WeaponMeleeKnockbackStick
+  description: And then he spleefed all over.
+  suffix: Do not map
+  components:
+  - type: MeleeThrowOnHit
+  - type: MeleeWeapon
+    damage:
+      types:
+        Blunt: 1
index 20008c6a97144ea67614a5c7d1b86f06b45d7611..8772c7290ed685c8bccc74e70c3cd07564ad024a 100644 (file)
       - PowerCellMicroreactor
       - PowerCellHigh
       - WeaponPistolCHIMP
+      - WeaponGauntletGorilla
       - SynthesizerInstrument
       - RPED
       - ClothingShoesBootsMag
index 234dd523596ec718b4848ab3bc096cc1973f90ed..8ac5fb03e3c55d0e029cd08383b86ccacf1ad69e 100644 (file)
       types:
         Slash: 1
   - type: EntitySpawnAnomaly
-    superCriticalSpawns: 
+    superCriticalSpawns:
     - ReagentSlimeSpawner
     spawns:
       - PuddleSparkle
index b5801994c0d2a169828ea2414d2d58f528791b17..06e08beb75733c3d27826b752cf12c6a761cc147 100644 (file)
@@ -23,7 +23,7 @@
   - type: ItemCooldown
   - type: EmitSoundOnUse #placeholder for future unical mechanic
     sound:
-      collection: RadiationPulse 
+      collection: RadiationPulse
   - type: UseDelay
     delay: 2
   - type: AnomalyCore
   id: BaseAnomalyInertCore
   abstract: true
   components:
+  - type: Sprite
+    layers:
+    - state: core
+    - state: pulse
+      visible: false
+      map: ["decay"]
   - type: AnomalyCore
-    timeToDecay: 1 #decay very fast
+    timeToDecay: 0
+    isDecayed: true
 
 - type: entity
   parent: BaseAnomalyInertCore
     radius: 1.5
     energy: 2.0
     color: "#ffffaa"
-    castShadows: false
\ No newline at end of file
+    castShadows: false
index 1593f125978cfae9179f70d944a2c82b21c9f099..d541c3635362e57b40e5aa4f9d29c7a1fcbc4678 100644 (file)
     Steel: 500
     Glass: 400
 
+- type: latheRecipe
+  id: WeaponGauntletGorilla
+  result: WeaponGauntletGorilla
+  completetime: 5
+  materials:
+    Steel: 1500
+    Plastic: 300
+    Glass: 500
+    Plasma: 500
+    Silver: 250
+
 - type: latheRecipe
   id: ClothingBackpackHolding
   result: ClothingBackpackHolding
index a58baf1b78803ad3bff66cfa568d1d9b209ecb25..f504f7fa2488af119869dc0b144913da81643fb3 100644 (file)
   recipeUnlocks:
   - ClothingShoesBootsMag
 
+- type: technology
+  id: AnomalyCoreHarnessing
+  name: research-technology-anomaly-harnessing
+  icon:
+    sprite: Objects/Weapons/Melee/gorilla.rsi
+    state: icon
+  discipline: Experimental
+  tier: 1
+  cost: 10000
+  recipeUnlocks:
+  - WeaponGauntletGorilla
+
 # Tier 2
 
 - type: technology
diff --git a/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/icon.png b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/icon.png
new file mode 100644 (file)
index 0000000..a57ce53
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-left.png
new file mode 100644 (file)
index 0000000..67c61ef
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-left.png differ
diff --git a/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-right.png
new file mode 100644 (file)
index 0000000..dd19bf4
Binary files /dev/null and b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/inhand-right.png differ
diff --git a/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/meta.json b/Resources/Textures/Objects/Weapons/Melee/gorilla.rsi/meta.json
new file mode 100644 (file)
index 0000000..97a5fdb
--- /dev/null
@@ -0,0 +1,22 @@
+{
+  "version": 1,
+  "license": "CC0-1.0",
+  "copyright": "Design and inhands by ricemar (discord) and icon by EmoGarbage404 (github)",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "icon"
+    },
+    {
+      "name": "inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "inhand-right",
+      "directions": 4
+    }
+  ]
+}