Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weapon Reflection Movement Mechanic #27219

Merged
merged 8 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions Content.Shared/Weapons/Reflect/ReflectComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,42 @@ public sealed partial class ReflectComponent : Component
[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]
Expand Down
61 changes: 55 additions & 6 deletions Content.Shared/Weapons/Reflect/ReflectSystem.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Content.Shared.ActionBlocker;
using Content.Shared.Administration.Logs;
using Content.Shared.Audio;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
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.Movement.Components;
using Content.Shared.Movement.Systems;
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;
Expand All @@ -35,6 +42,8 @@ public sealed class ReflectSystem : EntitySystem
[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!;

public override void Initialize()
{
Expand Down Expand Up @@ -91,15 +100,20 @@ private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref Pro

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);
Expand Down Expand Up @@ -137,6 +151,34 @@ private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid
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 ||
Expand All @@ -162,7 +204,14 @@ private bool TryReflectHitscan(
{
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;
Expand Down
1 change: 1 addition & 0 deletions Resources/Prototypes/Anomaly/behaviours.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
description: anomaly-behavior-reflect
components:
- type: Reflect
innate: true
reflectProb: 0.5
reflects:
- Energy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
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
innate: true # armor grants a passive shield that does not require concentration to maintain
reflects:
- Energy

Expand Down
1 change: 1 addition & 0 deletions Resources/Prototypes/Entities/Mobs/NPCs/hellspawn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- type: Perishable
- type: Reflect
reflectProb: 0.7
innate: true
reflects:
- Energy
- type: Fixtures
Expand Down
4 changes: 3 additions & 1 deletion Resources/Prototypes/Entities/Objects/Shields/shields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,14 +313,15 @@
name: mirror shield
parent: BaseShield
id: MirrorShield
description: Eerily glows red... you hear the geometer whispering
description: Eerily glows red. You hear the geometer whispering...
components:
- type: Sprite
state: mirror-icon
- type: Item
heldPrefix: mirror
- type: Reflect
reflectProb: 0.95
minReflectProb: 0.6
reflects:
- Energy
- type: Blocking #Mirror shield reflects heat/laser, but is relatively weak to everything else.
Expand Down Expand Up @@ -408,6 +409,7 @@
- type: Reflect
enabled: false
reflectProb: 0.95
minReflectProb: 0.6
reflects:
- Energy
- type: Blocking
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
malus: 0
- type: Reflect
enabled: false
reflectProb: 0.5
minReflectProb: 0.25
- type: IgnitionSource
temperature: 700

Expand Down Expand Up @@ -218,7 +220,7 @@
name: double-bladed energy sword
parent: EnergySword
id: EnergySwordDouble
description: Syndicate Command Interns thought that having one blade on the energy sword was not enough. This can be stored in pockets.
description: Syndicate Command's intern thought that having only one blade on energy swords was not cool enough. This can be stored in pockets.
components:
- type: EnergySword
- type: ItemToggle
Expand Down Expand Up @@ -269,7 +271,8 @@
size: Small
sprite: Objects/Weapons/Melee/e_sword_double-inhands.rsi
- type: Reflect
reflectProb: .75
reflectProb: .90
minReflectProb: .65
spread: 75
- type: UseDelay
delay: 1
Expand Down
21 changes: 16 additions & 5 deletions Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
attackRate: 1.5
damage:
types:
Slash: 17 #cmon, it has to be at least BETTER than the rest.
Slash: 15
soundHit:
path: /Audio/Weapons/bladeslice.ogg
- type: Reflect
enabled: true
reflectProb: .1
# Design intent: a robust captain or tot can sacrifice movement to make the most of this weapon, but they have to
# really restrict themselves to walking speed or less.
reflectProb: 0.5
velocityBeforeNotMaxProb: 1.0
velocityBeforeMinProb: 3.0
minReflectProb: 0.1
spread: 90
- type: Item
size: Normal
Expand Down Expand Up @@ -83,6 +88,9 @@
- Back
- Belt
- type: Reflect
reflectProb: 0.3
velocityBeforeNotMaxProb: 6.0 # don't punish ninjas for being ninjas
velocityBeforeMinProb: 10.0

- type: entity
name: machete
Expand Down Expand Up @@ -152,7 +160,7 @@
wideAnimationRotation: -135
damage:
types:
Slash: 16
Slash: 15
soundHit:
path: /Audio/Weapons/bladeslice.ogg
- type: Item
Expand All @@ -164,7 +172,7 @@
name: The Throngler
parent: BaseItem
id: Throngler
description: Why would you make this?
description: Why would someone make this?
components:
- type: Sharp
- type: Sprite
Expand All @@ -185,7 +193,10 @@
path: /Audio/Effects/explosion_small1.ogg
- type: Reflect
enabled: true
reflectProb: .25
reflectProb: 0.5 # In robust hands, deflects as well as an e-sword
velocityBeforeNotMaxProb: 1.0
velocityBeforeMinProb: 3.0
minReflectProb: 0.1
spread: 90
- type: Item
size: Ginormous
Expand Down
Loading