case CartridgeAmmoComponent cartridge:
if (!cartridge.Spent)
{
- if (cartridge.Count > 1)
- {
- var ev = new GunGetAmmoSpreadEvent(cartridge.Spread);
- RaiseLocalEvent(gunUid, ref ev);
-
- var angles = LinearSpread(mapAngle - ev.Spread / 2,
- mapAngle + ev.Spread / 2, cartridge.Count);
-
- for (var i = 0; i < cartridge.Count; i++)
- {
- var uid = Spawn(cartridge.Prototype, fromEnt);
- ShootOrThrow(uid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
- shotProjectiles.Add(uid);
- }
- }
- else
- {
- var uid = Spawn(cartridge.Prototype, fromEnt);
- ShootOrThrow(uid, mapDirection, gunVelocity, gun, gunUid, user);
- shotProjectiles.Add(uid);
- }
+ var uid = Spawn(cartridge.Prototype, fromEnt);
+ CreateAndFireProjectiles(uid, cartridge);
RaiseLocalEvent(ent!.Value, new AmmoShotEvent()
{
});
SetCartridgeSpent(ent.Value, cartridge, true);
- MuzzleFlash(gunUid, cartridge, mapDirection.ToAngle(), user);
- Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
if (cartridge.DeleteOnSpawn)
Del(ent.Value);
break;
// Ammo shoots itself
case AmmoComponent newAmmo:
- shotProjectiles.Add(ent!.Value);
- MuzzleFlash(gunUid, newAmmo, mapDirection.ToAngle(), user);
- Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
- ShootOrThrow(ent.Value, mapDirection, gunVelocity, gun, gunUid, user);
+ if (ent == null)
+ break;
+ CreateAndFireProjectiles(ent.Value, newAmmo);
+
break;
case HitscanPrototype hitscan:
{
FiredProjectiles = shotProjectiles,
});
+
+ void CreateAndFireProjectiles(EntityUid ammoEnt, AmmoComponent ammoComp)
+ {
+ if (TryComp<ProjectileSpreadComponent>(ammoEnt, out var ammoSpreadComp))
+ {
+ var spreadEvent = new GunGetAmmoSpreadEvent(ammoSpreadComp.Spread);
+ RaiseLocalEvent(gunUid, ref spreadEvent);
+
+ var angles = LinearSpread(mapAngle - spreadEvent.Spread / 2,
+ mapAngle + spreadEvent.Spread / 2, ammoSpreadComp.Count);
+
+ ShootOrThrow(ammoEnt, angles[0].ToVec(), gunVelocity, gun, gunUid, user);
+ shotProjectiles.Add(ammoEnt);
+
+ for (var i = 1; i < ammoSpreadComp.Count; i++)
+ {
+ var newuid = Spawn(ammoSpreadComp.Proto, fromEnt);
+ ShootOrThrow(newuid, angles[i].ToVec(), gunVelocity, gun, gunUid, user);
+ shotProjectiles.Add(ammoEnt);
+ }
+ }
+ else
+ {
+ ShootOrThrow(ammoEnt, mapDirection, gunVelocity, gun, gunUid, user);
+ shotProjectiles.Add(ammoEnt);
+ }
+
+ MuzzleFlash(gunUid, ammoComp, mapDirection.ToAngle(), user);
+ Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user);
+ }
}
private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user)
--- /dev/null
+using Content.Shared.Weapons.Ranged.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Projectiles;
+
+/// <summary>
+/// Spawns a spread of the projectiles when fired
+/// </summary>
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedGunSystem))]
+public sealed partial class ProjectileSpreadComponent : Component
+{
+ /// <summary>
+ /// The entity prototype that will be fired by the rest of the spread.
+ /// Will generally be the same entity prototype as the first projectile being fired.
+ /// Needed for ammo components that do not specify a fired prototype, unlike cartridges.
+ /// </summary>
+ [DataField(required: true)]
+ public EntProtoId Proto;
+
+ /// <summary>
+ /// How much the ammo spreads when shot, in degrees. Does nothing if count is 0.
+ /// </summary>
+ [DataField]
+ public Angle Spread = Angle.FromDegrees(5);
+
+ /// <summary>
+ /// How many prototypes are spawned when shot.
+ /// </summary>
+ [DataField]
+ public int Count = 1;
+}
- Cartridge
- ShellShotgun
- type: CartridgeAmmo
- count: 6
- spread: 15
soundEject:
collection: ShellEject
- type: Sprite
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
proto: PelletShotgunBeanbag
- count: 1
- type: SpentAmmoVisuals
state: "beanbag"
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
proto: PelletShotgunSlug
- count: 1
- spread: 0
- type: SpentAmmoVisuals
state: "slug"
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
proto: PelletShotgunFlare
- count: 1
- type: SpentAmmoVisuals
state: "flare"
- state: base
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
- proto: PelletShotgun
+ proto: PelletShotgunSpread
- type: entity
id: ShellShotgunIncendiary
- state: incendiary
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
- proto: PelletShotgunIncendiary
+ proto: PelletShotgunIncendiarySpread
- type: SpentAmmoVisuals
state: "incendiary"
- state: practice
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
- proto: PelletShotgunPractice
+ proto: PelletShotgunPracticeSpread
- type: SpentAmmoVisuals
state: "practice"
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
proto: PelletShotgunTranquilizer
- count: 1
- type: ChemicalAmmo
- type: SolutionContainerManager
solutions:
graph: ImprovisedShotgunShellGraph
node: shell
- type: CartridgeAmmo
- count: 10
- spread: 45
- proto: PelletShotgunImprovised
+ proto: PelletShotgunImprovisedSpread
- type: SpentAmmoVisuals
state: "improvised"
- state: depleted-uranium
map: [ "enum.AmmoVisualLayers.Base" ]
- type: CartridgeAmmo
- count: 5
- spread: 6
- proto: PelletShotgunUranium
+ proto: PelletShotgunUraniumSpread
- type: SpentAmmoVisuals
state: "depleted-uranium"
types:
Piercing: 10
+- type: entity
+ id: PelletShotgunSpread
+ noSpawn: true
+ parent: PelletShotgun
+ components:
+ - type: ProjectileSpread
+ proto: PelletShotgun
+ count: 6
+ spread: 15
+
- type: entity
id: PelletShotgunIncendiary
name: pellet (.50 incendiary)
- type: IgnitionSource
ignited: true
+- type: entity
+ id: PelletShotgunIncendiarySpread
+ noSpawn: true
+ parent: PelletShotgunIncendiary
+ components:
+ - type: ProjectileSpread
+ proto: PelletShotgunIncendiary
+ count: 6
+ spread: 15
+
- type: entity
id: PelletShotgunPractice
name: pellet (.50 practice)
types:
Blunt: 1
+- type: entity
+ id: PelletShotgunPracticeSpread
+ noSpawn: true
+ parent: PelletShotgunPractice
+ components:
+ - type: ProjectileSpread
+ proto: PelletShotgunPractice
+ count: 6
+ spread: 15
+
- type: entity
id: PelletShotgunImprovised
name: improvised pellet
Piercing: 3
Slash: 3
+- type: entity
+ id: PelletShotgunImprovisedSpread
+ noSpawn: true
+ parent: PelletShotgunImprovised
+ components:
+ - type: ProjectileSpread
+ proto: PelletShotgunImprovised
+ count: 10
+ spread: 45
- type: entity
id: PelletShotgunTranquilizer
Radiation: 5
Piercing: 5
+- type: entity
+ id: PelletShotgunUraniumSpread
+ noSpawn: true
+ parent: PelletShotgunUranium
+ components:
+ - type: ProjectileSpread
+ proto: PelletShotgunUranium
+ count: 5
+ spread: 6
+
- type: entity
id: PelletGrapeshot #tally fucking ho
name: grapeshot pellet
Piercing: 25
Structural: 5
+- type: entity
+ id: PelletGrapeshotSpread
+ noSpawn: true
+ parent: PelletGrapeshot
+ components:
+ - type: ProjectileSpread
+ proto: PelletGrapeshot
+ count: 5
+ spread: 40
+
- type: entity
id: PelletGlass
name: glass shard
damage:
types:
Slash: 25
+
+- type: entity
+ id: PelletGlassSpread
+ parent: PelletGlass
+ noSpawn: true
+ components:
+ - type: ProjectileSpread
+ proto: PelletGlass
+ count: 5
+ spread: 10