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;
예제 #2
0
    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);
    }
예제 #4
0
    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);
    }
예제 #7
0
    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);
    }