diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index 710ee7c7cbf..6ef0bae741d 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -5,6 +5,7 @@ using Content.Client.Weapons.Ranged.Components; using Content.Shared.Camera; using Content.Shared.CombatMode; +using Content.Shared.Mech.Components; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -150,6 +151,11 @@ public override void Update(float frameTime) var entity = entityNull.Value; + if (TryComp(entity, out var mechPilot)) + { + entity = mechPilot.Mech; + } + if (!TryGetGun(entity, out var gunUid, out var gun)) { return; @@ -157,7 +163,7 @@ public override void Update(float frameTime) var useKey = gun.UseKey ? EngineKeyFunctions.Use : EngineKeyFunctions.UseSecondary; - if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down && !gun.BurstActivated) + if (_inputSystem.CmdStates.GetState(useKey) != BoundKeyState.Down) { if (gun.ShotCounter != 0) EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = GetNetEntity(gunUid) }); diff --git a/Content.Server/Mech/Equipment/EntitySystems/MechGunSystem.cs b/Content.Server/Mech/Equipment/EntitySystems/MechGunSystem.cs new file mode 100644 index 00000000000..8ed55e7462a --- /dev/null +++ b/Content.Server/Mech/Equipment/EntitySystems/MechGunSystem.cs @@ -0,0 +1,61 @@ +using Content.Server.Mech.Systems; +using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Shared.Mech.Components; +using Content.Shared.Mech.Equipment.Components; +using Content.Shared.Throwing; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Random; + +namespace Content.Server.Mech.Equipment.EntitySystems; +public sealed class MechGunSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ThrowingSystem _throwing = default!; + [Dependency] private readonly MechSystem _mech = default!; + [Dependency] private readonly BatterySystem _battery = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(MechGunShot); + } + + private void MechGunShot(EntityUid uid, MechEquipmentComponent component, ref GunShotEvent args) + { + if (!component.EquipmentOwner.HasValue) + return; + + if (!TryComp(component.EquipmentOwner.Value, out var mech)) + return; + + if (TryComp(uid, out var battery)) + { + ChargeGunBattery(uid, battery); + return; + } + } + + private void ChargeGunBattery(EntityUid uid, BatteryComponent component) + { + if (!TryComp(uid, out var mechEquipment) || !mechEquipment.EquipmentOwner.HasValue) + return; + + if (!TryComp(mechEquipment.EquipmentOwner.Value, out var mech)) + return; + + var maxCharge = component.MaxCharge; + var currentCharge = component.CurrentCharge; + + var chargeDelta = maxCharge - currentCharge; + + // TODO: The battery charge of the mech would be spent directly when fired. + if (chargeDelta <= 0 || mech.Energy - chargeDelta < 0) + return; + + if (!_mech.TryChangeEnergy(mechEquipment.EquipmentOwner.Value, -chargeDelta, mech)) + return; + + _battery.SetCharge(uid, component.MaxCharge, component); + } +} \ No newline at end of file diff --git a/Content.Server/Mech/Systems/MechSystem.cs b/Content.Server/Mech/Systems/MechSystem.cs index 9da96a76f8e..3a1b99bbb5b 100644 --- a/Content.Server/Mech/Systems/MechSystem.cs +++ b/Content.Server/Mech/Systems/MechSystem.cs @@ -18,6 +18,8 @@ using Content.Shared.Wires; using Content.Server.Body.Systems; using Content.Shared.Tools.Systems; +using Content.Shared.Hands.Components; +using Content.Shared.Hands.EntitySystems; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Containers; @@ -39,6 +41,7 @@ public sealed partial class MechSystem : SharedMechSystem [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly SharedToolSystem _toolSystem = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; /// public override void Initialize() @@ -234,6 +237,14 @@ private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User); return; } + + if (!TryComp(args.Args.User, out var handsComponent)) + return; + + foreach (var hand in _hands.EnumerateHands(args.Args.User, handsComponent)) + { + _hands.DoDrop(args.Args.User, hand, true, handsComponent); + } TryInsert(uid, args.Args.User, component); _actionBlocker.UpdateCanMove(uid); diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index d22b5ec2af7..781a7b11fe9 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -1,12 +1,17 @@ using System.Linq; using System.Numerics; using Content.Server.Cargo.Systems; +using Content.Server.Interaction; +using Content.Server.Mech.Equipment.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Stunnable; using Content.Server.Weapons.Ranged.Components; using Content.Shared.Damage; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.Effects; +using Content.Shared.Interaction.Components; +using Content.Shared.Mech.Equipment.Components; using Content.Shared.Projectiles; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Ranged; @@ -30,13 +35,16 @@ public sealed partial class GunSystem : SharedGunSystem [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly DamageExamineSystem _damageExamine = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly PricingSystem _pricing = default!; [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly StaminaSystem _stamina = default!; + [Dependency] private readonly StunSystem _stun = default!; [Dependency] private readonly SharedContainerSystem _container = default!; private const float DamagePitchVariation = 0.05f; + public const float GunClumsyChance = 0.5f; public override void Initialize() { @@ -65,14 +73,26 @@ public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? { userImpulse = true; - if (user != null) + // Try a clumsy roll + // TODO: Who put this here + if (TryComp(user, out var clumsy) && gun.ClumsyProof == false) { - var selfEvent = new SelfBeforeGunShotEvent(user.Value, (gunUid, gun), ammo); - RaiseLocalEvent(user.Value, selfEvent); - if (selfEvent.Cancelled) + for (var i = 0; i < ammo.Count; i++) { - userImpulse = false; - return; + if (_interaction.TryRollClumsy(user.Value, GunClumsyChance, clumsy)) + { + // Wound them + Damageable.TryChangeDamage(user, clumsy.ClumsyDamage, origin: user); + _stun.TryParalyze(user.Value, TimeSpan.FromSeconds(3f), true); + + // Apply salt to the wound ("Honk!") + Audio.PlayPvs(new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/bang.ogg"), gunUid); + Audio.PlayPvs(clumsy.ClumsySound, gunUid); + + PopupSystem.PopupEntity(Loc.GetString("gun-clumsy"), user.Value); + userImpulse = false; + return; + } } } diff --git a/Content.Shared/Mech/Components/MechComponent.cs b/Content.Shared/Mech/Components/MechComponent.cs index ba380492bc9..ce7026796b8 100644 --- a/Content.Shared/Mech/Components/MechComponent.cs +++ b/Content.Shared/Mech/Components/MechComponent.cs @@ -13,6 +13,13 @@ namespace Content.Shared.Mech.Components; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class MechComponent : Component { + /// + /// Whether or not an emag disables it. + /// + [DataField("breakOnEmag")] + [AutoNetworkedField] + public bool BreakOnEmag = true; + /// /// How much "health" the mech has left. /// diff --git a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs b/Content.Shared/Mech/EntitySystem/SharedMechSystem.cs similarity index 97% rename from Content.Shared/Mech/EntitySystems/SharedMechSystem.cs rename to Content.Shared/Mech/EntitySystem/SharedMechSystem.cs index 2ec48085c47..97242973152 100644 --- a/Content.Shared/Mech/EntitySystems/SharedMechSystem.cs +++ b/Content.Shared/Mech/EntitySystem/SharedMechSystem.cs @@ -5,6 +5,8 @@ using Content.Shared.Destructible; using Content.Shared.DoAfter; using Content.Shared.DragDrop; +using Content.Shared.Emag.Components; +using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Content.Shared.Interaction.Components; @@ -15,6 +17,7 @@ using Content.Shared.Movement.Systems; using Content.Shared.Popups; using Content.Shared.Weapons.Melee; +using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Network; @@ -51,6 +54,7 @@ public override void Initialize() SubscribeLocalEvent(OnGetAdditionalAccess); SubscribeLocalEvent(OnDragDrop); SubscribeLocalEvent(OnCanDragDrop); + SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnGetMeleeWeapon); SubscribeLocalEvent(OnCanAttackFromContainer); @@ -449,6 +453,14 @@ private void OnCanDragDrop(EntityUid uid, MechComponent component, ref CanDropTa args.CanDrop |= !component.Broken && CanInsert(uid, args.Dragged, component); } + private void OnEmagged(EntityUid uid, MechComponent component, ref GotEmaggedEvent args) + { + if (!component.BreakOnEmag) + return; + args.Handled = true; + component.EquipmentWhitelist = null; + Dirty(uid, component); + } } /// diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs new file mode 100644 index 00000000000..99d499fd8d3 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -0,0 +1,282 @@ +using System.Numerics; +using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Shared.Weapons.Ranged.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[Access(typeof(SharedGunSystem))] +public sealed partial class GunComponent : Component +{ + #region Sound + + /// + /// The base sound to use when the gun is fired. + /// + [DataField] + public SoundSpecifier? SoundGunshot = new SoundPathSpecifier("/Audio/Weapons/Guns/Gunshots/smg.ogg"); + + /// + /// The sound to use when the gun is fired. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public SoundSpecifier? SoundGunshotModified; + + [DataField] + public SoundSpecifier? SoundEmpty = new SoundPathSpecifier("/Audio/Weapons/Guns/Empty/empty.ogg"); + + /// + /// Sound played when toggling the for this gun. + /// + [DataField] + public SoundSpecifier? SoundMode = new SoundPathSpecifier("/Audio/Weapons/Guns/Misc/selector.ogg"); + + #endregion + + #region Recoil + + // These values are very small for now until we get a debug overlay and fine tune it + + /// + /// The base scalar value applied to the vector governing camera recoil. + /// + [DataField, AutoNetworkedField] + public float CameraRecoilScalar = 1f; + + /// + /// A scalar value applied to the vector governing camera recoil. + /// If 0, there will be no camera recoil. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float CameraRecoilScalarModified = 1f; + + /// + /// Last time the gun fired. + /// Used for recoil purposes. + /// + [DataField] + public TimeSpan LastFire = TimeSpan.Zero; + + /// + /// What the current spread is for shooting. This gets changed every time the gun fires. + /// + [DataField] + [AutoNetworkedField] + public Angle CurrentAngle; + + /// + /// The base value for how much the spread increases every time the gun fires. + /// + [DataField] + public Angle AngleIncrease = Angle.FromDegrees(0.5); + + /// + /// How much the spread increases every time the gun fires. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public Angle AngleIncreaseModified; + + /// + /// The base value for how much the decreases per second. + /// + [DataField] + public Angle AngleDecay = Angle.FromDegrees(4); + + /// + /// How much the decreases per second. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public Angle AngleDecayModified; + + /// + /// The base value for the maximum angle allowed for + /// + [DataField] + [AutoNetworkedField] + public Angle MaxAngle = Angle.FromDegrees(2); + + /// + /// The maximum angle allowed for + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public Angle MaxAngleModified; + + /// + /// The base value for the minimum angle allowed for + /// + [DataField] + [AutoNetworkedField] + public Angle MinAngle = Angle.FromDegrees(1); + + /// + /// The minimum angle allowed for . + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public Angle MinAngleModified; + + #endregion + + /// + /// Whether this gun is shot via the use key or the alt-use key. + /// + [DataField, AutoNetworkedField] + public bool UseKey = true; + + /// + /// Where the gun is being requested to shoot. + /// + [ViewVariables] + public EntityCoordinates? ShootCoordinates = null; + + /// + /// Who the gun is being requested to shoot at directly. + /// + [ViewVariables] + public EntityUid? Target = null; + + /// + /// The base value for how many shots to fire per burst. + /// + [DataField, AutoNetworkedField] + public int ShotsPerBurst = 3; + + /// + /// How many shots to fire per burst. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public int ShotsPerBurstModified = 3; + + /// + /// How long time must pass between burstfire shots. + /// + [DataField, AutoNetworkedField] + public float BurstCooldown = 0.25f; + + /// + /// The fire rate of the weapon in burst fire mode. + /// + [DataField, AutoNetworkedField] + public float BurstFireRate = 8f; + + /// + /// Whether the burst fire mode has been activated. + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public bool BurstActivated = false; + + /// + /// The burst fire bullet count. + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public int BurstShotsCount = 0; + + /// + /// Used for tracking semi-auto / burst + /// + [ViewVariables] + [AutoNetworkedField] + public int ShotCounter = 0; + + /// + /// The base value for how many times it shoots per second. + /// + [DataField] + [AutoNetworkedField] + public float FireRate = 8f; + + /// + /// How many times it shoots per second. + /// + /// + [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float FireRateModified; + + /// + /// Starts fire cooldown when equipped if true. + /// + [DataField] + public bool ResetOnHandSelected = true; + + /// + /// The base value for how fast the projectile moves. + /// + [DataField] + public float ProjectileSpeed = 25f; + + /// + /// How fast the projectile moves. + /// + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float ProjectileSpeedModified; + + /// + /// When the gun is next available to be shot. + /// Can be set multiple times in a single tick due to guns firing faster than a single tick time. + /// + [DataField(customTypeSerializer:typeof(TimeOffsetSerializer))] + [AutoNetworkedField] + [AutoPausedField] + public TimeSpan NextFire = TimeSpan.Zero; + + /// + /// What firemodes can be selected. + /// + [DataField] + [AutoNetworkedField] + public SelectiveFire AvailableModes = SelectiveFire.SemiAuto; + + /// + /// What firemode is currently selected. + /// + [DataField] + [AutoNetworkedField] + public SelectiveFire SelectedMode = SelectiveFire.SemiAuto; + + /// + /// Whether or not information about + /// the gun will be shown on examine. + /// + [DataField] + public bool ShowExamineText = true; + + /// + /// Whether or not someone with the + /// clumsy trait can shoot this + /// + [DataField] + public bool ClumsyProof = false; + + /// + /// Firing direction for an item not being held (e.g. shuttle cannons, thrown guns still firing). + /// + [DataField] + public Vector2 DefaultDirection = new Vector2(0, -1); + + /// + /// Corvax-Next. The percentage chance of a given gun to accidentally discharge if violently thrown into a wall or person + /// + [DataField] + public float FireOnDropChance = 0.1f; +} + +[Flags] +public enum SelectiveFire : byte +{ + Invalid = 0, + // Combat mode already functions as the equivalent of Safety + SemiAuto = 1 << 0, + Burst = 1 << 1, + FullAuto = 1 << 2, // Not in the building! +} diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 0ab3f1e9989..5ef4a5a3cbf 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Gravity; using Content.Shared.Hands; using Content.Shared.Hands.Components; +using Content.Shared.Mech.Components; using Content.Shared.Popups; using Content.Shared.Projectiles; using Content.Shared.Tag; @@ -127,12 +128,14 @@ private void OnShootRequest(RequestShootEvent msg, EntitySessionEventArgs args) { var user = args.SenderSession.AttachedEntity; - if (user == null || - !_combatMode.IsInCombatMode(user) || - !TryGetGun(user.Value, out var ent, out var gun)) - { + if (user == null || !_combatMode.IsInCombatMode(user)) + return; + + if (TryComp(user.Value, out var mechPilot)) + user = mechPilot.Mech; + + if (!TryGetGun(user.Value, out var ent, out var gun)) return; - } if (ent != GetEntity(msg.Gun)) return; @@ -146,14 +149,18 @@ private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs { var gunUid = GetEntity(ev.Gun); - if (args.SenderSession.AttachedEntity == null || - !TryComp(gunUid, out var gun) || - !TryGetGun(args.SenderSession.AttachedEntity.Value, out _, out var userGun)) - { + var user = args.SenderSession.AttachedEntity; + + if (user == null) + return; + + if (TryComp(user.Value, out var mechPilot)) + user = mechPilot.Mech; + + if (!TryGetGun(user.Value, out var ent, out var gun)) return; - } - if (userGun != gun) + if (ent != gunUid) return; StopShooting(gunUid, gun); @@ -172,6 +179,15 @@ public bool TryGetGun(EntityUid entity, out EntityUid gunEntity, [NotNullWhen(tr gunEntity = default; gunComp = null; + if (TryComp(entity, out var mech) && + mech.CurrentSelectedEquipment.HasValue && + TryComp(mech.CurrentSelectedEquipment.Value, out var mechGun)) + { + gunEntity = mech.CurrentSelectedEquipment.Value; + gunComp = mechGun; + return true; + } + if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) && hands.ActiveHandEntity is { } held && TryComp(held, out GunComponent? gun)) @@ -218,7 +234,7 @@ public void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun, Ent /// public void AttemptShoot(EntityUid gunUid, GunComponent gun) { - var coordinates = new EntityCoordinates(gunUid, gun.DefaultDirection); + var coordinates = new EntityCoordinates(gunUid, new Vector2(0, -1)); gun.ShootCoordinates = coordinates; AttemptShoot(gunUid, gunUid, gun); gun.ShotCounter = 0; @@ -266,9 +282,6 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) var fireRate = TimeSpan.FromSeconds(1f / gun.FireRateModified); - if (gun.SelectedMode == SelectiveFire.Burst || gun.BurstActivated) - fireRate = TimeSpan.FromSeconds(1f / gun.BurstFireRate); - // First shot // Previously we checked shotcounter but in some cases all the bullets got dumped at once // curTime - fireRate is insufficient because if you time it just right you can get a 3rd shot out slightly quicker. @@ -278,8 +291,11 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) var shots = 0; var lastFire = gun.NextFire; + Log.Debug($"Nextfire={gun.NextFire} curTime={curTime}"); + while (gun.NextFire <= curTime) { + Log.Debug("Shots++"); gun.NextFire += fireRate; shots++; } @@ -289,26 +305,22 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) // Get how many shots we're actually allowed to make, due to clip size or otherwise. // Don't do this in the loop so we still reset NextFire. - if (!gun.BurstActivated) - { - switch (gun.SelectedMode) - { - case SelectiveFire.SemiAuto: - shots = Math.Min(shots, 1 - gun.ShotCounter); - break; - case SelectiveFire.Burst: - shots = Math.Min(shots, gun.ShotsPerBurstModified - gun.ShotCounter); - break; - case SelectiveFire.FullAuto: - break; - default: - throw new ArgumentOutOfRangeException($"No implemented shooting behavior for {gun.SelectedMode}!"); - } - } else + switch (gun.SelectedMode) { - shots = Math.Min(shots, gun.ShotsPerBurstModified - gun.ShotCounter); + case SelectiveFire.SemiAuto: + shots = Math.Min(shots, 1 - gun.ShotCounter); + break; + case SelectiveFire.Burst: + shots = Math.Min(shots, gun.ShotsPerBurstModified - gun.ShotCounter); + break; + case SelectiveFire.FullAuto: + break; + default: + throw new ArgumentOutOfRangeException($"No implemented shooting behavior for {gun.SelectedMode}!"); } + Log.Debug($"Shots fired: {shots}"); + var attemptEv = new AttemptShootEvent(user, null); RaiseLocalEvent(gunUid, ref attemptEv); @@ -318,8 +330,7 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) { PopupSystem.PopupClient(attemptEv.Message, gunUid, user); } - gun.BurstActivated = false; - gun.BurstShotsCount = 0; + gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds)); return; } @@ -346,10 +357,6 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) var emptyGunShotEvent = new OnEmptyGunShotEvent(); RaiseLocalEvent(gunUid, ref emptyGunShotEvent); - gun.BurstActivated = false; - gun.BurstShotsCount = 0; - gun.NextFire += TimeSpan.FromSeconds(gun.BurstCooldown); - // Play empty gun sounds if relevant // If they're firing an existing clip then don't play anything. if (shots > 0) @@ -369,22 +376,6 @@ private void AttemptShoot(EntityUid user, EntityUid gunUid, GunComponent gun) return; } - // Handle burstfire - if (gun.SelectedMode == SelectiveFire.Burst) - { - gun.BurstActivated = true; - } - if (gun.BurstActivated) - { - gun.BurstShotsCount += shots; - if (gun.BurstShotsCount >= gun.ShotsPerBurstModified) - { - gun.NextFire += TimeSpan.FromSeconds(gun.BurstCooldown); - gun.BurstActivated = false; - gun.BurstShotsCount = 0; - } - } - // 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); @@ -437,7 +428,7 @@ public void ShootProjectile(EntityUid uid, Vector2 direction, Vector2 gunVelocit Projectiles.SetShooter(uid, projectile, user ?? gunUid); projectile.Weapon = gunUid; - TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle() + projectile.Angle); + TransformSystem.SetWorldRotation(uid, direction.ToWorldAngle()); } protected abstract void Popup(string message, EntityUid? uid, EntityUid? user); diff --git a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml index 14a6c60e6d0..01a857798ca 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/chemistry.yml @@ -156,7 +156,6 @@ price: 30 - type: DnaSubstanceTrace - - type: entity name: beaker parent: BaseBeaker @@ -175,6 +174,10 @@ fillBaseName: beaker inHandsMaxFillLevels: 3 inHandsFillBaseName: -fill- + - type: ReverseEngineering # Silver + difficulty: 3 + recipes: + - BluespaceBeaker - type: entity suffix: cryoxadone @@ -224,6 +227,10 @@ Glass: 100 - type: StaticPrice price: 20 + - type: ReverseEngineering # Silver + difficulty: 1 + recipes: + - BluespaceBeaker - type: entity name: cryostasis beaker @@ -263,6 +270,10 @@ solutions: beaker: maxVol: 1000 + - type: ReverseEngineering # Silver + difficulty: 1 + recipes: + - BluespaceBeaker - type: entity name: dropper @@ -387,6 +398,9 @@ tags: - Syringe - Trash + - type: ReverseEngineering # Nyano + difficulty: 2 + newItem: SyringeBluespace - type: entity name: mini syringe @@ -485,6 +499,9 @@ tags: - Syringe - Trash + - type: ReverseEngineering # Nyano + difficulty: 2 + newItem: SyringeBluespace - type: entity id: SyringeCryostasis