From d63eff97ef5e5eb660bb49a4a7a5eb8d6a4352be Mon Sep 17 00:00:00 2001 From: FN Date: Fri, 3 May 2024 19:28:11 +0700 Subject: [PATCH 1/5] Reapply Executions --- .../Projectiles/ProjectileSystem.cs | 29 ++- .../Weapons/Ranged/Systems/GunSystem.cs | 148 ++++++++++- Content.Shared/Execution/DoafterEvent.cs | 9 + .../Execution/ExecutionComponent.cs | 26 ++ Content.Shared/Execution/ExecutionSystem.cs | 240 ++++++++++++++++++ .../Projectiles/SharedProjectileSystem.cs | 6 + .../Ranged/Events/ShotAttemptedEvent.cs | 4 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 109 ++++++-- .../Locale/en-US/execution/execution.ftl | 20 ++ .../components/butcherable-component.ftl | 2 +- .../Objects/Materials/crystal_shard.yml | 2 + .../Entities/Objects/Materials/shards.yml | 2 + .../Entities/Objects/Misc/broken_bottle.yml | 2 + .../Objects/Weapons/Guns/HMGs/hmgs.yml | 1 + .../Objects/Weapons/Guns/LMGs/lmgs.yml | 1 + .../Weapons/Guns/Launchers/launchers.yml | 1 + .../Objects/Weapons/Guns/Pistols/pistols.yml | 1 + .../Objects/Weapons/Guns/Rifles/rifles.yml | 1 + .../Objects/Weapons/Guns/SMGs/smgs.yml | 1 + .../Weapons/Guns/Shotguns/shotguns.yml | 2 + .../Objects/Weapons/Guns/Snipers/snipers.yml | 1 + .../Objects/Weapons/Guns/flare_gun.yml | 2 +- .../Objects/Weapons/Guns/pneumatic_cannon.yml | 1 + .../Objects/Weapons/Melee/armblade.yml | 2 + .../Objects/Weapons/Melee/fireaxe.yml | 2 + .../Entities/Objects/Weapons/Melee/knife.yml | 2 + .../Entities/Objects/Weapons/Melee/sword.yml | 43 +++- 27 files changed, 611 insertions(+), 49 deletions(-) create mode 100644 Content.Shared/Execution/DoafterEvent.cs create mode 100644 Content.Shared/Execution/ExecutionComponent.cs create mode 100644 Content.Shared/Execution/ExecutionSystem.cs create mode 100644 Resources/Locale/en-US/execution/execution.ftl diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index c26f2447467..6e7e623df16 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -32,10 +32,14 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St { // This is so entities that shouldn't get a collision are ignored. if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard - || component.DamagedEntity || component is { Weapon: null, OnlyCollideWhenShot: true }) + || component.DamagedEntity || component is + { Weapon: null, OnlyCollideWhenShot: true }) + { return; + } var target = args.OtherEntity; + // it's here so this check is only done once before possible hit var attemptEv = new ProjectileReflectAttemptEvent(uid, component, false); RaiseLocalEvent(target, ref attemptEv); @@ -45,11 +49,26 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St return; } + if (TryHandleProjectile(target, (uid, component))) + { + var direction = args.OurBody.LinearVelocity.Normalized(); + _sharedCameraRecoil.KickCamera(target, direction); + } + } + + /// + /// Tries to handle a projectile interacting with the target. + /// + /// True if the target isn't deleted. + public bool TryHandleProjectile(EntityUid target, Entity projectile) + { + var uid = projectile.Owner; + var component = projectile.Comp; + var ev = new ProjectileHitEvent(component.Damage, target, component.Shooter); RaiseLocalEvent(uid, ref ev); var otherName = ToPrettyString(target); - var direction = args.OurBody.LinearVelocity.Normalized(); var modifiedDamage = _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: component.Shooter); var deleted = Deleted(target); @@ -68,9 +87,11 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St if (!deleted) { _guns.PlayImpactSound(target, modifiedDamage, component.SoundHit, component.ForceSound); - _sharedCameraRecoil.KickCamera(target, direction); } + var afterProjectileHitEvent = new AfterProjectileHitEvent(component.Damage, target); + RaiseLocalEvent(uid, ref afterProjectileHitEvent); + if (component.PenetrationScore > 0 && _penetratableQuery.TryGetComponent(target, out var penetratable)) { component.DamagedEntity = component.PenetrationScore < penetratable.StoppingPower; @@ -87,5 +108,7 @@ private void OnStartCollide(EntityUid uid, ProjectileComponent component, ref St { RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, GetNetCoordinates(xform.Coordinates)), Filter.Pvs(xform.Coordinates, entityMan: EntityManager)); } + + return !deleted; } } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index f5f4e3f1995..d24fd0ca6d6 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Cargo.Systems; using Content.Server.Interaction; using Content.Server.Power.EntitySystems; +using Content.Server.Projectiles; using Content.Server.Stunnable; using Content.Server.Weapons.Ranged.Components; using Content.Shared.Damage; @@ -34,6 +35,7 @@ public sealed partial class GunSystem : SharedGunSystem [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; + [Dependency] private readonly ProjectileSystem _projectile = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly StaminaSystem _stamina = default!; [Dependency] private readonly StunSystem _stun = default!; @@ -63,6 +65,137 @@ private void OnBallisticPrice(EntityUid uid, BallisticAmmoProviderComponent comp 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(ammo.Count); + var cartridgeBullets = new List(); + + 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() { 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, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, out bool userImpulse, EntityUid? user = null, bool throwItems = false) { @@ -70,7 +203,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? // Try a clumsy roll // TODO: Who put this here - if (TryComp(user, out var clumsy) && gun.ClumsyProof == false) + if (TryComp(user, out var clumsy) && !gun.ClumsyProof) { for (var i = 0; i < ammo.Count; i++) { @@ -91,6 +224,8 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? } } + // As the above message wasn't obvious stop putting stuff here and use events + var fromMap = fromCoordinates.ToMap(EntityManager, TransformSystem); var toMap = toCoordinates.ToMapPos(EntityManager, TransformSystem); var mapDirection = toMap - fromMap.Position; @@ -98,7 +233,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? 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 var grid) + var fromEnt = MapManager.TryFindGridAt(fromMap, out var gridUid, out _) ? fromCoordinates.WithEntityId(gridUid, EntityManager) : new EntityCoordinates(MapManager.GetMapEntityId(fromMap.MapId), fromMap.Position); @@ -110,6 +245,7 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? // I must be high because this was getting tripped even when true. // DebugTools.Assert(direction != Vector2.Zero); var shotProjectiles = new List(ammo.Count); + var cartridgeBullets = new List(); foreach (var (ent, shootable) in ammo) { @@ -138,21 +274,23 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? { var uid = Spawn(cartridge.Prototype, fromEnt); ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user); - shotProjectiles.Add(uid); + cartridgeBullets.Add(uid); } } else { var uid = Spawn(cartridge.Prototype, fromEnt); ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user); - shotProjectiles.Add(uid); + cartridgeBullets.Add(uid); } RaiseLocalEvent(ent!.Value, new AmmoShotEvent() { - FiredProjectiles = shotProjectiles, + FiredProjectiles = cartridgeBullets, }); + shotProjectiles.AddRange(cartridgeBullets); + cartridgeBullets.Clear(); SetCartridgeSpent(ent.Value, cartridge, true); MuzzleFlash(gunUid, cartridge, user); Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); diff --git a/Content.Shared/Execution/DoafterEvent.cs b/Content.Shared/Execution/DoafterEvent.cs new file mode 100644 index 00000000000..78549745276 --- /dev/null +++ b/Content.Shared/Execution/DoafterEvent.cs @@ -0,0 +1,9 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Execution; + +[Serializable, NetSerializable] +public sealed partial class ExecutionDoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/Execution/ExecutionComponent.cs b/Content.Shared/Execution/ExecutionComponent.cs new file mode 100644 index 00000000000..f9c5111d63a --- /dev/null +++ b/Content.Shared/Execution/ExecutionComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Execution; + +/// +/// Added to entities that can be used to execute another target. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExecutionComponent : Component +{ + /// + /// How long the execution duration lasts. + /// + [DataField, AutoNetworkedField] + public float DoAfterDuration = 5f; + + [DataField, AutoNetworkedField] + public float DamageModifier = 9f; + + // Not networked because this is transient inside of a tick. + /// + /// True if it is currently executing for handlers. + /// + [DataField] + public bool Executing = true; +} diff --git a/Content.Shared/Execution/ExecutionSystem.cs b/Content.Shared/Execution/ExecutionSystem.cs new file mode 100644 index 00000000000..278001a5d21 --- /dev/null +++ b/Content.Shared/Execution/ExecutionSystem.cs @@ -0,0 +1,240 @@ +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; + +/// +/// Verb for violently murdering cuffed creatures. +/// +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"; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent>(OnGetInteractionsVerbs); + SubscribeLocalEvent(OnExecutionDoAfter); + SubscribeLocalEvent(OnGetMeleeDamage); + } + + private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetVerbsEvent 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(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) + { + BreakOnMove = 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(victim, out _)) + return false; + + // You can't execute something that cannot die + if (!TryComp(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(uid, out var melee) || + !TryComp(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 + ); + } +} diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 372dc8a75d1..0316cb6af77 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -185,3 +185,9 @@ public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, Projectile /// [ByRefEvent] public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, EntityUid? Shooter = null); + +/// +/// Raised after a projectile has dealt it's damage. +/// +[ByRefEvent] +public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target); diff --git a/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs b/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs index 40925ad614c..6325d953300 100644 --- a/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/ShotAttemptedEvent.cs @@ -19,7 +19,7 @@ public record struct ShotAttemptedEvent public bool Cancelled { get; private set; } - /// + /// /// Prevent the gun from shooting /// public void Cancel() @@ -27,7 +27,7 @@ public void Cancel() Cancelled = true; } - /// + /// /// Allow the gun to shoot again, only use if you know what you are doing /// public void Uncancel() diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 1482854399e..cda46005a43 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -22,6 +22,7 @@ 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; @@ -139,7 +140,7 @@ private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args) gun.ShootCoordinates = GetCoordinates(msg.Coordinates); 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) @@ -203,13 +204,38 @@ private void StopShooting(EntityUid uid, GunComponent gun) Dirty(uid, gun); } + /// + /// Attempts to shoot the specified target directly. + /// This may bypass projectiles firing etc. + /// + 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; + } + /// /// Attempts to shoot at the target coordinates. Resets the shot counter after every shot. /// public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, EntityCoordinates toCoordinates) { gun.ShootCoordinates = toCoordinates; - AttemptShoot(user, gunUid, gun); + AttemptShootInternal(user, gunUid, gun); gun.ShotCounter = 0; } @@ -220,20 +246,35 @@ public void AttemptShoot(EntityUid gunUid, GunComponent gun) { var coordinates = new EntityCoordinates(gunUid, new Vector2(0, -1)); gun.ShootCoordinates = coordinates; - AttemptShoot(gunUid, gunUid, gun); + AttemptShootInternal(gunUid, gunUid, gun); gun.ShotCounter = 0; } - private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) + private void AttemptShootInternal(EntityUid user, EntityUid gunUid, GunComponent gun) { - if (gun.FireRateModified <= 0f || - !_actionBlockerSystem.CanAttack(user)) + if (!TryTakeAmmo(user, gunUid, gun, out var fromCoordinates, out var toCoordinates, out var args)) return; - var toCoordinates = gun.ShootCoordinates; + Shoot(gunUid, gun, args.Ammo, fromCoordinates, toCoordinates, out var userImpulse, user: user); - if (toCoordinates == null) - return; + if (userImpulse && TryComp(user, out var userPhysics)) + { + if (_gravity.IsWeightless(user, userPhysics)) + CauseImpulse(fromCoordinates, toCoordinates, user, userPhysics); + } + } + + /// + /// Validates if a gun can currently shoot. + /// + [Pure] + private bool CanShoot(EntityUid user, EntityUid gunUid, GunComponent gun) + { + if (gun.FireRateModified <= 0f || + !_actionBlockerSystem.CanAttack(user)) + { + return false; + } var curTime = Timing.CurTime; @@ -245,17 +286,42 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) }; RaiseLocalEvent(gunUid, ref prevention); if (prevention.Cancelled) - return; + return false; RaiseLocalEvent(user, ref prevention); if (prevention.Cancelled) - return; + return false; // 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; + return false; + + return true; + } + + /// + /// Tries to return ammo prepped for shooting if a gun is available to shoot. + /// + 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 @@ -303,10 +369,11 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) } 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 var ev = new TakeAmmoEvent(shots, new List<(EntityUid? Entity, IShootable Shootable)>(), fromCoordinates, user); @@ -341,24 +408,18 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) // 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; + return false; } - return; + return false; } // 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); - if (userImpulse && TryComp(user, out var userPhysics)) - { - if (_gravity.IsWeightless(user, userPhysics)) - CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics); - } - - Dirty(gunUid, gun); + args = ev; + return true; } public void Shoot( diff --git a/Resources/Locale/en-US/execution/execution.ftl b/Resources/Locale/en-US/execution/execution.ftl new file mode 100644 index 00000000000..5bd4613e8c3 --- /dev/null +++ b/Resources/Locale/en-US/execution/execution.ftl @@ -0,0 +1,20 @@ +execution-verb-name = Execute +execution-verb-message = Use your weapon to execute someone. + +# All the below localisation strings have access to the following variables +# attacker (the person committing the execution) +# victim (the person being executed) +# weapon (the weapon used for the execution) + +execution-popup-gun-initial-internal = You ready the muzzle of {THE($weapon)} against {$victim}'s head. +execution-popup-gun-initial-external = {$attacker} readies the muzzle of {THE($weapon)} against {$victim}'s head. +execution-popup-gun-complete-internal = You blast {$victim} in the head! +execution-popup-gun-complete-external = {$attacker} blasts {$victim} in the head! +execution-popup-gun-clumsy-internal = You miss {$victim}'s head and shoot your 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-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-complete-internal = You slit the throat of {$victim}! +execution-popup-melee-complete-external = {$attacker} slits the throat of {$victim}! diff --git a/Resources/Locale/en-US/kitchen/components/butcherable-component.ftl b/Resources/Locale/en-US/kitchen/components/butcherable-component.ftl index ff28cc44db6..4a83cd455d7 100644 --- a/Resources/Locale/en-US/kitchen/components/butcherable-component.ftl +++ b/Resources/Locale/en-US/kitchen/components/butcherable-component.ftl @@ -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-need-knife = Use a sharp object to butcher { THE($target) }. butcherable-not-in-container = { CAPITALIZE(THE($target)) } can't be in a container. diff --git a/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml b/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml index 8f522abce42..47828ed8f58 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/crystal_shard.yml @@ -6,6 +6,8 @@ description: A small piece of crystal. components: - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: Sprite layers: - sprite: Objects/Materials/Shards/crystal.rsi diff --git a/Resources/Prototypes/Entities/Objects/Materials/shards.yml b/Resources/Prototypes/Entities/Objects/Materials/shards.yml index 621a3b1c389..57777240a3c 100644 --- a/Resources/Prototypes/Entities/Objects/Materials/shards.yml +++ b/Resources/Prototypes/Entities/Objects/Materials/shards.yml @@ -5,6 +5,8 @@ description: It's a shard of some unknown material. components: - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: Sprite layers: - sprite: Objects/Materials/Shards/shard.rsi diff --git a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml index 4abd31738ee..32af3100f2a 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/broken_bottle.yml @@ -5,6 +5,8 @@ description: In Space Glasgow this is called a conversation starter. components: - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: MeleeWeapon attackRate: 1.5 damage: diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml index 42e5b7ee554..a73a42728bc 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/HMGs/hmgs.yml @@ -19,6 +19,7 @@ path: /Audio/Weapons/Guns/Empty/lmg_empty.ogg - type: StaticPrice price: 500 + - type: Execution # No chamber because HMG may want its own - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml index d23bdfbdc9e..71f03f3e02e 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/LMGs/lmgs.yml @@ -60,6 +60,7 @@ price: 500 - type: UseDelay delay: 1 + - type: Execution - type: entity name: L6 SAW diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml index 9f94ed2c52b..baf16f15598 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml @@ -19,6 +19,7 @@ containers: ballistic-ammo: !type:Container ents: [] + - type: Execution - type: entity name: china lake diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml index 414540561d2..90b61460d26 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml @@ -65,6 +65,7 @@ - type: Appearance - type: StaticPrice price: 500 + - type: Execution - type: entity name: viper diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml index 0e7f1a62272..8ac7f048c5f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml @@ -49,6 +49,7 @@ gun_chamber: !type:ContainerSlot - type: StaticPrice price: 500 + - type: Execution - type: entity name: AKMS diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml index 696162e65c3..a00fb2df93f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml @@ -54,6 +54,7 @@ gun_chamber: !type:ContainerSlot - type: StaticPrice price: 500 + - type: Execution - type: entity name: Atreides diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml index 62229713fde..f70e08dc55c 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml @@ -42,6 +42,7 @@ ents: [] - type: StaticPrice price: 500 + - type: Execution - type: entity name: Bulldog @@ -99,6 +100,7 @@ - type: StaticPrice price: 500 - type: Contraband #frontier + - type: Execution - type: entity name: double-barreled shotgun diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml index a45924fd4c5..826cf9ca13a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml @@ -37,6 +37,7 @@ ents: [] - type: StaticPrice price: 500 + - type: Execution - type: entity name: Kardashev-Mosin diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/flare_gun.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/flare_gun.yml index a551b3d0794..fc8f4d184b0 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/flare_gun.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/flare_gun.yml @@ -37,9 +37,9 @@ slots: - Belt - suitStorage - - type: StaticPrice #Dunno if I should be doing this outside of the _NF file price: 150 - type: Tag # Frontier tags: # Frontier - Sidearm # Frontier + - type: Execution diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml index ae1f5df3c15..add776422db 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/pneumatic_cannon.yml @@ -107,6 +107,7 @@ containers: storagebase: !type:Container ents: [] + - type: Execution # shoots bullets instead of throwing them, no other changes - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml index 9e1134195d5..a20ae207b08 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml @@ -5,6 +5,8 @@ description: A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter. components: - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: Sprite sprite: Objects/Weapons/Melee/armblade.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml index 857a48351a6..6589088f7ac 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/fireaxe.yml @@ -8,6 +8,8 @@ tags: - FireAxe - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: Sprite sprite: Objects/Weapons/Melee/fireaxe.rsi state: icon diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml index 9cd1bb29408..56bb4c39e27 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/knife.yml @@ -7,6 +7,8 @@ tags: - Knife - type: Sharp + - type: Execution + doAfterDuration: 4.0 - type: Utensil types: - Knife diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 3e8ea4b4d46..1af1aa278d2 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -1,10 +1,21 @@ - type: entity - name: captain's sabre + name: Sword parent: BaseItem + id: BaseSword + description: A sharp sword. + abstract: true + components: + - type: Sharp + - type: Execution + doAfterDuration: 4.0 + - type: DisarmMalus + +- type: entity + name: captain's sabre + parent: BaseSword id: CaptainSabre description: A ceremonial weapon belonging to the captain of the station. components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/captain_sabre.rsi state: icon @@ -35,11 +46,10 @@ - type: entity name: katana - parent: BaseItem + parent: BaseSword id: Katana description: Ancient craftwork made with not so ancient plasteel. components: - - type: Sharp - type: Tag tags: - Katana @@ -56,12 +66,15 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/katana.rsi +<<<<<<< HEAD - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage +======= +>>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: energy katana @@ -96,11 +109,10 @@ - type: entity name: machete - parent: BaseItem + parent: BaseSword id: Machete description: A large, vicious looking blade. components: - - type: Sharp - type: Tag tags: - Machete @@ -117,20 +129,22 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/machete.rsi +<<<<<<< HEAD - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage +======= +>>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: claymore - parent: BaseItem + parent: BaseSword id: Claymore description: An ancient war blade. components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/claymore.rsi state: icon @@ -148,16 +162,18 @@ sprite: Objects/Weapons/Melee/claymore.rsi slots: - back +<<<<<<< HEAD - suitStorage - type: DisarmMalus +======= +>>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: cutlass - parent: BaseItem + parent: BaseSword id: Cutlass description: A wickedly curved blade, often seen in the hands of space pirates. components: - - type: Sharp - type: Tag tags: - Machete @@ -174,20 +190,22 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/cutlass.rsi +<<<<<<< HEAD - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage +======= +>>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: The Throngler - parent: BaseItem + parent: BaseSword id: Throngler description: Why would you make this? components: - - type: Sharp - type: Sprite sprite: Objects/Weapons/Melee/Throngler2.rsi state: icon @@ -211,4 +229,3 @@ - type: Item size: Ginormous sprite: Objects/Weapons/Melee/Throngler-in-hand.rsi - - type: DisarmMalus From 354003d7f1d451f1d933b8ccc8466deed5efdf6e Mon Sep 17 00:00:00 2001 From: FN Date: Fri, 3 May 2024 19:50:43 +0700 Subject: [PATCH 2/5] Fix swords --- .../Entities/Objects/Weapons/Melee/sword.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index 1af1aa278d2..73508ebdea7 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -66,15 +66,11 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/katana.rsi -<<<<<<< HEAD - - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage -======= ->>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: energy katana @@ -129,15 +125,11 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/machete.rsi -<<<<<<< HEAD - - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage -======= ->>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: claymore @@ -162,11 +154,7 @@ sprite: Objects/Weapons/Melee/claymore.rsi slots: - back -<<<<<<< HEAD - suitStorage - - type: DisarmMalus -======= ->>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: cutlass @@ -190,15 +178,11 @@ - type: Item size: Normal sprite: Objects/Weapons/Melee/cutlass.rsi -<<<<<<< HEAD - - type: DisarmMalus - type: Clothing quickEquip: false slots: - back - suitStorage -======= ->>>>>>> parent of bb0776c496 (Revert "Cleanup ExecutionSystem (#24382)" (#25555)) - type: entity name: The Throngler From 1f7c63b82ed14d395d1cdf46a8d28923feb6fbab Mon Sep 17 00:00:00 2001 From: FN Date: Fri, 3 May 2024 21:47:31 +0700 Subject: [PATCH 3/5] Completed task --- .../Weapons/Ranged/Systems/GunSystem.cs | 2 ++ Content.Shared/Execution/ExecutionSystem.cs | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index d24fd0ca6d6..e27abe6dcec 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -183,6 +183,8 @@ protected override bool ShootDirect(EntityUid gunUid, GunComponent gun, EntityUi continue; } + projectileComponent.Weapon = gunUid; + _projectile.TryHandleProjectile(target, (ammoUid, projectileComponent)); // Even this deletion handling is mega sussy. Del(ammoUid); diff --git a/Content.Shared/Execution/ExecutionSystem.cs b/Content.Shared/Execution/ExecutionSystem.cs index 278001a5d21..df6f38e2a90 100644 --- a/Content.Shared/Execution/ExecutionSystem.cs +++ b/Content.Shared/Execution/ExecutionSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Weapons.Ranged.Components; using Robust.Shared.Network; using Robust.Shared.Player; +using Content.Shared.Projectiles; namespace Content.Shared.Execution; @@ -47,6 +48,7 @@ public override void Initialize() SubscribeLocalEvent>(OnGetInteractionsVerbs); SubscribeLocalEvent(OnExecutionDoAfter); SubscribeLocalEvent(OnGetMeleeDamage); + SubscribeLocalEvent(OnProjectileHit); } private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetVerbsEvent args) @@ -54,6 +56,9 @@ private void OnGetInteractionsVerbs(EntityUid uid, ExecutionComponent comp, GetV if (args.Hands == null || args.Using == null || !args.CanAccess || !args.CanInteract) return; + if (!HasComp(args.Using)) + return; + var attacker = args.User; var weapon = args.Using.Value; var victim = args.Target; @@ -107,8 +112,8 @@ private void TryStartExecutionDoAfter(EntityUid weapon, EntityUid victim, Entity private bool CanExecuteWithAny(EntityUid victim, EntityUid attacker) { // Use suicide. - if (victim == attacker) - return false; + //if (victim == attacker) + // return false; // No point executing someone if they can't take damage if (!TryComp(victim, out _)) @@ -202,6 +207,15 @@ private void OnGetMeleeDamage(EntityUid uid, ExecutionComponent comp, ref GetMel args.Damage += bonus; } + private void OnProjectileHit(EntityUid entity, ProjectileComponent component, ref ProjectileHitEvent e) + { + if (!TryComp(component.Weapon, out var execution) || !execution.Executing) + return; + + var bonus = component.Damage * execution.DamageModifier - component.Damage; + e.Damage += bonus; + } + private void ShowExecutionInternalPopup(string locString, EntityUid attacker, EntityUid victim, EntityUid weapon, bool predict = true) { From e335e38b47b0950c4c3e1361f878eaeadc31f4bc Mon Sep 17 00:00:00 2001 From: Zekins <136648667+Zekins3366@users.noreply.github.com> Date: Fri, 3 May 2024 22:53:19 +0300 Subject: [PATCH 4/5] Create execution.ftl --- Resources/Locale/ru-RU/execution/execution.ftl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Resources/Locale/ru-RU/execution/execution.ftl diff --git a/Resources/Locale/ru-RU/execution/execution.ftl b/Resources/Locale/ru-RU/execution/execution.ftl new file mode 100644 index 00000000000..c55fcfb1622 --- /dev/null +++ b/Resources/Locale/ru-RU/execution/execution.ftl @@ -0,0 +1,18 @@ +execution-verb-name = Казнить +execution-verb-message = Использовать своё оружие, чтобы казнить кого-то. +# All the below localisation strings have access to the following variables +# attacker (the person committing the execution) +# victim (the person being executed) +# weapon (the weapon used for the execution) + +execution-popup-gun-initial-internal = Вы приставляете ствол {THE($weapon)}к голове {$victim}. +execution-popup-gun-initial-external = {$attacker} readies the muzzle of {THE($weapon)} against {$victim}'s head. +execution-popup-gun-complete-internal = Вы стреляете {$victim} в голову! +execution-popup-gun-complete-external = {$attacker} стреляет {$victim} в голову! +execution-popup-gun-clumsy-internal = Вы промахиваетесь мимо головы {$victim} и стреляете себе в ногу! +execution-popup-gun-clumsy-external = {$attacker} промахивается по {$victim} и стреляет себе в ногу! +execution-popup-gun-empty = {CAPITALIZE(THE($weapon))} издаёт щелчок. +execution-popup-melee-initial-internal = Вы вставляете {THE($weapon)} в рот {$victim}. +execution-popup-melee-initial-external = {$attacker} вставляет {$weapon} в рот {$victim}. +execution-popup-melee-complete-internal = Вы перерезали горло {$victim}! +execution-popup-melee-complete-external = {$attacker} перерзает горло {$victim}! From 6c110cabe2a3c0c90d337b7a743ddff84b0bca32 Mon Sep 17 00:00:00 2001 From: Zekins <136648667+Zekins3366@users.noreply.github.com> Date: Fri, 3 May 2024 22:59:46 +0300 Subject: [PATCH 5/5] mini fix translate --- Resources/Locale/ru-RU/execution/execution.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/Locale/ru-RU/execution/execution.ftl b/Resources/Locale/ru-RU/execution/execution.ftl index c55fcfb1622..37eff6cc195 100644 --- a/Resources/Locale/ru-RU/execution/execution.ftl +++ b/Resources/Locale/ru-RU/execution/execution.ftl @@ -6,7 +6,7 @@ execution-verb-message = Использовать своё оружие, что # weapon (the weapon used for the execution) execution-popup-gun-initial-internal = Вы приставляете ствол {THE($weapon)}к голове {$victim}. -execution-popup-gun-initial-external = {$attacker} readies the muzzle of {THE($weapon)} against {$victim}'s head. +execution-popup-gun-initial-external = {$attacker} приставляет ствол {THE($weapon)} к голове {$victim}. execution-popup-gun-complete-internal = Вы стреляете {$victim} в голову! execution-popup-gun-complete-external = {$attacker} стреляет {$victim} в голову! execution-popup-gun-clumsy-internal = Вы промахиваетесь мимо головы {$victim} и стреляете себе в ногу!