* Revert "Cleanup ExecutionSystem (#24382)" This reverts commitbcbe2ec1af. * Revert "Executions (#24150)" This reverts commit2e83f5a0ec. # Conflicts: # Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Execution;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ExecutionDoAfterEvent : SimpleDoAfterEvent
|
||||
{
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Execution;
|
||||
|
||||
/// <summary>
|
||||
/// Added to entities that can be used to execute another target.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ExecutionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long the execution duration lasts.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DoAfterDuration = 5f;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public float DamageModifier = 9f;
|
||||
|
||||
// Not networked because this is transient inside of a tick.
|
||||
/// <summary>
|
||||
/// True if it is currently executing for handlers.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Executing = true;
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared.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 ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly SharedGunSystem _gunSystem = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _combatSystem = default!;
|
||||
[Dependency] private readonly SharedMeleeWeaponSystem _meleeSystem = default!;
|
||||
|
||||
// TODO: Still needs more cleaning up.
|
||||
private const string DefaultInternalMeleeExecutionMessage = "execution-popup-melee-initial-internal";
|
||||
private const string DefaultExternalMeleeExecutionMessage = "execution-popup-melee-initial-external";
|
||||
private const string DefaultCompleteInternalMeleeExecutionMessage = "execution-popup-melee-complete-internal";
|
||||
private const string DefaultCompleteExternalMeleeExecutionMessage = "execution-popup-melee-complete-external";
|
||||
private const string DefaultInternalGunExecutionMessage = "execution-popup-gun-initial-internal";
|
||||
private const string DefaultExternalGunExecutionMessage = "execution-popup-gun-initial-external";
|
||||
private const string DefaultCompleteInternalGunExecutionMessage = "execution-popup-gun-complete-internal";
|
||||
private const string DefaultCompleteExternalGunExecutionMessage = "execution-popup-gun-complete-external";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ExecutionComponent, GetVerbsEvent<UtilityVerb>>(OnGetInteractionsVerbs);
|
||||
SubscribeLocalEvent<ExecutionComponent, ExecutionDoAfterEvent>(OnExecutionDoAfter);
|
||||
SubscribeLocalEvent<ExecutionComponent, GetMeleeDamageEvent>(OnGetMeleeDamage);
|
||||
}
|
||||
|
||||
private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, 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 (!CanExecuteWithAny(victim, attacker))
|
||||
return;
|
||||
|
||||
UtilityVerb verb = new()
|
||||
{
|
||||
Act = () => TryStartExecutionDoAfter(weapon, victim, attacker, comp),
|
||||
Impact = LogImpact.High,
|
||||
Text = Loc.GetString("execution-verb-name"),
|
||||
Message = Loc.GetString("execution-verb-message"),
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void TryStartExecutionDoAfter(EntityUid weapon, EntityUid victim, EntityUid attacker, ExecutionComponent comp)
|
||||
{
|
||||
if (!CanExecuteWithAny(victim, attacker))
|
||||
return;
|
||||
|
||||
// TODO: This should just be on the weapons as a single execution message.
|
||||
var defaultExecutionInternal = DefaultInternalMeleeExecutionMessage;
|
||||
var defaultExecutionExternal = DefaultExternalMeleeExecutionMessage;
|
||||
|
||||
if (HasComp<GunComponent>(weapon))
|
||||
{
|
||||
defaultExecutionExternal = DefaultInternalGunExecutionMessage;
|
||||
defaultExecutionInternal = DefaultExternalGunExecutionMessage;
|
||||
}
|
||||
|
||||
var internalMsg = defaultExecutionInternal;
|
||||
var externalMsg = defaultExecutionExternal;
|
||||
ShowExecutionInternalPopup(internalMsg, attacker, victim, weapon);
|
||||
ShowExecutionExternalPopup(externalMsg, attacker, victim, weapon);
|
||||
|
||||
var doAfter =
|
||||
new DoAfterArgs(EntityManager, attacker, comp.DoAfterDuration, new ExecutionDoAfterEvent(), weapon, target: victim, used: weapon)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfter);
|
||||
|
||||
}
|
||||
|
||||
private bool CanExecuteWithAny(EntityUid victim, EntityUid attacker)
|
||||
{
|
||||
// Use suicide.
|
||||
if (victim == attacker)
|
||||
return false;
|
||||
|
||||
// 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 void OnExecutionDoAfter(EntityUid uid, ExecutionComponent component, ExecutionDoAfterEvent 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 (!CanExecuteWithAny(victim, attacker))
|
||||
return;
|
||||
|
||||
// This is needed so the melee system does not stop it.
|
||||
var prev = _combatSystem.IsInCombatMode(attacker);
|
||||
_combatSystem.SetInCombatMode(attacker, true);
|
||||
component.Executing = true;
|
||||
string? internalMsg = null;
|
||||
string? externalMsg = null;
|
||||
|
||||
if (TryComp(uid, out MeleeWeaponComponent? melee))
|
||||
{
|
||||
_meleeSystem.AttemptLightAttack(attacker, weapon, melee, victim);
|
||||
internalMsg = DefaultCompleteInternalMeleeExecutionMessage;
|
||||
externalMsg = DefaultCompleteExternalMeleeExecutionMessage;
|
||||
}
|
||||
else if (TryComp(uid, out GunComponent? gun))
|
||||
{
|
||||
var clumsyShot = false;
|
||||
|
||||
// TODO: This should just be an event or something instead to get this.
|
||||
// TODO: Handle clumsy.
|
||||
if (!_gunSystem.AttemptDirectShoot(args.User, uid, args.Target.Value, gun))
|
||||
{
|
||||
internalMsg = null;
|
||||
externalMsg = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
internalMsg = DefaultCompleteInternalGunExecutionMessage;
|
||||
externalMsg = DefaultCompleteExternalGunExecutionMessage;
|
||||
}
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
_combatSystem.SetInCombatMode(attacker, prev);
|
||||
component.Executing = false;
|
||||
args.Handled = true;
|
||||
|
||||
if (internalMsg != null && externalMsg != null)
|
||||
{
|
||||
ShowExecutionInternalPopup(internalMsg, attacker, victim, uid);
|
||||
ShowExecutionExternalPopup(externalMsg, attacker, victim, uid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetMeleeDamage(EntityUid uid, ExecutionComponent comp, ref GetMeleeDamageEvent args)
|
||||
{
|
||||
if (!TryComp<MeleeWeaponComponent>(uid, out var melee) ||
|
||||
!TryComp<ExecutionComponent>(uid, out var execComp) ||
|
||||
!execComp.Executing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bonus = melee.Damage * execComp.DamageModifier - melee.Damage;
|
||||
args.Damage += bonus;
|
||||
}
|
||||
|
||||
private void ShowExecutionInternalPopup(string locString,
|
||||
EntityUid attacker, EntityUid victim, EntityUid weapon, bool predict = true)
|
||||
{
|
||||
if (predict)
|
||||
{
|
||||
_popupSystem.PopupClient(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
attacker,
|
||||
attacker,
|
||||
PopupType.Medium
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
attacker,
|
||||
Filter.Entities(attacker),
|
||||
true,
|
||||
PopupType.Medium
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ShowExecutionExternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon)
|
||||
{
|
||||
_popupSystem.PopupEntity(
|
||||
Loc.GetString(locString, ("attacker", attacker), ("victim", victim), ("weapon", weapon)),
|
||||
attacker,
|
||||
Filter.PvsExcept(attacker),
|
||||
true,
|
||||
PopupType.MediumCaution
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
|
||||
SubscribeLocalEvent<ProjectileComponent, AfterProjectileHitEvent>(AfterProjectileHit);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
|
||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
|
||||
@@ -162,6 +163,18 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
||||
{
|
||||
args.Cancel("pacified-cannot-throw-embed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the projectile is allowed to penetrate the target it hit.
|
||||
/// </summary>
|
||||
private void AfterProjectileHit(EntityUid uid, ProjectileComponent component, ref AfterProjectileHitEvent args)
|
||||
{
|
||||
//Overrides the original DeleteOnCollide if the projectile passes all penetration checks.
|
||||
//This is to prevent having to set DeleteOnCollide to false on every prototype
|
||||
//you want to give the ability to penetrate entities.
|
||||
if(component.DeleteOnCollide)
|
||||
component.DeleteOnCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
@@ -193,4 +206,4 @@ public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target
|
||||
/// Raised after a projectile has dealt it's damage.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target);
|
||||
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture);
|
||||
|
||||
@@ -19,7 +19,7 @@ public record struct ShotAttemptedEvent
|
||||
|
||||
public bool Cancelled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// Prevent the gun from shooting
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
@@ -27,7 +27,7 @@ public record struct ShotAttemptedEvent
|
||||
Cancelled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// Allow the gun to shoot again, only use if you know what you are doing
|
||||
/// </summary>
|
||||
public void Uncancel()
|
||||
|
||||
@@ -21,7 +21,6 @@ using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -144,7 +143,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
|
||||
gun.ShootCoordinates = GetCoordinates(msg.Coordinates);
|
||||
Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}");
|
||||
AttemptShootInternal(user.Value, ent, gun);
|
||||
AttemptShoot(user.Value, ent, gun);
|
||||
}
|
||||
|
||||
private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args)
|
||||
@@ -208,38 +207,13 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
Dirty(uid, gun);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to shoot the specified target directly.
|
||||
/// This may bypass projectiles firing etc.
|
||||
/// </summary>
|
||||
public bool AttemptDirectShoot(EntityUid user, EntityUid gunUid, EntityUid target, GunComponent gun)
|
||||
{
|
||||
// Unique name so people don't think it's "shoot towards" and not "I will teleport a bullet into them".
|
||||
gun.ShootCoordinates = Transform(target).Coordinates;
|
||||
|
||||
if (!TryTakeAmmo(user, gunUid, gun, out _, out _, out var args))
|
||||
{
|
||||
gun.ShootCoordinates = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = ShootDirect(gunUid, gun, target, args.Ammo, user: user);
|
||||
gun.ShootCoordinates = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual bool ShootDirect(EntityUid gunUid, GunComponent gun, EntityUid target, List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityUid user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to shoot at the target coordinates. Resets the shot counter after every shot.
|
||||
/// </summary>
|
||||
public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, EntityCoordinates toCoordinates)
|
||||
{
|
||||
gun.ShootCoordinates = toCoordinates;
|
||||
AttemptShootInternal(user, gunUid, gun);
|
||||
AttemptShoot(user, gunUid, gun);
|
||||
gun.ShotCounter = 0;
|
||||
}
|
||||
|
||||
@@ -250,35 +224,20 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
{
|
||||
var coordinates = new EntityCoordinates(gunUid, new Vector2(0, -1));
|
||||
gun.ShootCoordinates = coordinates;
|
||||
AttemptShootInternal(gunUid, gunUid, gun);
|
||||
AttemptShoot(gunUid, gunUid, gun);
|
||||
gun.ShotCounter = 0;
|
||||
}
|
||||
|
||||
private void AttemptShootInternal(EntityUid user, EntityUid gunUid, GunComponent gun)
|
||||
{
|
||||
if (!TryTakeAmmo(user, gunUid, gun, out var fromCoordinates, out var toCoordinates, out var args))
|
||||
return;
|
||||
|
||||
Shoot(gunUid, gun, args.Ammo, fromCoordinates, toCoordinates, out var userImpulse, user: user);
|
||||
|
||||
if (userImpulse && TryComp<PhysicsComponent>(user, out var userPhysics))
|
||||
{
|
||||
if (_gravity.IsWeightless(user, userPhysics))
|
||||
CauseImpulse(fromCoordinates, toCoordinates, user, userPhysics);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates if a gun can currently shoot.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
private bool CanShoot(EntityUid user, EntityUid gunUid, GunComponent gun)
|
||||
private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun)
|
||||
{
|
||||
if (gun.FireRateModified <= 0f ||
|
||||
!_actionBlockerSystem.CanAttack(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return;
|
||||
|
||||
var toCoordinates = gun.ShootCoordinates;
|
||||
|
||||
if (toCoordinates == null)
|
||||
return;
|
||||
|
||||
var curTime = Timing.CurTime;
|
||||
|
||||
@@ -290,42 +249,17 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
};
|
||||
RaiseLocalEvent(gunUid, ref prevention);
|
||||
if (prevention.Cancelled)
|
||||
return false;
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(user, ref prevention);
|
||||
if (prevention.Cancelled)
|
||||
return false;
|
||||
return;
|
||||
|
||||
// Need to do this to play the clicking sound for empty automatic weapons
|
||||
// but not play anything for burst fire.
|
||||
if (gun.NextFire > curTime)
|
||||
return false;
|
||||
return;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to return ammo prepped for shooting if a gun is available to shoot.
|
||||
/// </summary>
|
||||
private bool TryTakeAmmo(
|
||||
EntityUid user,
|
||||
EntityUid gunUid, GunComponent gun,
|
||||
out EntityCoordinates fromCoordinates,
|
||||
out EntityCoordinates toCoordinates,
|
||||
[NotNullWhen(true)] out TakeAmmoEvent? args)
|
||||
{
|
||||
toCoordinates = EntityCoordinates.Invalid;
|
||||
fromCoordinates = EntityCoordinates.Invalid;
|
||||
args = null;
|
||||
|
||||
if (!CanShoot(user, gunUid, gun))
|
||||
return false;
|
||||
|
||||
if (gun.ShootCoordinates == null)
|
||||
return false;
|
||||
|
||||
toCoordinates = gun.ShootCoordinates.Value;
|
||||
var curTime = Timing.CurTime;
|
||||
var fireRate = TimeSpan.FromSeconds(1f / gun.FireRateModified);
|
||||
|
||||
// First shot
|
||||
@@ -373,11 +307,10 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
}
|
||||
|
||||
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
fromCoordinates = Transform(user).Coordinates;
|
||||
|
||||
var fromCoordinates = Transform(user).Coordinates;
|
||||
// Remove ammo
|
||||
var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user);
|
||||
|
||||
@@ -412,18 +345,24 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
// May cause prediction issues? Needs more tweaking
|
||||
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
||||
Audio.PlayPredicted(gun.SoundEmpty, gunUid, user);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Shoot confirmed - sounds also played here in case it's invalid (e.g. cartridge already spent).
|
||||
Shoot(gunUid, gun, ev.Ammo, fromCoordinates, toCoordinates.Value, out var userImpulse, user, throwItems: attemptEv.ThrowItems);
|
||||
var shotEv = new GunShotEvent(user, ev.Ammo);
|
||||
RaiseLocalEvent(gunUid, ref shotEv);
|
||||
|
||||
args = ev;
|
||||
return true;
|
||||
if (userImpulse && TryComp<PhysicsComponent>(user, out var userPhysics))
|
||||
{
|
||||
if (_gravity.IsWeightless(user, userPhysics))
|
||||
CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics);
|
||||
}
|
||||
|
||||
Dirty(gunUid, gun);
|
||||
}
|
||||
|
||||
public void Shoot(
|
||||
|
||||
Reference in New Issue
Block a user