using System; using Content.Shared.Examine; using Content.Shared.Sound; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server.Weapon.Ranged.Ammunition.Components { /// /// Allows this entity to be loaded into a ranged weapon (if the caliber matches) /// Generally used for bullets but can be used for other things like bananas /// [RegisterComponent] [Friend(typeof(GunSystem))] public sealed class AmmoComponent : Component, ISerializationHooks { [DataField("caliber")] public BallisticCaliber Caliber { get; } = BallisticCaliber.Unspecified; public bool Spent { get { if (AmmoIsProjectile) { return false; } return _spent; } set => _spent = value; } private bool _spent; // TODO: Make it so null projectile = dis /// /// Used for anything without a case that fires itself /// [DataField("isProjectile")] public bool AmmoIsProjectile; /// /// Used for something that is deleted when the projectile is retrieved /// [DataField("caseless")] public bool Caseless { get; } // Rather than managing bullet / case state seemed easier to just have 2 toggles // ammoIsProjectile being for a beanbag for example and caseless being for ClRifle rounds /// /// For shotguns where they might shoot multiple entities /// [DataField("projectilesFired")] public int ProjectilesFired { get; } = 1; [DataField("projectile", customTypeSerializer: typeof(PrototypeIdSerializer))] public string? ProjectileId; // How far apart each entity is if multiple are shot [DataField("ammoSpread")] public float EvenSpreadAngle { get; } = default; /// /// How fast the shot entities travel /// [DataField("ammoVelocity")] public float Velocity { get; } = 20f; [DataField("muzzleFlash")] public ResourcePath? MuzzleFlashSprite = new("Objects/Weapons/Guns/Projectiles/bullet_muzzle.png"); [DataField("soundCollectionEject")] public SoundSpecifier SoundCollectionEject { get; } = new SoundCollectionSpecifier("CasingEject"); void ISerializationHooks.AfterDeserialization() { // Being both caseless and shooting yourself doesn't make sense DebugTools.Assert(!(AmmoIsProjectile && Caseless)); if (ProjectilesFired < 1) { Logger.Error("Ammo can't have less than 1 projectile"); } if (EvenSpreadAngle > 0 && ProjectilesFired == 1) { Logger.Error("Can't have an even spread if only 1 projectile is fired"); throw new InvalidOperationException(); } } } public enum BallisticCaliber { Unspecified = 0, A357, // Placeholder? ClRifle, SRifle, Pistol, A35, // Placeholder? LRifle, HRifle, Magnum, AntiMaterial, Shotgun, Cap, Rocket, Dart, // Placeholder Grenade, Energy, } }