GORILLA Gauntlets (#23012)

* GORILLA gauntlets

* oh shit this too
This commit is contained in:
Nemanja
2023-12-27 22:11:13 -05:00
committed by GitHub
parent f2379040f2
commit 9bd03824ac
22 changed files with 542 additions and 46 deletions

View File

@@ -0,0 +1,52 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
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, 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>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public double TimeToDecay = 600;
/// <summary>
/// The moment of core decay. It is set during entity initialization.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
[AutoNetworkedField]
public TimeSpan DecayMoment;
/// <summary>
/// The starting value of the entity.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public double StartPrice = 10000;
/// <summary>
/// The value of the object sought during decaying
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public double EndPrice = 200;
/// <summary>
/// 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;
}

View File

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

View File

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

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

View File

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

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

View File

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

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