-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Execution (you monster) not done * woops * more stuff * Melee executions * Prevent executing those who can interact * Better checks for if you can execute * Scale the execution time of a knife with its attack speed * Translations for fucking up an execution * rename some functions * Properly scale execution speed of melee weapons * Fix checks in CanExecuteWithAny * Allow executing yourself (funny) * More versatile localisation * Suicide with guns * Popups for successful gun executions * whoops * Stop flare guns crashing the game on executions * Various tweaks * Remove some old usings * Pacifists can no longer execute * Remove unnecessary check * Use CanShoot in gunsystem * Capitalisation in ftl string * Fix melee executions not playing a sound * localisation tweaks
- Loading branch information
Showing
4 changed files
with
437 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,397 @@ | ||
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 weapon, EntityUid victim, EntityUid attacker) | ||
{ | ||
// No point executing someone if they can't take damage | ||
if (!TryComp<DamageableComponent>(victim, out var damage)) | ||
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(weapon, 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(weapon, 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) | ||
{ | ||
BreakOnTargetMove = true, | ||
BreakOnUserMove = 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) | ||
{ | ||
BreakOnTargetMove = true, | ||
BreakOnUserMove = true, | ||
BreakOnDamage = true, | ||
NeedHand = true | ||
}; | ||
|
||
_doAfterSystem.TryStartDoAfter(doAfter); | ||
} | ||
|
||
private bool OnDoafterChecks(EntityUid uid, DoAfterEvent args) | ||
{ | ||
if (args.Handled || args.Cancelled || args.Used == null || args.Target == null) | ||
return false; | ||
|
||
if (!CanExecuteWithAny(args.Used.Value, args.Target.Value, uid)) | ||
return false; | ||
|
||
// All checks passed | ||
return true; | ||
} | ||
|
||
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.PopupClient(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); | ||
} | ||
} |
Oops, something went wrong.