Cleanup ExecutionSystem (#24382)
* Creat Execution Component and add to sharp items * Kill Server ExecutionSystem. Create ExecutionSystem in shared. Create ActiveExecution Component. Transferred the Execution system into shared. Heavily re-wrote the system in order to reduce duplication, and remove gun code from the system. The melee weapon modifier which was dependant on swing rate was removed. The ActiveExecutionComponent was created in order to apply the damage modifier to the shot from a gun execution. It is added just before the gun fires and removed after an attempt is made. * Fix bugs The execution completed text will now only show up if the gun fires. The client also no longer crashes because I forgot to network the component. * Remove clumsy text * Make BaseSword abstract * Add ExecutionComponent to every weapon * Fix bug * Remove execution comp from battery weapons Currently the gun system does not have a way to alter hitscan damage like it does with projectiles. * Cleanup * Revert "Remove clumsy text" This reverts commit a46da6448d5d179a4e936f9213d5622bedb58a16. * Actually fix the ExecutionSystem Everything about the shot goes through the gun system now. The Damage multiplier is only applied when a projectile impacts the target so people that get in the way don't get hit with 9 times damage for no reason. In order to make suicides work I needed to create fake EntityCoordinates because the gun system and the projectile system do not play well with a projectile that has the same start and end position. * Make launchers able to execute * Fix prediction bug The OnAmmoShotEvent is only raised on the server. * Readd ability for clowns to accidentally shoot themselves while executing * Cleanup * Reset melee cooldown to initial value * Address reviews fix bug Addressed reviews on overriding messages. Now I actually mark doafters as handled. Return normal cooldown to some meleeweapons I forgot on the previous commit. * Address Reviews Remove duplication * Exorcise codebase Remove evil null coercion that I was sure I removed a while ago * Address reviews again * Remove melee weapon attack logic and rely on the system. Remove gun and melee checks. * Make system functional again and cleanup * Remove code I forgot to remove * Cleanup * stalled * Selectively revert gun penetration The collision layer check doesn't work and I don't have time to fix it. * Fixes --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
@@ -1,397 +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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,10 +28,14 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
|||||||
{
|
{
|
||||||
// This is so entities that shouldn't get a collision are ignored.
|
// This is so entities that shouldn't get a collision are ignored.
|
||||||
if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard
|
if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard
|
||||||
|| component.DamagedEntity || component is { Weapon: null, OnlyCollideWhenShot: true })
|
|| component.DamagedEntity || component is
|
||||||
|
{ Weapon: null, OnlyCollideWhenShot: true })
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var target = args.OtherEntity;
|
var target = args.OtherEntity;
|
||||||
|
|
||||||
// it's here so this check is only done once before possible hit
|
// it's here so this check is only done once before possible hit
|
||||||
var attemptEv = new ProjectileReflectAttemptEvent(uid, component, false);
|
var attemptEv = new ProjectileReflectAttemptEvent(uid, component, false);
|
||||||
RaiseLocalEvent(target, ref attemptEv);
|
RaiseLocalEvent(target, ref attemptEv);
|
||||||
@@ -41,11 +45,26 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryHandleProjectile(target, (uid, component)))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var uid = projectile.Owner;
|
||||||
|
var component = projectile.Comp;
|
||||||
|
|
||||||
var ev = new ProjectileHitEvent(component.Damage, target, component.Shooter);
|
var ev = new ProjectileHitEvent(component.Damage, target, component.Shooter);
|
||||||
RaiseLocalEvent(uid, ref ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
|
|
||||||
var otherName = ToPrettyString(target);
|
var otherName = ToPrettyString(target);
|
||||||
var direction = args.OurBody.LinearVelocity.Normalized();
|
|
||||||
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
|
var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter);
|
||||||
var deleted = Deleted(target);
|
var deleted = Deleted(target);
|
||||||
|
|
||||||
@@ -64,12 +83,11 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
|||||||
if (!deleted)
|
if (!deleted)
|
||||||
{
|
{
|
||||||
_guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
|
_guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound);
|
||||||
_sharedCameraRecoil.KickCamera(target, direction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
component.DamagedEntity = true;
|
component.DamagedEntity = true;
|
||||||
|
|
||||||
var afterProjectileHitEvent = new AfterProjectileHitEvent(component.Damage, target, args.OtherFixture);
|
var afterProjectileHitEvent = new AfterProjectileHitEvent(component.Damage, target);
|
||||||
RaiseLocalEvent(uid, ref afterProjectileHitEvent);
|
RaiseLocalEvent(uid, ref afterProjectileHitEvent);
|
||||||
|
|
||||||
if (component.DeleteOnCollide)
|
if (component.DeleteOnCollide)
|
||||||
@@ -79,5 +97,7 @@ public sealed class ProjectileSystem : SharedProjectileSystem
|
|||||||
{
|
{
|
||||||
RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager));
|
RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !deleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Server.Administration.Logs;
|
|||||||
using Content.Server.Cargo.Systems;
|
using Content.Server.Cargo.Systems;
|
||||||
using Content.Server.Interaction;
|
using Content.Server.Interaction;
|
||||||
using Content.Server.Power.EntitySystems;
|
using Content.Server.Power.EntitySystems;
|
||||||
|
using Content.Server.Projectiles;
|
||||||
using Content.Server.Stunnable;
|
using Content.Server.Stunnable;
|
||||||
using Content.Server.Weapons.Ranged.Components;
|
using Content.Server.Weapons.Ranged.Components;
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
@@ -29,13 +30,13 @@ namespace Content.Server.Weapons.Ranged.Systems;
|
|||||||
|
|
||||||
public sealed partial class GunSystem : SharedGunSystem
|
public sealed partial class GunSystem : SharedGunSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
|
||||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||||
[Dependency] private readonly BatterySystem _battery = default!;
|
[Dependency] private readonly BatterySystem _battery = default!;
|
||||||
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
|
[Dependency] private readonly DamageExamineSystem _damageExamine = default!;
|
||||||
[Dependency] private readonly InteractionSystem _interaction = default!;
|
[Dependency] private readonly InteractionSystem _interaction = default!;
|
||||||
[Dependency] private readonly PricingSystem _pricing = default!;
|
[Dependency] private readonly PricingSystem _pricing = default!;
|
||||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||||
|
[Dependency] private readonly ProjectileSystem _projectile = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly StaminaSystem _stamina = default!;
|
[Dependency] private readonly StaminaSystem _stamina = default!;
|
||||||
[Dependency] private readonly StunSystem _stun = default!;
|
[Dependency] private readonly StunSystem _stun = default!;
|
||||||
@@ -65,6 +66,137 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
args.Price += price * component.UnspawnedCount;
|
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, new List<EntityUid>() { 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));
|
||||||
|
// 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,
|
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)
|
EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, out bool userImpulse, EntityUid? user = null, bool throwItems = false)
|
||||||
{
|
{
|
||||||
@@ -72,7 +204,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
|
|
||||||
// Try a clumsy roll
|
// Try a clumsy roll
|
||||||
// TODO: Who put this here
|
// TODO: Who put this here
|
||||||
if (TryComp<ClumsyComponent>(user, out var clumsy) && gun.ClumsyProof == false)
|
if (TryComp<ClumsyComponent>(user, out var clumsy) && !gun.ClumsyProof)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < ammo.Count; i++)
|
for (var i = 0; i < ammo.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -93,6 +225,8 @@ 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 fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem);
|
||||||
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem);
|
||||||
var mapDirection = toMap - fromMap.Position;
|
var mapDirection = toMap - fromMap.Position;
|
||||||
@@ -100,7 +234,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
var angle = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle());
|
var angle = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle());
|
||||||
|
|
||||||
// If applicable, this ensures the projectile is parented to grid on spawn, instead of the map.
|
// 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 var grid)
|
var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out _)
|
||||||
? fromCoordinates.WithEntityId(gridUid, EntityManager)
|
? fromCoordinates.WithEntityId(gridUid, EntityManager)
|
||||||
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
|
: new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position);
|
||||||
|
|
||||||
@@ -112,6 +246,7 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
// I must be high because this was getting tripped even when true.
|
// I must be high because this was getting tripped even when true.
|
||||||
// DebugTools.Assert(direction != Vector2.Zero);
|
// DebugTools.Assert(direction != Vector2.Zero);
|
||||||
var shotProjectiles = new List<EntityUid>(ammo.Count);
|
var shotProjectiles = new List<EntityUid>(ammo.Count);
|
||||||
|
var cartridgeBullets = new List<EntityUid>();
|
||||||
|
|
||||||
foreach (var (ent, shootable) in ammo)
|
foreach (var (ent, shootable) in ammo)
|
||||||
{
|
{
|
||||||
@@ -140,21 +275,23 @@ public sealed partial class GunSystem : SharedGunSystem
|
|||||||
{
|
{
|
||||||
var uid = Spawn(cartridge.Prototype, fromEnt);
|
var uid = Spawn(cartridge.Prototype, fromEnt);
|
||||||
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
|
ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
|
||||||
shotProjectiles.Add(uid);
|
cartridgeBullets.Add(uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uid = Spawn(cartridge.Prototype, fromEnt);
|
var uid = Spawn(cartridge.Prototype, fromEnt);
|
||||||
ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
|
ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
|
||||||
shotProjectiles.Add(uid);
|
cartridgeBullets.Add(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
|
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
|
||||||
{
|
{
|
||||||
FiredProjectiles = shotProjectiles,
|
FiredProjectiles = cartridgeBullets,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
shotProjectiles.AddRange(cartridgeBullets);
|
||||||
|
cartridgeBullets.Clear();
|
||||||
SetCartridgeSpent(ent.Value, cartridge, true);
|
SetCartridgeSpent(ent.Value, cartridge, true);
|
||||||
MuzzleFlash(gunUid, cartridge, user);
|
MuzzleFlash(gunUid, cartridge, user);
|
||||||
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
|
||||||
|
|||||||
26
Content.Shared/Execution/ExecutionComponent.cs
Normal file
26
Content.Shared/Execution/ExecutionComponent.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
241
Content.Shared/Execution/ExecutionSystem.cs
Normal file
241
Content.Shared/Execution/ExecutionSystem.cs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
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,7 +34,6 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
|
SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
|
||||||
SubscribeLocalEvent<ProjectileComponent, AfterProjectileHitEvent>(AfterProjectileHit);
|
|
||||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
|
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
|
||||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
|
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
|
||||||
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
|
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate);
|
||||||
@@ -163,18 +162,6 @@ public abstract partial class SharedProjectileSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
args.Cancel("pacified-cannot-throw-embed");
|
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]
|
[Serializable, NetSerializable]
|
||||||
@@ -206,4 +193,4 @@ public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target
|
|||||||
/// Raised after a projectile has dealt it's damage.
|
/// Raised after a projectile has dealt it's damage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture);
|
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public record struct ShotAttemptedEvent
|
|||||||
|
|
||||||
public bool Cancelled { get; private set; }
|
public bool Cancelled { get; private set; }
|
||||||
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// Prevent the gun from shooting
|
/// Prevent the gun from shooting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
@@ -27,7 +27,7 @@ public record struct ShotAttemptedEvent
|
|||||||
Cancelled = true;
|
Cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// </summary>
|
/// <summary>
|
||||||
/// Allow the gun to shoot again, only use if you know what you are doing
|
/// Allow the gun to shoot again, only use if you know what you are doing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Uncancel()
|
public void Uncancel()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using Content.Shared.Weapons.Melee;
|
|||||||
using Content.Shared.Weapons.Melee.Events;
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
using Content.Shared.Weapons.Ranged.Components;
|
using Content.Shared.Weapons.Ranged.Components;
|
||||||
using Content.Shared.Weapons.Ranged.Events;
|
using Content.Shared.Weapons.Ranged.Events;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Containers;
|
using Robust.Shared.Containers;
|
||||||
@@ -143,7 +144,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
|
|
||||||
gun.ShootCoordinates = GetCoordinates(msg.Coordinates);
|
gun.ShootCoordinates = GetCoordinates(msg.Coordinates);
|
||||||
Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}");
|
Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}");
|
||||||
AttemptShoot(user.Value, ent, gun);
|
AttemptShootInternal(user.Value, ent, gun);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args)
|
private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args)
|
||||||
@@ -207,13 +208,38 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
Dirty(uid, gun);
|
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>
|
/// <summary>
|
||||||
/// Attempts to shoot at the target coordinates. Resets the shot counter after every shot.
|
/// Attempts to shoot at the target coordinates. Resets the shot counter after every shot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, EntityCoordinates toCoordinates)
|
public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, EntityCoordinates toCoordinates)
|
||||||
{
|
{
|
||||||
gun.ShootCoordinates = toCoordinates;
|
gun.ShootCoordinates = toCoordinates;
|
||||||
AttemptShoot(user, gunUid, gun);
|
AttemptShootInternal(user, gunUid, gun);
|
||||||
gun.ShotCounter = 0;
|
gun.ShotCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,20 +250,35 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
var coordinates = new EntityCoordinates(gunUid, new Vector2(0, -1));
|
var coordinates = new EntityCoordinates(gunUid, new Vector2(0, -1));
|
||||||
gun.ShootCoordinates = coordinates;
|
gun.ShootCoordinates = coordinates;
|
||||||
AttemptShoot(gunUid, gunUid, gun);
|
AttemptShootInternal(gunUid, gunUid, gun);
|
||||||
gun.ShotCounter = 0;
|
gun.ShotCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun)
|
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)
|
||||||
{
|
{
|
||||||
if (gun.FireRateModified <= 0f ||
|
if (gun.FireRateModified <= 0f ||
|
||||||
!_actionBlockerSystem.CanAttack(user))
|
!_actionBlockerSystem.CanAttack(user))
|
||||||
return;
|
{
|
||||||
|
return false;
|
||||||
var toCoordinates = gun.ShootCoordinates;
|
}
|
||||||
|
|
||||||
if (toCoordinates == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var curTime = Timing.CurTime;
|
var curTime = Timing.CurTime;
|
||||||
|
|
||||||
@@ -249,17 +290,42 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
};
|
};
|
||||||
RaiseLocalEvent(gunUid, ref prevention);
|
RaiseLocalEvent(gunUid, ref prevention);
|
||||||
if (prevention.Cancelled)
|
if (prevention.Cancelled)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
RaiseLocalEvent(user, ref prevention);
|
RaiseLocalEvent(user, ref prevention);
|
||||||
if (prevention.Cancelled)
|
if (prevention.Cancelled)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
// Need to do this to play the clicking sound for empty automatic weapons
|
// Need to do this to play the clicking sound for empty automatic weapons
|
||||||
// but not play anything for burst fire.
|
// but not play anything for burst fire.
|
||||||
if (gun.NextFire > curTime)
|
if (gun.NextFire > curTime)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
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);
|
var fireRate = TimeSpan.FromSeconds(1f / gun.FireRateModified);
|
||||||
|
|
||||||
// First shot
|
// First shot
|
||||||
@@ -307,10 +373,11 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fromCoordinates = Transform(user).Coordinates;
|
fromCoordinates = Transform(user).Coordinates;
|
||||||
|
|
||||||
// Remove ammo
|
// Remove ammo
|
||||||
var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user);
|
var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user);
|
||||||
|
|
||||||
@@ -345,24 +412,18 @@ public abstract partial class SharedGunSystem : EntitySystem
|
|||||||
// May cause prediction issues? Needs more tweaking
|
// May cause prediction issues? Needs more tweaking
|
||||||
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
|
||||||
Audio.PlayPredicted(gun.SoundEmpty, gunUid, user);
|
Audio.PlayPredicted(gun.SoundEmpty, gunUid, user);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shoot confirmed - sounds also played here in case it's invalid (e.g. cartridge already spent).
|
// 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);
|
var shotEv = new GunShotEvent(user, ev.Ammo);
|
||||||
RaiseLocalEvent(gunUid, ref shotEv);
|
RaiseLocalEvent(gunUid, ref shotEv);
|
||||||
|
|
||||||
if (userImpulse && TryComp<PhysicsComponent>(user, out var userPhysics))
|
args = ev;
|
||||||
{
|
return true;
|
||||||
if (_gravity.IsWeightless(user, userPhysics))
|
|
||||||
CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dirty(gunUid, gun);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Shoot(
|
public void Shoot(
|
||||||
|
|||||||
@@ -14,17 +14,7 @@ execution-popup-gun-clumsy-internal = You miss {$victim}'s head and shoot your f
|
|||||||
execution-popup-gun-clumsy-external = {$attacker} misses {$victim} and shoots {POSS-ADJ($attacker)} foot instead!
|
execution-popup-gun-clumsy-external = {$attacker} misses {$victim} and shoots {POSS-ADJ($attacker)} foot instead!
|
||||||
execution-popup-gun-empty = {CAPITALIZE(THE($weapon))} clicks.
|
execution-popup-gun-empty = {CAPITALIZE(THE($weapon))} clicks.
|
||||||
|
|
||||||
suicide-popup-gun-initial-internal = You place the muzzle of {THE($weapon)} in your mouth.
|
|
||||||
suicide-popup-gun-initial-external = {$attacker} places the muzzle of {THE($weapon)} in {POSS-ADJ($attacker)} mouth.
|
|
||||||
suicide-popup-gun-complete-internal = You shoot yourself in the head!
|
|
||||||
suicide-popup-gun-complete-external = {$attacker} shoots {REFLEXIVE($attacker)} in the head!
|
|
||||||
|
|
||||||
execution-popup-melee-initial-internal = You ready {THE($weapon)} against {$victim}'s throat.
|
execution-popup-melee-initial-internal = You ready {THE($weapon)} against {$victim}'s throat.
|
||||||
execution-popup-melee-initial-external = {$attacker} readies {POSS-ADJ($attacker)} {$weapon} against the throat of {$victim}.
|
execution-popup-melee-initial-external = {$attacker} readies {POSS-ADJ($attacker)} {$weapon} against the throat of {$victim}.
|
||||||
execution-popup-melee-complete-internal = You slit the throat of {$victim}!
|
execution-popup-melee-complete-internal = You slit the throat of {$victim}!
|
||||||
execution-popup-melee-complete-external = {$attacker} slits the throat of {$victim}!
|
execution-popup-melee-complete-external = {$attacker} slits the throat of {$victim}!
|
||||||
|
|
||||||
suicide-popup-melee-initial-internal = You ready {THE($weapon)} against your throat.
|
|
||||||
suicide-popup-melee-initial-external = {$attacker} readies {POSS-ADJ($attacker)} {$weapon} against {POSS-ADJ($attacker)} throat.
|
|
||||||
suicide-popup-melee-complete-internal = You slit your throat with {THE($weapon)}!
|
|
||||||
suicide-popup-melee-complete-external = {$attacker} slits {POSS-ADJ($attacker)} throat with {THE($weapon)}!
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
butcherable-different-tool = You are going to need a different tool to butcher { THE($target) }.
|
butcherable-different-tool = You need a different tool to butcher { THE($target) }.
|
||||||
butcherable-knife-butchered-success = You butcher { THE($target) } with { THE($knife) }.
|
butcherable-knife-butchered-success = You butcher { THE($target) } with { THE($knife) }.
|
||||||
butcherable-need-knife = Use a sharp object to butcher { THE($target) }.
|
butcherable-need-knife = Use a sharp object to butcher { THE($target) }.
|
||||||
butcherable-not-in-container = { CAPITALIZE(THE($target)) } can't be in a container.
|
butcherable-not-in-container = { CAPITALIZE(THE($target)) } can't be in a container.
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
description: A small piece of crystal.
|
description: A small piece of crystal.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
layers:
|
layers:
|
||||||
- sprite: Objects/Materials/Shards/crystal.rsi
|
- sprite: Objects/Materials/Shards/crystal.rsi
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
description: It's a shard of some unknown material.
|
description: It's a shard of some unknown material.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
layers:
|
layers:
|
||||||
- sprite: Objects/Materials/Shards/shard.rsi
|
- sprite: Objects/Materials/Shards/shard.rsi
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
description: In Space Glasgow this is called a conversation starter.
|
description: In Space Glasgow this is called a conversation starter.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: MeleeWeapon
|
- type: MeleeWeapon
|
||||||
attackRate: 1.5
|
attackRate: 1.5
|
||||||
damage:
|
damage:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
path: /Audio/Weapons/Guns/Empty/lmg_empty.ogg
|
path: /Audio/Weapons/Guns/Empty/lmg_empty.ogg
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
# No chamber because HMG may want its own
|
# No chamber because HMG may want its own
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
price: 500
|
price: 500
|
||||||
- type: UseDelay
|
- type: UseDelay
|
||||||
delay: 1
|
delay: 1
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: L6 SAW
|
name: L6 SAW
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
containers:
|
containers:
|
||||||
ballistic-ammo: !type:Container
|
ballistic-ammo: !type:Container
|
||||||
ents: []
|
ents: []
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: china lake
|
name: china lake
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
- type: Appearance
|
- type: Appearance
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: viper
|
name: viper
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
gun_chamber: !type:ContainerSlot
|
gun_chamber: !type:ContainerSlot
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: AKMS
|
name: AKMS
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
gun_chamber: !type:ContainerSlot
|
gun_chamber: !type:ContainerSlot
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: Atreides
|
name: Atreides
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
ents: []
|
ents: []
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: Bulldog
|
name: Bulldog
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
- type: Appearance
|
- type: Appearance
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: double-barreled shotgun
|
name: double-barreled shotgun
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
ents: []
|
ents: []
|
||||||
- type: StaticPrice
|
- type: StaticPrice
|
||||||
price: 500
|
price: 500
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: Kardashev-Mosin
|
name: Kardashev-Mosin
|
||||||
|
|||||||
@@ -36,3 +36,4 @@
|
|||||||
quickEquip: false
|
quickEquip: false
|
||||||
slots:
|
slots:
|
||||||
- Belt
|
- Belt
|
||||||
|
- type: Execution
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
containers:
|
containers:
|
||||||
storagebase: !type:Container
|
storagebase: !type:Container
|
||||||
ents: []
|
ents: []
|
||||||
|
- type: Execution
|
||||||
|
|
||||||
# shoots bullets instead of throwing them, no other changes
|
# shoots bullets instead of throwing them, no other changes
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
description: A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter.
|
description: A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Weapons/Melee/armblade.rsi
|
sprite: Objects/Weapons/Melee/armblade.rsi
|
||||||
state: icon
|
state: icon
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
tags:
|
tags:
|
||||||
- FireAxe
|
- FireAxe
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Weapons/Melee/fireaxe.rsi
|
sprite: Objects/Weapons/Melee/fireaxe.rsi
|
||||||
state: icon
|
state: icon
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
tags:
|
tags:
|
||||||
- Knife
|
- Knife
|
||||||
- type: Sharp
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
- type: Utensil
|
- type: Utensil
|
||||||
types:
|
types:
|
||||||
- Knife
|
- Knife
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
|
- type: entity
|
||||||
|
name: Sword
|
||||||
|
parent: BaseItem
|
||||||
|
id: BaseSword
|
||||||
|
description: A sharp sword.
|
||||||
|
abstract: true
|
||||||
|
components:
|
||||||
|
- type: Sharp
|
||||||
|
- type: Execution
|
||||||
|
doAfterDuration: 4.0
|
||||||
|
- type: DisarmMalus
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: captain's sabre
|
name: captain's sabre
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: CaptainSabre
|
id: CaptainSabre
|
||||||
description: A ceremonial weapon belonging to the captain of the station.
|
description: A ceremonial weapon belonging to the captain of the station.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Weapons/Melee/captain_sabre.rsi
|
sprite: Objects/Weapons/Melee/captain_sabre.rsi
|
||||||
state: icon
|
state: icon
|
||||||
@@ -26,15 +37,13 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- CaptainSabre
|
- CaptainSabre
|
||||||
- type: DisarmMalus
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: katana
|
name: katana
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: Katana
|
id: Katana
|
||||||
description: Ancient craftwork made with not so ancient plasteel.
|
description: Ancient craftwork made with not so ancient plasteel.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- Katana
|
- Katana
|
||||||
@@ -51,7 +60,6 @@
|
|||||||
- type: Item
|
- type: Item
|
||||||
size: Normal
|
size: Normal
|
||||||
sprite: Objects/Weapons/Melee/katana.rsi
|
sprite: Objects/Weapons/Melee/katana.rsi
|
||||||
- type: DisarmMalus
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: energy katana
|
name: energy katana
|
||||||
@@ -86,11 +94,10 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: machete
|
name: machete
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: Machete
|
id: Machete
|
||||||
description: A large, vicious looking blade.
|
description: A large, vicious looking blade.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- Machete
|
- Machete
|
||||||
@@ -107,15 +114,13 @@
|
|||||||
- type: Item
|
- type: Item
|
||||||
size: Normal
|
size: Normal
|
||||||
sprite: Objects/Weapons/Melee/machete.rsi
|
sprite: Objects/Weapons/Melee/machete.rsi
|
||||||
- type: DisarmMalus
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: claymore
|
name: claymore
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: Claymore
|
id: Claymore
|
||||||
description: An ancient war blade.
|
description: An ancient war blade.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Weapons/Melee/claymore.rsi
|
sprite: Objects/Weapons/Melee/claymore.rsi
|
||||||
state: icon
|
state: icon
|
||||||
@@ -133,15 +138,13 @@
|
|||||||
sprite: Objects/Weapons/Melee/claymore.rsi
|
sprite: Objects/Weapons/Melee/claymore.rsi
|
||||||
slots:
|
slots:
|
||||||
- back
|
- back
|
||||||
- type: DisarmMalus
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: cutlass
|
name: cutlass
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: Cutlass
|
id: Cutlass
|
||||||
description: A wickedly curved blade, often seen in the hands of space pirates.
|
description: A wickedly curved blade, often seen in the hands of space pirates.
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
tags:
|
tags:
|
||||||
- Machete
|
- Machete
|
||||||
@@ -158,15 +161,13 @@
|
|||||||
- type: Item
|
- type: Item
|
||||||
size: Normal
|
size: Normal
|
||||||
sprite: Objects/Weapons/Melee/cutlass.rsi
|
sprite: Objects/Weapons/Melee/cutlass.rsi
|
||||||
- type: DisarmMalus
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: The Throngler
|
name: The Throngler
|
||||||
parent: BaseItem
|
parent: BaseSword
|
||||||
id: Throngler
|
id: Throngler
|
||||||
description: Why would you make this?
|
description: Why would you make this?
|
||||||
components:
|
components:
|
||||||
- type: Sharp
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Weapons/Melee/Throngler2.rsi
|
sprite: Objects/Weapons/Melee/Throngler2.rsi
|
||||||
state: icon
|
state: icon
|
||||||
@@ -190,4 +191,3 @@
|
|||||||
- type: Item
|
- type: Item
|
||||||
size: Ginormous
|
size: Ginormous
|
||||||
sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi
|
sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi
|
||||||
- type: DisarmMalus
|
|
||||||
|
|||||||
Reference in New Issue
Block a user