| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910 |
- // SPDX-FileCopyrightText: 2024 Skubman <ba.fallaria@gmail.com>
- // SPDX-FileCopyrightText: 2025 Aiden <28298836+Aidenkrz@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2025 Janet Blackquill <uhhadd@gmail.com>
- // SPDX-FileCopyrightText: 2025 Piras314 <p1r4s@proton.me>
- // SPDX-FileCopyrightText: 2025 deltanedas <39013340+deltanedas@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2025 deltanedas <@deltanedas:kde.org>
- // SPDX-FileCopyrightText: 2025 gluesniffler <159397573+gluesniffler@users.noreply.github.com>
- // SPDX-FileCopyrightText: 2025 gus <august.eymann@gmail.com>
- //
- // SPDX-License-Identifier: AGPL-3.0-or-later
- using Content.Shared.Humanoid;
- using Content.Shared.Humanoid.Markings;
- using Content.Shared.Body.Part;
- using Content.Shared.Body.Organ;
- using Content.Shared._Shitmed.BodyEffects;
- using Content.Shared._Shitmed.Body.Events;
- using Content.Shared.Buckle.Components;
- using Content.Shared.Containers.ItemSlots;
- using Content.Shared.Damage;
- using Content.Shared.Damage.Prototypes;
- using Content.Shared.DoAfter;
- using Content.Shared.IdentityManagement;
- using Content.Shared._Shitmed.Medical.Surgery.Conditions;
- using Content.Shared._Shitmed.Medical.Surgery.Effects.Step;
- using Content.Shared._Shitmed.Medical.Surgery.Steps;
- using Content.Shared._Shitmed.Medical.Surgery.Steps.Parts;
- using Content.Shared._Shitmed.Medical.Surgery.Tools;
- //using Content.Shared.Mood;
- using Content.Shared.Inventory;
- using Content.Shared.Item;
- using Content.Shared._Shitmed.Body.Organ;
- using Content.Shared._Shitmed.Body.Part;
- using Content.Shared.Popups;
- using Robust.Shared.Prototypes;
- using System.Linq;
- namespace Content.Shared._Shitmed.Medical.Surgery;
- public abstract partial class SharedSurgerySystem
- {
- private static readonly string[] BruteDamageTypes = { "Slash", "Blunt", "Piercing" };
- private static readonly string[] BurnDamageTypes = { "Heat", "Shock", "Cold", "Caustic" };
- private void InitializeSteps()
- {
- SubscribeLocalEvent<SurgeryStepComponent, SurgeryStepEvent>(OnToolStep);
- SubscribeLocalEvent<SurgeryStepComponent, SurgeryStepCompleteCheckEvent>(OnToolCheck);
- SubscribeLocalEvent<SurgeryStepComponent, SurgeryCanPerformStepEvent>(OnToolCanPerform);
- //SubSurgery<SurgeryCutLarvaRootsStepComponent>(OnCutLarvaRootsStep, OnCutLarvaRootsCheck);
- /* Abandon all hope ye who enter here. Now I am become shitcoder, the bloater of files.
- On a serious note, I really hate how much bloat this pattern of subscribing to a StepEvent and a CheckEvent
- creates in terms of readability. And while Check DOES only run on the server side, it's still annoying to parse through.*/
- SubSurgery<SurgeryTendWoundsEffectComponent>(OnTendWoundsStep, OnTendWoundsCheck);
- SubSurgery<SurgeryStepCavityEffectComponent>(OnCavityStep, OnCavityCheck);
- SubSurgery<SurgeryAddPartStepComponent>(OnAddPartStep, OnAddPartCheck);
- SubSurgery<SurgeryAffixPartStepComponent>(OnAffixPartStep, OnAffixPartCheck);
- SubSurgery<SurgeryRemovePartStepComponent>(OnRemovePartStep, OnRemovePartCheck);
- SubSurgery<SurgeryAddOrganStepComponent>(OnAddOrganStep, OnAddOrganCheck);
- SubSurgery<SurgeryRemoveOrganStepComponent>(OnRemoveOrganStep, OnRemoveOrganCheck);
- SubSurgery<SurgeryAffixOrganStepComponent>(OnAffixOrganStep, OnAffixOrganCheck);
- SubSurgery<SurgeryAddMarkingStepComponent>(OnAddMarkingStep, OnAddMarkingCheck);
- SubSurgery<SurgeryRemoveMarkingStepComponent>(OnRemoveMarkingStep, OnRemoveMarkingCheck);
- SubSurgery<SurgeryAddOrganSlotStepComponent>(OnAddOrganSlotStep, OnAddOrganSlotCheck);
- Subs.BuiEvents<SurgeryTargetComponent>(SurgeryUIKey.Key, subs =>
- {
- subs.Event<SurgeryStepChosenBuiMsg>(OnSurgeryTargetStepChosen);
- });
- }
- private void SubSurgery<TComp>(EntityEventRefHandler<TComp, SurgeryStepEvent> onStep,
- EntityEventRefHandler<TComp, SurgeryStepCompleteCheckEvent> onComplete) where TComp : IComponent
- {
- SubscribeLocalEvent(onStep);
- SubscribeLocalEvent(onComplete);
- }
- #region Event Methods
- private void OnToolStep(Entity<SurgeryStepComponent> ent, ref SurgeryStepEvent args)
- {
- if(!TryToolAudio(ent, args))
- return;
- AddOrRemoveComponentsToEntity(args.Part, ent.Comp.Add);
- AddOrRemoveComponentsToEntity(args.Part, ent.Comp.Remove, true);
- AddOrRemoveComponentsToEntity(args.Body, ent.Comp.BodyAdd);
- AddOrRemoveComponentsToEntity(args.Body, ent.Comp.BodyRemove,true);
- // Dude this fucking function is so bloated now what the fuck.
- HandleOrganModification(args.Part, args.Body, ent.Comp.AddOrganOnAdd);
- HandleOrganModification(args.Part, args.Body, ent.Comp.RemoveOrganOnAdd, false);
- HandleSanitization(args);
- }
- private void OnToolCheck(Entity<SurgeryStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (TryToolCheck(ent.Comp.Add, args.Part) ||
- TryToolCheck(ent.Comp.Remove, args.Part, checkMissing: false) ||
- TryToolCheck(ent.Comp.BodyAdd, args.Body) ||
- TryToolCheck(ent.Comp.BodyRemove, args.Body, checkMissing: false))
- {
- args.Cancelled = true;
- return;
- }
- if (TryToolOrganCheck(ent.Comp.AddOrganOnAdd, args.Part)
- || TryToolOrganCheck(ent.Comp.RemoveOrganOnAdd, args.Part, checkMissing: false))
- args.Cancelled = true;
- }
- private void OnToolCanPerform(Entity<SurgeryStepComponent> ent, ref SurgeryCanPerformStepEvent args)
- {
- if (HasComp<SurgeryOperatingTableConditionComponent>(ent))
- {
- if (!TryComp(args.Body, out BuckleComponent? buckle) ||
- !HasComp<OperatingTableComponent>(buckle.BuckledTo))
- {
- args.Invalid = StepInvalidReason.NeedsOperatingTable;
- return;
- }
- }
- if (_inventory.TryGetContainerSlotEnumerator(args.Body, out var containerSlotEnumerator, args.TargetSlots))
- {
- if (HasComp<SurgeryIgnoreClothingComponent>(args.User))
- return;
- while (containerSlotEnumerator.MoveNext(out var containerSlot))
- {
- if (!containerSlot.ContainedEntity.HasValue)
- continue;
- args.Invalid = StepInvalidReason.Armor;
- args.Popup = Loc.GetString("surgery-ui-window-steps-error-armor");
- return;
- }
- }
- RaiseLocalEvent(args.Body, ref args);
- if (args.Invalid != StepInvalidReason.None)
- return;
- if (ent.Comp.Tool != null)
- {
- args.ValidTools ??= new Dictionary<EntityUid, float>();
- foreach (var reg in ent.Comp.Tool.Values)
- {
- if (!AnyHaveComp(args.Tools, reg.Component, out var tool, out var speed))
- {
- args.Invalid = StepInvalidReason.MissingTool;
- if (reg.Component is ISurgeryToolComponent required)
- args.Popup = $"You need {required.ToolName} to perform this step!";
- return;
- }
- args.ValidTools[tool] = speed;
- }
- }
- }
- private void OnTendWoundsStep(Entity<SurgeryTendWoundsEffectComponent> ent, ref SurgeryStepEvent args)
- {
- var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes;
- if (!HasDamageGroup(args.Body, group, out var damageable)
- && !HasDamageGroup(args.Part, group, out var _)
- || damageable == null) // This shouldnt be possible but the compiler doesn't shut up.
- return;
- // Right now the bonus is based off the body's total damage, maybe we could make it based off each part in the future.
- var bonus = ent.Comp.HealMultiplier * damageable.DamagePerGroup[ent.Comp.MainGroup];
- if (_mobState.IsDead(args.Body))
- bonus *= 0.2;
- var adjustedDamage = new DamageSpecifier(ent.Comp.Damage);
- foreach (var type in group)
- adjustedDamage.DamageDict[type] -= bonus;
- var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, adjustedDamage, 0.5f);
- RaiseLocalEvent(args.Body, ref ev);
- }
- private void OnTendWoundsCheck(Entity<SurgeryTendWoundsEffectComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- var group = ent.Comp.MainGroup == "Brute" ? BruteDamageTypes : BurnDamageTypes;
- if (HasDamageGroup(args.Body, group, out var _)
- || HasDamageGroup(args.Part, group, out var _))
- args.Cancelled = true;
- }
- /*private void OnCutLarvaRootsStep(Entity<SurgeryCutLarvaRootsStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (TryComp(args.Body, out VictimInfectedComponent? infected) &&
- infected.BurstAt > _timing.CurTime &&
- infected.SpawnedLarva == null)
- {
- infected.RootsCut = true;
- }
- }
- private void OnCutLarvaRootsCheck(Entity<SurgeryCutLarvaRootsStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Body, out VictimInfectedComponent? infected) || !infected.RootsCut)
- args.Cancelled = true;
- // The larva has fully developed and surgery is now impossible
- // TODO: Surgery should still be possible, but the fully developed larva should escape while also saving the hosts life
- if (infected != null && infected.SpawnedLarva != null)
- args.Cancelled = true;
- }*/
- private void OnCavityStep(Entity<SurgeryStepCavityEffectComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Part, out BodyPartComponent? partComp) || partComp.PartType != BodyPartType.Torso)
- return;
- var activeHandEntity = _hands.EnumerateHeld(args.User).FirstOrDefault();
- if (activeHandEntity != default
- && ent.Comp.Action == "Insert"
- && TryComp(activeHandEntity, out ItemComponent? itemComp)
- && (itemComp.Size.Id == "Tiny"
- || itemComp.Size.Id == "Small"))
- _itemSlotsSystem.TryInsert(ent, partComp.ItemInsertionSlot, activeHandEntity, args.User);
- else if (ent.Comp.Action == "Remove")
- _itemSlotsSystem.TryEjectToHands(ent, partComp.ItemInsertionSlot, args.User);
- }
- private void OnCavityCheck(Entity<SurgeryStepCavityEffectComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- // Normally this check would simply be partComp.ItemInsertionSlot.HasItem, but as mentioned before,
- // For whatever reason it's not instantiating the field on the clientside after the wizmerge.
- if (!TryComp(args.Part, out BodyPartComponent? partComp)
- || !TryComp(args.Part, out ItemSlotsComponent? itemComp)
- || ent.Comp.Action == "Insert"
- && !itemComp.Slots[partComp.ContainerName].HasItem
- || ent.Comp.Action == "Remove"
- && itemComp.Slots[partComp.ContainerName].HasItem)
- args.Cancelled = true;
- }
- private void OnAddPartStep(Entity<SurgeryAddPartStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
- return;
- foreach (var tool in args.Tools)
- {
- if (TryComp(tool, out BodyPartComponent? partComp)
- && partComp.PartType == removedComp.Part
- && (removedComp.Symmetry == null || partComp.Symmetry == removedComp.Symmetry))
- {
- var slotName = removedComp.Symmetry != null
- ? $"{removedComp.Symmetry?.ToString().ToLower()} {removedComp.Part.ToString().ToLower()}"
- : removedComp.Part.ToString().ToLower();
- _body.TryCreatePartSlot(args.Part, slotName, partComp.PartType, out var _);
- _body.AttachPart(args.Part, slotName, tool);
- EnsureComp<BodyPartReattachedComponent>(tool);
- var ev = new BodyPartAttachedEvent((tool, partComp));
- RaiseLocalEvent(args.Body, ref ev);
- }
- }
- }
- private void OnAddOrganSlotStep(Entity<SurgeryAddOrganSlotStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryOrganSlotConditionComponent? condition))
- return;
- _body.TryCreateOrganSlot(args.Part, condition.OrganSlot, out _);
- }
- private void OnAddOrganSlotCheck(Entity<SurgeryAddOrganSlotStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryOrganSlotConditionComponent? condition))
- return;
- args.Cancelled = !_body.CanInsertOrgan(args.Part, condition.OrganSlot);
- }
- private void OnAffixPartStep(Entity<SurgeryAffixPartStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
- return;
- var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault();
- if (targetPart != default)
- {
- // We reward players for properly affixing the parts by healing a little bit of damage, and enabling the part temporarily.
- var ev = new BodyPartEnableChangedEvent(true);
- RaiseLocalEvent(targetPart.Id, ref ev);
- _damageable.TryChangeDamage(args.Body,
- _body.GetHealingSpecifier(targetPart.Component) * 2,
- canSever: false, // Just in case we heal a brute damage specifier and the logic gets fucky lol
- targetPart: _body.GetTargetBodyPart(targetPart.Component.PartType, targetPart.Component.Symmetry));
- RemComp<BodyPartReattachedComponent>(targetPart.Id);
- }
- }
- private void OnAffixPartCheck(Entity<SurgeryAffixPartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp))
- return;
- var targetPart = _body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).FirstOrDefault();
- if (targetPart != default
- && HasComp<BodyPartReattachedComponent>(targetPart.Id))
- args.Cancelled = true;
- }
- private void OnAddPartCheck(Entity<SurgeryAddPartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryPartRemovedConditionComponent? removedComp)
- || !_body.GetBodyChildrenOfType(args.Body, removedComp.Part, symmetry: removedComp.Symmetry).Any())
- args.Cancelled = true;
- }
- private void OnRemovePartStep(Entity<SurgeryRemovePartStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Part, out BodyPartComponent? partComp)
- || partComp.Body != args.Body)
- return;
- var ev = new AmputateAttemptEvent(args.Part);
- RaiseLocalEvent(args.Part, ref ev);
- _hands.TryPickupAnyHand(args.User, args.Part);
- }
- private void OnRemovePartCheck(Entity<SurgeryRemovePartStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Part, out BodyPartComponent? partComp)
- || partComp.Body == args.Body)
- args.Cancelled = true;
- }
- private void OnAddOrganStep(Entity<SurgeryAddOrganStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Part, out BodyPartComponent? partComp)
- || partComp.Body != args.Body
- || !TryComp(args.Surgery, out SurgeryOrganConditionComponent? organComp)
- || organComp.Organ == null)
- return;
- // Adding organs is generally done for a single one at a time, so we only need to check for the first.
- var firstOrgan = organComp.Organ.Values.FirstOrDefault();
- if (firstOrgan == default)
- return;
- foreach (var tool in args.Tools)
- {
- if (HasComp(tool, firstOrgan.Component.GetType())
- && TryComp<OrganComponent>(tool, out var insertedOrgan)
- && _body.InsertOrgan(args.Part, tool, insertedOrgan.SlotId, partComp, insertedOrgan))
- {
- EnsureComp<OrganReattachedComponent>(tool);
- if (_body.TrySetOrganUsed(tool, true, insertedOrgan)
- && insertedOrgan.OriginalBody != args.Body)
- {
- var ev = new SurgeryStepDamageChangeEvent(args.User, args.Body, args.Part, ent);
- RaiseLocalEvent(ent, ref ev);
- args.Complete = true;
- }
- break;
- }
- }
- }
- private void OnAddOrganCheck(Entity<SurgeryAddOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
- || organComp.Organ is null
- || !TryComp(args.Part, out BodyPartComponent? partComp)
- || partComp.Body != args.Body)
- return;
- // For now we naively assume that every entity will only have one of each organ type.
- // that we do surgery on, but in the future we'll need to reference their prototype somehow
- // to know if they need 2 hearts, 2 lungs, etc.
- foreach (var reg in organComp.Organ.Values)
- {
- if (!_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var _))
- {
- args.Cancelled = true;
- }
- }
- }
- private void OnAffixOrganStep(Entity<SurgeryAffixOrganStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp)
- || removedOrganComp.Organ == null
- || !removedOrganComp.Reattaching)
- return;
- foreach (var reg in removedOrganComp.Organ.Values)
- {
- _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
- if (organs != null && organs.Count > 0)
- RemComp<OrganReattachedComponent>(organs[0].Id);
- }
- }
- private void OnAffixOrganCheck(Entity<SurgeryAffixOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp(args.Surgery, out SurgeryOrganConditionComponent? removedOrganComp)
- || removedOrganComp.Organ == null
- || !removedOrganComp.Reattaching)
- return;
- foreach (var reg in removedOrganComp.Organ.Values)
- {
- _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
- if (organs != null
- && organs.Count > 0
- && organs.Any(organ => HasComp<OrganReattachedComponent>(organ.Id)))
- args.Cancelled = true;
- }
- }
- private void OnRemoveOrganStep(Entity<SurgeryRemoveOrganStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
- || organComp.Organ == null)
- return;
- foreach (var reg in organComp.Organ.Values)
- {
- _body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs);
- if (organs != null && organs.Count > 0)
- {
- _body.RemoveOrgan(organs[0].Id, organs[0].Organ);
- _hands.TryPickupAnyHand(args.User, organs[0].Id);
- }
- }
- }
- private void OnRemoveOrganCheck(Entity<SurgeryRemoveOrganStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- if (!TryComp<SurgeryOrganConditionComponent>(args.Surgery, out var organComp)
- || organComp.Organ == null
- || !TryComp(args.Part, out BodyPartComponent? partComp)
- || partComp.Body != args.Body)
- return;
- foreach (var reg in organComp.Organ.Values)
- {
- if (_body.TryGetBodyPartOrgans(args.Part, reg.Component.GetType(), out var organs)
- && organs != null
- && organs.Count > 0)
- {
- args.Cancelled = true;
- return;
- }
- }
- }
- // TODO: Refactor bodies to include ears as a prototype instead of doing whatever the hell this is.
- private void OnAddMarkingStep(Entity<SurgeryAddMarkingStepComponent> ent, ref SurgeryStepEvent args)
- {
- if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
- || ent.Comp.Organ == null)
- return;
- var organType = ent.Comp.Organ.Values.FirstOrDefault();
- if (organType == default)
- return;
- var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
- foreach (var tool in args.Tools)
- {
- if (TryComp(tool, out MarkingContainerComponent? markingComp)
- && HasComp(tool, organType.Component.GetType()))
- {
- if (!bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
- || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString)))
- {
- EnsureComp<BodyPartAppearanceComponent>(args.Part);
- _body.ModifyMarkings(args.Body, args.Part, bodyAppearance, ent.Comp.MarkingCategory, markingComp.Marking);
- if (ent.Comp.Accent != null
- && ent.Comp.Accent.Values.FirstOrDefault() is { } accent)
- {
- var compType = accent.Component.GetType();
- if (!HasComp(args.Body, compType))
- AddComp(args.Body, _compFactory.GetComponent(compType));
- }
- QueueDel(tool); // Again since this isnt actually being inserted we just delete it lol.
- }
- }
- }
- }
- private void OnAddMarkingCheck(Entity<SurgeryAddMarkingStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- var markingCategory = MarkingCategoriesConversion.FromHumanoidVisualLayers(ent.Comp.MarkingCategory);
- if (!TryComp(args.Body, out HumanoidAppearanceComponent? bodyAppearance)
- || !bodyAppearance.MarkingSet.Markings.TryGetValue(markingCategory, out var markingList)
- || !markingList.Any(marking => marking.MarkingId.Contains(ent.Comp.MatchString)))
- args.Cancelled = true;
- }
- private void OnRemoveMarkingStep(Entity<SurgeryRemoveMarkingStepComponent> ent, ref SurgeryStepEvent args)
- {
- }
- private void OnRemoveMarkingCheck(Entity<SurgeryRemoveMarkingStepComponent> ent, ref SurgeryStepCompleteCheckEvent args)
- {
- }
- private void OnSurgeryTargetStepChosen(Entity<SurgeryTargetComponent> ent, ref SurgeryStepChosenBuiMsg args)
- {
- var user = args.Actor;
- if (GetEntity(args.Entity) is {} body &&
- GetEntity(args.Part) is {} targetPart)
- {
- TryDoSurgeryStep(body, targetPart, user, args.Surgery, args.Step);
- }
- }
- #endregion
- #region Helper Methods
- // I wonder if theres not a function that can do this already.
- private bool HasDamageGroup(EntityUid entity, string[] group, out DamageableComponent? damageable)
- {
- if (!TryComp<DamageableComponent>(entity, out var damageableComp))
- {
- damageable = null;
- return false;
- }
- damageable = damageableComp;
- return group.Any(damageType => damageableComp.Damage.DamageDict.TryGetValue(damageType, out var value) && value > 0);
- }
- private void HandleSanitization(SurgeryStepEvent args)
- {
- if (_inventory.TryGetSlotEntity(args.User, "gloves", out var _)
- && _inventory.TryGetSlotEntity(args.User, "mask", out var _))
- return;
- if (HasComp<SanitizedComponent>(args.User))
- return;
- var sepsis = new DamageSpecifier(_prototypes.Index<DamageTypePrototype>("Poison"), 5);
- var ev = new SurgeryStepDamageEvent(args.User, args.Body, args.Part, args.Surgery, sepsis, 0.5f);
- RaiseLocalEvent(args.Body, ref ev);
- }
- private bool TryToolAudio(Entity<SurgeryStepComponent> ent, SurgeryStepEvent args)
- {
- if (ent.Comp.Tool == null)
- return true;
- foreach (var reg in ent.Comp.Tool.Values)
- {
- if (!AnyHaveComp(args.Tools, reg.Component, out var tool, out _))
- return false;
- if (_net.IsServer &&
- TryComp(tool, out SurgeryToolComponent? toolComp) &&
- toolComp.EndSound != null)
- {
- _audio.PlayPvs(toolComp.EndSound, tool);
- }
- }
- return true;
- }
- private void HandleOrganModification(EntityUid organTarget,
- EntityUid bodyTarget,
- Dictionary<string, ComponentRegistry>? modifications,
- bool remove = false)
- {
- if (modifications == null)
- return;
- var organSlotIdToOrgan = _body.GetPartOrgans(organTarget).ToDictionary(o => o.Item2.SlotId, o => o);
- foreach (var (slotId, components) in modifications)
- {
- if (!organSlotIdToOrgan.TryGetValue(slotId, out var organValue))
- continue;
- var (organId, organ) = organValue;
- if (remove)
- {
- if (organValue.Item2.OnAdd == null || organ.OnAdd == null)
- continue;
- RaiseLocalEvent(organId, new OrganComponentsModifyEvent(bodyTarget, false));
- foreach (var key in components.Keys)
- organ.OnAdd.Remove(key);
- }
- else
- {
- organ.OnAdd ??= new ComponentRegistry();
- foreach (var (key, compToAdd) in components)
- organ.OnAdd[key] = compToAdd;
- EnsureComp<OrganEffectComponent>(organId);
- RaiseLocalEvent(organId, new OrganComponentsModifyEvent(bodyTarget, true));
- }
- }
- }
- private void AddOrRemoveComponentsToEntity(EntityUid ent, ComponentRegistry? componentRegistry, bool remove = false)
- {
- if(componentRegistry == null)
- return;
- foreach (var reg in componentRegistry.Values)
- {
- var compType = reg.Component.GetType();
- if (remove)
- RemComp(ent, compType);
- else
- {
- if (HasComp(ent, compType))
- continue;
- AddComp(ent, _compFactory.GetComponent(compType));
- }
- }
- }
- private bool TryToolCheck(ComponentRegistry? components, EntityUid target, bool checkMissing = true)
- {
- if (components == null)
- return false;
- foreach (var (key,entry) in components)
- {
- var hasComponent = HasComp(target, entry.Component.GetType());
- if (checkMissing != hasComponent)
- return true; // Early exit if condition fails
- }
- return false;
- }
- private bool TryToolOrganCheck(IReadOnlyDictionary<string, ComponentRegistry>? organChanges, EntityUid part, bool checkMissing = true)
- {
- if (organChanges == null)
- return false;
- var organSlotIdToOrgan = _body.GetPartOrgans(part).ToDictionary(o => o.Item2.SlotId, o => o.Item2);
- foreach (var (organSlotId, compsToAdd) in organChanges)
- {
- if (!organSlotIdToOrgan.TryGetValue(organSlotId, out var organ))
- continue;
- if (checkMissing)
- {
- if (organ.OnAdd == null || compsToAdd.Keys.Any(key => !organ.OnAdd.ContainsKey(key)))
- {
- return true;
- }
- }
- else
- {
- if (organ.OnAdd == null)
- continue;
- if (compsToAdd.Keys.Any(key => organ.OnAdd != null && organ.OnAdd.ContainsKey(key)))
- {
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- /// Do a surgery step on a part, if it can be done.
- /// Returns true if it succeeded.
- /// </summary>
- public bool TryDoSurgeryStep(EntityUid body, EntityUid targetPart, EntityUid user, EntProtoId surgeryId, EntProtoId stepId)
- {
- if (!IsSurgeryValid(body, targetPart, surgeryId, stepId, user, out var surgery, out var part, out var step))
- return false;
- if (!PreviousStepsComplete(body, part, surgery, stepId) ||
- IsStepComplete(body, part, stepId, surgery))
- return false;
- if (!CanPerformStep(user, body, part, step, true, out _, out _, out var validTools))
- return false;
- var speed = 1f;
- var usedEv = new SurgeryToolUsedEvent(user, body);
- // We need to check for nullability because of surgeries that dont require a tool, like Cavity Implants
- if (validTools?.Count > 0)
- {
- foreach (var (tool, toolSpeed) in validTools)
- {
- RaiseLocalEvent(tool, ref usedEv);
- if (usedEv.Cancelled)
- return false;
- speed *= toolSpeed;
- }
- if (_net.IsServer)
- {
- foreach (var tool in validTools.Keys)
- {
- if (TryComp(tool, out SurgeryToolComponent? toolComp) &&
- toolComp.StartSound != null)
- {
- _audio.PlayPvs(toolComp.StartSound, tool);
- }
- }
- }
- }
- if (TryComp(body, out TransformComponent? xform))
- _rotateToFace.TryFaceCoordinates(user, _transform.GetMapCoordinates(body, xform).Position);
- var ev = new SurgeryDoAfterEvent(surgeryId, stepId);
- // TODO: Move 2 seconds to a field of SurgeryStepComponent
- var duration = GetSurgeryDuration(step, user, body, speed);
- if (TryComp(user, out SurgerySpeedModifierComponent? surgerySpeedMod)
- && surgerySpeedMod is not null)
- duration = duration / surgerySpeedMod.SpeedModifier;
- var doAfter = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(duration), ev, body, part)
- {
- BreakOnMove = true,
- //BreakOnTargetMove = true, I fucking hate wizden dude.
- CancelDuplicate = true,
- DuplicateCondition = DuplicateConditions.SameEvent,
- NeedHand = true,
- BreakOnHandChange = true,
- };
- if (!_doAfter.TryStartDoAfter(doAfter))
- return false;
- var userName = Identity.Entity(user, EntityManager);
- var targetName = Identity.Entity(body, EntityManager);
- var locName = $"surgery-popup-procedure-{surgeryId}-step-{stepId}";
- var locResult = Loc.GetString(locName,
- ("user", userName), ("target", targetName), ("part", part));
- if (locResult == locName)
- locResult = Loc.GetString($"surgery-popup-step-{stepId}",
- ("user", userName), ("target", targetName), ("part", part));
- _popup.PopupEntity(locResult, user);
- return true;
- }
- private float GetSurgeryDuration(EntityUid surgeryStep, EntityUid user, EntityUid target, float toolSpeed)
- {
- if (!TryComp(surgeryStep, out SurgeryStepComponent? stepComp))
- return 2f; // Shouldnt really happen but just a failsafe.
- var speed = toolSpeed;
- if (TryComp(user, out SurgerySpeedModifierComponent? surgerySpeedMod))
- speed *= surgerySpeedMod.SpeedModifier;
- return stepComp.Duration / speed;
- }
- private (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, Entity<SurgeryComponent?> surgery, List<EntityUid> requirements)
- {
- if (!Resolve(surgery, ref surgery.Comp))
- return null;
- if (requirements.Contains(surgery))
- throw new ArgumentException($"Surgery {surgery} has a requirement loop: {string.Join(", ", requirements)}");
- requirements.Add(surgery);
- if (surgery.Comp.Requirement is { } requirementId &&
- GetSingleton(requirementId) is { } requirement &&
- GetNextStep(body, part, requirement, requirements) is { } requiredNext)
- {
- return requiredNext;
- }
- for (var i = 0; i < surgery.Comp.Steps.Count; i++)
- {
- var surgeryStep = surgery.Comp.Steps[i];
- if (!IsStepComplete(body, part, surgeryStep, surgery))
- return ((surgery, surgery.Comp), i);
- }
- return null;
- }
- public (Entity<SurgeryComponent> Surgery, int Step)? GetNextStep(EntityUid body, EntityUid part, EntityUid surgery)
- {
- return GetNextStep(body, part, surgery, new List<EntityUid>());
- }
- public bool PreviousStepsComplete(EntityUid body, EntityUid part, Entity<SurgeryComponent> surgery, EntProtoId step)
- {
- // TODO RMC14 use index instead of the prototype id
- if (surgery.Comp.Requirement is { } requirement)
- {
- if (GetSingleton(requirement) is not { } requiredEnt ||
- !TryComp(requiredEnt, out SurgeryComponent? requiredComp) ||
- !PreviousStepsComplete(body, part, (requiredEnt, requiredComp), step))
- {
- return false;
- }
- }
- foreach (var surgeryStep in surgery.Comp.Steps)
- {
- if (surgeryStep == step)
- break;
- if (!IsStepComplete(body, part, surgeryStep, surgery))
- return false;
- }
- return true;
- }
- public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part,
- EntityUid step, bool doPopup, out string? popup, out StepInvalidReason reason,
- out Dictionary<EntityUid, float>? validTools)
- {
- var type = BodyPartType.Other;
- if (TryComp(part, out BodyPartComponent? partComp))
- {
- type = partComp.PartType;
- }
- var slot = type switch
- {
- BodyPartType.Head => SlotFlags.HEAD,
- BodyPartType.Torso => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
- BodyPartType.Arm => SlotFlags.OUTERCLOTHING | SlotFlags.INNERCLOTHING,
- BodyPartType.Hand => SlotFlags.GLOVES,
- BodyPartType.Leg => SlotFlags.OUTERCLOTHING | SlotFlags.LEGS,
- BodyPartType.Foot => SlotFlags.FEET,
- BodyPartType.Tail => SlotFlags.NONE,
- BodyPartType.Other => SlotFlags.NONE,
- _ => SlotFlags.NONE
- };
- var check = new SurgeryCanPerformStepEvent(user, body, GetTools(user), slot);
- RaiseLocalEvent(step, ref check);
- popup = check.Popup;
- validTools = check.ValidTools;
- if (check.Invalid != StepInvalidReason.None)
- {
- if (doPopup && check.Popup != null)
- _popup.PopupEntity(check.Popup, user, user, PopupType.SmallCaution);
- reason = check.Invalid;
- return false;
- }
- reason = default;
- return true;
- }
- public bool CanPerformStep(EntityUid user, EntityUid body, EntityUid part, EntityUid step, bool doPopup)
- {
- return CanPerformStep(user, body, part, step, doPopup, out _, out _, out _);
- }
- public bool IsStepComplete(EntityUid body, EntityUid part, EntProtoId step, EntityUid surgery)
- {
- if (GetSingleton(step) is not { } stepEnt)
- return false;
- var ev = new SurgeryStepCompleteCheckEvent(body, part, surgery);
- RaiseLocalEvent(stepEnt, ref ev);
- return !ev.Cancelled;
- }
- private bool AnyHaveComp(List<EntityUid> tools, IComponent component, out EntityUid withComp, out float speed)
- {
- foreach (var tool in tools)
- {
- if (EntityManager.TryGetComponent(tool, component.GetType(), out var found) && found is ISurgeryToolComponent toolComp)
- {
- withComp = tool;
- speed = toolComp.Speed;
- return true;
- }
- }
- withComp = EntityUid.Invalid;
- speed = 1f;
- return false;
- }
- #endregion
- }
|