using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.Events; using Content.Shared.Inventory; using Content.Shared.Popups; using Content.Shared.Projectiles; using Content.Shared.Tag; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Collections; namespace Content.Server.Chemistry.EntitySystems; /// /// System for handling the different inheritors of . /// Subscribes to relevent events and performs solution injections when they are raised. /// public sealed class SolutionInjectOnCollideSystem : EntitySystem { [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; [Dependency] private readonly TagSystem _tag = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(HandleProjectileHit); SubscribeLocalEvent(HandleEmbed); SubscribeLocalEvent(HandleMeleeHit); SubscribeLocalEvent(OnInjectOverTime); } private void HandleProjectileHit(Entity entity, ref ProjectileHitEvent args) { DoInjection((entity.Owner, entity.Comp), args.Target, args.Shooter); } private void HandleEmbed(Entity entity, ref EmbedEvent args) { DoInjection((entity.Owner, entity.Comp), args.Embedded, args.Shooter); } private void HandleMeleeHit(Entity entity, ref MeleeHitEvent args) { // MeleeHitEvent is weird, so we have to filter to make sure we actually // hit something and aren't just examining the weapon. if (args.IsHit) TryInjectTargets((entity.Owner, entity.Comp), args.HitEntities, args.User); } private void OnInjectOverTime(Entity entity, ref InjectOverTimeEvent args) { DoInjection((entity.Owner, entity.Comp), args.EmbeddedIntoUid); } private void DoInjection(Entity injectorEntity, EntityUid target, EntityUid? source = null) { TryInjectTargets(injectorEntity, [target], source); } /// /// Filters for valid targets and tries to inject a portion of into /// each valid target's bloodstream. /// /// /// Targets are invalid if any of the following are true: /// /// The target does not have a bloodstream. /// is false and the target is wearing a hardsuit. /// is not NONE and the target has an item equipped in any of the specified slots. /// /// /// true if at least one target was successfully injected, otherwise false private bool TryInjectTargets(Entity injector, IReadOnlyList targets, EntityUid? source = null) { // Make sure we have at least one target if (targets.Count == 0) return false; // Get the solution to inject if (!_solutionContainer.TryGetSolution(injector.Owner, injector.Comp.Solution, out var injectorSolution)) return false; // Build a list of bloodstreams to inject into var targetBloodstreams = new ValueList>(); foreach (var target in targets) { if (Deleted(target)) continue; // Yuck, this is way to hardcodey for my tastes // TODO blocking injection with a hardsuit should probably done with a cancellable event or something if (!injector.Comp.PierceArmor && _inventory.TryGetSlotEntity(target, "outerClothing", out var suit) && _tag.HasTag(suit.Value, "Hardsuit")) { // Only show popup to attacker if (source != null) _popup.PopupEntity(Loc.GetString(injector.Comp.BlockedByHardsuitPopupMessage, ("weapon", injector.Owner), ("target", target)), target, source.Value, PopupType.SmallCaution); continue; } // Check if the target has anything equipped in a slot that would block injection if (injector.Comp.BlockSlots != SlotFlags.NONE) { var blocked = false; var containerEnumerator = _inventory.GetSlotEnumerator(target, injector.Comp.BlockSlots); while (containerEnumerator.MoveNext(out var container)) { if (container.ContainedEntity != null) { blocked = true; break; } } if (blocked) continue; } // Make sure the target has a bloodstream if (!TryComp(target, out var bloodstream)) continue; // Checks passed; add this target's bloodstream to the list targetBloodstreams.Add((target, bloodstream)); } // Make sure we got at least one bloodstream if (targetBloodstreams.Count == 0) return false; // Extract total needed solution from the injector var removedSolution = _solutionContainer.SplitSolution(injectorSolution.Value, injector.Comp.TransferAmount * targetBloodstreams.Count); // Adjust solution amount based on transfer efficiency var solutionToInject = removedSolution.SplitSolution(removedSolution.Volume * injector.Comp.TransferEfficiency); // Calculate how much of the adjusted solution each target will get var volumePerBloodstream = solutionToInject.Volume * (1f / targetBloodstreams.Count); var anySuccess = false; foreach (var targetBloodstream in targetBloodstreams) { // Take our portion of the adjusted solution for this target var individualInjection = solutionToInject.SplitSolution(volumePerBloodstream); // Inject our portion into the target's bloodstream if (_bloodstream.TryAddToChemicals(targetBloodstream.Owner, individualInjection, targetBloodstream.Comp)) anySuccess = true; } // Huzzah! return anySuccess; } }