This reverts commit 3c9c149b81.
# Conflicts:
# Content.Server/Projectiles/ProjectileSystem.cs
# Content.Server/Weapons/Ranged/Systems/GunSystem.cs
# Content.Shared/Projectiles/SharedProjectileSystem.cs
# Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml
This commit is contained in:
@@ -1,419 +0,0 @@
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Execution;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Execution;
|
||||
|
||||
/// <summary>
|
||||
/// Verb for violently murdering cuffed creatures.
|
||||
/// </summary>
|
||||
public sealed class ExecutionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly GunSystem _gunSystem = default!;
|
||||
|
||||
private const float MeleeExecutionTimeModifier = 5.0f;
|
||||
private const float GunExecutionTime = 6.0f;
|
||||
private const float DamageModifier = 9.0f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharpComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionVerbsMelee);
|
||||
SubscribeLocalEvent<GunComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionVerbsGun);
|
||||
|
||||
SubscribeLocalEvent<SharpComponent, ExecutionDoAfterEvent>(OnDoafterMelee);
|
||||
SubscribeLocalEvent<GunComponent, ExecutionDoAfterEvent>(OnDoafterGun);
|
||||
}
|
||||
|
||||
private void OnGetInteractionVerbsMelee(
|
||||
EntityUid uid,
|
||||
SharpComponent component,
|
||||
GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
var attacker = args.User;
|
||||
var weapon = args.Using!.Value;
|
||||
var victim = args.Target;
|
||||
|
||||
if (!CanExecuteWithMelee(weapon, victim, attacker))
|
||||
return;
|
||||
|
||||
UtilityVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
TryStartMeleeExecutionDoafter(weapon, victim, attacker);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Text = Loc.GetString("execution-verb-name"),
|
||||
Message = Loc.GetString("execution-verb-message"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnGetInteractionVerbsGun(
|
||||
EntityUid uid,
|
||||
GunComponent component,
|
||||
GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
var attacker = args.User;
|
||||
var weapon = args.Using!.Value;
|
||||
var victim = args.Target;
|
||||
|
||||
if (!CanExecuteWithGun(weapon, victim, attacker))
|
||||
return;
|
||||
|
||||
UtilityVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
TryStartGunExecutionDoafter(weapon, victim, attacker);
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Text = Loc.GetString("execution-verb-name"),
|
||||
Message = Loc.GetString("execution-verb-message"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private bool CanExecuteWithAny(EntityUid victim, EntityUid attacker)
|
||||
{
|
||||
// No point executing someone if they can't take damage
|
||||
if (!TryComp<DamageableComponent>(victim, out _))
|
||||
return false;
|
||||
|
||||
// You can't execute something that cannot die
|
||||
if (!TryComp<MobStateComponent>(victim, out var mobState))
|
||||
return false;
|
||||
|
||||
// You're not allowed to execute dead people (no fun allowed)
|
||||
if (_mobStateSystem.IsDead(victim, mobState))
|
||||
return false;
|
||||
|
||||
// You must be able to attack people to execute
|
||||
if (!_actionBlockerSystem.CanAttack(attacker, victim))
|
||||
return false;
|
||||
|
||||
// The victim must be incapacitated to be executed
|
||||
if (victim != attacker && _actionBlockerSystem.CanInteract(victim, null))
|
||||
return false;
|
||||
|
||||
// All checks passed
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CanExecuteWithMelee(EntityUid weapon, EntityUid victim, EntityUid user)
|
||||
{
|
||||
if (!CanExecuteWithAny(victim, user)) return false;
|
||||
|
||||
// We must be able to actually hurt people with the weapon
|
||||
if (!TryComp<MeleeWeaponComponent>(weapon, out var melee) && melee!.Damage.GetTotal() > 0.0f)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CanExecuteWithGun(EntityUid weapon, EntityUid victim, EntityUid user)
|
||||
{
|
||||
if (!CanExecuteWithAny(victim, user)) return false;
|
||||
|
||||
// We must be able to actually fire the gun
|
||||
if (!TryComp<GunComponent>(weapon, out var gun) && _gunSystem.CanShoot(gun!))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryStartMeleeExecutionDoafter(EntityUid weapon, EntityUid victim, EntityUid attacker)
|
||||
{
|
||||
if (!CanExecuteWithMelee(weapon, victim, attacker))
|
||||
return;
|
||||
|
||||
var executionTime = (1.0f / Comp<MeleeWeaponComponent>(weapon).AttackRate) * MeleeExecutionTimeModifier;
|
||||
|
||||
if (attacker == victim)
|
||||
{
|
||||
ShowExecutionPopup("suicide-popup-melee-initial-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("suicide-popup-melee-initial-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowExecutionPopup("execution-popup-melee-initial-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("execution-popup-melee-initial-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
|
||||
var doAfter = new DoAfterArgs(EntityManager, attacker, executionTime, new ExecutionDoAfterEvent(), weapon,
|
||||
target: victim, used: weapon)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfter);
|
||||
}
|
||||
|
||||
private void TryStartGunExecutionDoafter(EntityUid weapon, EntityUid victim, EntityUid attacker)
|
||||
{
|
||||
if (!CanExecuteWithGun(weapon, victim, attacker))
|
||||
return;
|
||||
|
||||
if (attacker == victim)
|
||||
{
|
||||
ShowExecutionPopup("suicide-popup-gun-initial-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("suicide-popup-gun-initial-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowExecutionPopup("execution-popup-gun-initial-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("execution-popup-gun-initial-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
|
||||
var doAfter = new DoAfterArgs(EntityManager, attacker, GunExecutionTime, new ExecutionDoAfterEvent(), weapon,
|
||||
target: victim, used: weapon)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfter);
|
||||
}
|
||||
|
||||
private void OnDoafterMelee(EntityUid uid, SharpComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
|
||||
return;
|
||||
|
||||
var attacker = args.User;
|
||||
var victim = args.Target!.Value;
|
||||
var weapon = args.Used!.Value;
|
||||
|
||||
if (!CanExecuteWithMelee(weapon, victim, attacker)) return;
|
||||
|
||||
if (!TryComp<MeleeWeaponComponent>(weapon, out var melee) && melee!.Damage.GetTotal() > 0.0f)
|
||||
return;
|
||||
|
||||
_damageableSystem.TryChangeDamage(victim, melee.Damage * DamageModifier, true);
|
||||
_audioSystem.PlayEntity(melee.HitSound, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
|
||||
|
||||
if (attacker == victim)
|
||||
{
|
||||
ShowExecutionPopup("suicide-popup-melee-complete-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("suicide-popup-melee-complete-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowExecutionPopup("execution-popup-melee-complete-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("execution-popup-melee-complete-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This repeats a lot of the code of the serverside GunSystem, make it not do that
|
||||
private void OnDoafterGun(EntityUid uid, GunComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null)
|
||||
return;
|
||||
|
||||
var attacker = args.User;
|
||||
var weapon = args.Used!.Value;
|
||||
var victim = args.Target!.Value;
|
||||
|
||||
if (!CanExecuteWithGun(weapon, victim, attacker)) return;
|
||||
|
||||
// Check if any systems want to block our shot
|
||||
var prevention = new ShotAttemptedEvent
|
||||
{
|
||||
User = attacker,
|
||||
Used = weapon
|
||||
};
|
||||
|
||||
RaiseLocalEvent(weapon, ref prevention);
|
||||
if (prevention.Cancelled)
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(attacker, ref prevention);
|
||||
if (prevention.Cancelled)
|
||||
return;
|
||||
|
||||
// Not sure what this is for but gunsystem uses it so ehhh
|
||||
var attemptEv = new AttemptShootEvent(attacker, null);
|
||||
RaiseLocalEvent(weapon, ref attemptEv);
|
||||
|
||||
if (attemptEv.Cancelled)
|
||||
{
|
||||
if (attemptEv.Message != null)
|
||||
{
|
||||
_popupSystem.PopupEntity(attemptEv.Message, weapon, attacker);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Take some ammunition for the shot (one bullet)
|
||||
var fromCoordinates = Transform(attacker).Coordinates;
|
||||
var ev = new TakeAmmoEvent(1, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, attacker);
|
||||
RaiseLocalEvent(weapon, ev);
|
||||
|
||||
// Check if there's any ammo left
|
||||
if (ev.Ammo.Count <= 0)
|
||||
{
|
||||
_audioSystem.PlayEntity(component.SoundEmpty, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
|
||||
ShowExecutionPopup("execution-popup-gun-empty", Filter.Pvs(weapon), PopupType.Medium, attacker, victim,
|
||||
weapon);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Information about the ammo like damage
|
||||
DamageSpecifier damage = new DamageSpecifier();
|
||||
|
||||
// Get some information from IShootable
|
||||
var ammoUid = ev.Ammo[0].Entity;
|
||||
switch (ev.Ammo[0].Shootable)
|
||||
{
|
||||
case CartridgeAmmoComponent cartridge:
|
||||
// Get the damage value
|
||||
var prototype = _prototypeManager.Index<EntityPrototype>(cartridge.Prototype);
|
||||
prototype.TryGetComponent<ProjectileComponent>(out var projectileA,
|
||||
_componentFactory); // sloth forgive me
|
||||
|
||||
if (projectileA != null)
|
||||
{
|
||||
damage = projectileA.Damage * cartridge.Count;
|
||||
}
|
||||
|
||||
// Expend the cartridge
|
||||
cartridge.Spent = true;
|
||||
_appearanceSystem.SetData(ammoUid!.Value, AmmoVisuals.Spent, true);
|
||||
Dirty(ammoUid.Value, cartridge);
|
||||
|
||||
break;
|
||||
|
||||
case AmmoComponent newAmmo:
|
||||
TryComp<ProjectileComponent>(ammoUid, out var projectileB);
|
||||
if (projectileB != null)
|
||||
{
|
||||
damage = projectileB.Damage;
|
||||
}
|
||||
|
||||
Del(ammoUid);
|
||||
break;
|
||||
|
||||
case HitscanPrototype hitscan:
|
||||
damage = hitscan.Damage!;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Clumsy people have a chance to shoot themselves
|
||||
if (TryComp<ClumsyComponent>(attacker, out var clumsy) && component.ClumsyProof == false)
|
||||
{
|
||||
if (_interactionSystem.TryRollClumsy(attacker, 0.33333333f, clumsy))
|
||||
{
|
||||
ShowExecutionPopup("execution-popup-gun-clumsy-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("execution-popup-gun-clumsy-external", Filter.PvsExcept(attacker),
|
||||
PopupType.MediumCaution, attacker, victim, weapon);
|
||||
|
||||
// You shoot yourself with the gun (no damage multiplier)
|
||||
_damageableSystem.TryChangeDamage(attacker, damage, origin: attacker);
|
||||
_audioSystem.PlayEntity(component.SoundGunshot, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Gun successfully fired, deal damage
|
||||
_damageableSystem.TryChangeDamage(victim, damage * DamageModifier, true);
|
||||
_audioSystem.PlayEntity(component.SoundGunshot, Filter.Pvs(weapon), weapon, false, AudioParams.Default);
|
||||
|
||||
// Popups
|
||||
if (attacker != victim)
|
||||
{
|
||||
ShowExecutionPopup("execution-popup-gun-complete-internal", Filter.Entities(attacker), PopupType.Medium,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("execution-popup-gun-complete-external", Filter.PvsExcept(attacker),
|
||||
PopupType.LargeCaution, attacker, victim, weapon);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowExecutionPopup("suicide-popup-gun-complete-internal", Filter.Entities(attacker), PopupType.LargeCaution,
|
||||
attacker, victim, weapon);
|
||||
|
||||
ShowExecutionPopup("suicide-popup-gun-complete-external", Filter.PvsExcept(attacker),
|
||||
PopupType.LargeCaution, attacker, victim, weapon);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowExecutionPopup(
|
||||
string locString,
|
||||
Filter filter,
|
||||
PopupType type,
|
||||
EntityUid attacker,
|
||||
EntityUid victim,
|
||||
EntityUid weapon)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString(
|
||||
locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
attacker, filter, true, type);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared._White;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
@@ -62,27 +61,12 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryHandleProjectile(target, (uid, component), args.OtherFixture))
|
||||
{
|
||||
var direction = args.OurBody.LinearVelocity.Normalized();
|
||||
_sharedCameraRecoil.KickCamera(target, direction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to handle a projectile interacting with the target.
|
||||
/// </summary>
|
||||
/// <returns>True if the target isn't deleted.</returns>
|
||||
public bool TryHandleProjectile(EntityUid target, Entity<ProjectileComponent> projectile, Fixture? otherFixture)
|
||||
{
|
||||
var uid = projectile.Owner;
|
||||
var component = projectile.Comp;
|
||||
|
||||
var ev = new ProjectileHitEvent(component.Damage, target, component.Shooter);
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
|
||||
var otherName = ToPrettyString(target);
|
||||
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
|
||||
var direction = args.OurBody.LinearVelocity.Normalized();
|
||||
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage * DamageModifier, component.IgnoreResistances, origin: component.Shooter);
|
||||
var deleted = Deleted(target);
|
||||
|
||||
if (modifiedDamage is not null && EntityManager.EntityExists(component.Shooter))
|
||||
@@ -100,13 +84,11 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
if (!deleted)
|
||||
{
|
||||
_guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
|
||||
_sharedCameraRecoil.KickCamera(target, direction);
|
||||
}
|
||||
|
||||
component.DamagedEntity = true;
|
||||
|
||||
var afterProjectileHitEvent = new AfterProjectileHitEvent(component.Damage, target, otherFixture);
|
||||
RaiseLocalEvent(uid, ref afterProjectileHitEvent);
|
||||
|
||||
if (component.DeleteOnCollide)
|
||||
QueueDel(uid);
|
||||
|
||||
@@ -114,7 +96,5 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
||||
{
|
||||
RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager));
|
||||
}
|
||||
|
||||
return !deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Numerics;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Server.Projectiles;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.Weapons.Ranged.Components;
|
||||
using Content.Server._White.Crossbow;
|
||||
@@ -38,7 +37,6 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly ProjectileSystem _projectile = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StaminaSystem _stamina = default!;
|
||||
[Dependency] private readonly StunSystem _stun = default!;
|
||||
@@ -69,137 +67,6 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
args.Price += price * component.UnspawnedCount;
|
||||
}
|
||||
|
||||
protected override bool ShootDirect(EntityUid gunUid, GunComponent gun, EntityUid target, List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityUid user)
|
||||
{
|
||||
var result = false;
|
||||
|
||||
// TODO: This is dogshit. I just want to get executions slightly better.
|
||||
// Ideally you'd pull out cartridge + ammo to separate handling functions and re-use it here, then hitscan you need to bypass entirely.
|
||||
// You should also make shooting into a struct of args given how many there are now.
|
||||
var fromCoordinates = Transform(gunUid).Coordinates;
|
||||
var toCoordinates = Transform(target).Coordinates;
|
||||
|
||||
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
|
||||
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
||||
var mapDirection = toMap - fromMap.Position;
|
||||
var angle = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle());
|
||||
|
||||
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
|
||||
var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out _)
|
||||
? fromCoordinates.WithEntityId(gridUid, EntityManager)
|
||||
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
|
||||
|
||||
// I must be high because this was getting tripped even when true.
|
||||
// DebugTools.Assert(direction != Vector2.Zero);
|
||||
var shotProjectiles = new List<EntityUid>(ammo.Count);
|
||||
var cartridgeBullets = new List<EntityUid>();
|
||||
|
||||
foreach (var (ent, shootable) in ammo)
|
||||
{
|
||||
switch (shootable)
|
||||
{
|
||||
// Cartridge shoots something else
|
||||
case CartridgeAmmoComponent cartridge:
|
||||
if (!cartridge.Spent)
|
||||
{
|
||||
for (var i = 0; i < cartridge.Count; i++)
|
||||
{
|
||||
var uid = Spawn(cartridge.Prototype, fromEnt);
|
||||
cartridgeBullets.Add(uid);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
|
||||
{
|
||||
FiredProjectiles = cartridgeBullets,
|
||||
});
|
||||
|
||||
shotProjectiles.AddRange(cartridgeBullets);
|
||||
cartridgeBullets.Clear();
|
||||
SetCartridgeSpent(ent.Value, cartridge, true);
|
||||
MuzzleFlash(gunUid, cartridge, user);
|
||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||
|
||||
if (cartridge.DeleteOnSpawn)
|
||||
Del(ent.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Audio.PlayPredicted(gun.SoundEmpty, gunUid, user);
|
||||
}
|
||||
|
||||
// Something like ballistic might want to leave it in the container still
|
||||
if (!cartridge.DeleteOnSpawn && !Containers.IsEntityInContainer(ent!.Value))
|
||||
EjectCartridge(ent.Value, angle);
|
||||
|
||||
result = true;
|
||||
Dirty(ent!.Value, cartridge);
|
||||
break;
|
||||
// Ammo shoots itself
|
||||
case AmmoComponent newAmmo:
|
||||
result = true;
|
||||
shotProjectiles.Add(ent!.Value);
|
||||
MuzzleFlash(gunUid, newAmmo, user);
|
||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||
break;
|
||||
case HitscanPrototype hitscan:
|
||||
result = true;
|
||||
var hitEntity = target;
|
||||
if (hitscan.StaminaDamage > 0f)
|
||||
_stamina.TakeStaminaDamage(hitEntity, hitscan.StaminaDamage, source: user);
|
||||
|
||||
var dmg = hitscan.Damage;
|
||||
|
||||
var hitName = ToPrettyString(hitEntity);
|
||||
if (dmg != null)
|
||||
dmg = Damageable.TryChangeDamage(hitEntity, dmg, origin: user);
|
||||
|
||||
// check null again, as TryChangeDamage returns modified damage values
|
||||
if (dmg != null)
|
||||
{
|
||||
if (!Deleted(hitEntity))
|
||||
{
|
||||
if (dmg.Any())
|
||||
{
|
||||
_color.RaiseEffect(Color.Red, [hitEntity], Filter.Pvs(hitEntity, entityManager: EntityManager));
|
||||
}
|
||||
|
||||
// TODO get fallback position for playing hit sound.
|
||||
PlayImpactSound(hitEntity, dmg, hitscan.Sound, hitscan.ForceSound);
|
||||
}
|
||||
|
||||
Logs.Add(LogType.HitScanHit,
|
||||
$"{ToPrettyString(user):user} hit {hitName:target} using hitscan and dealt {dmg.GetTotal():damage} damage");
|
||||
}
|
||||
|
||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ammoUid in shotProjectiles)
|
||||
{
|
||||
// TODO: Handle this shit
|
||||
if (!TryComp(ammoUid, out ProjectileComponent? projectileComponent))
|
||||
{
|
||||
QueueDel(ammoUid);
|
||||
continue;
|
||||
}
|
||||
|
||||
_projectile.TryHandleProjectile(target, (ammoUid, projectileComponent), null);
|
||||
// Even this deletion handling is mega sussy.
|
||||
Del(ammoUid);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(gunUid, new AmmoShotEvent()
|
||||
{
|
||||
FiredProjectiles = shotProjectiles,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo,
|
||||
EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, out bool userImpulse, EntityUid? user = null, bool throwItems = false)
|
||||
{
|
||||
@@ -207,7 +74,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
// Try a clumsy roll
|
||||
// TODO: Who put this here
|
||||
if (TryComp<ClumsyComponent>(user, out var clumsy) && !gun.ClumsyProof)
|
||||
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
|
||||
{
|
||||
for (var i = 0; i < ammo.Count; i++)
|
||||
{
|
||||
@@ -230,8 +97,6 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
}
|
||||
}
|
||||
|
||||
// As the above message wasn't obvious stop putting stuff here and use events
|
||||
|
||||
var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
|
||||
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
||||
var mapDirection = toMap - fromMap.Position;
|
||||
@@ -258,7 +123,6 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
// I must be high because this was getting tripped even when true.
|
||||
// DebugTools.Assert(direction != Vector2.Zero);
|
||||
var shotProjectiles = new List<EntityUid>(ammo.Count);
|
||||
var cartridgeBullets = new List<EntityUid>();
|
||||
|
||||
foreach (var (ent, shootable) in ammo)
|
||||
{
|
||||
@@ -287,23 +151,21 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
{
|
||||
var uid = Spawn(cartridge.Prototype, fromEnt);
|
||||
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
|
||||
cartridgeBullets.Add(uid);
|
||||
shotProjectiles.Add(uid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var uid = Spawn(cartridge.Prototype, fromEnt);
|
||||
ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
|
||||
cartridgeBullets.Add(uid);
|
||||
shotProjectiles.Add(uid);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
|
||||
{
|
||||
FiredProjectiles = cartridgeBullets,
|
||||
FiredProjectiles = shotProjectiles,
|
||||
});
|
||||
|
||||
shotProjectiles.AddRange(cartridgeBullets);
|
||||
cartridgeBullets.Clear();
|
||||
SetCartridgeSpent(ent.Value, cartridge, true);
|
||||
MuzzleFlash(gunUid, cartridge, user);
|
||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||
|
||||
Reference in New Issue
Block a user