using Content.Shared.Destructible; using Content.Shared.EntityTable; using Content.Shared.Gatherable.Components; using Content.Shared.Interaction; using Content.Shared.Projectiles; using Content.Shared.Random.Helpers; using Content.Shared.Tag; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Whitelist; using Robust.Shared.Audio.Systems; using Robust.Shared.Physics.Events; using Robust.Shared.Timing; namespace Content.Shared.Gatherable; /// /// System for handling collecting items from in-the-world structures, /// like gathering mushrooms or some rare herbs. /// public sealed partial class GatherableSystem : EntitySystem { [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedDestructibleSystem _destructible = default!; [Dependency] private EntityTableSystem _entityTable = default!; [Dependency] private TagSystem _tagSystem = default!; [Dependency] private IGameTiming _timing = default!; [Dependency] private EntityWhitelistSystem _whitelistSystem = default!; /// /// If a gatherable entity is attacked with a tool that passes the tool entity whitelist (or if that whitelist doesn't exist), then it will be gathered. /// [SubscribeLocalEvent] private void OnAttacked(Entity gatherable, ref AttackedEvent args) { if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.Used)) return; Gather(gatherable.AsNullable(), args.User); } /// /// If a gatherable entity is activated by a user that passes the tool entity whitelist (or if that whitelist doesn't exist), then it will be gathered. /// [SubscribeLocalEvent] private void OnActivate(Entity gatherable, ref ActivateInWorldEvent args) { if (args.Handled || !args.Complex) return; if (_whitelistSystem.IsWhitelistFailOrNull(gatherable.Comp.ToolWhitelist, args.User)) return; Gather(gatherable.AsNullable(), args.User); args.Handled = true; } /// /// If a projectile hits a gatherable entity, it will gather that entity and decrease its own gathering amount. /// If the projectile’s gathering amount is zero or less, it will be deleted. /// [SubscribeLocalEvent] private void OnProjectileCollide(Entity gathering, ref StartCollideEvent args) { if (!args.OtherFixture.Hard) return; if (args.OurFixtureId != SharedProjectileSystem.ProjectileFixture) return; if (gathering.Comp.Amount <= 0) return; if (!TryComp(args.OtherEntity, out var gatherable)) return; Gather((args.OtherEntity, gatherable), gathering); gathering.Comp.Amount--; Dirty(gathering); if (gathering.Comp.Amount <= 0) PredictedQueueDel(gathering); } /// /// Destroys the gathered entity, plays a sound if it has , and spawns loot if possible. /// /// The entity that was gathered. /// The entity that gathered it. public void Gather(Entity gathered, EntityUid? gatherer = null) { if (!Resolve(gathered, ref gathered.Comp)) return; var pos = Transform(gathered).Coordinates; if (TryComp(gathered, out var soundComp)) _audio.PlayPredicted(soundComp.Sound, pos, gatherer); _destructible.DestroyEntity(gathered); if (gathered.Comp.Loot is null) return; foreach (var (tag, table) in gathered.Comp.Loot) { if (tag != "All" && gatherer != null && !_tagSystem.HasTag(gatherer.Value, tag)) continue; var random = SharedRandomExtensions.PredictedRandom(_timing, GetNetEntity(gathered)); foreach (var loot in _entityTable.GetSpawns(table, random)) { PredictedSpawnAtPosition(loot, pos.Offset(random.NextVector2(gathered.Comp.GatherOffset))); } } } }