| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- using Content.Server.DoAfter;
- using Content.Server.Popups;
- using Content.Shared.Chemistry.EntitySystems;
- using Content.Shared.Audio;
- using Content.Shared.Chemistry.Components.SolutionManager;
- using Content.Shared.Database;
- using Content.Shared.DoAfter;
- using Content.Shared.Examine;
- using Content.Shared.FixedPoint;
- using Content.Shared.Fluids;
- using Content.Shared.Fluids.Components;
- using Content.Shared.Interaction;
- using Content.Shared.Tag;
- using Content.Shared.Verbs;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Random;
- using Robust.Shared.Utility;
- namespace Content.Server.Fluids.EntitySystems;
- public sealed class DrainSystem : SharedDrainSystem
- {
- [Dependency] private readonly EntityLookupSystem _lookup = default!;
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
- [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly TagSystem _tagSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly PuddleSystem _puddleSystem = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- private readonly HashSet<Entity<PuddleComponent>> _puddles = new();
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<DrainComponent, MapInitEvent>(OnDrainMapInit);
- SubscribeLocalEvent<DrainComponent, GetVerbsEvent<Verb>>(AddEmptyVerb);
- SubscribeLocalEvent<DrainComponent, ExaminedEvent>(OnExamined);
- SubscribeLocalEvent<DrainComponent, AfterInteractUsingEvent>(OnInteract);
- SubscribeLocalEvent<DrainComponent, DrainDoAfterEvent>(OnDoAfter);
- }
- private void OnDrainMapInit(Entity<DrainComponent> ent, ref MapInitEvent args)
- {
- // Randomise puddle drains so roundstart ones don't all dump at the same time.
- ent.Comp.Accumulator = _random.NextFloat(ent.Comp.DrainFrequency);
- }
- private void AddEmptyVerb(Entity<DrainComponent> entity, ref GetVerbsEvent<Verb> args)
- {
- if (!args.CanAccess || !args.CanInteract || args.Using == null)
- return;
- if (!TryComp(args.Using, out SpillableComponent? spillable) ||
- !TryComp(args.Target, out DrainComponent? drain))
- return;
- var used = args.Using.Value;
- var target = args.Target;
- Verb verb = new()
- {
- Text = Loc.GetString("drain-component-empty-verb-inhand", ("object", Name(used))),
- Act = () =>
- {
- Empty(used, spillable, target, drain);
- },
- Impact = LogImpact.Low,
- Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/eject.svg.192dpi.png"))
- };
- args.Verbs.Add(verb);
- }
- private void Empty(EntityUid container, SpillableComponent spillable, EntityUid target, DrainComponent drain)
- {
- // Find the solution in the container that is emptied
- if (!_solutionContainerSystem.TryGetDrainableSolution(container, out var containerSoln, out var containerSolution) || containerSolution.Volume == FixedPoint2.Zero)
- {
- _popupSystem.PopupEntity(
- Loc.GetString("drain-component-empty-verb-using-is-empty-message", ("object", container)),
- container);
- return;
- }
- // try to find the drain's solution
- if (!_solutionContainerSystem.ResolveSolution(target, DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
- {
- return;
- }
- // Try to transfer as much solution as possible to the drain
- var amountToPutInDrain = drainSolution.AvailableVolume;
- var amountToSpillOnGround = containerSolution.Volume - drainSolution.AvailableVolume;
- if (amountToPutInDrain > 0)
- {
- var solutionToPutInDrain = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToPutInDrain);
- _solutionContainerSystem.TryAddSolution(drain.Solution.Value, solutionToPutInDrain);
- _audioSystem.PlayPvs(drain.ManualDrainSound, target);
- _ambientSoundSystem.SetAmbience(target, true);
- }
- // Spill the remainder.
- if (amountToSpillOnGround > 0)
- {
- var solutionToSpill = _solutionContainerSystem.SplitSolution(containerSoln.Value, amountToSpillOnGround);
- _puddleSystem.TrySpillAt(Transform(target).Coordinates, solutionToSpill, out _);
- _popupSystem.PopupEntity(
- Loc.GetString("drain-component-empty-verb-target-is-full-message", ("object", target)),
- container);
- }
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var managerQuery = GetEntityQuery<SolutionContainerManagerComponent>();
- var query = EntityQueryEnumerator<DrainComponent>();
- while (query.MoveNext(out var uid, out var drain))
- {
- drain.Accumulator += frameTime;
- if (drain.Accumulator < drain.DrainFrequency)
- {
- continue;
- }
- drain.Accumulator -= drain.DrainFrequency;
- if (!managerQuery.TryGetComponent(uid, out var manager))
- continue;
- // Best to do this one every second rather than once every tick...
- if (!_solutionContainerSystem.ResolveSolution((uid, manager), DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
- continue;
- if (drainSolution.Volume <= 0 && !drain.AutoDrain)
- {
- _ambientSoundSystem.SetAmbience(uid, false);
- continue;
- }
- // Remove a bit from the buffer
- _solutionContainerSystem.SplitSolution(drain.Solution.Value, (drain.UnitsDestroyedPerSecond * drain.DrainFrequency));
- // This will ensure that UnitsPerSecond is per second...
- var amount = drain.UnitsPerSecond * drain.DrainFrequency;
- if (drain.AutoDrain)
- {
- _puddles.Clear();
- _lookup.GetEntitiesInRange(Transform(uid).Coordinates, drain.Range, _puddles);
- if (_puddles.Count == 0 && drainSolution.Volume <= 0)
- {
- _ambientSoundSystem.SetAmbience(uid, false);
- continue;
- }
- _ambientSoundSystem.SetAmbience(uid, true);
- amount /= _puddles.Count;
- foreach (var puddle in _puddles)
- {
- // Queue the solution deletion if it's empty. EvaporationSystem might also do this
- // but queuedelete should be pretty safe.
- if (!_solutionContainerSystem.ResolveSolution(puddle.Owner, puddle.Comp.SolutionName, ref puddle.Comp.Solution, out var puddleSolution))
- {
- EntityManager.QueueDeleteEntity(puddle);
- continue;
- }
- // Removes the lowest of:
- // the drain component's units per second adjusted for # of puddles
- // the puddle's remaining volume (making it cleanly zero)
- // the drain's remaining volume in its buffer.
- var transferSolution = _solutionContainerSystem.SplitSolution(puddle.Comp.Solution.Value,
- FixedPoint2.Min(FixedPoint2.New(amount), puddleSolution.Volume, drainSolution.AvailableVolume));
- drainSolution.AddSolution(transferSolution, _prototypeManager);
- if (puddleSolution.Volume <= 0)
- {
- QueueDel(puddle);
- }
- }
- }
- _solutionContainerSystem.UpdateChemicals(drain.Solution.Value);
- }
- }
- private void OnExamined(Entity<DrainComponent> entity, ref ExaminedEvent args)
- {
- if (!args.IsInDetailsRange ||
- !HasComp<SolutionContainerManagerComponent>(entity) ||
- !_solutionContainerSystem.ResolveSolution(entity.Owner, DrainComponent.SolutionName, ref entity.Comp.Solution, out var drainSolution))
- {
- return;
- }
- var text = drainSolution.AvailableVolume != 0
- ? Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume))
- : Loc.GetString("drain-component-examine-hint-full");
- args.PushMarkup(text);
- }
- private void OnInteract(Entity<DrainComponent> entity, ref AfterInteractUsingEvent args)
- {
- if (!args.CanReach || args.Target == null ||
- !_tagSystem.HasTag(args.Used, DrainComponent.PlungerTag) ||
- !_solutionContainerSystem.ResolveSolution(args.Target.Value, DrainComponent.SolutionName, ref entity.Comp.Solution, out var drainSolution))
- {
- return;
- }
- if (drainSolution.AvailableVolume > 0)
- {
- _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-notapplicable", ("object", args.Target.Value)), args.Target.Value);
- return;
- }
- _audioSystem.PlayPvs(entity.Comp.PlungerSound, entity);
- var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.UnclogDuration, new DrainDoAfterEvent(), entity, args.Target, args.Used)
- {
- BreakOnDamage = true,
- BreakOnMove = true,
- BreakOnHandChange = true
- };
- _doAfterSystem.TryStartDoAfter(doAfterArgs);
- }
- private void OnDoAfter(Entity<DrainComponent> entity, ref DrainDoAfterEvent args)
- {
- if (args.Target == null)
- return;
- if (!_random.Prob(entity.Comp.UnclogProbability))
- {
- _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-fail", ("object", args.Target.Value)), args.Target.Value);
- return;
- }
- if (!_solutionContainerSystem.ResolveSolution(args.Target.Value, DrainComponent.SolutionName, ref entity.Comp.Solution))
- {
- return;
- }
- _solutionContainerSystem.RemoveAllSolution(entity.Comp.Solution.Value);
- _audioSystem.PlayPvs(entity.Comp.UnclogSound, args.Target.Value);
- _popupSystem.PopupEntity(Loc.GetString("drain-component-unclog-success", ("object", args.Target.Value)), args.Target.Value);
- }
- }
|