Files
Delta-v/Content.Server/_Goobstation/Devil/DevilSystem.cs
T
b465cfb3fd Nubody Merge (#5670)
* Nubody (#42419)

* Nubody

* fix test fails

* gibbing

* lung test returns

* doc comment

* hand organ test

* giblet test

* yaml formatting

* returning

* relocate

* trimming

* re-smite

* oops thusd tweak

* arachnids have slower metabolism i guess

* never mind the old behaviour is bad actually

* rider whyyy

* style changes and allat

* fix collision

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* C# files done

* Visual nubody (humanoid appearance refactor) (#42476)

* initial visual nubody

* oops overlay

* im so pheeming rn

* conversion...

* tests

* comeback of the underwear

* oops eyes

* blabbl

* zeds

* yaml linted

* search and visible count constraints

* reordering

* preserve previously selected markings colors

* fix test

* some ui niceties

* ordering

* make DB changes backwards-compatible/downgrade-friendly

* fix things again

* fix migration

* vulpkanin markings limit increase

* wrapping

* code cleanup and more code cleanup and more code cleanup and more code cleanup and

* fix slop ports

* better sampling API

* make filter work + use the method i made for its intended purpose

* fix test fails real quick

* magic mirror cleanup, remove TODO

* don't 0-init the organ profile data

* remove deltastates

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* Let vox eat trash again (#42503)

* C# complete port

* Getting closer

* Harpy Nubody Port

* Port Feroxi, Kitsune. Fix some yml typos and remove ipcs stuff

* Port Oni

* Port Rest, except Motorkind

* Port Motorkind

* Remove more surgery, restore too much deleted stuff

* Fix Typos and change some organ names. Need to split eyes next

* Harpy HAve Wings again

* Some fixes, More Surgeyr Removal, Copying of eye organs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Use correct Body imports

* Markings Refactoring, Harpy Layering

* Motorkind doesn't crash anymore. Some layer remappings.

* Allow Organs to have Markings Displacements again. (#43604)

* first shtep

* readd markings support

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* Fix STalk eyes and several layerings

* Fix sexed organs (#42554)

* Fix markings for several species, fit them for sexed organs and rename some files to fit them for it

* Additional IPC-related removals

* Vulpkanin eyes are now correct again

* Fix Subtle Cultist Marks on species

* Fix Asakim Sprites (sexed) and make CD imports work

* Migration, error fixes, and Height

* Fix humanoid profile voice being broken (#42550)

Fix humanoid appearance voice being broken

* Change Kitsune SexChanged Event to with ref

* Fix asakim issue, Respirator fix

* Simplify hands UI code (#42534)

* Simplify hands UI code

* i remembered about SortedHands in the component

* minor cleanup

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* Set size of Analyzer back to 350, Kitsune can't gib themselves no more. Migration

* yml linter errors and an rudimentary onAdd Organ Functionality

* Fixing it trying to remove nothing

* midpoint migration

* Why do you put so many organs everywhere

* More missing prototypes

* Migration of more organs

* Forgot lungs

* Move character preview handling into a specialized control (#41252)

* Move character preview handling into a specialized control

Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>

* Restore job name that I accidentally removed from character picker buttons

* Just resolve dependencies the standard way

---------

Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: Janet Blackquill <uhhadd@gmail.com>

* Drop ICharacterProfile/ICharacterAppearance interfaces (#42661)

* Ensure profile loading only returns valid species (#42842) (Stable merge) (#42865)

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>

* Changes to make it work for us.

* No more bald Harpy

* OperatingTables instead of null

* Adding Delta V Comments, commenting out instead of deleting

* Fix broken Height Scale.

* Remove HumanoidSilicon Mention

* Did an error. Readded, uncommented.

* Clean up Marking data structure, add tests for Zombie transformation (#42756)

* Clean up Marking data structure, add tests for Zombie transformation

* empty

* AAAAAAAAAAAAAAAA

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* Fix markings colour setting (#42771)

* Fix: Markings Color For Downstream

* Comment out A Leg. Remove Todo

* Revert to original hidden species code

---------

Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com>
Co-authored-by: Absotively <jen@jenpollock.ca>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
2026-05-12 21:55:29 +02:00

292 lines
12 KiB
C#

// SPDX-FileCopyrightText: 2025 GoobBot <uristmchands@proton.me>
// SPDX-FileCopyrightText: 2025 Solstice <solsticeofthewinter@gmail.com>
// SPDX-FileCopyrightText: 2025 SolsticeOfTheWinter <solsticeofthewinter@gmail.com>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
using System.Text.RegularExpressions;
using Content.Shared._Goobstation.Religion;
using Content.Server._Goobstation.Devil.Condemned;
using Content.Server._Goobstation.Devil.Contract;
using Content.Server._Goobstation.Devil.Objectives.Components;
using Content.Server._Goobstation.Possession;
using Content.Shared._Goobstation.CheatDeath;
using Content.Shared._Goobstation.CrematorImmune;
using Content.Shared._Goobstation.Devil;
using Content.Shared._Goobstation.Devil.Condemned;
using Content.Shared._Goobstation.Exorcism;
using Content.Server.Actions;
using Content.Shared.Administration.Systems;
using Content.Server.Antag.Components;
using Content.Server.Atmos.Components;
using Content.Server.Destructible;
using Content.Server.Hands.Systems;
using Content.Server.Jittering;
using Content.Server.Mind;
using Content.Server.Polymorph.Systems;
using Content.Server.Popups;
using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Server.Stunnable;
using Content.Server.Temperature.Components;
using Content.Shared.Zombies;
using Content.Shared._Lavaland.Chasm;
using Content.Shared.Actions;
using Content.Shared.CombatMode;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Mobs.Systems;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Shuttles.Components;
using Content.Shared.Temperature.Components;
using Robust.Server.Containers;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Content.Server.Bible.Components;
using Content.Server.Body.Components; // Delta V - Nubody Merge
using Content.Shared._EE.Silicon.Components;
using Content.Shared.Body; // Delta V - Nubody Merge
namespace Content.Server._Goobstation.Devil;
public sealed partial class DevilSystem : EntitySystem
{
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly PolymorphSystem _poly = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
[Dependency] private readonly DevilContractSystem _contract = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly PossessionSystem _possession = default!;
[Dependency] private readonly CondemnedSystem _condemned = default!;
[Dependency] private readonly MobStateSystem _state = default!;
[Dependency] private readonly JitteringSystem _jittering = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly ContainerSystem _container = default!;
private static readonly Regex WhitespaceAndNonWordRegex = new(@"[\s\W]+", RegexOptions.Compiled);
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DevilComponent, MapInitEvent>(OnStartup);
SubscribeLocalEvent<DevilComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DevilComponent, ListenEvent>(OnListen);
SubscribeLocalEvent<DevilComponent, SoulAmountChangedEvent>(OnSoulAmountChanged);
SubscribeLocalEvent<DevilComponent, PowerLevelChangedEvent>(OnPowerLevelChanged);
SubscribeLocalEvent<DevilComponent, ExorcismDoAfterEvent>(OnExorcismDoAfter);
SubscribeLocalEvent<IdentityBlockerComponent, InventoryRelayedEvent<IsEyesCoveredCheckEvent>>(OnEyesCoveredCheckEvent);
InitializeHandshakeSystem();
SubscribeAbilities();
}
#region Startup & Remove
private void OnStartup(Entity<DevilComponent> devil, ref MapInitEvent args)
{
// Remove human components.
RemComp<CombatModeComponent>(devil);
RemComp<HungerComponent>(devil);
RemComp<ThirstComponent>(devil);
RemComp<TemperatureComponent>(devil);
RemComp<TemperatureSpeedComponent>(devil);
RemComp<CondemnedComponent>(devil);
RemComp<DestructibleComponent>(devil);
RemComp<RespiratorComponent>(devil); // Delta V - Remove Respirator since no more BreathingImmunity
// Adjust stats
EnsureComp<ZombieImmuneComponent>(devil);
EnsureComp<PressureImmunityComponent>(devil);
EnsureComp<ActiveListenerComponent>(devil);
EnsureComp<WeakToHolyComponent>(devil).AlwaysTakeHoly = true;
EnsureComp<CrematoriumImmuneComponent>(devil);
EnsureComp<AntagImmuneComponent>(devil);
EnsureComp<PreventChasmFallingComponent>(devil).DeleteOnUse = false;
EnsureComp<FTLSmashImmuneComponent>(devil);
// Allow infinite revival
var revival = EnsureComp<CheatDeathComponent>(devil);
revival.InfiniteRevives = true;
revival.CanCheatStanding = true;
// Change damage modifier
_damageable.SetDamageModifierSetId(devil.Owner, devil.Comp.DevilDamageModifierSet);
// Add base actions
foreach (var actionId in devil.Comp.BaseDevilActions)
_actions.AddAction(devil, actionId);
// Self Explanatory
GenerateTrueName(devil);
}
#endregion
#region Event Listeners
private void OnSoulAmountChanged(Entity<DevilComponent> devil, ref SoulAmountChangedEvent args)
{
if (!_mind.TryGetMind(args.User, out var mindId, out var mind))
return;
devil.Comp.Souls += args.Amount;
_popup.PopupEntity(Loc.GetString("contract-soul-added"), args.User, args.User, PopupType.MediumCaution);
if (devil.Comp.Souls is > 1 and < 7 && devil.Comp.Souls % 2 == 0)
{
devil.Comp.PowerLevel = (DevilPowerLevel)(devil.Comp.Souls / 2); // malicious casting to enum
// Raise event
var ev = new PowerLevelChangedEvent(args.User, devil.Comp.PowerLevel);
RaiseLocalEvent(args.User, ref ev);
}
if (_mind.TryGetObjectiveComp<SignContractConditionComponent>(mindId, out var objectiveComp, mind))
objectiveComp.ContractsSigned += args.Amount;
}
private void OnPowerLevelChanged(Entity<DevilComponent> devil, ref PowerLevelChangedEvent args)
{
var popup = Loc.GetString($"devil-power-level-increase-{args.NewLevel.ToString().ToLowerInvariant()}");
_popup.PopupEntity(popup, args.User, args.User, PopupType.Large);
if (!_prototype.TryIndex(devil.Comp.DevilBranchPrototype, out var proto))
return;
foreach (var ability in proto.PowerActions)
{
if (args.NewLevel < ability.Key) // DeltaV - Just incase of admin shenanigans
continue;
foreach (var actionId in ability.Value)
_actions.AddAction(devil, actionId);
}
}
private void OnExamined(Entity<DevilComponent> ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange || ent.Comp.PowerLevel < DevilPowerLevel.Weak)
return;
var ev = new IsEyesCoveredCheckEvent();
RaiseLocalEvent(ent, ev);
if (ev.IsEyesProtected)
return;
args.PushMarkup(Loc.GetString("devil-component-examined", ("target", Identity.Entity(ent, EntityManager))));
}
private void OnEyesCoveredCheckEvent(Entity<IdentityBlockerComponent> ent, ref InventoryRelayedEvent<IsEyesCoveredCheckEvent> args)
{
if (ent.Comp.Enabled)
args.Args.IsEyesProtected = true;
}
private void OnListen(Entity<DevilComponent> devil, ref ListenEvent args)
{
// Other Devils and entities without souls have no authority over you.
if (HasComp<DevilComponent>(args.Source)
|| HasComp<CondemnedComponent>(args.Source)
|| HasComp<SiliconComponent>(args.Source)
|| args.Source == devil.Owner)
return;
var message = WhitespaceAndNonWordRegex.Replace(args.Message.ToLowerInvariant(), "");
var trueName = WhitespaceAndNonWordRegex.Replace(devil.Comp.TrueName.ToLowerInvariant(), "");
if (!message.Contains(trueName))
return;
// hardcoded, but this is just flavor so who cares :godo:
_jittering.DoJitter(devil, TimeSpan.FromSeconds(4), true);
if (_timing.CurTime < devil.Comp.LastTriggeredTime + devil.Comp.CooldownDuration)
return;
devil.Comp.LastTriggeredTime = _timing.CurTime;
if (HasComp<BibleUserComponent>(args.Source))
{
_damageable.TryChangeDamage(devil.Owner, devil.Comp.DamageOnTrueName * devil.Comp.BibleUserDamageMultiplier, true);
_stun.TryAddParalyzeDuration(devil, devil.Comp.ParalyzeDurationOnTrueName * devil.Comp.BibleUserDamageMultiplier);
var popup = Loc.GetString("devil-true-name-heard-chaplain", ("speaker", args.Source), ("target", devil));
_popup.PopupEntity(popup, devil, PopupType.LargeCaution);
}
else
{
_stun.TryAddParalyzeDuration(devil, devil.Comp.ParalyzeDurationOnTrueName);
_damageable.TryChangeDamage(devil.Owner, devil.Comp.DamageOnTrueName, true);
var popup = Loc.GetString("devil-true-name-heard", ("speaker", args.Source), ("target", devil));
_popup.PopupEntity(popup, devil, PopupType.LargeCaution);
}
}
private void OnExorcismDoAfter(Entity<DevilComponent> devil, ref ExorcismDoAfterEvent args)
{
if (args.Target is not { } target
|| args.Cancelled
|| args.Handled)
return;
_popup.PopupEntity(Loc.GetString("devil-exorcised", ("target", Name(devil))), devil, PopupType.LargeCaution);
_condemned.StartCondemnation(target, behavior: CondemnedBehavior.Banish, doFlavor: false);
}
#endregion
#region Helper Methods
private static bool TryUseAbility(BaseActionEvent action)
{
if (action.Handled)
return false;
action.Handled = true;
return true;
}
private void PlayFwooshSound(EntityUid uid, DevilComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
_audio.PlayPvs(comp.FwooshPath, uid, new AudioParams(-2f, 1f, SharedAudioSystem.DefaultSoundRange, 1f, false, 0f));
}
private void DoContractFlavor(EntityUid devil, string name)
{
var flavor = Loc.GetString("contract-summon-flavor", ("name", name));
_popup.PopupEntity(flavor, devil, PopupType.Medium);
}
private void GenerateTrueName(DevilComponent comp)
{
// Generate true name.
var firstNameOptions = _prototype.Index(comp.FirstNameTrue);
var lastNameOptions = _prototype.Index(comp.LastNameTrue);
comp.TrueName = string.Concat(_random.Pick(firstNameOptions.Values), " ", _random.Pick(lastNameOptions.Values));
}
#endregion
}