using System; using Content.Shared.Examine; using Content.Shared.Sound; using Content.Shared.Weapons.Ranged.Barrels.Components; using Robust.Server.GameObjects; 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.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.Utility.Markup; 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] #pragma warning disable 618 public class AmmoComponent : Component, IExamine, ISerializationHooks #pragma warning restore 618 { [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; public override string Name => "Ammo"; [DataField("caliber")] public BallisticCaliber Caliber { get; } = BallisticCaliber.Unspecified; public bool Spent { get { if (_ammoIsProjectile) { return false; } return _spent; } } private bool _spent; /// /// Used for anything without a case that fires itself /// [DataField("isProjectile")] private 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")] private 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")] private string _muzzleFlashSprite = "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 == true && Caseless == true)); 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 EntityUid? TakeBullet(EntityCoordinates spawnAt) { if (_ammoIsProjectile) { return Owner; } if (_spent) { return null; } _spent = true; if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearanceComponent)) { appearanceComponent.SetData(AmmoVisuals.Spent, true); } var entity = _entMan.SpawnEntity(_projectileId, spawnAt); return entity; } public void MuzzleFlash(EntityUid entity, Angle angle) { if (_muzzleFlashSprite == null) { return; } var time = _gameTiming.CurTime; var deathTime = time + TimeSpan.FromMilliseconds(200); // Offset the sprite so it actually looks like it's coming from the gun var offset = angle.ToVec().Normalized / 2; var message = new EffectSystemMessage { EffectSprite = _muzzleFlashSprite, Born = time, DeathTime = deathTime, AttachedEntityUid = entity, AttachedOffset = offset, //Rotated from east facing Rotation = (float) angle.Theta, Color = Vector4.Multiply(new Vector4(255, 255, 255, 255), 1.0f), ColorDelta = new Vector4(0, 0, 0, -1500f), Shaded = false }; EntitySystem.Get().CreateParticle(message); } public void Examine(FormattedMessage.Builder message, bool inDetailsRange) { var text = Loc.GetString("ammo-component-on-examine",("caliber", Caliber)); message.AddMarkup(text); } } public enum BallisticCaliber { Unspecified = 0, A357, // Placeholder? ClRifle, SRifle, Pistol, A35, // Placeholder? LRifle, Magnum, AntiMaterial, Shotgun, Cap, Rocket, Dart, // Placeholder Grenade, Energy, } }