using Content.Server.Light.Components; using Content.Server.Nutrition.Components; using Content.Server.Objectives.Components; using Content.Server.Radio.Components; using Content.Shared.Changeling; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components.SolutionManager; using Content.Shared.Chemistry.Reagent; using Content.Shared.Cuffs.Components; using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Damage.Prototypes; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.IdentityManagement; using Content.Shared.Mobs; using Content.Shared.Store.Components; using Content.Shared.Popups; using Robust.Shared.Prototypes; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Stealth.Components; using Content.Shared._Goobstation.Weapons.AmmoSelector; using Content.Shared.Actions; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Overlays.Switchable; using Robust.Shared.Utility; using Robust.Shared.Physics.Components; using Content.Shared.Mobs.Systems; using Content.Server.Chat; using Content.Shared.Chat; using Content.Server.GameTicking; namespace Content.Server.Changeling; public sealed partial class ChangelingSystem { public void SubscribeAbilities() { SubscribeLocalEvent(OnOpenEvolutionMenu); SubscribeLocalEvent(OnAbsorb); // WD EDIT START SubscribeLocalEvent(OnFirstAbsorbDoAfter); SubscribeLocalEvent(OnSecondAbsorbDoAfter); SubscribeLocalEvent(OnThirdAbsorbDoAfter); // WD EDIT END SubscribeLocalEvent(OnInfect); SubscribeLocalEvent(OnInfectDoAfter); SubscribeLocalEvent(OnStingExtractDNA); SubscribeLocalEvent(OnTransformCycle); SubscribeLocalEvent(OnTransform); SubscribeLocalEvent(OnEnterStasis); SubscribeLocalEvent(OnGhostAttempt); // WWDP EDIT SubscribeLocalEvent(OnExitStasis); SubscribeLocalEvent(OnToggleArmblade); SubscribeLocalEvent(OnToggleHammer); SubscribeLocalEvent(OnToggleClaw); SubscribeLocalEvent(OnToggleDartGun); SubscribeLocalEvent(OnCreateBoneShard); SubscribeLocalEvent(OnToggleArmor); SubscribeLocalEvent(OnToggleShield); SubscribeLocalEvent(OnShriekDissonant); SubscribeLocalEvent(OnShriekResonant); SubscribeLocalEvent(OnToggleStrainedMuscles); SubscribeLocalEvent(OnStingReagent); SubscribeLocalEvent(OnStingTransform); SubscribeLocalEvent(OnStingFakeArmblade); SubscribeLocalEvent(OnLayEgg); SubscribeLocalEvent(OnAnatomicPanacea); SubscribeLocalEvent(OnBiodegrade); SubscribeLocalEvent(OnChameleonSkin); SubscribeLocalEvent(OnEphedrineOverdose); SubscribeLocalEvent(OnHealUltraSwag); SubscribeLocalEvent(OnLastResort); SubscribeLocalEvent(OnLesserForm); SubscribeLocalEvent(OnSpacesuit); SubscribeLocalEvent(OnHivemindAccess); SubscribeLocalEvent(OnAbsorbBiomatter); SubscribeLocalEvent(OnAbsorbBiomatterDoAfter); } #region Basic Abilities private void OnOpenEvolutionMenu(EntityUid uid, ChangelingComponent comp, ref OpenEvolutionMenuEvent args) { if (!TryComp(uid, out StoreComponent? store)) return; _store.ToggleUi(uid, uid, store); } private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEvent args) { var target = args.Target; if (!IsIncapacitated(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailIncapacitated), uid, uid); return; } if (HasComp(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailAbsorbed), uid, uid); return; } if (!HasComp(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailUnabsorbable), uid, uid); return; } if (TryComp(target, out var pullable)) // Agressive grab check { if (pullable.GrabStage <= GrabStage.Soft) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailNoGrab), uid, uid); return; } } if (!TryUseAbility(uid, comp, args)) return; _popup.PopupEntity(Loc.GetString(comp.AbsorbStart), uid, uid); // WD EDIT //PlayMeatySound(uid, comp); // WWDP EDIT var dargs = new DoAfterArgs(EntityManager, uid, comp.AbsorbTime, new AbsorbDNADoAfterFirstEvent(), uid, target) { DistanceThreshold = 1.5f, BreakOnDamage = false, // WWDP EDIT BreakOnHandChange = false, BreakOnMove = true, BreakOnWeightlessMove = true, AttemptFrequency = AttemptFrequency.StartAndEnd, MultiplyDelay = false, }; _doAfter.TryStartDoAfter(dargs); } /// /// This number is based on the "Average Human" constant mass, with the desired target that succ'ing an average human // should give the same number of evolution points as before (2 points). /// private const float SuccMassRatio = 30f; /// /// This number is based on the "Average Human" constant mass, with the desired target that succ'ing an average human /// should give the same number of chemicals as before (7 points). /// private const float SuccChemicalsRatio = 7f; // WWDP EDIT START // todo: consider moving this bullshit to a whole another system just for handling chained doafters like these. private void OnFirstAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterFirstEvent args) { if (args.Cancelled || args.Args.Target is not EntityUid target || !_proto.TryIndex(comp.AbsorbedDamageType, out var damageProto)) return; if (!IsIncapacitated(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailIncapacitated), uid, uid); return; } if (HasComp(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailAbsorbed), uid, uid); return; } var dargs = new DoAfterArgs(EntityManager, uid, comp.AbsorbTime, new AbsorbDNADoAfterSecondEvent(), uid, target) { DistanceThreshold = 1.5f, BreakOnDamage = false, BreakOnHandChange = false, BreakOnMove = true, BreakOnWeightlessMove = true, AttemptFrequency = AttemptFrequency.StartAndEnd, MultiplyDelay = false, }; var popupOthers = Loc.GetString(comp.AbsorbPopupFirst, ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOthers, uid, comp.AbsorbPopupType); _doAfter.TryStartDoAfter(dargs); } private void OnSecondAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterSecondEvent args) { if (args.Cancelled || args.Args.Target is not EntityUid target || !_proto.TryIndex(comp.AbsorbedDamageType, out var damageProto)) return; if (!IsIncapacitated(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailIncapacitated), uid, uid); return; } if (HasComp(target)) { _popup.PopupEntity(Loc.GetString(comp.AbsorbFailAbsorbed), uid, uid); return; } var dargs = new DoAfterArgs(EntityManager, uid, comp.AbsorbTime, new AbsorbDNADoAfterThirdEvent(), uid, target) { DistanceThreshold = 1.5f, BreakOnDamage = false, BreakOnHandChange = false, BreakOnMove = true, BreakOnWeightlessMove = true, AttemptFrequency = AttemptFrequency.StartAndEnd, MultiplyDelay = false, }; var popupOthers = Loc.GetString(comp.AbsorbPopupSecond, ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOthers, uid, comp.AbsorbPopupType); _doAfter.TryStartDoAfter(dargs); } // WWDP EDIT END private void OnThirdAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterThirdEvent args) // WWDP EDIT { if (args.Args.Target is null || !_proto.TryIndex(comp.AbsorbedDamageType, out var damageProto)) return; var target = args.Args.Target.Value; if (args.Cancelled || !IsIncapacitated(target) || HasComp(target) || !TryComp(target, out DamageableComponent? damageable) || !TryComp(target, out PhysicsComponent? physicsComponent) || physicsComponent.Mass <= 0) return; if (!_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var deadThreshold) || deadThreshold is null || deadThreshold <= 0) { DebugTools.Assert($"entity {MetaData(target).EntityPrototype} has an Absorbable component, but does not also have a dead threshold. Double check if it's intended or not that changelings can SUCC them. Are they a robot?"); return; } var dmg = new DamageSpecifier(damageProto, deadThreshold!.Value.Int()); var dmgTotal = _damage.TryChangeDamage(target, dmg, false, damageable: damageable, origin: uid); if (dmgTotal is null || !dmgTotal.AnyPositive()) return; _blood.ChangeBloodReagent(target, comp.AbsorbedBloodReagent); _blood.SpillAllSolutions(target); PlayMeatySound(args.User, comp); EnsureComp(target); var popup = Loc.GetString("changeling-absorb-end-self-ling"); var bonusChemicals = 0f; var bonusEvolutionPoints = 0f; var massToSucc = Math.Max((int) (physicsComponent.Mass / SuccMassRatio), 1); // WOE UPON THE CREW IF A CHANGELING SUCCS A LAMIA. if (TryComp(target, out var targetComp)) { bonusChemicals += targetComp.MaxChemicals / 2; bonusEvolutionPoints += targetComp.TotalEvolutionPoints; // SURVIVAL OF THE FITTEST. comp.TotalEvolutionPoints += targetComp.TotalEvolutionPoints + massToSucc; } else { popup = Loc.GetString("changeling-absorb-end-self"); bonusChemicals += physicsComponent.Mass / SuccChemicalsRatio; bonusEvolutionPoints += massToSucc; comp.TotalEvolutionPoints += massToSucc; } TryStealDNA(uid, target, comp, true); comp.TotalAbsorbedEntities++; // WD EDIT START var popupOthers = Loc.GetString(comp.AbsorbPopupThird, ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOthers, uid, comp.AbsorbPopupType); // WD EDIT END _popup.PopupEntity(popup, args.User, args.User); comp.MaxChemicals += bonusChemicals; if (TryComp(args.User, out var store)) { _store.TryAddCurrency(new Dictionary { { "EvolutionPoint", bonusEvolutionPoints } }, args.User, store); _store.UpdateUserInterface(args.User, args.User, store); } if (_mind.TryGetMind(uid, out var mindId, out var mind)) if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) objective.Absorbed += 1; } private void OnInfect(EntityUid uid, ChangelingComponent comp, ref ChangelingInfectTargetEvent args) { var target = args.Target; if (!IsIncapacitated(target)) { _popup.PopupEntity(Loc.GetString("changeling-convert-fail-incapacitated"), uid, uid); return; } if (HasComp(target)) { _popup.PopupEntity(Loc.GetString("changeling-convert-fail-already"), uid, uid); return; } if (!HasComp(target)) { _popup.PopupEntity(Loc.GetString("changeling-convert-fail-incompatible"), uid, uid); return; } if (!TryUseAbility(uid, comp, args)) return; var popupOthers = Loc.GetString("changeling-convert-start", ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOthers, uid, PopupType.LargeCaution); PlayMeatySound(uid, comp); var dargs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(30), new ChangelingInfectTargetDoAfterEvent(), uid, target) { DistanceThreshold = 1.5f, BreakOnDamage = true, BreakOnHandChange = false, BreakOnMove = true, BreakOnWeightlessMove = true, AttemptFrequency = AttemptFrequency.StartAndEnd }; _doAfter.TryStartDoAfter(dargs); } private void OnInfectDoAfter(EntityUid uid, ChangelingComponent comp, ref ChangelingInfectTargetDoAfterEvent args) { if (args.Args.Target == null) return; var target = args.Args.Target.Value; if (args.Cancelled || !IsIncapacitated(target) || HasComp(target)) return; if (TryComp(target, out var targetComp)) { var popupOther = Loc.GetString("changeling-convert-end-immune", ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOther, args.User, args.User, PopupType.LargeCaution); return; } PlayMeatySound(args.User, comp); EnsureComp(target); var popup = Loc.GetString("changeling-convert-end", ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popup, args.User, args.User, PopupType.Medium); var popupTwo = Loc.GetString("changeling-convert-end-warning", ("user", Identity.Entity(uid, EntityManager))); _popup.PopupEntity(popupTwo, target, target, PopupType.LargeCaution); } public List> BiomassAbsorbedChemicals = new() { "Nutriment", "Protein", "UncookedAnimalProteins", "Fat" }; // fat so absorbing raw meat good private void OnAbsorbBiomatter(EntityUid uid, ChangelingComponent comp, ref AbsorbBiomatterEvent args) { var target = args.Target; if (!TryUseAbility(uid, comp, args)) return; if (!TryComp(target, out var food)) return; if (!TryComp(target, out var solMan)) return; var totalFood = FixedPoint2.New(0); foreach (var (_, sol) in _solution.EnumerateSolutions((target, solMan))) foreach (var proto in BiomassAbsorbedChemicals) totalFood += sol.Comp.Solution.GetTotalPrototypeQuantity(proto); if (food.RequiresSpecialDigestion || totalFood == 0) // no eating winter coats or food that won't give you anything { var popup = Loc.GetString("changeling-absorbbiomatter-bad-food"); _popup.PopupEntity(popup, uid, uid); return; } var popupOthers = Loc.GetString("changeling-absorbbiomatter-start", ("user", Identity.Entity(uid, EntityManager))); _popup.PopupEntity(popupOthers, uid, PopupType.MediumCaution); PlayMeatySound(uid, comp); // so you can't just instantly mukbang a bag of food mid-combat, 2.7s for raw meat var dargs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(totalFood.Float() * 0.15f), new AbsorbBiomatterDoAfterEvent(), uid, target) { DistanceThreshold = 1.5f, BreakOnDamage = true, BreakOnHandChange = false, BreakOnMove = true, BreakOnWeightlessMove = true, DuplicateCondition = DuplicateConditions.SameEvent, AttemptFrequency = AttemptFrequency.StartAndEnd }; _doAfter.TryStartDoAfter(dargs); } private void OnAbsorbBiomatterDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbBiomatterDoAfterEvent args) { if (args.Args.Target == null) return; var target = args.Args.Target.Value; if (args.Cancelled) return; if (!TryComp(target, out var solMan)) return; var totalFood = FixedPoint2.New(0); foreach (var (name, sol) in _solution.EnumerateSolutions((target, solMan))) { var solution = sol.Comp.Solution; foreach (var proto in BiomassAbsorbedChemicals) { var quant = solution.GetTotalPrototypeQuantity(proto); totalFood += quant; solution.RemoveReagent(proto, quant); } _puddle.TrySpillAt(target, solution, out var _); } UpdateChemicals(uid, comp, totalFood.Float() * 2); // 36 for raw meat QueueDel(target); // eaten } private void OnStingExtractDNA(EntityUid uid, ChangelingComponent comp, ref StingExtractDNAEvent args) { if (!TrySting(uid, comp, args, true)) return; var target = args.Target; if (!TryStealDNA(uid, target, comp, true)) { _popup.PopupEntity(Loc.GetString("changeling-sting-extract-fail"), uid, uid); // royal cashback comp.Chemicals += Comp(args.Action).ChemicalCost; } else _popup.PopupEntity(Loc.GetString("changeling-sting", ("target", Identity.Entity(target, EntityManager))), uid, uid); } private void OnTransformCycle(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformCycleEvent args) { comp.AbsorbedDNAIndex += 1; if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count) comp.AbsorbedDNAIndex = 0; if (comp.AbsorbedDNA.Count == 0) { _popup.PopupEntity(Loc.GetString("changeling-transform-cycle-empty"), uid, uid); return; } var selected = comp.AbsorbedDNA.ToArray()[comp.AbsorbedDNAIndex]; comp.SelectedForm = selected; _popup.PopupEntity(Loc.GetString("changeling-transform-cycle", ("target", selected.Name)), uid, uid); } private void OnTransform(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformEvent args) { if (!TryUseAbility(uid, comp, args)) return; if (!TryTransform(uid, comp)) comp.Chemicals += Comp(args.Action).ChemicalCost; } private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterStasisEvent args) { if (comp.IsInStasis || HasComp(uid) || !TryComp(uid, out var damageable)) // WWDP EDIT { _popup.PopupEntity(Loc.GetString("changeling-stasis-enter-fail"), uid, uid); return; } if (!TryUseAbility(uid, comp, args)) return; comp.Chemicals = 0f; if (_mobState.IsAlive(uid)) { // fake our death var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", uid)); _popup.PopupEntity(othersMessage, uid, Robust.Shared.Player.Filter.PvsExcept(uid), true); var selfMessage = Loc.GetString("changeling-stasis-enter"); _popup.PopupEntity(selfMessage, uid, uid); } comp.IsInStasis = true; _suicide.ApplyLethalDamage(new(uid, damageable), "Asphyxiation"); // WWDP EDIT } // WWDP EDIT START private void OnGhostAttempt(GhostAttemptHandleEvent args) { if(TryComp(args.Mind.CurrentEntity, out var comp) && comp.IsInStasis) { args.Handled = true; args.Result = false; } } // WWDP EDIT END private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasisEvent args) { if (!comp.IsInStasis) { _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail"), uid, uid); return; } if (HasComp(uid)) { _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail-dead"), uid, uid); return; } if (!TryUseAbility(uid, comp, args)) return; if (!TryComp(uid, out var damageable)) return; // heal of everything _rejuv.PerformRejuvenate(uid); _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); comp.IsInStasis = false; } #endregion #region Combat Abilities private void OnToggleArmblade(EntityUid uid, ChangelingComponent comp, ref ToggleArmbladeEvent args) { if (!TryUseAbility(uid, comp, args, GetEquipmentChemCostOverride(comp, ArmbladePrototype))) return; if (!TryToggleItem(uid, ArmbladePrototype, comp, out _)) return; PlayMeatySound(uid, comp); } private void OnToggleHammer(EntityUid uid, ChangelingComponent comp, ref ToggleArmHammerEvent args) { if (!TryUseAbility(uid, comp, args, GetEquipmentChemCostOverride(comp, HammerPrototype))) return; if (!TryToggleItem(uid, HammerPrototype, comp, out _)) return; PlayMeatySound(uid, comp); } private void OnToggleClaw(EntityUid uid, ChangelingComponent comp, ref ToggleArmClawEvent args) { if (!TryUseAbility(uid, comp, args, GetEquipmentChemCostOverride(comp, ClawPrototype))) return; if (!TryToggleItem(uid, ClawPrototype, comp, out _)) return; PlayMeatySound(uid, comp); } private void OnToggleDartGun(EntityUid uid, ChangelingComponent comp, ref ToggleDartGunEvent args) { var chemCostOverride = GetEquipmentChemCostOverride(comp, DartGunPrototype); if (!TryUseAbility(uid, comp, args, chemCostOverride)) return; if (!TryToggleItem(uid, DartGunPrototype, comp, out var dartgun)) return; if (!TryComp(dartgun, out AmmoSelectorComponent? ammoSelector)) { PlayMeatySound(uid, comp); return; } if (!_mind.TryGetMind(uid, out var mindId, out _) || !TryComp(mindId, out ActionsContainerComponent? container)) return; var setProto = false; foreach (var ability in container.Container.ContainedEntities) { if (!TryComp(ability, out ChangelingReagentStingComponent? sting) || sting.DartGunAmmo == null) continue; ammoSelector.Prototypes.Add(sting.DartGunAmmo.Value); if (setProto) continue; _selectableAmmo.TrySetProto((dartgun.Value, ammoSelector), sting.DartGunAmmo.Value); setProto = true; } if (ammoSelector.Prototypes.Count == 0) { comp.Chemicals += chemCostOverride ?? Comp(args.Action).ChemicalCost; _popup.PopupEntity(Loc.GetString("changeling-dartgun-no-stings"), uid, uid); comp.Equipment.Remove(DartGunPrototype); QueueDel(dartgun.Value); return; } Dirty(dartgun.Value, ammoSelector); PlayMeatySound(uid, comp); } private void OnCreateBoneShard(EntityUid uid, ChangelingComponent comp, ref CreateBoneShardEvent args) { if (!TryUseAbility(uid, comp, args)) return; var star = Spawn(BoneShardPrototype, Transform(uid).Coordinates); _hands.TryPickupAnyHand(uid, star); PlayMeatySound(uid, comp); } private void OnToggleArmor(EntityUid uid, ChangelingComponent comp, ref ToggleChitinousArmorEvent args) { float? chemCostOverride = comp.ActiveArmor == null ? null : 0f; if (!TryUseAbility(uid, comp, args, chemCostOverride)) return; if (!TryToggleArmor(uid, comp, [(ArmorHelmetPrototype, "head"), (ArmorPrototype, "outerClothing")])) { _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); comp.Chemicals += chemCostOverride ?? Comp(args.Action).ChemicalCost; return; } PlayMeatySound(uid, comp); } private void OnToggleShield(EntityUid uid, ChangelingComponent comp, ref ToggleOrganicShieldEvent args) { if (!TryUseAbility(uid, comp, args, GetEquipmentChemCostOverride(comp, ShieldPrototype))) return; if (!TryToggleItem(uid, ShieldPrototype, comp, out _)) return; PlayMeatySound(uid, comp); } private void OnShriekDissonant(EntityUid uid, ChangelingComponent comp, ref ShriekDissonantEvent args) { if (!TryUseAbility(uid, comp, args)) return; DoScreech(uid, comp); var pos = _transform.GetMapCoordinates(uid); var power = comp.ShriekPower; _emp.EmpPulse(pos, power, 5000f, power * 2); } private void OnShriekResonant(EntityUid uid, ChangelingComponent comp, ref ShriekResonantEvent args) { if (!TryUseAbility(uid, comp, args)) return; DoScreech(uid, comp); var power = comp.ShriekPower; _flash.FlashArea(uid, uid, power, power * 2f * 1000f); var lookup = _lookup.GetEntitiesInRange(uid, power); var lights = GetEntityQuery(); foreach (var ent in lookup) if (lights.HasComponent(ent)) _light.TryDestroyBulb(ent); } private void OnToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp, ref ToggleStrainedMusclesEvent args) { if (!TryUseAbility(uid, comp, args)) return; ToggleStrainedMuscles(uid, comp); } private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) { if (!comp.StrainedMusclesActive) { _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); comp.StrainedMusclesActive = true; } else { _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); comp.StrainedMusclesActive = false; } PlayMeatySound(uid, comp); _speed.RefreshMovementSpeedModifiers(uid); } #endregion #region Stings private void OnStingReagent(EntityUid uid, ChangelingComponent comp, StingReagentEvent args) { TryReagentSting(uid, comp, args); } private void OnStingTransform(EntityUid uid, ChangelingComponent comp, ref StingTransformEvent args) { if (!TrySting(uid, comp, args, true)) return; var target = args.Target; if (!TryTransform(target, comp, true, true)) comp.Chemicals += Comp(args.Action).ChemicalCost; } private void OnStingFakeArmblade(EntityUid uid, ChangelingComponent comp, ref StingFakeArmbladeEvent args) { if (!TrySting(uid, comp, args)) return; var target = args.Target; var fakeArmblade = EntityManager.SpawnEntity(FakeArmbladePrototype, Transform(target).Coordinates); if (!_hands.TryPickupAnyHand(target, fakeArmblade)) { QueueDel(fakeArmblade); comp.Chemicals += Comp(args.Action).ChemicalCost; _popup.PopupEntity(Loc.GetString("changeling-sting-fail-simplemob"), uid, uid); return; } PlayMeatySound(target, comp); } public void OnLayEgg(EntityUid uid, ChangelingComponent comp, ref StingLayEggsEvent args) { var target = args.Target; if (!_proto.TryIndex(comp.AbsorbedDamageType, out var damageProto) || !TryComp(target, out DamageableComponent? damageable)) return; if (!_mobState.IsDead(target)) { _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-incapacitated"), uid, uid); return; } if (HasComp(target)) { _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-absorbed"), uid, uid); return; } if (!HasComp(target)) { _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-unabsorbable"), uid, uid); return; } if (!_mobThreshold.TryGetThresholdForState(target, MobState.Dead, out var deadThreshold) || deadThreshold is null || deadThreshold <= 0) { DebugTools.Assert($"entity {MetaData(target).EntityPrototype} has an Absorbable component, but does not also have a dead threshold. Double check if it's intended or not that changelings can SUCC them. Are they a robot?"); return; } var mind = _mind.GetMind(uid); if (mind == null) return; if (!TryComp(uid, out var storeComp)) return; var dmg = new DamageSpecifier(damageProto, deadThreshold!.Value.Int()); var dmgTotal = _damage.TryChangeDamage(target, dmg, false, damageable: damageable, origin: uid); if (dmgTotal is null || !dmgTotal.AnyPositive()) return; comp.IsInLastResort = false; comp.IsInLesserForm = true; var eggComp = EnsureComp(target); eggComp.LingComp = comp; eggComp.LingMind = (EntityUid) mind; eggComp.LingStore = _serialization.CreateCopy(storeComp, notNullableOverride: true); eggComp.AugmentedEyesightPurchased = HasComp(uid); EnsureComp(target); _blood.ChangeBloodReagent(target, comp.AbsorbedBloodReagent); _blood.SpillAllSolutions(target); PlayMeatySound(uid, comp); _bodySystem.GibBody(uid); } #endregion #region Utilities public void OnAnatomicPanacea(EntityUid uid, ChangelingComponent comp, ref ActionAnatomicPanaceaEvent args) { if (!TryUseAbility(uid, comp, args)) return; var reagents = new Dictionary { { "LingPanacea", 10f }, }; if (TryInjectReagents(uid, reagents)) _popup.PopupEntity(Loc.GetString("changeling-panacea"), uid, uid); else return; PlayMeatySound(uid, comp); } public void OnBiodegrade(EntityUid uid, ChangelingComponent comp, ref ActionBiodegradeEvent args) { if (!TryUseAbility(uid, comp, args)) return; if (TryComp(uid, out var cuffs) && cuffs.Container.ContainedEntities.Count > 0) { var cuff = cuffs.LastAddedCuffs; _cuffs.Uncuff(uid, cuffs.LastAddedCuffs, cuff); QueueDel(cuff); } var soln = new Solution(); soln.AddReagent("PolytrinicAcid", 10f); if (_pull.IsPulled(uid)) { var puller = Comp(uid).Puller; if (puller != null) { _puddle.TrySplashSpillAt((EntityUid) puller, Transform((EntityUid) puller).Coordinates, soln, out _); return; } } _puddle.TrySplashSpillAt(uid, Transform(uid).Coordinates, soln, out _); } public void OnChameleonSkin(EntityUid uid, ChangelingComponent comp, ref ActionChameleonSkinEvent args) { if (!TryUseAbility(uid, comp, args)) return; if (HasComp(uid) && HasComp(uid)) { RemComp(uid); RemComp(uid); _popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid); return; } EnsureComp(uid); EnsureComp(uid); _popup.PopupEntity(Loc.GetString("changeling-chameleon-start"), uid, uid); } public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref ActionEphedrineOverdoseEvent args) { if (!TryUseAbility(uid, comp, args)) return; var stam = EnsureComp(uid); stam.StaminaDamage = 0; var reagents = new Dictionary { { "Desoxyephedrine", 5f } }; if (TryInjectReagents(uid, reagents)) _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); else { _popup.PopupEntity(Loc.GetString("changeling-inject-fail"), uid, uid); return; } } // john space made me do this public void OnHealUltraSwag(EntityUid uid, ChangelingComponent comp, ref ActionFleshmendEvent args) { if (!TryUseAbility(uid, comp, args)) return; var reagents = new Dictionary { { "LingFleshmend", 10f }, }; if (TryInjectReagents(uid, reagents)) _popup.PopupEntity(Loc.GetString("changeling-fleshmend"), uid, uid); else return; PlayMeatySound(uid, comp); } public void OnLastResort(EntityUid uid, ChangelingComponent comp, ref ActionLastResortEvent args) { if (!TryUseAbility(uid, comp, args)) return; comp.IsInLastResort = true; var newUid = TransformEntity( uid, protoId: "Headslug", // WD EDIT comp: comp, dropInventory: true, transferDamage: false); if (newUid == null) { comp.IsInLastResort = false; comp.Chemicals += Comp(args.Action).ChemicalCost; return; } _explosionSystem.QueueExplosion( (EntityUid) newUid, typeId: "Default", totalIntensity: 1, slope: 4, maxTileIntensity: 2); _actions.AddAction((EntityUid) newUid, "ActionLayEgg"); PlayMeatySound((EntityUid) newUid, comp); } public void OnLesserForm(EntityUid uid, ChangelingComponent comp, ref ActionLesserFormEvent args) { if (!TryUseAbility(uid, comp, args)) return; comp.IsInLesserForm = true; var newUid = TransformEntity(uid, protoId: "MobMonkey", comp: comp); if (newUid == null) { comp.IsInLesserForm = false; comp.Chemicals += Comp(args.Action).ChemicalCost; return; } PlayMeatySound((EntityUid) newUid, comp); var loc = Loc.GetString("changeling-transform-others", ("user", Identity.Entity((EntityUid) newUid, EntityManager))); _popup.PopupEntity(loc, (EntityUid) newUid, PopupType.LargeCaution); } public void OnSpacesuit(EntityUid uid, ChangelingComponent comp, ref ActionSpacesuitEvent args) { if (!TryUseAbility(uid, comp, args)) return; if (!TryToggleArmor(uid, comp, [(SpacesuitHelmetPrototype, "head"), (SpacesuitPrototype, "outerClothing")])) { _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); comp.Chemicals += Comp(args.Action).ChemicalCost; return; } PlayMeatySound(uid, comp); } public void OnHivemindAccess(EntityUid uid, ChangelingComponent comp, ref ActionHivemindAccessEvent args) { if (!TryUseAbility(uid, comp, args)) return; if (HasComp(uid)) { _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); return; } EnsureComp(uid); var reciever = EnsureComp(uid); var transmitter = EnsureComp(uid); var radio = EnsureComp(uid); radio.Channels = new() { "Hivemind" }; transmitter.Channels = new() { "Hivemind" }; _popup.PopupEntity(Loc.GetString("changeling-hivemind-start"), uid, uid); } #endregion }