private void OnChamberMagazineTakeAmmo(EntityUid uid, ChamberMagazineAmmoProviderComponent component, TakeAmmoEvent args) { // So chamber logic is kinda sussier than the others // Essentially we want to treat the chamber as a potentially free slot and then the mag as the remaining slots // i.e. if we shoot 3 times, then we use the chamber once (regardless if it's empty or not) and 2 from the mag // We move the n + 1 shot into the chamber as we essentially treat it like a stack. TryComp <AppearanceComponent>(uid, out var appearance); if (TryTakeChamberEntity(uid, out var chamberEnt)) { args.Ammo.Add(EnsureComp <AmmoComponent>(chamberEnt.Value)); } var magEnt = GetMagazineEntity(uid); // Pass an event to the magazine to get more (to refill chamber or for shooting). if (magEnt != null) { // We pass in Shots not Shots - 1 as we'll take the last entity and move it into the chamber. var relayedArgs = new TakeAmmoEvent(args.Shots, new List <IShootable>(), args.Coordinates, args.User); RaiseLocalEvent(magEnt.Value, relayedArgs, false); // Put in the nth slot back into the chamber // Rest of the ammo gets shot if (relayedArgs.Ammo.Count > 0) { var newChamberEnt = ((AmmoComponent)relayedArgs.Ammo[^ 1]).Owner;
private void AttemptShoot(EntityUid user, GunComponent gun) { if (gun.FireRate <= 0f) { return; } var toCoordinates = gun.ShootCoordinates; if (toCoordinates == null) { return; } if (TagSystem.HasTag(user, "GunsDisabled")) { Popup(Loc.GetString("gun-disabled"), user, user); return; } var curTime = Timing.CurTime; // 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; } // First shot if (gun.ShotCounter == 0 && gun.NextFire < curTime) { gun.NextFire = curTime; } var shots = 0; var lastFire = gun.NextFire; var fireRate = TimeSpan.FromSeconds(1f / gun.FireRate); while (gun.NextFire <= curTime) { gun.NextFire += fireRate; shots++; } // 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. switch (gun.SelectedMode) { case SelectiveFire.SemiAuto: shots = Math.Min(shots, 1 - gun.ShotCounter); break; case SelectiveFire.Burst: shots = Math.Min(shots, 3 - gun.ShotCounter); break; case SelectiveFire.FullAuto: break; default: throw new ArgumentOutOfRangeException($"No implemented shooting behavior for {gun.SelectedMode}!"); } var fromCoordinates = Transform(user).Coordinates; // Remove ammo var ev = new TakeAmmoEvent(shots, new List <IShootable>(), fromCoordinates, user); // Listen it just makes the other code around it easier if shots == 0 to do this. if (shots > 0) { RaiseLocalEvent(gun.Owner, ev, false); } DebugTools.Assert(ev.Ammo.Count <= shots); DebugTools.Assert(shots >= 0); UpdateAmmoCount(gun.Owner); // Even if we don't actually shoot update the ShotCounter. This is to avoid spamming empty sounds // where the gun may be SemiAuto or Burst. gun.ShotCounter += shots; if (ev.Ammo.Count <= 0) { // Play empty gun sounds if relevant // If they're firing an existing clip then don't play anything. if (shots > 0) { // Don't spam safety sounds at gun fire rate, play it at a reduced rate. // May cause prediction issues? Needs more tweaking gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds)); PlaySound(gun.Owner, gun.SoundEmpty?.GetSound(Random, ProtoManager), user); Dirty(gun); return; } return; } // Shoot confirmed - sounds also played here in case it's invalid (e.g. cartridge already spent). Shoot(gun, ev.Ammo, fromCoordinates, toCoordinates.Value, user); Dirty(gun); }
private void OnRevolverTakeAmmo(EntityUid uid, RevolverAmmoProviderComponent component, TakeAmmoEvent args) { var currentIndex = component.CurrentIndex; Cycle(component, args.Shots); // Revolvers provide the bullets themselves rather than the cartridges so they stay in the revolver. for (var i = 0; i < args.Shots; i++) { var index = (currentIndex + i) % component.Capacity; var chamber = component.Chambers[index]; // Get unspawned ent first if possible. if (chamber != null) { if (chamber == true) { // TODO: This is kinda sussy boy var ent = Spawn(component.FillPrototype, args.Coordinates); if (TryComp <CartridgeAmmoComponent>(ent, out var cartridge)) { component.Chambers[index] = false; SetCartridgeSpent(cartridge, true); args.Ammo.Add(EnsureComp <AmmoComponent>(Spawn(cartridge.Prototype, args.Coordinates))); Del(ent); continue; } component.Chambers[i] = null; args.Ammo.Add(EnsureComp <AmmoComponent>(ent)); } } else if (component.AmmoSlots[index] != null) { var ent = component.AmmoSlots[index] !; if (TryComp <CartridgeAmmoComponent>(ent, out var cartridge)) { if (cartridge.Spent) { continue; } SetCartridgeSpent(cartridge, true); args.Ammo.Add(EnsureComp <AmmoComponent>(Spawn(cartridge.Prototype, args.Coordinates))); continue; } component.AmmoContainer.Remove(ent.Value); component.AmmoSlots[index] = null; args.Ammo.Add(EnsureComp <AmmoComponent>(ent.Value)); Transform(ent.Value).Coordinates = args.Coordinates; } } UpdateRevolverAppearance(component); Dirty(component); }
private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args) { var shots = Math.Min(args.Shots, component.Shots); // Don't dirty if it's an empty fire. if (shots == 0) { return; } for (var i = 0; i < shots; i++) { args.Ammo.Add(GetShootable(component, args.Coordinates)); component.Shots--; } TakeCharge(uid, component); UpdateBatteryAppearance(uid, component); Dirty(component); }
private void OnMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, TakeAmmoEvent args) { var magEntity = GetMagazineEntity(uid); TryComp <AppearanceComponent>(uid, out var appearance); if (magEntity == null) { appearance?.SetData(AmmoVisuals.MagLoaded, false); return; } // Pass the event onwards. RaiseLocalEvent(magEntity.Value, args, false); // Should be Dirtied by what other ammoprovider is handling it. var ammoEv = new GetAmmoCountEvent(); RaiseLocalEvent(magEntity.Value, ref ammoEv, false); FinaliseMagazineTakeAmmo(uid, component, args, ammoEv.Count, ammoEv.Capacity, appearance); }
private void FinaliseMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, TakeAmmoEvent args, int count, int capacity, AppearanceComponent?appearance) { // If no ammo then check for autoeject if (component.AutoEject && args.Ammo.Count == 0) { EjectMagazine(component); PlaySound(uid, component.SoundAutoEject?.GetSound(Random, ProtoManager), args.User); } UpdateMagazineAppearance(appearance, true, count, capacity); }
private void OnBasicEntityTakeAmmo(EntityUid uid, BasicEntityAmmoProviderComponent component, TakeAmmoEvent args) { for (int i = 0; i < args.Shots; i++) { if (component.Count <= 0) { return; } if (component.Count != null) { component.Count--; } var ent = Spawn(component.Proto, args.Coordinates); args.Ammo.Add(EnsureComp <AmmoComponent>(ent)); } UpdateBasicEntityAppearance(component); Dirty(component); }