Files
wwdpublic/Content.Server/_Goobstation/Changeling/ChangelingSystem.Abilities.cs
Spatison 54086988e3 Mass clean up (#587)
* mass clean up

(cherry picked from commit 12bb873b02c1ef50e20763542b030452cc0613da)

* Revert "Centrifuge buff (#393)"

This reverts commit 2a59a18230.

(cherry picked from commit 9ee495ab4bb365e1ccd3dc627ecb55114fea6944)

* Shoving merge conflict

* fix rich traitor

* fix test

* yml

* fix test

* fix test

* ohh
2025-06-16 20:35:48 +03:00

976 lines
38 KiB
C#

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<ChangelingComponent, OpenEvolutionMenuEvent>(OnOpenEvolutionMenu);
SubscribeLocalEvent<ChangelingComponent, AbsorbDNAEvent>(OnAbsorb);
// WD EDIT START
SubscribeLocalEvent<ChangelingComponent, AbsorbDNADoAfterFirstEvent>(OnFirstAbsorbDoAfter);
SubscribeLocalEvent<ChangelingComponent, AbsorbDNADoAfterSecondEvent>(OnSecondAbsorbDoAfter);
SubscribeLocalEvent<ChangelingComponent, AbsorbDNADoAfterThirdEvent>(OnThirdAbsorbDoAfter);
// WD EDIT END
SubscribeLocalEvent<ChangelingComponent, ChangelingInfectTargetEvent>(OnInfect);
SubscribeLocalEvent<ChangelingComponent, ChangelingInfectTargetDoAfterEvent>(OnInfectDoAfter);
SubscribeLocalEvent<ChangelingComponent, StingExtractDNAEvent>(OnStingExtractDNA);
SubscribeLocalEvent<ChangelingComponent, ChangelingTransformCycleEvent>(OnTransformCycle);
SubscribeLocalEvent<ChangelingComponent, ChangelingTransformEvent>(OnTransform);
SubscribeLocalEvent<ChangelingComponent, EnterStasisEvent>(OnEnterStasis);
SubscribeLocalEvent<GhostAttemptHandleEvent>(OnGhostAttempt); // WWDP EDIT
SubscribeLocalEvent<ChangelingComponent, ExitStasisEvent>(OnExitStasis);
SubscribeLocalEvent<ChangelingComponent, ToggleArmbladeEvent>(OnToggleArmblade);
SubscribeLocalEvent<ChangelingComponent, ToggleArmHammerEvent>(OnToggleHammer);
SubscribeLocalEvent<ChangelingComponent, ToggleArmClawEvent>(OnToggleClaw);
SubscribeLocalEvent<ChangelingComponent, ToggleDartGunEvent>(OnToggleDartGun);
SubscribeLocalEvent<ChangelingComponent, CreateBoneShardEvent>(OnCreateBoneShard);
SubscribeLocalEvent<ChangelingComponent, ToggleChitinousArmorEvent>(OnToggleArmor);
SubscribeLocalEvent<ChangelingComponent, ToggleOrganicShieldEvent>(OnToggleShield);
SubscribeLocalEvent<ChangelingComponent, ShriekDissonantEvent>(OnShriekDissonant);
SubscribeLocalEvent<ChangelingComponent, ShriekResonantEvent>(OnShriekResonant);
SubscribeLocalEvent<ChangelingComponent, ToggleStrainedMusclesEvent>(OnToggleStrainedMuscles);
SubscribeLocalEvent<ChangelingComponent, StingReagentEvent>(OnStingReagent);
SubscribeLocalEvent<ChangelingComponent, StingTransformEvent>(OnStingTransform);
SubscribeLocalEvent<ChangelingComponent, StingFakeArmbladeEvent>(OnStingFakeArmblade);
SubscribeLocalEvent<ChangelingComponent, StingLayEggsEvent>(OnLayEgg);
SubscribeLocalEvent<ChangelingComponent, ActionAnatomicPanaceaEvent>(OnAnatomicPanacea);
SubscribeLocalEvent<ChangelingComponent, ActionBiodegradeEvent>(OnBiodegrade);
SubscribeLocalEvent<ChangelingComponent, ActionChameleonSkinEvent>(OnChameleonSkin);
SubscribeLocalEvent<ChangelingComponent, ActionEphedrineOverdoseEvent>(OnEphedrineOverdose);
SubscribeLocalEvent<ChangelingComponent, ActionFleshmendEvent>(OnHealUltraSwag);
SubscribeLocalEvent<ChangelingComponent, ActionLastResortEvent>(OnLastResort);
SubscribeLocalEvent<ChangelingComponent, ActionLesserFormEvent>(OnLesserForm);
SubscribeLocalEvent<ChangelingComponent, ActionSpacesuitEvent>(OnSpacesuit);
SubscribeLocalEvent<ChangelingComponent, ActionHivemindAccessEvent>(OnHivemindAccess);
SubscribeLocalEvent<ChangelingComponent, AbsorbBiomatterEvent>(OnAbsorbBiomatter);
SubscribeLocalEvent<ChangelingComponent, AbsorbBiomatterDoAfterEvent>(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<AbsorbedComponent>(target))
{
_popup.PopupEntity(Loc.GetString(comp.AbsorbFailAbsorbed), uid, uid);
return;
}
if (!HasComp<AbsorbableComponent>(target))
{
_popup.PopupEntity(Loc.GetString(comp.AbsorbFailUnabsorbable), uid, uid);
return;
}
if (TryComp<PullableComponent>(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);
}
/// <summary>
/// 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).
/// </summary>
private const float SuccMassRatio = 30f;
/// <summary>
/// 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).
/// </summary>
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<DamageTypePrototype>(comp.AbsorbedDamageType, out var damageProto))
return;
if (!IsIncapacitated(target))
{
_popup.PopupEntity(Loc.GetString(comp.AbsorbFailIncapacitated), uid, uid);
return;
}
if (HasComp<AbsorbedComponent>(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<DamageTypePrototype>(comp.AbsorbedDamageType, out var damageProto))
return;
if (!IsIncapacitated(target))
{
_popup.PopupEntity(Loc.GetString(comp.AbsorbFailIncapacitated), uid, uid);
return;
}
if (HasComp<AbsorbedComponent>(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<DamageTypePrototype>(comp.AbsorbedDamageType, out var damageProto))
return;
var target = args.Args.Target.Value;
if (args.Cancelled || !IsIncapacitated(target) || HasComp<AbsorbedComponent>(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<AbsorbedComponent>(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<ChangelingComponent>(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<StoreComponent>(args.User, out var store))
{
_store.TryAddCurrency(new Dictionary<string, FixedPoint2> { { "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<AbsorbConditionComponent>(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<ChangelingInfectionComponent>(target))
{
_popup.PopupEntity(Loc.GetString("changeling-convert-fail-already"), uid, uid);
return;
}
if (!HasComp<AbsorbableComponent>(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<ChangelingInfectionComponent>(target))
return;
if (TryComp<ChangelingComponent>(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<ChangelingInfectionComponent>(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<ProtoId<ReagentPrototype>> 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<FoodComponent>(target, out var food))
return;
if (!TryComp<SolutionContainerManagerComponent>(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<SolutionContainerManagerComponent>(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<ChangelingActionComponent>(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<ChangelingActionComponent>(args.Action).ChemicalCost;
}
private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterStasisEvent args)
{
if (comp.IsInStasis || HasComp<AbsorbedComponent>(uid) || !TryComp<DamageableComponent>(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<ChangelingComponent>(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<AbsorbedComponent>(uid))
{
_popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail-dead"), uid, uid);
return;
}
if (!TryUseAbility(uid, comp, args))
return;
if (!TryComp<DamageableComponent>(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<ChangelingActionComponent>(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<ChangelingActionComponent>(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<PoweredLightComponent>();
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<ChangelingActionComponent>(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<ChangelingActionComponent>(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<DamageTypePrototype>(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<AbsorbedComponent>(target))
{
_popup.PopupEntity(Loc.GetString("changeling-absorb-fail-absorbed"), uid, uid);
return;
}
if (!HasComp<AbsorbableComponent>(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<StoreComponent>(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<ChangelingEggComponent>(target);
eggComp.LingComp = comp;
eggComp.LingMind = (EntityUid) mind;
eggComp.LingStore = _serialization.CreateCopy(storeComp, notNullableOverride: true);
eggComp.AugmentedEyesightPurchased = HasComp<ThermalVisionComponent>(uid);
EnsureComp<AbsorbedComponent>(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<string, FixedPoint2>
{
{ "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<CuffableComponent>(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<PullableComponent>(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<StealthComponent>(uid) && HasComp<StealthOnMoveComponent>(uid))
{
RemComp<StealthComponent>(uid);
RemComp<StealthOnMoveComponent>(uid);
_popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid);
return;
}
EnsureComp<StealthComponent>(uid);
EnsureComp<StealthOnMoveComponent>(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<StaminaComponent>(uid);
stam.StaminaDamage = 0;
var reagents = new Dictionary<string, FixedPoint2>
{
{ "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<string, FixedPoint2>
{
{ "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<ChangelingActionComponent>(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<ChangelingActionComponent>(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<ChangelingActionComponent>(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<HivemindComponent>(uid))
{
_popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid);
return;
}
EnsureComp<HivemindComponent>(uid);
var reciever = EnsureComp<IntrinsicRadioReceiverComponent>(uid);
var transmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(uid);
var radio = EnsureComp<ActiveRadioComponent>(uid);
radio.Channels = new() { "Hivemind" };
transmitter.Channels = new() { "Hivemind" };
_popup.PopupEntity(Loc.GetString("changeling-hivemind-start"), uid, uid);
}
#endregion
}