using System; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.GameObjects.EntitySystems; 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; namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition { /// /// 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] public class AmmoComponent : Component, IExamine, ISerializationHooks { [Dependency] private readonly IGameTiming _gameTiming = default!; public override string Name => "Ammo"; [field: 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 /// [field: 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 /// [field: DataField("projectilesFired")] public int ProjectilesFired { get; } = 1; [DataField("projectile")] private string? _projectileId; // How far apart each entity is if multiple are shot [field: DataField("ammoSpread")] public float EvenSpreadAngle { get; } = default; /// /// How fast the shot entities travel /// [field: DataField("ammoVelocity")] public float Velocity { get; } = 20f; [DataField("muzzleFlash")] private string _muzzleFlashSprite = "Objects/Weapons/Guns/Projectiles/bullet_muzzle.png"; [field: DataField("soundCollectionEject")] public string? SoundCollectionEject { get; } = "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 IEntity? TakeBullet(EntityCoordinates spawnAt) { if (_ammoIsProjectile) { return Owner; } if (_spent) { return null; } _spent = true; if (Owner.TryGetComponent(out AppearanceComponent? appearanceComponent)) { appearanceComponent.SetData(AmmoVisuals.Spent, true); } var entity = Owner.EntityManager.SpawnEntity(_projectileId, spawnAt); return entity; } public void MuzzleFlash(IEntity 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.Uid, 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 message, bool inDetailsRange) { var text = Loc.GetString("It's [color=white]{0}[/color] ammo.", 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, CreamPie, // I can't wait for this enum to be a prototype type... } }