[ViewVariables(VVAccess.ReadWrite), DataField("reflects")]
public ReflectType Reflects = ReflectType.Energy | ReflectType.NonEnergy;
+ [DataField("spread"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public Angle Spread = Angle.FromDegrees(45);
+
+ [DataField("soundOnReflect")]
+ public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg");
+
/// <summary>
- /// Probability for a projectile to be reflected.
+ /// Is the deflection an innate power or something actively maintained? If true, this component grants a flat
+ /// deflection chance rather than a chance that degrades when moving/weightless/stunned/etc.
+ /// </summary>
+ [DataField]
+ public bool Innate = false;
+
+ /// <summary>
+ /// Maximum probability for a projectile to be reflected.
/// </summary>
[DataField("reflectProb"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public float ReflectProb = 0.25f;
- [DataField("spread"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
- public Angle Spread = Angle.FromDegrees(45);
+ /// <summary>
+ /// The maximum velocity a wielder can move at before losing effectiveness.
+ /// </summary>
+ [DataField]
+ public float VelocityBeforeNotMaxProb = 2.5f; // Walking speed for a human. Suitable for a weightless deflector like an e-sword.
- [DataField("soundOnReflect")]
- public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg");
+ /// <summary>
+ /// The velocity a wielder has to be moving at to use the minimum effectiveness value.
+ /// </summary>
+ [DataField]
+ public float VelocityBeforeMinProb = 4.5f; // Sprinting speed for a human. Suitable for a weightless deflector like an e-sword.
+
+ /// <summary>
+ /// Minimum probability for a projectile to be reflected.
+ /// </summary>
+ [DataField]
+ public float MinReflectProb = 0.1f;
}
[Flags]
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Content.Shared.Administration.Logs;
+using Content.Shared.Alert;
using Content.Shared.Audio;
+using Content.Shared.Damage.Components;
using Content.Shared.Database;
+using Content.Shared.Gravity;
using Content.Shared.Hands;
using Content.Shared.Inventory;
using Content.Shared.Inventory.Events;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Popups;
using Content.Shared.Projectiles;
+using Content.Shared.Standing;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Physics.Components;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
+ [Dependency] private readonly SharedGravitySystem _gravity = default!;
+ [Dependency] private readonly StandingStateSystem _standing = default!;
+ [Dependency] private readonly AlertsSystem _alerts = default!;
public override void Initialize()
{
private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
{
- if (!Resolve(reflector, ref reflect, false) ||
+ // Do we have the components needed to try a reflect at all?
+ if (
+ !Resolve(reflector, ref reflect, false) ||
!reflect.Enabled ||
!TryComp<ReflectiveComponent>(projectile, out var reflective) ||
(reflect.Reflects & reflective.Reflective) == 0x0 ||
- !_random.Prob(reflect.ReflectProb) ||
- !TryComp<PhysicsComponent>(projectile, out var physics))
- {
+ !TryComp<PhysicsComponent>(projectile, out var physics) ||
+ TryComp<StaminaComponent>(reflector, out var staminaComponent) && staminaComponent.Critical ||
+ _standing.IsDown(reflector)
+ )
+ return false;
+
+ if (!_random.Prob(CalcReflectChance(reflector, reflect)))
return false;
- }
var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite();
var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics);
return true;
}
+ private float CalcReflectChance(EntityUid reflector, ReflectComponent reflect)
+ {
+ /*
+ * The rules of deflection are as follows:
+ * If you innately reflect things via magic, biology etc., you always have a full chance.
+ * If you are standing up and standing still, you're prepared to deflect and have full chance.
+ * If you have velocity, your deflection chance depends on your velocity, clamped.
+ * If you are floating, your chance is the minimum value possible.
+ * You cannot deflect if you are knocked down or stunned.
+ */
+
+ if (reflect.Innate)
+ return reflect.ReflectProb;
+
+ if (_gravity.IsWeightless(reflector))
+ return reflect.MinReflectProb;
+
+ if (!TryComp<PhysicsComponent>(reflector, out var reflectorPhysics))
+ return reflect.ReflectProb;
+
+ return MathHelper.Lerp(
+ reflect.MinReflectProb,
+ reflect.ReflectProb,
+ // Inverse progression between velocities fed in as progression between probabilities. We go high -> low so the output here needs to be _inverted_.
+ 1 - Math.Clamp((reflectorPhysics.LinearVelocity.Length() - reflect.VelocityBeforeNotMaxProb) / (reflect.VelocityBeforeMinProb - reflect.VelocityBeforeNotMaxProb), 0, 1)
+ );
+ }
+
private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args)
{
if (args.Reflected ||
{
if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
!reflect.Enabled ||
- !_random.Prob(reflect.ReflectProb))
+ TryComp<StaminaComponent>(reflector, out var staminaComponent) && staminaComponent.Critical ||
+ _standing.IsDown(reflector))
+ {
+ newDirection = null;
+ return false;
+ }
+
+ if (!_random.Prob(CalcReflectChance(reflector, reflect)))
{
newDirection = null;
return false;
return;
EnsureComp<ReflectUserComponent>(args.Equipee);
+
+ if (component.Enabled)
+ EnableAlert(args.Equipee);
}
private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args)
return;
EnsureComp<ReflectUserComponent>(args.User);
+
+ if (component.Enabled)
+ EnableAlert(args.User);
}
private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args)
{
comp.Enabled = args.Activated;
Dirty(uid, comp);
+
+ if (comp.Enabled)
+ EnableAlert(uid);
+ else
+ DisableAlert(uid);
}
/// <summary>
continue;
EnsureComp<ReflectUserComponent>(user);
+ EnableAlert(user);
+
return;
}
RemCompDeferred<ReflectUserComponent>(user);
+ DisableAlert(user);
+ }
+
+ private void EnableAlert(EntityUid alertee)
+ {
+ _alerts.ShowAlert(alertee, AlertType.Deflecting);
+ }
+
+ private void DisableAlert(EntityUid alertee)
+ {
+ _alerts.ClearAlert(alertee, AlertType.Deflecting);
}
}