2023-04-07 11:21:12 -07:00
using System.Diagnostics.CodeAnalysis ;
2023-07-08 14:08:32 +10:00
using System.Numerics ;
2023-06-01 00:57:31 -05:00
using Content.Shared.Administration.Logs ;
2023-04-02 16:48:32 +03:00
using Content.Shared.Audio ;
2023-06-01 00:57:31 -05:00
using Content.Shared.Database ;
2023-04-02 16:48:32 +03:00
using Content.Shared.Hands.Components ;
2023-04-19 20:02:30 +10:00
using Content.Shared.Weapons.Ranged.Events ;
2023-07-30 18:07:45 -07:00
using Content.Shared.Inventory ;
using Content.Shared.Inventory.Events ;
2023-04-19 20:02:30 +10:00
using Robust.Shared.Physics.Components ;
2023-04-07 11:21:12 -07:00
using Content.Shared.Popups ;
2023-05-15 15:21:05 +10:00
using Content.Shared.Projectiles ;
using Content.Shared.Weapons.Ranged.Components ;
using Robust.Shared.Network ;
2023-04-07 11:21:12 -07:00
using Robust.Shared.Physics.Systems ;
using Robust.Shared.Random ;
2023-04-02 16:48:32 +03:00
namespace Content.Shared.Weapons.Reflect ;
/// <summary>
/// This handles reflecting projectiles and hitscan shots.
/// </summary>
public abstract class SharedReflectSystem : EntitySystem
{
2023-05-15 15:21:05 +10:00
[Dependency] private readonly INetManager _netManager = default ! ;
2023-04-02 16:48:32 +03:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2023-06-01 00:57:31 -05:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2023-04-02 16:48:32 +03:00
[Dependency] private readonly SharedPopupSystem _popup = default ! ;
[Dependency] private readonly SharedPhysicsSystem _physics = default ! ;
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
[Dependency] private readonly SharedTransformSystem _transform = default ! ;
2023-07-30 18:07:45 -07:00
[Dependency] private readonly InventorySystem _inventorySystem = default ! ;
2023-04-02 16:48:32 +03:00
public override void Initialize ( )
{
base . Initialize ( ) ;
2023-07-30 18:07:45 -07:00
2023-04-07 11:21:12 -07:00
SubscribeLocalEvent < HandsComponent , ProjectileReflectAttemptEvent > ( OnHandReflectProjectile ) ;
SubscribeLocalEvent < HandsComponent , HitScanReflectAttemptEvent > ( OnHandsReflectHitscan ) ;
2023-04-02 16:48:32 +03:00
2023-05-15 15:21:05 +10:00
SubscribeLocalEvent < ReflectComponent , ProjectileCollideEvent > ( OnReflectCollide ) ;
SubscribeLocalEvent < ReflectComponent , HitScanReflectAttemptEvent > ( OnReflectHitscan ) ;
2023-07-30 18:07:45 -07:00
SubscribeLocalEvent < ReflectComponent , GotEquippedEvent > ( OnReflectEquipped ) ;
SubscribeLocalEvent < ReflectComponent , GotUnequippedEvent > ( OnReflectUnequipped ) ;
2023-04-02 16:48:32 +03:00
}
2023-05-15 15:21:05 +10:00
private void OnReflectCollide ( EntityUid uid , ReflectComponent component , ref ProjectileCollideEvent args )
2023-04-02 16:48:32 +03:00
{
2023-05-15 15:21:05 +10:00
if ( args . Cancelled )
{
2023-04-19 20:02:30 +10:00
return ;
2023-05-15 15:21:05 +10:00
}
2023-04-19 20:02:30 +10:00
2023-05-15 15:21:05 +10:00
if ( TryReflectProjectile ( uid , args . OtherEntity , reflect : component ) )
args . Cancelled = true ;
2023-04-02 16:48:32 +03:00
}
2023-04-07 11:21:12 -07:00
private void OnHandReflectProjectile ( EntityUid uid , HandsComponent hands , ref ProjectileReflectAttemptEvent args )
2023-04-02 16:48:32 +03:00
{
if ( args . Cancelled )
return ;
2023-04-19 20:02:30 +10:00
2023-05-15 15:21:05 +10:00
if ( hands . ActiveHandEntity ! = null & & TryReflectProjectile ( hands . ActiveHandEntity . Value , args . ProjUid ) )
2023-04-02 16:48:32 +03:00
args . Cancelled = true ;
}
2023-04-19 20:02:30 +10:00
2023-05-15 15:21:05 +10:00
private bool TryReflectProjectile ( EntityUid reflector , EntityUid projectile , ProjectileComponent ? projectileComp = null , ReflectComponent ? reflect = null )
2023-04-02 16:48:32 +03:00
{
2023-05-15 15:21:05 +10:00
if ( ! Resolve ( reflector , ref reflect , false ) | |
2023-04-19 20:02:30 +10:00
! reflect . Enabled | |
2023-05-15 15:21:05 +10:00
! TryComp < ReflectiveComponent > ( projectile , out var reflective ) | |
( reflect . Reflects & reflective . Reflective ) = = 0x0 | |
2023-04-19 20:02:30 +10:00
! _random . Prob ( reflect . ReflectProb ) | |
! TryComp < PhysicsComponent > ( projectile , out var physics ) )
2023-04-02 16:48:32 +03:00
{
2023-04-19 20:02:30 +10:00
return false ;
}
2023-04-02 16:48:32 +03:00
2023-04-19 20:02:30 +10:00
var rotation = _random . NextAngle ( - reflect . Spread / 2 , reflect . Spread / 2 ) . Opposite ( ) ;
var existingVelocity = _physics . GetMapLinearVelocity ( projectile , component : physics ) ;
2023-05-15 15:21:05 +10:00
var relativeVelocity = existingVelocity - _physics . GetMapLinearVelocity ( reflector ) ;
2023-04-19 20:02:30 +10:00
var newVelocity = rotation . RotateVec ( relativeVelocity ) ;
2023-04-02 16:48:32 +03:00
2023-04-19 20:02:30 +10:00
// Have the velocity in world terms above so need to convert it back to local.
var difference = newVelocity - existingVelocity ;
2023-04-02 16:48:32 +03:00
2023-04-19 20:02:30 +10:00
_physics . SetLinearVelocity ( projectile , physics . LinearVelocity + difference , body : physics ) ;
var locRot = Transform ( projectile ) . LocalRotation ;
var newRot = rotation . RotateVec ( locRot . ToVec ( ) ) ;
_transform . SetLocalRotation ( projectile , newRot . ToAngle ( ) ) ;
2023-05-15 15:21:05 +10:00
if ( _netManager . IsServer )
{
_popup . PopupEntity ( Loc . GetString ( "reflect-shot" ) , reflector ) ;
_audio . PlayPvs ( reflect . SoundOnReflect , reflector , AudioHelpers . WithVariation ( 0.05f , _random ) ) ;
}
if ( Resolve ( projectile , ref projectileComp , false ) )
{
2023-06-01 00:57:31 -05:00
_adminLogger . Add ( LogType . BulletHit , LogImpact . Medium , $"{ToPrettyString(reflector)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}" ) ;
2023-05-15 15:21:05 +10:00
projectileComp . Shooter = reflector ;
projectileComp . Weapon = reflector ;
Dirty ( projectileComp ) ;
}
2023-06-01 00:57:31 -05:00
else
{
_adminLogger . Add ( LogType . BulletHit , LogImpact . Medium , $"{ToPrettyString(reflector)} reflected {ToPrettyString(projectile)}" ) ;
}
2023-05-15 15:21:05 +10:00
2023-04-19 20:02:30 +10:00
return true ;
2023-04-02 16:48:32 +03:00
}
2023-04-07 11:21:12 -07:00
private void OnHandsReflectHitscan ( EntityUid uid , HandsComponent hands , ref HitScanReflectAttemptEvent args )
2023-04-02 16:48:32 +03:00
{
2023-05-15 15:21:05 +10:00
if ( args . Reflected | | hands . ActiveHandEntity = = null )
return ;
2023-06-01 00:57:31 -05:00
if ( TryReflectHitscan ( hands . ActiveHandEntity . Value , args . Shooter , args . SourceItem , args . Direction , out var dir ) )
2023-05-15 15:21:05 +10:00
{
args . Direction = dir . Value ;
args . Reflected = true ;
}
}
private void OnReflectHitscan ( EntityUid uid , ReflectComponent component , ref HitScanReflectAttemptEvent args )
{
if ( args . Reflected | |
( component . Reflects & args . Reflective ) = = 0x0 )
{
2023-04-02 16:48:32 +03:00
return ;
2023-05-15 15:21:05 +10:00
}
2023-04-19 20:02:30 +10:00
2023-06-01 00:57:31 -05:00
if ( TryReflectHitscan ( uid , args . Shooter , args . SourceItem , args . Direction , out var dir ) )
2023-04-02 16:48:32 +03:00
{
args . Direction = dir . Value ;
args . Reflected = true ;
}
}
2023-06-01 00:57:31 -05:00
private bool TryReflectHitscan ( EntityUid reflector , EntityUid ? shooter , EntityUid shotSource , Vector2 direction ,
2023-05-15 15:21:05 +10:00
[NotNullWhen(true)] out Vector2 ? newDirection )
2023-04-02 16:48:32 +03:00
{
2023-05-15 15:21:05 +10:00
if ( ! TryComp < ReflectComponent > ( reflector , out var reflect ) | |
! reflect . Enabled | |
! _random . Prob ( reflect . ReflectProb ) )
{
newDirection = null ;
return false ;
}
if ( _netManager . IsServer )
2023-04-02 16:48:32 +03:00
{
2023-05-15 15:21:05 +10:00
_popup . PopupEntity ( Loc . GetString ( "reflect-shot" ) , reflector ) ;
_audio . PlayPvs ( reflect . SoundOnReflect , reflector , AudioHelpers . WithVariation ( 0.05f , _random ) ) ;
2023-04-02 16:48:32 +03:00
}
2023-04-19 20:02:30 +10:00
2023-05-15 15:21:05 +10:00
var spread = _random . NextAngle ( - reflect . Spread / 2 , reflect . Spread / 2 ) ;
newDirection = - spread . RotateVec ( direction ) ;
2023-06-01 00:57:31 -05:00
if ( shooter ! = null )
_adminLogger . Add ( LogType . HitScanHit , LogImpact . Medium , $"{ToPrettyString(reflector)} reflected hitscan from {ToPrettyString(shotSource)} shot by {ToPrettyString(shooter.Value)}" ) ;
else
_adminLogger . Add ( LogType . HitScanHit , LogImpact . Medium , $"{ToPrettyString(reflector)} reflected hitscan from {ToPrettyString(shotSource)}" ) ;
2023-05-15 15:21:05 +10:00
return true ;
2023-04-02 16:48:32 +03:00
}
2023-07-30 18:07:45 -07:00
private void OnReflectEquipped ( EntityUid uid , ReflectComponent comp , GotEquippedEvent args )
{
if ( ! TryComp ( args . Equipee , out ReflectComponent ? reflection ) )
return ;
reflection . Enabled = true ;
// 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 ;
}
private void OnReflectUnequipped ( EntityUid uid , ReflectComponent comp , GotUnequippedEvent args )
{
if ( ! TryComp ( args . Equipee , out ReflectComponent ? reflection ) )
return ;
if ( ! _inventorySystem . TryGetSlots ( args . Equipee , out var slotDef ) )
return ;
// 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 )
{
if ( ! _inventorySystem . TryGetSlotEntity ( args . Equipee , slot . Name , out var slotEnt ) )
continue ;
if ( ! TryComp ( slotEnt , out ReflectComponent ? refcomp ) )
continue ;
reflecting = true ;
var prob = refcomp . ReflectProb ;
newProb - = newProb * prob ;
}
reflection . ReflectProb = 1 - newProb ;
reflection . Enabled = reflecting ;
}
2023-04-02 16:48:32 +03:00
}