diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs
index 5541c2e439..a17caa0bcb 100644
--- a/Content.Shared/Hands/HandEvents.cs
+++ b/Content.Shared/Hands/HandEvents.cs
@@ -206,21 +206,33 @@ namespace Content.Shared.Hands
}
}
+ ///
+ /// Raised directed on an entity when it is equipped into hands.
+ ///
public sealed class GotEquippedHandEvent : EquippedHandEvent
{
public GotEquippedHandEvent(EntityUid user, EntityUid unequipped, Hand hand) : base(user, unequipped, hand) { }
}
+ ///
+ /// Raised directed on an entity when it is unequipped from hands.
+ ///
public sealed class GotUnequippedHandEvent : UnequippedHandEvent
{
public GotUnequippedHandEvent(EntityUid user, EntityUid unequipped, Hand hand) : base(user, unequipped, hand) { }
}
+ ///
+ /// Raised directed on a user when it picks something up.
+ ///
public sealed class DidEquipHandEvent : EquippedHandEvent
{
public DidEquipHandEvent(EntityUid user, EntityUid unequipped, Hand hand) : base(user, unequipped, hand) { }
}
+ ///
+ /// Raised directed on a user when something leaves its hands.
+ ///
public sealed class DidUnequipHandEvent : UnequippedHandEvent
{
public DidUnequipHandEvent(EntityUid user, EntityUid unequipped, Hand hand) : base(user, unequipped, hand) { }
diff --git a/Content.Shared/Inventory/Events/EquippedEvents.cs b/Content.Shared/Inventory/Events/EquippedEvents.cs
index 85aafb2f6b..77fbe83e8a 100644
--- a/Content.Shared/Inventory/Events/EquippedEvents.cs
+++ b/Content.Shared/Inventory/Events/EquippedEvents.cs
@@ -37,6 +37,9 @@ public abstract class EquippedEventBase : EntityEventArgs
}
}
+///
+/// Raised directed on an equipee when something is equipped.
+///
public sealed class DidEquipEvent : EquippedEventBase
{
public DidEquipEvent(EntityUid equipee, EntityUid equipment, SlotDefinition slotDefinition) : base(equipee, equipment, slotDefinition)
@@ -44,6 +47,9 @@ public sealed class DidEquipEvent : EquippedEventBase
}
}
+///
+/// Raised directed on equipment when it's equipped to an equipee
+///
public sealed class GotEquippedEvent : EquippedEventBase
{
public GotEquippedEvent(EntityUid equipee, EntityUid equipment, SlotDefinition slotDefinition) : base(equipee, equipment, slotDefinition)
diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs
index 41bd6e0372..0e24d2ec24 100644
--- a/Content.Shared/Inventory/InventorySystem.Helpers.cs
+++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs
@@ -1,11 +1,43 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Content.Shared.Hands.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared.Inventory;
public partial class InventorySystem
{
+ ///
+ /// Yields all entities in hands or inventory slots with the specific flags.
+ ///
+ public IEnumerable GetHandOrInventoryEntities(EntityUid user, SlotFlags flags = SlotFlags.All)
+ {
+ if (TryComp(user, out var handsComp))
+ {
+ foreach (var hand in handsComp.Hands.Values)
+ {
+ if (hand.HeldEntity == null)
+ continue;
+
+ yield return hand.HeldEntity.Value;
+ }
+ }
+
+ if (TryComp(user, out var inventoryComp))
+ {
+ var slotEnumerator = new ContainerSlotEnumerator(user, inventoryComp.TemplateId,
+ _prototypeManager, this, flags);
+
+ while (slotEnumerator.MoveNext(out var slot))
+ {
+ if (slot.ContainedEntity == null)
+ continue;
+
+ yield return slot.ContainedEntity.Value;
+ }
+ }
+ }
+
///
/// Returns the definition of the inventory slot that the given entity is currently in..
///
diff --git a/Content.Shared/Inventory/InventorySystem.cs b/Content.Shared/Inventory/InventorySystem.cs
index b5d1a0f43a..ae62a4cec0 100644
--- a/Content.Shared/Inventory/InventorySystem.cs
+++ b/Content.Shared/Inventory/InventorySystem.cs
@@ -1,3 +1,5 @@
+using Content.Shared.Hands.Components;
+
namespace Content.Shared.Inventory;
public partial class InventorySystem
diff --git a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs
new file mode 100644
index 0000000000..bcb450c759
--- /dev/null
+++ b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Weapons.Reflect;
+
+///
+/// Added to an entity if it equips a reflection item in a hand slot or into its clothing.
+/// Reflection events will then be relayed.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed class ReflectUserComponent : Component
+{
+
+}
diff --git a/Content.Shared/Weapons/Reflect/SharedReflectSystem.cs b/Content.Shared/Weapons/Reflect/SharedReflectSystem.cs
index 1a5e00d6ce..8b68fc21d8 100644
--- a/Content.Shared/Weapons/Reflect/SharedReflectSystem.cs
+++ b/Content.Shared/Weapons/Reflect/SharedReflectSystem.cs
@@ -3,16 +3,19 @@ using System.Numerics;
using Content.Shared.Administration.Logs;
using Content.Shared.Audio;
using Content.Shared.Database;
+using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
+using Content.Shared.Item;
using Robust.Shared.Physics.Components;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Network;
using Robust.Shared.Physics.Systems;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Weapons.Reflect;
@@ -23,6 +26,7 @@ namespace Content.Shared.Weapons.Reflect;
public abstract class SharedReflectSystem : EntitySystem
{
[Dependency] private readonly INetManager _netManager = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
@@ -35,37 +39,55 @@ public abstract class SharedReflectSystem : EntitySystem
{
base.Initialize();
- SubscribeLocalEvent(OnHandReflectProjectile);
- SubscribeLocalEvent(OnHandsReflectHitscan);
-
SubscribeLocalEvent(OnReflectCollide);
SubscribeLocalEvent(OnReflectHitscan);
+ SubscribeLocalEvent(OnReflectUserCollide);
+ SubscribeLocalEvent(OnReflectUserHitscan);
SubscribeLocalEvent(OnReflectEquipped);
SubscribeLocalEvent(OnReflectUnequipped);
+ SubscribeLocalEvent(OnReflectHandEquipped);
+ SubscribeLocalEvent(OnReflectHandUnequipped);
+ }
+
+ private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, ref HitScanReflectAttemptEvent args)
+ {
+ if (args.Reflected)
+ return;
+
+ foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
+ {
+ if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir))
+ continue;
+
+ args.Direction = dir.Value;
+ args.Reflected = true;
+ break;
+ }
+ }
+
+ private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args)
+ {
+ foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
+ {
+ if (!TryReflectProjectile(uid, ent, args.ProjUid))
+ continue;
+
+ args.Cancelled = true;
+ break;
+ }
}
private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref ProjectileReflectAttemptEvent args)
{
if (args.Cancelled)
- {
return;
- }
- if (TryReflectProjectile(uid, args.ProjUid, reflect: component))
+ if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component))
args.Cancelled = true;
}
- private void OnHandReflectProjectile(EntityUid uid, HandsComponent hands, ref ProjectileReflectAttemptEvent args)
- {
- if (args.Cancelled)
- return;
-
- if (hands.ActiveHandEntity != null && TryReflectProjectile(hands.ActiveHandEntity.Value, args.ProjUid))
- args.Cancelled = true;
- }
-
- private bool TryReflectProjectile(EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
+ private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
{
if (!Resolve(reflector, ref reflect, false) ||
!reflect.Enabled ||
@@ -79,7 +101,7 @@ public abstract class SharedReflectSystem : EntitySystem
var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite();
var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics);
- var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(reflector);
+ var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user);
var newVelocity = rotation.RotateVec(relativeVelocity);
// Have the velocity in world terms above so need to convert it back to local.
@@ -93,38 +115,26 @@ public abstract class SharedReflectSystem : EntitySystem
if (_netManager.IsServer)
{
- _popup.PopupEntity(Loc.GetString("reflect-shot"), reflector);
- _audio.PlayPvs(reflect.SoundOnReflect, reflector, AudioHelpers.WithVariation(0.05f, _random));
+ _popup.PopupEntity(Loc.GetString("reflect-shot"), user);
+ _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
}
if (Resolve(projectile, ref projectileComp, false))
{
- _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(reflector)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}");
+ _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}");
- projectileComp.Shooter = reflector;
- projectileComp.Weapon = reflector;
- Dirty(projectileComp);
+ projectileComp.Shooter = user;
+ projectileComp.Weapon = user;
+ Dirty(projectile, projectileComp);
}
else
{
- _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(reflector)} reflected {ToPrettyString(projectile)}");
+ _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)}");
}
return true;
}
- private void OnHandsReflectHitscan(EntityUid uid, HandsComponent hands, ref HitScanReflectAttemptEvent args)
- {
- if (args.Reflected || hands.ActiveHandEntity == null)
- return;
-
- if (TryReflectHitscan(hands.ActiveHandEntity.Value, args.Shooter, args.SourceItem, args.Direction, out var dir))
- {
- args.Direction = dir.Value;
- args.Reflected = true;
- }
- }
-
private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args)
{
if (args.Reflected ||
@@ -133,14 +143,19 @@ public abstract class SharedReflectSystem : EntitySystem
return;
}
- if (TryReflectHitscan(uid, args.Shooter, args.SourceItem, args.Direction, out var dir))
+ if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, out var dir))
{
args.Direction = dir.Value;
args.Reflected = true;
}
}
- private bool TryReflectHitscan(EntityUid reflector, EntityUid? shooter, EntityUid shotSource, Vector2 direction,
+ private bool TryReflectHitscan(
+ EntityUid user,
+ EntityUid reflector,
+ EntityUid? shooter,
+ EntityUid shotSource,
+ Vector2 direction,
[NotNullWhen(true)] out Vector2? newDirection)
{
if (!TryComp(reflector, out var reflect) ||
@@ -153,65 +168,55 @@ public abstract class SharedReflectSystem : EntitySystem
if (_netManager.IsServer)
{
- _popup.PopupEntity(Loc.GetString("reflect-shot"), reflector);
- _audio.PlayPvs(reflect.SoundOnReflect, reflector, AudioHelpers.WithVariation(0.05f, _random));
+ _popup.PopupEntity(Loc.GetString("reflect-shot"), user);
+ _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
}
var spread = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2);
newDirection = -spread.RotateVec(direction);
if (shooter != null)
- _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(reflector)} reflected hitscan from {ToPrettyString(shotSource)} shot by {ToPrettyString(shooter.Value)}");
+ _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected hitscan from {ToPrettyString(shotSource)} shot by {ToPrettyString(shooter.Value)}");
else
- _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(reflector)} reflected hitscan from {ToPrettyString(shotSource)}");
+ _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected hitscan from {ToPrettyString(shotSource)}");
return true;
}
- private void OnReflectEquipped(EntityUid uid, ReflectComponent comp, GotEquippedEvent args)
+ private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args)
{
-
- if (!TryComp(args.Equipee, out ReflectComponent? reflection))
- return;
-
- if (args.Slot == "pocket1" || args.Slot == "pocket2")
- return;
-
- reflection.Enabled = comp.Enabled;
- // reflection probability should be: (1 - old probability) * newly-equipped item probability + old probability
- // example: if entity has .25 reflection and newly-equipped item has .7, entity should have (1 - .25) * .7 + .25 = .775
- reflection.ReflectProb += (1 - reflection.ReflectProb) * comp.ReflectProb;
-
+ EnsureComp(args.Equipee);
}
private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args)
{
+ RefreshReflectUser(args.Equipee);
+ }
- if (!TryComp(args.Equipee, out ReflectComponent? reflection))
- return;
+ private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args)
+ {
+ EnsureComp(args.User);
+ }
- if (!_inventorySystem.TryGetSlots(args.Equipee, out var slotDef))
- return;
+ private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args)
+ {
+ RefreshReflectUser(args.User);
+ }
- // you could recalculate reflectprob with new = (old - component) / (1 - component), but component=1 introduces loss
- // still need to either maintain a counter or loop through all slots to determine reflection.enabled anyway?
- float newProb = 1;
- var reflecting = false;
-
- foreach (var slot in slotDef)
+ ///
+ /// Refreshes whether someone has reflection potential so we can raise directed events on them.
+ ///
+ private void RefreshReflectUser(EntityUid user)
+ {
+ foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
{
- if (!_inventorySystem.TryGetSlotEntity(args.Equipee, slot.Name, out var slotEnt))
+ if (!HasComp(ent))
continue;
- if (!TryComp(slotEnt, out ReflectComponent? refcomp))
- continue;
-
- reflecting = true;
- var prob = refcomp.ReflectProb;
- newProb -= newProb * prob;
+ EnsureComp(user);
+ return;
}
- reflection.ReflectProb = 1 - newProb;
- reflection.Enabled = reflecting;
+ RemCompDeferred(user);
}
}
diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
index 5bcc49b154..666cbc2d20 100644
--- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
+++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
@@ -96,6 +96,8 @@
Heat: 0.4 # this technically means it protects against fires pretty well? -heat is just for lasers and stuff, not atmos temperature
- type: Reflect
reflectProb: 1
+ reflects:
+ - Energy
- type: entity
parent: ClothingOuterBaseLarge